1. 程式人生 > >EOS智慧合約開發(一)

EOS智慧合約開發(一)

一、 智慧合約功能

EOSIO智慧合約是在區塊鏈上註冊並在EOSIO節點上執行的軟體,實現了“合約”的功能,合約行動請求帳目儲存在區塊鏈中。智慧合約定義了介面(操作,引數,資料結構)和實現介面的程式碼。程式碼被編譯成規範的位元組碼格式,使節點可以檢索和執行。區塊鏈儲存合約的交易(例如,合法轉移,遊戲移動)。每份智慧合約都必須附有一份李嘉圖合約,其中定義了具有法律約束力的條款和條件。

二、 通訊模式

2.1 執行機制

EOSIO智慧合約由一組操作和型別定義組成。操作定義指定並實現合約的行為,型別定義指定所需的內容和結構。 操作主要在基於訊息的通訊體系結構中執行:客戶端通過向nodeos傳送(推送)訊息來呼叫操作。可以使用cleos命令完成。它也可以使用EOSIO傳送方法(例如,eosio :: action :: send)來完成。 nodeos將操作請求分派給實現合約的WASM程式碼,該程式碼執行完之後繼續處理下一個操作。
EOSIO智慧合約可以彼此通訊,例如,讓另一個合約執行與當前交易的完成相關的某些操作,或者觸發當前交易範圍之外的未來交易。
EOSIO支援兩種基本通訊模型,內聯和延遲。在當前交易中執行的操作是內聯操作的示例,而觸發的將來交易是延遲操作的示例。
合約之間的溝通應視為非同步發生,非同步通訊模型可能導致垃圾郵件,資源限制演算法將解析垃圾郵件。

2.2 內聯通訊

內聯通訊採用請求其他操作的形式,需要作為呼叫操作的一部分執行。其使用和原始交易的相同作用域和許可權進行操作,並保證使用當前交易執行,即呼叫交易中的巢狀交易。 如果交易的任何部分失敗,則內聯操作將與交易的其餘部分一起展開。無論成功與否,呼叫內聯操作都不會在交易範圍之外生成任何通知。

2.3 延遲通訊

延遲通訊在採用傳送到對等交易的動作通知的形式。 根據生產者的判斷,延遲的操作最多可以安排在稍後的時間執行,無法保證將執行延期操作。從始發交易的角度來看,即建立延遲交易的交易,它只能確定建立請求是否成功提交或者是否失敗(如果失敗,它將立即失敗)。 延期交易具有傳送合約的許可權,交易可以取消延期交易。

2.4 交易和動作

動作表示單個操作,而交易是一個或多個動作的集合。 合約和帳戶以行動的形式進行溝通。 如果要將操作作為一個整體執行,則可以單獨傳送操作,也可以以組合形式傳送操作。
交易中可以有一個動作也可以有多個 ,當有多個操作時,其中一個操作失敗,則這個交易失敗。

2.5 交易確認

交易完成後,將生成交易收據,此收據採用雜湊形式。 接收交易雜湊並不意味著交易已被確認,它只意味著節點接受它而沒有錯誤,這也意味著其他生產者很可能會接受它。通過確認,在交易歷史記錄中可以看到包含它的塊編號的交易。

2.6 動作處理程式和動作的apply上下文

智慧合約提供動作處理程式來執行所請求操作的工作。每次動作執行時,通過在合約實現中執行apply方法“應用”操作,EOSIO會建立一個新的動作“應用”上下文,動作在該上下文中執行。 下圖說明了apply上下文動作的關鍵元素:


EOSIO網路中的每個節點都會獲得每個合約中的每個動作的副本並執行。一些節點正在執行合約的實際工作,而其他節點正在處理以證明交易塊的有效性。因此,重要的是合約能夠確定“他們是誰”,或者基本上,他們在哪個環境下執行。在動作上下文中提供上下文標識資訊,如上圖中 的receiver, code, action。 receiver是當前正在處理該操作的帳戶。code 是授權合約的帳戶。 action是當前正在執行的操作的ID。
動作在交易中運作;如果交易失敗,則必須回滾所有操作的結果。動作上下文的關鍵部分是當前交易資料,這包含交易頭、交易中所有原始操作的有序向量、交易中上下文自由操作的向量,由code定義的可修復的上下文無關資料集(作為blob的向量提供)實現合約,以及blob向量的完整索引。
在處理動作之前,EOSIO會為動作設定一個乾淨的工作記憶體。操作的工作記憶體僅對該操作可用,即使對於同一交易中的操作也是如此。在執行另一個操作時可能已設定的變數在另一個操作的上下文中不可用。在動作之間傳遞狀態的唯一方法是將其持久化並從EOSIO資料庫中檢索它。動作可能有許多影響, 其中包括:更改狀態在EOSIO持久儲存中保持不變;通知當前交易的收件人;將內聯操作請求傳送給新接收方;生成新的(延期)交易;取消現有的延期交易。

