EOS入門指南PART6——別忙著開發,先來看看智慧合約資料是怎麼存的
上一章我們學習了開發智慧合約之前需要知道的必要概念:
- 什麼是webAssembly以及它在智慧合約上下游中的位置;
- 什麼是ABI以及怎樣使用eosiocpp工具產生ABI和wasm、wast
hello
智慧合約的簡單入門:部署和呼叫如果說智慧合約開發是一個鎖著門的圖書館,那麼之前的學習就是鑰匙。現在我們終於可以拿著鑰匙開啟大門,走進去一探究竟。
摘要
說到智慧合約開發,大家首先想到的肯定是寫程式碼、像solidity開發一樣教語法。EOS的智慧合約是用C++寫的,基本語法大家可以去買本C++的書;這一篇主要介紹EOS智慧合約的資料儲存,為大家掃清智慧合約開發道路上的最後一道障礙。
什麼是RAM
之前我們在學習如何在主網建立賬戶
時,曾經接觸到RAM
的概念。我們不能像操作以太坊那樣,憑空生成一個地址作為自己的賬戶,而是要先有一個賬戶,再去建立另一個賬戶。其中就涉及到一個關於RAM很關鍵的知識點:建立賬戶需要消耗RAM。
當時我們只是很模糊地知道,RAM大概是記憶體的意思。
RAM is used to store data in an in-memory database. DApps will use this to store state information so it is quickly available to their app.
RAM是EOS主網中的記憶體,用於儲存使用者在EOS中使用頻率高的資料(賬戶餘額、合約狀態等)。
BM對EOS的期望是成為區塊鏈世界中的【作業系統】,因此可以把計算機的一些概念對標到EOS中,其中計算機中的執行記憶體,在EOS中就可以看做是RAM;而硬碟就可以對標EOS區塊鏈資料庫。
所以高訪問量的決策類資料,例如賬戶餘額、智慧合約的當前狀態等就會被儲存在RAM中,並且這部分資料將長期佔用RAM;而低訪問的費決策性存證資料,例如交易資料,就會儲存在EOS系統的硬碟中,也就是區塊鏈中。當儲存賬號狀態的空間不足,即RAM不足時,轉賬或部署合約等相關操作就無法執行。
什麼是EOS資料庫
之前我們曾經介紹過transaction和action,action是智慧合約執行的基本單元,transaction可以認為是一個或幾個action組成的原子性操作。action在被稱為action上下文
action上下文
提供了執行action所需的一些條件,其中一個就是action的工作記憶體,這是action儲存工作狀態的地方。在處理一個action之前,eosio會先為它清理一次記憶體,因此當變數在一個action中被賦值後,另一個action的上下文是拿不到這個值的。那麼在action之間傳遞狀態的唯一方法就是把它持久儲存到EOS資料庫中,如下圖:
這個持久化儲存就是資料庫儲存資料。EOS允許智慧合約定義自己的私有資料庫表。比如上圖,Apply Context的內容都是一次性的,一次action執行完成,物件就釋放了,只有儲存到EOSIO database的才被儲存。
什麼是multi_index
接著上面介紹的資料庫往下說, 這個私有資料表是通過multi_index
來訪問和互動的。EOS的multi_index
類似boost的multi_index
,即多索引容器。有了多級索引,智慧合約就具備了操作類似資料庫模組的功能。
multi_index是一個非常方便的、可以和資料庫互動的容器。
從字面意思來看,multi_index就是一個可以使用多索引的資料表。
如下圖:
每一個multi_index容器都可以理解成傳統資料庫中的一張表,但是行和列稍有不同。和傳統的多列的表不同的是,multi_index只有一列。這一列中的每一行都表示一個物件,通常這個物件是struct
或者是class
型別的,有多個成員變數。因此雖然只有一列,但是multi_index的靈活性絲毫不亞於傳統的資料表。
舉個例子,比如下面的這個struct
:
// @abi table proposal i64
struct Proposal {
uint64_t id;
account_name owner = 0;
string description;
std::vector<account_name, uint32_t> votes;
uint64_t primary_key() const { return id; }
EOSLIB_SERIALIZE( Proposal, (id)(owner)(description)(votes))
};
typedef eosio::multi_index<N(Proposal), Proposal> proposals;
首先看第一行:
// @abi table proposal i64
在部署合約之前,我們都會用eosiocpp
來生成ABI,EOS智慧合約編譯器可以讀取struct
結構體和public
方法之前的注。在註釋中我們可以傳入兩種型別:action
和table
,ABI就會根據我們的宣告,自動在生成的ABI中新增相應的方法或者表定義。
第二個proposal
就是表名,而第三個i64
就是表的主鍵的型別。在這裡主鍵就是id
。
account_name
注意到第二個成員變數owner
:
account_name owner = 0;
這裡的account_name
是EOS自己定義的型別,也就是之前我們曾經建立過的賬戶名。可以理解成以太坊中的address
型別。
vector
std::vector<account_name, uint32_t> votes;
vector
這裡可以理解成以太坊中的mapping
- 自定義的一個對映集合。
Primary Keys
uint64_t primary_key() const { return id; }
eosio::multi_index
規定,每行必須有一個主鍵,型別為64位的無符號整型。表中的物件都會根據主鍵,升序或者降序排列。通過在struct
中定義primary_key()
方法來獲取。在這個例子中,Proposal
的主鍵就是id
,型別為uint64_t
。當然主鍵的型別也可以是account_name
等。(account_name在eos中被儲存為uint64_int
型別)。
資料序列化
EOSLIB_SERIALIZE( Proposal, (id)(owner)(description)(votes))
通過閱讀contracts/eosiolib/serialize.hpp
檔案可以知道,它其實是使用了BOOST_PP_SEQ_FOR_EACH
巨集。它的作用基本上就是賦予了struct
額外的操作,可以把資料序列化到multi_index
,或者從multi_index
中反序列化出來。
multi_index相關操作
雖然不想SQL語句那樣豐富,但是multi_index依然提供了一些基礎的操作:
- 建立:使用
.emplace
- 查詢:使用
.find
- 修改已存在的入口:使用
.modify
舉個查詢的例子:
auto itr = proposals.find(proposal_id)
以上語句查詢了特定id的proposal,它返回的是一個迭代器iterator
。這就涉及到我們如何使用表中的資料,答案就是迭代器。
可以把迭代器想象成一個電梯,在整個資料表中上下滑動(來定位資料),任何對資料的操作都必須通過迭代器。
結束語
這一章概念雖然有些多,但是值得大家仔細研究。這一章我們學習了:
- RAM的概念:儲存使用者高頻使用的資料的地方,比如當前狀態;
- EOS資料庫的概念以及如何儲存;
- multi_index是什麼 - 和資料庫互動的容器
這一章理論性的內容偏多,理解上可能不如之前的幾篇直觀。下一篇我們將學習如何操作multi_index,以此帶大家更加深入地理解multi_index,同時正式開啟我們的智慧合約開發之旅。