AEDPoS合约

circle-info

AEDPoS合约与ConsensusService联合调度aelf的区块生产,完成(一部分)区块验证。

aelf区块生产调度 开篇扬明的五个问题,实质上都由AEDPoS合约来回答。

GetConsensusCommand

GetConsensusCommand接口用于获取某个公钥下一次生产区块的时间等信息。

在AEDPoS的实现中,其输入仅为一个公钥(public key),该接口实现方法的调用时间另外作为参考(其实也是一个重要的输入)。AElf区块链中,当系统内部调用只读交易时,合约执行的上下文是自行构造出来的,调用时间也就是通过C#自带函数库的DateTime.UtcNow生成了一个时间,然后把这个时间转化为protobuf提供的时间戳数据类型Timestamp,传入合约执行的上下文中。

事实上,无论要执行的交易是否为只读交易,合约代码中都可以通过Context.CurrentBlockTime来获取当前合约执行上下文传进来的时间戳。

这一节主要解释AEDPoS共识如何实现GetConsensusCommand。在此之前,对不了解AElf共识的同学简单介绍一下AEDPoS的流程。

AEDPoS流程

假设现在aelf主链通过投票选举出17个节点,我们称之为BP。

这些BP是通过全民投票在某个区块高度(或者说时间点)的结果,直接取前17名得到。每次重新统计前17名候选人并重新任命BP,称为换届(Term)。

在每一届中,所有的BP按轮(Round)次生产区块。每一轮有17+1个时间槽,每位BP随机地占据前17个时间槽之一,最后一个时间槽由本轮额外区块生产者负责生产区块。额外区块生产者会根据本轮每个BP公布的随机数初始化下一轮的信息。18个时间槽后,下一轮开始。如此循环。

Round的数据结构如下:

message Round {
    // The round number.
    int64 round_number = 1;
    // Current miner information, miner public key -> miner information.
    map<string, MinerInRound> real_time_miners_information = 2;
    // The round number on the main chain
    int64 main_chain_miners_round_number = 3;
    // The time from chain start to current round (seconds).
    int64 blockchain_age = 4;
    // The miner public key that produced the extra block in the previous round.
    string extra_block_producer_of_previous_round = 5;
    // The current term number.
    int64 term_number = 6;
    // The height of the confirmed irreversible block.
    int64 confirmed_irreversible_block_height = 7;
    // The round number of the confirmed irreversible block.
    int64 confirmed_irreversible_block_round_number = 8;
    // Is miner list different from the the miner list in the previous round.
    bool is_miner_list_just_changed = 9;
    // The round id, calculated by summing block producers’ expecting time (second).
    int64 round_id_for_validation = 10;
}

message MinerInRound {
    // The order of the miner producing block.
    int32 order = 1;
    // Is extra block producer in the current round.
    bool is_extra_block_producer = 2;
    // Generated by secret sharing and used for validation between miner.
    aelf.Hash in_value = 3;
    // Calculated from current in value.
    aelf.Hash out_value = 4;
    // Calculated from current in value and signatures of previous round.
    aelf.Hash signature = 5;
    // The expected mining time.
    google.protobuf.Timestamp expected_mining_time = 6;
    // The amount of produced blocks.
    int64 produced_blocks = 7;
    // The amount of missed time slots.
    int64 missed_time_slots = 8;
    // The public key of this miner.
    string pubkey = 9;
    // The InValue of the previous round.
    aelf.Hash previous_in_value = 10;
    // The supposed order of mining for the next round.
    int32 supposed_order_of_next_round = 11;
    // The final order of mining for the next round.
    int32 final_order_of_next_round = 12;
    // The actual mining time, miners must fill actual mining time when they do the mining.
    repeated google.protobuf.Timestamp actual_mining_times = 13;
    // The encrypted pieces of InValue.
    map<string, bytes> encrypted_pieces = 14;
    // The decrypted pieces of InValue.
    map<string, bytes> decrypted_pieces = 15;
    // The amount of produced tiny blocks.
    int64 produced_tiny_blocks = 16;
    // The irreversible block height that current miner recorded.
    int64 implied_irreversible_block_height = 17;
}

