【劉文彬】EOS技術研究:合約與數據庫交互

原文鏈接: https://www.cnblogs.com/Evsward/p/multi_index.html

智能合約操作鏈數據庫是很常見的應用場景。EOS提供了專門的工具來做這件事(相當於Ethereum的leveldb),專業術語叫做持久化API,本文將完整嚴密地介紹這個工具以及對它的使用測試。

關鍵字:EOS,智能合約,鏈數據庫,eosio::multi_index,constructor,emplace,erase,find。

需求

首先來看EOS中智能合約涉及到持久化的場景需求。一個action在執行時會有上下文變量出現,包括事務機制的處理,這些內容會應用鏈上分配的內存資源,而如果沒有持久化技術,執行超過作用域時就會丟失掉這些上下文數據。因此要使用持久化技術將關鍵內容記錄在鏈數據庫中,任何時候使用都不受影響。持久化技術應該包括:

  • 記錄一些狀態持久化到數據庫中
  • 具備查詢的能力從數據庫中獲取內容
  • 提供C++ 的API來調用這些服務,也服務於合約開發者

eosio::multi_index

這是模仿boost::multi_index開發的一套庫。它使用C++編寫,提供了合約與數據庫的交互持久化接口。

Multi-Index Iterators:不同於其他key-value數據庫,multi_index提供了不同類型的key對應的值也是可迭代的複雜集合類型。

創建表

  • class/struct 定義一個持久化對象。
  • 該對象需要有一個const的成員作爲主鍵,類型爲uint64_t
  • 二級主鍵可選,提供不同的鍵類型
  • 爲每個二級索引定義一個鍵導出器,鍵導出器是一個函數,可以用來從Multi_index表中獲取鍵

使用Multi-Index表

一般來講,對數據庫的操作無外乎增刪改查,

  • 增加對應的方法是emplace
  • 修改就是modify
  • 刪除是erase
  • 查找包括find和get,以及迭代器操作

實戰

下面我們通過一個智能合約操作底層數據庫的實例,來演示對持久化api,multi_index的使用。過程中,也會展示相應api的定義。

車輛維修跟蹤

首先,創建一個service表,用來創建服務記錄報告,它包含的字段有:

  • 主鍵,客戶ID不可當做主鍵,因爲一個客戶可以有多條記錄。實際上,我們並不直接需要主鍵,所以我們可以讓系統爲我們生成一個。
  • 客戶ID,與賬戶名字對應
  • 服務日期
  • 里程錶,汽車里程錶
#include <eosiolib/eosio.hpp>

using namespace eosio;

class vehicle : public eosio::contract {
public:
    /// @abi table
    struct service_rec {
        uint64_t        pkey;
        account_name    customer;
        uint32_t        service_date;
        uint32_t        odometer;

        auto primary_key() const { return pkey; }

        account_name get_customer() const { return customer; }

        EOSLIB_SERIALIZE(service_rec, (pkey)(customer)(service_date)(odometer))
    };

    typedef multi_index<N(service), service_rec> service_table_type;

    using contract::contract;

    /// @abi action
    void exec(account_name owner, account_name customer) {
        print("Hello, ", name{customer});
        service_table_type service_table(current_receiver(), owner);// 構造器,第一個參數是code代表表的擁有者,目前是current_receiver(),也可以使用contract基類的構造器初始化時的賬戶名_self,第二個參數是scope,在代碼層次範圍的標識符,這裏就使用傳入的owner賬戶。
        uint64_t pkeyf;// 主鍵
        service_table.emplace(owner, [&](auto &s_rec) {
            s_rec.pkey = service_table.available_primary_key();// 主鍵自增
            pkeyf = s_rec.pkey;
            print(pkeyf);// 打印主鍵內容
            s_rec.customer = customer;
            s_rec.service_date = 2000;
            s_rec.odometer = 1000;
        });
        service_rec result = service_table.get(pkeyf);
        print("_", result.pkey);
        print("_", result.customer);
        print("_", result.service_date);
        print("_", result.odometer);
    }

};

