From 937cc62b856eca3446bb90a83186e21f4ab38954 Mon Sep 17 00:00:00 2001 From: tersec Date: Sun, 7 Apr 2024 07:58:11 +0000 Subject: [PATCH] block_sim runs electra (#6181) --- .../consensus_manager.nim | 60 +++++++------ .../gossip_processing/block_processor.nim | 7 +- beacon_chain/spec/beaconstate.nim | 85 +++++++++++++++++++ .../eth2_apis/eth2_rest_serialization.nim | 5 +- beacon_chain/spec/state_transition.nim | 13 +++ research/block_sim.nim | 77 +++++++++++++++-- research/simutils.nim | 5 +- .../test_fixture_fork_choice.nim | 23 ++--- tests/test_light_client_processor.nim | 1 - 9 files changed, 214 insertions(+), 62 deletions(-) diff --git a/beacon_chain/consensus_object_pools/consensus_manager.nim b/beacon_chain/consensus_object_pools/consensus_manager.nim index 3502c4398..50fcec775 100644 --- a/beacon_chain/consensus_object_pools/consensus_manager.nim +++ b/beacon_chain/consensus_object_pools/consensus_manager.nim @@ -370,38 +370,36 @@ proc runProposalForkchoiceUpdated*( let safeBlockHash = beaconHead.safeExecutionBlockHash withState(self.dag.headState): - debugRaiseAssert "foo" - when consensusFork != ConsensusFork.Electra: - template callForkchoiceUpdated(fcPayloadAttributes: auto) = - let (status, _) = await self.elManager.forkchoiceUpdated( - headBlockHash, safeBlockHash, - beaconHead.finalizedExecutionBlockHash, - payloadAttributes = some fcPayloadAttributes) - debug "Fork-choice updated for proposal", status + template callForkchoiceUpdated(fcPayloadAttributes: auto) = + let (status, _) = await self.elManager.forkchoiceUpdated( + headBlockHash, safeBlockHash, + beaconHead.finalizedExecutionBlockHash, + payloadAttributes = some fcPayloadAttributes) + debug "Fork-choice updated for proposal", status - static: doAssert high(ConsensusFork) == ConsensusFork.Electra - when consensusFork >= ConsensusFork.Deneb: - # https://github.com/ethereum/execution-apis/blob/90a46e9137c89d58e818e62fa33a0347bba50085/src/engine/prague.md - # does not define any new forkchoiceUpdated, so reuse V3 from Dencun - callForkchoiceUpdated(PayloadAttributesV3( - timestamp: Quantity timestamp, - prevRandao: FixedBytes[32] randomData, - suggestedFeeRecipient: feeRecipient, - withdrawals: - toEngineWithdrawals get_expected_withdrawals(forkyState.data), - parentBeaconBlockRoot: beaconHead.blck.bid.root.asBlockHash)) - elif consensusFork >= ConsensusFork.Capella: - callForkchoiceUpdated(PayloadAttributesV2( - timestamp: Quantity timestamp, - prevRandao: FixedBytes[32] randomData, - suggestedFeeRecipient: feeRecipient, - withdrawals: - toEngineWithdrawals get_expected_withdrawals(forkyState.data))) - else: - callForkchoiceUpdated(PayloadAttributesV1( - timestamp: Quantity timestamp, - prevRandao: FixedBytes[32] randomData, - suggestedFeeRecipient: feeRecipient)) + static: doAssert high(ConsensusFork) == ConsensusFork.Electra + when consensusFork >= ConsensusFork.Deneb: + # https://github.com/ethereum/execution-apis/blob/90a46e9137c89d58e818e62fa33a0347bba50085/src/engine/prague.md + # does not define any new forkchoiceUpdated, so reuse V3 from Dencun + callForkchoiceUpdated(PayloadAttributesV3( + timestamp: Quantity timestamp, + prevRandao: FixedBytes[32] randomData, + suggestedFeeRecipient: feeRecipient, + withdrawals: + toEngineWithdrawals get_expected_withdrawals(forkyState.data), + parentBeaconBlockRoot: beaconHead.blck.bid.root.asBlockHash)) + elif consensusFork >= ConsensusFork.Capella: + callForkchoiceUpdated(PayloadAttributesV2( + timestamp: Quantity timestamp, + prevRandao: FixedBytes[32] randomData, + suggestedFeeRecipient: feeRecipient, + withdrawals: + toEngineWithdrawals get_expected_withdrawals(forkyState.data))) + else: + callForkchoiceUpdated(PayloadAttributesV1( + timestamp: Quantity timestamp, + prevRandao: FixedBytes[32] randomData, + suggestedFeeRecipient: feeRecipient)) ok() diff --git a/beacon_chain/gossip_processing/block_processor.nim b/beacon_chain/gossip_processing/block_processor.nim index 7407033e7..31a9e5277 100644 --- a/beacon_chain/gossip_processing/block_processor.nim +++ b/beacon_chain/gossip_processing/block_processor.nim @@ -716,10 +716,9 @@ proc storeBlock( template callForkChoiceUpdated: auto = case self.consensusManager.dag.cfg.consensusForkAtEpoch( newHead.get.blck.bid.slot.epoch) - of ConsensusFork.Electra: - let x = 4 - debugRaiseAssert "callFCU" - of ConsensusFork.Deneb: + of ConsensusFork.Deneb, ConsensusFork.Electra: + # https://github.com/ethereum/execution-apis/blob/90a46e9137c89d58e818e62fa33a0347bba50085/src/engine/prague.md + # does not define any new forkchoiceUpdated, so reuse V3 from Dencun callExpectValidFCU(payloadAttributeType = PayloadAttributesV3) of ConsensusFork.Capella: callExpectValidFCU(payloadAttributeType = PayloadAttributesV2) diff --git a/beacon_chain/spec/beaconstate.nim b/beacon_chain/spec/beaconstate.nim index ef953b856..b646fbd2c 100644 --- a/beacon_chain/spec/beaconstate.nim +++ b/beacon_chain/spec/beaconstate.nim @@ -1441,6 +1441,91 @@ func upgrade_to_deneb*(cfg: RuntimeConfig, pre: capella.BeaconState): historical_summaries: pre.historical_summaries ) +func upgrade_to_electra*(cfg: RuntimeConfig, pre: deneb.BeaconState): + ref electra.BeaconState = + debugRaiseAssert "verify upgrade_to_electra" + let + epoch = get_current_epoch(pre) + latest_execution_payload_header = electra.ExecutionPayloadHeader( + parent_hash: pre.latest_execution_payload_header.parent_hash, + fee_recipient: pre.latest_execution_payload_header.fee_recipient, + state_root: pre.latest_execution_payload_header.state_root, + receipts_root: pre.latest_execution_payload_header.receipts_root, + logs_bloom: pre.latest_execution_payload_header.logs_bloom, + prev_randao: pre.latest_execution_payload_header.prev_randao, + block_number: pre.latest_execution_payload_header.block_number, + gas_limit: pre.latest_execution_payload_header.gas_limit, + gas_used: pre.latest_execution_payload_header.gas_used, + timestamp: pre.latest_execution_payload_header.timestamp, + extra_data: pre.latest_execution_payload_header.extra_data, + base_fee_per_gas: pre.latest_execution_payload_header.base_fee_per_gas, + block_hash: pre.latest_execution_payload_header.block_hash, + transactions_root: pre.latest_execution_payload_header.transactions_root, + withdrawals_root: pre.latest_execution_payload_header.withdrawals_root, + blob_gas_used: 0, # [New in Deneb] + excess_blob_gas: 0 # [New in Deneb] + ) + + (ref electra.BeaconState)( + # Versioning + genesis_time: pre.genesis_time, + genesis_validators_root: pre.genesis_validators_root, + slot: pre.slot, + fork: Fork( + previous_version: pre.fork.current_version, + current_version: cfg.ELECTRA_FORK_VERSION, # [Modified in Deneb] + epoch: epoch + ), + + # History + latest_block_header: pre.latest_block_header, + block_roots: pre.block_roots, + state_roots: pre.state_roots, + historical_roots: pre.historical_roots, + + # Eth1 + eth1_data: pre.eth1_data, + eth1_data_votes: pre.eth1_data_votes, + eth1_deposit_index: pre.eth1_deposit_index, + + # Registry + validators: pre.validators, + balances: pre.balances, + + # Randomness + randao_mixes: pre.randao_mixes, + + # Slashings + slashings: pre.slashings, + + # Participation + previous_epoch_participation: pre.previous_epoch_participation, + current_epoch_participation: pre.current_epoch_participation, + + # Finality + justification_bits: pre.justification_bits, + previous_justified_checkpoint: pre.previous_justified_checkpoint, + current_justified_checkpoint: pre.current_justified_checkpoint, + finalized_checkpoint: pre.finalized_checkpoint, + + # Inactivity + inactivity_scores: pre.inactivity_scores, + + # Sync + current_sync_committee: pre.current_sync_committee, + next_sync_committee: pre.next_sync_committee, + + # Execution-layer + latest_execution_payload_header: latest_execution_payload_header, # [Modified in Deneb] + + # Withdrawals + next_withdrawal_index: pre.next_withdrawal_index, + next_withdrawal_validator_index: pre.next_withdrawal_validator_index, + + # Deep history valid from Capella onwards + historical_summaries: pre.historical_summaries + ) + func latest_block_root(state: ForkyBeaconState, state_root: Eth2Digest): Eth2Digest = # The root of the last block that was successfully applied to this state - diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index de6c84f04..24993f9cc 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -3363,10 +3363,7 @@ proc writeValue*(writer: var JsonWriter[RestJson], if value.consensusValue.isSome(): writer.writeField("consensus_block_value", $(value.consensusValue.get())) - when consensusFork == ConsensusFork.Electra: - debugRaiseAssert "electra JsonWriter ProduceBlockV3 missing" - else: - writer.writeField("data", forkyMaybeBlindedBlck) + writer.writeField("data", forkyMaybeBlindedBlck) writer.endRecord() proc readValue*(reader: var JsonReader[RestJson], diff --git a/beacon_chain/spec/state_transition.nim b/beacon_chain/spec/state_transition.nim index c5f9feab3..489731a42 100644 --- a/beacon_chain/spec/state_transition.nim +++ b/beacon_chain/spec/state_transition.nim @@ -216,12 +216,25 @@ func maybeUpgradeStateToDeneb( denebData: deneb.HashedBeaconState( root: hash_tree_root(newState[]), data: newState[]))[] +func maybeUpgradeStateToElectra( + cfg: RuntimeConfig, state: var ForkedHashedBeaconState) = + # Both process_slots() and state_transition_block() call this, so only run it + # once by checking for existing fork. + if getStateField(state, slot).epoch == cfg.ELECTRA_FORK_EPOCH and + state.kind == ConsensusFork.Deneb: + let newState = upgrade_to_electra(cfg, state.denebData.data) + state = (ref ForkedHashedBeaconState)( + kind: ConsensusFork.Electra, + electraData: electra.HashedBeaconState( + root: hash_tree_root(newState[]), data: newState[]))[] + func maybeUpgradeState*( cfg: RuntimeConfig, state: var ForkedHashedBeaconState) = cfg.maybeUpgradeStateToAltair(state) cfg.maybeUpgradeStateToBellatrix(state) cfg.maybeUpgradeStateToCapella(state) cfg.maybeUpgradeStateToDeneb(state) + cfg.maybeUpgradeStateToElectra(state) proc process_slots*( cfg: RuntimeConfig, state: var ForkedHashedBeaconState, slot: Slot, diff --git a/research/block_sim.nim b/research/block_sim.nim index 5581cb996..bb8823ebc 100644 --- a/research/block_sim.nim +++ b/research/block_sim.nim @@ -142,6 +142,49 @@ proc makeSimulationBlock( ok(blck) +proc makeSimulationBlock( + cfg: RuntimeConfig, + state: var electra.HashedBeaconState, + proposer_index: ValidatorIndex, + randao_reveal: ValidatorSig, + eth1_data: Eth1Data, + graffiti: GraffitiBytes, + attestations: seq[Attestation], + deposits: seq[Deposit], + exits: BeaconBlockValidatorChanges, + sync_aggregate: SyncAggregate, + execution_payload: electra.ExecutionPayloadForSigning, + bls_to_execution_changes: SignedBLSToExecutionChangeList, + rollback: RollbackHashedProc[electra.HashedBeaconState], + cache: var StateCache, + # TODO: + # `verificationFlags` is needed only in tests and can be + # removed if we don't use invalid signatures there + verificationFlags: UpdateFlags = {}): Result[electra.BeaconBlock, cstring] = + ## Create a block for the given state. The latest block applied to it will + ## be used for the parent_root value, and the slot will be take from + ## state.slot meaning process_slots must be called up to the slot for which + ## the block is to be created. + + # To create a block, we'll first apply a partial block to the state, skipping + # some validations. + + var blck = partialBeaconBlock( + cfg, state, proposer_index, randao_reveal, eth1_data, graffiti, + attestations, deposits, exits, sync_aggregate, execution_payload) + + let res = process_block( + cfg, state.data, blck.asSigVerified(), verificationFlags, cache) + + if res.isErr: + rollback(state) + return err(res.error()) + + state.root = hash_tree_root(state.data) + blck.state_root = state.root + + ok(blck) + # TODO confutils is an impenetrable black box. how can a help text be added here? cli do(slots = SLOTS_PER_EPOCH * 7, validators = SLOTS_PER_EPOCH * 500, @@ -343,6 +386,8 @@ cli do(slots = SLOTS_PER_EPOCH * 7, addr state.capellaData elif T is deneb.SignedBeaconBlock: addr state.denebData + elif T is electra.SignedBeaconBlock: + addr state.electraData else: static: doAssert false message = makeSimulationBlock( @@ -359,7 +404,9 @@ cli do(slots = SLOTS_PER_EPOCH * 7, eth1ProposalData.deposits, BeaconBlockValidatorChanges(), sync_aggregate, - when T is deneb.SignedBeaconBlock: + when T is electra.SignedBeaconBlock: + default(electra.ExecutionPayloadForSigning) + elif T is deneb.SignedBeaconBlock: default(deneb.ExecutionPayloadForSigning) elif T is capella.SignedBeaconBlock: default(capella.ExecutionPayloadForSigning) @@ -432,6 +479,28 @@ cli do(slots = SLOTS_PER_EPOCH * 7, do: raiseAssert "withUpdatedState failed" + proc proposeElectraBlock(slot: Slot) = + if rand(r, 1.0) > blockRatio: + return + + dag.withUpdatedState(tmpState[], dag.getBlockIdAtSlot(slot).expect("block")) do: + let + newBlock = getNewBlock[electra.SignedBeaconBlock](updatedState, slot, cache) + added = dag.addHeadBlock(verifier, newBlock) do ( + blckRef: BlockRef, signedBlock: electra.TrustedSignedBeaconBlock, + epochRef: EpochRef, unrealized: FinalityCheckpoints): + # Callback add to fork choice if valid + attPool.addForkChoice( + epochRef, blckRef, unrealized, signedBlock.message, + blckRef.slot.start_beacon_time) + + dag.updateHead(added[], quarantine[], []) + if dag.needStateCachesAndForkChoicePruning(): + dag.pruneStateCachesDAG() + attPool.prune() + do: + raiseAssert "withUpdatedState failed" + var lastEth1BlockAt = genesisTime eth1BlockNum = 1000 @@ -472,12 +541,10 @@ cli do(slots = SLOTS_PER_EPOCH * 7, if blockRatio > 0.0: withTimer(timers[t]): case dag.cfg.consensusForkAtEpoch(slot.epoch) - of ConsensusFork.Electra: - echo "no electra here" - debugRaiseAssert "no electra" + of ConsensusFork.Electra: proposeElectraBlock(slot) of ConsensusFork.Deneb: proposeDenebBlock(slot) of ConsensusFork.Capella: proposeCapellaBlock(slot) - of ConsensusFork.Bellatrix, ConsensusFork.Altair, ConsensusFork.Phase0: + of ConsensusFork.Phase0 .. ConsensusFork.Bellatrix: doAssert false if attesterRatio > 0.0: withTimer(timers[tAttest]): diff --git a/research/simutils.nim b/research/simutils.nim index 11e9bfd94..906a212ff 100644 --- a/research/simutils.nim +++ b/research/simutils.nim @@ -60,7 +60,8 @@ func getSimulationConfig*(): RuntimeConfig {.compileTime.} = cfg.ALTAIR_FORK_EPOCH = 0.Epoch cfg.BELLATRIX_FORK_EPOCH = 0.Epoch cfg.CAPELLA_FORK_EPOCH = 0.Epoch - cfg.DENEB_FORK_EPOCH = 2.Epoch + cfg.DENEB_FORK_EPOCH = 1.Epoch + cfg.ELECTRA_FORK_EPOCH = 3.Epoch cfg proc loadGenesis*( @@ -191,4 +192,4 @@ proc printTimers*[Timers: enum]( echo "Validators: ", getStateField(state, validators).len, ", epoch length: ", SLOTS_PER_EPOCH echo "Validators per attestation (mean): ", attesters.mean - printTimers(validate, timers) + printTimers(validate, timers) \ No newline at end of file diff --git a/tests/consensus_spec/test_fixture_fork_choice.nim b/tests/consensus_spec/test_fixture_fork_choice.nim index ba46298ec..87fdfdd17 100644 --- a/tests/consensus_spec/test_fixture_fork_choice.nim +++ b/tests/consensus_spec/test_fixture_fork_choice.nim @@ -303,13 +303,8 @@ proc doRunTest( let stores = withConsensusFork(fork): - when consensusFork != ConsensusFork.Electra: - # another withFoo when it statically knows, sure, ok, so this means another place it has to be disabled - initialLoad( - path, db, consensusFork.BeaconState, consensusFork.BeaconBlock) - else: - debugRaiseAssert "electra etc" - default(tuple[dag: ChainDAGRef, fkChoice: ref ForkChoice]) + initialLoad( + path, db, consensusFork.BeaconState, consensusFork.BeaconBlock) rng = HmacDrbgContext.new() taskpool = @@ -402,14 +397,12 @@ template fcSuite(suiteName: static[string], testPathElem: static[string]) = continue let fork = forkForPathComponent(path).valueOr: raiseAssert "Unknown test fork: " & testsPath - if true: - debugRaiseAssert "no electra in fc tests" - for kind, path in walkDir(testsPath, relative = true, checkDir = true): - let basePath = testsPath/path/"pyspec_tests" - if kind != pcDir: - continue - for kind, path in walkDir(basePath, relative = true, checkDir = true): - runTest(suiteName, basePath/path, fork) + for kind, path in walkDir(testsPath, relative = true, checkDir = true): + let basePath = testsPath/path/"pyspec_tests" + if kind != pcDir: + continue + for kind, path in walkDir(basePath, relative = true, checkDir = true): + runTest(suiteName, basePath/path, fork) from ../../beacon_chain/conf import loadKzgTrustedSetup discard loadKzgTrustedSetup() # Required for Deneb tests diff --git a/tests/test_light_client_processor.nim b/tests/test_light_client_processor.nim index 8ce58dac2..6d70c7bf7 100644 --- a/tests/test_light_client_processor.nim +++ b/tests/test_light_client_processor.nim @@ -34,7 +34,6 @@ suite "Light client processor" & preset(): res.BELLATRIX_FORK_EPOCH = 2.Epoch res.CAPELLA_FORK_EPOCH = (EPOCHS_PER_SYNC_COMMITTEE_PERIOD * 1).Epoch res.DENEB_FORK_EPOCH = (EPOCHS_PER_SYNC_COMMITTEE_PERIOD * 2).Epoch - debugRaiseAssert "presumably FAR_FUTURE_EPOCH is less than optimal" res.ELECTRA_FORK_EPOCH = FAR_FUTURE_EPOCH res