Symbol のノードを運営していると、委任ハーベストをしてくれていた人が、いつの間にかノードから外れていることがある。何もしていないのに壊れた、ってやつである。
この記事は、外れた委任をチェーンから見つけ直して、ノードに戻すまでの考え方の話である。道具は、自作の heika を使う。
何が起きるのか
委任ハーベストでは、委任する人が、ノードあてに暗号化したメッセージ(委任の要求)を送る。ノードはそれを受け取ると、中のリモートアカウントと VRF の鍵を harvesters.dat に書いて、ハーベストに使う。
ところが、委任した人の importance が 0 になる時期があると、ノードはその委任を外してしまう。importance が戻っても、ノードは自分からは取り戻さない。委任の要求は一度送ったきりなので、委任した人がもう一度送り直さない限り、そのままになる。
ノードの側から見ると、ハーベストに使える委任が、知らないうちに減っていく。
委任の要求はチェーンに残っている
救いは、委任の要求がただの転送のトランザクションだということだ。ノードの鍵あてに暗号化されたメッセージとして、ブロックにずっと残っている。
なので、こうすれば外れた委任を見つけ直せる。
- ブロックを順にたどり、ノードあての委任の要求を拾う
- ノードの鍵で復号して、中のリモートアカウントと VRF の鍵を取り出す
- 同じブロックの並びから、委任した人のメインアカウントに今どの鍵がリンクされているかを追う
- 要求の鍵とリンクが3つとも合っていれば、その委任は今も有効である
要求を送ったあとに委任した人がリンクを付け替えていれば、その要求はもう使えない。なので、拾うだけでなく、リンクと突き合わせるのが大事になる。heika は、有効(3つとも合う)、無効(合わないものがある)、不明(走査した範囲でリンクを見ていない)の3つに分けて出す。
heika node delegations --connect <ホスト>:<ポート> --client-cert node.full.crt.pem --from-height <高さ> < node.key.pem
--from-height は、ノードが委任を受け付け始めたころにする。それより前から読むと時間がかかるだけで、それより後から読むと、リンクを見逃して不明が増える。
戦略: 速いノードを先に探す
ブロックを読むなら、本当は自分のノードが一番近くて速いはずだ。ただ、委任が外れていると気づいて全力で復旧しているときに限って、自分のノードは使えなかったりする。harvesters.dat を差し替えるために止めていたり、同期し直していたりするからだ。長い範囲のブロックを読むには時間もかかるので、その間ずっと待っているわけにもいかない。
ブロックの中身はどのノードでも同じなので、読むのは自分のノードでなくてもよい。そこで、先に近くて速いノードを探してから、そのノードからたどることにした。
heika node find --connect <ホスト>:<ポート> --client-cert node.full.crt.pem --crawl --limit 300 < node.key.pem
heika node find は、ノードに TLS でつないで、接続にかかる時間、高さ、ブロックの読み出しの速さを測る。--crawl を付けると、測ったノードから教えてもらったノードもたどる。読み出しが速くて、高さが遅れていないノードを選んで、heika node delegations の --connect に渡す。
復号に使うのは手元のノードの鍵だけなので、どのノードから読んでも、正直なノードなら結果は同じになる。ただし、つなぐときは自分のノードの証明書で名乗るので、つないだ先のノードには、どのノードが読みに来たかがわかる。
よそのノードから読んだものを信じてよいか
先に言っておくと、これはトラストレスなやり方ではない。よそのノードが、ブロックを抜いたり、中身を差し替えたりして返してきたら、委任を見落としたり、ありもしない委任を拾ったりしてしまう。
heika は、読んだブロックを次のように確かめてから使い、合わなければそこで止める。
- 頼んだ高さから、途切れずに返ってきたか。 高さ 100 から頼んで 101 が返ってきたら止める。
- 前のブロックとつながっているか。 ブロックのヘッダーには、1つ前のブロックのハッシュが入っている。それが、実際に読んだ1つ前のブロックのハッシュと合うかを見る。
- 中身がヘッダーと合っているか。 ヘッダーには、中のトランザクションから計算した TransactionsHash が入っている。読んだトランザクションから計算し直して、合うかを見る。
ヘッダーは次のブロックとハッシュでつながり、中身は TransactionsHash でヘッダーとつながる。なので、途中の1件だけを抜いたり差し替えたりすると、どこかで合わなくなって止まる。
ただし、これで言えるのは「読んだ範囲が、つじつまの合った1本の鎖だ」というところまでである。最初からまるごと作りものの鎖を用意して、嘘をつき通すノードまでは見破れない。別のノードと結果を比べても、両方が同じ嘘をついていれば同じ結果になるので、正しさの保証にはならない。
なので、ワークアラウンドとしては機能するけれど、信頼できるノードを使うか、相手がビザンチン故障しているかもしれないことは頭の片隅に置いておきたい。まあ、止まったら別のノードでやり直せば、いつかは正直なノードに当たって成功するんじゃないかと思う。しらんけど。
一番確かなのは、自分のノードが復旧したあとで、同じ範囲を自分のノードで走査し直して、答え合わせをすることだ。自分のノードは、チェーンを全部自分で検証しているからである。
もう1つ、知らない種類のトランザクションが出てきたら、heika はそこで止める。読み飛ばすと、その中にあったリンクの付け替えを見落として、結果が正しくなくなるかもしれないからだ。
ノードに戻す
見つけた委任が今の harvesters.dat に入っているかは、--harvesters-dat で突き合わせられる。
heika node delegations --connect <ホスト>:<ポート> --client-cert node.full.crt.pem --from-height <高さ> \
--harvesters-dat harvesters.dat --write-harvesters-dat harvesters.new.dat < node.key.pem
--write-harvesters-dat を付けると、今の harvesters.dat の項目をすべて残したまま、入っていない有効な委任と、有効かわからない委任を足して、新しいファイルに書く。無効なものや合わないものは、ノードが起動したあとで自分で外すので、足しても害はない。
書く前と書いたあとに、どの項目もノードの鍵で復号できることを確かめている。ノードの鍵で復号できない項目が1つでもあると、ノードが起動しなくなるからだ。
元の harvesters.dat は書き換えない。ノードを止め、元のファイルを残したうえで、新しいファイルに置き換えて起動する。
まとめ
- importance が 0 になると、ノードは委任を外し、自分からは戻さない
- 委任の要求はチェーンに残っているので、ブロックをたどれば見つけ直せる
- 拾った要求は、メインアカウントのリンクと突き合わせて、今も有効かを見る
- 自分のノードが使えないときは、速いノードを先に探して、そこから読む
- よそのノードから読んだブロックは、つながりと TransactionsHash を確かめてから使う。ただしトラストレスではないので、自分のノードが戻ったら答え合わせをするほうがいいかも
- harvesters.dat は、元を残して、足したファイルに置き換える
heika: