web3-provider-engineについて
What is
密かにいろんなweb3関連ツールで使われてるmiddleware的な存在。
truffle-hdwallet-providerとかでも利用されている
作成元はMetaMaskチーム。もともとMetaMaskで使っていたhook関連の処理をlibrary化したものぽい。
Repository
Providers
web3-provider-engineで定義しているProviderの紹介
web3-provider-engine
こいつがメイン。これに対してsubproviderをaddProviderする。
利用例1 (web3をフックする)
code: example
const ProviderEngine = require('web3-provider-engine')
const NonceSubprovider = require('web3-provider-engine/subprovider/nonce-tracker')
const ProviderSubprovider = require('web3-provider-engine/subprovider/provider')
// web3@1.0だとsendAsyncがなくてエラーになるので定義しておく
web3.currentProvider.sendAsync = web3.currentProvider.send
// subprovidersのインスタンスを作成
const wrapper = new ProviderSubprovider(web3.currentProvider)
const nonceProvider = new NonceSubprovider()
// web3-provider-engineを作成
const engine = new ProviderEngine()
// subprovidersをaddしていく。順番は重要(後述)
engine.addProvider(nonceProvider)
engine.addProvider(wrapper)
engine.start()
...
engine.stop()
使用例2(外部からblock生成を監視する)
web3-provider-engineは内部でeth-block-trackerを使って、blockの生成を監視している。
かつ、EventEmmiterを継承していて、block、start、endのeventがキャプチャ可能。
web3をフックする形じゃなくて、外部からblock生成を監視することができる。
RpcSourceをaddProviderすることでEthereum nodeと通信できる様にする。
code: example
const ProviderEngine = require('web3-provider-engine')
const RpcSource = require('web3-provider-engine/subprovider/rpc')
// subprovidersのインスタンスを作成
// web3-provider-engineを作成
const engine = new ProviderEngine()
// rpcSourceをaddする
engine.addProvider(rpcSource)
// block eventをキャプチャ
engine.on('block', block => console.log(block))
engine.start()
...
engine.stop()
Subprovider
path
web3-provider-engine/subprovider/subprovider.js
説明
Subproviderのベースクラス。
色々なSubproviderはこいつを継承して作られている。CustomeSubprovider作るときもこいつを継承して作る
handleRequestのみが唯一実装が必要なメソッド。
それ以外として、start, stop, も必要に応じてオーバーライドして使う。
ProviderSubprovider
path
web3-provider-engine/subprovider/provider.js
説明
何かしらのproviderをラップするためのSubprovider
多分、基本的にはweb3.jsで定義されている HttpProvider IPCProvider WebSocketProviderをラップするために使うんだと思う。
handleRequestで受け取ったpayloadを引数にしてwrapしたprovider.sendAsyncを呼び出す。
handleRequestの最後にendを呼ぶので、このSubproviderは一番最後にaddしないといけない。
RpcSource
path
web3-provider-engine/subprovider/rpc.js
説明
JSON-RPCを投げて色々処理してくれるやつ。
ある意味HttpProviderの代わり的なもの。
web3と別で何かしらの監視とかしたいときにweb3-provider-engineを使いたいときに使う?
こいつもhandleRequestの最後にendを呼ぶ。
NonceSubprovider
path
web3-provider-engine/subprovider/nonce.js
説明
nonceをキャッシュしてくれるやつ
eth_getTransactionCountとeth_sendRawTransactionのみをhookする。ので、いまいち使いどころが。。。
truffle-hdwallet-providerで利用している。
handleRequestの最後にnextを呼ぶので、このSubproviderは最後に追加したらエラーになる。
アーキテクチャ
web3-provider-engineがsubproviderを呼び出す流れを説明する
handleRequest シーケンス
sendAsync -> _handleRequest -> next -> next -> ... -> end -> after fns -> finished
web3-provider-engineのnextが呼ばれると、次のSubproviderのhandleRequestが呼ばれる。
Subprovider.handleRequest(payload, next, end)なので、Subprovider自身が次のSubproviderに処理を移譲するかどうかの権限を持つ。
Subprovider.handleRequestの中で、endが呼ばれると、bubblingはそこで終了して、後ろにいるSubproviderはもう呼ばれない。
あるpayloadのときだけ処理を奪いたいときはendを呼ぶって感じで使う。
Subprovider.handleRequestの中で、nextが呼ばれると次のSubproviderに処理が移る。
hookするけど処理は奪わない様なばあいはnextを呼ぶ
nextを呼んだけど自分より後ろにSubproviderがいない場合はエラー。つまり、web3-provider-engineの一番最後にはendを呼び出すSubproviderをaddする必要がある。
nextは終了フェーズ(endが呼ばれた後のフェーズ)で処理をするための、after functionを引数で受け取る。next(after)
after 関数は最後に追加されたものから順に実行される。つまり、LIFO。もしくは、pusu-pop型のスタック。
after関数はfunction (error, result, callback)となっており、JSON-RPCのresponseもしくはerrorが受け取れる。afterで処理が終わった後はcallback()を呼び出すことで次のafter関数に処理が移譲される。