PlatON WASM contract (八) - 内置数据结构
合约一般可视为Dapp的“后台”,不可避免将对一些业务相关的数据进行记录。本篇针对可能常用的三类结构(platon::StorageType、platon::db::Map、platon::db::MultiIndex)进行示例讲解。其中platon::StorageType是最为灵活的使用方式,支持用户自定义数据结构,platon::db::Map的用法与std::map较为类似,platon::db::MultiIndex支持创建多索引的数据结构,便于根据不同的索引进行查询。由于链上存储有其独有的特征,这三类数据结构在操作性能上可能存在一定的差异,有机会笔者将在后续系列文章中将对此进行讲解。
#
合约代码示例#
WasmDataStruct.h代码#pragma once
#include <platon/platon.hpp>#include <string>#include <vector>
struct UserDefinedData{ platon::u128 uID; std::string uName;
std::string uContent;
PLATON_SERIALIZE(UserDefinedData, (uID)(uName)(uContent))
platon::u128 UserID() const { return uID; } std::string UserName() const { return uName; }};
class WasmDataStruct{ //map platon::db::Map<"UserDataMap"_n, platon::u128, UserDefinedData> _mUserMap;
//multiIndex table platon::db::MultiIndex< "UserTable"_n, UserDefinedData, platon::db::IndexedBy<"id"_n, platon::db::IndexMemberFun<UserDefinedData, platon::u128, &UserDefinedData::UserID, platon::db::IndexType::UniqueIndex>>, platon::db::IndexedBy<"name"_n, platon::db::IndexMemberFun<UserDefinedData, std::string, &UserDefinedData::UserName, platon::db::IndexType::NormalIndex>>> _mUser_table;
//StorageType platon::StorageType<"UserData"_n, std::map<platon::u128, UserDefinedData>> _mUser_Data;
public: ACTION void init();
ACTION void AddUser(const UserDefinedData& udd);
ACTION void ModifyUserInfo(const UserDefinedData& udd);
ACTION void UserErase(const platon::u128& uID);
ACTION void Clear();
CONST std::vector<UserDefinedData> getUserFromTable_ID(const platon::u128& uID); CONST std::vector<UserDefinedData> getUserFromTable_Name(const std::string& uName);
CONST std::vector<UserDefinedData> getUserFromMap(const platon::u128& uID);
CONST std::vector<UserDefinedData> getUserFromStorageType(const platon::u128& uID);};
PLATON_DISPATCH(WasmDataStruct, (init)(AddUser)(ModifyUserInfo)(UserErase)(Clear)(getUserFromTable_ID)(getUserFromTable_Name)(getUserFromMap)(getUserFromStorageType))
#
WasmDataStruct.cpp代码#include "WasmDataStruct.h"
void WasmDataStruct::init(){ }
void WasmDataStruct::AddUser(const UserDefinedData& udd){ _mUserMap[udd.uID] = udd; _mUser_Data.self()[udd.uID] = udd;
_mUser_table.emplace([&](auto& userTable) { userTable = udd; });}
void WasmDataStruct::ModifyUserInfo(const UserDefinedData& udd){ //_mUserMap if (_mUserMap.contains(udd.uID)) { _mUserMap[udd.uID] = udd; }
//_mUser_Data auto udItr = _mUser_Data.self().find(udd.uID); if (udItr != _mUser_Data.self().end()) { udItr->second = udd; }
//_mUser_table auto uTableItr = _mUser_table.find<"id"_n>(udd.uID); if (uTableItr != _mUser_table.cend()) { _mUser_table.modify(uTableItr, [&](auto& userData) { // keys cannot be changed userData.uContent = udd.uContent; }); }}
void WasmDataStruct::UserErase(const platon::u128& uID){ //_mUserMap if (_mUserMap.contains(uID)) { _mUserMap.erase(uID); }
//_mUser_Data auto udItr = _mUser_Data.self().find(uID); if (udItr != _mUser_Data.self().end()) { _mUser_Data.self().erase(udItr); }
//_mUser_table auto uTableItr = _mUser_table.find<"id"_n>(uID); if (uTableItr != _mUser_table.cend()) { _mUser_table.erase(uTableItr); }}
void WasmDataStruct::Clear(){ //the clear of db::Map and db::MultiIndex are Some trouble std::vector<platon::u128> idVec; for (auto usItr = _mUser_Data.self().begin(); usItr != _mUser_Data.self().end(); ++usItr) { idVec.push_back(usItr->first); }
//normal map _mUser_Data.self().clear();
//db::Map for (auto idItr = idVec.begin(); idItr != idVec.end(); ++idItr) { _mUserMap.erase(*idItr); }
//db::MultiIndex auto tableItr = _mUser_table.cbegin(); while (tableItr != _mUser_table.cend()) { auto tempItr = tableItr; ++tableItr; _mUser_table.erase(tempItr); }}
std::vector<UserDefinedData> WasmDataStruct::getUserFromTable_ID(const platon::u128& uID){ std::vector<UserDefinedData> udVec;
auto uTableItr = _mUser_table.find<"id"_n>(uID); if (uTableItr != _mUser_table.cend()) { udVec.push_back(*uTableItr); }
//if not found, return [] return udVec;}
std::vector<UserDefinedData> WasmDataStruct::getUserFromTable_Name(const std::string& uName){ std::vector<UserDefinedData> udVec; //Here's where it gets more confusing, maybe set parameters in get_index is better auto normalIndexes = _mUser_table.get_index<"name"_n>(); for (auto itemItr = normalIndexes.cbegin(uName); itemItr != normalIndexes.cend(uName); ++itemItr) { udVec.push_back(*itemItr); }
return udVec;}
std::vector<UserDefinedData> WasmDataStruct::getUserFromMap(const platon::u128& uID){ std::vector<UserDefinedData> udVec;
if (_mUserMap.contains(uID)) { udVec.push_back(_mUserMap[uID]); }
return udVec;}
std::vector<UserDefinedData> WasmDataStruct::getUserFromStorageType(const platon::u128& uID){ std::vector<UserDefinedData> udVec;
auto udItr = _mUser_Data.self().find(uID); if (udItr != _mUser_Data.self().end()) { udVec.push_back(udItr->second); }
return udVec;}
说明:
1、本示例分别定了三种数据类型(_mUserMap、_mUser_table、_mUser_Data),用于存放UserDefinedData;
2、添加元素:AddUser,同时将数据写入三种数据类型中,可在.cpp实现中看到三种添加方式的区别,db::MultiIndex区别较大,需要通过lambda操作来进行元素构造添加;在本示例中,添加元素故意没有进行判重操作,在后续调用测试中,可见db::Map、StorageType中的相关元素都已经被覆盖,但db::MultiIndex中本身具备了判重的能力;
3、修改元素:ModifyUserInfo,同时修改三种数据类型中的相同元素,在操作上db::Map与用户在StorageType中自定义的Map类似,但db::MultiIndex区别较大,需要通过lambda操作来进行修改;
4、删除元素:UserErase,删除操作差别不大,值得一提的是db::Map没有提供相应的迭代器,可通过key直接删除;
5、清空元素:Clear,清空操作db::Map比较麻烦,需要先将所有的key存下来,具体做法可参见源码;实际上,对于db::Map的清空是有优化的方法的,这里先按下不表,在后续的性能分析文章中会进行讲解
6、查询元素:从db::Map、StorageType中查询较为简单。db::MultiIndex支持多索引查询,其中通过唯一键(platon::db::IndexType::UniqueIndex)查询也比较简单,值得一提的是通过普通键进行查询,需要先通过get_index<"keyname"_n>()获取所有元素(内部做了顺序处理),然后通过cbigin(name)查找所有key匹配的元素,最终将返回多个普通键值为name的元素,详见.cpp中第104至114行。
#
Client代码示例#
C端代码(python)from client_sdk_python import Web3, HTTPProvider, WebsocketProviderfrom client_sdk_python.eth import PlatONfrom client_sdk_python import utilsfrom client_sdk_python.utils import contracts
import json
ContractAddr = 'lat1ddydcvxtj0wus3ctt9pkgwjdl5hsjx099ccd3a'
clientAccount = 'your account for testNet'devIP = 'ip of testNet node'
def addItem(): fp = open('./PlatON/config/WasmDataStruct.abi.json') Contract_abi = json.load(fp) #print(breakingNews_abi)
w3 = Web3(HTTPProvider(devIP)) platon = PlatON(w3) hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)
#print(hello.functions.getUserFromTable_ID(73).call())
tx_receipt = hello.functions.AddUser([1, 'jason', 'Hello world']).transact({'from':clientAccount,'gas':1500000}) receipt_info = platon.waitForTransactionReceipt(tx_receipt) print(receipt_info)
tx_receipt = hello.functions.AddUser([2, 'jason', 'Hello Platon']).transact({'from':clientAccount,'gas':1500000}) receipt_info = platon.waitForTransactionReceipt(tx_receipt) print(receipt_info)
tx_receipt = hello.functions.AddUser([3, 'cross', 'Hello Cross']).transact({'from':clientAccount,'gas':1500000}) receipt_info = platon.waitForTransactionReceipt(tx_receipt) print(receipt_info)
#ID's all ready exist tx_receipt = hello.functions.AddUser([3, 'none', 'Hello None']).transact({'from':clientAccount,'gas':1500000}) receipt_info = platon.waitForTransactionReceipt(tx_receipt) print(receipt_info)
def getItems(): fp = open('./PlatON/config/WasmDataStruct.abi.json') Contract_abi = json.load(fp) #print(breakingNews_abi)
w3 = Web3(HTTPProvider(devIP)) platon = PlatON(w3) hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)
print(hello.functions.getUserFromTable_ID(1).call()) print(hello.functions.getUserFromTable_ID(2).call()) print(hello.functions.getUserFromTable_ID(3).call()) print(hello.functions.getUserFromMap(3).call()) print(hello.functions.getUserFromStorageType(3).call())
#call from name by table print(hello.functions.getUserFromTable_Name('jason').call())
def modifyItem(): fp = open('./PlatON/config/WasmDataStruct.abi.json') Contract_abi = json.load(fp) #print(breakingNews_abi)
w3 = Web3(HTTPProvider(devIP)) platon = PlatON(w3) hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)
tx_receipt = hello.functions.ModifyUserInfo([2, 'jason', 'Hello PlatON Cross']).transact({'from':clientAccount,'gas':1500000}) receipt_info = platon.waitForTransactionReceipt(tx_receipt) print(receipt_info)
print(hello.functions.getUserFromTable_ID(2).call())
def ClearData(): fp = open('./PlatON/config/WasmDataStruct.abi.json') Contract_abi = json.load(fp) #print(breakingNews_abi)
w3 = Web3(HTTPProvider(devIP)) platon = PlatON(w3) hello = platon.wasmcontract(address=ContractAddr, abi=Contract_abi, vmtype=1)
tx_receipt = hello.functions.Clear().transact({'from':clientAccount,'gas':1500000}) receipt_info = platon.waitForTransactionReceipt(tx_receipt) print(receipt_info)
print(hello.functions.getUserFromTable_ID(1).call()) print(hello.functions.getUserFromTable_ID(2).call()) print(hello.functions.getUserFromTable_ID(3).call())
#addItem()getItems()#modifyItem()#ClearData()
#
调用结果#
call addItem:说明:
1、第四次调用使用了与第三次效用相同的主键,本文示例故意没有在合约的添加元素操作中进行判重操作,这主要是为了体现三种结构的差异,结果将在getItems中呈现。
#
call getItems:说明:
1、在.py第53~55行可以看到,调用查询主键为3的元素,三种数据类型返回的值不一样,这是由于db::MultiIndex自带判重的操作;
#
call modifyItem:说明:
1、操作成功;
#
call ClearData:说明:
1、操作成功。
#
总结本文对wasm合约内置的几种典型数据结构进行了简单讲解,如在开发中遇到问题,可联系cross团队。
本教程贡献者 @xiyu