From ad23d255e12512c1f9ae9fa069d90c74e8f595ec Mon Sep 17 00:00:00 2001 From: lodekeeper Date: Tue, 4 Aug 2026 10:04:36 +0000 Subject: [PATCH 1/4] test: fix Heze spec coverage --- .../src/execution/engine/interface.ts | 3 +- .../beacon-node/src/execution/engine/types.ts | 4 ++ .../test/spec/presets/sanity.test.ts | 4 +- .../test/spec/utils/specTestIterator.ts | 1 - .../unit/executionEngine/httpRetry.test.ts | 46 +++++++++++++++++++ specrefs/.ethspecify.yml | 2 - specrefs/dataclasses.yml | 4 +- specrefs/functions.yml | 4 +- 8 files changed, 60 insertions(+), 8 deletions(-) diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index ec5fe5f17567..5b8ef7a836fb 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -9,7 +9,7 @@ import { ForkPreFulu, WITHDRAWAL_REQUEST_TYPE, } from "@lodestar/params"; -import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, capella} from "@lodestar/types"; +import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, capella, gloas} from "@lodestar/types"; import {BlobAndProof} from "@lodestar/types/deneb"; import {BlobAndProofV2} from "@lodestar/types/fulu"; import {PayloadId, PayloadIdCache, WithdrawalV1} from "./payloadIdCache.js"; @@ -99,6 +99,7 @@ export type PayloadAttributes = { parentBeaconBlockRoot?: Uint8Array; slotNumber?: number; // EIP-7843 targetGasLimit?: bigint; // GLOAS (PayloadAttributesV4, execution-apis#796) + inclusionListTransactions?: gloas.Transaction[]; // HEZE (PayloadAttributesV5, EIP-7805) }; export type VersionedHashes = Uint8Array[]; diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index d8e3067fda28..dfd40ebb9252 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -260,6 +260,8 @@ export type PayloadAttributesRpc = { slotNumber?: QUANTITY; /** QUANTITY, 64 Bits - target value for the gasLimit field of the new payload (GLOAS, execution-apis#796) */ targetGasLimit?: QUANTITY; + /** Array of DATA - transactions selected from inclusion lists (HEZE, EIP-7805) */ + inclusionListTransactions?: DATA[]; }; export type ClientVersionRpc = { @@ -441,6 +443,7 @@ export function serializePayloadAttributes(data: PayloadAttributes): PayloadAttr parentBeaconBlockRoot: data.parentBeaconBlockRoot ? bytesToData(data.parentBeaconBlockRoot) : undefined, slotNumber: data.slotNumber !== undefined ? numToQuantity(data.slotNumber) : undefined, targetGasLimit: data.targetGasLimit !== undefined ? numToQuantity(data.targetGasLimit) : undefined, + inclusionListTransactions: data.inclusionListTransactions?.map(bytesToData), }; } @@ -459,6 +462,7 @@ export function deserializePayloadAttributes(data: PayloadAttributesRpc): Payloa parentBeaconBlockRoot: data.parentBeaconBlockRoot ? dataToBytes(data.parentBeaconBlockRoot, 32) : undefined, slotNumber: data.slotNumber !== undefined ? quantityToNum(data.slotNumber) : undefined, targetGasLimit: data.targetGasLimit !== undefined ? quantityToBigint(data.targetGasLimit) : undefined, + inclusionListTransactions: data.inclusionListTransactions?.map((transaction) => dataToBytes(transaction, null)), }; } diff --git a/packages/beacon-node/test/spec/presets/sanity.test.ts b/packages/beacon-node/test/spec/presets/sanity.test.ts index 1d35ab48ecf8..93f511f8f501 100644 --- a/packages/beacon-node/test/spec/presets/sanity.test.ts +++ b/packages/beacon-node/test/spec/presets/sanity.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import {getConfig} from "@lodestar/config/test-utils"; -import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; +import {ACTIVE_PRESET, ForkName, ForkSeq} from "@lodestar/params"; import {InputType} from "@lodestar/spec-test-util"; import { BeaconStateAllForks, @@ -46,7 +46,7 @@ const sanitySlots: TestRunnerFn = (for post: ssz[fork].BeaconState, }, shouldError: (testCase) => !testCase.post, - timeout: 30000, + timeout: ForkSeq[fork] >= ForkSeq.gloas ? 120000 : 30000, getExpected: (testCase) => testCase.post, expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 02dcab19dac3..c61fc712dcb1 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -101,7 +101,6 @@ export const defaultSkipOpts: SkipOpts = { skippedTests: [ // TODO-GLOAS: re-enable after gloas light client is implemented /\/gloas_fork$/, - /\/heze_fork$/, // TODO GLOAS: Proposer-boost dependent-root gate uses stale cached head across epoch-boundary ticks; // boost wrongly denied. Fails identically on every pre-gloas fork. // Enable this after https://github.com/ChainSafe/lodestar/issues/9666 is resolved diff --git a/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts b/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts index 69f0a9ef29af..6f448c9426be 100644 --- a/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts +++ b/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts @@ -131,5 +131,51 @@ describe("ExecutionEngine / http ", () => { expect(reqJsonRpcPayload).toEqual(request); expect(errorResponsesBeforeSuccess).toBe(0); }); + + it("notifyForkchoiceUpdate serializes Heze inclusion list transactions", async () => { + errorResponsesBeforeSuccess = 0; + const forkChoiceHeadData = { + headBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", + safeBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", + finalizedBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", + }; + const payloadAttributes: PayloadAttributes = { + timestamp: 1647036763, + prevRandao: fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000"), + suggestedFeeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + inclusionListTransactions: [fromHexString("0x010203")], + }; + + returnValue = { + jsonrpc: "2.0", + id: 67, + result: { + payloadStatus: {status: "VALID", latestValidHash: null, validationError: null}, + payloadId: Buffer.alloc(8, 1), + }, + }; + + await executionEngine.notifyForkchoiceUpdate( + ForkName.heze, + forkChoiceHeadData.headBlockHash, + forkChoiceHeadData.safeBlockHash, + forkChoiceHeadData.finalizedBlockHash, + payloadAttributes + ); + + expect(reqJsonRpcPayload).toEqual({ + jsonrpc: "2.0", + method: "engine_forkchoiceUpdatedV4", + params: [ + forkChoiceHeadData, + { + timestamp: numToQuantity(payloadAttributes.timestamp), + prevRandao: bytesToData(payloadAttributes.prevRandao), + suggestedFeeRecipient: payloadAttributes.suggestedFeeRecipient, + inclusionListTransactions: ["0x010203"], + }, + ], + }); + }); }); }); diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml index c4f83b5f3539..d67df75cf856 100644 --- a/specrefs/.ethspecify.yml +++ b/specrefs/.ethspecify.yml @@ -100,7 +100,6 @@ exceptions: # heze (not implemented) - GetInclusionListResponse#heze - InclusionListStore#heze - - PayloadAttributes#heze - Store#heze presets: [] @@ -372,7 +371,6 @@ exceptions: - process_inclusion_list#heze - record_payload_inclusion_list_satisfaction#heze - should_extend_payload#heze - - upgrade_to_heze#heze # phase0 fast confirmation / not implemented - adjust_committee_weight_estimate_to_ensure_safety#phase0 diff --git a/specrefs/dataclasses.yml b/specrefs/dataclasses.yml index b3208d8489e5..65c05b637f3b 100644 --- a/specrefs/dataclasses.yml +++ b/specrefs/dataclasses.yml @@ -364,7 +364,9 @@ - name: PayloadAttributes#heze - sources: [] + sources: + - file: packages/types/src/heze/sszTypes.ts + search: export const PayloadAttributes = spec: | class PayloadAttributes: diff --git a/specrefs/functions.yml b/specrefs/functions.yml index 71160be55823..4fbd60155074 100644 --- a/specrefs/functions.yml +++ b/specrefs/functions.yml @@ -13587,7 +13587,9 @@ - name: upgrade_to_heze#heze - sources: [] + sources: + - file: packages/state-transition/src/slot/upgradeStateToHeze.ts + search: export function upgradeStateToHeze( spec: | def upgrade_to_heze(pre: gloas.BeaconState) -> BeaconState: From a6bf1cadac283e24e7303743b1d566835f89b63c Mon Sep 17 00:00:00 2001 From: lodekeeper Date: Tue, 4 Aug 2026 10:50:56 +0000 Subject: [PATCH 2/4] fix: address Heze review feedback --- .../test/spec/presets/sanity.test.ts | 4 ++-- .../test/spec/utils/specTestIterator.ts | 5 +++++ .../src/block/processExecutionPayloadBid.ts | 21 ++++++++++++------- packages/state-transition/src/index.ts | 2 -- .../src/signatureSets/executionPayloadBid.ts | 7 +++---- .../src/stateView/interface.ts | 21 +++---------------- packages/state-transition/src/util/gloas.ts | 6 +++--- packages/types/src/heze/sszTypes.ts | 7 ------- 8 files changed, 29 insertions(+), 44 deletions(-) diff --git a/packages/beacon-node/test/spec/presets/sanity.test.ts b/packages/beacon-node/test/spec/presets/sanity.test.ts index 93f511f8f501..1d35ab48ecf8 100644 --- a/packages/beacon-node/test/spec/presets/sanity.test.ts +++ b/packages/beacon-node/test/spec/presets/sanity.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import {getConfig} from "@lodestar/config/test-utils"; -import {ACTIVE_PRESET, ForkName, ForkSeq} from "@lodestar/params"; +import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; import {InputType} from "@lodestar/spec-test-util"; import { BeaconStateAllForks, @@ -46,7 +46,7 @@ const sanitySlots: TestRunnerFn = (for post: ssz[fork].BeaconState, }, shouldError: (testCase) => !testCase.post, - timeout: ForkSeq[fork] >= ForkSeq.gloas ? 120000 : 30000, + timeout: 30000, getExpected: (testCase) => testCase.post, expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index c61fc712dcb1..e64456526f77 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -101,6 +101,11 @@ export const defaultSkipOpts: SkipOpts = { skippedTests: [ // TODO-GLOAS: re-enable after gloas light client is implemented /\/gloas_fork$/, + /\/heze_fork$/, + // TODO-GLOAS: investigate why these mainnet post-Gloas historical accumulator slot tests + // take ~40-50s locally. Skipping for now instead of raising the global sanity/slots timeout. + /^gloas\/sanity\/slots\/pyspec_tests\/historical_accumulator$/, + /^heze\/sanity\/slots\/pyspec_tests\/historical_accumulator$/, // TODO GLOAS: Proposer-boost dependent-root gate uses stale cached head across epoch-boundary ticks; // boost wrongly denied. Fails identically on every pre-gloas fork. // Enable this after https://github.com/ChainSafe/lodestar/issues/9666 is resolved diff --git a/packages/state-transition/src/block/processExecutionPayloadBid.ts b/packages/state-transition/src/block/processExecutionPayloadBid.ts index b401f5d65948..df59fed1eaa0 100644 --- a/packages/state-transition/src/block/processExecutionPayloadBid.ts +++ b/packages/state-transition/src/block/processExecutionPayloadBid.ts @@ -1,12 +1,13 @@ import {PublicKey, Signature, verify} from "@chainsafe/blst"; import { BUILDER_INDEX_SELF_BUILD, + ForkName, ForkSeq, GENESIS_SLOT, PAYLOAD_BUILDER_VERSION, SLOTS_PER_EPOCH, } from "@lodestar/params"; -import {Slot, gloas, heze, ssz} from "@lodestar/types"; +import {Slot, gloas, heze, ssz, sszTypesFor} from "@lodestar/types"; import {byteArrayEquals, toHex, toRootHex} from "@lodestar/utils"; import {G2_POINT_AT_INFINITY} from "../constants/constants.js"; import {getExecutionPayloadBidSigningRoot} from "../signatureSets/executionPayloadBid.js"; @@ -16,7 +17,7 @@ import {getBlockRootAtSlot, getCurrentEpoch, getRandaoMix} from "../util/index.j export function processExecutionPayloadBid( state: CachedBeaconStateGloas | CachedBeaconStateHeze, - signedBid: gloas.SignedExecutionPayloadBid + signedBid: gloas.SignedExecutionPayloadBid | heze.SignedExecutionPayloadBid ): Slot { const bid = signedBid.message; const {builderIndex, value: amount} = bid; @@ -47,7 +48,7 @@ export function processExecutionPayloadBid( } // Verify that the builder has funds to cover the bid - if (!canBuilderCoverBid(state as CachedBeaconStateGloas, builderIndex, amount)) { + if (!canBuilderCoverBid(state, builderIndex, amount)) { throw Error(`Invalid execution payload bid: builder ${builderIndex} has insufficient balance`); } @@ -107,11 +108,15 @@ export function processExecutionPayloadBid( const parentSlot = state.latestExecutionPayloadBid.slot; if (state.config.getForkSeq(state.slot) >= ForkSeq.heze) { - (state as CachedBeaconStateHeze).latestExecutionPayloadBid = ssz.heze.ExecutionPayloadBid.toViewDU( - bid as heze.ExecutionPayloadBid - ); + (state as CachedBeaconStateHeze).latestExecutionPayloadBid = sszTypesFor( + ForkName.heze, + "ExecutionPayloadBid" + ).toViewDU(bid as heze.ExecutionPayloadBid); } else { - (state as CachedBeaconStateGloas).latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); + (state as CachedBeaconStateGloas).latestExecutionPayloadBid = sszTypesFor( + ForkName.gloas, + "ExecutionPayloadBid" + ).toViewDU(bid as gloas.ExecutionPayloadBid); } return parentSlot; @@ -120,7 +125,7 @@ export function processExecutionPayloadBid( function verifyExecutionPayloadBidSignature( state: CachedBeaconStateGloas | CachedBeaconStateHeze, pubkey: Uint8Array, - signedBid: gloas.SignedExecutionPayloadBid + signedBid: gloas.SignedExecutionPayloadBid | heze.SignedExecutionPayloadBid ): boolean { const signingRoot = getExecutionPayloadBidSigningRoot(state.config, state.slot, signedBid.message); diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 0525ae4b61a3..0d844a50c175 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -52,7 +52,6 @@ export { type IBeaconStateViewElectra, type IBeaconStateViewFulu, type IBeaconStateViewGloas, - type IBeaconStateViewHeze, isStatePostAltair, isStatePostBellatrix, isStatePostCapella, @@ -60,7 +59,6 @@ export { isStatePostElectra, isStatePostFulu, isStatePostGloas, - isStatePostHeze, } from "./stateView/interface.js"; export {createBeaconStateView, createBeaconStateViewForHistoricalRegen} from "./stateView/stateViewFactory.js"; export type { diff --git a/packages/state-transition/src/signatureSets/executionPayloadBid.ts b/packages/state-transition/src/signatureSets/executionPayloadBid.ts index 76f80b017296..a3f5bd6cdf30 100644 --- a/packages/state-transition/src/signatureSets/executionPayloadBid.ts +++ b/packages/state-transition/src/signatureSets/executionPayloadBid.ts @@ -1,6 +1,6 @@ import {BeaconConfig} from "@lodestar/config"; -import {DOMAIN_BEACON_BUILDER, ForkSeq} from "@lodestar/params"; -import {ExecutionPayloadBid, Slot, ssz} from "@lodestar/types"; +import {DOMAIN_BEACON_BUILDER, type ForkPostGloas} from "@lodestar/params"; +import {ExecutionPayloadBid, Slot, sszTypesFor} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; export function getExecutionPayloadBidSigningRoot( @@ -9,8 +9,7 @@ export function getExecutionPayloadBidSigningRoot( bid: ExecutionPayloadBid ): Uint8Array { const domain = config.getDomain(stateSlot, DOMAIN_BEACON_BUILDER); - const sszType = - config.getForkSeq(stateSlot) >= ForkSeq.heze ? ssz.heze.ExecutionPayloadBid : ssz.gloas.ExecutionPayloadBid; + const sszType = sszTypesFor(config.getForkName(stateSlot) as ForkPostGloas, "ExecutionPayloadBid"); return computeSigningRoot(sszType, bid, domain); } diff --git a/packages/state-transition/src/stateView/interface.ts b/packages/state-transition/src/stateView/interface.ts index 58472a831805..6097eabd0a8f 100644 --- a/packages/state-transition/src/stateView/interface.ts +++ b/packages/state-transition/src/stateView/interface.ts @@ -9,7 +9,6 @@ import { ForkPostElectra, ForkPostFulu, ForkPostGloas, - ForkPostHeze, isForkPostAltair, isForkPostBellatrix, isForkPostCapella, @@ -17,7 +16,6 @@ import { isForkPostElectra, isForkPostFulu, isForkPostGloas, - isForkPostHeze, } from "@lodestar/params"; import { BeaconBlock, @@ -40,7 +38,6 @@ import { electra, fulu, gloas, - heze, phase0, rewards, } from "@lodestar/types"; @@ -270,26 +267,18 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu { withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas; } -/** Heze+ state fields — use isStatePostHeze() guard */ -export interface IBeaconStateViewHeze extends IBeaconStateViewGloas { - forkName: ForkPostHeze; - /** Modified in heze: ExecutionPayloadBid carries `inclusion_list_bits`. */ - latestExecutionPayloadBid: heze.ExecutionPayloadBid; -} - /** * Type constraint for the concrete BeaconStateView class. - * Requires all fields from the latest fork interface (IBeaconStateViewHeze) but keeps + * Requires all fields from the latest fork interface (IBeaconStateViewGloas) but keeps * forkName as ForkName since the class wraps any fork's state. * Sub-interfaces retain their narrowed forkName discriminants for caller-side type guards. */ export type IBeaconStateViewLatestFork = Omit< - IBeaconStateViewHeze, - "forkName" | "latestExecutionPayloadHeader" | "latestExecutionPayloadBid" | "payloadBlockNumber" + IBeaconStateViewGloas, + "forkName" | "latestExecutionPayloadHeader" | "payloadBlockNumber" > & { forkName: ForkName; latestExecutionPayloadHeader: ExecutionPayloadHeader; - latestExecutionPayloadBid: ExecutionPayloadBid; payloadBlockNumber: number; }; @@ -348,7 +337,3 @@ export function isStatePostFulu(state: IBeaconStateView): state is IBeaconStateV export function isStatePostGloas(state: IBeaconStateView): state is IBeaconStateViewGloas { return isForkPostGloas(state.forkName); } - -export function isStatePostHeze(state: IBeaconStateView): state is IBeaconStateViewHeze { - return isForkPostHeze(state.forkName); -} diff --git a/packages/state-transition/src/util/gloas.ts b/packages/state-transition/src/util/gloas.ts index 8d0cd3722be0..fef66507f583 100644 --- a/packages/state-transition/src/util/gloas.ts +++ b/packages/state-transition/src/util/gloas.ts @@ -17,7 +17,7 @@ import {BuilderIndex, Epoch, ValidatorIndex, gloas, ssz} from "@lodestar/types"; import {AttestationData} from "@lodestar/types/phase0"; import {byteArrayEquals} from "@lodestar/utils"; import {ZERO_HASH} from "../constants/index.js"; -import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js"; +import {CachedBeaconStateFulu, CachedBeaconStateGloas, CachedBeaconStateHeze} from "../types.js"; import {computeDomain} from "./domain.js"; import {computeEpochAtSlot} from "./epoch.js"; import {computeEpochShuffling} from "./epochShuffling.js"; @@ -117,7 +117,7 @@ export function isGasLimitTargetCompatible(parentGasLimit: bigint, gasLimit: big * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-get_pending_balance_to_withdraw_for_builder */ export function getPendingBalanceToWithdrawForBuilder( - state: CachedBeaconStateGloas, + state: CachedBeaconStateGloas | CachedBeaconStateHeze, builderIndex: BuilderIndex ): number { let pendingBalance = 0; @@ -146,7 +146,7 @@ export function getPendingBalanceToWithdrawForBuilder( * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-can_builder_cover_bid */ export function canBuilderCoverBid( - state: CachedBeaconStateGloas, + state: CachedBeaconStateGloas | CachedBeaconStateHeze, builderIndex: BuilderIndex, bidAmount: number ): boolean { diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index 011b5648da1c..fb4dd937e549 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -21,7 +21,6 @@ export const InclusionListTransactions = new ProgressiveListCompositeType(gloasS typeName: "InclusionListTransactions", }); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#inclusionlist export const InclusionList = new ContainerType( { slot: Slot, @@ -32,7 +31,6 @@ export const InclusionList = new ContainerType( {typeName: "InclusionList", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#signedinclusionlist export const SignedInclusionList = new ContainerType( { message: InclusionList, @@ -41,7 +39,6 @@ export const SignedInclusionList = new ContainerType( {typeName: "SignedInclusionList", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/p2p-interface.md#inclusionlistsbyindices-v1 export const InclusionListsByIndicesRequest = new ContainerType( { slot: Slot, @@ -51,7 +48,6 @@ export const InclusionListsByIndicesRequest = new ContainerType( {typeName: "InclusionListsByIndicesRequest", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#executionpayloadbid export const ExecutionPayloadBid = new ProgressiveContainerType( { ...gloasSsz.ExecutionPayloadBid.fields, @@ -61,7 +57,6 @@ export const ExecutionPayloadBid = new ProgressiveContainerType( {typeName: "ExecutionPayloadBid", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#signedexecutionpayloadbid export const SignedExecutionPayloadBid = new ContainerType( { message: ExecutionPayloadBid, // [Modified in Heze:EIP7805] @@ -73,7 +68,6 @@ export const SignedExecutionPayloadBid = new ContainerType( export const DataColumnSidecar = gloasSsz.DataColumnSidecar; export const DataColumnSidecars = gloasSsz.DataColumnSidecars; -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#beaconstate export const BeaconState = new ProgressiveContainerType( { ...gloasSsz.BeaconState.fields, @@ -117,7 +111,6 @@ export const BlockContents = new ContainerType( ); // PayloadAttributes primarily for SSE event -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/fork-choice.md#modified-payloadattributes export const PayloadAttributes = new ContainerType( { ...gloasSsz.PayloadAttributes.fields, From eb17db058d59ced5ea047f289a9465a5304f998c Mon Sep 17 00:00:00 2001 From: lodekeeper Date: Tue, 4 Aug 2026 10:58:43 +0000 Subject: [PATCH 3/4] chore: trim Heze specref cleanup scope --- .../src/execution/engine/interface.ts | 3 +- .../beacon-node/src/execution/engine/types.ts | 4 -- .../test/spec/utils/specTestIterator.ts | 4 -- .../unit/executionEngine/httpRetry.test.ts | 46 ------------------- .../src/block/processExecutionPayloadBid.ts | 21 ++++----- packages/state-transition/src/index.ts | 2 + .../src/signatureSets/executionPayloadBid.ts | 7 +-- .../src/stateView/interface.ts | 21 +++++++-- packages/state-transition/src/util/gloas.ts | 6 +-- packages/types/src/heze/sszTypes.ts | 7 +++ 10 files changed, 43 insertions(+), 78 deletions(-) diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index 5b8ef7a836fb..ec5fe5f17567 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -9,7 +9,7 @@ import { ForkPreFulu, WITHDRAWAL_REQUEST_TYPE, } from "@lodestar/params"; -import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, capella, gloas} from "@lodestar/types"; +import {BlobsBundle, ExecutionPayload, ExecutionRequests, Root, RootHex, Wei, capella} from "@lodestar/types"; import {BlobAndProof} from "@lodestar/types/deneb"; import {BlobAndProofV2} from "@lodestar/types/fulu"; import {PayloadId, PayloadIdCache, WithdrawalV1} from "./payloadIdCache.js"; @@ -99,7 +99,6 @@ export type PayloadAttributes = { parentBeaconBlockRoot?: Uint8Array; slotNumber?: number; // EIP-7843 targetGasLimit?: bigint; // GLOAS (PayloadAttributesV4, execution-apis#796) - inclusionListTransactions?: gloas.Transaction[]; // HEZE (PayloadAttributesV5, EIP-7805) }; export type VersionedHashes = Uint8Array[]; diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index dfd40ebb9252..d8e3067fda28 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -260,8 +260,6 @@ export type PayloadAttributesRpc = { slotNumber?: QUANTITY; /** QUANTITY, 64 Bits - target value for the gasLimit field of the new payload (GLOAS, execution-apis#796) */ targetGasLimit?: QUANTITY; - /** Array of DATA - transactions selected from inclusion lists (HEZE, EIP-7805) */ - inclusionListTransactions?: DATA[]; }; export type ClientVersionRpc = { @@ -443,7 +441,6 @@ export function serializePayloadAttributes(data: PayloadAttributes): PayloadAttr parentBeaconBlockRoot: data.parentBeaconBlockRoot ? bytesToData(data.parentBeaconBlockRoot) : undefined, slotNumber: data.slotNumber !== undefined ? numToQuantity(data.slotNumber) : undefined, targetGasLimit: data.targetGasLimit !== undefined ? numToQuantity(data.targetGasLimit) : undefined, - inclusionListTransactions: data.inclusionListTransactions?.map(bytesToData), }; } @@ -462,7 +459,6 @@ export function deserializePayloadAttributes(data: PayloadAttributesRpc): Payloa parentBeaconBlockRoot: data.parentBeaconBlockRoot ? dataToBytes(data.parentBeaconBlockRoot, 32) : undefined, slotNumber: data.slotNumber !== undefined ? quantityToNum(data.slotNumber) : undefined, targetGasLimit: data.targetGasLimit !== undefined ? quantityToBigint(data.targetGasLimit) : undefined, - inclusionListTransactions: data.inclusionListTransactions?.map((transaction) => dataToBytes(transaction, null)), }; } diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index e64456526f77..02dcab19dac3 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -102,10 +102,6 @@ export const defaultSkipOpts: SkipOpts = { // TODO-GLOAS: re-enable after gloas light client is implemented /\/gloas_fork$/, /\/heze_fork$/, - // TODO-GLOAS: investigate why these mainnet post-Gloas historical accumulator slot tests - // take ~40-50s locally. Skipping for now instead of raising the global sanity/slots timeout. - /^gloas\/sanity\/slots\/pyspec_tests\/historical_accumulator$/, - /^heze\/sanity\/slots\/pyspec_tests\/historical_accumulator$/, // TODO GLOAS: Proposer-boost dependent-root gate uses stale cached head across epoch-boundary ticks; // boost wrongly denied. Fails identically on every pre-gloas fork. // Enable this after https://github.com/ChainSafe/lodestar/issues/9666 is resolved diff --git a/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts b/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts index 6f448c9426be..69f0a9ef29af 100644 --- a/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts +++ b/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts @@ -131,51 +131,5 @@ describe("ExecutionEngine / http ", () => { expect(reqJsonRpcPayload).toEqual(request); expect(errorResponsesBeforeSuccess).toBe(0); }); - - it("notifyForkchoiceUpdate serializes Heze inclusion list transactions", async () => { - errorResponsesBeforeSuccess = 0; - const forkChoiceHeadData = { - headBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", - safeBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", - finalizedBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", - }; - const payloadAttributes: PayloadAttributes = { - timestamp: 1647036763, - prevRandao: fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000"), - suggestedFeeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - inclusionListTransactions: [fromHexString("0x010203")], - }; - - returnValue = { - jsonrpc: "2.0", - id: 67, - result: { - payloadStatus: {status: "VALID", latestValidHash: null, validationError: null}, - payloadId: Buffer.alloc(8, 1), - }, - }; - - await executionEngine.notifyForkchoiceUpdate( - ForkName.heze, - forkChoiceHeadData.headBlockHash, - forkChoiceHeadData.safeBlockHash, - forkChoiceHeadData.finalizedBlockHash, - payloadAttributes - ); - - expect(reqJsonRpcPayload).toEqual({ - jsonrpc: "2.0", - method: "engine_forkchoiceUpdatedV4", - params: [ - forkChoiceHeadData, - { - timestamp: numToQuantity(payloadAttributes.timestamp), - prevRandao: bytesToData(payloadAttributes.prevRandao), - suggestedFeeRecipient: payloadAttributes.suggestedFeeRecipient, - inclusionListTransactions: ["0x010203"], - }, - ], - }); - }); }); }); diff --git a/packages/state-transition/src/block/processExecutionPayloadBid.ts b/packages/state-transition/src/block/processExecutionPayloadBid.ts index df59fed1eaa0..b401f5d65948 100644 --- a/packages/state-transition/src/block/processExecutionPayloadBid.ts +++ b/packages/state-transition/src/block/processExecutionPayloadBid.ts @@ -1,13 +1,12 @@ import {PublicKey, Signature, verify} from "@chainsafe/blst"; import { BUILDER_INDEX_SELF_BUILD, - ForkName, ForkSeq, GENESIS_SLOT, PAYLOAD_BUILDER_VERSION, SLOTS_PER_EPOCH, } from "@lodestar/params"; -import {Slot, gloas, heze, ssz, sszTypesFor} from "@lodestar/types"; +import {Slot, gloas, heze, ssz} from "@lodestar/types"; import {byteArrayEquals, toHex, toRootHex} from "@lodestar/utils"; import {G2_POINT_AT_INFINITY} from "../constants/constants.js"; import {getExecutionPayloadBidSigningRoot} from "../signatureSets/executionPayloadBid.js"; @@ -17,7 +16,7 @@ import {getBlockRootAtSlot, getCurrentEpoch, getRandaoMix} from "../util/index.j export function processExecutionPayloadBid( state: CachedBeaconStateGloas | CachedBeaconStateHeze, - signedBid: gloas.SignedExecutionPayloadBid | heze.SignedExecutionPayloadBid + signedBid: gloas.SignedExecutionPayloadBid ): Slot { const bid = signedBid.message; const {builderIndex, value: amount} = bid; @@ -48,7 +47,7 @@ export function processExecutionPayloadBid( } // Verify that the builder has funds to cover the bid - if (!canBuilderCoverBid(state, builderIndex, amount)) { + if (!canBuilderCoverBid(state as CachedBeaconStateGloas, builderIndex, amount)) { throw Error(`Invalid execution payload bid: builder ${builderIndex} has insufficient balance`); } @@ -108,15 +107,11 @@ export function processExecutionPayloadBid( const parentSlot = state.latestExecutionPayloadBid.slot; if (state.config.getForkSeq(state.slot) >= ForkSeq.heze) { - (state as CachedBeaconStateHeze).latestExecutionPayloadBid = sszTypesFor( - ForkName.heze, - "ExecutionPayloadBid" - ).toViewDU(bid as heze.ExecutionPayloadBid); + (state as CachedBeaconStateHeze).latestExecutionPayloadBid = ssz.heze.ExecutionPayloadBid.toViewDU( + bid as heze.ExecutionPayloadBid + ); } else { - (state as CachedBeaconStateGloas).latestExecutionPayloadBid = sszTypesFor( - ForkName.gloas, - "ExecutionPayloadBid" - ).toViewDU(bid as gloas.ExecutionPayloadBid); + (state as CachedBeaconStateGloas).latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); } return parentSlot; @@ -125,7 +120,7 @@ export function processExecutionPayloadBid( function verifyExecutionPayloadBidSignature( state: CachedBeaconStateGloas | CachedBeaconStateHeze, pubkey: Uint8Array, - signedBid: gloas.SignedExecutionPayloadBid | heze.SignedExecutionPayloadBid + signedBid: gloas.SignedExecutionPayloadBid ): boolean { const signingRoot = getExecutionPayloadBidSigningRoot(state.config, state.slot, signedBid.message); diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 0d844a50c175..0525ae4b61a3 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -52,6 +52,7 @@ export { type IBeaconStateViewElectra, type IBeaconStateViewFulu, type IBeaconStateViewGloas, + type IBeaconStateViewHeze, isStatePostAltair, isStatePostBellatrix, isStatePostCapella, @@ -59,6 +60,7 @@ export { isStatePostElectra, isStatePostFulu, isStatePostGloas, + isStatePostHeze, } from "./stateView/interface.js"; export {createBeaconStateView, createBeaconStateViewForHistoricalRegen} from "./stateView/stateViewFactory.js"; export type { diff --git a/packages/state-transition/src/signatureSets/executionPayloadBid.ts b/packages/state-transition/src/signatureSets/executionPayloadBid.ts index a3f5bd6cdf30..76f80b017296 100644 --- a/packages/state-transition/src/signatureSets/executionPayloadBid.ts +++ b/packages/state-transition/src/signatureSets/executionPayloadBid.ts @@ -1,6 +1,6 @@ import {BeaconConfig} from "@lodestar/config"; -import {DOMAIN_BEACON_BUILDER, type ForkPostGloas} from "@lodestar/params"; -import {ExecutionPayloadBid, Slot, sszTypesFor} from "@lodestar/types"; +import {DOMAIN_BEACON_BUILDER, ForkSeq} from "@lodestar/params"; +import {ExecutionPayloadBid, Slot, ssz} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; export function getExecutionPayloadBidSigningRoot( @@ -9,7 +9,8 @@ export function getExecutionPayloadBidSigningRoot( bid: ExecutionPayloadBid ): Uint8Array { const domain = config.getDomain(stateSlot, DOMAIN_BEACON_BUILDER); - const sszType = sszTypesFor(config.getForkName(stateSlot) as ForkPostGloas, "ExecutionPayloadBid"); + const sszType = + config.getForkSeq(stateSlot) >= ForkSeq.heze ? ssz.heze.ExecutionPayloadBid : ssz.gloas.ExecutionPayloadBid; return computeSigningRoot(sszType, bid, domain); } diff --git a/packages/state-transition/src/stateView/interface.ts b/packages/state-transition/src/stateView/interface.ts index 6097eabd0a8f..58472a831805 100644 --- a/packages/state-transition/src/stateView/interface.ts +++ b/packages/state-transition/src/stateView/interface.ts @@ -9,6 +9,7 @@ import { ForkPostElectra, ForkPostFulu, ForkPostGloas, + ForkPostHeze, isForkPostAltair, isForkPostBellatrix, isForkPostCapella, @@ -16,6 +17,7 @@ import { isForkPostElectra, isForkPostFulu, isForkPostGloas, + isForkPostHeze, } from "@lodestar/params"; import { BeaconBlock, @@ -38,6 +40,7 @@ import { electra, fulu, gloas, + heze, phase0, rewards, } from "@lodestar/types"; @@ -267,18 +270,26 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu { withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas; } +/** Heze+ state fields — use isStatePostHeze() guard */ +export interface IBeaconStateViewHeze extends IBeaconStateViewGloas { + forkName: ForkPostHeze; + /** Modified in heze: ExecutionPayloadBid carries `inclusion_list_bits`. */ + latestExecutionPayloadBid: heze.ExecutionPayloadBid; +} + /** * Type constraint for the concrete BeaconStateView class. - * Requires all fields from the latest fork interface (IBeaconStateViewGloas) but keeps + * Requires all fields from the latest fork interface (IBeaconStateViewHeze) but keeps * forkName as ForkName since the class wraps any fork's state. * Sub-interfaces retain their narrowed forkName discriminants for caller-side type guards. */ export type IBeaconStateViewLatestFork = Omit< - IBeaconStateViewGloas, - "forkName" | "latestExecutionPayloadHeader" | "payloadBlockNumber" + IBeaconStateViewHeze, + "forkName" | "latestExecutionPayloadHeader" | "latestExecutionPayloadBid" | "payloadBlockNumber" > & { forkName: ForkName; latestExecutionPayloadHeader: ExecutionPayloadHeader; + latestExecutionPayloadBid: ExecutionPayloadBid; payloadBlockNumber: number; }; @@ -337,3 +348,7 @@ export function isStatePostFulu(state: IBeaconStateView): state is IBeaconStateV export function isStatePostGloas(state: IBeaconStateView): state is IBeaconStateViewGloas { return isForkPostGloas(state.forkName); } + +export function isStatePostHeze(state: IBeaconStateView): state is IBeaconStateViewHeze { + return isForkPostHeze(state.forkName); +} diff --git a/packages/state-transition/src/util/gloas.ts b/packages/state-transition/src/util/gloas.ts index fef66507f583..8d0cd3722be0 100644 --- a/packages/state-transition/src/util/gloas.ts +++ b/packages/state-transition/src/util/gloas.ts @@ -17,7 +17,7 @@ import {BuilderIndex, Epoch, ValidatorIndex, gloas, ssz} from "@lodestar/types"; import {AttestationData} from "@lodestar/types/phase0"; import {byteArrayEquals} from "@lodestar/utils"; import {ZERO_HASH} from "../constants/index.js"; -import {CachedBeaconStateFulu, CachedBeaconStateGloas, CachedBeaconStateHeze} from "../types.js"; +import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js"; import {computeDomain} from "./domain.js"; import {computeEpochAtSlot} from "./epoch.js"; import {computeEpochShuffling} from "./epochShuffling.js"; @@ -117,7 +117,7 @@ export function isGasLimitTargetCompatible(parentGasLimit: bigint, gasLimit: big * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-get_pending_balance_to_withdraw_for_builder */ export function getPendingBalanceToWithdrawForBuilder( - state: CachedBeaconStateGloas | CachedBeaconStateHeze, + state: CachedBeaconStateGloas, builderIndex: BuilderIndex ): number { let pendingBalance = 0; @@ -146,7 +146,7 @@ export function getPendingBalanceToWithdrawForBuilder( * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-can_builder_cover_bid */ export function canBuilderCoverBid( - state: CachedBeaconStateGloas | CachedBeaconStateHeze, + state: CachedBeaconStateGloas, builderIndex: BuilderIndex, bidAmount: number ): boolean { diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index fb4dd937e549..011b5648da1c 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -21,6 +21,7 @@ export const InclusionListTransactions = new ProgressiveListCompositeType(gloasS typeName: "InclusionListTransactions", }); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#inclusionlist export const InclusionList = new ContainerType( { slot: Slot, @@ -31,6 +32,7 @@ export const InclusionList = new ContainerType( {typeName: "InclusionList", jsonCase: "eth2"} ); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#signedinclusionlist export const SignedInclusionList = new ContainerType( { message: InclusionList, @@ -39,6 +41,7 @@ export const SignedInclusionList = new ContainerType( {typeName: "SignedInclusionList", jsonCase: "eth2"} ); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/p2p-interface.md#inclusionlistsbyindices-v1 export const InclusionListsByIndicesRequest = new ContainerType( { slot: Slot, @@ -48,6 +51,7 @@ export const InclusionListsByIndicesRequest = new ContainerType( {typeName: "InclusionListsByIndicesRequest", jsonCase: "eth2"} ); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#executionpayloadbid export const ExecutionPayloadBid = new ProgressiveContainerType( { ...gloasSsz.ExecutionPayloadBid.fields, @@ -57,6 +61,7 @@ export const ExecutionPayloadBid = new ProgressiveContainerType( {typeName: "ExecutionPayloadBid", jsonCase: "eth2"} ); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#signedexecutionpayloadbid export const SignedExecutionPayloadBid = new ContainerType( { message: ExecutionPayloadBid, // [Modified in Heze:EIP7805] @@ -68,6 +73,7 @@ export const SignedExecutionPayloadBid = new ContainerType( export const DataColumnSidecar = gloasSsz.DataColumnSidecar; export const DataColumnSidecars = gloasSsz.DataColumnSidecars; +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/beacon-chain.md#beaconstate export const BeaconState = new ProgressiveContainerType( { ...gloasSsz.BeaconState.fields, @@ -111,6 +117,7 @@ export const BlockContents = new ContainerType( ); // PayloadAttributes primarily for SSE event +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.13/specs/heze/fork-choice.md#modified-payloadattributes export const PayloadAttributes = new ContainerType( { ...gloasSsz.PayloadAttributes.fields, From b35b3f4ba1455d9942a82736fb8ac96c081e63a9 Mon Sep 17 00:00:00 2001 From: lodekeeper Date: Tue, 4 Aug 2026 21:17:17 +0000 Subject: [PATCH 4/4] test: keep Heze payload attributes unmapped --- specrefs/.ethspecify.yml | 1 + specrefs/dataclasses.yml | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml index d67df75cf856..3af0b8a8d08c 100644 --- a/specrefs/.ethspecify.yml +++ b/specrefs/.ethspecify.yml @@ -100,6 +100,7 @@ exceptions: # heze (not implemented) - GetInclusionListResponse#heze - InclusionListStore#heze + - PayloadAttributes#heze - Store#heze presets: [] diff --git a/specrefs/dataclasses.yml b/specrefs/dataclasses.yml index 65c05b637f3b..b3208d8489e5 100644 --- a/specrefs/dataclasses.yml +++ b/specrefs/dataclasses.yml @@ -364,9 +364,7 @@ - name: PayloadAttributes#heze - sources: - - file: packages/types/src/heze/sszTypes.ts - search: export const PayloadAttributes = + sources: [] spec: | class PayloadAttributes: