I dag kan konsensusklienter ikke nemt levere individuelle stykker data fra BeaconState sammen med de beviser, der er nødvendige for at verificere dem. Ethereums Light Client-system definerer nogle bevisveje, men der er ingen universel eller standard måde for klienter at generere eller servicere disse beviser. Det er ikke realistisk, at staten er omkring , som er for stor til at sende over netværket hurtigt og lægger unødvendig belastning på både knuden og brugeren. specifikationen advarer endda om, at de debug-endpoint, der bruges til at hente fulde tilstande, kun er beregnet til diagnostik, ikke real-world brug. Beaconstater Løbevej 12,145,344 271 MB En meget bedre løsning er at bruge Dette er især nyttigt, fordi størstedelen af statstørrelsen kommer fra validatorer (~232 MB) og balancer (~15 MB); resten af felterne er omkring ~24 MB. Hvis en bruger kun har brug for et lille felt, er det spildt at downloade hele 271 MB-staten. Merkle proofs or multiproofs På grund af dette har vi brug for en generel og standardiseret måde for kunder at anmode Dette reducerer båndbredden, reducerer CPU-belastningen og erstatter dagens spredte og brugerdefinerede implementeringer (f.eks. Særlig behandling af ) af kun de data, de har brug for, af Nimbus historical_summaries Dette arbejde er også vigtigt for fremtiden for Ethereum. SSZ bliver mere central til protokollen: foreslår at erstatte RLP med SSZ, og den kommende (Også kaldet den vil Så opbygning af en ren, effektiv og standard metode til evidensbaseret dataadgang er et vigtigt skridt mod fremtidige protokolopgraderinger. Hvidløg (EIP-7919) beam chain lean chain Leverage Proposed Solution: Introducing the SSZ Query Language (SSZ-QL) Proposed Solution: Introducing the SSZ Query Language (SSZ-QL) Ideen om SSZ-QL blev oprindeligt foreslået af Hans hovedspørgsmål var simpelt, men kraftfuldt: af Etan Kissling “What if we had a standard way to request SSZ field — together with a Merkle proof — directly from any consensus client?” any Enhver I dag tilbyder konsensusklienter ikke en generel eller standardiseret metode til at anmode om specifikke SSZ-data med beviser. ), men der er ingen ordentlig, universel SSZ forespørgselssprog til rådighed - og måske intet klar på det tidspunkt denne idé blev skrevet. Web3underskriver Etans forslag beskriver, hvad et SSZ-forespørgselssprog skal tillade: Anmodning om et undertræ inde i et SSZ-objekt Vælg, om et felt skal udvides fuldt ud eller returneres kun som hash_tree_root Filtrering (f.eks. at finde en transaktion med en bestemt rod) Brug af back-referencer (f.eks. at hente kvitteringen på samme indeks som en matchende transaktion) Angivelse af, hvor beviset skal forankres Støtte for fremtidig kompatibilitet, så kunder sikkert kan ignorere ukendte fremtidige felter Denne type API kan bruges af både konsensus- og eksekveringsklienter med forhåndskompatible SSZ-typer (såsom dem fra ), anmodning og respons strukturer kan endda genereres automatisk. EØS-7495 På baggrund af denne idé, , der udvikler dette som en del af deres EPF-projekt i prysm, er at tilføje et nyt Beacon API-endpoint, der understøtter SSZ Query Language (SSZ-QL). Dette endpoint giver brugerne mulighed for at hente præcis de SSZ-data, de har brug for - ikke mere, ikke mindre - sammen med en Merkle-bevis, der bekræfter dens korrekthed. the proposed solution by and Jun er af Fernando Jun er af Fernando Denne udvidede version vil understøtte avancerede funktioner som filtrering, anmodning om dataområder og valg af brugerdefinerede ankerpunkter, alt sammen med Merkle-beviser inkluderet. Forstå generaliserede indekser (GI) før du dykker ind i SSZ-QL I SSZ, alle objekter – herunder hele er repræsenteret som a . A er blot et tal, der unikt identificerer inde i træet. BeaconState binary Merkle tree generalized index (GI) any node Reglerne er meget enkle: Root node har generaliseret indeks:GI = 1 For ethvert knudepunkt med indeks i:venstre barn = 2*i,højre barn = 2*i + 1 Så hele træet er nummereret som: GI:1 / \ GI:2 GI:3 / \ / \ GI:4 GI:5 GI:6 GI:7 ... Denne nummerering gør Merkle-beviser nemme. , du ved præcis, hvor det sidder i træet, og hvilke søskende hashes skal medtages for at verificere det. 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 Der er 21 felter på øverste niveau (indekseret 0..20). For at placere disse i et Merkle-træ, pads SSZ dem op til den næste kraft på to (32). 32 blade → dybde = 5. Top-niveau blade besætter GI området: 32 ... 63 Vi beregner GI for et topniveau felt ved hjælp af: af formlen: GI_top = 2^depth + field_index for , feltindeks = .validators 11 Og så: GI_validators = 2^5 + 11 = 32 + 11 = 43. Det vil sige ( ) er den fulde forpligtelse af hele inside the global af træ. 43 validator’s subtree BeaconState Multi-Level Proof: Example With validators[42].withdrawal_credentials Lad os sige, at vi vil have et bevis for: BeaconState.validators[42].withdrawal_credentials Dette kræver : 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 Forstå SSZ serialisering før beregning generaliserede indekser For at beregne en korrekt Først og fremmest skal du forstå, hvordan og Forskellige typer data. Generaliserede indekser eksisterer ikke isoleret – de er afledt af , og træets form afhænger helt af, hvordan SSZ fortolker de underliggende Go-strukturfelter. generalized index Serialiserer Merklæder Formen af Merkle træet I SSZ kan hvert felt kun være en af to kategorier: 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 For at beregne et generaliseret indeks for et hvilket som helst felt, skal vi først forstå af objektet: SSZ structure hvilke områder der findes, om hvert felt er en liste eller en vektor, hvor mange stykker hvert felt besætter, og hvordan nestede typer skal krydses. Det er præcis, hvad den funktion gør i Prysm, beliggende på 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 Det er den funktion, der og tal ud Det er. det er en - Det gør afhænger af de faktiske runtime-værdier, kun på Go-typen og struktur-tags. analyzeType examines a Go value using reflection what kind of SSZ type Pure type-analyse trin not Når du giver det et felt eller struktur, det: Tjek Go-typen (uint, struct, slice, pointer osv.) Læser SSZ-relaterede struktur tags som ssz-size og ssz-max 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 Tænk på Som den funktion, der og producerer a For denne type. analyzeType scans the type definition static SSZ layout blueprint What PopulateVariableLengthInfo Does Mens Undersøgelser af de , kan nogle SSZ-objekter ikke beskrives fuldt ud uden . analyzeType Typisk Faktisk værdi Eksempler på: Lister ([]T) skal kende deres nuværende længde Variable container felter har brug for deres faktiske offset Nested lists need each element’s actual size Udfyld denne manglende information. PopulateVariableLengthInfo Det er: Ser på SszInfo blueprint skabt af analyzeType Ser på den faktiske værdi af objektet passeret 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 Den behandler alt f.eks. en container med en liste, der indeholder strukturer med lister, vil alle blive udfyldt. recursively Tænk på Som den funktion, der og udfylder de faktiske målinger baseret på den faktiske værdi, du passerer. PopulateVariableLengthInfo takes the blueprint from analyzeType Example: Lad os teste denne funktion med en passerende BeaconState struktur 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()) } Udgang af: 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) I SSZ-analysatorens output, den vist for hvert felt repræsenterer den nøjagtige byteposition, hvor det felt begynder, når hele strukturen serialiseres i henhold til SSZ-reglerne. , stramt pakket en efter en, og offset fortæller dig, hvor hvert af disse felter starter inden for den pakkede bytestrøm. Om Feltet er en 32-byte fast størrelse værdi, og dens serialiserede byte starter på position i den SSZ-kodede byte array. angiver, hvor mange byte feltet bidrager til den serialiserede output (32 byte i dette tilfælde). For faste størrelser er størrelsen forudbestemt, mens for variable størrelser beregner analysatoren størrelsen baseret på den faktiske værdi. Sammen viser offset og størrelse præcis, hvordan SSZ-layoutet er organiseret i hukommelsen, når strukturen serialiseres. 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 Eksempel: Find Merkle bladet for et felt ved hjælp af offset Lad os tage et reelt felt fra 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) Vi vil gerne bevise området: fork.epoch Den “fork” feltet i Begynder på i den serialiserede byte stream. BeaconState offset 48 Indenfor , den Feltet starter på (Hvad angår begyndelsen af Fork) fork epoch offset 8 Og så: absolute_offset = base_offset_of_fork + offset_of_epoch_inside_fork absolute_offset = 48 + 8 = 56 bytes begynder ved byte 56 af den fulde serialiserede BeaconState. fork.epoch SSZ opdeler serialisering i : 32-byte chunks Chunk 0 → byte 0–31 Chunk 1 → byte 32–63 Chunk 2 → bytes 64–95 … Find ud af, hvilken byte der er indeholdt : 56 chunk_index = floor(56 / 32) = 1 Og så: Bladet, der indeholder Det er Leaf / Chunk 1. fork.epoch er en integer fork.epoch 8-byte Inden for del 1 (byte 32-63): local_offset = 56 - 32 = 24 Så inde i 32-byte bladet ser byterne ud som: [ 0 … 23 ] → unrelated fields [ 24 … 31 ] → fork.epoch (8 bytes) To prove this value, you: Tag chunk 1 – dette er dit blad. 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. Fortsæt, indtil du når toppen Merkle rod. De indsamlede søskende hashes danner din: Det er SSZ Merkle proof branch for fork.epoch Anyone can verify this by recomputing: hash_tree_root(leaf + all_siblings) == state_root Dette introducerer to nye slutpunkter, der afslører den oprindelige version af af Prysm: SSZ Query Language (SSZ-QL) /prysm/v1/beacon/states/{state_id}/query /prysm/v1/beacon/blocks/{block_id}/query Begge endpoints følger SSZ-QL-endpoint-specifikationen og giver klienter mulighed for at anmode om specifikke felter inde i en BeaconState eller BeaconBlock ved hjælp af en forespørgselsstreng. Serveren returnerer det anmodede SSZ-felt kodet som rå SSZ-byte. Flag ignoreres – PR returnerer altid svar uden Merkle-beviser. include_proof Anmodningsstrukturen er: type SSZQueryRequest struct { Query string `json:"query"` IncludeProof bool `json:"include_proof,omitempty"` } Og begge slutpunkter returnerer et SSZ-kodet svar af denne form: 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 } For den fulde specifikation og eksempler, kan du henvise til dette Link til og oplysninger fra SSZ-analysatoren, i stedet for at bruge et generaliseret indeks. For now, the implementation locates the requested field using the computed offset size For mere information kan du se Jun Songs arbejde – implementeret sammen med Fernando som en del af deres EPF-projekt i prysm. For more information, you can check out ’s work — implemented together with as part of their EPF project in prysm. Jun Song Fernando af Jun Song af Fernando