2.7 交易限制

每個交易必須在30ms或更短時間內執行。 如果交易包含多個操作,並且這些操作的總和大於30毫秒,則整個交易將失敗。 在沒有對其操作進行併發性要求的情況下,可以通過在單獨的交易中包含CPU消費動作來規避這種情況。

三、檔案結構

3.1 建立框架

$ eosiocpp -n ${contract}

以上將在./$ {conttract}目錄中建立一個帶有兩個檔案:

${contract}.hpp ${contract}.cpp

3.2 apply處理程式

每個智慧合約都必須提供應用操作處理程式。 apply動作處理程式是一個偵聽所有傳入操作並執行所需行為的函式。 為了響應特定操作,code識別和響應特定操作請求。 apply使用receiver、code、action輸入引數作為過濾器,來對映到實現特定操作的所需功能。 apply函式可以使用以下方法過濾code引數:

if (code == N(${contract_name}) {
   //對特定動作的處理
}

在給定code之後,可以使用如下方法過濾action引數:

if (action == N(${action_name}) {
    //your handler to respond to a particular action
}

3.3 EOSIO_ABI 巨集

EOSIO_ABI巨集封裝了apply函式的基本動作:

#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( code == self ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
} \

只需要在巨集中指定合約中的code和action名稱即可,所有底層C程式碼對映邏輯都由巨集生成。 如EOSIO_ABI(hello,(hi))中,hello和hi是來自合約的值。
apply函式記錄所交付的行為,不做任何其他檢查。 如果塊生產者允許,任何人都可以隨時提供任何操作。 如果沒有任何必要的簽名,合約將按消耗的頻寬計費。
(1)apply:動作處理程式,它監聽所有傳入的動作並根據函式中的規範做出反應。 需要兩個輸入引數code和action。
(2)code filter:

if (code == N(${contract_name}) {
    // your handler to respond to particular action
}

(3)action filter:

if (action == N(${action_name}) {
    //your handler to respond to a particular action
}

(4)wast:部署到區塊鏈的程式必須是wast格式,編譯方法如下:

$ eosiocpp -o ${contract}.wast ${contract}.cpp

(5)abi:應用程式二進位制介面(ABI)是一個JSON,在JSON和二進位制之間轉換使用者操作。 abi還描述瞭如何將資料庫狀態轉換為JSON或從JSON轉換。 通過abi描述合約後,開發人員和使用者將能夠通過JSON無縫地與所開發的合約進行互動。可以如下編譯產生:

$ eosiocpp -g ${contract}.abi ${contract}.hpp

ABI的一個例子:

{
//定義現有型別的別名列表,在這裡,將name定義為account_name的別名。
  "types": [{
      "new_type_name": "account_name",
      "type": "name"
    }
  ],
  "structs": [{
  //在此處定義了transfer型別
      "name": "transfer",
      "base": "",
  //from、to、quantity與"types"相對應
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
      "name": "account",
      "base": "",
      "fields": {
        "account": "name",
        "balance": "uint64"
      }
    }
  ],
  //在此定義了action:transfer,型別為transfer。告訴EOSIO遇到${account}->transfer時應該載入trransfer類
  "actions": [{
      "action": "transfer",
      "type": "transfer"
    }
  ],
  "tables": [{
      "table": "account",
      "type": "account",
      "index_type": "i64",
      "key_names" : ["account"],
      "key_types" : ["name"]
    }
  ]
}

四、多索引資料庫API

EOSIO提供了一組服務和介面,使合約開發人員能夠跨行動保持狀態,從而保持事務邊界。 如果沒有永續性,當處理超出範圍時,在處理操作和事務期間生成的狀態將丟失。
對永續性服務的需求
acttion執行EOSIO合同的工作。 action在上下文的環境中執行。 如action apply上下文關係圖中所示,操作上下文提供了執行操作所需的一些操作。 在處理操作之前,EOSIO會為操作設定一個乾淨的工作記憶體。 在操作之間傳遞狀態的唯一方法是將其持久化並從EOSIO資料庫中檢索它。
永續性元件包括:
1)在資料庫中保持狀態的服務
2)增強查詢功能以查詢和檢索資料庫內容
3)服務的C ++ API,供合約開發人員使用
4)用於訪問核心服務的C API,對庫和系統開發人員有用

