eos源码赏析(十三):EOS智能合约数据持久化存储(上)

2021-11-23 10:32:09 浏览数 (1)

前面的文章(eos源码赏析(十):EOS智能合约入门之区块上链)中提到了fork_db,区块生产之后会将区块的状态信息等存储在fork_db中,但是当这个动作完成之后,fork_db中的内容就会变化,用来存储下一个区块的状态信息,并不能实现对历史区块信息的保存。对于区块链来说,一定要有一个持久化数据存储机制方能保证记录eosio链上所有区块信息,并提供查询接口,不然区块生产的意义就已经不存在了。我们结合一个智能合约来谈谈eosio中如何实现区块信息的持久化存储,文章共分为上下两篇

上篇主要包括以下内容:

  • Multi-Index的官方说明
  • 智能合约中Multi-Index的使用
  • 智能合约中数据增、删、改、查

下篇主要包含以下内容

  • Multi-Index和ChainBase之间的交互
  • boost::multi-index的介绍及使用

一、Multi-Index的官方说明

关于Multi-Index的说明参见:https://developers.eos.io/eosio-cpp/docs/db-api

图1 数据持久化存储的必要性

上图为官方给出的示例图,说明了我们为什么需要持久化的数据存储

Actionsperform the work of EOSIO contracts. Actions operate within an environmentknown as the action context. As illustrated in the Action "Apply"Context Diagram, an action context provides several things necessary for theexecution of the action. One of those things is the action's working memory.This is where the action maintains its working state. Before processing anaction, EOSIO sets up a clean working memory for the action. Variables thatmight have been set when another action executed are not available within thenew action's context. The only way to pass state among actions is to persist itto and retrieve it from the EOSIO database. 简单的说就是在每次action执行之前,eosio都会为其分配一块内存,当另一个action执行的时候上一个action中的状态信息就不存在了,为了解决这个问题,只有持久化数据存储,而后从数据库中进行数据的查询。

为此,eosio中引入了Multi-Index,Multi-Index改写了boost中的Multi-Index,在eos中Multi-Index为eosio的数据库提供了c 的接口,它可以存储任意数据类型。类似于传统的数据库,每一张表都有一个表名,而在eosio中每一个Multi-Index就是一张表,与传统数据库不同的是他没有行只有列,或者说一行N列,每一行都存储一个对象,有过eos开发经验的或许都知道,我们在Multi-Index中存储的大多是结构体变量,每个结构体变量中会存在有多个成员变量,也就是说Multi-Index变相的帮我们存储了很多数据。如我们上篇文章中所讲到的狼人游戏中结构体变量的存储:

图2 狼人游戏中Multi-Index的使用

传统数据库大都只有唯一主键,而Multi-Index支持多主键索引,通过访问映射底层存储数据上不同类型的索引所直接获取到的并不是存储底层的数据实体,而是存储实体的一个视图。和其他区块链技术的基础架构对比,eosio持久性服务的一个关键区别是它的多索引迭代器。eosio多索引允许智能合约开发者保留各种不同主键类型排序的对象集合,这些主键类型可以从对象内的数据派生。

二、智能合约中Multi-Index的使用

注:本文中代码右滑可以查看更多,或者找我要。

为了更方便的说明智能合约中Multi-Inde的使用,我们来编写一个智能合约,其中使用Multi-Index来存储《天龙八部》中各位英雄豪杰的相关信息。该合约的头文件如下:

代码语言:javascript复制
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

using namespace std;
using namespace eosio;

class tianlongbabu : public eosio::contract
{
private:
        account_name _this_contract;
        // @abi table heros
        struct heros
        {
                uint64_t heroid;          //天龙英雄的id
                string heroname;          //天龙英雄的名字
                string herodes;           //天龙英雄的个人描述
                string heroborn;          //天龙英雄的出生地
                uint32_t heroforceidx;    //天龙英雄的武力值
                uint32_t heroinsideidx;   //天龙英雄的内力值

                uint64_t primary_key() const{return heroid;}
                string get_heroname() const{return heroname;}
                string get_herodes() const{return herodes;}
                string get_heroborn() const{return heroborn;}
                uint32_t get_heroforceidx() const{return heroforceidx;}
                uint32_t get_heroinsideidx() const{return heroinsideidx;}

                EOSLIB_SERIALIZE(heros,(heroid)(heroname)(herodes)(heroborn)(heroforceidx)(heroinsideidx))
        };
        typedef eosio::multi_index<N(heros),heros> heros_table;
        heros_table ht;
public:
        tianlongbabu(account_name self):
        contract(self),
        _this_contract(self),
        ht(self,self)
        {
                //构造函数
        }
        // @abi action
//      void create(account_name heroname,string strheroname,string strherodes,string strheroborn,const uint32_t heroforceidx,const uint32_t heroinsideidx);
        // @abi action
        void create(account_name heroname,uint32_t heroforceidx,uint32_t heroinsideidx,string herodes,string heroborn);
        void deletebyid(uint64_t heroid);
        void deletebyforce(uint64_t forceindex);
        void modifytitle(account_name heroname,uint64_t heroid,string strherodes);
        void selectbyid(uint64_t heroid);
};

