Step by Step 搭建私有链集群(Docker版本)
前言
最近研究了一段时间以太坊,各种专业术语解释,各种文档资料查看,尝试多种方案,最佳入手的体验私有链模式还是Docker,所以本文将详细描述利用Docker一步一步搭建私有链集群的过程。说是集群,其实不过搭建3个节点,没有优化过程,旨在给大家提供一个可以模拟入门的教程。
私有链:控制权限集中在某个公司或者组织,读取权限可以根据业务情况进行公开或者限定范围内公开,应用范围比较广泛。有关公有链、联盟链和私有链之间的关系,建议详细延伸阅读原版英文说明 Public and Private Blockchains。
前提准备:
- CentOS 7.* 64bit 操作系统,虚拟化即可 【主要用于运行Docker宿主机器以及节点监控服务】
- 服务器配置(4核心,4G内存,硬盘容量20G即可)
- 必要的Docker容器知识,本文档不会详细解释具体的含义
目标:
- 搭建3个节点的Docker服务,运行私有链服务geth
- 创建6个钱包地址
- 同节点和不同节点间钱包之间相互转账
- 查看区块,节点间的区块同步
- 查询挂起交易,同步交易,交易详情
- 搭建私有链节点运行的监控平台
1. 服务器环境配置
1.1 安装EPEL
相关软件我们基本全部采用yum进行安装,由于一些软件包只包含在EPEL资源库中,所以必须引入EPEL源,以便整个演示能够顺利进行。
# yum install -y epel-release
1.2 安装必要的软件包
演示过程中,我们需要下载github项目,所以必须安装git;
其他软件主要是方便查看和测试使用;
Docker容器服务是我们主要使用的软件;
# yum install -y git wget tree telnet
# yum install -y docker
查看Docker容器是否安装成功,出现如下的版本号提示,说明成功。
# docker --version
Docker version 1.13.1, build 774336d/1.13.1
2. 配置Docker容器
2.1 启动Docker容器服务
# /bin/systemctl start docker.service
# /bin/systemctl enable docker.service
设置开机自动启动
2.2 下载官方镜像文件
由于下载镜像需要时间较长,我们提前将需要的ubuntu 镜像下载下来,减少后续的等待时间。本演示我们使用ubuntu作为基础镜像。
# docker pull ubuntu
2.3 定制化Dockerfile文件
为什么使用Dockerfile?以及如何自己写Dockerfile?这里不做更详细的解释,建议用户参看官方说明文档Best practices for writing Dockerfiles
# mkdir /home/ethereum
# cd /home/ethereum
# vi Dockerfile
文件的详细内容如下:
FROM ubuntu
LABEL version="1.0"
LABEL maintainer="zhoulg@outlook.com"
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install --yes software-properties-common
RUN add-apt-repository ppa:ethereum/ethereum
RUN apt-get update && apt-get install --yes geth
RUN adduser --disabled-login --gecos "ethereum user" eth
COPY genesis.json /home/eth/genesis.json
RUN chown -R eth:eth /home/eth/genesis.json
USER eth
WORKDIR /home/eth
RUN geth init genesis.json
ENTRYPOINT bash
文件内容简要说明:
- 使用ubuntu:latest 作为基础镜像
- 更新和安装必要的软件包
- 创建运行账号,复制必要的文件以及工作目录
- 运行geth(go-ethereum)初始化程序
2.4 生成genesis.json
文件
注意上面的Dockerfile文件里面有一行,COPY命令,复制了本地的一个文件到容器内指定目录下。该文件是运行私有链的必须的配置文件,我们需要手工生成。网络上面有很多genesis.json
文件的例子,官方也有很多介绍,这里不做过多的解释,如果用户对于该文件每一项的含义需要更加详细的说明,建议延伸阅读What does each genesis.json parameter mean?
这里我们的演示文件内容如下:
{
"alloc": {
},
"config": {
"chainId": 15,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"nonce": "0x000000000000002a",
"difficulty": "0x002000",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x2fefd8"
}
genesis.json
必须在所有的节点都是相同的,所以我们直接复制到镜像文件里面,保持统一。
2.5 编译自定义的镜像
# docker build -t eth_node .
Sending build context to Docker daemon 3.072 kB
Step 1/14 : FROM ubuntu
---> c9d990395902
Step 2/14 : LABEL version "1.0"
---> Running in c2efa7362edd
...... 其他信息省略,软件包下载需要一段时间......
Step 14/14 : ENTRYPOINT bash
---> Running in 69887d7b1b15
---> 32bb44f69667
Removing intermediate container 69887d7b1b15
Successfully built 32bb44f69667
最后出现Successfully 说明创建自定义镜像成功
查看镜像列表验证下
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
eth_node latest 32bb44f69667 About a minute ago 287 MB
docker.io/ubuntu latest c9d990395902 14 hours ago 113 MB
其中 eth_node
就是我们刚才自定义的镜像文件,接下来我们的演示将主要以这个镜像文件为基础。
3. 搭建私有链节点
3.1 启动3个节点
由于我们是在一台宿主机上面运行,所以我们需要规划好节点的对应端口号,默认
geth
运行端口为 8545,我们定义节点如下:
+节点一名称:eth_node_1 映射宿主机端口号:8545
+节点二名称:eth_node_2 映射宿主机端口号:8546
+节点三名称:eth_node_3 映射宿主机端口号:8547
节点一启动
# docker run --rm -it -p 8545:8545 --name eth_node_1 eth_node
默认启动后,已经登录进入容器内部,保持终端不动
容器内部退出终端的命令:
ctrl + p + q
(不要使用exit命令,会结束容器进程)
节点一已经启动,暂时保持不动
现在,重新打开一个新的终端
节点二启动
# docker run --rm -it -p 8546:8545 --name eth_node_2 eth_node
默认启动后,已经登录进入容器内部,保持终端不动
留意端口映射的不同,和节点名称
再次重新打开一个新的终端
节点三启动
# docker run --rm -it -p 8547:8545 --name eth_node_3 eth_node
默认启动后,已经登录进入容器内部,保持终端不动
留意端口映射的不同,和节点名称
以上我们已经启动了3个节点的容器服务
3.2 查询节点容器信息
检查节点的运行情况
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
20b354c9dd15 eth_node "/bin/sh -c bash" 5 seconds ago Up 4 seconds 0.0.0.0:8547->8545/tcp eth_node_3
f5d2b9558a3b eth_node "/bin/sh -c bash" 17 seconds ago Up 16 seconds 0.0.0.0:8546->8545/tcp eth_node_2
4cd4ed065442 eth_node "/bin/sh -c bash" 56 seconds ago Up 55 seconds 0.0.0.0:8545->8545/tcp eth_node_1
几个重要的信息点,节点的名称,节点对应的ID(前4个字母即可),以及对应的端口映射,容器的运行状态
查询节点对应的IP地址
# 查询节点一的IP
# docker inspect 4cd4 --format='{{.NetworkSettings.IPAddress}}'
172.17.0.2
# 查询节点二的IP
# docker inspect f5d2 --format='{{.NetworkSettings.IPAddress}}'
172.17.0.3
# 查询节点三的IP
# docker inspect 20b3 --format='{{.NetworkSettings.IPAddress}}'
172.17.0.4
查询后对应的信息如下,需要记录下来备用:
节点 | IP地址 | 映射端口 | 容器ID |
---|---|---|---|
eth_node_1 | 172.17.0.2 | 8545 | 4cd4 |
eth_node_2 | 172.17.0.3 | 8546 | f5d2 |
eth_node_3 | 172.17.0.4 | 8547 | 20b3 |
3.3 配置节点容器
3.3.1 创建初始钱包
创建初始节点钱包,用于接收记账奖励,每个节点都需要创建。节点记账(挖矿)程序运行前,必须先建立初始的钱包,用于接收记账奖励,否则会无法启动记账程序。
%进入节点一(eth_node_1)的终端,创建初始钱包。为了减少交互输入密码步骤,我们简单采用密码文件的形式,正式环境不能这么操作。
# echo "123456" > mypassword.txt
# geth --password mypassword.txt account new
INFO [04-16|02:32:39] Maximum peer count ETH=25 LES=0 total=25
Address: {5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d}
记录下生成的钱包地址:5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d
查看钱包列表
# geth account list
INFO [04-16|02:35:02] Maximum peer count ETH=25 LES=0 total=25
Account #0: {5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d} keystore:///home/eth/.ethereum/keystore/UTC--2018-04-16T02-32-39.350890163Z--5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d
PS:keystore指向的加密的密钥文件
同样在eth_node_2 和 eth_node_3 上面执行同样的命令,全部创建好初始钱包地址。
3.3.2 创建用户钱包
同时创建一个用户钱包,稍后将用于本节点钱包转账以及不同节点间钱包转账。
# geth --password mypassword.txt account new
INFO [04-16|05:34:15] Maximum peer count ETH=25 LES=0 total=25
Address: {ef41fbddb7fd8cc42076c7613f98c533d55e8d99}
记录下用户钱包地址,后续将会使用到。
查看创建的钱包地址列表
# geth account list
INFO [04-16|05:34:58] Maximum peer count ETH=25 LES=0 total=25
Account #0: {5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d} keystore:///home/eth/.ethereum/keystore/UTC--2018-04-16T02-32-39.350890163Z--5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d
Account #1: {ef41fbddb7fd8cc42076c7613f98c533d55e8d99} keystore:///home/eth/.ethereum/keystore/UTC--2018-04-16T05-34-15.740848716Z--ef41fbddb7fd8cc42076c7613f98c533d55e8d99
#0号钱包地址为初始的钱包。
同样在eth_node_2 和 eth_node_3 上面执行同样的命令,全部创建一个用户钱包备用。
3.3.3 开始运行记账程序(也叫挖矿程序)
准备工作全部完成后,现在开始启动记账(挖矿)程序,需要指定一些参数。
节点一(eth_node_1)
# geth --identity="NODE_1" --networkid="500" --verbosity=1 --mine --minerthreads=1 --rpc --rpcaddr 0.0.0.0 console
Welcome to the Geth JavaScript console!
instance: Geth/NODE_1/v1.8.2-stable-b8b9f7f4/linux-amd64/go1.9.4
coinbase: 0x5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d
at block: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
datadir: /home/eth/.ethereum
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>eth.blockNumber
0
>... 一般需要等待10分钟左右创建创世块...
>eth.blockNumber
7
节点二(eth_node_2)
# geth --identity="NODE_2" --networkid="500" --verbosity=1 --mine --minerthreads=1 --rpc --rpcaddr 0.0.0.0 console
Welcome to the Geth JavaScript console!
instance: Geth/NODE_2/v1.8.2-stable-b8b9f7f4/linux-amd64/go1.9.4
coinbase: 0xa849dc84dc2500ac4abb872b125c7e67012f6ec2
at block: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
datadir: /home/eth/.ethereum
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>
节点三(eth_node_3)
# geth --identity="NODE_3" --networkid="500" --verbosity=1 --mine --minerthreads=1 --rpc --rpcaddr 0.0.0.0 console
Welcome to the Geth JavaScript console!
instance: Geth/NODE_3/v1.8.2-stable-b8b9f7f4/linux-amd64/go1.9.4
coinbase: 0x26214c499202e2e6021f820e58c1eff2b1a252f9
at block: 0 (Thu, 01 Jan 1970 00:00:00 UTC)
datadir: /home/eth/.ethereum
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>
留意3个节点的启动参数不同
参数 | 说明 |
---|---|
--identity | 每个节点必须唯一 |
--networkid | 在集群里面保持一致,所有的节点都相同 |
--verbosity | 有几个级别0=silent, 1=error, 2=warn, 3=info, 4=core, 5=debug, 6=detail (default: 3) |
--mine | 开始启动记账(挖矿)程序 |
--rpc | 允许RPC |
--rpcport | 指定的RPC端口号,演示保持一样,宿主机映射不一样 |
3.3.4 命令参数简要说明
所有的参数的详细解释说明,请参看geth命令行参数说明
4 配置节点集群
4.1 查询节点信息IPC RPC 参数
所有的节点都开始记账(挖矿)程序后,我们就需要配置集群的节点信息,让3个节点能够互相发现,并同步信息。
> admin.peers
[]
默认都是空的,没有其他节点信息
现在查询出来3个节点的参数
节点一(eth_node_1)
> admin.nodeInfo.enode
"enode://4250b0b6b1029b0874a5c5b0806299b04206ef21c22a0a5a476fe87ae622beeca69c55b4e000110d2cb2a72da3af15325bb4e43d4ee6d19111ec4319256348e1@[::]:30303"
我们将末尾的 [::]替换为我们的内网IP地址,前面有记录,172.17.0.2
现在节点一对应的修改如下:
enode://4250b0b6b1029b0874a5c5b0806299b04206ef21c22a0a5a476fe87ae622beeca69c55b4e000110d2cb2a72da3af15325bb4e43d4ee6d19111ec4319256348e1@172.17.0.2:30303
其他2个节点执行同样的操作和替换,实例如下:
eth_node_1:"enode://4250b0b6****6348e1@172.17.0.2:30303"
eth_node_2:"enode://ddd066c0****35f75c5@172.17.0.3:30303"
eth_node_3:"enode://af5e0fd27****31d170@172.17.0.4:30303"
为了演示,中间的字符省略
4.2 添加节点
现在我们已经得到所有节点的URL信息,我们需要让其他节点能够发现另外的2个节点,手工添加上对应的参数。
在所有的节点都执行如下的定义
> enode1 = "enode://4250b0b6b1029b0874***48e1@172.17.0.2:30303"
>
> enode2 = "enode://ddd066c0442662976ac***75c5@172.17.0.3:30303"
>
> enode3 = "enode://af5e0fd27bf9480621****d170@172.17.0.4:30303"
>
在每个几点上面增加其他节点的信息
节点一(eth_node_1)
> admin.addPeer(enode2)
true
> admin.addPeer(enode3)
true
节点二(eth_node_2)
> admin.addPeer(enode1)
true
> admin.addPeer(enode3)
true
节点三(eth_node_3)
> admin.addPeer(enode1)
true
> admin.addPeer(enode2)
true
4.3 查询节点集群信息
所有的节点都添加信息后,再次查看节点情况
> admin.peers
[{
caps: ["eth/63"],
id: "af5e0fd27bf94806213b824ac0814c45106bbc834e406fd617f838af61dd98fd131c83c2394145678367ee0d80ea55a066283a4f7586793143c35aa2c431d170",
name: "Geth/NODE_3/v1.8.2-stable-b8b9f7f4/linux-amd64/go1.9.4",
network: {
inbound: false,
localAddress: "172.17.0.2:37019",
remoteAddress: "172.17.0.4:30303",
static: true,
trusted: false
},
protocols: {
eth: {
difficulty: 8192,
head: "0x8e3caef2da8f25e5172a86bac2dbd2611fa4de6c3cc28c301186d537afc77943",
version: 63
}
}
}, {
caps: ["eth/63"],
id: "ddd066c0442662976ac3765d80929ad76ea0420b915b69ac7ca5bc7002b37f180b6129af7e7a9e0dc2a5d9153578a05810a409a57878ef919ddc2edd735f75c5",
name: "Geth/NODE_2/v1.8.2-stable-b8b9f7f4/linux-amd64/go1.9.4",
network: {
inbound: false,
localAddress: "172.17.0.2:42844",
remoteAddress: "172.17.0.3:30303",
static: true,
trusted: false
},
protocols: {
eth: {
difficulty: 4337792,
head: "0xb54837c3a85bac126297b3b6be302f22b31aa66c78f7089584b7d787808cf5b4",
version: 63
}
}
}]
其他2个节点的信息基本一致,除了remoteAddress不一样
4.4 查询节点区块同步情况
节点都加入集群后,在所有的节点查看区块情况,应该所有的节点都是一样的,或者由于延迟相差几个区块。
> eth.blockNumber
282
这样我们的私有链集群都搭建成功了,所有的节点区块保持一致。
4.5 监控节点区块同步情况
接下来,不要关闭上面的3个节点终端,留待备用。
我们重新打开一个宿主机的终端,尝试通过接口监控节点的区块同步情况,这里我们仅演示下nodejs。
为了运行nodejs 程序,我们需要安装nodejs软件包
# yum install -y nodejs
# node -v
v6.14.0
本监控脚步我们引用了 node-json-rpc 库,需要提前安装
# npm install node-json-rpc
npm: relocation error: npm: symbol SSL_set_cert_cb, version libssl.so.10 not defined in file libssl.so.10 with link time reference
发现报错了,这里是由于openssl库的问题导致的,重新安装openssl库
# yum install -y openssl
然后再次执行 npm 安装程序
# npm install node-json-rpc
/home/ethereum
└── node-json-rpc@0.0.1
OK,安装成功。
默认库文件就在当前目录下
# tree node_modules/
node_modules/
└── node-json-rpc
├── lib
│ ├── index.js
│ ├── rpcauth.js
│ ├── rpcclient.js
│ └── rpcserver.js
├── LICENSE
├── package.json
├── README.md
└── test
├── etc
│ ├── optsclient.js
│ └── optsserver.js
└── rpc.js
4 directories, 10 files
运行命令行NodeJS 的监控同步程序
# node monitor.js
ETH_NODE_2 coinbase: 0xa849dc84dc2500ac4abb872b125c7e67012f6ec2
ETH_NODE_3 block number: 556
ETH_NODE_1 coinbase: 0x5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d
ETH_NODE_1 block number: 556
ETH_NODE_2 block number: 556
ETH_NODE_3 coinbase: 0x26214c499202e2e6021f820e58c1eff2b1a252f9
ETH_NODE_1 coinbase: 0x5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d
ETH_NODE_3 coinbase: 0x26214c499202e2e6021f820e58c1eff2b1a252f9
ETH_NODE_2 coinbase: 0xa849dc84dc2500ac4abb872b125c7e67012f6ec2
ETH_NODE_1 block number: 557
ETH_NODE_2 block number: 557
ETH_NODE_3 block number: 557
......
上面我们看见3个节点的区块都保持一致,私链集群运行成功。
由于是单服务器上面做的演示,这时候宿主机的负载就有点大了,区块的产生也不会很快。
5 钱包转账
5.1 查询初始钱包余额
接下来开始演示钱包的相关操作
进入节点一(eth_node_1)终端,查看钱包余额
> eth.accounts
["0x5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d", "0xef41fbddb7fd8cc42076c7613f98c533d55e8d99"]
> node1_w1 = eth.accounts[0]
> eth.getBalance(node1_w1)
3.013125e+21
> web3.fromWei(eth.getBalance(node1_w1))
3037.03125
web3.fromWei 函数将余额转换为易读的单位wei,便于后续演示。
其他节点基本相同。
5.2 查询用户钱包余额
现在查看用户的钱包的余额情况
> eth.accounts
["0x5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d", "0xef41fbddb7fd8cc42076c7613f98c533d55e8d99"]
> node1_w2 = eth.accounts[1]
> web3.fromWei(eth.getBalance(node1_w2))
0
现在用户钱包还是空的,没有余额
5.3 初始钱包->用户钱包转账
我们尝试做一笔转账操作
在节点一(eth_node_1),从初始钱包->用户钱包,转账40个Wei
> eth.sendTransaction({from: node1_w1, to: node1_w2, value: web3.toWei(40, "ether")})
Error: authentication needed: password or unlock
at web3.js:3143:20
at web3.js:6347:15
at web3.js:5081:36
at <anonymous>:1:1
我们发现报错了,密码不对或者需要解锁
我们先把初始账号进行解锁操作
> personal.unlockAccount(node1_w1, "123456", 300)
true
解锁钱包,需要执行我们生成钱包的时候指定的密码,这里是123456,解锁时间300秒。
返回true 说明解锁成功,继续我们的转账操作
> eth.sendTransaction({from: node1_w1, to: node1_w2, value: web3.toWei(40, "ether")})
"0x844c074011fc256e8c3ae8e0a50f7a149c5785afcb78e750e091be90bbede22e"
返回的是交易号
5.4 查询挂起的交易
> eth.pendingTransactions
[]
这里如果区块同步的慢,可以看到挂起的交易
出现[],说明我们的交易已经同步了
> eth.getTransaction('0x844c074011fc256e8c3ae8e0a50f7a149c5785afcb78e750e091be90bbede22e')
{
blockHash: "0x54c455696b0321e58cee3e4ea9d50da98ee5842d6d2db5bcdd774de56d5f688d",
blockNumber: 851,
from: "0x5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d",
gas: 90000,
gasPrice: 18000000000,
hash: "0x844c074011fc256e8c3ae8e0a50f7a149c5785afcb78e750e091be90bbede22e",
input: "0x",
nonce: 0,
r: "0xf17019be2e3d55ede4a5fde3333a4f1a8164b34004764143d74565db77985df1",
s: "0x48f5ad381e776de128cc9a137a3979b3f3de94cf6412e3a35213d1e7592c0315",
to: "0xef41fbddb7fd8cc42076c7613f98c533d55e8d99",
transactionIndex: 0,
v: "0x41",
value: 40000000000000000000
}
由于我们3个节点是同步的,所以在所有的区块,都查看到交易
5.5 查询用户钱包余额
交易确认后,我们就可以在用户钱包查看到余额
> web3.fromWei(eth.getBalance(node1_w2))
40
这样我们在同一个节点的转账操作就成功了。
5.6 跨节点钱包转账
现在我们进行在不同节点之间的钱包转账操作
上一步操作中,我们节点一的用户钱包,已经有40wei,这一步我们转移到节点二用户钱包 15wei,转移到节点三用户钱包 10wei。
节点一(eth_node_1)
> eth.accounts
["0x5edd8d6c2ea0328f54f91cd7714d1f9bbcb9911d", "0xef41fbddb7fd8cc42076c7613f98c533d55e8d99"]
节点二(eth_node_2)
> eth.accounts
["0xa849dc84dc2500ac4abb872b125c7e67012f6ec2", "0xbe38275ed6414268698294a9a1c06909e8b96a5a"]
节点三(eth_node_3)
> eth.accounts
["0x26214c499202e2e6021f820e58c1eff2b1a252f9", "0x48aa3d8e793e8d2da00b23ecea1555c3b82e3262"]
重新登录节点一(eth_node_1)
先确认用户钱包余额
> web3.fromWei(eth.getBalance('0xef41fbddb7fd8cc42076c7613f98c533d55e8d99'))
40
> web3.fromWei(eth.getBalance('0xbe38275ed6414268698294a9a1c06909e8b96a5a'))
0
> web3.fromWei(eth.getBalance('0x48aa3d8e793e8d2da00b23ecea1555c3b82e3262'))
0
开始转账操作,同样转账前,我们需要先解锁节点一的用户钱包
> personal.unlockAccount('0xef41fbddb7fd8cc42076c7613f98c533d55e8d99', "123456", 300)
true
> eth.sendTransaction({from: '0xef41fbddb7fd8cc42076c7613f98c533d55e8d99', to: '0xbe38275ed6414268698294a9a1c06909e8b96a5a', value: web3.toWei(15, "ether")})
"0x6a60647492fcaea93a721ab7dc761a8fade6fd44d7f0283ee38fb5d146773c4e"
> eth.sendTransaction({from: '0xef41fbddb7fd8cc42076c7613f98c533d55e8d99', to: '0x48aa3d8e793e8d2da00b23ecea1555c3b82e3262', value: web3.toWei(10, "ether")})
"0xd95914eeccc1ec05fd22c15f29e0d3515b904268074124aa9f669fd529efcae1"
> eth.pendingTransactions
[]
没有挂起的交易
> eth.sendTransaction({from: '0xef41fbddb7fd8cc42076c7613f98c533d55e8d99', to: '0xbe38275ed6414268698294a9a1c06909e8b96a5a', value: web3.toWei(15, "ether")})
"0x6a60647492fcaea93a721ab7dc761a8fade6fd44d7f0283ee38fb5d146773c4e"
> eth.sendTransaction({from: '0xef41fbddb7fd8cc42076c7613f98c533d55e8d99', to: '0x48aa3d8e793e8d2da00b23ecea1555c3b82e3262', value: web3.toWei(10, "ether")})
"0xd95914eeccc1ec05fd22c15f29e0d3515b904268074124aa9f669fd529efcae1"
> eth.pendingTransactions
[{
blockHash: null,
blockNumber: null,
from: "0xef41fbddb7fd8cc42076c7613f98c533d55e8d99",
gas: 90000,
gasPrice: 18000000000,
hash: "0x6a60647492fcaea93a721ab7dc761a8fade6fd44d7f0283ee38fb5d146773c4e",
input: "0x",
nonce: 0,
r: "0x703aef116bf4226cf5d4ae14c16ab2e365240b951f22c127f0834f4d07f1c91",
s: "0x5811d14a3ae3abd47eff111648fbdbbef8d13878961981a3d0110e1b6ac43c0",
to: "0xbe38275ed6414268698294a9a1c06909e8b96a5a",
transactionIndex: 0,
v: "0x42",
value: 15000000000000000000
}, {
blockHash: null,
blockNumber: null,
from: "0xef41fbddb7fd8cc42076c7613f98c533d55e8d99",
gas: 90000,
gasPrice: 18000000000,
hash: "0xd95914eeccc1ec05fd22c15f29e0d3515b904268074124aa9f669fd529efcae1",
input: "0x",
nonce: 1,
r: "0x31d052be55fe988aa06931b807e59b9bd9dd20fa47c076e2135c40022b48df57",
s: "0x24faf81fb0b88c50ed2fa5f26d9292f0554eeed41e112bffba0bc374a82ad730",
to: "0x48aa3d8e793e8d2da00b23ecea1555c3b82e3262",
transactionIndex: 0,
v: "0x41",
value: 10000000000000000000
}]
这里可以查到有2笔挂起的交易待确认,延迟一会等交易确认完毕。
> eth.pendingTransactions
[]
5.7 查询用户钱包余额
交易全部确认完毕后,再次查询所有钱包的用户钱包余额
> web3.fromWei(eth.getBalance('0xbe38275ed6414268698294a9a1c06909e8b96a5a'))
15
> web3.fromWei(eth.getBalance('0x48aa3d8e793e8d2da00b23ecea1555c3b82e3262'))
10
> web3.fromWei(eth.getBalance('0xef41fbddb7fd8cc42076c7613f98c533d55e8d99'))
14.999244
这里我们发现,节点二和节点三的用户钱包,都已经有余额了,说明转账成功了。
节点一的钱包余额不是 40-15-10 = 15,而是比15小一些,这里面主要每一笔交易都需要交易费的,从转出方进行扣除,也就是手续费,手续费支付给参与记账的节点。
6 监控私有链节点运行情况
6.1 eth-netstats
以太坊网络监控,官方网站:https://ethstats.net/
本小节的目标,就是我们搭建一个自己的私链的监控平台
首先,新打开一个宿主机终端,下载源码
# cd /home/ethereum
# git clone https://github.com/cubedro/eth-netstats.git
# cd eth-netstats
# npm install
# npm install -g grunt-cli
这一步需要一段时间
这时候一般会报告一些错误,主要一些依赖库没有自动安装
我们需要手工安装上
# npm install debug
# npm install lodash logger chalk http express primus primus-emit primus-spark-latency
# npm install body-parser websockets
# npm install --save ws
# npm install d3 geoip-lite
# npm install jade
# npm install
# npm install -g grunt-cli
运行成功后,通过浏览器访问页面,默认监听的宿主机的3000端口
在我的演示中:
eth-netstat.png
现在还没有数据,显示的都是空的,接下来我们需要给页面推送数据过来。
6.2 eth-net-intelligence-api
这个是后端服务,与以太坊一起运行并跟踪网络状态,通过JSON-RPC获取信息并通过WebSockets连接到eth-netstats以提供信息。 有关完整安装说明,请前往阅读eth-net-intelligence-api
另外打开一个宿主机的终端
# cd /home/ethereum
# git clone https://github.com/cubedro/eth-net-intelligence-api.git
需要提前安装pm2 软件包
# npm install -g pm2
# pm2 --version
2.10.2
需要复制一份app.json 文件对应的节点
# cp app.json enode1.json
文件内容如下
# cat enode1.json
[
{
"name" : "enodeAA",
"script" : "app.js",
"log_date_format" : "YYYY-MM-DD HH:mm Z",
"merge_logs" : false,
"watch" : false,
"max_restarts" : 10,
"exec_interpreter" : "node",
"exec_mode" : "fork_mode",
"env":
{
"NODE_ENV" : "production",
"RPC_HOST" : "172.17.0.2",
"RPC_PORT" : "8545",
"LISTENING_PORT" : "30303",
"INSTANCE_NAME" : "enodeAA",
"CONTACT_DETAILS" : "",
"WS_SERVER" : "http://192.168.6.116:3000",
"WS_SECRET" : "admin",
"VERBOSITY" : 2
}
}
]
同样再次复制一份生成enode2.json 和 enode3.json
# cat enode2.json
[
{
"name" : "enodeAB",
"script" : "app.js",
"log_date_format" : "YYYY-MM-DD HH:mm Z",
"merge_logs" : false,
"watch" : false,
"max_restarts" : 10,
"exec_interpreter" : "node",
"exec_mode" : "fork_mode",
"env":
{
"NODE_ENV" : "production",
"RPC_HOST" : "172.17.0.3",
"RPC_PORT" : "8545",
"LISTENING_PORT" : "30303",
"INSTANCE_NAME" : "enodeAB",
"CONTACT_DETAILS" : "",
"WS_SERVER" : "http://192.168.6.116:3000",
"WS_SECRET" : "admin",
"VERBOSITY" : 2
}
}
]
# cat enode3.json
[
{
"name" : "enodeAC",
"script" : "app.js",
"log_date_format" : "YYYY-MM-DD HH:mm Z",
"merge_logs" : false,
"watch" : false,
"max_restarts" : 10,
"exec_interpreter" : "node",
"exec_mode" : "fork_mode",
"env":
{
"NODE_ENV" : "production",
"RPC_HOST" : "172.17.0.4",
"RPC_PORT" : "8545",
"LISTENING_PORT" : "30303",
"INSTANCE_NAME" : "enodeAC",
"CONTACT_DETAILS" : "",
"WS_SERVER" : "http://192.168.6.116:3000",
"WS_SECRET" : "admin",
"VERBOSITY" : 2
}
}
]
开始运行底层数据收集程序
# pm2 start enode1.json
# pm2 start enode2.json
# pm2 start enode3.json
# pm2 list
┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬──────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │
├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼──────┼──────────┤
│ enodeAA │ 0 │ fork │ 3365 │ online │ 85 │ 2s │ 24% │ 21.3 MB │ root │ disabled │
│ enodeAB │ 1 │ fork │ 3351 │ online │ 5 │ 2s │ 0% │ 22.2 MB │ root │ disabled │
│ enodeAC │ 2 │ fork │ 3333 │ online │ 1 │ 2s │ 0% │ 24.1 MB │ root │ disabled │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴──────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app
状态一栏,如果是online说明运行成功,如果有错误,会提示 errored
可以通过下面的命令找到提示信息,安装需要的依赖库
# pm2 logs
如果报错,需要提前安装好一些依赖库
# npm install chalk util web3 debounce geth
6.3 浏览器监控
状态如下图所示
eth3.png
7 备注说明
后续进行持续更新!