4.1 EOSIO多索引API

eosio :: multi_index對應傳統資料庫中的表,其中行是容器中的單個物件,列是容器中物件的成員屬性,索引提供了物件的快速查詢。 key與物件成員屬性相容。
傳統的資料庫表允許索引是表中某些列的使用者定義函式。 eosio :: multi_index類似地允許索引是任何使用者定義的函式(作為元素型別的類/結構的成員函式提供),但其返回值僅限於一組受限的支援鍵型別。
統的資料庫表通常只有一個允許的唯一主鍵明確標識表中的特定行,並提供表中行的標準排序順序。 eosio :: multi_index支援類似的語義,但eosio :: multi_index容器中物件的主鍵必須是唯一的無符號64位整數。 eosio :: multi_index容器中的物件按主鍵索引按無符號64位整數主鍵的升序排序。

4.2 EOSIO多索引迭代器

EOSIO永續性服務提供多索引迭代器。 與僅提供鍵值儲存的其他區塊鏈不同,EOSIO多索引表允許合同開發者保留按各種不同鍵型別排序的物件集合,這些鍵型別可以從物件內的資料派生。 這實現了豐富的檢索功能。 最多可以定義16個二級索引,每個索引都有自己的排序和檢索表內容的方式。
EOSIO多索引迭代器遵循C ++迭代器通用的模式。 所有迭代器都是雙向const,const_iterator或const_reverse_iterator。 可以取消引用迭代器以提供對多索引表中物件的訪問。

4.3 建立多索引表

以下是使用EOSIO Multi-Index表建立自己的持久資料的步驟:
1)使用C ++類或結構體定義物件。每個物件都在其自己的多索引表中。
2)在名為primary_key的類/結構中定義一個const成員函式,該函式返回物件的uint64_t主鍵值。
3)確定二級指數。最多支援16個附加索引。二級指數支援幾種金鑰型別:
idx64 - 原始64位無符號整數鍵
idx128 - 原始128位無符號整數鍵,或128位固定大小的詞典鍵
idx256 - 256位固定大小的詞典鍵
idx_double - 雙精度浮點鍵
idx_long_double - 四倍精度浮點鍵
為每個二級索引定義一個金鑰提取器。金鑰提取器是用於從多索引表的元素獲取金鑰的函式。

4.4 建立多索引表

(1)例項化多索引表

#include 
using namespace eosio;
using namespace std;
class addressbook: contract {
  struct address {
     uint64_t account_name;
     string first_name;
     string last_name;
     string street;
     string city;
     string state;
     uint64_t primary_key() const { return account_name; }
     EOSLIB_SERIALIZE( address, (account_name)(first_name)(last_name)(street)(city)(state) )
  };
  public:
    addressbook(account_name self):contract(self) {}
    typedef eosio::multi_index< N(address), address > address_index;
    void myaction() {
      address_index addresses(_self, _self); // code, scope
    }
}
EOSIO_ABI( addressbook, (myaction) )

(2)插入操作:

#include 
using namespace eosio;
using namespace std;
class addressbook: contract {
  struct address {
     uint64_t account_name;
     string first_name;
     string last_name;
     string street;
     string city;
     string state;
     uint64_t primary_key() const { return account_name; }
     EOSLIB_SERIALIZE( address, (account_name)(first_name)(last_name)(street)(city)(state) )
  };
  public:
    addressbook(account_name self):contract(self) {}
    typedef eosio::multi_index< N(address), address > address_index;
    void myaction() {
      address_index addresses(_self, _self); // code, scope
      // add to table, first argument is account to bill for storage
      addresses.emplace(_self, [&](auto& address) {
        address.account_name = N(dan);
        address.first_name = "Daniel";
        address.last_name = "Larimer";
        address.street = "1 EOS Way";
        address.city = "Blacksburg";
        address.state = "VA";
      });
    }
}
EOSIO_ABI( addressbook, (myaction) )

(2)修改:

