写点什么

浅入浅出智能合约 - 部署(二)

  • 2019-12-05
  • 本文字数:3642 字

    阅读完需:约 12 分钟

浅入浅出智能合约 - 部署(二)


上一篇文章 浅入浅出智能合约 - 概述(一) 介绍了智能合约中的一些基本概念以及面向合约的编程语言 Solidity,在这篇文章中我们将要介绍智能合约在编写之后是如何部署到 Ethereum 网络的。



部署一个新的智能合约或者说 DApp 其实总共只需要两个步骤,首先要将已经编写好的合约代码编译成二进制代码,然后将二进制数据和构造参数打包成交易发送到网络中,等待当前交易被矿工追加到区块链就可以了。

编译

合约代码的编译过程非常简单,我们使用如下的合约代码为例,简单介绍合约的编译过程:


JavaScript


pragma solidity ^0.4.22;
contract Contract { constructor() public { }}
复制代码


编译 Solidity 代码需要 solidity 编译器参与工作,编译器的使用也非常简单,我们可以直接使用如下的命令将上述合约编译成二进制:


Bash


$ solc --bin contract.sol
======= contract.sol:Contract =======Binary:6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820d9b24bc33db482b29de2352889cc2dfeb66029c28b0daf251aad5a5c4788774a0029
复制代码


如果我们使用了 Ethereum Wallet 等客户端,就可以将上述二进制数据添加到如下图所示的 DATA 中:



用于创建合约的交易不需要填写目标地址需要在 DATA 中填写合约的二进制数据和编码后的构造器二进制参数,由于这个合约的构造器并不包含任何参数,所以我们只需要添加合约的二进制数据,点击发送后会生成如下的交易 e74c796a041bad60469f2ee023c87e08


JavaScript


{    "jsonrpc": "2.0",    "id": 1,    "result": {        "blockHash": "0xfb508342b89066fe2efa45d7dbb9a3ae241486eee66103c03049e2228a159ee8",        "blockNumber": "0x208c0a",        "from": "0xe118559d65f87aaa8caa4383b112ff679a21223a",        "gas": "0x2935a",        "gasPrice": "0x9502f9000",        "hash": "0xe74c796a041bad60469f2ee023c87e087847a6603b27972839d0c0de2e852315",        "input": "0x6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820d9b24bc33db482b29de2352889cc2dfeb66029c28b0daf251aad5a5c4788774a0029",        "nonce": "0x2",        "to": null,        "transactionIndex": "0x5",        "value": "0x0",        "v": "0x2c",        "r": "0xa5516d78a7d486d111f818b6b16eef19989ccf46f44981ed119f12d5578022db",        "s": "0x7125e271468e256c1577b1d7a40d26e2841ff6f0ebcc4da073610ab8d76c19d5"    }}
复制代码


在这个用于创建合约的特殊交易中,我们可以看到目标地址 to 的值为空,input 的值就是我们在 Ethereum Wallet 中发送交易时填写的 DATA,即合约的二进制代码。这笔交易被纳入区块链之后,我们就能在 Etherscan 上看到这笔交易成功的创建了一个合约 0xa6a158a131476d4e071f4a3a0d9af2d88769b25a

发送

从上面的测试已经可以看到合约都是由交易(Transaction)创建的,每一个创建合约的交易的 to 字段都是 null,而 input 是合约代码编译之后的二进制;经历了编译这一过程,剩下的就是交易的打包、签名和发送了。


在这里,我们可以通过阅读一个经典的 Ethereum 实现 parity 的源代码来研究交易是如何打包、签名和发送的;从源代码中,我们可以找到签名交易的入口 sign_transaction 函数:


Rust


