EOSIO Developer Portal 解説 データの永続化とインラインアクション 第1回

  • 2020.02.12
  • 2020.02.26
  • EOS
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第1回

はじめに


本記事ではEOS Developer Portalで紹介されているaddressbookコントラクトを用いて、EOSにおけるデータの永続化と、コントラクト内でアクションを実行するインラインアクションについて、全3回で解説します。

第1回では、EOSにおけるデータの扱いやMulti-Index APIなどを中心に、データの永続化について解説します。

本記事は、過去にコンセンサス・ベイスが主宰していたオンラインサロンの記事です。記事は2017年~2018年にかけて執筆されたため、一部は、既に古くなっている可能性があります。あらかじめご了承ください。


関連する記事
EOSIO Developer Portal 解説 最短でHello World!
EOSIO Developer Portal 解説 独自トークンの発行 第1回
EOSIO Developer Portal 解説 独自トークンの発行 第2回
EOSIO Developer Portal 解説 独自トークンの発行 第3回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第1回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第2回
EOSIO Developer Portal 解説 データの永続化とインラインアクション 第3回

EOSにおけるデータの扱い


EOSにおけるスマートコントラクトの記述を、SolidityによるEthereum上のスマートコントラクトと比較すると、Solidityで存在したmemory変数やstorage変数といった区別が存在しないことに気が付きます。

EOSでは、あるアクション中で宣言した変数はすべてmemory変数として扱われ、アクションの実行が終わるとデータも消えてしまいます。したがって、複数のアクションで変数を共有するためには、データをアクションの外部に永続化する必要があります。

EOSの手数料とストレージ


EOSのアクション内の変数がすべてmemory変数として扱われる理由は、EOSにおける手数料の概念と関係があります。EOSでは、DAppsの利用者がそれほど手数料を負担せず、DAppsに参入しやすい環境を実現するため、独自の手数料の概念を導入しています。

EOSでは、新規にストレージの容量を使用する場合にのみ手数料を要求し、単に状態を変更するだけのトランザクションは無料で利用できる設計をおこなっています。

新規にストレージ容量を使用する場合とは、例えば新しくアカウントを発行したり、新しいコントラクトをデプロイしたりする場合です。新規にストレージ容量を使用する場合は、EOSトークンでRAMと呼ばれるリソースを購入し、そのRAMを消費する必要があります。

一方、単純な送金トランザクションなどは、既存の残高データを更新するだけで、新たにストレージ容量を消費するわけではないので、RAMの消費は必要ありません。ただし、物理的に無制限にトランザクションを発行できるわけではなく、トランザクションの実行に必要な計算リソースや、ブロックに含められるデータサイズには上限があります。そこで、EOSでは、CPUとNETという単位で、計算リソースやブロックサイズのリソースを表現し、トランザクションを発行する際にはこれらのリソースを消費する、という形を取っています。

CPUやNETは、EOSトークンをStakingすることで入手できます。StakingしたEOSトークンはあとで引き出すことができるため、CPUやNETは実質無料で利用できることになります。

EOSIO Database


EOSのスマートコントラクトで永続化するデータは、EOSIO Databaseと呼ばれる領域に格納されます。EOSのアクションとEOSIO Databaseの関係を図1に示します。EOSのアクションは、Action Contextと呼ばれる環境内で実行されます。アクションのける変数は、Action Context内のWorking Memory内で処理されます。Working MemoryはAction Contextごとに初期化されるため、異なるアクションから参照することができません。そこで、Transaction Side-EffectsにてEOSIO Database内の状態を更新し、データを永続化する必要があります。


図1. Action “Apply” Context(developers.eos.io Multi-Index DB API より引用)

EOSのコントラクトからEOSIO DatabaseにアクセスするためのC++インターフェイスが、Multi-Index APIです。Multi-Index APIは、ユニークなキーを用いてデータを格納し、インデックスを用いてデータを高速に検索したり、特定の値で絞り込んだりすることができます。Multi-Index APIは、 EOSIO/eosレポジトリの contracts/eosiolib フォルダにある eosio::multi_index クラスで定義されています。