#include 
using namespace eosio;
using namespace std;
class addressbook: contract {
  struct address {
     uint64_t account_name;
     string first_name;
     string last_name;
     string street;
     string city;
     string state;
     uint64_t primary_key() const { return account_name; }
     EOSLIB_SERIALIZE( address, (account_name)(first_name)(last_name)(street)(city)(state) )
  };
  public:
    addressbook(account_name self):contract(self) {}
    typedef eosio::multi_index< N(address), address > address_index;
    void myaction() {
      address_index addresses(_self, _self); // code, scope
      // add to table, first argument is account to bill for storage
      addresses.emplace(_self, [&](auto& address) {
        address.account_name = N(dan);
        address.first_name = "Daniel";
        address.last_name = "Larimer";
        address.street = "1 EOS Way";
        address.city = "Blacksburg";
        address.state = "VA";
      });
      auto itr = addresses.find(N(dan));
      eosio_assert(itr != addresses.end(), "Address for account not found");
      addresses.modify( itr, account payer, [&]( auto& address ) {
        address.city = "San Luis Obispo";
        address.state = "CA";
      });
    }
}
EOSIO_ABI( addressbook, (myaction) )

(3)刪除

#include 
using namespace eosio;
using namespace std;
class addressbook: contract {
  struct address {
     uint64_t account_name;
     string first_name;
     string last_name;
     string street;
     string city;
     string state;
     uint64_t primary_key() const { return account_name; }
     EOSLIB_SERIALIZE( address, (account_name)(first_name)(last_name)(street)(city)(state) )
  };
  public:
    addressbook(account_name self):contract(self) {}
    typedef eosio::multi_index< N(address), address > address_index;
    void myaction() {
      address_index addresses(_self, _self); // code, scope
      // add to table, first argument is account to bill for storage
      addresses.emplace(_self, [&](auto& address) {
        address.account_name = N(dan);
        address.first_name = "Daniel";
        address.last_name = "Larimer";
        address.street = "1 EOS Way";
        address.city = "Blacksburg";
        address.state = "VA";
      });
      auto itr = addresses.find(N(dan));
      eosio_assert(itr != addresses.end(), "Address for account not found");
      addresses.erase( itr );
      eosio_assert(itr != addresses.end(), "Address not erased properly");
    }
}
EOSIO_ABI( addressbook, (myaction) )

(5)get
使用主鍵查詢,返回對包含指定主鍵的物件的常量引用。

#include 
using namespace eosio;
using namespace std;
class addressbook: contract {
  struct address {
     uint64_t account_name;
     string first_name;
     string last_name;
     string street;
     string city;
     string state;
     uint64_t primary_key() const { return account_name; }
     EOSLIB_SERIALIZE( address, (account_name)(first_name)(last_name)(street)(city)(state) )
  };
  public:
    addressbook(account_name self):contract(self) {}
    typedef eosio::multi_index< N(address), address > address_index;
    void myaction() {
      address_index addresses(_self, _self); // code, scope
      // add to table, first argument is account to bill for storage
      addresses.emplace(_self, [&](auto& address) {
        address.account_name = N(dan);
        address.first_name = "Daniel";
        address.last_name = "Larimer";
        address.street = "1 EOS Way";
        address.city = "Blacksburg";
        address.state = "VA";
      });
      auto user = addresses.get(N(dan));
      eosio_assert(user.first_name == "Daniel", "Couldn't get him.");
    }
}
EOSIO_ABI( addressbook, (myaction) )

(6)find
使用主鍵在表中搜索現有物件。如果找不到具有主鍵primary的物件,則找到的物件的迭代器,其主鍵等於主鍵或引用表的結束迭代器。

#include 
using namespace eosio;
using namespace std;
class addressbook: contract {
  struct address {
     uint64_t account_name;
     string first_name;
     string last_name;
     string street;
     string city;
     string state;
     uint64_t primary_key() const { return account_name; }
     EOSLIB_SERIALIZE( address, (account_name)(first_name)(last_name)(street)(city)(state) )
  };
  public:
    addressbook(account_name self):contract(self) {}
    typedef eosio::multi_index< N(address), address > address_index;
    void myaction() {
      address_index addresses(_self, _self); // code, scope
      // add to table, first argument is account to bill for storage
      addresses.emplace(_self, [&](auto& address) {
        address.account_name = N(dan);
        address.first_name = "Daniel";
        address.last_name = "Larimer";
        address.street = "1 EOS Way";
        address.city = "Blacksburg";
        address.state = "VA";
      });
      auto itr = addresses.find(N(dan));
      eosio_assert(itr != addresses.end(), "Couldn't get him.");
    }
}
EOSIO_ABI( addressbook, (myaction) )