前言
本篇文章简单介绍使用go-ethereum连接以太坊的主网和测试网以及一些基础开发须知,着重介绍如何搭建一个私网的以太坊,至于以太坊应用开发,有机会的话,将在后面的文章中重点介绍。
本篇文章中包含笔者花费大量心血理解并整理的配置和参数说明资料,望可以为诸位道友提供一些帮助。
安装
Go Ethereum可以安装的操作系统平台有Mac OS X,Windows以及Linux/Unix。详情请看Installation Instructions。
这里介绍在Linux上,从go-ethereum源码构建的安装流程。官方提到的Linux/Unix平台包括Ubuntu,Arch和FreeBSD。这里以Ubuntu为例。
编译环境准备
编译环境要求有 Go 和 C编译程序。安装方法如下:
apt-get install -y build-essential golang
获取源码并编译
获取源码
切换到你准备编译go-ethereum源码的目录,执行命令:
git clone https://github.com/ethereum/go-ethereum
上述命令会clone go-ethereum的主干分支代码,该分支代码是持续开发代码,如果想要使用发布代码,参见go-ethereum release。这里以当时最新的发布代码 v1.8.7 为例:
- 下载代码
wget https://github.com/ethereum/go-ethereum/archive/v1.8.7.tar.gz
- 解压
tar zxvf v1.8.7.tar.gz
将解压后的代码移动到你准备编译go-ethereum源码的目录。
编译源码
当你准备好go-ethereum的源码后,进入go-ethereum源码工程顶级目录,执行命令:
make geth
(推荐)如果你想要编译出所有工具,执行:
make all
编译完成后,所有工具可以在 build/bin 目录下找到,将这些二进制文件取出来放到你选好的安装目录,或者就在原来的位置放着,这里我放到 /opt/xingweidong/eth/bin 目录下,接下来根据你的喜好设置系统环境变量,添加如下内容:
export ETH_HOME=/opt/xingweidong/eth
export PATH=$PATH:$ETH_HOME/bin
别忘了使用 source /etc/profile 或者其他你喜欢的方法更新你的环境变量信息。
运行geth
geth命令,主要的Ethereum CLI客户端,是Ethereum网(main-, test- 或 private网)的入口,有能力作为一个full node(默认)存档节点(保留所有历史数据)或者一个light node(现场检索数据)节点运行。经由暴露在HTTP, WebSocket 或 IPC传输的顶部的JSON RPC端点,它可以被其他过程用作Ethereum网的网关。
提示:查看 geth 所有命令行选项使用 geth --help 或者查看 CLI Wiki page。
Ethereum main网的full node
目前为止,大多数情况是人们想要简单地与Ethereum网进行交互:创建账户;转移资金;部署并与合约交互。对于这种特殊的使用情况,用户不关心以前的历史数据,所以我们能快速的同步当前的Ethereum网状态。执行命令:
geth console
命令作用:
- 开始 geth 进入快速同步模式(默认模式,可以使用 --syncmode 选项改变同步模式),由于它为了避免处理整个Ethereum网的历史数据,会下载大量的事务数据,所以会占用大量CPU资源。
- 启动 Geth 内在的交互式 JavaScript console,(经由 console 子命令)通过这个,你能调用所有官方 web3 methods 以及Geth自己的 management APIs。这个也是可选的,如果你离开,你能使用 geth attach 连接一个已经存在的 Geth 实例。
Ethereum test网的full node
向开发者过渡,如果你想要创造合约,你几乎肯定想要在没有任何真实资金的情况下做到这一点,直到你掌握整个系统。换句话说,代替连接到Ethereum main网,你会想要加入到一个 test网,它是和 main网完全等价的,只需要:
geth --testnet console
console子命令与上面的含义完全相同并且它们在一个测试网上也同样有用。如果你跳到这一步,请看上面对它们的解释。
然而,指定 --testnet 将重新配置你的 Geth 实例一小部分:
- 代替使用默认数据目录(例如Linux上的 ~/.ethereum),Geth将会深入一层目录创建 testnet 子文件夹(在Linux上是 ~/.ethereum/testnet)。注意,在OSX和Linux上,这也意味着连接一个运行的testnet节点要求使用一个自定义的端点,因为 geth attach 默认将连接一个生产节点。例如:geth attach <datadir>/testnet/geth.ipc。Windows用户不受影响。
- 代替连接 Ethereum main网,客户端将连接到一个 test网,使用不同的P2P bootnodes,不同的网络ID和创世状态。
注意:尽管有一些内部安全措施防止main网和test网的交易互换,你也应该确认总是为测试资产和真实资产使用分开的账户。除非你手动移动账户,Geth将默认正确分开两个网络,并且在它们之间将不会有任何账户可用。
Rinkeby test网的full node
上述测试网络是基于ethash工作证明共识算法的跨客户端网络。因此,由于网络的低难度/安全性,它有一定的额外开销,并且更容易受到重组攻击。Go Ethereum还支持连接到称为Rinkeby的权威证明测试网络(由社区成员运营)。这个网络更轻,更安全,但只受到go-ethereum的支持。
geth --rinkeby console
配置
代替传递大量选项给 geth 二进制,你能传递一个配置文件经由:
geth --config /path/to/your_config.toml
为了理解这个文件的写法,你能使用 dumpconfig 子命令export你的现有配置:
geth --your-favourite-flags dumpconfig
eg:geth --testnet dumpconfig
注意:这个只在 geth v1.6.0以及以上版本有效。
Docker quick start
通过使用Docker,你可以在你的机器上快速启动Ethereum并运行:
docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
-p 8545:8545 -p 30303:30303 \
ethereum/client-go
上述命令将以快速同步模式启动geth,并具有1GB的DB内存容量。它也将在你的home目录下创建一个永久卷来储存你的区块链和映射的默认端口。还有一个 alpine 标签可用于image的精简版本。
如果你想要从其他容器或主机访问RPC,不要忘了 --rpcaddr 0.0.0.0。默认情况下,geth 绑定本地接口并且不可从外访问RPC端点。
可编程接口 Geth nodes
作为一个开发者,你将想要及早开始经由你自己的程序而不是手动控制台与Geth和Ethereum网交互。为此,Geth已经内置支持一个基于APIs(standard APIs 和 Geth specific APIs)的JSON-RPC。它能够经由HTTP, WebSockets 和 IPC (unix sockets on unix based platforms, and named pipes on Windows)暴露。
IPC接口默认是激活的并暴露所有Geth支持的APIs,然而HTTP 和 WS接口需要手动激活并且由于安全原因只能暴露一个APIs子集。这些可以根据你的需要打开或关闭并被配置。
详情请看 Programatically interfacing Geth nodes
操作一个私网
维护你自己的私网是十分复杂的,因为在一个正规的网络中,大量配置的获取和授权需要手动设置。下面,我们建立一个简单的私网以太坊。
说明:为了使下面创建私网以太坊的步骤更加直白,笔者将默认使用以下的geth选项值(请自行查看选项含义):
- --datadir /root/privatenet/.ethereum
- --config config/privatenet.toml
在开始之前,笔者先介绍一下如何创建以太坊账户:
- 创建主网以太坊账户,执行命令:
geth account new
- 创建私网以太坊账户,执行命令:
geth --datadir /root/privatenet/.ethereum account new
按照提示输入账户密码即可。
注意:创建私网以太坊账户时,务必指定 --datadir 选项,否则会默认创建主网以太坊账户。
定义一个私有的创世状态
首先,你需要创造你的网络的所有节点需要意识到并同意的创世状态。这个由一个小JSON文件组成(例如,称它为 genesis.json):
{
"config": {
"chainId": 15,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000001993",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
}
参数说明(下表是笔者参考go-ethereum源码README和ethereum_yellow_paper整理所得,如有错误,请帮忙指正):
参数 | 描述 |
---|---|
alloc | 可以预置账号以及账号的以太币数量 |
coinbase | 区块受益者地址,可以设置成已存在的账户。后面挖出的区块的受益者将是挖掘出那个区块的账户(矿工) |
difficulty | 代表当前区块的难度等级(十六进制),这里定义创世区块的难度等级,难度等级越高,挖矿越难。后面生成的区块难度等级根据前一个区块的难度等级和时间戳计算得到 |
extraData | 一个包含这个区块相关数据的字节数组,任意填写。必须是32位以内 |
gasLimit | 执行这个事务应该被使用的gas的最大量。这个在任何计算被做之前是预付的,并且在之后不会增加 |
nonce | 代表从这个地址发送的事务数目,或者在关联代码的账户情况下,这代表这个账户创造的合约数目。(在Yellow Paper中对nonce有多处描述,这里选择了4.1章节的描述,) |
mixhash | 一个256位的hash,由nonce合并,证明在这个区块上已经执行足够量的计算 |
parentHash | 前一个(父级)区块的header的keccak256算法hash |
timestamp | 这个区块开始的Unix的time()和合理输出 |
上面这些域应该可以满足大多数需求,不过我们建议改变 nonce 为一些随机值,这样你就能阻止不知名的远程节点访问你。如果你想要为了更早测试,预储备一些账户,你能在 alloc 域进行账户配置:
"alloc": {
"0x0000000000000000000000000000000000000001": {"balance": "111111111"},
"0x0000000000000000000000000000000000000002": {"balance": "222222222"}
}
随着创世状态定义在上面的JSON文件,你应该在启动每个节点之前,优先初始化它,以确认所有区块链参数被正确设置:
geth --datadir /root/privatenet/.ethereum init genesis.json
启动bootstrap
当所有你想要运行的节点初始化到期望的创世状态,你将需要开始一个bootstrap节点,其他节点可以使用它在你的网络或因特网中找到彼此。干净的方式是配置并运行一个独立的bootnode:
- 每个ethereum节点,包括一个bootnode,通过一个enode标识符联系。这些标识符源自一个key。于是你将需要给bootnode这样一个key。因为我们当前没有这样的key,所以我们可以在bootnode启动前生成一个key(并存储它到一个文件):
bootnode -genkey bootnode.key
- 为了bootnode每次启动都使用相同的enode,需要在bootnode启动时指定一个key:
bootnode -nodekey bootnode.key
当bootnode上线,它将展示一个 enode URL,例如:
INFO [05-09|01:47:05] UDP listener up self=enode://75535ebac1f5b2a644edb134dbe91c6c288353be1a5301864edae529630b35c5ff0c0ae9e07b2bcdef578c3ac1b72b2cda105c061c2c77067f1fd8ec54d852b7@[::]:30301
其他节点可以使用这个 enode URL 连接它并交换对等信息。确认用你的外部访问IP替换展示的IP地址信息(很可能是 [::])去得到真正的 enode URL。例如:
enode://75535ebac1f5b2a644edb134dbe91c6c288353be1a5301864edae529630b35c5ff0c0ae9e07b2bcdef578c3ac1b72b2cda105c061c2c77067f1fd8ec54d852b7@192.168.1.214:30301
保存这个准确的 enode URL 到你的一个文本中或者其他什么地方,下面需要用到。
注意:你也可以使用完全成熟的Geth节点作为引导节点,但这是不太推荐的方式。
定义一个配置文件
为了成员节点启动时使用的配置一致,需要写一个配置文件,例如 privatenet.toml(参考自 testnet 的配置, 执行命令 geth --testnet dumpconfig 可见):
# Note: this config doesn't contain the genesis block.
[Eth]
NetworkId = 3369
DatabaseCache = 768
GasPrice = 18000000000
[Eth.Ethash]
CacheDir = "ethash"
CachesInMem = 2
CachesOnDisk = 3
DatasetDir = "/root/privatenet/.ethash"
DatasetsInMem = 1
DatasetsOnDisk = 2
[Eth.TxPool]
NoLocals = false
Journal = "transactions.rlp"
Rejournal = 3600000000000
PriceLimit = 1
PriceBump = 10
AccountSlots = 16
GlobalSlots = 4096
AccountQueue = 64
GlobalQueue = 1024
Lifetime = 10800000000000
[Eth.GPO]
Blocks = 20
Percentile = 60
[Shh]
MaxMessageSize = 1048576
MinimumAcceptedPOW = 2e-01
[Node]
DataDir = "/root/privatenet/.ethereum"
IPCPath = "geth.ipc"
HTTPPort = 8545
HTTPVirtualHosts = ["localhost"]
HTTPModules = ["net", "web3", "eth", "shh"]
WSPort = 8546
WSModules = ["net", "web3", "eth", "shh"]
[Node.P2P]
MaxPeers = 25
NoDiscovery = false
BootstrapNodes = ["enode://75535ebac1f5b2a644edb134dbe91c6c288353be1a5301864edae529630b35c5ff0c0ae9e07b2bcdef578c3ac1b72b2cda105c061c2c77067f1fd8ec54d852b7@1
92.168.1.214:30301"]StaticNodes = []
TrustedNodes = []
ListenAddr = ":30303"
EnableMsgEvents = false
[Dashboard]
Host = "localhost"
Port = 8080
Refresh = 5000000000
配置说明(下表是笔者参考geth参数说明和go-ethereum相关配置项代码整理所得,如有错误,请帮忙指正):
域 | 配置项 | 对应参数 | 说明 |
---|---|---|---|
Eth | NetworkId | --networkid value | Network标识符(integer类型,1=Frontier,2=Morden(disused),3=Ropsten,4=Rinkeby),默认为1。如果建立在私网上,使用另外的任意值,比如:3369 |
Eth | DatabaseCache | null | (个人理解)为database申请的系统内存,单位为MB,最小值和默认值是16MB |
Eth | GasPrice | --gasprice “18000000000” | 接受挖掘事务的最低gas价格。可能指miner的报酬 |
Eth.Ethash | CacheDir | --ethash.cachedir | 存储ethash证明缓存的目录(默认在 datadir 目录里) |
Eth.Ethash | CachesInMem | --ethash.cachesinmem value | 保留在内存中的最新ethash缓存的数目(每16MB)(默认:2)。 |
Eth.Ethash | CachesOnDisk | --ethash.cachesondisk value | 保留在磁盘中的最新ethash缓存的数目(每16MB)(默认:3)。 |
Eth.Ethash | DatasetDir | --ethash.dagdir "/home/karalabe/.ethash" | 存储ethash挖掘DAGs的目录(默认在home目录里) |
Eth.Ethash | DatasetsInMem | --ethash.dagsinmem value | 保留在内存中的最新ethash挖掘DAGs(每1+GB)(默认:1)。 |
Eth.Ethash | DatasetsOnDisk | --ethash.dagsondisk value | 保留在磁盘中的最新ethash挖掘DAGs(每1+GB)(默认:2)。 |
Eth.TxPool | NoLocals | --txpool.nolocals | 免除本地提交事务的费用 |
Eth.TxPool | Journal | --txpool.journal value | 用于节点重启的本地事务磁盘日志(默认:"transactions.rlp") |
Eth.TxPool | Rejournal | --txpool.rejournal value | 重新生成本地事务日志的时间间隔(默认:1h0m0s) |
Eth.TxPool | PriceLimit | --txpool.pricelimit value | 强制接纳入池的最小gas价格限制(默认:1) |
Eth.TxPool | PriceBump | --txpool.pricebump value | 替代一个已经存在的事务的价格碰撞百分比(默认:10) |
Eth.TxPool | AccountSlots | --txpool.accountslots value | 每个账户担保的可执行事务时隙的最小数目(默认:16) |
Eth.TxPool | GlobalSlots | --txpool.globalslots value | 所有账户的可执行事务时隙的最大数目(默认:4096) |
Eth.TxPool | AccountQueue | --txpool.accountqueue value | 每个账户许可的非可执行事务时隙的最大数目(默认:64) |
Eth.TxPool | GlobalQueue | --txpool.globalqueue | 所有账户的非可执行事务时隙的最大数目(默认:1024) |
Eth.TxPool | Lifetime | --txpool.lifetime value | 非可执行事务的排队最大时间(默认:3h0m0s) |
Eth.GPO | Blocks | --gpoblocks value | 检查gas价格的最新区块的数目(默认:10) |
Eth.GPO | Percentile | --gpopercentile value | 建议的gas价格是一组最新事务gas价格的百分位(默认:50) |
Shh | MaxMessageSize | --shh.maxmessagesize value | 可接受的最大信息大小(默认:1048576) |
Shh | MinimumAcceptedPOW | --shh.pow value | 可接受的最小POW(默认:0.2) |
Node | DataDir | --datadir "/home/karalabe/.ethereum" | databases和keystore的数据目录 |
Node | IPCPath | --ipcpath | datadir里的IPC socket/pipe的文件名 |
Node | HTTPPort | --rpcport value | HTTP-RPC服务监听端口(默认:8545) |
Node | HTTPVirtualHosts | --rpcaddr value | HTTP-RPC服务监听接口(默认:"localhost") |
Node | HTTPModules | null | 经由HTTP RPC接口暴露的API modules列表 |
Node | WSPort | --wsport value | WS-RPC 服务监听端口(默认:8546) |
Node | WSModules | null | 经由websocket RPC接口暴露的API modules列表,如果modules是空的,所有指向public的RPC API端点将会被暴露 |
Node.P2P | MaxPeers | --maxpeers value | network peers的最大数目(如果设置为0,network失效)(默认:25) |
Node.P2P | NoDiscovery | --nodiscover | 使peer发现机制无效(手动peer添加)。这里设置为false,以便使用这个配置文件的新节点可以被发现。 |
Node.P2P | BootstrapNodes | --bootnodes value | 逗号分割的P2P discovery bootstrap enode URLs(对于 light servers,设置 v4+v5 代替)。将上面启动bootnodes时获取的enode URL替换IP后添加到这里。 |
Node.P2P | BootstrapNodesV5 | --bootnodesv5 value | 逗号分割的P2P v5 discovery bootstrap enode URLs(light server,light nodes) |
Node.P2P | StaticNodes | null | 配置作为static nodes的节点enode URLs列表 |
Node.P2P | TrustedNodes | null | 配置作为trusted nodes的节点enode URLs列表 |
Node.P2P | ListenAddr | --port | network监听端口(默认:30303) |
Node.P2P | EnableMsgEvents | null | 如果EnableMsgEvents被设置,服务器将发出PeerEvents,无论一个peer何时发送或接收一条信息 |
Dashboard | Host | null | 启动dashboard服务的主机接口,如果这个域为空,则没有dashboard将被启动 |
Dashboard | Port | null | 启动dashboard服务的TCP端口数字。默认0值是有效的,并将使用一个随机端口数字(用于临时节点) |
Dashboard | Refresh | null | 数据更新的刷新速率,chartEntry将被经常收集 |
在你的操作目录创建config文件夹,将写好的配置文件privatenet.toml移动到config目录里。
启动你的成员节点
以太坊的成员节点,之间是完全对等的,每个节点都可以有多个账户。
启动私网以太坊的成员节点:
geth --config config/privatenet.toml
连接到刚刚启动的或者已经在运行的node,开始一个交互式JavaScript环境:
geth attach privatenet/.ethereum/geth.ipc
官方说明: 当bootnode运转起来并且外部可达(你能尝试 telnet <ip> <port> 去确认它的确可达),开始随后的Geth节点,为了对等发现,经由 --bootnodes 选项指向bootnode。保持你的私网的数据目录单独将很可能是明智的选择,所以也指定一个自定义的 --datadir 选项。
geth --datadir=path/to/custom/data/folder --bootnodes=<bootnode-enode-url-from-above>
注意:因为你的网络将被从main和test网完全切除,所以你将需要配置一个 miner 去处理交易并为你创造新块。
运行一个私有的miner
公共Ethereum网的mining是一个复杂的任务,因为它唯一可行的是使用GPUs,要求一个OpenCL 或 CUDA激活 ethminer 实例。更多信息请查阅 EtherMining subreddit和 Genoil miner 仓库。
然而在一个私网的设置,一个单一的 CPU miner实例是足够满足实际需求的,因为它不需要沉重的资源(考虑到运行在一个单一的线程上,也不需要多个)就能在一个正确的间隔内生产一个稳定的区块流(stable stream of blocks)。为mining开始一个Geth实例,指定你通常使用的选项运行它,通过以下方法扩展:
geth --config config/privatenet.toml --mine --minerthreads=1 --etherbase=0x0000000000000000000000000000000000000000
这将开始mining区块并在一个单一CPU线程上交易,存入所有事件到一个 --etherbase 选项指定的账户,如果不指定账户,则会默认指定当前节点上的第一个账户。你能进一步调节mining,通过(--targetgaslimit)改变默认gas限制区块并且在(--gasprice)处接受价格交易。
另外,也可以在交互式JavaScript环境中控制mining实例:
- 开始一个4线程的mining实例:
miner.start(4)
- 停止mining实例:
miner.stop()
更多Mining相关信息,请参看Mining
至此,私网以太坊搭建完成,感谢阅读!
原创不易,与君共勉!
所有玩世不恭的生灵,都有一颗至真至纯的心!