Neutrinoで働くブロックチェーンエンジニアのブログ

渋谷の専門特化コワークNeutrinoに入居してブロックチェーンエンジニアとして働いています。元丸の内。(Neutrino運営企業とは直接関係ありません)

Ethereumにおけるparityクライアントのstate保存オプションを検証する

RustベースのEthereumクライアントでparityが提供されています。

色々と便利な機能があり、stateを保存するオプション仕様を検証してみます。

f:id:naomasabit:20181126235703p:plain

stateとは

stateとは、Ethereumのアカウント状態全ての集合です。

各アカウント(アドレス)は

を持ちます。 残高など、変化する情報であるためstate(状態)という名前がついてます。

f:id:naomasabit:20181127014325p:plain

こちらの画像のように、ブロックごとに変化していくイメージです。

※画像はsolidity - Contract's state after a Selfdestruct - Ethereum Stack Exchangeより。

僕が先日発表した資料でもstateについて書かせていただきました。(53ページより)

speakerdeck.com

stateを捨てるpruning

stateは全て持っているととても重いデータになります。なので、通常の同期では適宜削除するようにしていますが、stateを保持する期間(ブロック数)を設定できるのがparityクライアントになります。

stateを捨てる処理をpruningと言います。

全てのブロックのstateを保持するpruning=archiveモード

archiveモードは全てのブロックのstateを取得します。なので相当重くなりますし、おそらく2018年11月24日時点で10TBほど必要になったと思います。

state保持期間を指定できるpruning-historyオプション

pruningのデフォルトである、pruning=fastモードは通常最新64blockあるいは32MBまでしか持ちませんが、保持するstateをより長くするためのpruning-historyオプションが用意されています。

pruning-history=[NUM]で指定し、NUMに設定したブロック数分の最新stateを保持するオプションです。

なお、pruning-memoryという設定したメモリ量分の最新stateを保持するオプションもありますが、pruning-historyが優先されます。

特定ブロック時点のstateのスナップショットを取得するwarp-syncについて

wiki.parity.io

parityではwarp-syncという、5,000ブロックずつ、ブロック時点でのstate(おそらく全アドレスのstate = World State)スナップショットを作成しておき、それを取得する高速同期方法がデフォルトだと使われています。

例えば、最新ブロックが119,000だとすると、115,000ブロック時点のEthereumのstateがスナップショットとしてダウンロードされ、それ以前のstateは取得されたり計算され直したりされないということになります。

スナップショットで同期されると、スナップショット前のブロック時点におけるstateが取得できないパターンがあるため、warp syncとpruningの関係性を確認する必要がありそうです。

pruning-historywarp同期との関係性を検証する

- pruning=fast
- pruning-memory=1 (検証のため最小の1MBにしておきます)
- pruning-history=100000

上記条件であれば、100000ブロック前のstateが保持できるはずです。

しかしながら、上記で見たようにwarp-syncでは最新スナップショットが落とされて来るため、スナップショット以前のstateが取得できるか確認する必要があります。

そこで、上記条件に、warp syncの条件を[warp / no-warp / warp-barrier=6000000]と変えてparityを起動し適当なところで止めて、100000番目と100001番目のstateを取得できるかを確認します。

なお、結論としては、

となりました

事前準備:parityのインストール

今回はソースコードからビルドせず、ビルド済のバイナリを取得する手抜きをしました。

  • parity install
