今日、コンセンサスクライアントは、これらを検証するために必要な証拠とともに、BeaconStateからの個々のデータを簡単に提供することはできません。EthereumのLight Clientシステムはいくつかの証拠パスを定義しますが、クライアントがこれらの証拠を生成またはサービスするための普遍的または標準的な方法はありません。 現実主義ではない――国は 周りは ネットワークを迅速に送信するには大きすぎて、ノードとユーザーの両方に不必要な負荷を負わせます。このスペクションは、完全な状態を取得するために使用されるデバッグエンドポイントが診断のためだけではなく、現実世界での使用のためであることを警告します。 ビーコン トップページ > 12145344 271 MB より良い方法は、使用することです。 これは特に有用であるため、ほとんどの状態サイズは、検証者(~232 MB)とバランス(~15 MB)から来ていますが、その他のフィールドは約24 MBです。ユーザーが1つの小さなフィールドを必要とする場合、271 MBの状態全体をダウンロードすることは無駄です。 Merkle proofs or multiproofs そのため、顧客が要求する一般的かつ標準化された方法が必要です。 これは、帯域幅を減らし、CPUの負荷を減らし、現在の分散型およびカスタマイズされた実装を置き換える(例えば、 特別扱い ( ) 必要なデータだけは、 NIMBUSの historical_summaries この作業はまた、Ethereumの未来にとっても重要です. SSZはプロトコルにますます中心的になっています: RLPをSSZに置き換えることを提案し、次期の (こちらも呼ばれる) ウィル したがって、証拠ベースのデータアクセスのためのクリーンで効率的で標準的な方法を構築することは、将来のプロトコルアップグレードに向けた重要なステップです。 ピンク(EIP-7919) beam chain lean chain リベラル Proposed Solution: Introducing the SSZ Query Language (SSZ-QL) SSZ クエリ言語(SSZ-QL)の導入 SSZ-QLのアイデアは当初、 彼の主な質問は、単純で強力なものでした。 エタン・キスリング “What if we had a standard way to request SSZ field — together with a Merkle proof — directly from any consensus client?” any どんな 今日、コンセンサスクライアントは、特定のSSZデータを証拠として要求する一般的または標準化された方法を提供していません。 ), しかし、適切な、普遍的なSSZクエリ言語が利用できず、このアイデアが書かれた時点で準備ができていなかった。 Web3サイン Etan の提案では、SSZ クエリ 言語が何を許可すべきかを説明しています。 SSZ オブジェクト内のサブツリーを要求する フィールドを完全に拡張するか、ハッシュ_tree_root としてのみ返すかを選択する フィルタリング(例えば、特定のルートを持つ取引を見つける) バックリファレンスを使用する(例えば、一致する取引と同一のインデックスで受信を取得する) 証拠がどこに根付くべきかを指定する 顧客が未知の将来フィールドを無視できるように、前向きな互換性をサポート この種類の API は、コンセンサス クライアントと実行クライアントの両方で使用できます。 )、リクエストと応答構造は、自動的に生成することもできます。 エイプ7495 このアイデアに基づいて、 このエンドポイントは、SSZ Query Language (SSZ-QL) をサポートする新しい Beacon API エンドポイントを追加しています。このエンドポイントは、ユーザーが必要な SSZ データを正確に取得することを可能にします - 少なくともそれ以上ではなく - メルクル証拠とともに、その正確性を検証します。 the proposed solution by and ジュン フェルナンド ジュン フェルナンド この最小限のバージョンに加えて、この拡張版は、フィルタリング、データ範囲の要求、カスタムアンカーポイントの選択などの高度な機能をサポートし、すべて Merkle の証拠を含む予定です。 SSZ-QL に潜入する前に一般化インデックス (GI) を理解する SSZ では、すべてのオブジェクト(全体を含む) A として代表される。 . A 単純に識別する数字です。 この木の内側 BeaconState binary Merkle tree generalized index (GI) any node ルールはとてもシンプルです: ルートノードは一般化指数:GI = 1 インデックス i:left child = 2*i,right child = 2*i + 1 したがって、木全体は次のように数えられています。 GI:1 / \ GI:2 GI:3 / \ / \ GI:4 GI:5 GI:6 GI:7 ... この数字は、Merkelの証拠を容易にします。 , あなたはそれが木に座っている場所を正確に知っており、それを検証するためにどの兄弟ハッシュを含めなければなりません。 generalized index of a leaf Example with Beacon State: 0 GenesisTime string 1 GenesisValidatorsRoot string 2 Slot string 3 Fork *Fork 4 LatestBlockHeader *BeaconBlockHeader 5 BlockRoots []string 6 StateRoots []string 7 HistoricalRoots []string 8 Eth1Data *Eth1Data 9 Eth1DataVotes []*Eth1Data 10 Eth1DepositIndex string 11 Validators []*Validator ← (p = 11) 12 Balances []string 13 RandaoMixes []string 14 Slashings []string 15 PreviousEpochAttestations []*pendingAttestation 16 CurrentEpochAttestations []*pedningAttestation 17 JustificationBits string 18 PreviousJustifiedCheckpoint *Checkpoint 19 CurrentJustifiedCheckpoint *Checkpoint 20 FinalizedCheckpoint *Checkpoint There are 21 top-level fields (indexed 0..20). To place these into a Merkle tree, SSZ pads them up to the next power of two (32). 32 葉 → 深さ = 5 トップレベルの葉はGI範囲を占める: 32 ... 63 トップレベルのフィールドの GI を計算する: 公式: GI_top = 2^depth + field_index のために フィールド インデックス = .validators 11 だから: GI_validators = 2^5 + 11 = 32 + 11 = 43. イギリス( )は、全体の約束の葉です。 グローバル内部 木です。 43 validator’s subtree BeaconState Multi-Level Proof: Example With validators[42].withdrawal_credentials 次に、我々は証拠を求めていると仮定します: BeaconState.validators[42].withdrawal_credentials この要求 : two levels of proof Prove that the entire validator’s subtree is included in the BeaconState root We already know: Top-level GI for validators = 43 Using GI 43, the consensus client collects the sibling hashes on the path from leaf 43 up to root (e.g., ). GI 43 → 21 → 10 → 5 → 2 → 1 This gives the proof: validators_root ---> BeaconState_root Prove that is inside the validator’s subtree validator[42].withdrawal_credentials Now treat the . validators list as its own Merkle tree Inside this subtree: Validator is the 42-nd element → it maps to some leaf index (e.g. chunk ) inside this subtree. 42 k Withdrawal credentials lives inside one of the 32-byte SSZ chunks of validator #42 (for example chunk — number doesn’t matter, just concept). k = 128 We now generate: leaf (withdrawal_credentials chunk) ---> validators_root by collecting sibling hashes inside the local validator-subtree. Final Combined Proof You end up with: 1. Local Level Proof Proves withdrawal_credentials --> validator_root 2. Top-level branch proof Proves validator_root --> BeaconState_root A verifier can now reconstruct the BeaconState root from only: the requested leaf the two lists of sibling nodes the known BeaconState root No full state download needed. ┌───────────────────────────────┐ │ BeaconState Root │ └───────────────────────────────┘ ▲ │ (Top-level Merkle Proof) │ Sibling hashes for GI = 43 │ ┌─────────────────────────────────────────┐ │ validators_root (GI = 43) │ └─────────────────────────────────────────┘ ▲ │ (Local Subtree Proof) │ Proof inside validators list │ for index = 42 │ ┌─────────────────────────────────────────────────────────┐ │ Validator[42] Subtree (list element #42) │ └─────────────────────────────────────────────────────────┘ ▲ │ (Field-level Merkle Proof) │ Sibling hashes inside the │ validator struct │ ┌──────────────────────────────────────────┐ │ validator[42].withdrawal_credentials │ ← requested field └──────────────────────────────────────────┘ Understanding SSZ Serialization Before Computing Generalized Indices SSZ serialization before computing generalized indexes(一般化指数を計算する前にSSZ serializationを理解する) 正しく計算するには まず、SSZの仕方を知る必要があります。 そして different data types. Generalized indexes do not exist in isolation - they are derived from the , and the shape of the tree depends entirely on how SSZ interprets the underlying Go struct fields. 木の形状は、SSZがベースのGo構造フィールドをどのように解釈するかに完全に依存します。 generalized index シリアル マーケティング Merkle Treeの形 SSZ では、各フィールドは 2 つのカテゴリのうちの 1 つしかありません。 Base Types (fixed-size values) , , , etc. These are straightforward — they always serialize into a fixed number of bytes. uint64 Bytes32 Bytes20 uint256 Composite Types (like BeaconState), (fixed length), (variable length), , And each of them is serialized in a slightly different way. Container Vector[T, N] List[T, N] Bitvector[N] Bitlist[N] To compute a for any field inside a state, the SSZ tree must first know . This is why the generated files include tags such as: generalized index (g-index) how that field is serialized *.pb.go ssz-size:"8192,32" → Vector ssz-max:"16" → List ssz-size:"?,32" → List of Vector 任意のフィールドの一般的なインデックスを計算するには、まず、 オブジェクトについて: SSZ structure どんな領域があるのか、 各フィールドがリストまたはベクターであるかどうかは、 それぞれのフィールドがいくつ占めているか、 どのように横断されるべきか。 まさにこれが、The function does in Prysm, located at AnalyzeObject encoding/ssz/query/analyzer.go // AnalyzeObject analyzes given object and returns its SSZ information. func AnalyzeObject(obj SSZObject) (*SszInfo, error) { value := reflect.ValueOf(obj) info, err := analyzeType(value, nil) if err != nil { return nil, fmt.Errorf("could not analyze type %s: %w", value.Type().Name(), err) } // Populate variable-length information using the actual value. err = PopulateVariableLengthInfo(info, value) if err != nil { return nil, fmt.Errorf("could not populate variable length info for type %s: %w", value.Type().Name(), err) } return info, nil } What analyzeType Does という機能は、 and figures out A. A. A. それは ♪It Does 実際のランタイム値に依存し、Go タイプと struct タグにのみ依存します。 analyzeType examines a Go value using reflection what kind of SSZ type pure type-analysis step not あなたがそれにフィールドまたは構造を与えるとき、それは: Go type (uint, struct, slice, pointer, etc.) をチェックします。 ssz-size や ssz-max などの SSZ 関連の struct タグを読み取ります。 Decides : whether this field is a basic SSZ type ( , , ) uint64 uint32 bool a Vector ( ) ssz-size:"N" a List ( ) ssz-max:"N" a Bitvector / Bitlist a Container (struct) Builds an that describes: SszInfo record the SSZ type (List, Vector, Container...) whether it is fixed-sized or variable-sized offsets of fields (for Containers) nested SSZ information for child fields 考える という機能として、 生産A このタイプのために analyzeType scans the type definition static SSZ layout blueprint What PopulateVariableLengthInfo Does たとえ 研究 The いくつかのSSZオブジェクトは、そのオブジェクトなしでは完全に記述できない。 . analyzeType タイプ actual value 例 : リスト([]T)は現在の長さを知る必要があります。 Variable-sized container fields need their actual offset Nested lists need each element’s actual size この欠けているランタイム情報を記入します。 PopulateVariableLengthInfo IT : analyzeType によって作成された SszInfo ブループレートを見る 過去のオブジェクトの実際の値を見る Computes values that can only be known at runtime: length of Lists sizes of nested variable elements offsets of variable-sized fields inside Containers bitlist length from bytes すべてを処理する たとえば、リストを含む構造を含むリストを含むコンテナがすべて埋め込まれる。 recursively 考える という機能として、 そして、あなたが渡す実際の値に基づいて実際の測定を記入します。 PopulateVariableLengthInfo takes the blueprint from analyzeType Example: この機能を通過する BeaconState 構造でテストしましょう。 type BeaconState struct { state protoimpl.MessageState `protogen:"open.v1"` GenesisTime uint64 `protobuf:"varint,1001,opt,name=genesis_time,json=genesisTime,proto3" json:"genesis_time,omitempty"` GenesisValidatorsRoot []byte `protobuf:"bytes,1002,opt,name=genesis_validators_root,json=genesisValidatorsRoot,proto3" json:"genesis_validators_root,omitempty" ssz-size:"32"` Slot github_com_OffchainLabs_prysm_v7_consensus_types_primitives.Slot `protobuf:"varint,1003,opt,name=slot,proto3" json:"slot,omitempty" cast-type:"github.com/OffchainLabs/prysm/v7/consensus-types/primitives.Slot"` Fork *Fork `protobuf:"bytes,1004,opt,name=fork,proto3" json:"fork,omitempty"` LatestBlockHeader *BeaconBlockHeader `protobuf:"bytes,2001,opt,name=latest_block_header,json=latestBlockHeader,proto3" json:"latest_block_header,omitempty"` BlockRoots [][]byte `protobuf:"bytes,2002,rep,name=block_roots,json=blockRoots,proto3" json:"block_roots,omitempty" ssz-size:"8192,32"` StateRoots [][]byte `protobuf:"bytes,2003,rep,name=state_roots,json=stateRoots,proto3" json:"state_roots,omitempty" ssz-size:"8192,32"` HistoricalRoots [][]byte `protobuf:"bytes,2004,rep,name=historical_roots,json=historicalRoots,proto3" json:"historical_roots,omitempty" ssz-max:"16777216" ssz-size:"?,32"` Eth1Data *Eth1Data `protobuf:"bytes,3001,opt,name=eth1_data,json=eth1Data,proto3" json:"eth1_data,omitempty"` Eth1DataVotes []*Eth1Data `protobuf:"bytes,3002,rep,name=eth1_data_votes,json=eth1DataVotes,proto3" json:"eth1_data_votes,omitempty" ssz-max:"2048"` Eth1DepositIndex uint64 `protobuf:"varint,3003,opt,name=eth1_deposit_index,json=eth1DepositIndex,proto3" json:"eth1_deposit_index,omitempty"` Validators []*Validator `protobuf:"bytes,4001,rep,name=validators,proto3" json:"validators,omitempty" ssz-max:"1099511627776"` Balances []uint64 `protobuf:"varint,4002,rep,packed,name=balances,proto3" json:"balances,omitempty" ssz-max:"1099511627776"` RandaoMixes [][]byte `protobuf:"bytes,5001,rep,name=randao_mixes,json=randaoMixes,proto3" json:"randao_mixes,omitempty" ssz-size:"65536,32"` Slashings []uint64 `protobuf:"varint,6001,rep,packed,name=slashings,proto3" json:"slashings,omitempty" ssz-size:"8192"` PreviousEpochAttestations []*PendingAttestation `protobuf:"bytes,7001,rep,name=previous_epoch_attestations,json=previousEpochAttestations,proto3" json:"previous_epoch_attestations,omitempty" ssz-max:"4096"` CurrentEpochAttestations []*PendingAttestation `protobuf:"bytes,7002,rep,name=current_epoch_attestations,json=currentEpochAttestations,proto3" json:"current_epoch_attestations,omitempty" ssz-max:"4096"` JustificationBits github_com_OffchainLabs_go_bitfield.Bitvector4 `protobuf:"bytes,8001,opt,name=justification_bits,json=justificationBits,proto3" json:"justification_bits,omitempty" cast-type:"github.com/OffchainLabs/go-bitfield.Bitvector4" ssz-size:"1"` PreviousJustifiedCheckpoint *Checkpoint `protobuf:"bytes,8002,opt,name=previous_justified_checkpoint,json=previousJustifiedCheckpoint,proto3" json:"previous_justified_checkpoint,omitempty"` CurrentJustifiedCheckpoint *Checkpoint `protobuf:"bytes,8003,opt,name=current_justified_checkpoint,json=currentJustifiedCheckpoint,proto3" json:"current_justified_checkpoint,omitempty"` FinalizedCheckpoint *Checkpoint `protobuf:"bytes,8004,opt,name=finalized_checkpoint,json=finalizedCheckpoint,proto3" json:"finalized_checkpoint,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } package main import ( "fmt" "github.com/OffchainLabs/prysm/v7/encoding/ssz/query" eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" ) func main() { v := &eth.BeaconState{} // Analyze it with Prysm’s existing SSZ analyzer info, _ := query.AnalyzeObject(v) fmt.Println(info.Print()) } 出力: BeaconState (Variable-size / size: 2687377) ├─ genesis_time (offset: 0) uint64 (Fixed-size / size: 8) ├─ genesis_validators_root (offset: 8) Bytes32 (Fixed-size / size: 32) ├─ slot (offset: 40) Slot (Fixed-size / size: 8) ├─ fork (offset: 48) Fork (Fixed-size / size: 16) │ ├─ previous_version (offset: 0) Bytes4 (Fixed-size / size: 4) │ ├─ current_version (offset: 4) Bytes4 (Fixed-size / size: 4) │ └─ epoch (offset: 8) Epoch (Fixed-size / size: 8) ├─ latest_block_header (offset: 64) BeaconBlockHeader (Fixed-size / size: 112) │ ├─ slot (offset: 0) Slot (Fixed-size / size: 8) │ ├─ proposer_index (offset: 8) ValidatorIndex (Fixed-size / size: 8) │ ├─ parent_root (offset: 16) Bytes32 (Fixed-size / size: 32) │ ├─ state_root (offset: 48) Bytes32 (Fixed-size / size: 32) │ └─ body_root (offset: 80) Bytes32 (Fixed-size / size: 32) ├─ block_roots (offset: 176) Vector[Bytes32, 8192] (Fixed-size / size: 262144) ├─ state_roots (offset: 262320) Vector[Bytes32, 8192] (Fixed-size / size: 262144) ├─ historical_roots (offset: 2687377) List[Bytes32, 16777216] (Variable-size / length: 0, size: 0) ├─ eth1_data (offset: 524468) Eth1Data (Fixed-size / size: 72) │ ├─ deposit_root (offset: 0) Bytes32 (Fixed-size / size: 32) │ ├─ deposit_count (offset: 32) uint64 (Fixed-size / size: 8) │ └─ block_hash (offset: 40) Bytes32 (Fixed-size / size: 32) ├─ eth1_data_votes (offset: 2687377) List[Eth1Data, 2048] (Variable-size / length: 0, size: 0) ├─ eth1_deposit_index (offset: 524544) uint64 (Fixed-size / size: 8) ├─ validators (offset: 2687377) List[Validator, 1099511627776] (Variable-size / length: 0, size: 0) ├─ balances (offset: 2687377) List[uint64, 1099511627776] (Variable-size / length: 0, size: 0) ├─ randao_mixes (offset: 524560) Vector[Bytes32, 65536] (Fixed-size / size: 2097152) ├─ slashings (offset: 2621712) Vector[uint64, 8192] (Fixed-size / size: 65536) ├─ previous_epoch_attestations (offset: 2687377) List[PendingAttestation, 4096] (Variable-size / length: 0, size: 0) ├─ current_epoch_attestations (offset: 2687377) List[PendingAttestation, 4096] (Variable-size / length: 0, size: 0) ├─ justification_bits (offset: 2687256) Bitvector[8] (Fixed-size / size: 1) ├─ previous_justified_checkpoint (offset: 2687257) Checkpoint (Fixed-size / size: 40) │ ├─ epoch (offset: 0) Epoch (Fixed-size / size: 8) │ └─ root (offset: 8) Bytes32 (Fixed-size / size: 32) ├─ current_justified_checkpoint (offset: 2687297) Checkpoint (Fixed-size / size: 40) │ ├─ epoch (offset: 0) Epoch (Fixed-size / size: 8) │ └─ root (offset: 8) Bytes32 (Fixed-size / size: 32) └─ finalized_checkpoint (offset: 2687337) Checkpoint (Fixed-size / size: 40) ├─ epoch (offset: 0) Epoch (Fixed-size / size: 8) └─ root (offset: 8) Bytes32 (Fixed-size / size: 32) SSZ analyzer の出力では、 表示される各フィールドは、そのフィールドが SSZ ルールに従って構造全体を序列化したときに始まる正確なバイト位置を表します。 , は一つずつ密接にパッケージされ、オフセットは、これらのフィールドのそれぞれがそのパッケージされたバイト ストリーム内で始まる場所を表示します。 , フィールド is a 32-byte fixed-size value, and its serialized bytes begin at position. 32-byte fixed-size value, and its serialized bytes begin at position. SSZ-encoded byte array で、 フィールドが連続出力に貢献するバイト数を示す(この場合の32バイト)。固定サイズ型の場合、サイズは事前定義され、変数サイズ型の場合、アナリストは実際の値に基づいてサイズを計算します。 offset fixed-size fields first root (offset: 8) Bytes32 (Fixed-size / size: 32) root 8 size Example: Finding the Merkle Leaf for a Field Using the Offset Example: Finding the Merkle Leaf for a Field Using the Offset Let’s take a real field from the SSZ Analyzer Output: ├─ fork (offset: 48) Fork (Fixed-size / size: 16) │ ├─ previous_version (offset: 0) Bytes4 (Fixed-size / size: 4) │ ├─ current_version (offset: 4) Bytes4 (Fixed-size / size: 4) │ └─ epoch (offset: 8) Epoch (Fixed-size / size: 8) フィールドを証明したい: fork.epoch “fork” フィールド スタート at シリアルなバイトストリーム BeaconState offset 48 内側 , the フィールドスタート at (フォークの始まりについて) fork epoch offset 8 だから: absolute_offset = base_offset_of_fork + offset_of_epoch_inside_fork absolute_offset = 48 + 8 = 56 bytes 56 バイトで始まる、完全にシリアル化された BeaconState。 fork.epoch SSZ は serialization を分割します。 : 32-byte chunks Chunk 0 → バイト 0–31 Chunk 1 → バイト 32–63 Chunk 2 → バイト 64–95 … Now find which chunk contains byte. バイトが含まれている部分 : 56 chunk_index = floor(56 / 32) = 1 だから: The leaf containing Leaf / Chunk 1です。 fork.epoch is an インテル fork.epoch 8-byte Chunk 1 (byte 32-63) 内: local_offset = 56 - 32 = 24 32 バイト表の内側では、バイトは次のようになります。 [ 0 … 23 ] → unrelated fields [ 24 … 31 ] → fork.epoch (8 bytes) To prove this value, you: 第1話「これは君の葉だ」 When hashing up the tree, at each level: If chunk is a left child → record the right sibling hash. If chunk is a right child → record the left sibling hash. メルケル根のトップに到達するまで続ける。 The collected sibling hashes form your: ↓ ↓ SSZ Merkle proof branch for fork.epoch Anyone can verify this by recomputing: hash_tree_root(leaf + all_siblings) == state_root これは、最初のバージョンを明らかにする2つの新しいエンドポイントを導入します。 プレスミス: SSZ Query Language (SSZ-QL) /prysm/v1/beacon/states/{state_id}/query /prysm/v1/beacon/blocks/{block_id}/query どちらのエンドポイントも SSZ-QL エンドポイント仕様に従い、クライアントが BeaconState または BeaconBlock 内の特定のフィールドをクエリ シートを使用してリクエストをリクエストすることを可能にします。サーバは、リクエストごとに単一のクエリをサポートし、現在、この機能はリクエストごとに単一のクエリをサポートしています。 フラッグは無視され、PRは常にメルクルの証拠なしに回答を返します。 include_proof 要請の構造は: type SSZQueryRequest struct { Query string `json:"query"` IncludeProof bool `json:"include_proof,omitempty"` } そして、両方のエンドポイントは、この形式のSSZ暗号化応答を返します。 type SSZQueryResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Root []byte `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty" ssz-size:"32"` Result []byte `protobuf:"bytes,2,opt,name=result,proto3" json:"result,omitempty" ssz-max:"1073741824"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } 詳細な説明と例については、こちらをご覧ください。 リンク そして 一般的なインデックスを使用するのではなく、SSZアナライザーからの情報です。 For now, the implementation locates the requested field using the computed offset size 詳細については、Jun Songの作品をご覧いただけます - フェルナンドと共にEPFプロジェクトの一環として prysmで実施されました。 For more information, you can check out ’s work — implemented together with as part of their EPF project in prysm. Jun Song Fernando Jun Song フェルナンド