diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index 1f6fc125a44a..ccfc917421b2 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -293,7 +293,13 @@ export async function verifyBlockExecutionPayload( ForkSeq[fork] >= ForkSeq.deneb ? (block.message.body as deneb.BeaconBlockBody).blobKzgCommitments.map(kzgCommitmentToVersionedHash) : undefined; - const execResult = await chain.executionEngine.notifyNewPayload(fork, executionPayloadEnabled, versionedHashes); + const parentBlockRoot = ForkSeq[fork] >= ForkSeq.deneb ? block.message.parentRoot : undefined; + const execResult = await chain.executionEngine.notifyNewPayload( + fork, + executionPayloadEnabled, + versionedHashes, + parentBlockRoot + ); chain.metrics?.engineNotifyNewPayloadResult.inc({result: execResult.status}); diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index e15d159f43ba..38a97efd38a2 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -147,6 +147,7 @@ export class PrepareNextSlotScheduler { this.chain, this.logger, fork as ForkExecution, // State is of execution type + fromHex(headRoot), safeBlockHash, finalizedBlockHash, prepareState, diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 521c80bdaa23..e8a76ffc64b1 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -28,7 +28,7 @@ import { } from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {ForkSeq, ForkExecution, isForkExecution} from "@lodestar/params"; -import {toHex, sleep, Logger, fromHex} from "@lodestar/utils"; +import {toHex, sleep, Logger} from "@lodestar/utils"; import type {BeaconChain} from "../chain.js"; import {PayloadId, IExecutionEngine, IExecutionBuilder, PayloadAttributes} from "../../execution/index.js"; @@ -154,6 +154,7 @@ export async function produceBlockBody( this, this.logger, fork, + parentBlockRoot, safeBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX, currentState as CachedBeaconStateBellatrix, @@ -196,6 +197,7 @@ export async function produceBlockBody( this, this.logger, fork, + parentBlockRoot, safeBlockHash, finalizedBlockHash ?? ZERO_HASH_HEX, currentState as CachedBeaconStateExecutions, @@ -316,6 +318,7 @@ export async function prepareExecutionPayload( }, logger: Logger, fork: ForkExecution, + parentBlockRoot: Root, safeBlockHash: RootHex, finalizedBlockHash: RootHex, state: CachedBeaconStateExecutions, @@ -357,15 +360,12 @@ export async function prepareExecutionPayload( prepType = PayloadPreparationType.Fresh; } - const attributes: PayloadAttributes = { - timestamp, - prevRandao, - suggestedFeeRecipient, - }; - - if (ForkSeq[fork] >= ForkSeq.capella) { - attributes.withdrawals = getExpectedWithdrawals(state as CachedBeaconStateCapella).withdrawals; - } + const attributes: PayloadAttributes = preparePayloadAttributes(fork, chain, { + prepareState: state, + prepareSlot: state.slot, + parentBlockRoot, + feeRecipient: suggestedFeeRecipient, + }); payloadId = await chain.executionEngine.notifyForkchoiceUpdate( fork, @@ -469,19 +469,12 @@ export async function getPayloadAttributesForSSE( if (!parentHashRes.isPremerge) { const {parentHash} = parentHashRes; - const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime); - const prevRandao = getRandaoMix(prepareState, prepareState.epochCtx.epoch); - const payloadAttributes = { - timestamp, - prevRandao, - suggestedFeeRecipient: fromHex(feeRecipient), - }; - - if (ForkSeq[fork] >= ForkSeq.capella) { - (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( - prepareState as CachedBeaconStateCapella - ).withdrawals; - } + const payloadAttributes = preparePayloadAttributes(fork, chain, { + prepareState, + prepareSlot, + parentBlockRoot, + feeRecipient, + }); const ssePayloadAttributes: allForks.SSEPayloadAttributes = { proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot), @@ -497,4 +490,42 @@ export async function getPayloadAttributesForSSE( } } +function preparePayloadAttributes( + fork: ForkExecution, + chain: { + config: ChainForkConfig; + }, + { + prepareState, + prepareSlot, + parentBlockRoot, + feeRecipient, + }: { + prepareState: CachedBeaconStateExecutions; + prepareSlot: Slot; + parentBlockRoot: Root; + feeRecipient: string; + } +): allForks.SSEPayloadAttributes["payloadAttributes"] { + const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime); + const prevRandao = getRandaoMix(prepareState, prepareState.epochCtx.epoch); + const payloadAttributes = { + timestamp, + prevRandao, + suggestedFeeRecipient: feeRecipient, + }; + + if (ForkSeq[fork] >= ForkSeq.capella) { + (payloadAttributes as capella.SSEPayloadAttributes["payloadAttributes"]).withdrawals = getExpectedWithdrawals( + prepareState as CachedBeaconStateCapella + ).withdrawals; + } + + if (ForkSeq[fork] >= ForkSeq.deneb) { + (payloadAttributes as deneb.SSEPayloadAttributes["payloadAttributes"]).parentBeaconBlockRoot = parentBlockRoot; + } + + return payloadAttributes; +} + /** process_sync_committee_contributions is implemented in syncCommitteeContribution.getSyncAggregate */ diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index 11ab396e6cd4..35c54a96418e 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -1,4 +1,4 @@ -import {RootHex, allForks, Wei} from "@lodestar/types"; +import {Root, RootHex, allForks, Wei} from "@lodestar/types"; import {SLOTS_PER_EPOCH, ForkName, ForkSeq} from "@lodestar/params"; import {ErrorJsonRpcResponse, HttpRpcError} from "../../eth1/provider/jsonRpcHttpClient.js"; @@ -25,6 +25,7 @@ import { serializeExecutionPayload, serializeVersionedHashes, serializePayloadAttributes, + serializeBeaconBlockRoot, ExecutionPayloadBody, assertReqSizeLimit, deserializeExecutionPayloadBody, @@ -138,7 +139,8 @@ export class ExecutionEngineHttp implements IExecutionEngine { async notifyNewPayload( fork: ForkName, executionPayload: allForks.ExecutionPayload, - versionedHashes?: VersionedHashes + versionedHashes?: VersionedHashes, + parentBlockRoot?: Root ): Promise { const method = ForkSeq[fork] >= ForkSeq.deneb @@ -148,18 +150,23 @@ export class ExecutionEngineHttp implements IExecutionEngine { : "engine_newPayloadV1"; const serializedExecutionPayload = serializeExecutionPayload(fork, executionPayload); - const serializedVersionedHashes = - versionedHashes !== undefined ? serializeVersionedHashes(versionedHashes) : undefined; let engingRequest: EngineRequest; if (ForkSeq[fork] >= ForkSeq.deneb) { - if (serializedVersionedHashes === undefined) { + if (versionedHashes === undefined) { throw Error(`versionedHashes required in notifyNewPayload for fork=${fork}`); } + if (parentBlockRoot === undefined) { + throw Error(`parentBlockRoot required in notifyNewPayload for fork=${fork}`); + } + + const serializedVersionedHashes = serializeVersionedHashes(versionedHashes); + const parentBeaconBlockRoot = serializeBeaconBlockRoot(parentBlockRoot); + const method = "engine_newPayloadV3"; engingRequest = { method, - params: [serializedExecutionPayload, serializedVersionedHashes], + params: [serializedExecutionPayload, serializedVersionedHashes, parentBeaconBlockRoot], methodOpts: notifyNewPayloadOpts, }; } else { diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index d1bea600f5f3..c77c80a22e54 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -1,6 +1,6 @@ import {ForkName} from "@lodestar/params"; import {KZGCommitment, Blob, KZGProof} from "@lodestar/types/deneb"; -import {RootHex, allForks, capella, Wei} from "@lodestar/types"; +import {Root, RootHex, allForks, capella, Wei} from "@lodestar/types"; import {DATA, QUANTITY} from "../../eth1/provider/utils.js"; import {PayloadIdCache, PayloadId, WithdrawalV1} from "./payloadIdCache.js"; @@ -52,6 +52,7 @@ export type PayloadAttributes = { // avoid any conversions suggestedFeeRecipient: string; withdrawals?: capella.Withdrawal[]; + parentBeaconBlockRoot?: Uint8Array; }; export type TransitionConfigurationV1 = { @@ -92,7 +93,8 @@ export interface IExecutionEngine { notifyNewPayload( fork: ForkName, executionPayload: allForks.ExecutionPayload, - versionedHashes?: VersionedHashes + versionedHashes?: VersionedHashes, + parentBeaconBlockRoot?: Root ): Promise; /** diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index 50608b3155e1..55f9e07b737a 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -1,4 +1,4 @@ -import {allForks, capella, deneb, Wei, bellatrix} from "@lodestar/types"; +import {allForks, capella, deneb, Wei, bellatrix, Root} from "@lodestar/types"; import { BYTES_PER_LOGS_BLOOM, FIELD_ELEMENTS_PER_BLOB, @@ -33,7 +33,7 @@ export type EngineApiRpcParamTypes = { */ engine_newPayloadV1: [ExecutionPayloadRpc]; engine_newPayloadV2: [ExecutionPayloadRpc]; - engine_newPayloadV3: [ExecutionPayloadRpc, VersionedHashesRpc]; + engine_newPayloadV3: [ExecutionPayloadRpc, VersionedHashesRpc, DATA]; /** * 1. Object - Payload validity status with respect to the consensus rules: * - blockHash: DATA, 32 Bytes - block hash value of the payload @@ -283,6 +283,10 @@ export function serializePayloadAttributes(data: PayloadAttributes): PayloadAttr }; } +export function serializeBeaconBlockRoot(data: Root): DATA { + return bytesToData(data); +} + export function deserializePayloadAttributes(data: PayloadAttributesRpc): PayloadAttributes { return { timestamp: quantityToNum(data.timestamp), diff --git a/packages/types/src/allForks/sszTypes.ts b/packages/types/src/allForks/sszTypes.ts index 49610ae476f1..8c0397ddf2c2 100644 --- a/packages/types/src/allForks/sszTypes.ts +++ b/packages/types/src/allForks/sszTypes.ts @@ -83,7 +83,7 @@ export const allForksExecution = { ExecutionPayloadHeader: deneb.ExecutionPayloadHeader, BuilderBid: deneb.BuilderBid, SignedBuilderBid: deneb.SignedBuilderBid, - SSEPayloadAttributes: capella.SSEPayloadAttributes, + SSEPayloadAttributes: deneb.SSEPayloadAttributes, }, }; diff --git a/packages/types/src/allForks/types.ts b/packages/types/src/allForks/types.ts index c4a76ff6a884..7f3e0f14e9e8 100644 --- a/packages/types/src/allForks/types.ts +++ b/packages/types/src/allForks/types.ts @@ -94,7 +94,10 @@ export type LightClientStore = altair.LightClientStore | capella.LightClientStor export type SignedBeaconBlockAndBlobsSidecar = deneb.SignedBeaconBlockAndBlobsSidecar; -export type SSEPayloadAttributes = bellatrix.SSEPayloadAttributes | capella.SSEPayloadAttributes; +export type SSEPayloadAttributes = + | bellatrix.SSEPayloadAttributes + | capella.SSEPayloadAttributes + | deneb.SSEPayloadAttributes; /** * Types known to change between forks */ @@ -222,7 +225,9 @@ export type AllForksExecutionSSZTypes = { typeof bellatrixSsz.SignedBuilderBid | typeof capellaSsz.SignedBuilderBid | typeof denebSsz.SignedBuilderBid >; SSEPayloadAttributes: AllForksTypeOf< - typeof bellatrixSsz.SSEPayloadAttributes | typeof capellaSsz.SSEPayloadAttributes + | typeof bellatrixSsz.SSEPayloadAttributes + | typeof capellaSsz.SSEPayloadAttributes + | typeof denebSsz.SSEPayloadAttributes >; }; diff --git a/packages/types/src/bellatrix/sszTypes.ts b/packages/types/src/bellatrix/sszTypes.ts index 5604ecf72ab6..8677c2a7c30e 100644 --- a/packages/types/src/bellatrix/sszTypes.ts +++ b/packages/types/src/bellatrix/sszTypes.ts @@ -9,6 +9,7 @@ import { import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; import {ssz as altairSsz} from "../altair/index.js"; +import {stringType} from "../utils/StringType.js"; const { Bytes32, @@ -218,7 +219,7 @@ export const SignedBuilderBid = new ContainerType( // PayloadAttributes primarily for SSE event export const PayloadAttributes = new ContainerType( - {timestamp: UintNum64, prevRandao: Bytes32, suggestedFeeRecipient: ExecutionAddress}, + {timestamp: UintNum64, prevRandao: Bytes32, suggestedFeeRecipient: stringType}, {typeName: "PayloadAttributes", jsonCase: "eth2"} ); diff --git a/packages/types/src/deneb/sszTypes.ts b/packages/types/src/deneb/sszTypes.ts index 764170522d67..70101a0135ac 100644 --- a/packages/types/src/deneb/sszTypes.ts +++ b/packages/types/src/deneb/sszTypes.ts @@ -14,6 +14,7 @@ import { import {ssz as primitiveSsz} from "../primitive/index.js"; import {ssz as phase0Ssz} from "../phase0/index.js"; import {ssz as altairSsz} from "../altair/index.js"; +import {ssz as bellatrixSsz} from "../bellatrix/index.js"; import {ssz as capellaSsz} from "../capella/index.js"; const { @@ -385,3 +386,20 @@ export const LightClientStore = new ContainerType( }, {typeName: "LightClientStore", jsonCase: "eth2"} ); + +// PayloadAttributes primarily for SSE event +export const PayloadAttributes = new ContainerType( + { + ...capellaSsz.PayloadAttributes.fields, + parentBeaconBlockRoot: Root, + }, + {typeName: "PayloadAttributes", jsonCase: "eth2"} +); + +export const SSEPayloadAttributes = new ContainerType( + { + ...bellatrixSsz.SSEPayloadAttributesCommon.fields, + payloadAttributes: PayloadAttributes, + }, + {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} +); diff --git a/packages/types/src/deneb/types.ts b/packages/types/src/deneb/types.ts index c804fef02f5c..a4d2286af1e6 100644 --- a/packages/types/src/deneb/types.ts +++ b/packages/types/src/deneb/types.ts @@ -48,6 +48,7 @@ export type FullOrBlindedExecutionPayload = ExecutionPayload | ExecutionPayloadH export type BuilderBid = ValueOf; export type SignedBuilderBid = ValueOf; +export type SSEPayloadAttributes = ValueOf; export type LightClientHeader = ValueOf; export type LightClientBootstrap = ValueOf;