2.7 自定义调度程序

简介:EOSIO_ABI宏

到目前为止,我们一直在使用一个名为EOSIO_ABI的宏来处理被发送到WASM API的合约中的函数操作。

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

EOSIO_ABI宏以一种普通的模式抽象调度程序.

在给定代码中,程序可以通过过滤动作参数来响应特定动作。 这通常与代码过滤器一起使用。

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

为了简化合约开发人员的工作,EOSIO_ABI宏封装了apply函数的低级动作映射细节,使开发人员能够专注于他们的应用程序实现。

##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); */ \
} \
} \
} \

开发人员只需要在宏中指定合同中的代码和操作名称,并且所有底层C代码映射逻辑都由宏生成。 上面可以看到宏的使用示例,即EOSIO_ABI(hello,(hi)),其中hello和hi是来自合约的值。在这个例子中,你可以看到有一个apply函数。 它所做的就是记录所交付的行为,不做任何其他检查。 如果区块生产者允许,任何人都可以随时提供任何操作。 如果没有任何必要的签名,合约将按消耗的带宽计费。

apply

apply是动作处理程序,它监听所有传入的动作并根据函数中的规范做出反应。 apply函数需要两个输入参数,即代码和操作。

code filter

为了响应特定的动作,按如下方式构造应用函数。 您还可以通过省略代码过滤器来构建对常规操作的响应。

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

您还可以在代码区块中定义对相应操作的响应。

action filter

要响应特定操作,请按如下方式构建应用函数。 这通常与代码过滤器结合使用。

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

第2步:将EOSIO_ABI转换为各种样式的调度程序

在以往教程中的地址簿合同,我们使用EOSIO_ABI宏作为我们的调度程序。 我们现在将采用此宏的功能并将其应用于各种可能的调度程序模式。

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

EOSIO_ABI调度程序

此示例调度程序非常类似于EOSIO_ABI宏的分解化的工作方式。 由于此调度程序使用execute_action(),因此我们的操作将接收位置参数,使用eosio-cpp的ABI生成器。

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": execute_action( &_addressbook, &addressbook::upsert );
case "notify": execute_action( &_addressbook, &addressbook::notify );
case "erase": execute_action( &_addressbook, &addressbook::erase );
}
}
};

  1. 在分派操作之前检查code== self。
  2. 实例化地址簿
  3. 向switch()传入操作
  4. 将对地址簿实例的引用以及对addressbook类的公共操作的调用者引用传递给execute_action函数

上面的模式只是EOSIO_ABI和EOSIO_API宏的分解视图而没有进行实际,因此称之为是“Exploded EOSIO_ABI Dispatcher”。上面的爆炸式调度程序模式实际上仅适用于处理内部操作。 如果您的合约需要以特定方式处理其他合同发送的操作,则使用switch()会引入逻辑模糊,这可能会引入漏洞。 如果你喜欢这种方式,但希望处理来自另一个合约的收入行为,请参阅下一个示例。

灵活简单的调度

此模式以可维护性为代价提供了对安全性有更多控制。 优化if … else if语句而不是简单地进行转换。
此调度程序在最后一种情况下添加了eosio.token的传输操作。这种模式是约定和配置之间的良好组合。 虽然调度程序本身需要一些繁琐的配置,但我们利用execute_action的好处来自动设置_self并解压缩给定操作的参数并使用这些解压缩的参数调用操作(类方法)。 因此,我们的动作(类方法)将接收位置参数,因此可以使用eosio-cpp的ABI生成器。 它在调度员中很灵活,但在行动中却极简,因此得名。

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宏”模式相同,但使用更详细的条件进行操作处理。 这样可以更精确地处理操作,这在处理来自外部合同的操作时尤其有用。

  1. 实例化地址簿
  2. 对每个代码和操作对添加一个单独的if语句。
  3. 将地址簿实例的引用以及对addressbook类的公共操作的调用者引用传递给execute_action函数。

    fleming调度(灵活)

    此调度程序将大多数安全逻辑和控件放在操作处理程序中,但是,不能使用eosio-cpp的ABI生成器。 这个调度程序几乎完全重写了我们已经完成的地址簿合约。 我们不会重写整个合约,但会重写其中一个动作来演示如何使用这种模式。

为了处理这些请求,我们将调度程序中的安全逻辑移动到操作中。 这可以提供更多控制,但可能会引入冗余,特别是对于大型合同。 最后,它完成了与上述模式相同的所有点,同时允许更多信息进入范围(即代码)。 在某些情况下,访问操作中的代码可能很有用。 这种模式在调度程序和操作中提供了最大的灵活性,因此“完全灵活”。

  1. 检查代码== self以确保安全性。
  2. 将操作请求解压缩到可以访问参数的变量中,例如data.user
  3. 无论我们想要什么,这个合约或self都可以被调用。使用execute_action或使用execute_action的宏时,“self”会自动设置为_self。

这种模式与eosio-cpp的ABI生成器不兼容,用这种模式编写的合约将需要手工编写的ABI文件。

自定义 EOSIO_ABI 宏

上述调度程序可能不适合采取许多行动的复杂合约。 使用自定义调度程序可能会引入难以维护的冗余代码,并且可能会引入漏洞的意外后果。最后一个教程是编写一个自定义宏来考虑您的独特需求。 如果您的合约需要处理来自外部合约的操作,并且您的合约具有比上述模式更实际的操作,那么您应该真的必须这样做。
下面是您可能已经看过的常见自定义宏的代码。

  1. 检查代码== self以及该操作是否未转移。 如果省略此条件,您可能会在合约中引入漏洞。 如果代码是eosio.token并且操作是传输,它允许操作。 此检查可防止另一个具有转移功能的合同利用您的合约。
  2. 实例化TYPE(标准的C ++ 11类)
  3. 添加操作开关
  4. 使用EOSIO_API宏,它将每个MEMBER的大小写插入到您的交换机中。 在这种情况下,它使用与灵活简单的调度中演示的模式非常相似的模式调用execute_action

然后,您可以像使用提供的EOSIO_ABI宏一样使用此宏。 以下内容包括虚构的转移行动,以使此演示相关。

Copy

EOSIO_ABI_CUSTOM( addressbook, (upsert)(notify)(erase)(transfer) )

之前撰写的地址簿合约不包括转移行动,上面列出的是为了证明假设的一个案例。

第3步:安全,安全,安全……

您的合约的第一道安全线始于您的调度员。 了解如何处理向合约分派行动是必要的,以限制行动中的逻辑暴露。 编写自定义调度程序时务必小心谨慎,并注意每个模式的安全隐患。

结论

除了为相关性添加的额外eosio.token案例之外,所有示例都得到了相同的结果,但具有不同的优点和缺点。 合约中具体采用的方法取决于您的使用案例和个人偏好。
EOSIO_ABI非常合适仅执行内部公共操作的简单合约,可以消除数据不足并大大降低引入逻辑错误的可能性。

原文链接:https://developers.eos.io/eosio-home/docs/writing-a-custom-dispatcher