$ bash <(curl https://get.parity.io -L) -r stable

1.warp-syncをしない(no-warp-syncオプション)で昔のstateを取得する

1-1.parityでブロックをダウンロードする

  • parity起動オプションに--no-warpがつきます。
$ parity --no-warp --pruning=fast --pruning-history=100000 --pruning-memory=1
  • 他のノードと接続しないでparityを再起動する

--max-peers=0オプションをつけて他のノードと接続しないことにより、新規ブロックはダウンロードされません

$ parity --no-warp --pruning=fast --pruning-history=100000 --pruning-memory=1 --max-peers=0 --jsonrpc-cors=all

1-2.昔のstate取得を試してみる

  • ダウンロードした最新block番号を確認

同期したブロック番号を確認します。

$ curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545

{
  "jsonrpc": "2.0",
  "result": "0x26566",
  "id": 1
}

最新ブロック番号は0x26566=157029です。ここから過去のstateを取得してみます。

  • 100000こ前のstateを取得する(ブロック番号: 0xDEC7 = 57030)
$ curl --data '{"method":"eth_getBalance","params":["0xBf71642d7CBae8FaF1CFdc6C1C48fcB45b15eD22","0xDEC7"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","result":"0x158c6baff469c60000","id":1}

過去のstateを取得できました!

  • 1000001こ前のstateを取得してみる(ブロック番号: 0xDEC6 = 57029)
$ curl --data '{"method":"eth_getBalance","params":["0xBf71642d7CBae8FaF1CFdc6C1C48fcB45b15eD22","0xDEC6"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","error":{"code":-32000,"message":"This request is not supported because your node is running with state pruning. Run with --pruning=archive."},"id":1}

1000001こ前ではpruningしてしまったというエラーになりました。pruning-historyが効いて指定分までのstateのみを保持するようになっています。

2.通常のwarp-syncで昔のstateを取得する

2-1.parityでブロックをダウンロードする

$ parity --pruning=fast --pruning-history=100000 --pruning-memory=1

Warp-syncのためスナップショットの同期が始まります

2018-11-26 23:33:39  Updated conversion rate to Ξ1 = US$108.26 (43985816 wei/gas)
2018-11-26 23:33:43  Syncing snapshot 0/2399        #0    5/25 peers   8 KiB chain 3 MiB db 0 bytes queue 11 KiB sync  RPC:  0 conn,    0 req/s,    0 µs
2018-11-26 23:33:53  Syncing snapshot 1/2399        #0   25/25 peers   8 KiB chain 3 MiB db 0 bytes queue 11 KiB sync  RPC:  0 conn,    0 req/s,    0 µs
2018-11-26 23:33:58  Syncing snapshot 1/2399        #0   25/25 peers   8 KiB chain 3 MiB db 0 bytes queue 11 KiB sync  RPC:  0 conn,    0 req/s,    0 µs
2018-11-26 23:34:03  Syncing snapshot 4/2399        #0   25/25 peers   8 KiB chain 3 MiB db 0 bytes queue 11 KiB sync  RPC:  0 conn,    0 req/s,    0 µ

しばらく(半日ほどかかりました)放置するとスナップショット同期が完了して通常同期になります。

6775000ブロックまでのスナップショットを同期して、そこから通常同期になったことがログから読み取れます。(通常同期で6775000に近い6775008ブロックを取得しているログが見えるため)

2018-11-27 13:44:45  Syncing snapshot 2395/2399        #0   26/50 peers   8 KiB chain 3 MiB db 0 bytes queue 11 KiB sync  RPC:  0 conn,    0 req/s, 21407 µs
2018-11-27 13:44:55  Syncing snapshot 2397/2399        #0   26/50 peers   8 KiB chain 3 MiB db 0 bytes queue 11 KiB sync  RPC:  0 conn,    0 req/s, 21407 µs
2018-11-27 13:45:36  sleep: Cannot sleep - syncing ongoing.
2018-11-27 13:45:36  Syncing #6775008 0x1242…7e17     0.20 blk/s   18.7 tx/s    1 Mgas/s      0+  470 Qed  #6775478   25/25 peers   416 KiB chain 10 MiB db 43 MiB queue 1 MiB sync  RPC:  0 conn,    0 req/s, 21407 µs
2018-11-27 13:45:36  Syncing #6775008 0x1242…7e17     0.00 blk/s    0.0 tx/s    0 Mgas/s      0+  470 Qed  #6775478   25/25 peers   416 KiB chain 10 MiB db 43 MiB queue 1 MiB sync  RPC:  0 conn,    0 req/s, 21407 µs

parityを停止します。

  • 他のノードと接続しないでparityを再起動する

--max-peers=0オプションをつけて「1」同様に再起動します。

parity --pruning=fast --pruning-history=100000 --pruning-memory=1 --max-peers=0 --jsonrpc-cors=all

2-2.prune-historyで設定した昔のstate取得を試してみる

  • ダウンロードした最新block番号を確認
$ curl --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","result":"0x6763b4","id":1}

最新ブロック番号は0x6763b4=6775732のようです。

  • 100000こ前のstateを取得する(ブロック番号: 0x65DD15 = 6675733)
$ curl --data '{"method":"eth_getBalance","params":["0xBf71642d7CBae8FaF1CFdc6C1C48fcB45b15eD22","0x65DD15"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","error":{"code":-32602,"message":"Unknown block number"},"id":1}

Unknown block number というエラーが出てきました。

  1. stateを取ろうと指定したブロック番号は6675733
  2. warp-syncで同期したスナップショットのブロック番号は6775000

で、1が2より前だからこのエラーになったようです。

2-3.warp-syncの最新スナップショット前後でstateを取得する

  • warp-syncしたスナップショットのブロック番号でstateを取得する

最新スナップショットである6775000ブロック前後でstateを取得してみます。 ブロック番号0x6739C8 = 6775000でstate取得

$ curl --data '{"method":"eth_getBalance","params":["0xBf71642d7CBae8FaF1CFdc6C1C48fcB45b15eD22","0x6760D8"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","result":"0x53b2ad59067c00c00","id":1}

取得できています。

ブロック番号0x6739C87 = 6764999でstate取得

$ curl --data '{"method":"eth_getBalance","params":["0xBf71642d7CBae8FaF1CFdc6C1C48fcB45b15eD22","0x6760D7"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","error":{"code":-32000,"message":"This request is not supported because your node is running with state pruning. Run with --pruning=archive."},"id":1}

pruningしてしまったというエラーが出ました。pruning-historyを設定していても最新スナップショットまでのブロックしかstateは遡れないようです。

  • 番外 pruningエラーとUnknown block numberエラーとなる境界ブロック番号を探る。

さらに5000遡って、ブロック番号0x674D50=6770000で取得してみるとUnknown block numberエラーになりますが、ブロック番号0x674D51=6770001で取得してみるとpruningエラーとなりました。

依然として、最新スナップショット以降しかstateが取れないことは変わりありませんが、もしかすると最新から一つ前のスナップショットまではstate以外のデータ、トランザクションなども保持しているのかもしれません。

$ curl --data '{"method":"eth_getBalance","params":["0xBf71642d7CBae8FaF1CFdc6C1C48fcB45b15eD22","0x674D50"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","error":{"code":-32602,"message":"Unknown block number"},"id":1}
$ curl --data '{"method":"eth_getBalance","params":["0xBf71642d7CBae8FaF1CFdc6C1C48fcB45b15eD22","0x674D51"],"id":1,"jsonrpc":"2.0"}' -H "Content-Type: application/json" -X POST localhost:8545
{"jsonrpc":"2.0","error":{"code":-32000,"message":"This request is not supported because your node is running with state pruning. Run with --pruning=archive."},"id":1}

結論:warp-syncで取得するスナップショット以前のstateは取得できない

no-warpであればpruning-historyの設定通りに取得できました。これでstateの保存期間は指定できます。

しかしながら、高速同期であるwarp-syncではpruning-historyの設定があってもスナップショット以前のstateは取得できません。

任意ブロックのスナップショットまでwarp-syncする方法も探してみましたが、見当たりませんでした。

もしご存知の方がいれば教えてください。

補足:warp-barrierオプションについて

warp-barrierオプションというものもあり、これを利用すれば任意のスナップショットからstateを復元できるかと考えましたが、そうでもないようです。

Now with Warp Barrier, you can jump to a minimum block number to make sure you're getting the latest possible snapshot

あくまで最新のスナップショットを取るためのオプションのようで、実際に試してみましたがwarp-syncと結果は同じでした。

www.parity.io