// parity/rpc/src/v1/traits/eth_signing.rs#[rpc(meta, name = "eth_signTransaction")]fn sign_transaction(&self, Self::Metadata, TransactionRequest) -> BoxFuture<RichRawTransaction>;
// parity/rpc/src/v1/impls/signing.rsfn sign_transaction(&self, meta: Metadata, request: RpcTransactionRequest) -> BoxFuture<RpcRichRawTransaction> { let res = self.dispatch( RpcConfirmationPayload::SignTransaction(request), meta.dapp_id().into(), meta.origin, );
Box::new(res.flatten().and_then(move |response| { match response { RpcConfirmationResponse::SignTransaction(tx) => Ok(tx), e => Err(errors::internal("Unexpected result.", e)), } }))}
复制代码


上述函数初始化了一个 RpcConfirmationPayload::SignTransaction 结构体并在最后调用 fill_optional_fields 将请求中的参数 fromtononcegas_pricegasvaluedata 以及 condition 等数据填入到最终被签名的交易中:


Rust


// rpc/src/v1/helpers/dispatch.rsfn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)                        -> BoxFuture<FilledTransactionRequest>{  let request = request;  let from = request.from.unwrap_or(default_sender);  let nonce = if force_nonce {    request.nonce.or_else(|| Some(self.state_nonce(&from)))  } else {    request.nonce  };
Box::new(future::ok(FilledTransactionRequest { from, used_default_from: request.from.is_none(), to: request.to, nonce, gas_price: request.gas_price.unwrap_or_else(|| { default_gas_price(&*self.client, &*self.miner, self.gas_price_percentile) }), gas: request.gas.unwrap_or_else(|| self.miner.sensible_gas_limit()), value: request.value.unwrap_or_else(|| 0.into()), data: request.data.unwrap_or_else(Vec::new), condition: request.condition, }))}
复制代码


经过内部的两次 RPC 请求 SignTransactionSignMessage 最后会由 signature.rs 文件中的 sign 函数使用 secp256k1 完成对传入数据的签名:


Rust


// ethkey/src/signature.rspub fn sign(secret: &Secret, message: &Message) -> Result<Signature, Error> {  let context = &SECP256K1;  let sec = SecretKey::from_slice(context, &secret)?;  let s = context.sign_recoverable(&SecpMessage::from_slice(&message[..])?, &sec)?;  let (rec_id, data) = s.serialize_compact(context);  let mut data_arr = [0; 65];
// no need to check if s is low, it always is data_arr[0..64].copy_from_slice(&data[0..64]); data_arr[64] = rec_id.to_i32() as u8; Ok(Signature(data_arr))}
复制代码


eth_signTransaction 的作用其实只有两部分,一部分是将传入的参数组合成一个交易,另一部分是通过 secp256k1 对交易进行签名:



签名好的二进制交易可以通过 eth_sendRawTransaction 广播到整个 Ethereum 网络,而 JSON 格式的交易可以通过 eth_sendTransaction 发送:


Rust


// parity/rpc/src/v1/traits/eth.rs#[rpc(name = "eth_sendRawTransaction")]fn send_raw_transaction(&self, Bytes) -> Result<H256>;
// parity/rpc/src/v1/impls/eth.rsfn send_raw_transaction(&self, raw: Bytes) -> Result<RpcH256> { Rlp::new(&raw.into_vec()).as_val() .map_err(errors::rlp) .and_then(|tx| SignedTransaction::new(tx).map_err(errors::transaction)) .and_then(|signed_transaction| { FullDispatcher::dispatch_transaction( &*self.client, &*self.miner, signed_transaction.into(), ) }) .map(Into::into)}
复制代码


send_raw_transaction 最终会将签名好的交易加入到当前节点的交易队列等待处理,交易队列中的交易随后会广播到整个网络中并被整个网络确认并纳入新的区块中。


Rust


fn import_own_transaction<C: miner::BlockChainClient>(  &self,  chain: &C,  pending: PendingTransaction,) -> Result<(), transaction::Error> {  let client = self.pool_client(chain);  let imported = self.transaction_queue.import(    client,    vec![pool::verifier::Transaction::Local(pending)]  ).pop().expect("one result returned per added transaction; one added => one result; qed");
if imported.is_ok() && self.options.reseal_on_own_tx && self.sealing.lock().reseal_allowed() { if self.engine.seals_internally().unwrap_or(false) || !self.prepare_pending_block(chain) { self.update_sealing(chain); } }
imported}
复制代码


