From 0b1c83a0a795b11ca34e850851f1962e4ef839bd Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 3 Dec 2025 17:04:43 +0100 Subject: [PATCH 01/21] chore: remove merge transition code --- .github/workflows/test-sim-merge.yml | 58 --- packages/beacon-node/package.json | 1 - .../beacon-node/src/api/impl/debug/index.ts | 8 +- .../utils/pandaMergeTransitionBanner.ts | 43 -- .../src/chain/blocks/verifyBlock.ts | 26 +- .../blocks/verifyBlocksExecutionPayloads.ts | 144 +----- packages/beacon-node/src/chain/chain.ts | 13 +- .../beacon-node/src/chain/forkChoice/index.ts | 34 +- .../beacon-node/src/chain/prepareNextSlot.ts | 2 +- .../chain/produceBlock/produceBlockBody.ts | 143 ++---- .../src/eth1/eth1MergeBlockTracker.ts | 328 ------------- packages/beacon-node/src/eth1/index.ts | 67 +-- packages/beacon-node/src/eth1/interface.ts | 46 +- .../src/metrics/metrics/lodestar.ts | 52 --- packages/beacon-node/src/node/notifier.ts | 36 +- .../e2e/eth1/eth1MergeBlockTracker.test.ts | 148 ------ .../opPools/aggregatedAttestationPool.test.ts | 10 +- .../test/scripts/el-interop/geth/pre-merge.sh | 8 - .../el-interop/gethdocker/pre-merge.sh | 9 - .../el-interop/mergemock/common-setup.sh | 19 - .../el-interop/mergemock/genesisPre.tmpl | 36 -- .../test/scripts/el-interop/mergemock/jwt.hex | 1 - .../el-interop/mergemock/post-merge.sh | 9 - .../el-interop/nethermind/pre-merge.sh | 9 - .../el-interop/netherminddocker/pre-merge.sh | 10 - .../beacon-node/test/sim/mergemock.test.ts | 273 ----------- .../test/spec/presets/fork_choice.test.ts | 25 +- .../unit/chain/forkChoice/forkChoice.test.ts | 2 +- .../unit/eth1/eth1MergeBlockTracker.test.ts | 256 ---------- packages/beacon-node/test/utils/runEl.ts | 2 +- packages/beacon-node/test/utils/state.ts | 4 +- .../beacon-node/test/utils/typeGenerator.ts | 4 +- .../test/utils/validationData/attestation.ts | 4 +- .../fork-choice/src/forkChoice/forkChoice.ts | 109 +---- .../fork-choice/src/forkChoice/interface.ts | 7 - packages/fork-choice/src/index.ts | 3 +- packages/fork-choice/src/protoArray/errors.ts | 1 - .../fork-choice/src/protoArray/interface.ts | 27 +- .../fork-choice/src/protoArray/protoArray.ts | 19 +- .../fork-choice/test/perf/forkChoice/util.ts | 14 +- .../test/unit/forkChoice/forkChoice.test.ts | 10 +- .../unit/forkChoice/getProposerHead.test.ts | 15 +- .../shouldOverrideForkChoiceUpdate.test.ts | 15 +- .../test/unit/forkChoice/utils.test.ts | 71 --- .../protoArray/executionStatusUpdates.test.ts | 437 ------------------ .../unit/protoArray/getCommonAncestor.test.ts | 8 +- .../test/unit/protoArray/protoArray.test.ts | 12 +- .../state-transition/src/util/execution.ts | 59 +-- 48 files changed, 179 insertions(+), 2458 deletions(-) delete mode 100644 .github/workflows/test-sim-merge.yml delete mode 100644 packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts delete mode 100644 packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts delete mode 100644 packages/beacon-node/test/e2e/eth1/eth1MergeBlockTracker.test.ts delete mode 100755 packages/beacon-node/test/scripts/el-interop/geth/pre-merge.sh delete mode 100755 packages/beacon-node/test/scripts/el-interop/gethdocker/pre-merge.sh delete mode 100755 packages/beacon-node/test/scripts/el-interop/mergemock/common-setup.sh delete mode 100644 packages/beacon-node/test/scripts/el-interop/mergemock/genesisPre.tmpl delete mode 100644 packages/beacon-node/test/scripts/el-interop/mergemock/jwt.hex delete mode 100755 packages/beacon-node/test/scripts/el-interop/mergemock/post-merge.sh delete mode 100755 packages/beacon-node/test/scripts/el-interop/nethermind/pre-merge.sh delete mode 100755 packages/beacon-node/test/scripts/el-interop/netherminddocker/pre-merge.sh delete mode 100644 packages/beacon-node/test/sim/mergemock.test.ts delete mode 100644 packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts delete mode 100644 packages/fork-choice/test/unit/forkChoice/utils.test.ts delete mode 100644 packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts diff --git a/.github/workflows/test-sim-merge.yml b/.github/workflows/test-sim-merge.yml deleted file mode 100644 index 38a0253e0b22..000000000000 --- a/.github/workflows/test-sim-merge.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Sim merge execution/builder tests - -concurrency: - # If PR, cancel prev commits. head_ref = source branch name on pull_request, null if push - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -on: - push: - # We intentionally don't run push on feature branches. See PR for rational. - branches: [unstable, stable] - pull_request: - workflow_dispatch: - -env: - GETH_IMAGE: ethereum/client-go:v1.10.25 - NETHERMIND_IMAGE: nethermind/nethermind:1.14.3 - MERGEMOCK_IMAGE: g11tech/mergemock:latest - GETH_WITHDRAWALS_IMAGE: g11tech/geth:withdrawalsfeb8 - ETHEREUMJS_WITHDRAWALS_IMAGE: g11tech/ethereumjs:blobs-b6b63 - NETHERMIND_WITHDRAWALS_IMAGE: nethermindeth/nethermind:withdrawals_yolo - ETHEREUMJS_BLOBS_IMAGE: g11tech/ethereumjs:blobs-b6b63 - -jobs: - sim-merge-tests: - name: Sim merge tests - runs-on: buildjet-4vcpu-ubuntu-2204 - steps: - - uses: actions/checkout@v4 - - uses: "./.github/actions/setup-and-build" - with: - node: 22 - - - name: Pull Geth - run: docker pull $GETH_IMAGE - - - name: Pull Nethermind - run: docker pull $NETHERMIND_IMAGE - - - name: Pull mergemock - run: docker pull $MERGEMOCK_IMAGE - - - name: Test Lodestar <> mergemock relay - run: yarn test:sim:mergemock - working-directory: packages/beacon-node - env: - EL_BINARY_DIR: ${{ env.MERGEMOCK_IMAGE }} - EL_SCRIPT_DIR: mergemock - LODESTAR_PRESET: mainnet - ENGINE_PORT: 8551 - ETH_PORT: 8661 - - - name: Upload debug log test files - if: ${{ always() }} - uses: actions/upload-artifact@v4 - with: - name: debug-test-logs - path: packages/beacon-node/test-logs diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 63f58ad7a191..19b15643f643 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -103,7 +103,6 @@ "test:unit": "vitest run --project unit --project unit-minimal", "test:e2e": "vitest run --project e2e --project e2e-mainnet", "test:sim": "vitest run test/sim/**/*.test.ts", - "test:sim:mergemock": "vitest run test/sim/mergemock.test.ts", "test:sim:blobs": "vitest run test/sim/4844-interop.test.ts", "download-spec-tests": "node --loader=ts-node/esm test/spec/downloadTests.ts", "test:spec:bls": "vitest run --project spec-minimal test/spec/bls/", diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index f1f0562fd067..75d2db0ef983 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,7 +1,7 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ExecutionStatus} from "@lodestar/fork-choice"; -import {ZERO_HASH_HEX, isForkPostDeneb, isForkPostFulu} from "@lodestar/params"; +import {isForkPostDeneb, isForkPostFulu} from "@lodestar/params"; import {BeaconState, deneb, fulu, sszTypesFor} from "@lodestar/types"; import {fromAsync, toRootHex} from "@lodestar/utils"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; @@ -47,11 +47,10 @@ export function getDebugApi({ case ExecutionStatus.Invalid: return "invalid"; case ExecutionStatus.Syncing: - case ExecutionStatus.PreMerge: return "optimistic"; } })(), - executionBlockHash: node.executionPayloadBlockHash ?? ZERO_HASH_HEX, + executionBlockHash: node.executionPayloadBlockHash, })), }, }; @@ -59,10 +58,7 @@ export function getDebugApi({ async getProtoArrayNodes() { const nodes = chain.forkChoice.getAllNodes().map((node) => ({ - // if node has executionPayloadNumber, it will overwrite the below default - executionPayloadNumber: 0, ...node, - executionPayloadBlockHash: node.executionPayloadBlockHash ?? "", parent: String(node.parent), bestChild: String(node.bestChild), bestDescendant: String(node.bestDescendant), diff --git a/packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts b/packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts deleted file mode 100644 index 53251cb9a035..000000000000 --- a/packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Note: 1 extra new line for space -// Note: String.raw preserve backslash character -// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw -// from https://stackoverflow.com/questions/30466581/preserve-all-backslashes-in-a-string -export const POS_PANDA_MERGE_TRANSITION_BANNER = String.raw` - - _,..._,m, |, _,..._,m, |, - ,/' '""; | |, /' '""; | |, - / ". ". - ,'mmmMMMMmm. \ -|-_" 'mmmMMMMmm. \ -|-_" - _/-"^^^^^"""%#%mm, ; | _ o /-"^^^^^"""%#%mm, ; | _ o - ,m,_,' "###) ;, m,_,' "###) ;, - (###% \#/ ;##mm. (###% \#/ ;##mm. - ^#/__ ___ ; (######) #/__ ___ ; (######) - ; //.\\ //.\\ ; \####/ //.\\ //.\\ ; \####/ - _; (#\"// \\"/#) ; ,/ _; (#\"// \\"/#) ; ,/ - @##\ \##/ = "=" ,;mm/ @##\ \##/ = "=" ,;mm/ - \##>.____,...,____,<####@ \##>.____,...,____,<####@ - ""' ""' - _______ ______ ______ ______ __ __ -/ \ / \ / \ / \ / | / | -$$$$$$$ | ______ ______ ______ /$$$$$$ | ______ /$$$$$$ | /$$$$$$ |_$$ |_ ______ $$ | __ ______ -$$ |__$$ |/ \ / \ / \ $$ |_ $$/ / \ $$ |_ $$/ $$ \__$$// $$ | / \ $$ | / | / \ -$$ $$//$$$$$$ |/$$$$$$ |/$$$$$$ |$$ | /$$$$$$ |$$ | $$ \$$$$$$/ $$$$$$ |$$ |_/$$/ /$$$$$$ | -$$$$$$$/ $$ | $$/ $$ | $$ |$$ | $$ |$$$$/ $$ | $$ |$$$$/ $$$$$$ | $$ | __ / $$ |$$ $$< $$ $$ | -$$ | $$ | $$ \__$$ |$$ \__$$ |$$ | $$ \__$$ |$$ | / \__$$ | $$ |/ |/$$$$$$$ |$$$$$$ \ $$$$$$$$/ -$$ | $$ | $$ $$/ $$ $$/ $$ | $$ $$/ $$ | $$ $$/ $$ $$/ $$ $$ |$$ | $$ |$$ | -$$/ $$/ $$$$$$/ $$$$$$/ $$/ $$$$$$/ $$/ $$$$$$/ $$$$/ $$$$$$$/ $$/ $$/ $$$$$$$/ - - - - ______ __ __ __ __ - / \ / | / | / | / | -/$$$$$$ | _______ _$$ |_ $$/ __ __ ______ _$$ |_ ______ ____$$ | -$$ |__$$ | / |/ $$ | / |/ \ / |/ \ / $$ | / \ / $$ | -$$ $$ |/$$$$$$$/ $$$$$$/ $$ |$$ \ /$$/ $$$$$$ |$$$$$$/ /$$$$$$ |/$$$$$$$ | -$$$$$$$$ |$$ | $$ | __ $$ | $$ /$$/ / $$ | $$ | __ $$ $$ |$$ | $$ | -$$ | $$ |$$ \_____ $$ |/ |$$ | $$ $$/ /$$$$$$$ | $$ |/ |$$$$$$$$/ $$ \__$$ | -$$ | $$ |$$ | $$ $$/ $$ | $$$/ $$ $$ | $$ $$/ $$ |$$ $$ | -$$/ $$/ $$$$$$$/ $$$$/ $$/ $/ $$$$$$$/ $$$$/ $$$$$$$/ $$$$$$$/ - -`; -// Note: 2 extra new lines for space diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 7ff8f3397a9f..5644691b5a26 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -1,4 +1,3 @@ -import {ChainForkConfig} from "@lodestar/config"; import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice"; import {ForkName, isForkPostFulu} from "@lodestar/params"; import { @@ -7,8 +6,7 @@ import { computeEpochAtSlot, isStateValidatorsNodesPopulated, } from "@lodestar/state-transition"; -import {IndexedAttestation, bellatrix, deneb} from "@lodestar/types"; -import {Logger, toRootHex} from "@lodestar/utils"; +import {IndexedAttestation, deneb} from "@lodestar/types"; import type {BeaconChain} from "../chain.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; import {BlockProcessOpts} from "../options.js"; @@ -18,7 +16,6 @@ import {ImportBlockOpts} from "./types.js"; import {DENEB_BLOWFISH_BANNER} from "./utils/blowfishBanner.js"; import {ELECTRA_GIRAFFE_BANNER} from "./utils/giraffeBanner.js"; import {CAPELLA_OWL_BANNER} from "./utils/ownBanner.js"; -import {POS_PANDA_MERGE_TRANSITION_BANNER} from "./utils/pandaMergeTransitionBanner.js"; import {FULU_ZEBRA_BANNER} from "./utils/zebraBanner.js"; import {verifyBlocksDataAvailability} from "./verifyBlocksDataAvailability.js"; import {SegmentExecStatus, verifyBlocksExecutionPayload} from "./verifyBlocksExecutionPayloads.js"; @@ -103,7 +100,7 @@ export async function verifyBlocksInEpoch( : Promise.resolve({ execAborted: null, executionStatuses: blocks.map((_blk) => ExecutionStatus.Syncing), - mergeBlockFound: null, + executionTime: Date.now(), } as SegmentExecStatus); // Store indexed attestations for each block to avoid recomputing them during import @@ -162,12 +159,6 @@ export async function verifyBlocksInEpoch( ]); if (opts.verifyOnly !== true) { - if (segmentExecStatus.execAborted === null && segmentExecStatus.mergeBlockFound !== null) { - // merge block found and is fully valid = state transition + signatures + execution payload. - // TODO: Will this banner be logged during syncing? - logOnPowBlock(this.logger, this.config, segmentExecStatus.mergeBlockFound); - } - const fromForkBoundary = this.config.getForkBoundaryAtEpoch(computeEpochAtSlot(parentBlock.slot)); const toForkBoundary = this.config.getForkBoundaryAtEpoch(computeEpochAtSlot(lastBlock.message.slot)); @@ -250,16 +241,3 @@ export async function verifyBlocksInEpoch( abortController.abort(); } } - -function logOnPowBlock(logger: Logger, config: ChainForkConfig, mergeBlock: bellatrix.BeaconBlock): void { - const mergeBlockHash = toRootHex(config.getForkTypes(mergeBlock.slot).BeaconBlock.hashTreeRoot(mergeBlock)); - const mergeExecutionHash = toRootHex(mergeBlock.body.executionPayload.blockHash); - const mergePowHash = toRootHex(mergeBlock.body.executionPayload.parentHash); - logger.info(POS_PANDA_MERGE_TRANSITION_BANNER); - logger.info("Execution transitioning from PoW to PoS!!!"); - logger.info("Importing block referencing terminal PoW block", { - blockHash: mergeBlockHash, - executionHash: mergeExecutionHash, - powHash: mergePowHash, - }); -} diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index 1183c72074c0..327627b4e78e 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -6,7 +6,6 @@ import { LVHValidResponse, MaybeValidExecutionStatus, ProtoBlock, - assertValidTerminalPowBlock, } from "@lodestar/fork-choice"; import {ForkSeq, SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import { @@ -14,11 +13,9 @@ import { isExecutionBlockBodyType, isExecutionEnabled, isExecutionStateType, - isMergeTransitionBlock as isMergeTransitionBlockFn, } from "@lodestar/state-transition"; import {Slot, bellatrix, electra} from "@lodestar/types"; import {ErrorAborted, Logger, toRootHex} from "@lodestar/utils"; -import {IEth1ForBlockProduction} from "../../eth1/index.js"; import {ExecutionPayloadStatus, IExecutionEngine} from "../../execution/engine/interface.js"; import {Metrics} from "../../metrics/metrics.js"; import {IClock} from "../../util/clock.js"; @@ -29,7 +26,6 @@ import {IBlockInput} from "./blockInput/types.js"; import {ImportBlockOpts} from "./types.js"; export type VerifyBlockExecutionPayloadModules = { - eth1: IEth1ForBlockProduction; executionEngine: IExecutionEngine; clock: IClock; logger: Logger; @@ -44,9 +40,8 @@ export type SegmentExecStatus = execAborted: null; executionStatuses: MaybeValidExecutionStatus[]; executionTime: number; - mergeBlockFound: bellatrix.BeaconBlock | null; } - | {execAborted: ExecAbortType; invalidSegmentLVH?: LVHInvalidResponse; mergeBlockFound: null}; + | {execAborted: ExecAbortType; invalidSegmentLVH?: LVHInvalidResponse}; type VerifyExecutionErrorResponse = | {executionStatus: ExecutionStatus.Invalid; lvhResponse: LVHInvalidResponse; execError: BlockError} @@ -55,8 +50,7 @@ type VerifyExecutionErrorResponse = type VerifyBlockExecutionResponse = | VerifyExecutionErrorResponse | {executionStatus: ExecutionStatus.Valid; lvhResponse: LVHValidResponse; execError: null} - | {executionStatus: ExecutionStatus.Syncing; lvhResponse?: LVHValidResponse; execError: null} - | {executionStatus: ExecutionStatus.PreMerge; lvhResponse: undefined; execError: null}; + | {executionStatus: ExecutionStatus.Syncing; lvhResponse?: LVHValidResponse; execError: null}; /** * Verifies 1 or more execution payloads from a linear sequence of blocks. @@ -72,7 +66,6 @@ export async function verifyBlocksExecutionPayload( opts: BlockProcessOpts & ImportBlockOpts ): Promise { const executionStatuses: MaybeValidExecutionStatus[] = []; - let mergeBlockFound: bellatrix.BeaconBlock | null = null; const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000); const lastBlock = blockInputs.at(-1); @@ -96,56 +89,17 @@ export async function verifyBlocksExecutionPayload( // will either validate or prune invalid blocks // // We need to track and keep updating if its safe to optimistically import these blocks. - // The following is how we determine for a block if its safe: - // - // (but we need to modify this check for this segment of blocks because it checks if the - // parent of any block imported in forkchoice is post-merge and currently we could only - // have blocks[0]'s parent imported in the chain as this is no longer one by one verify + - // import.) - // // // When to import such blocks: // From: https://github.com/ethereum/consensus-specs/pull/2844 // A block MUST NOT be optimistically imported, unless either of the following // conditions are met: // - // 1. Parent of the block has execution - // - // Since with the sync optimizations, the previous block might not have been in the - // forkChoice yet, so the below check could fail for safeSlotsToImportOptimistically - // - // Luckily, we can depend on the preState0 to see if we are already post merge w.r.t - // the blocks we are importing. - // - // Or in other words if - // - block status is syncing - // - and we are not in a post merge world and is parent is not optimistically safe - // - and we are syncing close to the chain head i.e. clock slot - // - and parent is optimistically safe - // - // then throw error - // - // - // - if we haven't yet imported a post merge ancestor in forkchoice i.e. - // - and we are syncing close to the clockSlot, i.e. merge Transition could be underway - // - // - // 2. The current slot (as per the system clock) is at least - // SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY ahead of the slot of the block being - // imported. - // This means that the merge transition could be underway and we can't afford to import - // a block which is not fully validated as it could affect liveliness of the network. - // + // 1. Parent of the block has execution - always true post-merge // - // For this segment of blocks: - // We are optimistically safe with respect to this entire block segment if: - // - all the blocks are way behind the current slot - // - or we have already imported a post-merge parent of first block of this chain in forkchoice const currentSlot = chain.clock.currentSlot; - const safeSlotsToImportOptimistically = opts.safeSlotsToImportOptimistically ?? SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY; - let isOptimisticallySafe = - parentBlock.executionStatus !== ExecutionStatus.PreMerge || - lastBlock.slot + safeSlotsToImportOptimistically < currentSlot; + // Post-merge, we're always optimistically safe + const isOptimisticallySafe = true; for (let blockIndex = 0; blockIndex < blockInputs.length; blockIndex++) { const blockInput = blockInputs[blockIndex]; @@ -170,75 +124,7 @@ export async function verifyBlocksExecutionPayload( // If we are here then its because executionStatus is one of MaybeValidExecutionStatus const {executionStatus} = verifyResponse; - // It becomes optimistically safe for following blocks if a post-merge block is deemed fit - // for import. If it would not have been safe verifyBlockExecutionPayload would have - // returned execError and loop would have been aborted - if (executionStatus !== ExecutionStatus.PreMerge) { - isOptimisticallySafe = true; - } executionStatuses.push(executionStatus); - - const blockBody = blockInput.getBlock().message.body; - const isMergeTransitionBlock = - // If the merge block is found, stop the search as the isMergeTransitionBlockFn condition - // will still evaluate to true for the following blocks leading to errors (while syncing) - // as the preState0 still belongs to the pre state of the first block on segment - mergeBlockFound === null && - isExecutionStateType(preState0) && - isExecutionBlockBodyType(blockBody) && - isMergeTransitionBlockFn(preState0, blockBody); - - // If this is a merge transition block, check to ensure if it references - // a valid terminal PoW block. - // - // However specs define this check to be run inside forkChoice's onBlock - // (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/fork-choice.md#on_block) - // but we perform the check here (as inspired from the lighthouse impl) - // - // Reasons: - // 1. If the block is not valid, we should fail early and not wait till - // forkChoice import. - // 2. It makes logical sense to pair it with the block validations and - // deal it with the external services like eth1 tracker here than - // in import block - if (isMergeTransitionBlock) { - const mergeBlock = blockInput.getBlock().message as bellatrix.BeaconBlock; - const mergeBlockHash = toRootHex(chain.config.getForkTypes(mergeBlock.slot).BeaconBlock.hashTreeRoot(mergeBlock)); - const powBlockRootHex = toRootHex(mergeBlock.body.executionPayload.parentHash); - const powBlock = await chain.eth1.getPowBlock(powBlockRootHex).catch((error) => { - // Lets just warn the user here, errors if any will be reported on - // `assertValidTerminalPowBlock` checks - chain.logger.warn( - "Error fetching terminal PoW block referred in the merge transition block", - {powBlockHash: powBlockRootHex, mergeBlockHash}, - error - ); - return null; - }); - - const powBlockParent = - powBlock && - (await chain.eth1.getPowBlock(powBlock.parentHash).catch((error) => { - // Lets just warn the user here, errors if any will be reported on - // `assertValidTerminalPowBlock` checks - chain.logger.warn( - "Error fetching parent of the terminal PoW block referred in the merge transition block", - {powBlockParentHash: powBlock.parentHash, powBlock: powBlockRootHex, mergeBlockHash}, - error - ); - return null; - })); - - // executionStatus will never == ExecutionStatus.PreMerge if it's the mergeBlock. But gotta make TS happy =D - if (executionStatus === ExecutionStatus.PreMerge) { - throw Error("Merge block must not have executionStatus == PreMerge"); - } - - assertValidTerminalPowBlock(chain.config, mergeBlock, {executionStatus, powBlock, powBlockParent}); - // Valid execution payload, but may not be in a valid beacon chain block. Delay printing the POS ACTIVATED banner - // to the end of the verify block routine, which confirms that this block is fully valid. - mergeBlockFound = mergeBlock; - } } const executionTime = Date.now(); @@ -265,7 +151,6 @@ export async function verifyBlocksExecutionPayload( execAborted: null, executionStatuses, executionTime, - mergeBlockFound, }; } @@ -285,18 +170,13 @@ export async function verifyBlockExecutionPayload( const executionPayloadEnabled = isExecutionStateType(preState0) && isExecutionBlockBodyType(block.message.body) && - // Safe to use with a state previous to block's preState. isMergeComplete can only transition from false to true. - // - If preState0 is after merge block: condition is true, and will always be true - // - If preState0 is before merge block: the block could lie but then state transition function will throw above - // It is kinda safe to send non-trusted payloads to the execution client because at most it can trigger sync. - // TODO: If this becomes a problem, do some basic verification beforehand, like checking the proposer signature. isExecutionEnabled(preState0, block.message) ? block.message.body.executionPayload : null; if (!executionPayloadEnabled) { - // isExecutionEnabled() -> false - return {executionStatus: ExecutionStatus.PreMerge, execError: null} as VerifyBlockExecutionResponse; + // This should not happen post-merge, but handle gracefully + throw Error("Execution payload not enabled for post-merge block"); } // TODO: Handle better notifyNewPayload() returning error is syncing @@ -418,14 +298,8 @@ function getSegmentErrorResponse( } // If there is no valid in the segment then we have to propagate invalid response - // in forkchoice as well if - // - if the parentBlock is also not the lvh - // - and parentBlock is not pre merge - if ( - !lvhFound && - parentBlock.executionStatus !== ExecutionStatus.PreMerge && - parentBlock.executionPayloadBlockHash !== lvhResponse.latestValidExecHash - ) { + // in forkchoice as well if the parentBlock is also not the lvh + if (!lvhFound && parentBlock.executionPayloadBlockHash !== lvhResponse.latestValidExecHash) { invalidSegmentLVH = { executionStatus: ExecutionStatus.Invalid, latestValidExecHash: lvhResponse.latestValidExecHash, diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 091739b7d3da..9ba8afe541de 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -3,7 +3,7 @@ import {PrivateKey} from "@libp2p/interface"; import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz"; import {BeaconConfig} from "@lodestar/config"; -import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; +import {CheckpointWithHex, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; import {LoggerNode} from "@lodestar/logger/node"; import {EFFECTIVE_BALANCE_INCREMENT, GENESIS_SLOT, SLOTS_PER_EPOCH, isForkPostElectra} from "@lodestar/params"; import { @@ -1177,17 +1177,6 @@ export class BeaconChain implements IBeaconChain { this.seenAggregatedAttestations.prune(epoch); this.seenBlockAttesters.prune(epoch); this.beaconProposerCache.prune(epoch); - - // Poll for merge block in the background to speed-up block production. Only if: - // - after BELLATRIX_FORK_EPOCH - // - Beacon node synced - // - head state not isMergeTransitionComplete - if (this.config.BELLATRIX_FORK_EPOCH - epoch < 1) { - const head = this.forkChoice.getHead(); - if (epoch - computeEpochAtSlot(head.slot) < 5 && head.executionStatus === ExecutionStatus.PreMerge) { - this.eth1.startPollingMergeBlock(); - } - } } protected onNewHead(head: ProtoBlock): void { diff --git a/packages/beacon-node/src/chain/forkChoice/index.ts b/packages/beacon-node/src/chain/forkChoice/index.ts index d83dc9949d7e..3dc2c6f032a5 100644 --- a/packages/beacon-node/src/chain/forkChoice/index.ts +++ b/packages/beacon-node/src/chain/forkChoice/index.ts @@ -18,11 +18,9 @@ import { getBlockRootAtSlot, getEffectiveBalanceIncrementsZeroInactive, isExecutionStateType, - isMergeTransitionComplete, } from "@lodestar/state-transition"; import {Slot, ssz} from "@lodestar/types"; import {Logger, toRootHex} from "@lodestar/utils"; -import {GENESIS_SLOT} from "../../constants/index.js"; import {Metrics} from "../../metrics/index.js"; import {ChainEvent, ChainEventEmitter} from "../emitter.js"; @@ -103,6 +101,11 @@ export function initializeForkChoiceFromFinalizedState( // production code use ForkChoice constructor directly const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice; + const executionPayloadBlockHash = isExecutionStateType(state) + ? toRootHex(state.latestExecutionPayloadHeader.blockHash) + : ZERO_HASH_HEX; + const executionPayloadNumber = isExecutionStateType(state) ? state.latestExecutionPayloadHeader.blockNumber : 0; + return new forkchoiceConstructor( config, @@ -135,13 +138,9 @@ export function initializeForkChoiceFromFinalizedState( unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root), - ...(isExecutionStateType(state) && isMergeTransitionComplete(state) - ? { - executionPayloadBlockHash: toRootHex(state.latestExecutionPayloadHeader.blockHash), - executionPayloadNumber: state.latestExecutionPayloadHeader.blockNumber, - executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing, - } - : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}), + executionPayloadBlockHash, + executionPayloadNumber, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, @@ -184,6 +183,13 @@ export function initializeForkChoiceFromUnfinalizedState( }; logger?.warn("Initializing fork choice from unfinalized state", logCtx); + const executionPayloadBlockHash = isExecutionStateType(unfinalizedState) + ? toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash) + : ZERO_HASH_HEX; + const executionPayloadNumber = isExecutionStateType(unfinalizedState) + ? unfinalizedState.latestExecutionPayloadHeader.blockNumber + : 0; + // this is not the justified state, but there is no other ways to get justified balances const justifiedBalances = getEffectiveBalanceIncrementsZeroInactive(unfinalizedState); const store = new ForkChoiceStore( @@ -216,13 +222,9 @@ export function initializeForkChoiceFromUnfinalizedState( unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root), - ...(isExecutionStateType(unfinalizedState) && isMergeTransitionComplete(unfinalizedState) - ? { - executionPayloadBlockHash: toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash), - executionPayloadNumber: unfinalizedState.latestExecutionPayloadHeader.blockNumber, - executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing, - } - : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}), + executionPayloadBlockHash, + executionPayloadNumber, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 43fbb0226a48..78967f2ff293 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -197,7 +197,7 @@ export class PrepareNextSlotScheduler { this.chain.opts.emitPayloadAttributes === true && this.chain.emitter.listenerCount(routes.events.EventType.payloadAttributes) ) { - const data = await getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, { + const data = getPayloadAttributesForSSE(fork as ForkPostBellatrix, this.chain, { prepareState: updatedPrepareState, prepareSlot, parentBlockRoot: fromHex(headRoot), diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 3736a141291a..aadec95c47e2 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -17,10 +17,8 @@ import { CachedBeaconStateCapella, CachedBeaconStateExecutions, computeTimeAtSlot, - getCurrentEpoch, getExpectedWithdrawals, getRandaoMix, - isMergeTransitionComplete, } from "@lodestar/state-transition"; import { BLSPubkey, @@ -44,12 +42,9 @@ import { deneb, electra, fulu, - ssz, - sszTypesFor, } from "@lodestar/types"; import {Logger, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils"; -import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js"; -import {IEth1ForBlockProduction} from "../../eth1/index.js"; +import {ZERO_HASH_HEX} from "../../constants/index.js"; import {numToQuantity} from "../../eth1/provider/utils.js"; import { IExecutionBuilder, @@ -337,14 +332,6 @@ export async function produceBlockBody( feeRecipient ); - if (prepareRes.isPremerge) { - return { - ...prepareRes, - executionPayload: sszTypesFor(fork).ExecutionPayload.defaultValue(), - executionPayloadValue: BigInt(0), - }; - } - const {prepType, payloadId} = prepareRes; Object.assign(logMeta, {executionPayloadPrepType: prepType}); @@ -365,38 +352,12 @@ export async function produceBlockBody( }); return {...prepareRes, ...payloadRes}; - })().catch((e) => { - // catch payload fetch here, because there is still a recovery path possible if we - // are pre-merge. We don't care the same for builder segment as the execution block - // will takeover if the builder flow was activated and errors - this.metrics?.blockPayload.payloadFetchErrors.inc(); - - if (!isMergeTransitionComplete(currentState as CachedBeaconStateBellatrix)) { - this.logger?.warn( - "Fetch payload from the execution failed, however since we are still pre-merge proceeding with an empty one.", - {}, - e as Error - ); - // ok we don't have an execution payload here, so we can assign an empty one - // if pre-merge - return { - isPremerge: true as const, - executionPayload: sszTypesFor(fork).ExecutionPayload.defaultValue(), - executionPayloadValue: BigInt(0), - }; - } - // since merge transition is complete, we need a valid payload even if with an - // empty (transactions) one. defaultValue isn't gonna cut it! - throw e; - }); + })(); const [engineRes, commonBlockBody] = await Promise.all([enginePromise, commonBlockBodyPromise]); blockBody = Object.assign({}, commonBlockBody) as AssembledBodyType; - if (engineRes.isPremerge) { - (blockBody as BeaconBlockBody).executionPayload = engineRes.executionPayload; - executionPayloadValue = engineRes.executionPayloadValue; - } else { + { const {prepType, payloadId, executionPayload, blobsBundle, executionRequests} = engineRes; shouldOverrideBuilder = engineRes.shouldOverrideBuilder; @@ -512,7 +473,6 @@ export async function produceBlockBody( */ export async function prepareExecutionPayload( chain: { - eth1: IEth1ForBlockProduction; executionEngine: IExecutionEngine; config: ChainForkConfig; }, @@ -523,14 +483,8 @@ export async function prepareExecutionPayload( finalizedBlockHash: RootHex, state: CachedBeaconStateExecutions, suggestedFeeRecipient: string -): Promise<{isPremerge: true} | {isPremerge: false; prepType: PayloadPreparationType; payloadId: PayloadId}> { - const parentHashRes = await getExecutionPayloadParentHash(chain, state); - if (parentHashRes.isPremerge) { - // Return null only if the execution is pre-merge - return {isPremerge: true}; - } - - const {parentHash} = parentHashRes; +): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> { + const {parentHash} = getExecutionPayloadParentHash(chain, state); const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime); const prevRandao = getRandaoMix(state, state.epochCtx.epoch); @@ -586,12 +540,11 @@ export async function prepareExecutionPayload( // We are only returning payloadId here because prepareExecutionPayload is also called from // prepareNextSlot, which is an advance call to execution engine to start building payload // Actual payload isn't produced till getPayload is called. - return {isPremerge: false, payloadId, prepType}; + return {payloadId, prepType}; } async function prepareExecutionPayloadHeader( chain: { - eth1: IEth1ForBlockProduction; executionBuilder?: IExecutionBuilder; config: ChainForkConfig; }, @@ -608,53 +561,23 @@ async function prepareExecutionPayloadHeader( throw Error("executionBuilder required"); } - const parentHashRes = await getExecutionPayloadParentHash(chain, state); - if (parentHashRes.isPremerge) { - throw Error("External builder disabled pre-merge"); - } - - const {parentHash} = parentHashRes; + const {parentHash} = getExecutionPayloadParentHash(chain, state); return chain.executionBuilder.getHeader(fork, state.slot, parentHash, proposerPubKey); } -export async function getExecutionPayloadParentHash( - chain: { - eth1: IEth1ForBlockProduction; +export function getExecutionPayloadParentHash( + _chain: { config: ChainForkConfig; }, state: CachedBeaconStateExecutions -): Promise<{isPremerge: true} | {isPremerge: false; parentHash: Root}> { - // Use different POW block hash parent for block production based on merge status. - // Returned value of null == using an empty ExecutionPayload value - if (isMergeTransitionComplete(state)) { - // Post-merge, normal payload - return {isPremerge: false, parentHash: state.latestExecutionPayloadHeader.blockHash}; - } - - if ( - !ssz.Root.equals(chain.config.TERMINAL_BLOCK_HASH, ZERO_HASH) && - getCurrentEpoch(state) < chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - ) { - throw new Error( - `InvalidMergeTBH epoch: expected >= ${ - chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - }, actual: ${getCurrentEpoch(state)}` - ); - } - - const terminalPowBlockHash = await chain.eth1.getTerminalPowBlock(); - if (terminalPowBlockHash === null) { - // Pre-merge, no prepare payload call is needed - return {isPremerge: true}; - } - // Signify merge via producing on top of the last PoW block - return {isPremerge: false, parentHash: terminalPowBlockHash}; +): {parentHash: Root} { + // Post-merge, use the latest execution payload header block hash as parent + return {parentHash: state.latestExecutionPayloadHeader.blockHash}; } -export async function getPayloadAttributesForSSE( +export function getPayloadAttributesForSSE( fork: ForkPostBellatrix, chain: { - eth1: IEth1ForBlockProduction; config: ChainForkConfig; }, { @@ -663,30 +586,24 @@ export async function getPayloadAttributesForSSE( parentBlockRoot, feeRecipient, }: {prepareState: CachedBeaconStateExecutions; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string} -): Promise { - const parentHashRes = await getExecutionPayloadParentHash(chain, prepareState); - - if (!parentHashRes.isPremerge) { - const {parentHash} = parentHashRes; - const payloadAttributes = preparePayloadAttributes(fork, chain, { - prepareState, - prepareSlot, - parentBlockRoot, - feeRecipient, - }); - - const ssePayloadAttributes: SSEPayloadAttributes = { - proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot), - proposalSlot: prepareSlot, - parentBlockNumber: prepareState.latestExecutionPayloadHeader.blockNumber, - parentBlockRoot, - parentBlockHash: parentHash, - payloadAttributes, - }; - return ssePayloadAttributes; - } +): SSEPayloadAttributes { + const {parentHash} = getExecutionPayloadParentHash(chain, prepareState); + const payloadAttributes = preparePayloadAttributes(fork, chain, { + prepareState, + prepareSlot, + parentBlockRoot, + feeRecipient, + }); - throw Error("The execution is still pre-merge"); + const ssePayloadAttributes: SSEPayloadAttributes = { + proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot), + proposalSlot: prepareSlot, + parentBlockNumber: prepareState.latestExecutionPayloadHeader.blockNumber, + parentBlockRoot, + parentBlockHash: parentHash, + payloadAttributes, + }; + return ssePayloadAttributes; } function preparePayloadAttributes( diff --git a/packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts b/packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts deleted file mode 100644 index ea2d134fe4dd..000000000000 --- a/packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts +++ /dev/null @@ -1,328 +0,0 @@ -import {ChainConfig} from "@lodestar/config"; -import {RootHex} from "@lodestar/types"; -import {Logger, pruneSetToMax, toRootHex} from "@lodestar/utils"; -import {ZERO_HASH_HEX} from "../constants/index.js"; -import {Metrics} from "../metrics/index.js"; -import {enumToIndexMap} from "../util/enum.js"; -import {EthJsonRpcBlockRaw, IEth1Provider, PowMergeBlock, PowMergeBlockTimestamp, TDProgress} from "./interface.js"; -import {dataToRootHex, quantityToBigint, quantityToNum} from "./provider/utils.js"; - -export enum StatusCode { - STOPPED = "STOPPED", - SEARCHING = "SEARCHING", - FOUND = "FOUND", -} - -type Status = - | {code: StatusCode.STOPPED} - | {code: StatusCode.SEARCHING} - | {code: StatusCode.FOUND; mergeBlock: PowMergeBlock}; - -/** For metrics, index order = declaration order of StatusCode */ -const statusCodeIdx = enumToIndexMap(StatusCode); - -/** - * Bounds `blocksByHashCache` cache, imposing a max distance between highest and lowest block numbers. - * In case of extreme forking the cache might grow unbounded. - */ -const MAX_CACHE_POW_BLOCKS = 1024; - -const MAX_TD_RENDER_VALUE = Number.MAX_SAFE_INTEGER; - -export type Eth1MergeBlockTrackerModules = { - config: ChainConfig; - logger: Logger; - signal: AbortSignal; - metrics: Metrics | null; -}; - -// get_pow_block_at_total_difficulty - -/** - * Follows the eth1 chain to find a (or multiple?) merge blocks that cross the threshold of total terminal difficulty - * - * Finding the mergeBlock could be done in demand when proposing pre-merge blocks. However, that would slow block - * production during the weeks between BELLATRIX_EPOCH and TTD. - */ -export class Eth1MergeBlockTracker { - private readonly config: ChainConfig; - private readonly logger: Logger; - private readonly metrics: Metrics | null; - - private readonly blocksByHashCache = new Map(); - private readonly intervals: NodeJS.Timeout[] = []; - - private status: Status; - private latestEth1Block: PowMergeBlockTimestamp | null = null; - private getTerminalPowBlockFromEth1Promise: Promise | null = null; - private readonly safeTDFactor: bigint; - - constructor( - {config, logger, signal, metrics}: Eth1MergeBlockTrackerModules, - private readonly eth1Provider: IEth1Provider - ) { - this.config = config; - this.logger = logger; - this.metrics = metrics; - - this.status = {code: StatusCode.STOPPED}; - - signal.addEventListener("abort", () => this.close(), {once: true}); - - this.safeTDFactor = getSafeTDFactor(this.config.TERMINAL_TOTAL_DIFFICULTY); - const scaledTTD = this.config.TERMINAL_TOTAL_DIFFICULTY / this.safeTDFactor; - - // Only run metrics if necessary - if (metrics) { - // TTD can't be dynamically changed during execution, register metric once - metrics.eth1.eth1MergeTTD.set(Number(scaledTTD as bigint)); - metrics.eth1.eth1MergeTDFactor.set(Number(this.safeTDFactor as bigint)); - - metrics.eth1.eth1MergeStatus.addCollect(() => { - // Set merge ttd, merge status and merge block status - metrics.eth1.eth1MergeStatus.set(statusCodeIdx[this.status.code]); - - if (this.latestEth1Block !== null) { - // Set latestBlock stats - metrics.eth1.eth1LatestBlockNumber.set(this.latestEth1Block.number); - metrics.eth1.eth1LatestBlockTD.set(Number(this.latestEth1Block.totalDifficulty / this.safeTDFactor)); - metrics.eth1.eth1LatestBlockTimestamp.set(this.latestEth1Block.timestamp); - } - }); - } - } - - /** - * Returns the most recent POW block that satisfies the merge block condition - */ - async getTerminalPowBlock(): Promise { - switch (this.status.code) { - case StatusCode.STOPPED: - // If not module is not polling fetch the mergeBlock explicitly - return this.getTerminalPowBlockFromEth1(); - - case StatusCode.SEARCHING: - // Assume that polling would have found the block - return null; - - case StatusCode.FOUND: - return this.status.mergeBlock; - } - } - - getTDProgress(): TDProgress | null { - if (this.latestEth1Block === null) { - return this.latestEth1Block; - } - - const tdDiff = this.config.TERMINAL_TOTAL_DIFFICULTY - this.latestEth1Block.totalDifficulty; - - if (tdDiff > BigInt(0)) { - return { - ttdHit: false, - tdFactor: this.safeTDFactor, - tdDiffScaled: Number((tdDiff / this.safeTDFactor) as bigint), - ttd: this.config.TERMINAL_TOTAL_DIFFICULTY, - td: this.latestEth1Block.totalDifficulty, - timestamp: this.latestEth1Block.timestamp, - }; - } - return { - ttdHit: true, - }; - } - - /** - * Get a POW block by hash checking the local cache first - */ - async getPowBlock(powBlockHash: string): Promise { - // Check cache first - const cachedBlock = this.blocksByHashCache.get(powBlockHash); - if (cachedBlock) { - return cachedBlock; - } - - // Fetch from node - const blockRaw = await this.eth1Provider.getBlockByHash(powBlockHash); - if (blockRaw) { - const block = toPowBlock(blockRaw); - this.cacheBlock(block); - return block; - } - - return null; - } - - /** - * Should only start polling for mergeBlock if: - * - after BELLATRIX_FORK_EPOCH - * - Beacon node synced - * - head state not isMergeTransitionComplete - */ - startPollingMergeBlock(): void { - if (this.status.code !== StatusCode.STOPPED) { - return; - } - - this.status = {code: StatusCode.SEARCHING}; - this.logger.info("Starting search for terminal POW block", { - TERMINAL_TOTAL_DIFFICULTY: this.config.TERMINAL_TOTAL_DIFFICULTY, - }); - - const interval = setInterval(() => { - // Preemptively try to find merge block and cache it if found. - // Future callers of getTerminalPowBlock() will re-use the cached found mergeBlock. - this.getTerminalPowBlockFromEth1().catch((e) => { - this.logger.error("Error on findMergeBlock", {}, e as Error); - this.metrics?.eth1.eth1PollMergeBlockErrors.inc(); - }); - }, this.config.SECONDS_PER_ETH1_BLOCK * 1000); - - this.intervals.push(interval); - } - - private close(): void { - this.intervals.forEach(clearInterval); - } - - private async getTerminalPowBlockFromEth1(): Promise { - if (!this.getTerminalPowBlockFromEth1Promise) { - this.getTerminalPowBlockFromEth1Promise = this.internalGetTerminalPowBlockFromEth1() - .then((mergeBlock) => { - // Persist found merge block here to affect both caller paths: - // - internal searcher - // - external caller if STOPPED - if (mergeBlock && this.status.code !== StatusCode.FOUND) { - if (this.status.code === StatusCode.SEARCHING) { - this.close(); - } - - this.logger.info("Terminal POW block found!", { - hash: mergeBlock.blockHash, - number: mergeBlock.number, - totalDifficulty: mergeBlock.totalDifficulty, - }); - - this.status = {code: StatusCode.FOUND, mergeBlock}; - this.metrics?.eth1.eth1MergeBlockDetails.set( - { - terminalBlockHash: mergeBlock.blockHash, - // Convert all number/bigints to string labels - terminalBlockNumber: mergeBlock.number.toString(10), - terminalBlockTD: mergeBlock.totalDifficulty.toString(10), - }, - 1 - ); - } - - return mergeBlock; - }) - .finally(() => { - this.getTerminalPowBlockFromEth1Promise = null; - }); - } else { - // This should no happen, since getTerminalPowBlockFromEth1() should resolve faster than SECONDS_PER_ETH1_BLOCK. - // else something is wrong: the el-cl comms are two slow, or the backsearch got stuck in a deep search. - this.metrics?.eth1.getTerminalPowBlockPromiseCacheHit.inc(); - } - - return this.getTerminalPowBlockFromEth1Promise; - } - - /** - * **internal** + **unsafe** since it can create multiple backward searches that overload the eth1 client. - * Must be called in a wrapper to ensure that there's only once concurrent call to this fn. - */ - private async internalGetTerminalPowBlockFromEth1(): Promise { - // Search merge block by hash - // Terminal block hash override takes precedence over terminal total difficulty - const terminalBlockHash = toRootHex(this.config.TERMINAL_BLOCK_HASH); - if (terminalBlockHash !== ZERO_HASH_HEX) { - const block = await this.getPowBlock(terminalBlockHash); - if (block) { - return block; - } - // if a TERMINAL_BLOCK_HASH other than ZERO_HASH is configured and we can't find it, return NONE - return null; - } - - // Search merge block by TTD - const latestBlockRaw = await this.eth1Provider.getBlockByNumber("latest"); - if (!latestBlockRaw) { - throw Error("getBlockByNumber('latest') returned null"); - } - - let block = toPowBlock(latestBlockRaw); - this.latestEth1Block = {...block, timestamp: quantityToNum(latestBlockRaw.timestamp)}; - this.cacheBlock(block); - - // This code path to look backwards for the merge block is only necessary if: - // - The network has not yet found the merge block - // - There are descendants of the merge block in the eth1 chain - // For the search below to require more than a few hops, multiple block proposers in a row must fail to detect - // an existing merge block. Such situation is extremely unlikely, so this search is left un-optimized. Since - // this class can start eagerly looking for the merge block when not necessary, startPollingMergeBlock() should - // only be called when there is certainty that a mergeBlock search is necessary. - - while (true) { - if (block.totalDifficulty < this.config.TERMINAL_TOTAL_DIFFICULTY) { - // TTD not reached yet - return null; - } - - // else block.totalDifficulty >= this.config.TERMINAL_TOTAL_DIFFICULTY - // Potential mergeBlock! Must find the first block that passes TTD - - // Allow genesis block to reach TTD https://github.com/ethereum/consensus-specs/pull/2719 - if (block.parentHash === ZERO_HASH_HEX) { - return block; - } - - const parent = await this.getPowBlock(block.parentHash); - if (!parent) { - throw Error(`Unknown parent of block with TD>TTD ${block.parentHash}`); - } - - this.metrics?.eth1.eth1ParentBlocksFetched.inc(); - - // block.td > TTD && parent.td < TTD => block is mergeBlock - if (parent.totalDifficulty < this.config.TERMINAL_TOTAL_DIFFICULTY) { - // Is terminal total difficulty block AND has verified block -> parent relationship - return block; - } - block = parent; - } - } - - private cacheBlock(block: PowMergeBlock): void { - this.blocksByHashCache.set(block.blockHash, block); - pruneSetToMax(this.blocksByHashCache, MAX_CACHE_POW_BLOCKS); - } -} - -export function toPowBlock(block: EthJsonRpcBlockRaw): PowMergeBlock { - // Validate untrusted data from API - return { - number: quantityToNum(block.number), - blockHash: dataToRootHex(block.hash), - parentHash: dataToRootHex(block.parentHash), - totalDifficulty: quantityToBigint(block.totalDifficulty), - }; -} - -/** - * TTD values can be very large, for xDAI > 1e45. So scale down. - * To be good, TTD should be rendered as a number < Number.MAX_TD_RENDER_VALUE ~= 9e15 - */ -export function getSafeTDFactor(ttd: bigint): bigint { - const safeIntegerMult = ttd / BigInt(MAX_TD_RENDER_VALUE); - - // TTD < MAX_TD_RENDER_VALUE, no need to scale down - if (safeIntegerMult === BigInt(0)) { - return BigInt(1); - } - - // Return closest power of 10 to ensure TD < max - const safeIntegerMultDigits = safeIntegerMult.toString(10).length; - return BigInt(10) ** BigInt(safeIntegerMultDigits); -} diff --git a/packages/beacon-node/src/eth1/index.ts b/packages/beacon-node/src/eth1/index.ts index 81b2ab6d7b91..02dffb3d4a75 100644 --- a/packages/beacon-node/src/eth1/index.ts +++ b/packages/beacon-node/src/eth1/index.ts @@ -1,9 +1,6 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; -import {Root} from "@lodestar/types"; -import {fromHex} from "@lodestar/utils"; import {Eth1DepositDataTracker, Eth1DepositDataTrackerModules} from "./eth1DepositDataTracker.js"; -import {Eth1MergeBlockTracker, Eth1MergeBlockTrackerModules} from "./eth1MergeBlockTracker.js"; -import {Eth1DataAndDeposits, IEth1ForBlockProduction, IEth1Provider, PowMergeBlock, TDProgress} from "./interface.js"; +import {Eth1DataAndDeposits, IEth1ForBlockProduction, IEth1Provider} from "./interface.js"; import {Eth1Options} from "./options.js"; import {Eth1Provider} from "./provider/eth1Provider.js"; export {Eth1Provider}; @@ -23,23 +20,6 @@ export type {IEth1ForBlockProduction, IEth1Provider}; // // - Fetch ALL deposit events from the deposit contract to build the deposit tree and validate future merkle proofs. // Then it must follow deposit events at a distance roughly similar to the `ETH1_FOLLOW_DISTANCE` parameter above. -// -// - [New bellatrix]: After BELLATRIX_FORK_EPOCH, it must fetch the block with hash -// `state.eth1_data.block_hash` to compute `terminal_total_difficulty`. Note this may change with -// https://github.com/ethereum/consensus-specs/issues/2603. -// -// - [New bellatrix]: On block production post BELLATRIX_FORK_EPOCH, pre merge, the beacon node must find the merge block -// crossing the `terminal_total_difficulty` boundary and include it in the block. After the merge block production -// will just use `execution_engine.assemble_block` without fetching individual blocks. -// -// - [New bellatrix]: Fork-choice must validate the merge block ensuring it crossed the `terminal_total_difficulty` -// boundary, so it must fetch the POW block referenced in the merge block + its POW parent block. -// -// With the merge the beacon node has to follow the eth1 chain at two distances: -// 1. At `ETH1_FOLLOW_DISTANCE` for eth1Data to be re-org safe -// 2. At the head to get the first merge block, tolerating possible re-orgs -// -// Then both streams of blocks should not be merged since it's harder to guard against re-orgs from (2) to (1). export function initializeEth1ForBlockProduction( opts: Eth1Options, @@ -59,12 +39,8 @@ export function initializeEth1ForBlockProduction( export class Eth1ForBlockProduction implements IEth1ForBlockProduction { private readonly eth1DepositDataTracker: Eth1DepositDataTracker | null; - private readonly eth1MergeBlockTracker: Eth1MergeBlockTracker; - constructor( - opts: Eth1Options, - modules: Eth1DepositDataTrackerModules & Eth1MergeBlockTrackerModules & {eth1Provider?: IEth1Provider} - ) { + constructor(opts: Eth1Options, modules: Eth1DepositDataTrackerModules & {eth1Provider?: IEth1Provider}) { const eth1Provider = modules.eth1Provider || new Eth1Provider( @@ -77,8 +53,6 @@ export class Eth1ForBlockProduction implements IEth1ForBlockProduction { this.eth1DepositDataTracker = opts.disableEth1DepositDataTracker ? null : new Eth1DepositDataTracker(opts, modules, eth1Provider); - - this.eth1MergeBlockTracker = new Eth1MergeBlockTracker(modules, eth1Provider); } async getEth1DataAndDeposits(state: CachedBeaconStateAllForks): Promise { @@ -88,23 +62,6 @@ export class Eth1ForBlockProduction implements IEth1ForBlockProduction { return this.eth1DepositDataTracker.getEth1DataAndDeposits(state); } - async getTerminalPowBlock(): Promise { - const block = await this.eth1MergeBlockTracker.getTerminalPowBlock(); - return block && fromHex(block.blockHash); - } - - getPowBlock(powBlockHash: string): Promise { - return this.eth1MergeBlockTracker.getPowBlock(powBlockHash); - } - - getTDProgress(): TDProgress | null { - return this.eth1MergeBlockTracker.getTDProgress(); - } - - startPollingMergeBlock(): void { - this.eth1MergeBlockTracker.startPollingMergeBlock(); - } - isPollingEth1Data(): boolean { return this.eth1DepositDataTracker?.isPollingEth1Data() ?? false; } @@ -127,30 +84,10 @@ export class Eth1ForBlockProductionDisabled implements IEth1ForBlockProduction { return {eth1Data: state.eth1Data, deposits: []}; } - /** - * Will miss the oportunity to propose the merge block but will still produce valid blocks - */ - async getTerminalPowBlock(): Promise { - return null; - } - - /** Will not be able to validate the merge block */ - async getPowBlock(_powBlockHash: string): Promise { - throw Error("eth1 must be enabled to verify merge block"); - } - - getTDProgress(): TDProgress | null { - return null; - } - isPollingEth1Data(): boolean { return false; } - startPollingMergeBlock(): void { - // Ignore - } - stopPollingEth1Data(): void { // Ignore } diff --git a/packages/beacon-node/src/eth1/interface.ts b/packages/beacon-node/src/eth1/interface.ts index c247e30a691c..eecec8fc7756 100644 --- a/packages/beacon-node/src/eth1/interface.ts +++ b/packages/beacon-node/src/eth1/interface.ts @@ -1,6 +1,6 @@ import {BeaconConfig} from "@lodestar/config"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; -import {Root, RootHex, phase0} from "@lodestar/types"; +import {phase0} from "@lodestar/types"; export type EthJsonRpcBlockRaw = { /** the block number. null when its pending block. `"0x1b4"` */ @@ -47,22 +47,6 @@ export type Eth1DataAndDeposits = { export interface IEth1ForBlockProduction { getEth1DataAndDeposits(state: CachedBeaconStateAllForks): Promise; - /** Returns the most recent POW block that satisfies the merge block condition */ - getTerminalPowBlock(): Promise; - /** Get a POW block by hash checking the local cache first */ - getPowBlock(powBlockHash: string): Promise; - - /** Get current TD progress for log notifier */ - getTDProgress(): TDProgress | null; - - /** - * Should only start polling for mergeBlock if: - * - after BELLATRIX_FORK_EPOCH - * - Beacon node synced - * - head state not isMergeTransitionComplete - */ - startPollingMergeBlock(): void; - isPollingEth1Data(): boolean; /** @@ -78,34 +62,6 @@ export type Eth1Block = { timestamp: number; }; -export type PowMergeBlock = { - number: number; - blockHash: RootHex; - parentHash: RootHex; - totalDifficulty: bigint; -}; - -export type PowMergeBlockTimestamp = PowMergeBlock & { - /** in seconds */ - timestamp: number; -}; - -export type TDProgress = - | { - ttdHit: false; - /** Power of ten by which tdDiffScaled is scaled down */ - tdFactor: bigint; - /** (TERMINAL_TOTAL_DIFFICULTY - block.totalDifficulty) / tdFactor */ - tdDiffScaled: number; - /** TERMINAL_TOTAL_DIFFICULTY */ - ttd: bigint; - /** totalDifficulty of latest fetched eth1 block */ - td: bigint; - /** timestamp in sec of latest fetched eth1 block */ - timestamp: number; - } - | {ttdHit: true}; - export type BatchDepositEvents = { depositEvents: phase0.DepositEvent[]; blockNumber: number; diff --git a/packages/beacon-node/src/metrics/metrics/lodestar.ts b/packages/beacon-node/src/metrics/metrics/lodestar.ts index 721b6ffbcbac..7e352e318ec6 100644 --- a/packages/beacon-node/src/metrics/metrics/lodestar.ts +++ b/packages/beacon-node/src/metrics/metrics/lodestar.ts @@ -1668,58 +1668,6 @@ export function createLodestarMetrics( name: "lodestar_eth1_logs_batch_size_dynamic", help: "Dynamic batch size to fetch deposit logs", }), - - // Merge Search info - eth1MergeStatus: register.gauge({ - name: "lodestar_eth1_merge_status", - help: "Eth1 Merge Status 0 PRE_MERGE 1 SEARCHING 2 FOUND 3 POST_MERGE", - }), - eth1MergeTDFactor: register.gauge({ - name: "lodestar_eth1_merge_td_factor", - help: "TTD set for the merge", - }), - eth1MergeTTD: register.gauge({ - name: "lodestar_eth1_merge_ttd", - help: "TTD set for the merge scaled down by td_factor", - }), - - eth1PollMergeBlockErrors: register.gauge({ - name: "lodestar_eth1_poll_merge_block_errors_total", - help: "Total count of errors polling merge block", - }), - getTerminalPowBlockPromiseCacheHit: register.gauge({ - name: "lodestar_eth1_get_terminal_pow_block_promise_cache_hit_total", - help: "Total count of skipped runs in poll merge block, because a previous promise existed", - }), - eth1ParentBlocksFetched: register.gauge({ - name: "lodestar_eth1_parent_blocks_fetched_total", - help: "Total count of parent blocks fetched searching for merge block", - }), - - // Latest block details - eth1LatestBlockTD: register.gauge({ - name: "lodestar_eth1_latest_block_ttd", - help: "Eth1 latest Block td scaled down by td_factor", - }), - eth1LatestBlockNumber: register.gauge({ - name: "lodestar_eth1_latest_block_number", - help: "Eth1 latest block number", - }), - eth1LatestBlockTimestamp: register.gauge({ - name: "lodestar_eth1_latest_block_timestamp", - help: "Eth1 latest block timestamp", - }), - - // Merge details - eth1MergeBlockDetails: register.gauge<{ - terminalBlockHash: string; - terminalBlockNumber: string; - terminalBlockTD: string; - }>({ - name: "lodestar_eth1_merge_block_details", - help: "If found then 1 with terminal block details", - labelNames: ["terminalBlockHash", "terminalBlockNumber", "terminalBlockTD"], - }), }, eth1HttpClient: { diff --git a/packages/beacon-node/src/node/notifier.ts b/packages/beacon-node/src/node/notifier.ts index 36ed384656e1..7dfd11eb7b00 100644 --- a/packages/beacon-node/src/node/notifier.ts +++ b/packages/beacon-node/src/node/notifier.ts @@ -1,12 +1,11 @@ import {BeaconConfig} from "@lodestar/config"; -import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice"; +import {ProtoBlock} from "@lodestar/fork-choice"; import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params"; import { CachedBeaconStateAllForks, computeEpochAtSlot, computeStartSlotAtEpoch, isExecutionCachedStateType, - isMergeTransitionComplete, } from "@lodestar/state-transition"; import {Epoch} from "@lodestar/types"; import {ErrorAborted, Logger, prettyBytes, prettyBytesShort, sleep} from "@lodestar/utils"; @@ -36,7 +35,6 @@ export async function runNodeNotifier(modules: NodeNotifierModules): Promise { - const logger = testLogger(); - - function getConfig(ttd: bigint): ChainConfig { - return { - // Set time units to 1s to make the test faster - SECONDS_PER_ETH1_BLOCK: 1, - SLOT_DURATION_MS: 1000, - DEPOSIT_CONTRACT_ADDRESS: Buffer.alloc(32, 0), - TERMINAL_TOTAL_DIFFICULTY: ttd, - TERMINAL_BLOCK_HASH: ZERO_HASH, - } as Partial as ChainConfig; - } - const eth1Config = {DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}; - - // Compute lazily since getGoerliRpcUrl() throws if GOERLI_RPC_URL is not set - let eth1Options: Eth1Options; - beforeAll(() => { - eth1Options = { - enabled: true, - providerUrls: [getGoerliRpcUrl()], - depositContractDeployBlock: 0, - unsafeAllowDepositDataOverwrite: false, - }; - }); - - let controller: AbortController; - beforeEach(() => { - controller = new AbortController(); - }); - afterEach(() => controller.abort()); - - it("Should find terminal pow block through TERMINAL_BLOCK_HASH", async () => { - const eth1Provider = new Eth1Provider(eth1Config, eth1Options, controller.signal); - const latestBlock = await eth1Provider.getBlockByNumber("latest"); - if (!latestBlock) throw Error("No latestBlock"); - const terminalTotalDifficulty = quantityToBigint(latestBlock.totalDifficulty) - BigInt(1000); - const config = getConfig(terminalTotalDifficulty); - config.TERMINAL_BLOCK_HASH = fromHexString(latestBlock.hash); - const eth1MergeBlockTracker = new Eth1MergeBlockTracker( - { - config, - logger, - signal: controller.signal, - metrics: null, - }, - eth1Provider as IEth1Provider - ); - - // Wait for Eth1MergeBlockTracker to find at least one merge block - while (!controller.signal.aborted) { - if (await eth1MergeBlockTracker.getTerminalPowBlock()) break; - await sleep(500, controller.signal); - } - - // Status should acknowlege merge block is found - expect(eth1MergeBlockTracker["status"]).toBe(StatusCode.FOUND); - - // Given the total difficulty offset the block that has TTD is the `difficultyOffset`nth block - const mergeBlock = await eth1MergeBlockTracker.getTerminalPowBlock(); - if (!mergeBlock) throw Error("terminal pow block not found"); - expect(mergeBlock.totalDifficulty).toBe(quantityToBigint(latestBlock.totalDifficulty)); - }); - - it("Should find merge block polling future 'latest' blocks", async () => { - const eth1Provider = new Eth1Provider(eth1Config, eth1Options, controller.signal); - const latestBlock = await eth1Provider.getBlockByNumber("latest"); - if (!latestBlock) throw Error("No latestBlock"); - - // Set TTD to current totalDifficulty + 1, so the next block is the merge block - const terminalTotalDifficulty = quantityToBigint(latestBlock.totalDifficulty) + BigInt(1); - - const eth1MergeBlockTracker = new Eth1MergeBlockTracker( - { - config: getConfig(terminalTotalDifficulty), - logger, - signal: controller.signal, - metrics: null, - }, - eth1Provider as IEth1Provider - ); - - // Wait for Eth1MergeBlockTracker to find at least one merge block - while (!controller.signal.aborted) { - if (await eth1MergeBlockTracker.getTerminalPowBlock()) break; - await sleep(500, controller.signal); - } - - // Status should acknowlege merge block is found - expect(eth1MergeBlockTracker["status"]).toBe(StatusCode.FOUND); - - // Given the total difficulty offset the block that has TTD is the `difficultyOffset`nth block - const mergeBlock = await eth1MergeBlockTracker.getTerminalPowBlock(); - if (!mergeBlock) throw Error("mergeBlock not found"); - // "mergeBlock.totalDifficulty is not >= TTD" - expect(mergeBlock.totalDifficulty).toBeGreaterThanOrEqual(terminalTotalDifficulty); - }); - - it("Should find merge block fetching past blocks", async () => { - const eth1Provider = new Eth1Provider(eth1Config, eth1Options, controller.signal); - const latestBlock = await eth1Provider.getBlockByNumber("latest"); - if (!latestBlock) throw Error("No latestBlock"); - - // Set TTD to current totalDifficulty + 1, so the previous block is the merge block - const terminalTotalDifficulty = quantityToBigint(latestBlock.totalDifficulty) - BigInt(1); - - const eth1MergeBlockTracker = new Eth1MergeBlockTracker( - { - config: getConfig(terminalTotalDifficulty), - logger, - signal: controller.signal, - metrics: null, - }, - eth1Provider as IEth1Provider - ); - - // Wait for Eth1MergeBlockTracker to find at least one merge block - while (!controller.signal.aborted) { - if (await eth1MergeBlockTracker.getTerminalPowBlock()) break; - await sleep(500, controller.signal); - } - - // Status should acknowlege merge block is found - expect(eth1MergeBlockTracker["status"]).toBe(StatusCode.FOUND); - - // Given the total difficulty offset the block that has TTD is the `difficultyOffset`nth block - const mergeBlock = await eth1MergeBlockTracker.getTerminalPowBlock(); - if (!mergeBlock) throw Error("mergeBlock not found"); - // "mergeBlock.totalDifficulty is not >= TTD" - expect(mergeBlock.totalDifficulty).toBeGreaterThanOrEqual(terminalTotalDifficulty); - }); -}); diff --git a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts index 08d0481d01d6..f6818be1df5d 100644 --- a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts @@ -62,8 +62,9 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { unrealizedJustifiedRoot: toHexString(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root), - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: toHexString(finalizedCheckpoint.root), + executionPayloadNumber: computeStartSlotAtEpoch(finalizedCheckpoint.epoch), + executionStatus: ExecutionStatus.Valid, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, @@ -88,8 +89,9 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { unrealizedJustifiedRoot: toHexString(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root), - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: toHexString(getBlockRootAtSlot(originalState, slot)), + executionPayloadNumber: slot, + executionStatus: ExecutionStatus.Valid, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, diff --git a/packages/beacon-node/test/scripts/el-interop/geth/pre-merge.sh b/packages/beacon-node/test/scripts/el-interop/geth/pre-merge.sh deleted file mode 100755 index fccf432e145c..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/geth/pre-merge.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -x - -scriptDir=$(dirname $0) -currentDir=$(pwd) - -. $scriptDir/common-setup.sh - -$EL_BINARY_DIR/geth --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret $currentDir/$DATA_DIR/jwtsecret --datadir $DATA_DIR --allow-insecure-unlock --unlock $pubKey --password $DATA_DIR/password.txt --nodiscover --mine --syncmode full diff --git a/packages/beacon-node/test/scripts/el-interop/gethdocker/pre-merge.sh b/packages/beacon-node/test/scripts/el-interop/gethdocker/pre-merge.sh deleted file mode 100755 index a760fec65b30..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/gethdocker/pre-merge.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -x - -scriptDir=$(dirname $0) -currentDir=$(pwd) - -. $scriptDir/common-setup.sh - -# EL_BINARY_DIR refers to the local docker image build from kiln/gethdocker folder -docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --name custom-execution --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret /data/jwtsecret --allow-insecure-unlock --unlock $pubKey --password /data/password.txt --datadir /data/geth --nodiscover --mine --syncmode full diff --git a/packages/beacon-node/test/scripts/el-interop/mergemock/common-setup.sh b/packages/beacon-node/test/scripts/el-interop/mergemock/common-setup.sh deleted file mode 100755 index aea73c4c7a40..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/mergemock/common-setup.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -x - -echo $TTD -echo $DATA_DIR -echo $EL_BINARY_DIR -echo $JWT_SECRET_HEX - -echo $scriptDir -echo $currentDir - - -env TTD=$TTD envsubst < $scriptDir/genesisPre.tmpl > $DATA_DIR/genesis.json -echo "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" > $DATA_DIR/sk.json -echo "12345678" > $DATA_DIR/password.txt -pubKey="0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" - -# echo a hex encoded 256 bit secret into a file, however remove leading 0x as mergemock doesnt like it -echo $JWT_SECRET_HEX | sed 's/0x//' > $DATA_DIR/jwtsecret -docker rm -f custom-execution diff --git a/packages/beacon-node/test/scripts/el-interop/mergemock/genesisPre.tmpl b/packages/beacon-node/test/scripts/el-interop/mergemock/genesisPre.tmpl deleted file mode 100644 index e3e1fa4141be..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/mergemock/genesisPre.tmpl +++ /dev/null @@ -1,36 +0,0 @@ -{ - "config": { - "chainId": 1, - "homesteadBlock": 0, - "eip150Block": 0, - "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "berlinBlock": 0, - "londonBlock": 0, - "clique": { - "period": 5, - "epoch": 30000 - }, - "terminalTotalDifficulty": ${TTD} - }, - "nonce": "0x42", - "timestamp": "0x0", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x1c9c380", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "alloc": { - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {"balance": "0x6d6172697573766477000000"} - }, - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "baseFeePerGas": "0x7" -} diff --git a/packages/beacon-node/test/scripts/el-interop/mergemock/jwt.hex b/packages/beacon-node/test/scripts/el-interop/mergemock/jwt.hex deleted file mode 100644 index ed20b10a08a6..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/mergemock/jwt.hex +++ /dev/null @@ -1 +0,0 @@ -0xdc6457099f127cf0bac78de8b297df04951281909db4f58b43def7c7151e765d diff --git a/packages/beacon-node/test/scripts/el-interop/mergemock/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/mergemock/post-merge.sh deleted file mode 100755 index 902b868cc8c2..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/mergemock/post-merge.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -x - -scriptDir=$(dirname $0) -currentDir=$(pwd) - -. $scriptDir/common-setup.sh - -# if we don't provide any datadir merge mock stores data in memory which is fine by us -docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --name custom-execution --network host -v $currentDir/$DATA_DIR/genesis.json:/usr/app/genesis.json -v $currentDir/$DATA_DIR/jwtsecret:/usr/app/jwt.hex $EL_BINARY_DIR relay --listen-addr 127.0.0.1:$ETH_PORT --engine-listen-addr 127.0.0.1:$ENGINE_PORT --log.level debug --genesis-validators-root 0x3e8bd71d9925794b4f5e8623e15094ea6edc0fd206e3551e13dd2d10e08fbaba diff --git a/packages/beacon-node/test/scripts/el-interop/nethermind/pre-merge.sh b/packages/beacon-node/test/scripts/el-interop/nethermind/pre-merge.sh deleted file mode 100755 index 72cf36efa649..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/nethermind/pre-merge.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -x - -scriptDir=$(dirname $0) -currentDir=$(pwd) - -. $scriptDir/common-setup.sh - -cd $EL_BINARY_DIR -dotnet run -c Release -- --config themerge_kiln_m2 --Merge.TerminalTotalDifficulty $TTD --JsonRpc.JwtSecretFile $currentDir/$DATA_DIR/jwtsecret --Merge.Enabled true --Init.DiagnosticMode=None --JsonRpc.Enabled true --JsonRpc.Host 0.0.0.0 --JsonRpc.AdditionalRpcUrls "http://localhost:$ETH_PORT|http|net;eth;subscribe;engine;web3;client|no-auth,http://localhost:$ENGINE_PORT|http|eth;engine" --Sync.SnapSync false diff --git a/packages/beacon-node/test/scripts/el-interop/netherminddocker/pre-merge.sh b/packages/beacon-node/test/scripts/el-interop/netherminddocker/pre-merge.sh deleted file mode 100755 index 468e00933576..000000000000 --- a/packages/beacon-node/test/scripts/el-interop/netherminddocker/pre-merge.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -x - -scriptDir=$(dirname $0) -currentDir=$(pwd) - -. $scriptDir/common-setup.sh - -echo "sleeping for 10 seconds..." - -docker run --rm --network host --name custom-execution -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --datadir /data/nethermind --config themerge_kiln_m2 --Merge.TerminalTotalDifficulty $TTD --JsonRpc.JwtSecretFile /data/jwtsecret --Merge.Enabled true --Init.DiagnosticMode=None --JsonRpc.Enabled true --JsonRpc.Host 0.0.0.0 --JsonRpc.AdditionalRpcUrls "http://localhost:$ETH_PORT|http|net;eth;subscribe;engine;web3;client|no-auth,http://localhost:$ENGINE_PORT|http|eth;engine" --Sync.SnapSync false diff --git a/packages/beacon-node/test/sim/mergemock.test.ts b/packages/beacon-node/test/sim/mergemock.test.ts deleted file mode 100644 index 1fe15518f76a..000000000000 --- a/packages/beacon-node/test/sim/mergemock.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -import fs from "node:fs"; -import {afterAll, afterEach, describe, it, vi} from "vitest"; -import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {routes} from "@lodestar/api"; -import {ChainConfig} from "@lodestar/config"; -import {TimestampFormatCode} from "@lodestar/logger"; -import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {Epoch, SignedBeaconBlock, bellatrix} from "@lodestar/types"; -import {LogLevel, sleep} from "@lodestar/utils"; -import {ValidatorProposerConfig} from "@lodestar/validator"; -import {BeaconRestApiServerOpts} from "../../src/api/index.js"; -import {ZERO_HASH} from "../../src/constants/index.js"; -import {BuilderStatus} from "../../src/execution/builder/http.js"; -import {Eth1Provider} from "../../src/index.js"; -import {ClockEvent} from "../../src/util/clock.js"; -import {TestLoggerOpts, testLogger} from "../utils/logger.js"; -import {getDevBeaconNode} from "../utils/node/beacon.js"; -import {simTestInfoTracker} from "../utils/node/simTest.js"; -import {getAndInitDevValidators} from "../utils/node/validator.js"; -import {ELClient, ELStartMode, runEL} from "../utils/runEl.js"; -import {logFilesDir} from "./params.js"; -import {shell} from "./shell.js"; - -// NOTE: How to run -// EL_BINARY_DIR=g11tech/mergemock:latest EL_SCRIPT_DIR=mergemock LODESTAR_PRESET=mainnet ETH_PORT=8661 ENGINE_PORT=8551 yarn vitest run test/sim/mergemock.test.ts -// ``` - -const jwtSecretHex = "0xdc6457099f127cf0bac78de8b297df04951281909db4f58b43def7c7151e765d"; - -describe("executionEngine / ExecutionEngineHttp", () => { - if (!process.env.EL_BINARY_DIR || !process.env.EL_SCRIPT_DIR) { - throw Error( - `EL ENV must be provided, EL_BINARY_DIR: ${process.env.EL_BINARY_DIR}, EL_SCRIPT_DIR: ${process.env.EL_SCRIPT_DIR}` - ); - } - vi.setConfig({testTimeout: 10 * 60 * 1000}); - - const dataPath = fs.mkdtempSync("lodestar-test-mergemock"); - const elSetupConfig = { - elScriptDir: process.env.EL_SCRIPT_DIR, - elBinaryDir: process.env.EL_BINARY_DIR, - }; - const elRunOptions = { - dataPath, - jwtSecretHex, - enginePort: parseInt(process.env.ENGINE_PORT ?? "8551"), - ethPort: parseInt(process.env.ETH_PORT ?? "8545"), - }; - - const controller = new AbortController(); - afterAll(async () => { - controller?.abort(); - await shell(`sudo rm -rf ${dataPath}`); - }); - - const afterEachCallbacks: (() => Promise | void)[] = []; - afterEach(async () => { - while (afterEachCallbacks.length > 0) { - const callback = afterEachCallbacks.pop(); - if (callback) await callback(); - } - }); - - it("Test builder flow", async () => { - console.log("\n\nPost-merge, run for a few blocks\n\n"); - const {elClient, tearDownCallBack} = await runEL( - {...elSetupConfig, mode: ELStartMode.PostMerge}, - {...elRunOptions, ttd: BigInt(0)}, - controller.signal - ); - afterEachCallbacks.push(() => tearDownCallBack()); - - await runNodeWithEL({ - elClient, - bellatrixEpoch: 0, - testName: "post-merge", - }); - }); - - type RunOpts = {elClient: ELClient; bellatrixEpoch: Epoch; testName: string}; - - async function runNodeWithEL({elClient, bellatrixEpoch, testName}: RunOpts): Promise { - const {genesisBlockHash, ttd, engineRpcUrl, ethRpcUrl} = elClient; - const validatorClientCount = 1; - const validatorsPerClient = 32; - - const testParams: Pick = { - SLOT_DURATION_MS: 2000, - }; - - // Should reach justification in 6 epochs max. - // Merge block happens at epoch 2 slot 4. Then 4 epochs to finalize - const expectedEpochsToFinish = 1; - // 1 epoch of margin of error - const epochsOfMargin = 1; - const timeoutSetupMargin = 30 * 1000; // Give extra 30 seconds of margin - - // We only expect builder blocks since `builderalways` is configured - // In a perfect run expected builder = 32, expected engine = 0 - // keeping 4 missed slots and 4 engine blocks due to fallback as margin - const expectedBuilderBlocks = 28; - const maximumEngineBlocks = 4; - - // All assertions are tracked w.r.t. fee recipient by attaching different fee recipient to - // execution and builder - const feeRecipientLocal = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - const feeRecipientEngine = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; - const feeRecipientMevBoost = "0xcccccccccccccccccccccccccccccccccccccccc"; - - // delay a bit so regular sync sees it's up to date and sync is completed from the beginning - const genesisSlotsDelay = 8; - - const timeout = - ((epochsOfMargin + expectedEpochsToFinish) * SLOTS_PER_EPOCH + genesisSlotsDelay) * testParams.SLOT_DURATION_MS; - - vi.setConfig({testTimeout: timeout + 2 * timeoutSetupMargin}); - - const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * (testParams.SLOT_DURATION_MS / 1000); - - const testLoggerOpts: TestLoggerOpts = { - level: LogLevel.info, - file: { - filepath: `${logFilesDir}/mergemock-${testName}.log`, - level: LogLevel.debug, - }, - timestampFormat: { - format: TimestampFormatCode.EpochSlot, - genesisTime, - slotsPerEpoch: SLOTS_PER_EPOCH, - secondsPerSlot: testParams.SLOT_DURATION_MS / 1000, - }, - }; - const loggerNodeA = testLogger("Node-A", testLoggerOpts); - - const bn = await getDevBeaconNode({ - params: { - ...testParams, - ALTAIR_FORK_EPOCH: 0, - BELLATRIX_FORK_EPOCH: bellatrixEpoch, - TERMINAL_TOTAL_DIFFICULTY: ttd, - }, - options: { - api: {rest: {enabled: true} as BeaconRestApiServerOpts}, - sync: {isSingleNode: true}, - network: {allowPublishToZeroPeers: true, discv5: null}, - // Now eth deposit/merge tracker methods directly available on engine endpoints - eth1: {enabled: false, providerUrls: [engineRpcUrl], jwtSecretHex}, - executionEngine: {urls: [engineRpcUrl], jwtSecretHex}, - executionBuilder: { - url: ethRpcUrl, - enabled: true, - issueLocalFcUWithFeeRecipient: feeRecipientMevBoost, - allowedFaults: 8, - faultInspectionWindow: 32, - }, - chain: {suggestedFeeRecipient: feeRecipientLocal}, - }, - validatorCount: validatorClientCount * validatorsPerClient, - logger: loggerNodeA, - genesisTime, - eth1BlockHash: fromHexString(genesisBlockHash), - }); - if (!bn.chain.executionBuilder) { - throw Error("executionBuilder should have been initialized"); - } - // Enable builder by default, else because of circuit breaker we always start it with disabled - bn.chain.executionBuilder.updateStatus(BuilderStatus.enabled); - - afterEachCallbacks.push(async () => { - await bn.close(); - await sleep(1000); - }); - - const stopInfoTracker = simTestInfoTracker(bn, loggerNodeA); - const valProposerConfig = { - defaultConfig: { - graffiti: "default graffiti", - strictFeeRecipientCheck: true, - feeRecipient: feeRecipientEngine, - builder: { - gasLimit: 60000000, - selection: routes.validator.BuilderSelection.BuilderAlways, - }, - }, - } as ValidatorProposerConfig; - - const {validators} = await getAndInitDevValidators({ - logPrefix: "mergemock", - node: bn, - validatorsPerClient, - validatorClientCount, - startIndex: 0, - // At least one sim test must use the REST API for beacon <-> validator comms - useRestApi: true, - testLoggerOpts, - valProposerConfig, - }); - - afterEachCallbacks.push(async () => { - await Promise.all(validators.map((v) => v.close())); - }); - - let engineBlocks = 0; - let builderBlocks = 0; - await new Promise((resolve, _reject) => { - bn.chain.emitter.on(routes.events.EventType.block, async (blockData) => { - const {data: fullOrBlindedBlock} = (await bn.api.beacon.getBlockV2({blockId: blockData.block})) as { - data: SignedBeaconBlock; - }; - if (fullOrBlindedBlock !== undefined) { - const blockFeeRecipient = toHexString( - (fullOrBlindedBlock as bellatrix.SignedBeaconBlock).message.body.executionPayload.feeRecipient - ); - if (blockFeeRecipient === feeRecipientMevBoost) { - builderBlocks++; - } else { - engineBlocks++; - } - } - }); - bn.chain.clock.on(ClockEvent.epoch, (epoch) => { - // Resolve only if the finalized checkpoint includes execution payload - if (epoch >= expectedEpochsToFinish) { - console.log("\nGot event epoch, stopping validators and nodes\n"); - resolve(); - } - }); - }); - - // Stop chain and un-subscribe events so the execution engine won't update it's head - // Allow some time to broadcast finalized events and complete the importBlock routine - await Promise.all(validators.map((v) => v.close())); - await bn.close(); - await sleep(500); - - if (bn.chain.beaconProposerCache.get(1) !== feeRecipientEngine) { - throw Error("Invalid feeRecipient set at BN"); - } - - // Assertions to make sure the end state is good - // 1. The proper head is set - const rpc = new Eth1Provider({DEPOSIT_CONTRACT_ADDRESS: ZERO_HASH}, {providerUrls: [engineRpcUrl], jwtSecretHex}); - const consensusHead = bn.chain.forkChoice.getHead(); - const executionHeadBlock = await rpc.getBlockByNumber("latest"); - - if (!executionHeadBlock) throw Error("Execution has not head block"); - if (consensusHead.executionPayloadBlockHash !== executionHeadBlock.hash) { - throw Error( - "Consensus head not equal to execution head: " + - JSON.stringify({ - executionHeadBlockHash: executionHeadBlock.hash, - consensusHeadExecutionPayloadBlockHash: consensusHead.executionPayloadBlockHash, - consensusHeadSlot: consensusHead.slot, - }) - ); - } - - // 2. builder blocks are as expected - if (builderBlocks < expectedBuilderBlocks) { - throw Error(`Incorrect builderBlocks=${builderBlocks} (expected=${expectedBuilderBlocks})`); - } - - // 3. engine blocks do not exceed max limit - if (engineBlocks > maximumEngineBlocks) { - throw Error(`Incorrect engineBlocks=${engineBlocks} (limit=${maximumEngineBlocks})`); - } - - // wait for 1 slot to print current epoch stats - await sleep(1 * bn.config.SLOT_DURATION_MS); - stopInfoTracker(); - console.log("\n\nDone\n\n"); - } -}); diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts index a10ddeb438c9..1589ae38d684 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -40,7 +40,6 @@ import {defaultChainOptions} from "../../../src/chain/options.js"; import {validateBlockDataColumnSidecars} from "../../../src/chain/validation/dataColumnSidecar.js"; import {ZERO_HASH_HEX} from "../../../src/constants/constants.js"; import {Eth1ForBlockProductionDisabled} from "../../../src/eth1/index.js"; -import {PowMergeBlock} from "../../../src/eth1/interface.js"; import {ExecutionPayloadStatus} from "../../../src/execution/engine/interface.js"; import {ExecutionEngineMockBackend} from "../../../src/execution/engine/mock.js"; import {getExecutionEngineFromBackend} from "../../../src/execution/index.js"; @@ -344,9 +343,8 @@ const forkChoiceTest = blockHash: toHexString(powBlock.blockHash), parentHash: toHexString(powBlock.parentHash), }); - // Register PowBlock for `get_pow_block(hash: Hash32)` calls in verifyBlock - eth1.addPowBlock(powBlock); // Register PowBlock to allow validation in execution engine + // Note: eth1.addPowBlock removed since merge validation is no longer needed executionEngineBackend.addPowBlock(powBlock); } @@ -655,24 +653,9 @@ function isCheck(step: Step): step is Checks { return typeof (step as Checks).checks === "object"; } -// Extend Eth1ForBlockProductionDisabled to not have to re-implement new methods -class Eth1ForBlockProductionMock extends Eth1ForBlockProductionDisabled { - private items = new Map(); - - async getPowBlock(powBlockHash: string): Promise { - return this.items.get(powBlockHash) ?? null; - } - - addPowBlock(powBlock: bellatrix.PowBlock): void { - this.items.set(toHexString(powBlock.blockHash), { - // not used by verifyBlock() - number: 0, - blockHash: toHexString(powBlock.blockHash), - parentHash: toHexString(powBlock.parentHash), - totalDifficulty: powBlock.totalDifficulty, - }); - } -} +// Note: PowMergeBlock tracking removed since merge validation is no longer needed +// Just use Eth1ForBlockProductionDisabled directly +const Eth1ForBlockProductionMock = Eth1ForBlockProductionDisabled; specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), { fork_choice: {type: RunnerType.default, fn: forkChoiceTest({onlyPredefinedResponses: false})}, diff --git a/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts b/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts index f35e7be05188..c90d488f7cf3 100644 --- a/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts +++ b/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts @@ -48,7 +48,7 @@ describe("LodestarForkChoice", () => { justifiedBalances[0] = 1; justifiedBalances[1] = 2; justifiedBalances[2] = 3; - const executionStatus = ExecutionStatus.PreMerge; + const executionStatus = ExecutionStatus.Valid; const dataAvailabilityStatus = DataAvailabilityStatus.PreData; const blockDelaySec = 0; diff --git a/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts b/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts deleted file mode 100644 index 5f19c5c4f57e..000000000000 --- a/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts +++ /dev/null @@ -1,256 +0,0 @@ -import {afterEach, beforeEach, describe, expect, it} from "vitest"; -import {toHexString} from "@chainsafe/ssz"; -import {ChainConfig} from "@lodestar/config"; -import {sleep} from "@lodestar/utils"; -import {ZERO_HASH} from "../../../src/constants/index.js"; -import {Eth1MergeBlockTracker, StatusCode, toPowBlock} from "../../../src/eth1/eth1MergeBlockTracker.js"; -import {Eth1ProviderState, EthJsonRpcBlockRaw} from "../../../src/eth1/interface.js"; -import {IEth1Provider} from "../../../src/index.js"; -import {testLogger} from "../../utils/logger.js"; - -describe("eth1 / Eth1MergeBlockTracker", () => { - const logger = testLogger(); - - const terminalTotalDifficulty = 1000; - let config: ChainConfig; - let controller: AbortController; - beforeEach(() => { - controller = new AbortController(); - - config = { - // Set time units to 0 to make the test as fast as possible - SECONDS_PER_ETH1_BLOCK: 0, - SLOT_DURATION_MS: 0, - // Hardcode TTD to a low value - TERMINAL_TOTAL_DIFFICULTY: BigInt(terminalTotalDifficulty), - TERMINAL_BLOCK_HASH: ZERO_HASH, - } as Partial as ChainConfig; - }); - - afterEach(() => controller.abort()); - - it("Should find terminal pow block through TERMINAL_BLOCK_HASH", async () => { - config.TERMINAL_BLOCK_HASH = Buffer.alloc(32, 1); - const block: EthJsonRpcBlockRaw = { - number: toHex(10), - hash: toRootHex(11), - parentHash: toRootHex(10), - totalDifficulty: toHex(100), - timestamp: "0x0", - }; - const terminalPowBlock = toPowBlock(block); - const eth1Provider: IEth1Provider = { - deployBlock: 0, - getBlockNumber: async () => 0, - getBlockByNumber: async () => { - throw Error("Not implemented"); - }, - getBlockByHash: async (blockHashHex): Promise => { - return blockHashHex === toHexString(config.TERMINAL_BLOCK_HASH) ? block : null; - }, - getBlocksByNumber: async (): Promise => { - throw Error("Not implemented"); - }, - getDepositEvents: async (): Promise => { - throw Error("Not implemented"); - }, - validateContract: async (): Promise => { - throw Error("Not implemented"); - }, - getState: () => Eth1ProviderState.ONLINE, - }; - - const eth1MergeBlockTracker = new Eth1MergeBlockTracker( - { - config, - logger, - signal: controller.signal, - metrics: null, - }, - eth1Provider - ); - eth1MergeBlockTracker.startPollingMergeBlock(); - - // Wait for Eth1MergeBlockTracker to find at least one merge block - while (!controller.signal.aborted) { - if (await eth1MergeBlockTracker.getTerminalPowBlock()) break; - await sleep(10, controller.signal); - } - - // Status should acknowlege merge block is found - expect(eth1MergeBlockTracker["status"].code).toBe(StatusCode.FOUND); - - // Given the total difficulty offset the block that has TTD is the `difficultyOffset`nth block - expect(await eth1MergeBlockTracker.getTerminalPowBlock()).toEqual(terminalPowBlock); - }); - - it("Should find terminal pow block polling future 'latest' blocks", async () => { - // Set current network totalDifficulty to behind terminalTotalDifficulty by 5. - // Then on each call to getBlockByNumber("latest") increase totalDifficulty by 1. - const numOfBlocks = 5; - const difficulty = 1; - - let latestBlockPointer = 0; - - const blocks: EthJsonRpcBlockRaw[] = []; - const blocksByHash = new Map(); - - for (let i = 0; i < numOfBlocks + 1; i++) { - const block: EthJsonRpcBlockRaw = { - number: toHex(i), - hash: toRootHex(i + 1), - parentHash: toRootHex(i), - // Latest block is under TTD, so past block search is stopped - totalDifficulty: toHex(terminalTotalDifficulty - numOfBlocks * difficulty + i * difficulty), - timestamp: "0x0", - }; - blocks.push(block); - } - - const eth1Provider: IEth1Provider = { - deployBlock: 0, - getBlockNumber: async () => 0, - getBlockByNumber: async (blockNumber) => { - // On each call simulate that the eth1 chain advances 1 block with +1 totalDifficulty - if (blockNumber === "latest") { - if (latestBlockPointer >= blocks.length) { - throw Error("Fetched too many blocks"); - } - return blocks[latestBlockPointer++]; - } - return blocks[blockNumber]; - }, - getBlockByHash: async (blockHashHex) => blocksByHash.get(blockHashHex) ?? null, - getBlocksByNumber: async (): Promise => { - throw Error("Not implemented"); - }, - getDepositEvents: async (): Promise => { - throw Error("Not implemented"); - }, - validateContract: async (): Promise => { - throw Error("Not implemented"); - }, - getState: () => Eth1ProviderState.ONLINE, - }; - - await runFindMergeBlockTest(eth1Provider, blocks.at(-1) as EthJsonRpcBlockRaw); - }); - - it("Should find terminal pow block fetching past blocks", async () => { - // Set current network totalDifficulty to behind terminalTotalDifficulty by 5. - // Then on each call to getBlockByNumber("latest") increase totalDifficulty by 1. - - const numOfBlocks = 5; - const difficulty = 1; - const ttdOffset = 1 * difficulty; - const hashOffset = 100; - const blocks: EthJsonRpcBlockRaw[] = []; - - for (let i = 0; i < numOfBlocks * 2; i++) { - const block: EthJsonRpcBlockRaw = { - number: toHex(hashOffset + i), - hash: toRootHex(hashOffset + i + 1), - parentHash: toRootHex(hashOffset + i), - // Latest block is under TTD, so past block search is stopped - totalDifficulty: toHex(terminalTotalDifficulty + i * difficulty - ttdOffset), - timestamp: "0x0", - }; - blocks.push(block); - } - - // Before last block (with ttdOffset = 1) is the merge block - const expectedMergeBlock = blocks[ttdOffset]; - - const eth1Provider = mockEth1ProviderFromBlocks(blocks); - await runFindMergeBlockTest(eth1Provider, expectedMergeBlock); - }); - - it("Should find terminal pow block fetching past blocks till genesis", async () => { - // There's no block with TD < TTD, searcher should stop at genesis block - - const numOfBlocks = 5; - const difficulty = 1; - const blocks: EthJsonRpcBlockRaw[] = []; - - for (let i = 0; i < numOfBlocks * 2; i++) { - const block: EthJsonRpcBlockRaw = { - number: toHex(i), - hash: toRootHex(i + 1), - parentHash: toRootHex(i), - // Latest block is under TTD, so past block search is stopped - totalDifficulty: toHex(terminalTotalDifficulty + i * difficulty + 1), - timestamp: "0x0", - }; - blocks.push(block); - } - - // Merge block must be genesis block - const expectedMergeBlock = blocks[0]; - - const eth1Provider = mockEth1ProviderFromBlocks(blocks); - await runFindMergeBlockTest(eth1Provider, expectedMergeBlock); - }); - - function mockEth1ProviderFromBlocks(blocks: EthJsonRpcBlockRaw[]): IEth1Provider { - const blocksByHash = new Map(); - - for (const block of blocks) { - blocksByHash.set(block.hash, block); - } - - return { - deployBlock: 0, - getBlockNumber: async () => 0, - getBlockByNumber: async (blockNumber) => { - // Always return the same block with totalDifficulty > TTD and unknown parent - if (blockNumber === "latest") return blocks.at(-1) as EthJsonRpcBlockRaw; - return blocks[blockNumber]; - }, - getBlockByHash: async (blockHashHex) => blocksByHash.get(blockHashHex) ?? null, - getBlocksByNumber: async (from, to) => blocks.slice(from, to), - getDepositEvents: async (): Promise => { - throw Error("Not implemented"); - }, - validateContract: async (): Promise => { - throw Error("Not implemented"); - }, - getState: () => Eth1ProviderState.ONLINE, - }; - } - - async function runFindMergeBlockTest( - eth1Provider: IEth1Provider, - expectedMergeBlock: EthJsonRpcBlockRaw - ): Promise { - const eth1MergeBlockTracker = new Eth1MergeBlockTracker( - { - config, - logger, - signal: controller.signal, - metrics: null, - }, - eth1Provider - ); - eth1MergeBlockTracker.startPollingMergeBlock(); - - // Wait for Eth1MergeBlockTracker to find at least one merge block - while (!controller.signal.aborted) { - if (await eth1MergeBlockTracker.getTerminalPowBlock()) break; - await sleep(10, controller.signal); - } - - // Status should acknowlege merge block is found - expect(eth1MergeBlockTracker["status"].code).toBe(StatusCode.FOUND); - - // Given the total difficulty offset the block that has TTD is the `difficultyOffset`nth block - expect(await eth1MergeBlockTracker.getTerminalPowBlock()).toEqual(toPowBlock(expectedMergeBlock)); - } -}); - -function toHex(num: number | bigint): string { - return "0x" + num.toString(16); -} - -function toRootHex(num: number): string { - return "0x" + num.toString(16).padStart(64, "0"); -} diff --git a/packages/beacon-node/test/utils/runEl.ts b/packages/beacon-node/test/utils/runEl.ts index ae5ad992ae16..3d311e30f4d7 100644 --- a/packages/beacon-node/test/utils/runEl.ts +++ b/packages/beacon-node/test/utils/runEl.ts @@ -9,8 +9,8 @@ import {shell} from "../sim/shell.js"; let txRpcId = 1; +// Note: PreMerge mode removed since all networks are post-merge export enum ELStartMode { - PreMerge = "pre-merge", PostMerge = "post-merge", } diff --git a/packages/beacon-node/test/utils/state.ts b/packages/beacon-node/test/utils/state.ts index 64ee9d9514f9..80ed44539f5c 100644 --- a/packages/beacon-node/test/utils/state.ts +++ b/packages/beacon-node/test/utils/state.ts @@ -177,6 +177,8 @@ export const zeroProtoBlock: ProtoBlock = { timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: ZERO_HASH_HEX, + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; diff --git a/packages/beacon-node/test/utils/typeGenerator.ts b/packages/beacon-node/test/utils/typeGenerator.ts index 7886df877050..f4e180d30125 100644 --- a/packages/beacon-node/test/utils/typeGenerator.ts +++ b/packages/beacon-node/test/utils/typeGenerator.ts @@ -41,7 +41,9 @@ export function generateProtoBlock(overrides: Partial = {}): ProtoBl timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: ZERO_HASH_HEX, + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, ...overrides, } as ProtoBlock; diff --git a/packages/beacon-node/test/utils/validationData/attestation.ts b/packages/beacon-node/test/utils/validationData/attestation.ts index 798303e62887..f335636686c6 100644 --- a/packages/beacon-node/test/utils/validationData/attestation.ts +++ b/packages/beacon-node/test/utils/validationData/attestation.ts @@ -76,7 +76,9 @@ export function getAttestationValidData(opts: AttestationValidDataOpts): { timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: ZERO_HASH_HEX, + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index f6c405e4b42d..d9e76ee5619f 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -1,4 +1,4 @@ -import {ChainConfig, ChainForkConfig} from "@lodestar/config"; +import {ChainForkConfig} from "@lodestar/config"; import {SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import { CachedBeaconStateAllForks, @@ -23,7 +23,6 @@ import { RootHex, Slot, ValidatorIndex, - bellatrix, phase0, ssz, } from "@lodestar/types"; @@ -32,7 +31,6 @@ import {ForkChoiceMetrics} from "../metrics.js"; import {computeDeltas} from "../protoArray/computeDeltas.js"; import {ProtoArrayError, ProtoArrayErrorCode} from "../protoArray/errors.js"; import { - ExecutionStatus, HEX_ZERO_HASH, LVHExecResponse, MaybeValidExecutionStatus, @@ -49,7 +47,6 @@ import { EpochDifference, IForkChoice, NotReorgedReason, - PowBlockHex, ShouldOverrideForkChoiceUpdateResult, } from "./interface.js"; import {CheckpointWithHex, IForkChoiceStore, JustifiedBalances, toCheckpointWithHex} from "./store.js"; @@ -737,6 +734,11 @@ export class ForkChoice implements IForkChoice { // This does not apply a vote to the block, it just makes fork choice aware of the block so // it can still be identified as the head even if it doesn't have any votes. + // Post-merge: always require execution data + if (!isExecutionBlockBodyType(block.body) || !isExecutionStateType(state) || !isExecutionEnabled(state, block)) { + throw Error("Fork choice requires execution-enabled block and state post-merge"); + } + const protoBlock: ProtoBlock = { slot: slot, blockRoot: blockRootHex, @@ -754,18 +756,10 @@ export class ForkChoice implements IForkChoice { unrealizedFinalizedEpoch: unrealizedFinalizedCheckpoint.epoch, unrealizedFinalizedRoot: unrealizedFinalizedCheckpoint.rootHex, - ...(isExecutionBlockBodyType(block.body) && isExecutionStateType(state) && isExecutionEnabled(state, block) - ? { - executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash), - executionPayloadNumber: block.body.executionPayload.blockNumber, - executionStatus: this.getPostMergeExecStatus(executionStatus), - dataAvailabilityStatus, - } - : { - executionPayloadBlockHash: null, - executionStatus: this.getPreMergeExecStatus(executionStatus), - dataAvailabilityStatus: this.getPreMergeDataStatus(dataAvailabilityStatus), - }), + executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash), + executionPayloadNumber: block.body.executionPayload.blockNumber, + executionStatus, + dataAvailabilityStatus, }; this.protoArray.onBlock(protoBlock, currentSlot); @@ -1228,30 +1222,6 @@ export class ForkChoice implements IForkChoice { return secFromSlot * 1000 <= proposerReorgCutoff; } - private getPreMergeExecStatus(executionStatus: MaybeValidExecutionStatus): ExecutionStatus.PreMerge { - if (executionStatus !== ExecutionStatus.PreMerge) - throw Error(`Invalid pre-merge execution status: expected: ${ExecutionStatus.PreMerge}, got ${executionStatus}`); - return executionStatus; - } - - private getPreMergeDataStatus(dataAvailabilityStatus: DataAvailabilityStatus): DataAvailabilityStatus.PreData { - if (dataAvailabilityStatus !== DataAvailabilityStatus.PreData) - throw Error( - `Invalid pre-merge data status: expected: ${DataAvailabilityStatus.PreData}, got ${dataAvailabilityStatus}` - ); - return dataAvailabilityStatus; - } - - private getPostMergeExecStatus( - executionStatus: MaybeValidExecutionStatus - ): ExecutionStatus.Valid | ExecutionStatus.Syncing { - if (executionStatus === ExecutionStatus.PreMerge) - throw Error( - `Invalid post-merge execution status: expected: ${ExecutionStatus.Syncing} or ${ExecutionStatus.Valid} , got ${executionStatus}` - ); - return executionStatus; - } - /** * Why `getJustifiedBalances` getter? * - updateCheckpoints() is called in both on_block and on_tick. @@ -1609,65 +1579,6 @@ export class ForkChoice implements IForkChoice { } } -/** - * This function checks the terminal pow conditions on the merge block as - * specified in the config either via TTD or TBH. This function is part of - * forkChoice because if the merge block was previously imported as syncing - * and the EL eventually signals it catching up via validateLatestHash - * the specs mandates validating terminal conditions on the previously - * imported merge block. - */ -export function assertValidTerminalPowBlock( - config: ChainConfig, - block: bellatrix.BeaconBlock, - preCachedData: { - executionStatus: ExecutionStatus.Syncing | ExecutionStatus.Valid; - powBlock?: PowBlockHex | null; - powBlockParent?: PowBlockHex | null; - } -): void { - if (!ssz.Root.equals(config.TERMINAL_BLOCK_HASH, ZERO_HASH)) { - if (computeEpochAtSlot(block.slot) < config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH) - throw Error(`Terminal block activation epoch ${config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH} not reached`); - - // powBock.blockHash is hex, so we just pick the corresponding root - if (!ssz.Root.equals(block.body.executionPayload.parentHash, config.TERMINAL_BLOCK_HASH)) - throw new Error( - `Invalid terminal block hash, expected: ${toRootHex(config.TERMINAL_BLOCK_HASH)}, actual: ${toRootHex( - block.body.executionPayload.parentHash - )}` - ); - } else { - // If no TERMINAL_BLOCK_HASH override, check ttd - - // Delay powBlock checks if the payload execution status is unknown because of - // syncing response in notifyNewPayload call while verifying - if (preCachedData?.executionStatus === ExecutionStatus.Syncing) return; - - const {powBlock, powBlockParent} = preCachedData; - if (!powBlock) throw Error("onBlock preCachedData must include powBlock"); - // if powBlock is genesis don't assert powBlockParent - if (!powBlockParent && powBlock.parentHash !== HEX_ZERO_HASH) - throw Error("onBlock preCachedData must include powBlockParent"); - - const isTotalDifficultyReached = powBlock.totalDifficulty >= config.TERMINAL_TOTAL_DIFFICULTY; - // If we don't have powBlockParent here, powBlock is the genesis and as we would have errored above - // we can mark isParentTotalDifficultyValid as valid - const isParentTotalDifficultyValid = - !powBlockParent || powBlockParent.totalDifficulty < config.TERMINAL_TOTAL_DIFFICULTY; - if (!isTotalDifficultyReached) { - throw Error( - `Invalid terminal POW block: total difficulty not reached expected >= ${config.TERMINAL_TOTAL_DIFFICULTY}, actual = ${powBlock.totalDifficulty}` - ); - } - - if (!isParentTotalDifficultyValid) { - throw Error( - `Invalid terminal POW block parent: expected < ${config.TERMINAL_TOTAL_DIFFICULTY}, actual = ${powBlockParent.totalDifficulty}` - ); - } - } -} // Approximate https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#calculate_committee_fraction // Calculates proposer boost score when committeePercent = config.PROPOSER_SCORE_BOOST export function getCommitteeFraction( diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index 51ee5bb28fd5..32d9b6c68f78 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -246,10 +246,3 @@ export interface IForkChoice { */ getDependentRoot(block: ProtoBlock, atEpochDiff: EpochDifference): RootHex; } - -/** Same to the PowBlock but we want RootHex to work with forkchoice conveniently */ -export type PowBlockHex = { - blockHash: RootHex; - parentHash: RootHex; - totalDifficulty: bigint; -}; diff --git a/packages/fork-choice/src/index.ts b/packages/fork-choice/src/index.ts index 59c7553a19c0..e476660ee9ad 100644 --- a/packages/fork-choice/src/index.ts +++ b/packages/fork-choice/src/index.ts @@ -6,14 +6,13 @@ export { type InvalidBlock, InvalidBlockCode, } from "./forkChoice/errors.js"; -export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt, assertValidTerminalPowBlock} from "./forkChoice/forkChoice.js"; +export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js"; export { type AncestorResult, AncestorStatus, EpochDifference, type IForkChoice, NotReorgedReason, - type PowBlockHex, } from "./forkChoice/interface.js"; export * from "./forkChoice/safeBlocks.js"; export { diff --git a/packages/fork-choice/src/protoArray/errors.ts b/packages/fork-choice/src/protoArray/errors.ts index 650fd1c0b244..13a61417a070 100644 --- a/packages/fork-choice/src/protoArray/errors.ts +++ b/packages/fork-choice/src/protoArray/errors.ts @@ -2,7 +2,6 @@ import {Epoch, RootHex} from "@lodestar/types"; import {LodestarError} from "@lodestar/utils"; export enum LVHExecErrorCode { - PreMergeToInvalid = "PreMergeToInvalid", ValidToInvalid = "ValidToInvalid", InvalidToValid = "InvalidToValid", } diff --git a/packages/fork-choice/src/protoArray/interface.ts b/packages/fork-choice/src/protoArray/interface.ts index 74b908cb1fc6..963823294e9b 100644 --- a/packages/fork-choice/src/protoArray/interface.ts +++ b/packages/fork-choice/src/protoArray/interface.ts @@ -20,7 +20,6 @@ export type VoteIndex = number; export enum ExecutionStatus { Valid = "Valid", Syncing = "Syncing", - PreMerge = "PreMerge", Invalid = "Invalid", } @@ -37,18 +36,18 @@ export type LVHExecResponse = LVHValidResponse | LVHInvalidResponse; export type MaybeValidExecutionStatus = Exclude; -export type BlockExtraMeta = - | { - executionPayloadBlockHash: RootHex; - executionPayloadNumber: UintNum64; - executionStatus: Exclude; - dataAvailabilityStatus: DataAvailabilityStatus; - } - | { - executionPayloadBlockHash: null; - executionStatus: ExecutionStatus.PreMerge; - dataAvailabilityStatus: DataAvailabilityStatus.PreData; - }; +/** + * Extra metadata for execution-related data. + * + * Note: executionStatus uses full ExecutionStatus enum because blocks + * can become Invalid through EL validation after being added as Valid/Syncing. + */ +export type BlockExtraMeta = { + executionPayloadBlockHash: RootHex; + executionPayloadNumber: UintNum64; + executionStatus: ExecutionStatus; + dataAvailabilityStatus: DataAvailabilityStatus; +}; /** * A block that is to be applied to the fork choice @@ -93,7 +92,7 @@ export type ProtoBlock = BlockExtraMeta & { /** * A block root with additional metadata required to form a DAG - * with vote weights and best blocks stored as metadata + * with vote weights and best blocks stored as metadata. */ export type ProtoNode = ProtoBlock & { parent?: number; diff --git a/packages/fork-choice/src/protoArray/protoArray.ts b/packages/fork-choice/src/protoArray/protoArray.ts index 50a994d46476..a1f38a50f01f 100644 --- a/packages/fork-choice/src/protoArray/protoArray.ts +++ b/packages/fork-choice/src/protoArray/protoArray.ts @@ -314,7 +314,7 @@ export class ProtoArray { // propagate till we keep encountering syncing status while (nodeIndex !== undefined) { const node = this.getNodeFromIndex(nodeIndex); - if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) { + if (node.executionStatus === ExecutionStatus.Valid) { break; } this.validateNodeByIndex(nodeIndex); @@ -372,10 +372,7 @@ export class ProtoArray { let nodeIndex: number | undefined = ancestorFromIndex; while (nodeIndex !== undefined && nodeIndex >= 0) { const node = this.getNodeFromIndex(nodeIndex); - if ( - (node.executionStatus === ExecutionStatus.PreMerge && latestValidExecHash === ZERO_HASH_HEX) || - node.executionPayloadBlockHash === latestValidExecHash - ) { + if (node.executionPayloadBlockHash === latestValidExecHash) { break; } nodeIndex = node.parent; @@ -386,19 +383,13 @@ export class ProtoArray { private invalidateNodeByIndex(nodeIndex: number): ProtoNode { const invalidNode = this.getNodeFromIndex(nodeIndex); - // If node to be invalidated is pre-merge or valid,it is a catastrophe, + // If node to be invalidated is valid, it is a catastrophe, // and indicates consensus failure and a non recoverable damage. // // There is no further processing that can be done. // Just assign error for marking proto-array perma damaged and throw! - if ( - invalidNode.executionStatus === ExecutionStatus.Valid || - invalidNode.executionStatus === ExecutionStatus.PreMerge - ) { - const lvhCode = - invalidNode.executionStatus === ExecutionStatus.Valid - ? LVHExecErrorCode.ValidToInvalid - : LVHExecErrorCode.PreMergeToInvalid; + if (invalidNode.executionStatus === ExecutionStatus.Valid) { + const lvhCode = LVHExecErrorCode.ValidToInvalid; this.lvhError = { lvhCode, diff --git a/packages/fork-choice/test/perf/forkChoice/util.ts b/packages/fork-choice/test/perf/forkChoice/util.ts index 5b72dd88b9f5..663cdaf21dfb 100644 --- a/packages/fork-choice/test/perf/forkChoice/util.ts +++ b/packages/fork-choice/test/perf/forkChoice/util.ts @@ -27,9 +27,14 @@ export function initializeForkChoice(opts: Opts): ForkChoice { justifiedRoot: genesisRoot, finalizedEpoch: genesisEpoch, finalizedRoot: genesisRoot, + unrealizedJustifiedEpoch: genesisEpoch, + unrealizedJustifiedRoot: genesisRoot, + unrealizedFinalizedEpoch: genesisEpoch, + unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: genesisRoot, + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, } as Omit, genesisSlot @@ -75,8 +80,9 @@ export function initializeForkChoice(opts: Opts): ForkChoice { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: blockRoot, + executionPayloadNumber: slot, + executionStatus: ExecutionStatus.Valid, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, diff --git a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts index 91f1fc8614e9..fafe2646963a 100644 --- a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts @@ -38,8 +38,9 @@ describe("Forkchoice", () => { finalizedEpoch: genesisEpoch, finalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: genesisRoot, + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, } as Omit, genesisSlot @@ -99,8 +100,9 @@ describe("Forkchoice", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: getBlockRoot(slot), + executionPayloadNumber: slot, + executionStatus: ExecutionStatus.Valid, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index 0eb01f3580c3..96c4dbd5c303 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -37,8 +37,9 @@ describe("Forkchoice / GetProposerHead", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: genesisRoot, + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, @@ -60,8 +61,9 @@ describe("Forkchoice / GetProposerHead", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: getBlockRoot(headSlot), + executionPayloadNumber: headSlot, + executionStatus: ExecutionStatus.Valid, timeliness: false, @@ -85,8 +87,9 @@ describe("Forkchoice / GetProposerHead", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: getBlockRoot(parentSlot), + executionPayloadNumber: parentSlot, + executionStatus: ExecutionStatus.Valid, timeliness: false, weight: 212, // 240 - 29 + 1 diff --git a/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts b/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts index 803c5fcbd5bb..6a7848c7db45 100644 --- a/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts @@ -37,8 +37,9 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: genesisRoot, + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, @@ -60,8 +61,9 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: getBlockRoot(headSlot), + executionPayloadNumber: headSlot, + executionStatus: ExecutionStatus.Valid, timeliness: false, @@ -85,8 +87,9 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, + executionPayloadBlockHash: getBlockRoot(parentSlot), + executionPayloadNumber: parentSlot, + executionStatus: ExecutionStatus.Valid, timeliness: false, weight: 212, // 240 - 29 + 1 diff --git a/packages/fork-choice/test/unit/forkChoice/utils.test.ts b/packages/fork-choice/test/unit/forkChoice/utils.test.ts deleted file mode 100644 index c029f82eb837..000000000000 --- a/packages/fork-choice/test/unit/forkChoice/utils.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {describe, expect, it} from "vitest"; -import {createChainForkConfig} from "@lodestar/config"; -import {ssz} from "@lodestar/types"; -import {ExecutionStatus, assertValidTerminalPowBlock} from "../../../src/index.js"; - -describe("assertValidTerminalPowBlock", () => { - const config = createChainForkConfig({TERMINAL_TOTAL_DIFFICULTY: BigInt(10)}); - const block = ssz.bellatrix.BeaconBlock.defaultValue(); - const executionStatus = ExecutionStatus.Valid; - it("should accept ttd >= genesis block as terminal without powBlockParent", () => { - const powBlock = { - blockHash: "0x" + "ab".repeat(32), - // genesis powBlock will have zero parent hash - parentHash: "0x" + "00".repeat(32), - totalDifficulty: BigInt(10), - }; - expect(() => - assertValidTerminalPowBlock(config, block, {executionStatus, powBlockParent: null, powBlock}) - ).not.toThrow(); - }); - - it("should require powBlockParent if powBlock not genesis", () => { - const powBlock = { - blockHash: "0x" + "ab".repeat(32), - // genesis powBlock will have non zero parent hash - parentHash: "0x" + "01".repeat(32), - totalDifficulty: BigInt(10), - }; - expect(() => - assertValidTerminalPowBlock(config, block, {executionStatus, powBlockParent: null, powBlock}) - ).toThrow(); - }); - - it("should require powBlock >= ttd", () => { - const powBlock = { - blockHash: "0x" + "ab".repeat(32), - // genesis powBlock will have non zero parent hash - parentHash: "0x" + "01".repeat(32), - totalDifficulty: BigInt(9), - }; - expect(() => - assertValidTerminalPowBlock(config, block, {executionStatus, powBlockParent: powBlock, powBlock}) - ).toThrow(); - }); - - it("should require powBlockParent < ttd", () => { - const powBlock = { - blockHash: "0x" + "ab".repeat(32), - // genesis powBlock will have non zero parent hash - parentHash: "0x" + "01".repeat(32), - totalDifficulty: BigInt(10), - }; - expect(() => - assertValidTerminalPowBlock(config, block, {executionStatus, powBlockParent: powBlock, powBlock}) - ).toThrow(); - }); - - it("should accept powBlockParent < ttd and powBlock >= ttd", () => { - const powBlock = { - blockHash: "0x" + "ab".repeat(32), - // genesis powBlock will have non zero parent hash - parentHash: "0x" + "01".repeat(32), - totalDifficulty: BigInt(10), - }; - const powBlockParent = { - ...powBlock, - totalDifficulty: BigInt(9), - }; - expect(() => assertValidTerminalPowBlock(config, block, {executionStatus, powBlockParent, powBlock})).not.toThrow(); - }); -}); diff --git a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts deleted file mode 100644 index 5e77b48a8a23..000000000000 --- a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts +++ /dev/null @@ -1,437 +0,0 @@ -import {describe, expect, it} from "vitest"; -import {DataAvailabilityStatus} from "@lodestar/state-transition"; -import { - BlockExtraMeta, - ExecutionStatus, - MaybeValidExecutionStatus, - ProtoArray, - ProtoBlock, -} from "../../../src/index.js"; -import {LVHExecErrorCode} from "../../../src/protoArray/errors.js"; - -type ValidationTestCase = { - root: string; - bestChild?: string; - bestDescendant?: string; - executionStatus: ExecutionStatus | undefined; -}; - -type TestBlock = {slot: number; root: string; parent: string; executionStatus: MaybeValidExecutionStatus}; -type TestCase = [string, string | undefined, string | undefined, ExecutionStatus]; -const blocks: TestBlock[] = [ - {slot: 1, root: "1A", parent: "0", executionStatus: ExecutionStatus.Syncing}, - {slot: 2, root: "2A", parent: "1A", executionStatus: ExecutionStatus.Syncing}, - {slot: 3, root: "3A", parent: "2A", executionStatus: ExecutionStatus.Syncing}, - {slot: 2, root: "2B", parent: "1A", executionStatus: ExecutionStatus.Syncing}, - {slot: 2, root: "3B", parent: "2B", executionStatus: ExecutionStatus.Syncing}, - {slot: 2, root: "2C", parent: "none", executionStatus: ExecutionStatus.Syncing}, - {slot: 3, root: "3C", parent: "2C", executionStatus: ExecutionStatus.Syncing}, -]; -const fcRoots: string[] = ["0"]; -for (const block of blocks) { - fcRoots.push(block.root); -} - -function toFcTestCase(testCases: TestCase[]): ValidationTestCase[] { - return testCases.map((testCase) => ({ - root: testCase[0], - bestChild: testCase[1], - bestDescendant: testCase[2], - executionStatus: testCase[3], - })); -} - -const expectedPreValidationFC1: TestCase[] = [ - ["0", "1A", "3B", ExecutionStatus.PreMerge], - ["1A", "2B", "3B", ExecutionStatus.Syncing], - ["2A", "3A", "3A", ExecutionStatus.Syncing], - ["3A", undefined, undefined, ExecutionStatus.Syncing], - ["2B", "3B", "3B", ExecutionStatus.Syncing], - ["3B", undefined, undefined, ExecutionStatus.Syncing], - ["2C", "3C", "3C", ExecutionStatus.Syncing], - ["3C", undefined, undefined, ExecutionStatus.Syncing], -]; -const expectedPreValidationFC: ValidationTestCase[] = toFcTestCase(expectedPreValidationFC1); - -/** - * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) - * - * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Syncing) <- 3B (Syncing) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - -function setupForkChoice(): ProtoArray { - const fc = ProtoArray.initialize( - { - slot: 0, - stateRoot: "-", - parentRoot: "-", - blockRoot: "0", - - justifiedEpoch: 0, - justifiedRoot: "-", - finalizedEpoch: 0, - finalizedRoot: "-", - - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, - } as Omit, - 0 - ); - - for (const block of blocks) { - const executionData = ( - block.executionStatus === ExecutionStatus.PreMerge - ? { - executionPayloadBlockHash: null, - executionStatus: ExecutionStatus.PreMerge, - dataAvailabilityStatus: DataAvailabilityStatus.PreData, - } - : { - executionPayloadBlockHash: block.root, - executionPayloadNumber: block.slot, - executionStatus: block.executionStatus, - dataAvailabilityStatus: DataAvailabilityStatus.PreData, - } - ) as BlockExtraMeta; - fc.onBlock( - { - slot: block.slot, - blockRoot: block.root, - parentRoot: block.parent, - stateRoot: "-", - targetRoot: "-", - - justifiedEpoch: 0, - justifiedRoot: "-", - finalizedEpoch: 0, - finalizedRoot: "-", - - unrealizedJustifiedEpoch: 0, - unrealizedJustifiedRoot: "-", - unrealizedFinalizedEpoch: 0, - unrealizedFinalizedRoot: "-", - - timeliness: false, - - ...executionData, - }, - block.slot - ); - } - - const deltas = Array.from({length: fc.nodes.length}, () => 0); - fc.applyScoreChanges({ - deltas, - proposerBoost: null, - justifiedEpoch: 0, - justifiedRoot: "-", - finalizedEpoch: 0, - finalizedRoot: "-", - currentSlot: 3, - }); - return fc; -} - -describe("executionStatus / normal updates", () => { - const fc = setupForkChoice(); - - /** - * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Syncing) <- 3B (Syncing) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - const preValidation = collectProtoarrayValidationStatus(fc); - it("preValidation forkchoice setup should be correct", () => { - expect(preValidation).toEqual(expectedPreValidationFC); - }); - - /** - * Invalidate 3C with LVH on 2C which stays in Syncing - * - * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Syncing) <- 3B (Syncing) - * ~~ 2C (Syncing) <- 3C (Invalid) - */ - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Invalid, - latestValidExecHash: "2C", - invalidateFromParentBlockRoot: "3C", - }, - 3 - ); - - const invalidate3CValidate2CForkChoice = collectProtoarrayValidationStatus(fc); - it("correcly invalidate 3C and validate 2C only", () => { - expect(invalidate3CValidate2CForkChoice).toEqual( - toFcTestCase([ - ["0", "1A", "3B", ExecutionStatus.PreMerge], - ["1A", "2B", "3B", ExecutionStatus.Syncing], - ["2A", "3A", "3A", ExecutionStatus.Syncing], - ["3A", undefined, undefined, ExecutionStatus.Syncing], - ["2B", "3B", "3B", ExecutionStatus.Syncing], - ["3B", undefined, undefined, ExecutionStatus.Syncing], - ["2C", undefined, undefined, ExecutionStatus.Syncing], - ["3C", undefined, undefined, ExecutionStatus.Invalid], - ]) - ); - }); - - /** - * Validate 3B, 2B, 1A (premerge) - * - * 0 (PreMerge) <- 1A (Valid) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Valid) <- 3B (Valid) - * ~~ 2C (Syncing) <- 3C (Invalid) - */ - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Valid, - latestValidExecHash: "3B", - }, - 3 - ); - const validate3B2B1A = collectProtoarrayValidationStatus(fc); - it("Validate 3B, 2B, 1A", () => { - expect(validate3B2B1A).toEqual( - toFcTestCase([ - ["0", "1A", "3B", ExecutionStatus.PreMerge], - ["1A", "2B", "3B", ExecutionStatus.Valid], - ["2A", "3A", "3A", ExecutionStatus.Syncing], - ["3A", undefined, undefined, ExecutionStatus.Syncing], - ["2B", "3B", "3B", ExecutionStatus.Valid], - ["3B", undefined, undefined, ExecutionStatus.Valid], - ["2C", undefined, undefined, ExecutionStatus.Syncing], - ["3C", undefined, undefined, ExecutionStatus.Invalid], - ]) - ); - }); - - /** - * Invalidate 3A, 2A with 2A loosing its bestChild, bestDescendant - * - * 0 (PreMerge) <- 1A (Valid) <- 2A (Invalid) <- 3A (Invalid) - * ^- 2B (Valid) <- 3B (Valid) - * ~~ 2C (Syncing) <- 3C (Invalid) - */ - - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Invalid, - latestValidExecHash: "1A", - invalidateFromParentBlockRoot: "3A", - }, - 3 - ); - const invalidate3A2A = collectProtoarrayValidationStatus(fc); - it("Invalidate 3A, 2A with 2A loosing its bestChild, bestDescendant", () => { - expect(invalidate3A2A).toEqual( - toFcTestCase([ - ["0", "1A", "3B", ExecutionStatus.PreMerge], - ["1A", "2B", "3B", ExecutionStatus.Valid], - ["2A", undefined, undefined, ExecutionStatus.Invalid], - ["3A", undefined, undefined, ExecutionStatus.Invalid], - ["2B", "3B", "3B", ExecutionStatus.Valid], - ["3B", undefined, undefined, ExecutionStatus.Valid], - ["2C", undefined, undefined, ExecutionStatus.Syncing], - ["3C", undefined, undefined, ExecutionStatus.Invalid], - ]) - ); - }); -}); - -describe("executionStatus / invalidate all postmerge chain", () => { - const fc = setupForkChoice(); - - /** - * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) - * - * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Syncing) <- 3B (Syncing) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - const preValidation = collectProtoarrayValidationStatus(fc); - it("preValidation forkchoice setup should be correct", () => { - expect(preValidation).toEqual(expectedPreValidationFC); - }); - - /** - * All post merge blocks should be invalidated except Cs - * - * 0 (PreMerge) <- 1A (Invalid) <- 2A (Invalid) <- 3A (Invalid) - * ^- 2B (Invalid) <- 3B (Invalid) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Invalid, - latestValidExecHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - invalidateFromParentBlockRoot: "3B", - }, - 3 - ); - const postMergeInvalidated = collectProtoarrayValidationStatus(fc); - it("all post merge blocks should be invalidated except Cs", () => { - expect(postMergeInvalidated).toEqual( - toFcTestCase([ - ["0", undefined, undefined, ExecutionStatus.PreMerge], - ["1A", undefined, undefined, ExecutionStatus.Invalid], - ["2A", undefined, undefined, ExecutionStatus.Invalid], - ["3A", undefined, undefined, ExecutionStatus.Invalid], - ["2B", undefined, undefined, ExecutionStatus.Invalid], - ["3B", undefined, undefined, ExecutionStatus.Invalid], - ["2C", "3C", "3C", ExecutionStatus.Syncing], - ["3C", undefined, undefined, ExecutionStatus.Syncing], - ]) - ); - }); - - const fcHead = fc.findHead("0", 3); - it("pre merge block should be the FC head", () => { - expect(fcHead).toBe("0"); - }); -}); - -describe("executionStatus / poision forkchoice if we invalidate previous valid", () => { - const fc = setupForkChoice(); - - /** - * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) - * - * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Syncing) <- 3B (Syncing) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - const preValidation = collectProtoarrayValidationStatus(fc); - it("preValidation forkchoice setup should be correct", () => { - expect(preValidation).toEqual(expectedPreValidationFC); - }); - - /** - * Validate 3B, 2B, 1A (premerge) - * - * 0 (PreMerge) <- 1A (Valid) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Valid) <- 3B (Valid) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Valid, - latestValidExecHash: "3B", - }, - 3 - ); - const validate3B2B1A = collectProtoarrayValidationStatus(fc); - it("Validate 3B, 2B, 1A", () => { - expect(validate3B2B1A).toEqual( - toFcTestCase([ - ["0", "1A", "3B", ExecutionStatus.PreMerge], - ["1A", "2B", "3B", ExecutionStatus.Valid], - ["2A", "3A", "3A", ExecutionStatus.Syncing], - ["3A", undefined, undefined, ExecutionStatus.Syncing], - ["2B", "3B", "3B", ExecutionStatus.Valid], - ["3B", undefined, undefined, ExecutionStatus.Valid], - ["2C", "3C", "3C", ExecutionStatus.Syncing], - ["3C", undefined, undefined, ExecutionStatus.Syncing], - ]) - ); - }); - - it("protoarray should be poisioned with a buggy LVH response", () => { - expect(() => - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Invalid, - latestValidExecHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - invalidateFromParentBlockRoot: "3A", - }, - 3 - ) - ).toThrow(Error); - - expect(fc.lvhError).toEqual({lvhCode: LVHExecErrorCode.ValidToInvalid, blockRoot: "1A", execHash: "1A"}); - expect(() => fc.findHead("0", 3)).toThrow(Error); - }); -}); - -describe("executionStatus / poision forkchoice if we validate previous invalid", () => { - const fc = setupForkChoice(); - - /** - * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) - * - * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) - * ^- 2B (Syncing) <- 3B (Syncing) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - const preValidation = collectProtoarrayValidationStatus(fc); - it("preValidation forkchoice setup should be correct", () => { - expect(preValidation).toEqual(expectedPreValidationFC); - }); - - /** - * Invalidate 3B, 2B, 1A - * - * 0 (PreMerge) <- 1A (Invalid) <- 2A (Invalid) <- 3A (Invalid) - * ^- 2B (Invalid) <- 3B (Invalid) - * ~~ 2C (Syncing) <- 3C (Syncing) - */ - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Invalid, - latestValidExecHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - invalidateFromParentBlockRoot: "3B", - }, - 3 - ); - const validate3B2B1A = collectProtoarrayValidationStatus(fc); - it("Inalidate 3B, 2B, 1A", () => { - expect(validate3B2B1A).toEqual( - toFcTestCase([ - ["0", undefined, undefined, ExecutionStatus.PreMerge], - ["1A", undefined, undefined, ExecutionStatus.Invalid], - ["2A", undefined, undefined, ExecutionStatus.Invalid], - ["3A", undefined, undefined, ExecutionStatus.Invalid], - ["2B", undefined, undefined, ExecutionStatus.Invalid], - ["3B", undefined, undefined, ExecutionStatus.Invalid], - ["2C", "3C", "3C", ExecutionStatus.Syncing], - ["3C", undefined, undefined, ExecutionStatus.Syncing], - ]) - ); - }); - - it("protoarray should be poisioned with a buggy LVH response", () => { - expect(() => - fc.validateLatestHash( - { - executionStatus: ExecutionStatus.Valid, - latestValidExecHash: "2A", - }, - 3 - ) - ).toThrow(Error); - - expect(fc.lvhError).toEqual({lvhCode: LVHExecErrorCode.InvalidToValid, blockRoot: "2A", execHash: "2A"}); - expect(() => fc.findHead("0", 3)).toThrow(Error); - }); -}); - -function collectProtoarrayValidationStatus(fcArray: ProtoArray): ValidationTestCase[] { - const expectedForkChoice: ValidationTestCase[] = []; - - for (const fcRoot of fcRoots) { - const fcNode = fcArray.getNode(fcRoot); - const bestChild = - fcNode?.bestChild !== undefined ? fcArray["getNodeFromIndex"](fcNode.bestChild).blockRoot : undefined; - const bestDescendant = - fcNode?.bestDescendant !== undefined ? fcArray["getNodeFromIndex"](fcNode.bestDescendant).blockRoot : undefined; - expectedForkChoice.push({ - root: fcRoot, - bestChild, - bestDescendant, - executionStatus: fcNode?.executionStatus, - }); - } - return expectedForkChoice; -} diff --git a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts index b6a3f50af7eb..6800b206ba24 100644 --- a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts +++ b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts @@ -43,7 +43,9 @@ describe("getCommonAncestor", () => { timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: "0", + executionPayloadNumber: 0, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, 0 @@ -69,7 +71,9 @@ describe("getCommonAncestor", () => { timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: block.root, + executionPayloadNumber: block.slot, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, block.slot diff --git a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts index 6be99553ba3f..04978f7ad56c 100644 --- a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts +++ b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts @@ -32,7 +32,9 @@ describe("ProtoArray", () => { timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: finalizedRoot, + executionPayloadNumber: genesisSlot, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot @@ -58,7 +60,9 @@ describe("ProtoArray", () => { timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: finalizedDesc, + executionPayloadNumber: genesisSlot + 1, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot + 1 @@ -84,7 +88,9 @@ describe("ProtoArray", () => { timeliness: false, - ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + executionPayloadBlockHash: notFinalizedDesc, + executionPayloadNumber: genesisSlot + 1, + executionStatus: ExecutionStatus.Valid, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot + 1 diff --git a/packages/state-transition/src/util/execution.ts b/packages/state-transition/src/util/execution.ts index a5ad0be8163f..b6710d93f336 100644 --- a/packages/state-transition/src/util/execution.ts +++ b/packages/state-transition/src/util/execution.ts @@ -10,12 +10,10 @@ import { capella, deneb, isBlindedBeaconBlockBody, - isExecutionPayload, ssz, } from "@lodestar/types"; import { BeaconStateAllForks, - BeaconStateBellatrix, BeaconStateCapella, BeaconStateExecutions, CachedBeaconStateAllForks, @@ -23,60 +21,19 @@ import { } from "../types.js"; /** - * Execution enabled = merge is done. - * When (A) state has execution data OR (B) block has execution data + * Execution is always enabled post-merge. + * Since all networks have transitioned, this always returns true for execution states. */ -export function isExecutionEnabled(state: BeaconStateExecutions, block: BeaconBlock | BlindedBeaconBlock): boolean { - if (isMergeTransitionComplete(state)) { - return true; - } - - // Throws if not post-bellatrix block. A fork-guard in isExecutionEnabled() prevents this from happening - const payload = getFullOrBlindedPayload(block); - // Note: spec says to check all payload is zero-ed. However a state-root cannot be zero for any non-empty payload - // TODO: Consider comparing with the payload root if this assumption is not correct. - // return !byteArrayEquals(payload.stateRoot, ZERO_HASH); - - // UPDATE: stateRoot comparision should have been enough with zero hash, but spec tests were failing - // Revisit this later to fix specs and make this efficient - return isExecutionPayload(payload) - ? !ssz.bellatrix.ExecutionPayload.equals(payload, ssz.bellatrix.ExecutionPayload.defaultValue()) - : !ssz.bellatrix.ExecutionPayloadHeader.equals( - state.latestExecutionPayloadHeader, - // TODO: Performance - ssz.bellatrix.ExecutionPayloadHeader.defaultValue() - ); -} - -/** - * Merge block is the SINGLE block that transitions from POW to POS. - * state has no execution data AND this block has execution data - */ -export function isMergeTransitionBlock(state: BeaconStateExecutions, body: bellatrix.BeaconBlockBody): boolean { - return ( - !isMergeTransitionComplete(state) && - !ssz.bellatrix.ExecutionPayload.equals(body.executionPayload, ssz.bellatrix.ExecutionPayload.defaultValue()) - ); +export function isExecutionEnabled(_state: BeaconStateExecutions, _block: BeaconBlock | BlindedBeaconBlock): boolean { + return true; } /** - * Merge is complete when the state includes execution layer data: - * state.latestExecutionPayloadHeader NOT EMPTY + * Merge is complete for all networks. + * This function is kept for API compatibility but always returns true. */ -export function isMergeTransitionComplete(state: BeaconStateExecutions): boolean { - if (!isCapellaStateType(state)) { - return !ssz.bellatrix.ExecutionPayloadHeader.equals( - (state as BeaconStateBellatrix).latestExecutionPayloadHeader, - // TODO: Performance - ssz.bellatrix.ExecutionPayloadHeader.defaultValue() - ); - } - - return !ssz.capella.ExecutionPayloadHeader.equals( - state.latestExecutionPayloadHeader, - // TODO: Performance - ssz.capella.ExecutionPayloadHeader.defaultValue() - ); +export function isMergeTransitionComplete(_state: BeaconStateExecutions): boolean { + return true; } /** Type guard for bellatrix.BeaconState */ From 2aa79f08bef92bd1e05efc08351cbb846bb07783 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 9 Dec 2025 13:33:51 +0100 Subject: [PATCH 02/21] Restore merge transition panda banner --- .../utils/pandaMergeTransitionBanner.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts diff --git a/packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts b/packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts new file mode 100644 index 000000000000..53251cb9a035 --- /dev/null +++ b/packages/beacon-node/src/chain/blocks/utils/pandaMergeTransitionBanner.ts @@ -0,0 +1,43 @@ +// Note: 1 extra new line for space +// Note: String.raw preserve backslash character +// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw +// from https://stackoverflow.com/questions/30466581/preserve-all-backslashes-in-a-string +export const POS_PANDA_MERGE_TRANSITION_BANNER = String.raw` + + _,..._,m, |, _,..._,m, |, + ,/' '""; | |, /' '""; | |, + / ". ". + ,'mmmMMMMmm. \ -|-_" 'mmmMMMMmm. \ -|-_" + _/-"^^^^^"""%#%mm, ; | _ o /-"^^^^^"""%#%mm, ; | _ o + ,m,_,' "###) ;, m,_,' "###) ;, + (###% \#/ ;##mm. (###% \#/ ;##mm. + ^#/__ ___ ; (######) #/__ ___ ; (######) + ; //.\\ //.\\ ; \####/ //.\\ //.\\ ; \####/ + _; (#\"// \\"/#) ; ,/ _; (#\"// \\"/#) ; ,/ + @##\ \##/ = "=" ,;mm/ @##\ \##/ = "=" ,;mm/ + \##>.____,...,____,<####@ \##>.____,...,____,<####@ + ""' ""' + _______ ______ ______ ______ __ __ +/ \ / \ / \ / \ / | / | +$$$$$$$ | ______ ______ ______ /$$$$$$ | ______ /$$$$$$ | /$$$$$$ |_$$ |_ ______ $$ | __ ______ +$$ |__$$ |/ \ / \ / \ $$ |_ $$/ / \ $$ |_ $$/ $$ \__$$// $$ | / \ $$ | / | / \ +$$ $$//$$$$$$ |/$$$$$$ |/$$$$$$ |$$ | /$$$$$$ |$$ | $$ \$$$$$$/ $$$$$$ |$$ |_/$$/ /$$$$$$ | +$$$$$$$/ $$ | $$/ $$ | $$ |$$ | $$ |$$$$/ $$ | $$ |$$$$/ $$$$$$ | $$ | __ / $$ |$$ $$< $$ $$ | +$$ | $$ | $$ \__$$ |$$ \__$$ |$$ | $$ \__$$ |$$ | / \__$$ | $$ |/ |/$$$$$$$ |$$$$$$ \ $$$$$$$$/ +$$ | $$ | $$ $$/ $$ $$/ $$ | $$ $$/ $$ | $$ $$/ $$ $$/ $$ $$ |$$ | $$ |$$ | +$$/ $$/ $$$$$$/ $$$$$$/ $$/ $$$$$$/ $$/ $$$$$$/ $$$$/ $$$$$$$/ $$/ $$/ $$$$$$$/ + + + + ______ __ __ __ __ + / \ / | / | / | / | +/$$$$$$ | _______ _$$ |_ $$/ __ __ ______ _$$ |_ ______ ____$$ | +$$ |__$$ | / |/ $$ | / |/ \ / |/ \ / $$ | / \ / $$ | +$$ $$ |/$$$$$$$/ $$$$$$/ $$ |$$ \ /$$/ $$$$$$ |$$$$$$/ /$$$$$$ |/$$$$$$$ | +$$$$$$$$ |$$ | $$ | __ $$ | $$ /$$/ / $$ | $$ | __ $$ $$ |$$ | $$ | +$$ | $$ |$$ \_____ $$ |/ |$$ | $$ $$/ /$$$$$$$ | $$ |/ |$$$$$$$$/ $$ \__$$ | +$$ | $$ |$$ | $$ $$/ $$ | $$$/ $$ $$ | $$ $$/ $$ |$$ $$ | +$$/ $$/ $$$$$$$/ $$$$/ $$/ $/ $$$$$$$/ $$$$/ $$$$$$$/ $$$$$$$/ + +`; +// Note: 2 extra new lines for space From 299f13b5bae996f780c62ee25ddadf04ef870118 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 9 Dec 2025 13:41:07 +0100 Subject: [PATCH 03/21] Remove isOptimisticallySafe --- .../blocks/verifyBlocksExecutionPayloads.ts | 45 +++---------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index 327627b4e78e..c7d83cbc2342 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -7,14 +7,14 @@ import { MaybeValidExecutionStatus, ProtoBlock, } from "@lodestar/fork-choice"; -import {ForkSeq, SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; +import {ForkSeq} from "@lodestar/params"; import { CachedBeaconStateAllForks, isExecutionBlockBodyType, isExecutionEnabled, isExecutionStateType, } from "@lodestar/state-transition"; -import {Slot, bellatrix, electra} from "@lodestar/types"; +import {bellatrix, electra} from "@lodestar/types"; import {ErrorAborted, Logger, toRootHex} from "@lodestar/utils"; import {ExecutionPayloadStatus, IExecutionEngine} from "../../execution/engine/interface.js"; import {Metrics} from "../../metrics/metrics.js"; @@ -92,15 +92,6 @@ export async function verifyBlocksExecutionPayload( // // When to import such blocks: // From: https://github.com/ethereum/consensus-specs/pull/2844 - // A block MUST NOT be optimistically imported, unless either of the following - // conditions are met: - // - // 1. Parent of the block has execution - always true post-merge - // - const currentSlot = chain.clock.currentSlot; - // Post-merge, we're always optimistically safe - const isOptimisticallySafe = true; - for (let blockIndex = 0; blockIndex < blockInputs.length; blockIndex++) { const blockInput = blockInputs[blockIndex]; // If blocks are invalid in consensus the main promise could resolve before this loop ends. @@ -108,14 +99,7 @@ export async function verifyBlocksExecutionPayload( if (signal.aborted) { throw new ErrorAborted("verifyBlockExecutionPayloads"); } - const verifyResponse = await verifyBlockExecutionPayload( - chain, - blockInput, - preState0, - opts, - isOptimisticallySafe, - currentSlot - ); + const verifyResponse = await verifyBlockExecutionPayload(chain, blockInput, preState0); // If execError has happened, then we need to extract the segmentExecStatus and return if (verifyResponse.execError !== null) { @@ -160,10 +144,7 @@ export async function verifyBlocksExecutionPayload( export async function verifyBlockExecutionPayload( chain: VerifyBlockExecutionPayloadModules, blockInput: IBlockInput, - preState0: CachedBeaconStateAllForks, - opts: BlockProcessOpts, - isOptimisticallySafe: boolean, - currentSlot: Slot + preState0: CachedBeaconStateAllForks ): Promise { const block = blockInput.getBlock(); /** Not null if execution is enabled */ @@ -223,24 +204,10 @@ export async function verifyBlockExecutionPayload( } // Accepted and Syncing have the same treatment, as final validation of block is pending + // Post-merge, we're always safe to optimistically import case ExecutionPayloadStatus.ACCEPTED: - case ExecutionPayloadStatus.SYNCING: { - // Check if the entire segment was deemed safe or, this block specifically itself if not in - // the safeSlotsToImportOptimistically window of current slot, then we can import else - // we need to throw and not import his block - const safeSlotsToImportOptimistically = - opts.safeSlotsToImportOptimistically ?? SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY; - if (!isOptimisticallySafe && blockInput.slot + safeSlotsToImportOptimistically >= currentSlot) { - const execError = new BlockError(block, { - code: BlockErrorCode.EXECUTION_ENGINE_ERROR, - execStatus: ExecutionPayloadStatus.UNSAFE_OPTIMISTIC_STATUS, - errorMessage: `not safe to import ${execResult.status} payload within ${opts.safeSlotsToImportOptimistically} of currentSlot`, - }); - return {executionStatus: null, execError} as VerifyBlockExecutionResponse; - } - + case ExecutionPayloadStatus.SYNCING: return {executionStatus: ExecutionStatus.Syncing, execError: null}; - } // If the block has is not valid, or it referenced an invalid terminal block then the // block is invalid, however it has no bearing on any forkChoice cleanup From 580a1785b50b9a079dafd3e1d8f61e9c03d98f5f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 9 Dec 2025 13:52:35 +0100 Subject: [PATCH 04/21] Remove usage of SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY --- packages/beacon-node/src/chain/options.ts | 6 ------ .../perf/chain/produceBlock/produceBlockBody.test.ts | 2 -- .../test/perf/chain/verifyImportBlocks.test.ts | 3 +-- packages/beacon-node/test/utils/networkWithMockDb.ts | 1 - packages/cli/src/options/beaconNodeOptions/chain.ts | 11 ----------- .../cli/test/unit/options/beaconNodeOptions.test.ts | 2 -- packages/params/src/index.ts | 1 + 7 files changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index 6a7ceadf7a72..5d95927232e0 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -1,4 +1,3 @@ -import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; import {DEFAULT_ARCHIVE_MODE} from "./archiveStore/constants.js"; import {ArchiveMode, ArchiveStoreOpts} from "./archiveStore/interface.js"; @@ -56,10 +55,6 @@ export type BlockProcessOpts = { * Will double processing times. Use only for debugging purposes. */ disableBlsBatchVerify?: boolean; - /** - * Override SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY - */ - safeSlotsToImportOptimistically?: number; /** * Assert progressive balances the same to EpochTransitionCache */ @@ -109,7 +104,6 @@ export const defaultChainOptions: IChainOptions = { proposerBoost: true, proposerBoostReorg: true, computeUnrealized: true, - safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, serveHistoricalState: false, assertCorrectProgressiveBalances: false, diff --git a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts index 14dd293e150e..4d52b4a64d5c 100644 --- a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts +++ b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts @@ -3,7 +3,6 @@ import {afterAll, beforeAll, bench, describe} from "@chainsafe/benchmark"; import {fromHexString} from "@chainsafe/ssz"; import {config} from "@lodestar/config/default"; import {LevelDbController} from "@lodestar/db/controller/level"; -import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import {CachedBeaconStateAltair} from "@lodestar/state-transition"; import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; import {generatePerfTestCachedStateAltair} from "../../../../../state-transition/test/perf/util.js"; @@ -31,7 +30,6 @@ describe("produceBlockBody", () => { proposerBoost: true, proposerBoostReorg: true, computeUnrealized: false, - safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, skipCreateStateCacheIfAvailable: true, diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index 7d9b21ff94cf..e7cd6a1b2c9c 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -2,7 +2,7 @@ import {generateKeyPair} from "@libp2p/crypto/keys"; import {afterAll, beforeAll, bench, describe, setBenchOpts} from "@chainsafe/benchmark"; import {config} from "@lodestar/config/default"; import {LevelDbController} from "@lodestar/db/controller/level"; -import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {sleep, toHex} from "@lodestar/utils"; import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; import {rangeSyncTest} from "../../../../state-transition/test/perf/params.js"; @@ -82,7 +82,6 @@ describe.skip("verify+import blocks - range sync perf test", () => { proposerBoost: true, proposerBoostReorg: true, computeUnrealized: false, - safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, skipCreateStateCacheIfAvailable: true, diff --git a/packages/beacon-node/test/utils/networkWithMockDb.ts b/packages/beacon-node/test/utils/networkWithMockDb.ts index 5572a7cba992..5194713fd383 100644 --- a/packages/beacon-node/test/utils/networkWithMockDb.ts +++ b/packages/beacon-node/test/utils/networkWithMockDb.ts @@ -48,7 +48,6 @@ export async function getNetworkForTest( const chain = new BeaconChain( { - safeSlotsToImportOptimistically: 0, archiveStateEpochFrequency: 0, suggestedFeeRecipient: "", blsVerifyAllMainThread: true, diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 9987883a63a0..04af60b9086b 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -23,7 +23,6 @@ export type ChainArgs = { "chain.computeUnrealized"?: boolean; "chain.assertCorrectProgressiveBalances"?: boolean; "chain.maxSkipSlots"?: number; - "safe-slots-to-import-optimistically": number; emitPayloadAttributes?: boolean; broadcastValidationStrictness?: string; "chain.minSameMessageSignatureSetsToBatch"?: number; @@ -63,7 +62,6 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { computeUnrealized: args["chain.computeUnrealized"], assertCorrectProgressiveBalances: args["chain.assertCorrectProgressiveBalances"], maxSkipSlots: args["chain.maxSkipSlots"], - safeSlotsToImportOptimistically: args["safe-slots-to-import-optimistically"], emitPayloadAttributes: args.emitPayloadAttributes, broadcastValidationStrictness: args.broadcastValidationStrictness, minSameMessageSignatureSetsToBatch: @@ -227,15 +225,6 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, - "safe-slots-to-import-optimistically": { - hidden: true, - type: "number", - description: - "Slots from current (clock) slot till which its safe to import a block optimistically if the merge is not justified yet.", - default: defaultOptions.chain.safeSlotsToImportOptimistically, - group: "chain", - }, - "chain.archiveStateEpochFrequency": { description: "Minimum number of epochs between archived states", default: defaultOptions.chain.archiveStateEpochFrequency, diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index ac02498621b9..c32a35132fa0 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -33,7 +33,6 @@ describe("options / beaconNodeOptions", () => { suggestedFeeRecipient: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "chain.assertCorrectProgressiveBalances": true, "chain.maxSkipSlots": 100, - "safe-slots-to-import-optimistically": 256, "chain.archiveStateEpochFrequency": 1024, "chain.minSameMessageSignatureSetsToBatch": 32, "chain.maxShufflingCacheEpochs": 100, @@ -139,7 +138,6 @@ describe("options / beaconNodeOptions", () => { preaggregateSlotDistance: 1, attDataCacheSlotDistance: 2, computeUnrealized: true, - safeSlotsToImportOptimistically: 256, suggestedFeeRecipient: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", assertCorrectProgressiveBalances: true, maxSkipSlots: 100, diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index bcee6d82e129..9bb619a1fbec 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -271,6 +271,7 @@ export const MAX_REQUEST_LIGHT_CLIENT_COMMITTEE_HASHES = 128; /** * Optimistic sync + * @deprecated No longer used post-merge. All blocks are safe to import optimistically. */ export const SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY = 128; /** @deprecated */ From 6a8663f711e8dd7f9977d00f1a805b497f369d6f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 9 Dec 2025 14:40:48 +0100 Subject: [PATCH 05/21] Fix running node from phase0 --- .../beacon-node/src/api/impl/debug/index.ts | 6 ++++- .../blocks/verifyBlocksExecutionPayloads.ts | 18 +++++-------- .../beacon-node/src/chain/forkChoice/index.ts | 26 +++++++++---------- .../beacon-node/src/chain/validation/block.ts | 3 +-- packages/beacon-node/src/node/notifier.ts | 4 +-- .../opPools/aggregatedAttestationPool.test.ts | 10 +++---- .../crucible/assertions/mergeAssertion.ts | 7 ++--- .../fork-choice/src/forkChoice/forkChoice.ts | 18 ++++++------- .../fork-choice/src/protoArray/interface.ts | 23 +++++++++++----- packages/state-transition/src/block/index.ts | 4 +-- .../src/block/processExecutionPayload.ts | 22 ++++++---------- .../state-transition/src/util/execution.ts | 16 ------------ 12 files changed, 72 insertions(+), 85 deletions(-) diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index 75d2db0ef983..ba280e202b73 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -48,9 +48,11 @@ export function getDebugApi({ return "invalid"; case ExecutionStatus.Syncing: return "optimistic"; + case ExecutionStatus.PreMerge: + return "valid"; // Pre-merge blocks are considered valid } })(), - executionBlockHash: node.executionPayloadBlockHash, + executionBlockHash: node.executionPayloadBlockHash ?? "0x", })), }, }; @@ -59,6 +61,8 @@ export function getDebugApi({ async getProtoArrayNodes() { const nodes = chain.forkChoice.getAllNodes().map((node) => ({ ...node, + executionPayloadBlockHash: node.executionPayloadBlockHash ?? "", + executionPayloadNumber: node.executionPayloadNumber ?? 0, parent: String(node.parent), bestChild: String(node.bestChild), bestDescendant: String(node.bestDescendant), diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index c7d83cbc2342..fec36bd24817 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -8,12 +8,7 @@ import { ProtoBlock, } from "@lodestar/fork-choice"; import {ForkSeq} from "@lodestar/params"; -import { - CachedBeaconStateAllForks, - isExecutionBlockBodyType, - isExecutionEnabled, - isExecutionStateType, -} from "@lodestar/state-transition"; +import {CachedBeaconStateAllForks, isExecutionBlockBodyType, isExecutionStateType} from "@lodestar/state-transition"; import {bellatrix, electra} from "@lodestar/types"; import {ErrorAborted, Logger, toRootHex} from "@lodestar/utils"; import {ExecutionPayloadStatus, IExecutionEngine} from "../../execution/engine/interface.js"; @@ -50,7 +45,8 @@ type VerifyExecutionErrorResponse = type VerifyBlockExecutionResponse = | VerifyExecutionErrorResponse | {executionStatus: ExecutionStatus.Valid; lvhResponse: LVHValidResponse; execError: null} - | {executionStatus: ExecutionStatus.Syncing; lvhResponse?: LVHValidResponse; execError: null}; + | {executionStatus: ExecutionStatus.Syncing; lvhResponse?: LVHValidResponse; execError: null} + | {executionStatus: ExecutionStatus.PreMerge; lvhResponse: undefined; execError: null}; /** * Verifies 1 or more execution payloads from a linear sequence of blocks. @@ -149,15 +145,13 @@ export async function verifyBlockExecutionPayload( const block = blockInput.getBlock(); /** Not null if execution is enabled */ const executionPayloadEnabled = - isExecutionStateType(preState0) && - isExecutionBlockBodyType(block.message.body) && - isExecutionEnabled(preState0, block.message) + isExecutionStateType(preState0) && isExecutionBlockBodyType(block.message.body) ? block.message.body.executionPayload : null; if (!executionPayloadEnabled) { - // This should not happen post-merge, but handle gracefully - throw Error("Execution payload not enabled for post-merge block"); + // Pre-merge block, no execution payload to verify + return {executionStatus: ExecutionStatus.PreMerge, lvhResponse: undefined, execError: null}; } // TODO: Handle better notifyNewPayload() returning error is syncing diff --git a/packages/beacon-node/src/chain/forkChoice/index.ts b/packages/beacon-node/src/chain/forkChoice/index.ts index 3dc2c6f032a5..b00d2d6a09c1 100644 --- a/packages/beacon-node/src/chain/forkChoice/index.ts +++ b/packages/beacon-node/src/chain/forkChoice/index.ts @@ -101,10 +101,10 @@ export function initializeForkChoiceFromFinalizedState( // production code use ForkChoice constructor directly const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice; - const executionPayloadBlockHash = isExecutionStateType(state) - ? toRootHex(state.latestExecutionPayloadHeader.blockHash) - : ZERO_HASH_HEX; - const executionPayloadNumber = isExecutionStateType(state) ? state.latestExecutionPayloadHeader.blockNumber : 0; + const isPostMerge = isExecutionStateType(state); + const executionPayloadBlockHash = isPostMerge ? toRootHex(state.latestExecutionPayloadHeader.blockHash) : null; + const executionPayloadNumber = isPostMerge ? state.latestExecutionPayloadHeader.blockNumber : null; + const executionStatus = isPostMerge ? ExecutionStatus.Valid : ExecutionStatus.PreMerge; return new forkchoiceConstructor( config, @@ -140,7 +140,7 @@ export function initializeForkChoiceFromFinalizedState( executionPayloadBlockHash, executionPayloadNumber, - executionStatus: ExecutionStatus.Valid, + executionStatus, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, @@ -183,12 +183,12 @@ export function initializeForkChoiceFromUnfinalizedState( }; logger?.warn("Initializing fork choice from unfinalized state", logCtx); - const executionPayloadBlockHash = isExecutionStateType(unfinalizedState) + const isPostMerge = isExecutionStateType(unfinalizedState); + const executionPayloadBlockHash = isPostMerge ? toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash) - : ZERO_HASH_HEX; - const executionPayloadNumber = isExecutionStateType(unfinalizedState) - ? unfinalizedState.latestExecutionPayloadHeader.blockNumber - : 0; + : null; + const executionPayloadNumber = isPostMerge ? unfinalizedState.latestExecutionPayloadHeader.blockNumber : null; + const executionStatus = isPostMerge ? ExecutionStatus.Valid : ExecutionStatus.PreMerge; // this is not the justified state, but there is no other ways to get justified balances const justifiedBalances = getEffectiveBalanceIncrementsZeroInactive(unfinalizedState); @@ -205,7 +205,7 @@ export function initializeForkChoiceFromUnfinalizedState( ); // this is the same to the finalized state - const headBlock: ProtoBlock = { + const headBlock = { slot: blockHeader.slot, parentRoot: toRootHex(blockHeader.parentRoot), stateRoot: toRootHex(blockHeader.stateRoot), @@ -224,10 +224,10 @@ export function initializeForkChoiceFromUnfinalizedState( executionPayloadBlockHash, executionPayloadNumber, - executionStatus: ExecutionStatus.Valid, + executionStatus, dataAvailabilityStatus: DataAvailabilityStatus.PreData, - }; + } as ProtoBlock; const parentSlot = blockHeader.slot - 1; const parentEpoch = computeEpochAtSlot(parentSlot); diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index cc45a2625e93..a55865f307a6 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -6,7 +6,6 @@ import { computeTimeAtSlot, getBlockProposerSignatureSet, isExecutionBlockBodyType, - isExecutionEnabled, isExecutionStateType, } from "@lodestar/state-transition"; import {SignedBeaconBlock, deneb} from "@lodestar/types"; @@ -140,7 +139,7 @@ export async function validateGossipBlock( if (fork === ForkName.bellatrix) { if (!isExecutionBlockBodyType(block.body)) throw Error("Not merge block type"); const executionPayload = block.body.executionPayload; - if (isExecutionStateType(blockState) && isExecutionEnabled(blockState, block)) { + if (isExecutionStateType(blockState)) { const expectedTimestamp = computeTimeAtSlot(config, blockSlot, chain.genesisTime); if (executionPayload.timestamp !== computeTimeAtSlot(config, blockSlot, chain.genesisTime)) { throw new BlockGossipError(GossipAction.REJECT, { diff --git a/packages/beacon-node/src/node/notifier.ts b/packages/beacon-node/src/node/notifier.ts index 7dfd11eb7b00..dce75391ecae 100644 --- a/packages/beacon-node/src/node/notifier.ts +++ b/packages/beacon-node/src/node/notifier.ts @@ -169,8 +169,8 @@ function getHeadExecutionInfo( const executionStatusStr = headInfo.executionStatus.toLowerCase(); - // Add execution status to notifier only if head is on/post bellatrix - if (isExecutionCachedStateType(headState)) { + // Add execution status to notifier only if head is on/post bellatrix and has execution payload + if (isExecutionCachedStateType(headState) && headInfo.executionPayloadBlockHash !== null) { return [ `exec-block: ${executionStatusStr}(${headInfo.executionPayloadNumber} ${prettyBytesShort( headInfo.executionPayloadBlockHash diff --git a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts index f6818be1df5d..20f0ae511474 100644 --- a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts @@ -2,7 +2,7 @@ import {beforeAll, bench, describe} from "@chainsafe/benchmark"; import {BitArray, toHexString} from "@chainsafe/ssz"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {ExecutionStatus, ForkChoice, IForkChoiceStore, ProtoArray} from "@lodestar/fork-choice"; -import {HISTORICAL_ROOTS_LIMIT, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {HISTORICAL_ROOTS_LIMIT, SLOTS_PER_EPOCH, ZERO_HASH_HEX} from "@lodestar/params"; import { CachedBeaconStateAltair, DataAvailabilityStatus, @@ -62,8 +62,8 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { unrealizedJustifiedRoot: toHexString(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root), - executionPayloadBlockHash: toHexString(finalizedCheckpoint.root), - executionPayloadNumber: computeStartSlotAtEpoch(finalizedCheckpoint.epoch), + executionPayloadBlockHash: ZERO_HASH_HEX, + executionPayloadNumber: 0, executionStatus: ExecutionStatus.Valid, timeliness: false, @@ -89,8 +89,8 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { unrealizedJustifiedRoot: toHexString(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root), - executionPayloadBlockHash: toHexString(getBlockRootAtSlot(originalState, slot)), - executionPayloadNumber: slot, + executionPayloadBlockHash: ZERO_HASH_HEX, + executionPayloadNumber: 0, executionStatus: ExecutionStatus.Valid, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, diff --git a/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts b/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts index 6e0c1a63d7af..00edd120fb9c 100644 --- a/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts @@ -1,4 +1,4 @@ -import {BeaconStateAllForks, isExecutionStateType, isMergeTransitionComplete} from "@lodestar/state-transition"; +import {BeaconStateAllForks, isExecutionStateType} from "@lodestar/state-transition"; import {Assertion, AssertionResult} from "../interfaces.js"; import {neverMatcher} from "./matchers.js"; @@ -12,8 +12,9 @@ export const mergeAssertion: Assertion<"merge", string> = { const res = await node.beacon.api.debug.getStateV2({stateId: "head"}); const state = res.value() as unknown as BeaconStateAllForks; - if (!(isExecutionStateType(state) && isMergeTransitionComplete(state))) { - errors.push("Node has not yet completed the merged transition"); + // Post-merge, we just check that the state is an execution state type (Bellatrix+) + if (!isExecutionStateType(state)) { + errors.push("Node state is not an execution state type (pre-Bellatrix)"); } return errors; diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index d9e76ee5619f..d68b9ddd9826 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -10,7 +10,6 @@ import { computeStartSlotAtEpoch, getAttesterSlashableIndices, isExecutionBlockBodyType, - isExecutionEnabled, isExecutionStateType, } from "@lodestar/state-transition"; import {computeUnrealizedCheckpoints} from "@lodestar/state-transition/epoch"; @@ -734,12 +733,9 @@ export class ForkChoice implements IForkChoice { // This does not apply a vote to the block, it just makes fork choice aware of the block so // it can still be identified as the head even if it doesn't have any votes. - // Post-merge: always require execution data - if (!isExecutionBlockBodyType(block.body) || !isExecutionStateType(state) || !isExecutionEnabled(state, block)) { - throw Error("Fork choice requires execution-enabled block and state post-merge"); - } + const isPostMerge = isExecutionBlockBodyType(block.body) && isExecutionStateType(state); - const protoBlock: ProtoBlock = { + const protoBlock = { slot: slot, blockRoot: blockRootHex, parentRoot: parentRootHex, @@ -756,11 +752,15 @@ export class ForkChoice implements IForkChoice { unrealizedFinalizedEpoch: unrealizedFinalizedCheckpoint.epoch, unrealizedFinalizedRoot: unrealizedFinalizedCheckpoint.rootHex, - executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash), - executionPayloadNumber: block.body.executionPayload.blockNumber, + executionPayloadBlockHash: isPostMerge + ? toRootHex((block.body as {executionPayload: {blockHash: Uint8Array}}).executionPayload.blockHash) + : null, + executionPayloadNumber: isPostMerge + ? (block.body as {executionPayload: {blockNumber: number}}).executionPayload.blockNumber + : null, executionStatus, dataAvailabilityStatus, - }; + } as ProtoBlock; this.protoArray.onBlock(protoBlock, currentSlot); diff --git a/packages/fork-choice/src/protoArray/interface.ts b/packages/fork-choice/src/protoArray/interface.ts index 963823294e9b..ae9ec29be5b1 100644 --- a/packages/fork-choice/src/protoArray/interface.ts +++ b/packages/fork-choice/src/protoArray/interface.ts @@ -18,6 +18,7 @@ export const NULL_VOTE_INDEX = 0xffffffff; export type VoteIndex = number; export enum ExecutionStatus { + PreMerge = "PreMerge", Valid = "Valid", Syncing = "Syncing", Invalid = "Invalid", @@ -41,13 +42,23 @@ export type MaybeValidExecutionStatus = Exclude; + dataAvailabilityStatus: DataAvailabilityStatus; + }; /** * A block that is to be applied to the fork choice diff --git a/packages/state-transition/src/block/index.ts b/packages/state-transition/src/block/index.ts index 5f20ab50b6fc..bff80e417286 100644 --- a/packages/state-transition/src/block/index.ts +++ b/packages/state-transition/src/block/index.ts @@ -2,7 +2,7 @@ import {ForkSeq} from "@lodestar/params"; import {BeaconBlock, BlindedBeaconBlock, altair, capella} from "@lodestar/types"; import {BeaconStateTransitionMetrics} from "../metrics.js"; import {CachedBeaconStateAllForks, CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; -import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js"; +import {getFullOrBlindedPayload} from "../util/execution.js"; import {BlockExternalData, DataAvailabilityStatus} from "./externalData.js"; import {processBlobKzgCommitments} from "./processBlobKzgCommitments.js"; import {processBlockHeader} from "./processBlockHeader.js"; @@ -43,7 +43,7 @@ export function processBlock( // The call to the process_execution_payload must happen before the call to the process_randao as the former depends // on the randao_mix computed with the reveal of the previous block. - if (fork >= ForkSeq.bellatrix && isExecutionEnabled(state as CachedBeaconStateBellatrix, block)) { + if (fork >= ForkSeq.bellatrix) { const fullOrBlindedPayload = getFullOrBlindedPayload(block); // TODO Deneb: Allow to disable withdrawals for interop testing // https://github.com/ethereum/consensus-specs/blob/b62c9e877990242d63aa17a2a59a49bc649a2f2e/specs/eip4844/beacon-chain.md#disabling-withdrawals diff --git a/packages/state-transition/src/block/processExecutionPayload.ts b/packages/state-transition/src/block/processExecutionPayload.ts index 0af784074cdb..65f28822a022 100644 --- a/packages/state-transition/src/block/processExecutionPayload.ts +++ b/packages/state-transition/src/block/processExecutionPayload.ts @@ -3,11 +3,7 @@ import {ForkName, ForkSeq, isForkPostDeneb} from "@lodestar/params"; import {BeaconBlockBody, BlindedBeaconBlockBody, deneb, isExecutionPayload} from "@lodestar/types"; import {toHex, toRootHex} from "@lodestar/utils"; import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; -import { - executionPayloadToPayloadHeader, - getFullOrBlindedPayloadFromBody, - isMergeTransitionComplete, -} from "../util/execution.js"; +import {executionPayloadToPayloadHeader, getFullOrBlindedPayloadFromBody} from "../util/execution.js"; import {computeEpochAtSlot, computeTimeAtSlot, getRandaoMix} from "../util/index.js"; import {BlockExternalData, ExecutionPayloadStatus} from "./externalData.js"; @@ -21,15 +17,13 @@ export function processExecutionPayload( const forkName = ForkName[ForkSeq[fork] as ForkName]; // Verify consistency of the parent hash, block number, base fee per gas and gas limit // with respect to the previous execution payload header - if (isMergeTransitionComplete(state)) { - const {latestExecutionPayloadHeader} = state; - if (!byteArrayEquals(payload.parentHash, latestExecutionPayloadHeader.blockHash)) { - throw Error( - `Invalid execution payload parentHash ${toRootHex(payload.parentHash)} latest blockHash ${toRootHex( - latestExecutionPayloadHeader.blockHash - )}` - ); - } + const {latestExecutionPayloadHeader} = state; + if (!byteArrayEquals(payload.parentHash, latestExecutionPayloadHeader.blockHash)) { + throw Error( + `Invalid execution payload parentHash ${toRootHex(payload.parentHash)} latest blockHash ${toRootHex( + latestExecutionPayloadHeader.blockHash + )}` + ); } // Verify random diff --git a/packages/state-transition/src/util/execution.ts b/packages/state-transition/src/util/execution.ts index b6710d93f336..06206bc4b293 100644 --- a/packages/state-transition/src/util/execution.ts +++ b/packages/state-transition/src/util/execution.ts @@ -20,22 +20,6 @@ import { CachedBeaconStateExecutions, } from "../types.js"; -/** - * Execution is always enabled post-merge. - * Since all networks have transitioned, this always returns true for execution states. - */ -export function isExecutionEnabled(_state: BeaconStateExecutions, _block: BeaconBlock | BlindedBeaconBlock): boolean { - return true; -} - -/** - * Merge is complete for all networks. - * This function is kept for API compatibility but always returns true. - */ -export function isMergeTransitionComplete(_state: BeaconStateExecutions): boolean { - return true; -} - /** Type guard for bellatrix.BeaconState */ export function isExecutionStateType(state: BeaconStateAllForks): state is BeaconStateExecutions { return (state as BeaconStateExecutions).latestExecutionPayloadHeader !== undefined; From 27f7924ff957982e498f66e133a731fdc4504d6d Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 9 Dec 2025 15:41:47 +0100 Subject: [PATCH 06/21] Remove stale comments --- packages/beacon-node/src/execution/engine/mock.ts | 2 +- packages/fork-choice/src/forkChoice/forkChoice.ts | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/beacon-node/src/execution/engine/mock.ts b/packages/beacon-node/src/execution/engine/mock.ts index d28632d07a13..8aa65a980136 100644 --- a/packages/beacon-node/src/execution/engine/mock.ts +++ b/packages/beacon-node/src/execution/engine/mock.ts @@ -258,7 +258,7 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { // section of the EIP. Additionally, if this validation fails, client software MUST NOT update the forkchoice // state and MUST NOT begin a payload build process. // - // > TODO + // > N/A: All networks have completed the merge transition // 4. Before updating the forkchoice state, client software MUST ensure the validity of the payload referenced by // forkchoiceState.headBlockHash, and MAY validate the payload while processing the call. The validation process diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index d68b9ddd9826..761599c7685c 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -652,16 +652,6 @@ export class ForkChoice implements IForkChoice { this.proposerBoostRoot = blockRootHex; } - // As per specs, we should be validating here the terminal conditions of - // the PoW if this were a merge transition block. - // (https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/fork-choice.md#on_block) - // - // However this check has been moved to the `verifyBlockStateTransition` in - // `packages/beacon-node/src/chain/blocks/verifyBlock.ts` as: - // - // 1. Its prudent to fail fast and not try importing a block in forkChoice. - // 2. Also the data to run such a validation is readily available there. - const justifiedCheckpoint = toCheckpointWithHex(state.currentJustifiedCheckpoint); const finalizedCheckpoint = toCheckpointWithHex(state.finalizedCheckpoint); const stateJustifiedEpoch = justifiedCheckpoint.epoch; From 4ea9014b02876c452aed49a041b433dd333af914 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 13:19:53 +0100 Subject: [PATCH 07/21] Review changes --- .../beacon-node/src/api/impl/debug/index.ts | 10 ++-- .../src/chain/blocks/verifyBlock.ts | 1 - .../blocks/verifyBlocksExecutionPayloads.ts | 10 +++- .../beacon-node/src/chain/forkChoice/index.ts | 37 +++++++------- .../fork-choice/src/forkChoice/forkChoice.ts | 51 ++++++++++++++----- .../fork-choice/src/protoArray/interface.ts | 24 +++------ .../state-transition/src/util/execution.ts | 1 - 7 files changed, 76 insertions(+), 58 deletions(-) diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index ba280e202b73..f1f0562fd067 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,7 +1,7 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ExecutionStatus} from "@lodestar/fork-choice"; -import {isForkPostDeneb, isForkPostFulu} from "@lodestar/params"; +import {ZERO_HASH_HEX, isForkPostDeneb, isForkPostFulu} from "@lodestar/params"; import {BeaconState, deneb, fulu, sszTypesFor} from "@lodestar/types"; import {fromAsync, toRootHex} from "@lodestar/utils"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; @@ -47,12 +47,11 @@ export function getDebugApi({ case ExecutionStatus.Invalid: return "invalid"; case ExecutionStatus.Syncing: - return "optimistic"; case ExecutionStatus.PreMerge: - return "valid"; // Pre-merge blocks are considered valid + return "optimistic"; } })(), - executionBlockHash: node.executionPayloadBlockHash ?? "0x", + executionBlockHash: node.executionPayloadBlockHash ?? ZERO_HASH_HEX, })), }, }; @@ -60,9 +59,10 @@ export function getDebugApi({ async getProtoArrayNodes() { const nodes = chain.forkChoice.getAllNodes().map((node) => ({ + // if node has executionPayloadNumber, it will overwrite the below default + executionPayloadNumber: 0, ...node, executionPayloadBlockHash: node.executionPayloadBlockHash ?? "", - executionPayloadNumber: node.executionPayloadNumber ?? 0, parent: String(node.parent), bestChild: String(node.bestChild), bestDescendant: String(node.bestDescendant), diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 5644691b5a26..0ea007841b9b 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -100,7 +100,6 @@ export async function verifyBlocksInEpoch( : Promise.resolve({ execAborted: null, executionStatuses: blocks.map((_blk) => ExecutionStatus.Syncing), - executionTime: Date.now(), } as SegmentExecStatus); // Store indexed attestations for each block to avoid recomputing them during import diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index fec36bd24817..a74e3f033db1 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -259,8 +259,14 @@ function getSegmentErrorResponse( } // If there is no valid in the segment then we have to propagate invalid response - // in forkchoice as well if the parentBlock is also not the lvh - if (!lvhFound && parentBlock.executionPayloadBlockHash !== lvhResponse.latestValidExecHash) { + // in forkchoice as well if + // - if the parentBlock is also not the lvh + // - and parentBlock is not pre merge + if ( + !lvhFound && + parentBlock.executionStatus !== ExecutionStatus.PreMerge && + parentBlock.executionPayloadBlockHash !== lvhResponse.latestValidExecHash + ) { invalidSegmentLVH = { executionStatus: ExecutionStatus.Invalid, latestValidExecHash: lvhResponse.latestValidExecHash, diff --git a/packages/beacon-node/src/chain/forkChoice/index.ts b/packages/beacon-node/src/chain/forkChoice/index.ts index b00d2d6a09c1..56da740ee99b 100644 --- a/packages/beacon-node/src/chain/forkChoice/index.ts +++ b/packages/beacon-node/src/chain/forkChoice/index.ts @@ -21,6 +21,7 @@ import { } from "@lodestar/state-transition"; import {Slot, ssz} from "@lodestar/types"; import {Logger, toRootHex} from "@lodestar/utils"; +import {GENESIS_SLOT} from "../../constants/index.js"; import {Metrics} from "../../metrics/index.js"; import {ChainEvent, ChainEventEmitter} from "../emitter.js"; @@ -101,11 +102,6 @@ export function initializeForkChoiceFromFinalizedState( // production code use ForkChoice constructor directly const forkchoiceConstructor = opts.forkchoiceConstructor ?? ForkChoice; - const isPostMerge = isExecutionStateType(state); - const executionPayloadBlockHash = isPostMerge ? toRootHex(state.latestExecutionPayloadHeader.blockHash) : null; - const executionPayloadNumber = isPostMerge ? state.latestExecutionPayloadHeader.blockNumber : null; - const executionStatus = isPostMerge ? ExecutionStatus.Valid : ExecutionStatus.PreMerge; - return new forkchoiceConstructor( config, @@ -138,9 +134,13 @@ export function initializeForkChoiceFromFinalizedState( unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root), - executionPayloadBlockHash, - executionPayloadNumber, - executionStatus, + ...(isExecutionStateType(state) + ? { + executionPayloadBlockHash: toRootHex(state.latestExecutionPayloadHeader.blockHash), + executionPayloadNumber: state.latestExecutionPayloadHeader.blockNumber, + executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing, + } + : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}), dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, @@ -183,13 +183,6 @@ export function initializeForkChoiceFromUnfinalizedState( }; logger?.warn("Initializing fork choice from unfinalized state", logCtx); - const isPostMerge = isExecutionStateType(unfinalizedState); - const executionPayloadBlockHash = isPostMerge - ? toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash) - : null; - const executionPayloadNumber = isPostMerge ? unfinalizedState.latestExecutionPayloadHeader.blockNumber : null; - const executionStatus = isPostMerge ? ExecutionStatus.Valid : ExecutionStatus.PreMerge; - // this is not the justified state, but there is no other ways to get justified balances const justifiedBalances = getEffectiveBalanceIncrementsZeroInactive(unfinalizedState); const store = new ForkChoiceStore( @@ -205,7 +198,7 @@ export function initializeForkChoiceFromUnfinalizedState( ); // this is the same to the finalized state - const headBlock = { + const headBlock: ProtoBlock = { slot: blockHeader.slot, parentRoot: toRootHex(blockHeader.parentRoot), stateRoot: toRootHex(blockHeader.stateRoot), @@ -222,12 +215,16 @@ export function initializeForkChoiceFromUnfinalizedState( unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toRootHex(finalizedCheckpoint.root), - executionPayloadBlockHash, - executionPayloadNumber, - executionStatus, + ...(isExecutionStateType(unfinalizedState) + ? { + executionPayloadBlockHash: toRootHex(unfinalizedState.latestExecutionPayloadHeader.blockHash), + executionPayloadNumber: unfinalizedState.latestExecutionPayloadHeader.blockNumber, + executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing, + } + : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}), dataAvailabilityStatus: DataAvailabilityStatus.PreData, - } as ProtoBlock; + }; const parentSlot = blockHeader.slot - 1; const parentEpoch = computeEpochAtSlot(parentSlot); diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 761599c7685c..02234e8f7367 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -30,6 +30,7 @@ import {ForkChoiceMetrics} from "../metrics.js"; import {computeDeltas} from "../protoArray/computeDeltas.js"; import {ProtoArrayError, ProtoArrayErrorCode} from "../protoArray/errors.js"; import { + ExecutionStatus, HEX_ZERO_HASH, LVHExecResponse, MaybeValidExecutionStatus, @@ -723,9 +724,7 @@ export class ForkChoice implements IForkChoice { // This does not apply a vote to the block, it just makes fork choice aware of the block so // it can still be identified as the head even if it doesn't have any votes. - const isPostMerge = isExecutionBlockBodyType(block.body) && isExecutionStateType(state); - - const protoBlock = { + const protoBlock: ProtoBlock = { slot: slot, blockRoot: blockRootHex, parentRoot: parentRootHex, @@ -742,15 +741,19 @@ export class ForkChoice implements IForkChoice { unrealizedFinalizedEpoch: unrealizedFinalizedCheckpoint.epoch, unrealizedFinalizedRoot: unrealizedFinalizedCheckpoint.rootHex, - executionPayloadBlockHash: isPostMerge - ? toRootHex((block.body as {executionPayload: {blockHash: Uint8Array}}).executionPayload.blockHash) - : null, - executionPayloadNumber: isPostMerge - ? (block.body as {executionPayload: {blockNumber: number}}).executionPayload.blockNumber - : null, - executionStatus, - dataAvailabilityStatus, - } as ProtoBlock; + ...(isExecutionBlockBodyType(block.body) && isExecutionStateType(state) + ? { + executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash), + executionPayloadNumber: block.body.executionPayload.blockNumber, + executionStatus: this.getPostMergeExecStatus(executionStatus), + dataAvailabilityStatus, + } + : { + executionPayloadBlockHash: null, + executionStatus: this.getPreMergeExecStatus(executionStatus), + dataAvailabilityStatus: this.getPreMergeDataStatus(dataAvailabilityStatus), + }), + }; this.protoArray.onBlock(protoBlock, currentSlot); @@ -1212,6 +1215,30 @@ export class ForkChoice implements IForkChoice { return secFromSlot * 1000 <= proposerReorgCutoff; } + private getPreMergeExecStatus(executionStatus: MaybeValidExecutionStatus): ExecutionStatus.PreMerge { + if (executionStatus !== ExecutionStatus.PreMerge) + throw Error(`Invalid pre-merge execution status: expected: ${ExecutionStatus.PreMerge}, got ${executionStatus}`); + return executionStatus; + } + + private getPreMergeDataStatus(dataAvailabilityStatus: DataAvailabilityStatus): DataAvailabilityStatus.PreData { + if (dataAvailabilityStatus !== DataAvailabilityStatus.PreData) + throw Error( + `Invalid pre-merge data status: expected: ${DataAvailabilityStatus.PreData}, got ${dataAvailabilityStatus}` + ); + return dataAvailabilityStatus; + } + + private getPostMergeExecStatus( + executionStatus: MaybeValidExecutionStatus + ): ExecutionStatus.Valid | ExecutionStatus.Syncing { + if (executionStatus === ExecutionStatus.PreMerge) + throw Error( + `Invalid post-merge execution status: expected: ${ExecutionStatus.Syncing} or ${ExecutionStatus.Valid} , got ${executionStatus}` + ); + return executionStatus; + } + /** * Why `getJustifiedBalances` getter? * - updateCheckpoints() is called in both on_block and on_tick. diff --git a/packages/fork-choice/src/protoArray/interface.ts b/packages/fork-choice/src/protoArray/interface.ts index ae9ec29be5b1..74b908cb1fc6 100644 --- a/packages/fork-choice/src/protoArray/interface.ts +++ b/packages/fork-choice/src/protoArray/interface.ts @@ -18,9 +18,9 @@ export const NULL_VOTE_INDEX = 0xffffffff; export type VoteIndex = number; export enum ExecutionStatus { - PreMerge = "PreMerge", Valid = "Valid", Syncing = "Syncing", + PreMerge = "PreMerge", Invalid = "Invalid", } @@ -37,27 +37,17 @@ export type LVHExecResponse = LVHValidResponse | LVHInvalidResponse; export type MaybeValidExecutionStatus = Exclude; -/** - * Extra metadata for execution-related data. - * - * Note: executionStatus uses full ExecutionStatus enum because blocks - * can become Invalid through EL validation after being added as Valid/Syncing. - * - * For PreMerge blocks, executionPayloadBlockHash and executionPayloadNumber - * will be null since there is no execution payload. - */ export type BlockExtraMeta = - | { - executionPayloadBlockHash: null; - executionPayloadNumber: null; - executionStatus: ExecutionStatus.PreMerge; - dataAvailabilityStatus: DataAvailabilityStatus; - } | { executionPayloadBlockHash: RootHex; executionPayloadNumber: UintNum64; executionStatus: Exclude; dataAvailabilityStatus: DataAvailabilityStatus; + } + | { + executionPayloadBlockHash: null; + executionStatus: ExecutionStatus.PreMerge; + dataAvailabilityStatus: DataAvailabilityStatus.PreData; }; /** @@ -103,7 +93,7 @@ export type ProtoBlock = BlockExtraMeta & { /** * A block root with additional metadata required to form a DAG - * with vote weights and best blocks stored as metadata. + * with vote weights and best blocks stored as metadata */ export type ProtoNode = ProtoBlock & { parent?: number; diff --git a/packages/state-transition/src/util/execution.ts b/packages/state-transition/src/util/execution.ts index 06206bc4b293..a64f21a08699 100644 --- a/packages/state-transition/src/util/execution.ts +++ b/packages/state-transition/src/util/execution.ts @@ -2,7 +2,6 @@ import {ForkName, ForkPostBellatrix, ForkPreGloas, ForkSeq} from "@lodestar/para import { BeaconBlock, BeaconBlockBody, - BlindedBeaconBlock, BlindedBeaconBlockBody, ExecutionPayload, ExecutionPayloadHeader, From f3a4d30654aad44af29aea9402e74fad44a9b1ab Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 13:26:14 +0100 Subject: [PATCH 08/21] Remove merge tracking dashboard panels --- dashboards/lodestar_execution_engine.json | 987 ++-------------------- 1 file changed, 59 insertions(+), 928 deletions(-) diff --git a/dashboards/lodestar_execution_engine.json b/dashboards/lodestar_execution_engine.json index 63c1ab636e51..434563d54eef 100644 --- a/dashboards/lodestar_execution_engine.json +++ b/dashboards/lodestar_execution_engine.json @@ -90,6 +90,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -173,6 +174,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -258,6 +260,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -343,6 +346,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -428,6 +432,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -513,6 +518,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -624,6 +630,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -637,6 +644,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, @@ -728,10 +736,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "auto" + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -760,6 +770,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -773,6 +784,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -843,6 +855,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -856,6 +869,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -926,6 +940,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -939,6 +954,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, @@ -1010,6 +1026,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1023,6 +1040,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1105,6 +1123,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1118,6 +1137,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1185,6 +1205,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1198,6 +1219,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1277,6 +1299,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1290,6 +1313,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1382,6 +1406,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1395,6 +1420,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1487,6 +1513,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1500,6 +1527,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1618,6 +1646,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1631,6 +1660,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1759,10 +1789,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "value" + "textMode": "value", + "wideLayout": true }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -1809,10 +1841,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "auto" + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -1861,10 +1895,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "auto" + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -1913,10 +1949,12 @@ "fields": "/^Time$/", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "auto" + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -1947,6 +1985,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1960,6 +1999,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2054,6 +2094,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2067,6 +2108,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, @@ -2138,6 +2180,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2151,6 +2194,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2244,6 +2288,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2257,6 +2302,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 1, @@ -2326,6 +2372,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2339,6 +2386,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2409,927 +2457,10 @@ ], "title": "Eth1 fetch rate", "type": "timeseries" - }, - { - "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 92 - }, - "id": 437, - "panels": [], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "refId": "A" - } - ], - "title": "Merge Tracking", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [ - { - "options": { - "0": { - "index": 0, - "text": "STOPPED" - }, - "1": { - "index": 1, - "text": "SEARCHING" - }, - "2": { - "index": 2, - "text": "FOUND" - }, - "3": { - "index": 3, - "text": "MERGE_COMPLETE" - } - }, - "type": "value" - } - ], - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 0, - "y": 93 - }, - "id": 439, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "value" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_merge_status", - "hide": false, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Merge Status", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 2, - "y": 93 - }, - "id": 459, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_merge_ttd", - "format": "time_series", - "instant": false, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Target TTD", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 4, - "y": 93 - }, - "id": 475, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "(lodestar_eth1_merge_ttd - lodestar_eth1_latest_block_ttd)\n/\nrate(lodestar_eth1_latest_block_ttd[32m])", - "hide": false, - "interval": "", - "legendFormat": "", - "refId": "B" - } - ], - "title": "Time to TTD", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [], - "unit": "dateTimeAsSystem" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 4, - "x": 8, - "y": 93 - }, - "id": 476, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "(time()\n+\n(lodestar_eth1_merge_ttd - lodestar_eth1_latest_block_ttd)\n/\nrate(lodestar_eth1_latest_block_ttd[32m])\n)*1000", - "hide": false, - "interval": "", - "legendFormat": "", - "refId": "B" - } - ], - "title": "TTD date aprox", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 12, - "y": 93 - }, - "id": 462, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "value" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_parent_blocks_fetched_total", - "hide": false, - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "title": "Parents fetched", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 16, - "y": 93 - }, - "id": 456, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "name" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_merge_block_details", - "format": "time_series", - "instant": true, - "interval": "", - "legendFormat": "{{terminalBlockNumber}}", - "refId": "A" - } - ], - "title": "Terminal Block Num", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 20, - "y": 93 - }, - "id": 464, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "name" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_merge_block_details", - "format": "time_series", - "instant": true, - "interval": "", - "legendFormat": "{{terminalBlockNumber}}", - "refId": "A" - } - ], - "title": "Terminal Block Time", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 0, - "y": 96 - }, - "id": 460, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_merge_td_factor", - "hide": false, - "interval": "", - "legendFormat": "td factor", - "refId": "A" - } - ], - "title": "TD factor", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 2, - "y": 96 - }, - "id": 450, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_latest_block_ttd", - "hide": false, - "interval": "", - "legendFormat": "latest block ttd", - "refId": "A" - } - ], - "title": "Latest td", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [], - "unit": "dateTimeFromNow" - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 4, - "y": 96 - }, - "id": 463, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_latest_block_timestamp * 1000", - "hide": false, - "interval": "", - "legendFormat": "latest block ttd", - "refId": "A" - } - ], - "title": "Latest block time", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 6, - "y": 96 - }, - "id": 461, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_latest_block_number", - "hide": false, - "interval": "", - "legendFormat": "latest block ttd", - "refId": "A" - } - ], - "title": "Latest num", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 4, - "x": 12, - "y": 96 - }, - "id": 467, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "last" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_get_terminal_pow_block_promise_cache_hit_total", - "hide": false, - "interval": "", - "legendFormat": "cache hits", - "refId": "A" - } - ], - "title": "Promise cache hits", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "mappings": [] - }, - "overrides": [] - }, - "gridPos": { - "h": 3, - "w": 8, - "x": 16, - "y": 96 - }, - "id": 455, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "name" - }, - "pluginVersion": "9.3.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_merge_block_details", - "format": "time_series", - "instant": true, - "interval": "", - "legendFormat": "{{terminalBlockHash}}", - "refId": "A" - } - ], - "title": "Terminal Block Hash", - "type": "stat" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "links": [], - "mappings": [], - "unit": "none" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 99 - }, - "id": 458, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.2.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_latest_block_ttd", - "hide": false, - "interval": "", - "legendFormat": "lastest block td", - "refId": "A" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "lodestar_eth1_merge_ttd", - "hide": false, - "interval": "", - "legendFormat": "merge ttd", - "refId": "B" - } - ], - "title": "Latest TD vs TTD", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": true, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "links": [], - "mappings": [], - "unit": "short" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 99 - }, - "id": 444, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "8.2.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "exemplar": false, - "expr": "rate(lodestar_eth1_poll_merge_block_errors_total[$rate_interval])", - "hide": false, - "interval": "", - "legendFormat": "polling errors", - "refId": "A" - } - ], - "title": "Error rate", - "type": "timeseries" } ], "refresh": "10s", - "schemaVersion": 38, - "style": "dark", + "schemaVersion": 39, "tags": [ "lodestar" ], From 06c1dd2b1d85125a30e7f5f811e84be7cc421ece Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 14:40:09 +0100 Subject: [PATCH 09/21] Review changes --- .../chain/produceBlock/produceBlockBody.ts | 28 +- .../beacon-node/src/execution/engine/http.ts | 16 +- .../beacon-node/src/execution/engine/mock.ts | 16 +- packages/beacon-node/src/node/notifier.ts | 14 +- .../opPools/aggregatedAttestationPool.test.ts | 12 +- .../test/sim/electra-interop.test.ts | 6 +- .../test/spec/presets/fork_choice.test.ts | 47 +- .../unit/chain/forkChoice/forkChoice.test.ts | 2 +- packages/beacon-node/test/utils/runEl.ts | 13 +- packages/beacon-node/test/utils/state.ts | 4 +- .../beacon-node/test/utils/typeGenerator.ts | 4 +- .../test/utils/validationData/attestation.ts | 4 +- packages/cli/src/config/beaconParams.ts | 9 +- packages/cli/src/options/paramsOptions.ts | 46 +- packages/cli/test/sim/multiFork.test.ts | 9 - .../crucible/assertions/mergeAssertion.ts | 22 - .../utils/crucible/clients/execution/geth.ts | 5 +- .../utils/crucible/clients/execution/index.ts | 6 +- .../cli/test/utils/crucible/interfaces.ts | 6 - .../utils/crucible/utils/executionGenesis.ts | 19 +- packages/fork-choice/src/protoArray/errors.ts | 1 + .../fork-choice/src/protoArray/protoArray.ts | 19 +- .../fork-choice/test/perf/forkChoice/util.ts | 14 +- .../test/unit/forkChoice/forkChoice.test.ts | 10 +- .../unit/forkChoice/getProposerHead.test.ts | 15 +- .../shouldOverrideForkChoiceUpdate.test.ts | 15 +- .../protoArray/executionStatusUpdates.test.ts | 437 ++++++++++++++++++ .../unit/protoArray/getCommonAncestor.test.ts | 8 +- .../test/unit/protoArray/protoArray.test.ts | 12 +- packages/types/src/bellatrix/sszTypes.ts | 9 - packages/types/src/bellatrix/types.ts | 1 - 31 files changed, 528 insertions(+), 301 deletions(-) delete mode 100644 packages/cli/test/utils/crucible/assertions/mergeAssertion.ts create mode 100644 packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index aadec95c47e2..556e4a42f74a 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -352,7 +352,10 @@ export async function produceBlockBody( }); return {...prepareRes, ...payloadRes}; - })(); + })().catch((e) => { + this.metrics?.blockPayload.payloadFetchErrors.inc(); + throw e; + }); const [engineRes, commonBlockBody] = await Promise.all([enginePromise, commonBlockBodyPromise]); blockBody = Object.assign({}, commonBlockBody) as AssembledBodyType; @@ -465,11 +468,7 @@ export async function produceBlockBody( } /** - * Produce ExecutionPayload for pre-merge, merge, and post-merge. - * - * Expects `eth1MergeBlockFinder` to be actively searching for blocks well in advance to being called. - * - * @returns PayloadId = pow block found, null = pow NOT found + * Produce ExecutionPayload for post-merge. */ export async function prepareExecutionPayload( chain: { @@ -484,7 +483,7 @@ export async function prepareExecutionPayload( state: CachedBeaconStateExecutions, suggestedFeeRecipient: string ): Promise<{prepType: PayloadPreparationType; payloadId: PayloadId}> { - const {parentHash} = getExecutionPayloadParentHash(chain, state); + const parentHash = state.latestExecutionPayloadHeader.blockHash; const timestamp = computeTimeAtSlot(chain.config, state.slot, state.genesisTime); const prevRandao = getRandaoMix(state, state.epochCtx.epoch); @@ -561,20 +560,10 @@ async function prepareExecutionPayloadHeader( throw Error("executionBuilder required"); } - const {parentHash} = getExecutionPayloadParentHash(chain, state); + const parentHash = state.latestExecutionPayloadHeader.blockHash; return chain.executionBuilder.getHeader(fork, state.slot, parentHash, proposerPubKey); } -export function getExecutionPayloadParentHash( - _chain: { - config: ChainForkConfig; - }, - state: CachedBeaconStateExecutions -): {parentHash: Root} { - // Post-merge, use the latest execution payload header block hash as parent - return {parentHash: state.latestExecutionPayloadHeader.blockHash}; -} - export function getPayloadAttributesForSSE( fork: ForkPostBellatrix, chain: { @@ -587,14 +576,13 @@ export function getPayloadAttributesForSSE( feeRecipient, }: {prepareState: CachedBeaconStateExecutions; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string} ): SSEPayloadAttributes { - const {parentHash} = getExecutionPayloadParentHash(chain, prepareState); + const parentHash = prepareState.latestExecutionPayloadHeader.blockHash; const payloadAttributes = preparePayloadAttributes(fork, chain, { prepareState, prepareSlot, parentBlockRoot, feeRecipient, }); - const ssePayloadAttributes: SSEPayloadAttributes = { proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot), proposalSlot: prepareSlot, diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index e4e69f9d5fab..32739eba4ce9 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -194,15 +194,12 @@ export class ExecutionEngineHttp implements IExecutionEngine { * 1. {status: INVALID_BLOCK_HASH, latestValidHash: null, validationError: * errorMessage | null} if the blockHash validation has failed * - * 2. {status: INVALID_TERMINAL_BLOCK, latestValidHash: null, validationError: - * errorMessage | null} if terminal block conditions are not satisfied - * - * 3. {status: SYNCING, latestValidHash: null, validationError: null} if the payload + * 2. {status: SYNCING, latestValidHash: null, validationError: null} if the payload * extends the canonical chain and requisite data for its validation is missing * with the payload status obtained from the Payload validation process if the payload * has been fully validated while processing the call * - * 4. {status: ACCEPTED, latestValidHash: null, validationError: null} if the + * 3. {status: ACCEPTED, latestValidHash: null, validationError: null} if the * following conditions are met: * i) the blockHash of the payload is valid * ii) the payload doesn't extend the canonical chain @@ -330,16 +327,11 @@ export class ExecutionEngineHttp implements IExecutionEngine { * errorMessage | null}, payloadId: null} * obtained from the Payload validation process if the payload is deemed INVALID * - * 3. {payloadStatus: {status: INVALID_TERMINAL_BLOCK, latestValidHash: null, - * validationError: errorMessage | null}, payloadId: null} - * either obtained from the Payload validation process or as a result of validating a - * PoW block referenced by forkchoiceState.headBlockHash - * - * 4. {payloadStatus: {status: VALID, latestValidHash: forkchoiceState.headBlockHash, + * 3. {payloadStatus: {status: VALID, latestValidHash: forkchoiceState.headBlockHash, * validationError: null}, payloadId: null} * if the payload is deemed VALID and a build process hasn't been started * - * 5. {payloadStatus: {status: VALID, latestValidHash: forkchoiceState.headBlockHash, + * 4. {payloadStatus: {status: VALID, latestValidHash: forkchoiceState.headBlockHash, * validationError: null}, payloadId: buildProcessId} * if the payload is deemed VALID and the build process has begun. * diff --git a/packages/beacon-node/src/execution/engine/mock.ts b/packages/beacon-node/src/execution/engine/mock.ts index 8aa65a980136..b6d30dece51c 100644 --- a/packages/beacon-node/src/execution/engine/mock.ts +++ b/packages/beacon-node/src/execution/engine/mock.ts @@ -9,7 +9,7 @@ import { ForkSeq, } from "@lodestar/params"; import {ExecutionPayload, RootHex, bellatrix, deneb, ssz} from "@lodestar/types"; -import {fromHex, toHex, toRootHex} from "@lodestar/utils"; +import {fromHex, toRootHex} from "@lodestar/utils"; import {ZERO_HASH_HEX} from "../../constants/index.js"; import {quantityToNum} from "../../eth1/provider/utils.js"; import {INTEROP_BLOCK_HASH} from "../../node/utils/interop/state.js"; @@ -70,7 +70,7 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { finalizedBlockHash = ZERO_HASH_HEX; readonly payloadIdCache = new PayloadIdCache(); - /** Known valid blocks, both pre-merge and post-merge */ + /** Known valid blocks */ private readonly validBlocks = new Map(); /** Preparing payloads to be retrieved via engine_getPayloadV1 */ private readonly preparingPayloads = new Map(); @@ -135,18 +135,6 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { return [] as ExecutionPayloadBodyRpc[]; } - /** - * Mock manipulator to add more known blocks to this mock. - */ - addPowBlock(powBlock: bellatrix.PowBlock): void { - this.validBlocks.set(toHex(powBlock.blockHash), { - parentHash: toHex(powBlock.parentHash), - blockHash: toHex(powBlock.blockHash), - timestamp: 0, - blockNumber: 0, - }); - } - /** * Mock manipulator to add predefined responses before execution engine client calls */ diff --git a/packages/beacon-node/src/node/notifier.ts b/packages/beacon-node/src/node/notifier.ts index dce75391ecae..aabb1850a060 100644 --- a/packages/beacon-node/src/node/notifier.ts +++ b/packages/beacon-node/src/node/notifier.ts @@ -1,5 +1,5 @@ import {BeaconConfig} from "@lodestar/config"; -import {ProtoBlock} from "@lodestar/fork-choice"; +import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice"; import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params"; import { CachedBeaconStateAllForks, @@ -169,12 +169,14 @@ function getHeadExecutionInfo( const executionStatusStr = headInfo.executionStatus.toLowerCase(); - // Add execution status to notifier only if head is on/post bellatrix and has execution payload - if (isExecutionCachedStateType(headState) && headInfo.executionPayloadBlockHash !== null) { + // Add execution status to notifier only if head is on/post bellatrix + if (isExecutionCachedStateType(headState)) { + const executionPayloadHashInfo = + headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadBlockHash : "empty"; + const executionPayloadNumberInfo = + headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadNumber : NaN; return [ - `exec-block: ${executionStatusStr}(${headInfo.executionPayloadNumber} ${prettyBytesShort( - headInfo.executionPayloadBlockHash - )})`, + `exec-block: ${executionStatusStr}(${executionPayloadNumberInfo} ${prettyBytesShort(executionPayloadHashInfo)})`, ]; } diff --git a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts index 20f0ae511474..08d0481d01d6 100644 --- a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts @@ -2,7 +2,7 @@ import {beforeAll, bench, describe} from "@chainsafe/benchmark"; import {BitArray, toHexString} from "@chainsafe/ssz"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {ExecutionStatus, ForkChoice, IForkChoiceStore, ProtoArray} from "@lodestar/fork-choice"; -import {HISTORICAL_ROOTS_LIMIT, SLOTS_PER_EPOCH, ZERO_HASH_HEX} from "@lodestar/params"; +import {HISTORICAL_ROOTS_LIMIT, SLOTS_PER_EPOCH} from "@lodestar/params"; import { CachedBeaconStateAltair, DataAvailabilityStatus, @@ -62,9 +62,8 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { unrealizedJustifiedRoot: toHexString(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root), - executionPayloadBlockHash: ZERO_HASH_HEX, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, @@ -89,9 +88,8 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { unrealizedJustifiedRoot: toHexString(justifiedCheckpoint.root), unrealizedFinalizedEpoch: finalizedCheckpoint.epoch, unrealizedFinalizedRoot: toHexString(finalizedCheckpoint.root), - executionPayloadBlockHash: ZERO_HASH_HEX, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, diff --git a/packages/beacon-node/test/sim/electra-interop.test.ts b/packages/beacon-node/test/sim/electra-interop.test.ts index d4dfa5b5d73c..59c0eb13face 100644 --- a/packages/beacon-node/test/sim/electra-interop.test.ts +++ b/packages/beacon-node/test/sim/electra-interop.test.ts @@ -20,7 +20,7 @@ import {TestLoggerOpts, testLogger} from "../utils/logger.js"; import {getDevBeaconNode} from "../utils/node/beacon.js"; import {simTestInfoTracker} from "../utils/node/simTest.js"; import {getAndInitDevValidators} from "../utils/node/validator.js"; -import {ELClient, ELStartMode, runEL, sendRawTransactionBig} from "../utils/runEl.js"; +import {ELClient, runEL, sendRawTransactionBig} from "../utils/runEl.js"; import {logFilesDir} from "./params.js"; import {shell} from "./shell.js"; @@ -67,7 +67,7 @@ describe("executionEngine / ExecutionEngineHttp", () => { it("Send and get payloads with depositRequests to/from EL", async () => { const {elClient, tearDownCallBack} = await runEL( - {...elSetupConfig, mode: ELStartMode.PostMerge, genesisTemplate: "electra.tmpl"}, + {...elSetupConfig, genesisTemplate: "electra.tmpl"}, {...elRunOptions, ttd: BigInt(0)}, controller.signal ); @@ -232,7 +232,7 @@ describe("executionEngine / ExecutionEngineHttp", () => { it.skip("Post-merge, run for a few blocks", async () => { console.log("\n\nPost-merge, run for a few blocks\n\n"); const {elClient, tearDownCallBack} = await runEL( - {...elSetupConfig, mode: ELStartMode.PostMerge, genesisTemplate: "electra.tmpl"}, + {...elSetupConfig, genesisTemplate: "electra.tmpl"}, {...elRunOptions, ttd: BigInt(0)}, controller.signal ); diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts index 1589ae38d684..33bfb100c7f4 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -21,7 +21,6 @@ import { BeaconBlock, RootHex, SignedBeaconBlock, - bellatrix, deneb, fulu, ssz, @@ -60,7 +59,6 @@ const ANCHOR_BLOCK_FILE_NAME = "anchor_block"; const BLOCK_FILE_NAME = "^(block)_([0-9a-zA-Z]+)$"; const BLOBS_FILE_NAME = "^(blobs)_([0-9a-zA-Z]+)$"; const COLUMN_FILE_NAME = "^(column)_([0-9a-zA-Z]+)$"; -const POW_BLOCK_FILE_NAME = "^(pow_block)_([0-9a-zA-Z]+)$"; const ATTESTATION_FILE_NAME = "^(attestation)_([0-9a-zA-Z])+$"; const ATTESTER_SLASHING_FILE_NAME = "^(attester_slashing)_([0-9a-zA-Z])+$"; @@ -79,7 +77,7 @@ const forkChoiceTest = /** This is to track test's tickTime to be used in proposer boost */ let tickTime = 0; const clock = new ClockStopped(currentSlot); - const eth1 = new Eth1ForBlockProductionMock(); + const eth1 = new Eth1ForBlockProductionDisabled(); const executionEngineBackend = new ExecutionEngineMockBackend({ onlyPredefinedResponses: opts.onlyPredefinedResponses, genesisBlockHash: isExecutionStateType(anchorState) @@ -332,22 +330,6 @@ const forkChoiceTest = } } - // **on_merge_block execution** - // Adds PowBlock data which is required for executing on_block(store, block). - // The file is located in the same folder (see below). PowBlocks should be used as return values for - // get_pow_block(hash: Hash32) -> PowBlock function if hashes match. - else if (isPowBlock(step)) { - const powBlock = testcase.powBlocks.get(step.pow_block); - if (!powBlock) throw Error(`pow_block ${step.pow_block} not found`); - logger.debug(`Step ${i}/${stepsLen} pow_block`, { - blockHash: toHexString(powBlock.blockHash), - parentHash: toHexString(powBlock.parentHash), - }); - // Register PowBlock to allow validation in execution engine - // Note: eth1.addPowBlock removed since merge validation is no longer needed - executionEngineBackend.addPowBlock(powBlock); - } - // Optional step for optimistic sync tests. else if (isOnPayloadInfoStep(step)) { logger.debug(`Step ${i}/${stepsLen} payload_status`, {blockHash: step.block_hash}); @@ -453,7 +435,6 @@ const forkChoiceTest = [BLOCK_FILE_NAME]: ssz[fork].SignedBeaconBlock, [BLOBS_FILE_NAME]: ssz.deneb.Blobs, [COLUMN_FILE_NAME]: ssz.fulu.DataColumnSidecar, - [POW_BLOCK_FILE_NAME]: ssz.bellatrix.PowBlock, [ATTESTATION_FILE_NAME]: sszTypesFor(fork).Attestation, [ATTESTER_SLASHING_FILE_NAME]: sszTypesFor(fork).AttesterSlashing, }, @@ -462,7 +443,6 @@ const forkChoiceTest = const blocks = new Map(); const blobs = new Map(); const columns = new Map(); - const powBlocks = new Map(); const attestations = new Map(); const attesterSlashings = new Map(); for (const key in t) { @@ -480,10 +460,6 @@ const forkChoiceTest = if (columnMatch) { columns.set(key, t[key]); } - const powBlockMatch = key.match(POW_BLOCK_FILE_NAME); - if (powBlockMatch) { - powBlocks.set(key, t[key]); - } const attMatch = key.match(ATTESTATION_FILE_NAME); if (attMatch) { attestations.set(key, t[key]); @@ -501,7 +477,6 @@ const forkChoiceTest = blocks, blobs, columns, - powBlocks, attestations, attesterSlashings, }; @@ -528,7 +503,7 @@ function toSpecTestCheckpoint(checkpoint: CheckpointWithHex): SpecTestCheckpoint }; } -type Step = OnTick | OnAttestation | OnAttesterSlashing | OnBlock | OnPowBlock | OnPayloadInfo | Checks; +type Step = OnTick | OnAttestation | OnAttesterSlashing | OnBlock | OnPayloadInfo | Checks; type SpecTestCheckpoint = {epoch: bigint; root: string}; @@ -569,15 +544,6 @@ type OnBlock = { valid?: number; }; -/** Optional step for optimistic sync tests. */ -type OnPowBlock = { - /** - * the name of the `pow_block_<32-byte-root>.ssz_snappy` file. To - * execute `on_pow_block(store, block)` - */ - pow_block: string; -}; - type OnPayloadInfo = { /** Encoded 32-byte value of payload's block hash. */ block_hash: string; @@ -620,7 +586,6 @@ type ForkChoiceTestCase = { blocks: Map; blobs: Map; columns: Map; - powBlocks: Map; attestations: Map; attesterSlashings: Map; }; @@ -641,10 +606,6 @@ function isBlock(step: Step): step is OnBlock { return typeof (step as OnBlock).block === "string"; } -function isPowBlock(step: Step): step is OnPowBlock { - return typeof (step as OnPowBlock).pow_block === "string"; -} - function isOnPayloadInfoStep(step: Step): step is OnPayloadInfo { return typeof (step as OnPayloadInfo).block_hash === "string"; } @@ -653,10 +614,6 @@ function isCheck(step: Step): step is Checks { return typeof (step as Checks).checks === "object"; } -// Note: PowMergeBlock tracking removed since merge validation is no longer needed -// Just use Eth1ForBlockProductionDisabled directly -const Eth1ForBlockProductionMock = Eth1ForBlockProductionDisabled; - specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), { fork_choice: {type: RunnerType.default, fn: forkChoiceTest({onlyPredefinedResponses: false})}, sync: {type: RunnerType.default, fn: forkChoiceTest({onlyPredefinedResponses: true})}, diff --git a/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts b/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts index c90d488f7cf3..f35e7be05188 100644 --- a/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts +++ b/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts @@ -48,7 +48,7 @@ describe("LodestarForkChoice", () => { justifiedBalances[0] = 1; justifiedBalances[1] = 2; justifiedBalances[2] = 3; - const executionStatus = ExecutionStatus.Valid; + const executionStatus = ExecutionStatus.PreMerge; const dataAvailabilityStatus = DataAvailabilityStatus.PreData; const blockDelaySec = 0; diff --git a/packages/beacon-node/test/utils/runEl.ts b/packages/beacon-node/test/utils/runEl.ts index 3d311e30f4d7..4f398a7b4994 100644 --- a/packages/beacon-node/test/utils/runEl.ts +++ b/packages/beacon-node/test/utils/runEl.ts @@ -9,12 +9,7 @@ import {shell} from "../sim/shell.js"; let txRpcId = 1; -// Note: PreMerge mode removed since all networks are post-merge -export enum ELStartMode { - PostMerge = "post-merge", -} - -export type ELSetupConfig = {mode: ELStartMode; elScriptDir: string; elBinaryDir: string; genesisTemplate?: string}; +export type ELSetupConfig = {elScriptDir: string; elBinaryDir: string; genesisTemplate?: string}; export type ELRunOptions = {ttd: bigint; dataPath: string; jwtSecretHex: string; enginePort: number; ethPort: number}; export type ELClient = { genesisBlockHash: string; @@ -26,7 +21,7 @@ export type ELClient = { }; /** - * A util function to start an EL in a "pre-merge" or "post-merge" mode using an `elScriptDir` setup + * A util function to start an EL using an `elScriptDir` setup * scripts folder in packages/beacon-node/test/scripts/el-interop. * * Returns an ELRunConfig after starting the EL, which can be used to initialize the genesis @@ -34,11 +29,11 @@ export type ELClient = { */ export async function runEL( - {mode, elScriptDir, elBinaryDir, genesisTemplate: template}: ELSetupConfig, + {elScriptDir, elBinaryDir, genesisTemplate: template}: ELSetupConfig, {ttd, dataPath, jwtSecretHex, enginePort, ethPort}: ELRunOptions, signal: AbortSignal ): Promise<{elClient: ELClient; tearDownCallBack: () => Promise}> { - const network = `${elScriptDir}/${mode}`; + const network = `${elScriptDir}`; const ethRpcUrl = `http://127.0.0.1:${ethPort}`; const engineRpcUrl = `http://127.0.0.1:${enginePort}`; const genesisTemplate = template ?? "genesisPre.tmpl"; diff --git a/packages/beacon-node/test/utils/state.ts b/packages/beacon-node/test/utils/state.ts index 80ed44539f5c..64ee9d9514f9 100644 --- a/packages/beacon-node/test/utils/state.ts +++ b/packages/beacon-node/test/utils/state.ts @@ -177,8 +177,6 @@ export const zeroProtoBlock: ProtoBlock = { timeliness: false, - executionPayloadBlockHash: ZERO_HASH_HEX, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; diff --git a/packages/beacon-node/test/utils/typeGenerator.ts b/packages/beacon-node/test/utils/typeGenerator.ts index f4e180d30125..7886df877050 100644 --- a/packages/beacon-node/test/utils/typeGenerator.ts +++ b/packages/beacon-node/test/utils/typeGenerator.ts @@ -41,9 +41,7 @@ export function generateProtoBlock(overrides: Partial = {}): ProtoBl timeliness: false, - executionPayloadBlockHash: ZERO_HASH_HEX, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, ...overrides, } as ProtoBlock; diff --git a/packages/beacon-node/test/utils/validationData/attestation.ts b/packages/beacon-node/test/utils/validationData/attestation.ts index f335636686c6..798303e62887 100644 --- a/packages/beacon-node/test/utils/validationData/attestation.ts +++ b/packages/beacon-node/test/utils/validationData/attestation.ts @@ -76,9 +76,7 @@ export function getAttestationValidData(opts: AttestationValidDataOpts): { timeliness: false, - executionPayloadBlockHash: ZERO_HASH_HEX, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; diff --git a/packages/cli/src/config/beaconParams.ts b/packages/cli/src/config/beaconParams.ts index 7abb0e9a5537..db01dfc57f3a 100644 --- a/packages/cli/src/config/beaconParams.ts +++ b/packages/cli/src/config/beaconParams.ts @@ -6,13 +6,7 @@ import { createChainForkConfig, } from "@lodestar/config"; import {NetworkName, getNetworkBeaconParams} from "../networks/index.js"; -import { - GlobalArgs, - ITerminalPowArgs, - defaultNetwork, - parseBeaconParamsArgs, - parseTerminalPowArgs, -} from "../options/index.js"; +import {GlobalArgs, defaultNetwork, parseBeaconParamsArgs} from "../options/index.js"; import {readFile} from "../util/index.js"; import {IBeaconParamsUnparsed} from "./types.js"; @@ -44,7 +38,6 @@ export function getBeaconParamsFromArgs(args: GlobalArgs): ChainConfig { paramsFile: args.paramsFile, additionalParamsCli: { ...parseBeaconParamsArgs(args as IBeaconParamsUnparsed), - ...parseTerminalPowArgs(args as ITerminalPowArgs), }, }); } diff --git a/packages/cli/src/options/paramsOptions.ts b/packages/cli/src/options/paramsOptions.ts index bd89ffa77d9e..787cbdaae1e7 100644 --- a/packages/cli/src/options/paramsOptions.ts +++ b/packages/cli/src/options/paramsOptions.ts @@ -1,4 +1,4 @@ -import {ChainConfig, chainConfigTypes} from "@lodestar/config"; +import {chainConfigTypes} from "@lodestar/config"; import {CliCommandOptions, CliOptionDefinition} from "@lodestar/utils"; import {IBeaconParamsUnparsed} from "../config/types.js"; import {ObjectKeys} from "../util/index.js"; @@ -7,12 +7,7 @@ import {ObjectKeys} from "../util/index.js"; // If an arbitrary key notation is used, it removes type safety on most of this CLI arg parsing code. // Params will be parsed from an args object assuming to contain the required keys -export type ITerminalPowArgs = { - "terminal-total-difficulty-override"?: string; - "terminal-block-hash-override"?: string; - "terminal-block-hash-epoch-override"?: string; -}; -export type IParamsArgs = Record & ITerminalPowArgs; +export type IParamsArgs = Record; const getArgKey = (key: keyof IBeaconParamsUnparsed): string => `params.${key}`; @@ -24,7 +19,7 @@ export function parseBeaconParamsArgs(args: Record): IB }, {}); } -const paramsOptionsByName = ObjectKeys(chainConfigTypes).reduce( +export const paramsOptions: CliCommandOptions = ObjectKeys(chainConfigTypes).reduce( (options: Record, key): Record => { options[getArgKey(key)] = { hidden: true, @@ -35,38 +30,3 @@ const paramsOptionsByName = ObjectKeys(chainConfigTypes).reduce( }, {} ); - -const terminalArgsToParamsMap: {[K in keyof ITerminalPowArgs]: keyof ChainConfig} = { - "terminal-total-difficulty-override": "TERMINAL_TOTAL_DIFFICULTY", - "terminal-block-hash-override": "TERMINAL_BLOCK_HASH", - "terminal-block-hash-epoch-override": "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH", -}; - -export function parseTerminalPowArgs(args: ITerminalPowArgs): IBeaconParamsUnparsed { - const parsedArgs = ObjectKeys(terminalArgsToParamsMap).reduce((beaconParams: Partial, key) => { - const paramOption = terminalArgsToParamsMap[key]; - const value = args[key]; - if (paramOption != null && value != null) beaconParams[paramOption] = value; - return beaconParams; - }, {}); - return parsedArgs; -} - -export const paramsOptions: CliCommandOptions = { - ...paramsOptionsByName, - - "terminal-total-difficulty-override": { - description: "Terminal PoW block TTD override", - type: "string", - }, - - "terminal-block-hash-override": { - description: "Terminal PoW block hash override", - type: "string", - }, - - "terminal-block-hash-epoch-override": { - description: "Terminal PoW block hash override activation epoch", - type: "string", - }, -}; diff --git a/packages/cli/test/sim/multiFork.test.ts b/packages/cli/test/sim/multiFork.test.ts index ce227db84860..6974fe135b71 100644 --- a/packages/cli/test/sim/multiFork.test.ts +++ b/packages/cli/test/sim/multiFork.test.ts @@ -2,7 +2,6 @@ import path from "node:path"; import {createAccountBalanceAssertion} from "../utils/crucible/assertions/accountBalanceAssertion.js"; import {createExecutionHeadAssertion} from "../utils/crucible/assertions/executionHeadAssertion.js"; import {createForkAssertion} from "../utils/crucible/assertions/forkAssertion.js"; -import {mergeAssertion} from "../utils/crucible/assertions/mergeAssertion.js"; import {nodeAssertion} from "../utils/crucible/assertions/nodeAssertion.js"; import {createWithdrawalAssertions} from "../utils/crucible/assertions/withdrawalsAssertion.js"; import {BeaconClient, ExecutionClient, Match, ValidatorClient} from "../utils/crucible/interfaces.js"; @@ -116,14 +115,6 @@ env.tracker.register({ }, }); -env.tracker.register({ - ...mergeAssertion, - match: ({slot}) => { - // Check at the end of bellatrix fork, merge should happen by then - return slot === env.clock.getLastSlotOfEpoch(bellatrixForkEpoch) ? Match.Assert | Match.Remove : Match.None; - }, -}); - env.tracker.register( createAccountBalanceAssertion({ address: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", diff --git a/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts b/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts deleted file mode 100644 index 00edd120fb9c..000000000000 --- a/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {BeaconStateAllForks, isExecutionStateType} from "@lodestar/state-transition"; -import {Assertion, AssertionResult} from "../interfaces.js"; -import {neverMatcher} from "./matchers.js"; - -export const mergeAssertion: Assertion<"merge", string> = { - id: "merge", - // Include into particular test with custom condition - match: neverMatcher, - async assert({node}) { - const errors: AssertionResult[] = []; - - const res = await node.beacon.api.debug.getStateV2({stateId: "head"}); - const state = res.value() as unknown as BeaconStateAllForks; - - // Post-merge, we just check that the state is an execution state type (Bellatrix+) - if (!isExecutionStateType(state)) { - errors.push("Node state is not an execution state type (pre-Bellatrix)"); - } - - return errors; - }, -}; diff --git a/packages/cli/test/utils/crucible/clients/execution/geth.ts b/packages/cli/test/utils/crucible/clients/execution/geth.ts index 6de53f9543ec..0b59273e3280 100644 --- a/packages/cli/test/utils/crucible/clients/execution/geth.ts +++ b/packages/cli/test/utils/crucible/clients/execution/geth.ts @@ -3,7 +3,7 @@ import path from "node:path"; import {Web3} from "web3"; import {fetch} from "@lodestar/utils"; import {EL_GENESIS_PASSWORD, EL_GENESIS_SECRET_KEY, SHARED_JWT_SECRET, SIM_ENV_NETWORK_ID} from "../../constants.js"; -import {ExecutionClient, ExecutionNodeGenerator, ExecutionStartMode, JobOptions, RunnerType} from "../../interfaces.js"; +import {ExecutionClient, ExecutionNodeGenerator, JobOptions, RunnerType} from "../../interfaces.js"; import {getNodeMountedPaths} from "../../utils/paths.js"; import {getNodePorts} from "../../utils/ports.js"; import {registerWeb3JsPlugins} from "../../web3js/plugins/index.js"; @@ -13,7 +13,7 @@ export const generateGethNode: ExecutionNodeGenerator = (o throw new Error("GETH_BINARY_DIR or GETH_DOCKER_IMAGE must be provided"); } - const {id, mode, ttd, address, mining, clientOptions, nodeIndex} = opts; + const {id, ttd, address, mining, clientOptions, nodeIndex} = opts; const ports = getNodePorts(nodeIndex); const isDocker = !!process.env.GETH_DOCKER_IMAGE; @@ -136,7 +136,6 @@ export const generateGethNode: ExecutionNodeGenerator = (o "--verbosity", "5", ...(mining ? ["--mine"] : []), - ...(mode === ExecutionStartMode.PreMerge ? ["--nodiscover"] : []), ...clientOptions, ], env: {}, diff --git a/packages/cli/test/utils/crucible/clients/execution/index.ts b/packages/cli/test/utils/crucible/clients/execution/index.ts index 14ba093ed66b..b40a0a1d29e9 100644 --- a/packages/cli/test/utils/crucible/clients/execution/index.ts +++ b/packages/cli/test/utils/crucible/clients/execution/index.ts @@ -6,7 +6,6 @@ import { ExecutionGeneratorOptions, ExecutionGenesisOptions, ExecutionNode, - ExecutionStartMode, } from "../../interfaces.js"; import {getGethGenesisBlock} from "../../utils/executionGenesis.js"; import {getEstimatedForkTime} from "../../utils/index.js"; @@ -57,16 +56,13 @@ export async function createExecutionNode( ...options, ...genesisOptions, id: elId, - mode: - options.mode ?? - (forkConfig.BELLATRIX_FORK_EPOCH > 0 ? ExecutionStartMode.PreMerge : ExecutionStartMode.PostMerge), address: runner.getNextIp(), mining: options.mining ?? false, }; await ensureDirectories(opts.paths); await writeFile(opts.paths.jwtsecretFilePath, SHARED_JWT_SECRET); - await writeFile(opts.paths.genesisFilePath, JSON.stringify(getGethGenesisBlock(opts.mode, genesisOptions))); + await writeFile(opts.paths.genesisFilePath, JSON.stringify(getGethGenesisBlock(genesisOptions))); switch (client) { case ExecutionClient.Mock: { diff --git a/packages/cli/test/utils/crucible/interfaces.ts b/packages/cli/test/utils/crucible/interfaces.ts index d44f8b5695aa..06d7bb439cb3 100644 --- a/packages/cli/test/utils/crucible/interfaces.ts +++ b/packages/cli/test/utils/crucible/interfaces.ts @@ -45,11 +45,6 @@ export enum ExecutionClient { Nethermind = "execution-nethermind", } -export enum ExecutionStartMode { - PreMerge = "pre-merge", - PostMerge = "post-merge", -} - export type BeaconClientsOptions = { [BeaconClient.Lodestar]: Partial; [BeaconClient.Lighthouse]: Record; @@ -137,7 +132,6 @@ export interface ExecutionGenesisOptions extends ExecutionGenesisOptions, GeneratorOptions { - mode: ExecutionStartMode; mining: boolean; paths: ExecutionPaths; clientOptions: ExecutionClientsOptions[E]; diff --git a/packages/cli/test/utils/crucible/utils/executionGenesis.ts b/packages/cli/test/utils/crucible/utils/executionGenesis.ts index 7250c147e2c1..82c8036c7160 100644 --- a/packages/cli/test/utils/crucible/utils/executionGenesis.ts +++ b/packages/cli/test/utils/crucible/utils/executionGenesis.ts @@ -1,10 +1,7 @@ import {SIM_ENV_CHAIN_ID, SIM_ENV_NETWORK_ID} from "../constants.js"; -import {Eth1GenesisBlock, ExecutionGenesisOptions, ExecutionStartMode} from "../interfaces.js"; +import {Eth1GenesisBlock, ExecutionGenesisOptions} from "../interfaces.js"; -export const getGethGenesisBlock = ( - mode: ExecutionStartMode, - options: ExecutionGenesisOptions -): Record => { +export const getGethGenesisBlock = (options: ExecutionGenesisOptions): Record => { const {ttd, cliqueSealingPeriod, shanghaiTime, genesisTime, cancunTime, pragueTime} = options; const genesis = { @@ -63,20 +60,12 @@ export const getGethGenesisBlock = ( baseFeePerGas: "0x0", }; - if (mode === ExecutionStartMode.PreMerge) { - return genesis; - } - - // TODO: Figure out PostMerge genesis later return genesis; }; -export const getNethermindChainSpec = ( - mode: ExecutionStartMode, - options: ExecutionGenesisOptions -): Record => { +export const getNethermindChainSpec = (options: ExecutionGenesisOptions): Record => { const {ttd, shanghaiTime} = options; - const genesis = getGethGenesisBlock(mode, options) as Eth1GenesisBlock; + const genesis = getGethGenesisBlock(options) as Eth1GenesisBlock; return { name: "simulation-dev", diff --git a/packages/fork-choice/src/protoArray/errors.ts b/packages/fork-choice/src/protoArray/errors.ts index 13a61417a070..650fd1c0b244 100644 --- a/packages/fork-choice/src/protoArray/errors.ts +++ b/packages/fork-choice/src/protoArray/errors.ts @@ -2,6 +2,7 @@ import {Epoch, RootHex} from "@lodestar/types"; import {LodestarError} from "@lodestar/utils"; export enum LVHExecErrorCode { + PreMergeToInvalid = "PreMergeToInvalid", ValidToInvalid = "ValidToInvalid", InvalidToValid = "InvalidToValid", } diff --git a/packages/fork-choice/src/protoArray/protoArray.ts b/packages/fork-choice/src/protoArray/protoArray.ts index a1f38a50f01f..50a994d46476 100644 --- a/packages/fork-choice/src/protoArray/protoArray.ts +++ b/packages/fork-choice/src/protoArray/protoArray.ts @@ -314,7 +314,7 @@ export class ProtoArray { // propagate till we keep encountering syncing status while (nodeIndex !== undefined) { const node = this.getNodeFromIndex(nodeIndex); - if (node.executionStatus === ExecutionStatus.Valid) { + if (node.executionStatus === ExecutionStatus.PreMerge || node.executionStatus === ExecutionStatus.Valid) { break; } this.validateNodeByIndex(nodeIndex); @@ -372,7 +372,10 @@ export class ProtoArray { let nodeIndex: number | undefined = ancestorFromIndex; while (nodeIndex !== undefined && nodeIndex >= 0) { const node = this.getNodeFromIndex(nodeIndex); - if (node.executionPayloadBlockHash === latestValidExecHash) { + if ( + (node.executionStatus === ExecutionStatus.PreMerge && latestValidExecHash === ZERO_HASH_HEX) || + node.executionPayloadBlockHash === latestValidExecHash + ) { break; } nodeIndex = node.parent; @@ -383,13 +386,19 @@ export class ProtoArray { private invalidateNodeByIndex(nodeIndex: number): ProtoNode { const invalidNode = this.getNodeFromIndex(nodeIndex); - // If node to be invalidated is valid, it is a catastrophe, + // If node to be invalidated is pre-merge or valid,it is a catastrophe, // and indicates consensus failure and a non recoverable damage. // // There is no further processing that can be done. // Just assign error for marking proto-array perma damaged and throw! - if (invalidNode.executionStatus === ExecutionStatus.Valid) { - const lvhCode = LVHExecErrorCode.ValidToInvalid; + if ( + invalidNode.executionStatus === ExecutionStatus.Valid || + invalidNode.executionStatus === ExecutionStatus.PreMerge + ) { + const lvhCode = + invalidNode.executionStatus === ExecutionStatus.Valid + ? LVHExecErrorCode.ValidToInvalid + : LVHExecErrorCode.PreMergeToInvalid; this.lvhError = { lvhCode, diff --git a/packages/fork-choice/test/perf/forkChoice/util.ts b/packages/fork-choice/test/perf/forkChoice/util.ts index 663cdaf21dfb..5b72dd88b9f5 100644 --- a/packages/fork-choice/test/perf/forkChoice/util.ts +++ b/packages/fork-choice/test/perf/forkChoice/util.ts @@ -27,14 +27,9 @@ export function initializeForkChoice(opts: Opts): ForkChoice { justifiedRoot: genesisRoot, finalizedEpoch: genesisEpoch, finalizedRoot: genesisRoot, - unrealizedJustifiedEpoch: genesisEpoch, - unrealizedJustifiedRoot: genesisRoot, - unrealizedFinalizedEpoch: genesisEpoch, - unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: genesisRoot, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, dataAvailabilityStatus: DataAvailabilityStatus.PreData, } as Omit, genesisSlot @@ -80,9 +75,8 @@ export function initializeForkChoice(opts: Opts): ForkChoice { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: blockRoot, - executionPayloadNumber: slot, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, diff --git a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts index fafe2646963a..91f1fc8614e9 100644 --- a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts @@ -38,9 +38,8 @@ describe("Forkchoice", () => { finalizedEpoch: genesisEpoch, finalizedRoot: genesisRoot, - executionPayloadBlockHash: genesisRoot, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, dataAvailabilityStatus: DataAvailabilityStatus.PreData, } as Omit, genesisSlot @@ -100,9 +99,8 @@ describe("Forkchoice", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: getBlockRoot(slot), - executionPayloadNumber: slot, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index 96c4dbd5c303..0eb01f3580c3 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -37,9 +37,8 @@ describe("Forkchoice / GetProposerHead", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: genesisRoot, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, @@ -61,9 +60,8 @@ describe("Forkchoice / GetProposerHead", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: getBlockRoot(headSlot), - executionPayloadNumber: headSlot, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, @@ -87,9 +85,8 @@ describe("Forkchoice / GetProposerHead", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: getBlockRoot(parentSlot), - executionPayloadNumber: parentSlot, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, weight: 212, // 240 - 29 + 1 diff --git a/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts b/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts index 6a7848c7db45..803c5fcbd5bb 100644 --- a/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/shouldOverrideForkChoiceUpdate.test.ts @@ -37,9 +37,8 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: genesisRoot, - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, dataAvailabilityStatus: DataAvailabilityStatus.PreData, @@ -61,9 +60,8 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: getBlockRoot(headSlot), - executionPayloadNumber: headSlot, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, @@ -87,9 +85,8 @@ describe("Forkchoice / shouldOverrideForkChoiceUpdate", () => { unrealizedFinalizedEpoch: genesisEpoch, unrealizedFinalizedRoot: genesisRoot, - executionPayloadBlockHash: getBlockRoot(parentSlot), - executionPayloadNumber: parentSlot, - executionStatus: ExecutionStatus.Valid, + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, timeliness: false, weight: 212, // 240 - 29 + 1 diff --git a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts new file mode 100644 index 000000000000..5e77b48a8a23 --- /dev/null +++ b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts @@ -0,0 +1,437 @@ +import {describe, expect, it} from "vitest"; +import {DataAvailabilityStatus} from "@lodestar/state-transition"; +import { + BlockExtraMeta, + ExecutionStatus, + MaybeValidExecutionStatus, + ProtoArray, + ProtoBlock, +} from "../../../src/index.js"; +import {LVHExecErrorCode} from "../../../src/protoArray/errors.js"; + +type ValidationTestCase = { + root: string; + bestChild?: string; + bestDescendant?: string; + executionStatus: ExecutionStatus | undefined; +}; + +type TestBlock = {slot: number; root: string; parent: string; executionStatus: MaybeValidExecutionStatus}; +type TestCase = [string, string | undefined, string | undefined, ExecutionStatus]; +const blocks: TestBlock[] = [ + {slot: 1, root: "1A", parent: "0", executionStatus: ExecutionStatus.Syncing}, + {slot: 2, root: "2A", parent: "1A", executionStatus: ExecutionStatus.Syncing}, + {slot: 3, root: "3A", parent: "2A", executionStatus: ExecutionStatus.Syncing}, + {slot: 2, root: "2B", parent: "1A", executionStatus: ExecutionStatus.Syncing}, + {slot: 2, root: "3B", parent: "2B", executionStatus: ExecutionStatus.Syncing}, + {slot: 2, root: "2C", parent: "none", executionStatus: ExecutionStatus.Syncing}, + {slot: 3, root: "3C", parent: "2C", executionStatus: ExecutionStatus.Syncing}, +]; +const fcRoots: string[] = ["0"]; +for (const block of blocks) { + fcRoots.push(block.root); +} + +function toFcTestCase(testCases: TestCase[]): ValidationTestCase[] { + return testCases.map((testCase) => ({ + root: testCase[0], + bestChild: testCase[1], + bestDescendant: testCase[2], + executionStatus: testCase[3], + })); +} + +const expectedPreValidationFC1: TestCase[] = [ + ["0", "1A", "3B", ExecutionStatus.PreMerge], + ["1A", "2B", "3B", ExecutionStatus.Syncing], + ["2A", "3A", "3A", ExecutionStatus.Syncing], + ["3A", undefined, undefined, ExecutionStatus.Syncing], + ["2B", "3B", "3B", ExecutionStatus.Syncing], + ["3B", undefined, undefined, ExecutionStatus.Syncing], + ["2C", "3C", "3C", ExecutionStatus.Syncing], + ["3C", undefined, undefined, ExecutionStatus.Syncing], +]; +const expectedPreValidationFC: ValidationTestCase[] = toFcTestCase(expectedPreValidationFC1); + +/** + * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) + * + * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Syncing) <- 3B (Syncing) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + +function setupForkChoice(): ProtoArray { + const fc = ProtoArray.initialize( + { + slot: 0, + stateRoot: "-", + parentRoot: "-", + blockRoot: "0", + + justifiedEpoch: 0, + justifiedRoot: "-", + finalizedEpoch: 0, + finalizedRoot: "-", + + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + } as Omit, + 0 + ); + + for (const block of blocks) { + const executionData = ( + block.executionStatus === ExecutionStatus.PreMerge + ? { + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, + } + : { + executionPayloadBlockHash: block.root, + executionPayloadNumber: block.slot, + executionStatus: block.executionStatus, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, + } + ) as BlockExtraMeta; + fc.onBlock( + { + slot: block.slot, + blockRoot: block.root, + parentRoot: block.parent, + stateRoot: "-", + targetRoot: "-", + + justifiedEpoch: 0, + justifiedRoot: "-", + finalizedEpoch: 0, + finalizedRoot: "-", + + unrealizedJustifiedEpoch: 0, + unrealizedJustifiedRoot: "-", + unrealizedFinalizedEpoch: 0, + unrealizedFinalizedRoot: "-", + + timeliness: false, + + ...executionData, + }, + block.slot + ); + } + + const deltas = Array.from({length: fc.nodes.length}, () => 0); + fc.applyScoreChanges({ + deltas, + proposerBoost: null, + justifiedEpoch: 0, + justifiedRoot: "-", + finalizedEpoch: 0, + finalizedRoot: "-", + currentSlot: 3, + }); + return fc; +} + +describe("executionStatus / normal updates", () => { + const fc = setupForkChoice(); + + /** + * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Syncing) <- 3B (Syncing) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + const preValidation = collectProtoarrayValidationStatus(fc); + it("preValidation forkchoice setup should be correct", () => { + expect(preValidation).toEqual(expectedPreValidationFC); + }); + + /** + * Invalidate 3C with LVH on 2C which stays in Syncing + * + * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Syncing) <- 3B (Syncing) + * ~~ 2C (Syncing) <- 3C (Invalid) + */ + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Invalid, + latestValidExecHash: "2C", + invalidateFromParentBlockRoot: "3C", + }, + 3 + ); + + const invalidate3CValidate2CForkChoice = collectProtoarrayValidationStatus(fc); + it("correcly invalidate 3C and validate 2C only", () => { + expect(invalidate3CValidate2CForkChoice).toEqual( + toFcTestCase([ + ["0", "1A", "3B", ExecutionStatus.PreMerge], + ["1A", "2B", "3B", ExecutionStatus.Syncing], + ["2A", "3A", "3A", ExecutionStatus.Syncing], + ["3A", undefined, undefined, ExecutionStatus.Syncing], + ["2B", "3B", "3B", ExecutionStatus.Syncing], + ["3B", undefined, undefined, ExecutionStatus.Syncing], + ["2C", undefined, undefined, ExecutionStatus.Syncing], + ["3C", undefined, undefined, ExecutionStatus.Invalid], + ]) + ); + }); + + /** + * Validate 3B, 2B, 1A (premerge) + * + * 0 (PreMerge) <- 1A (Valid) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Valid) <- 3B (Valid) + * ~~ 2C (Syncing) <- 3C (Invalid) + */ + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Valid, + latestValidExecHash: "3B", + }, + 3 + ); + const validate3B2B1A = collectProtoarrayValidationStatus(fc); + it("Validate 3B, 2B, 1A", () => { + expect(validate3B2B1A).toEqual( + toFcTestCase([ + ["0", "1A", "3B", ExecutionStatus.PreMerge], + ["1A", "2B", "3B", ExecutionStatus.Valid], + ["2A", "3A", "3A", ExecutionStatus.Syncing], + ["3A", undefined, undefined, ExecutionStatus.Syncing], + ["2B", "3B", "3B", ExecutionStatus.Valid], + ["3B", undefined, undefined, ExecutionStatus.Valid], + ["2C", undefined, undefined, ExecutionStatus.Syncing], + ["3C", undefined, undefined, ExecutionStatus.Invalid], + ]) + ); + }); + + /** + * Invalidate 3A, 2A with 2A loosing its bestChild, bestDescendant + * + * 0 (PreMerge) <- 1A (Valid) <- 2A (Invalid) <- 3A (Invalid) + * ^- 2B (Valid) <- 3B (Valid) + * ~~ 2C (Syncing) <- 3C (Invalid) + */ + + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Invalid, + latestValidExecHash: "1A", + invalidateFromParentBlockRoot: "3A", + }, + 3 + ); + const invalidate3A2A = collectProtoarrayValidationStatus(fc); + it("Invalidate 3A, 2A with 2A loosing its bestChild, bestDescendant", () => { + expect(invalidate3A2A).toEqual( + toFcTestCase([ + ["0", "1A", "3B", ExecutionStatus.PreMerge], + ["1A", "2B", "3B", ExecutionStatus.Valid], + ["2A", undefined, undefined, ExecutionStatus.Invalid], + ["3A", undefined, undefined, ExecutionStatus.Invalid], + ["2B", "3B", "3B", ExecutionStatus.Valid], + ["3B", undefined, undefined, ExecutionStatus.Valid], + ["2C", undefined, undefined, ExecutionStatus.Syncing], + ["3C", undefined, undefined, ExecutionStatus.Invalid], + ]) + ); + }); +}); + +describe("executionStatus / invalidate all postmerge chain", () => { + const fc = setupForkChoice(); + + /** + * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) + * + * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Syncing) <- 3B (Syncing) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + const preValidation = collectProtoarrayValidationStatus(fc); + it("preValidation forkchoice setup should be correct", () => { + expect(preValidation).toEqual(expectedPreValidationFC); + }); + + /** + * All post merge blocks should be invalidated except Cs + * + * 0 (PreMerge) <- 1A (Invalid) <- 2A (Invalid) <- 3A (Invalid) + * ^- 2B (Invalid) <- 3B (Invalid) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Invalid, + latestValidExecHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + invalidateFromParentBlockRoot: "3B", + }, + 3 + ); + const postMergeInvalidated = collectProtoarrayValidationStatus(fc); + it("all post merge blocks should be invalidated except Cs", () => { + expect(postMergeInvalidated).toEqual( + toFcTestCase([ + ["0", undefined, undefined, ExecutionStatus.PreMerge], + ["1A", undefined, undefined, ExecutionStatus.Invalid], + ["2A", undefined, undefined, ExecutionStatus.Invalid], + ["3A", undefined, undefined, ExecutionStatus.Invalid], + ["2B", undefined, undefined, ExecutionStatus.Invalid], + ["3B", undefined, undefined, ExecutionStatus.Invalid], + ["2C", "3C", "3C", ExecutionStatus.Syncing], + ["3C", undefined, undefined, ExecutionStatus.Syncing], + ]) + ); + }); + + const fcHead = fc.findHead("0", 3); + it("pre merge block should be the FC head", () => { + expect(fcHead).toBe("0"); + }); +}); + +describe("executionStatus / poision forkchoice if we invalidate previous valid", () => { + const fc = setupForkChoice(); + + /** + * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) + * + * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Syncing) <- 3B (Syncing) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + const preValidation = collectProtoarrayValidationStatus(fc); + it("preValidation forkchoice setup should be correct", () => { + expect(preValidation).toEqual(expectedPreValidationFC); + }); + + /** + * Validate 3B, 2B, 1A (premerge) + * + * 0 (PreMerge) <- 1A (Valid) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Valid) <- 3B (Valid) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Valid, + latestValidExecHash: "3B", + }, + 3 + ); + const validate3B2B1A = collectProtoarrayValidationStatus(fc); + it("Validate 3B, 2B, 1A", () => { + expect(validate3B2B1A).toEqual( + toFcTestCase([ + ["0", "1A", "3B", ExecutionStatus.PreMerge], + ["1A", "2B", "3B", ExecutionStatus.Valid], + ["2A", "3A", "3A", ExecutionStatus.Syncing], + ["3A", undefined, undefined, ExecutionStatus.Syncing], + ["2B", "3B", "3B", ExecutionStatus.Valid], + ["3B", undefined, undefined, ExecutionStatus.Valid], + ["2C", "3C", "3C", ExecutionStatus.Syncing], + ["3C", undefined, undefined, ExecutionStatus.Syncing], + ]) + ); + }); + + it("protoarray should be poisioned with a buggy LVH response", () => { + expect(() => + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Invalid, + latestValidExecHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + invalidateFromParentBlockRoot: "3A", + }, + 3 + ) + ).toThrow(Error); + + expect(fc.lvhError).toEqual({lvhCode: LVHExecErrorCode.ValidToInvalid, blockRoot: "1A", execHash: "1A"}); + expect(() => fc.findHead("0", 3)).toThrow(Error); + }); +}); + +describe("executionStatus / poision forkchoice if we validate previous invalid", () => { + const fc = setupForkChoice(); + + /** + * Set up the following forkchoice (~~ parent not in forkchoice, possibly bogus/NA) + * + * 0 (PreMerge) <- 1A (Syncing) <- 2A (Syncing) <- 3A (Syncing) + * ^- 2B (Syncing) <- 3B (Syncing) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + const preValidation = collectProtoarrayValidationStatus(fc); + it("preValidation forkchoice setup should be correct", () => { + expect(preValidation).toEqual(expectedPreValidationFC); + }); + + /** + * Invalidate 3B, 2B, 1A + * + * 0 (PreMerge) <- 1A (Invalid) <- 2A (Invalid) <- 3A (Invalid) + * ^- 2B (Invalid) <- 3B (Invalid) + * ~~ 2C (Syncing) <- 3C (Syncing) + */ + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Invalid, + latestValidExecHash: "0x0000000000000000000000000000000000000000000000000000000000000000", + invalidateFromParentBlockRoot: "3B", + }, + 3 + ); + const validate3B2B1A = collectProtoarrayValidationStatus(fc); + it("Inalidate 3B, 2B, 1A", () => { + expect(validate3B2B1A).toEqual( + toFcTestCase([ + ["0", undefined, undefined, ExecutionStatus.PreMerge], + ["1A", undefined, undefined, ExecutionStatus.Invalid], + ["2A", undefined, undefined, ExecutionStatus.Invalid], + ["3A", undefined, undefined, ExecutionStatus.Invalid], + ["2B", undefined, undefined, ExecutionStatus.Invalid], + ["3B", undefined, undefined, ExecutionStatus.Invalid], + ["2C", "3C", "3C", ExecutionStatus.Syncing], + ["3C", undefined, undefined, ExecutionStatus.Syncing], + ]) + ); + }); + + it("protoarray should be poisioned with a buggy LVH response", () => { + expect(() => + fc.validateLatestHash( + { + executionStatus: ExecutionStatus.Valid, + latestValidExecHash: "2A", + }, + 3 + ) + ).toThrow(Error); + + expect(fc.lvhError).toEqual({lvhCode: LVHExecErrorCode.InvalidToValid, blockRoot: "2A", execHash: "2A"}); + expect(() => fc.findHead("0", 3)).toThrow(Error); + }); +}); + +function collectProtoarrayValidationStatus(fcArray: ProtoArray): ValidationTestCase[] { + const expectedForkChoice: ValidationTestCase[] = []; + + for (const fcRoot of fcRoots) { + const fcNode = fcArray.getNode(fcRoot); + const bestChild = + fcNode?.bestChild !== undefined ? fcArray["getNodeFromIndex"](fcNode.bestChild).blockRoot : undefined; + const bestDescendant = + fcNode?.bestDescendant !== undefined ? fcArray["getNodeFromIndex"](fcNode.bestDescendant).blockRoot : undefined; + expectedForkChoice.push({ + root: fcRoot, + bestChild, + bestDescendant, + executionStatus: fcNode?.executionStatus, + }); + } + return expectedForkChoice; +} diff --git a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts index 6800b206ba24..b6a3f50af7eb 100644 --- a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts +++ b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts @@ -43,9 +43,7 @@ describe("getCommonAncestor", () => { timeliness: false, - executionPayloadBlockHash: "0", - executionPayloadNumber: 0, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, 0 @@ -71,9 +69,7 @@ describe("getCommonAncestor", () => { timeliness: false, - executionPayloadBlockHash: block.root, - executionPayloadNumber: block.slot, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, block.slot diff --git a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts index 04978f7ad56c..6be99553ba3f 100644 --- a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts +++ b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts @@ -32,9 +32,7 @@ describe("ProtoArray", () => { timeliness: false, - executionPayloadBlockHash: finalizedRoot, - executionPayloadNumber: genesisSlot, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot @@ -60,9 +58,7 @@ describe("ProtoArray", () => { timeliness: false, - executionPayloadBlockHash: finalizedDesc, - executionPayloadNumber: genesisSlot + 1, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot + 1 @@ -88,9 +84,7 @@ describe("ProtoArray", () => { timeliness: false, - executionPayloadBlockHash: notFinalizedDesc, - executionPayloadNumber: genesisSlot + 1, - executionStatus: ExecutionStatus.Valid, + ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot + 1 diff --git a/packages/types/src/bellatrix/sszTypes.ts b/packages/types/src/bellatrix/sszTypes.ts index 08560552d112..2eda1c1b67d9 100644 --- a/packages/types/src/bellatrix/sszTypes.ts +++ b/packages/types/src/bellatrix/sszTypes.ts @@ -98,15 +98,6 @@ export const SignedBeaconBlock = new ContainerType( {typeName: "SignedBeaconBlock", jsonCase: "eth2"} ); -export const PowBlock = new ContainerType( - { - blockHash: Root, - parentHash: Root, - totalDifficulty: Uint256, - }, - {typeName: "PowBlock", jsonCase: "eth2"} -); - // we don't reuse phase0.BeaconState fields since we need to replace some keys // and we cannot keep order doing that export const BeaconState = new ContainerType( diff --git a/packages/types/src/bellatrix/types.ts b/packages/types/src/bellatrix/types.ts index dcd45d7c97ea..1cbfad5e8152 100644 --- a/packages/types/src/bellatrix/types.ts +++ b/packages/types/src/bellatrix/types.ts @@ -9,7 +9,6 @@ export type BeaconBlockBody = ValueOf; export type BeaconBlock = ValueOf; export type SignedBeaconBlock = ValueOf; export type BeaconState = ValueOf; -export type PowBlock = ValueOf; export type BlindedBeaconBlockBody = ValueOf; export type BlindedBeaconBlock = ValueOf; From 9d90baecd2f7b157184cb481fa2553363d681e34 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 14:44:40 +0100 Subject: [PATCH 10/21] Fix check types --- .../cli/test/utils/crucible/clients/execution/nethermind.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/cli/test/utils/crucible/clients/execution/nethermind.ts b/packages/cli/test/utils/crucible/clients/execution/nethermind.ts index 92615aeb4cc5..7a85584c154f 100644 --- a/packages/cli/test/utils/crucible/clients/execution/nethermind.ts +++ b/packages/cli/test/utils/crucible/clients/execution/nethermind.ts @@ -16,7 +16,6 @@ export const generateNethermindNode: ExecutionNodeGenerator Date: Wed, 10 Dec 2025 15:36:32 +0100 Subject: [PATCH 11/21] Remove ttd usage --- .../beacon/genericServerTest/config.test.ts | 1 - .../test/sim/electra-interop.test.ts | 3 +- packages/beacon-node/test/utils/runEl.ts | 1 - .../utils/crucible/clients/execution/index.ts | 1 - .../cli/test/utils/crucible/interfaces.ts | 1 - .../cli/test/utils/crucible/utils/index.ts | 34 +------------------ packages/config/src/chainConfig/types.ts | 3 ++ packages/validator/src/util/params.ts | 4 +-- 8 files changed, 7 insertions(+), 41 deletions(-) diff --git a/packages/api/test/unit/beacon/genericServerTest/config.test.ts b/packages/api/test/unit/beacon/genericServerTest/config.test.ts index 534bc69f519e..eff05ac9263a 100644 --- a/packages/api/test/unit/beacon/genericServerTest/config.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/config.test.ts @@ -16,7 +16,6 @@ describe("beacon / config", () => { PRESET_BASE: "mainnet", DEPOSIT_CONTRACT_ADDRESS: "0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b", GENESIS_FORK_VERSION: "0x00001020", - TERMINAL_TOTAL_DIFFICULTY: "115792089237316195423570985008687907853269984665640564039457584007913129639936", MIN_GENESIS_TIME: "1606824000", }; diff --git a/packages/beacon-node/test/sim/electra-interop.test.ts b/packages/beacon-node/test/sim/electra-interop.test.ts index 59c0eb13face..6166df8cd213 100644 --- a/packages/beacon-node/test/sim/electra-interop.test.ts +++ b/packages/beacon-node/test/sim/electra-interop.test.ts @@ -259,7 +259,7 @@ describe("executionEngine / ExecutionEngineHttp", () => { electraEpoch: Epoch; testName: string; }): Promise { - const {genesisBlockHash, ttd, engineRpcUrl, ethRpcUrl} = elClient; + const {genesisBlockHash, engineRpcUrl, ethRpcUrl} = elClient; const validatorClientCount = 1; const validatorsPerClient = 32; @@ -306,7 +306,6 @@ describe("executionEngine / ExecutionEngineHttp", () => { CAPELLA_FORK_EPOCH: 0, DENEB_FORK_EPOCH: 0, ELECTRA_FORK_EPOCH: electraEpoch, - TERMINAL_TOTAL_DIFFICULTY: ttd, }, options: { api: {rest: {enabled: true} as BeaconRestApiServerOpts}, diff --git a/packages/beacon-node/test/utils/runEl.ts b/packages/beacon-node/test/utils/runEl.ts index 4f398a7b4994..1614a899b0d7 100644 --- a/packages/beacon-node/test/utils/runEl.ts +++ b/packages/beacon-node/test/utils/runEl.ts @@ -13,7 +13,6 @@ export type ELSetupConfig = {elScriptDir: string; elBinaryDir: string; genesisTe export type ELRunOptions = {ttd: bigint; dataPath: string; jwtSecretHex: string; enginePort: number; ethPort: number}; export type ELClient = { genesisBlockHash: string; - ttd: bigint; engineRpcUrl: string; ethRpcUrl: string; network: string; diff --git a/packages/cli/test/utils/crucible/clients/execution/index.ts b/packages/cli/test/utils/crucible/clients/execution/index.ts index b40a0a1d29e9..d9dcb0e1cdd9 100644 --- a/packages/cli/test/utils/crucible/clients/execution/index.ts +++ b/packages/cli/test/utils/crucible/clients/execution/index.ts @@ -23,7 +23,6 @@ export async function createExecutionNode( const genesisOptions: ExecutionGenesisOptions = { ...options, - ttd: options.ttd ?? forkConfig.TERMINAL_TOTAL_DIFFICULTY, cliqueSealingPeriod: options.cliqueSealingPeriod ?? CLIQUE_SEALING_PERIOD, shanghaiTime: options.shanghaiTime ?? diff --git a/packages/cli/test/utils/crucible/interfaces.ts b/packages/cli/test/utils/crucible/interfaces.ts index 06d7bb439cb3..887656f7aa12 100644 --- a/packages/cli/test/utils/crucible/interfaces.ts +++ b/packages/cli/test/utils/crucible/interfaces.ts @@ -120,7 +120,6 @@ export interface ValidatorGeneratorOptions { - ttd: bigint; cliqueSealingPeriod: number; shanghaiTime: number; cancunTime: number; diff --git a/packages/cli/test/utils/crucible/utils/index.ts b/packages/cli/test/utils/crucible/utils/index.ts index 1c8c766632c5..62792176c7e4 100644 --- a/packages/cli/test/utils/crucible/utils/index.ts +++ b/packages/cli/test/utils/crucible/utils/index.ts @@ -1,13 +1,7 @@ import {ChainConfig, ChainForkConfig, createChainForkConfig} from "@lodestar/config"; import {activePreset} from "@lodestar/params"; import {Epoch} from "@lodestar/types"; -import { - CLIQUE_SEALING_PERIOD, - ETH_TTD_INCREMENT, - SIM_ENV_CHAIN_ID, - SIM_ENV_NETWORK_ID, - SIM_TESTS_SLOT_DURATION_MS, -} from "../constants.js"; +import {CLIQUE_SEALING_PERIOD, SIM_ENV_CHAIN_ID, SIM_ENV_NETWORK_ID, SIM_TESTS_SLOT_DURATION_MS} from "../constants.js"; import {Simulation} from "../simulation.js"; export const logFilesDir = "test-logs"; @@ -65,20 +59,11 @@ export function defineSimTestConfig( graceExtraTimeFraction: 0.3, }) * 1000; - const ttd = getEstimatedTTD({ - bellatrixForkEpoch: opts.BELLATRIX_FORK_EPOCH ?? Infinity, - secondsPerSlot: opts.SLOT_DURATION_MS ? opts.SLOT_DURATION_MS / 1000 : SIM_TESTS_SLOT_DURATION_MS / 1000, - cliqueSealingPeriod: opts.cliqueSealingPeriod ?? CLIQUE_SEALING_PERIOD, - // To make sure bellatrix already started - additionalSlots: opts.additionalSlotsForTTD ?? 2, - }); - const forkConfig = createChainForkConfig({ ...opts, GENESIS_DELAY: genesisDelaySeconds, SECONDS_PER_SLOT: (opts.SLOT_DURATION_MS ?? SIM_TESTS_SLOT_DURATION_MS) / 1000, SLOT_DURATION_MS: opts.SLOT_DURATION_MS ?? SIM_TESTS_SLOT_DURATION_MS, - TERMINAL_TOTAL_DIFFICULTY: ttd, DEPOSIT_CHAIN_ID: SIM_ENV_CHAIN_ID, DEPOSIT_NETWORK_ID: SIM_ENV_NETWORK_ID, SECONDS_PER_ETH1_BLOCK: opts.cliqueSealingPeriod ?? CLIQUE_SEALING_PERIOD, @@ -107,23 +92,6 @@ export const getEstimatedTimeInSecForRun = ({ return Math.round(durationSec + durationSec * graceExtraTimeFraction); }; -export const getEstimatedTTD = ({ - cliqueSealingPeriod, - secondsPerSlot, - additionalSlots, - bellatrixForkEpoch, -}: { - cliqueSealingPeriod: number; - additionalSlots: number; - secondsPerSlot: number; - bellatrixForkEpoch: number; -}): bigint => { - const secondsTillBellatrix = - bellatrixForkEpoch * activePreset.SLOTS_PER_EPOCH * secondsPerSlot + additionalSlots * secondsPerSlot; - - return BigInt(Math.ceil(secondsTillBellatrix / cliqueSealingPeriod) * ETH_TTD_INCREMENT); -}; - export const getEstimatedForkTime = ({ genesisTime, secondsPerSlot, diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 5cf0d79549e8..afb8452a4224 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -15,8 +15,11 @@ export type ChainConfig = { CONFIG_NAME: string; // Transition + /** @deprecated No longer used post-merge */ TERMINAL_TOTAL_DIFFICULTY: bigint; + /** @deprecated No longer used post-merge */ TERMINAL_BLOCK_HASH: Uint8Array; + /** @deprecated No longer used post-merge */ TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: number; // Genesis diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index bd6cd2a5358c..93ea589fa2c5 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -110,8 +110,8 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Wed, 10 Dec 2025 15:38:13 +0100 Subject: [PATCH 12/21] Keep PowBlock ssz type --- packages/types/src/bellatrix/sszTypes.ts | 9 +++++++++ packages/types/src/bellatrix/types.ts | 2 ++ 2 files changed, 11 insertions(+) diff --git a/packages/types/src/bellatrix/sszTypes.ts b/packages/types/src/bellatrix/sszTypes.ts index 2eda1c1b67d9..08560552d112 100644 --- a/packages/types/src/bellatrix/sszTypes.ts +++ b/packages/types/src/bellatrix/sszTypes.ts @@ -98,6 +98,15 @@ export const SignedBeaconBlock = new ContainerType( {typeName: "SignedBeaconBlock", jsonCase: "eth2"} ); +export const PowBlock = new ContainerType( + { + blockHash: Root, + parentHash: Root, + totalDifficulty: Uint256, + }, + {typeName: "PowBlock", jsonCase: "eth2"} +); + // we don't reuse phase0.BeaconState fields since we need to replace some keys // and we cannot keep order doing that export const BeaconState = new ContainerType( diff --git a/packages/types/src/bellatrix/types.ts b/packages/types/src/bellatrix/types.ts index 1cbfad5e8152..02ee66ed1003 100644 --- a/packages/types/src/bellatrix/types.ts +++ b/packages/types/src/bellatrix/types.ts @@ -9,6 +9,8 @@ export type BeaconBlockBody = ValueOf; export type BeaconBlock = ValueOf; export type SignedBeaconBlock = ValueOf; export type BeaconState = ValueOf; +/** @deprecated */ +export type PowBlock = ValueOf; export type BlindedBeaconBlockBody = ValueOf; export type BlindedBeaconBlock = ValueOf; From c424099ac76ef5afc404582592a49db4175d57c3 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 16:12:26 +0100 Subject: [PATCH 13/21] Wording --- packages/config/src/chainConfig/types.ts | 6 +++--- packages/params/src/index.ts | 2 +- packages/validator/src/util/params.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index afb8452a4224..41e33cf84a7b 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -15,11 +15,11 @@ export type ChainConfig = { CONFIG_NAME: string; // Transition - /** @deprecated No longer used post-merge */ + /** @deprecated All networks have completed the merge transition */ TERMINAL_TOTAL_DIFFICULTY: bigint; - /** @deprecated No longer used post-merge */ + /** @deprecated All networks have completed the merge transition */ TERMINAL_BLOCK_HASH: Uint8Array; - /** @deprecated No longer used post-merge */ + /** @deprecated All networks have completed the merge transition */ TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: number; // Genesis diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 9bb619a1fbec..abecfb024368 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -271,7 +271,7 @@ export const MAX_REQUEST_LIGHT_CLIENT_COMMITTEE_HASHES = 128; /** * Optimistic sync - * @deprecated No longer used post-merge. All blocks are safe to import optimistically. + * @deprecated All networks have completed the merge transition, blocks are always safe to import optimistically. */ export const SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY = 128; /** @deprecated */ diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 93ea589fa2c5..0f3efaefcc68 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -111,7 +111,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Wed, 10 Dec 2025 16:39:34 +0100 Subject: [PATCH 14/21] Keep ttd in execution genesis options --- packages/cli/test/utils/crucible/clients/execution/index.ts | 1 + packages/cli/test/utils/crucible/interfaces.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/cli/test/utils/crucible/clients/execution/index.ts b/packages/cli/test/utils/crucible/clients/execution/index.ts index d9dcb0e1cdd9..b40a0a1d29e9 100644 --- a/packages/cli/test/utils/crucible/clients/execution/index.ts +++ b/packages/cli/test/utils/crucible/clients/execution/index.ts @@ -23,6 +23,7 @@ export async function createExecutionNode( const genesisOptions: ExecutionGenesisOptions = { ...options, + ttd: options.ttd ?? forkConfig.TERMINAL_TOTAL_DIFFICULTY, cliqueSealingPeriod: options.cliqueSealingPeriod ?? CLIQUE_SEALING_PERIOD, shanghaiTime: options.shanghaiTime ?? diff --git a/packages/cli/test/utils/crucible/interfaces.ts b/packages/cli/test/utils/crucible/interfaces.ts index 887656f7aa12..06d7bb439cb3 100644 --- a/packages/cli/test/utils/crucible/interfaces.ts +++ b/packages/cli/test/utils/crucible/interfaces.ts @@ -120,6 +120,7 @@ export interface ValidatorGeneratorOptions { + ttd: bigint; cliqueSealingPeriod: number; shanghaiTime: number; cancunTime: number; From 0ccdfd2a69e18e55eee3a899d71b1c119230b384 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 16:40:19 +0100 Subject: [PATCH 15/21] Skip merge transition spec tests --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index b83d3726e1c5..f4944f6941a9 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -64,6 +64,9 @@ export const defaultSkipOpts: SkipOpts = { // BeaconBlockBody proof in lightclient is the new addition in v1.3.0-rc.2-hotfix // Skip them for now to enable subsequently skippedTestSuites: [ + // Merge transition tests are skipped because we no longer support performing the merge transition. + // All networks have already completed the merge, so this code path is no longer needed. + /^bellatrix\/fork_choice\/on_merge_block\/.*/, /^capella\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^deneb\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^electra\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, From dc56e5efc850e1074d4c505c89e616f27aef4bbd Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 17:18:20 +0100 Subject: [PATCH 16/21] Restore some ttd related code in sim tests --- .../cli/test/utils/crucible/utils/index.ts | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/cli/test/utils/crucible/utils/index.ts b/packages/cli/test/utils/crucible/utils/index.ts index 62792176c7e4..1c8c766632c5 100644 --- a/packages/cli/test/utils/crucible/utils/index.ts +++ b/packages/cli/test/utils/crucible/utils/index.ts @@ -1,7 +1,13 @@ import {ChainConfig, ChainForkConfig, createChainForkConfig} from "@lodestar/config"; import {activePreset} from "@lodestar/params"; import {Epoch} from "@lodestar/types"; -import {CLIQUE_SEALING_PERIOD, SIM_ENV_CHAIN_ID, SIM_ENV_NETWORK_ID, SIM_TESTS_SLOT_DURATION_MS} from "../constants.js"; +import { + CLIQUE_SEALING_PERIOD, + ETH_TTD_INCREMENT, + SIM_ENV_CHAIN_ID, + SIM_ENV_NETWORK_ID, + SIM_TESTS_SLOT_DURATION_MS, +} from "../constants.js"; import {Simulation} from "../simulation.js"; export const logFilesDir = "test-logs"; @@ -59,11 +65,20 @@ export function defineSimTestConfig( graceExtraTimeFraction: 0.3, }) * 1000; + const ttd = getEstimatedTTD({ + bellatrixForkEpoch: opts.BELLATRIX_FORK_EPOCH ?? Infinity, + secondsPerSlot: opts.SLOT_DURATION_MS ? opts.SLOT_DURATION_MS / 1000 : SIM_TESTS_SLOT_DURATION_MS / 1000, + cliqueSealingPeriod: opts.cliqueSealingPeriod ?? CLIQUE_SEALING_PERIOD, + // To make sure bellatrix already started + additionalSlots: opts.additionalSlotsForTTD ?? 2, + }); + const forkConfig = createChainForkConfig({ ...opts, GENESIS_DELAY: genesisDelaySeconds, SECONDS_PER_SLOT: (opts.SLOT_DURATION_MS ?? SIM_TESTS_SLOT_DURATION_MS) / 1000, SLOT_DURATION_MS: opts.SLOT_DURATION_MS ?? SIM_TESTS_SLOT_DURATION_MS, + TERMINAL_TOTAL_DIFFICULTY: ttd, DEPOSIT_CHAIN_ID: SIM_ENV_CHAIN_ID, DEPOSIT_NETWORK_ID: SIM_ENV_NETWORK_ID, SECONDS_PER_ETH1_BLOCK: opts.cliqueSealingPeriod ?? CLIQUE_SEALING_PERIOD, @@ -92,6 +107,23 @@ export const getEstimatedTimeInSecForRun = ({ return Math.round(durationSec + durationSec * graceExtraTimeFraction); }; +export const getEstimatedTTD = ({ + cliqueSealingPeriod, + secondsPerSlot, + additionalSlots, + bellatrixForkEpoch, +}: { + cliqueSealingPeriod: number; + additionalSlots: number; + secondsPerSlot: number; + bellatrixForkEpoch: number; +}): bigint => { + const secondsTillBellatrix = + bellatrixForkEpoch * activePreset.SLOTS_PER_EPOCH * secondsPerSlot + additionalSlots * secondsPerSlot; + + return BigInt(Math.ceil(secondsTillBellatrix / cliqueSealingPeriod) * ETH_TTD_INCREMENT); +}; + export const getEstimatedForkTime = ({ genesisTime, secondsPerSlot, From 650ac9fa09805c6975c3c6a1e94beafea19f574f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 17:18:39 +0100 Subject: [PATCH 17/21] Move comment --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index f4944f6941a9..3286cc146f0b 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -60,13 +60,13 @@ const coveredTestRunners = [ // ``` export const defaultSkipOpts: SkipOpts = { skippedForks: ["eip7805"], - // TODO: capella - // BeaconBlockBody proof in lightclient is the new addition in v1.3.0-rc.2-hotfix - // Skip them for now to enable subsequently skippedTestSuites: [ // Merge transition tests are skipped because we no longer support performing the merge transition. // All networks have already completed the merge, so this code path is no longer needed. /^bellatrix\/fork_choice\/on_merge_block\/.*/, + // TODO: capella + // BeaconBlockBody proof in lightclient is the new addition in v1.3.0-rc.2-hotfix + // Skip them for now to enable subsequently /^capella\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^deneb\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^electra\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, From 17c7b7952bf31b3c6d3eeaaf3a30ecef448d7e9e Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 17:59:49 +0100 Subject: [PATCH 18/21] Skip more spec tests related to merge transition --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 3286cc146f0b..428b6602d4f2 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -75,7 +75,12 @@ export const defaultSkipOpts: SkipOpts = { /^gloas\/(finality|fork_choice|networking|sanity|transition)\/.*$/, /^gloas\/ssz_static\/ForkChoiceNode.*$/, ], - skippedTests: [], + skippedTests: [ + // These tests validate "first payload" scenarios where is_execution_enabled was false pre-merge. + // Since we removed merge transition support, these code paths no longer exist. + /^bellatrix\/operations\/execution_payload\/.+\/bad_parent_hash_first_payload$/, + /^bellatrix\/sanity\/blocks\/.+\/is_execution_enabled_false$/, + ], skippedRunners: [], }; From f6960b32f7939a65786ca52bd318acec8e5e51aa Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 18:56:17 +0100 Subject: [PATCH 19/21] Update scripts --- .../test/scripts/el-interop/{besu/post-merge.sh => besu.sh} | 3 ++- .../beacon-node/test/scripts/el-interop/besu/common-setup.sh | 4 ++-- .../el-interop/{besudocker/post-merge.sh => besudocker.sh} | 3 ++- .../test/scripts/el-interop/besudocker/common-setup.sh | 4 ++-- .../{ethereumjsdocker/post-merge.sh => ethereumjsdocker.sh} | 3 ++- .../test/scripts/el-interop/ethereumjsdocker/common-setup.sh | 4 ++-- .../test/scripts/el-interop/{geth/post-merge.sh => geth.sh} | 3 ++- .../beacon-node/test/scripts/el-interop/geth/common-setup.sh | 4 ++-- .../el-interop/{gethdocker/post-merge.sh => gethdocker.sh} | 3 ++- .../test/scripts/el-interop/gethdocker/common-setup.sh | 4 ++-- .../el-interop/{nethermind/post-merge.sh => nethermind.sh} | 3 ++- .../{netherminddocker/post-merge.sh => netherminddocker.sh} | 3 ++- .../test/scripts/el-interop/netherminddocker/common-setup.sh | 2 +- 13 files changed, 25 insertions(+), 18 deletions(-) rename packages/beacon-node/test/scripts/el-interop/{besu/post-merge.sh => besu.sh} (88%) rename packages/beacon-node/test/scripts/el-interop/{besudocker/post-merge.sh => besudocker.sh} (90%) rename packages/beacon-node/test/scripts/el-interop/{ethereumjsdocker/post-merge.sh => ethereumjsdocker.sh} (86%) rename packages/beacon-node/test/scripts/el-interop/{geth/post-merge.sh => geth.sh} (87%) rename packages/beacon-node/test/scripts/el-interop/{gethdocker/post-merge.sh => gethdocker.sh} (89%) rename packages/beacon-node/test/scripts/el-interop/{nethermind/post-merge.sh => nethermind.sh} (89%) rename packages/beacon-node/test/scripts/el-interop/{netherminddocker/post-merge.sh => netherminddocker.sh} (91%) diff --git a/packages/beacon-node/test/scripts/el-interop/besu/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/besu.sh similarity index 88% rename from packages/beacon-node/test/scripts/el-interop/besu/post-merge.sh rename to packages/beacon-node/test/scripts/el-interop/besu.sh index d864814ece7a..1f54d8646793 100755 --- a/packages/beacon-node/test/scripts/el-interop/besu/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/besu.sh @@ -2,7 +2,8 @@ scriptDir=$(dirname $0) currentDir=$(pwd) +elDir=$scriptDir/besu -. $scriptDir/common-setup.sh +. $elDir/common-setup.sh $EL_BINARY_DIR/besu --engine-rpc-enabled --rpc-http-enabled --rpc-http-api ADMIN,ETH,MINER,NET --rpc-http-port $ETH_PORT --engine-rpc-port $ENGINE_PORT --engine-jwt-secret $currentDir/$DATA_DIR/jwtsecret --data-path $DATA_DIR --data-storage-format BONSAI --genesis-file $DATA_DIR/genesis.json diff --git a/packages/beacon-node/test/scripts/el-interop/besu/common-setup.sh b/packages/beacon-node/test/scripts/el-interop/besu/common-setup.sh index f211f5d0714b..46e6cc6fda40 100755 --- a/packages/beacon-node/test/scripts/el-interop/besu/common-setup.sh +++ b/packages/beacon-node/test/scripts/el-interop/besu/common-setup.sh @@ -6,11 +6,11 @@ echo $EL_BINARY_DIR echo $JWT_SECRET_HEX echo $TEMPLATE_FILE -echo $scriptDir +echo $elDir echo $currentDir -env TTD=$TTD envsubst < $scriptDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json +env TTD=$TTD envsubst < $elDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json echo "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" > $DATA_DIR/sk.json echo "12345678" > $DATA_DIR/password.txt pubKey="0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" diff --git a/packages/beacon-node/test/scripts/el-interop/besudocker/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/besudocker.sh similarity index 90% rename from packages/beacon-node/test/scripts/el-interop/besudocker/post-merge.sh rename to packages/beacon-node/test/scripts/el-interop/besudocker.sh index d26307ee3d24..30117834d468 100755 --- a/packages/beacon-node/test/scripts/el-interop/besudocker/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/besudocker.sh @@ -2,7 +2,8 @@ scriptDir=$(dirname $0) currentDir=$(pwd) +elDir=$scriptDir/besudocker -. $scriptDir/common-setup.sh +. $elDir/common-setup.sh docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --name custom-execution -p $ETH_PORT:$ETH_PORT -p $ENGINE_PORT:$ENGINE_PORT -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --engine-rpc-enabled --rpc-http-enabled --rpc-http-api ADMIN,ETH,MINER,NET --rpc-http-port $ETH_PORT --engine-rpc-port $ENGINE_PORT --engine-jwt-secret /data/jwtsecret --data-path /data/besu --data-storage-format BONSAI --genesis-file /data/genesis.json diff --git a/packages/beacon-node/test/scripts/el-interop/besudocker/common-setup.sh b/packages/beacon-node/test/scripts/el-interop/besudocker/common-setup.sh index b3d93190ef2d..cde122a65686 100644 --- a/packages/beacon-node/test/scripts/el-interop/besudocker/common-setup.sh +++ b/packages/beacon-node/test/scripts/el-interop/besudocker/common-setup.sh @@ -6,11 +6,11 @@ echo $EL_BINARY_DIR echo $JWT_SECRET_HEX echo $TEMPLATE_FILE -echo $scriptDir +echo $elDir echo $currentDir -env TTD=$TTD envsubst < $scriptDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json +env TTD=$TTD envsubst < $elDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json echo "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" > $DATA_DIR/sk.json echo "12345678" > $DATA_DIR/password.txt pubKey="0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" diff --git a/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker.sh similarity index 86% rename from packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/post-merge.sh rename to packages/beacon-node/test/scripts/el-interop/ethereumjsdocker.sh index fbf9dcaaf929..42293061e1c0 100755 --- a/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker.sh @@ -2,7 +2,8 @@ scriptDir=$(dirname $0) currentDir=$(pwd) +elDir=$scriptDir/ethereumjsdocker -. $scriptDir/common-setup.sh +. $elDir/common-setup.sh docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --name custom-execution --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --dataDir /data/ethereumjs --gethGenesis /data/genesis.json --rpc --rpcEngineAddr 0.0.0.0 --rpcAddr 0.0.0.0 --rpcEngine --jwt-secret /data/jwtsecret --logLevel debug --isSingleNode diff --git a/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/common-setup.sh b/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/common-setup.sh index f5e64eadf26e..46741823b8a1 100755 --- a/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/common-setup.sh +++ b/packages/beacon-node/test/scripts/el-interop/ethereumjsdocker/common-setup.sh @@ -6,11 +6,11 @@ echo $EL_BINARY_DIR echo $JWT_SECRET_HEX echo $TEMPLATE_FILE -echo $scriptDir +echo $elDir echo $currentDir -env TTD=$TTD envsubst < $scriptDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json +env TTD=$TTD envsubst < $elDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json echo "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" > $DATA_DIR/sk.json echo "12345678" > $DATA_DIR/password.txt pubKey="0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" diff --git a/packages/beacon-node/test/scripts/el-interop/geth/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/geth.sh similarity index 87% rename from packages/beacon-node/test/scripts/el-interop/geth/post-merge.sh rename to packages/beacon-node/test/scripts/el-interop/geth.sh index 7f2ce0c96fa0..b9e6923df40f 100755 --- a/packages/beacon-node/test/scripts/el-interop/geth/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/geth.sh @@ -2,7 +2,8 @@ scriptDir=$(dirname $0) currentDir=$(pwd) +elDir=$scriptDir/geth -. $scriptDir/common-setup.sh +. $elDir/common-setup.sh $EL_BINARY_DIR/geth --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret $currentDir/$DATA_DIR/jwtsecret --datadir $DATA_DIR --allow-insecure-unlock --unlock $pubKey --password $DATA_DIR/password.txt --syncmode full \ No newline at end of file diff --git a/packages/beacon-node/test/scripts/el-interop/geth/common-setup.sh b/packages/beacon-node/test/scripts/el-interop/geth/common-setup.sh index b91a1a49ba61..aac1ec4c20be 100755 --- a/packages/beacon-node/test/scripts/el-interop/geth/common-setup.sh +++ b/packages/beacon-node/test/scripts/el-interop/geth/common-setup.sh @@ -5,11 +5,11 @@ echo $DATA_DIR echo $EL_BINARY_DIR echo $JWT_SECRET_HEX -echo $scriptDir +echo $elDir echo $currentDir -env TTD=$TTD envsubst < $scriptDir/genesisPre.tmpl > $DATA_DIR/genesis.json +env TTD=$TTD envsubst < $elDir/genesisPre.tmpl > $DATA_DIR/genesis.json echo "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" > $DATA_DIR/sk.json echo "12345678" > $DATA_DIR/password.txt pubKey="0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" diff --git a/packages/beacon-node/test/scripts/el-interop/gethdocker/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/gethdocker.sh similarity index 89% rename from packages/beacon-node/test/scripts/el-interop/gethdocker/post-merge.sh rename to packages/beacon-node/test/scripts/el-interop/gethdocker.sh index 177d0dcb7e3b..a7f8fcb208a3 100755 --- a/packages/beacon-node/test/scripts/el-interop/gethdocker/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/gethdocker.sh @@ -2,7 +2,8 @@ scriptDir=$(dirname $0) currentDir=$(pwd) +elDir=$scriptDir/gethdocker -. $scriptDir/common-setup.sh +. $elDir/common-setup.sh docker run --rm -u $(id -u ${USER}):$(id -g ${USER}) --name custom-execution --network host -v $currentDir/$DATA_DIR:/data $EL_BINARY_DIR --http -http.api "engine,net,eth,miner" --http.port $ETH_PORT --authrpc.port $ENGINE_PORT --authrpc.jwtsecret /data/jwtsecret --allow-insecure-unlock --unlock $pubKey --password /data/password.txt --datadir /data/geth --syncmode full diff --git a/packages/beacon-node/test/scripts/el-interop/gethdocker/common-setup.sh b/packages/beacon-node/test/scripts/el-interop/gethdocker/common-setup.sh index 4e77c3c10842..0183c470644b 100755 --- a/packages/beacon-node/test/scripts/el-interop/gethdocker/common-setup.sh +++ b/packages/beacon-node/test/scripts/el-interop/gethdocker/common-setup.sh @@ -6,11 +6,11 @@ echo $EL_BINARY_DIR echo $JWT_SECRET_HEX echo $TEMPLATE_FILE -echo $scriptDir +echo $elDir echo $currentDir -env TTD=$TTD envsubst < $scriptDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json +env TTD=$TTD envsubst < $elDir/$TEMPLATE_FILE > $DATA_DIR/genesis.json echo "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" > $DATA_DIR/sk.json echo "12345678" > $DATA_DIR/password.txt pubKey="0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" diff --git a/packages/beacon-node/test/scripts/el-interop/nethermind/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/nethermind.sh similarity index 89% rename from packages/beacon-node/test/scripts/el-interop/nethermind/post-merge.sh rename to packages/beacon-node/test/scripts/el-interop/nethermind.sh index d379ff40324c..6c0323ed814a 100755 --- a/packages/beacon-node/test/scripts/el-interop/nethermind/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/nethermind.sh @@ -2,8 +2,9 @@ scriptDir=$(dirname $0) currentDir=$(pwd) +elDir=$scriptDir/nethermind -. $scriptDir/common-setup.sh +. $elDir/common-setup.sh cd $EL_BINARY_DIR dotnet run -c Release -- --config themerge_kiln_testvectors --Merge.TerminalTotalDifficulty $TTD --JsonRpc.JwtSecretFile $currentDir/$DATA_DIR/jwtsecret --JsonRpc.Enabled true --JsonRpc.Host 0.0.0.0 --JsonRpc.AdditionalRpcUrls "http://localhost:$ETH_PORT|http|net;eth;subscribe;engine;web3;client|no-auth,http://localhost:$ENGINE_PORT|http|eth;engine" --Sync.SnapSync false diff --git a/packages/beacon-node/test/scripts/el-interop/netherminddocker/post-merge.sh b/packages/beacon-node/test/scripts/el-interop/netherminddocker.sh similarity index 91% rename from packages/beacon-node/test/scripts/el-interop/netherminddocker/post-merge.sh rename to packages/beacon-node/test/scripts/el-interop/netherminddocker.sh index 2a41fe8979eb..fa3a2b5ab15e 100755 --- a/packages/beacon-node/test/scripts/el-interop/netherminddocker/post-merge.sh +++ b/packages/beacon-node/test/scripts/el-interop/netherminddocker.sh @@ -2,8 +2,9 @@ scriptDir=$(dirname $0) currentDir=$(pwd) +elDir=$scriptDir/netherminddocker -. $scriptDir/common-setup.sh +. $elDir/common-setup.sh if [ "$TEMPLATE_FILE" == "genesisPostWithdraw.tmpl" ] then diff --git a/packages/beacon-node/test/scripts/el-interop/netherminddocker/common-setup.sh b/packages/beacon-node/test/scripts/el-interop/netherminddocker/common-setup.sh index 8892332be47a..bde72a2c058e 100755 --- a/packages/beacon-node/test/scripts/el-interop/netherminddocker/common-setup.sh +++ b/packages/beacon-node/test/scripts/el-interop/netherminddocker/common-setup.sh @@ -5,7 +5,7 @@ echo $DATA_DIR echo $EL_BINARY_DIR echo $JWT_SECRET_HEX -echo $scriptDir +echo $elDir echo $currentDir # echo a hex encoded 256 bit secret into a file From 19384ff14699f9b5af897d657357666074103445 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 19:16:50 +0100 Subject: [PATCH 20/21] Update docs --- docs/pages/contribution/advanced-topics/setting-up-a-testnet.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md b/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md index 04cac75c4b0a..e9cb16d1d73b 100644 --- a/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md +++ b/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md @@ -101,4 +101,3 @@ To set up a local testnet with a Post-Merge configuration, you may need to add t - `--params.ALTAIR_FORK_EPOCH 0` - `--params.BELLATRIX_FORK_EPOCH 0` -- `--terminal-total-difficulty-override 0` From 69b442292453592a0268e09a7089a55cf1894050 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 10 Dec 2025 19:20:50 +0100 Subject: [PATCH 21/21] Update geth image --- .env.test | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.env.test b/.env.test index 38a2e490d5f9..c0628a1dc9c4 100644 --- a/.env.test +++ b/.env.test @@ -1,7 +1,5 @@ # We use these images during sim and e2e tests -# This is the last version which supports pre/post merge chains in the same network -# All newer versions only work with post merge chains -GETH_DOCKER_IMAGE=ethereum/client-go:v1.16.2 +GETH_DOCKER_IMAGE=ethereum/client-go:v1.16.7 # Use either image or local binary for the testing GETH_BINARY_DIR= LIGHTHOUSE_DOCKER_IMAGE=ethpandaops/lighthouse:unstable-d235f2c