2.8 自定义Dispatchers

优质
小牛编辑
130浏览
2023-12-01

到现在为止,我们已经使用叫做EOSIO_ABi的宏来处理dispatching actions,将WASM API sent到我们函数的在我们的合约中.

EOSIO_ABI( addressbook, (upsert)(notify)(erase) )

EOSIO_ABI宏以普通的模式将dispatcher抽象出来.

  • 基类,myclass
  • 定义好的,需要披露的actions
  • A dispatched action's arguments are positional.

Step 1:理解EOSIO_ABI宏

每个智能合约都必须提供一个apply action handler,或"dispatcher".dispatcher是一个可以监听所有传入actions和执行所需action的函数.为了对指定的action进行响应,需要code来对特定actions请求进行识别和响应.apply使用receiver, code,和action输入参数作为过滤器,以映射到实现了特定actions的所需函数.apply函数能使用诸如下面的例子来基于code参数过滤.

if (code == N(${contract_name}) {
   // your handler to respond to particular code
}

如果给定了一个code,可以通过过滤action参数来对特定action进行响应.通常结合code filter一起使用:

if (action == N(${action_name}) {
    //your handler to respond to a particular action
}

为了简化合约开发者的工作,EOS_ABI宏压缩了apply函数的低级action映射细节,使开发者可以专注于他们的应用实现.

#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( code == self ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
} \

开发者只需要从合约中指定codeaction的名字到宏中,所有底层的C代码映射逻辑就交给宏来生成了.我们已经见过使用宏的例子了,例如EOSIO_ABI(hello,(hi) ),hellohi都是来自于合约的值.

在这个例子中,你能看到一个函数,apply.它只记录action的发表(delivered),不做其他任何检查.只要区块producers允许,任何人都可以在任何时间发表(deliver)任何action.如果没有任何必要的签名,合约将按消耗了的带宽进行支付.

apply

apply是action handler,它用来监听所有传入的actions并根据函数的说明进行做出反应.apply函数需要两个参数,codeaction.

code filter

为了对特定的action进行响应,以下面为例构造apply函数.你可能还需要通过省略code filter构造一个响应给普通的action.

if (code == N(${contract_name}) {
    // your handler to respond to particular code
}

你也可以在代码块中对各个action定义响应.

action filter

为了响应特定的action,以下面为例构造你的apply函数.通常会结合code filter一起使用.

if (action == N(${action_name}) {
    //your handler to respond to a particular action
}

Step 2:将EOSIO_ABI转换为各式各样的dispatcher

先前教程的addressbook中,我们使用EOSIO_ABI宏作为我们的dispatcher.我们现在会将该宏的功能性应用到各种可行的dispatcher设计模式.

EOSIO_ABI( addressbook, (upsert)(notify)(erase) )

Exploded EOSIO_ABI Dispatcher

该示例dispatcher很接近的模仿EDOSIO_ABI宏是如何工作的当你striped away该宏的时候.因为这一dispatcher使用execute_action(),我们的actions会接受positional arguments以让eosio-cpp的ABI generator能被使用.如果你从另一个合约处理actions,这不是一个好的解决方案.

extern "C" {
  void apply(uint64_t receiver, uint64_t code, uint64_t action) {
    auto self = receiver;
    if(code==self){
      addressbook _addressbook(self);
      switch(action){
          case "upsert": return execute_action( &_addressbook, &addressbook::upsert );
        case "notify": return execute_action( &_addressbook, &addressbook::notify );
        case "erase":  return execute_action( &_addressbook, &addressbook::erase );
      }
  }
};
  1. 在分发(dispatching)action前先检查code==self
  2. 实例化addressbook
  3. action传入switch()
  4. 将引用传给我们的addressbook实例,并将addressbookclass的public action调用者引用传给execute_action函数.

上面这一设计模式是EOSIO_ABI和EOSIO_API宏的简单分解视图(在不会真实使用它们的情况下),因此是"Exploded EOSIO_ABI Dispatcher".

上面的exploded dispatcher pattern真的只在处理内部actions时适用.如果你的合约需要处理从其他合约通过特定途径传过来的action,使用switch()引进(introduces)逻辑含糊不清,这可能会引入(introduce)安全隐患.如果你喜欢该设计模式的折衷(the tradeoffs of this pattern),但是又想从其他合约处理传入actions,看接下来的例子.

Flexible/Compatible Dispatcher

这一设计模式通过牺牲可维护性来提高更多对安全性的控制.使用if...else if语句来作为switch的替换,固然能提供更多细粒度.

这一dispatcher在上一个案例的eosio.token中添加了一个 transfer action.

这一设计模式很好的综合了约定(convention)和配置(configuration) .虽然该dispatcher需要比较丰富的配置,但我们可以从自动设置_selfexecute_action中受益(we utilize the benefits of execute_action to automatically set _self ),还能将所给action的参数解包(unpack并使用这些解包参数来调用action(class method).因此,我们的cations(class methods)可以接收positional arguemnts以及使用eosio-cpp的ABI generator.它在dispacher中很灵活并且与ABI兼容.

之前编写的addressbook合约不包含transfer action,现在将其纳入仅用于示范目的.

extern "C" {
  void apply(uint64_t self, uint64_t code, uint64_t action) {
    addressbook _addressbook(self);
    if(code==self && action==N(upsert)) {
      execute_action( &_addressbook, &addressbook::upsert );
    }
    else if(code==self && action==N(notify)) {
      execute_action( &_addressbook, &addressbook::notify );
    }
    else if(code==self && action==N(erase)) {
      execute_action( &_addressbook, &addressbook::erase );
    }
    else if(code==N(eosio.token) && action==N(transfer)) {
      execute_action( &_addressbook, &addressbook::transfer );
    }
  }
};

上面这个设计模式和"Exploded ABI Macro"一样,但是使用更冗长的条件处理action.这能让actions的处理更精确,这在处理来自外部合约的action时尤其有用.

  1. 实例化addressbook
  2. 为每一个code,action对提供单独的if语句
  3. 传入addressbook实例的引用,并将addressbookclass的public action调用者引用传给execute_action函数.

Fleming's Dispatcher(Flexible)

这一dispatcher能将绝大多数的安全相关逻辑与空放到action handler中,大师不能使用eosio-cpp的ABI gennerator.

Custom EOSIO_ABI macro

Step 3:安全性,安全性,安全性...

合约的第一条安全线从dispatcher开始.理解如何处理向合约dispatching actions是很必要的,以此限制action中的逻辑暴露.在编写自定义dispatcher时一定一直都要保持小心,并了解每种实现方法的安全隐患.

Conclusion

以上提供的示例都能提供几乎一样的结果,但是在可维护性和显示安全考虑方面有不同的权衡.采用哪种实现方法需要基于合约的使用情况和个人偏好来选择.

对只执行内部public actions的简单合约来说,EOSIO_ABI是非常适合的,排除了令人讨厌了的内容以及很好的减小了引入逻辑错误的概率.