6.6.1. 质押规则
普通节点通过质押权益成为候选人,具有如下的规则:
成为候选人的节点自身需要至少抵押该stake.minSelfDelegation数量的权益
stake.minSelfDelegation为链启动时配置的参数,候选人最少抵押的权益数量
任何人/组织都可以将权益委托质押给某个候选人,候选人质押的权益数量越多,被选为验证者的几率越大
所有质押的权益会被一直冻结,直到用户退出质押且在一段时间后stake.completionUnbondingEpochNum,所冻结的质押资产才会退还给用户
stake.completionUnbondingEpochNum为链启动时配置的参数,用户退出质押后,间隔几个世代再将用户的资产退还到相应的账户
用户的质押权益会转入stake合约的系统账户地址中,该系统账户地址是由合约名通过算法生成的
stakeAddress = base58(sha256(stakeContractName))
6.6.2. 验证人选举规则
每个世代会从候选人中选择一批验证者进行共识,为了在参考候选人质押资金的同时,也可以尽可能维护系统的公平性,设计了如下概率性选择算法,使所有候选人都有成为验证者的可能:
候选人的数量为M,选举的验证者集合的数量为N;依据候选人质押资金将其分为两个集合,质押资金topN的候选人为优先级集合,其余为普通集合
从优先级集合中选举N/2的节点作为验证者,剩余未被选中节点合并至普通集合,参与剩余的N/2个验证者的选举
6.6.3. 接口
type DPoS interface {
CreateDPoSRWSet(preBlkHash []byte, proposedBlock *consensuspb.ProposalBlock) error
VerifyConsensusArgs(block *common.Block, blockTxRwSet map[string]*common.TxRWSet) error
GetValidators() ([]string, error)
}
CreateDPoSRWSet: 依据节点提案的区块和链上数据,生成DPoS共识的读写集,并将读写集添加至区块中
VerifyConsensusArgs: 验证区块中包含的DPoS读写集是否正确
GetValidators: 获取当前世代的验证者
6.6.4. 系统合约
DPoS共识使用了两个系统合约来分别处理、存储一些状态;使用ERC20系统合约,存储链上所有用户的资产,并处理权益的转移、增发等逻辑;使用Stake系统合约,存储共识中的质押、退出质押、世代(每个世代包含验证者集合、下一个世代的创建高度)等信息,并处理用户的质押、退出质押、关联NodeID逻辑;
注意:关联NodeID操作的原因,NodeID是由节点证书内包含的公钥生成的,用于表示节点在链上的身份,且验证者生成的区块是用该证书对应的私钥进行签名的,因此该私钥需要放置在运行节点的服务器上;为了保证用户质押资金的安全,使用了另一套证书来标示用户的身份,用户的资金存储在该证书内公钥生成的地址上,该证书可以离线存储,以保证资金的安全。
因此,节点质押资金成为候选人时,需要先在链上关联用户质押地址对应的NodeID。
6.6.5. 配置参数
consensus:
# 共识类型(0-SOLO,1-TBFT,3-HOTSTUFF,4-RAFT,5-DPoS,10-POW)
type: 5
dpos_config:
#ERC20合约配置
- key: erc20.total
value: "1250000000000000000000000"
- key: erc20.owner
value: "4WUXfiUpLkx7meaNu8TNS5rNM7YtZk6fkNWXihc54PbM"
- key: erc20.decimals
value: "18"
- key: erc20.account:SYSTEM_CONTRACT_DPOS_STAKE
value: "1000000000000000000000000"
- key: erc20.account:4QUXfiUpNmj7meaNu8TNS5rNM7YtZk6fkNWXihc589kN
value: "250000000000000000000000"
#Stake合约配置
- key: stake.minSelfDelegation
value: "25000000000000000000000"
- key: stake.epochValidatorNum
value: "4"
- key: stake.epochBlockNum
value: "10"
- key: stake.completionUnbondingEpochNum
value: "1"
- key: stake.candidate:4WUXfiUpLkx7meaNu8TNS5rNM7YtZk6fkNWXihc54PbM
value: "250000000000000000000000"
......
- key: stake.nodeID:4WUXfiUpLkx7meaNu8TNS5rNM7YtZk6fkNWXihc54PbM
value: "QmRmTtfm7w5KwVAJALLU23y9TyiCNTUiqVbcErfBqhenrh"
......
erc20.total: ERC20合约发行权益的总量
erc20.owner: ERC20合约的管理员,拥有增发权益的权利
erc20.decimals: ERC20合约中权益的精度
erc20.account:: ERC20合约中创世块时每个账户所拥有的资金
SYSTEM_CONTRACT_DPOS_STAKE: 由于stake合约地址是由合约名通过sha256计算后base58编码得到的,为固定值;但base58的值可读、可写不便,容易配置出错,因此,对于stake合约的地址配置为合约名;链启动后可以通过该命令查询stake合约地址
其它的账户的初始资金配置,使用用户的地址进行配置
stake.minSelfDelegation: 候选人自身最少质押的权益数量
stake.epochValidatorNum: 每个世代验证者的数量
stake.epochBlockNum : 每个世代的区块数量
stake.completionUnbondingEpochNum:用户退出质押后间隔几个世代将资金退还给用户
stake.candidate:: 创世块时每个候选人质押的资金
stake.nodeID:创世块时每个候选人关联的NodeID