【劉文彬】EOS技術研究:合約與資料庫互動

圓方圓區塊鏈發表於2018-12-11

原文連結:醒者呆的部落格園,www.cnblogs.com/Evsward/p/m…

智慧合約操作鏈資料庫是很常見的應用場景。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檔案。 eosiocpp -g vehicle.abi vehicle.cpp | eosiocpp -o vehicle.wasm vehicle.cpp 然後在終端中部署該合約, cleos set contract one work/CLionProjects/github.com/eos/contracts/vehicle 呼叫合約的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))
複製程式碼

這裡加入了對資料的增刪改查功能。然後我們使用 eosiocpp -o todolist.wast todolist.cpp && eosiocpp -g todolist.abi todolist.cpp 建立對應的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

相關文章和視訊推薦

圓方圓學院彙集大批區塊鏈名師,打造精品的區塊鏈技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。

公開課地址:ke.qq.com/course/3451…

相關文章