对于任何一个节点,可在任意时刻,从合约中请求共识命令;
得到有效出块时间的节点在调度器倒计时终结之后,从合约中获得共识数据,并基于此数据生产区块。
节点在将某个区块添加至本地区块链时,将区块信息提交给共识合约,以进行一系列针对共识数据的验证。
请求共识命令 - GetConsensusCommand
这个方法的大致逻辑如下:
public override ConsensusCommand GetConsensusCommand(BytesValue input)
{
_processingBlockMinerPubkey = input.Value.ToHex();
if (Context.CurrentHeight < 2) return ConsensusCommandProvider.InvalidConsensusCommand;
// 如果查询不到当前轮次信息,返回一个无效的时间。
if (!TryToGetCurrentRoundInformation(out var currentRound))
return ConsensusCommandProvider.InvalidConsensusCommand;
// 如果请求共识命令的公钥不在生产节点列表中,返回一个无效时间。
if (!currentRound.IsInMinerList(_processingBlockMinerPubkey))
return ConsensusCommandProvider.InvalidConsensusCommand;
// 这条链开始运行的时间戳(大致)
var blockchainStartTimestamp = GetBlockchainStartTimestamp();
var behaviour = IsMainChain
? new MainChainConsensusBehaviourProvider(currentRound, _processingBlockMinerPubkey,
GetMaximumBlocksCount(),
Context.CurrentBlockTime, blockchainStartTimestamp, State.TimeEachTerm.Value)
.GetConsensusBehaviour()
: new SideChainConsensusBehaviourProvider(currentRound, _processingBlockMinerPubkey,
GetMaximumBlocksCount(),
Context.CurrentBlockTime).GetConsensusBehaviour();
Context.LogDebug(() =>
$"{currentRound.ToString(_processingBlockMinerPubkey)}\nArranged behaviour: {behaviour.ToString()}");
return behaviour == aelfConsensusBehaviour.Nothing
? ConsensusCommandProvider.InvalidConsensusCommand
: GetConsensusCommand(behaviour, currentRound, _processingBlockMinerPubkey, Context.CurrentBlockTime);
}
在该实现中,代码运行到获得区块链开始运行的时间戳后,获取共识命令分为两个步骤:
1.根据轮次(Round)信息和该条链是主链还是侧链,判断该公钥接下来应该生产一个什么类型的区块,这里描述为Consensus Behaviour;
2.如果能得到一个有效的Consensus Behaviour,再去进一步组装出共识命令(Consensus Command),作为结果返回。
获取Consensus Behaviour
aelf侧链和主链的区别在于,侧链不存在生产节点选举换届等事务(即Consensus Behaviour),在联合挖矿(Merged Mining)设计模式下,侧链共享主链的生产节点,因此与生产节点竞选等事务相关的合约只需要部署在主链上即可。
ConsensusBehaviourProviderBase为主链和侧链共享Consensus Behaviour的实现逻辑。
//
// First step of getting consensus command for any pubkey:
// to get expected consensus behaviour.
//
private abstract class ConsensusBehaviourProviderBase
{
protected readonly Round CurrentRound;
private readonly string _pubkey;
private readonly int _maximumBlocksCount;
private readonly Timestamp _currentBlockTime;
private readonly bool _isTimeSlotPassed;
private readonly MinerInRound _minerInRound;
protected ConsensusBehaviourProviderBase(Round currentRound, string pubkey, int maximumBlocksCount,
Timestamp currentBlockTime)
{
CurrentRound = currentRound;
_pubkey = pubkey;
_maximumBlocksCount = maximumBlocksCount;
_currentBlockTime = currentBlockTime;
_isTimeSlotPassed = CurrentRound.IsTimeSlotPassed(_pubkey, _currentBlockTime);
_minerInRound = CurrentRound.RealTimeMinersInformation[_pubkey];
}
public aelfConsensusBehaviour GetConsensusBehaviour()
{
// The most simple situation: provided pubkey isn't a miner.
if (!CurrentRound.IsInMinerList(_pubkey))
{
return aelfConsensusBehaviour.Nothing;
}
// If out value is null, it means provided pubkey hasn't mine any block during current round period.
if (_minerInRound.OutValue == null)
{
var behaviour = HandleMinerInNewRound();
// It's possible HandleMinerInNewRound can't handle all the situations, if this method returns Nothing,
// just go ahead. Otherwise, return it's result.
if (behaviour != aelfConsensusBehaviour.Nothing)
{
return behaviour;
}
}
else if (!_isTimeSlotPassed
) // Provided pubkey mined blocks during current round, and current block time is still in his time slot.
{
if (_minerInRound.ActualMiningTimes.Count < _maximumBlocksCount)
{
// Provided pubkey can keep producing tiny blocks.
return aelfConsensusBehaviour.TinyBlock;
}
var blocksBeforeCurrentRound =
_minerInRound.ActualMiningTimes.Count(t => t <= CurrentRound.GetRoundStartTime());
// If provided pubkey is the one who terminated previous round, he can mine
// (_maximumBlocksCount + blocksBeforeCurrentRound) blocks
// because he has two time slots recorded in current round.
if (CurrentRound.ExtraBlockProducerOfPreviousRound ==
_pubkey && // Provided pubkey terminated previous round
!CurrentRound.IsMinerListJustChanged && // & Current round isn't the first round of current term
_minerInRound.ActualMiningTimes.Count.Add(1) <
_maximumBlocksCount.Add(
blocksBeforeCurrentRound) // & Provided pubkey hasn't mine enough blocks for current round.
)
{
// Then provided pubkey can keep producing tiny blocks.
return aelfConsensusBehaviour.TinyBlock;
}
}
return GetConsensusBehaviourToTerminateCurrentRound();
}
//
// If this miner come to a new round, normally, there are three possible behaviour:
// UPDATE_VALUE (most common)
// TINY_BLOCK (happens if this miner is mining blocks for extra block time slot of previous round)
// NEXT_ROUND (only happens in first round)
//
//
private aelfConsensusBehaviour HandleMinerInNewRound()
{
if (
// For first round, the expected mining time is incorrect (due to configuration),
CurrentRound.RoundNumber == 1 &&
// so we'd better prevent miners' ain't first order (meanwhile he isn't boot miner) from mining fork blocks
_minerInRound.Order != 1 &&
// by postpone their mining time
CurrentRound.FirstMiner().OutValue == null
)
{
return aelfConsensusBehaviour.NextRound;
}
if (
// If this miner is extra block producer of previous round,
CurrentRound.ExtraBlockProducerOfPreviousRound == _pubkey &&
// and currently the time is ahead of current round,
_currentBlockTime < CurrentRound.GetRoundStartTime() &&
// make this miner produce some tiny blocks.
_minerInRound.ActualMiningTimes.Count < _maximumBlocksCount
)
{
return aelfConsensusBehaviour.TinyBlock;
}
return !_isTimeSlotPassed ? aelfConsensusBehaviour.UpdateValue : aelfConsensusBehaviour.Nothing;
}
//
// Main Chain & Side Chains are different (because side chains have no election mechanism thus no NEXT_TERM behaviour).
//
//
protected abstract aelfConsensusBehaviour GetConsensusBehaviourToTerminateCurrentRound();
}
先大致介绍一下AEDPoS共识的设计机制和区块生产时序。众所周知,在DPoS系列共识下,区块由一系列被选举出来的代理节点进行生产,而AEDPoS的区块生产周期以轮次(Round)为单位,每一轮中,每个生产节点被随机分配一个时间槽(Time Slot),只有在被分配时间槽中生产区块才被认为是合法的。生产节点分配顺序由上一轮中各自产生的随机数确定(为验证上一轮的随机数,需要生产节点在上一轮公布一个随机数的哈希值,再在本轮公布上一轮的随机数,验证随机数的哈希值与上一轮公布的哈希值是否相等即可验证,这一验证机制也被用于AEDPoS共识Commitment Scheme随机数的验证上)。每一轮最后一个生产节点时间槽之后,包含一个额外时间槽,用来生产额外区块,该区块用于对下一轮信息进行初始化。
因此生产节点获取Consensus Behaviour的实现逻辑就比较清晰(主链启动后的第一轮默认时间为1970年,需要做特殊处理,此处略过):
1.如果该节点本轮还未生产过区块:
a.如果当前时间已经过了被分配的时间槽,就先不确定Consensus Behaviour,返回Nothing;
b.如果当前时间还未到被分配的时间槽,返回Consensus Behaviour,称之为UpdateValue,因为本质上Consensus Behaviour是用来更新共识数据的。
2.如果该节点本轮已经生产过区块,则判断能不能生产小块(每个时间槽可以连续生产8个区块):
a.若能,则Consensus Behaviour执行TinyBlock;
b.若不能,就给当前节点分配一个终结本轮的时间槽:
i.如果当前节点恰好是本轮预先指定的额外区块生产节点(Extra Block Producer),那么就将本轮自带的最后一个时间槽之后的一个时间槽分配给该生产节点;
ii.如果当前节点不是指定的额外区块生产节点,在本轮自带的最后一个时间槽之后,按照其在本轮的次序,给予分配相应次序的时间槽——如果指定的额外区块生产者因为分叉或掉线而未能按时出块的话,那么就有机会轮到当前节点来终结本轮。
判断是否错过时间槽的方法位于Round中:
public bool IsTimeSlotPassed(string publicKey, Timestamp currentBlockTime)
{
var miningInterval = GetMiningInterval();
if (!RealTimeMinersInformation.ContainsKey(publicKey)) return false;
var minerInRound = RealTimeMinersInformation[publicKey];
if (RoundNumber != 1)
{
return minerInRound.ExpectedMiningTime + new Duration {Seconds = miningInterval.Div(1000)} <
currentBlockTime;
}
var actualStartTimes = FirstMiner().ActualMiningTimes;
if (actualStartTimes.Count == 0)
{
return false;
}
var actualStartTime = actualStartTimes.First();
var runningTime = currentBlockTime - actualStartTime;
var expectedOrder = runningTime.Seconds.Div(miningInterval.Div(1000)).Add(1);
return minerInRound.Order < expectedOrder;
}
对于侧链而言,要终结本轮,直接返回NextRound即可:
//
生产节点换届周期设计为7天,因此要根据时间来判断是否进入下一轮竞选,此时Consensus Behaviour执行NextTerm:
//
其中,NeedToChangeTerm判定规则为:三分之二以上节点的出块最新时间戳相较上次换届时间戳差达7天之后,则返回true。
public int MinersCountOfConsent => RealTimeMinersInformation.Count.Mul(2).Div(3).Add(1);//
组装Consensus Command
获取到Consensus Behaviour之后,便可以基于此组装Consensus Command:
//
// aelf Consensus Behaviour is changeable in this method when
// this miner should skip his time slot more precisely.
//
//
//
//
//
//
private ConsensusCommand GetConsensusCommand(aelfConsensusBehaviour behaviour, Round currentRound,
string pubkey, Timestamp currentBlockTime = null)
{
if (SolitaryMinerDetection(currentRound, pubkey))
return ConsensusCommandProvider.InvalidConsensusCommand;
if (currentRound.RoundNumber == 1 && behaviour != aelfConsensusBehaviour.TinyBlock)
return new ConsensusCommandProvider(new FirstRoundCommandStrategy(currentRound, pubkey,
currentBlockTime, behaviour)).GetConsensusCommand();
Context.LogDebug(() => $"Params to get command: {behaviour}, {pubkey}, {currentBlockTime}");
switch (behaviour)
{
case aelfConsensusBehaviour.UpdateValue:
TryToGetPreviousRoundInformation(out var previousRound);
return new ConsensusCommandProvider(new NormalBlockCommandStrategy(currentRound, pubkey,
currentBlockTime, previousRound.RoundId)).GetConsensusCommand();
case aelfConsensusBehaviour.NextRound:
case aelfConsensusBehaviour.NextTerm:
return new ConsensusCommandProvider(
new TerminateRoundCommandStrategy(currentRound, pubkey, currentBlockTime,
behaviour == aelfConsensusBehaviour.NextTerm))
.GetConsensusCommand();
case aelfConsensusBehaviour.TinyBlock:
{
var consensusCommand =
new ConsensusCommandProvider(new TinyBlockCommandStrategy(currentRound, pubkey,
currentBlockTime, GetMaximumBlocksCount())).GetConsensusCommand();
if (consensusCommand.Hint ==
new aelfConsensusHint {Behaviour = aelfConsensusBehaviour.NextRound}.ToByteString())
{
Context.LogDebug(() => "Re-arranged behaviour from TinyBlock to NextRound.");
}
return consensusCommand;
}
}
return ConsensusCommandProvider.InvalidConsensusCommand;
}
在组装Consensus Command之前需做一个判断:如果最近三轮都只有当前一个生产节点在产生区块,则说明网络或者整条链一定出了问题,那么将直接暂停出块,需等待同步网络上其他区块而重新Trigger共识服务。
//
// If current miner mined blocks only himself for 2 rounds,
// just stop and waiting to execute other miners' blocks.
//
//
//
//
private bool SolitaryMinerDetection(Round currentRound, string pubkey)
{
var isAlone = false;
// Skip this detection until 4th round.
if (currentRound.RoundNumber > 3 && currentRound.RealTimeMinersInformation.Count > 2)
{
// Not single node.
var minedMinersOfCurrentRound = currentRound.GetMinedMiners();
isAlone = minedMinersOfCurrentRound.Count == 0;
// If only this node mined during previous round, stop mining.
if (TryToGetPreviousRoundInformation(out var previousRound) && isAlone)
{
var minedMiners = previousRound.GetMinedMiners();
isAlone = minedMiners.Count == 1 &&
minedMiners.Select(m => m.Pubkey).Contains(pubkey);
}
// check one further round.
if (isAlone && TryToGetRoundInformation(previousRound.RoundNumber.Sub(1),
out var previousPreviousRound))
{
var minedMiners = previousPreviousRound.GetMinedMiners();
isAlone = minedMiners.Count == 1 &&
minedMiners.Select(m => m.Pubkey).Contains(pubkey);
}
}
return isAlone;
}
接下来,就可以根据传入的Consensus Behaviour来选择对应的策略:
FirstRoundCommandStrategy
(针对第一轮的特殊处理策略)
NormalBlockCommandStrategy
(对应UpdateValue)
TerminateRoundCommandStrategy
(对应NextRound和NextTerm)
TinyBlockCommandStrategy
(对应TinyBlock)
我们通过NormalBlockCommandStrategy(对应UpdateValue)的策略看其实现逻辑:
public override ConsensusCommand GetAEDPoSConsensusCommand(){ var arrangedMiningTime = MiningTimeArrangingService.ArrangeNormalBlockMiningTime(CurrentRound, Pubkey, CurrentBlockTime); return new ConsensusCommand { Hint = new aelfConsensusHint { Behaviour = aelfConsensusBehaviour.UpdateValue, RoundId = CurrentRound.RoundId, PreviousRoundId = _previousRoundId }.ToByteString(), ArrangedMiningTime = arrangedMiningTime, // Cancel mining after time slot of current miner because of the task queue. MiningDueTime = CurrentRound.GetExpectedMiningTime(Pubkey).AddMilliseconds(MiningInterval), LimitMillisecondsOfMiningBlock = DefaultBlockMiningLimit }; }
该策略通过获取预计出块时间,来组装执行一个ConsensusCommand实例。其余的策略实现逻辑也大致如此。
生成共识数据 - GetConsensusExtraData & GenerateConsensusTransactions
在节点获得Consensus Command之后,便会根据其中的ArrangedMiningTime信息更新本地的共识调度器;倒计时结束后,即开始产生区块。区块中的共识信息位于两个位置,分别位于区块头(Block Header)中的额外数据(一个二进制数组的列表,其中之一为共识数据),和一个共识交易作为系统交易,其哈希值保存在区块头中,交易相关数据保存在区块体(Block Body)中。
其中,区块头中的信息经由调用GetConsensusExtraData产生,共识交易由调用GenerateConsensusTransactions产生。
AEDPoS对于二者的实现,都不可避免地要使用下面的方法:
private BytesValue GetConsensusBlockExtraData(BytesValue input, bool isGeneratingTransactions = false)
{
var triggerInformation = new aelfConsensusTriggerInformation();
triggerInformation.MergeFrom(input.Value);
Assert(triggerInformation.Pubkey.Any(), "Invalid pubkey.");
if (!TryToGetCurrentRoundInformation(out var currentRound))
{
Assert(false, "Failed to get current round information.");
}
var publicKeyBytes = triggerInformation.Pubkey;
var pubkey = publicKeyBytes.ToHex();
var information = new aelfConsensusHeaderInformation();
switch (triggerInformation.Behaviour)
{
case aelfConsensusBehaviour.UpdateValue:
information = GetConsensusExtraDataToPublishOutValue(currentRound, pubkey,
triggerInformation);
if (!isGeneratingTransactions)
{
information.Round = information.Round.GetUpdateValueRound(pubkey);
}
break;
case aelfConsensusBehaviour.TinyBlock:
information = GetConsensusExtraDataForTinyBlock(currentRound, pubkey,
triggerInformation);
break;
case aelfConsensusBehaviour.NextRound:
information = GetConsensusExtraDataForNextRound(currentRound, pubkey,
triggerInformation);
break;
case aelfConsensusBehaviour.NextTerm:
information = GetConsensusExtraDataForNextTerm(pubkey, triggerInformation);
break;
}
if (!isGeneratingTransactions)
{
information.Round.DeleteSecretSharingInformation();
}
return information.ToBytesV
首先反序列化输入的信息需通过aelfConsensusTriggerInformation实例化,再根据其中的Consensus Behaviour分别去获取不同的可更新的共识数据。
这次我们看一下NextRound对应的方法:
private aelfConsensusHeaderInformation GetConsensusExtraDataForNextRound(Round currentRound,
string pubkey, aelfConsensusTriggerInformation triggerInformation)
{
if (!GenerateNextRoundInformation(currentRound, Context.CurrentBlockTime, out var nextRound))
{
Assert(false, "Failed to generate next round information.");
}
if (!nextRound.RealTimeMinersInformation.Keys.Contains(pubkey))
{
return new aelfConsensusHeaderInformation
{
SenderPubkey = pubkey.ToByteString(),
Round = nextRound,
Behaviour = triggerInformation.Behaviour
};
}
RevealSharedInValues(currentRound, pubkey);
nextRound.RealTimeMinersInformation[pubkey].ProducedBlocks =
nextRound.RealTimeMinersInformation[pubkey].ProducedBlocks.Add(1);
Context.LogDebug(() => $"Mined blocks: {nextRound.GetMinedBlocks()}");
nextRound.ExtraBlockProducerOfPreviousRound = pubkey;
nextRound.RealTimeMinersInformation[pubkey].ProducedTinyBlocks = 1;
nextRound.RealTimeMinersInformation[pubkey].ActualMiningTimes
.Add(Context.CurrentBlockTime);
return new aelfConsensusHeaderInformation
{
SenderPubkey = pubkey.ToByteString(),
Round = nextRound,
Behaviour = triggerInformation.Behaviour
};
}
基于本轮所更新的共识数据,生成下一轮的信息,然后更新几个区块生产信息。
GetConsensusExtraData直接使用这个方法的返回值即可,GenerateConsensusTransactions则需对返回值进行再次处理,生成一个共识交易(GetConsensusBlockExtraData的bool类型的参数用来指示为生成交易时填充更为详尽的信息):
public override TransactionList GenerateConsensusTransactions(BytesValue input)
{
var triggerInformation = new aelfConsensusTriggerInformation();
triggerInformation.MergeFrom(input.Value);
// Some basic checks.
Assert(triggerInformation.Pubkey.Any(),
"Data to request consensus information should contain public key.");
var pubkey = triggerInformation.Pubkey;
var consensusInformation = new aelfConsensusHeaderInformation();
consensusInformation.MergeFrom(GetConsensusBlockExtraData(input, true).Value);
var transactionList = GenerateTransactionListByExtraData(consensusInformation, pubkey);
return transactionList;
}
验证共识消息 - ValidateConsensusBeforeExecution
& GenerateConsensusTransactions
对于一个区块的验证,依然要基于当前区块的生产动机——Consensus Behaviour。在ValidateConsensusBeforeExecution实现中,需根据Consensus Behaviour的不同添加不同的IHeaderInformationValidationProvider。IHeaderInformationValidationProvider目前有以下几种:
ContinuousBlocksValidationProvider(用来阻止节点连续产生过多区块)
LibInformationValidationProvider(用来验证不可逆转块的信息是否正确)
MiningPermissionValidationProvider(用来验证该节点是否有出块权限)
NextRoundMiningOrderValidationProvider(用来验证下一轮的出块顺序是否正确)
RoundTerminateValidationProvider(用来验证下一轮基本信息是否正确)
TimeSlotValidationProvider(用来验证区块是否生产自正确时间槽)
UpdateValueValidationProvider(用来验证各个节点更新的共识信息是否合法)
//
// This method will be executed before executing a block.
//
//
//
private ValidationResult ValidateBeforeExecution(aelfConsensusHeaderInformation extraData)
{
// According to current round information:
if (!TryToGetCurrentRoundInformation(out var baseRound))
{
return new ValidationResult {Success = false, Message = "Failed to get current round information."};
}
if (extraData.Behaviour == aelfConsensusBehaviour.UpdateValue)
{
baseRound.RecoverFromUpdateValue(extraData.Round, extraData.SenderPubkey.ToHex());
}
if (extraData.Behaviour == aelfConsensusBehaviour.TinyBlock)
{
baseRound.RecoverFromTinyBlock(extraData.Round, extraData.SenderPubkey.ToHex());
}
var validationContext = new ConsensusValidationContext
{
BaseRound = baseRound,
CurrentTermNumber = State.CurrentTermNumber.Value,
CurrentRoundNumber = State.CurrentRoundNumber.Value,
PreviousRound = TryToGetPreviousRoundInformation(out var previousRound) ? previousRound : new Round(),
LatestPubkeyToTinyBlocksCount = State.LatestPubkeyToTinyBlocksCount.Value,
ExtraData = extraData
};
/* Ask several questions: */
// Add basic providers at first.
var validationProviders = new List
{
// Is sender in miner list (of base round)?
new MiningPermissionValidationProvider(),
// Is this block produced in proper time?
new TimeSlotValidationProvider(),
// Is sender produced too many blocks at one time?
new ContinuousBlocksValidationProvider()
};
switch (extraData.Behaviour)
{
case aelfConsensusBehaviour.UpdateValue:
validationProviders.Add(new UpdateValueValidationProvider());
// Is confirmed lib height and lib round number went down? (Which should not happens.)
validationProviders.Add(new LibInformationValidationProvider());
break;
case aelfConsensusBehaviour.NextRound:
// Is sender's order of next round correct?
validationProviders.Add(new NextRoundMiningOrderValidationProvider());
validationProviders.Add(new RoundTerminateValidationProvider());
break;
case aelfConsensusBehaviour.NextTerm:
validationProviders.Add(new RoundTerminateValidationProvider());
break;
}
var service = new HeaderInformationValidationService(validationProviders);
Context.LogDebug(() => $"Validating behaviour: {extraData.Behaviour.ToString()}");
var validationResult = service.ValidateInformation(validationContext);
if (validationResult.Success == false)
{
Context.LogDebug(() => $"Consensus Validation before execution failed : {validationResult.Message}");
}
return validationResult;
}
ValidateConsensusAfterExecution的实现只需要检查共识交易执行后所实际更新的共识信息(的重要部分)与Block Header Extra Data中的共识信息是否一致:
public override ValidationResult ValidateConsensusAfterExecution(BytesValue input)
{
var headerInformation = new aelfConsensusHeaderInformation();
headerInformation.MergeFrom(input.Value);
if (TryToGetCurrentRoundInformation(out var currentRound))
{
if (headerInformation.Behaviour == aelfConsensusBehaviour.UpdateValue)
{
headerInformation.Round =
currentRound.RecoverFromUpdateValue(headerInformation.Round,
headerInformation.SenderPubkey.ToHex());
}
if (headerInformation.Behaviour == aelfConsensusBehaviour.TinyBlock)
{
headerInformation.Round =
currentRound.RecoverFromTinyBlock(headerInformation.Round,
headerInformation.SenderPubkey.ToHex());
}
var isContainPreviousInValue = !currentRound.IsMinerListJustChanged;
if (headerInformation.Round.GetHash(isContainPreviousInValue) !=
currentRound.GetHash(isContainPreviousInValue))
{
Context.LogDebug(() => $"Round information of block header:\n{headerInformation.Round}");
Context.LogDebug(() => $"Round information of executing result:\n{currentRound}");
return new ValidationResult
{
Success = false, Message = "Current round information is different with consensus extra data."
};
}
}
return new ValidationResult {Success = true}
}
由于区块头额外信息中的共识信息是被简化过的(如并不包含Secret Share相关信息),ValidateConsensusAfterExecution需要将被简化的内容进行适度补全,最终直接验证StateDB中的本轮信息和补全后的信息是否完全一致,而验证本身也只关心比较重要的共识数据,会对其他多余的数据做一定裁剪,这就是是为何需要RecoverFromUpdateValue、RecoverFromTinyBlock和在GetHash中调用GetCheckableRound这几个方法。