跳到主要内容

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