tlbb.hpp 代码(右滑可以查看更多)

在类tianlongbabu中定义了结构体heros其中包含了天龙英雄的id、天龙英雄的名字、天龙英雄的出生地、天龙英雄的描述、天龙英雄的武力值、天龙英雄的内力值等相关变量。在使用EOSLIB_SERIALIZE对其序列化之后,使用Multi-Index并声明一个变量ht,我们接下来的操作都在围绕这个ht进行。此后,还声明了几个函数create用来创建天龙八部中的英雄,deletebyid用来根据英雄id来删除表中的英雄数据,deletebyforce用来根据英雄的武力值遍历删除小于某个武力值的英雄数据,modifytitle根据英雄的id去修改该英雄的描述,selectbyid根据英雄的id去查找英雄的相关数据。

三、智能合约中数据增、删、改、查

我们知道,在传统数据库中对数据的操作包含有增、删、改、查,而在Multi-Index中也不例外,Multi-Index使用emplace、erase、modify、get等实现了对数据的各种操作,在看代码之前,我们先创建一些账号,并部署相关合约:

代码语言:javascript复制
cleos create account eosio tlbb.code {pub-key} {pub-key}
cleos create account eosio xiaofeng {pub-key} {pub-key}
cleos create account eosio xuzhu {pub-key} {pub-key}
cleos create account eosio duanyu {pub-key} {pub-key}
cleos create account eosio murongfu {pub-key} {pub-key}
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ eosiocpp -o tlbb.wast tlbb.cpp 
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ eosiocpp -g tlbb.abi tlbb.cpp 
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos set contract tlbb.code /home/ubuntu/eos/contracts/tlbb -p tlbb.code

账号创建部署合约

下面我们结合代码一个个来看:

代码语言:javascript复制
void tianlongbabu::create(account_name heroname,uint32_t heroforceidx,uint32_t heroinsideidx,string herodes,string heroborn)
{
        print(name{heroname},"出生在:",heroborn,",现在是",herodes,",他的武力值:",heroforceidx,",他的内力值:",heroinsideidx);
        uint64_t heroindex = 0;
        ht.emplace(_this_contract,[&](auto &p)
        {
                p.heroid = ht.available_primary_key();   //主键自增
                heroindex = p.heroid;
                p.herodes        = herodes;
                p.heroborn       = heroborn;
                p.heroforceidx   = heroforceidx;
                p.heroinsideidx  = heroinsideidx;

        });
        heros result = ht.get(heroindex);
        print("-该英雄的编号是:",result.heroid);
}

create创建英雄

在create中我们将push action中传入的参数使用emplace写入到表ht中,此处的available_primary_key()可以实现主键的自增,其余的操作类似,类似于一个结构体变量作为一行多列的数据存储。在写入完成之后我们使用简单的例子查询到该英雄的id。然后我们执行以下action(记得nodeos运行的时候加入--contracts-console参数或者在config.ini中加入相关配置,这样方便查看合约执行的结果,在终端可以打印出来),这样我们就完成了四个英雄的创建,萧峰、虚竹、段誉、慕容复,并将这四个英雄的相关数据写入到了ht中:

代码语言:javascript复制
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code create '["xiaofeng","1000","500","丐帮帮主","

辽国"]' -p xiaofeng
executed transaction: ca536e0e13182f2ea6dc5e681c4aa3d81940d760c7435cba948bd9d56cc82460  128 bytes  1106 us
#     tlbb.code <= tlbb.code::create            

