diff --git a/beacon_chain/beacon_chain_db.nim b/beacon_chain/beacon_chain_db.nim index 54500005d9..fac0cd2f5e 100644 --- a/beacon_chain/beacon_chain_db.nim +++ b/beacon_chain/beacon_chain_db.nim @@ -542,12 +542,22 @@ proc new*(T: type BeaconChainDB, "lc_deneb_headers" else: "", + electraHeaders: + if cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH: + "lc_electra_headers" + else: + "", altairCurrentBranches: "lc_altair_current_branches", + electraCurrentBranches: + if cfg.ELECTRA_FORK_EPOCH != FAR_FUTURE_EPOCH: + "lc_electra_current_branches" + else: + "", altairSyncCommittees: "lc_altair_sync_committees", legacyAltairBestUpdates: "lc_altair_best_updates", bestUpdates: "lc_best_updates", sealedPeriods: "lc_sealed_periods")).expectDb() - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra var blobs : KvStoreRef if cfg.DENEB_FORK_EPOCH != FAR_FUTURE_EPOCH: diff --git a/beacon_chain/beacon_chain_db_light_client.nim b/beacon_chain/beacon_chain_db_light_client.nim index 8c7cdddd20..3ba23b0229 100644 --- a/beacon_chain/beacon_chain_db_light_client.nim +++ b/beacon_chain/beacon_chain_db_light_client.nim @@ -28,13 +28,15 @@ logScope: topics = "lcdata" # - Altair: ~38 KB per `SyncCommitteePeriod` (~1.0 MB per month) # - Capella: ~221 KB per `SyncCommitteePeriod` (~6.0 MB per month) # - Deneb: ~225 KB per `SyncCommitteePeriod` (~6.2 MB per month) +# - Electra: ~249 KB per `SyncCommitteePeriod` (~6.8 MB per month) # -# `lc_altair_current_branches` holds Merkle proofs needed to +# `lc_xxxxx_current_branches` holds Merkle proofs needed to # construct `LightClientBootstrap` objects. # SSZ because this data does not compress well, and because this data # needs to be bundled together with other data to fulfill requests. # Mainnet data size (all columns): # - Altair ... Deneb: ~42 KB per `SyncCommitteePeriod` (~1.1 MB per month) +# - Electra: ~50 KB per `SyncCommitteePeriod` (~1.4 MB per month) # # `lc_altair_sync_committees` contains a copy of finalized sync committees. # They are initially populated from the main DAG (usually a fast state access). @@ -42,7 +44,7 @@ logScope: topics = "lcdata" # SSZ because this data does not compress well, and because this data # needs to be bundled together with other data to fulfill requests. # Mainnet data size (all columns): -# - Altair ... Deneb: ~24 KB per `SyncCommitteePeriod` (~0.7 MB per month) +# - Altair ... Electra: ~24 KB per `SyncCommitteePeriod` (~0.7 MB per month) # # `lc_best_updates` holds full `LightClientUpdate` objects in SSZ form. # These objects are frequently queried in bulk, but there is only one per @@ -59,6 +61,7 @@ logScope: topics = "lcdata" # - Altair: ~25 KB per `SyncCommitteePeriod` (~0.7 MB per month) # - Capella: ~26 KB per `SyncCommitteePeriod` (~0.7 MB per month) # - Deneb: ~26 KB per `SyncCommitteePeriod` (~0.7 MB per month) +# - Electra: ~27 KB per `SyncCommitteePeriod` (~0.7 MB per month) # # `lc_sealed_periods` contains the sync committee periods for which # full light client data was imported. Data for these periods may no longer @@ -73,12 +76,16 @@ logScope: topics = "lcdata" # 600 = 32+20+32+32+256+32+8+8+8+8+4+32+32+32+32+32 # - Deneb: 256*(112+4+616+128+40)/1024*28/1024 # 616 = 32+20+32+32+256+32+8+8+8+8+4+32+32+32+32+32+8+8 +# - Electra: 256*(112+4+712+128+40)/1024*28/1024 +# 712 = 32+20+32+32+256+32+8+8+8+8+4+32+32+32+32+32+8+8+32+32+32 # # Committee branch computations: # - Altair: 256*(5*32+8)/1024*28/1024 +# - Electra: 256*(6*32+8)/1024*28/1024 # # Finality branch computations: # - Altair: 256*(6*32+8)/1024*28/1024 +# - Electra: 256*(7*32+8)/1024*28/1024 # # Committee computations: # - Altair: (24624+8)/1024*28/1024 @@ -91,6 +98,7 @@ logScope: topics = "lcdata" # - Altair: (112+24624+5*32+112+6*32+112+8+9)/1024*28/1024 # - Capella: (4+884+24624+5*32+4+884+6*32+112+8+9)/1024*28/1024 # - Deneb: (4+900+24624+5*32+4+900+6*32+112+8+9)/1024*28/1024 +# - Electra: (4+996+24624+6*32+4+996+7*32+112+8+9)/1024*28/1024 type LightClientHeaderStore = object @@ -98,6 +106,11 @@ type putStmt: SqliteStmt[(array[32, byte], int64, seq[byte]), void] keepFromStmt: SqliteStmt[int64, void] + BranchFork {.pure.} = enum + None = 0, + Altair, + Electra + CurrentSyncCommitteeBranchStore = object containsStmt: SqliteStmt[int64, int64] getStmt: SqliteStmt[int64, seq[byte]] @@ -135,8 +148,8 @@ type ## Eth2Digest -> (Slot, LightClientHeader) ## Cached block headers to support longer retention than block storage. - currentBranches: CurrentSyncCommitteeBranchStore - ## Slot -> altair.CurrentSyncCommitteeBranch + currentBranches: array[BranchFork, CurrentSyncCommitteeBranchStore] + ## Slot -> CurrentSyncCommitteeBranch ## Cached data for creating future `LightClientBootstrap` instances. ## Key is the block slot of which the post state was used to get the data. ## Data stored for all finalized epoch boundary blocks. @@ -234,12 +247,14 @@ func putHeader*[T: ForkyLightClientHeader]( proc initCurrentBranchesStore( backend: SqStoreRef, - name: string): KvResult[CurrentSyncCommitteeBranchStore] = + name, typeName: string): KvResult[CurrentSyncCommitteeBranchStore] = + if name == "": + return ok CurrentSyncCommitteeBranchStore() if not backend.readOnly: ? backend.exec(""" CREATE TABLE IF NOT EXISTS `""" & name & """` ( `slot` INTEGER PRIMARY KEY, -- `Slot` (up through 2^63-1) - `branch` BLOB -- `altair.CurrentSyncCommitteeBranch` (SSZ) + `branch` BLOB -- `""" & typeName & """` (SSZ) ); """) if not ? backend.hasTable(name): @@ -278,40 +293,46 @@ func close(store: var CurrentSyncCommitteeBranchStore) = store.putStmt.disposeSafe() store.keepFromStmt.disposeSafe() -func hasCurrentSyncCommitteeBranch*( +template kind(x: typedesc[altair.CurrentSyncCommitteeBranch]): BranchFork = + BranchFork.Altair + +template kind(x: typedesc[electra.CurrentSyncCommitteeBranch]): BranchFork = + BranchFork.Electra + +func hasCurrentSyncCommitteeBranch*[T: ForkyCurrentSyncCommitteeBranch]( db: LightClientDataDB, slot: Slot): bool = if not slot.isSupportedBySQLite or - distinctBase(db.currentBranches.containsStmt) == nil: + distinctBase(db.currentBranches[T.kind].containsStmt) == nil: return false var exists: int64 - for res in db.currentBranches.containsStmt.exec(slot.int64, exists): + for res in db.currentBranches[T.kind].containsStmt.exec(slot.int64, exists): res.expect("SQL query OK") doAssert exists == 1 return true false -proc getCurrentSyncCommitteeBranch*( - db: LightClientDataDB, slot: Slot): Opt[altair.CurrentSyncCommitteeBranch] = +proc getCurrentSyncCommitteeBranch*[T: ForkyCurrentSyncCommitteeBranch]( + db: LightClientDataDB, slot: Slot): Opt[T] = if not slot.isSupportedBySQLite or - distinctBase(db.currentBranches.getStmt) == nil: - return Opt.none(altair.CurrentSyncCommitteeBranch) + distinctBase(db.currentBranches[T.kind].getStmt) == nil: + return Opt.none(T) var branch: seq[byte] - for res in db.currentBranches.getStmt.exec(slot.int64, branch): + for res in db.currentBranches[T.kind].getStmt.exec(slot.int64, branch): res.expect("SQL query OK") try: - return ok SSZ.decode(branch, altair.CurrentSyncCommitteeBranch) + return ok SSZ.decode(branch, T) except SerializationError as exc: - error "LC data store corrupted", store = "currentBranches", + error "LC data store corrupted", store = "currentBranches", kind = T.kind, slot, exc = exc.msg - return Opt.none(altair.CurrentSyncCommitteeBranch) + return Opt.none(T) -func putCurrentSyncCommitteeBranch*( - db: LightClientDataDB, slot: Slot, - branch: altair.CurrentSyncCommitteeBranch) = +func putCurrentSyncCommitteeBranch*[T: ForkyCurrentSyncCommitteeBranch]( + db: LightClientDataDB, slot: Slot, branch: T) = doAssert not db.backend.readOnly # All `stmt` are non-nil if not slot.isSupportedBySQLite: return - let res = db.currentBranches.putStmt.exec((slot.int64, SSZ.encode(branch))) + let res = db.currentBranches[T.kind].putStmt.exec( + (slot.int64, SSZ.encode(branch))) res.expect("SQL query OK") proc initSyncCommitteesStore( @@ -643,9 +664,11 @@ func keepPeriodsFrom*( let res = db.syncCommittees.keepFromStmt.exec(minPeriod.int64) res.expect("SQL query OK") let minSlot = min(minPeriod.start_slot, int64.high.Slot) - block: - let res = db.currentBranches.keepFromStmt.exec(minSlot.int64) - res.expect("SQL query OK") + for branchFork, store in db.currentBranches: + if branchFork > BranchFork.None and + distinctBase(store.keepFromStmt) != nil: + let res = store.keepFromStmt.exec(minSlot.int64) + res.expect("SQL query OK") for lcDataFork, store in db.headers: if lcDataFork > LightClientDataFork.None and distinctBase(store.keepFromStmt) != nil: @@ -656,7 +679,9 @@ type LightClientDataDBNames* = object altairHeaders*: string capellaHeaders*: string denebHeaders*: string + electraHeaders*: string altairCurrentBranches*: string + electraCurrentBranches*: string altairSyncCommittees*: string legacyAltairBestUpdates*: string bestUpdates*: string @@ -665,7 +690,7 @@ type LightClientDataDBNames* = object proc initLightClientDataDB*( backend: SqStoreRef, names: LightClientDataDBNames): KvResult[LightClientDataDB] = - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra let headers = [ # LightClientDataFork.None @@ -678,10 +703,21 @@ proc initLightClientDataDB*( names.capellaHeaders, "capella.LightClientHeader"), # LightClientDataFork.Deneb ? backend.initHeadersStore( - names.denebHeaders, "deneb.LightClientHeader") + names.denebHeaders, "deneb.LightClientHeader"), + # LightClientDataFork.Electra + ? backend.initHeadersStore( + names.electraHeaders, "electra.LightClientHeader"), + ] + currentBranches = [ + # BranchFork.None + CurrentSyncCommitteeBranchStore(), + # BranchFork.Altair + ? backend.initCurrentBranchesStore( + names.altairCurrentBranches, "altair.CurrentSyncCommitteeBranch"), + # BranchFork.Electra + ? backend.initCurrentBranchesStore( + names.electraCurrentBranches, "electra.CurrentSyncCommitteeBranch"), ] - currentBranches = - ? backend.initCurrentBranchesStore(names.altairCurrentBranches) syncCommittees = ? backend.initSyncCommitteesStore(names.altairSyncCommittees) legacyBestUpdates = @@ -706,7 +742,9 @@ proc close*(db: LightClientDataDB) = for lcDataFork in LightClientDataFork: if lcDataFork > LightClientDataFork.None: db.headers[lcDataFork].close() - db.currentBranches.close() + for branchFork in BranchFork: + if branchFork > BranchFork.None: + db.currentBranches[branchFork].close() db.syncCommittees.close() db.legacyBestUpdates.close() db.bestUpdates.close() diff --git a/beacon_chain/consensus_object_pools/block_pools_types_light_client.nim b/beacon_chain/consensus_object_pools/block_pools_types_light_client.nim index a7b9d8da9d..b8ea3e6386 100644 --- a/beacon_chain/consensus_object_pools/block_pools_types_light_client.nim +++ b/beacon_chain/consensus_object_pools/block_pools_types_light_client.nim @@ -33,11 +33,13 @@ type CachedLightClientData* = object ## Cached data from historical non-finalized states to improve speed when ## creating future `LightClientUpdate` and `LightClientBootstrap` instances. - current_sync_committee_branch*: altair.CurrentSyncCommitteeBranch - next_sync_committee_branch*: altair.NextSyncCommitteeBranch + current_sync_committee_branch*: + LightClientDataFork.high.CurrentSyncCommitteeBranch + next_sync_committee_branch*: + LightClientDataFork.high.NextSyncCommitteeBranch finalized_slot*: Slot - finality_branch*: altair.FinalityBranch + finality_branch*: LightClientDataFork.high.FinalityBranch current_period_best_update*: ref ForkedLightClientUpdate latest_signature_slot*: Slot diff --git a/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim b/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim index 6a5aff997f..da8b154f96 100644 --- a/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim +++ b/beacon_chain/consensus_object_pools/blockchain_dag_light_client.nim @@ -22,6 +22,15 @@ template nextEpochBoundarySlot(slot: Slot): Slot = ## referring to a block at given slot. (slot + (SLOTS_PER_EPOCH - 1)).epoch.start_slot +func hasCurrentSyncCommitteeBranch(dag: ChainDAGRef, slot: Slot): bool = + let epoch = dag.cfg.consensusForkAtEpoch(slot.epoch) + withLcDataFork(lcDataForkAtConsensusFork(epoch)): + when lcDataFork > LightClientDataFork.None: + hasCurrentSyncCommitteeBranch[lcDataFork.CurrentSyncCommitteeBranch]( + dag.lcDataStore.db, slot) + else: + true + proc updateExistingState( dag: ChainDAGRef, state: var ForkedHashedBeaconState, bsi: BlockSlotId, save: bool, cache: var StateCache): bool = @@ -226,7 +235,7 @@ proc initLightClientBootstrapForPeriod( bid = bsi.bid boundarySlot = bid.slot.nextEpochBoundarySlot if boundarySlot == nextBoundarySlot and bid.slot >= lowSlot and - not dag.lcDataStore.db.hasCurrentSyncCommitteeBranch(bid.slot): + not dag.hasCurrentSyncCommitteeBranch(bid.slot): let bdata = dag.getExistingForkedBlock(bid).valueOr: dag.handleUnexpectedLightClientError(bid.slot) res.err() @@ -246,7 +255,7 @@ proc initLightClientBootstrapForPeriod( forkyBlck.toLightClientHeader(lcDataFork)) dag.lcDataStore.db.putCurrentSyncCommitteeBranch( bid.slot, forkyState.data.build_proof( - altair.CURRENT_SYNC_COMMITTEE_GINDEX).get) + lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX).get) else: raiseAssert "Unreachable" res @@ -393,13 +402,13 @@ proc initLightClientUpdateForPeriod( update = ForkedLightClientUpdate.init(lcDataFork.LightClientUpdate( attested_header: forkyBlck.toLightClientHeader(lcDataFork), next_sync_committee: forkyState.data.next_sync_committee, - next_sync_committee_branch: - forkyState.data.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get, + next_sync_committee_branch: forkyState.data.build_proof( + lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX).get, finality_branch: if finalizedBid.slot != FAR_FUTURE_SLOT: - forkyState.data.build_proof(altair.FINALIZED_ROOT_GINDEX).get + forkyState.data.build_proof(lcDataFork.FINALIZED_ROOT_GINDEX).get else: - default(FinalityBranch))) + default(lcDataFork.FinalityBranch))) else: raiseAssert "Unreachable" do: dag.handleUnexpectedLightClientError(attestedBid.slot) @@ -464,17 +473,21 @@ proc cacheLightClientData( ## Cache data for a given block and its post-state to speed up creating future ## `LightClientUpdate` and `LightClientBootstrap` instances that refer to this ## block and state. + const lcDataFork = lcDataForkAtConsensusFork(typeof(state).kind) let bid = blck.toBlockId() cachedData = CachedLightClientData( - current_sync_committee_branch: - state.data.build_proof(altair.CURRENT_SYNC_COMMITTEE_GINDEX).get, - next_sync_committee_branch: - state.data.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get, + current_sync_committee_branch: normalize_merkle_branch( + state.data.build_proof(lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX).get, + LightClientDataFork.high.CURRENT_SYNC_COMMITTEE_GINDEX), + next_sync_committee_branch: normalize_merkle_branch( + state.data.build_proof(lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX).get, + LightClientDataFork.high.NEXT_SYNC_COMMITTEE_GINDEX), finalized_slot: state.data.finalized_checkpoint.epoch.start_slot, - finality_branch: - state.data.build_proof(altair.FINALIZED_ROOT_GINDEX).get, + finality_branch: normalize_merkle_branch( + state.data.build_proof(lcDataFork.FINALIZED_ROOT_GINDEX).get, + LightClientDataFork.high.FINALIZED_ROOT_GINDEX), current_period_best_update: current_period_best_update, latest_signature_slot: @@ -538,15 +551,18 @@ proc assignLightClientData( when lcDataFork > LightClientDataFork.None: forkyObject.next_sync_committee = next_sync_committee.get - forkyObject.next_sync_committee_branch = - attested_data.next_sync_committee_branch + forkyObject.next_sync_committee_branch = normalize_merkle_branch( + attested_data.next_sync_committee_branch, + lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX) else: doAssert next_sync_committee.isNone var finalized_slot = attested_data.finalized_slot withForkyObject(obj): when lcDataFork > LightClientDataFork.None: if finalized_slot == forkyObject.finalized_header.beacon.slot: - forkyObject.finality_branch = attested_data.finality_branch + forkyObject.finality_branch = normalize_merkle_branch( + attested_data.finality_branch, + lcDataFork.FINALIZED_ROOT_GINDEX) elif finalized_slot < max(dag.tail.slot, dag.backfill.slot): forkyObject.finalized_header.reset() forkyObject.finality_branch.reset() @@ -564,10 +580,14 @@ proc assignLightClientData( attested_data.finalized_slot = finalized_slot dag.lcDataStore.cache.data[attested_bid] = attested_data if finalized_slot == forkyObject.finalized_header.beacon.slot: - forkyObject.finality_branch = attested_data.finality_branch + forkyObject.finality_branch = normalize_merkle_branch( + attested_data.finality_branch, + lcDataFork.FINALIZED_ROOT_GINDEX) elif finalized_slot == GENESIS_SLOT: forkyObject.finalized_header.reset() - forkyObject.finality_branch = attested_data.finality_branch + forkyObject.finality_branch = normalize_merkle_branch( + attested_data.finality_branch, + lcDataFork.FINALIZED_ROOT_GINDEX) else: var fin_header = dag.getExistingLightClientHeader(finalized_bid) if fin_header.kind == LightClientDataFork.None: @@ -577,7 +597,9 @@ proc assignLightClientData( else: fin_header.migrateToDataFork(lcDataFork) forkyObject.finalized_header = fin_header.forky(lcDataFork) - forkyObject.finality_branch = attested_data.finality_branch + forkyObject.finality_branch = normalize_merkle_branch( + attested_data.finality_branch, + lcDataFork.FINALIZED_ROOT_GINDEX) withForkyObject(obj): when lcDataFork > LightClientDataFork.None: forkyObject.sync_aggregate = sync_aggregate @@ -1014,7 +1036,7 @@ proc getLightClientBootstrap( # Ensure `current_sync_committee_branch` is known if dag.lcDataStore.importMode == LightClientDataImportMode.OnDemand and - not dag.lcDataStore.db.hasCurrentSyncCommitteeBranch(slot): + not dag.hasCurrentSyncCommitteeBranch(slot): let bsi = dag.getExistingBlockIdAtSlot(slot).valueOr: return default(ForkedLightClientBootstrap) @@ -1022,13 +1044,14 @@ proc getLightClientBootstrap( dag.withUpdatedExistingState(tmpState[], bsi) do: withState(updatedState): when consensusFork >= ConsensusFork.Altair: + const lcDataFork = lcDataForkAtConsensusFork(consensusFork) if not dag.lcDataStore.db.hasSyncCommittee(period): dag.lcDataStore.db.putSyncCommittee( period, forkyState.data.current_sync_committee) dag.lcDataStore.db.putHeader(header) dag.lcDataStore.db.putCurrentSyncCommitteeBranch( slot, forkyState.data.build_proof( - altair.CURRENT_SYNC_COMMITTEE_GINDEX).get) + lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX).get) else: raiseAssert "Unreachable" do: return default(ForkedLightClientBootstrap) @@ -1050,7 +1073,8 @@ proc getLightClientBootstrap( debug "LC bootstrap unavailable: Sync committee not cached", period return default(ForkedLightClientBootstrap)), current_sync_committee_branch: (block: - dag.lcDataStore.db.getCurrentSyncCommitteeBranch(slot).valueOr: + getCurrentSyncCommitteeBranch[lcDataFork.CurrentSyncCommitteeBranch]( + dag.lcDataStore.db, slot).valueOr: debug "LC bootstrap unavailable: Committee branch not cached", slot return default(ForkedLightClientBootstrap)))) diff --git a/beacon_chain/spec/datatypes/deneb.nim b/beacon_chain/spec/datatypes/deneb.nim index 042b55664d..6d0a19dcbe 100644 --- a/beacon_chain/spec/datatypes/deneb.nim +++ b/beacon_chain/spec/datatypes/deneb.nim @@ -626,14 +626,16 @@ func kzg_commitment_inclusion_proof_gindex*( BLOB_KZG_COMMITMENTS_FIRST_GINDEX + index -# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/deneb/light-client/sync-protocol.md#modified-get_lc_execution_root +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/deneb/light-client/sync-protocol.md#modified-get_lc_execution_root func get_lc_execution_root*( header: LightClientHeader, cfg: RuntimeConfig): Eth2Digest = let epoch = header.beacon.slot.epoch + # [New in Deneb] if epoch >= cfg.DENEB_FORK_EPOCH: return hash_tree_root(header.execution) + # [Modified in Deneb] if epoch >= cfg.CAPELLA_FORK_EPOCH: let execution_header = capella.ExecutionPayloadHeader( parent_hash: header.execution.parent_hash, @@ -655,11 +657,12 @@ func get_lc_execution_root*( ZERO_HASH -# https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/deneb/light-client/sync-protocol.md#modified-is_valid_light_client_header +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/deneb/light-client/sync-protocol.md#modified-is_valid_light_client_header func is_valid_light_client_header*( header: LightClientHeader, cfg: RuntimeConfig): bool = let epoch = header.beacon.slot.epoch + # [New in Deneb:EIP4844] if epoch < cfg.DENEB_FORK_EPOCH: if header.execution.blob_gas_used != 0 or header.execution.excess_blob_gas != 0: diff --git a/beacon_chain/spec/datatypes/electra.nim b/beacon_chain/spec/datatypes/electra.nim index 4fba931f98..a45f0854b8 100644 --- a/beacon_chain/spec/datatypes/electra.nim +++ b/beacon_chain/spec/datatypes/electra.nim @@ -29,24 +29,25 @@ from stew/bitops2 import log2trunc from stew/byteutils import to0xHex from ./altair import EpochParticipationFlags, InactivityScores, SyncAggregate, SyncCommittee, - TrustedSyncAggregate + TrustedSyncAggregate, num_active_participants from ./bellatrix import BloomLogs, ExecutionAddress, Transaction from ./capella import - HistoricalSummary, SignedBLSToExecutionChangeList, Withdrawal + ExecutionBranch, HistoricalSummary, SignedBLSToExecutionChangeList, + Withdrawal, EXECUTION_PAYLOAD_GINDEX from ./deneb import Blobs, BlobsBundle, KzgCommitments, KzgProofs export json_serialization, base, kzg4844 const - # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/altair/light-client/sync-protocol.md#constants + # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#constants # All of these indices are rooted in `BeaconState`. # The first member (`genesis_time`) is 64, subsequent members +1 each. # If there are ever more than 64 members in `BeaconState`, indices change! # `FINALIZED_ROOT_GINDEX` is one layer deeper, i.e., `84 * 2 + 1`. # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/ssz/merkle-proofs.md - FINALIZED_ROOT_GINDEX = 169.GeneralizedIndex # finalized_checkpoint > root - CURRENT_SYNC_COMMITTEE_GINDEX = 86.GeneralizedIndex # current_sync_committee - NEXT_SYNC_COMMITTEE_GINDEX = 87.GeneralizedIndex # next_sync_committee + FINALIZED_ROOT_GINDEX* = 169.GeneralizedIndex # finalized_checkpoint > root + CURRENT_SYNC_COMMITTEE_GINDEX* = 86.GeneralizedIndex # current_sync_committee + NEXT_SYNC_COMMITTEE_GINDEX* = 87.GeneralizedIndex # next_sync_committee type # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/beacon-chain.md#depositrequest @@ -182,15 +183,6 @@ type source_pubkey*: ValidatorPubKey target_pubkey*: ValidatorPubKey - FinalityBranch = - array[log2trunc(FINALIZED_ROOT_GINDEX), Eth2Digest] - - CurrentSyncCommitteeBranch = - array[log2trunc(CURRENT_SYNC_COMMITTEE_GINDEX), Eth2Digest] - - NextSyncCommitteeBranch = - array[log2trunc(NEXT_SYNC_COMMITTEE_GINDEX), Eth2Digest] - # https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#aggregateandproof AggregateAndProof* = object aggregator_index*: uint64 # `ValidatorIndex` after validation @@ -202,6 +194,15 @@ type message*: AggregateAndProof signature*: ValidatorSig + FinalityBranch* = + array[log2trunc(FINALIZED_ROOT_GINDEX), Eth2Digest] + + CurrentSyncCommitteeBranch* = + array[log2trunc(CURRENT_SYNC_COMMITTEE_GINDEX), Eth2Digest] + + NextSyncCommitteeBranch* = + array[log2trunc(NEXT_SYNC_COMMITTEE_GINDEX), Eth2Digest] + # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/capella/light-client/sync-protocol.md#modified-lightclientheader LightClientHeader* = object beacon*: BeaconBlockHeader @@ -675,6 +676,233 @@ func shortLog*(v: ExecutionPayload): auto = excess_blob_gas: $(v.excess_blob_gas) ) +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#modified-get_lc_execution_root +func get_lc_execution_root*( + header: LightClientHeader, cfg: RuntimeConfig): Eth2Digest = + let epoch = header.beacon.slot.epoch + + # [New in Electra] + if epoch >= cfg.ELECTRA_FORK_EPOCH: + return hash_tree_root(header.execution) + + # [Modified in Electra] + if epoch >= cfg.DENEB_FORK_EPOCH: + let execution_header = deneb.ExecutionPayloadHeader( + parent_hash: header.execution.parent_hash, + fee_recipient: header.execution.fee_recipient, + state_root: header.execution.state_root, + receipts_root: header.execution.receipts_root, + logs_bloom: header.execution.logs_bloom, + prev_randao: header.execution.prev_randao, + block_number: header.execution.block_number, + gas_limit: header.execution.gas_limit, + gas_used: header.execution.gas_used, + timestamp: header.execution.timestamp, + extra_data: header.execution.extra_data, + base_fee_per_gas: header.execution.base_fee_per_gas, + block_hash: header.execution.block_hash, + transactions_root: header.execution.transactions_root, + withdrawals_root: header.execution.withdrawals_root, + blob_gas_used: header.execution.blob_gas_used, + excess_blob_gas: header.execution.excess_blob_gas) + return hash_tree_root(execution_header) + + if epoch >= cfg.CAPELLA_FORK_EPOCH: + let execution_header = capella.ExecutionPayloadHeader( + parent_hash: header.execution.parent_hash, + fee_recipient: header.execution.fee_recipient, + state_root: header.execution.state_root, + receipts_root: header.execution.receipts_root, + logs_bloom: header.execution.logs_bloom, + prev_randao: header.execution.prev_randao, + block_number: header.execution.block_number, + gas_limit: header.execution.gas_limit, + gas_used: header.execution.gas_used, + timestamp: header.execution.timestamp, + extra_data: header.execution.extra_data, + base_fee_per_gas: header.execution.base_fee_per_gas, + block_hash: header.execution.block_hash, + transactions_root: header.execution.transactions_root, + withdrawals_root: header.execution.withdrawals_root) + return hash_tree_root(execution_header) + + ZERO_HASH + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#modified-is_valid_light_client_header +func is_valid_light_client_header*( + header: LightClientHeader, cfg: RuntimeConfig): bool = + let epoch = header.beacon.slot.epoch + + # [New in Electra:EIP6110:EIP7002:EIP7251] + if epoch < cfg.ELECTRA_FORK_EPOCH: + if not header.execution.deposit_requests_root.isZero or + not header.execution.withdrawal_requests_root.isZero or + not header.execution.consolidation_requests_root.isZero: + return false + + if epoch < cfg.DENEB_FORK_EPOCH: + if header.execution.blob_gas_used != 0 or + header.execution.excess_blob_gas != 0: + return false + + if epoch < cfg.CAPELLA_FORK_EPOCH: + return + header.execution == default(ExecutionPayloadHeader) and + header.execution_branch == default(ExecutionBranch) + + is_valid_merkle_branch( + get_lc_execution_root(header, cfg), + header.execution_branch, + log2trunc(EXECUTION_PAYLOAD_GINDEX), + get_subtree_index(EXECUTION_PAYLOAD_GINDEX), + header.beacon.body_root) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#normalize_merkle_branch +func normalize_merkle_branch*[N]( + branch: array[N, Eth2Digest], + gindex: static GeneralizedIndex): array[log2trunc(gindex), Eth2Digest] = + const depth = log2trunc(gindex) + var res: array[depth, Eth2Digest] + when depth >= branch.len: + const num_extra = depth - branch.len + res[num_extra ..< depth] = branch + else: + const num_extra = branch.len - depth + for node in branch[0 ..< num_extra]: + doAssert node.isZero, "Truncation of Merkle branch cannot lose info" + res[0 ..< depth] = branch[num_extra ..< branch.len] + res + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data +func upgrade_lc_header_to_electra*( + pre: deneb.LightClientHeader): LightClientHeader = + LightClientHeader( + beacon: pre.beacon, + execution: ExecutionPayloadHeader( + parent_hash: pre.execution.parent_hash, + fee_recipient: pre.execution.fee_recipient, + state_root: pre.execution.state_root, + receipts_root: pre.execution.receipts_root, + logs_bloom: pre.execution.logs_bloom, + prev_randao: pre.execution.prev_randao, + block_number: pre.execution.block_number, + gas_limit: pre.execution.gas_limit, + gas_used: pre.execution.gas_used, + timestamp: pre.execution.timestamp, + extra_data: pre.execution.extra_data, + base_fee_per_gas: pre.execution.base_fee_per_gas, + block_hash: pre.execution.block_hash, + transactions_root: pre.execution.transactions_root, + withdrawals_root: pre.execution.withdrawals_root, + blob_gas_used: pre.execution.blob_gas_used, + excess_blob_gas: pre.execution.blob_gas_used, + deposit_requests_root: ZERO_HASH, # [New in Electra:EIP6110] + withdrawal_requests_root: ZERO_HASH, # [New in Electra:EIP7002:EIP7251] + consolidation_requests_root: ZERO_HASH), # [New in Electra:EIP7251] + execution_branch: pre.execution_branch) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data +func upgrade_lc_bootstrap_to_electra*( + pre: deneb.LightClientBootstrap): LightClientBootstrap = + LightClientBootstrap( + header: upgrade_lc_header_to_electra(pre.header), + current_sync_committee: pre.current_sync_committee, + current_sync_committee_branch: normalize_merkle_branch( + pre.current_sync_committee_branch, CURRENT_SYNC_COMMITTEE_GINDEX)) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data +func upgrade_lc_update_to_electra*( + pre: deneb.LightClientUpdate): LightClientUpdate = + LightClientUpdate( + attested_header: upgrade_lc_header_to_electra(pre.attested_header), + next_sync_committee: pre.next_sync_committee, + next_sync_committee_branch: normalize_merkle_branch( + pre.next_sync_committee_branch, NEXT_SYNC_COMMITTEE_GINDEX), + finalized_header: upgrade_lc_header_to_electra(pre.finalized_header), + finality_branch: normalize_merkle_branch( + pre.finality_branch, FINALIZED_ROOT_GINDEX), + sync_aggregate: pre.sync_aggregate, + signature_slot: pre.signature_slot) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data +func upgrade_lc_finality_update_to_electra*( + pre: deneb.LightClientFinalityUpdate): LightClientFinalityUpdate = + LightClientFinalityUpdate( + attested_header: upgrade_lc_header_to_electra(pre.attested_header), + finalized_header: upgrade_lc_header_to_electra(pre.finalized_header), + finality_branch: normalize_merkle_branch( + pre.finality_branch, FINALIZED_ROOT_GINDEX), + sync_aggregate: pre.sync_aggregate, + signature_slot: pre.signature_slot) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-light-client-data +func upgrade_lc_optimistic_update_to_electra*( + pre: deneb.LightClientOptimisticUpdate): LightClientOptimisticUpdate = + LightClientOptimisticUpdate( + attested_header: upgrade_lc_header_to_electra(pre.attested_header), + sync_aggregate: pre.sync_aggregate, + signature_slot: pre.signature_slot) + +func shortLog*(v: LightClientHeader): auto = + ( + beacon: shortLog(v.beacon), + execution: ( + block_hash: v.execution.block_hash, + block_number: v.execution.block_number) + ) + +func shortLog*(v: LightClientBootstrap): auto = + ( + header: shortLog(v.header) + ) + +func shortLog*(v: LightClientUpdate): auto = + ( + attested: shortLog(v.attested_header), + has_next_sync_committee: + v.next_sync_committee != default(typeof(v.next_sync_committee)), + finalized: shortLog(v.finalized_header), + num_active_participants: v.sync_aggregate.num_active_participants, + signature_slot: v.signature_slot + ) + +func shortLog*(v: LightClientFinalityUpdate): auto = + ( + attested: shortLog(v.attested_header), + finalized: shortLog(v.finalized_header), + num_active_participants: v.sync_aggregate.num_active_participants, + signature_slot: v.signature_slot + ) + +func shortLog*(v: LightClientOptimisticUpdate): auto = + ( + attested: shortLog(v.attested_header), + num_active_participants: v.sync_aggregate.num_active_participants, + signature_slot: v.signature_slot, + ) + +chronicles.formatIt LightClientBootstrap: shortLog(it) +chronicles.formatIt LightClientUpdate: shortLog(it) +chronicles.formatIt LightClientFinalityUpdate: shortLog(it) +chronicles.formatIt LightClientOptimisticUpdate: shortLog(it) + +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/fork.md#upgrading-the-store +func upgrade_lc_store_to_electra*( + pre: deneb.LightClientStore): LightClientStore = + let best_valid_update = + if pre.best_valid_update.isNone: + Opt.none(LightClientUpdate) + else: + Opt.some upgrade_lc_update_to_electra(pre.best_valid_update.get) + LightClientStore( + finalized_header: upgrade_lc_header_to_electra(pre.finalized_header), + current_sync_committee: pre.current_sync_committee, + next_sync_committee: pre.next_sync_committee, + best_valid_update: best_valid_update, + optimistic_header: upgrade_lc_header_to_electra(pre.optimistic_header), + previous_max_active_participants: pre.previous_max_active_participants, + current_max_active_participants: pre.current_max_active_participants) + template asSigned*( x: SigVerifiedSignedBeaconBlock | MsgTrustedSignedBeaconBlock | diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 13762ec7df..d2a223f32d 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -252,6 +252,11 @@ RestJson.useDefaultSerializationFor( electra.ExecutionPayload, electra.ExecutionPayloadHeader, electra.IndexedAttestation, + electra.LightClientBootstrap, + electra.LightClientFinalityUpdate, + electra.LightClientHeader, + electra.LightClientOptimisticUpdate, + electra.LightClientUpdate, electra.SignedBeaconBlock, electra.TrustedAttestation, electra_mev.BlindedBeaconBlock, diff --git a/beacon_chain/spec/forks.nim b/beacon_chain/spec/forks.nim index 3c86b4926e..5dc50ee20c 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -1325,8 +1325,10 @@ func forkVersion*(cfg: RuntimeConfig, consensusFork: ConsensusFork): Version = func lcDataForkAtConsensusFork*( consensusFork: ConsensusFork): LightClientDataFork = - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb - if consensusFork >= ConsensusFork.Deneb: + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra + if consensusFork >= ConsensusFork.Electra: + LightClientDataFork.Electra + elif consensusFork >= ConsensusFork.Deneb: LightClientDataFork.Deneb elif consensusFork >= ConsensusFork.Capella: LightClientDataFork.Capella diff --git a/beacon_chain/spec/forks_light_client.nim b/beacon_chain/spec/forks_light_client.nim index 780ba47391..837c261eaa 100644 --- a/beacon_chain/spec/forks_light_client.nim +++ b/beacon_chain/spec/forks_light_client.nim @@ -16,32 +16,42 @@ type None = 0, # only use non-0 in DB to detect accidentally uninitialized data Altair = 1, Capella = 2, - Deneb = 3 + Deneb = 3, + Electra = 4 + + ForkyCurrentSyncCommitteeBranch* = + altair.CurrentSyncCommitteeBranch | + electra.CurrentSyncCommitteeBranch ForkyLightClientHeader* = altair.LightClientHeader | capella.LightClientHeader | - deneb.LightClientHeader + deneb.LightClientHeader | + electra.LightClientHeader ForkyLightClientBootstrap* = altair.LightClientBootstrap | capella.LightClientBootstrap | - deneb.LightClientBootstrap + deneb.LightClientBootstrap | + electra.LightClientBootstrap ForkyLightClientUpdate* = altair.LightClientUpdate | capella.LightClientUpdate | - deneb.LightClientUpdate + deneb.LightClientUpdate | + electra.LightClientUpdate ForkyLightClientFinalityUpdate* = altair.LightClientFinalityUpdate | capella.LightClientFinalityUpdate | - deneb.LightClientFinalityUpdate + deneb.LightClientFinalityUpdate | + electra.LightClientFinalityUpdate ForkyLightClientOptimisticUpdate* = altair.LightClientOptimisticUpdate | capella.LightClientOptimisticUpdate | - deneb.LightClientOptimisticUpdate + deneb.LightClientOptimisticUpdate | + electra.LightClientOptimisticUpdate SomeForkyLightClientUpdateWithSyncCommittee* = ForkyLightClientUpdate @@ -62,7 +72,8 @@ type ForkyLightClientStore* = altair.LightClientStore | capella.LightClientStore | - deneb.LightClientStore + deneb.LightClientStore | + electra.LightClientStore ForkedLightClientHeader* = object case kind*: LightClientDataFork @@ -74,6 +85,8 @@ type capellaData*: capella.LightClientHeader of LightClientDataFork.Deneb: denebData*: deneb.LightClientHeader + of LightClientDataFork.Electra: + electraData*: electra.LightClientHeader ForkedLightClientBootstrap* = object case kind*: LightClientDataFork @@ -85,6 +98,8 @@ type capellaData*: capella.LightClientBootstrap of LightClientDataFork.Deneb: denebData*: deneb.LightClientBootstrap + of LightClientDataFork.Electra: + electraData*: electra.LightClientBootstrap ForkedLightClientUpdate* = object case kind*: LightClientDataFork @@ -96,6 +111,8 @@ type capellaData*: capella.LightClientUpdate of LightClientDataFork.Deneb: denebData*: deneb.LightClientUpdate + of LightClientDataFork.Electra: + electraData*: electra.LightClientUpdate ForkedLightClientFinalityUpdate* = object case kind*: LightClientDataFork @@ -107,6 +124,8 @@ type capellaData*: capella.LightClientFinalityUpdate of LightClientDataFork.Deneb: denebData*: deneb.LightClientFinalityUpdate + of LightClientDataFork.Electra: + electraData*: electra.LightClientFinalityUpdate ForkedLightClientOptimisticUpdate* = object case kind*: LightClientDataFork @@ -118,6 +137,8 @@ type capellaData*: capella.LightClientOptimisticUpdate of LightClientDataFork.Deneb: denebData*: deneb.LightClientOptimisticUpdate + of LightClientDataFork.Electra: + electraData*: electra.LightClientOptimisticUpdate SomeForkedLightClientUpdateWithSyncCommittee* = ForkedLightClientUpdate @@ -145,11 +166,15 @@ type capellaData*: capella.LightClientStore of LightClientDataFork.Deneb: denebData*: deneb.LightClientStore + of LightClientDataFork.Electra: + electraData*: electra.LightClientStore func lcDataForkAtEpoch*( cfg: RuntimeConfig, epoch: Epoch): LightClientDataFork = - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb - if epoch >= cfg.DENEB_FORK_EPOCH: + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra + if epoch >= cfg.ELECTRA_FORK_EPOCH: + LightClientDataFork.Electra + elif epoch >= cfg.DENEB_FORK_EPOCH: LightClientDataFork.Deneb elif epoch >= cfg.CAPELLA_FORK_EPOCH: LightClientDataFork.Capella @@ -188,8 +213,71 @@ template kind*( deneb.LightClientStore]): LightClientDataFork = LightClientDataFork.Deneb +template kind*( + x: typedesc[ # `SomeLightClientObject` doesn't work here (Nim 1.6) + electra.LightClientHeader | + electra.LightClientBootstrap | + electra.LightClientUpdate | + electra.LightClientFinalityUpdate | + electra.LightClientOptimisticUpdate | + electra.LightClientStore]): LightClientDataFork = + LightClientDataFork.Electra + +template FINALIZED_ROOT_GINDEX*( + kind: static LightClientDataFork): GeneralizedIndex = + when kind >= LightClientDataFork.Electra: + electra.FINALIZED_ROOT_GINDEX + elif kind >= LightClientDataFork.Altair: + altair.FINALIZED_ROOT_GINDEX + else: + static: raiseAssert "Unreachable" + +template FinalityBranch*(kind: static LightClientDataFork): auto = + when kind >= LightClientDataFork.Electra: + typedesc[electra.FinalityBranch] + elif kind >= LightClientDataFork.Altair: + typedesc[altair.FinalityBranch] + else: + static: raiseAssert "Unreachable" + +template CURRENT_SYNC_COMMITTEE_GINDEX*( + kind: static LightClientDataFork): GeneralizedIndex = + when kind >= LightClientDataFork.Electra: + electra.CURRENT_SYNC_COMMITTEE_GINDEX + elif kind >= LightClientDataFork.Altair: + altair.CURRENT_SYNC_COMMITTEE_GINDEX + else: + static: raiseAssert "Unreachable" + +template CurrentSyncCommitteeBranch*(kind: static LightClientDataFork): auto = + when kind >= LightClientDataFork.Electra: + typedesc[electra.CurrentSyncCommitteeBranch] + elif kind >= LightClientDataFork.Altair: + typedesc[altair.CurrentSyncCommitteeBranch] + else: + static: raiseAssert "Unreachable" + +template NEXT_SYNC_COMMITTEE_GINDEX*( + kind: static LightClientDataFork): GeneralizedIndex = + when kind >= LightClientDataFork.Electra: + electra.NEXT_SYNC_COMMITTEE_GINDEX + elif kind >= LightClientDataFork.Altair: + altair.NEXT_SYNC_COMMITTEE_GINDEX + else: + static: raiseAssert "Unreachable" + +template NextSyncCommitteeBranch*(kind: static LightClientDataFork): auto = + when kind >= LightClientDataFork.Electra: + typedesc[electra.NextSyncCommitteeBranch] + elif kind >= LightClientDataFork.Altair: + typedesc[altair.NextSyncCommitteeBranch] + else: + static: raiseAssert "Unreachable" + template LightClientHeader*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + typedesc[electra.LightClientHeader] + elif kind == LightClientDataFork.Deneb: typedesc[deneb.LightClientHeader] elif kind == LightClientDataFork.Capella: typedesc[capella.LightClientHeader] @@ -199,7 +287,9 @@ template LightClientHeader*(kind: static LightClientDataFork): auto = static: raiseAssert "Unreachable" template LightClientBootstrap*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + typedesc[electra.LightClientBootstrap] + elif kind == LightClientDataFork.Deneb: typedesc[deneb.LightClientBootstrap] elif kind == LightClientDataFork.Capella: typedesc[capella.LightClientBootstrap] @@ -209,7 +299,9 @@ template LightClientBootstrap*(kind: static LightClientDataFork): auto = static: raiseAssert "Unreachable" template LightClientUpdate*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + typedesc[electra.LightClientUpdate] + elif kind == LightClientDataFork.Deneb: typedesc[deneb.LightClientUpdate] elif kind == LightClientDataFork.Capella: typedesc[capella.LightClientUpdate] @@ -219,7 +311,9 @@ template LightClientUpdate*(kind: static LightClientDataFork): auto = static: raiseAssert "Unreachable" template LightClientFinalityUpdate*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + typedesc[electra.LightClientFinalityUpdate] + elif kind == LightClientDataFork.Deneb: typedesc[deneb.LightClientFinalityUpdate] elif kind == LightClientDataFork.Capella: typedesc[capella.LightClientFinalityUpdate] @@ -229,7 +323,9 @@ template LightClientFinalityUpdate*(kind: static LightClientDataFork): auto = static: raiseAssert "Unreachable" template LightClientOptimisticUpdate*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + typedesc[electra.LightClientOptimisticUpdate] + elif kind == LightClientDataFork.Deneb: typedesc[deneb.LightClientOptimisticUpdate] elif kind == LightClientDataFork.Capella: typedesc[capella.LightClientOptimisticUpdate] @@ -239,7 +335,9 @@ template LightClientOptimisticUpdate*(kind: static LightClientDataFork): auto = static: raiseAssert "Unreachable" template LightClientStore*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + typedesc[electra.LightClientStore] + elif kind == LightClientDataFork.Deneb: typedesc[deneb.LightClientStore] elif kind == LightClientDataFork.Capella: typedesc[capella.LightClientStore] @@ -298,7 +396,10 @@ template Forked*(x: typedesc[ForkyLightClientStore]): auto = template withAll*( x: typedesc[LightClientDataFork], body: untyped): untyped = - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra + block: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + body block: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb body @@ -315,6 +416,9 @@ template withAll*( template withLcDataFork*( x: LightClientDataFork, body: untyped): untyped = case x + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb body @@ -331,6 +435,10 @@ template withLcDataFork*( template withForkyHeader*( x: ForkedLightClientHeader, body: untyped): untyped = case x.kind + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + template forkyHeader: untyped {.inject, used.} = x.electraData + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb template forkyHeader: untyped {.inject, used.} = x.denebData @@ -350,6 +458,10 @@ template withForkyHeader*( template withForkyBootstrap*( x: ForkedLightClientBootstrap, body: untyped): untyped = case x.kind + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + template forkyBootstrap: untyped {.inject, used.} = x.electraData + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb template forkyBootstrap: untyped {.inject, used.} = x.denebData @@ -369,6 +481,10 @@ template withForkyBootstrap*( template withForkyUpdate*( x: ForkedLightClientUpdate, body: untyped): untyped = case x.kind + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + template forkyUpdate: untyped {.inject, used.} = x.electraData + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb template forkyUpdate: untyped {.inject, used.} = x.denebData @@ -388,6 +504,10 @@ template withForkyUpdate*( template withForkyFinalityUpdate*( x: ForkedLightClientFinalityUpdate, body: untyped): untyped = case x.kind + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + template forkyFinalityUpdate: untyped {.inject, used.} = x.electraData + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb template forkyFinalityUpdate: untyped {.inject, used.} = x.denebData @@ -407,6 +527,10 @@ template withForkyFinalityUpdate*( template withForkyOptimisticUpdate*( x: ForkedLightClientOptimisticUpdate, body: untyped): untyped = case x.kind + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + template forkyOptimisticUpdate: untyped {.inject, used.} = x.electraData + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb template forkyOptimisticUpdate: untyped {.inject, used.} = x.denebData @@ -426,6 +550,10 @@ template withForkyOptimisticUpdate*( template withForkyObject*( x: SomeForkedLightClientObject, body: untyped): untyped = case x.kind + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + template forkyObject: untyped {.inject, used.} = x.electraData + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb template forkyObject: untyped {.inject, used.} = x.denebData @@ -445,6 +573,10 @@ template withForkyObject*( template withForkyStore*( x: ForkedLightClientStore, body: untyped): untyped = case x.kind + of LightClientDataFork.Electra: + const lcDataFork {.inject, used.} = LightClientDataFork.Electra + template forkyStore: untyped {.inject, used.} = x.electraData + body of LightClientDataFork.Deneb: const lcDataFork {.inject, used.} = LightClientDataFork.Deneb template forkyStore: untyped {.inject, used.} = x.denebData @@ -473,7 +605,9 @@ func init*( type ResultType = typeof(forkyData).Forked static: doAssert ResultType is x const kind = typeof(forkyData).kind - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + ResultType(kind: kind, electraData: forkyData) + elif kind == LightClientDataFork.Deneb: ResultType(kind: kind, denebData: forkyData) elif kind == LightClientDataFork.Capella: ResultType(kind: kind, capellaData: forkyData) @@ -488,7 +622,9 @@ template forky*( SomeForkedLightClientObject | ForkedLightClientStore, kind: static LightClientDataFork): untyped = - when kind == LightClientDataFork.Deneb: + when kind == LightClientDataFork.Electra: + x.electraData + elif kind == LightClientDataFork.Deneb: x.denebData elif kind == LightClientDataFork.Capella: x.capellaData @@ -641,7 +777,15 @@ func migrateToDataFork*( denebData: upgrade_lc_header_to_deneb( x.forky(LightClientDataFork.Capella))) - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + # Upgrade to Electra + when newKind >= LightClientDataFork.Electra: + if x.kind == LightClientDataFork.Deneb: + x = ForkedLightClientHeader( + kind: LightClientDataFork.Electra, + electraData: upgrade_lc_header_to_electra( + x.forky(LightClientDataFork.Deneb))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra doAssert x.kind == newKind func migrateToDataFork*( @@ -676,7 +820,15 @@ func migrateToDataFork*( denebData: upgrade_lc_bootstrap_to_deneb( x.forky(LightClientDataFork.Capella))) - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + # Upgrade to Electra + when newKind >= LightClientDataFork.Electra: + if x.kind == LightClientDataFork.Deneb: + x = ForkedLightClientBootstrap( + kind: LightClientDataFork.Electra, + electraData: upgrade_lc_bootstrap_to_electra( + x.forky(LightClientDataFork.Deneb))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra doAssert x.kind == newKind func migrateToDataFork*( @@ -711,7 +863,15 @@ func migrateToDataFork*( denebData: upgrade_lc_update_to_deneb( x.forky(LightClientDataFork.Capella))) - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + # Upgrade to Electra + when newKind >= LightClientDataFork.Electra: + if x.kind == LightClientDataFork.Deneb: + x = ForkedLightClientUpdate( + kind: LightClientDataFork.Electra, + electraData: upgrade_lc_update_to_electra( + x.forky(LightClientDataFork.Deneb))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra doAssert x.kind == newKind func migrateToDataFork*( @@ -746,7 +906,15 @@ func migrateToDataFork*( denebData: upgrade_lc_finality_update_to_deneb( x.forky(LightClientDataFork.Capella))) - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + # Upgrade to Electra + when newKind >= LightClientDataFork.Electra: + if x.kind == LightClientDataFork.Deneb: + x = ForkedLightClientFinalityUpdate( + kind: LightClientDataFork.Electra, + electraData: upgrade_lc_finality_update_to_electra( + x.forky(LightClientDataFork.Deneb))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra doAssert x.kind == newKind func migrateToDataFork*( @@ -781,7 +949,15 @@ func migrateToDataFork*( denebData: upgrade_lc_optimistic_update_to_deneb( x.forky(LightClientDataFork.Capella))) - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + # Upgrade to Electra + when newKind >= LightClientDataFork.Electra: + if x.kind == LightClientDataFork.Deneb: + x = ForkedLightClientOptimisticUpdate( + kind: LightClientDataFork.Electra, + electraData: upgrade_lc_optimistic_update_to_electra( + x.forky(LightClientDataFork.Deneb))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra doAssert x.kind == newKind func migrateToDataFork*( @@ -816,7 +992,15 @@ func migrateToDataFork*( denebData: upgrade_lc_store_to_deneb( x.forky(LightClientDataFork.Capella))) - static: doAssert LightClientDataFork.high == LightClientDataFork.Deneb + # Upgrade to Electra + when newKind >= LightClientDataFork.Electra: + if x.kind == LightClientDataFork.Deneb: + x = ForkedLightClientStore( + kind: LightClientDataFork.Electra, + electraData: upgrade_lc_store_to_electra( + x.forky(LightClientDataFork.Deneb))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Electra doAssert x.kind == newKind func migratingToDataFork*[ @@ -951,6 +1135,108 @@ func toDenebLightClientHeader( execution_branch: blck.message.body.build_proof( capella.EXECUTION_PAYLOAD_GINDEX).get) +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/full-node.md#modified-block_to_light_client_header +func toElectraLightClientHeader( + blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) + phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock | + altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock | + bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock +): electra.LightClientHeader = + # Note that during fork transitions, `finalized_header` may still + # point to earlier forks. While Bellatrix blocks also contain an + # `ExecutionPayload` (minus `withdrawals_root`), it was not included + # in the corresponding light client data. To ensure compatibility + # with legacy data going through `upgrade_lc_header_to_capella`, + # leave out execution data. + electra.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader()) + +func toElectraLightClientHeader( + blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) + capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock +): electra.LightClientHeader = + template payload: untyped = blck.message.body.execution_payload + electra.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader(), + execution: electra.ExecutionPayloadHeader( + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data, + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: hash_tree_root(payload.transactions), + withdrawals_root: hash_tree_root(payload.withdrawals)), + execution_branch: blck.message.body.build_proof( + capella.EXECUTION_PAYLOAD_GINDEX).get) + +func toElectraLightClientHeader( + blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) + deneb.SignedBeaconBlock | deneb.TrustedSignedBeaconBlock +): electra.LightClientHeader = + template payload: untyped = blck.message.body.execution_payload + electra.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader(), + execution: electra.ExecutionPayloadHeader( + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data, + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: hash_tree_root(payload.transactions), + withdrawals_root: hash_tree_root(payload.withdrawals), + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas), + execution_branch: blck.message.body.build_proof( + capella.EXECUTION_PAYLOAD_GINDEX).get) + +func toElectraLightClientHeader( + blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) + electra.SignedBeaconBlock | electra.TrustedSignedBeaconBlock +): electra.LightClientHeader = + template payload: untyped = blck.message.body.execution_payload + electra.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader(), + execution: electra.ExecutionPayloadHeader( + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom, + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data, + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: hash_tree_root(payload.transactions), + withdrawals_root: hash_tree_root(payload.withdrawals), + blob_gas_used: payload.blob_gas_used, + excess_blob_gas: payload.excess_blob_gas, + deposit_requests_root: hash_tree_root(payload.deposit_requests), + withdrawal_requests_root: hash_tree_root(payload.withdrawal_requests), + consolidation_requests_root: + hash_tree_root(payload.consolidation_requests)), + execution_branch: blck.message.body.build_proof( + capella.EXECUTION_PAYLOAD_GINDEX).get) + func toLightClientHeader*( blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock | @@ -960,9 +1246,8 @@ func toLightClientHeader*( deneb.SignedBeaconBlock | deneb.TrustedSignedBeaconBlock | electra.SignedBeaconBlock | electra.TrustedSignedBeaconBlock, kind: static LightClientDataFork): auto = - when blck is electra.SignedBeaconBlock or blck is electra.TrustedSignedBeaconBlock: - debugComment "toLightClientHeader electra missing" - default(deneb.LightClientHeader) + when kind == LightClientDataFork.Electra: + blck.toElectraLightClientHeader() elif kind == LightClientDataFork.Deneb: blck.toDenebLightClientHeader() elif kind == LightClientDataFork.Capella: @@ -990,9 +1275,13 @@ func shortLog*[ capellaData: typeof(x.capellaData.shortLog()) of LightClientDataFork.Deneb: denebData: typeof(x.denebData.shortLog()) + of LightClientDataFork.Electra: + electraData: typeof(x.electraData.shortLog()) let xKind = x.kind # Nim 1.6.12: Using `kind: x.kind` inside case is broken case xKind + of LightClientDataFork.Electra: + ResultType(kind: xKind, electraData: x.electraData.shortLog()) of LightClientDataFork.Deneb: ResultType(kind: xKind, denebData: x.denebData.shortLog()) of LightClientDataFork.Capella: diff --git a/beacon_chain/spec/light_client_sync.nim b/beacon_chain/spec/light_client_sync.nim index ef58198ae6..d735e41528 100644 --- a/beacon_chain/spec/light_client_sync.nim +++ b/beacon_chain/spec/light_client_sync.nim @@ -15,6 +15,21 @@ import from ../consensus_object_pools/block_pools_types import VerifierError export block_pools_types.VerifierError +# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.3/specs/electra/light-client/sync-protocol.md#is_valid_normalized_merkle_branch +func is_valid_normalized_merkle_branch[N]( + leaf: Eth2Digest, + branch: array[N, Eth2Digest], + gindex: static GeneralizedIndex, + root: Eth2Digest): bool = + const + depth = log2trunc(gindex) + index = get_subtree_index(gindex) + num_extra = branch.len - depth + for i in 0 ..< num_extra: + if not branch[i].isZero: + return false + is_valid_merkle_branch(leaf, branch[num_extra .. ^1], depth, index, root) + # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/altair/light-client/sync-protocol.md#initialize_light_client_store func initialize_light_client_store*( trusted_block_root: Eth2Digest, @@ -29,13 +44,15 @@ func initialize_light_client_store*( if hash_tree_root(bootstrap.header.beacon) != trusted_block_root: return ResultType.err(VerifierError.Invalid) - if not is_valid_merkle_branch( - hash_tree_root(bootstrap.current_sync_committee), - bootstrap.current_sync_committee_branch, - log2trunc(altair.CURRENT_SYNC_COMMITTEE_GINDEX), - get_subtree_index(altair.CURRENT_SYNC_COMMITTEE_GINDEX), - bootstrap.header.beacon.state_root): - return ResultType.err(VerifierError.Invalid) + withLcDataFork(lcDataForkAtConsensusFork( + cfg.consensusForkAtEpoch(bootstrap.header.beacon.slot.epoch))): + when lcDataFork > LightClientDataFork.None: + if not is_valid_normalized_merkle_branch( + hash_tree_root(bootstrap.current_sync_committee), + bootstrap.current_sync_committee_branch, + lcDataFork.CURRENT_SYNC_COMMITTEE_GINDEX, + bootstrap.header.beacon.state_root): + return ResultType.err(VerifierError.Invalid) return ResultType.ok(typeof(bootstrap).kind.LightClientStore( finalized_header: bootstrap.header, @@ -109,13 +126,15 @@ proc validate_light_client_update*( finalized_root.reset() else: return err(VerifierError.Invalid) - if not is_valid_merkle_branch( - finalized_root, - update.finality_branch, - log2trunc(altair.FINALIZED_ROOT_GINDEX), - get_subtree_index(altair.FINALIZED_ROOT_GINDEX), - update.attested_header.beacon.state_root): - return err(VerifierError.Invalid) + withLcDataFork(lcDataForkAtConsensusFork( + cfg.consensusForkAtEpoch(update.attested_header.beacon.slot.epoch))): + when lcDataFork > LightClientDataFork.None: + if not is_valid_normalized_merkle_branch( + finalized_root, + update.finality_branch, + lcDataFork.FINALIZED_ROOT_GINDEX, + update.attested_header.beacon.state_root): + return err(VerifierError.Invalid) # Verify that the `next_sync_committee`, if present, actually is the # next sync committee saved in the state of the `attested_header` @@ -128,13 +147,15 @@ proc validate_light_client_update*( if attested_period == store_period and is_next_sync_committee_known: if update.next_sync_committee != store.next_sync_committee: return err(VerifierError.UnviableFork) - if not is_valid_merkle_branch( - hash_tree_root(update.next_sync_committee), - update.next_sync_committee_branch, - log2trunc(altair.NEXT_SYNC_COMMITTEE_GINDEX), - get_subtree_index(altair.NEXT_SYNC_COMMITTEE_GINDEX), - update.attested_header.beacon.state_root): - return err(VerifierError.Invalid) + withLcDataFork(lcDataForkAtConsensusFork( + cfg.consensusForkAtEpoch(update.attested_header.beacon.slot.epoch))): + when lcDataFork > LightClientDataFork.None: + if not is_valid_normalized_merkle_branch( + hash_tree_root(update.next_sync_committee), + update.next_sync_committee_branch, + lcDataFork.NEXT_SYNC_COMMITTEE_GINDEX, + update.attested_header.beacon.state_root): + return err(VerifierError.Invalid) # Verify sync committee aggregate signature let sync_committee = diff --git a/tests/consensus_spec/altair/test_fixture_light_client_sync_protocol.nim b/tests/consensus_spec/altair/test_fixture_light_client_sync_protocol.nim index 7e8def2a4d..59974c0c5b 100644 --- a/tests/consensus_spec/altair/test_fixture_light_client_sync_protocol.nim +++ b/tests/consensus_spec/altair/test_fixture_light_client_sync_protocol.nim @@ -172,14 +172,15 @@ proc runTest(storeDataFork: static LightClientDataFork) = # Sync committee signing the attested_header (sync_aggregate, signature_slot) = get_sync_aggregate(cfg, forked[]) next_sync_committee = SyncCommittee() - next_sync_committee_branch = default(altair.NextSyncCommitteeBranch) + next_sync_committee_branch = + default(storeDataFork.NextSyncCommitteeBranch) # Ensure that finality checkpoint is genesis check state.finalized_checkpoint.epoch == 0 # Finality is unchanged let finality_header = default(storeDataFork.LightClientHeader) - finality_branch = default(altair.FinalityBranch) + finality_branch = default(storeDataFork.FinalityBranch) update = storeDataFork.LightClientUpdate( attested_header: attested_header, @@ -228,11 +229,12 @@ proc runTest(storeDataFork: static LightClientDataFork) = # Sync committee signing the attested_header (sync_aggregate, signature_slot) = get_sync_aggregate(cfg, forked[]) next_sync_committee = SyncCommittee() - next_sync_committee_branch = default(altair.NextSyncCommitteeBranch) + next_sync_committee_branch = + default(storeDataFork.NextSyncCommitteeBranch) # Finality is unchanged finality_header = default(storeDataFork.LightClientHeader) - finality_branch = default(altair.FinalityBranch) + finality_branch = default(storeDataFork.FinalityBranch) update = storeDataFork.LightClientUpdate( attested_header: attested_header, @@ -283,12 +285,13 @@ proc runTest(storeDataFork: static LightClientDataFork) = # Sync committee is updated template next_sync_committee(): auto = state.next_sync_committee let - next_sync_committee_branch = - state.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get + next_sync_committee_branch = normalize_merkle_branch( + state.build_proof(altair.NEXT_SYNC_COMMITTEE_GINDEX).get, + storeDataFork.NEXT_SYNC_COMMITTEE_GINDEX) # Finality is unchanged finality_header = default(storeDataFork.LightClientHeader) - finality_branch = default(altair.FinalityBranch) + finality_branch = default(storeDataFork.FinalityBranch) update = storeDataFork.LightClientUpdate( attested_header: attested_header, @@ -345,7 +348,8 @@ proc runTest(storeDataFork: static LightClientDataFork) = # Updated sync_committee and finality next_sync_committee = SyncCommittee() - next_sync_committee_branch = default(altair.NextSyncCommitteeBranch) + next_sync_committee_branch = + default(storeDataFork.NextSyncCommitteeBranch) finalized_block = blocks[SLOTS_PER_EPOCH - 1].altairData finalized_header = finalized_block.toLightClientHeader(storeDataFork) check: @@ -354,7 +358,9 @@ proc runTest(storeDataFork: static LightClientDataFork) = finalized_header.beacon.hash_tree_root() == state.finalized_checkpoint.root let - finality_branch = state.build_proof(altair.FINALIZED_ROOT_GINDEX).get + finality_branch = normalize_merkle_branch( + state.build_proof(altair.FINALIZED_ROOT_GINDEX).get, + storeDataFork.FINALIZED_ROOT_GINDEX) update = storeDataFork.LightClientUpdate( attested_header: attested_header,