それでは、サンプルコントラクトを用いて Multi-Index APIの使い方について見ていきましょう。

サンプルコントラクトによる実装


今回のサンプルコントラクトは、アカウントごとの名前や住所、連絡先などを管理する addressbook コントラクトを用います。まず、コマンド1に示す手順で、空のaddressbook.cppファイルを作成します

コマンド1. 空のaddressbook.cppファイルの作成
$ cd CONTRACTS_DIR
$ mkdir addressbook
$ cd addressbook
$ touch addressbook.cpp

addressbook.cppをエディタで開き、コード1に示すコントラクトの雛形を記述します。

コード1. eosio::contractを継承したコントラクトの雛形
#include 

using namespace eosio;

class [[eosio::contract]] addressbook : public eosio::contract {
  public:
       
  private: 
  
};

Multi-Index APIを使用する前に、扱いたいデータのStructを定義しておく必要があります。今回は、addressbookで扱うためのpersonというStructを定義します。コード1で示した雛形の private: 以下に、コード2のperson structの定義を追加します。

Multi-Index APIを使用するためには、各データを識別するためのユニークな主キーが必要です。今回は、各アカウントごとに一つのレコードを保持する仕様とするため、アカウント名が主キーの候補となります。

コード2. person structの定義
private:
  struct [[eosio::table]] person {
    name key;
    std::string first_name;
    std::string last_name;
    std::string street;
    std::string city;
    std::string state;
    uint64_t primary_key() const { return key.value; }
  };

person structの定義が完了したら、続けてmulti_indexの定義をします。コード2に続けて、コード3に示すpeople tableの定義を記述します。people tableは、person struct型のデータを保持するデータベースです。”people”_n における _n 演算子は、eosio::name型のデータを返します。このmulti_indexの定義に、address_indexという型を紐付けています。

コード3. people tableの定義
typedef eosio::multi_index<"people"_n, person> address_index;

続いて、コントラクトのアクションの実装をしていきましょう。

まず、コントラクトのコンストラクタとして、addressbook関数を定義します。コントラクトの雛形における public: 以下に、コード4に示すコンストラクタを記述します。

コード4. addressbookコンストラクタの定義
public:
  using contract::contract;
  
  addressbook(name receiver, name code,  datastream ds): contract(receiver, code, ds) {}

 
コード4に続けて、さきほど定義した address_index型のmulti_indexのデータを更新するupsert関数を定義します。upsertとは、データの更新をするupdateと、データの追加をするinsertを組み合わせた言葉で、主キーで区別されるデータが存在しなければ新しくデータを追加し、すでに存在するデータであれば上書きする、という挙動をします。コード4に続けて、コード5に示すupsertの定義を追記します。

upsert関数では、追加・更新したいアカウントの名前や住所などを引数として指定します。

今回の仕様では、各アカウントが自身の情報のみを管理するため、require_auth で主キーに指定した名前のパーミッションを確認しています。

multi_indexのインスタンス化には、さきほど定義したaddress_index型の変数を用いいます。このとき、code, scopeという2つの引数が必要です。codeはコントラクトのアカウントを表し、scopeはコントラクト内のユニーク性を示すために用います。今回の場合、テーブルはコントラクト内に1つしかないため、scopeは特に意識しなくても問題ありません。コントラクトのアカウントは _code で参照でき、scopeは _code.value を指定します。

address_index型として初期化したaddressesは、findメソッドを用いて既存のデータを検索するiteratorを作成できます。もし、iteratorが最後まで探索してもデータが見つからなかった場合 ( iterator == addresses.end() ) 、まだデータが存在しないということなので、新規にデータを追加します。データの追加は、emplaceメソッドを用います。

一方、iteratorが途中でデータを発見した場合、そのデータを受け取った引数で更新します。データの更新には、modifyメソッドを用います。

