本地自动化编译、部署和调用智能合约
因为师门工作需要,我必须完成如题所示的相关工作。但是在网上搜了半天,确发现没有一个博文完全贴合我的需求。要么是内容不全,要么是手动编译、部署和调用智能合约。于是只能自己慢慢摸索,在遇到 n 多坑,排了 n 多雷之后,终于成功了。遂向大家分享一下经验,有需自取。欢迎打扰、讨论。(补充:如有大牛,请自行绕过…)
1. 环境要求
具体环境如何安装,请 google,小编就不赘述了。这里放上各种软件和对应版本
-
操作系统:Ubuntu 18.04
-
nodejs:10.15.3
-
npm:6.4.1
-
geth:1.9.0-unstable
-
solc:0.5.1
-
web3:0.20.0
-
vscode:1.33.1
2. 本地模拟搭建双节点私有链
具体如何搭建请 google,有很多教程,很简单。这里注意,因为我们要使用 web3 来和 geth 节点交互,于是我们在启动 geth 时需要顺便将 rpc 服务开启
以下是两个节点启动的指令(其实就是打开两个 bash ,输入如下指令):
-
节点1:
geth --datadir data --networkid 8081 --port 8082 --unlock 0 --rpc --rpcport "8545" --rpcapi admin,eth,miner,personal,txpool,eth,web3,net console
-
节点2:
geth --datadir data --networkid 8081 --port 8083 console --bootnodes "enode://926a5eae181ef9ff3bbf8e16a24f9734e2a9084f7950d23363a05aac386b5967374c8913a6b84b263c8d468eade0fcf50e63ee32d4a31377df46cb525be6bdfa@127.0.0.1:8082" --unlock 0
注意:两个节点的 networkid 必须相同。节点2中 --bootnodes 的值需要动态调整。这些内容在其它各种教程中都有。此外 --unlock 0 配置的意思是将 geth 中默认账户解锁,如果不解锁的话就无法利用该账户发起交易。
如果顺利的话可以在节点1中运行 admin.peers,如果出现如下图所示内容,就说明私有链搭建成功
在节点2中运行 miner.start()
,让节点2一直处于挖矿状态,以便之后的操作
3. 编译、部署和调用智能合约
这里直接贴代码吧。用的编辑器和各种配置在第一部分已经贴出来了,这儿就不赘述了。代码中重要的部分都有注释,方便理解。讲解一下大致流程吧:
-
引入 solc 包,主要用于编译智能合约
-
声明智能合约,功能就是 set() - 给字符串赋值,get() - 获取字符串值。此外我们将字符串初始化值为 “qiubing”,便于验证
-
调整编译所需格式,调用 solc.compile() 方法,编译智能合约,获得 output
-
解析 output ,获得 abi 和 bytecode,这两个内容都是部署合约必须的
-
引入 web3 包,用于连接 geth,部署和调用智能合约
-
判断 geth 连接是否成功
-
调用 web3.eth.contract() 方法,初始化 abi
-
调用 trustietransactionContract.new()方法,部署智能合约
-
部署成功之后,获得合约地址,然后在回调函数中调用 contract.get() 获得智能合约中字符串初始化的值 “qiubing”
var solc = require('solc');
// 待编译的合约源码
var contractCode = "pragma solidity >=0.4.0 <0.6.0;" +
"contract trustieTransaction {" +
"string public storedData = 'qiubing';" +
"function set(string memory x) public { storedData = x;}" +
"function get() public view returns (string memory) { return storedData;}}";
var input = {
language: 'Solidity',
sources: {
'trustieTransaction.sol': {
content: contractCode
}
},
settings: {
outputSelection: {
'*': {
'*': [ '*' ]
}
}
}
}
//编译合约源码
var output = JSON.parse(solc.compile(JSON.stringify(input)))
//获取编译后的 bytecode、abi 和 gas
var abi = JSON.stringify(output.contracts['trustieTransaction.sol'].trustieTransaction.abi);
// console.log("abi:", abi);
var bytecode = output.contracts['trustieTransaction.sol'].trustieTransaction.evm.bytecode.object;
// console.log("\n\nbytecode:", bytecode);
var gas = output.contracts['trustieTransaction.sol'].trustieTransaction.evm.gasEstimates.creation.totalCost;
// console.log("\n\ngas:", gas);
// 连接本地启动的 geth rpc 服务,连接成功之后进行如发起交易等操作
var Web3 = require('web3')
// 连接本地启动的 geth rpc 服务
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));//默认http://localhost:8545
}
// 判断是否连接成功
if(!web3.isConnected()) {
console.log("连接失败!!")
} else {
console.log("\n连接成功!!\n")
}
// 本地部署经过编译的合约
var trustietransactionContract = web3.eth.contract(JSON.parse(abi));
var trustietransaction = trustietransactionContract.new(
{
from: web3.eth.accounts[0],
data: "0x" + bytecode,
gas: '4700000' //修改源码,将 storedData 字符串初始化为 “qiubing”。编译源码获得的 gas 值为 `infinite`,导致出错,于是这儿固定一个 `4700000`值
// gas: String(parseInt(gas) * 10) //由于 solc 编译出来的 gas 过低,会导致 gas 不足的问题,于是 * 10
}, function (e, contract){
// console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('\nContract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash + "\n");
// 检测合约是否部署成功
var contract = web3.eth.contract(JSON.parse(abi)).at(contract.address);
var result = contract.get();
console.log("\n" + result)
}
})
如果一切顺利的话可以获得如下输出:
恭喜您!又 get 到一项技能!
参考: