1. 程式人生 > >EOS入門指南PART6——別忙著開發,先來看看智慧合約資料是怎麼存的

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資料庫中,如下圖:

action_and_database

這個持久化儲存就是資料庫儲存資料。EOS允許智慧合約定義自己的私有資料庫表。比如上圖,Apply Context的內容都是一次性的,一次action執行完成,物件就釋放了,只有儲存到EOSIO database的才被儲存。

什麼是multi_index

接著上面介紹的資料庫往下說, 這個私有資料表是通過multi_index來訪問和互動的。EOS的multi_index類似boost的multi_index,即多索引容器。有了多級索引,智慧合約就具備了操作類似資料庫模組的功能。

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方法之前的注。在註釋中我們可以傳入兩種型別:actiontable,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。這就涉及到我們如何使用表中的資料,答案就是迭代器。

eos_iterator

可以把迭代器想象成一個電梯,在整個資料表中上下滑動(來定位資料),任何對資料的操作都必須通過迭代器。

結束語

這一章概念雖然有些多,但是值得大家仔細研究。這一章我們學習了:

  • RAM的概念:儲存使用者高頻使用的資料的地方,比如當前狀態;
  • EOS資料庫的概念以及如何儲存;
  • multi_index是什麼 - 和資料庫互動的容器

這一章理論性的內容偏多,理解上可能不如之前的幾篇直觀。下一篇我們將學習如何操作multi_index,以此帶大家更加深入地理解multi_index,同時正式開啟我們的智慧合約開發之旅。