本文转载自微信公众号 PlatON Cross
原创作者: PlatON Cross小编
熟悉传统面向对象设计模式的朋友应该知道,最原始的代理者接口与被代理实例的接口几乎是一一对应的,也就是说,一般情况下,传统代理机制几乎是针对某一类型对象实例的。
在区块链领域,智能合约的在线升级一直相对麻烦,大多公链都会面临以下问题:
1、合约地址的变更,因为合约升级就意味着需要重新部署(EOS支持在不变更地址的情况下进行合约重部署)。
2、abi的变更,虽然可以在设计之初,对接口尽量考虑周全,但难免遇到在新增功能时,需要增加新接口的情况。
因此,传统的代理模式在区块链领域并不太适合,而合约升级又是区块链项目的推进过程中难以避免的事件。在前序文章中,Cross团队已经对代理合约的部分机制进行了介绍,接下来,将向大家介绍一种相对通用的代理机制,这种代理机制主要包含两部分:
1、代理调用的通用化(Access agent)。
2、合约映射管理(Contract manager)。
本篇主要针对代理调用的通用化进行简单的讲解。
▌机制简介
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网络上的合约调试工具,如下图所示:
说明(对应图中的编号):
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智能合约开发(六)——进阶(代理调用的事件捕获与解析)》。
● 结果展示
● 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机制进行讲解。
本文转载自