Substrateとは
ブロックチェーンを利用したDApps(Decentralized Applications)の開発においては、レイヤー1であるブロックチェーンのコア実装がアプリケーション開発に大きく影響します。
例としてEthereumのスマートコントラクトによるDAppsの実装では、TPS(Transaction per second)の低さによる処理の遅延や、段階的なハードフォークが、実用的なアプリケーション開発を遠ざけている原因の1つとして考えられています。TPSの対策としてはサイドチェーンを利用して処理をオフロードするPlasmaがレイヤー2技術として検討されています。Ethereumはまだ開発途中の段階であり、現在のConstantinopleからSharding やPoS(Proof of Stake)の実装を経てSerenityへ移行することでハードフォークは限定的になると考えられますが、アプリケーションがレイヤー1のブロックチェーンコアの技術仕様に依存している状況は変わりません。
クラシックなアプリケーションで喩えると、TCP/UDPに制約が存在し、開発のボトルネックになっている状況とも言えるかもしれません。従来のWebアプリケーションはTCPやUDPを使用して開発されています。Substrateはネットワーク上で自由にアプリケーションを構築するブロックチェーン基盤を作成できるプラットフォーム技術のようなものです(上記はアナロジーであってSubstrateのネットワークでTCP/UDPを使用しないという意味ではない)。ブロックチェーンにおける下部レイヤーをアプリケーションに応じて提供することで、柔軟なDAppsの開発が可能になります。
仮にスクラッチから新たなブロックチェーンを開発しようとすると様々な項目の検討が必要となります。[2]
- コンセンサスアルゴリズムの検討、耐障害性含むトリレンマ
- ブロック構造や効率的なストレージのデザイン、メッセージシリアライゼーション
- P2Pネットワーク(ノードのピアリング、ブロック生成とトランザクション処理)
- ステートマシーン、ランタイム実行、スマートコントラクトの仕様
Substrateでは様々なコンポーネントをモジュラー化して提供しているため、開発するアプリケーションに応じて必要な機能を加えたり、自由にカスタマイズしてブロックチェーンを展開することが可能です。
Substrate, a library that can help you build your own custom blockchain. Substrate is created by Parity Technologies and provides the basis for Polkadot.
Substrateのサイトでは非常に広範囲な内容を説明しています。その中でも以下、ライブラリ群を理解する上で重要だと思ったポイントです。
- WebAssembly(Wasm)ランタイム
- フォークレスアップグレード(Forkless Upgrade)
- ライトクライアント(Light client)
WebAssembly (Wasm) ランタイム
Substrateではブロックチェーンのステート、ストレージの内容をSTF(State Transition Function)と呼ばれる関数で表現します。STFはWebAssembly(Wasm)ランタイムとして展開することが可能となっていて、このランタイム上で展開するため、STFはプロセッサ、オペレーティングシステム、ブラウザなどに対して依存関係が発生しないようになっています。例えばJavaがJRE(Java Runtime Environment)で実行されることを想像すると比較的理解しやすいかもしれません。JREで実行されるバイトコードはポータブルでOSやプラットフォームに依存していません。WebAssembly(Wasm)ランタイムはSubstrateの仮想マシン上(Virtual Machine)でSTFを展開することを可能にしています。
Substrate 1.0 Betaでは Aura/GRANDPAというコンセンサスアルゴリズムがサポートされていますが、続くリリースで徐々に対応可能なアルゴリズムは増えていくそうです。
フォークレスアップグレード
SubstrateおよびSubstrate Runtime Module Library(SRML)はRustで書かれているのでクライアントのネイティブコードもしくはWasmランタイムどちらでも実行できるようになっています。[3]
クライアントソフトウェアは基本的に2つのコンパイルされたコードを持ちます。1つがソフトウェア側でネイティブにコンパイルされたもので、もう1つが仮想マシン(Virtual Machine)上で実行可能なWasmのランタイムイメージです。コードが実行される際に、クライアントソフトウェアでクライアントのランタイムとチェーン上のWasmのランタイムが一致するかを確認します。一致する場合、ソフトウェアはランタイムの実行権限をソフトウェア側へ移譲します。ソフトウェア側でコードを実行することでWasmで展開するよりも高速にコード実行することが可能です。
またWebAssembly(Wasm)ランタイムによって、ブロックチェーンのステートやストレージは抽象化され、仮想マシン上(Virtual Machine)で展開されるようになるため、ブロックチェーンをフォークすることなくロジックやストレージをアップグレードすることができます。
ネットワークはRustとlibp2pで実装されたSubstrate Nodeで提供され、コンセンサスアルゴリズムは組み換え自由です。独自アルゴリズムの実装もサポートされています。
ライトクライアント
パブリックチェーンにおいてGenesisブロックから現在のブロック高までのデータ量は非常に大きく、同期するまでのストレージ容量やネットワーク通信によるコストが少なくありません。パブリックなブロックチェーンではフルノードを運用したとしても、マイニングによるリベートが少ないなどの理由で、マイニング事業を中止する事業者も現在は出てきています。
ブロックチェーンのフルノードではなく、モバイルデバイスなどの軽量なユーザデバイスでも動作可能な『ライトクライアント』の概念が生まれました。ライトクライアントではブロック内の重要な部分のみをユーザローカルに保持することで、限られた要件の中でも動作させることが可能です。
Substrateではライトクライアントが導入時からサポート可能なように設計されており、Substrateで作成されたブロックチェーンはモバイルデバイス、IoTデバイスにおいてネイティブに動作させることができます。非常に実用的でDApps開発のことまで考えられています。
Substrate Runtime Module Library (SRML)
オンチェーンで展開されるランタイムを記述するためのライブラリ群です。Rustでコーディングできます。
- Accounts & Balances — 基本的な仮想通貨、アカウントや残高保持
- Assets — セキュアな代替可能アセット、FT(Fungible Token)
- Consensus —コンセンサスの機能(ランタイムコードとストレージの設定および変更、オフライン状態や不正な動作するバリデーターの報告)
- Contracts — Wasmベースのスマートコントラクト
- Council — 議題提案、選挙の機能(投票)
- Democracy — 議案、国民投票の機能(公開投票)
- Sessions — Authorityの鍵ローテーションの機能
- Staking — Proof of Stakeのロジック(ステークおよびバリデーター推薦含む)
- Timestamp — タイムスタンプ機能
- Treasury — 非中央集権的な決定機能、DAOのモジュール
例として『Proof of Stake』のロジックを持つブロックチェーンを作成したいとすると、Substrate Coreと『Accounts & Balances』『Staking』『Contracts』のモジュールを使用して設計していく、という流れになります。Polkadotが正式にローンチされた後は、作成したブロックチェーンをPolkadotへ接続することも可能です。Cosmosなども複数のブロックチェーンを相互接続してインターオペラビリティを確立することを提唱していますが、いわゆるInternet of Blockchainsの世界ですね。[6]
Substrate Node
Substrate NodeはJSONファイルで設定可能なブロックチェーンのノードです。
Substrateインストール
SubstrateのインストールおよびSubstrate Nodeの起動はこちらのチュートリアルを参考にしています。[7]
※環境はUbuntu 16.04.1 LTSを使用
Substrateはワンライナーでインストールできます。
$ curl https://getsubstrate.io -sSf | bash
Ubuntu/Debian Linux detected.
Hit:1 http://jp.archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://jp.archive.ubuntu.com/ubuntu xenial-updates InRelease [109 kB]
Get:3 http://jp.archive.ubuntu.com/ubuntu xenial-backports InRelease [107 kB]
...
必要なライブラリやパッケージのインストールが始まり、Cargo/RustなどSubstrateのコンポーネントが順番にインストールされていきます。ちなみに掃除、洗濯できるぐらい時間がかかりました(-_-;)
これでインストール完了です。
Cloning into '/tmp/tmp.syyVafKCU8'...
remote: Enumerating objects: 48, done.
remote: Counting objects: 100% (48/48), done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 169 (delta 25), reused 23 (delta 12), pack-reused 121
Receiving objects: 100% (169/169), 39.58 KiB | 0 bytes/s, done.
Resolving deltas: 100% (92/92), done.
Checking connectivity... done.
Run source ~/.cargo/env now to update environment
githubのsubstrate-upリポジトリからCLIのヘルパーをダウンロードしてきます。
$ git clone https://github.com/paritytech/substrate-up;
$ cp -a substrate-up/substrate-* ~/.cargo/bin;
$ rm -rf substrate-up
以下のコマンドで正しくインストールできていることを確認します。
$ substrate --version
substrate 1.0.0-3ec6247-x86_64-linux-gnu
Substrate Nodeの起動
Substrate UIをインストールします。
$ substrate-ui-new substrateSubstrate UI Setup
Cloning substrate-ui into subdirectory substrate-ui...
...
次にSubstrate Nodeテンプレートを展開します。substrate-node-new
コマンドは、指定したプロジェクト名のテンプレートノードおよびプロジェクトのディレクトリを作成します。ここではauthor
はtestとしました。このコマンドはテンプレートノードの作成とプロジェクトに必要なファイル群をScaffoldしてくれます。コマンドの本体は Cargo new
だと思われます。
$ substrate-node-new <project name> <author> // Example
$ substrate-node-new substrate-node-template test
作成されたディレクトリの中にruntime
というサブディレクトリがあります。ここにモジュールと各ランタイムが保存されています。展開が完了したらノードを起動してみましょう。ブロックの採掘が進んでいくことが確認できると思います。
$ cd substrate-node-template
$ ./target/release/template-node --dev
エラーが発生する場合は、以下のようにpurge-chain
を試してみてください。
$ ./target/release/template-node purge-chain --dev
Are you sure to remove "/.local/share/template-node/chains/dev/db"? (y/n)y
別ターミナルを立ち上げて、Substrate UIをブラウザから確認します。DefaultというWalletが確認できると思います。
$ cd substrate-ui
$ yarn run dev
チュートリアルではテンプレートノードのGenesisブロックに宣言されているAliceというユーザからDefaultユーザへunitを送金するテストがあるのですが事情により確認できませんでした。記事下部に補足情報として内容を記載しております。
トークン発行と移転のランタイム作成
Substrate Nodeの動作が確認できたので、シンプルなトークン発行およびアカウント間のトークン移転の機能を実装してみます。ここでは以下の状態(ステート)と動作(ファンクション)を含むランタイムを作成します。
ステート
- トークンのトータル発行高
- 各アカウントとトークン数の記録
ファンクション
- トークン発行(トータルサプライは発行したオーナーのアカウントへ紐づける)
- アカウント間でのトークン移転
runtime/src
配下にあるtemplate.rs
がランタイムモジュールのテンプレートファイルです。中にはダミーのコードが記載されていますが、このファイルを書き換えて実装してみます。template.rs
と別のファイルでコーディングすることも可能ですが、付随してlib.rs
の修正や追記も必要となるのでここでは簡単な方法でコーディングします。
最初にストレージ decl_storage!
トータル発行高およびアカウントとトークン数のマッピングのストレージを宣言します。Init boolはトークンが発行された際にtrueとするブーリアン変数です。また元のファイルから不要なコードは削除しています。
※Rustではインデントはスペース4つです。タブは使わないみたいです。
decl_storage! {
trait Store for Module<T: Trait> as TemplateModule {
pub TotalSupply get(total_supply): u64 = 21000000;
pub BalanceOf get(balance_of): map T::AccountId => u64;
Init get(is_init): bool;
}
}
次にランタイムのロジック部分を書いていきます。 decl_module!
へトークン発行および各アカウント間でのトークン移転のコードを記載します。 Self::total_supply()
および <BalanceOf<T>>
を使用することでストレージのモジュールへアクセスし値の取得や変更ができます。
decl_module! {
/// The module declaration.
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
// Initialize default event handling
fn deposit_event<T>() = default; // Initialize the token
// transfers the total_supply amount to the caller
fn init(origin) -> Result {
let sender = ensure_signed(origin)?;
ensure!(Self::is_init() == false, "Already initialized."); <BalanceOf<T>>::insert(sender, Self::total_supply());
<Init<T>>::put(true); Ok(())
} // Transfer tokens from an account to another
fn transfer(_origin, to: T::AccountId, value: u64) -> Result {
let sender = ensure_signed(_origin)?;
let sender_balance = Self::balance_of(sender.clone());
// This ensures that sender has more than sending units
ensure!(sender_balance >= value, "No enough balance."); let updated_from_balance = sender_balance.checked_sub(value).ok_or("Overflow happened in balance")?; let recipient_balance = Self::balance_of(to.clone());
let updated_to_balance = recipient_balance.checked_add(value).ok_or("overflow happened in balance")?; // Sub sender's balance
<BalanceOf<T>>::insert(sender, updated_from_balance); // Add recipient's balance
<BalanceOf<T>>::insert(to.clone(), updated_to_balance); Self::deposit_event(RawEvent::Transfer(sender, to, value)); Ok(())
}
}
}
トークン移転が発生した際のイベントTransfer
を追加します。Transfer
はfn transfer
内でトークン移転後に呼び出されます。
decl_event!(
pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {
// Event for transfer of tokens
// from, to, value
Transfer(AccountId, AccountId, u64),
}
);
runtime/src/template.rs
の修正後コードのgistです。
ここまででコーディングは完了です。
Wasmのバイナリおよびネイティブ環境向けのバイナリをそれぞれコンパイルします。まずはWasmです。
$ ./build.sh
Building webassembly binary in runtime/wasm...
Compiling substrate-node-template-runtime v1.0.0 (/substrate-node-template/runtime)
...Compiling substrate-node-template-runtime-wasm v1.0.0 (/substrate-node-template/runtime/wasm)
Finished release [optimized] target(s) in 19.61s
バイナリビルドです。こちらは時間がかかりました。ビルドするだけなのに、以下の例では7分ほど(-_-;)
$ cargo build --release
Compiling substrate-node-template-runtime v1.0.0 (/substrate-node-template/runtime)
...Compiling substrate-node-template v1.0.0 (/substrate-node-template)
Finished release [optimized] target(s) in 7m 03s
コンパイルが終わったら再度Substrate Nodeを走らせます。
$ ./target/release/template-node --dev
Polkadot Apps UIで動作確認
Polkadot Apps UIはPolkadotのアプリケーション用のポータルですが、Substrate Nodeも接続して操作ができます。 Settings
をクリックしてリモートのエンドポイントを『Local Node (127.0.0.1:9944)』に変更します。『Save & Reload』を押してください。
Substrate Nodeとの接続が確認できたら Extrinsics
に『Alice』のアカウントとinit()ファンクションがあることを確認します。
Submit Transaction
をクリックしてブロックが取り込まれると 21000000トークンが『Alice』のトークン残高として移転されます。メニューの Chain state
へ行き、 template
モジュールの balanceOf(AccountId): u64
というファンクションを選択し『+』をクリックしてください。
template.balanceOf: u64
の箇所に『Alice』の保有するトークン残高が表示されていれば成功です。
『Aliceの謎』
//AliceのアカウントをWalletで再現できない件です。Substrateの暗号化がed25519
からsr25519
に変更されているために、以下のSeedを入れても彼女と一致しないようです。ed25519
のSeedを入れてアカウント作成した場合は、まったく別人の『Alice』となりました。詳しい方コメントください。[8]
sr25519
$ subkey inspect //Alice
Secret Key URI `//Alice` is account:
Public key (hex): 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
Address (SS58): 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
ed25519
$ subkey -e inspect //Alice
Secret Key URI `//Alice` is account:
Seed: 0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115
Public key (hex): 0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee
Address (SS58): 5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TmTd
以上です。
まとめ
- Substrateはプログラミング言語で記述可能なブロックチェーンのライブラリ群である
- Substrateはフォークレスアップグレードをサポートしている
- WebAssembly(Wasm)ランタイムはポータブルなSTF(State Transition Function)で主にRustでステートやストレージを記述できる
- モバイルデバイス、IoTデバイスなどの軽量なライトクライアントでネイティブ動作させることができる
- SubstrateのインストールおよびSubstrate Nodeの起動確認ができた
- トークン発行およびアカウント間のトークン移転のランタイムを作成できた