当交易成为新区块的一部分之后,我们就能通过 Etherscan 或者其他方式查看被创建合约 0xa6a158a131476d4e071f4a3a0d9af2d88769b25a 的信息了。

总结

在 Ethereum 上部署合约的过程其实与交易发送的过程基本完全相似,唯一的区别就是用于创建合约的交易目前地址为空,并且 data 字段中的内容就是合约的二进制代码,也就是合约的部署由两部分组成:编译合约和发送消息。对于合约的部署这里差不多介绍完了,在下一篇文章中,我们将分析如何调用智能合约中声明的函数。


本文转载自 Draveness 技术博客。


原文链接:https://draveness.me/smart-contract-deploy


2019-12-05 18:171023

评论

发布
暂无评论
发现更多内容

IndexedDB 代码封装、性能摸索以及多标签支持

网易云信

JavaScript 数据库

GitHub榜首的阿里“绝巅版”工程师面试手册

爱好编程进阶

Java 面试 后端开发

Java-进阶:Java-File-&-IO—1

爱好编程进阶

Java 面试 后端开发

Java中高级核心知识全面解析——常用框架(Spring中-Bean-的作用域与生命周期)

爱好编程进阶

Java 面试 后端开发

啃论文俱乐部的团队之道和成长之路

PaperResearch

开源 OpenHarmony 啃论文俱乐部 技术自由

Java树结构实际应用(平衡二叉树-AVL树)

爱好编程进阶

Java 面试 后端开发

【二级等保】二级等保需要做日志审计吗?

行云管家

等保 日志审计 等保2.0 二级等保

IDEA的Docker插件实战(Docker-compose篇)

爱好编程进阶

Java 面试 后端开发

极致体验,揭秘抖音背后的音视频技术

字节跳动视频云技术团队

音视频开发 视频云

Java中IO字符流、File类

爱好编程进阶

Java 面试 后端开发

Java并发之Condition详解

爱好编程进阶

Java 面试 后端开发

InfoQ 公开课开放报名,融云场景化低代码平台探究

融云 RongCloud

银行应构建主动式客户体验管理体系,助力客户价值增长

易观分析

银行 客户体验管理

Java面试题总结(附答案)

爱好编程进阶

Java 面试 后端开发

JavaOOP面试题50题(含答案)

爱好编程进阶

Java 面试 后端开发

蕉下招股书里提了26次的DTC,到底是啥?

易观分析

DTC

确定还不来看看?这样管理你的代码库既方便又省心!

Jianmu

开源 持续集成 工作流 代码管理 workflow

数字化赋能塑化产业,B2B电商模式引领企业增长新引擎

数商云

产业互联网 数字化转型

服务可用性成险企智能运维关键破局能力,博睿数据APM下场助力

博睿数据

GX Works2、MX OPC 6

爱好编程进阶

Java 面试 后端开发

Java基础知识点总结

爱好编程进阶

Java 面试 后端开发

Java流程控制语句-分支结构(选择结构)

爱好编程进阶

Java 面试 后端开发

JDK、JRE、JIT

爱好编程进阶

Java 面试 后端开发

Java之Java特点

爱好编程进阶

Java 面试 后端开发

Java基础 - Eclipse,API,Object常用方法

爱好编程进阶

Java 面试 后端开发

FTP指的是什么协议?由什么组成?有什么作用?

行云管家

运维 网络协议 服务器 ftp

旺链周 | 我们的价值观,我们来代言

旺链科技

区块链 活动 价值观

Java Shutdown Hook 场景使用和源码分析

爱好编程进阶

Java 面试 后端开发

Java8的这些集合骚操作,你掌握了嘛?

爱好编程进阶

Java 面试 后端开发

在Redis中使用Pipelining提升查询速度

CRMEB

Go能实现AOP吗?

捉虫大师

Java Go aop 4月月更

浅入浅出智能合约 - 部署(二)_语言 & 开发_Draveness_InfoQ精选文章