使用docker搭建以太坊私链
准备工作
1、宿主机安装 Docker 和 Go 编程语言。
2、在本地计算机上克隆 go-ethereum 代码库
git clone http://github.com/ethereum/go-ethereum.git
3、宿主机编译以太坊客户端
make geth
该命令将在当前目录下的 build/bin 目录中生成可执行文件 geth。 4、创建一个新目录,用于存放节点相关数据。在该目录下创建一个名为“genesis.json”的文件,用于定义创世区块。并初始化用户,存储私链配置文件和数据。
以上内容在下面的部署过程中详细说明
docker-compose部署
以下只是一个大概的方法,步骤可供参考。由于第一次下载使用的基础镜像,以太坊源码版本都太低,导致没有第一笔交易无法进行挖矿,因此换了第二种方法,并使用了更高版本的以太坊源码和基础镜像,所有功能测试都通过
1、宿主机下载geth
下载位置https://geth.ethereum.org/downloads,然后解压
tar -xvf geth-linux-amd64-1.12.2-bed84606.tar.gz
此时无法识别 geth ,需要将其添加进系统路径中,可以先打印查看当前系统路径
echo $PATH
分别执行以下两条命令
echo 'export PATH=$PATH:/root/go-ethereum/geth-linux-amd64-1.12.2-bed84606' >> ~/.bashrc source ~/.bashrc
表示在~/.bashrc
文件的末尾添加一行,将/root/go-ethereum/geth-linux-amd64-1.12.2-bed84606
添加到$PATH中。source ~/.bashrc
表示立即生效。
此时再执行 geth ,如果不添加任何参数,就开始更新主网了
2、预先创建账户,初始化
新建一个目录,首先创建密码文件,
echo "123456" > .passwd
创建账户时使用以下语句分别创建两个节点,每个节点包含两个账户,分别存储于node1/node2
目录下
for ((n=0;n<2;n++)); do geth account new --password .passwd --datadir ./node1; done
这个命令会在./node1
目录下创建两个新的以太坊账户,并将它们的加密私钥存储到默认的账户目录中。--password
将从名为.passwd
的文件中读取密码,并将其用于加密私钥。--datadir
表示想要使用的目录路径。
账户1地址为 coinbase 地址,即挖矿得到的奖励地址,账户2为转账交易地址,地址如下表
节点 | 账户1地址(挖矿账户 | 账户2地址(资金账户 |
---|---|---|
node1 | 0xb03CDd04856f4429A7e834A3aC7f3CeD6B23d002 | 0x202b971c3b9D7366532942E50Cb701DbbA893B0F |
node2 | 0x7B9fc9e4CAAda8Eec649274e2860C67E0976420F | 0xd5ad8AA9eB84820bC7361FE3227f687ed3C26ECe |
执行完后,会出现两个目录,其中存储了私钥文件keystore
3、创建创世区块
创世区块为文件genesis.json
,位于/ethnode
目录下
{ "config": { "chainId": 150, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0 }, "alloc": {}, "coinbase": "0x0000000000000000000000000000000000000000", "difficulty": "0x10000", "extraData": "", "gasLimit": "0x2fefd8", "nonce": "0x0000000000000042", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x00" }
4、初始化
执行以下命令初始化两个节点
geth init --datadir ./node1 genesis.json
返回内容
部分返回解释:
Maximum peer count
:最大对等节点数。在这个例子中,ETH网络的最大对等节点数为50。Set global gas cap
:设置全局gas上限。在这个例子中,全局gas上限为50,000,000。
Writing custom genesis block
:正在写入自定义创世块。Successfully wrote genesis state
:成功写入创世状态。
5、编写Dockerfile
使用了两个镜像,其中golang 1.10的alpine
镜像为基础环境,从 github 上下载源码编译,然后使用第二个镜像alpine:latest
构建最终的Geth节点,并在其中运行Geth二进制文件。
#编译geth所需镜像 FROM golang:1.10-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git RUN git clone --depth 1 --branch release/1.8 https://github.com/ethereum/go-ethereum /go-ethereum RUN cd /go-ethereum && make all #运行geth的基础镜像 FROM alpine:latest #安装ca证书,用于节点间安全通信 RUN apk add --no-cache ca-certificates #从builder镜像中复制编译好的Geth二进制文件到/usr/local/bin/目录中 COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ WORKDIR "/opt" ENV coinbase="" ENV datadir="" #启动一个Geth节点,并将其配置为使用指定的数据目录、coinbase地址和密码文件。 CMD exec geth --datadir ./$datadir --verbosity=4 --rpc --rpcapi "eth,web3,personal,net,miner,admin,debug,db" --rpcaddr "0.0.0.0" --rpccorsdomain "*" --syncmode=full --mine --unlock $coinbase --password .passwd #通过RPC接口与Geth节点进行通信 EXPOSE 8545 #通过P2P网络与其他节点进行通信 EXPOSE 30303
6、配置docker-compose
定义两个容器,命名为sealnode-1/2
,配置环境,将他们连接到一个网络下进行通信。
正常来说docker-compose.yaml
文件要通过image
指定使用哪个镜像,但是该文件通过build
关键字指定 dockerfile 构建上下文,也就是将使用当前目录中的Dockerfile来构建镜像。
version: "3" services: sealnode-1: container_name: sealnode-1 hostname: sealnode-1 #环境变量,geth节点的coinbase地址和数据目录 environment: coinbase: 82a6d98ea4a194258b37c9d6376d2857f626fe78 datadir: node1 #构建上下文 build: context: . #端口映射,本机-容器 ports: - 8011:8545 - 30011:30303 #将当前目录映射至容器/opt目录中 volumes: - .:/opt #网络配置 networks: - mynet sealnode-2: container_name: sealnode-2 hostname: sealnode-2 environment: coinbase: db9a80b86bb062519f0d9d4475fd990f5ea351ff datadir: node2 build: context: . ports: - 8012:8545 - 30012:30303 volumes: - .:/opt networks: - mynet #定义两个桥接网络,默认和用于容器间通信的mynet networks: default: driver: bridge mynet: driver: bridge
7、启动
启动三个终端,分别用于挂起 docker 查看日志和查看两个容器
终端1
挂起 docker 查看日志
docker-compose up
在不断监控两个节点状态中
以上内容表示正在挖矿,删除死节点、重新计算下载器 QoS 值,还可以看到生成 DAG 的信息。DAG 是以太坊挖矿所需的数据集,用于计算区块的哈希值。在生成 DAG 期间,节点会执行一些计算操作,并将结果保存到本地硬盘中
终端2
另起终端,先查看当前Docker Compose项目中正在运行的容器的状态信息,包括容器ID、名称、状态、端口映射等信息,进入容器内部,连接到geth控制台
docker exec -it sealnode-1 /bin/sh geth attach node1/geth.ipc
终端3
该终端进入第二个容器,同样的操作不演示
附目录结构
当前目录结构
├── .passwd ├── docker-compose.yaml ├── dockerfile ├── genesis.json ├── node1 │ ├── geth │ ├── geth.ipc │ └── keystore └── node2 ├── geth ├── geth.ipc └── keystore
shell脚本部署
通过 shell 脚本开启两个容器,并使他们处于同一网络下。目录结构如下
├── .passwd ├── go-ethereum-1.11.4 ├── dockerfile ├── script.sh ├── script1.sh ├── node1 │ ├── genesis.json │ └── data │ ├── geth │ ├── geth.ipc │ └── keystore └── node2 ├── genesis.json └── data ├── geth ├── geth.ipc └── keystore
1、创建虚拟网络
docker network create ethproject
在启动容器时添加--network=ethproject
参数,两个节点就会在同一网络下
2、创建创世区块
与上面的一样,可以用一样的genesis.json
文件,这里把网络id改成了666
{ "config": { "chainId": 666, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0 }, "alloc": { "0xd8e6708334bdf3e9144a62859c99998caa7df32d": { "balance": "1000000000000000000000000000" }, "0x4beff4fceff14ef1c24956fb81387d21f24ca04b": { "balance": "1000000000000000000000000000" } }, "coinbase": "0x0000000000000000000000000000000000000000", "difficulty": "100000", "extraData": "", "gasLimit": "0x2fefd8", "nonce": "0x0000000000000042", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x00" }
3、初始化用户脚本
和方法一差不多,只是把这些放到脚本里一次运行了,
#!/bin/bash #先删除节点同步的链上信息与账户信息 rm -rf ./node1/data rm -rf ./node2/data #在两个节点的/data路径下分别创建两个用户 echo "111111" > .passwd for ((n=0;n<2;n++)); do geth account new --password .passwd --datadir ./node1/data; done for ((n=0;n<2;n++)); do geth account new --password .passwd --datadir ./node2/data; done #初始化 geth init --datadir ./node1/data ./node1/genesis.json geth init --datadir ./node2/data ./node2/genesis.json
4、编写dockerfile创建镜像
dockerfile
FROM golang:1.19-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git RUN mkdir -p /go-ethereum COPY ./go-ethereum-1.11.4/ /go-ethereum/ RUN go env -w GOPROXY=https://goproxy.cn,direct RUN cd /go-ethereum && make all FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ WORKDIR "/opt" COPY ./ ./ ENV datadir="" ENV coinbase="" CMD exec geth --datadir ./data --networkid 666 --verbosity=4 --port 30001 --nodiscover --syncmode=full --mine --miner.etherbase $coinbase --allow-insecure-unlock --http --http.addr 0.0.0.0 --http.port 8001 --http.corsdomain "*" --http.api eth,web3,personal,net,miner,admin,debug,db --password .passwd EXPOSE 8001 EXPOSE 30001
参数解释(如果不是v1.11.4版本,可以阅读源码文件中的 readme 文档,或查看geth -help
查看最新参数)--verbosity
:用于控制节点的日志详细程度,值越高越详细,4为debug,最详细为5
--nodiscover
:用于控制节点是否启用对等节点的自动发现机制,默认情况下,以太坊节点会尝试通过网络自动发现其他节点,以建立对等连接。若启动该参数,则不会主动发现其他节点,需要手动添加其他节点--syncmode
:用于控制以太坊节点的同步模式,包括full,light,snap,full表示同步所有区块信息--miner.etherbase
:用于指定挖矿奖励应该发送到的以太坊账户地址
--allow-insecure-unlock
:允许通过不安全的方式解锁账户,开启此选项可使用 http 解锁账户进行转账
构建镜像
完成后可查看镜像
5、容器启动脚本
分别获取两个节点下的第一个账户地址,作为挖矿奖励,然后使用刚刚创建的镜像分别启动两个容器,注意端口映射关系,这样可以通过不同的端口访问不同的节点
#!/bin/bash coinbase1="" for file in ./node1/data/keystore/* do if [ -f "$file" ] then name1=$(echo "$file" | tail -c 41) coinbase="0x${name1}" coinbase1=$(echo "$coinbase" | head -n 1) echo "$coinbase1" break fi done coinbase2="" for file in ./node2/data/keystore/* do if [ -f "$file" ] then name2=$(echo "$file" | tail -c 41) coinbase="0x${name2}" coinbase2=$(echo "$coinbase" | head -n 1) echo "$coinbase2" break fi done docker run -d --name ethnode1 --network=ethproject -p 8001:8001 -p 30001:30001 -v ~/eth-docker/node1/data:/opt/data -e coinbase=$coinbase1 eth_docker docker run -d --name ethnode2 --network=ethproject -p 8002:8001 -p 30002:30001 -v ~/eth-docker/node2/data:/opt/data -e coinbase=$coinbase2 eth_docker
执行过程中会输出两个挖矿奖励接收的地址,以及两个容器id,再次查看容器,就在运行中了
6、远程连接节点并绑定对等节点
使用 rpc 远程连接节点1 ,同理,8002端口可连接节点2
geth attach http://192.168.123.215:8001
这里用 节点1 连接 节点2 ,可以另起一个终端进入 节点2 ,然后查看当前节点enode
信息
admin.nodeInfo.enode
复制下来,将红框部分改为对应的 ip 和端口,去掉disport
,切换到 节点1 ,执行以下命令,
>admin.addPeer("enode://xxx@192.168.123.215:30002")
语法正确会返回 true ,但不代表连接成功,需要执行admin.peers
或net.peerCount
确定,如果出现以下数组即为绑定成功,这个时候两个节点可以任意查看绑定的节点信息
7、功能测试
包含以下命令
#查看当前节点账户 > eth.accounts #查看账户1的余额(单位wei转换为Eth) > web3.fromWei(eth.getBalance(eth.accounts[0]), "ether") #查看账户2的余额 > web3.fromWei(eth.getBalance(eth.accounts[1]), "ether") #从账户1转入3个ETH给账户2 > eth.sendTransaction({from:eth.accounts[0], to:eth.accounts[1], value:web3.toWei(3, "ether")}) #设置挖矿收益账户 > miner.setEtherbase(eth.accounts[0]) #挖矿 > miner.start() #停止挖矿 > miner.stop() #查看区块高度 > eth.blockNumber #解锁账户 > personal.unlockAccount(eth.accounts[0],”111111”) #查看交易池状态 > txpool.status
我的测试过程如下,先分别开启挖矿,十分钟后查看区块高度,发现两个节点是一样的,即代表同步成功。然后分别查看两个地址账户余额,第一个是挖矿奖励地址,第二个是普通地址。另一个节点同样有钱,这里只展示一个,结果如下
然后测试转账功能,转账之前需要先解锁账户,否则会报错
解锁后给第二个账户转账30,再次查看余额
功能测试完成