2.8 自定义Dispatchers
到现在为止,我们已经使用叫做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); */ \
} \
} \
} \
开发者只需要从合约中指定code
和action
的名字到宏中,所有底层的C代码映射逻辑就交给宏来生成了.我们已经见过使用宏的例子了,例如EOSIO_ABI(hello,(hi) )
,hello
和hi
都是来自于合约的值.
在这个例子中,你能看到一个函数,apply
.它只记录action的发表(delivered),不做其他任何检查.只要区块producers允许,任何人都可以在任何时间发表(deliver)任何action.如果没有任何必要的签名,合约将按消耗了的带宽进行支付.
apply
apply
是action handler,它用来监听所有传入的actions并根据函数的说明进行做出反应.apply
函数需要两个参数,code
和action
.
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 );
}
}
};
- 在分发(dispatching)action前先检查
code==self
- 实例化
addressbook
- 将
action
传入switch()
- 将引用传给我们的
addressbook
实例,并将addressbook
class的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需要比较丰富的配置,但我们可以从自动设置_self
的execute_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时尤其有用.
- 实例化
addressbook
- 为每一个
code
,action
对提供单独的if
语句 - 传入
addressbook
实例的引用,并将addressbook
class的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
是非常适合的,排除了令人讨厌了的内容以及很好的减小了引入逻辑错误的概率.