PlatON Cross | PlatON上的WASM智能合约开发(七)——进阶(通用代理1)

本文转载自微信公众号 PlatON Cross

原创作者: PlatON Cross小编

PlatON Cross | PlatON上的WASM智能合约开发(七)——进阶(通用代理1)

熟悉传统面向对象设计模式的朋友应该知道,最原始的代理者接口与被代理实例的接口几乎是一一对应的,也就是说,一般情况下,传统代理机制几乎是针对某一类型对象实例的。

在区块链领域,智能合约的在线升级一直相对麻烦,大多公链都会面临以下问题:

1、合约地址的变更,因为合约升级就意味着需要重新部署(EOS支持在不变更地址的情况下进行合约重部署)。

2、abi的变更,虽然可以在设计之初,对接口尽量考虑周全,但难免遇到在新增功能时,需要增加新接口的情况。

因此,传统的代理模式在区块链领域并不太适合,而合约升级又是区块链项目的推进过程中难以避免的事件。在前序文章中,Cross团队已经对代理合约的部分机制进行了介绍,接下来,将向大家介绍一种相对通用的代理机制,这种代理机制主要包含两部分:

1、代理调用的通用化(Access agent)。

2、合约映射管理(Contract manager)。

本篇主要针对代理调用的通用化进行简单的讲解。


机制简介

PlatON Cross | PlatON上的WASM智能合约开发(七)——进阶(通用代理1)

client可通过proxy contract获取相关业务合约的基本信息,再通过代理接口访问service contract提供的具体服务。client无需事先知晓service contract具体地址、abi等部署信息,这样能够在一定程度上,使service contract的升级、维护、更新对client透明。

本篇文章将讲述proxy contract中的Access agent机制,contract manager机制将在后续系列中进行讲解。


合约开发

● service contract

#include <platon/platon.hpp>

#include <vector>

CONTRACT AddressParam : platon::Contract {

public:

    PLATON_EVENT1(addCallResult, std::string, int)

    ACTION void init(){}

    ACTION bool testAddFromGeneralProxy(std::vector<int>& eles)

    {

        int rst = 0;

        for (auto itr = eles.begin(); itr != eles.end(); ++itr)

        {

            rst += *itr;

        }

        PLATON_EMIT_EVENT1(addCallResult, “sum” , rst);

        return true;

    }

};

PLATON_DISPATCH(AddressParam, (init)(testAddFromGeneralProxy))

说明:

1、该合约的功能非常简单,主要发挥作用的是ACTION类型的接口testAddFromGeneralProxy,接收一个int数据列表,然后对其求和,将结果通过PLATON_EMIT_EVENT1返回。

2、实际上,在交易(ACTION)类型的智能合约方法调用中,通过事件来返回用户想要了解的执行结果,是一种非常常用的方式,具体可参见《PlatON上的WASM智能合约开发(三)——事件机制》《PlatON上的WASM智能合约开发(六)——进阶(代理调用的事件捕获与解析)》

3、service contract的被代理方法,推荐(非强制)采用bool类型的返回值,这样代理合约可以比较直观的了解其执行状态。

● proxy contract

#include <platon/platon.hpp>

#include <string>

class AddrProxy: public platon::Contract{

public:

    ACTION void init(){}

    ACTION bool generalCall(const std::string& contractAddr, const platon::bytes& params)

    {

        auto rst = platon::make_address(contractAddr);

        if (!rst.second)

        {

            platon::internal::platon_throw(“invalid contract address!”);

            return false;

        }

        else

        {

            bool result = platon_call(rst.first, params, uint32_t(0), uint32_t(0));

            if(result)

            {

                bool callRst = false;

                platon::get_call_output(callRst);

                return callRst;

            }

            else{

                platon::internal::platon_throw(“proxy call failed!”);

                return false;

            }

        }

    }

};

PLATON_DISPATCH(AddrProxy, (init)(generalCall))

说明:

1、该合约实现Access agent的功能,对应的具体接口为ACTION类型的generalCall。

2、需要解释的是,由于本篇中尚未对contract manager映射机制进行介绍,所以generalCall的第一个参数暂时使用合约地址。

3、generalCall的第二个参数是platon::bytes类型(具体为std::vector<byte>,详情参见”.\platon-cdt\platon.cdt\include\platon\common.h”),client在通过proxy访问service contract时,service方法名、输入参数会基于合约abi进行编码打包,PlatON在client SDK中提供了编码打包的接口(node.js、python亲测有效,java尚未进行测试),编码打包接口的使用方法将在下文“合约调用”中详细说明。

4、在generalCall的具体实现中,首先通过调用platon_call(以service contract地址、编码字好的调用信息作为参数),返回值result标识调用过程本身的执行结果,如果成功,则继续调用platon::get_call_output,获取调用执行的具体情况。


合约部署

● PlatON主网

在PlatON主网进行合约部署,需要使用platon-truffle工具完成,详细操作参见《PlatON上的WASM智能合约开发(一)——合约开发入门》

● alaya

目前,官方已经发布了alaya网络上进行合约开发、部署的IDE——PlatON Studio,其安装、配置、启动以及新建合约项目、合约编译、部署的详细教程可参见https://github.com/ObsidianLabs/PlatON-Studio,本文不再赘述。

PlatON Studio还提供了alaya网络上的合约调试工具,如下图所示:

PlatON Cross | PlatON上的WASM智能合约开发(七)——进阶(通用代理1)

说明(对应图中的编号):

1、合约地址,输入合约地址后,即可看到相应合约的详细信息。

2、合约相关的ACTION接口方法,下方为接口调用需要输入的参数。