EOSIO_ABI(vehicle, (exec))

使用eosiocpp工具生成abi文件和wast文件。
<br/>eosiocpp -g vehicle.abi vehicle.cpp | eosiocpp -o vehicle.wasm vehicle.cpp<br/>
然後在終端中部署該合約,
<br/>cleos set contract one work/CLionProjects/github.com/eos/contracts/vehicle <br/>
調用合約的exec方法,並輸出結果:

liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action one exec '["one","two"]' -p one
executed transaction: 3a45eaeb06732ad0c53ba7b157003e1c503f74ed447029d82cecbe12926cc9a9  112 bytes  365 us
#           one <= one::exec                    {"owner":"one","customer":"two"}
>> Hello, two13_13_14927180964919508992_2000_1000
warning: transaction executed locally, but may not be confirmed by the network yet

通過輸出結果,可以知道主鍵爲13,customer賬戶名被轉爲無符號32位整型數據14927180964919508992,服務時間爲2000,里程錶爲1000。我們已經成功將數據存入了multi_index並取了出來。

刪除的話可以通過service_table.erase(result);來刪除掉對應記錄。

find涉及二級索引,迭代器等操作,end判斷等multi_index的api操作沒有給出具體實例,未來在其他合約使用時會直接說明。

再演練一個例子

爲了更好的熟悉multi_index的機制,我們再演練一個簡單的例子:維護一個todolist的數據庫表。直接上代碼如下:

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>

class todolist : public eosio::contract
{
  public:
    using eosio::contract::contract;
    // @abi table todos i64
    struct todo
    {
        uint64_t id;             // 待辦事項主鍵id
        std::string description; // 待辦事項的描述參數
        uint64_t completed;      // 標記一個待辦事項是否已完成

        uint64_t primary_key() const { return id; }
        EOSLIB_SERIALIZE(todo, (id)(description)(completed))
    };

    typedef eosio::multi_index<N(todos), todo> todo_table;

    // @abi action
    void create(account_name author, const uint32_t id, const std::string &description)
    {
        todo_table todos(_self, author);
        todos.emplace(author, [&](auto &new_todo) {
            new_todo.id = id;
            new_todo.description = description;
            new_todo.completed = 0;
        });

        eosio::print("todo#", id, " created");
    }

    // @abi action
    void complete(account_name author, const uint32_t id)
    {
        todo_table todos(_self, author);
        auto todo_lookup = todos.find(id);
        eosio_assert(todo_lookup != todos.end(), "Todo does not exist");

        todos.modify(todo_lookup, author, [&](auto &modifiable_todo) {
            modifiable_todo.completed = 1;
        });

        eosio::print("todo#", id, " marked as complete");
    }

    // @abi action
    void destroy(account_name author, const uint32_t id)
    {
        todo_table todos(_self, author);
        auto todo_lookup = todos.find(id);
        todos.erase(todo_lookup);

        eosio::print("todo#", id, " destroyed");
    }
};

EOSIO_ABI(todolist, (create)(complete)(destroy))

這裏加入了對數據的增刪改查功能。然後我們使用
<br/>eosiocpp -o todolist.wast todolist.cpp && eosiocpp -g todolist.abi todolist.cpp<br/>
創建對應的abi和wast文件。接下來創建用戶todo.user,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos create account eosio todo.user EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
executed transaction: 7d7191e2935c6fd0024f571e228ee11d51f4f44a64ed2ce977326fb27679f0c6  200 bytes  174 us
#         eosio <= eosio::newaccount            {"creator":"eosio","name":"todo.user","owner":{"threshold":1,"keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfnV...
warning: transaction executed locally, but may not be confirmed by the network yet

