原文連結:醒者呆的部落格園,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…