3、执行按钮,点击后,将执行相应合约接口的调用。

4、合约相关的CONST接口方法。

5、CONST接口方法对应的输入参数。

6、CONST方法的执行返回值。

7、合约访问的快捷栏,可选择已部署的合约,进入合约调试的详细页面。


合约调用

本篇文章通过platon-clientsdk-python进行合约调用的测试,在node.js中相关操作更为简洁,本文不再赘述。

● 代理访问测试用例

from client_sdk_python import Web3, HTTPProvider, WebsocketProvider

from client_sdk_python.eth import PlatON

from client_sdk_python.utils import contracts

false = False

true = True

instanceABI = [{“anonymous”:false,”input”:[{“name”:”topic”,”type”:”string”},{“name”:”arg1″,”type”:”int32″}],”name”:”addCallResult”,”topic”:1,”type”:”Event”},{“constant”:false,”input”:[],”name”:”init”,”output”:”void”,”type”:”Action”},{“constant”:false,”input”:[{“name”:”eles”,”type”:”int32[]”}],”name”:”testAddFromGeneralProxy”,”output”:”bool”,”type”:”Action”}]

instanceContractAddr = ‘lat1dgcfjgtdw56rjg7fa90gwzg0fdw9lyrs3kka28’

#测试网/主网账户地址

clientAccount = ‘latxxxx’

proxyABI= [{“constant”:false,”input”:[],”name”:”init”,”output”:”void”,”type”:”Action”},{“constant”:false,”input”:[{“name”:”contractAddr”,”type”:”string”},{“name”:”params”,”type”:”uint8[]”}],”name”:”generalCall”,”output”:”bool”,”type”:”Action”}]

proxyContractAddr = ‘lat1assxpver7n0a4tldhuyhpdlxt2qmdtngsetaem’

#测试网节点地址(或者主网)

devIP = ‘http://127.0.0.1:6789’

def find_and_cheke_fn_abi(abi, fn_name):

    for i in abi:

        if i[‘name’] == fn_name:

            i[‘inputs’] = i.pop(‘input’)

            return i

    return None

def generalProxy():

    w3 = Web3(HTTPProvider(devIP))

    platon = PlatON(w3)

    hello = platon.wasmcontract(address=proxyContractAddr, abi=proxyABI, vmtype=1)

    instanceCalled = platon.wasmcontract(address=instanceContractAddr, abi=instanceABI,vmtype=1)

    #这里需使用实例的fn_abi进行打包(本例中为AddressParam::testAddFromGeneralProxy接口)

    fn_abi = find_and_cheke_fn_abi(instanceABI, ‘testAddFromGeneralProxy’)

    print(fn_abi)

    if not fn_abi is None:

        params = contracts.encode_abi(w3, fn_abi, [[11, 12, 13]], vmtype=1, data=None, setabi=instanceABI)

        print(params)

        print(int(params, 16))

        print(Web3.toBytes(int(params, 16)))

        tx_events_hash = hello.functions.generalCall(instanceContractAddr, int(params, 16)).transact({‘from’:clientAccount,’gas’:1500000})

        tx_events_receipt = platon.waitForTransactionReceipt(tx_events_hash)

        #使用instance的wasmcontract实例来捕获事件

        topic_param = instanceCalled.events.addCallResult().processReceipt(tx_events_receipt)

        print(topic_param)

说明:

1、PlatON官方发布的SDK提供了基于abi进行参数序列化的编码接口,在python SDK中是contracts.encode_abi,通过from client_sdk_python.utils import contracts引入模块。

2、contracts.encode_abi的关键参数有4个:

    ①Web3实例。

    ②service contract相应的接口方法abi,需要从合约abi中获取,本案例中封装了find_and_cheke_fn_abi函数来获取。

    ③service contract方法调用所需的参数,参数的传入规则参见《PlatON上的WASM智能合约开发(四)——详解参数传递》

    ④vmtype,合约类型,wasm合约固定填写1。

3、通过hello.functions.generalCall调用代理合约接口,访问service contract的相应接口,需要注意的是,在python中,需要将encode_abi打包后的结果,通过int(params, 16)转化为16进制整型,才能对应PlatON WASM合约中的platon::bytes数据类型。

4、最后通过事件机制捕获用户关心的service contract实际执行结果,事件机制参见《PlatON上的WASM智能合约开发(六)——进阶(代理调用的事件捕获与解析)》

● 结果展示

PlatON Cross | PlatON上的WASM智能合约开发(七)——进阶(通用代理1)

● alaya相关说明

在alaya中执行测试的话,需要安装alaya专用的sdk,目前python的版本官网上尚未发布,node.js的版本具体参见:https://devdocs.alaya.network/alaya-devdocs/zh-CN/JS_SDK/。


总结

本文介绍了PlatON WASM合约方面通用代理代理合约的设计,对其中的Access agent机制进行了讲解,其关键要素如下:

1、在代理合约中,通过platon::platon_call(…)进行service contract合约调用;

2、在代理合约中,通过platon::get_call_output(…)获取service contract合约调用的执行状态;

3、在client_sdk_python中,通过contracts.encode_abi进行接口、参数编码打包;

4、在client_sdk_python中,进行代理访问时,需要将encode_abi打包结果转换成16进制整型。

在后续系列中,将对代理合约中的contract manager机制进行讲解。

本文转载自

(0)
PlatON Cross的头像PlatON Cross编辑
上一篇 30 7 月, 2021 10:00
下一篇 30 7 月, 2021 13:25

相关推荐

发表回复

登录后才能评论