DBFT共识简单原理

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 memoryPoolTransactions = Blockchain.Singleton.MemPool.GetSortedVerifiedTransactions();
            foreach (IPolicyPlugin plugin in Plugin.Policies)
                memoryPoolTransactions = plugin.FilterForBlock(memoryPoolTransactions);
            List 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共识简单原理)