ACS:AElf Contract Standard,AElf合约标准,顾名思义,就是开发AElf智能合约时需要继承和实现的一些接口。
所有的ACS都通过protobuf的service定义。
ACS4作为ACS之一,是实现任意一种共识合约时,需要实现的一些接口。
本文主要讨论共识标准接口的可行性和AElf中为共识合约的实现所提供的接口及其他支持。
抽象共识的思路
站在实现区块链共识的角度,我们主要关心三件事:
- 谁可以产生区块,如PoW共识允许每一个人参与算力竞争,PoS和DPoS共识则要对此做出一定限制;
- 如果可以产生区块,那么应该在什么时候产生,或者说当前的时间能不能开始尝试广播区块;
- 作为一个区块链全节点,应该怎么验证这个区块的合法性。
针对这三点,我们很容易想到三类接口:
- 输入公钥,判断这个公钥有没有资格产生区块,PoW共识直接返回true就可以了,DPoS可能会对大部分人返回false。
- 输入公钥,返回这个公钥下一次能够产生区块的时间戳,或者返回这个公钥当前能不能产生区块。
- 全节点得到区块头(block header)之后,输入从中提取到的共识数据,验证这个区块的1和2相关信息,即,PoW共识需要验证nonce的合法性,DPoS共识需要验证新区块的生产者身份合法性、生产者是否尊重自己的时间槽,得到验证结果。
除此以外,为了得到接口3中的输入,即区块头中的共识数据,至少还需要为区块生产者提供一个生成区块头共识数据的方法。
AElf中的实践
首先,AElf的主链选择的共识属于DPoS,本文虽说讨论的是通用共识接口,也免不了倾向于多讨论(AE)DPoS。
其次,所有的共识合约标准上的接口,都是只读的,因为单纯获取这些数据无需改动WorldState。(WorldState是以太坊中的概念,AElf在开发中称用于存储合约的状态的数据库为State DB;除此之外还有Chain DB,用于存储区块本身,包括区块中的交易。)
ACS4中合并了接口1和接口2,得到一个接口:
rpc GetConsensusCommand (google.protobuf.BytesValue) returns (ConsensusCommand) { option (aelf.is_view) = true; } message ConsensusCommand { int32 NextBlockMiningLeftMilliseconds = 1;// How many milliseconds left to trigger the mining of next block. int32 LimitMillisecondsOfMiningBlock = 2;// Time limit of mining next block. bytes Hint = 3;// Context of Hint is diverse according to the consensus protocol we choose, so we use bytes. google.protobuf.Timestamp ExpectedMiningTime = 4; }
很显然,NextBlockMiningLeftMilliseconds的值取决于ExpectedMiningTime(预期出块时间)与当前时间(调用这个接口的时间)的差值。
LimitMillisecondsOfMiningBlock是系统分配给这个块用于打包的时间,决定了这个区块能够打包多少用户发送的交易(给打包用户发送的交易留了多长时间)。
Hint字段用来传递一些单独的状态,取决于选用的共识协议,在PoW中这个字段可能显得不必要,而AEDPoS定义了一些区块类型,如Normal Block,Extra Block,只更新少量共识信息的Tiny Block。这些,都需要反应在Hint中——共识合约不仅需要告知区块生产者多久以后可以着手产生区块,也需要告知他应该产生什么类型的区块,而不同区块会更新不同的共识信息。除此之外,引入Hint字段也能为实现其他共识协议提供了更多的可能。
这个接口分别会在链刚刚启动时、在本地Best分支上同步完一个新的区块(无论这个区块是不是自己产生的)时、区块生产者在执行自己的区块前的验证中忽然发现当前时间已经超出了自己的时间槽(即调用下文的ValidateConsensusBeforeExecution接口)后被调用。
调用之后,Consensus Service会将NextBlockMiningLeftMilliseconds传入共识的调度器中,时间一到,就会去触发生产区块的逻辑。
注意,调度器中的时间是可以随时被覆盖的。事实上,每一次同步一个新的区块,调度器都会被重新初始化。
事关接口3,ACS4拆分出两个接口:
rpc ValidateConsensusBeforeExecution (google.protobuf.BytesValue) returns (ValidationResult) { option (aelf.is_view) = true; } rpc ValidateConsensusAfterExecution (google.protobuf.BytesValue) returns (ValidationResult) { option (aelf.is_view) = true; } message ValidationResult { bool Success = 1; string Message = 2; }
也就是说,AElf中,每一个区块执行前后,会分别调用以上两个接口的实现对区块头进行验证。验证的逻辑位于实现ACS4的合约中,验证的数据就不得不基于State DB。因为你不能凭空去验证区块头里的共识信息对不对,共识合约里需要存储最近的共识信息,才能给最近的共识信息提供验证的支撑。
既然验证的数据基于State DB,而ACS4的接口都是只读的方法,那就需要单独生成一笔交易去更新State DB(区块链的特性,更新WorldState或者State DB必须通过执行交易来完成)。
这样,ACS4除了要提供生成区块头信息的方法之外,还需要能够返回一个(或者一组)能够更新State DB中的共识信息的交易。这个交易会作为系统交易被生成,然后在打包过程中,最先添加进区块中。系统交易的执行会先于普通交易,这样普通交易在执行时会获取到最近更新的系统提供的信息,如hash-commit-reveal策略下的随机数。在AElf中,每个区块会至少包含一个共识系统交易。
ACS4的最后两个与更新共识信息相关的接口如下:
rpc GetInformationToUpdateConsensus (google.protobuf.BytesValue) returns (google.protobuf.BytesValue) { option (aelf.is_view) = true; } rpc GenerateConsensusTransactions (google.protobuf.BytesValue) returns (TransactionList) { option (aelf.is_view) = true; } message TransactionList { repeated aelf.Transaction Transactions = 1; }
在AElf的kernel模块代码中,GetInformationToUpdateConsensus会在生成区块头的过程中被调用,该接口返回的数据会作为区块头的Extra Data之一,用于收到区块的人验证共识信息。GenerateConsensusTransactions接口会在生成区块头之后,进一步生成系统交易的过程中被调用。补充一句,ValidateConsensusAfterExecution接口本质上只是为了验证区块头中的共识信息和执行完共识系统交易后State DB中的共识信息的一致性,防止区块生产者“出尔反尔”。
ACS4的完整代码在这里。关于对本文或者ACS4或者其他AElf共识组件的建议都会被认真对待,欢迎留言、私信甚至直接去github开issue。
以上是AElf区块链共识合约标准ACS4的大致介绍,下一篇文章会介绍其中最为复杂的GetConsensusCommand接口在AEDPoS共识下的具体实现。