Днес клиентите с консенсус не могат лесно да предоставят отделни парчета данни от BeaconState заедно с доказателствата, необходими за тяхното потвърждаване. Системата на Light Client на Ethereum дефинира някои пътища за доказване, но няма универсален или стандартен начин за клиентите да генерират или обслужват тези доказателства. Не е реалистично, че държавата е около , което е твърде голямо, за да се изпрати бързо през мрежата и поставя ненужно натоварване както на възела, така и на потребителя. спецификацията дори предупреждава, че крайните точки за премахване, използвани за извличане на пълни състояния, са предназначени само за диагностика, а не за използване в реалния свят. BeaconРедактиране Заглавие: 12,145 344 271 MB Много по-добро решение е да се използва Това е особено полезно, защото по-голямата част от размера на състоянието идва от валидатори (~232 MB) и баланси (~15 MB); останалата част от полетата са около ~24 MB. Ако потребителят се нуждае само от едно малко поле, е излишно да изтеглите цялата 271 MB състояние. Merkle proofs or multiproofs Поради това се нуждаем от общ и стандартизиран начин за клиентите да поискат Това намалява честотната лента, намалява натоварването на процесора и заменя днешните разпръснати и персонализирани реализации (например, Специално третиране на ) на само данните, от които се нуждаят, Нимбус historical_summaries Тази работа е важна и за бъдещето на Ethereum. SSZ става все по-центричен към протокола: предлага замяна на RLP с SSZ, и следващата (нарича се също Ще По този начин изграждането на чист, ефективен и стандартен метод за достъпа до данни, базиран на доказателства, е ключова стъпка към бъдещи надстройки на протокола. Пурът (ЕИП-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 данни с доказателства.Някои ad-hoc решения съществуват (например, основните заявки на Nimbus, използвани от проверяващия ), но няма подходящ, универсален език за заявки SSZ на разположение - и може би нищо не е готово по времето, когато тази идея е написана. Web3Подписване Предложението на Etan описва какво трябва да позволява SSZ Query Language: Запитване на всяко поддърво в обект SSZ Избиране дали дадено поле трябва да бъде напълно разширено или да се връща само като hash_tree_root Филтриране (например намиране на транзакция с определен корен) Използване на обратни препратки (напр. извличане на разписката на същия индекс като съвпадаща транзакция) Определяне на мястото, където трябва да бъде закрепено доказателството Подкрепа за бъдеща съвместимост, така че клиентите да могат безопасно да игнорират неизвестни бъдещи полета Този тип API може да се използва както от клиентите за консенсус, така и от клиентите за изпълнение. , структурата на заявката и отговора може дори да бъде генерирана автоматично. Епизод 7495 Въз основа на тази идея, , които разработват това като част от своя EPF проект в prysm, е да добавят нова Beacon API крайна точка, която поддържа SSZ Query Language (SSZ-QL). Тази крайна точка позволява на потребителите да вземат точно SSZ данните, от които се нуждаят - не повече, не по-малко - заедно с доказване на Merkle, което проверява неговата коректност. първоначалната версия ще предложи минимален, но практичен набор от функции, който вече обхваща повечето реални случаи на употреба. (Спецификацията на проекта на API е достъпна за преглед.) the proposed solution by and Юн Фернандо Юн Фернандо Тази разширена версия ще поддържа усъвършенствани функции като филтриране, искане на диапазони от данни и избор на персонализирани котва точки, всички с Merkle доказателства включени. Разбиране на генерализираните индекси (GI) преди да се потопите в SSZ-QL В SSZ всеки обект – включително цялата Той е представен като а . A е просто число, което уникално идентифицира вътре в това дърво. BeaconState binary Merkle tree generalized index (GI) any node Правилата са много прости: Коренният възел има генерализиран индекс:GI = 1 За всеки възел с индекс i: ляво дете = 2*i, дясно дете = 2*i + 1 Така цялото дърво се брои така: GI:1 / \ GI:2 GI:3 / \ / \ GI:4 GI:5 GI:6 GI:7 ... Това число прави доказателствата на Меркел лесни. , вие знаете точно къде седи в дървото и кои братски хеш трябва да бъдат включени, за да го проверите. 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 Има 21 полета на най-високо ниво (индексирани 0..20). За да ги поставите в Merkle дърво, SSZ ги подрежда до следващата мощност от две (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 сериализация преди изчисляване на генерализирани индекси За да изчислите правилно Първо трябва да разберете как се и Различни видове данни. Обобщените индекси не съществуват изолирано – те са получени от , и формата на дървото зависи изцяло от това как SSZ интерпретира основните полета на структурата Go. generalized index Сериализиране Меркел Формата на дървото Меркел В SSZ всяко поле може да бъде само една от две категории: 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 Кои области съществуват, дали всяко поле е списък или вектор, Колко парчета всяка област заема, и как трябва да се пресичат гнездото типове. Това е точно това, което функцията прави в Prysm, разположен на 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 Това е функцията, която Цифрите излизат Това е.Това е една - Това прави зависи от действителните стойности на времето за изпълнение, само от типа Go и таговете struct. analyzeType examines a Go value using reflection what kind of SSZ type Чист тип-анализ стъпка not Когато му дадете поле или структура, той: Проверява типа Go (уинт, струк, филийка, указател и др.) Чете етикети за структури, свързани с SSZ, като ssz-size и 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 Мислете за Функцията, която Произвежда се а За този тип. analyzeType scans the type definition static SSZ layout blueprint What PopulateVariableLengthInfo Does Докато Проучване на , някои SSZ обекти не могат да бъдат напълно описани без . analyzeType Тип Реална стойност Примери за: Списъците ([]T) трябва да знаят текущата си дължина Контейнерните полета с променлив размер се нуждаят от действителното им компенсиране Вградените списъци се нуждаят от действителния размер на всеки елемент Попълнете тази липсваща информация за времето за изпълнение. PopulateVariableLengthInfo It: Изглежда на SszInfo синоним, създаден от analyzeType Изглежда на действителната стойност на прехвърления обект 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 анализатора, показан за всяко поле представлява точната позиция на байта, където това поле започва, когато цялата структура е сериализирана според правилата на SSZ. , tightly packed one after another, and the offset tells you where each of these fields starts within that packed byte stream. For example, in the line Полето е е 32-битова стойност с фиксиран размер, а нейните сериализирани байтове започват на позиция в SSZ-кодиран байт мащаб. показва колко байта полето допринася за сериализирания изход (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 Пример: Намиране на листа на Меркел за поле, използващо офсета Нека вземем реално поле от изхода на SSZ Analyzer: ├─ 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) We want to prove the field: fork.epoch Полето „Форд“ в Започва в Сериализиран байт поток. BeaconState offset 48 Вътре , на field starts at (По отношение на началото на Fork). 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 divides serialization into : 32-byte chunks Чунк 0 → байтове 0–31 Chunk 1 → байтове 32–63 Chunk 2 → байтове 64–95 … Сега разберете коя част съдържа байт : 56 chunk_index = floor(56 / 32) = 1 Така че: Листата, съдържащи е Лист / Чунк 1. fork.epoch е един интегритет fork.epoch 8-byte В рамките на част 1 (байтове 32–63): local_offset = 56 - 32 = 24 Така че вътре в 32-битовия лист байтовете изглеждат така: [ 0 … 23 ] → unrelated fields [ 24 … 31 ] → fork.epoch (8 bytes) To prove this value, you: Take → this is your leaf. chunk 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. Продължете, докато достигнете върха на корена на Меркел. Събраните братовчедни хаши формират вашия: SSZ Merkle proof branch for fork.epoch Anyone can verify this by recomputing: hash_tree_root(leaf + all_siblings) == state_root Това въвежда две нови крайни точки, които разкриват първоначалната версия на на преден план: 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 За повече информация можете да разгледате работата на Юн Сонг – реализирана заедно с Фернандо като част от техния 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 Юни Песен Фернандо