以太坊私网搭建练习

1. 环境
Win 10 Home
CentOS 7 (3.10.0-693.el7.x86_64, 在VirtualBox虚机)
GETH 1.8.2 (1.9.4)

2. 安装
https://geth.ethereum.org/downloads/
注:此页面由于从gethstore.blob.core.windows.net请求一个很大的list文件,容易打不开。记录的1.8.2的下载地址是:
https://gethstore.blob.core.windows.net/builds/geth-alltools-windows-amd64-1.8.12-37685930.zip
https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.12-37685930.tar.gz

2.1 Win10,bootnode安装
bootnode是一个简化版的节点,仅用于其他节点之间互相发现。

从下载包里解压bootnode.exe到例如F:\tmp\ethereum\node0

2.2 CentOS, member node安装
三台VirtualBox虚机,分别建测试目录/tmp/ethereum/node1, /tmp/ethereum/node2, /tmp/ethereum/node3.

从下载包解压geth可执行文件到三个目录。

在三个目录建立文件genesis.json,内容:
    {
      "config": {
        "chainId": 15,
        "homesteadBlock": 0,
        "eip150Block": 0,
        "eip155Block": 0,
        "eip158Block": 0,
        "byzantiumBlock": 0,
        "constantinopleBlock": 0,
        "petersburgBlock": 0
      },
      "alloc": {
          "0x0000000000000000000000000000000000000001": {
            "balance": "111111111"
          },
          "0x0000000000000000000000000000000000000002": {
            "balance": "222222222"
          },
          "0x0000000000000000000000000000000000000003": {
            "balance": "333333333"
          }
      },
      "coinbase": "0x0000000000000000000000000000000000000000",
      "difficulty": "0x20000",
      "extraData": "",
      "gasLimit": "0xf12345",
      "nonce": "0x4510809143055965",
      "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "timestamp": "0x00"
    }
