启动一条以太坊私链

以太坊网络分为主网、测试网。这两个网络都是公开的链,为世界所有开发者和用户所用。

我们今天的学习蓝本是以太坊的私有网络,或者称为私链。

私链和公链是不互通的,通过网络进行隔离。我们教程的私链里,有且仅有我们一台计算机运行。

以太坊源代码及软件都是开源的,任何人都可以拷贝一份源代码并且运行自己的以太坊私链。以下是接入不同的网络所需下载数据量的对比。

  • main net 主网 –下载量 >1TB 的区块链数据,共需3-5天时间。
  • test net 测试网 –下载量 ~60GB的代号为Ropsten的测试网数据,约10小时。
  • private net 私链 –无需下载,创世后等待数分钟即可正常运转。

我们将以构建私有网络,亲手创世一条以太坊私链的形式进行下面的教学。

设立工作目录

我们设立一个新的工作目录并新建一个创世启动配置文件。

1
2
3
4
mkdir ether-test
cd ether-test
touch gensis.json
mkdir db

至此,我们的创世目录生成完毕,目录结构如下。

ether-test/
├── db
└── gensis.json

1 directory, 1 file

编辑创世配置

如果读者回忆起 以太坊的特色 的讲解,与比特币不同,以太坊的创世就已经分配了约70%的以太币,随后才进入挖矿过程,大家竞争挖矿分配剩余的币。我们私链也同样可以通过创世方法分配好一些以太币供测试使用,或者从零开始一点一点通过挖矿的方式挖掘以太币。

创世配置就是供以太坊私有链第一次启动时所使用,创世区块是最特殊的区块,它没有前一个区块,因为它自己是第一个块。我们下面来配置创世区块 gensis.json 文件。

// gensis.json
{
  "config": {
      "chainId": 987,
      "homesteadBlock": 0,
      "eip155Block": 0,
      "eip158Block": 0
  },
  "difficulty": "0x400",
  "gasLimit": "0x8000000",
  "alloc": {}
}

以上就是一份最小可行的创世区块配置,包含了区块链配置,挖矿难度设置,单一区块最大包含 Gas 的上限,以及一个空置的创世币分配区alloc(意味着没有预先分配以太币)。当然,也可以扩充一些启动的其他参数诸如时间戳以及前一区块哈希等,这些都是非必填的。完整版的 gensis.json 如下所示。

{
  "config": {
      "chainId": 987,
      "homesteadBlock": 0,
      "eip155Block": 0,
      "eip158Block": 0
  },
  "difficulty": "0x400",
  "gasLimit": "0xffffffff",
  // 可选填的参数
  "coinbase": "0x0000000000000000000000000000000000000000",
  "extraData": "0x00",
  "nonce": "0x0000000000000001",
  "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp": "0x00",
  "alloc": {
    "430e986e0cca10a174baad96871ec9cb308c6d05": {"balance": "111111"}
  }
}

各个字段解释如下:

必填  
chainId 自定义私链的网络ID,不同的网络ID无法互联通讯,以太坊公链ID 为1,我们设置为987以防止与网络中其他私链冲突。
homesteadBlock 是否为HomeStead版本的区块,设置为0表明是。
eip155Block EIP155 [1] 是一个以太坊分叉提议,为了和以前的以太坊经典ETC 链条分叉而存在,我们私链不需要它,设为0。
eip158Block EIP158 [2] 是一个以太坊分叉提议,为了解决之前以太坊空账户造成效率低下的协议漏洞而分叉,我们私链不需要它,设为0。
difficulty 设置当前区块难度,若难度过大挖矿就很慢,我们设置较小值。
gasLimit 单一区块最大 gas 消耗上限,用来限制能够打包进入块的交易数量与信息总和,我们在学习中可以设置为最大。
选填  
coinbase 打包该块的矿工的奖励地址,因为是创世块,可设为0地址。
extraData 打包该块时矿工记录的笔记。
nonce 打包该块时矿工挖矿所用到的Ethash输入参数nonce。
mixHash 与nonce配合用于挖矿,创世区无前一个区块,可不填。
parentHash 前一个区块头的哈希值,创世区块无前一个区块,设为0。
timestamp 打包该块的时间戳,符合Unix Timestamp标准,设为0。
alloc 创世时各账户分配以太币的数量,不想分配则忽略。

后文将以简化版本的 gensis.json 作为创世私有链的基础。

初始化创世配置

我们已经准备好了创世的配置,接下来就是将该链条的配置初始化,形成区块链的起点,初始化之后我们就有了第一个区块,接下来就可以根据第一个区块来挖掘第二个区块了。

