Action.requireRecepient、Action.sendInline和Transaction.send

在Ultrain的合约体系中,我们是没有办法将其它合约的代码嵌入到当前代码中来执行的,但是这并不意味不能执行其它合约中的代码。我们提供了三个方法,允许你从自己的合约中调用其它合约的代码:Action.requireRecepient()Action.sendInline()Transaction.send()。这三个方法可以提供不同的调用其它合约的方法。

Action.requireRecepient

从这个方法的名字中我们也可以看出来,这是一个通知。它的原型是
Action.requireRecepient(to: account_name): void
这个方法被调用时,to合约上部署的同名方法将被调用。假如我们编写了含有以下方法的两个合约:
合约1:

//...
    @action
    recepient(name: string): void {
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call recepient with parameter: ").s(name).flush();
        Action.requireRecipient(NAME("jack"));
    }

我们将这个合约部署到帐号rose上。

合约2:

//...
    @action
    recepient(name: string): void {
        Action.requireAuth(NAME("rose"));
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", recepient was called with parameter: ").s(name).flush();
    }

这个合约部署到了jack上。

这样我们就可以发起一笔交易来测试一下:

clultrain push action rose recepient '["messi"]' -p rose

如果一切正常的话,会产生以下输出

executed transaction: 6d2cfdd6fa9de76f00b7f8a46eeafa7acef955f8890e5976ab4a42e0ac31ae8d  112 bytes  848 us
#          rose <= rose::recepient              {"name":"messi"}
>> hi, it is rose, I will call recepient with parameter: messi
#          jack <= rose::recepient              {"name":"messi"}
>> hi, it is jack, recepient was called with parameter: messi

从上面的小示例我们可以看到以下几个事实:

  • jack的++同名方法recepient++也被调用了。
  • 传递的参数和交易发起时的参数messi是一致,不需要明确的传递这个参数。
  • rose和jack的recepient方法都在同一个transaction里被执行了。
  • rose和jack的recepient方法都具有rose的权限。

Action.sendInline

从Action.requireRecepient()的测试结果中,我们看到,requireRecepient()方法只能用相同的参数调用同名方法,这个在很多时候是很受限制的。所以,我们需要另一种方式,能够调用任意的方法。
这个新的方法就是Action.sendInline(),它允许我们调用我们想调用的任意方法。同样的,我们用例子来说明。

rose帐号的合约:

//...
    @action
    inline(name: string): void {
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call sendInline with parameter: ").s(name).flush();
        let pl = new PermissionLevel(this.receiver, NAME("active"));
        let params = new Parameters();
        params.name = "messi";
        Action.sendInline([pl], NAME("jack"), NEX("onInline"), params);
    }
//...

jack帐号的合约:

//...
@action
    onInline(name: string): void {
        Action.requireAuth(NAME("rose"));
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onInline was called with parameter: ").s(name).flush();
    }
//...

代码编译完之后,分别将它们部署到rose和jack帐号上。然后执行下面的命令:
clultrain push action rose inline '["cr7"]' -p rose
成功执行之后,将会产生以下输出:

executed transaction: 6a65a2ee39d35e469d2ea21e36665547090d2ee9795994511d3e17d48b500131  112 bytes  821 us
#          rose <= rose::inline                 {"name":"cr7"}
>> hi, it is rose, I will call sendInline with parameter: cr7
#          jack <= jack::onInline               {"name":"messi"}
>> hi, it is jack, onInline was called with parameter: messi

我们可以得到以下事实:

  • rose的合约中可以调用jack任意的方法。
  • 调用方法时,可以传递任意参数。
  • jack中的方法被调用时,具有发起时一样的权限(rose)。
  • 交易在同一个transaction中被执行。

Transaction.send

前面我们介绍了Action.requireRecepient()和Action.sendInline()的使用方法和它们的特点,其中一条就是它们都在++同一个transaction中++被执行,这也就意味着,整个执行链条上如果有一个action失败了,那么整个transaction也就失败了。有些情况下,我们并不想所有的actions作为一个事务处理,这时候我们就需要Transaction.send()。
我们来演示一下这个方法是怎么使用的。

rose的合约:

//...
    @action
    deferred(name: string): void {
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call Tx.send deferred with parameter: ").s(name).flush();

        let p = new Parameters();
        p.name = name;

        let act = new ActionImpl();
        act.account = NAME("jack");
        act.name = NEX("onDeferred");
        act.data = SerializableToArray(p);
        act.authorization.push(new PermissionLevel(this.receiver, NAME("active")));

        let tx = new Transaction(0);
        tx.actions.push(act);
        tx.header.delay_sec = 5;

        tx.send(1111, this.receiver, false);
    }
//...

jack的合约:

//...
@action
    onDeferred(name: string): void {
        Action.requireAuth(NAME("rose"));
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onDeferred was called with parameter: ").s(name).flush();
    }
