EOS源码分析:transaction的一生 (2)

小插曲:关于block.timestamp 与 expiration的处理在第②步的代码注释中分析到了,expiration的正确取值直接影响到了rpc的getRequiredKeys方法的调用,否则会报错:“Invalid Transaction”,这是由于事务体属性字段出错导致。另外时区的问题也要注意,new Date得到的是UTC时间,客户端一般可根据自己所在时区自动调整。

④本地签名 signatureProvider.sign({ // 本地签名。 chainId: chainId, requiredKeys: reKeys, serializedTransaction: sTransaction }).then(function (signedTrx) { console.log(signedTrx); });

注意,这部分代码要代替第③步中的console.log(reKeys);,以达到回调顺序依赖的效果。得到的签名事务的结果如下:

{ signatures: ['SIG_K1_Khut1qkaDDeL26VVT4nEqa6vzHf2wgy5uk3dwNF1Fei9GM1c8JvonZswMdc3W5pZmvNnQeEeLLgoCwqaYMtstV3h5YyesV'], serializedTransaction: Uint8Array[117, 185, 91, 93, 114, 182, 131, 21, 248, 224, 0, 0, 0, 0, 1, 96, 140, 49, 198, 24, 115, 21, 214, 0, 0, 0, 0, 0, 0, 128, 107, 1, 96, 140, 49, 198, 24, 115, 21, 214, 0, 0, 0, 0, 168, 237, 50, 50, 8, 0, 0, 0, 87, 101, 195, 141, 194, 0] }

注意是由signatures和serializedTransaction两个属性构成的。

⑤推送事务

push_transaction方法的参数与第④步得到的结果结构是一致的,因此该对象可以直接被推送。

rpc.push_transaction(signedTrx).then(function (result) { console.log(result); })

注意,这部分代码要代替第④步中的console.log(signedTrx);,以达到回调顺序依赖的效果。得到推送结果为:

{ transaction_id: '4bc089165103879c4fcfc5331c8b03402e8206f8030c0c53374d31f5a1b35688', processed: { id: '4bc089165103879c4fcfc5331c8b03402e8206f8030c0c53374d31f5a1b35688', block_num: 47078, block_time: '2019-08-20T09:15:24.000', producer_block_id: null, receipt: { status: 'executed', cpu_usage_us: 800, net_usage_words: 13 }, elapsed: 800, net_usage: 104, scheduled: false, action_traces: [ [Object] ], except: null } }

注意receipt响应值中包含了status: 'executed的内容,这个属性将是下文着重提及的。

源码位置 小结

事务的打包与签名是在客户端通过eosjs等工具完成的。从应用角度来看,直接使用api提供的transact是最简单的方法,但如果要理解其中的逻辑,可以自行编写一遍,但没必要重新做封装,毕竟transact已经有了。

节点的处理:校验、执行和广播

经过上一节,请求从客户端发出来到达了RPC供应商。RPC服务的提供者包括出块节点和非出块节点,一般来讲是非出块节点。非出块节点也会通过EOSIO/eos搭建一个nodeos服务,可以配置选择自己同步的数据区域,不具备出块能力。非出块节点如果想具备释放RPC服务的能力,需要配置chain_api_plugin,http_plugin。这部分内容可以转到《EOS行为核心:解析插件chain_plugin》详述。

push_transaction的返回结构体与上一节的响应数据体是一致的。

struct push_transaction_results { chain::transaction_id_type transaction_id; fc::variant processed; };

记住这两个字段,然后向上滑动一点点,观察具体的响应数据内容。

关于RPC的push_transaction方法的论述链接。继承这篇文章的内容,下面进行补充。

transaction_async

事务的同步是通过transaction_async方法完成的,调用关系是chain_plugin插件通过method机制跳转到producer_plugin中。
此时事务停留在非出块节点的chain_plugin.cpp的void read_write::push_transaction方法中。除了传入的事务体对象参数外,还有作为回调接收响应的push_transaction_results结构的实例next。进入函数体,首先针对传入的参数对象params(具体内容参见上一节④本地签名最后的签名事务),转为transaction_metadata的实例ptrx。接下来调用

app().get_method<incoming::methods::transaction_async>()

这是method模板的语法,方法后紧跟传入等待同步的参数ptrx等以及一个result接收结果的对象(result由非出块节点接收,这部分将在下一小节展开)。transaction_async作为method的Key值,被声明在incoming::methods::transaction_async命名空间下。app应用实例的method集合中曾经注册过该Key值,注册的方式是关联一个handle provider。这段注册的代码位于producer_plugin.cpp,

incoming::methods::transaction_async::method_type::handle _incoming_transaction_async_provider;

该provider内容实际上是调用了producer_plugin.cpp的on_incoming_transaction_async方法,正在同步进来的事务。接下来调用process_incoming_transaction_async方法,处理正在进入的事务同步。这个方法首先会判断当前节点是否正在出块,如果未出块则进入_pending_incoming_transactions容器,这是一个双队列结构。

这些等待中的事务将会在出块节点开始出块时通过start_block方法触发重新回到process_incoming_transaction_async方法进行打包。

transaction_ack

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsszzf.html