EOS源码分析:transaction的一生

最近在处理智能合约的事务上链问题,发现其中仍旧有知识盲点。原有的认识是一个事务请求会从客户端设备打包签名,然后通过RPC传到非出块节点,广播给超级节点,校验打包到可逆区块,共识确认最后变为不可逆区块。在执行事务完毕以后给客户端一个“executed”的状态响应。基于这个认识,本文将通过最新EOS代码详细分析验证。

关键字:EOS,区块链,eosjs,transaction,签名,节点,出块节点,事务校验,事务广播

客户端的处理:打包与签名

客户端设备可以通过eosjs完成本地的事务体构建。下面以调用hello智能合约为例。

注意:eosio.cdt的hello合约中hi方法的参数名为nm,而不是user,我们下面采用与cdt相一致的方式。

方便起见,可以首先使用eosjs-api提供的transact方法,它可以帮助我们直接将事务体打包签名并推送出去。

(async () => { const result = await api.transact({ actions: [{ account: 'useraaaaaaaa', // 合约部署者,是一个EOS账户 name: 'hi', // 调用方法名,hello合约的一个方法。 authorization: [{ // 该方法需要的权限,默认为合约部署者权限 actor: 'useraaaaaaaa', permission: 'active', }], data: { // 方法参数 nm: 'water' }, }] }, { blocksBehind: 3, // 顶部区块之前的某区块信息作为引用数据,这是TAPoS的概念。 expireSeconds: 30, // 过期时间设置,自动计算当前区块时间加上过期时间,得到截止时间。 }); })();

然后我们可以进入transact方法中查看,仿照其实现逻辑,自行编写一个完整流程的版本。

“打包”在EOS中与“压缩”,“序列化”,“转hex”等是相同的,因此所有之前提到过的压缩,转化等概念都是指同一件事。例如compression:none属性,之前也提到过zlib的方式;cleos中convert命令;rpc中的abi_json_to_bin等。

①打包Actions

actions的结构与前面是相同的。

// actions结构与上面相同,这是我们与链交互的“个性化参数” let actions = [{ account: 'useraaaaaaaa', name: 'hi', authorization: [ { actor: 'useraaaaaaaa', permission: 'active' } ], data: { nm: 'seawater' } }]; // 打包Actions let sActions = await api.serializeActions(actions);

eosjs中通过serializeActions方法将Actions对象序列化,序列化会把data的值压缩(可理解为密文传输参数以及参数的值),最终变为:

[{ account: 'useraaaaaaaa', name: 'hi', authorization: [{ actor: 'useraaaaaaaa', permission: 'active' }], data: '0000005765C38DC2' }] ②打包Transaction

首先设置事务Transactions的属性字段。

let expireSeconds = 3; // 设置过期时间为3秒 let blocktime = new Date(block.timestamp).getTime(); // 获得引用区块的时间:1566263146500 let timezone = new Date(blocktime + 8*60*60*1000).getTime(); // 获得+8时区时间:1566291946500 let expired = new Date(timezone + expireSeconds * 1000); // 获得过期时间:2019-08-20T09:05:49.500Z let expiration = expired.toISOString().split('.')[0]; // 转换一下,得到合适的值:2019-08-20T09:05:49 expiration: expiration, // 根据延迟时间与引用区块的时间计算得到的截止时间 ref_block_num: block.block_num, // 引用区块号,来自于查询到的引用区块的属性值 ref_block_prefix: block.ref_block_prefix, // 引用区块前缀,来自于查询到的引用区块的属性值 max_net_usage_words: 0, // 设置该事务的最大net使用量,实际执行时评估超过这个值则自动退回,0为不设限制 max_cpu_usage_ms: 0, // 设置该事务的最大cpu使用量,实际执行时评估超过这个值则自动退回,0为不设限制 compression: 'none', // 事务压缩格式,默认为none,除此之外还有zlib等。 delay_sec: 0, // 设置延迟事务的延迟时间,一般不使用。 context_free_actions: [], actions: sActions, // 将前面处理好的Actions对象传入。 transaction_extensions: [], // 事务扩展字段,一般为空。 }; let sTransaction = await api.serializeTransaction(transaction); // 打包事务

注释中没有对context_free_actions进行说明,是因为这个字段在《区块链 + 大数据:EOS存储》中有详解。
eosjs中通过serializeTransaction方法将Transaction对象序列化,得到一个Uint8Array类型的数组,这就是事务压缩完成的值。

Uint8Array[198, 164, 91, 93, 21, 141, 3, 236, 69, 55, 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] ③准备密钥

密钥的准备分两步:首先通过已处理完毕的事务体获得所需密钥requiredKeys,然后在本地密钥库中查看可用密钥availableKeys,比对找到对应密钥。

signatureProvider.getAvailableKeys().then(function (avKeys) { // 获得本地可用密钥 // 查询事务必须密钥 rpc.getRequiredKeys({transaction: transaction, availableKeys: avKeys}).then(function (reKeys) { // 匹配成功:本地可用密钥库中包含事务必须密钥 console.log(reKeys); }); });

由于执行结果存在先后的依赖关系,因此要采用回调嵌套的方式调用。最后成功获得匹配的密钥:

[ 'PUB_K1_69X3383RzBZj41k73CSjUNXM5MYGpnDxyPnWUKPEtYQmVzqTY7' ]

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

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