注:
  - "chainId": identifies the current chain and is used for replay protection. You should set it to a unique value for your private chain (see  https://ethereum.stackexchange.com/questions/15682/the-meaning-specification-of-config-in-genesis-json)
  - "alloc" 预先给一些账号充值,可以没内容("alloc":{})
  - "difficulty": "0x20000" 此难度值较低
  - "gasLimit": "0xf12345":一个区块最大允许的gas。随着以太坊升级,某些指令所需gas会增加。后续测试如果创建/调用合约时提示gas不够,可以调高此值从头再来(see  https://blog.csdn.net/weixin_34390105/article/details/89697561
  - "nonce" 据说要用随机值

3. 转账

3.1 卸载删除
bootnode: 
    del boot.key
member node:  
    rm -rf ~/.ethash/ ~/.ethereum/
    注:Linux下数据默认存放在这2个目录。

3.2
# 生成 boot.key 文件
bootnode --genkey=boot.key
# 启动boot node
bootnode --nodekey=boot.key

启动后会打印一个url如:
node://fa5e5f9df4e1436e89465c798e81e7682cd8fc6840c80fbbc88e1f5a3a47103802b0f0dff9042c9f8d32c5259946462b891aaa099cf9447459a154523f494af4@[::]:30301
将其中的ip改成三个member node能连接到的ip,后面要用。例如:
node://fa5e5f9df4e1436e89465c798e81e7682cd8fc6840c80fbbc88e1f5a3a47103802b0f0dff9042c9f8d32c5259946462b891aaa099cf9447459a154523f494af4@172.16.45.228:30301
(注:1.9.4的格式是“......@127.0.0.1:0?discport=30301”)

3.3 初始化三个节点
./geth init genesis.json

3.4 在三个节点各建立一个账号(为简化测试,密码可分别设为例如1、2、3)。记录产生的账号地址

./geth account new

node1: c34af3b19a1adc89a817e44991934dcf6a3fdfac
node2: 266aed6dbe9cd172a5121a1c8b32519fb657ab23
node3: a2b9c13157e7a5b3220876322a7da3b852710357

3.5 分别启动三个节点

./geth --networkid 15 --bootnodes "enode://fa5e5f9df4e1436e89465c798e81e7682cd8fc6840c80fbbc88e1f5a3a47103802b0f0dff9042c9f8d32c5259946462b891aaa099cf9447459a154523f494af4@172.16.45.228:30301" console

注-"networkid":
  -  Since connections between nodes are valid only if peers have identical protocol version and network ID, you can effectively isolate your network by setting either of these to a non default value (see  https://geth.ethereum.org/doc/Private-network)
  - Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby) (default: 1)

启动后在命令行输入“net.peerCount”,应该输出2,验证这三个节点已经互相发现。

3.6 node1

# 查询账户余额。应该输出0
eth.getBalance(eth.coinbase)
(eth.coinbase=0xc34af3b19a1adc89a817e44991934dcf6a3fdfac)

personal.unlockAccount(eth.coinbase)
解锁当前账号,需输入账号的密码。解锁后过段时间会自动关闭 - 下次再执行transaction又要解锁。

eth.sendTransaction({from:eth.coinbase, to:"0x266aed6dbe9cd172a5121a1c8b32519fb657ab23", value:web3.toWei(0.0005, "ether")})
尝试往账号2(node2)转账。因为本账号目前没钱,会提示错误:
Error: insufficient funds for gas * price + value

miner.start(8)
开始挖矿,8=线程数:
Generating DAG in progress ......
经过10分钟左右会提示挖到矿:
Successfully sealed new block ......
此时查询余额就有钱了(挖矿的奖励):
38000000000000000000
38后18个零,即38 ether/以太币(计量单位see  https://blog.csdn.net/wo541075754/article/details/79049425)。按当前行情171.38 * 38 = 6512.44美元。注意:一定要区分生产网络账号和私网账号(https://github.com/ethereum/go-ethereum:  you should make sure to always use separate accounts for play-money and real-money ......)

注:按上述配置应该不用等太久就可挖到矿,否则就是出问题了。

# 解锁后给node2 转1 ether
personal.unlockAccount(eth.coinbase)
eth.sendTransaction({from:eth.coinbase, to:"0x266aed6dbe9cd172a5121a1c8b32519fb657ab23", value:web3.toWei(1, "ether")})
看到输出“Successfully sealed new block” 应该交易已成功了。

# 在node2 查账:
eth.getBalance(eth.coinbase)
输出1000000000000000000,已到账。

注:有时解锁较慢。需要等到输出“true”才完成。

# 在node2
# node2 转0.0005 ether给node3:
personal.unlockAccount(eth.coinbase)
eth.sendTransaction({from:eth.coinbase, to:"0xa2b9c13157e7a5b3220876322a7da3b852710357", value:web3.toWei(0.0005, "ether")})

注:由于是node1 挖矿,挖到矿后新的区块同步到node2,node2会输出“Imported new chain segment ......”

node3 查到:500000000000000(即5e14)。
再查node2:999122000000000000。
即手续费/gas = 1000000000000000000 - 999122000000000000 - 500000000000000 = 378000000000000。web3.fromWei('378000000000000', 'ether') = 0.000378 ether。0.000378 * 171.38 = 0.06478164美元。

注:经试验,转账1 ether的手续费也是0.000378 ether。

4. 合约

https://solidity.readthedocs.io/en/v0.5.11/introduction-to-smart-contracts.html#a-simple-smart-contract

pragma solidity >=0.4.0 <0.7.0;

contract SimpleStorage {
    uint storedData;

    function set(uint x) public {
        storedData = x;
    }

    function get() public view returns (uint) {
        return storedData;
    }
}

这个最简单的合约也有一个‘坑’(花费了一天时间),就是get方法的modifier "view" 在GETH 1.9.4运行时总是返回0,即调用合约的set方法总是好像没有实际调用到一样。经试验将"view" 改成 "constant",并改用GETH 1.8.2 + Solidity 0.4版本编译(当前最新0.5)可行。

https://remix.ethereum.org/#optimize=false&evmVersion=null&version=soljson-v0.4.26+commit.4563c3fc.js

remix.ethereum.org用来在线编译上面以Solidity语言编写的以太坊智能合约。在其中新建文件"SimpleStorage.sol",录入程序内容(注意"view"=>"constant");Compiler选择0.4.26;其他选项不变。点击“Compile SimpleStorage.sol”按钮编译。应该会编译成功。之后点击下方的“Compilation Details”,在弹框里找到自动生成的“WEB3DEPLOY”部署代码:

var simplestorageContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]);
var simplestorage = simplestorageContract.new(
   {
     from: web3.eth.accounts[0], 
     data: '0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a72305820cbdbd7f507adeac3b8bbabf01359315da795777ddf17a0bd5f81f3a9ccaeb5250029', 
     gas: '4700000'
   }, function (e, contract){
    console.log(e, contract);
    if (typeof contract.address !== 'undefined') {
         console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
    }
 })

上面“[{"constant" ......]”是合约JSON格式的ABI (application binary interface),描述合约的对外接口。“data: '0x ......'”是合约内容的编译结果。gas是编译器估算创建合约所需费用。

4.1 node1窗口

# 先解锁
personal.unlockAccount(eth.coinbase)

再粘贴上面这2段代码,回车。

等1~2分钟node1会挖到矿(执行创建合约的代码)。输出:

Contract mined! address: 0x71763e8bd5af4cf3db13460177cddceabda12833 transactionHash: 0xe9712292e73fd55b24b36ec4cb488c22bf904ce49ff2310b325e69206f936bf1

"0x71763e8bd5af4cf3db13460177cddceabda12833"是合约地址 - 现在该合约已经存在于node1,而且随着node1将挖矿新产生的区块广播给其他节点,node2、node3也会更新它们本地存储,最终全网达成共识接受创建这个新合约。

输入“simplestorage” 查看这个合约对象。输入“eth.getTransaction("0xe9712292e73fd55b24b36ec4cb488c22bf904ce49ff2310b325e69206f936bf1")” 查看创建合约的这个交易。

simplestorage.get()
输出0,说明合约的字段“storedData” 目前还没有赋值(由于get方法不改变链上内容,可以直接调用不需要transaction - 此方法直接查询当前节点的本地存储)。

simplestorage.set.sendTransaction(3, {from: eth.accounts[0]})
调用合约set方法,设置字段storedData=3。由于会改变链上内容,需要调用交易(并支付一定gas给矿工)。

等待提示挖矿成功后再查询:
simplestorage.get()
输出3,说明调用set成功。

4.2 node2窗口

var simplestorage = eth.contract([{"constant":false,"inputs":[{"name":"x","type":"uint256"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]).at("0x71763e8bd5af4cf3db13460177cddceabda12833")

simplestorage

simplestorage.get()
输出3 - 在node2查到正确的合约字段值。

personal.unlockAccount(eth.coinbase)
simplestorage.set.sendTransaction(4, {from: eth.accounts[0]})
node2修改合约字段值=4。等待node1挖矿后再查询应该修改成功变成4。

注:由于node1在不停挖矿,node2可能会先查到旧值再查到新值例如:
Submitted transaction
Imported new chain segment
> simplestorage.get()
3
Imported new chain segment
> simplestorage.get()
4
Imported new chain segment

回到node1窗口,确认字段值也是4。

4.3 退出

# node1
miner.stop()
exit

# node2
exit

# node3
exit

# bootnode (win10)
# Ctrl-C 退出

4.4 重新进入

# bootnode
bootnode --nodekey=boot.key

# node1|2|3
./geth --networkid 15 --bootnodes "enode://fa5e5f9df4e1436e89465c798e81e7682cd8fc6840c80fbbc88e1f5a3a47103802b0f0dff9042c9f8d32c5259946462b891aaa099cf9447459a154523f494af4@172.16.45.228:30301" console

验证合约的字段值仍在。

5. 补充

5.1 简化版的genesis.json 应该也可用:
{
    "config": {
        "chainId": 15,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "difficulty": "0x20000",
    "gasLimit": "0xf12345",
    "alloc": {
        "0x0000000000000000000000000000000000000001": { "balance": "111111111" },
        "0x0000000000000000000000000000000000000002": { "balance": "222222222" }
    }
}

5.2

查看区块高度
eth.blockNumber
查看未打包的交易
txpool.status
查看一个区块
eth.getBlock(eth.blockNumber)

专门的挖矿节点好像没效:
./geth --networkid 15 --bootnodes "enode://......" --mine --minerthreads=2 --etherbase=0x...... console

估算gas
var gasValue = eth.estimateGas({data: binXXXXXX......})

指定gasPrice
eth.contract(ABI......)..new({from: xx, data: bin, gas: gasValue, gasPrice: 1e12}, function...)

SimpleStorage.sol
"constant"在Solidity 0.5.0时删除了。"view"+0.5对比 "constant"+0.4,编译出来的ABI和data 均稍有差异。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值