aelf交易生命周期

概览

交易实体

在proto文件中,Transaction的结构定义如下:

  • from:交易由谁发起,与签signature的aelf账号一定是一致的,否则会校验失败arrow-up-right(VerifySignature方法的实现:由签名恢复出的公钥能否生成与from相同的地址)。

  • to:交易逻辑所在的合约的地址。在这里,合约的proto文件可以类比为一个接口,对相同proto文件的不同实现可以分别部署为多个合约,这些合约使用地址作为唯一标识。

  • method_name:要执行的合约里的方法名。

  • params:执行方法的时候传入的参数。

  • ref_block_number & ref_block_prefix:在构造交易arrow-up-right的时候,需要获取当时对应aelf链(可能是主链或某一条侧链)的区块高度和区块哈希的前4bytes,并设置到ref_block_numberref_block_prefix,作为标记。此举有两个作用:

    • 标记构造此交易的链的分支,如果交易被构造在一个软分叉的分支上,可以防止该错误的交易被aelf主分支执行;

    • 当交易长时间得不到打包,可以根据ref_block_number值进行抛弃,也就是有一个过期高度(该高度当前被hard code为512)

  • signature:交易签名。交易的构造者需要使用自己的私钥对除该字段以外的其他所有字段进行签名。节点在收到交易的时候会进行验证。

交易Id - GetHash

  1. 验证fromtomethod_nameref_block_number这四个字段是否有效arrow-up-right

  2. 使用除了signature之外的其他字段,调用Protobuf的.ToByteArray()方法得到交易Idarrow-up-right

客户端构造交易

aelf无法离线构造交易。为了标记构造交易所基于的aelf链的分支和实现交易过期抛弃arrow-up-right,需要在交易中填充ref_block_numberref_block_prefix字段,因此交易的构造需要一个aelf全节点配合。一个aelf sdk中使用较多的典型的构造交易arrow-up-right的过程如下:

  1. 连接一个aelf全节点,调用API:api/blockChain/chainStatus

  2. 初始化一个Transaction实例,其中ref_block_number赋值为返回值的BestChainHeight,ref_block_prefix赋值为返回值的BestChainHash的前若干位置;

  3. 根据需求填写fromtomethod_nameparam参数;

  4. 使用私钥对该Transaction实例进行签名arrow-up-right

然后就得到了一个可以用来广播的交易。

客户端通知全节点

客户端通过POST连接的节点的api/blockChain/sendTransaction通知Full Node构造出来的交易,这个过程是(TransactionAppService类服务):

  1. SendRawTransactionAsync函数,收到交易

  2. 调用PublishTransactionsAsync函数

    1. 检查Contract名称、方法等

    2. 验证签名transaction.VerifySignature()

    3. 异步扔出TransactionsReceivedEvent,触发对应处理过程

      1. 交易放入交易池(见下文)

节点处理交易

circle-info

本节描述交易从节点网络层到进入节点交易池(Tx Pool)的过程。

全节点和生产节点从网络层接收到交易的时候,采用相同的处理逻辑。

GRpc层收到交易

节点处理交易的入口为网络层,具体实现位于GRpcServerServiceTransactionBroadcastStreamarrow-up-right方法。

  1. 对收到的Transaction做一系列验证(高度是否合法、是否已经过期等),并判断该交易是否已经处理过了;

  2. 通过EventBus发送一个TransactionsReceivedEvent事件。

交易进入交易池(Transaction Pool)

