Skip to content
5 changes: 3 additions & 2 deletions packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ForkChoiceError,
ForkChoiceErrorCode,
NotReorgedReason,
getFinalizedExecutionBlockHash,
getSafeExecutionBlockHash,
} from "@lodestar/fork-choice";
import {
Expand Down Expand Up @@ -443,8 +444,8 @@ export async function importBlock(
* the current finalized block does not contain any execution payload at all (pre MERGE_EPOCH) or if it contains a
* zero block hash (pre TTD)
*/
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice, this.logger);
if (headBlockHash !== ZERO_HASH_HEX) {
this.executionEngine
.notifyForkchoiceUpdate(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {routes} from "@lodestar/api";
import {ExecutionStatus, PayloadExecutionStatus, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
import {
ExecutionStatus,
PayloadExecutionStatus,
getFinalizedExecutionBlockHash,
getSafeExecutionBlockHash,
} from "@lodestar/fork-choice";
import {DataAvailabilityStatus, isStatePostGloas} from "@lodestar/state-transition";
import {isErrorAborted} from "@lodestar/utils";
import {ZERO_HASH_HEX} from "../../constants/index.js";
import {ExecutionPayloadStatus} from "../../execution/index.js";
import {isQueueErrorAborted} from "../../util/queue/index.js";
import {BeaconChain} from "../chain.js";
Expand Down Expand Up @@ -245,8 +249,8 @@ export async function importExecutionPayload(
// 7. Queue notifyForkchoiceUpdate to engine api
const head = this.forkChoice.getHead();
if (!this.opts.disableImportExecutionFcU && blockRootHex === head.blockRoot) {
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice, this.logger);
this.executionEngine.notifyForkchoiceUpdate(fork, blockHashHex, safeBlockHash, finalizedBlockHash).catch((e) => {
if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
this.logger.error("Error pushing notifyForkchoiceUpdate()", {blockHashHex, finalizedBlockHash}, e);
Expand Down
9 changes: 4 additions & 5 deletions packages/beacon-node/src/chain/prepareNextSlot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {routes} from "@lodestar/api";
import {ChainForkConfig} from "@lodestar/config";
import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
import {getFinalizedExecutionBlockHash, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
import {
ForkPostBellatrix,
ForkSeq,
Expand All @@ -23,7 +23,7 @@ import {
} from "@lodestar/state-transition";
import {Bytes32, Slot, ValidatorIndex} from "@lodestar/types";
import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
import {GENESIS_SLOT, ZERO_HASH_HEX} from "../constants/constants.js";
import {GENESIS_SLOT} from "../constants/constants.js";
import {BuilderStatus} from "../execution/builder/http.js";
import {Metrics} from "../metrics/index.js";
import {ClockEvent} from "../util/clock.js";
Expand Down Expand Up @@ -223,9 +223,8 @@ export class PrepareNextSlotScheduler {
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);

const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice);
const finalizedBlockHash =
this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice, this.logger);

// awaiting here instead of throwing an async call because there is no other task
// left for scheduler and this gives nice semantics to catch and log errors in the
Expand Down
15 changes: 10 additions & 5 deletions packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {BitArray} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {IForkChoice, ProtoBlock, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
import {
IForkChoice,
ProtoBlock,
getFinalizedExecutionBlockHash,
getSafeExecutionBlockHash,
} from "@lodestar/fork-choice";
import {
BUILDER_INDEX_SELF_BUILD,
ForkName,
Expand Down Expand Up @@ -265,8 +270,8 @@ export async function produceBlockBody<T extends BlockType>(
// TODO GLOAS: support non self-building here, the block type differentiation between
// full and blinded no longer makes sense in gloas, it might be a good idea to move
// this into a completely separate function and have pre/post gloas more separated
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice, this.logger);
// TODO GLOAS: post-Gloas, proposer feeRecipient is also carried (signed) in
// ProposerPreferencesPool. Consider using this unified cache instead
// see https://github.com/ChainSafe/lodestar/issues/9379
Expand Down Expand Up @@ -411,8 +416,8 @@ export async function produceBlockBody<T extends BlockType>(
throw new Error("Expected Bellatrix state for execution block production");
}

const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
const finalizedBlockHash = this.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice, this.logger);
const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex);
const feeRecipientType = requestedFeeRecipient
? "requested"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,8 +314,8 @@ describe("api/validator - produceBlockV3", () => {
syncCommitteeBits: ssz.altair.SyncCommitteeBits.defaultValue(),
syncCommitteeSignature: G2_POINT_AT_INFINITY,
});
modules.forkChoice.getJustifiedBlock.mockReturnValue({} as ProtoBlock);
modules.forkChoice.getFinalizedBlock.mockReturnValue({} as ProtoBlock);
modules.forkChoice.getConfirmedBlock.mockReturnValue(generateProtoBlock());
modules.forkChoice.getFinalizedBlock.mockReturnValue(generateProtoBlock());

modules.chain["executionEngine"].payloadIdCache = new PayloadIdCache();
modules.chain["executionEngine"].notifyForkchoiceUpdate.mockResolvedValue("0x");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ describe("PrepareNextSlot scheduler", () => {
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.getFinalizedBlock.mockReturnValue({} as ProtoBlock);
forkChoiceStub.getConfirmedBlock.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock);
forkChoiceStub.getFinalizedBlock.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock);
updateBuilderStatus.mockReturnValue(void 0);
const state = generateCachedBellatrixState();
vi.spyOn(state.epochCtx, "getBeaconProposer").mockReturnValue(proposerIndex);
Expand Down
4 changes: 3 additions & 1 deletion packages/beacon-node/test/utils/typeGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice";
import {ExecutionStatus, PayloadStatus, ProtoBlock} from "@lodestar/fork-choice";
import {DataAvailabilityStatus} from "@lodestar/state-transition";
import {Slot, phase0, ssz} from "@lodestar/types";
import {fromHex} from "@lodestar/utils";
Expand Down Expand Up @@ -40,6 +40,8 @@ export function generateProtoBlock(overrides: Partial<ProtoBlock> = {}): ProtoBl
unrealizedFinalizedRoot: ZERO_HASH_HEX,

timeliness: false,
payloadStatus: PayloadStatus.FULL,
parentBlockHash: null,

...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge},
dataAvailabilityStatus: DataAvailabilityStatus.PreData,
Expand Down
64 changes: 52 additions & 12 deletions packages/fork-choice/src/forkChoice/safeBlocks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ZERO_HASH_HEX} from "@lodestar/params";
import {GENESIS_SLOT, ZERO_HASH_HEX} from "@lodestar/params";
import {Root, RootHex} from "@lodestar/types";
import {fromHex} from "@lodestar/utils";
import {LogLevel, Logger, fromHex} from "@lodestar/utils";
import {HEX_ZERO_HASH, ProtoBlock, isGloasBlock} from "../protoArray/interface.js";
import {IForkChoice} from "./interface.js";

/**
Expand All @@ -19,17 +20,56 @@ export function getSafeBeaconBlockRoot(fc: IForkChoice): Root {
}

/**
* Get execution payload hash for the safe block
* Get execution payload hash to report as `safeBlockHash` in `engine_forkchoiceUpdated`.
*
* https://github.com/ethereum/consensus-specs/blob/master/fork_choice/safe-block.md#get_safe_execution_block_hash
* Pre-Gloas: the confirmed block's own payload hash.
* Post-Gloas: the confirmed block's bid `parent_block_hash` — under ePBS the block's own
* payload may not yet be confirmed canonical, so we report the parent EL block which has
* been (the bid commits to extending it).
*
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/bellatrix/fast-confirmation.md#new-get_safe_execution_block_hash
*/
export function getSafeExecutionBlockHash(forkChoice: IForkChoice): RootHex {
const confirmedRoot = forkChoice.getConfirmedRoot();
if (confirmedRoot) {
const confirmedBlock = forkChoice.getBlockHexDefaultStatus(confirmedRoot);
if (confirmedBlock?.executionPayloadBlockHash) {
return confirmedBlock.executionPayloadBlockHash;
}
export function getSafeExecutionBlockHash(forkChoice: IForkChoice, logger?: Pick<Logger, LogLevel.warn>): RootHex {
const confirmedBlock = forkChoice.getConfirmedBlock();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to log when confirmed block is the finalized block

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added in #9794

if (confirmedBlock === null) {
logger?.warn("Confirmed block not found, using zero safe execution block hash", {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should throw error instead
this should not happen on any cases, even when FCR is enabled is disabled
otherwise, we'll have other issues in downstream flows, and the CL <-> EL investigate always take time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with this, had same thought when reviewing this pr, did another quick audit of code and getConfirmedBlock() returns this.fcStore.justified.checkpoint.rootHex so that should always be there, also addressed in #9794

confirmedRoot: forkChoice.getConfirmedRoot(),
});
return ZERO_HASH_HEX;
}
return ZERO_HASH_HEX;

return getExecutionBlockHash(confirmedBlock, logger);
}

/**
* Get execution payload hash to report as `finalizedBlockHash` in `engine_forkchoiceUpdated`.
* Mirrors `getSafeExecutionBlockHash`: post-Gloas returns the bid `parent_block_hash`.
*
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/gloas/fork-choice.md#notify_forkchoice_updated
*/
export function getFinalizedExecutionBlockHash(forkChoice: IForkChoice, logger?: Pick<Logger, LogLevel.warn>): RootHex {
return getExecutionBlockHash(forkChoice.getFinalizedBlock(), logger);
}

function getExecutionBlockHash(block: ProtoBlock, logger?: Pick<Logger, LogLevel.warn>): RootHex {
if (isGloasBlock(block)) {
return block.parentBlockHash;
}

// The genesis block body carries a default execution payload, so it is not an execution block
// per the spec (`is_execution_block`); its proto-array payload hash comes from the anchor
// state header instead of a block body and must not be reported as safe.
if (block.slot === GENESIS_SLOT) {
return HEX_ZERO_HASH;
}

if (block.executionPayloadBlockHash === null) {
logger?.warn("Execution payload block hash not found, using zero hash", {
blockRoot: block.blockRoot,
slot: block.slot,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same to above, I think we should throw error instead
the only case executionPayloadBlockHash could be null is when pre-bellatrix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also updated in #9794

});
return HEX_ZERO_HASH;
}

return block.executionPayloadBlockHash;
}
2 changes: 1 addition & 1 deletion packages/fork-choice/src/protoArray/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export enum PayloadStatus {
/**
* Check if a block is in the Gloas fork (ePBS enabled)
*/
export function isGloasBlock(block: ProtoBlock): boolean {
export function isGloasBlock(block: ProtoBlock): block is ProtoBlock & {parentBlockHash: RootHex} {
return block.parentBlockHash !== null;
}

Expand Down
Loading
Loading