GitHub地址 https://github.com/TTCECO/gttc
基于以太坊go-ethereum的DPOS实现
一 构建以太坊
1 git clone https://github.com/TTCECO/gttc.git
- 目录结构为~/github.com/TTCECO/gttc
2 在根目录make gttc(make all)
二.构建有多个节点的以太坊链
Before read this instruction, please make sure the gttc, bootnode
and puppeth already compiled and installed correctly in your server.
1 在目录 gttc/build/bin下创建节点目录
$ mkdir node1 node2
2 生成超级节点账号
$ gttc --datadir node1/ account new
$ gttc --datadir node2/ account new
- 输入密码后会显示生成的地址node1_address和node2_address
3 把账号信息写入文件来启动节点
$ echo 'node1_address' >> account.txt
$ echo 'node2_address' >> account.txt
$ echo 'password1' >> node1/password.txt
$ echo 'password2' >> node2/password.txt
4 通过puppeth来构建创世配置文件
$ puppeth
+-----------------------------------------------------------+
| Welcome to puppeth, your private network manager |
| |
| This tool lets you create a new Ethereum network down to |
| the genesis block, bootnodes, miners and ethstats servers |
| without the hassle that it would normally entail. |
| |
| Puppeth uses SSH to dial in to remote servers, and builds |
| its network components out of Docker containers using the |
| docker-compose toolset. |
+-----------------------------------------------------------+
Please specify a network name to administer (no spaces or hyphens, please)
> devnet
Sweet, you can set this via --network=devnet next time!
INFO [06-04|12:33:34] Administering Ethereum network name=devnet
WARN [06-04|12:33:34] No previous configurations found path=/Users/tataufo/.puppeth/devnet
What would you like to do? (default = stats)
1. Show network stats
2. Configure new genesis
3. Track new remote server
4. Deploy network components
> 2
Which consensus engine to use? (default = alien)
1. Ethash - proof-of-work
2. Clique - proof-of-authority
3. Alien - delegated-proof-of-stake
> 3
How many seconds should blocks take? (default = 3)
> 4
How many blocks create for one epoch? (default = 30000)
> 30
What is the max number of signers? (default = 21)
> 3
What is the minimize balance for valid voter ? (default = 10000TTC)
> 100
How many minutes delay to create first block ? (default = 5 minutes)
> 5
Which accounts are vote by themselves to seal the block?(least one, those accounts will be auto pre-funded)
(The follow two address can be found in account.txt)
> 0xfa846876ef5ed3826e483303f42d987a66af8e15
> 0x62739566c666df9a057d7e7c92898511d4e64c07
> 0x
Which accounts should be pre-funded? (advisable at least one)
> 0x
Specify your chain/network ID if you want an explicit one (default = random)
>
INFO [06-04|12:35:27] Configured new genesis block
What would you like to do? (default = stats)
1. Show network stats
2. Manage existing genesis
3. Track new remote server
4. Deploy network components
> 2
1. Modify existing fork rules
2. Export genesis configuration
3. Remove genesis configuration
> 2
Which file to save the genesis into? (default = devnet.json)
> genesis.json
INFO [06-04|12:35:45] Exported existing genesis block
What would you like to do? (default = stats)
1. Show network stats
2. Manage existing genesis
3. Track new remote server
4. Deploy network components
> ^C
5 初始化节点数据
$ gttc --datadir node1/ init genesis.json
$ gttc --datadir node2/ init genesis.json
if old data in node folder, please remove them before initialize.
6 生成bootnode
$ bootnode -genkey boot.key
bootnode简化了Ethereum客户端实现,它只参与网络节点发现协议,但不运行任何高级应用程序协议。它可以用作轻量级的引导节点,以帮助在私有网络中找到对等点。
总而言之,就是一个用于节点发现或者说节点引导的轻量节点,方便联盟链的搭建~
7 运行bootnode
$ bootnode -nodekey boot.key -verbosity 9 -addr 127.0.0.1:30310
上面命令运行完,会打印类似下面的log
enode://20940eac58b9e615706ea3c357c409aecbb44998d1388db49a8df61e727f92029019708b2ad69467f94eef9a49b5d4ffb2cc1e71bb06addeb134fe8bdbc62153@127.0.0.1:30310
encode后面的这么一长串东西,就是这个节点的ID信息,下面启动geth节点的时候要指定连接这个bootnode~
8 运行节点1和节点2
- 下面命令中 unlock后面的地址为之前写入account.txt文件中的地址,以挖矿的形式启动
- enode地址为上面第7步生成的encode。 (也可以不用第七步生成的encode节点地址作为下面的启动参数,后续直接将节点进行关联即可(第九步))
- networkid 为genesis.json中的 ChinaId
$ gttc --datadir node1/ --syncmode 'full' --port 30311 --rpc --rpcaddr 'localhost' --rpcport 8501 --rpcapi 'personal,db,eth,net,web3,txpool,miner,alien,admin' --bootnodes 'enode://20940eac58b9e615706ea3c357c409aecbb44998d1388db49a8df61e727f92029019708b2ad69467f94eef9a49b5d4ffb2cc1e71bb06addeb134fe8bdbc62153@127.0.0.1:30310' --networkid 1014 --gasprice '1' -unlock 'fa846876ef5ed3826e483303f42d987a66af8e15' --password node1/password.txt --mine
后台启动方式:
nohup ./gwsc --datadir node1/ --syncmode 'full' --port 30311 --rpc --rpcaddr '127.0.0.1' --rpcport 8501 --rpcapi 'personal,db,eth,net,web3,txpool,miner,alien,admin' --networkid 1001 --gasprice '1' -unlock '6672c4a5d01751e92f86e5d0393feceb1513481b' --password node1/password.txt --mine >/dev/null 2>log &
$ gttc --datadir node2/ --syncmode 'full' --port 30312 --rpc --rpcaddr 'localhost' --rpcport 8502 --rpcapi 'personal,db,eth,net,web3,txpool,miner,alien,admin' --bootnodes 'enode://20940eac58b9e615706ea3c357c409aecbb44998d1388db49a8df61e727f92029019708b2ad69467f94eef9a49b5d4ffb2cc1e71bb06addeb134fe8bdbc62153@127.0.0.1:30310' --networkid 1014 --gasprice '1' -unlock '62739566c666df9a057d7e7c92898511d4e64c07' --password node2/password.txt --mine
不用第七步生成的encode节点地址作为下面的启动参数
gttc --datadir node1/ --syncmode 'full' --port 30311 --rpc --rpcaddr 'localhost' --rpcport 8501 --rpcapi 'personal,db,eth,net,web3,txpool,miner,alien,admin' --networkid 1001 --gasprice '1' -unlock 'e363576de44cc5ca22fd96b49daa0724a3d4f6e4' --password node1/password.txt --mine
9 节点互联
1 进入geth JavaScript控制台
./gttc attach rpc:http://127.0.0.1:8501
./gttc attach rpc:http://127.0.0.1:8502
2 查看节点是否互联
admin.peers
[]
返回数据为[] 说明节点之间没有互相发现。
3 设置节点互联
admin.nodeInfo
记录下enode
"enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[::]:30311"
"enode://6ab4f74058b9c1e43d2d0c6f55f538ea7f2f366dd9f8f560024f14603333f017d3404b9c9711538289fa76504fecf33cf0e36cce7b0414604f673abe93012413@[::]:30312"
4 进入node1 geth控制台
#添加节点2的监视器
admin.addPeer("enode://7f2f1a5818b4bb7e756036ab08834386534807bbf5c5a305ddcbefa1ff9ea99028feb00cb78322ac39340501d5b7c6147e169aadbb028daf20f8d73dbdfea98e@[::]:30311")
addPeer的时候注意如果节点不是在同一台服务器上,需要把@后面[::]换成当前节点所在机器的ip地址。
- 进入node2 geth控制台 添加节点1的监视器
三 进行投票
投票操作也属于一笔交易,是从投票人给候选者进行投票。投票人的金额必须大于投票最小金额,否则投票无效。参数为:genesis.json中的minVoterBalance
- ufo : prefix of custom data
- 1 : custom info version
- event : category of info (event, oplog …)
- vote : vote event
The sample vote in console is like this
personal.sendTransaction({from:"t07544bf9c90d175da395b8d08fcaf34da0a3e0688",to:"t0aafeaaf6111762fea733ff7b4c8b59ac69316385",value:0,data:web3.toHex("ufo:1:event:vote")})
从A地址向B地址转移数量为0的代币,代表A投*票给B地址,而票数的计算则是A地址当前拥有货币的数量。
四 生成新的超级节点地址进行挖矿(方式跟node1和node2类似)
1 创建超级节点账户
./gttc --datadir super_node/ account new
- 输入密码后会显示生成的地址暂且把地址记为:super_node_address
2 把密码写入super_node文件下
$ echo 'password' >> super_node/password.txt
3 初始化超级节点
$ gttc --datadir super_node/ init genesis.json
4 Run bootnode
$ bootnode -nodekey boot.key -verbosity 9 -addr 127.0.0.1:30310
- boot.key使用之前运行node1生成的就可以了
5 运行超级节点进行挖矿(同理也可以不用第4步生成的encode地址参数)
$ gttc --datadir super_node/ --syncmode 'full' --port 30313 --rpc --rpcaddr 'localhost' --rpcport 8503 --rpcapi 'personal,db,eth,net,web3,txpool,miner,net' --bootnodes 'enode://20940eac58b9e615706ea3c357c409aecbb44998d1388db49a8df61e727f92029019708b2ad69467f94eef9a49b5d4ffb2cc1e71bb06addeb134fe8bdbc62153@127.0.0.1:30310' --networkid 1014 --gasprice '1' -unlock '62739566c666df9a057d7e7c92898511d4e64c07' --password super_node/password.txt --mine
6 节点互联
- 进入node1的geth控制台 添加super_node的监视器(addPeer) 。node2 同理
- 进入super_node的geth控制台 添加node1和node2的监视器。进行节点之间互联。
- 超级节点地址需要有人投票并且成为有效见证人后才能挖矿。
用户投票是有最小投票金额限制的,否则无效。
参数为:genesis.json中的minVoterBalance
参数:
alien.getSnapshotAtNumber(48)
{
confirmedNumber: 44,
confirms: {
41: ["t08feba63259ef7e79da13246dc20ff79fe446a478", "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1", "t07c352585cd7549bfbc0a7e88bf820fa574174598"],
42: ["t07c352585cd7549bfbc0a7e88bf820fa574174598", "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1", "t08feba63259ef7e79da13246dc20ff79fe446a478"],
43: ["t0cc9c08721c7d8a792e238e80f6c20cb066e919a1", "t07c352585cd7549bfbc0a7e88bf820fa574174598", "t08feba63259ef7e79da13246dc20ff79fe446a478"],
44: ["t07c352585cd7549bfbc0a7e88bf820fa574174598", "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1", "t08feba63259ef7e79da13246dc20ff79fe446a478"],
45: ["t08feba63259ef7e79da13246dc20ff79fe446a478", "t07c352585cd7549bfbc0a7e88bf820fa574174598", "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1"],
46: ["t08feba63259ef7e79da13246dc20ff79fe446a478", "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1", "t07c352585cd7549bfbc0a7e88bf820fa574174598"]
},
hash: "t027009a5d7480cb2802e45b9d0ef2547ce7ad5e720d9281b0d628f314998a6416",
headerTime: 1533341653,
historyHash: ["t0fb741ff569db0a9277111285d157a0412c4dc6e30509834e7e87ce84755db336", "t0855589fcf6fb8157b29df69cba0393737a1c15a7427666f5e84b34c8a5680523", "t07b37e235e0686c8defaa0068b0508fe55921bd5e27bf0f1ee3e6909184dc74f4", "t0664bb4e6889acfd8bd4c277ba109cd9fbaf1366ce8472761b050451c01cf1a04", "t0d3d64b715e8568be7d86a82dbfd9a801013132d4229d96963f3869b584426f7a", "t068a1241b27c209ab584155a670a0473f29ae302eb2fe47d1a75b1eea45898ab2", "t04c8e6e969c950810c2acf128866f2aa416dc56b1ef2987281de4812622ebb44f", "t03c7047ba4574bf1e49c9e068d82e1b2c3f59e958658d897f4836db23f6ffcc3c", "t0c05b07cb1c00be507742bd9de8702a42d8cf77f43c566f52196f5b0bc47d084c", "t0d1f96774a2feb4c21add4aeb14ac0d92d5357b1cdc53cead0ed9df459ce308ac", "t0c95bd9b4fd9f9d4ca5a38d2433f6f9d6f48027098316d8b9d95fa1436639338d", "t0300addbe720652487d69c62b37d7281298200b2c7c68170a24dad8503c73940e", "t06ede482139d9a8f5a7bc385b16c5d3c59ad11715091ad1609aa451f605fcc633", "t027009a5d7480cb2802e45b9d0ef2547ce7ad5e720d9281b0d628f314998a6416"],
loopStartTime: 1533341639,
number: 48,
period: 4,
punished: {
t07c352585cd7549bfbc0a7e88bf820fa574174598: 880,
t08feba63259ef7e79da13246dc20ff79fe446a478: 950,
t0cc9c08721c7d8a792e238e80f6c20cb066e919a1: 1370
},
signers: ["t07c352585cd7549bfbc0a7e88bf820fa574174598", "t08feba63259ef7e79da13246dc20ff79fe446a478", "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1", "t07c352585cd7549bfbc0a7e88bf820fa574174598", "t08feba63259ef7e79da13246dc20ff79fe446a478", "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1", "t07c352585cd7549bfbc0a7e88bf820fa574174598"],
tally: {
t07c352585cd7549bfbc0a7e88bf820fa574174598: 9.046256971665328e+74,
t08feba63259ef7e79da13246dc20ff79fe446a478: 9.046256971665328e+74,
t0cc9c08721c7d8a792e238e80f6c20cb066e919a1: 9.046256971665328e+74
},
voters: {
t07c352585cd7549bfbc0a7e88bf820fa574174598: 0,
t08feba63259ef7e79da13246dc20ff79fe446a478: 0,
t0cc9c08721c7d8a792e238e80f6c20cb066e919a1: 0
},
votes: {
t07c352585cd7549bfbc0a7e88bf820fa574174598: {
Candidate: "t07c352585cd7549bfbc0a7e88bf820fa574174598",
Stake: 9.046256971665328e+74,
Voter: "t07c352585cd7549bfbc0a7e88bf820fa574174598"
},
t08feba63259ef7e79da13246dc20ff79fe446a478: {
Candidate: "t08feba63259ef7e79da13246dc20ff79fe446a478",
Stake: 9.046256971665328e+74,
Voter: "t08feba63259ef7e79da13246dc20ff79fe446a478"
},
t0cc9c08721c7d8a792e238e80f6c20cb066e919a1: {
Candidate: "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1",
Stake: 9.046256971665328e+74,
Voter: "t0cc9c08721c7d8a792e238e80f6c20cb066e919a1"
}
}
}
- signers : Signers queue in current loop (每一轮的超级节点队列)
- loopStartTime: Start Time of the current loop, used to calculate the right miner(signer) by time(每一轮的开始时间)
- 重新选取超级节点时间:当出块数=节点数*轮数时,会重重选一次超级节点。
轮数参数:consensus/alien/alien.go文件中的defauleLoopCntRecalculateSigners = 10(默认为10轮) - period: 4 每4秒出一个块
> 投票规则: 假如超级节点为3个。则每出30个块(10轮)signers中的节点会重新选取3个超级节点。每出三个块(一轮12秒),loopStartTime会变一次,同时signers的顺序也会变一次。但是不换出块节点,只有当出了30个块则更换节点
> 超级节点挖矿的收益
go-ethereum consensus/alien/alien.go中方法accumulateRewards
主要代码如下:
secondsPerYear = 365 * 24 * 3600
//config.Alien.Period:为多少秒出一次块
blockNumPerYear := secondsPerYear / config.Alien.Period
initSignerBlockReward := new(big.Int).Div(totalBlockReward, big.NewInt(int64(2*blockNumPerYear)))
yearCount := header.Number.Uint64() / blockNumPerYear
//根据块数计算当前年数 再进行右位移运算 得到当前挖出一块的总收益
blockReward := new(big.Int).Rsh(initSignerBlockReward, uint(yearCount))
minerReward := new(big.Int).Set(blockReward)
minerReward.Mul(minerReward, new(big.Int).SetUint64(snap.MinerReward))
//minerReward为挖矿人的收益。
minerReward.Div(minerReward, big.NewInt(1000)) // cause the reward is calculate by cnt per thousand
//votersReward 投票人获得的返佣收益(投票人平分)
votersReward := blockReward.Sub(blockReward, minerReward)
blockReward = minerReward + votersReward。