y-nakajo.hatenablog.com
前回の記事の続きになります。
タイトルの通りweb3.eth.subscribeを試してみました。
が、テストのためだけにGethのノードを準備するのはめんどくさいのでtestRPCあらため、ganache-cliでWebSocketサーバ起動できないのかなぁ?と思い調べてみました。
そしたらこんなIssueを発見!
github.com
ということでganache-cli@betaを使ってweb3.eth.subscribe(= event filter)を利用してみます。
以降はtruffleをインストール済みで、何かしらのEventを発行するContractを作成済みという前提で進めます。
- ganache-cli@betaのインストール
- ganache-cliの起動
- truffle.jsの準備
- truffleの起動
- web3@1.0.0をロードして、WebsocketProviderでnodeに接続する
- web3@1.0.0でcontractのインスタンスを作り直す
- web3@1.0.0のContractからweb3.eth.subscribe(= event filter)を取得する
- Eventを発行してログを確認する
- ganache-cliのログをチェック
- web3@0.2x.xのweb3.eth.filter.watchするとganache-cliのログがどうなるか見てみる
- まとめ
- 参考
ganache-cli@betaのインストール
まずはWebSocketを実装したganache-cliをインストールします。betaバージョンを指定してインストールすればOKです。
web3@1.0.0も一緒にインストールします。
npm i ganache-cli@beta web3
ganache-cliの起動
アカウントアドレスを固定したかったのでtruffle developで使われているmnemonicを渡して起動します。けど、別に固定する必要なかったので、-mオプションはつけなくてもOKです。
WebSocketを起動する特別なオプションなどは必要ありません。普通に起動すればhttp, wsのエンドポイントが起動します。
node_modules/.bin/ganache-cli -m "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
WebSocketは最初のハンドシェイクにhttpを利用して、その後ハンドシェイクで利用したtcpセッションをそのまま使って通信するため、httpとwsのポートは同じになるらしいです。詳しくは以下を参照ください。
今さら聞けないWebSocket~WebSocketとは~ - Qiita
WebSocketについて調べてみた。 - Qiita
truffle.jsの準備
truffleを使ってganache-cliに接続します。ganache-cliへ接続するためのconfigは次の通りです。とはいえここでも特別なものは何も必要ないのでいつも通りの設定でOKです。
module.exports = { networks: { live: { host: "localhost", port: 8545, // ポートを指定してganache-cliを起動した場合は適宜修正してください network_id: "*" } } };
web3@1.0.0をロードして、WebsocketProviderでnodeに接続する
*これ以降のコマンドはすべてtruffle console上で実行します。*
truffle consoleを起動するとすぐにweb3が使えるようになっていますが、これはtruffleにバンドルされているweb3@0.2x.x系なので先ほどインストールしたweb3@1.0.0を有効化します。
その後、WebsocketProviderの作成と設定をしてweb3@1.0.0のインスタンスを作成します。
最後に接続確認のためにaccount addressの取得とbalanceの取得を行っています。
Web31 = require('web3') wshost = web3.currentProvider.host.replace("http", "ws") wsProvider = new Web31.providers.WebsocketProvider(wshost) repl.repl.on("exit", () => wsProvider.connection.close()) // (追記)truffle終了時にwsProviderのconnectionを解放してあげないとganache-cli落とすまで終了しなくなる web31 = new Web31(wsProvider) // 以下2つは接続確認 web31.eth.getAccounts((err, res) => account = res[0]) web31.eth.getBalance(account)
web3@1.0.0でcontractのインスタンスを作り直す
migrateを実行してContractをデプロイします。
ここで作成したContractはTruffleContractであってweb3@1.0.0のeth.Contractではないので作り直します。
migrate // まずはデプロイする web31.eth.getAccounts((err, res) => account = res[0]) options = {from: account, gas: 100000, gasPrice: 100} // contractがtransactionに設定するデフォルト値 MyContract.deployed().then((ins) => contract = new web31.eth.Contract(ins.abi, ins.address, options))
MyContractは適宜自分で作成したContract名に置き換えてください。
これでcontractにweb3@1.0.0のeth.Contractが生成されました。
web3@1.0.0のContractからweb3.eth.subscribe(= event filter)を取得する
ようやく本題です。先ほど生成したweb3@1.0.0のContractからevent filterを取得します。
event = contract.events.MyEvent() event.on("data", (data) => console.log("data", data)) event.on("changed", (data) => console.log("changed", data)) event.on("error", (error) => console.log("error", error)) event.id
最後のevent.idがnullではなく何かしらのバイト値を表示していればsubscribeが成功しています。
event filterであるSubscriptionはEventEmitterを持っており、data, changed, errorのイベントが発行されるのでそれぞれのイベント発生時に実行するlistenerを登録しています。
Eventを発行してログを確認する
ContractのEventを発行するmethodを実行してeventログが拾えるか試してみます。
Promise.resolve(contract).then((c) => {c.methods.exec(1, [1]).send(); return null})
MyMethod(args)は自分で作成したContractのメソッド名と引数に適宜置き換えてください。
web3@0.2x.x系であればcontractのinstanceに続けてmethod名、event名をつけることで、methodやevent filterが取得できましたが、web3@1.0.0 系ではmethods, eventsをつけてからmethod名やevent名をつなげる必要があります。
さらに、method呼び出しであれば最後に、call()やsend()を繋げないと実際に発行されません。
直接実行するとtransactionReceiptのログが表示されて、event filterがキャッチしたログが流れてしまうので、transactionReceiptが流れないようにPromiseでラップして実行しています。
コンソール上にeventログが表示されたら成功です。
ganache-cliのログをチェック
ganache-cliのログにはこんな感じのRPCログが記録されていると思います。
eth_getTransactionReceipt eth_subscribe eth_sendTransaction Transaction: 0x2e19a43a01f3b515badb1a4242e44c109b4d6a8a29fdc8ef3d00635a56f85dec Gas usage: 56365 Block Number: 7 Block Time: Thu Mar 01 2018 10:37:52 GMT+0900 (JST) eth_getTransactionReceipt
eth_subscribeでevent watcherを登録して、それ以降はサーバ側からevent logがpushされているのがわかります。
web3@0.2x.xのweb3.eth.filter.watchするとganache-cliのログがどうなるか見てみる
web3.eth.filter.watchではeventのwatchingのためになんどもeth_getFilterChangesを発行しています。
それを実際に確認してみます。
TruffleContractからevent watchすればいいので次のコマンドで確認できます。
MyContract.deployed().then((ins) => myContract = ins) filter = myContract.MyEvent() filter.watch((err, res) => err ? console.log("error", err) : console.log("event", res))
filter.watchを実行した後に、ganache-cliのコンソールを開くとeth_getFilterChangesのRPCログがズラーッと流れていくのがわかると思います。このようにweb3.eth.filter.watchでは定期的にRPCサーバへ変更を問い合わせるのでEventのwatchingにはDapps、RPCサーバ共に負荷がかかります。
watchingを止めたい場合は
filter.stopWatching()
で止まります。
まとめ
web3@1.0.0のevent filterはPUB/SUBモデルのため、node側がWebSocket対応のRPCサーバを立ててくれないと使えないという制約はありますが、その分低コストで運用できます。
また、event受け取り時の処理をEventEmitterで実装しているため、複数のlistenerを登録したり、途中でlistenerを組み替えたりなどが柔軟にできます。
Infura.ioがすでにWebSocketに対応しています。Infura.ioをバックボーンとして利用しているDappsならAPI発行回数制限の問題もあるので、web3@1.0.0のweb3.eth.subscribe機能へ乗り換えたほうがいいでしょうね。