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è)提案通過。則直接出新塊.并廣播