部署合約,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos set contract todo.user work/VSCode-Projects/eos/contracts/todolist -p todo.user
Reading WAST/WASM from work/VSCode-Projects/eos/contracts/todolist/todolist.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: 03befa58d6a54970db708beaa0520179277b01addf1ec647a76a9b3f6459ff57  5128 bytes  2203 us
#         eosio <= eosio::setcode               {"account":"todo.user","vmtype":0,"vmversion":0,"code":"0061736d01000000016e1260047f7e7f7f0060037f7e...
#         eosio <= eosio::setabi                {"account":"todo.user","abi":{"types":[],"structs":[{"name":"todo","base":"","fields":[{"name":"id",...
warning: transaction executed locally, but may not be confirmed by the network yet

創建新條目,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user create '["todo.user",1,"hello, world"]' -p todo.user
executed transaction: 54d9825971370a242fa51fa7cc587a6478dd7852039c263d91058c6b1163a4bc  120 bytes  336 us
#     todo.user <= todo.user::create            {"author":"todo.user","id":1,"description":"hello, world"}
>> todo#1 created

查詢,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
  "rows": [{
      "id": 1,
      "description": "hello, world",
      "completed": 0
    }
  ],
  "more": false
}

完成事項,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user complete '["todo.user",1]' -p todo.user
executed transaction: 11423637cb321969961b3ce0305c93ba7f95a83b6d82c1a4e31a08c569f2dcaa  104 bytes  312 us
#     todo.user <= todo.user::complete          {"author":"todo.user","id":1}
>> todo#1 marked as complete
warning: transaction executed locally, but may not be confirmed by the network yet

再次查詢,completed字段已置爲1,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
  "rows": [{
      "id": 1,
      "description": "hello, world",
      "completed": 1
    }
  ],
  "more": false
}

創建多條記錄,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user create '["todo.user",2,"eos funk"]' -p todo.user
executed transaction: 35f417a4d7438e6ea9ffd837e3c261fca0cb3926b534dc6603fdb38f94d5cd77  120 bytes  329 us
#     todo.user <= todo.user::create            {"author":"todo.user","id":2,"description":"eos funk"}
>> todo#2 created
warning: transaction executed locally, but may not be confirmed by the network yet
liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user create '["todo.user",10,"go to bank"]' -p todo.user
executed transaction: cd4cd2c85500e93a79eb5c3b3852a0a96a9fb220696c63f2683b473d58a6ca34  120 bytes  311 us
#     todo.user <= todo.user::create            {"author":"todo.user","id":10,"description":"go to bank"}
>> todo#10 created
warning: transaction executed locally, but may not be confirmed by the network yet

查詢,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
  "rows": [{
      "id": 1,
      "description": "hello, world",
      "completed": 1
    },{
      "id": 2,
      "description": "eos funk",
      "completed": 0
    },{
      "id": 10,
      "description": "go to bank",
      "completed": 0
    }
  ],
  "more": false
}

刪除一條數據,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos push action todo.user destroy '["todo.user",2]' -p todo.user
executed transaction: c87e37f4521e0ce2a865acbeb63c0f3a0681b9b5a0a6c5db8cd53584d6d13dca  104 bytes  2198 us
#     todo.user <= todo.user::destroy           {"author":"todo.user","id":2}
>> todo#2 destroyed
warning: transaction executed locally, but may not be confirmed by the network yet

再次查詢,

liuwenbin@liuwenbin-H81M-DS2:~$ cleos get table todo.user todo.user todos
{
  "rows": [{
      "id": 1,
      "description": "hello, world",
      "completed": 1
    },{
      "id": 10,
      "description": "go to bank",
      "completed": 0
    }
  ],
  "more": false
}

這是一個完整的,通過multi_index進行curd的一個例子(第一個例子是官方文檔給出的,其中內容有bug)。

總結

通過本篇文章的學習,我們掌握瞭如何在EOS中使用智能合約調用multi_index實現數據的持久化。

參考文檔

  • EOS官方文檔
  • EOS asia
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章