コード5. upsert関数の定義
 [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, std::string street, std::string city, std::string state) {
    require_auth( user );
    address_index addresses(_code, _code.value);
    auto iterator = addresses.find(user.value);
    if( iterator == addresses.end() )
    {
      addresses.emplace(user, [&]( auto& row ) {
       row.key = user;
       row.first_name = first_name;
       row.last_name = last_name;
       row.street = street;
       row.city = city;
       row.state = state;
      });
    }
    else {
      std::string changes;
      addresses.modify(iterator, user, [&]( auto& row ) {
        row.key = user;
        row.first_name = first_name;
        row.last_name = last_name;
        row.street = street;
        row.city = city;
        row.state = state;
      });
    }
  }

続いて、データの削除をおこなう erase 関数を実装します。コード5に続けて、コード6の定義を追記します。upsert関数での処理と同様にiteratorでデータを検索し、該当データが見つからない場合はアサーションでエラーを返します。テーブル内のデータを削除するには、eraseメソッドを用います。

コード6. erase関数の定義
  [[eosio::action]]
  void erase(name user) {
    require_auth(user);

    address_index addresses(_self, _code.value);

    auto iterator = addresses.find(user.value);
    eosio_assert(iterator != addresses.end(), "Record does not exist");
    addresses.erase(iterator);
  }


最後に、コード7の記述をコントラクトの最後に追加し、コントラクトの名前と公開する関数名をEOSIO_DISPATCHマクロに渡します。これにより、wasmの関数呼び出しとコントラクト内の関数の対応付けをおこないます。

コード7. 
EOSIO_DISPATCH( addressbook, (upsert)(erase) )

サンプルコントラクトによる動作確認


コントラクトの実装が完了したら、デプロイをして動作確認をおこなってみましょう。コマンド2を用いて、実装したcppファイルからwasmファイルとabiファイルを生成します。

コマンド2. コントラクトのコンパイル
$ eosio-cpp -o addressbook.wasm addressbook.cpp --abigen

また、コマンド3を用いて、コントラクトをデプロイするためのaddressbookアカウントを作成しておきます。

コマンド3. addressbookアカウントの作成
$ cleos create account eosio addressbook YOUR_PUBLIC_KEY YOUR_PUBLIC_KEY -p eosio@active

準備が整ったら、コマンド4を用いてaddressbookコントラクトをデプロイします。正しくデプロイされれば、下記のようなログが表示されます。

コマンド4. addressbookコントラクトのデプロイ
$ cleos set contract addressbook CONTRACTS_DIR/addressbook -p addressbook@active

5f78f9aea400783342b41a989b1b4821ffca006cd76ead38ebdf97428559daa0  5152 bytes  727 us
#         eosio <= eosio::setcode               {"account":"addressbook","vmtype":0,"vmversion":0,"code":"0061736d010000000191011760077f7e7f7f7f7f7f...
#         eosio <= eosio::setabi                {"account":"addressbook","abi":"0e656f73696f3a3a6162692f312e30010c6163636f756e745f6e616d65046e616d65...
warning: transaction executed locally, but may not be confirmed by the network yet    ]

それでは、コントラクトの動作確認として、データの追加をしてみましょう。コマンド5を用いて、aliceのパーミッションでaliceの名前や住所などのデータを登録します。

コマンド5. aliceのパーミッションでaliceのデータを追加
$ cleos push action addressbook upsert '["alice", "alice", "liddell", "123 drink me way", "wonderland", "amsterdam"]' -p alice@active

executed transaction: 003f787824c7823b2cc8210f34daed592c2cfa66cbbfd4b904308b0dfeb0c811  152 bytes  692 us
#   addressbook <= addressbook::upsert          {"user":"alice","first_name":"alice","last_name":"liddell","street":"123 drink me way","city":"wonde..

正しくテーブルにデータが登録されかは、コマンド6に示すcleos get tableコマンドを用いて確認できます。ここでは、addressbookコントラクトのpeopleテーブルから、キーが「alice」より小さいデータを1件取得しています。