进入 ether-test 目录,执行初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
cd ether-test
geth --datadir "./db" init gensis.json
>
INFO [09-16|15:04:53.320] Maximum peer count
INFO [09-16|15:04:53.326] Allocated cache and file handles
INFO [09-16|15:04:53.329] Writing custom genesis block
INFO [09-16|15:04:53.329] Persisted trie from memory database
INFO [09-16|15:04:53.329] Successfully wrote genesis state
INFO [09-16|15:04:53.329] Allocated cache and file handles
INFO [09-16|15:04:53.331] Writing custom genesis block
INFO [09-16|15:04:53.331] Persisted trie from memory database
INFO [09-16|15:04:53.331] Successfully wrote genesis state

此时我们看到控制台上输出了 “Successfully wrote genesis state” 意味着我们的初始化已经成功,趁此机会我们了解一下究竟本地目录发生了怎样的变化:

ether-test/
├── db
│   ├── geth
│   │   ├── chaindata
│   │   │   ├── 000001.log
│   │   │   ├── CURRENT
│   │   │   ├── LOCK
│   │   │   ├── LOG
│   │   │   └── MANIFEST-000000
│   │   └── lightchaindata
│   │       ├── 000001.log
│   │       ├── CURRENT
│   │       ├── LOCK
│   │       ├── LOG
│   │       └── MANIFEST-000000
│   └── keystore
└── gensis.json

5 directories, 11 files

在 db 目录数据库,程序自动新建了 geth 目录。 该目录存放了区块链的所有运行时产生的区块链数据、日志、世界状态并随着区块链增长而占据更多存储硬盘空间。 它内部的两个子目录存储了具体的区块链数据。 db 目录下还有一个 keystore 目录。该目录下保存了各个账户的私钥的加密后的文件, 与我们学习过的 资料篇:Keystore 与私钥保存 相对应。只有在签名的时候用户才会解锁 keystore 执行签名操作。

启动Geth 节点

一切准备就绪,弗兰肯斯坦的巨人马上就要苏醒,在我们现在的环境下,需要启动一个 Geth 节点来接入私链网络(实际上也是这个私链网络的唯一一个节点),负责在创世块后挖出第一个块。该节点也是我们与以太坊私链通信的节点服务器。下面我们来启动这样一个节点。

1
2
3
4
5
6
7
cd ether-test
geth --datadir ./db/ --rpc --rpcaddr=127.0.0.1 --rpcport 8545 --rpccorsdomain "*" \
   --rpcapi "eth,net,web3,personal,admin,shh,txpool,debug,miner" \
   --nodiscover --maxpeers 30 --networkid 198989 --port 30303 \
   --mine --minerthreads 1 \
   --etherbase "0x7df9a875a174b3bc565e6424a0050ebc1b2d1d82" \
   console

geth启动时命令行参数解释如下表:

参数 解释
–rpc 开启JSON-RPC 服务,可供调用/调试访问。
–rpcaddr 本地监听JSON-RPC的地址。
–rpcport 本地监听JSON-RPC的端口。
–prccorsdomain 本地监听JSON-RPC允许的域名访问。
–rpcapi 允许提供的RPC服务模块,在示例中选择了数个模块加载。
–nodiscover 关闭自动发现节点,私有链开发时防止他人意外接入,可选择关闭该选项避免他人加入网络。
–maxpeers 允许最大节点链接数目。
–networkid 指定以太坊网络ID。
–port 监听以太坊节点之间P2P消息的TCP/UDP端口,默认30303。
–mine 节点启动挖矿功能,参与挖矿。
–minerthreads 挖矿的多线程配置,例子中配置为1个线程。
–etherbase 若启动挖矿功能,挖矿奖励的接受地址,例子中我们随便填了一个。
console (可选) 启动后进入命令行模式,直接输入命令互动操作。

输入回车,启动成功!此时控制台会输出一组日志信息并有如下的欢迎信息:

INFO [09-16|15:33:26.872] Maximum peer count                       ETH=30 LES=0 total=30
INFO [09-16|15:33:26.879] Starting peer-to-peer node               instance=Geth/v1.8.14-stable/darwin-amd64/go1.10.3
INFO [09-16|15:33:26.879] Allocated cache and file handles         database=/ether-test/db/geth/chaindata cache=768 handles=1024
INFO [09-16|15:33:26.889] Initialised chain configuration          config="{ChainID: 987 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: <nil> EIP155: 0 EIP158: 0 Byzantium: <nil> Constantinople: <nil> Engine: unknown}"
INFO [09-16|15:33:26.889] Disk storage enabled for ethash caches   dir=/ether-test/db/geth/ethash count=3
INFO [09-16|15:33:26.889] Disk storage enabled for ethash DAGs     dir=/.ethash                   count=2
INFO [09-16|15:33:26.889] Initialising Ethereum protocol           versions="[63 62]" network=198989
INFO [09-16|15:33:26.890] Loaded most recent local header          number=0 hash=4e048d…366eb4 td=1024
INFO [09-16|15:33:26.890] Loaded most recent local full block      number=0 hash=4e048d…366eb4 td=1024
INFO [09-16|15:33:26.890] Loaded most recent local fast block      number=0 hash=4e048d…366eb4 td=1024
INFO [09-16|15:33:26.890] Regenerated local transaction journal    transactions=0 accounts=0
INFO [09-16|15:33:26.890] Starting P2P networking
...

