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

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

はじめに


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

第3回では、コントラクトの内部でアクションを作成・送信する方法として、インラインアクションの実装方法を紹介します。

本記事は、過去にコンセンサス・ベイスが主宰していたオンラインサロンの記事です。記事は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回

インラインアクションとは


インラインアクションとは、コントラクトのアクションの中で他のアクションを実行する手法の一つです。インラインアクションを用いることで、自身のコントラクトの別のアクションを実行したり、他のコントラクトのアクションを実行することができます。

コントラクトの中でアクションを実行する手法は、インラインアクションの他にディファード(遅延)アクションがあります。ディファードアクションは、呼び出し元のアクションとは非同期に実行されるアクションで、呼び出し元のアクションではディファードアクションの予約のみが実行されます。そのため、ディファードアクションが成功するかどうかは、呼び出し元のアクションでは判断できません。

一方、インラインアクションは、アクションの中で同期的に実行されるアクションで、呼び出し元のアクションの一部として実行されます。そのため、インラインアクションが失敗した場合は呼び出し元のアクション自体が失敗します。

インラインアクションの実装と動作確認


それでは、インラインアクションの実装例として、アクション内から同じコントラクトの別アクションを実行してみます。

addressbookコントラクトの修正


前回のaddressbookコントラクトのパブリックアクションに、コード1に示すnotifyというアクションを追加します。notifyアクションは、アクションが実行されたことをアカウントに通知するアクションです。require_recipient関数を用いると、アクションのトランザクションのコピーが指定したアカウントに送信されます。これにより、アカウントはそのアクションが実行されたことを検知できます。

コード1. addressbookコントラクトにnotifyアクションを実装
public:
  ...
  [[eosio::action]]
  void notify(name user, std::string msg) {
    require_auth(get_self());
    require_recipient(user);
  }

続いて、notifyアクションをインラインアクションとして実行する補助関数として、コード2に示すsend_summary関数を実装します。インラインアクションの作成には、アクションを実行するパーミッション、ターゲットアカウント、ターゲットアクション、アクションのパラメタを指定します。アクションの送信には、アクションのsendメソッドを実行します。

コード2. addressbookコントラクトにsend_summary関数を実装
private:
  struct [[eosio::table]] person {
    ...
  };

  void send_summary(name user, std::string message) {
    action(
      permission_level{get_self(),"active"_n},
      get_self(),
      "notify"_n,
      std::make_tuple(user, name{user}.to_string() + message)
    ).send();
  };

send_summary関数の呼び出しを、addressesテーブルのデータの作成、更新、削除のタイミングでおこなうよう修正します。コード3に示すとおり、send_summary関数の呼び出しを3箇所に追記します。

コード3. addressbookコントラクトにsend_summary関数の呼び出しを追記
  [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
    ...
    if( iterator == addresses.end() )
    {
      ...
      send_summary(user, " successfully emplaced record to addressbook");
    }
    else {
      ...
      send_summary(user, " successfully modified record to addressbook");
    }
  }

  [[eosio::action]]
  void erase(name user) {
    ...
    send_summary(user, " successfully erased record from addressbook");
  }

最後に、コード4に示すとおり、EOSIO_DISPATCHにnotifyアクションを追加します。

コード4. addressbookコントラクトのDISPATCHにnotifyアクションを追加
EOSIO_DISPATCH( addressbook, (upsert)(notify)(erase))

addressbookコントラクトを修正したので、コンパイルをしてwasmファイルとabiファイルを更新します。

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

コンパイルが完了したら、再度addressbookコントラクトをデプロイします。

コマンド2. addressbookコントラクトのデプロイ
$ cleos set contract addressbook CONTRACTS_DIR/addressbook

パーミッションの追加と動作確認


addressbookコントラクトの修正は完了しましたが、このままではインラインアクションを実行することはできません。試しに、コマンド3を用いてupsertアクションを実行してみると、エラーが発生することが分かります。

コマンド3. upsertアクションの実行
$ cleos push action addressbook upsert '["alice", "alice", "liddell", 21, "123 drink me way", "wonderland", "amsterdam"]' -p alice@active

Error 3100006: Subjective exception thrown during block production

EOSでは、セキュリティ上の安全性を担保するために、デフォルトではコントラクトがアクションに署名をすることが許可されていません。インラインアクションを実行するためには、明示的にコントラクトのアカウントにパーミッションを追加してあげる必要があります。

コマンド4を用いて、現在のaddressbookアカウントのパーミッションを確認してみましょう。パーミッションにはownerとactiveがあり、アカウントを作成する際に指定した公開鍵が権限に紐付けられていることが分かります。

コマンド4. addressbookアカウントのパーミッションを確認
$ cleos get account addressbook

