引言
最近在学区块链,做了个简单的投票DApp,仅包含后端,主要学习一下与合约的交互。因为过程中踩了无数的坑,特此记录。
环境
geth version 1.10.14-unstable-99be62a9-20211220 nodejs version v10.13.0
初始化nmp
进入项目文件
npm init
npm install web3@0.20.7
npm install solc@0.4.22
创建私链
编写genesis.json文件。这个文件是geth官网给的PoA协议的模板,直接复制过来即可。
{
"config": {
"chainId": 15,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"clique": {
"period": 5,
"epoch": 30000
}
},
"difficulty": "1",
"gasLimit": "8000000",
"extradata": "0x0000000000000000000000000000000000000000000000000000000000000000E35586d5C0e2f41938A005546f83c1B23798ca130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"alloc": {
"E35586d5C0e2f41938A005546f83c1B23798ca13": { "balance": "3000000000000000000000" },
"f41c74c9ae680c1aa78f42e5647a62f353b7bdde": { "balance": "4000000000000000000000" }
}
}
初始化私链
geth init --datadir data genesis.json
启动私链
geth --datadir ./data --nodiscover --http --http.api personal,eth,net,web3 --allow-insecure-unlock --dev --networkid 15 console 2>output.log
这里面的参数是我踩了许多坑最终确定的,–nodiscover代表不要寻找peer nodes(不加的话会一直刷peer nodes),–allow-insecure-unlock可以使得其他控制台能够访问到http.api。在 dev 模式下,启动节点后,系统默认提供一个开发者账号,这个账号会作为当前的 coinbase 账号,在 keystore 目录下也有对应的加密私钥文件,这个账户里会有很多很多的钱,可以用来转账,同时每发生一笔交易,都会自动挖矿上传区块。console打开控制台,日志写到output.log里。
现在私链就已经成功启动啦,可以另开一个窗口实时查看日志。
tail -f output.log
回到原来的窗口,在控制台上输入eth.accounts可以查看账户,dev模式下应该是有一个默认账户,也就是accounts[0],里面有很多钱。
> eth.accounts
["0x3c9487d9680666e181d75e48adbafce7c9111fe5"]
下面创建一个新的账户
> personal.newAccount()
Passphrase:
Repeat passphrase:
"0xe40ec1d1dd73bd9045f5a2c98f9c8807665823f8"
往这个账户里转一些钱用于创建合约
> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(2,'ether')})
"0xe3ed27f27dd02bc8f72dc59ff415269f07a21e765f5ec9c9ed8cc6233779fcc7"
> eth.getBalance(eth.accounts[1])
2000000000000000000
编写合约
这里面我直接copy了一个solidity模板,一个很简单的投票合约。存储为Voting.sol
pragma solidity ^0.4.22;
contract Voting {
mapping (bytes32 => uint8) public votesReceived;
bytes32[] public candidateList;
constructor(bytes32[] candidateNames) public {
candidateList = candidateNames;
}
function totalVotesFor(bytes32 candidate) view public returns (uint8) {
require(validCandidate(candidate));
return votesReceived[candidate];
}
function voteForCandidate(bytes32 candidate) public {
require(validCandidate(candidate));
votesReceived[candidate] += 1;
}
function validCandidate(bytes32 candidate) view public returns (bool) {
for(uint i = 0; i < candidateList.length; i++) {
if (candidateList[i] == candidate) {
return true;
}
}
return false;
}
}
编译合约
输入node打开nodejs控制台。
> Web3 = require('web3')
> web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
> code = fs.readFileSync('Voting.sol').toString()
> solc = require('solc')
> compiledCode = solc.compile(code)
部署合约
Execute this in your node console:
> abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface)
> VotingContract = web3.eth.contract(abiDefinition)
> byteCode = compiledCode.contracts[':Voting'].bytecode
> deployedContract = VotingContract.new(['Alice','Bob','Cary'],{data: '0x'+byteCode, from:
web3.eth.accounts[1], gas: 4700000})
Error: invalid argument 0: json: cannot unmarshal hex string without 0x prefix into Go struct field TransactionArgs.data of type hexutil.Bytes
at Object.InvalidResponse (/home/dyj/project-block/project/node_modules/web3/lib/web3/errors.js:38:16)
at RequestManager.send (/home/dyj/project-block/project/node_modules/web3/lib/web3/requestmanager.js:61:22)
at Eth.send [as sendTransaction] (/home/dyj/project-block/project/node_modules/web3/lib/web3/method.js:145:58)
at ContractFactory.new (/home/dyj/project-block/project/node_modules/web3/lib/web3/contract.js:228:33)
Error: authentication needed: password or unlock
at Object.InvalidResponse (/home/dyj/project-block/project/node_modules/web3/lib/web3/errors.js:38:16)
at RequestManager.send (/home/dyj/project-block/project/node_modules/web3/lib/web3/requestmanager.js:61:22)
at Eth.send [as sendTransaction] (/home/dyj/project-block/project/node_modules/web3/lib/web3/method.js:145:58)
at ContractFactory.new (/home/dyj/project-block/project/node_modules/web3/lib/web3/contract.js:228:33)
>web3.personal.unlockAccount(web3.eth.accounts[1],'your password')
>deployedContract = VotingContract.new(['Alice','Bob','Cary'],{data: '0x'+byteCode, from:
web3.eth.accounts[1], gas: 4700000})
以上合约就部署成功了,但是问题又没有完全解决,此时如果直接调用deployedContract.address会返回undefined
> deployedContract.address
undefined
明明已经挖矿了,在区块链上可以看到合约信息,但是合约地址就是undefined。找了各种论坛,终于找到了原因,可能是因为账户授权过期了。由于解决过程比较玄学,放下原贴。 https://ethereum.stackexchange.com/questions/10542/address-is-undefined-after-deploying-a-smart-contract
基于此,重新unlock一下,再部署。然后挖矿。挖矿成功后,应该就能看到address。(虽然是dev环境启动的,但是这里仍需要手动挖一下。) 关于挖矿,在console里输入
>miner.start()
>miner.stop()
控制台交互
接下来就可以使用合约地址调用合约了
> contractInstance = VotingContract.at(deployedContract.address)
'0xeb5cfe629990e8ec7d8e468913e97a6f4ed15899323fab5b2d88ac0cf35232d3'
> contractInstance.voteForCandidate('Alice', {from:web3.eth.accounts[1]})
> contractInstance.totalVotesFor.call('Alice').toLocaleString()
'0'
> contractInstance.totalVotesFor.call('Alice').toLocaleString()
'1'
以上就完成了后端的交互。
|