PlatON WASM合约开发(三)—— 事件机制
#
概述智能合约是部署在区块链上 DApp 生态应用的“后台”,DApp 客户端程序在与合约协同工作时,必然会涉及到消息的交互,这些消息可能是所请求的数据、处理结果、控制指令等。交互方式包括同步调用(CONST)类型的接口,这类接口不能改变链上的状态,可直接获取到处理返回值;异步调用(ACTION)类型的接口,这些接口需要通过发送交易完成调用,可以改变链上的状态。事件机制一般在ACTION类型的接口中使用(CONST类型接口中也可使用事件机制),调用返回值可通过事件的emit发送出来,使用者可通过异步调用获取到处理结果。 区块链是去中心化的系统,智能合约也是链上状态的一种,每个节点都保存了链上的状态。本质上,CONST类型的接口,其执行实际是在client注册的节点上直接进行完成的(因为不会修改链上状态);而ACTION类型的接口,由于会修改链上状态,因此需要通过发起交易,由链上打包该交易的共识节点来执行。
#
合约代码#include <platon/platon.hpp>#include <string>#include <vector>using namespace std;
class inputParams{public: inputParams(){} inputParams(std::vector<int> vParams) { myParams = vParams; }
public: std::vector<int> myParams; PLATON_SERIALIZE(inputParams, (myParams))};
CONTRACT simpleOps: public platon::Contract{public: //PLATON事件机制,这里采用EVENT1,需要在事件名称后面,包含一个std::string类型的topic_type PLATON_EVENT1(Add, std::string, uint32_t) PLATON_EVENT1(call2, std::string, int) PLATON_EVENT1(put, std::string, int) PLATON_EVENT1(clear, std::string, uint32_t)
ACTION void init(const inputParams& ipa){ iParams.self() = ipa; }
CONST std::vector<int> getParams(){ return iParams.self().myParams; }
CONST int makeCall(){ std::vector<int> params = iParams.self().myParams; int rst = 0; for (auto itr = params.begin(); itr != params.end(); ++itr) { rst += *itr; } return rst; }
ACTION void putElement(int ele){ iParams.self().myParams.push_back(ele); //emit事件,在sdk中可以捕捉该事件,一般可将需要传递的信息作为第三个参数 PLATON_EMIT_EVENT1(put, "putElement" , ele); }
ACTION void clearElement(){ if (!iParams.self().myParams.empty()) { iParams.self().myParams.clear(); } uint32_t returnValue = 73; PLATON_EMIT_EVENT1(clear, "clearElement" , returnValue); }
ACTION int AddCalc(){ int rst = 0; for (auto itr = iParams.self().myParams.begin(); itr != iParams.self().myParams.end(); ++itr) { rst += *itr; }
PLATON_EMIT_EVENT1(Add, "AddCalc", rst); return rst; }
CONST int makeCall2(int a, int b){ int c = a + b; PLATON_EMIT_EVENT1(call2, "makeCall2", c); return c; }
private: platon::StorageType<"Params"_n, inputParams> iParams;};//platon wasm合约的外部入口PLATON_DISPATCH(simpleOps, (init)(getParams)(makeCall)(putElement)(clearElement)(AddCalc)(makeCall2))
说明:
- 合约源文件名为main.cpp;
- PlatON提供了四种合约事件:PLATON_EVENT0、PLATON_EVENT1、PLATON_EVENT2、PLATON_EVENT3,其区别在于需要输入的topic_type的数量,本文中的合约采用了PLATON_EVENT1,仅需要1个std::string类型的topic_type;
- 上面的WASM合约非常简单,具有一定的代表性,可保存多个int型的元素,提供对这些元素求和的接口。
#
在platon-truffle进行合约部署与调用#
合约部署:说明:
- 合约需要编译和部署后才能使用,编译和部署方式详见https://mp.weixin.qq.com/s/66mdamOzGBYGrNy09m_GHg,这里需要注意的是,init方法需要传入inputParams类型,而inputParams的构造函数需要传入std::vector vParams作为参数,因此在部署合约时,需要以'[[[73, 8]]]'做为初始化参数;
- 部署成功的信息如上图中所示。
#
创建合约对象实例:说明:
- 创建合约对象实例需要abi、合约地址、合约类型作为参数,{vmType:1}表示所创建的是wasm合约;
- 创建成功后,在console中调用getParams接口,可以看到当前存在的元素为部署合约是传入的初始化元素集合。
#
调用接口(向合约中插入元素):说明:
- 调用putElement,插入元素;
- 当交易尚未完成时,虽然插入操作已经返回,但链上状态尚未改变;
#
执行加法操作(AddCalc、makeCall、makeCall2):说明:
- AddCalc属于ACTION接口(需要通过交易完成),执行了求元素和的操作,但其结果无法直接通过取返回值获得;
- makeCall和makeCall2属于CONST接口,执行后可直接通过返回值获得结果。
在platon-truffle中能够进行基本的操作,但是对于通过交易调用的接口,很难获取到处理的结果,即捕捉到事件中真正想要的信息,这一点需要通过client-sdk实现。
注:node.js的SDK需要通过websocket来进行连接。
#
在 client-sdk 中进行合约调用,使用事件机制注:本文的用例都基于 python 的 client-sdk 进行编写,关于 python SDK 的官方文档参见: https://devdocs.platon.network/docs/zh-CN/Python_SDK/
#
需要导入的模块如下:from client_sdk_python import Web3, HTTPProvider, WebsocketProviderfrom client_sdk_python.eth import PlatONfrom hexbytes import HexBytes
#
执行 clear 操作,清空合约上的列表,并捕获 clear 事件:def testClear(): w3 = Web3(HTTPProvider("http://47.105.180.114:6789")) platon = PlatON(w3) hello = platon.wasmcontract(address=contractAddr, abi=cabi,vmtype=1)
tx_hash = hello.functions.clearElement().transact({'from': from_address, 'gas': 999999})
tx_receipt = platon.waitForTransactionReceipt(tx_hash)
topic_param = hello.events.clear().processReceipt(tx_receipt) print(topic_param)
说明:
- tx_hash为交易哈希值,通过它可以获取到tx_receipt交易收据,交易收据用于获取事件主题名称以及事件值;
- topic_param的具体形式如下文输出所示;
- 由于涉及到交易的合约调用,需要通过waitForTransactionReceipt来等待相关交易的完成;
- clear()即为想要捕获的事件。
#
输出:(AttributeDict({'args': AttributeDict({'topic': 'clearElement', 'arg1': 73}), 'event': 'clear', 'logIndex': 0, 'transactionIndex': 0, 'transactionHash': HexBytes('0x988c09572de8af93884daf2ed00a701835d3e9029fd780e3ed9ef5c5201b46dd'), 'address': 'lat1ktum8z9m9j4pz0l0gytqqrfgt8uv9sxnptxg9d', 'blockHash': HexBytes('0xa8595637bf8c5a12038a3c06cae28726f83f36201fec6270599a5a0d9ade432f'), 'blockNumber': 4689207}),)
说明:
- ”'arg1': 73“即为捕获的clear事件中,传递出来的用户感兴趣的结果;
- ”'topic': 'clearElement'“为返回的事件topic_type。
#
执行putElement向合约插入元素,通过事件返回所插入的值def testOps(): w3 = Web3(HTTPProvider("http://47.105.180.114:6789")) platon = PlatON(w3) hello = platon.wasmcontract(address=contractAddr, abi=cabi,vmtype=1)
tx_hash = hello.functions.putElement(11).transact({'from': from_address, 'gas': 999999})
tx_receipt = platon.waitForTransactionReceipt(tx_hash)
topic_param = hello.events.put().processReceipt(tx_receipt) print(topic_param)
说明:
- 通过捕获”put“事件,获取合约调用所传递出来的感兴趣的结果。
#
输出:说明:
- 在本用例中,三次调用了putElement操作,分别传入了数值13、12、11。
#
调用AddCalc操作,计算元素的和:def testAdd(): w3 = Web3(HTTPProvider("http://47.105.180.114:6789")) platon = PlatON(w3) hello = platon.wasmcontract(address=contractAddr, abi=cabi,vmtype=1)
tx_hash = hello.functions.AddCalc().transact({'from': from_address, 'gas': 999999})
tx_receipt = platon.waitForTransactionReceipt(tx_hash)
topic_param = hello.events.Add().processReceipt(tx_receipt) print(topic_param)
说明:
- 实际上,在执行完hello.functions.AddCalc().transact(...)后,可以根据自身业务需求,进行其他操作,并行等待waitForTransactionReceipt(...)的执行,成功后可通过事件”Add“获取执行结果。
#
输出:(AttributeDict({'args': AttributeDict({'topic': 'AddCalc', 'arg1': 36}), 'event': 'Add', 'logIndex': 0, 'transactionIndex': 0, 'transactionHash': HexBytes('0x9a609d616de9d1243ca75fac8e41268e9b83744418460080fafb38bb1a63b0f9'), 'address': 'lat1ktum8z9m9j4pz0l0gytqqrfgt8uv9sxnptxg9d', 'blockHash': HexBytes('0x566e03fa9a8581945c53970ea18ba769502d29bfdecda5c50a5437b68f029463'), 'blockNumber': 4690710}),)
说明:
- 事件中获取的”'arg1': 36“,为事件传递信息中用户感兴趣的结果,在本例中为元素的和。
目前,PlatON官方正在对python的client sdk进行持续的优化,相信未来将能够更加高效的支撑多样化应用生态的开发!
本教程贡献者 @xiyu