{"heroname":"xiaofeng","heroforceidx":1000,"heroinsideidx":500,"herodes":"丐帮帮主","heroborn":"...
>> xiaofeng出生在:辽国,现在是丐帮帮主,他的武力值:1000,他的内力值:500-该英雄的编号是:14

ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code create '["xuzhu","600","600","灵鹫宫宫主","中

原"]' -p xuzhu
executed transaction: 2a93021425148e8c1ff5679566d758318ab7201ee4fb6bf06b5e536de7cc8f84  136 bytes  1121 us
#     tlbb.code <= tlbb.code::create            

{"heroname":"xuzhu","heroforceidx":600,"heroinsideidx":600,"herodes":"灵鹫宫宫主","heroborn":"?.
>> xuzhu出生在:中原,现在是灵鹫宫宫主,他的武力值:600,他的内力值:600-该英雄的编号是:15

ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code create '["duanyu","400","800","大理国皇帝","

大.理"] -p duanyu
executed transaction: 3b134c346cddb1cc0ef8eb9fefba41c3d82d1dda4019a189fa209e60157e65ac  136 bytes  1248 us
#     tlbb.code <= tlbb.code::create            

{"heroname":"xuzhu","heroforceidx":400,"heroinsideidx":800,"herodes":"大理国皇帝","heroborn":"?.
>> duanyu出生在:大理,现在是大理国皇帝,他的武力值:400,他的内力值:800-该英雄的编号是:16
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code create '["murongfu","400","100","燕国皇 室后

代","姑苏"]' -p murongfu
executed transaction: 564d9acedd8e4dbbcb590ebcbdb2ef4c4151f35e8d611767065750d3f9852b5f  136 bytes  1303 us
#     tlbb.code <= tlbb.code::create            

{"heroname":"murongfu","heroforceidx":400,"heroinsideidx":100,"herodes":"燕国皇室后代","herobo...
>> murongfu出生在:姑苏,现在是燕国皇室后代,他的武力值:400,他的内力值:100-该英雄的编号是:17

相信很多朋友都不太喜欢慕容复,因为其阴险狡诈、不自量力且贪生怕死,卖友求荣,最不能让我忍受的是包不同的死,可怜一世家臣,却落得如此下场,那么我们就根据慕容复的id把慕容复从英雄库中删除吧,根据id删除英雄数据的代码如下:

代码语言:javascript复制
void tianlongbabu::deletebyid(uint64_t heroid)
{
        auto findhero = ht.find(heroid);
        eosio_assert(findhero != ht.end(), "您要删除的英雄不存在");
        ht.erase(findhero);
        print("编号为:",heroid,"的英雄被删除了");
}

而后我们执行以下action来删除慕容复:

代码语言:javascript复制
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code deletebyid '["17"]' -p tlbb.code
executed transaction: 970814ddd5c351878690ee53357fb82ad9c8b7da4aa395fd388918ba2ebf7fe3  104 bytes  821 us
#     tlbb.code <= tlbb.code::deletebyid        {"heroid":17}
>> 编号为:17的英雄被删除了

虚竹的人生可以说是跌宕起伏的,从出生被偷走做了十几年的小和尚到成为灵鹫宫宫主、逍遥派掌门,再到少林寺一役认出自己的爹妈,不就爹妈惨死,也是可怜,我们可以尝试着修改下虚竹的相关信息,让他还是老老实实的当个小和尚,Multi-Index中修改数据内容的代码如下:

代码语言:javascript复制
void tianlongbabu::modifytitle(account_name heroname,uint64_t heroid,string strherodes)
{
        require_auth(heroname);
        eosio_assert(!strherodes.empty(),"您输入的字符不能为空");
        auto findhero = ht.find(heroid);
        eosio_assert(findhero != ht.end(),"您要修改的英雄不存在");
        ht.modify(findhero,heroname,[&](auto &p)
        {
                p.herodes = strherodes;
        });
        print(name{heroname},"现在是",strherodes);
}

在这里我们使用require_auth获取了用户的active权限来保证该英雄的数据不能被别人修改,然后执行以下action来修改虚竹的数据:

代码语言:javascript复制
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code modifytitle '["xuzhu","15","少林寺一个小和

尚"]' -p xuzhu@active
executed transaction: d5b44a7f42ab1a499eb8b7bcdd69f26af591a9cf7cb277b48d6a36ceded55645  136 bytes  804 us
#     tlbb.code <= tlbb.code::modifytitle       {"heroname":"xuzhu","heroid":15,"strherodes":"少林寺一个小和尚"}
>> xuzhu现在是少林寺一个小和尚

说完了增删改,其实上面已经有了数据查询的过程,下面我们在通过一个简单的例子,查询下虚竹的信息是不是修改成功了,代码如下:

代码语言:javascript复制
void tianlongbabu::selectbyid(uint64_t heroid)
{
        auto gethero = ht.get(heroid);
        print("编号:",heroid,"出生在:",gethero.heroborn,"现在是:",gethero.herodes);
}

然后执行以下action:

代码语言:javascript复制
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code selectbyid '["15"]' -p tlbb.code
executed transaction: 91715b3ae749425c70b0b93a2a2de6fdac6cef2f031de825480ab806d8096cee  104 bytes  694 us
#     tlbb.code <= tlbb.code::selectbyid        {"heroid":15}
>> 编号:15出生在:中原现在是:少林寺一个小和尚
ubuntu@VM-16-6-ubuntu:~/eos/contracts/tlbb$ cleos push action tlbb.code selectbyid '["14"]' -p tlbb.code
executed transaction: 50bd5837e1301ae903d2475550a6a3a8502de8c7debcd34288dcb7c3f434a2a7  104 bytes  956 us
#     tlbb.code <= tlbb.code::selectbyid        {"heroid":14}
>> 编号:14出生在:辽国现在是:丐帮帮主

分别查询了虚竹和萧峰的相关英雄信息。

本文主要介绍了Multi-Index在智能合约中的应用,其中包含区块链数据持久存储的必要性、Multi-Index的一些简单介绍、Multi-Index在只能合约中应用,智能合约中数据的增、删、改、查等操作。

我们知道,Multi-Index为多索引,那么如何使用这个多索引呢,我们能否根据别的参数去查询相关的英雄信息,在emplace、erase、modify、get等背后又是和数据库chainbase之间是如何交互的呢?我们下篇文章中继续讨论。

0 人点赞