前面两篇文章我们分析了eosio.msig
合约,中间有些内容因为篇幅没有仔细讲解,今天开始打算把一些知识点攻克一下,有些比较难的知识点,自然会详细介绍;有些呢,则看起来比较简单,然而深入进去之后,确可以加深对EOS系统的理解。今天介绍第三个知识点:deferred action与inline action。
action
EOS系统是以消息通信为基础的,action就是EOS上消息的载体。如果你想调用某个智能合约,那么就要给它发action消息。如果你的智能合约要提供某种服务,供别人调用,那该智能合约就要提供action的处理器(handler)函数,这些handler函数就是你对外界发来的action做出响应地方,也可以说是,你给别人提供服务的地方。
在没有严加区分的时候,我说的action函数
,实际上就是action的handler函数;说成action函数是为了方便。
这种通过消息调用其他智能合约接口的方式,有点类似于远程调用(RPC),实际上,它们的原理也是很类似的,都是发起方把自己的调用参数和相关数据序列化,通过某种消息通道,发送给接收方,接收方反序列化消息并解析出相关要求,然后按照要求执行操作,最后把操作结果以类似的方式返回给发起方。
RPC有同步调用和异步调用之分,同步调用是指,发起方在发送调用消息之后,会等待服务方的返回,直到调用方返回数据后才执行后面的语句。异步调用是指,发起方在发送调用消息之后,不会傻傻等待服务方的返回,而是继续执行下面的语句;当收到服务方返回数据的时候,发起方也会作出相应的处理。
与RPC调用类似,action消息也有类似的两种形式:分别对应于deferred action
和inline action
。不过,这两种action消息都是异步的,没有同步的action消息。
那么deferred action
和inline action
有什么区别呢?
inline action
inline action
有人翻译成在线 action
,有人翻译成内联action
。我倾向于后者,后者更能表明它的含义。inline action
相当于原有action的一部分,它和原有的action在同一个事务中执行;如果inline action
失败了,则整个事务也就失败了,并会回滚该事务已经产生的任何结果。
我们修改一下hello
合约,演示一下这种情况;再创建一个action处理器,命名为say:
#include
#include
using namespace eosio;
class hello : public eosio::contract {
public:
using contract::contract;
/// @abi action
void hi( account_name user ) {
require_auth( user);
print("before inline action\n");
action(
permission_level{user, N(active)},
N(hello.code), N(say),
user
).send();
print("end of hi");
}
void say( account_name user ) {
require_auth( user );
print( "Say, ", name{user});
}
};
EOSIO_ABI( hello, (hi)(say) )
与之前部署hello合约一样,我们编译并部署到hello.code
账户:
~ ./MakeContract hello
~ cleos set contract hello.code ./hello -p hello.code@active
Reading WAST/WASM from ./hello/hello.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: ec3e7517282b946c2c17e952936a1c22f89e68b88558790cd4545f1bf7f53629 2728 bytes 16026 us
# eosio <= eosio::setcode {"account":"hello.code","vmtype":0,"vmversion":0,"code":"0061736d01000000013b0c60027f7e0060000060000...
# eosio <= eosio::setabi {"account":"hello.code","abi":"0e656f73696f3a3a6162692f312e30000202686900010475736572046e616d6503736...
MakeContract
是之前我们写的一个编译脚本,还记得吧。
然后我们再向hello.code发送hi
action:
~ cleos push action hello.code hi '["user"]' -p user@active
executed transaction: 637e139bd4d332ed116c087f45605e61ec593225636ff35ee93b86096c749e39 104 bytes 1790 us
# hello.code <= hello.code::hi {"user":"user"}
>> before inline action
# hello.code <= hello.code::say {"user":"user"}
>> Say, user
可以看到print("end of hi");
没有把"end of hi"打印出来,这是因为命令行这里的打印结果有时候会有缺失,不要紧,我们可以从nodeos里看log,要这样启动nodeos
才能看到合约执行的log:
nodeos --contracts-console
然后我们再重新发送hi
action,我们可以nodeos
的output里看到绿色的执行log如下:
2018-08-27T08:53:04.692 thread-0 apply_context.cpp:28 print_debug ]
[(hello.code,hi)->hello.code]: CONSOLE OUTPUT BEGIN =====================
before inline action
end of hi
[(hello.code,hi)->hello.code]: CONSOLE OUTPUT END =====================
2018-08-27T08:53:04.692 thread-0 apply_context.cpp:28 print_debug ]
[(hello.code,say)->hello.code]: CONSOLE OUTPUT BEGIN =====================
Say, user
[(hello.code,say)->hello.code]: CONSOLE OUTPUT END =====================
在这里,你会发现,先打印出end of hi
,后打印出Say, user
。而我们的代码,是先发送say
action再打印end of hi
的。这正说明,发送say
action是异步进行的。
我们这里发送inline action
的方式是构造一个action对象,然后调用它的send方法:
action(
permission_level{user, N(active)},
N(hello.code), N(say),
user
).send();
你也可以用这种方式:
INLINE_ACTION_SENDER(hello, say)(N(hello.code), {N(user), N(active)}, {user});
INLINE_ACTION_SENDER是一个宏,它的用法格式是这样的:
INLINE_ACTION_SENDER(, )(, {, }, {});
这里通过inline action
调用自己的其他的action handler
,能正常成功,但如果调用其他的合约呢?我们下一篇文章详解其中玄机。
deferred action
有人翻译成延迟的action
,我觉得不错,我们以后也这样翻译吧。既然是延迟
,那肯定是异步了,那这里的异步与inline action
的异步有何区别呢?区别有3:
inline action
与原来的action是同一个事务。如果inline action
失败了,整个事务会回滚;deferred action
与原来的action不属于同一个事务,并且不保证deferred action
能够成功执行,如果失败了,也不会引起原有action的事务回滚。- 因为
inline action
与原来的action是同一个事务,所以他们肯定会被打包在同一个块中;而deferred action
不保证这一点,实际上,一般情况都不会在同一个区块中。deferred action
可以设置延迟多少时间后执行;而inline action
,BP会保证他们在同一个事务中,因而会尽可能快的执行它,所以不能设置延迟时间。
我们看个deferred action
的例子吧?
#include
#include
using namespace eosio;
class hello : public eosio::contract {
public:
using contract::contract;
/// @abi action
void hi( account_name user ) {
require_auth( user);
print("before deferred action\n");
eosio::transaction txn{};
const uint128_t sender_id = 100;
txn.actions.emplace_back(
action(eosio::permission_level(user, N(active)),
N(hello.code),
N(say),
std::make_tuple(user)));
txn.delay_sec = 0;
txn.send(sender_id, user);
print("end of hi");
}
void say( account_name user ) {
require_auth( user );
print( "Say, ", name{user});
}
};
EOSIO_ABI( hello, (hi)(say) )
这里故意把延迟时间delay_sec
设置为0,可以从下面nodeos的output看到:
2018-08-27T09:32:04.002 thread-0 producer_plugin.cpp:1234 produce_block ] Produced block 000105488afa390b... #66888 @ 2018-08-27T09:32:04.000 signed by eosio [trxs: 0, lib: 66887, confirmed: 0]
2018-08-27T09:32:04.419 thread-0 apply_context.cpp:28 print_debug ]
[(hello.code,hi)->hello.code]: CONSOLE OUTPUT BEGIN =====================
before inline action
end of hi
[(hello.code,hi)->hello.code]: CONSOLE OUTPUT END =====================
2018-08-27T09:32:04.505 thread-0 producer_plugin.cpp:1234 produce_block ] Produced block 00010549e3d459b0... #66889 @ 2018-08-27T09:32:04.500 signed by eosio [trxs: 1, lib: 66888, confirmed: 0]
2018-08-27T09:32:04.510 thread-0 apply_context.cpp:28 print_debug ]
[(hello.code,say)->hello.code]: CONSOLE OUTPUT BEGIN =====================
Say, user
[(hello.code,say)->hello.code]: CONSOLE OUTPUT END =====================
可以看到,即便我们把delay_sec
置为0,deferred action
还是和原有的action不在同一个区块里。
我们发送deferred action
是这么做的:
eosio::transaction txn{};
const uint128_t sender_id = 100;
txn.actions.emplace_back(
action(eosio::permission_level(user, N(active)),
N(hello.code),
N(say),
std::make_tuple(user)));
txn.delay_sec = 0;
txn.send(sender_id, user);
我们构造了一个transaction
对象, 然后调用它的send
方法,这个send的实现是这样的:
void send(const uint128_t& sender_id, account_name payer, bool replace_existing = false) const {
auto serialize = pack(*this);
send_deferred(sender_id, payer, serialize.data(), serialize.size(), replace_existing);
}
你可能注意到了,sender_id
和replace_existing
这两个参数。
sender_id
具体的值,实际上是你自己定的,可以取消还没有发生的延迟交易。cancel的方法签名如下:
int cancel_deferred(const uint128_t& sender_id);
replace_existing
如果为true,代表想要替换掉之前同一sender_id
对应的延迟action
;如果为false,代表不替换之前的,也就是新增一个当前参数指定的延迟action
。
今天就这样吧。
简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
微信公众号:know_it_well
知识星球地址:https://t.zsxq.com/QvbuzFM