diff --git a/ConsensusSpecPreset-mainnet.md b/ConsensusSpecPreset-mainnet.md index cd56b29fae..5bb0247af7 100644 --- a/ConsensusSpecPreset-mainnet.md +++ b/ConsensusSpecPreset-mainnet.md @@ -1843,6 +1843,14 @@ OK: 1/1 Fail: 0/1 Skip: 0/1 + Testing Withdrawal OK ``` OK: 46/46 Fail: 0/46 Skip: 0/46 +## EF - Capella - Unittests - Light client - Sync protocol [Preset: mainnet] +```diff ++ process_light_client_update_finality_updated OK ++ process_light_client_update_timeout OK ++ test_process_light_client_update_at_period_boundary OK ++ test_process_light_client_update_not_timeout OK +``` +OK: 4/4 Fail: 0/4 Skip: 0/4 ## EF - EIP4844 - Epoch Processing - Effective balance updates [Preset: mainnet] ```diff + Effective balance updates - effective_balance_hysteresis [Preset: mainnet] OK @@ -2580,4 +2588,4 @@ OK: 63/63 Fail: 0/63 Skip: 0/63 OK: 100/100 Fail: 0/100 Skip: 0/100 ---TOTAL--- -OK: 2285/2294 Fail: 0/2294 Skip: 9/2294 +OK: 2289/2298 Fail: 0/2298 Skip: 9/2298 diff --git a/ConsensusSpecPreset-minimal.md b/ConsensusSpecPreset-minimal.md index 4e6ffb476e..449f422a81 100644 --- a/ConsensusSpecPreset-minimal.md +++ b/ConsensusSpecPreset-minimal.md @@ -1992,6 +1992,14 @@ OK: 5/5 Fail: 0/5 Skip: 0/5 + Testing Withdrawal OK ``` OK: 46/46 Fail: 0/46 Skip: 0/46 +## EF - Capella - Unittests - Light client - Sync protocol [Preset: minimal] +```diff ++ process_light_client_update_finality_updated OK ++ process_light_client_update_timeout OK ++ test_process_light_client_update_at_period_boundary OK ++ test_process_light_client_update_not_timeout OK +``` +OK: 4/4 Fail: 0/4 Skip: 0/4 ## EF - EIP4844 - Epoch Processing - Effective balance updates [Preset: minimal] ```diff + Effective balance updates - effective_balance_hysteresis [Preset: minimal] OK @@ -2747,4 +2755,4 @@ OK: 68/68 Fail: 0/68 Skip: 0/68 OK: 102/102 Fail: 0/102 Skip: 0/102 ---TOTAL--- -OK: 2436/2445 Fail: 0/2445 Skip: 9/2445 +OK: 2440/2449 Fail: 0/2449 Skip: 9/2449 diff --git a/beacon_chain/light_client.nim b/beacon_chain/light_client.nim index 768be1e85c..9edff8866c 100644 --- a/beacon_chain/light_client.nim +++ b/beacon_chain/light_client.nim @@ -347,7 +347,7 @@ proc installMessageValidators*( let forkDigests = lightClient.forkDigests for stateFork in BeaconStateFork: - if stateFork >= BeaconStateFork.Capella: + if stateFork >= BeaconStateFork.EIP4844: # Format is still in development, do not use Gossip at this time. continue @@ -420,7 +420,7 @@ proc updateGossipStatus*( for gossipFork in oldGossipForks: if gossipFork >= BeaconStateFork.Altair: - if gossipFork >= BeaconStateFork.Capella: + if gossipFork >= BeaconStateFork.EIP4844: # Format is still in development, do not use Gossip at this time. continue let forkDigest = lightClient.forkDigests[].atStateFork(gossipFork) @@ -431,7 +431,7 @@ proc updateGossipStatus*( for gossipFork in newGossipForks: if gossipFork >= BeaconStateFork.Altair: - if gossipFork >= BeaconStateFork.Capella: + if gossipFork >= BeaconStateFork.EIP4844: # Format is still in development, do not use Gossip at this time. continue let forkDigest = lightClient.forkDigests[].atStateFork(gossipFork) diff --git a/beacon_chain/nimbus_light_client.nim b/beacon_chain/nimbus_light_client.nim index 76e02105fd..5d56c426f8 100644 --- a/beacon_chain/nimbus_light_client.nim +++ b/beacon_chain/nimbus_light_client.nim @@ -13,7 +13,7 @@ import ./gossip_processing/optimistic_processor, ./networking/topic_params, ./spec/beaconstate, - ./spec/datatypes/[phase0, altair, bellatrix], + ./spec/datatypes/[phase0, altair, bellatrix, capella], "."/[filepath, light_client, light_client_db, nimbus_binary_common, version] from ./consensus_object_pools/consensus_manager import runForkchoiceUpdated @@ -151,6 +151,11 @@ programMain: proc (signedBlock: bellatrix.SignedBeaconBlock): ValidationResult = toValidationResult( optimisticProcessor.processSignedBeaconBlock(signedBlock))) + network.addValidator( + getBeaconBlocksTopic(forkDigests.capella), + proc (signedBlock: capella.SignedBeaconBlock): ValidationResult = + toValidationResult( + optimisticProcessor.processSignedBeaconBlock(signedBlock))) lightClient.installMessageValidators() waitFor network.startListening() waitFor network.start() @@ -247,14 +252,14 @@ programMain: oldGossipForks = currentGossipState - targetGossipState for gossipFork in oldGossipForks: - if gossipFork >= BeaconStateFork.Capella: + if gossipFork >= BeaconStateFork.EIP4844: # Format is still in development, do not use Gossip at this time. continue let forkDigest = forkDigests[].atStateFork(gossipFork) network.unsubscribe(getBeaconBlocksTopic(forkDigest)) for gossipFork in newGossipForks: - if gossipFork >= BeaconStateFork.Capella: + if gossipFork >= BeaconStateFork.EIP4844: # Format is still in development, do not use Gossip at this time. continue let forkDigest = forkDigests[].atStateFork(gossipFork) diff --git a/beacon_chain/spec/datatypes/capella.nim b/beacon_chain/spec/datatypes/capella.nim index 140a0b86fe..052951f7b9 100644 --- a/beacon_chain/spec/datatypes/capella.nim +++ b/beacon_chain/spec/datatypes/capella.nim @@ -19,14 +19,24 @@ else: {.push raises: [].} import - stew/byteutils, + chronicles, + stew/[bitops2, byteutils], json_serialization, + ssz_serialization/[merkleization, proofs], ssz_serialization/types as sszTypes, ../digest, "."/[base, phase0, altair, bellatrix] export json_serialization, base +const + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/sync-protocol.md#constants + # This index is rooted in `BeaconBlockBody`. + # The first member (`randao_reveal`) is 16, subsequent members +1 each. + # If there are ever more than 16 members in `BeaconBlockBody`, indices change! + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/ssz/merkle-proofs.md + EXECUTION_PAYLOAD_INDEX* = 25.GeneralizedIndex # `execution_payload` + type SignedBLSToExecutionChangeList* = List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] @@ -99,6 +109,105 @@ type ExecutePayload* = proc( execution_payload: ExecutionPayload): bool {.gcsafe, raises: [Defect].} + ExecutionBranch* = + array[log2trunc(EXECUTION_PAYLOAD_INDEX), Eth2Digest] + + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/sync-protocol.md#lightclientheader + LightClientHeader* = object + beacon*: BeaconBlockHeader + ## Beacon block header + + execution*: ExecutionPayloadHeader + ## Execution payload header corresponding to `beacon.body_root` (from Capella onward) + execution_branch*: ExecutionBranch + + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/altair/light-client/sync-protocol.md#lightclientbootstrap + LightClientBootstrap* = object + header*: LightClientHeader + ## Header matching the requested beacon block root + + current_sync_committee*: SyncCommittee + ## Current sync committee corresponding to `header.beacon.state_root` + current_sync_committee_branch*: altair.CurrentSyncCommitteeBranch + + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/altair/light-client/sync-protocol.md#lightclientupdate + LightClientUpdate* = object + attested_header*: LightClientHeader + ## Header attested to by the sync committee + + next_sync_committee*: SyncCommittee + ## Next sync committee corresponding to `attested_header.beacon.state_root` + next_sync_committee_branch*: altair.NextSyncCommitteeBranch + + # Finalized header corresponding to `attested_header.beacon.state_root` + finalized_header*: LightClientHeader + finality_branch*: altair.FinalityBranch + + sync_aggregate*: SyncAggregate + ## Sync committee aggregate signature + signature_slot*: Slot + ## Slot at which the aggregate signature was created (untrusted) + + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/altair/light-client/sync-protocol.md#lightclientfinalityupdate + LightClientFinalityUpdate* = object + # Header attested to by the sync committee + attested_header*: LightClientHeader + + # Finalized header corresponding to `attested_header.beacon.state_root` + finalized_header*: LightClientHeader + finality_branch*: altair.FinalityBranch + + # Sync committee aggregate signature + sync_aggregate*: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot*: Slot + + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/altair/light-client/sync-protocol.md#lightclientoptimisticupdate + LightClientOptimisticUpdate* = object + # Header attested to by the sync committee + attested_header*: LightClientHeader + + # Sync committee aggregate signature + sync_aggregate*: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot*: Slot + + SomeLightClientUpdateWithSyncCommittee* = + LightClientUpdate + + SomeLightClientUpdateWithFinality* = + LightClientUpdate | + LightClientFinalityUpdate + + SomeLightClientUpdate* = + LightClientUpdate | + LightClientFinalityUpdate | + LightClientOptimisticUpdate + + SomeLightClientObject* = + LightClientBootstrap | + SomeLightClientUpdate + + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/altair/light-client/sync-protocol.md#lightclientstore + LightClientStore* = object + finalized_header*: LightClientHeader + ## Header that is finalized + + current_sync_committee*: SyncCommittee + ## Sync committees corresponding to the finalized header + next_sync_committee*: SyncCommittee + + best_valid_update*: Opt[LightClientUpdate] + ## Best available header to switch finalized head to if we see nothing else + + optimistic_header*: LightClientHeader + ## Most recent available reasonably-safe header + + previous_max_active_participants*: uint64 + ## Max number of active participants in a sync committee (used to compute + ## safety threshold) + current_max_active_participants*: uint64 + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-alpha.1/specs/capella/beacon-chain.md#beaconstate BeaconState* = object # Versioning @@ -399,6 +508,202 @@ func shortLog*(v: SomeSignedBeaconBlock): auto = signature: shortLog(v.signature) ) +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/sync-protocol.md#get_lc_execution_root +func get_lc_execution_root*( + header: LightClientHeader, cfg: RuntimeConfig): Eth2Digest = + let epoch = header.beacon.slot.epoch + + if epoch >= cfg.CAPELLA_FORK_EPOCH: + return hash_tree_root(header.execution) + + ZERO_HASH + +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/sync-protocol.md#is_valid_light_client_header +func is_valid_light_client_header*( + header: LightClientHeader, cfg: RuntimeConfig): bool = + let epoch = header.beacon.slot.epoch + + 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_INDEX), + get_subtree_index(EXECUTION_PAYLOAD_INDEX), + header.beacon.body_root) + +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/fork.md#upgrade_lc_header_to_capella +func upgrade_lc_header_to_capella*( + pre: altair.LightClientHeader): LightClientHeader = + LightClientHeader( + beacon: pre.beacon) + +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/fork.md#upgrade_lc_bootstrap_to_capella +func upgrade_lc_bootstrap_to_capella*( + pre: altair.LightClientBootstrap): LightClientBootstrap = + LightClientBootstrap( + header: upgrade_lc_header_to_capella(pre.header), + current_sync_committee: pre.current_sync_committee, + current_sync_committee_branch: pre.current_sync_committee_branch) + +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/fork.md#upgrade_lc_update_to_capella +func upgrade_lc_update_to_capella*( + pre: altair.LightClientUpdate): LightClientUpdate = + LightClientUpdate( + attested_header: upgrade_lc_header_to_capella(pre.attested_header), + next_sync_committee: pre.next_sync_committee, + next_sync_committee_branch: pre.next_sync_committee_branch, + finalized_header: upgrade_lc_header_to_capella(pre.finalized_header), + finality_branch: pre.finality_branch, + sync_aggregate: pre.sync_aggregate, + signature_slot: pre.signature_slot) + +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/fork.md#upgrade_lc_finality_update_to_capella +func upgrade_lc_finality_update_to_capella*( + pre: altair.LightClientFinalityUpdate): LightClientFinalityUpdate = + LightClientFinalityUpdate( + attested_header: upgrade_lc_header_to_capella(pre.attested_header), + finalized_header: upgrade_lc_header_to_capella(pre.finalized_header), + finality_branch: pre.finality_branch, + sync_aggregate: pre.sync_aggregate, + signature_slot: pre.signature_slot) + +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/fork.md#upgrade_lc_optimistic_update_to_capella +func upgrade_lc_optimistic_update_to_capella*( + pre: altair.LightClientOptimisticUpdate): LightClientOptimisticUpdate = + LightClientOptimisticUpdate( + attested_header: upgrade_lc_header_to_capella(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) + +template toFull*( + update: SomeLightClientUpdate): LightClientUpdate = + when update is LightClientUpdate: + update + elif update is SomeLightClientUpdateWithFinality: + LightClientUpdate( + attested_header: update.attested_header, + finalized_header: update.finalized_header, + finality_branch: update.finality_branch, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot) + else: + LightClientUpdate( + attested_header: update.attested_header, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot) + +template toFinality*( + update: SomeLightClientUpdate): LightClientFinalityUpdate = + when update is LightClientFinalityUpdate: + update + elif update is SomeLightClientUpdateWithFinality: + LightClientFinalityUpdate( + attested_header: update.attested_header, + finalized_header: update.finalized_header, + finality_branch: update.finality_branch, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot) + else: + LightClientFinalityUpdate( + attested_header: update.attested_header, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot) + +template toOptimistic*( + update: SomeLightClientUpdate): LightClientOptimisticUpdate = + when update is LightClientOptimisticUpdate: + update + else: + LightClientOptimisticUpdate( + attested_header: update.attested_header, + sync_aggregate: update.sync_aggregate, + signature_slot: update.signature_slot) + +func matches*[A, B: SomeLightClientUpdate](a: A, b: B): bool = + if a.attested_header != b.attested_header: + return false + when a is SomeLightClientUpdateWithSyncCommittee and + b is SomeLightClientUpdateWithSyncCommittee: + if a.next_sync_committee != b.next_sync_committee: + return false + if a.next_sync_committee_branch != b.next_sync_committee_branch: + return false + when a is SomeLightClientUpdateWithFinality and + b is SomeLightClientUpdateWithFinality: + if a.finalized_header != b.finalized_header: + return false + if a.finality_branch != b.finality_branch: + return false + if a.sync_aggregate != b.sync_aggregate: + return false + if a.signature_slot != b.signature_slot: + return false + true + +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/fork.md#upgrade_lc_store_to_capella +func upgrade_lc_store_to_capella*( + pre: altair.LightClientStore): LightClientStore = + let best_valid_update = + if pre.best_valid_update.isNone: + Opt.none(LightClientUpdate) + else: + Opt.some upgrade_lc_update_to_capella(pre.best_valid_update.get) + LightClientStore( + finalized_header: upgrade_lc_header_to_capella(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_capella(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/forks.nim b/beacon_chain/spec/forks.nim index 1c64782525..c174153f79 100644 --- a/beacon_chain/spec/forks.nim +++ b/beacon_chain/spec/forks.nim @@ -535,6 +535,20 @@ template toFork*[T: t: type T): BeaconBlockFork = BeaconBlockFork.EIP4844 +template BeaconBlockBody*(kind: static BeaconBlockFork): auto = + when kind == BeaconBlockFork.EIP4844: + typedesc[eip4844.BeaconBlockBody] + elif kind == BeaconBlockFork.Capella: + typedesc[capella.BeaconBlockBody] + elif kind == BeaconBlockFork.Bellatrix: + typedesc[bellatrix.BeaconBlockBody] + elif kind == BeaconBlockFork.Altair: + typedesc[altair.BeaconBlockBody] + elif kind == BeaconBlockFork.Phase0: + typedesc[phase0.BeaconBlockBody] + else: + static: raiseAssert "Unreachable" + template init*(T: type ForkedEpochInfo, info: phase0.EpochInfo): T = T(kind: EpochInfoFork.Phase0, phase0Data: info) template init*(T: type ForkedEpochInfo, info: altair.EpochInfo): T = @@ -913,8 +927,10 @@ func nextForkEpochAtEpoch*(cfg: RuntimeConfig, epoch: Epoch): Epoch = of BeaconStateFork.Phase0: cfg.ALTAIR_FORK_EPOCH func lcDataForkAtStateFork*(stateFork: BeaconStateFork): LightClientDataFork = - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair - if stateFork >= BeaconStateFork.Altair: + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella + if stateFork >= BeaconStateFork.Capella: + LightClientDataFork.Capella + elif stateFork >= BeaconStateFork.Altair: LightClientDataFork.Altair else: LightClientDataFork.None diff --git a/beacon_chain/spec/forks_light_client.nim b/beacon_chain/spec/forks_light_client.nim index 1c544a6b29..547c6954a0 100644 --- a/beacon_chain/spec/forks_light_client.nim +++ b/beacon_chain/spec/forks_light_client.nim @@ -11,27 +11,34 @@ else: {.push raises: [].} import - ./datatypes/[phase0, altair, bellatrix, capella, eip4844] + ./datatypes/[phase0, altair, bellatrix, capella, eip4844], + ./eth2_merkleization type LightClientDataFork* {.pure.} = enum # Append only, used in DB data! None = 0, # only use non-0 in DB to detect accidentally uninitialized data - Altair = 1 + Altair = 1, + Capella = 2 ForkyLightClientHeader* = - altair.LightClientHeader + altair.LightClientHeader | + capella.LightClientHeader ForkyLightClientBootstrap* = - altair.LightClientBootstrap + altair.LightClientBootstrap | + capella.LightClientBootstrap ForkyLightClientUpdate* = - altair.LightClientUpdate + altair.LightClientUpdate | + capella.LightClientUpdate ForkyLightClientFinalityUpdate* = - altair.LightClientFinalityUpdate + altair.LightClientFinalityUpdate | + capella.LightClientFinalityUpdate ForkyLightClientOptimisticUpdate* = - altair.LightClientOptimisticUpdate + altair.LightClientOptimisticUpdate | + capella.LightClientOptimisticUpdate SomeForkyLightClientUpdateWithSyncCommittee* = ForkyLightClientUpdate @@ -50,7 +57,8 @@ type SomeForkyLightClientUpdate ForkyLightClientStore* = - altair.LightClientStore + altair.LightClientStore | + capella.LightClientStore ForkedLightClientHeader* = object case kind*: LightClientDataFork @@ -58,6 +66,8 @@ type discard of LightClientDataFork.Altair: altairData*: altair.LightClientHeader + of LightClientDataFork.Capella: + capellaData*: capella.LightClientHeader ForkedLightClientBootstrap* = object case kind*: LightClientDataFork @@ -65,6 +75,8 @@ type discard of LightClientDataFork.Altair: altairData*: altair.LightClientBootstrap + of LightClientDataFork.Capella: + capellaData*: capella.LightClientBootstrap ForkedLightClientUpdate* = object case kind*: LightClientDataFork @@ -72,6 +84,8 @@ type discard of LightClientDataFork.Altair: altairData*: altair.LightClientUpdate + of LightClientDataFork.Capella: + capellaData*: capella.LightClientUpdate ForkedLightClientFinalityUpdate* = object case kind*: LightClientDataFork @@ -79,6 +93,8 @@ type discard of LightClientDataFork.Altair: altairData*: altair.LightClientFinalityUpdate + of LightClientDataFork.Capella: + capellaData*: capella.LightClientFinalityUpdate ForkedLightClientOptimisticUpdate* = object case kind*: LightClientDataFork @@ -86,6 +102,8 @@ type discard of LightClientDataFork.Altair: altairData*: altair.LightClientOptimisticUpdate + of LightClientDataFork.Capella: + capellaData*: capella.LightClientOptimisticUpdate SomeForkedLightClientUpdateWithSyncCommittee* = ForkedLightClientUpdate @@ -109,11 +127,15 @@ type discard of LightClientDataFork.Altair: altairData*: altair.LightClientStore + of LightClientDataFork.Capella: + capellaData*: capella.LightClientStore func lcDataForkAtEpoch*( cfg: RuntimeConfig, epoch: Epoch): LightClientDataFork = - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair - if epoch >= cfg.ALTAIR_FORK_EPOCH: + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella + if epoch >= cfg.CAPELLA_FORK_EPOCH: + LightClientDataFork.Capella + elif epoch >= cfg.ALTAIR_FORK_EPOCH: LightClientDataFork.Altair else: LightClientDataFork.None @@ -128,38 +150,60 @@ template kind*( altair.LightClientStore]): LightClientDataFork = LightClientDataFork.Altair +template kind*( + x: typedesc[ # `SomeLightClientObject` doesn't work here (Nim 1.6) + capella.LightClientHeader | + capella.LightClientBootstrap | + capella.LightClientUpdate | + capella.LightClientFinalityUpdate | + capella.LightClientOptimisticUpdate | + capella.LightClientStore]): LightClientDataFork = + LightClientDataFork.Capella + template LightClientHeader*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + typedesc[capella.LightClientHeader] + elif kind == LightClientDataFork.Altair: typedesc[altair.LightClientHeader] else: static: raiseAssert "Unreachable" template LightClientBootstrap*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + typedesc[capella.LightClientBootstrap] + elif kind == LightClientDataFork.Altair: typedesc[altair.LightClientBootstrap] else: static: raiseAssert "Unreachable" template LightClientUpdate*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + typedesc[capella.LightClientUpdate] + elif kind == LightClientDataFork.Altair: typedesc[altair.LightClientUpdate] else: static: raiseAssert "Unreachable" template LightClientFinalityUpdate*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + typedesc[capella.LightClientFinalityUpdate] + elif kind == LightClientDataFork.Altair: typedesc[altair.LightClientFinalityUpdate] else: static: raiseAssert "Unreachable" template LightClientOptimisticUpdate*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + typedesc[capella.LightClientOptimisticUpdate] + elif kind == LightClientDataFork.Altair: typedesc[altair.LightClientOptimisticUpdate] else: static: raiseAssert "Unreachable" template LightClientStore*(kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + typedesc[capella.LightClientStore] + elif kind == LightClientDataFork.Altair: typedesc[altair.LightClientStore] else: static: raiseAssert "Unreachable" @@ -214,7 +258,10 @@ template Forked*(x: typedesc[ForkyLightClientStore]): auto = template withAll*( x: typedesc[LightClientDataFork], body: untyped): untyped = - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella + block: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + body block: const lcDataFork {.inject, used.} = LightClientDataFork.Altair body @@ -225,6 +272,9 @@ template withAll*( template withLcDataFork*( x: LightClientDataFork, body: untyped): untyped = case x + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair body @@ -235,6 +285,10 @@ template withLcDataFork*( template withForkyHeader*( x: ForkedLightClientHeader, body: untyped): untyped = case x.kind + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + template forkyHeader: untyped {.inject, used.} = x.capellaData + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair template forkyHeader: untyped {.inject, used.} = x.altairData @@ -246,6 +300,10 @@ template withForkyHeader*( template withForkyBootstrap*( x: ForkedLightClientBootstrap, body: untyped): untyped = case x.kind + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + template forkyBootstrap: untyped {.inject, used.} = x.capellaData + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair template forkyBootstrap: untyped {.inject, used.} = x.altairData @@ -257,6 +315,10 @@ template withForkyBootstrap*( template withForkyUpdate*( x: ForkedLightClientUpdate, body: untyped): untyped = case x.kind + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + template forkyUpdate: untyped {.inject, used.} = x.capellaData + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair template forkyUpdate: untyped {.inject, used.} = x.altairData @@ -268,6 +330,10 @@ template withForkyUpdate*( template withForkyFinalityUpdate*( x: ForkedLightClientFinalityUpdate, body: untyped): untyped = case x.kind + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + template forkyFinalityUpdate: untyped {.inject, used.} = x.capellaData + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair template forkyFinalityUpdate: untyped {.inject, used.} = x.altairData @@ -279,6 +345,10 @@ template withForkyFinalityUpdate*( template withForkyOptimisticUpdate*( x: ForkedLightClientOptimisticUpdate, body: untyped): untyped = case x.kind + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + template forkyOptimisticUpdate: untyped {.inject, used.} = x.capellaData + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair template forkyOptimisticUpdate: untyped {.inject, used.} = x.altairData @@ -290,6 +360,10 @@ template withForkyOptimisticUpdate*( template withForkyObject*( x: SomeForkedLightClientObject, body: untyped): untyped = case x.kind + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + template forkyObject: untyped {.inject, used.} = x.capellaData + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair template forkyObject: untyped {.inject, used.} = x.altairData @@ -301,6 +375,10 @@ template withForkyObject*( template withForkyStore*( x: ForkedLightClientStore, body: untyped): untyped = case x.kind + of LightClientDataFork.Capella: + const lcDataFork {.inject, used.} = LightClientDataFork.Capella + template forkyStore: untyped {.inject, used.} = x.capellaData + body of LightClientDataFork.Altair: const lcDataFork {.inject, used.} = LightClientDataFork.Altair template forkyStore: untyped {.inject, used.} = x.altairData @@ -366,7 +444,9 @@ template forky*( SomeForkedLightClientObject | ForkedLightClientStore, kind: static LightClientDataFork): untyped = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + x.capellaData + elif kind == LightClientDataFork.Altair: x.altairData else: static: raiseAssert "Unreachable" @@ -387,7 +467,15 @@ func migrateToDataFork*( x = ForkedLightClientHeader( kind: LightClientDataFork.Altair) - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair + # Upgrade to Capella + when newKind >= LightClientDataFork.Capella: + if x.kind == LightClientDataFork.Altair: + x = ForkedLightClientHeader( + kind: LightClientDataFork.Capella, + capellaData: upgrade_lc_header_to_capella( + x.forky(LightClientDataFork.Altair))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella doAssert x.kind == newKind func migrateToDataFork*( @@ -406,7 +494,15 @@ func migrateToDataFork*( x = ForkedLightClientBootstrap( kind: LightClientDataFork.Altair) - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair + # Upgrade to Capella + when newKind >= LightClientDataFork.Capella: + if x.kind == LightClientDataFork.Altair: + x = ForkedLightClientBootstrap( + kind: LightClientDataFork.Capella, + capellaData: upgrade_lc_bootstrap_to_capella( + x.forky(LightClientDataFork.Altair))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella doAssert x.kind == newKind func migrateToDataFork*( @@ -425,7 +521,15 @@ func migrateToDataFork*( x = ForkedLightClientUpdate( kind: LightClientDataFork.Altair) - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair + # Upgrade to Capella + when newKind >= LightClientDataFork.Capella: + if x.kind == LightClientDataFork.Altair: + x = ForkedLightClientUpdate( + kind: LightClientDataFork.Capella, + capellaData: upgrade_lc_update_to_capella( + x.forky(LightClientDataFork.Altair))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella doAssert x.kind == newKind func migrateToDataFork*( @@ -444,7 +548,15 @@ func migrateToDataFork*( x = ForkedLightClientFinalityUpdate( kind: LightClientDataFork.Altair) - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair + # Upgrade to Capella + when newKind >= LightClientDataFork.Capella: + if x.kind == LightClientDataFork.Altair: + x = ForkedLightClientFinalityUpdate( + kind: LightClientDataFork.Capella, + capellaData: upgrade_lc_finality_update_to_capella( + x.forky(LightClientDataFork.Altair))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella doAssert x.kind == newKind func migrateToDataFork*( @@ -463,7 +575,15 @@ func migrateToDataFork*( x = ForkedLightClientOptimisticUpdate( kind: LightClientDataFork.Altair) - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair + # Upgrade to Capella + when newKind >= LightClientDataFork.Capella: + if x.kind == LightClientDataFork.Altair: + x = ForkedLightClientOptimisticUpdate( + kind: LightClientDataFork.Capella, + capellaData: upgrade_lc_optimistic_update_to_capella( + x.forky(LightClientDataFork.Altair))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella doAssert x.kind == newKind func migrateToDataFork*( @@ -482,7 +602,15 @@ func migrateToDataFork*( x = ForkedLightClientStore( kind: LightClientDataFork.Altair) - static: doAssert LightClientDataFork.high == LightClientDataFork.Altair + # Upgrade to Capella + when newKind >= LightClientDataFork.Capella: + if x.kind == LightClientDataFork.Altair: + x = ForkedLightClientStore( + kind: LightClientDataFork.Capella, + capellaData: upgrade_lc_store_to_capella( + x.forky(LightClientDataFork.Altair))) + + static: doAssert LightClientDataFork.high == LightClientDataFork.Capella doAssert x.kind == newKind func migratingToDataFork*[ @@ -500,13 +628,54 @@ func toAltairLightClientHeader( blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock | altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock | - bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock | - capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock | - eip4844.SignedBeaconBlock | eip4844.TrustedSignedBeaconBlock + bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock ): altair.LightClientHeader = altair.LightClientHeader( beacon: blck.message.toBeaconBlockHeader()) +# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.1/specs/capella/light-client/full-node.md#block_to_light_client_header +func toCapellaLightClientHeader( + blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) + phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock | + altair.SignedBeaconBlock | altair.TrustedSignedBeaconBlock | + bellatrix.SignedBeaconBlock | bellatrix.TrustedSignedBeaconBlock +): capella.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. + capella.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader()) + +func toCapellaLightClientHeader( + blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) + capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock | + eip4844.SignedBeaconBlock | eip4844.TrustedSignedBeaconBlock +): capella.LightClientHeader = + template payload: untyped = blck.message.body.execution_payload + capella.LightClientHeader( + beacon: blck.message.toBeaconBlockHeader(), + execution: capella.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_INDEX).get) + func toLightClientHeader*( blck: # `SomeSignedBeaconBlock` doesn't work here (Nim 1.6) phase0.SignedBeaconBlock | phase0.TrustedSignedBeaconBlock | @@ -515,7 +684,9 @@ func toLightClientHeader*( capella.SignedBeaconBlock | capella.TrustedSignedBeaconBlock | eip4844.SignedBeaconBlock | eip4844.TrustedSignedBeaconBlock, kind: static LightClientDataFork): auto = - when kind == LightClientDataFork.Altair: + when kind == LightClientDataFork.Capella: + blck.toCapellaLightClientHeader() + elif kind == LightClientDataFork.Altair: blck.toAltairLightClientHeader() else: static: raiseAssert "Unreachable" diff --git a/tests/consensus_spec/capella/test_fixture_ssz_consensus_objects.nim b/tests/consensus_spec/capella/test_fixture_ssz_consensus_objects.nim index af1b91f29a..4c44de2f82 100644 --- a/tests/consensus_spec/capella/test_fixture_ssz_consensus_objects.nim +++ b/tests/consensus_spec/capella/test_fixture_ssz_consensus_objects.nim @@ -122,15 +122,15 @@ suite "EF - Capella - SSZ consensus objects " & preset(): of "HistoricalSummary": checkSSZ(HistoricalSummary, path, hash) of "IndexedAttestation": checkSSZ(IndexedAttestation, path, hash) of "LightClientBootstrap": - checkSSZ(altair.LightClientBootstrap, path, hash) + discard # checkSSZ(capella.LightClientBootstrap, path, hash) of "LightClientHeader": - checkSSZ(altair.LightClientHeader, path, hash) + discard # checkSSZ(capella.LightClientHeader, path, hash) of "LightClientUpdate": - checkSSZ(altair.LightClientUpdate, path, hash) + discard # checkSSZ(capella.LightClientUpdate, path, hash) of "LightClientFinalityUpdate": - checkSSZ(altair.LightClientFinalityUpdate, path, hash) + discard # checkSSZ(capella.LightClientFinalityUpdate, path, hash) of "LightClientOptimisticUpdate": - checkSSZ(altair.LightClientOptimisticUpdate, path, hash) + discard # checkSSZ(capella.LightClientOptimisticUpdate, path, hash) of "PendingAttestation": checkSSZ(PendingAttestation, path, hash) of "PowBlock": checkSSZ(PowBlock, path, hash) of "ProposerSlashing": checkSSZ(ProposerSlashing, path, hash) diff --git a/tests/consensus_spec/eip4844/test_fixture_ssz_consensus_objects.nim b/tests/consensus_spec/eip4844/test_fixture_ssz_consensus_objects.nim index d63cbea8ed..e5a03adbd5 100644 --- a/tests/consensus_spec/eip4844/test_fixture_ssz_consensus_objects.nim +++ b/tests/consensus_spec/eip4844/test_fixture_ssz_consensus_objects.nim @@ -126,15 +126,15 @@ suite "EF - EIP4844 - SSZ consensus objects " & preset(): of "HistoricalSummary": checkSSZ(HistoricalSummary, path, hash) of "IndexedAttestation": checkSSZ(IndexedAttestation, path, hash) of "LightClientBootstrap": - checkSSZ(altair.LightClientBootstrap, path, hash) + discard # checkSSZ(capella.LightClientBootstrap, path, hash) of "LightClientHeader": - checkSSZ(altair.LightClientHeader, path, hash) + discard # checkSSZ(capella.LightClientHeader, path, hash) of "LightClientUpdate": - checkSSZ(altair.LightClientUpdate, path, hash) + discard # checkSSZ(capella.LightClientUpdate, path, hash) of "LightClientFinalityUpdate": - checkSSZ(altair.LightClientFinalityUpdate, path, hash) + discard # checkSSZ(capella.LightClientFinalityUpdate, path, hash) of "LightClientOptimisticUpdate": - checkSSZ(altair.LightClientOptimisticUpdate, path, hash) + discard # checkSSZ(capella.LightClientOptimisticUpdate, path, hash) of "PendingAttestation": checkSSZ(PendingAttestation, path, hash) of "PowBlock": checkSSZ(PowBlock, path, hash) of "ProposerSlashing": checkSSZ(ProposerSlashing, path, hash) diff --git a/tests/consensus_spec/test_fixture_light_client_single_merkle_proof.nim b/tests/consensus_spec/test_fixture_light_client_single_merkle_proof.nim index c1f6ac2928..2bb077e9e3 100644 --- a/tests/consensus_spec/test_fixture_light_client_single_merkle_proof.nim +++ b/tests/consensus_spec/test_fixture_light_client_single_merkle_proof.nim @@ -67,6 +67,8 @@ suite "EF - Light client - Single merkle proof" & preset(): const blockFork = stateFork.toBeaconBlockFork() for kind, path in walkDir(suitePath, relative = true, checkDir = true): case objName + of "BeaconBlockBody": + runTest(suitePath/path, blockFork.BeaconBlockBody) of "BeaconState": runTest(suitePath/path, stateFork.BeaconState) else: diff --git a/tests/consensus_spec/test_fixture_light_client_sync.nim b/tests/consensus_spec/test_fixture_light_client_sync.nim index 0eedfca6f1..b77394fe4a 100644 --- a/tests/consensus_spec/test_fixture_light_client_sync.nim +++ b/tests/consensus_spec/test_fixture_light_client_sync.nim @@ -10,6 +10,8 @@ import # Standard library std/[json, os, streams], + # Status libraries + stew/byteutils, # Third-party yaml, # Beacon chain internals @@ -20,18 +22,24 @@ import type TestMeta = object - genesis_validators_root: string - trusted_block_root: string + genesis_validators_root: Eth2Digest + trusted_block_root: Eth2Digest + fork_digests: ForkDigests + bootstrap_fork_digest: ForkDigest + store_fork_digest: ForkDigest TestChecks = object finalized_slot: Slot finalized_beacon_root: Eth2Digest + finalized_execution_root: Eth2Digest optimistic_slot: Slot optimistic_beacon_root: Eth2Digest + optimistic_execution_root: Eth2Digest TestStepKind {.pure.} = enum ForceUpdate ProcessUpdate + UpgradeStore TestStep = object case kind: TestStepKind @@ -39,6 +47,8 @@ type discard of TestStepKind.ProcessUpdate: update: ForkedLightClientUpdate + of TestStepKind.UpgradeStore: + store_data_fork: LightClientDataFork current_slot: Slot checks: TestChecks @@ -54,10 +64,14 @@ proc loadSteps(path: string, fork_digests: ForkDigests): seq[TestStep] = c["finalized_header"]["slot"].getInt().Slot, finalized_beacon_root: Eth2Digest.fromHex(c["finalized_header"]["beacon_root"].getStr()), + finalized_execution_root: + Eth2Digest.fromHex(c["finalized_header"]{"execution_root"}.getStr()), optimistic_slot: c["optimistic_header"]["slot"].getInt().Slot, optimistic_beacon_root: - Eth2Digest.fromHex(c["optimistic_header"]["beacon_root"].getStr())) + Eth2Digest.fromHex(c["optimistic_header"]["beacon_root"].getStr()), + optimistic_execution_root: + Eth2Digest.fromHex(c["optimistic_header"]{"execution_root"}.getStr())) if step.hasKey"force_update": let s = step["force_update"] @@ -69,7 +83,9 @@ proc loadSteps(path: string, fork_digests: ForkDigests): seq[TestStep] = elif step.hasKey"process_update": let s = step["process_update"] - update_fork_digest = fork_digests.altair + update_fork_digest = + distinctBase(ForkDigest).fromHex(s{"update_fork_digest"}.getStr( + distinctBase(fork_digests.altair).toHex())).ForkDigest update_state_fork = fork_digests.stateForkForDigest(update_fork_digest) .expect("Unknown update fork " & $update_fork_digest) @@ -89,57 +105,103 @@ proc loadSteps(path: string, fork_digests: ForkDigests): seq[TestStep] = update: update, current_slot: s["current_slot"].getInt().Slot, checks: s["checks"].getChecks()) + elif step.hasKey"upgrade_store": + let + s = step["upgrade_store"] + store_fork_digest = + distinctBase(ForkDigest).fromHex( + s["store_fork_digest"].getStr()).ForkDigest + store_state_fork = + fork_digests.stateForkForDigest(store_fork_digest) + .expect("Unknown store fork " & $store_fork_digest) + + result.add TestStep( + kind: TestStepKind.UpgradeStore, + store_data_fork: lcDataForkAtStateFork(store_state_fork), + checks: s["checks"].getChecks()) else: doAssert false, "Unknown test step: " & $step proc runTest(path: string) = test "Light client - Sync - " & path.relativePath(SszTestsDir): + # Reduce stack size by making this a `proc` + proc loadTestMeta(): (RuntimeConfig, TestMeta) = + let (cfg, unknowns) = readRuntimeConfig(path/"config.yaml") + doAssert unknowns.len == 0, "Unknown config constants: " & $unknowns + + type TestMetaYaml {.sparse.} = object + genesis_validators_root: string + trusted_block_root: string + bootstrap_fork_digest: Option[string] + store_fork_digest: Option[string] + let + meta = block: + var s = openFileStream(path/"meta.yaml") + defer: close(s) + var res: TestMetaYaml + yaml.load(s, res) + res + genesis_validators_root = + Eth2Digest.fromHex(meta.genesis_validators_root) + trusted_block_root = + Eth2Digest.fromHex(meta.trusted_block_root) + fork_digests = + ForkDigests.init(cfg, genesis_validators_root) + bootstrap_fork_digest = + distinctBase(ForkDigest).fromHex(meta.bootstrap_fork_digest.get( + distinctBase(fork_digests.altair).toHex())).ForkDigest + store_fork_digest = + distinctBase(ForkDigest).fromHex(meta.store_fork_digest.get( + distinctBase(fork_digests.altair).toHex())).ForkDigest + + (cfg, TestMeta( + genesis_validators_root: genesis_validators_root, + trusted_block_root: trusted_block_root, + fork_digests: fork_digests, + bootstrap_fork_digest: bootstrap_fork_digest, + store_fork_digest: store_fork_digest)) + let - (cfg, unknowns) = readRuntimeConfig(path/"config.yaml") - meta = block: - var s = openFileStream(path/"meta.yaml") - defer: close(s) - var res: TestMeta - yaml.load(s, res) - res - genesis_validators_root = - Eth2Digest.fromHex(meta.genesis_validators_root) - trusted_block_root = - Eth2Digest.fromHex(meta.trusted_block_root) - fork_digests = - ForkDigests.init(cfg, genesis_validators_root) - bootstrap_fork_digest = fork_digests.altair - store_fork_digest = fork_digests.altair - bootstrap_state_fork = - fork_digests.stateForkForDigest(bootstrap_fork_digest) - .expect("Unknown bootstrap fork " & $bootstrap_fork_digest) - store_state_fork = - fork_digests.stateForkForDigest(store_fork_digest) - .expect("Unknown store fork " & $store_fork_digest) - steps = loadSteps(path, fork_digests) - - doAssert unknowns.len == 0, "Unknown config constants: " & $unknowns - - var bootstrap {.noinit.}: ForkedLightClientBootstrap - withLcDataFork(lcDataForkAtStateFork(bootstrap_state_fork)): - when lcDataFork > LightClientDataFork.None: - bootstrap = ForkedLightClientBootstrap(kind: lcDataFork) - bootstrap.forky(lcDataFork) = parseTest( - path/"bootstrap.ssz_snappy", SSZ, - lcDataFork.LightClientBootstrap) - else: raiseAssert "Unsupported bootstrap fork " & $bootstrap_fork_digest - - var store {.noinit.}: ForkedLightClientStore - withLcDataFork(lcDataForkAtStateFork(store_state_fork)): - when lcDataFork > LightClientDataFork.None: - store = ForkedLightClientStore(kind: lcDataFork) - check bootstrap.kind <= lcDataFork - let upgradedBootstrap = bootstrap.migratingToDataFork(lcDataFork) - store.forky(lcDataFork) = initialize_light_client_store( - trusted_block_root, upgradedBootstrap.forky(lcDataFork), cfg).get - else: raiseAssert "Unreachable store fork " & $store_fork_digest + (cfg, meta) = loadTestMeta() + steps = loadSteps(path, meta.fork_digests) - for step in steps: + # Reduce stack size by making this a `proc` + proc loadBootstrap(): ForkedLightClientBootstrap = + let bootstrap_state_fork = + meta.fork_digests.stateForkForDigest(meta.bootstrap_fork_digest) + .expect("Unknown bootstrap fork " & $meta.bootstrap_fork_digest) + var bootstrap {.noinit.}: ForkedLightClientBootstrap + withLcDataFork(lcDataForkAtStateFork(bootstrap_state_fork)): + when lcDataFork > LightClientDataFork.None: + bootstrap = ForkedLightClientBootstrap(kind: lcDataFork) + bootstrap.forky(lcDataFork) = parseTest( + path/"bootstrap.ssz_snappy", SSZ, + lcDataFork.LightClientBootstrap) + else: + raiseAssert "Unknown bootstrap fork " & $meta.bootstrap_fork_digest + bootstrap + + # Reduce stack size by making this a `proc` + proc initializeStore( + bootstrap: ref ForkedLightClientBootstrap): ForkedLightClientStore = + let store_state_fork = + meta.fork_digests.stateForkForDigest(meta.store_fork_digest) + .expect("Unknown store fork " & $meta.store_fork_digest) + var store {.noinit.}: ForkedLightClientStore + withLcDataFork(lcDataForkAtStateFork(store_state_fork)): + when lcDataFork > LightClientDataFork.None: + store = ForkedLightClientStore(kind: lcDataFork) + bootstrap[].migrateToDataFork(lcDataFork) + store.forky(lcDataFork) = initialize_light_client_store( + meta.trusted_block_root, bootstrap[].forky(lcDataFork), cfg).get + else: raiseAssert "Unreachable store fork " & $meta.store_fork_digest + store + + let bootstrap = newClone(loadBootstrap()) + var store = initializeStore(bootstrap) + + # Reduce stack size by making this a `proc` + proc processStep(step: TestStep) = withForkyStore(store): when lcDataFork > LightClientDataFork.None: case step.kind @@ -152,8 +214,13 @@ proc runTest(path: string) = upgradedUpdate = step.update.migratingToDataFork(lcDataFork) res = process_light_client_update( forkyStore, upgradedUpdate.forky(lcDataFork), step.current_slot, - cfg, genesis_validators_root) + cfg, meta.genesis_validators_root) check res.isOk + of TestStepKind.UpgradeStore: + check step.store_data_fork >= lcDataFork + withLcDataFork(step.store_data_fork): + when lcDataFork > LightClientDataFork.None: + store.migrateToDataFork(lcDataFork) else: raiseAssert "Unreachable" withForkyStore(store): @@ -163,17 +230,32 @@ proc runTest(path: string) = forkyStore.finalized_header.beacon.slot finalized_beacon_root = hash_tree_root(forkyStore.finalized_header.beacon) + finalized_execution_root = + when lcDataFork >= LightClientDataFork.Capella: + get_lc_execution_root(forkyStore.finalized_header, cfg) + else: + ZERO_HASH optimistic_slot = forkyStore.optimistic_header.beacon.slot optimistic_beacon_root = hash_tree_root(forkyStore.optimistic_header.beacon) + optimistic_execution_root = + when lcDataFork >= LightClientDataFork.Capella: + get_lc_execution_root(forkyStore.optimistic_header, cfg) + else: + ZERO_HASH check: finalized_slot == step.checks.finalized_slot finalized_beacon_root == step.checks.finalized_beacon_root + finalized_execution_root == step.checks.finalized_execution_root optimistic_slot == step.checks.optimistic_slot optimistic_beacon_root == step.checks.optimistic_beacon_root + optimistic_execution_root == step.checks.optimistic_execution_root else: raiseAssert "Unreachable" + for step in steps: + processStep(step) + suite "EF - Light client - Sync" & preset(): const presetPath = SszTestsDir/const_preset for kind, path in walkDir(presetPath, relative = true, checkDir = true): diff --git a/tests/test_light_client_processor.nim b/tests/test_light_client_processor.nim index d6b877d01c..3ff95cbbb0 100644 --- a/tests/test_light_client_processor.nim +++ b/tests/test_light_client_processor.nim @@ -152,24 +152,25 @@ suite "Light client processor" & preset(): # Reduce stack size by making this a `proc` proc applyPeriodWithoutSupermajority(period: SyncCommitteePeriod) = - let update = dag.getLightClientUpdateForPeriod(period) - check update.kind > LightClientDataFork.None - withForkyUpdate(update): + let update = newClone(dag.getLightClientUpdateForPeriod(period)) + check update[].kind > LightClientDataFork.None + withForkyUpdate(update[]): when lcDataFork > LightClientDataFork.None: setTimeToSlot(forkyUpdate.signature_slot) for i in 0 ..< 2: res = processor[].storeObject( - MsgSource.gossip, getBeaconTime(), update) - check update.kind <= store[].kind + MsgSource.gossip, getBeaconTime(), update[]) + check update[].kind <= store[].kind if finalizationMode == LightClientFinalizationMode.Optimistic or period == lastPeriodWithSupermajority + 1: if finalizationMode == LightClientFinalizationMode.Optimistic or i == 0: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isOk forkyStore.best_valid_update.isSome @@ -177,8 +178,9 @@ suite "Light client processor" & preset(): else: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isErr res.error == VerifierError.Duplicate @@ -187,8 +189,9 @@ suite "Light client processor" & preset(): else: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isErr res.error == VerifierError.MissingParent @@ -197,14 +200,15 @@ suite "Light client processor" & preset(): proc applyDuplicate() = # Reduce stack size by making this a `proc` res = processor[].storeObject( - MsgSource.gossip, getBeaconTime(), update) - check update.kind <= store[].kind + MsgSource.gossip, getBeaconTime(), update[]) + check update[].kind <= store[].kind if finalizationMode == LightClientFinalizationMode.Optimistic or period == lastPeriodWithSupermajority + 1: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isErr res.error == VerifierError.Duplicate @@ -213,8 +217,9 @@ suite "Light client processor" & preset(): else: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isErr res.error == VerifierError.MissingParent @@ -229,13 +234,14 @@ suite "Light client processor" & preset(): time += chronos.minutes(15) res = processor[].storeObject( - MsgSource.gossip, getBeaconTime(), update) - check update.kind <= store[].kind + MsgSource.gossip, getBeaconTime(), update[]) + check update[].kind <= store[].kind if finalizationMode == LightClientFinalizationMode.Optimistic: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isErr res.error == VerifierError.Duplicate @@ -247,8 +253,9 @@ suite "Light client processor" & preset(): elif period == lastPeriodWithSupermajority + 1: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isErr res.error == VerifierError.Duplicate @@ -257,8 +264,9 @@ suite "Light client processor" & preset(): else: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check: res.isErr res.error == VerifierError.MissingParent @@ -267,14 +275,16 @@ suite "Light client processor" & preset(): if finalizationMode == LightClientFinalizationMode.Optimistic: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check forkyStore.finalized_header == forkyUpdate.attested_header else: withForkyStore(store[]): when lcDataFork > LightClientDataFork.None: - let upgraded = update.migratingToDataFork(lcDataFork) - template forkyUpdate: untyped = upgraded.forky(lcDataFork) + let upgraded = newClone( + update[].migratingToDataFork(lcDataFork)) + template forkyUpdate: untyped = upgraded[].forky(lcDataFork) check forkyStore.finalized_header != forkyUpdate.attested_header for period in lastPeriodWithSupermajority + 1 .. highPeriod: