Substrateでブロックチェーンとアプリケーションを作る Runtimeの設定から〜ゲームPlayまで


こんにちは、Stakedの渡辺創太です。

前回こちらの記事で、Substrateのインストールからアカウントの作成までを行いました。この記事はその続きです。Gavin Woodの Web3 Summitでのプレゼンを参考にしています。

Runtimeモジュールを作成する

RuntimeとはSubstrateにおけるブロック処理ロジックなどを決める機能です。State Transaction Functiuonとも言われるときもあり、WebAssemblyバイナリーでオンチェーンに記載さてています。詳しくはScraoboxに記載しています。

前回、substrate-node-templateをインストールしましたが、./runtime/src/demo.rsのフォルダに記載していきます。

作成したdemo.rsにいくつかのライブラリーをインストールします。

use parity_codec::Encode;
use support::{StorageValue, dispatch::Result, decl_module, decl_storage};
use runtime_primitives::traits::Hash;
use {balances, system::{self, ensure_signed}};

pub trait Trait: balance::Trait {}

Web3 Summitのプレゼンでは、簡単な賭けゲームを作成していました。ゲームに勝てば、Potに溜まっていた金額を得ることができ、負ければPotに没収されるという簡単な仕組みです。

ライブラリーをインストールしたので、次に、モジュールを宣言する必要があります。(module declaration)

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin {
        fn play(origin) -> Result{
            //ゲームをプレイするロジック
        }

        fn set_payment(_origin, value: T::Balance) -> Result{
            //ゲームのpaymentの仕様
        }
    }
}

decl_storage! {
  trait Store for Module<T: Trait> as Demo {
    Payment get(payment): Option<T::Balance>;
    Pot get(pot): T::Balance;
  }
}

strageモジュールはオンチェーンに載せる情報を定義するために使用します。

次に、モジュール内のロジックを書いていきます。

ゲームをプレイするロジック

fn play(origin) -> Result {

  let sender = ensure_signed(origin)?;
  //decl_strage!で宣言しているからpaymentが使える
  let payment = Self::payment().ok_or("Must have payment amount set")?;
 //senderの残高を減少させる
  <balances::Module<T>>::decrease_free_balance(&sender, payment)?;

  //ハッシュ関数を通してハッシュ値の最初のbyteが128以下であれば勝ち。potにあった金額がSenderに払われる
  if (<system::Module<T>>::random_seed(), &sender)
  .using_encoded(<T as system::Trait>::Hashing::hash)
  .using_encoded(|e| e[0] < 128)
  {
    <balances::Module<T>>::increase_free_balance_creating(&sender, <Pot<T>>::take());
  }
 
  //結果どうあれ、senderが賭けに参加した金額がデポジットされる
  <Pot<T>>::mutate(|pot| *pot += payment);

  Ok(())
}

ゲーム内のPaymentのロジック


fn set_payment(_origin, value: T::Balance) -> Result {
 //イニシャルpaymentがセットされていない場合の処理
  if Self::payment().is_none() {

    <Payment<T>>::put(value);
    <Pot<T>>::put(value);
  }

  Ok(())
}

変更をアップデートする

上記でModuleを設定しました。変更を実行するために./runtime/src/lib.rsを編集します。

mod demo;の追記。

impl demo::Trait for Runtime {}の追記

Demo: demo::{Module, Call, Storage},の追記

上記で新しいRuntimeモジュールを作成したので、次にブロックチェーンのアップデートをします。

Substrate-node-templateのディレクトリでビルド
substrate-node-template $ ./build.sh

ビルド後、substrate-uiのnpm run devで接続したlocalhost8000にて一番下にRuntime Upgradeができるので、Select Runtimeを押し、

./runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm

を選択する。これでRuntimeのアップデートができました。Developer Consoleでみると動いていることがわかります。

UIを整える

UIを整えるにはsubstrate-uiレポジトリーを編集する必要があります。
./substrate-ui/src/app.jsxを編集します。

新しく以下のセクションを追加します。

<Divider hidden />
<Segment style={{margin: '1em'}} padded>
  <Header as='h2'>
    <Icon name='game' />
    <Header.Content>
      Play the game
      <Header.Subheader>Play the game here!</Header.Subheader>
    </Header.Content>
  </Header>
  <div style={{paddingBottom: '1em'}}>
    <div style={{fontSize: 'small'}}>player</div>
    <SignerBond bond={this.player}/>
    <If condition={this.player.ready()} then={<span>
      <Label>Balance
        <Label.Detail>
          <Pretty value={runtime.balances.balance(this.player)}/>
        </Label.Detail>
      </Label>
    </span>}/>
  </div>
  <TransactButton
    content="Play"
    icon='game'
    tx={{
      sender: this.player,
      call: calls.demo.play()
    }}
  />
  <Label>Pot Balance
    <Label.Detail>
      <Pretty value={runtime.demo.pot}/>
    </Label.Detail>
  </Label>
</Segment>

同時に、exportの部分にthis.player = new Bond;を追記します。

................
this.seedAccount.use()
this.runtime = new Bond;
//これ
this.player = new Bond;
}

これでUIを構築することができ、ゲームをPlayすることができます。

Source: https://www.youtube.com/watch?v=0IoUZdDi5Is&t=22s