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消息时。表示议长这个提案通过。则直接出新块.并广播
网友评论