Skip to main content
コミット済みの状態は、互いに引っ張り合う2つの要求を満たさなければなりません。実行側が求めるのは、あるキーの現在値を高速に、何百万回も取り出すことです。検証側が求めるのは、ある値が特定のバージョン時点でネットワークの言うとおりだったという証明です。ひとつの構造で両方をまかなえば、両方とも中途半端になります。 そこでストレージは問題を別々のストアに分割し、それぞれを自分のアクセスパターンに合わせた形にしています。

3つのストア

コミット済みブロック
レジャーストアトランザクション · 出力 · イベント · ライトセット · アキュムレータ · ブロックメタデータ
リプレイと監査
ステートKVストア現在値と過去の値、16分割でシャーディング、ホットな状態は専用ティア
クエリと実行
ステートMerkleストアバージョン付きスパースMerkleツリーと、置換ノードを追跡するインデックス
検証:証明
値だけが必要な読み取りはツリー走査のコストを払わず、証明は単一キー参照向けに最適化されたストアから組み立て直す必要がありません。
レジャーストアはチェーンの履歴を保持します。トランザクション、その出力と補助データ、イベント、ライトセット、ブロックのメタデータ、アキュムレータです。リプレイの対象はこれです。 ステートKVストアは状態の値を、キーとバージョンでアドレス指定して保持します。実行とクエリが読むのはこれです。16分割でシャーディングされており、1ブロック分の書き込みは1つのインスタンスで競合するのではなく、独立した16のRocksDBインスタンスへ分散します。アクセス頻度の高い状態はさらに専用のティアに置かれるため、活発なマーケットのワーキングセットを、チェーンがこれまで保存してきたすべての中から探し出す必要がありません。 ステートMerkleストアは認証構造を保持します。値を証明可能にするツリーノードと、どのノードが置き換えられたかを追跡するインデックスです。 分割することで、値だけが必要な読み取りはツリー走査のコストを払わずに済み、証明は単一キー参照向けに最適化されたストアから組み立て直す必要がなくなります。

認証

2つのMerkle構造が、それぞれ別の仕事をします。
バージョン付きスパースMerkleツリー
アキュムレータ
対象のキーとその値
兄弟ノードのハッシュ
兄弟ノードのハッシュ
ステートルート、コンセンサスがコミット
対象のトランザクションかイベント
トランザクション · イベントのアキュムレータ含まれた全体を順序どおりコミットする
レジャールート、コンセンサスがコミット
ツリーは値が何であったかを証明し、アキュムレータは何がどの順で起きたかを証明します。両者が揃うことで、「このトランザクションはチェーンのこの位置にある」ことを、チェーンを保持せずに証明できます。
バージョン付きスパースMerkleツリーが状態を認証します。すべてのキーはハッシュで決まる位置を持ち、ツリーの各バージョンは変化しなかったノードを共有します。そのため、1ブロックで1つのキーを書いても増えるのは経路1本であって、ツリー1本ではありません。あるバージョンにおけるキーの証明とは、そのキーの葉からネットワークがコミットしたルートまでの経路のことです。 アキュムレータは順序を認証します。トランザクション用とイベント用の2つがあり、それぞれ、そこまでに含めたすべてを順序どおりにコミットするルートを生成します。「このトランザクションはチェーンのこの位置にある」ことを、チェーンを保持せずに証明できるのはこのおかげです。 両者の役割分担はこうです。ステートツリーは値が何であったかを証明し、アキュムレータは何が、どの順で起きたかを証明します。

投機的状態

ブロックの結果は、コミットされる前から存在します。永続的なツリーへ書き込んでおいて、ブロックがコミットされなければ取り消す、という作りではありません。未コミットの状態は、最後にコミットされたバージョンの上に重ねたメモリ上のスパースMerkleオーバーレイに保持されます。 実行はこのオーバーレイ越しに読み、一貫したビューを見ます。ブロックがコミットされればオーバーレイは実体化されます。コミットされなければオーバーレイは破棄され、永続領域には一切手が付いていません。投機的実行がストレージに残骸を残さないのは、この仕組みによります。

キャッシュ

ツリーノードは2層でキャッシュされます。直近のバージョンをアドレス指定可能に保つバージョン対応キャッシュと、その下にあるLRUキャッシュです。取引チェーンのアクセスパターン(毎ブロック触られる少数のホットキーと、めったに触られないロングテール)は、まさにこの構成が想定している形です。

プルーニング

すべてのバージョンを永久に保持することは、要件ではなく選択です。3つのストアそれぞれに独立したプルーナが走り、いずれも固有の保持ポリシーを持ちます。レジャー用、状態の値用、Merkleノード用です。 Merkle用と状態の値用のプルーナを駆動するのは、データと同時に書き込まれる陳腐化インデックス(stale index)です。あるバージョンがノードや値を置き換えると、置き換えられた側はそのバージョンで陳腐化したものとして記録されます。そのためプルーニングは、ゴミを探し回る処理ではなくインデックスに対するレンジスキャンになります。何がいつ回収可能になるかは、書き手がすでに言い残しているからです。
保持期間は運用者の判断であり、現実に結果が伴います。積極的にプルーニングしたノードは現在の状態を効率よく提供しますが、過去に関するクエリには答えられず、より古い地点から始めるノードへのステートシンクも提供できません。アーカイブノードはすべてを保持し、その分のコストを払います。ノードを運用するを参照してください。

バックアップとリストア

各ストアは、稼働中のノードとは独立にバックアップおよびリストアできます。これによって、ジェネシスからリプレイするのではなく、スナップショットからノードを立ち上げられます。リストアしたノードの状態は、バックアップを信用するのではなく、コミット済みのルートに照らして検証できます。

次に読む

ステートシンク

ノードが履歴のすべてをリプレイせずにチェーンへ追いつく方法です。

ステートモデル

何が保存されているのか、どの表現が権威を持つのかです。

インデクサー

コミット済みの記録から履歴を再構成します。

ノードを運用する

ノードの役割と、運用について問い合わせる方法です。