created: 2019-02-25T03:21:10.000
permissions:
     owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
        active     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV

コマンド5を用いて、インラインアクションを実行するための「eosio.code」という疑似権限を、addressbookアカウントのactiveパーミッションに追加します。

コマンド5. addressbookアカウントのactiveパーミッションにeosio.codeを追加
$ cleos set account permission addressbook active --add-code

コマンド6を用いて、再度addressbookアカウントのパーミッションを確認してみます。すると、さきほどは存在しなかった addressbook@eosio.code という権限が、activeパーミッションに加わっていることが分かります。

コマンド6.  addressbookアカウントのパーミッションを確認
$ cleos get account addressbook

created: 2019-02-25T03:21:10.000
permissions:
     owner     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
        active     1:    1 EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV, 1 addressbook@eosio.code

eosio.code権限が加わった状態で、再度コマンド3を実行すると、正常にupsertアクションが実行できます。

upsertアクションが実行されると、そのトランザクションのコピーが実行者に送られているはずです。コマンド7を用いて、アカウントが受け取ったアクションの履歴を確認してみると、確かにさきほど実行したアクションが通知されています。

コマンド7. aliceの受け取ったアクションを確認
$ cleos get actions alice

#  seq  when                              contract::action => receiver      trx id...   args
===============================================================
#   62   2018-09-15T12:57:09.000       addressbook::notify => alice         685ecc09... {"user":"alice","msg":"alice successfully added record to ad...

外部コントラクトのアクション実行


続いて、addressbookコントラクトから、外部のコントラクトのアクションをインラインアクションで実行する例を紹介します。

abcounterコントラクトの実装


コマンド8を用いて、空のabcouter.cppを作成します。

コマンド8. abcounter.cppの作成
$ cd CONTRACTS_DIR
$ mkdir abcounter
$ cd abcounter
$ touch abcounter.cpp

abcounter.cppをエディタで開き、コード4の内容を記述します。abcounterコントラクトは、addressbookのデータの登録、更新、削除の実行回数を、ユーザーごとに保持するcountsテーブルを持ちます。

countsテーブルの更新アクションはabcounterコントラクトが実装しており、このアクションをaddressbookコントラクトから実行します。

コード4. abcounterコントラクトの実装
#include 

using namespace eosio;

class [[eosio::contract]] abcounter : public eosio::contract {
  public:
    using contract::contract;

    abcounter(name receiver, name code,  datastream ds): contract(receiver, code, ds) {}

    [[eosio::action]]
    void count(name user, std::string type) {
      require_auth( name("addressbook"));
      count_index counts(name(_code), _code.value);
      auto iterator = counts.find(user.value);
      
      if (iterator == counts.end()) {
        counts.emplace("addressbook"_n, [&]( auto& row ) {
          row.key = user;
          row.emplaced = (type == "emplace") ? 1 : 0;
          row.modified = (type == "modify") ? 1 : 0;
          row.erased = (type == "erase") ? 1 : 0;
        });
      }
      else {
        counts.modify(iterator, "addressbook"_n, [&]( auto& row ) {
          if(type == "emplace") { row.emplaced += 1; }
          if(type == "modify") { row.modified += 1; }
          if(type == "erase") { row.erased += 1; }
        });
      }
    }

  private:
    struct [[eosio::table]] counter {
      name key;
      uint64_t emplaced;
      uint64_t modified;
      uint64_t erased;
      uint64_t primary_key() const { return key.value; }
    };

    using count_index = eosio::multi_index<"counts"_n, counter>;
};

EOSIO_DISPATCH( abcounter, (count));

コマンド9を用いて、abcounterアカウントの作成とコントラクトのコンパイル・デプロイをおこないます。

コマンド9. abcounterアカウントの作成とコントラクトのコンパイル・デプロイ
$ cleos create account eosio abcounter YOUR_PUBLIC_KEY

$ eosio-cpp -o abcounter.wasm abcounter.cpp --abigen

$ cleos set contract abcounter CONTRACTS_DIR/abcounter

addressbookコントラクトの修正


続いて、abcounterのアクションを実行するよう、addressbookコントラクトを修正します。コマンド10のとおりaddressbookフォルダに移動します。

コマンド10. addressbookフォルダに移動
$ cd CONTRACTS_DIR/addressbook

addressbook.cppをエディタで開き、コード5に示す修正を加えます。コード5では、プライベート関数としてincrement_counterを実装し、それをupsert, eraseアクション内の3箇所で呼び出しています。

increment_counter関数内で、abcounterコントラクトのcountアクションをインラインアクションとして呼び出しています。

