From 70dcc9d1a45d744f4b7fd5076070111da886d130 Mon Sep 17 00:00:00 2001 From: NC Date: Mon, 8 Apr 2024 10:11:59 +0800 Subject: [PATCH 01/17] Second batch of changes --- packages/beacon-node/src/chain/chain.ts | 37 ++++- packages/beacon-node/src/chain/interface.ts | 12 ++ packages/beacon-node/src/chain/options.ts | 1 + .../beacon-node/src/chain/prepareNextSlot.ts | 29 +++- .../beacon-node/src/metrics/metrics/beacon.ts | 8 +- .../test/e2e/chain/proposerBoostReorg.test.ts | 140 ++++++++++++++++++ .../test/mocks/fork-choice/timeliness.ts | 24 +++ .../test/mocks/mockedBeaconChain.ts | 2 + .../produceBlock/produceBlockBody.test.ts | 1 + .../perf/chain/verifyImportBlocks.test.ts | 1 + .../api/impl/validator/produceBlockV2.test.ts | 3 +- .../api/impl/validator/produceBlockV3.test.ts | 1 + .../test/unit/chain/prepareNextSlot.test.ts | 1 + packages/beacon-node/test/utils/logger.ts | 2 +- .../src/options/beaconNodeOptions/chain.ts | 10 ++ .../unit/options/beaconNodeOptions.test.ts | 2 + .../fork-choice/src/forkChoice/forkChoice.ts | 41 ++++- .../fork-choice/src/forkChoice/interface.ts | 2 + packages/fork-choice/src/index.ts | 2 +- 19 files changed, 305 insertions(+), 14 deletions(-) create mode 100644 packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts create mode 100644 packages/beacon-node/test/mocks/fork-choice/timeliness.ts diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 08e0727a15db..e47399efacf3 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -29,7 +29,7 @@ import { bellatrix, isBlindedBeaconBlock, } from "@lodestar/types"; -import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; +import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toHex} from "@lodestar/utils"; import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; @@ -279,7 +279,8 @@ export class BeaconChain implements IBeaconChain { clock.currentSlot, cachedState, opts, - this.justifiedBalancesGetter.bind(this) + this.justifiedBalancesGetter.bind(this), + logger ); const regen = new QueuedStateRegenerator({ config, @@ -691,10 +692,38 @@ export class BeaconChain implements IBeaconChain { recomputeForkChoiceHead(): ProtoBlock { this.metrics?.forkChoice.requests.inc(); - const timer = this.metrics?.forkChoice.findHead.startTimer(); + const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.recomputeForkChoiceHead}); try { - return this.forkChoice.updateHead(); + return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicialHead}); + } catch (e) { + this.metrics?.forkChoice.errors.inc(); + throw e; + } finally { + timer?.(); + } + } + + predictProposerHead(slot: Slot): ProtoBlock { + this.metrics?.forkChoice.requests.inc(); + const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.predictProposerHead}); + + try { + return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot}); + } catch (e) { + this.metrics?.forkChoice.errors.inc(); + throw e; + } finally { + timer?.(); + } + } + + getProposerHead(slot: Slot): ProtoBlock { + this.metrics?.forkChoice.requests.inc(); + const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.getProposerHead}); + + try { + return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetProposerHead, slot}); } catch (e) { this.metrics?.forkChoice.errors.inc(); throw e; diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 9ae703475cb6..903b3f4ba53e 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -64,6 +64,12 @@ export type StateGetOpts = { allowRegen: boolean; }; +export enum FindHeadFnName { + recomputeForkChoiceHead = "recomputeForkChoiceHead", + predictProposerHead = "predictProposerHead", + getProposerHead = "getProposerHead", +} + /** * The IBeaconChain service deals with processing incoming blocks, advancing a state transition * and applying the fork choice rule to update the chain head @@ -186,6 +192,12 @@ export interface IBeaconChain { recomputeForkChoiceHead(): ProtoBlock; + /** When proposerBoostReorg is enabled, this is called at slot n-1 to predict the head block to build on if we are proposing at slot n */ + predictProposerHead(slot: Slot): ProtoBlock; + + /** When proposerBoostReorg is enabled and we are proposing a block, this is called to determine which head block to build on */ + getProposerHead(slot: Slot): ProtoBlock; + waitForBlock(slot: Slot, root: RootHex): Promise; updateBeaconProposerData(epoch: Epoch, proposers: ProposerPreparationData[]): Promise; diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index c0d32449b072..eacc767a63fc 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -96,6 +96,7 @@ export const defaultChainOptions: IChainOptions = { blsVerifyAllMultiThread: false, disableBlsBatchVerify: false, proposerBoostEnabled: true, + proposerBoostReorgEnabled: false, computeUnrealized: true, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index c155c3198269..ab89734e08b3 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -2,6 +2,7 @@ import { computeEpochAtSlot, isExecutionStateType, computeTimeAtSlot, + CachedBeaconStateExecutions, StateHashTreeRootSource, } from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; @@ -144,7 +145,29 @@ export class PrepareNextSlotScheduler { if (isExecutionStateType(prepareState)) { const proposerIndex = prepareState.epochCtx.getBeaconProposer(prepareSlot); const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex); + let updatedPrepareState = prepareState; + let updatedHeadRoot = headRoot; + if (feeRecipient) { + // If we are proposing next slot, we need to predict if we can proposer-boost-reorg or not + const {slot: proposerHeadSlot, blockRoot: proposerHeadRoot} = this.chain.predictProposerHead(clockSlot); + + // If we predict we can reorg, update prepareState with proposer head block + if (proposerHeadRoot !== headRoot || proposerHeadSlot !== headSlot) { + this.logger.verbose("Weak head detected. May build on this block instead:", { + proposerHeadSlot, + proposerHeadRoot, + }); + this.metrics?.weakHeadDetected.inc(); + updatedPrepareState = (await this.chain.regen.getBlockSlotState( + proposerHeadRoot, + prepareSlot, + {dontTransferCache: !isEpochTransition}, + RegenCaller.precomputeEpoch + )) as CachedBeaconStateExecutions; + updatedHeadRoot = proposerHeadRoot; + } + // Update the builder status, if enabled shoot an api call to check status this.chain.updateBuilderStatus(clockSlot); if (this.chain.executionBuilder?.status) { @@ -167,10 +190,10 @@ export class PrepareNextSlotScheduler { this.chain, this.logger, fork as ForkExecution, // State is of execution type - fromHex(headRoot), + fromHex(updatedHeadRoot), safeBlockHash, finalizedBlockHash, - prepareState, + updatedPrepareState, feeRecipient ); this.logger.verbose("PrepareNextSlotScheduler prepared new payload", { @@ -183,7 +206,7 @@ export class PrepareNextSlotScheduler { // If emitPayloadAttributes is true emit a SSE payloadAttributes event if (this.chain.opts.emitPayloadAttributes === true) { const data = await getPayloadAttributesForSSE(fork as ForkExecution, this.chain, { - prepareState, + prepareState: updatedPrepareState, prepareSlot, parentBlockRoot: fromHex(headRoot), // The likely consumers of this API are builders and will anyway ignore the diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 9366174ef6c6..4547027bf8d0 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -57,10 +57,11 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { // Non-spec'ed forkChoice: { - findHead: register.histogram({ + findHead: register.histogram<{entrypoint: string}>({ name: "beacon_fork_choice_find_head_seconds", help: "Time taken to find head in seconds", buckets: [0.1, 1, 10], + labelNames: ["entrypoint"], }), requests: register.gauge({ name: "beacon_fork_choice_requests_total", @@ -198,5 +199,10 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { name: "beacon_clock_epoch", help: "Current clock epoch", }), + + weakHeadDetected: register.gauge({ + name: "beacon_weak_head_detected", + help: "Detected current head block is weak. May reorg it out when proposing next slot. See proposer boost reorg for more", + }), }; } diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts new file mode 100644 index 000000000000..0c18a5b73528 --- /dev/null +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -0,0 +1,140 @@ +import {describe, it, afterEach, expect} from "vitest"; +import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import {TimestampFormatCode} from "@lodestar/logger"; +import {ChainConfig} from "@lodestar/config"; +import {RootHex, Slot} from "@lodestar/types"; +import {routes} from "@lodestar/api"; +import {toHexString} from "@lodestar/utils"; +import {LogLevel, TestLoggerOpts, testLogger} from "../../utils/logger.js"; +import {getDevBeaconNode} from "../../utils/node/beacon.js"; +import {TimelinessForkChoice} from "../../mocks/fork-choice/timeliness.js"; +import {getAndInitDevValidators} from "../../utils/node/validator.js"; +import {waitForEvent} from "../../utils/events/resolver.js"; +import {ReorgEventData} from "../../../src/chain/emitter.js"; + +describe( + "proposer boost reorg", + function () { + const validatorCount = 8; + const testParams: Pick = + { + // eslint-disable-next-line @typescript-eslint/naming-convention + SECONDS_PER_SLOT: 2, + // need this to make block `reorgSlot - 1` strong enough + // eslint-disable-next-line @typescript-eslint/naming-convention + REORG_PARENT_WEIGHT_THRESHOLD: 80, + // need this to make block `reorgSlot + 1` to become the head + // eslint-disable-next-line @typescript-eslint/naming-convention + PROPOSER_SCORE_BOOST: 120, + }; + + const afterEachCallbacks: (() => Promise | void)[] = []; + afterEach(async () => { + while (afterEachCallbacks.length > 0) { + const callback = afterEachCallbacks.pop(); + if (callback) await callback(); + } + }); + + const reorgSlot = 10; + const proposerBoostReorgEnabled = true; + /** + * reorgSlot + * / + * reorgSlot - 1 ------------ reorgSlot + 1 + * + * Note that in additional of being not timely, there are other criterion that + * the block needs to satisfied before being re-orged out. This test assumes + * other criterion are satisfied except timeliness. + * Note that in additional of being not timely, there are other criterion that + * the block needs to satisfy before being re-orged out. This test assumes + * other criterion are already satisfied + */ + it(`should reorg a late block at slot ${reorgSlot}`, async () => { + // the node needs time to transpile/initialize bls worker threads + const genesisSlotsDelay = 7; + const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; + const testLoggerOpts: TestLoggerOpts = { + level: LogLevel.debug, + timestampFormat: { + format: TimestampFormatCode.EpochSlot, + genesisTime, + slotsPerEpoch: SLOTS_PER_EPOCH, + secondsPerSlot: testParams.SECONDS_PER_SLOT, + }, + }; + const logger = testLogger("BeaconNode", testLoggerOpts); + const bn = await getDevBeaconNode({ + params: testParams, + options: { + sync: {isSingleNode: true}, + network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false}, + // run the first bn with ReorgedForkChoice, no nHistoricalStates flag so it does not have to reload + chain: { + blsVerifyAllMainThread: true, + forkchoiceConstructor: TimelinessForkChoice, + proposerBoostEnabled: true, + proposerBoostReorgEnabled, + }, + }, + validatorCount, + genesisTime, + logger, + }); + + (bn.chain.forkChoice as TimelinessForkChoice).lateSlot = reorgSlot; + afterEachCallbacks.push(async () => bn.close()); + const {validators} = await getAndInitDevValidators({ + node: bn, + logPrefix: "vc-0", + validatorsPerClient: validatorCount, + validatorClientCount: 1, + startIndex: 0, + useRestApi: false, + testLoggerOpts, + }); + afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close()))); + + const commonAncestor = await waitForEvent<{slot: Slot; block: RootHex}>( + bn.chain.emitter, + routes.events.EventType.head, + 240000, + ({slot}) => slot === reorgSlot - 1 + ); + // reorgSlot + // / + // commonAncestor ------------ newBlock + const commonAncestorRoot = commonAncestor.block; + const reorgBlockEventData = await waitForEvent<{slot: Slot; block: RootHex}>( + bn.chain.emitter, + routes.events.EventType.head, + 240000, + ({slot}) => slot === reorgSlot + ); + const reorgBlockRoot = reorgBlockEventData.block; + const [newBlockEventData, reorgEventData] = await Promise.all([ + waitForEvent<{slot: Slot; block: RootHex}>( + bn.chain.emitter, + routes.events.EventType.block, + 240000, + ({slot}) => slot === reorgSlot + 1 + ), + waitForEvent(bn.chain.emitter, routes.events.EventType.chainReorg, 240000), + ]); + expect(reorgEventData.slot).toEqual(reorgSlot + 1); + const newBlock = await bn.chain.getBlockByRoot(newBlockEventData.block); + if (newBlock == null) { + throw Error(`Block ${reorgSlot + 1} not found`); + } + expect(reorgEventData.oldHeadBlock).toEqual(reorgBlockRoot); + expect(reorgEventData.newHeadBlock).toEqual(newBlockEventData.block); + expect(reorgEventData.depth).toEqual(2); + expect(toHexString(newBlock?.block.message.parentRoot)).toEqual(commonAncestorRoot); + logger.info("New block", { + slot: newBlock.block.message.slot, + parentRoot: toHexString(newBlock.block.message.parentRoot), + }); + }); + }, + {timeout: 60000} +); diff --git a/packages/beacon-node/test/mocks/fork-choice/timeliness.ts b/packages/beacon-node/test/mocks/fork-choice/timeliness.ts new file mode 100644 index 000000000000..72b3ff66a084 --- /dev/null +++ b/packages/beacon-node/test/mocks/fork-choice/timeliness.ts @@ -0,0 +1,24 @@ +import {ForkChoice} from "@lodestar/fork-choice"; +import {Slot, allForks} from "@lodestar/types"; + +/** + * A specific forkchoice implementation to mark some blocks as timely or not. + */ +export class TimelinessForkChoice extends ForkChoice { + /** + * These need to be in the constructor, however we want to keep the constructor signature the same. + * So they are set after construction in the test instead. + */ + lateSlot: Slot | undefined; + + /** + * This is to mark the `lateSlot` as not timely. + */ + protected isBlockTimely(block: allForks.BeaconBlock, blockDelaySec: number): boolean { + if (block.slot === this.lateSlot) { + return false; + } + + return super.isBlockTimely(block, blockDelaySec); + } +} diff --git a/packages/beacon-node/test/mocks/mockedBeaconChain.ts b/packages/beacon-node/test/mocks/mockedBeaconChain.ts index aa8228dcece0..39b62b597076 100644 --- a/packages/beacon-node/test/mocks/mockedBeaconChain.ts +++ b/packages/beacon-node/test/mocks/mockedBeaconChain.ts @@ -128,10 +128,12 @@ vi.mock("../../src/chain/chain.js", async (importActual) => { beaconProposerCache: new BeaconProposerCache(), shufflingCache: new ShufflingCache(), produceCommonBlockBody: vi.fn(), + getProposerHead: vi.fn(), produceBlock: vi.fn(), produceBlindedBlock: vi.fn(), getCanonicalBlockAtSlot: vi.fn(), recomputeForkChoiceHead: vi.fn(), + predictProposerHead: vi.fn(), getHeadStateAtCurrentEpoch: vi.fn(), getHeadState: vi.fn(), updateBuilderStatus: vi.fn(), 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 96dda3acaece..56132cedd790 100644 --- a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts +++ b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts @@ -29,6 +29,7 @@ describe("produceBlockBody", () => { chain = new BeaconChain( { proposerBoostEnabled: true, + proposerBoostReorgEnabled: false, computeUnrealized: false, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index 41d8aa76865b..c9b22667fc5a 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -85,6 +85,7 @@ describe.skip("verify+import blocks - range sync perf test", () => { const chain = new BeaconChain( { proposerBoostEnabled: true, + proposerBoostReorgEnabled: false, computeUnrealized: false, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, diff --git a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts index 1ec3f738669d..370a25d7cc92 100644 --- a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts +++ b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts @@ -47,6 +47,7 @@ describe("api/validator - produceBlockV2", function () { const graffiti = "a".repeat(32); const feeRecipient = "0xcccccccccccccccccccccccccccccccccccccccc"; + modules.chain.getProposerHead.mockReturnValue(generateProtoBlock({blockRoot: toHexString(parentBlockRoot)})); modules.chain.recomputeForkChoiceHead.mockReturnValue( generateProtoBlock({blockRoot: toHexString(parentBlockRoot)}) ); @@ -87,7 +88,7 @@ describe("api/validator - produceBlockV2", function () { const feeRecipient = "0xccccccccccccccccccccccccccccccccccccccaa"; const headSlot = 0; - modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: headSlot})); + modules.chain.getProposerHead.mockReturnValue(generateProtoBlock({slot: headSlot})); modules.chain.recomputeForkChoiceHead.mockReturnValue(generateProtoBlock({slot: headSlot})); modules.chain["opPool"].getSlashingsAndExits.mockReturnValue([[], [], [], []]); diff --git a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts index d182cfeb537e..4adb07cd154b 100644 --- a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts +++ b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts @@ -87,6 +87,7 @@ describe("api/validator - produceBlockV3", function () { modules.chain.recomputeForkChoiceHead.mockReturnValue({ blockRoot: toHexString(fullBlock.parentRoot), } as ProtoBlock); + modules.chain.getProposerHead.mockReturnValue({blockRoot: toHexString(fullBlock.parentRoot)} as ProtoBlock); if (enginePayloadValue !== null) { const commonBlockBody: CommonBlockBody = { diff --git a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts index 6d1be3fa8dd5..652749492240 100644 --- a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts +++ b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts @@ -120,6 +120,7 @@ describe("PrepareNextSlot scheduler", () => { chainStub.emitter.on(routes.events.EventType.payloadAttributes, spy); getForkStub.mockReturnValue(ForkName.bellatrix); chainStub.recomputeForkChoiceHead.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock); + chainStub.predictProposerHead.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock); forkChoiceStub.getJustifiedBlock.mockReturnValue({} as ProtoBlock); forkChoiceStub.getFinalizedBlock.mockReturnValue({} as ProtoBlock); updateBuilderStatus.mockReturnValue(void 0); diff --git a/packages/beacon-node/test/utils/logger.ts b/packages/beacon-node/test/utils/logger.ts index 1c1526514565..10f27565216f 100644 --- a/packages/beacon-node/test/utils/logger.ts +++ b/packages/beacon-node/test/utils/logger.ts @@ -21,6 +21,6 @@ export const testLogger = (module?: string, opts?: TestLoggerOpts): LoggerNode = opts.module = module; } const level = getEnvLogLevel(); - opts.level = level ?? LogLevel.info; + opts.level = level ?? opts.level ?? LogLevel.info; return getNodeLogger(opts); }; diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 0fee440a792b..7bdaedde25ee 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -13,6 +13,7 @@ export type ChainArgs = { // as this is defined as part of BeaconPaths // "chain.persistInvalidSszObjectsDir": string; "chain.proposerBoostEnabled"?: boolean; + "chain.proposerBoostReorgEnabled"?: boolean; "chain.disableImportExecutionFcU"?: boolean; "chain.preaggregateSlotDistance"?: number; "chain.attDataCacheSlotDistance"?: number; @@ -44,6 +45,7 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any persistInvalidSszObjectsDir: undefined as any, proposerBoostEnabled: args["chain.proposerBoostEnabled"], + proposerBoostReorgEnabled: args["chain.proposerBoostReorgEnabled"], disableImportExecutionFcU: args["chain.disableImportExecutionFcU"], preaggregateSlotDistance: args["chain.preaggregateSlotDistance"], attDataCacheSlotDistance: args["chain.attDataCacheSlotDistance"], @@ -131,6 +133,14 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, + "chain.proposerBoostReorgEnabled": { + hidden: true, + type: "boolean", + description: "Enable proposer boost reorg to reorg out a late block", + defaultDescription: String(defaultOptions.chain.proposerBoostReorgEnabled), + group: "chain", + }, + "chain.disableImportExecutionFcU": { hidden: true, type: "boolean", diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index f31cc604f775..193a532e4f9c 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -24,6 +24,7 @@ describe("options / beaconNodeOptions", () => { "chain.persistProducedBlocks": true, "chain.persistInvalidSszObjects": true, "chain.proposerBoostEnabled": false, + "chain.proposerBoostReorgEnabled": false, "chain.disableImportExecutionFcU": false, "chain.preaggregateSlotDistance": 1, "chain.attDataCacheSlotDistance": 2, @@ -130,6 +131,7 @@ describe("options / beaconNodeOptions", () => { persistProducedBlocks: true, persistInvalidSszObjects: true, proposerBoostEnabled: false, + proposerBoostReorgEnabled: false, disableImportExecutionFcU: false, preaggregateSlotDistance: 1, attDataCacheSlotDistance: 2, diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 7adc2c425816..eb9cb86e15cb 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -46,9 +46,21 @@ import {IForkChoiceStore, CheckpointWithHex, toCheckpointWithHex, JustifiedBalan export type ForkChoiceOpts = { proposerBoostEnabled?: boolean; + proposerBoostReorgEnabled?: boolean; computeUnrealized?: boolean; }; +export enum UpdateHeadOpt { + GetCanonicialHead, // Skip getProposerHead + GetProposerHead, // With getProposerHead + GetPredictedProposerHead, // With predictProposerHead +} + +export type UpdateAndGetHeadOpt = + | {mode: UpdateHeadOpt.GetCanonicialHead} + | {mode: UpdateHeadOpt.GetProposerHead; slot: Slot} + | {mode: UpdateHeadOpt.GetPredictedProposerHead; slot: Slot}; + /** * Provides an implementation of "Ethereum Consensus -- Beacon Chain Fork Choice": * @@ -156,6 +168,29 @@ export class ForkChoice implements IForkChoice { return this.head; } + /** + * + * A multiplexer to wrap around the traditional `updateHead()` according to the scenario + * Scenarios as follow: + * Prepare to propose in the next slot: getHead() -> predictProposerHead() + * Proposing in the current slot: updateHead() -> getProposerHead() + * Others eg. initializing forkchoice, importBlock: updateHead() + */ + updateAndGetHead(opt: UpdateAndGetHeadOpt): ProtoBlock { + const {mode} = opt; + + const canonicialHeadBlock = mode === UpdateHeadOpt.GetPredictedProposerHead ? this.getHead() : this.updateHead(); + switch (mode) { + case UpdateHeadOpt.GetPredictedProposerHead: + return this.predictProposerHead(canonicialHeadBlock, opt.slot); + case UpdateHeadOpt.GetProposerHead: + return this.getProposerHead(canonicialHeadBlock, opt.slot); + case UpdateHeadOpt.GetCanonicialHead: + default: + return canonicialHeadBlock; + } + } + /** * Get the proposer boost root */ @@ -176,7 +211,7 @@ export class ForkChoice implements IForkChoice { */ predictProposerHead(headBlock: ProtoBlock, currentSlot?: Slot): ProtoBlock { // Skip re-org attempt if proposer boost (reorg) are disabled - if (!this.opts?.proposerBoostEnabled) { + if (!this.opts?.proposerBoostEnabled || !this.opts?.proposerBoostReorgEnabled) { this.logger?.verbose("No proposer boot reorg prediction since the related flags are disabled"); return headBlock; } @@ -209,7 +244,7 @@ export class ForkChoice implements IForkChoice { * * This function takes in the canonical head block and determine the proposer head (canonical head block or its parent) * https://github.com/ethereum/consensus-specs/pull/3034 for info about proposer boost reorg - * This function should only be called during block proposal and only be called after `updateHead()` + * This function should only be called during block proposal and only be called after `updateHead()` in `updateAndGetHead()` * * Same as https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/phase0/fork-choice.md#get_proposer_head */ @@ -222,7 +257,7 @@ export class ForkChoice implements IForkChoice { let proposerHead = headBlock; // Skip re-org attempt if proposer boost (reorg) are disabled - if (!this.opts?.proposerBoostEnabled) { + if (!this.opts?.proposerBoostEnabled || !this.opts?.proposerBoostReorgEnabled) { this.logger?.verbose("No proposer boot reorg attempt since the related flags are disabled"); return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ProposerBoostReorgDisabled}; } diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index 5086013bd3a1..257ac7198e10 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -3,6 +3,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, Slot, ValidatorIndex, phase0, allForks, Root, RootHex} from "@lodestar/types"; import {ProtoBlock, MaybeValidExecutionStatus, LVHExecResponse, ProtoNode} from "../protoArray/interface.js"; import {CheckpointWithHex} from "./store.js"; +import { UpdateAndGetHeadOpt } from "./forkChoice.js"; export type CheckpointHex = { epoch: Epoch; @@ -93,6 +94,7 @@ export interface IForkChoice { getHeadRoot(): RootHex; getHead(): ProtoBlock; updateHead(): ProtoBlock; + updateAndGetHead(mode: UpdateAndGetHeadOpt): ProtoBlock; /** * Retrieves all possible chain heads (leaves of fork choice tree). */ diff --git a/packages/fork-choice/src/index.ts b/packages/fork-choice/src/index.ts index ff0711599a54..12b678d7db2b 100644 --- a/packages/fork-choice/src/index.ts +++ b/packages/fork-choice/src/index.ts @@ -9,7 +9,7 @@ export type { } from "./protoArray/interface.js"; export {ExecutionStatus} from "./protoArray/interface.js"; -export {ForkChoice, type ForkChoiceOpts, assertValidTerminalPowBlock} from "./forkChoice/forkChoice.js"; +export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt, assertValidTerminalPowBlock} from "./forkChoice/forkChoice.js"; export { type IForkChoice, type PowBlockHex, From 26591cabb83330bff1b54bb613d2a54a75b3d0be Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 9 Apr 2024 00:04:54 +0800 Subject: [PATCH 02/17] Wire proposer boost related code to block production --- .../src/api/impl/validator/index.ts | 6 ++-- packages/beacon-node/src/chain/chain.ts | 25 ++++++++++++--- .../beacon-node/src/metrics/metrics/beacon.ts | 8 +++++ .../test/e2e/chain/proposerBoostReorg.test.ts | 4 +-- .../fork-choice/src/forkChoice/forkChoice.ts | 24 ++++++++++---- .../fork-choice/src/forkChoice/interface.ts | 32 +++++++++++-------- 6 files changed, 70 insertions(+), 29 deletions(-) diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 658c86b905ff..793f84edbe12 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -363,7 +363,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } @@ -430,7 +430,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } @@ -508,7 +508,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - const parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + const parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); const fork = config.getForkName(slot); // set some sensible opts diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index e47399efacf3..8c11b99a817e 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -45,7 +45,14 @@ import {isOptimisticBlock} from "../util/forkChoice.js"; import {BufferPool} from "../util/bufferPool.js"; import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js"; import {ChainEventEmitter, ChainEvent} from "./emitter.js"; -import {IBeaconChain, ProposerPreparationData, BlockHash, StateGetOpts, CommonBlockBody} from "./interface.js"; +import { + IBeaconChain, + ProposerPreparationData, + BlockHash, + StateGetOpts, + CommonBlockBody, + FindHeadFnName, +} from "./interface.js"; import {IChainOptions} from "./options.js"; import {QueuedStateRegenerator, RegenCaller} from "./regen/index.js"; import {initializeForkChoice} from "./forkChoice/index.js"; @@ -695,7 +702,7 @@ export class BeaconChain implements IBeaconChain { const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.recomputeForkChoiceHead}); try { - return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicialHead}); + return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicialHead}).head; } catch (e) { this.metrics?.forkChoice.errors.inc(); throw e; @@ -709,7 +716,7 @@ export class BeaconChain implements IBeaconChain { const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.predictProposerHead}); try { - return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot}); + return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot}).head; } catch (e) { this.metrics?.forkChoice.errors.inc(); throw e; @@ -721,9 +728,19 @@ export class BeaconChain implements IBeaconChain { getProposerHead(slot: Slot): ProtoBlock { this.metrics?.forkChoice.requests.inc(); const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.getProposerHead}); + const secFromSlot = this.clock.secFromSlot(slot); try { - return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetProposerHead, slot}); + const {head, isHeadTimely, notReorgedReason} = this.forkChoice.updateAndGetHead({ + mode: UpdateHeadOpt.GetProposerHead, + secFromSlot, + slot, + }); + this.metrics?.forkChoice.isBlockTimely.set(isHeadTimely ? 1 : 0); + if (notReorgedReason !== undefined) { + this.metrics?.forkChoice.notReorgedReason.set(notReorgedReason); + } + return head; } catch (e) { this.metrics?.forkChoice.errors.inc(); throw e; diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 4547027bf8d0..311d9eb3b074 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -110,6 +110,14 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { name: "beacon_fork_choice_indices_count", help: "Current count of indices in fork choice data structures", }), + isBlockTimely: register.gauge({ + name: "beacon_fork_choice_is_block_timely", + help: "Whether the current head (or original head if re-orged out because it is late) is timely or not", + }), + notReorgedReason: register.gauge({ + name: "beacon_fork_choice_not_reorged_reason", + help: "Reason why the current head is not re-orged out", + }), }, parentBlockDistance: register.histogram({ diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts index 0c18a5b73528..583a5f5d0ac0 100644 --- a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -46,8 +46,8 @@ describe( * Note that in additional of being not timely, there are other criterion that * the block needs to satisfied before being re-orged out. This test assumes * other criterion are satisfied except timeliness. - * Note that in additional of being not timely, there are other criterion that - * the block needs to satisfy before being re-orged out. This test assumes + * Note that in additional of being not timely, there are other criterion that + * the block needs to satisfy before being re-orged out. This test assumes * other criterion are already satisfied */ it(`should reorg a late block at slot ${reorgSlot}`, async () => { diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index eb9cb86e15cb..a218acf960ba 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -58,7 +58,7 @@ export enum UpdateHeadOpt { export type UpdateAndGetHeadOpt = | {mode: UpdateHeadOpt.GetCanonicialHead} - | {mode: UpdateHeadOpt.GetProposerHead; slot: Slot} + | {mode: UpdateHeadOpt.GetProposerHead; secFromSlot: number; slot: Slot} | {mode: UpdateHeadOpt.GetPredictedProposerHead; slot: Slot}; /** @@ -175,19 +175,31 @@ export class ForkChoice implements IForkChoice { * Prepare to propose in the next slot: getHead() -> predictProposerHead() * Proposing in the current slot: updateHead() -> getProposerHead() * Others eg. initializing forkchoice, importBlock: updateHead() + * + * Only `GetProposerHead` returns additional field `isHeadTimely` and `notReorgedReason` for metrics purpose */ - updateAndGetHead(opt: UpdateAndGetHeadOpt): ProtoBlock { + updateAndGetHead(opt: UpdateAndGetHeadOpt): { + head: ProtoBlock; + isHeadTimely?: boolean; + notReorgedReason?: NotReorgedReason; + } { const {mode} = opt; const canonicialHeadBlock = mode === UpdateHeadOpt.GetPredictedProposerHead ? this.getHead() : this.updateHead(); switch (mode) { case UpdateHeadOpt.GetPredictedProposerHead: - return this.predictProposerHead(canonicialHeadBlock, opt.slot); - case UpdateHeadOpt.GetProposerHead: - return this.getProposerHead(canonicialHeadBlock, opt.slot); + return {head: this.predictProposerHead(canonicialHeadBlock, opt.slot)}; + case UpdateHeadOpt.GetProposerHead: { + const { + proposerHead: head, + isHeadTimely, + notReorgedReason, + } = this.getProposerHead(canonicialHeadBlock, opt.secFromSlot, opt.slot); + return {head, isHeadTimely, notReorgedReason}; + } case UpdateHeadOpt.GetCanonicialHead: default: - return canonicialHeadBlock; + return {head: canonicialHeadBlock}; } } diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index 257ac7198e10..546620a37134 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -3,7 +3,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, Slot, ValidatorIndex, phase0, allForks, Root, RootHex} from "@lodestar/types"; import {ProtoBlock, MaybeValidExecutionStatus, LVHExecResponse, ProtoNode} from "../protoArray/interface.js"; import {CheckpointWithHex} from "./store.js"; -import { UpdateAndGetHeadOpt } from "./forkChoice.js"; +import {UpdateAndGetHeadOpt} from "./forkChoice.js"; export type CheckpointHex = { epoch: Epoch; @@ -44,18 +44,18 @@ export type AncestorResult = // Reason for not proposer boost reorging export enum NotReorgedReason { - HeadBlockIsTimely, - ParentBlockNotAvailable, - ProposerBoostReorgDisabled, - NotShufflingStable, - NotFFGCompetitive, - ChainLongUnfinality, - ParentBlockDistanceMoreThanOneSlot, - ReorgMoreThanOneSlot, - ProposerBoostNotWornOff, - HeadBlockNotWeak, - ParentBlockIsStrong, - NotProposingOnTime, + HeadBlockIsTimely = 0, + ParentBlockNotAvailable = 1, + ProposerBoostReorgDisabled = 2, + NotShufflingStable = 3, + NotFFGCompetitive = 4, + ChainLongUnfinality = 5, + ParentBlockDistanceMoreThanOneSlot = 6, + ReorgMoreThanOneSlot = 7, + ProposerBoostNotWornOff = 8, + HeadBlockNotWeak = 9, + ParentBlockIsStrong = 10, + NotProposingOnTime = 11, } export type ForkChoiceMetrics = { @@ -94,7 +94,11 @@ export interface IForkChoice { getHeadRoot(): RootHex; getHead(): ProtoBlock; updateHead(): ProtoBlock; - updateAndGetHead(mode: UpdateAndGetHeadOpt): ProtoBlock; + updateAndGetHead(mode: UpdateAndGetHeadOpt): { + head: ProtoBlock; + isHeadTimely?: boolean; + notReorgedReason?: NotReorgedReason; + }; /** * Retrieves all possible chain heads (leaves of fork choice tree). */ From 52d207e5cd295879aea4f8b0d65a49b6b1416d67 Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 9 Apr 2024 00:46:29 +0800 Subject: [PATCH 03/17] Update test --- .../fork-choice/test/unit/forkChoice/getProposerHead.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index 9764b18068bb..672cbb01e278 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -236,7 +236,7 @@ describe("Forkchoice / GetProposerHead", function () { const forkChoice = new ForkChoice(config, fcStore, protoArr, { proposerBoostEnabled: true, - // proposerBoostReorgEnabled: true, + proposerBoostReorgEnabled: true, }); const {proposerHead, isHeadTimely, notReorgedReason} = forkChoice.getProposerHead( From 1fab30df016b5513ce257adee3850e11ebb2d3f5 Mon Sep 17 00:00:00 2001 From: NC Date: Tue, 16 Apr 2024 15:45:27 +0800 Subject: [PATCH 04/17] Update metrics --- packages/beacon-node/src/chain/chain.ts | 6 +++--- packages/beacon-node/src/metrics/metrics/beacon.ts | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 8c11b99a817e..1cf85eded801 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -736,9 +736,9 @@ export class BeaconChain implements IBeaconChain { secFromSlot, slot, }); - this.metrics?.forkChoice.isBlockTimely.set(isHeadTimely ? 1 : 0); - if (notReorgedReason !== undefined) { - this.metrics?.forkChoice.notReorgedReason.set(notReorgedReason); + + if (isHeadTimely && notReorgedReason !== undefined) { + this.metrics?.forkChoice.notReorgedReason.inc({reason: notReorgedReason}); } return head; } catch (e) { diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 311d9eb3b074..a4c5c6eca622 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -1,4 +1,5 @@ import {ProducedBlockSource} from "@lodestar/types"; +import {NotReorgedReason} from "@lodestar/fork-choice/lib/forkChoice/interface.js"; import {RegistryMetricCreator} from "../utils/registryMetricCreator.js"; import {BlockProductionStep, PayloadPreparationType} from "../../chain/produceBlock/index.js"; @@ -110,13 +111,10 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { name: "beacon_fork_choice_indices_count", help: "Current count of indices in fork choice data structures", }), - isBlockTimely: register.gauge({ - name: "beacon_fork_choice_is_block_timely", - help: "Whether the current head (or original head if re-orged out because it is late) is timely or not", - }), - notReorgedReason: register.gauge({ + notReorgedReason: register.gauge<{reason: NotReorgedReason}>({ name: "beacon_fork_choice_not_reorged_reason", help: "Reason why the current head is not re-orged out", + labelNames: ["reason"], }), }, From 46d6df4041f4448320d4fed68c87ac88e99ef326 Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 29 May 2024 16:24:37 +0200 Subject: [PATCH 05/17] Update packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts Co-authored-by: twoeths --- packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts index 583a5f5d0ac0..ebcef54bc584 100644 --- a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -43,7 +43,7 @@ describe( * / * reorgSlot - 1 ------------ reorgSlot + 1 * - * Note that in additional of being not timely, there are other criterion that + * Note that in addition of being not timely, there are other criterion that * the block needs to satisfied before being re-orged out. This test assumes * other criterion are satisfied except timeliness. * Note that in additional of being not timely, there are other criterion that From 09d35d8e3d959aa3e2831947e0c304a175a4447a Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Wed, 29 May 2024 16:29:22 +0200 Subject: [PATCH 06/17] Address comment --- packages/beacon-node/src/chain/chain.ts | 6 ++--- .../beacon-node/src/metrics/metrics/beacon.ts | 6 +++-- .../test/e2e/chain/proposerBoostReorg.test.ts | 4 --- .../test/spec/presets/fork_choice.test.ts | 2 +- .../fork-choice/src/forkChoice/forkChoice.ts | 6 ++--- .../fork-choice/src/forkChoice/interface.ts | 25 +++++++++---------- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 1cf85eded801..e45d4c9e6f5a 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -704,7 +704,7 @@ export class BeaconChain implements IBeaconChain { try { return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicialHead}).head; } catch (e) { - this.metrics?.forkChoice.errors.inc(); + this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicialHead}); throw e; } finally { timer?.(); @@ -718,7 +718,7 @@ export class BeaconChain implements IBeaconChain { try { return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot}).head; } catch (e) { - this.metrics?.forkChoice.errors.inc(); + this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetPredictedProposerHead}); throw e; } finally { timer?.(); @@ -742,7 +742,7 @@ export class BeaconChain implements IBeaconChain { } return head; } catch (e) { - this.metrics?.forkChoice.errors.inc(); + this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetProposerHead}); throw e; } finally { timer?.(); diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index a4c5c6eca622..d34c03ff1878 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -2,6 +2,7 @@ import {ProducedBlockSource} from "@lodestar/types"; import {NotReorgedReason} from "@lodestar/fork-choice/lib/forkChoice/interface.js"; import {RegistryMetricCreator} from "../utils/registryMetricCreator.js"; import {BlockProductionStep, PayloadPreparationType} from "../../chain/produceBlock/index.js"; +import { UpdateHeadOpt } from "@lodestar/fork-choice"; export type BeaconMetrics = ReturnType; @@ -68,9 +69,10 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { name: "beacon_fork_choice_requests_total", help: "Count of occasions where fork choice has tried to find a head", }), - errors: register.gauge({ + errors: register.gauge<{entrypoint: UpdateHeadOpt}>({ name: "beacon_fork_choice_errors_total", help: "Count of occasions where fork choice has returned an error when trying to find a head", + labelNames: ["entrypoint"] }), changedHead: register.gauge({ name: "beacon_fork_choice_changed_head_total", @@ -207,7 +209,7 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { }), weakHeadDetected: register.gauge({ - name: "beacon_weak_head_detected", + name: "beacon_weak_head_detected_total", help: "Detected current head block is weak. May reorg it out when proposing next slot. See proposer boost reorg for more", }), }; diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts index ebcef54bc584..2a79cc3a4906 100644 --- a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -44,9 +44,6 @@ describe( * reorgSlot - 1 ------------ reorgSlot + 1 * * Note that in addition of being not timely, there are other criterion that - * the block needs to satisfied before being re-orged out. This test assumes - * other criterion are satisfied except timeliness. - * Note that in additional of being not timely, there are other criterion that * the block needs to satisfy before being re-orged out. This test assumes * other criterion are already satisfied */ @@ -69,7 +66,6 @@ describe( options: { sync: {isSingleNode: true}, network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false}, - // run the first bn with ReorgedForkChoice, no nHistoricalStates flag so it does not have to reload chain: { blsVerifyAllMainThread: true, forkchoiceConstructor: TimelinessForkChoice, 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 49d78cc42f6a..3d7b74634bca 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -263,7 +263,7 @@ const forkChoiceTest = logger.debug(`Step ${i}/${stepsLen} check`); // Forkchoice head is computed lazily only on request - const head = chain.forkChoice.updateHead(); + const head = (chain.forkChoice as ForkChoice).updateHead(); const proposerBootRoot = (chain.forkChoice as ForkChoice).getProposerBoostRoot(); if (step.checks.head !== undefined) { diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index a218acf960ba..9ca12c52c6ba 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -51,9 +51,9 @@ export type ForkChoiceOpts = { }; export enum UpdateHeadOpt { - GetCanonicialHead, // Skip getProposerHead - GetProposerHead, // With getProposerHead - GetPredictedProposerHead, // With predictProposerHead + GetCanonicialHead = "getCanonicialHead", // Skip getProposerHead + GetProposerHead = "getProposerHead", // With getProposerHead + GetPredictedProposerHead = "getPredictedProposerHead", // With predictProposerHead } export type UpdateAndGetHeadOpt = diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index 546620a37134..bbd8a287f57f 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -44,18 +44,18 @@ export type AncestorResult = // Reason for not proposer boost reorging export enum NotReorgedReason { - HeadBlockIsTimely = 0, - ParentBlockNotAvailable = 1, - ProposerBoostReorgDisabled = 2, - NotShufflingStable = 3, - NotFFGCompetitive = 4, - ChainLongUnfinality = 5, - ParentBlockDistanceMoreThanOneSlot = 6, - ReorgMoreThanOneSlot = 7, - ProposerBoostNotWornOff = 8, - HeadBlockNotWeak = 9, - ParentBlockIsStrong = 10, - NotProposingOnTime = 11, + HeadBlockIsTimely = "headBlockIsTimely", + ParentBlockNotAvailable = "parentBlockNotAvailable", + ProposerBoostReorgDisabled = "proposerBoostReorgDisabled", + NotShufflingStable = "notShufflingStable", + NotFFGCompetitive = "notFFGCompetitive", + ChainLongUnfinality = "chainLongUnfinality", + ParentBlockDistanceMoreThanOneSlot = "parentBlockDistanceMoreThanOneSlot", + ReorgMoreThanOneSlot = "reorgMoreThanOneSlot", + ProposerBoostNotWornOff = "proposerBoostNotWornOff", + HeadBlockNotWeak = "headBlockNotWeak", + ParentBlockIsStrong = "parentBlockIsStrong", + NotProposingOnTime = "notProposingOnTime", } export type ForkChoiceMetrics = { @@ -93,7 +93,6 @@ export interface IForkChoice { */ getHeadRoot(): RootHex; getHead(): ProtoBlock; - updateHead(): ProtoBlock; updateAndGetHead(mode: UpdateAndGetHeadOpt): { head: ProtoBlock; isHeadTimely?: boolean; From 2f23cd7a480265cfb3c8ffa3a744a0b5c7c99fdf Mon Sep 17 00:00:00 2001 From: NC Date: Wed, 5 Jun 2024 14:44:22 +0200 Subject: [PATCH 07/17] Update packages/beacon-node/src/metrics/metrics/beacon.ts Co-authored-by: Nico Flaig --- packages/beacon-node/src/metrics/metrics/beacon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index d34c03ff1878..706f10fedae1 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -114,7 +114,7 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { help: "Current count of indices in fork choice data structures", }), notReorgedReason: register.gauge<{reason: NotReorgedReason}>({ - name: "beacon_fork_choice_not_reorged_reason", + name: "beacon_fork_choice_not_reorged_reason_total", help: "Reason why the current head is not re-orged out", labelNames: ["reason"], }), From 06f9517980f70b6cbd96f602b7564fca4818c44a Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 11:39:51 +0200 Subject: [PATCH 08/17] Compute hash treet root of updatedPrepareState --- .../beacon-node/src/chain/prepareNextSlot.ts | 23 ++++++++++++------- .../beacon-node/src/metrics/metrics/beacon.ts | 4 ++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index ab89734e08b3..8bb3764648d9 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -4,6 +4,7 @@ import { computeTimeAtSlot, CachedBeaconStateExecutions, StateHashTreeRootSource, + CachedBeaconStateAllForks, } from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {ForkSeq, SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params"; @@ -114,14 +115,6 @@ export class PrepareNextSlotScheduler { RegenCaller.precomputeEpoch ); - // cache HashObjects for faster hashTreeRoot() later, especially for computeNewStateRoot() if we need to produce a block at slot 0 of epoch - // see https://github.com/ChainSafe/lodestar/issues/6194 - const hashTreeRootTimer = this.metrics?.stateHashTreeRootTime.startTimer({ - source: StateHashTreeRootSource.prepareNextSlot, - }); - prepareState.hashTreeRoot(); - hashTreeRootTimer?.(); - // assuming there is no reorg, it caches the checkpoint state & helps avoid doing a full state transition in the next slot // + when gossip block comes, we need to validate and run state transition // + if next slot is a skipped slot, it'd help getting target checkpoint state faster to validate attestations @@ -159,6 +152,7 @@ export class PrepareNextSlotScheduler { proposerHeadRoot, }); this.metrics?.weakHeadDetected.inc(); + // TODO: figure out how to call regen.getBlockSlotState() only once in the case of proposing next slot updatedPrepareState = (await this.chain.regen.getBlockSlotState( proposerHeadRoot, prepareSlot, @@ -167,6 +161,7 @@ export class PrepareNextSlotScheduler { )) as CachedBeaconStateExecutions; updatedHeadRoot = proposerHeadRoot; } + this.computeStateHashTreeRoot(updatedPrepareState); // Update the builder status, if enabled shoot an api call to check status this.chain.updateBuilderStatus(clockSlot); @@ -215,6 +210,8 @@ export class PrepareNextSlotScheduler { }); this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork}); } + } else { + this.computeStateHashTreeRoot(prepareState); } } catch (e) { if (!isErrorAborted(e) && !isQueueErrorAborted(e)) { @@ -223,4 +220,14 @@ export class PrepareNextSlotScheduler { } } }; + + computeStateHashTreeRoot(state: CachedBeaconStateAllForks): void { + // cache HashObjects for faster hashTreeRoot() later, especially for computeNewStateRoot() if we need to produce a block at slot 0 of epoch + // see https://github.com/ChainSafe/lodestar/issues/6194 + const hashTreeRootTimer = this.metrics?.stateHashTreeRootTime.startTimer({ + source: StateHashTreeRootSource.prepareNextSlot, + }); + state.hashTreeRoot(); + hashTreeRootTimer?.(); + } } diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 706f10fedae1..141121de9079 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -1,8 +1,8 @@ import {ProducedBlockSource} from "@lodestar/types"; import {NotReorgedReason} from "@lodestar/fork-choice/lib/forkChoice/interface.js"; +import {UpdateHeadOpt} from "@lodestar/fork-choice"; import {RegistryMetricCreator} from "../utils/registryMetricCreator.js"; import {BlockProductionStep, PayloadPreparationType} from "../../chain/produceBlock/index.js"; -import { UpdateHeadOpt } from "@lodestar/fork-choice"; export type BeaconMetrics = ReturnType; @@ -72,7 +72,7 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { errors: register.gauge<{entrypoint: UpdateHeadOpt}>({ name: "beacon_fork_choice_errors_total", help: "Count of occasions where fork choice has returned an error when trying to find a head", - labelNames: ["entrypoint"] + labelNames: ["entrypoint"], }), changedHead: register.gauge({ name: "beacon_fork_choice_changed_head_total", From cfc859176db0befbe748b9dc71fbb0c65e531948 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 12:02:11 +0200 Subject: [PATCH 09/17] computeStateHashTreeRoot after prepareExecutionPayload --- packages/beacon-node/src/chain/prepareNextSlot.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 8bb3764648d9..f7f85f9acbb7 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -161,7 +161,6 @@ export class PrepareNextSlotScheduler { )) as CachedBeaconStateExecutions; updatedHeadRoot = proposerHeadRoot; } - this.computeStateHashTreeRoot(updatedPrepareState); // Update the builder status, if enabled shoot an api call to check status this.chain.updateBuilderStatus(clockSlot); @@ -198,6 +197,8 @@ export class PrepareNextSlotScheduler { }); } + this.computeStateHashTreeRoot(updatedPrepareState); + // If emitPayloadAttributes is true emit a SSE payloadAttributes event if (this.chain.opts.emitPayloadAttributes === true) { const data = await getPayloadAttributesForSSE(fork as ForkExecution, this.chain, { From 8d0171f359f938d559ce1109f76b775503a3143b Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 12:43:47 +0200 Subject: [PATCH 10/17] fix build issue --- packages/beacon-node/src/chain/prepareNextSlot.ts | 1 - packages/fork-choice/src/forkChoice/forkChoice.ts | 2 +- .../fork-choice/test/unit/forkChoice/getProposerHead.test.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index f7f85f9acbb7..fddbc9026fca 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -152,7 +152,6 @@ export class PrepareNextSlotScheduler { proposerHeadRoot, }); this.metrics?.weakHeadDetected.inc(); - // TODO: figure out how to call regen.getBlockSlotState() only once in the case of proposing next slot updatedPrepareState = (await this.chain.regen.getBlockSlotState( proposerHeadRoot, prepareSlot, diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 77efba7bc5b2..9ca12c52c6ba 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -328,7 +328,7 @@ export class ForkChoice implements IForkChoice { const parentNode = this.protoArray.getNode(parentBlock.blockRoot); // If parentNode is unavailable, give up reorg if (parentNode === undefined || parentNode.weight <= parentThreshold) { - return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockNotStrong}; + return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockIsStrong}; } // Reorg if all above checks fail diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index e154c1819863..672cbb01e278 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -193,7 +193,7 @@ describe("Forkchoice / GetProposerHead", function () { parentBlock: {...baseParentHeadBlock, weight: 211}, headBlock: {...baseHeadBlock}, expectReorg: false, - expectedNotReorgedReason: NotReorgedReason.ParentBlockNotStrong, + expectedNotReorgedReason: NotReorgedReason.ParentBlockIsStrong, }, { id: "No reorg if not proposing on time", From 2535519704a9f4e2e847b9ac7f7611ea97389d87 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 13:12:40 +0200 Subject: [PATCH 11/17] Fix spec test --- packages/beacon-node/test/spec/presets/fork_choice.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 53793b767cb4..52f1da894635 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -97,6 +97,8 @@ const forkChoiceTest = // we don't use these in fork choice spec tests disablePrepareNextSlot: true, assertCorrectProgressiveBalances, + proposerBoostEnabled: true, + proposerBoostReorgEnabled: true, }, { config: createBeaconConfig(config, state.genesisValidatorsRoot), @@ -312,7 +314,7 @@ const forkChoiceTest = tickTime % config.SECONDS_PER_SLOT, currentSlot ); - logger.debug(`Not reorged reason ${notReorgedReason} at step ${i}`); + console.log(`Not reorged reason ${notReorgedReason} at step ${i}`); expect(proposerHead.blockRoot).toEqualWithMessage( step.checks.get_proposer_head, `Invalid proposer head at step ${i}` From 3fe01854e22d03cf2fc626c3a3d49707f4d40599 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 14:04:28 +0200 Subject: [PATCH 12/17] lint --- packages/beacon-node/test/spec/presets/fork_choice.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 52f1da894635..45a397b5b542 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -314,7 +314,7 @@ const forkChoiceTest = tickTime % config.SECONDS_PER_SLOT, currentSlot ); - console.log(`Not reorged reason ${notReorgedReason} at step ${i}`); + logger.debug(`Not reorged reason ${notReorgedReason} at step ${i}`); expect(proposerHead.blockRoot).toEqualWithMessage( step.checks.get_proposer_head, `Invalid proposer head at step ${i}` From 3b7a87cf9ec17459ab58dab250aa2dd477006037 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 17:27:47 +0200 Subject: [PATCH 13/17] Remove Enabled suffix --- packages/beacon-node/src/chain/options.ts | 4 ++-- .../test/e2e/chain/proposerBoostReorg.test.ts | 6 +++--- .../chain/stateCache/nHistoricalStates.test.ts | 4 ++-- .../chain/produceBlock/produceBlockBody.test.ts | 4 ++-- .../test/perf/chain/verifyImportBlocks.test.ts | 4 ++-- .../test/spec/presets/fork_choice.test.ts | 4 ++-- .../cli/src/options/beaconNodeOptions/chain.ts | 16 ++++++++-------- .../test/unit/options/beaconNodeOptions.test.ts | 8 ++++---- .../fork-choice/src/forkChoice/forkChoice.ts | 12 ++++++------ .../test/unit/forkChoice/getProposerHead.test.ts | 4 ++-- 10 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index eacc767a63fc..7c7cfcdde75b 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -95,8 +95,8 @@ export const defaultChainOptions: IChainOptions = { blsVerifyAllMainThread: false, blsVerifyAllMultiThread: false, disableBlsBatchVerify: false, - proposerBoostEnabled: true, - proposerBoostReorgEnabled: false, + proposerBoost: true, + proposerBoostReorg: false, computeUnrealized: true, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts index 2a79cc3a4906..145f378935fe 100644 --- a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -37,7 +37,7 @@ describe( }); const reorgSlot = 10; - const proposerBoostReorgEnabled = true; + const proposerBoostReorg = true; /** * reorgSlot * / @@ -69,8 +69,8 @@ describe( chain: { blsVerifyAllMainThread: true, forkchoiceConstructor: TimelinessForkChoice, - proposerBoostEnabled: true, - proposerBoostReorgEnabled, + proposerBoost: true, + proposerBoostReorg, }, }, validatorCount, diff --git a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts index 7de3f14435e9..170081e4dcd8 100644 --- a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts +++ b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts @@ -295,7 +295,7 @@ describe( chain: { blsVerifyAllMainThread: true, forkchoiceConstructor: ReorgedForkChoice, - proposerBoostEnabled: true, + proposerBoost: true, }, }, validatorCount, @@ -318,7 +318,7 @@ describe( nHistoricalStates: true, maxBlockStates, maxCPStateEpochsInMemory, - proposerBoostEnabled: true, + proposerBoost: true, }, metrics: {enabled: true}, }, 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 56132cedd790..7bf8c2f7252f 100644 --- a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts +++ b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts @@ -28,8 +28,8 @@ describe("produceBlockBody", () => { state = stateOg.clone(); chain = new BeaconChain( { - proposerBoostEnabled: true, - proposerBoostReorgEnabled: false, + proposerBoost: true, + proposerBoostReorg: false, computeUnrealized: false, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index c9b22667fc5a..19d33072bd7b 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -84,8 +84,8 @@ describe.skip("verify+import blocks - range sync perf test", () => { const state = stateOg.value.clone(); const chain = new BeaconChain( { - proposerBoostEnabled: true, - proposerBoostReorgEnabled: false, + proposerBoost: true, + proposerBoostReorg: false, computeUnrealized: false, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, 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 45a397b5b542..3b5d83bbeb95 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -97,8 +97,8 @@ const forkChoiceTest = // we don't use these in fork choice spec tests disablePrepareNextSlot: true, assertCorrectProgressiveBalances, - proposerBoostEnabled: true, - proposerBoostReorgEnabled: true, + proposerBoost: true, + proposerBoostReorg: true, }, { config: createBeaconConfig(config, state.genesisValidatorsRoot), diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 7bdaedde25ee..5fa794fffc1d 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -12,8 +12,8 @@ export type ChainArgs = { // No need to define chain.persistInvalidSszObjects as part of ChainArgs // as this is defined as part of BeaconPaths // "chain.persistInvalidSszObjectsDir": string; - "chain.proposerBoostEnabled"?: boolean; - "chain.proposerBoostReorgEnabled"?: boolean; + "chain.proposerBoost"?: boolean; + "chain.proposerBoostReorg"?: boolean; "chain.disableImportExecutionFcU"?: boolean; "chain.preaggregateSlotDistance"?: number; "chain.attDataCacheSlotDistance"?: number; @@ -44,8 +44,8 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { persistInvalidSszObjects: args["chain.persistInvalidSszObjects"], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any persistInvalidSszObjectsDir: undefined as any, - proposerBoostEnabled: args["chain.proposerBoostEnabled"], - proposerBoostReorgEnabled: args["chain.proposerBoostReorgEnabled"], + proposerBoost: args["chain.proposerBoost"], + proposerBoostReorg: args["chain.proposerBoostReorg"], disableImportExecutionFcU: args["chain.disableImportExecutionFcU"], preaggregateSlotDistance: args["chain.preaggregateSlotDistance"], attDataCacheSlotDistance: args["chain.attDataCacheSlotDistance"], @@ -125,19 +125,19 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, - "chain.proposerBoostEnabled": { + "chain.proposerBoost": { hidden: true, type: "boolean", description: "Enable proposer boost to reward a timely block", - defaultDescription: String(defaultOptions.chain.proposerBoostEnabled), + defaultDescription: String(defaultOptions.chain.proposerBoost), group: "chain", }, - "chain.proposerBoostReorgEnabled": { + "chain.proposerBoostReorg": { hidden: true, type: "boolean", description: "Enable proposer boost reorg to reorg out a late block", - defaultDescription: String(defaultOptions.chain.proposerBoostReorgEnabled), + defaultDescription: String(defaultOptions.chain.proposerBoostReorg), group: "chain", }, diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index 193a532e4f9c..d74ae73b966f 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -23,8 +23,8 @@ describe("options / beaconNodeOptions", () => { "chain.disableBlsBatchVerify": true, "chain.persistProducedBlocks": true, "chain.persistInvalidSszObjects": true, - "chain.proposerBoostEnabled": false, - "chain.proposerBoostReorgEnabled": false, + "chain.proposerBoost": false, + "chain.proposerBoostReorg": false, "chain.disableImportExecutionFcU": false, "chain.preaggregateSlotDistance": 1, "chain.attDataCacheSlotDistance": 2, @@ -130,8 +130,8 @@ describe("options / beaconNodeOptions", () => { disableBlsBatchVerify: true, persistProducedBlocks: true, persistInvalidSszObjects: true, - proposerBoostEnabled: false, - proposerBoostReorgEnabled: false, + proposerBoost: false, + proposerBoostReorg: false, disableImportExecutionFcU: false, preaggregateSlotDistance: 1, attDataCacheSlotDistance: 2, diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 9ca12c52c6ba..e83a61621922 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -45,8 +45,8 @@ import { import {IForkChoiceStore, CheckpointWithHex, toCheckpointWithHex, JustifiedBalances} from "./store.js"; export type ForkChoiceOpts = { - proposerBoostEnabled?: boolean; - proposerBoostReorgEnabled?: boolean; + proposerBoost?: boolean; + proposerBoostReorg?: boolean; computeUnrealized?: boolean; }; @@ -223,7 +223,7 @@ export class ForkChoice implements IForkChoice { */ predictProposerHead(headBlock: ProtoBlock, currentSlot?: Slot): ProtoBlock { // Skip re-org attempt if proposer boost (reorg) are disabled - if (!this.opts?.proposerBoostEnabled || !this.opts?.proposerBoostReorgEnabled) { + if (!this.opts?.proposerBoost || !this.opts?.proposerBoostReorg) { this.logger?.verbose("No proposer boot reorg prediction since the related flags are disabled"); return headBlock; } @@ -269,7 +269,7 @@ export class ForkChoice implements IForkChoice { let proposerHead = headBlock; // Skip re-org attempt if proposer boost (reorg) are disabled - if (!this.opts?.proposerBoostEnabled || !this.opts?.proposerBoostReorgEnabled) { + if (!this.opts?.proposerBoost || !this.opts?.proposerBoostReorg) { this.logger?.verbose("No proposer boot reorg attempt since the related flags are disabled"); return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ProposerBoostReorgDisabled}; } @@ -375,7 +375,7 @@ export class ForkChoice implements IForkChoice { * starting from the proposerIndex */ let proposerBoost: {root: RootHex; score: number} | null = null; - if (this.opts?.proposerBoostEnabled && this.proposerBoostRoot) { + if (this.opts?.proposerBoost && this.proposerBoostRoot) { const proposerBoostScore = this.justifiedProposerBoostScore ?? getCommitteeFraction(this.fcStore.justified.totalBalance, { @@ -533,7 +533,7 @@ export class ForkChoice implements IForkChoice { // before attesting interval = before 1st interval const isTimely = this.isBlockTimely(block, blockDelaySec); if ( - this.opts?.proposerBoostEnabled && + this.opts?.proposerBoost && isTimely && // only boost the first block we see this.proposerBoostRoot === null diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index 672cbb01e278..f1927f7e8202 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -235,8 +235,8 @@ describe("Forkchoice / GetProposerHead", function () { }); const forkChoice = new ForkChoice(config, fcStore, protoArr, { - proposerBoostEnabled: true, - proposerBoostReorgEnabled: true, + proposerBoost: true, + proposerBoostReorg: true, }); const {proposerHead, isHeadTimely, notReorgedReason} = forkChoice.getProposerHead( From c9a15919e18f14ff3aa3fdd723709d0ead1f8dd0 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 17:30:55 +0200 Subject: [PATCH 14/17] Fix merge --- packages/fork-choice/src/forkChoice/forkChoice.ts | 2 +- packages/fork-choice/src/forkChoice/interface.ts | 2 +- .../fork-choice/test/unit/forkChoice/getProposerHead.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index e83a61621922..b2e1a5314012 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -328,7 +328,7 @@ export class ForkChoice implements IForkChoice { const parentNode = this.protoArray.getNode(parentBlock.blockRoot); // If parentNode is unavailable, give up reorg if (parentNode === undefined || parentNode.weight <= parentThreshold) { - return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockIsStrong}; + return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ParentBlockNotStrong}; } // Reorg if all above checks fail diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index bbd8a287f57f..d91a338bbca5 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -54,7 +54,7 @@ export enum NotReorgedReason { ReorgMoreThanOneSlot = "reorgMoreThanOneSlot", ProposerBoostNotWornOff = "proposerBoostNotWornOff", HeadBlockNotWeak = "headBlockNotWeak", - ParentBlockIsStrong = "parentBlockIsStrong", + ParentBlockNotStrong = "ParentBlockNotStrong", NotProposingOnTime = "notProposingOnTime", } diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index f1927f7e8202..25d539a5e33b 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -193,7 +193,7 @@ describe("Forkchoice / GetProposerHead", function () { parentBlock: {...baseParentHeadBlock, weight: 211}, headBlock: {...baseHeadBlock}, expectReorg: false, - expectedNotReorgedReason: NotReorgedReason.ParentBlockIsStrong, + expectedNotReorgedReason: NotReorgedReason.ParentBlockNotStrong, }, { id: "No reorg if not proposing on time", From 30e8e20055435a33119c0600bef161f66ec92951 Mon Sep 17 00:00:00 2001 From: Navie Chan Date: Thu, 6 Jun 2024 18:03:04 +0200 Subject: [PATCH 15/17] Add alias --- packages/cli/src/options/beaconNodeOptions/chain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 5fa794fffc1d..bc744c738bdf 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -126,6 +126,7 @@ Will double processing times. Use only for debugging purposes.", }, "chain.proposerBoost": { + alias: "proposerBoostEnabled", hidden: true, type: "boolean", description: "Enable proposer boost to reward a timely block", From c5b187bfb7315446771359a56fc2b25cd310f132 Mon Sep 17 00:00:00 2001 From: NC Date: Thu, 6 Jun 2024 18:15:06 +0200 Subject: [PATCH 16/17] Update packages/cli/src/options/beaconNodeOptions/chain.ts Co-authored-by: Nico Flaig --- packages/cli/src/options/beaconNodeOptions/chain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index bc744c738bdf..aae97b6db68f 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -126,7 +126,7 @@ Will double processing times. Use only for debugging purposes.", }, "chain.proposerBoost": { - alias: "proposerBoostEnabled", + alias: ["chain.proposerBoostEnabled"], hidden: true, type: "boolean", description: "Enable proposer boost to reward a timely block", From 69d56c24bf94acaaf03f9b84a91c563db13431c3 Mon Sep 17 00:00:00 2001 From: Tuyen Nguyen Date: Fri, 7 Jun 2024 09:02:30 +0700 Subject: [PATCH 17/17] chore: add predictProposerHead regen enum --- packages/beacon-node/src/chain/prepareNextSlot.ts | 4 +++- packages/beacon-node/src/chain/regen/interface.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index fddbc9026fca..3f730df3bf1d 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -150,13 +150,15 @@ export class PrepareNextSlotScheduler { this.logger.verbose("Weak head detected. May build on this block instead:", { proposerHeadSlot, proposerHeadRoot, + headSlot, + headRoot, }); this.metrics?.weakHeadDetected.inc(); updatedPrepareState = (await this.chain.regen.getBlockSlotState( proposerHeadRoot, prepareSlot, {dontTransferCache: !isEpochTransition}, - RegenCaller.precomputeEpoch + RegenCaller.predictProposerHead )) as CachedBeaconStateExecutions; updatedHeadRoot = proposerHeadRoot; } diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index 650d92143a8e..a1021de4aeab 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -11,6 +11,7 @@ export enum RegenCaller { validateGossipBlock = "validateGossipBlock", validateGossipBlob = "validateGossipBlob", precomputeEpoch = "precomputeEpoch", + predictProposerHead = "predictProposerHead", produceAttestationData = "produceAttestationData", processBlocksInEpoch = "processBlocksInEpoch", validateGossipAggregateAndProof = "validateGossipAggregateAndProof",