diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index b54a48accb20..658c86b905ff 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -323,10 +323,20 @@ export function getValidatorApi({ { skipHeadChecksAndUpdate, commonBlockBody, - }: Omit & { - skipHeadChecksAndUpdate?: boolean; - commonBlockBody?: CommonBlockBody; - } = {} + parentBlockRoot: inParentBlockRoot, + }: Omit & + ( + | { + skipHeadChecksAndUpdate: true; + commonBlockBody: CommonBlockBody; + parentBlockRoot: Root; + } + | { + skipHeadChecksAndUpdate?: false | undefined; + commonBlockBody?: undefined; + parentBlockRoot?: undefined; + } + ) = {} ): Promise { const version = config.getForkName(slot); if (!isForkExecution(version)) { @@ -344,6 +354,7 @@ export function getValidatorApi({ throw Error("Execution builder disabled"); } + let parentBlockRoot: Root; if (skipHeadChecksAndUpdate !== true) { notWhileSyncing(); await waitForSlot(slot); // Must never request for a future slot > currentSlot @@ -352,7 +363,9 @@ 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); - chain.recomputeForkChoiceHead(); + parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + } else { + parentBlockRoot = inParentBlockRoot; } let timer; @@ -360,6 +373,7 @@ export function getValidatorApi({ timer = metrics?.blockProductionTime.startTimer(); const {block, executionPayloadValue, consensusBlockValue} = await chain.produceBlindedBlock({ slot, + parentBlockRoot, randaoReveal, graffiti: toGraffitiBuffer(graffiti || ""), commonBlockBody, @@ -393,14 +407,21 @@ export function getValidatorApi({ strictFeeRecipientCheck, skipHeadChecksAndUpdate, commonBlockBody, - }: Omit & { - skipHeadChecksAndUpdate?: boolean; - commonBlockBody?: CommonBlockBody; - } = {} + parentBlockRoot: inParentBlockRoot, + }: Omit & + ( + | { + skipHeadChecksAndUpdate: true; + commonBlockBody: CommonBlockBody; + parentBlockRoot: Root; + } + | {skipHeadChecksAndUpdate?: false | undefined; commonBlockBody?: undefined; parentBlockRoot?: undefined} + ) = {} ): Promise { const source = ProducedBlockSource.engine; metrics?.blockProductionRequests.inc({source}); + let parentBlockRoot: Root; if (skipHeadChecksAndUpdate !== true) { notWhileSyncing(); await waitForSlot(slot); // Must never request for a future slot > currentSlot @@ -409,7 +430,9 @@ 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); - chain.recomputeForkChoiceHead(); + parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + } else { + parentBlockRoot = inParentBlockRoot; } let timer; @@ -417,6 +440,7 @@ export function getValidatorApi({ timer = metrics?.blockProductionTime.startTimer(); const {block, executionPayloadValue, consensusBlockValue, shouldOverrideBuilder} = await chain.produceBlock({ slot, + parentBlockRoot, randaoReveal, graffiti: toGraffitiBuffer(graffiti || ""), feeRecipient, @@ -484,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); - chain.recomputeForkChoiceHead(); + const parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); const fork = config.getForkName(slot); // set some sensible opts @@ -531,6 +555,7 @@ export function getValidatorApi({ logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext); const commonBlockBody = await chain.produceCommonBlockBody({ slot, + parentBlockRoot, randaoReveal, graffiti: toGraffitiBuffer(graffiti || ""), }); @@ -555,6 +580,7 @@ export function getValidatorApi({ // skip checking and recomputing head in these individual produce calls skipHeadChecksAndUpdate: true, commonBlockBody, + parentBlockRoot, }) : Promise.reject(new Error("Builder disabled")); @@ -565,6 +591,7 @@ export function getValidatorApi({ // skip checking and recomputing head in these individual produce calls skipHeadChecksAndUpdate: true, commonBlockBody, + parentBlockRoot, }).then((engineBlock) => { // Once the engine returns a block, in the event of either: // - suspected builder censorship diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 6417c0d5cfde..08e0727a15db 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -516,22 +516,19 @@ export class BeaconChain implements IBeaconChain { } async produceCommonBlockBody(blockAttributes: BlockAttributes): Promise { - const {slot} = blockAttributes; - const head = this.forkChoice.getHead(); + const {slot, parentBlockRoot} = blockAttributes; const state = await this.regen.getBlockSlotState( - head.blockRoot, + toHexString(parentBlockRoot), slot, {dontTransferCache: true}, RegenCaller.produceBlock ); - const parentBlockRoot = fromHexString(head.blockRoot); // TODO: To avoid breaking changes for metric define this attribute const blockType = BlockType.Full; return produceCommonBlockBody.call(this, blockType, state, { ...blockAttributes, - parentBlockRoot, parentSlot: slot - 1, }); } @@ -555,21 +552,26 @@ export class BeaconChain implements IBeaconChain { async produceBlockWrapper( blockType: T, - {randaoReveal, graffiti, slot, feeRecipient, commonBlockBody}: BlockAttributes & {commonBlockBody?: CommonBlockBody} + { + randaoReveal, + graffiti, + slot, + feeRecipient, + commonBlockBody, + parentBlockRoot, + }: BlockAttributes & {commonBlockBody?: CommonBlockBody} ): Promise<{ block: AssembledBlockType; executionPayloadValue: Wei; consensusBlockValue: Wei; shouldOverrideBuilder?: boolean; }> { - const head = this.forkChoice.getHead(); const state = await this.regen.getBlockSlotState( - head.blockRoot, + toHexString(parentBlockRoot), slot, {dontTransferCache: true}, RegenCaller.produceBlock ); - const parentBlockRoot = fromHexString(head.blockRoot); const proposerIndex = state.epochCtx.getBeaconProposer(slot); const proposerPubKey = state.epochCtx.index2pubkey[proposerIndex].toBytes(); diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 7697e89807ea..c1c1d5eaa447 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -64,6 +64,7 @@ export type BlockAttributes = { randaoReveal: BLSSignature; graffiti: Bytes32; slot: Slot; + parentBlockRoot: Root; feeRecipient?: string; }; @@ -95,7 +96,6 @@ export async function produceBlockBody( currentState: CachedBeaconStateAllForks, blockAttr: BlockAttributes & { parentSlot: Slot; - parentBlockRoot: Root; proposerIndex: ValidatorIndex; proposerPubKey: BLSPubkey; commonBlockBody?: CommonBlockBody; @@ -580,7 +580,6 @@ export async function produceCommonBlockBody( parentBlockRoot, }: BlockAttributes & { parentSlot: Slot; - parentBlockRoot: Root; } ): Promise { const stepsMetrics = 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 0c506a8b1425..118048495cc9 100644 --- a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts +++ b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts @@ -249,6 +249,7 @@ describe( // chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted numEpochsPersisted: 4, // chain is NOT finalized end of test + skip: 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 deb148d34b5a..1ec3f738669d 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 @@ -1,4 +1,4 @@ -import {fromHexString} from "@chainsafe/ssz"; +import {fromHexString, toHexString} from "@chainsafe/ssz"; import {describe, it, expect, beforeEach, afterEach, vi} from "vitest"; import {ssz} from "@lodestar/types"; import {ProtoBlock} from "@lodestar/fork-choice"; @@ -43,9 +43,13 @@ describe("api/validator - produceBlockV2", function () { // Set the node's state to way back from current slot const slot = 100000; const randaoReveal = fullBlock.body.randaoReveal; + const parentBlockRoot = fullBlock.parentRoot; const graffiti = "a".repeat(32); const feeRecipient = "0xcccccccccccccccccccccccccccccccccccccccc"; + modules.chain.recomputeForkChoiceHead.mockReturnValue( + generateProtoBlock({blockRoot: toHexString(parentBlockRoot)}) + ); modules.chain.produceBlock.mockResolvedValue({ block: fullBlock, executionPayloadValue, @@ -58,6 +62,7 @@ describe("api/validator - produceBlockV2", function () { randaoReveal, graffiti: toGraffitiBuffer(graffiti), slot, + parentBlockRoot, feeRecipient, }); @@ -68,6 +73,7 @@ describe("api/validator - produceBlockV2", function () { randaoReveal, graffiti: toGraffitiBuffer(graffiti), slot, + parentBlockRoot, feeRecipient: undefined, }); }); @@ -83,6 +89,7 @@ describe("api/validator - produceBlockV2", function () { const headSlot = 0; modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: headSlot})); + modules.chain.recomputeForkChoiceHead.mockReturnValue(generateProtoBlock({slot: headSlot})); modules.chain["opPool"].getSlashingsAndExits.mockReturnValue([[], [], [], []]); modules.chain["aggregatedAttestationPool"].getAttestationsForBlock.mockReturnValue([]); modules.chain["eth1"].getEth1DataAndDeposits.mockResolvedValue({ 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 ba0267fc5810..d182cfeb537e 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 @@ -1,8 +1,10 @@ import {describe, it, expect, beforeEach, afterEach, vi} from "vitest"; +import {toHexString} from "@chainsafe/ssz"; import {ssz} from "@lodestar/types"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {routes} from "@lodestar/api"; import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config"; +import {ProtoBlock} from "@lodestar/fork-choice"; import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js"; import {SyncState} from "../../../../../src/sync/interface.js"; import {getValidatorApi} from "../../../../../src/api/impl/validator/index.js"; @@ -82,6 +84,9 @@ describe("api/validator - produceBlockV3", function () { vi.spyOn(modules.chain.clock, "currentSlot", "get").mockReturnValue(currentSlot); vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Synced); + modules.chain.recomputeForkChoiceHead.mockReturnValue({ + blockRoot: toHexString(fullBlock.parentRoot), + } as ProtoBlock); if (enginePayloadValue !== null) { const commonBlockBody: CommonBlockBody = {