//...

把合约部署到链上之后,我们执行一下rose的deferred方法(需要将rose的active权限代理给utrio.code,否则这个方法执行时会失败,设置代理的命令参考
clultrain set account permission rose active '{"threshold": 1,"keys": [{"key":"pubkey_of_rose","weight": 1}],"accounts": [{"permission":{"actor":"rose","permission":"utrio.code"},"weight":1}]}' owner -p rose):
clultrain push action rose deferred '["henry"]' -p rose )

这时候我们发现产生的返回信息:

executed transaction: 386b8c647cf6812586e7d7c2a711482eb5b9b25851f78d2a608932ffc513ffe5  152 bytes  1522 us
#          rose <= rose::deferred               {"name":"henry"}
>> hi, it is rose, I will call Tx.send deferred with parameter: henry

这里并没有jack合约中打印的log信息啊,log到哪里去了呢?如果我们可以看到节点的log的话,会发现有这样的log:

[(jack,onDeferred)->jack]: CONSOLE OUTPUT BEGIN =====================
hi, it is jack, onDeferred was called with parameter: henry
[(jack,onDeferred)->jack]: CONSOLE OUTPUT END   =====================

这也说明,这个action后面被执行了。
Transaction.send具有以下特性:

  • Transaction.send()可以调用jack任意的方法。
  • 调用方法时,可以传递任意参数。
  • jack中的方法被调用时,具有发起时一样的权限(rose)。
  • 交易在不同的transaction中被执行。

总结

通过上面三个方法的执行结果对比,我们可以做一个关于它们的小总结:

方法 调用对方的方法 参数 权限 是否事务性质
Action.requireRecepient 同名方法 相同参数 和发起方权限一致 是的
Action.sendInline 任意方法 任意参数 和发起方权限一致 是的
Transaction.send 任意方法 任意参数 和发起方权限一致 不是

源码

上面我们提供的是代码片断,下面我们附上完整的源码,供大家参考。

rose合约的源码:


import "allocator/arena";

import { Log } from "../../../src/log";
import { Contract } from "../../../src/contract";
import { NAME, RNAME } from "../../../src/account";
import { Action, ActionImpl, SerializableToArray } from "../../../src/action";
import { PermissionLevel } from "../../../src/permission-level";
import { NEX } from "../../../lib/name_ex";
import { Transaction, OnErrorValue } from "../../../src/transaction";

class Parameters implements Serializable {
    name: string;
}

class SourceContract extends Contract {
    @action
    recepient(name: string): void {
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call recepient with parameter: ").s(name).flush();
        Action.requireRecipient(NAME("jack"));
    }

    @action
    inline(name: string): void {
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call sendInline with parameter: ").s(name).flush();
        let pl = new PermissionLevel(this.receiver, NAME("active"));
        let params = new Parameters();
        params.name = "messi";
        Action.sendInline([pl], NAME("jack"), NEX("onInline"), params);
    }

    @action
    deferred(name: string): void {
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", I will call Tx.send deferred with parameter: ").s(name).flush();

        let p = new Parameters();
        p.name = name;

        let act = new ActionImpl();
        act.account = NAME("jack");
        act.name = NEX("onDeferred");
        act.data = SerializableToArray(p);
        act.authorization.push(new PermissionLevel(this.receiver, NAME("active")));

        let tx = new Transaction(0);
        tx.actions.push(act);
        tx.header.delay_sec = 5;

        tx.send(1111, this.receiver, false);
    }

    public onError(): void {
        let error = OnErrorValue.fromCurrentAction();
        Log.s("I am ").s(RNAME(this.receiver)).s(", I get a onError calling for id: ").i(error.sender_id).flush();
        if (error.sender_id == 1111) {
            let tx = error.getTransaction();
            Log.s("onError action account: ").s(RNAME(tx.actions[0].account)).flush();
            // you send deferred tx but something wrong happened.
            // you can do something to handle this case.
        }
    }
}

jack的合约源码:


import "allocator/arena";

import { Log } from "../../../src/log";
import { Contract } from "../../../src/contract";
import { RNAME, NAME } from "../../../src/account";
import { Action } from "../../../src/action";

class TargetContract extends Contract {
    @action
    recepient(name: string): void {
        Action.requireAuth(NAME("rose"));
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", recepient was called with parameter: ").s(name).flush();
    }

    @action
    onInline(name: string): void {
        Action.requireAuth(NAME("rose"));
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onInline was called with parameter: ").s(name).flush();
    }

    @action
    onDeferred(name: string): void {
        Action.requireAuth(NAME("rose"));
        Log.s("hi, it is ").s(RNAME(this.receiver)).s(", onDeferred was called with parameter: ").s(name).flush();
    }

    public filterAction(orginalReceiver: account_name): boolean {
        return true; // 这里设置本合约可以接受requireRecepient()调用。
    }
}