aelf技術點解讀 | AEDPoS合約實現邏輯

aelf技術點解讀 | AEDPoS合約實現邏輯
在aelf的共識合約標準中,其五個接口可以分爲三組:

對於任何一個節點,可在任意時刻,從合約中請求共識命令;

得到有效出塊時間的節點在調度器倒計時終結之後,從合約中獲得共識數據,並基於此數據生產區塊。

節點在將某個區塊添加至本地區塊鏈時,將區塊信息提交給共識合約,以進行一系列針對共識數據的驗證。

請求共識命令 - 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的實現邏輯。

// <summary>
// First step of getting consensus command for any pubkey:
// to get expected consensus behaviour.
// </summary>
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();
}
// <summary>
// 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)
// </summary>
// <returns></returns>
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;
}

// <summary>
// Main Chain & Side Chains are different (because side chains have no election mechanism thus no NEXT_TERM behaviour).
// </summary>
// <returns></returns>
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即可:

// <summary>// Simply return NEXT_ROUND for side chain.// </summary>// <returns></returns>protected override aelfConsensusBehaviour GetConsensusBehaviourToTerminateCurrentRound() => aelfConsensusBehaviour.NextRound;
生產節點換屆週期設計爲7天,因此要根據時間來判斷是否進入下一輪競選,此時Consensus Behaviour執行NextTerm:
// <summary>// The blockchain start timestamp is incorrect during the first round,// don't worry, we can return NextRound without hesitation.// Besides, return only NextRound for single node running.// </summary>// <returns></returns>protected override aelfConsensusBehaviour GetConsensusBehaviourToTerminateCurrentRound() => CurrentRound.RoundNumber == 1 || // Return NEXT_ROUND in first round. !CurrentRound.NeedToChangeTerm(_blockchainStartTimestamp, CurrentRound.TermNumber, _timeEachTerm) || CurrentRound.RealTimeMinersInformation.Keys.Count == 1 // Return NEXT_ROUND for single node. ? aelfConsensusBehaviour.NextRound : aelfConsensusBehaviour.NextTerm;
其中,NeedToChangeTerm判定規則爲:三分之二以上節點的出塊最新時間戳相較上次換屆時間戳差達7天之後,則返回true。

public int MinersCountOfConsent => RealTimeMinersInformation.Count.Mul(2).Div(3).Add(1);//<summary>// Change term if two thirds of miners latest ActualMiningTime meets threshold of changing term.// </summary>// <param name="blockchainStartTimestamp"></param>// <param name="currentTermNumber"></param>// <param name="timeEachTerm"></param>// <returns></returns>public bool NeedToChangeTerm(Timestamp blockchainStartTimestamp, long currentTermNumber, long timeEachTerm){ return RealTimeMinersInformation.Values .Where(m => m.ActualMiningTimes.Any()) .Select(m => m.ActualMiningTimes.Last()) .Count(t => IsTimeToChangeTerm(blockchainStartTimestamp, t, currentTermNumber, timeEachTerm)) >= MinersCountOfConsent;}// <summary>// If timeEachTerm == 7:// 1, 1, 1 => 0 != 1 - 1 => false// 1, 2, 1 => 0 != 1 - 1 => false// 1, 8, 1 => 1 != 1 - 1 => true => term number will be 2// 1, 9, 2 => 1 != 2 - 1 => false// 1, 15, 2 => 2 != 2 - 1 => true => term number will be 3.// </summary>// <param name="blockchainStartTimestamp"></param>// <param name="termNumber"></param>// <param name="blockProducedTimestamp"></param>// <param name="timeEachTerm"></param>// <returns></returns>private static bool IsTimeToChangeTerm(Timestamp blockchainStartTimestamp, Timestamp blockProducedTimestamp, long termNumber, long timeEachTerm){ return (blockProducedTimestamp - blockchainStartTimestamp).Seconds.Div(timeEachTerm) != termNumber - 1;}

組裝Consensus Command

獲取到Consensus Behaviour之後,便可以基於此組裝Consensus Command:

// <summary>
// aelf Consensus Behaviour is changeable in this method when
// this miner should skip his time slot more precisely.
// </summary>
// <param name="behaviour"></param>
// <param name="currentRound"></param>
// <param name="pubkey"></param>
// <param name="currentBlockTime"></param>
// <returns></returns>
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共識服務。

//<summary>
// If current miner mined blocks only himself for 2 rounds,
// just stop and waiting to execute other miners' blocks.
// </summary>
// <param name="currentRound"></param>
// <param name="pubkey"></param>
// <returns></returns>
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(用來驗證各個節點更新的共識信息是否合法)

// <summary>
// This method will be executed before executing a block.
// </summary>
// <param name="extraData"></param>
// <returns></returns>
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<IHeaderInformationValidationProvider>
{
    // 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這幾個方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章