合约一般可视为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) {
userData = udd;
});
}
}
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存下来,具体做法可参见源码;
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, WebsocketProvider
from client_sdk_python.eth import PlatON
from client_sdk_python import utils
from 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团队。