EOS Asia
本教程作者為 EOS Asia,亞洲最具技術(shù)實(shí)力和最國(guó)際化的 EOS 超級(jí)節(jié)點(diǎn)競(jìng)選者。EOS Asia 同時(shí)也是 EOS Gems 和 Traffic Exchange Token 這兩個(gè)項(xiàng)目背后的開(kāi)發(fā)者。
本篇是 EOS 智能合約系列第二彈,該系列教程旨在幫助開(kāi)發(fā)者從 0 到 1 快速上手如何在 EOS 生態(tài)下開(kāi)發(fā) DApp。如果有任何希望深入討論的主題,歡迎留言給我們!如果你還不知道怎么在 EOS 下部署智能合約,請(qǐng)先閱讀系列第一彈《EOS智能合約開(kāi)發(fā):第一課》
在大部分的應(yīng)用場(chǎng)景中,開(kāi)發(fā)者都需要通過(guò)智能合約與區(qū)塊鏈上“永久保存”的數(shù)據(jù)進(jìn)行交互。本次教程中,我們會(huì)一起通過(guò) To-do List(待辦事項(xiàng))這個(gè)實(shí)例,來(lái)教會(huì)你如何實(shí)現(xiàn)與數(shù)據(jù)交互的標(biāo)準(zhǔn)操作(CRUD - Create, Retrieve, Update, Delete)
深入了解 Boost.MultiIndex
由于 EOS 的智能合約基于 C++,我們需要利用 Boost.MultiIndex Containers這個(gè)庫(kù)。下面是該庫(kù)的說(shuō)明:
Boost多索引容器庫(kù)提供了名為 multi_index_container 的類模板,可以用于建造擁有一個(gè)或多個(gè)索引(indices) 的容器,不同的索引具有不同的排序和訪問(wèn)語(yǔ)義。這些索引都提供了類似于STL容器的接口,因此使用起來(lái)也非常相似。在一組元素之上維護(hù)多個(gè)索引的想法來(lái)自 于關(guān)系數(shù)據(jù)庫(kù),并且考慮到簡(jiǎn)單的 set 和 map 無(wú)法滿足多索引表中的復(fù)雜數(shù)據(jù)結(jié)構(gòu)的規(guī)范。
讓我們把上述的一些概念拆分講解一下,并與開(kāi)發(fā)者所熟知的傳統(tǒng)數(shù)據(jù)庫(kù)概念做類比:
容器(Containers)
包含很多元素的類(table/表)
元素(Elements)
數(shù)據(jù)對(duì)象(rows in a table/表中的行)
接口(Interface)
容器讀取元素的方法(query/查詢)
在 EOS 智能合約中,可以使用eosio::multi_index?來(lái)定義多索引容器。如果我們讀一讀使用了這個(gè)特性的一些合約例子,比如這個(gè)“骰子合約”:
https://github.com/EOSIO/eos/blob/master/contracts/dice/dice.cpp
你會(huì)發(fā)現(xiàn)很難真正搞明白到底是哪一部分是在處理區(qū)塊鏈上的數(shù)據(jù)。不過(guò)別擔(dān)心,我們會(huì)帶你理解它,很快你就能自己實(shí)現(xiàn)一個(gè)有存儲(chǔ)功能的智能合約。
我們將通過(guò)開(kāi)發(fā)一個(gè)To-do List (待辦事項(xiàng)表)小 DApp 來(lái)理解上述的內(nèi)容。從功能上,要能勾掉已經(jīng)完成的事情,添加新事項(xiàng),以及刪除不需要的事項(xiàng)。在這個(gè)例子中,我們將用?todos?作為容器名,todo?作為元素結(jié)構(gòu)。
從初始化第一個(gè)容器為開(kāi)始,首先,我們向?eosio::multi_index?傳入兩個(gè)模板參數(shù)。第一個(gè)參數(shù)是我們的容器名,第二個(gè)參數(shù)是定義元素的數(shù)據(jù)結(jié)構(gòu)。來(lái)給我們的 todo 模型創(chuàng)建一個(gè)小例子,如下:
struct todo {
?uint64_t id;
?uint64_t primary_key() const { return id; }
?EOSLIB_SERIALIZE(todo, (id))
};
typedef eosio::multi_index todo_table;
todo_table todos;
簡(jiǎn)單有效!我們簡(jiǎn)單地定義了一個(gè) 64 位無(wú)符號(hào)整型的 ID,并通過(guò) primary_key 來(lái)訪問(wèn)它。把多索引定義成 typedef,暫時(shí)還不需要把它實(shí)例化。目前為止,這個(gè) todo 模型里面還沒(méi)有什么東西,下面來(lái)添加一些參數(shù):
struct todo {
?uint64_t id;
?std::string description;
?uint64_t completed;
?EOSLIB_SERIALIZE(todo, (id)(description)(completed))
};
typedef eosio::multi_index todo_table;
todo_table todos;
現(xiàn)在我們更近了一步,加入了待辦事項(xiàng)的描述參數(shù)-?description?(比如 “完成小說(shuō)撰寫(xiě)”)和狀態(tài)參數(shù)-?completed(用來(lái)記錄一個(gè)事項(xiàng)在當(dāng)前是否完成了)。
為了方便自動(dòng)生成我們的ABI(Application Binary Interface),我們?cè)谌萜鞫x前面加一行注釋來(lái)幫助生成器:@abi table profiles i64
那么在注釋里的i64是什么意思呢,它是我們的查詢索引。默認(rèn)情況下,我們需要一種在容器里查詢?cè)氐姆椒?,而我們的?4位(64位類型下,基本上是first key)就可以用來(lái)干這件事。一般情況下都用uint64_t id;對(duì)于first key,也可以用account_name類型,因?yàn)樵诘讓悠鋵?shí)account_name類型也是一個(gè)uint64_t類型。參考如下:
https://github.com/EOSIO/eos/blob/2f2c8c7e3811caca178a7553192c8fe59a22576d/contracts/eosiolib/types.h#L22
此時(shí)我們應(yīng)該有了一個(gè)功能簡(jiǎn)單的容器, 代碼看起來(lái)是這樣的:
// @abi table todos i64
struct todo {
?uint64_t id;
?std::string description;
?uint64_t completed;
?uint64_t primary_key() const { return id; }
?EOSLIB_SERIALIZE(todo, (id)(description)(completed))
};
typedef eosio::multi_index todo_table;
todo_table todos;
使用你的新容器
現(xiàn)在已經(jīng)有了一個(gè)定義好的容器,我們可以使用它里面的元素。在智能合約里,將通過(guò)不同的函數(shù)與這些元素進(jìn)行交互。
對(duì)于鏈上的永久性儲(chǔ)存有四種基本函數(shù):創(chuàng)建(Create),檢索 (Retrieve),更新 (Update),刪除 (Delete)。 在這個(gè)例子里,我們不需要考慮檢索,因?yàn)闄z索是由前端讀取合約來(lái)處理的而不用函數(shù)。對(duì)其他的三個(gè),我們將分別創(chuàng)建函數(shù)。
創(chuàng)建(Create)- 創(chuàng)建事項(xiàng)
添加一個(gè)待辦事項(xiàng)進(jìn)入列表
可以用?emplace?完成
// @abi action
void create(account_name author, const uint32_t id, const std::string& description) {
?todos.emplace(author, [&](auto& new_todo) {
? ?new_todo.id ?= id;
? ?new_todo.description = description;
? ?new_todo.completed = 0;
?});
?eosio::print("todo#", id, " created");
}
一個(gè)重要細(xì)節(jié)是我們把 author 作為一個(gè)參數(shù)也傳入了。在?emplace?方法中第一個(gè)參量是必須的。
更新(Update)- 完成事項(xiàng)
創(chuàng)建一個(gè)完成事項(xiàng)的函數(shù)可以通過(guò)更新參數(shù) completed 的狀態(tài)來(lái)實(shí)現(xiàn):
// @abi action
void complete(account_name author, const uint32_t id) {
?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");
}
刪除(Delete)- 刪除事項(xiàng)
這是一個(gè)內(nèi)部調(diào)用的智能合約,不用太擔(dān)心安全性和權(quán)限問(wèn)題。 我們專心搞清楚刪除函數(shù)如何最簡(jiǎn)化地實(shí)行就可以了:
// @abi action
void destroy(account_name author, const uint32_t id) {
?auto todo_lookup = todos.find(id);
?todos.erase(todo_lookup);
?eosio::print("todo#", id, " destroyed");
}
部署,測(cè)試,與前端打通
在上一篇教程中,我們用一個(gè)簡(jiǎn)單的 ping/pong 實(shí)例講述了如何將一個(gè) EOS 的智能合約與網(wǎng)頁(yè)前端連接起來(lái)?,F(xiàn)在我們有了幾個(gè)與區(qū)塊鏈上的永久性數(shù)據(jù)交互的函數(shù),下面可以為這個(gè)待辦事項(xiàng)制作一個(gè)前端了。
部署
部署合約的過(guò)程比較直觀,就下面這幾步:
1. 建立合約 ABI 和 WASM:eosiocpp -o hello.wast hello.cpp && eosiocpp -g hello.abi hello.cpp
2. 建立賬戶/錢包:
cleos create account eosio todo.user EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4
cleos set contract todo.user ../todo -p todo.user
3.?測(cè)試合約也很簡(jiǎn)單:
$ cleos push action todo create '["todo", 1, "hello world"]' -p todo.user
executed transaction: bc5bfbd1e07f6e3361d894c26d4822edcdc2e42420bdd38b46a4fe55538affcf ?248 bytes ?107520 cycles
# ? ? ? ? ?todo <= todo::create ? ? ? ? ? ? ? ? {"author":"todo","id":1,"description":"hello world"}
>> todo created
4.?獲取數(shù)據(jù):
$ cleos get table todo todo todos
在前端測(cè)試
在這里我們就節(jié)省讀者的時(shí)間,不在文章中深究 React.js 的代碼了,不過(guò)我強(qiáng)烈推薦大家去看下這個(gè)例子的代碼倉(cāng)庫(kù),里面有前端部分的全部代碼:?
https://github.com/eosasia/eos-todo
筆記來(lái)源:https://bihu.com/article/402751