はじめに
今回はフロントエンドアプリ(React)からよくサンプルで出てくるWsProviderを使わずにSubstrateにデータを登録&取得できるかtxwrapperのドキュメントとtxwrapperのサンプルソースを参考にしつつ、どのように実装可能か試してみました。
意図としては、WsProviderを使う際にフロントエンドアプリからSubstrateノードに直接繋ぐ事になりますが、これを直接繋がずにAPIサーバー経由でブロックチェーン(Substrate)にデータを登録する事で、APIサーバー側でフロントエンドアプリからのアクセスを全て制御できます。
こうする事でバックエンド側のエンドポイントを1つにする事ができ状況や方針によっては運用しやすくなります。
実装方針
基本方針
txwrapperのサンプルソースを確認するとTypescriptのソース上でSubstrateノードにRPCでアクセスしてチェーン情報を取得し、その情報とアカウントの秘密鍵を使用してTXを作成して署名しています。そして、その署名したTX文字列をSubstrateノードに同じようにRPCで送信してブロックチェーンにデータを登録しています。
これをフロントエンドアプリ(React)上で以下処理を行いAPIサーバー経由にする事で、Substrateノードに直接繋ぐ事がなくなります。
- APIサーバー経由でブロックチェーン情報を取得
- 1の情報とアカウントの秘密鍵を元に署名済TX文字列を作成
- 署名済TX文字列をAPIサーバーに送信。APIサーバー側では受け取った文字列をブロックチェーンに投げてTX発行
さらにPolkadot{.js} extensionで作成したextenstionタイプ(ブラウザ拡張)のアカウントを使おうとするとtxwrapperサンプルソースの書き方とまた別の対応する必用があります。
extenstionタイプ(ブラウザ拡張)のアカウントと、Substrateのアカウント関連実装でよく出てくるkeyring pairのアカウントでTX署名の仕方が違うためです。
実装手順
ベース部分の実装
Substrate, APIサーバー, フロントエンドアプリ(React) のベース部分の実装について記載します。
Substrate
Substrateに関してはSubstrate node templateを使用します。
TX発行と値取得だけを確認する目的に使用するため、Substrate node template についているpallet-templateをそのまま使用します。
ソースは特に修正しません。
APIサーバー
今回はTypeScript+Node.jsベースのAPI向けフレームワークであるLoopback4を使います。
以下コマンドでベースとなるソースをまずは作成します。
% npm i -g @loopback/cli
% lb4 api
? プロジェクトの説明: api
? プロジェクト・ルート・ディレクトリー: api
? アプリケーション・クラス名: ApiApplication
? プロジェクトで有効にする機能を選択してください (Press <space> to select, <a> to toggle all, <i? プロジェクトで有効にする機能を選択してください Enable eslint, Enable prettier, Enable mocha, Enable loopbackBuild, En
able vscode, Enable docker, Enable repositories, Enable services
? Yarn は使用可能です。 デフォルトでこれを使用するようにしますか? Yes
フロントエンドアプリ(React)
React+Typescriptで構築します。
以下npxコマンドを使ってベースとなるソースが出力されます。
% npx create-react-app front --template typescript
APIサーバーの実装
APIサーバーから Substrate にアクセスするための実装を行います。
用意するAPIは以下4つです。
- 署名済のTX文字列を受け取りSubstrateにSubmitする
- 上記Substrate内にSubmitした値の取得
- Substrateのノード情報を取得
- アドレスのnonceを取得
こちらのソースのように実装しました。
詳細は省きますが、Substrateノードに直接繋ぐのでAPIサーバー側ではWsProviderを使用してSubstrateノードにアクセスしています。
後ほどDockerで動かすのでDockerfileも作成しました。
フロントエンドアプリ(React)の実装
以下を行う処理を簡単なUIを構築します。
- APIサーバーよりブロックチェーン情報を取得します。
- Polkadot{.js} extensionで作成したextenstionタイプ(ブラウザ拡張)のアカウントを使用してSubstrateにデータを登録するTXを署名した文字列を作成します。
- 2の文字列をAPIサーバーに投げる
ソースはこちらを参照してください。
以下ではベースのソースから大きく追加&更新した箇所だけ簡単に説明をします。
src/lib
独自に実装するソースを src/lib に入れることにしました。
- lib/api - APIサーバーとの接続
- lib/substrate - 署名済TX作成
Reactのフォルダ構成ベストプラクティスかは良くわからず、、、考えすぎないようにとの事なので、、、
src/lib/api - APIサーバーとの接続
axiosを使いAPIサーバーとやりとりしてSubstrateにアクセスできるようにします。
src/lib/substrate - 署名済TX作成
WsProviderを使わずにTX署名しようとするとtxwrapper-coreを使用することが多いと思いますがReact上でtxwrapper-coreを使ったnpmライブラリを呼び出すと
Uncaught TypeError: types_1.TypeRegistry is not a constructorというエラーが発生して使うことができなかったので、txwrapperライブラリの部分部分を組み合わせて、とりあえずなんとか動くように対応しました。
(txwrapper-coreのエラーが解消されたらまた新しいコードにしようと思います)
App.tsx
シングルページで、Polkadot{.js} extensionのアカウントを読み込んでリストボックスで表示し、そのアカウントでTX署名して値をブロックチェーンに保存させます。
また、その保存した値をブロックチェーンから取り出してデータが保存されたか確認もします。
確認手順
テストアカウント準備
まず Polkadot{.js} extension でアカウントを作成しておきます。
その後、先ほど作成したアカウントのアドレスにトークンをpolkadot.js.org/appsなどから送金しておきます。これは、TX発行時の手数料を支払うためです。
各Dockerコンテナ起動
まず以下で各Dockerコンテナを起動します。
% docker-compose up -d
その後、ターミナルを各Dockerコンテナ毎起動してテストの準備をします。
Substrateコンテナ
以下のようにDockerコンテナに入ってSubstrateを起動します。
% docker exec -it offtx-substrate /bin/bash
$ /usr/local/bin/substrate --dev --rpc-external --rpc-cors all --ws-external
APIサーバー
% docker exec -it offtx-api /bin/bash
$ yarn start
フロントエンドアプリ(React)
% docker exec -it offtx-frontend /bin/bash
$ yarn start
ブロックチェーンへのデータ投入&保存確認
ブラウザで http://localhost:3001/ にアクセスすると以下の画面が表示されるはずです。
この際 Status が ready になっている事を確認してください。もし ready になっていない場合は、Frontend の ターミナルで何度か ctrl+c & yarn start で reactを再起動していると ready になる事があります。
画面が表示されたら、以下の手順で値を確認していきます。
- Signer(Sender) で先ほどトークンを付与したアカウントを指定します。
- [Submit Value] に保存したい値を入力し[SUBMIT TO NODE TEMPLATE]ボタンを押します。
- しばらく待ってAPIサーバー側のターミナルログに[Transaction finalized]というログが流れてきたらSubstrateにデータ保存できているはずです。
- [GET VALUE FROM SUBSTRATE]ボタンを押すと、Substrateから値を取得してきて[Submit Value ]で保存したい値がボタン近くに表示されます。
無事、保存した値が画面に表示されればOKです。
これでフロントエンドアプリ(React)からSubstrateノードに直接繋がずにAPIサーバーを経由して処理を行っているので
- ブラウザからのアクセスをAPIサーバー側で一括管理しつつ
- 秘密鍵はサーバー側に渡すことはなくブラウザ側で管理する
という事が実現できました。
終わりに
フロントエンドアプリの接続先がSubstrateやAPIサーバーというようにそれぞれで存在してしまうと、意図せずフロントエンドアプリからSubstrateに直接接続して処理してしまうロジックを設計&実装してしまう可能性があり、運用&メンテが大変になる可能性があるため、今回の設計も1案としてありなのではと個人的には思います。
現状txwrapper-coreを使えていないので、この辺りもいまissueでやりとりしているため改善したらまた記事を更新したいと思います。