在AEDPoS合约中有一个map结构,key是long类型的RoundNumber,从1自增,value就是上述的Round结构,BP产生的每个区块都会更新当前轮或者下一轮的信息,以此推进共识和区块生产,并为共识验证提供基本依据。

ConsensusCommand

在AEDPoS共识的实现中,Hint字段为BP下一次生产什么类型的区块指了一条明路。我们为Hint提供了专门的数据结构,AElfConsensusHint:

而区块类型正包含在如下的AElfConsensusBehaviour中:

UpdateValue和UpdateValue(WithoutPreviousInValue)代表该BP要生产某一轮中的一个普普通通的区块(结合aelf白皮书或aelf系统概论中的共识模块理解)。在这两个行为中,BP重点要更新的共识信息包括他前一轮的in_value(previous_in_value),本轮产生的out_value,以及本轮用来产生out_value的in_value的密码片段。(该BP会用in_value和其他BP的公钥加密得到16个密码片段,其他BP只能各自用自己的私钥解密,当解密的片段达到一定数量后,原始的in_value就会被揭露;这是shamir's secret sharing的一个应用,细节可谷歌,aelf用了ECDH实现。)除此之外,还要在actual_mining_times中增加一条本次实际触发区块生产行为的时间戳。UpdateValueWithoutPreviousInValue和UpdateValue区别仅在于本次不需要公布上一轮的in_value(previous_in_value),因为当前轮是第一轮,或者刚刚换过届(而该BP是一个萌新BP)。NextRound代表该BP是本轮的额外区块生产者(或者补救者——当指定的额外区块生产者缺席时),要初始化下一轮信息。下一轮信息包括每个BP的时间槽排列及根据规则指定的下一轮的额外区块生产者。NextTerm类似于NextRound,只不过会重新统计选举的前17名,根据新一届的BP初始化下一轮信息。Nothing是发现输入的公钥并不是一个BP。TinyBlock代表该BP刚刚已经更新过共识信息,但是他的时间槽还没有过去,他还有时间去出几个额外的块。目前每个时间槽最多可以出8个小块。这样的好处是提高区块验证的效率(eos也是这么做的)。有一个时间槽的问题需要特别注意,由于AEDPoS选择在创世区块中生成第一轮共识信息(即所有最初的BP的时间槽等信息),而创世区块对于每一个节点都应该是完全一致的,因此第一轮的共识信息不得不指定给一个统一的时间(否则创世区块的哈希值会不一致):如今这个时间是1970年0点。这样会导致第一轮的时间槽极其不准确,因此在获取第一轮的ConsensusCommand时会做特殊处理。

GetConsensusBehaviour

AEDPoS合约中,要让GetConsensusCommand方法返回ConsensusCommand,首先会根据输入的公钥和调用时间得到AElfConsensusBehaviour。然后再使用AElfConsensusBehaviour判断下一次出块时间等信息。

这里的逻辑相对比较清晰,也许可以用一张图来解释清楚(结合代码看一下):

GetConsensusCommand - UpdateValue(WithoutPreviousInValue)

AElfConsensusBehaviour.UpdateValueWithoutPreviousInValue的主要作用是实现Commitment Schemearrow-up-right,仅包含一次commit phase,不包含reveal phase。对应共识Mining Process的阶段,就是每一届(当然包括第一届,也就是链刚刚启动的时候)的第一轮,BP要试图产生本轮第一个区块。如果当前处于第一届的第一轮,则需要从AEDPoS共识的Round.real_time_miners_information信息中读取提供公钥的BP在本轮中的次序order,预期出块时间即order * mining_interval毫秒之后。mining_interval默认为4000ms。否则,直接从Round信息中读取expected_mining_time,依据此来返回ConsensusCommand。

GetConsensusCommand - UpdateValue

AElfConsensusBehaviour.UpdateValue包含一次Commitment Scheme中的reveal phase,一次新的commit phase。对应共识Mining Process的阶段为每一届的第二轮及以后,BP试图产生本轮的第一个区块。直接读取当前轮的Round信息中该BP的公钥对应的expected_mining_time字段即可。

GetConsensusCommand - NextRound

AElfConsensusBehaviour.NextRound会根据本轮各个BP公布的信息,按照顺序计算规则,生成下一轮各个BP的顺序和对应时间槽,将RoundNumber往后推进一个数字。对于本轮指定为额外区块生产者的BP,直接读取本轮的额外区块生成时间槽即可。否则,为了防止指定的额外区块生产者掉线或者在另外一个分叉上出块(在网络不稳定的情况下会出现分叉),其他所有的BP也会得到一个互不相同额外区块生产的时间槽,这些BP在同步到任何一个BP生产的额外区块后,会立刻重置自己的调度器,所以不必担心产生冲突。对于第一届第一轮的特殊处理同AElfConsensusBehaviour.UpdateValue(WithoutPreviousInValue)。

GetConsensusCommand - NextTerm

AElfConsensusBehaviour.NextTerm会根据当前的选举结果重新选定17位BP,生成新一届第一轮的信息。方法同AElfConsensusBehaviour.NextRound不对第一届第一轮做特殊处理的情况。

GetConsensusCommand - TinyBlock

AElfConsensusBehaviour.TinyBlock发生在两种情况下:

  1. 当前BP为上一轮的额外区块生产者,在生产完包含NextRound交易的区块以后,需要在同一个时间槽里继续生产最多7个区块;

  2. 当前BP刚刚生产过包含UpdateValue交易的区块,需要在同一个时间槽继续生产最多7个区块。

基本判断逻辑是,如果当前BP为本轮出过包含UpdateValue交易的块,即情况2,就结合当前BP是上一轮额外区块生产者的情况,把一个长度为4000ms的时间槽切分成8个500ms的小块时间槽,进行分配;否则为上述的情况1,直接根据已经出过的小块的数量分配一个合理的小块时间槽。

GetConsensusExtraData

Consensus Extra Data保存于Block Header中的ExtraData字段中(aelf区块数据结构和ChainIdarrow-up-right )。该字段用于保存aelf系统各个部分可用于辅助完成区块验证的信息。Consensus Extra Data就是用于辅助完成共识信息验证的数据,由于保存在区块头中,用于帮助收到区块的全节点在执行区块中的交易之前,快速验证一下共识信息:如果包含在区块头中的共识信息就是错误的,那这个区块的交易就不必执行了,可以直接丢弃。BP在生产区块的过程中,通过AEDPoS合约的GetConsensusExtraData方法获得要设置进区块头中的Consensus Extra Data。这个方法的参数是二进制数组,不同的共识机制可以定制自己的形参类型,在方法实现中进行反序列化即可。在AEDPoS实现中,认为入参是一个AElfConsensusTriggerInformation结构:

在AEDPoS实现中,认为返回值是一个AElfConsensusHeaderInformation结构:

而Consensus Extra Data的获取,首先基于AElfConsensusTriggerInformation的behaviour字段,不同的共识行为会生成不同的区块头信息。比如UpdateValue中,会公开前一轮的In Value,公布这一轮的Out Value;在TinyBlock中,则只是把这一轮的Round信息中相应BP的小块数量加一;NextRound和NextTerm中,都会生成新的Round的信息,不同的地方在于后者会重新获取Miner List(TryToGetVictories方法,回去调用Election合约)。最后,为了防止返回的AElfConsensusHeaderInformation实例太大,会删除掉Secret Sharing相关的数据,这一部分数据无需包含在区块头中验证。

GenerateConsensusTransactions

关于系统交易,见[WIP]aelf System Transaction机制arrow-up-right 。这个方法用于辅助BP生成即将包含在新生产的区块中的Consensus System Transaction。入参同GetConsensusExtraData,也是AElfConsensusTriggerInformation。返回值是TransactionList:

根据behaviour的不同,分别产生UpdateValue、TinyBlock、NextRound、NextTerm交易。

这些交易执行后,会修改StateDb中的共识数据,如Round结构的数据等。

同时,这些交易会作为系统交易,被优先打包进即将产生的区块中。

ValidateConsensusBeforeExecution

该方法用于验证BlockHeader中的Consensus Extra Data是否正确。

  • 入参解析为AElfConsensusHeaderInformation结构:extraData;

  • 取StateDb中当前轮的共识信息:baseRound;

  • 在验证之前,使用extraData中的Round信息填充baseRound;

  • 构造验证上下文结构ConsensusValidationContext;

  • 做三项基本验证:

    • MiningPermissionValidationProvider:Sender在不在Miner List中;

    • TimeSlotValidationProvider:当前时间在不在Sender的时间槽中(根据Round信息获取时间槽);

    • ContinuousBlocksValidationProvider:为了放置一个BP一次性产生过多连续的区块(不能超过8个)。

  • 根据共识行为,添加验证:

    • UpdateValue:

      • UpdateValueValidationProvider:验证Out Value和Previous In Value信息;

      • LibInformationValidationProvider:验证一下lib信息是否正确;

    • NextRound:

      • NextRoundMiningOrderValidationProvider:验证新生成的一轮中BP出块顺序是否正确;

      • RoundTerminateValidationProvider:新生成的Round信息是否正确;

    • NextTerm:

      • RoundTerminateValidationProvider:新生成的Round信息是否正确。

ValidateConsensusAfterExecution

该方法用于验证共识系统交易(就是通过GenerateConsensusTransactions方法生成的交易)执行完毕后,执行结果是否与Consensus Extra Data一致。会顺便看一下这一轮的信息是否做过修改,如果被做过修改的话,进行的修改是否恰当。(比如这一轮发生了BP替换的话,会有修改,这时候会校验替换的结果是否正确。)

其他

侧链如何同步主链的BP列表

aelf的同构跨链使用Merged Mining方式实现,而选举只在主链完成,意味着侧链需要及时得知主链最近的BP列表(包括某个BP因为作恶被其他候选人换上来的信息也要及时同步)。这类信息同步最终通过acs11来实现。任何合约都可以通过实现acs11,把信息同步到其他互相索引的链上。当前acs11严重绑定了共识合约,可以认为只是为了往侧链同步共识的数据而存在。但是从原理上讲,能用来让任何组织同步任何数据。

  • GetChainInitializationInformation用于在初始化侧链的时候往侧链同步必要的信息(调用方为主链CrossChain合约:在CreateSideChain的时候通过这个方法获取主链的BP列表,随后通过GRpc把信息传递给侧链,把BP列表在创建侧链的时候就同步到侧链的AEDPoS合约里);

  • 理论上,不同的组织可以通过跨链机制同步自己的数据,而另外一条链同步时需要使用CheckCrossChainIndexingPermission方法检查发起数据同步的Sender是否合规;这里是在侧链CrossChain合约里检查同步的发起人是否为当前时间槽的BP;

  • UpdateInformationFromCrossChain方法用来把侧链CrossChain合约已经同步到的BP列表信息写进侧链的AEDPoS合约里。

数据其实是从主链AEDPoS合约同步给侧链AEDPoS合约,跨链模块和侧链的CrossChain合约负责整个同步过程。完整流程如下图,数据来自主链共识模块中的ConsensusExtraDataProvider(这里会读取主链AEDPoS合约),最终通过UpdateInformationFromCrossChain方法写到侧链的AEDPoS合约。

Last updated