14. Single及PoW共识¶
14.1. 介绍¶
Single以及PoW属于不同类型的区块链共识算法。其中,PoW(Proof Of Work,工作量证明)是通过解决一道特定的问题从而达成共识的区块链共识算法;而Single亦称为授权共识,在一个区块链网络中授权固定的address来记账本。Single一般在测试环境中使用,不适合大规模的应用环境。PoW适用于公有链应用场景。
14.2. 算法流程¶
Single共识
对于矿工:Single是固定 address 周期性出块,因此在调用 CompeteMaster 的时候主要判断当前时间与上一次出块时间间隔是否达到一个周期;
对于验证节点:验证节点除了密码学方面必要的验证之外,还会验证矿工与本地记录的矿工是否一致;
Pow共识
对于矿工:每次调用 CompeteMaster 都返回 true,表明每次调用 CompeteMaster 的结果都是矿工该出块了;
对于验证节点:验证节点除了密码学方面必要的验证之外,还会验证区块的难度值是否符合要求;
14.3. 在 XuperChain 中使用Single或PoW共识¶
只需修改 data/config 中的创世块配置即可指定使用共识
14.3.1. 使用Single共识的创世块配置¶
1{
2 "version" : "1",
3 "consensus" : {
4 # 共识算法类型
5 "type" : "single",
6 # 指定出块的address
7 "miner" : "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN"
8 },
9 # 预分配
10 "predistribution":[
11 {
12 "address" : "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN",
13 "quota" : "100000000000000000000"
14 }
15 ],
16 # 区块大小限制
17 "maxblocksize" : "128",
18 # 出块周期
19 "period" : "3000",
20 # 出块奖励
21 "award" : "428100000000",
22 # 精度
23 "decimals" : "8",
24 # 出块奖励衰减系数
25 "award_decay": {
26 "height_gap": 31536000,
27 "ratio": 1
28 },
29 # 系统权限相关配置
30 "permission": {
31 "CreateAccount" : { "rule" : "NULL", "acl": {}},
32 "SetAccountAcl": { "rule" : "NULL", "acl": {}},
33 "SetContractMethodAcl": { "rule" : "NULL", "acl": {}}
34 }
35}
14.3.2. 使用PoW共识的创世块配置¶
1{
2 "version" : "1",
3 # 预分配
4 "predistribution":[
5 {
6 "address" : "Y4TmpfV4pvhYT5W17J7TqHSLo6cqq23x3",
7 "quota" : "1000000000000000"
8 }
9 ],
10 "maxblocksize" : "128",
11 "award" : "1000000",
12 "decimals" : "8",
13 "award_decay": {
14 "height_gap": 31536000,
15 "ratio": 0.5
16 },
17 "genesis_consensus":{
18 "name": "pow",
19 "config": {
20 # 默认难度值
21 "defaultTarget": "19",
22 # 每隔10个区块做一次难度调整
23 "adjustHeightGap": "10",
24 "expectedPeriod": "15",
25 "maxTarget": "22"
26 }
27 }
28}
14.4. 关键技术¶
Single共识的原理简单,不再赘述。
PoW共识
解决一道难题过程,执行流程如下:
step1 每隔一个周期判断是否接收到新的区块。若是,跳出解决难题流程,若不是,进行 step2 ;
step2 判断当前计算难度值是否符合要求。若是,跳出难题解决流程,若不是难度值加1,继续 step1 ;
伪代码如下:
1// 在每次挖矿时,设置为true
2// StartPowMinning
3for {
4 // 每隔round次数,判断是否接收到新的区块,避免与网络其他节点不同步
5 if gussCount % round == 0 && !l.IsEnablePowMinning() {
6 break
7 }
8 // 判断当前计算难度值是否符合要求
9 if valid = IsProofed(block.Blockid, targetBits); !valid {
10 guessNonce += 1
11 block.Nonce = guessNonce
12 block.Blockid, err = MakeBlockID(block)
13 if err != nil {
14 return nil, err
15 }
16 guessCount++
17 continue
18 }
19 break
20}
21// valid为false说明还没挖到块
22// l.IsEnablePowMinning() == true --> 自己挖出块
23// l.IsEnablePowMinning() == false --> 被中断
24if !valid && !l.IsEnablePowMinning() {
25 l.xlog.Debug("I have been interrupted from a remote node, because it has a higher block")
26 return nil, ErrMinerInterrupt
27}
计算当前区块难度值过程,执行流程如下:
step1 判断当前区块所在高度是否比较小。若是,直接复用默认的难度值,跳出计算区块难度值过程,若不是,继续 step2 ;
step2 获取当前区块的前一个区块的难度值;
step3 判断当前区块是否在下一个难度调整周期范围内。若是,继续 step4 ;若不是,继续 step5 ;
step4 获取当前区块的前一个区块的难度值,并计算经历N个区块,预期/实际消耗的时间,并根据公式调整难度值,跳出计算区块难度值过程;
step5 如果当前区块所在高度在下一次区块难度调整的周期范围内,直接复用前一个区块的难度值,跳出计算区块难度值过程;
伪代码如下:
1func (pc *PowConsensus) calDifficulty(curBlock *pb.InternalBlock) int32 {
2 // 如果当前区块所在高度比较小,直接复用默认的难度值
3 if curBlock.Height <= int64(pc.config.adjustHeightGap) {
4 return pc.config.defaultTarget
5 }
6 height := curBlock.Height
7 preBlock, err := pc.getPrevBlock(curBlock, 1)
8 if err != nil {
9 pc.log.Warn("query prev block failed", "err", err, "height", height-1)
10 return pc.config.defaultTarget
11 }
12 // 获取当前区块前一个区块的难度值
13 prevTargetBits := pc.getTargetBitsFromBlock(preBlock)
14 // 如果当前区块所在高度恰好是难度值调整所在的高度周期
15 if height%int64(pc.config.adjustHeightGap) == 0 {
16 farBlock, err := pc.getPrevBlock(curBlock, pc.config.adjustHeightGap)
17 if err != nil {
18 pc.log.Warn("query far block failed", "err", err, "height", height-int64(pc.config.adjustHeightGap))
19 return pc.config.defaultTarget
20 }
21 // 经历N个区块,预期消耗的时间
22 expectedTimeSpan := pc.config.expectedPeriod * (pc.config.adjustHeightGap - 1)
23 // 经历N个区块,实际消耗的时间
24 actualTimeSpan := int32((preBlock.Timestamp - farBlock.Timestamp) / 1e9)
25 pc.log.Info("timespan diff", "expectedTimeSpan", expectedTimeSpan, "actualTimeSpan", actualTimeSpan)
26 //at most adjust two bits, left or right direction
27 // 避免难度值调整太快,防止恶意攻击
28 if actualTimeSpan < expectedTimeSpan/4 {
29 actualTimeSpan = expectedTimeSpan / 4
30 }
31 if actualTimeSpan > expectedTimeSpan*4 {
32 actualTimeSpan = expectedTimeSpan * 4
33 }
34 difficulty := big.NewInt(1)
35 difficulty.Lsh(difficulty, uint(prevTargetBits))
36 difficulty.Mul(difficulty, big.NewInt(int64(expectedTimeSpan)))
37 difficulty.Div(difficulty, big.NewInt(int64(actualTimeSpan)))
38 newTargetBits := int32(difficulty.BitLen() - 1)
39 if newTargetBits > pc.config.maxTarget {
40 pc.log.Info("retarget", "newTargetBits", newTargetBits)
41 newTargetBits = pc.config.maxTarget
42 }
43 pc.log.Info("adjust targetBits", "height", height, "targetBits", newTargetBits, "prevTargetBits", prevTargetBits)
44 return newTargetBits
45 } else {
46 // 如果当前区块所在高度在下一次区块难度调整的周期范围内,直接复用前一个区块的难度值
47 pc.log.Info("prev targetBits", "prevTargetBits", prevTargetBits)
48 return prevTargetBits
49 }
50}