到底有多久没有沉下心来学点有意思的技术了?可能是一年吧。上班以后的热情都被工作磨灭了,希望这次能够重新拾起年轻时的梦想和热血。

最近 NFT 比较火,背后的智能合约逐渐成熟,Web3 似乎到了即将快速发展时期。为了能够研究一下底层的技术,在本地搭建私链并进行调试最容易接触到整个智能合约运作的原理。我这里并没有选择直接本地安装GETH,为了更加真实的模拟多个节点的情况,使用docker来搭建多个容器部署GETH节点会更加的有意思些。同时除了建立本地私链,还可以接入本地的区块浏览器和监控进行观察。编写合约的流程就放到下一篇文章 (才不是因为懒)吧。

前期准备

环境

- Mac OS Big Sur 11.4
- Python 3.9

- Docker Client:
   - Cloud integration: v1.0.22
   - Version:           20.10.13

- Docker Server: Docker Desktop 4.6.1 (76265)
  - Engine:
  - Version:          20.10.13
    - Go version:       go1.16.15
  - containerd:
    - Version:          1.5.10
  - runc:
    - Version:          1.0.3
  - docker-init:
    - Version:          0.19.0

Docker image

  • ethereum/client-go:latest
  • kamael/eth-netstats:latest
  • alethio/ethereum-lite-explorer:latest

Git repo

  • eth-net-intelligence-api

搭建流程

节点初始化

首先需要起 2+个 docker 容器,其中一个作为Main节点,其他的作为Slave节点,将Slave节点连接到Main节点即可完成多 peers 的私链状态。

  • 启动Main节点容器
docker run --entrypoint /bin/sh -p 30303:30303 -p 30303:30303/udp -p 8545:8545 -it -v $(PATH):/root --privileged=true --name eth-main ethereum/client-go

其中,-p 30303:30303/udp是不可或缺的,节点的发现与连接是通过 UDP 进行的通信,不加上这一条就会导致无法addPeers8545则作为 RPC 通信端口。挂载的目的是为了之后方便修改配置和查看 log。这里容器的启动,都没有选择容器自带的 entrypoint 来启动GETH,主要是为了定制化的启动想要的GETH

  • 启动Slave节点容器
docker run --entrypoint /bin/sh -it -v $(PATH):/root --privileged=true --name eth-slave ethereum/client-go

Slave节点则不需要额外暴露端口,通过 docker 内部的网桥可以直接互联,当然也可能建立新的docker network。对了,这两个容器的挂载文件夹最好的不同的。

  • 启动GETH节点

为了创建自定义的本地 Private Network,需要创建genesis.json

{
  "config": {
    "chainId": 2333,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0
  },
  "coinbase": "0x0000000000000000000000000000000000000000",
  "difficulty": "10",
  "extraData": "",
  "gasLimit": "0xffffffff",
  "nonce": "0x0000000000000042",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp": "0x00",
  "alloc": {
    "a65cf03166298503d8e81053167bb8d97213f4a2": { "balance": "100000000" }
  }
}

这里面主要需要修改的是chainId,每个网络都有唯一的 id,节点之间的发现也是通过这个完成。其他config里的参数尽量保持,之前漏配上一个参数会导致无法进行创世块的初始化。difficulty则决定了挖矿(PoW)的难度,因为是本地测试,可以小一点。gasLimit可以设置大一点,方便跑复杂的合约。coinbase是挖矿获得收益存入的钱包地址。alloc是可以通过设定一些初始钱包的余额。

再完成了genesis.json的编辑后,需要进行初始化。这一步在MainSlave里都需要操作。需要把同一份配置文件放到每个容器挂载的目录里,保证初始化的信息是一致,不然之后节点之间因为 hash 不同无法互联。

geth --datadir /root/data init genesis.json

启动节点

启动 Main 节点

geth --networkid 2333 --http --http.addr=0.0.0.0 --http.port=8545 --http.api "web3,eth,debug,personal,net,admin,txpool" --http.corsdomain "*" --allow-insecure-unlock --datadir data --nodiscover --nat extip:172.17.0.2 -verbosity 10 --ipcdisable --vmdebug --ws --ws.addr=0.0.0.0 --ws.origins=* --graphql --graphql.corsdomain=* --graphql.vhosts=* --txpool.lifetime 0h5m0s --rpc.allow-unprotected-txs --identity "Main" console 2>> main.log