Note

Geth启动时会占用数个计算机端口,请确保它们可用。
  • 8545 TCP端口 – 供基于 HTTP的JSON-RPC API通信使用。
  • 8546 TCP端口 – 供基于 WebSocket的JSON-RPC API通信使用。
  • 30303 TCP/UDP端口 – 提供 P2P 协议同步数据块运行以太坊网络。
  • 30304 UDP端口– P2P通信中新节点发现机制所用。

在较慢的硬盘(非SSD)或者在虚拟机环境中,挖出一个区块的时间会非常长,约需要 5~10 分钟时间,在配置了 SSD 硬盘的新款 Macbook 上几乎是瞬间就能挖出一个区块。挖出区块时会有小锤子的标记,会显示如下的控制台输出:

INFO [09-16|15:34:03.651] Successfully sealed new block            number=1 hash=589650…007f89 elapsed=8.569s
INFO [09-16|15:34:03.651] 🔨 mined potential block                  number=1 hash=589650…007f89
INFO [09-16|15:34:03.652] Commit new mining work                   number=2 uncles=0 txs=0 gas=0 fees=0 elapsed=125.154µs
INFO [09-16|15:34:03.838] Successfully sealed new block            number=2 hash=fb5f62…81e023 elapsed=186.718ms
INFO [09-16|15:34:03.839] 🔨 mined potential block                  number=2 hash=fb5f62…81e023
INFO [09-16|15:34:03.839] Commit new mining work                   number=3 uncles=0 txs=0 gas=0 fees=0 elapsed=142.96µs
INFO [09-16|15:34:04.092] Successfully sealed new block            number=3 hash=4fb51d…5c5e1c elapsed=252.862ms
INFO [09-16|15:34:04.092] 🔨 mined potential block                  number=3 hash=4fb51d…5c5e1c

我们可以陆续看到诸如 number=1,number=2 的输出,这表明我们已经成功地挖掘出了第1,2,3…个区块,而区块的 uncles=0 ,说明并没有 叔块 产生,复习我们之前所学的知识 资料篇:矿工与挖矿奖励 ,在网络中有且仅有我们一个挖矿节点的时候,没有竞争对手产生同一个高度的块,所以显而易见的是不会产生孤立的块,也就不会产生叔块了。

至此,控制台持续在输出挖矿相关信息。你的私链已经稳定运行。由于我们启动时 etherbase 参数随便设置了一个挖矿矿工奖励地址,所以挖出的区块奖励都发送到了那个地址,但我们并没有持有这个地址,这等于白白浪费了。现在,我们来与Geth通信并且让它暂停一下挖矿,等我们新建好了账户之后再继续挖。

我们重新开一个控制台窗口,并连接上正在运行的 Geth,让其停下挖矿。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
cd ether-test
geth --datadir ./db attach ipc:./db/geth.ipc
>
Welcome to the Geth JavaScript console!

instance: Geth/v1.8.14-stable/darwin-amd64/go1.10.3
coinbase: 0x7df9a875a174b3bc565e6424a0050ebc1b2d1d82
at block: 90 (Sun, 16 Sep 2018 15:56:37 CST)
 datadir: /ether-test/db
 modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

> miner.stop()
true

我们使用 miner.stop() 命令来停止挖矿行为,此时可以看到之前启动的 Geth 节点的控制台输出已经停下,没有新区块被挖掘。需要重启挖矿,执行 miner.start() 即可。

至此我们回顾一下。我们已经启动了一条以太坊私链,在启动之时我们选填了一些参数。

–rpcapi “eth,net,web3,personal,admin,shh,txpool,debug,miner”

这些参数都是指定Geth客户端装载哪些功能模块的,这些模块有的是挖矿模块,有的是账户模块,根据使用者偏好可以选择性地启用,适当地增减当前节点具备的能力。具体的模块解释如下表 [3]

模块 解释
personal 账户相关操作–新建账户、锁定账户、发送签名交易等。
admin 节点管理相关–节点数据存储、网络状况、API 开放状况等。
txpool 交易池相关–交易池等待情况,查看某交易详情等。
debug 开发调试相关–追踪区块状况,分析区块详情、CPU 状况检测等。
miner 挖矿相关–更改奖励收款地址、开启/关闭挖矿功能、设置 gas 费用等。
web3 包含了以上模块的总入口,还包含单位换算函数。
eth 提供了操作区块链的相关方法。
shh 提供了分布式网络 P2P Whisper通信协议的相关方法
[1]Vitalik Buterin (2016), ‘Simple replay attack protection’, Available at: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
[2]Vitalik Buterin (2016), ‘State clearing’, Available at: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-158.md
[3]笔者注:更多参考见 https://github.com/ethereum/go-ethereum/wiki/Management-APIs