DBFT共識(shí)簡(jiǎn)單原理

1. NEO的共識(shí)過程,如何出新區(qū)塊

DBFT算法的大致原理是這樣的。參與記賬的是超級(jí)節(jié)點(diǎn),普通節(jié)點(diǎn)可以看到共識(shí)過程并同步賬本信息,但是不參與記賬。N個(gè)超級(jí)節(jié)點(diǎn)分為1個(gè)議長(zhǎng)和n-1個(gè)議員,議長(zhǎng)會(huì)輪流當(dāng)選,每次記賬時(shí),先由議長(zhǎng)發(fā)起區(qū)塊提案,也就是記賬的區(qū)塊內(nèi)容。一旦有2/3以上的記賬節(jié)點(diǎn)同意了這個(gè)提案,那么這個(gè)提案就會(huì)成為最終發(fā)布的區(qū)塊。

過程如下:

1.Neo持有者投票選擇共識(shí)節(jié)點(diǎn),也就是議員。

2.系統(tǒng)會(huì)根據(jù)議員的地址哈希做個(gè)排序,第一位為議長(zhǎng)。以后輪值

3.議長(zhǎng)從交易池中拿出交易打包,并根據(jù)當(dāng)前投票情況計(jì)算下一輪的共識(shí)節(jié)點(diǎn),這一步打包出的是提案塊

4.議長(zhǎng)打包出提案塊后,發(fā)給所有議員,議員接收到提案塊后,會(huì)對(duì)提案塊進(jìn)行驗(yàn)證。

5.議員驗(yàn)證通過后,對(duì)提案塊簽名,并廣播出去。

6.如果有2/3的議員對(duì)該提案簽名通過后,代表這個(gè)提案塊通過。則出正式塊.

我們開始閱讀代碼

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
            });
        }

這段代碼就是從內(nèi)存池獲取排序過的交易 ,并計(jì)算下一個(gè)出塊的共識(shí)節(jié)點(diǎn)。設(shè)置時(shí)間戳.打包的交易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消息后,對(duì)Parepare消息進(jìn)行驗(yàn)證(VerifyRequest),驗(yàn)證通過后,Make Response消息,并簽名,參數(shù)為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)
            }));
        }

當(dāng)一個(gè)節(jié)點(diǎn)收到超過2/3Response消息時(shí),進(jìn) 入Commit階段。并廣播Commit消息,參數(shù)為提案塊的簽名。

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 });
            }
        }

以上代碼就是當(dāng)一個(gè)節(jié)點(diǎn)收到超過2/3的Commit消息,并驗(yàn)證通過后,直接出新塊。并廣播.

總結(jié)下整個(gè)過程:

1.議長(zhǎng)從交易池取出交易組裝提案塊,發(fā)出Parepare消息.
2.議員收到提案塊后,對(duì) 其進(jìn)行 校驗(yàn)。校驗(yàn)通過,發(fā)出Response消息,表示議長(zhǎng)的這個(gè)提案通過.
3.當(dāng)一個(gè) 共識(shí)節(jié)點(diǎn)收到超過2/3的Response消息時(shí),進(jìn)入Commit階段。廣播Commit消息,附帶對(duì) 提案塊的簽名
4.當(dāng)一個(gè)共識(shí)節(jié)點(diǎn)收到超過2/3的Commit消息時(shí)。表示議長(zhǎng)這個(gè)提案通過。則直接出新塊.并廣播


2. NEO的投票規(guī)則,如何確定共識(shí)節(jié)點(diǎn)數(shù),和具體的共識(shí)節(jié)點(diǎn)

1.Neo持有者發(fā)一個(gè)StateTx交易進(jìn)行投票,和申請(qǐng)成為共識(shí)節(jié)點(diǎn),這些投票情況會(huì)被記錄下來

2.議長(zhǎng)根據(jù)這些投票情況確定節(jié)點(diǎn)個(gè)數(shù)N。N的確定方式是有一個(gè)公式的,簡(jiǎn)單的描述是去掉過大票數(shù)和過小票數(shù)的節(jié)點(diǎn),取中間投票數(shù)的中間值。這樣做的目的是防止有人作惡。具體算法,拿到當(dāng)前累加的票數(shù)/總票數(shù)得到所有節(jié)點(diǎn)的概率分布圖,然后選擇概率在0.25到0.75之間的期望值E,最后和備用節(jié)點(diǎn)個(gè)數(shù)相比取最大值。做為共識(shí)節(jié)點(diǎn)個(gè)數(shù)N.

3.選取投票排名前N位,做為這些投票的共識(shí)節(jié)點(diǎn).

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容