区块链公链都是基于p2p网络,本篇文章将建立一个多节点不同职责参与的EOS的测试网络,根据路上发现的可做文章的技术点大做文章。 关键字:EOS组网,全节点,交易确认,boot sequence,stake,帕累托分配模型,竞选出块节点,EOS出块奖励,代理投票,resign
源节点就是第一个EOS节点(Genesis node),也可以叫主节点,EOS多节点组网的前提是已经对单机环境非常熟悉,我们的架构如下:
注意对钱包进行修改时,例如删除钱包数据,重新创建,要预先手动kill掉keosd进程。
其他更详细的描述请转到《启动一个单独节点》。
单机准备就完成了,可以看出nodeos和keosd是分开的进程,最后通过alias别名将他们结合在了一起。
全节点不出块但会保持同步完整区块数据到本地。在另一台机器上,同样的拉取同版本源码构建安装命令,然后修改配置文件config.ini。这里我们要修改的是:
直接键入命令nodeos启动节点。
源码拉取-> checkout 最新版本号 -> 构建执行环境 -> 修改本地配置文件
然后使用命令:
nodeos --delete-all-block
清空旧的区块数据,重新启动链。
3435662ms thread-0 net_plugin.cpp:3055 plugin_startup ] starting listener, max clients is 2
3435671ms thread-0 net_plugin.cpp:749 connection ] created connection to 47.93.127.182:9876
3435672ms thread-0 net_plugin.cpp:1969 connect ] host: 47.93.127.182 port: 9876
3449419ms thread-0 net_plugin.cpp:773 connection ] accepted network connection
3449851ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block 5f1a90b86a211c11... #1000 @ 2018-06-21T03:24:52.500 signed by eosio [trxs: 0, lib: 999, conf: 0, latency: 109957351 ms]
3450291ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block 1476bbf0e003fcf4... #2000 @ 2018-06-21T03:33:12.500 signed by eosio [trxs: 0, lib: 1999, conf: 0, latency: 109457791 ms]
3450785ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block 8c4bea86b3433b78... #3000 @ 2018-06-21T03:41:32.500 signed by eosio [trxs: 0, lib: 2999, conf: 0, latency: 108958285 ms]
3451298ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block ddd23622b0174f2c... #4000 @ 2018-06-21T03:49:52.500 signed by eosio [trxs: 0, lib: 3999, conf: 0, latency: 108458798 ms]
3451794ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block 37d4aa8148f4a429... #5000 @ 2018-06-21T03:58:12.500 signed by eosio [trxs: 0, lib: 4999, conf: 0, latency: 107959294 ms]
全节点重新启动以后,可以观察到日志的内容与主节点有所不同:
全节点只同步区块,不生成区块,它拥有完整的区块数据,因此可以通过全节点暴露的接口对链上数据进行查询,
http://全节点IP:8888/v1/chain/get_info
{
"server_version": "79651199",
"chain_id": "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f",
"head_block_num": 3381,
"last_irreversible_block_num": 3380,
"last_irreversible_block_id": "00000d349eb386e22de1b2bdde422377d49b3d3e997af25de1124ff41bad8eb8",
"head_block_id": "00000d3500aec9d772fa3c0801b79366e76dc5e4426a574e6b14a56e220b865e",
"head_block_time": "2018-06-25T08:06:55.000",
"head_block_producer": "eosio",
"virtual_block_cpu_limit": 5869808,
"virtual_block_net_limit": 30835535,
"block_cpu_limit": 199900,
"block_net_limit": 1048576
}
现在有一台出块节点别名239,一台全节点别名182。
**@jack**
182(全节点) | 239(出块节点) |
---|---|
私钥A | eosio |
这样一来,182模拟是小白客户端,239是业务带头人,小白的秘钥完全是由自己创建自己保存,而小白的账户是业务带头人来创建的,小白只需要提供自己的公钥即可。这样一来,小白很有安全感,因为账户完全是自己的,自己做任何事情都需要本地私钥签名,不会被冒名。而业务带头人也会很开心,因为他仍旧可以经营自己的社区,知道谁是通过自己创建的,是自己的用户,但也仅此而已,业务带头人并不能对小白有任何多余的管辖。
下面从区块数据上面研究以上行为:
账户被创建是一个行为或者一个事务,创建时会返回一个transaction id,我们手动去查一下这个transaction,
cleos get transaction 3b0b14a72cc4a98dd9145989xxxxxxxx
返回的数据非常庞大,其中包含了该transaction被记录的区块号,我们通过区块号去查找区块信息,
cleos get block 203xx
返回的数据中,可以看到很多字段信息,其中有一个confirmed字段。
猜想TODO:这是区块确权的值,只要超过出块节点总数的2/3 + 1,就可以被认定为不可逆区块。但由于目前只有一个出块节点,该字段为0,所以这个猜想仍需要测试。
在239上的事务的确认,我们是否可以通过182上对事务结果的查询进行验证呢?答案是肯定的。以上我们在239上创建了账户jack,我们转到182,去查询
cleos get account jack
结果返回jack账户是正常有效的,这就可以确定另一台机器239上的创建账户的事务被确认。这种交易确认的方法要简单的多,属于结果验证论,意思也就是通过结果来判断是否完成一个动作。EOS就有可逆区块大小的配置,可逆区块就是未经确权的区块,一经确权就会变为不可逆区块上链。
实际上,在可逆区块确权的过程中,以太坊是会全网广播的,但EOS只会BP之间广播,因此全节点接收到的一定是不可逆区块,通过全节点来确认交易是个不错的方法。
我们在182上使用刚刚创建的jack来创建一个新的bob账户,
root@iZ2ze5wsiqz8cj0lqgf73xZ:~/182# cleos create account jack bob EOS5MLNon1NFXqnS4koDiKdVg2iTuu5ZS2NeZxve1RHTTifiCUfjg
executed transaction: 0e95f8e9f3abdfbada4f1c10304f04f052a0b58364c3165da4551e9275ab86bb 200 bytes 298 us
# eosio <= eosio::newaccount {"creator":"jack","name":"bob","owner":{"threshold":1,"keys":[{"key":"EOS5MLNon1NFXqnS4koDiKdVg2iTuu...
warning: transaction executed locally, but may not be confirmed by the network yet
然后在239上查询,
cleos get account bob
结果是同样的。也就是说,
方法二的交易确认重点不在于是否全节点来确认交易,出块节点同样可以确认交易。所以重点是是否可在其他机器上查到结果。只不过是因为EOS BP之间广播可逆区块的特性,所以去全节点上查询结果显得更稳妥。
前面我们的主节点+全节点的测试采用的是按需研究,下面我们整理一下真正去完整地启动一条链的步骤,也叫boot sequence,在这过程中,也会包括我结合源码位置 tutorials/bios-boot-tutorial/bios-boot-tutorial.py 脚本进行某些操作的具体实现的分析,步骤如下:
首先我定义为base阶段,因为这些操作我们耳熟能详,这里进行一个总结:
killall keosd nodeos || true
启动序列运行到这里,我们就已经拥有了一个独立节点,它安装了eosio.token, eosio.msig, eosio.system三个合约,目前它有eosio和eosio.msig两个特权账户(eosio.msig账户是eosio.msig合约的owner),以及其他eosio.*系统级用户,目前无普通用户。下面的操作因为我们之前的研究中未涉及,所以这里另起一小节进行描述。
股权账户staked accounts,就是我们理解的普通用户。
持股和抛股是区块链整个生命周期中的重要行为模式。但是在启动阶段时的持股初始化操作是特殊的,账户通过他们的token持股,然而直到producer选举出来之前,token都是冻结状态,也就是说账户的持股身份不可抛弃。因此启动阶段的初始化持股操作的目的是:
分配token到账户,准备使用,然后是参与投票过程,producer才能被选举出来,整个区块链才算是“活了”。
entity | staked |
---|---|
RAM | 0.1 SYS |
CPU | (0.45+45) SYS |
network | (0.45+45) SYS |
liquid | 9 SYS |
这个抵押token置换资源使用权的过程很清晰,因为用户的token量是足够的,可以完全按照上面的流程操作。
entity | staked |
---|---|
RAM | 0.1 SYS |
CPU | (0.45+0) SYS |
network | (0.45+0) SYS |
liquid | 4 SYS |
这个抵押token置换资源使用权的过程与上面稍有不同,因为用户的token量不足,所以按照上面流程操作,第三步流动liquid token的个数不足9个,经历前两步以后,账户B仅剩余4个SYS,免为其难地,liquid token只能抵押4个SYS。而第四步,由于没有剩余token,所以也不必执行了。
根据以上对账户持股抵押的研究结果,我们翻回来说boot sequence base 阶段的token SYS的分配策略,这个过程是夹在SYS create和issue的中间。是使用帕累托分配模型(Pareto distribution)将 十亿个SYS分发出去。
帕累托分配模型:是一个80-20规则,即80%的token被20%的人持有。具体实现过程在脚本bios-boot-tutorial.py中是通过Python Numpy库来生成帕累托分配的。
def allocateFunds(b, e):
dist = numpy.random.pareto(1.161, e - b).tolist() # 1.161 = 80/20 rule
dist.sort()
dist.reverse()
factor = 1_000_000_000 / sum(dist)
total = 0
for i in range(b, e):
funds = round(factor * dist[i - b] * 10000)
if i >= firstProducer and i < firstProducer + numProducers:
funds = max(funds, round(args.min_producer_funds * 10000))
total += funds
accounts[i]['funds'] = funds
return total
通过对源码的分析,可以知道accounts是accounts.json数据的集合,包含字段name、pub以及ppvt,分别代表账户名称、公钥和私钥的属性。然而,allocateFunds函数要做的事情是为accounts集合的每一个对象增加一个字段‘funds’,这个字段的值是通过帕累托分配模型计算出来的,可以使众多的普通账户的fund值呈现80-20规则。而目前funds的值并未真正是账户所拥有的token,而是相当于一个计划!后面会有使用到的地方,这里系个扣b1。
感兴趣的同学可以通过Python numpy.random.pareto函数的文档来研究它具体的思想以及实现方法。
下面使用system newaccount创建账户,并抵押资产。
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/239# cleos system newaccount eosio --transfer accountnum11 "EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb" --stake-net "100000.0000 SYS" --stake-cpu "100000.0000 SYS" --buy-ram="0.1 SYS"
1601937ms thread-0 main.cpp:429 create_action ] result: {"binargs":"0000000000ea30551082d4334f4d1132e8030000000000000453595300000000"} arg: {"code":"eosio","action":"buyram","args":{"payer":"eosio","receiver":"accountnum11","quant":"0.1000 SYS"}}
1601938ms thread-0 main.cpp:429 create_action ] result: {"binargs":"0000000000ea30551082d4334f4d113200ca9a3b00000000045359530000000000ca9a3b00000000045359530000000001"} arg: {"code":"eosio","action":"delegatebw","args":{"from":"eosio","receiver":"accountnum11","stake_net_quantity":"100000.0000 SYS","stake_cpu_quantity":"100000.0000 SYS","transfer":true}}
executed transaction: 24a805a6a582a35ddd594ae25b7cf4a506244201d3fbcb4cfb4d079bf582764d 344 bytes 6072 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"accountnum11","owner":{"threshold":1,"keys":[{"key":"EOS8aCaHAARJvWqD7Xsb...
# eosio <= eosio::buyram {"payer":"eosio","receiver":"accountnum11","quant":"0.1000 SYS"}
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"eosio.ram","quantity":"0.0995 SYS","memo":"buy ram"}
# eosio <= eosio.token::transfer {"from":"eosio","to":"eosio.ram","quantity":"0.0995 SYS","memo":"buy ram"}
# eosio.ram <= eosio.token::transfer {"from":"eosio","to":"eosio.ram","quantity":"0.0995 SYS","memo":"buy ram"}
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"eosio.ramfee","quantity":"0.0005 SYS","memo":"ram fee"}
# eosio <= eosio.token::transfer {"from":"eosio","to":"eosio.ramfee","quantity":"0.0005 SYS","memo":"ram fee"}
# eosio.ramfee <= eosio.token::transfer {"from":"eosio","to":"eosio.ramfee","quantity":"0.0005 SYS","memo":"ram fee"}
# eosio <= eosio::delegatebw {"from":"eosio","receiver":"accountnum11","stake_net_quantity":"100000.0000 SYS","stake_cpu_quantity...
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"eosio.stake","quantity":"200000.0000 SYS","memo":"stake bandwidth"}
# eosio <= eosio.token::transfer {"from":"eosio","to":"eosio.stake","quantity":"200000.0000 SYS","memo":"stake bandwidth"}
# eosio.stake <= eosio.token::transfer {"from":"eosio","to":"eosio.stake","quantity":"200000.0000 SYS","memo":"stake bandwidth"}
warning: transaction executed locally, but may not be confirmed by the network yet
注意这里--stake-net, --stake-cpu, --buy-ram的值都是手动随意填的,并不是自动算出来的。
关于这个问题,我们就可以解扣了,扣b1提到的填充组装到accounts集合的字段‘funds’,就是用来计算这些参数的值的,具体计算方式,可以通过脚本源码来看:
def createStakedAccounts(b, e):
ramFunds = round(args.ram_funds * 10000) # 通过参数ram_funds设置用于购买内存的资金
configuredMinStake = round(args.min_stake * 10000) # 通过参数min_stake设置最小抵押值
maxUnstaked = round(args.max_unstaked * 10000) # 最大非抵押值
for i in range(b, e):
a = accounts[i]
funds = a['funds'] # 获取‘funds’值
print('#' * 80)
print('# %d/%d %s %s' % (i, e, a['name'], intToCurrency(funds)))
print('#' * 80)
if funds < ramFunds:
print('skipping %s: not enough funds to cover ram' % a['name'])
continue
minStake = min(funds - ramFunds, configuredMinStake) # 最小抵押值
unstaked = min(funds - ramFunds - minStake, maxUnstaked) # 非抵押值
stake = funds - ramFunds - unstaked # 剩余可分配抵押值总数
stakeNet = round(stake / 2) # net和cpu均分,各抵押一半。
stakeCpu = stake - stakeNet
print('%s: total funds=%s, ram=%s, net=%s, cpu=%s, unstaked=%s' % (a['name'], intToCurrency(a['funds']), intToCurrency(ramFunds), intToCurrency(stakeNet), intToCurrency(stakeCpu), intToCurrency(unstaked)))
assert(funds == ramFunds + stakeNet + stakeCpu + unstaked)
retry(args.cleos + 'system newaccount --transfer eosio %s %s --stake-net "%s" --stake-cpu "%s" --buy-ram "%s" ' %
(a['name'], a['pub'], intToCurrency(stakeNet), intToCurrency(stakeCpu), intToCurrency(ramFunds)))
if unstaked: # 用完资源以后,再还回账户。
retry(args.cleos + 'transfer eosio %s "%s"' % (a['name'], intToCurrency(unstaked)))
三个变量stakeNet,stakeCpu,ramFunds就是我们用来抵押资源的值,这个策略与前面提到的“Stake 流程”有些不同,我们通过脚本参数--ram-funds指定了内存购买数,默认值是上面提到的0.1 SYS,另外还有最小抵押值和最大非抵押值等,所以这个流程更加复杂,具备生产可行性。
我们可以指定某个或某些个股权账户作为区块生产者。这个过程首先要先将股权账户注册为一个区块生产者候选人,通过以下命令执行:
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/239# cleos system regproducer accountnum11 EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb https://accountnum11.com/EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb
400025ms thread-0 main.cpp:429 create_action ] result: {"binargs":"1082d4334f4d11320003e5419cfdd7d6d511bc2c2f7f88c0e93432cf0ff39718fe99491e18e2069dd2674e68747470733a2f2f6163636f756e746e756d31312e636f6d2f454f5338614361484141524a7657714437587362714b323563346168444b543454776d716a76534346624433626f66384c313646620000"} arg: {"code":"eosio","action":"regproducer","args":{"producer":"accountnum11","producer_key":"EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb","url":"https://accountnum11.com/EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb","location":0}}
executed transaction: 8181fe1cd180afeae280b8f8f2ffc735aa63cb10a8c0cf12a86198e179203228 216 bytes 1481 us
# eosio <= eosio::regproducer {"producer":"accountnum11","producer_key":"EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb","u...
warning: transaction executed locally, but may not be confirmed by the network yet
参数介绍:
下面我们再用相同的流程多注册几个候选人,然后展示候选人列表:
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/239# cleos system listproducers
Producer Producer key Url Scaled votes
a EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb https://www.baidu.com 0.0000
accountnum1 EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb https://www.google.com 0.0000
accountnum11 EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4TwmqjvSCFbD3bof8L16Fb https://accountnum11.com/EOS8aCaHAARJvWqD7XsbqK25c4ahDKT4Tw 0.0000
两个问题:
使用一个候选人账户开启一条链,配置config.ini,手动去写比较复杂。我们直接使用脚本执行,执行前先安装numpy
sudo apt-get install python3-numpy
然后修改脚本中的一些数字为有效值,开始执行(我们约束只要3个bp,8个普通账户),
./bios-boot-tutorial.py -a --user-limit 8 --producer-limit 3
前面提到的流程全都跑完了,跑到当前位置停下,可以看到,先来查一下候选人列表:
bios-boot-tutorial.py: ../../build/programs/cleos/cleos --wallet-url http://localhost:6666 --url http://localhost:8000 system listproducers
Producer Producer key Url Scaled votes
producer111a EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz https://producer111a.com/EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8 0.0000
producer111b EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC https://producer111b.com/EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuE 0.0000
producer111c EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 https://producer111c.com/EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNj 0.0000
然后分别开启这三个账户的链,
bios-boot-tutorial.py: rm -rf ./nodes/01-producer111a/
bios-boot-tutorial.py: mkdir -p ./nodes/01-producer111a/
bios-boot-tutorial.py: ../../build/programs/nodeos/nodeos --max-irreversible-block-age 9999999 --contracts-console --genesis-json /root/lwb-work/eos/tutorials/bios-boot-tutorial/genesis.json --blocks-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/01-producer111a/blocks --config-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/01-producer111a --data-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/01-producer111a --chain-state-db-size-mb 1024 --http-server-address 127.0.0.1:8001 --p2p-listen-endpoint 127.0.0.1:9001 --max-clients 13 --p2p-max-nodes-per-host 13 --enable-stale-production --producer-name producer111a --private-key '["EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz","5KLGj1HGRWbk5xNmoKfrcrQHXvcVJBPdAckoiJgFftXSJjLPp7b"]' --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::producer_plugin --p2p-peer-address localhost:9000 2>>./nodes/01-producer111a/stderr
bios-boot-tutorial.py: rm -rf ./nodes/02-producer111b/
bios-boot-tutorial.py: mkdir -p ./nodes/02-producer111b/
bios-boot-tutorial.py: ../../build/programs/nodeos/nodeos --max-irreversible-block-age 9999999 --contracts-console --genesis-json /root/lwb-work/eos/tutorials/bios-boot-tutorial/genesis.json --blocks-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/02-producer111b/blocks --config-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/02-producer111b --data-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/02-producer111b --chain-state-db-size-mb 1024 --http-server-address 127.0.0.1:8002 --p2p-listen-endpoint 127.0.0.1:9002 --max-clients 13 --p2p-max-nodes-per-host 13 --enable-stale-production --producer-name producer111b --private-key '["EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC","5K6qk1KaCYYWX86UhAfUsbMwhGPUqrqHrZEQDjs9ekP5j6LgHUu"]' --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::producer_plugin --p2p-peer-address localhost:9000 --p2p-peer-address localhost:9001 2>>./nodes/02-producer111b/stderr
bios-boot-tutorial.py: rm -rf ./nodes/03-producer111c/
bios-boot-tutorial.py: mkdir -p ./nodes/03-producer111c/
bios-boot-tutorial.py: ../../build/programs/nodeos/nodeos --max-irreversible-block-age 9999999 --contracts-console --genesis-json /root/lwb-work/eos/tutorials/bios-boot-tutorial/genesis.json --blocks-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/03-producer111c/blocks --config-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/03-producer111c --data-dir /root/lwb-work/eos/tutorials/bios-boot-tutorial/nodes/03-producer111c --chain-state-db-size-mb 1024 --http-server-address 127.0.0.1:8003 --p2p-listen-endpoint 127.0.0.1:9003 --max-clients 13 --p2p-max-nodes-per-host 13 --enable-stale-production --producer-name producer111c --private-key '["EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6","5JCStvbRgUZ6hjyfUiUaxt5iU3HP6zC1kwx3W7SweaEGvs4EPfQ"]' --plugin eosio::http_plugin --plugin eosio::chain_api_plugin --plugin eosio::producer_plugin --p2p-peer-address localhost:9000 --p2p-peer-address localhost:9001 --p2p-peer-address localhost:9002 2>>./nodes/03-producer111c/stderr
启动候选人链时有几点注意:
任意一个股权用户均可以投票,
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 system voteproducer prods useraaaaaaab producer111a
2190240ms thread-0 main.cpp:429 create_action ] result: {"binargs":"708c31c6187315d600000000000000000160420857219de8ad"} arg: {"code":"eosio","action":"voteproducer","args":{"voter":"useraaaaaaab","proxy":"","producers":["producer111a"]}}
executed transaction: 729381cc691690061d9724b3553e1eca834317d9b4ebf8067f5093a97345d056 120 bytes 2242 us
# eosio <= eosio::voteproducer {"voter":"useraaaaaaab","proxy":"","producers":["producer111a"]}
warning: transaction executed locally, but may not be confirmed by the network yet
再来查看候选人列表:
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 system listproducers
Producer Producer key Url Scaled votes
producer111a EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz https://producer111a.com/EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8 1.0000
producer111b EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC https://producer111b.com/EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuE 0.0000
producer111c EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6 https://producer111c.com/EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNj 0.0000
可以看到候选人producer111a的Scaled项变为1。那么什么时候算投票结束呢?
直到有效投票数超过总可投票的15%,排在前面的候选者就开始出块。
注意一个账户只能头一次票给一个候选人,多次投票可以执行成功,但票数仅第一次有效。
给producer111a投票以后,我一直在监控几个日志平台,发现不知什么时候,00-eosio节点已经不出块了,开始接受块,而01-producer111a节点显示开始出块,其他候选人仍旧接受块。这时候我更换策略,开始用股权账户为producer111b投票,投完以后,没过多久,让我捕捉到01-producer111a节点的日志和producer111b节点的日志变化了。
01-producer111a节点的日志:
2887500ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 00000a7bd2326f26... #2683 @ 2018-06-27T11:48:07.500 signed by producer111a [trxs: 0, lib: 2682, confirmed: 0]
2887504ms thread-0 controller.cpp:752 start_block ] promoting proposed schedule (set in block 2683) to pending; current block: 2684 lib: 2683 schedule: {"version":2,"producers":[{"producer_name":"producer111a","block_signing_key":"EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz"},{"producer_name":"producer111b","block_signing_key":"EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC"}]}
2888000ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 00000a7c22b963b7... #2684 @ 2018-06-27T11:48:08.000 signed by producer111a [trxs: 0, lib: 2683, confirmed: 0]
2888500ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 00000a7d96adbfc0... #2685 @ 2018-06-27T11:48:08.500 signed by producer111a [trxs: 0, lib: 2684, confirmed: 0]
2889003ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block 6189af49442e9971... #2686 @ 2018-06-27T11:48:09.000 signed by producer111b [trxs: 0, lib: 2684, conf: 0, latency: 3 ms]
02-producer111b节点的日志:
2887510ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block d2326f262fefb570... #2683 @ 2018-06-27T11:48:07.500 signed by producer111a [trxs: 0, lib: 2682, conf: 0, latency: 10 ms]
2887510ms thread-0 controller.cpp:752 start_block ] promoting proposed schedule (set in block 2683) to pending; current block: 2684 lib: 2683 schedule: {"version":2,"producers":[{"producer_name":"producer111a","block_signing_key":"EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz"},{"producer_name":"producer111b","block_signing_key":"EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC"}]}
2888002ms thread-0 controller.cpp:752 start_block ] promoting proposed schedule (set in block 2683) to pending; current block: 2684 lib: 2683 schedule: {"version":2,"producers":[{"producer_name":"producer111a","block_signing_key":"EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz"},{"producer_name":"producer111b","block_signing_key":"EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC"}]}
2888004ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block 22b963b7ef2954ae... #2684 @ 2018-06-27T11:48:08.000 signed by producer111a [trxs: 0, lib: 2683, conf: 0, latency: 4 ms]
2888503ms thread-0 producer_plugin.cpp:290 on_incoming_block ] Received block 96adbfc0c04b06eb... #2685 @ 2018-06-27T11:48:08.500 signed by producer111a [trxs: 0, lib: 2684, conf: 0, latency: 3 ms]
2889000ms thread-0 producer_plugin.cpp:1073 produce_block ] Produced block 00000a7e6189af49... #2686 @ 2018-06-27T11:48:09.000 signed by producer111b [trxs: 0, lib: 2684, confirmed: 0]
可以看出,
那么这个start_block事件的内容在两个节点里报出来的都是相同的内容:
promoting proposed schedule (set in block 2683) to pending; current block: 2684 lib: 2683 schedule: {"version":2,"producers":[{"producer_name":"producer111a","block_signing_key":"EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz"},{"producer_name":"producer111b","block_signing_key":"EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC"}]}
意思就是producer111b晋升为出块节点。接着我们再继续观察日志,会发现:
producer111a和producer111b是交替出块,producer111a节点并没有因为producer111b的晋升而不再出块。
producer111c没有人给他投票,所以继续接收。
通过table来查询所有候选人(包含出块者)目前的状态,
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 get table eosio eosio producers
{
"rows": [{
"owner": "producer111a",
"total_votes": "140.00000000000000000",
"producer_key": "EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz",
"is_active": 1,
"url": "https://producer111a.com/EOS8imf2TDq6FKtLZ8mvXPWcd6EF2rQwo8zKdLNzsbU9EiMSt9Lwz",
"unpaid_blocks": 1124,
"last_claim_time": "1530101102000000",
"location": 0
},{
"owner": "producer111b",
"total_votes": "3767537711703276032.00000000000000000",
"producer_key": "EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC",
"is_active": 1,
"url": "https://producer111b.com/EOS7Ef4kuyTbXbtSPP5Bgethvo6pbitpuEz2RMWhXb8LXxEgcR7MC",
"unpaid_blocks": 2138,
"last_claim_time": 0,
"location": 0
},{
"owner": "producer111c",
"total_votes": "0.00000000000000000",
"producer_key": "EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6",
"is_active": 1,
"url": "https://producer111c.com/EOS5n442Qz4yVc4LbdPCDnxNSseAiUCrNjRxAfPhUvM8tWS5svid6",
"unpaid_blocks": 0,
"last_claim_time": 0,
"location": 0
}
],
"more": false
}
通过打印结果可以观察到三个候选节点的收到的投票数,公钥,url等属性,其中unpaid_blocks属性是还未申领奖励的区块数(属于该节点出的块),last_claim_time属性是上一次申领时间。
与比特币和以太坊相同的是,EOS的出块者也有挖矿奖励,只是比起前二者自动发放奖励,EOS出块者需要自行申领奖励,
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 system claimrewards producer111a
301762ms thread-0 main.cpp:429 create_action ] result: {"binargs":"60420857219de8ad"} arg: {"code":"eosio","action":"claimrewards","args":{"owner":"producer111a"}}
executed transaction: 4b7e9b1bec0f04f4d96aa4e61f9bc45516411cf6be3f82720e9c8cb6dfb7a162 104 bytes 6343 us
# eosio <= eosio::claimrewards {"owner":"producer111a"}
# eosio.token <= eosio.token::issue {"to":"eosio","quantity":"1855.4398 SYS","memo":"issue tokens for producer pay and savings"}
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"eosio.saving","quantity":"1484.3519 SYS","memo":"unallocated inflation"}
# eosio <= eosio.token::transfer {"from":"eosio","to":"eosio.saving","quantity":"1484.3519 SYS","memo":"unallocated inflation"}
# eosio.saving <= eosio.token::transfer {"from":"eosio","to":"eosio.saving","quantity":"1484.3519 SYS","memo":"unallocated inflation"}
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"eosio.bpay","quantity":"92.7719 SYS","memo":"fund per-block bucket"}
# eosio <= eosio.token::transfer {"from":"eosio","to":"eosio.bpay","quantity":"92.7719 SYS","memo":"fund per-block bucket"}
# eosio.bpay <= eosio.token::transfer {"from":"eosio","to":"eosio.bpay","quantity":"92.7719 SYS","memo":"fund per-block bucket"}
# eosio.token <= eosio.token::transfer {"from":"eosio","to":"eosio.vpay","quantity":"278.3160 SYS","memo":"fund per-vote bucket"}
# eosio <= eosio.token::transfer {"from":"eosio","to":"eosio.vpay","quantity":"278.3160 SYS","memo":"fund per-vote bucket"}
# eosio.vpay <= eosio.token::transfer {"from":"eosio","to":"eosio.vpay","quantity":"278.3160 SYS","memo":"fund per-vote bucket"}
# eosio.token <= eosio.token::transfer {"from":"eosio.bpay","to":"producer111a","quantity":"53.4400 SYS","memo":"producer block pay"}
# eosio.bpay <= eosio.token::transfer {"from":"eosio.bpay","to":"producer111a","quantity":"53.4400 SYS","memo":"producer block pay"}
# producer111a <= eosio.token::transfer {"from":"eosio.bpay","to":"producer111a","quantity":"53.4400 SYS","memo":"producer block pay"}
warning: transaction executed locally, but may not be confirmed by the network yet
从打印结果可以看到申领奖励的执行路径,这时候我们再来检查一下producer111a账户的余额,
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 get currency balance eosio.token producer111a
63.4400 SYS
刚刚发放的奖励53.4400 SYS已打入余额中,而之前的10 SYS是哪里来的?
脚本参数--max-unstaked,默认值为10,在股权账户被创建的时候,会读取这个参数的值,根据这个值来计算抵押创建账户消耗的资源,账户创建过程中,资源抵押的token是由eosio支付的,当账户创建完毕,资源被释放(即unstake),则会将10 SYS从eosio转账到账户中去。
投票过程说实在有点麻烦,因此有了代理投票的功能,代理投票分为两步:
我们通过命令将某个股权账户注册为一个代理,可接受小白的授权。
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 system regproxy useraaaaaaab
2212425ms thread-0 main.cpp:429 create_action ] result: {"binargs":"708c31c6187315d601"} arg: {"code":"eosio","action":"regproxy","args":{"proxy":"useraaaaaaab","isproxy":true}}
executed transaction: 4a8f2bad3a6f2e0d34d5ec1134e241f850e8a0c659cc65ce3cf4bedfaf28c97c 104 bytes 1216 us
# eosio <= eosio::regproxy {"proxy":"useraaaaaaab","isproxy":1}
warning: transaction executed locally, but may not be confirmed by the network yet
可以看到useraaaaaaab账户的isproxy项已置为1,成为代理。
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 system voteproducer proxy useraaaaaaaa useraaaaaaab
2402472ms thread-0 main.cpp:429 create_action ] result: {"binargs":"608c31c6187315d6708c31c6187315d600"} arg: {"code":"eosio","action":"voteproducer","args":{"voter":"useraaaaaaaa","proxy":"useraaaaaaab","producers":[]}}
executed transaction: c5501c7487d9ffcf6ce86b76f5c75d9fd68e22c84b376612d5266ea76199d37e 112 bytes 3062 us
# eosio <= eosio::voteproducer {"voter":"useraaaaaaaa","proxy":"useraaaaaaab","producers":[]}
# useraaaaaaab <= eosio::voteproducer {"voter":"useraaaaaaaa","proxy":"useraaaaaaab","producers":[]}
warning: transaction executed locally, but may not be confirmed by the network yet
我们成功将账户useraaaaaaaa的投票权代理给了代理账户useraaaaaaab。
由于每个账户给候选者只能投一次票,我们可以通过这个特性来验证代理投票。首先我们先通过get table查询三个候选者的票数,然后使用useraaaaaaab账户为producer111a投票,再次get table查询可以发现producer111a的票数升高了,此时再使用useraaaaaaaa账户为producer111a进行投票,操作成功,但get table去查询发现producer111a的票数不变,这说明useraaaaaaaa账户的票数已经通过代理账户useraaaaaaab成功代理投票。
当我们已经选举出来称职的出块者以后,出块者已经由原来的eosio变为众多出块者轮番出块,eosio变为接收块,随着启动时序接近尾声,eosio的作用越来越小,但它的权限仍是公开的密钥对,这是一件很有风险的事,所以综合考量,这一步骤,我们要改造eosio的权限。
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 push action eosio updateauth '{"account":"eosio","permission":"owner","parent":"","auth":{"threshold":1,"keys":[],"waits":[],"accounts":[{"weight":1,"permission":{"actor":"eosio.prods","permission":"active"}}]}}' -p eosio@owner
executed transaction: f738e289214b31b43255cd562bf12ac811f2c7b4cc0acc0b887a8ec7db603679 144 bytes 869 us
# eosio <= eosio::updateauth {"account":"eosio","permission":"owner","parent":"","auth":{"threshold":1,"keys":[],"accounts":[{"pe...
warning: transaction executed locally, but may not be confirmed by the network yet
通过为eosio账户更新permission,这里没有使用‘cleos set account permission ...’,是因为部署system合约以后,绝大部分的直接操作都失效了,所以转而使用system合约的push action eosio updateauth来更新eosio的permission为:
{
"account": "eosio",
"permission": "owner",
"parent": "",
"auth": {
"threshold": 1,
"keys": [],
"waits": [],
"accounts": [
{
"weight": 1,
"permission": {
"actor": "eosio.prods",
"permission": "active"
}
}
]
}
}
最终,我们检查eosio的owner权限为:
privileged: true
permissions:
owner 1: 1 eosio.prods@active,
然后对eosio.prods账户产生了好奇,那么我们就继续查看这个账户:
root@iZ2ze5wsiqz8cj0lqgf73tZ:~/lwb-work/eos/tutorials/bios-boot-tutorial/nodes# cleos --wallet-url http://localhost:6666 --url http://localhost:8000 get account eosio.prods
permissions:
owner 1:
active 2: 1 producer111a@active, 1 producer111b@active,
prod.major 2: 1 producer111a@active, 1 producer111b@active,
prod.minor 1: 1 producer111a@active, 1 producer111b@active,
memory:
quota: unlimited used: 2.594 KiB
net bandwidth:
used: unlimited
available: unlimited
limit: unlimited
cpu bandwidth:
used: unlimited
available: unlimited
limit: unlimited
可以发现这个账户eosio.prods已经完全被出块者(注意不是候选者,而是成功出块的节点账户)占据,并且有了prod.major和prod.minor两个自定义权限,这里不展开了。
这样一来,我们已经干掉了eosio的owner权限,同理,干掉eosio的active权限。
eosio账户的权限被resign以后,正像使用eosio.prods账户来resign eosio一样,我们使用eosio来resign 所有的系统账户eosio.*。
resign目标:eosio账户以及eosio.*账户的权限被最终下发到区块生产者账户。
到目前为止,我们已完成了所有启动阶段的操作的研究,可以看出这个启动流程有些麻烦,我们第一次去手动操作是为了理解每一步的具体含义和内容,而如果之后的生产阶段仍旧采取手动配置的方式无疑效率太低,因此上面反复提及的源码自带的脚本bios-boot-tutorial.py很好的解决了这个问题。前面的分析已基本覆盖脚本的内容,这里介绍上面未涵盖的三个步骤,这三个步骤是在以上内容的最后来执行的:
本文首先分为两大部分:第一部分介绍了手动启动一个源节点,全节点以及如何将这两个节点组网,并实现一些业务逻辑的设计,例如交易确认。第二部分,我们完整详细地分析了启动一个节点的所有必须动作序列(我们前面研究多签名也好,上面手动组网也好,碰到太多由于初始化 节点时缺乏必要步骤所导致的问题,在这种情况下,我决定系统地研究eos的启动序列)。首先重点介绍了股权账户的概念,其中在分配股权账户的策略上,我们也引入了帕累托分配模型;接着就是非常重要的出块者竞选的部分,包括如何注册,启动出块节点,投票,代理投票,成功出块,申领奖励一系列操作;最后,我们分析了eos的风险模型,将eosio账户以及其他eosio.*系统账户进行resign,也引出了resign之后system合约的多签名方式调用,对于eos的性能表现,也给出了压测方案,日志分析办法。