コマンド6. cleos get tableコマンドによるデータの検索
$ cleos get table addressbook addressbook people --lower alice --limit 1

{
  "rows": [{
      "key": "3773036822876127232",
      "first_name": "alice",
      "last_name": "liddell",
      "street": "123 drink me way",
      "city": "wonderland",
      "state": "amsterdam"
    }
  ],
  "more": false
}

また、aliceのパーミッションでbobのデータを追加・更新した場合の挙動を確認してみましょう。コマンド7に示すとおり、異なるパーミッションで他人のデータを更新しようとしても、期待したとおりエラーとなります。

コマンド7. aliceのパーミッションでbobのデータを追加できないことを確認
$ cleos push action addressbook upsert '["bob", "bob", "is a loser", "doesnt exist", "somewhere", "someplace"]' -p alice@active

Error 3090004: Missing required authority
Ensure that you have the related authority inside your transaction!;
If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.

最後に、erase関数でaliceのデータを削除することができるか確認します。

コマンド8. aliceのデータの削除
$ cleos push action addressbook erase '["alice"]' -p alice@active

executed transaction: 0a690e21f259bb4e37242cdb57d768a49a95e39a83749a02bced652ac4b3f4ed  104 bytes  1623 us
#   addressbook <= addressbook::erase           {"user":"alice"}
warning: transaction executed locally, but may not be confirmed by the network yet    ]

コマンド8の実行後、コマンド6と同様にaliceのデータを検索してもヒットせず、データが正しく削除されていることが分かります。

コマンド9. cleos get tableコマンドによるデータの検索
$ cleos get table addressbook addressbook people --lower alice --limit 1

{
  "rows": [],
  "more": false
}

第1回の記事では、サンプルコントラクトを用いて、Multi-Index APIを用いた簡単なデータの登録・削除について紹介しました。

次回は、永続化されたデータを柔軟に活用するための手段として、セカンダリーインデックスを用いたデータの抽出を解説します。

参考文献:

  • EOSIO Developer Portal – 2.4 Data Persistence

https://developers.eos.io/eosio-home/docs/data-persistence
  • EOSIO Developer Portal – Multi-Index DB API

https://developers.eos.io/eosio-cpp/v1.3.1/docs/db-api
     

免責事項

本記事に掲載されている記事の内容につきましては、正しい情報を提供することに務めてはおりますが、提供している記事の内容及び参考資料からいかなる損失や損害などの被害が発生したとしても、弊社では責任を負いかねます。実施される際には、法律事務所にご相談ください。

技術・サービス・実装方法等のレビュー、その他解説・分析・意見につきましてはblock-chani.jp運営者の個人的見解です。正確性・正当性を保証するものではありません。本記事掲載の記事内容のご利用は読者様個人の判断により自己責任でお願いいたします。

     

コンセンサス・ベイス(株)とブロックチェーン事業を行なってみませんか?

当サイトを運営するコンセンサス・ベイス株式会社は、2015年設立の国内で最も古いブロックチェーン専門企業です。これまでに、大手企業の顧客を中心に、日本トップクラスのブロックチェーンの開発・コンサルティング実績があります。

ブロックチェーンに関わるビジネスコンサル・システム開発・教育・講演などご希望でしたら、お気軽にお問い合わせください。

     
     

ブロックチェーン学習に最適の書籍の紹介

図解即戦力 ブロックチェーンのしくみと開発がこれ1冊でしっかりわかる教科書

ブロックチェーン イーサリアムへの入り口 第二版 (ブロックチェーン技術書籍)

本書は、ブロックチェーン技術に興味を持ったエンジニアや、その仕組みを学び、自分の仕事に活かしたいビジネスパーソンを対象にして、ブロックチェーンのコア技術とネットワーク維持の仕組みを平易な言葉で解説しています。この本を読んだうえで、実際にコードを書くような専門書、ブロックチェーンビジネスの解説書を読むことで、理解度が飛躍的に高まるでしょう。(はじめにより)

EOSカテゴリの最新記事