请注意,无论是FullNode收到了交易请求,还是FullNode或者BP通过gRPC广播了交易请求,最后都是下面的TxPoolInterestedEventsHandlerarrow-up-right来响应Event,后续处理过程是一样的。

  1. TransactionAppService的PublishTransactionsAsync在函数最后publish了TransactionsReceivedEventTxPoolInterestedEventsHandlerarrow-up-right的HandleEventAsync函数会响应TransactionsReceivedEvent事件。

  2. 注意,TxHub中首先将Transaction封装为QueuedTransaction,然后发给_processTransactionJobs来处理,这是一个TransformBlock对象:

    1. 注意,TransformBlock类是微软的一个流处理类,这个类的泛型指明输入和输出类型,这里都是QueuedTransaction,构造函数的方法是一个处理方法注入,这里注入的是UpdateBucketIndex,然后配合它的是一个ActionBlock,注入的处理方法是AcceptTransactionAsync,过程就是,如果有QueuedTransaction输入,先用UpdateBucketIndex处理,再用AcceptTransactionAsync处理,最后输出类型还是QueuedTransaction。

  3. UpdateBucketIndex只是做了一个随机数,AcceptTransactionAsync方法中,调用了TxHub拥有的ITransactionValidationService(其实现了ITransactionValidationProvider接口)的ValidateTransactionWhileCollectingAsync方法,实现了对交易的验证逻辑,注意这个方法,这个是交易放入交易池以前必须调用的验证方法,另外需要特别注意的是,其中包含交易的预执行验证,也就是说,如果在这里交易执行失败,就不会进入交易池,可以在一定程度上让aelf全节点阻挡DDoS攻击;

    1. TransactionValidationService中的ValidateTransactionWhileCollectingAsync方法,会调用其包含的IEnumerable<ITransactionValidationProvider> _transactionValidationProviders的诸多ITransactionValidationProvider的每一个,其中包含一个TransactionExecutionValidationProvider,它的ValidateTransactionAsync函数也将被调用,_plainTransactionExecutingService.ExecuteAsync,注意这个地方,这里就是在预执行合约,这里执行合约的,但是不修改stateDB。真正的执行是在出块的BP节点打包时执行,然后broadcast,其他BP收到后会首先自己执行合约,然后与出块节点发来的信息比较验证。

  4. 如果交易验证成功,会再次确认一下该交易有没有已经存储在Redis中,_blockchainService.HasTransactionAsync,注意,如果这个交易已经存储在Redis中,则不会再加入交易池以及广播。

  5. 交易通过TransactionManager,添加进aelf BlockchainDb中——交易在交易池中持久化,_transactionManager.AddTransactionAsync;

  6. 交易进入节点交易池后,会调用UpdateQueuedTransactionRefBlockStatusAsync 方法,其中向EventBus publish一个TransactionAcceptedEvent事件。

转发交易

TransactionAcceptedEventHandlerarrow-up-right处理TransactionAcceptedEvent事件,会直接调用NetworkService.BroadcastTransactionAsync,把交易转发给Peers节点。

生产节点打包交易

共识调度器请求出块

  1. 每当一个新的区块同步成功时,通过EventBus发送BestChainFoundEventData事件;

  2. ConsensusBestChainFoundEventHandler处理BestChainFoundEventData事件,调用IConsensusServiceTriggerConsensusAsyncarrow-up-right方法;

  3. TriggerConsensusAsync中根据当前节点的身份,询问共识合约能否生产区块;

  4. 如果该节点能够产生区块,就会给共识调度器一个合理的时间;

  5. 共识调度器中的倒计时结束,通过EventBus发送ConsensusRequestMiningEventData事件;

  6. ConsensusRequestMiningEventHandlerarrow-up-right处理ConsensusRequestMiningEventData事件,调用IMiningRequestService服务,开始出块流程。

从交易池取出交易

此处详见区块生产文档,这里只做简单介绍。

  1. BP节点在生产区块的过程中,会从ITransactionPoolServiceGetExecutableTransactionSetAsync方法把当前交易池中能够用于打包的交易拿出来(当然,数量上有一个上限);

  2. 但是,交易池中的交易只是用户构造和广播过来的交易,在生产区块时,还会打包系统交易。

对交易进行并行分组

通过acs2标准提供的接口为交易进行分组。分组后,同一组内的交易只能串行执行,不同组之间的交易可以并行执行。

执行交易和生成区块

MiningServicearrow-up-right中执行要打包的交易,生成用于更新区块链公共账本的state集,和一个区块。

节点同步交易(区块)

GRpc同步区块

  1. 当BP生产了一个新的区块后,调用NetworkServicearrow-up-right的BroadcastBlockWithTransactionsAsync方法把该区块广播给Peers;

  2. 其他aelf节点收到Peers转发过来的新区块后,由BlockBroadcastStreamarrow-up-right开始处理。

验证区块

当节点收到一个区块后,会以BlockValidationServicearrow-up-right为入口,对区块做最多三次验证:

  • ValidateBlockBeforeAttachAsync:在开始执行区块内的交易之前,验证一下区块信息

  • ValidateBlockBeforeExecuteAsync:在开始执行区块内的交易之前,验证一下区块头

  • ValidateBlockAfterExecuteAsync:完成区块内的交易执行之后,验证一下状态

节点高度增长

节点同步完成新的区块后,会更新本地BestChain。BestChain会作为当前节点对外提供查询服务所使用的分支。

通过lib确认交易(区块)

仅仅更新BestChain并不意味着交易的状态被最终确认了。交易的最终确认需要等共识合约抛出last irreversible block。

生命周期总结

一个交易有以下几个阶段:

  • 初始化的交易(已经填充了fromtomethod_nameparams字段)

  • 未签名的交易(填充ref_block_numberref_block_prefix字段)

  • 签名的交易(填充signature字段)

  • 广播的交易(有1个aelf节点收到了这个交易)

  • 交易池中的交易(交易通过了节点的预验证,进入了交易池)

    • 过期的交易(交易长时间未被打包,被抛弃)

  • 被打包的交易(交易被打包进了某个高度的区块中)

  • 被确认的交易(交易被打包的区块被lib机制确认为不可逆转的区块之一)

Last updated