启动 Slave 节点

geth --networkid 2333 --http --http.addr=0.0.0.0 --http.port=8545 --http.api "web3,eth,debug,personal,net" --http.corsdomain "*" --allow-insecure-unlock --datadir data --nodiscover --nat extip:172.17.0.3 -verbosity 10 --ipcdisable --vmdebug --ws --ws.addr=0.0.0.0 --ws.origins=* --graphql --graphql.corsdomain=* --graphql.vhosts=* --miner.threads 1 --txpool.lifetime 0h5m0s --rpc.allow-unprotected-txs --identity "Slave" console 2>> slave.log

img

这两个启动命令真的是又臭又长,主要是增加了很多配置,可以将其保存下来写到bash脚本里进行启动。接下来解释一下相关参数的作用:

  1. –networkid: 和genesis.json里的保持一致
  2. –http: 支持 http 接口调用,后面的相关参数都是为了配置 http 接口
  3. –allow-insecure-unlock: 允许不安全的解锁账户,方便调试
  4. –datadir: 之前初始化的节点数据位置
  5. –nodiscover: 如果不加上这个,在节点启动后,会有大量外部节点进行探测
  6. –nat: 开启 NAT 模式,不然容器节点无法互联,后面的地址是 docker 容器的地址。在容器启动这里非常关键,很多情况的节点无法addPeers就是这个原因
  7. –verbosity: 日志等级,方便排查问题
  8. –ipcdisable: 节点之间的互联没有通过 ipc 的方式,这里使用网络通信的方式
  9. –ws: 启用 websocket 方便之后搭建节点监控,–graphql 也是一样的作用
  10. –rpc.allow-unprotected-txs: 常规情况不会需要开启这个,由于 ERC1820 协议需要注册固定地址,需要开启这个配置,之后在部署合约的时候会详细讲解相关内容
  11. –identity: 定义节点名称
  12. –txpool.lifetime: 对于一些交易,如果长时间没有回应,自动终结,避免一些 tx 卡住
  13. –miner.threads: Slave节点用来控制 miner 线程
  • AddPeers

刚才虽然启动了节点,但是由于开启了--nodiscover,节点无法直接通过发现的方式自动互联,因此需要手动进行添加节点。通过查询 node 的信息,然后使用admin.addPeers()添加节点。

img

实际上,即使admin.addPeers()返回True也不代表真的就添加节点成功,需要通过admin.peers进行检查。

通过以下的curl也可以验证节点是否成功添加,以及 http 接口是否正常。

curl --location --request POST 'localhost:8545' --header 'Content-Type: application/json' --data-raw '{    "jsonrpc": "2.0",    "id": 1,   "method": "admin_peers",    "params": []}'

挖矿

首先确保要有 account,不然需要先去创建。然后选择其中一个节点作为挖矿收益汇入的钱包。

> miner.setEtherbase(eth.accounts[0])

通过这种方式,可以挖到一个block就停止。这里是保持每一笔交易都需要手动确认挖矿。另一种也可以将Slave节点启动

> miner.start();admin.sleepBlocks(1),miner.stop()

监控

本地区块浏览器

docker run -d -p 8000:80 -e APP_NODE_URL="http://0.0.0.0:8545" alethio/ethereum-lite-explorer

img

节点状态 Dashboard

启动监控

$ docker run -d -p 3000:3000 -e WS_SECRET=mysecret --name eth-netstats kamael/eth-netstats

$ git clone https://github.com/cubedro/eth-net-intelligence-api

img

img

常见问题

Q1: 无法成功 addPeers

A1: 网络配置问题,或者启动参数没有配置正确

Q2: tx 交易一直 pending

A2: 退出Geth节点终端,进入data/geth删去transactions.rlp然后重新启动节点即可,每个节点都需要删去这个文件

一些玩法

至此已经可以成功的在本地搭建起 ETH Private Network 了,之后就可以在上面随心所欲的玩耍了。结合remix在本地进行编写合约以及运行合约会更加的容易些。之后再来补上关于本地跑合约,创建自己的 Token,以及部署 ERC1820 在本地私链。