• 13. Single及PoW共识
    • 13.1. 介绍
    • 13.2. 算法流程
    • 13.3. 在超级链中使用Single或PoW共识
      • 13.3.1. 使用Single共识的创世块配置
      • 13.3.2. 使用PoW共识的创世块配置
    • 13.4. 关键技术

    13. Single及PoW共识

    13.1. 介绍

    Single以及PoW属于不同类型的区块链共识算法。其中,PoW(Proof Of Work,工作量证明)是通过解决一到特定的问题从而达成共识的区块链共识算法;而Single亦称为授权共识,在一个区块链网络中授权固定的address来记账本。Single一般在测试环境中使用,不适合大规模的应用环境。PoW适用于公有链应用场景。

    13.2. 算法流程

    Single共识

    • 对于矿工:Single是固定 address 周期性出块,因此在调用 CompeteMaster 的时候主要判断当前时间与上一次出块时间间隔是否达到一个周期;
    • 对于验证节点:验证节点除了密码学方面必要的验证之外,还会验证矿工与本地记录的矿工是否一致;

    Pow共识

    • 对于矿工:每次调用 CompeteMaster 都返回 true,表明每次调用 CompeteMaster 的结果都是矿工该出块了;
    • 对于验证节点:验证节点除了密码学方面必要的验证之外,还会验证区块的难度值是否符合要求;

    13.3. 在超级链中使用Single或PoW共识

    只需修改 data/config 中的创世块配置即可指定使用共识

    13.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. }

    13.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. }

    13.4. 关键技术

    Single共识的原理简单,不再赘述。

    PoW共识

    解决一道难题过程,执行流程如下:

    • step1 每隔一个周期判断是否接收到新的区块。若是,跳出解决难题流程,若不是,进行 step2
    • step2 判断当前计算难度值是否符合要求。若是,跳出难题解决流程,若不是难度值加1,继续 step1

    伪代码如下:

    1. // 在每次挖矿时,设置为true
    2. // StartPowMinning
    3. for {
    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 --> 被中断
    24. if !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 如果当前区块所在高度在下一次区块难度调整的周期范围内,直接复用前一个区块的难度值,跳出计算区块难度值过程;

    伪代码如下:

    1. func (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. }