DBFT共识简单原理

作者: ttblack | 来源:发表于2019-08-15 17:27 被阅读24次

    1. NEO的共识过程,如何出新区块

    DBFT算法的大致原理是这样的。参与记账的是超级节点,普通节点可以看到共识过程并同步账本信息,但是不参与记账。N个超级节点分为1个议长和n-1个议员,议长会轮流当选,每次记账时,先由议长发起区块提案,也就是记账的区块内容。一旦有2/3以上的记账节点同意了这个提案,那么这个提案就会成为最终发布的区块。

    过程如下:

    1.Neo持有者投票选择共识节点,也就是议员。

    2.系统会根据议员的地址哈希做个排序,第一位为议长。以后轮值

    3.议长从交易池中拿出交易打包,并根据当前投票情况计算下一轮的共识节点,这一步打包出的是提案块

    4.议长打包出提案块后,发给所有议员,议员接收到提案块后,会对提案块进行验证。

    5.议员验证通过后,对提案块签名,并广播出去。

    6.如果有2/3的议员对该提案签名通过后,代表这个提案块通过。则出正式块.

    我们开始阅读代码

    1. fill Prepare消息

      private void Fill()
            {
                IEnumerable<Transaction> memoryPoolTransactions = Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions();
                foreach (IPolicyPlugin plugin in Plugin.Policies)
                    memoryPoolTransactions = plugin.FilterForBlock(memoryPoolTransactions);
                List<Transaction> transactions = memoryPoolTransactions.ToList();
                TransactionHashes = transactions.Select(p => p.Hash).ToArray();
                Transactions = transactions.ToDictionary(p => p.Hash);
                NextConsensus = Blockchain.GetConsensusAddress(Snapshot.GetValidators().ToArray());
                Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestamp(), this.PrevHeader().Timestamp + 1);
            }
    
      public ConsensusPayload MakePrepareRequest()
            {
                Fill();
                return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest
                {
                    Timestamp = Timestamp,
                    Nonce = Nonce,
                    NextConsensus = NextConsensus,
                    TransactionHashes = TransactionHashes
                });
            }
    

    这段代码就是从内存池获取排序过的交易 ,并计算下一个出块的共识节点。设置时间戳.打包的交易hash列表.

    2.Response消息

    ~~~省略
    
    if (VerifyRequest())
     {
                        // if we are the primary for this view, but acting as a backup because we recovered our own
                        // previously sent prepare request, then we don't want to send a prepare response.
                        if (context.IsPrimary() || context.WatchOnly()) return true;
    
                        // Timeout extension due to prepare response sent
                        // around 2*15/M=30.0/5 ~ 40% block time (for M=5)
                        ExtendTimerByFactor(2);
    
                        Log($"send prepare response");
                        localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakePrepareResponse() });
    ~~~代码省略
     private bool VerifyRequest()
    {
                if (!Blockchain.GetConsensusAddress(context.Snapshot.GetValidators().ToArray()).Equals(context.NextConsensus))
                    return false;
                return true;
    }
     public ConsensusPayload MakePrepareResponse()
     {
                return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareResponse
                {
                    PreparationHash = PreparationPayloads[PrimaryIndex].Hash
                });
     }
    

    以上代码就是在收到Prepare消息后,对Parepare消息进行验证(VerifyRequest),验证通过后,Make Response消息,并签名,参数为Prepare消息的hash。

    3.Commit 消息

     private void CheckPreparations()
            {
                if (context.PreparationPayloads.Count(p => p != null) >= context.M() && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p)))
                {
                    ConsensusPayload payload = context.MakeCommit();
                    Log($"send commit");
                    context.Save();
                    localNode.Tell(new LocalNode.SendDirectly { Inventory = payload });
                    // Set timer, so we will resend the commit in case of a networking issue
                    ChangeTimer(TimeSpan.FromSeconds(Blockchain.SecondsPerBlock));
                    CheckCommits();
                }
            }
    
      public ConsensusPayload MakeCommit()
            {
                return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit
                {
                    Signature = MakeHeader()?.Sign(keyPair)
                }));
            }
    

    当一个节点收到超过2/3Response消息时,进 入Commit阶段。并广播Commit消息,参数为提案块的签名。

    4.出新块

    private void OnCommitReceived(ConsensusPayload payload, Commit commit)
            {
                ref ConsensusPayload existingCommitPayload = ref context.CommitPayloads[payload.ValidatorIndex];
                if (existingCommitPayload != null)
                {
                    if (existingCommitPayload.Hash != payload.Hash)
                        Log($"{nameof(OnCommitReceived)}: different commit from validator! height={payload.BlockIndex} index={payload.ValidatorIndex} view={commit.ViewNumber} existingView={existingCommitPayload.ConsensusMessage.ViewNumber}", LogLevel.Warning);
                    return;
                }
    
                // Timeout extension: commit has been received with success
                // around 4*15s/M=60.0s/5=12.0s ~ 80% block time (for M=5)
                ExtendTimerByFactor(4);
    
                if (commit.ViewNumber == context.ViewNumber)
                {
                    Log($"{nameof(OnCommitReceived)}: height={payload.BlockIndex} view={commit.ViewNumber} index={payload.ValidatorIndex} nc={context.CountCommitted()} nf={context.CountFailed()}");
    
                    byte[] hashData = context.MakeHeader()?.GetHashData();
                    if (hashData == null)
                    {
                        existingCommitPayload = payload;
                    }
                    else if (Crypto.Default.VerifySignature(hashData, commit.Signature,
                        context.Validators[payload.ValidatorIndex].EncodePoint(false)))
                    {
                        existingCommitPayload = payload;
                        CheckCommits();
                    }
                    return;
                }
                // Receiving commit from another view
                Log($"{nameof(OnCommitReceived)}: record commit for different view={commit.ViewNumber} index={payload.ValidatorIndex} height={payload.BlockIndex}");
                existingCommitPayload = payload;
            }
    
       private void CheckCommits()
            {
                if (context.CommitPayloads.Count(p => p?.ConsensusMessage.ViewNumber == context.ViewNumber) >= context.M() && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p)))
                {
                    Block block = context.CreateBlock();
                    Log($"relay block: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}");
                    localNode.Tell(new LocalNode.Relay { Inventory = block });
                }
            }
    

    以上代码就是当一个节点收到超过2/3的Commit消息,并验证通过后,直接出新块。并广播.

    总结下整个过程:

    1.议长从交易池取出交易组装提案块,发出Parepare消息.
    2.议员收到提案块后,对 其进行 校验。校验通过,发出Response消息,表示议长的这个提案通过.
    3.当一个 共识节点收到超过2/3的Response消息时,进入Commit阶段。广播Commit消息,附带对 提案块的签名
    4.当一个共识节点收到超过2/3的Commit消息时。表示议长这个提案通过。则直接出新块.并广播


    2. NEO的投票规则,如何确定共识节点数,和具体的共识节点

    1.Neo持有者发一个StateTx交易进行投票,和申请成为共识节点,这些投票情况会被记录下来

    2.议长根据这些投票情况确定节点个数N。N的确定方式是有一个公式的,简单的描述是去掉过大票数和过小票数的节点,取中间投票数的中间值。这样做的目的是防止有人作恶。具体算法,拿到当前累加的票数/总票数得到所有节点的概率分布图,然后选择概率在0.25到0.75之间的期望值E,最后和备用节点个数相比取最大值。做为共识节点个数N.

    3.选取投票排名前N位,做为这些投票的共识节点.

    相关文章

      网友评论

        本文标题:DBFT共识简单原理

        本文链接:https://www.haomeiwen.com/subject/ggigfctx.html