EOS智能合约开发系列(16): deferred action与inline action

前面两篇文章我们分析了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 actioninline action。不过,这两种action消息都是异步的,没有同步的action消息。

那么deferred actioninline 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:

  1. inline action与原来的action是同一个事务。如果inline action失败了,整个事务会回滚;deferred action 与原来的action不属于同一个事务,并且不保证deferred action 能够成功执行,如果失败了,也不会引起原有action的事务回滚。
  2. 因为inline action与原来的action是同一个事务,所以他们肯定会被打包在同一个块中;而deferred action不保证这一点,实际上,一般情况都不会在同一个区块中。
  3. 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_idreplace_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

你可能感兴趣的:(EOS智能合约开发系列(16): deferred action与inline action)