UtreexoをサポートするRustで実装された軽量ノード実装Florestaを試してみる。
https://github.com/getfloresta/Floresta
以前書いたように、UtreexoはUTXOセットのアキュムレーターとして機能するので、UTXOセット自体を保持するのではなく、有効なUTXOセットに対するコミットメントのみを保持するようになる。
- このコミットメントはマークルフォレストの各ツリーのルートハッシュのリストになるので、UTXOセット自体を保持するのと比べると劇的にデータ保持量は少なくなる。
- またFloresta自体はブロックは受信するもののその後破棄する
ため、これまでのノード実装と比べるとノードが保持するデータ量がとてもコンパクトになる。
Florestaの実行
先日、0.9.0がリリースされたばかりだったのでそれを利用する。リリースページにはバイナリがなかったので、ビルドする。
$ cargo build --release ... $ ./target/release/florestad --daemon
デフォルトのデータディレクトリは$HOME/.florestaで、変更する場合は--data-dir <パス>で指定する。
IBDが始まりブロックチェーンの同期が行われるが、約1時間20分ほどで同期が終わった。これは以前行ったBitcoin CoreのAssumeUTXOを利用した同期よりも速い。要因としては、
- AssumeValidにより、ブロック939,969までの各トランザクションのスクリプトの署名検証はスキップされる。
- Utreexoの仕組みによってUTXOセット自体をディスクに保存することがないので、ブロックの同期中に発生していたディスクIOがなくなった。
- AssumeUtreexoにより、ブロック939,969までのマークルフォレストのルートハッシュのリストがchainparams.rsにハードコードされており、マークルフォレストの更新はそれ以降のブロックに対して行われる。
- 一応、同期後にジェネシスブロックからハードコードされたブロックまでマークルフォレストを生成して検証が行われる(
--no-backfillオプションをつけて行わなくすることも可能)。
- 一応、同期後にジェネシスブロックからハードコードされたブロックまでマークルフォレストを生成して検証が行われる(
は挙げられる。おそらく2の効果が大きい。
getblockchaininfoを実行すると、root_hashesというフィールドにUTXOのマークルフォレストのルートハッシュのリストが表示される↓
$ ./target/release/floresta-cli getblockchaininfo { "best_block": "000000000000000000003c6c733368d6ff92d012ef3fe11119916cd48193124d", "height": 943984, "ibd": false, "validated": 943984, "latest_work": "00000000000000000000000000000000000000011d844a2abb6b83fb34be0ae8", "latest_block_time": 1775526916, "leaf_count": 3087305105, "root_count": 11, "root_hashes": [ "db5957872e6ef754f4e0bfe106b1d68469052c76808ef2e943abd275daccb6b4", "d97528a6964d45540edae4eff2319150e1cc72d652c920cf5b01a787472e07dc", "f2c17db54572d31e1909518aac369b88c1bb9f5aaac4ca200a6910083ba3dd7a", "ace99d96476e4e7b1575bde8cb7d86edb60990afb0b9e50e69444cd210966c55", "d27bdab690640d5c544cbb7638674df77066be98f098ec7adf63042799408426", "b7226a72fde83486a42bbb1740057e818eb4c042bef9696d18360061b645addd", "f5989c22810edfb0ac4ebbb5914e0270aef9138a6f150cbf359e3c234e3cd8c7", "1bf05999bcec61f320153be3d724dd827eae50bf616113029f91ffbd9d1d7b6e", "63e9ae72f97c34d3ec87ec1108ad0ba9c4c9511ee6bda05c3500a9ccf0d92d59", "fef9fec3a23b4cc9833ef4e36ae3011629e20074a6e4ea6b7750906a48848b25", "2da3e45cc5b725d4948866168a2c4c4fcef046d4751f1c2dd0de4317387d5c98" ], "chain": "bitcoin", "progress": 1.0, "difficulty": 138966872071213 }
データディレクトリ
Florestaのデータディレクトリの内容は(容量はブロック943984時点のmainnetのデータ)、まずディレクトリ直下が
| ファイル | サイズ | 内容 |
|---|---|---|
| cfilters | 12GB | BIP158コンパクトブロックフィルター(ウォレットスキャン用) |
| cfilters-index | 152B | フィルターの位置インデックス |
| db | 2.8KB | 監視専用ウォレットのKey/Valueストア |
| peers.json | 6.8MB | 既知のピアアドレス一覧 |
| conf | 62B | 設定ファイル |
他にデバッグログや、pidファイルがある。
続いて、chainstateディレクトリ内が
| ファイル | サイズ | 内容 |
|---|---|---|
| headers.bin | 2GB*1 | ブロックヘッダー関連のデータ(124バイト×約94万ブロック) |
| blocks_index.bin | 64MB | ブロックハッシュから高さを引くためのインデックス |
| accumulators.bin | 2.4MB*2 | Utreexoアキュムレーター状態(各ブロック毎に、マークルフォレストのリーフの数とルートハッシュのリストを保持。) |
| fork_headers.bin | 2MB | フォークチェーンのヘッダー(再編成時に使用) |
| metadata.bin | 2.2KB | チェーンの状態(ベストブロックや検証済み高さなど) |
コンパクトフィルター以外のデータの使用量はわずかなので、現状のmainnetでもディスク使用量は13GBまでいかない(後述するウォレットの連携をしないのであれば--no-cfiltersでこの部分は削減できる。また、--filters-start-heightを使って開始ブロック高を指定すれば過去の不要なインデックス分スペースを節約できる)。直近のフィルターのみを保持するようにすればデータサイズは数百MBで済む。
ちなみにgetblock RPCを実行するとトランザクションのリストも返って来て、あれブロック保持していないのに?と不思議に思ったけど、どうやら都度ネットワークにブロック問い合わせて返すようになってるっぽい。
ブリッジノードとの連携
Utreexoノードは、UTXOセットを保持していないため、新しいブロックを受信した際に、その中に含まれている各トランザクションがインプットで参照しているUTXOが有効なUTXOかどうか単体では確認できない。そのUTXOがマークルフォレストに含まれていることを証明するための包含証明を必要とする。この証明を提供してくれるのが、ブリッジノードと呼ばれるノードになる。ブリッジノードは(現在BIP-183のドラフトで定義中の)以下のサービスビットを通知している:
NODE_UTREEXO(1 << 12):
新しいブロックやトランザクションの包含証明を提供するブリッジノード(NODE_NETWORK_LIMITEDと組み合わせると直近288 ブロック分、NODE_NETWORKと組み合わせると全ブロック分)NODE_UTREEXO_ARCHIVE(1 << 13):
過去すべてのブロックに対してその包含証明のみを提供するブリッジノード(ブロック自体は提供しない)
Florestaは、DNSシードからこれらのサービスビットを持つノードの情報を入手して、ピアとして接続する。AssumeUtreexoを利用して同期する場合はNODE_UTREEXOノードでよく、利用せずに(--no-assume-utreexoで実行)ジェネシスブロックから同期する場合はNODE_UTREEXO_ARCHIVEノードに接続する必要がある。ブリッジノードと接続したら、BIP-183で新しく導入される2つのP2Pメッセージgetuproofとuproofを使って、ブロックの有効性を検証する。
sequenceDiagram
participant F as Floresta
participant B as ブリッジノード<br/>(UTREEXO)
F->>B: getdata (block_hash)
B->>F: block (生ブロック)
Note over F: 必要な入力を特定<br/>(ローカルキャッシュにないもの)
F->>B: getuproof
B->>F: uproof
Note over F: 1. 証明を検証<br/>2. ブロックを検証<br/>3. Utreexoフォレスト更新<br/>4. ブロック破棄(保存しない)
Txはリレーしない
今後Txもリレーするようにするのかもしれないけど、v0.9.0時点ではハンドシェイク時にrelay = falseを指定してるのでTx自体のリレーはしないようになっている。リレーする場合はブリッジノードとの間でutreexotxメッセージで証明付きのTxを要求するような実装が必要になる。
組み込みElectrumサーバー
Floresta自体にウォレットの機能はなく、Electrum サーバーが組み込まれている。ウォレットを利用したい場合は、別途ElectrumやSparrow などのウォレットアプリをFlorestaのElectrum サーバーに接続するという使い方になる。またその際は、予めウォレットからxpub(拡張公開鍵)をエクスポートして、floresta-cliでimportdescriptorコマンドで拡張公開鍵やディスクリプターをインポートしておく必要がある。Florestaにはwatch-onlyウォレットはあるので、インポートしたディスクリプターから導出したアドレスについてインデックスを構築する。