前の記事で、ウォレットの残高を確かめる話のときに「状態遷移を検証しないのも気持ち悪いけど、さすがにそれはノードに任せたいです。だってたいへんそうなんだもん」と書きました。
書きました。ノードを。
ここに、書きました、ノードを。
heika v0.8.0 で、Symbol(XYM)のノードが試験的に動くようになりました。Rust で一から書いていて、メインネットにもテストネットにも、ピアとして入れます。デスクトップは Windows でも Linux でも動きますし、しかも Android のスマホでも動きます。Web ブラウザで調べものをしていても、黙々と同期しています。えらい。Claude Code がないと何もできない私とは大違い……。
何をするノードか
ネットワークのほかのノードからブロックを取ってきて、全部自分で確かめながら積んでいきます。
- ブロックの署名と、難易度
- ハーベストの当たりと、その元になる VRF の証明
- 中のトランザクション
- 1ブロックごとに、状態を当て直して求めた StateHash と ReceiptsHash
- ファイナライズの証明(投票の署名と重み)
ひとつでも合わなければ、そのブロックは受け取りません。前の記事で「信じていること」に置いていた「そのブロックが本当に正しいチェーンのものか」を、やっと自分で確かめられるようになりました。気持ち悪さが一つ減りました。なんていいつつもウォレットの接続先はいまだに REST なんですけどね
追いつき方は2段にしています。
- 追いかける段階: ファイナライズされた高さまでは、いくつかのピアから分け合ってまとめて取り、まとめて実行して、まとめて書く
- 先端の段階: そこから先は、届いたブロックを1つずつ確かめて先端を追う。枝が割れたら取り替える。ファイナライズから遅れたら、また追いつく段階に戻る
残し方を3つから選べる
何をどこまで残すかを、3つから選べます。
| 状態とその証明 | ブロックとステートメント | |
|---|---|---|
| アーカイブ | 全部の高さ | 全部 |
| フル | 最近の分だけ | 全部 |
| ライト | 最近の分だけ | 再開に要る分だけ |
アーカイブは、昔のどの高さの残高でも、その高さの StateHash への証明付きで引けます。監査や調べもの向けで、そのぶんディスクを食べます。もぐもぐたいむです。とても食べます。たいへん美味しくいただきます。ライトはいちばん軽くて、スマホで動かすならまずこれです。どうかんがえてもこれです。フルは「特徴がないのが特徴」と言いかけましたが、昔からのブロックを全部持っているので、新しく入ってくるノードに渡せます。ネットワークを支える縁の下の力持ちです。
動かし方
コマンドラインなら、設定ファイルを書いて渡します。Kubernetes が好きなので、マニフェストっぽくしました。CRD でもないのに……。そもそも Containerfile すら書いてなかったの今思い出したのでいつか思い出して気が向いたときにやればいいと思います(やるとは言っていない)。
apiVersion: heika.hinablogchannel.github.io/v1alpha1
kind: Peer
metadata:
name: my-peer
spec:
network:
type: mainnet
dataDirectory: /path/to/data
certificates:
directory: /path/to/certificates
seed: <最初につなぐノード>:7900
storage:
mode: light
harvesters:
- signer: /path/to/remote.key # ブロックに署名する鍵(リンクしたリモートの鍵)
vrf: /path/to/vrf.key # リンクした VRF の鍵
heika experimental peer start --config peer.yaml
メインネットとテストネットのネットワークの設定は heika が持っているので、resources フォルダーを用意しなくても動きます。
harvesters に鍵のファイルを渡すと、ハーベストもします。当たりの高さが来たら、自分でブロックを作って、ほかのノードに配ります。手元のプライベートなネットワークで、作ったブロックが本家のノードに受け取られるところまでは確かめました。鍵のファイルがほかの人にも読めるようになっていると、警告を出します。
GUI なら、もっと簡単です。「ノード」のタブを開くと、軽い順に3つ並んでいるので、選ぶだけで動き出します。設定ファイルは見せません。鍵と証明書も、初めて起動したときに勝手に作ります。
一番上に「※一応建前上は試験的な機能です」と書いてあります。義理と人情とおふくろの味です。
スマホで動かすのにやったこと
デスクトップでは、ノードはウォレットとは別のプロセスで動かしています。アプリが自分自身を --node 付きでもう一つ起動して、そっちがノードになります。鍵を持つウォレットと、外とやりとりするノードを分けておきたかったからです。
ところが Android のアプリは実行ファイルではなく、Java から読み込まれるライブラリなので、自分をもう一つ起動する、ができません。なので Android では、アプリの中のスレッドでノードを動かし、そのあいだはフォアグラウンドサービスで「今ノードが動いてます」という通知を出し続けて、OS にプロセスを止められないようにしています。
サービスの種類は specialUse にしました。素直に選ぶと dataSync なのですが、Android 15 から dataSync は1日に6時間までしか動けないそうです。知らんけど。知らんとはいえ、ノードに6時間労働制は、労務管理の観点からちょっとつらいものがあるといいますか……。
速くするのにやったこと
追いつく段階は、取る、実行する、書くを別々のスレッドにして重ねています。実行はブロックの順にしかできないので、その前後を重ねるしかないんですね。
測ってみると、実行のスレッドで意外と重かったのが、ブロックごとの VRF の検証でした。これは状態に依らないので、別のスレッドに出して、コミットする前にそこまでの確かめが終わるのを待つことにしました。確かめていないブロックが DB に入ることはありません。これで、その段階にかかる時間がおよそ10分の1になりました。
ただ、そうしたら今度は書くスレッドが詰まるようになりました。ボトルネックはモグラたたきです。
ほかにも、昔の高さの状態を引くための索引は、ブロックごとに書くと B 木のあちこちを書き換えて重いので、追いつき終わってからまとめて、キーの順に並べ替えて作るようにしています。
それと、初めて動かしたときにシードとなる初期ノードの1か所からしかブロックを取っていなかった、という間抜けなバグがありました。ピアの一覧から足りない分をつなぎ足すようにしたら、4倍くらい速くなりました。
泣き言
ノードそのものより、UI/UX のほうが難しかったです。
抜けている人のつもりで、いろいろやらかしてみました。こういうときだけ人類が大活躍して悲しい……。
- 追いついている途中で窓を閉じる → 閉じるときにノードを止めるのを待つようにした
- 画面のほうだけ落ちる → 子のノードの出力を管にしていたら、書けなくなって道連れで落ちていたので、ファイルに書かせるようにした
- ライトで作ったデータで、うっかりアーカイブを押す → 黙って進んで、名ばかりのアーカイブになっていた。2回目からは選ばせないようにした
- アプリを2つ開く → DB のロックでぶつかるので、2つ目を開いたら1つ目の窓を出すようにした
通知領域(トレイ)にもだいぶ泣かされました。ノードが動いているあいだは、窓を閉じても通知領域に隠れるだけにしたのですが、
- Windows は、新しいアプリのアイコンを最初は「隠れているインジケーター」の中に入れる
- Linux は、通知領域を出すものがない環境だと、アイコンを置けたことになるのに見えない。ライブラリがなければ panic して、アプリごと落ちるところでした
という具合で、隠れた窓が二度と出てこない事故が起きかけました。結局、アプリをもう一度起動すれば、隠れていた窓が出てくるようにしてごまかしています。
GUI からハーベストできるようにするのも考えたのですが、リモートの鍵をどこに置くかで止まっています。ディスクに平文で置くと、そこからメインのアカウントがわかってしまうので。ここはもう少し考えます。Plausible deniability も尊重したいんですよね……。
まだできていないこと
- GUI からのハーベスト(コマンドラインならできる)
- (GUI が書く YAML をこっそりいじればできそう……と思ったら、起動のたびに書き直されて消えました。大声では言わないでおこーっと)
- ウォレットが手元のノードに問い合わせること(今の残高と送金は、まだ設定の REST を通す)
- catapult との速さ比較
- Symbol のノード好きな人がやってくれる気もする
- 「Wi-Fi のときだけ」「充電中だけ」動かす設定
試験的なので、中身も見た目も断りなく変わります。気分次第って素晴らしい。でも、動きます。よかったら触ってみてください。
