EOS、およびそのBUIDLの全て ①
DPoS-BFTで高速なスマートコントラクトを実現したEOSIOで開発に必要な情報を記します。前半はコードがないので、システムの仕様を概略として知りたい人にもおすすめです。
このページのOverviewの項目全部をひたすら和訳して解説していきます。https://developers.eos.io/eosio-cpp/docs/introduction
この記事①ではEOSの概略からコントラクト・アカウント間の通信仕様(Communication Model)、データベースの解説までの部分を和訳しました。EthereumでいうところのEOA/CA、EVM、トランザクション/メッセージあたり話から始まり、Solidityを触りはじめるくらいで止まるカンジです。以降のコントラクト・コーディングの詳細は②に書かせて頂きます。
デプロイまでの手順はDaiDaiさんの記事がとても分かりやすいです。
ここで、僕の方ではコントラクト自体の解説・作り方をひと通り載せていこうと思います。
#Page1: Introduction
現実世界のコントラクトとは、端的に言えばアクションや入力のセットに対する結果を制御する合意である。コントラクトは金融取引のような公的な法契約からゲームのルールくらいシンプルなものまで多岐にわたる。金融契約における資本の送金、あるいはゲームの1手のようなものが典型的なアクションだ。
EOSIOのスマートコントラクトはブロックチェーンに登録され、EOSIOノードで実行されるソフトウェアであり、コントラクトの実装方式としては、そのアクションの台帳がブロックチェーン上に保存されることとなる。スマートコントラクトはアクション・引数・データ構造といったインターフェース、それを実装するコードを定義する。コードはバイトコード形式にコンパイルされ、ノードはそれを解釈し実行する。ブロックチェーンは合法的な送金やゲームの1手のようなコントラクトのトランザクションを保存する。各スマートコントラクトはその法的条件を規定した”Ricardian Contract”とセットでなければならない。
[※Recardian Contractの例はこちら]
#Page2: Required Knowledge
ブロックチェーンに基づいたEOSIOはユーザーが作ったアプリケーションとWebAssembly(WASM)を使ったコードを実行する。WASMはGoogleやMicrosoft、Appleなどによって広くサポートされた急伸中のウェブスタンダードだ。現時点で、最も成熟したWASMへのコンパイルによるアプリケーション構築ツールチェーンはclang/llvmとそのCコンパイラである。最良の互換性からして、EOSIOのツールチェーンを使うべきと考えられる。
他のツールチェーンはRust,Python,Solidityといったサードパーティーによる開発がある。そういった他言語はシンプルに見えるが、そのパフォーマンスはあなたの作るアプリケーションのスケールを害しうるだろう。我々はC++がハイパフォーマンスで安全なスマートコントラクト開発にはベストであるだろうと予想し、予見可能な範囲ではC++を使う予定である。
#Page3: Communication Model
EOSIOのスマートコントラクトはactionのセットと型定義によって構成される。
型定義は内容と構造を特定する。EOSIOのactionは主にメッセージベースのコミュニケーションアーキテクチャーで操作を行う。クライアントはnodeosにメッセージを送ってactionを発生させる。これはcleosのコマンド使うことでできる。また、EOSIOのsendメソッドを使って行うこともできる。(例:eosio::action::send)nodeos
はactionのリクエストをコントラクト実装のWASMコードに送る。WASMコードはactionを全て実行し、次のactionの処理へと続く。
EOSIOのスマートコントラクトは互いにコミュニケーション可能であり、それは、例えばトランザクションの処理の完了のために他のコントラクトを動かすことや、現トランザクションのスコープ外で将来のトランザクションのトリガーを作ることなどに使われる。
EOSIOは2つのコミュニケーション基本モデルをサポートする。inline と deferredである。現トランザクションでのオペレーション実行はinline actionの例であり、将来のトランザクションのトリガーはdeferred actionの例である。
コントラクト間のコミュニケーションは非同期性を産むと考えるべきだろう。非同期なコミュニケーションはスパムになりうるが、リソース制限アルゴリズムによって解決される。
Inline Communication
Inline communicationはアクションを呼び出して実行するために必要な他のアクションへのリクエストという形態をとる。Inline actionは呼び出し元のトランザクションと同じスコープと同じ権限で扱われ、現トランザクションと共に実行されることが保証される。呼び出し元トランザクション内でのネストされたトランザクションと考えると速いかもしれない。もし、一部でもトランザクションが失敗したら、inline actionはトランザクション内の残りのactionもろとも巻き戻される。inline actionの呼び出しは、成功失敗に関わらず、そのトランザクションのスコープ外から認識されない。
Deferred Communication
Deferred communication はペアとなったトランザクションへのactionの通達という形態をとる。Deferred actionは送信元の裁量で、スケジュールを組まれ、後でベストなタイミングで実行される。実行される保証はない。
もう言った通り、deferred communicationは送信者の裁量で後にスケジュールされるので、トランザクションの大元という観点、つまりdeferredを伴うトランザクションという観点からすると、失敗する時はすぐに失敗するので、その生成リクエストが成功するかどうか分かるというだけである。Deferred transactionは送信元のコントラクトの権限を使うことになっており、トランザクションは伴うdeferredトランザクションをキャンセルできる。
Transactions VS. Actions
1つのactionは1つの処理を意味します、一方1つのtransactionは1つ以上のactionの集まりです。コントラクトとアカウントはactionの形式の中でコミュニケーションをとります。アクションは個々で送ることもでき、また全体として実行する意図で作られる場合、連結された形で送られることもある。
単一actionのトランザクションの例。
{
"expiration": "2018-04-01T15:20:44",
"region": 0,
"ref_block_num": 42580,
"ref_block_prefix": 3987474256,
"net_usage_words": 21,
"kcpu_usage": 1000,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "eosio.token",
"name": "issue",
"authorization": [{
"actor": "eosio",
"permission": "active"
}
],
"data": "00000000007015d640420f000000000004454f5300000000046d656d6f"
}
],
"signatures": [
""
],
"context_free_data": []
}
複数actionのトランザクションの例。全部成功するか全部失敗するかしかケースは存在しない。
{
"expiration": "...",
"region": 0,
"ref_block_num": ...,
"ref_block_prefix": ...,
"net_usage_words": ..,
"kcpu_usage": ..,
"delay_sec": 0,
"context_free_actions": [],
"actions": [{
"account": "...",
"name": "...",
"authorization": [{
"actor": "...",
"permission": "..."
}
],
"data": "..."
}, {
"account": "...",
"name": "...",
"authorization": [{
"actor": "...",
"permission": "..."
}
],
"data": "..."
}
],
"signatures": [
""
],
"context_free_data": []
}
Action Name Restrictions
アクションの型は実際は base32 でエンコードされた 64-bit の整数型である。 これが意味することとして、a~z,1~5そして”.”を最初の12文字に使う制限があるということであり、もし、13文字目があるのであれば「.」とa~pに限られるということだ。
詳しくはこちら link here
Transaction Confirmations
トランザクションの完了において、レシートが生成される。このレシートはハッシュの形をとる。トランザクションのハッシュを受け取ることはトランザクションが承認されたことを意味しない。ノードがエラー無しに受け取り、高い確率で他のブロック生成者も受け取るであろうことを意味する。
承認されたことにより、トランザクションが含まれたブロックの番号を使って、履歴にの中にそのトランザクションが確認できるようになるだろう。
Action Handlers and Action “Apply” Context
(アクション制御とアクション適用コンテキスト)
スマートコントラクトはアクション制御をアクション要求をこなすために提供する。詳しく言えば、actionが実行される度に、つまりはコントラクト実装にあるapplyメソッドが適用される度に、EOSIOはactionが実行されているcontextで新しいaction applyを作る。下図はアクション適用コンテキストの要旨である。
EOSIOブロックチェーンの総観からすると、EOSIOネットワークの全てのノードは全コントラクトで毎回アクションをコピーしては実行していることになる。いくつかのノードがコントラクトで実際の実行をしている一方、その他はトランザクションブロックの承認・確認のために処理を行っている。これは、すなわち、コントラクトが”これは誰か?”、あるいは”基本的にどのcontextで動いているのか”というのを判別できることが重要であるということだ。contextを規定する情報はaction contextの中で、上図のreceiver
, code
, action
と書かれてものによって提供される 。receiverはactionを実行中のアカウントである。codeはコントラクトに権限を与えたアカウントである。actionは現実行中のactionのIDである。
議論した通り、actionはトランザクション中で扱われる。もし、トランザクションが失敗すれば、トランザクション中のactionの結果全てが巻き戻される。action contextの最重要部はCurrent Transaction Dataである。ここはトランザクションのヘッダ、トランザクション中のもともとの複数アクション全ての整列された配列、トランザクション中のcontextに非依存のactionの配列、コントラクトコードによって定義されるcontext非依存データの可変な集合、BLOBデータの全index配列。
actionを実行する前に、EOSIOはワーキングメモリーをそのactionのために空ける。ここはactionで使う変数が保存されるところである。actionのワーキングメモリーはそのactionのためにしか使えず、同じトランザクションにあるactionにすら許されない。actionの実行時にセットされた変数は他のactionのcontextでは使えない。action間で状態を引き渡し合う唯一の方法は、EOSIOのデータベースから引き出すことである。Persistence APIをみて、詳しくEOSIO persistent serviceの使い方を見て頂きたい。
actionはたくさんの副次効果(side effects)を持つ。 それらは:
- EOSIO persistent databaseに保持された状態を変更すること
- 現トランザクションのレシートを通知すること
- inline actionのリクエストを 新しい受信者に送ること
- 新しいdeferred transactionを生成すること
- 今あるdeferred transactionをキャンセルすること (i.e., もう送られたdeferred transactionをキャンセルすること)
Transaction Limitations
全トランザクションは30ミリ秒以内に実行されなければならない。もしトランザクションが複数のactionを持っていて、合計実行時間が30msを超える場合、トランザクション全体が無効となる。その複数actionの同時実行のリクエストがない状況では、これらはCPUの消費が多いactionを複数トランザクションに分けて入れることで回避することができる。
#Page4: The Dispatcher Macro & Apply
全てのスマートコントラクトはapply
action制御を提供しなければならない。 The apply
action制御は入力される全てのactionを待機し意図された処理を行う関数である。特定のアクションに応答するために、コードはactionリクエストを特定していなければならない。 apply
関数はreceiver
, code
, action
を特定の(コントラクトで)実装された関数への割当を行うフィルターとして使う。 apply
関数は次のようにcodeパラメーターをフィルターに使う
if (code == name("contract_name").value) {
// codeに対応して何か制御を加えることができる
}
(次に下では)与えられたcodeに対して、コーダーは action
パラメーターを用いて特定のactionに対して対応することが出来る。code filterでの接続で使われるのが普通である。
if (action == name("action_name").value) {
//actionへ対応
}
The EOSIO_DISPATCH macro
コントラクト開発者の仕事を簡単にするために, EOSIO_DISPATCH マクロ
はapply関数の低階層のマッピングをカプセル化し、開発者にアプリケーション実装に集中させることを可能にしました。
#define EOSIO_DISPATCH( TYPE, MEMBERS ) \
開発者はマクロで code
and action
の名前をコントラクトから指定するだけです。すると全ての下層のCコードのマッピングロジックはマクロによって自動生成されます。上のマクロの使用例では、 EOSIO_DISPATCH( hello, (hi) )
と書き、hello
とhi
はコントラクトの変数です。
apply関数
apply
はaction制御で, 送られてくる全てのactionを待機し、コントラクトの関数で定義されたように反応します。 apply
関数には, code
とaction
の2つの引数が必要です。
code filter
特定のactionに応答するために, apply
関数が下のように構成される。 また、code filterを省くことで全てのactionに応答するよう設計することが可能だ.
if (code == name("contract_name").value) {
// code への対応
}
You can also define responses to respective actions in the code block.
action filter
与えられたactionに対して、コーダーはapply関数を次のように構成することができる。 code filterでの接続で使われるのが普通だ。
if (action == name("action_name").value) {
//action への対応
}
WASM
どんなEOSIOにデプロイされるどんなプログラムもWASM形式にコンパイルされなければならない。これのみがブロックチェーンで受け付けられる。
一度、CPPファイルができたなら、WASMファイル(.wasm)にeosio-cpp ツール
を用いてコンパイルすることができる。
$ eosio-cpp -o ${contract}.wasm ${contract}.cpp
ABI
アプリケーション・バイナリ・インターフェース(ABI) はJSONベースの表記で、ユーザーのactionをJSON/バイナリの表記の変換を記している。一度、ABIでコントラクトを記述したなら、開発者とユーザーはあなたのコントラクトとJSONを使って障害なくやりとりできるようになる。
ABIファイルはcppファイルをeosio-cppで--abigen
の入力指定を行うことで生成できる。$ eosio-cpp -o ${contract}.wasm ${contract}.cpp --abigen
下はスカスカのコントラクトのcontract ABI の例を示している:
{
"types": [{
"new_type_name": "name",
"type": "name"
}
],
"structs": [{
"name": "transfer",
"base": "",
"fields": {
"from": "name",
"to": "name",
"quantity": "uint64"
}
},{
"name": "account",
"base": "",
"fields": {
"account": "name",
"balance": "uint64"
}
}
],
"actions": [{
"action": "transfer",
"type": "transfer"
}
],
"tables": [{
"table": "account",
"type": "account",
"index_type": "i64",
"key_names" : ["account"],
"key_types" : ["name"]
}
]
}
このABIはtransfer
action のtransfer
型を定義していると分かるだろう。 This tells EOSIOは ${account}->transfer
のactionが 見られた時、トランザクションに載る入力は transfer
型ということだ。 transfer
型 はABIオブジェクトの中のstruct
配列でname
がtransfer
とセットされたことにより定義されている。
"structs": [{
"name": "transfer",
"base": "",
"fields": {
"from": "name",
"to": "name",
"quantity": "uint64"
}
},{
...
The ABI にはfrom
, to
,quantity
などいくつかフィールドがあるが、name
とuint64
など一致するフィールドがある。 name
は最初からある base32 string のuint64
型である。 最初からある型 に関してはcheck here.
{
"types": [{
"new_type_name": "name",
"type": "name"
}
],
...
上のtypes
の配列で、もうある型とのエイリアス(言い換え)を定義している。ここで我々はname
をaccount_name
のエイリアスと定義している。
#Page5: Multi-Index DB API
Overview
EOSIO はコントラクト開発者にデータベースの状態、結果のトランザクション、制限を一貫させるサービスとインターフェースをコンポーネントで与える。一貫性なしには、actionとトランザクションを実行中に生まれた状態はスコープを抜けた後に消失してしまう。一貫性コンポーネントは以下を扱う。
- データベースで状態を一貫させるサービス
- データベースのコンテンツを検索し引き出す高水準なクエリ
- 上記のサービスへのC++ API
- ライブラリ/システム開発者向けのコアサービスへのC APIs
上の3つをこの記事では扱う。
The Need for Persistence Services
(「1.データベースで状態を一貫させるサービス」に対応)
Action はEOSIO のコントラクトを動かす. Action はaction contextと呼ばれる環境で操作される。. Action “Apply” Context Diagramの図で描かれた通り、action contextはactionの実行に必要ないくつかのものを準備してくれる。1つはactionのワーキングメモリー。実行中の状態を保持することができる。 actionを実行する前に、 EOSIO はactionのメモリを空ける準備を行う。 actionのワーキングメモリーはそのactionのためにしか使えず、同じトランザクションにあるactionにすら許されない。actionの実行時にセットされた変数は他のactionのcontextでは使えない。action間で状態を引き渡し合う唯一の方法は、EOSIOのデータベースから引き出
すことである。
The EOSIO Multi-Index API
(「2.データベースのコンテンツを検索し引き出す高水準なクエリ」に対応)
EOSIO Multi-Index API はC++ からEOSIO データベースへのインターフェースを提供する。 EOSIO Multi-Index APIは Boost Multi-Index Containersを参照しパターン化される。このAPI はオブジェクトストレージのモデルを提供する。それは大容量の復元機能、異なるソーティングとアクセスの解釈もできるマルチインデックスをもつ。 Multi-Index APIは eosio::multi_index
というC++ のクラスによって提供されている。(contracts/eosiolib
EOSIO/eos
GitHub repository). これはC++ で書かれたコントラクトにEOSIO databaseで保持されたデータの読み込み書き込み能力を与えるものである。
Multi-Index のコンテナインターフェースである eosio::multi_index
は任意のC++ type (単純な型や固定長である必要はない) に対する一意に決まったコンテナを提供し、それはそのオブジェクトから生まれたkeyとvalueによるマルチインデックスでのソートされた状態に保たれている。これはテーブル、行列、インデックスで構成された従来のデータベースと比較参照することができる。Boost Multi-index Containersとの比べるのはより簡単だ。事実、 eosio::multi_index
のメンバー関数はboost::multi_index
をならってモデルをつくられる。たしかに重要な違いではあるのだが。
eosio::multi_index
は概念的に見ると、従来のデータベースにおけるテーブルであり、行(row)が個々のコンテナ内のオブジェクトで、列(column)がそのコンテナのオブジェクトのメンバー属性となり、 インデックスはオブジェクトをメンバー属性と互換なキーで高速に探す機能を提供するものだと言える。
従来のデータベースではインデックスをテーブルの列の中からユーザーに定義させている。 eosio::multi_index
も似たようにインデックスをユーザーに定義させているが、(class
/struct
) 、返り値は用意されたキーの型に制限されている。
従来のデータベースでは固有で1つの主キーによって厳密にテーブルの行を指定でき、行をソートしておくようにすることが典型的である。
eosio::multi_index
は似たような方式ではあるが, eosio::multi_index
コンテナにあるオブジェクトの主キーは固有のunsigned 64-bit integeでなければならない。 eosio::multi_index
コンテナのオブジェクトはunsigned 64-bit integerの主キーの順に並ぶことになる。
EOSIO Multi-Index Iterators
EOSIOの一貫データベース によるほかのブロックチェーンインフラとの差別化とはマルチインデックスの イテレーターだ。key-value関係の保存しか行わない他のブロックチェーンと違い, EOSIOマルチインデックス・テーブルはコントラクト開発者に色々なキーによってソートされたオブジェクトを保持することを許し、これはオブジェクト中のデータによるものでも構わない。これは大容量な引き出しを可能にする。 16 のサブのインデックスが定義でき、各々がテーブルコンテンツの整列・引き出しの方法を持っている。
EOSIOマルチインデックス・イテレーター はC++ のイテレーターとおなじ方式に準拠する。 すべてのイテレーターは双方向のconst
,あるいはconst_iterator
,const_reverse_iterator
である。 イテレーターはMulti-Index tableにあるオブジェクトにアクセスするために区別できるものとなっている。
Putting It All Together
How to Create Your EOSIO Multi-Index Table
ここに EOSIOマルチインデックス・テーブルを使った保持データの作り方を記しておく。
- C++でオブジェクトを
class
orstruct
を用いて定義する。各オブジェクトはマルチインデックステーブルに入ることになる。 const
メンバー関数を主キーによって呼ばれたclass
/struct
で定義する。この関数はuint64_t
の主キーオブジェクトを返す。- セカンダリーインデックスを作る。. 最高16個まで加えることができる。 1つのセカンダリインデックスに下記のいくつかのキー型を割り当てることができる。
idx64
- Primitive 64-bit unsigned integer key
idx128
- Primitive 128-bit unsigned integer key, or a 128-bit fixed-size lexicographical key
idx256
- 256-bit fixed-size lexicographical key
idx_double
- Double precision floating point key
idx_long_double
- Quadruple precision floating point key
- それぞれのセカンダリーインデックスにキーの抜き出し(extractor)関数を定義する。この関数はマルチインデックステーブルの要素からキーそのものを取り出すのに使う。
How to Use Your EOSIO Multi-Index Table
- マルチインデックステーブルをインスタンス化する
- C++の emplace を挿入し、続いて、コントラクトからの要求に応じてテーブルのオブジェクトを変えたり消したりする。
- get, find 、iterator のオペレーションをテーブルで行う際にオブジェクトを配置・変更する
[EOSコーディングは②に続く]
Author
極度妄想(しなさい) /Laughing Leona/日置玲於奈
・クラウドとブロックチェーンの融合
・資産流動性とWebの進化
をコーディングします。