コード5. addressbookコントラクトからabcounterのアクションを呼び出し
public:
  ...
  [[eosio::action]]
  void upsert(...) {
    ...
    if( iterator == addresses.end() )
    {
       ...
       increment_counter(user, "emplace");
      });
    }
    else {
       ...
       increment_counter(user, "modify");
      });

    }
  }

  [[eosio::action]]
  void erase(name user) {
    ...
    increment_counter(user, "erase");
  }

...

private:
  struct [[eosio::table]] person {
    ...
  };

  void send_summary(name user, std::string message) {
    ...
  };

  void increment_counter(name user, std::string type) {
    
    action counter = action(
      permission_level{get_self(),"active"_n},
      "abcounter"_n,
      "count"_n,
      std::make_tuple(user, type)
    );

    counter.send();
  }

    ...
};

コードの修正が完了したら、コマンド11を用いて再度addressbookコントラクトのコンパイルとデプロイをおこないます。

コマンド11. addressbookコントラクトのコンパイルとデプロイ
$ eosio-cpp -o addressbook.wasm addressbook.cpp

$ cleos set contract addressbook CONTRACTS_DIR/addressbook

動作確認


デプロイが終わったら、コマンド12を用いて、再度aliceのデータを更新してみましょう。

コマンド12. upsertアクションを実行してaliceのデータを更新
$ cleos push action addressbook upsert '["alice", "alice", "liddell", 19, "123 drink me way", "wonderland", "amsterdam"]' -p alice@active

executed transaction: cc46f20da7fc431124e418ecff90aa882d9ca017a703da78477b381a0246eaf7  152 bytes  1493 us
#   addressbook <= addressbook::upsert          {"user":"alice","first_name":"alice","last_name":"liddell","street":"123 drink me way","city":"wonde...
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alice successfully modified record in addressbook"}
#         alice <= addressbook::notify          {"user":"alice","msg":"alice successfully modified record in addressbook"}
#     abcounter <= abcounter::count             {"user":"alice","type":"modify"}

コマンド13を用いてabcounerコントラクトのcountsテーブルから、aliceのデータを確認してみると、modifiedの値が1になっていることが分かります。

コマンド13. abcounterのデータを確認
$ cleos get table abcounter abcounter counts --lower alice --limit 1

{
  "rows": [{
      "key": "alice",
      "emplaced": 0,
      "modified": 1,
      "erased": 0
    }
  ],
  "more": false
}

続いて、コマンド14を用いて、aliceのデータを一度削除します。

コマンド14. eraseアクションを用いてaliceのデータを削除
$ cleos push action addressbook erase '["alice"]' -p alice@active

executed transaction: aa82577cb1efecf7f2871eac062913218385f6ab2597eaf31a4c0d25ef1bd7df  104 bytes  973 us
#   addressbook <= addressbook::erase           {"user":"alice"}
>> Erased
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alice successfully erased record from addressbook"}
>> Notified
#         alice <= addressbook::notify          {"user":"alice","msg":"alice successfully erased record from addressbook"}
#     abcounter <= abcounter::count             {"user":"alice","type":"erase"}
warning: transaction executed locally, but may not be confirmed by the network yet    ]

aliceのデータが削除されたら、コマンド15を用いて再度aliceのデータを登録します。

コマンド15. upsertアクションを用いてaliceのデータを登録
$ cleos push action addressbook upsert '["alice", "alice", "liddell", 21,"1 there we go", "wonderland", "amsterdam"]' -p alice@active

executed transaction: c819ffeade670e3b44a40f09cf4462384d6359b5e44dd211f4367ac6d3ccbc70  152 bytes  909 us
#   addressbook <= addressbook::upsert          {"user":"alice","first_name":"alice","last_name":"liddell","street":"1 coming down","city":"normalla...
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alice successfully emplaced record to addressbook"}
>> Notified
#         alice <= addressbook::notify          {"user":"alice","msg":"alice successfully emplaced record to addressbook"}
#     abcounter <= abcounter::count             {"user":"alice","type":"emplace"}
warning: transaction executed locally, but may not be confirmed by the network yet    ]

コマンド6を用いて、再度abcounterのaliceのデータを確認すると、erasedとemplacedの値も1にインクリメントされていることが分かります。

コマンド16.  abcounterのデータを確認
$ cleos get table abcounter abcounter counts --lower alice 

{
  "rows": [{
      "key": "alice",
      "emplaced": 1,
      "modified": 1,
      "erased": 1
    }
  ],
  "more": false
}

本稿では、インラインアクションを用いて、同一コントラクト内のアクションや別コントラクトのアクションを同期的に呼び出す手法を紹介しました。インラインアクションにより、複数のアクションやコントラクトを連携して、複雑な処理を実現できるようになります。

参考文献:

     

免責事項

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

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

     

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

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

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

     
     

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

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

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

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

EOSカテゴリの最新記事