From 4b3af00140acc1ddac1aaa1f4486d5c56ce4f5b5 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:02:28 -0700 Subject: [PATCH 01/22] feat: add heze fork boilerplate with EIP-7805 inclusion list types Scaffold the heze fork (post-gloas) as a gloas-copy plus the EIP-7805 inclusion-list data model: IL containers (InclusionList, SignedInclusionList, committee/request), ExecutionPayloadBid extended with inclusionListBits, and the modified BeaconState/Body/PayloadAttributes. Wires fork registration (params, config, forkConfig, ssz/type maps), upgradeStateToHeze, IBeaconStateViewHeze, and fork-specific bid signing root/storage and genesis fork versions so the bid type round-trips correctly. Enables heze spec tests (skipping the FOCIL fork-choice scenarios shared with gloas pending #9233) and validates heze config fields against the spec. Excludes FOCIL feature logic (inclusion-list pools, validation, gossip, reqresp, duties) which remains on the focil branch. --- .../test/spec/presets/fork.test.ts | 3 + .../test/spec/presets/fork_choice.test.ts | 3 +- .../test/spec/presets/transition.test.ts | 11 ++ .../test/spec/utils/specTestIterator.ts | 6 +- .../test/unit/chain/blocks/blockInput.test.ts | 3 + .../upgradeLightClientHeader.test.ts | 3 + .../test/unit/network/fork.test.ts | 13 +- .../beacon-node/test/utils/blocksAndData.ts | 3 + .../config/src/chainConfig/configs/mainnet.ts | 15 ++ .../config/src/chainConfig/configs/minimal.ts | 13 ++ packages/config/src/chainConfig/types.ts | 20 +++ packages/config/src/forkConfig/index.ts | 10 +- packages/config/src/testUtils/config.ts | 12 ++ .../test/e2e/ensure-config-is-synced.test.ts | 8 +- packages/params/src/forkName.ts | 18 +++ packages/params/src/index.ts | 4 + packages/params/src/presets/mainnet.ts | 4 + packages/params/src/presets/minimal.ts | 4 + packages/params/src/types.ts | 8 + .../src/block/processExecutionPayloadBid.ts | 13 +- .../state-transition/src/cache/stateCache.ts | 2 + packages/state-transition/src/cache/types.ts | 1 + packages/state-transition/src/index.ts | 4 + .../src/lightClient/spec/utils.ts | 7 + .../src/signatureSets/executionPayloadBid.ts | 10 +- packages/state-transition/src/slot/index.ts | 1 + .../src/slot/upgradeStateToHeze.ts | 91 +++++++++++ .../state-transition/src/stateTransition.ts | 5 + .../src/stateView/interface.ts | 21 ++- packages/state-transition/src/types.ts | 2 + packages/state-transition/src/util/genesis.ts | 6 + .../test/unit/upgradeState.test.ts | 11 ++ packages/types/src/heze/index.ts | 5 + packages/types/src/heze/sszTypes.ts | 151 ++++++++++++++++++ packages/types/src/heze/types.ts | 21 +++ packages/types/src/sszTypes.ts | 13 ++ packages/types/src/types.ts | 43 +++++ packages/validator/src/util/params.ts | 10 ++ 38 files changed, 557 insertions(+), 21 deletions(-) create mode 100644 packages/state-transition/src/slot/upgradeStateToHeze.ts create mode 100644 packages/types/src/heze/index.ts create mode 100644 packages/types/src/heze/sszTypes.ts create mode 100644 packages/types/src/heze/types.ts diff --git a/packages/beacon-node/test/spec/presets/fork.test.ts b/packages/beacon-node/test/spec/presets/fork.test.ts index fcb9a6efad42..bf9b4c258c49 100644 --- a/packages/beacon-node/test/spec/presets/fork.test.ts +++ b/packages/beacon-node/test/spec/presets/fork.test.ts @@ -9,6 +9,7 @@ import { CachedBeaconStateDeneb, CachedBeaconStateElectra, CachedBeaconStateFulu, + CachedBeaconStateGloas, CachedBeaconStatePhase0, } from "@lodestar/state-transition"; import * as slotFns from "@lodestar/state-transition/slot"; @@ -44,6 +45,8 @@ const fork: TestRunnerFn = (forkNext) => { return slotFns.upgradeStateToFulu(preState as CachedBeaconStateElectra); case ForkName.gloas: return slotFns.upgradeStateToGloas(preState as CachedBeaconStateFulu); + case ForkName.heze: + return slotFns.upgradeStateToHeze(preState as CachedBeaconStateGloas); } }, options: { 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 cdd8cb3109cc..592637bec3be 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -735,7 +735,8 @@ const forkChoiceTest = shouldSkip: (_testcase, name, _index) => name.includes("invalid_incorrect_proof") || // TODO GLOAS: These tests will be unskipped by https://github.com/ChainSafe/lodestar/pull/9233 - (name.includes("gloas") && + // heze inherits gloas fork-choice, so the same proposer-boost-dependent-roots scenarios apply. + ((name.includes("gloas") || name.includes("heze")) && (name.includes("simple_attempted_reorg_without_enough_ffg_votes") || name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch") || name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_previous_epoch") || diff --git a/packages/beacon-node/test/spec/presets/transition.test.ts b/packages/beacon-node/test/spec/presets/transition.test.ts index ea411ede2b0a..220f07929e8a 100644 --- a/packages/beacon-node/test/spec/presets/transition.test.ts +++ b/packages/beacon-node/test/spec/presets/transition.test.ts @@ -127,6 +127,17 @@ function getTransitionConfig(fork: ForkName, forkEpoch: number): Partial = { @@ -33,6 +35,7 @@ const slots: Record = { electra: computeStartSlotAtEpoch(ELECTRA_FORK_EPOCH), fulu: computeStartSlotAtEpoch(FULU_FORK_EPOCH), gloas: computeStartSlotAtEpoch(GLOAS_FORK_EPOCH), + heze: computeStartSlotAtEpoch(HEZE_FORK_EPOCH), }; type BlockTestSet = { diff --git a/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts b/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts index a455f97c50ef..850ea882433f 100644 --- a/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts +++ b/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts @@ -17,6 +17,7 @@ describe("UpgradeLightClientHeader", () => { ELECTRA_FORK_EPOCH: 5, FULU_FORK_EPOCH: 6, GLOAS_FORK_EPOCH: 7, + HEZE_FORK_EPOCH: 8, }); const genesisValidatorsRoot = Buffer.alloc(32, 0xaa); @@ -32,6 +33,7 @@ describe("UpgradeLightClientHeader", () => { electra: ssz.deneb.LightClientHeader.defaultValue(), fulu: ssz.deneb.LightClientHeader.defaultValue(), gloas: ssz.deneb.LightClientHeader.defaultValue(), + heze: ssz.deneb.LightClientHeader.defaultValue(), }; testSlots = { @@ -43,6 +45,7 @@ describe("UpgradeLightClientHeader", () => { electra: 164, fulu: 216, gloas: 235, + heze: 260, }; }); diff --git a/packages/beacon-node/test/unit/network/fork.test.ts b/packages/beacon-node/test/unit/network/fork.test.ts index 3c995e0f38c5..471046c62ca9 100644 --- a/packages/beacon-node/test/unit/network/fork.test.ts +++ b/packages/beacon-node/test/unit/network/fork.test.ts @@ -12,6 +12,7 @@ function getForkConfig({ electra, fulu, gloas, + heze, }: { phase0: number; altair: number; @@ -21,6 +22,7 @@ function getForkConfig({ electra: number; fulu: number; gloas: number; + heze: number; }): BeaconConfig { const forks: Record = { phase0: { @@ -87,6 +89,14 @@ function getForkConfig({ prevVersion: Buffer.from([0, 0, 0, 6]), prevForkName: ForkName.fulu, }, + heze: { + name: ForkName.heze, + seq: ForkSeq.heze, + epoch: heze, + version: Buffer.from([0, 0, 0, 8]), + prevVersion: Buffer.from([0, 0, 0, 7]), + prevForkName: ForkName.gloas, + }, }; const forksAscendingEpochOrder = Object.values(forks); const forksDescendingEpochOrder = Object.values(forks).reverse(); @@ -171,9 +181,10 @@ for (const testScenario of testScenarios) { const electra = Infinity; const fulu = Infinity; const gloas = Infinity; + const heze = Infinity; describe(`network / fork: phase0: ${phase0}, altair: ${altair}, bellatrix: ${bellatrix} capella: ${capella}`, () => { - const forkConfig = getForkConfig({phase0, altair, bellatrix, capella, deneb, electra, fulu, gloas}); + const forkConfig = getForkConfig({phase0, altair, bellatrix, capella, deneb, electra, fulu, gloas, heze}); const forks = forkConfig.forks; for (const testCase of testCases) { const {epoch, currentFork, nextFork, activeForks} = testCase; diff --git a/packages/beacon-node/test/utils/blocksAndData.ts b/packages/beacon-node/test/utils/blocksAndData.ts index 3f23535e46a8..8a55ddcfe566 100644 --- a/packages/beacon-node/test/utils/blocksAndData.ts +++ b/packages/beacon-node/test/utils/blocksAndData.ts @@ -36,6 +36,7 @@ export const DENEB_FORK_EPOCH = 10; export const ELECTRA_FORK_EPOCH = 20; export const FULU_FORK_EPOCH = 30; export const GLOAS_FORK_EPOCH = 40; +export const HEZE_FORK_EPOCH = 50; export const config = createChainForkConfig({ ...defaultChainConfig, CAPELLA_FORK_EPOCH, @@ -43,6 +44,7 @@ export const config = createChainForkConfig({ ELECTRA_FORK_EPOCH, FULU_FORK_EPOCH, GLOAS_FORK_EPOCH, + HEZE_FORK_EPOCH, }); export const clock = new Clock({ config, @@ -60,6 +62,7 @@ export const slots: Record = { electra: computeStartSlotAtEpoch(ELECTRA_FORK_EPOCH), fulu: computeStartSlotAtEpoch(FULU_FORK_EPOCH), gloas: computeStartSlotAtEpoch(GLOAS_FORK_EPOCH), + heze: computeStartSlotAtEpoch(HEZE_FORK_EPOCH), }; /** diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 6025cb88431d..809758c5a3e1 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -60,6 +60,10 @@ export const chainConfig: ChainConfig = { GLOAS_FORK_VERSION: b("0x07000000"), GLOAS_FORK_EPOCH: Infinity, + // HEZE (EIP-7805) + HEZE_FORK_VERSION: b("0x08000000"), + HEZE_FORK_EPOCH: Infinity, + // Time parameters // --------------------------------------------------------------- // 12 seconds (DEPRECATED) @@ -76,6 +80,9 @@ export const chainConfig: ChainConfig = { SHARD_COMMITTEE_PERIOD: 256, // 2**11 (= 2,048) Eth1 blocks ~8 hours ETH1_FOLLOW_DISTANCE: 2048, + + // 67% of `SLOT_DURATION_MS` + INCLUSION_LIST_DUE_BPS: 6667, // 1667 basis points, ~17% of SLOT_DURATION_MS PROPOSER_REORG_CUTOFF_BPS: 1667, // 3333 basis points, ~33% of SLOT_DURATION_MS @@ -190,6 +197,14 @@ export const chainConfig: ChainConfig = { // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096, + // HEZE + // 2**4 (= 16) + MAX_REQUEST_INCLUSION_LIST: 16, + // 2**13 (=8192) + MAX_BYTES_PER_INCLUSION_LIST: 8192, + // 2**4 (= 16) + INCLUSION_LIST_COMMITTEE_SIZE: 16, + // Gloas // 2**7 (= 128) payloads MAX_REQUEST_PAYLOADS: 128, diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index ed9a3a8e22c1..f3cbae93a8ca 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -53,6 +53,9 @@ export const chainConfig: ChainConfig = { // GLOAS GLOAS_FORK_VERSION: b("0x07000001"), GLOAS_FORK_EPOCH: Infinity, + // HEZE (EIP-7805) + HEZE_FORK_VERSION: b("0x08000001"), + HEZE_FORK_EPOCH: Infinity, // Time parameters // --------------------------------------------------------------- @@ -70,6 +73,8 @@ export const chainConfig: ChainConfig = { SHARD_COMMITTEE_PERIOD: 64, // [customized] process deposits more quickly, but insecure ETH1_FOLLOW_DISTANCE: 16, + // 67% of `SLOT_DURATION_MS` + INCLUSION_LIST_DUE_BPS: 6667, // 1667 basis points, ~17% of SLOT_DURATION_MS PROPOSER_REORG_CUTOFF_BPS: 1667, // 3333 basis points, ~33% of SLOT_DURATION_MS @@ -193,6 +198,14 @@ export const chainConfig: ChainConfig = { // --------------------------------------------------------------- BLOB_SCHEDULE: [], + // HEZE + // 2**4 (= 16) + MAX_REQUEST_INCLUSION_LIST: 16, + // 2**13 (=8192) + MAX_BYTES_PER_INCLUSION_LIST: 8192, + // 2**4 (= 16) + INCLUSION_LIST_COMMITTEE_SIZE: 16, + // Fast Confirmation Rule // --------------------------------------------------------------- CONFIRMATION_BYZANTINE_THRESHOLD: 25, diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index e24135154daa..1c08f5e4f415 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -50,6 +50,9 @@ export type ChainConfig = { // GLOAS GLOAS_FORK_VERSION: Uint8Array; GLOAS_FORK_EPOCH: number; + // HEZE (EIP-7805) + HEZE_FORK_VERSION: Uint8Array; + HEZE_FORK_EPOCH: number; // Time parameters /** @deprecated Use `SLOT_DURATION_MS` instead. */ @@ -74,6 +77,8 @@ export type ChainConfig = { PAYLOAD_ATTESTATION_DUE_BPS: number; PAYLOAD_DUE_BPS: number; + INCLUSION_LIST_DUE_BPS: number; + // Validator cycle INACTIVITY_SCORE_BIAS: number; INACTIVITY_SCORE_RECOVERY_RATE: number; @@ -133,6 +138,11 @@ export type ChainConfig = { // Blob Scheduling BLOB_SCHEDULE: BlobSchedule; + // HEZE + MAX_REQUEST_INCLUSION_LIST: number; + MAX_BYTES_PER_INCLUSION_LIST: number; + INCLUSION_LIST_COMMITTEE_SIZE: number; + // Fast Confirmation Rule CONFIRMATION_BYZANTINE_THRESHOLD: number; }; @@ -174,6 +184,9 @@ export const chainConfigTypes: SpecTypes = { // GLOAS GLOAS_FORK_VERSION: "bytes", GLOAS_FORK_EPOCH: "number", + // HEZE (EIP-7805) + HEZE_FORK_VERSION: "bytes", + HEZE_FORK_EPOCH: "number", // Time parameters SECONDS_PER_SLOT: "number", @@ -197,6 +210,8 @@ export const chainConfigTypes: SpecTypes = { PAYLOAD_ATTESTATION_DUE_BPS: "number", PAYLOAD_DUE_BPS: "number", + INCLUSION_LIST_DUE_BPS: "number", + // Validator cycle INACTIVITY_SCORE_BIAS: "number", INACTIVITY_SCORE_RECOVERY_RATE: "number", @@ -250,6 +265,11 @@ export const chainConfigTypes: SpecTypes = { VALIDATOR_CUSTODY_REQUIREMENT: "number", BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: "number", + // HEZE + MAX_REQUEST_INCLUSION_LIST: "number", + MAX_BYTES_PER_INCLUSION_LIST: "number", + INCLUSION_LIST_COMMITTEE_SIZE: "number", + // Gloas MAX_REQUEST_PAYLOADS: "number", diff --git a/packages/config/src/forkConfig/index.ts b/packages/config/src/forkConfig/index.ts index 47bd518a3f55..0dd3f8a6e9d9 100644 --- a/packages/config/src/forkConfig/index.ts +++ b/packages/config/src/forkConfig/index.ts @@ -85,10 +85,18 @@ export function createForkConfig(config: ChainConfig): ForkConfig { prevVersion: config.FULU_FORK_VERSION, prevForkName: ForkName.fulu, }; + const heze: ForkInfo = { + name: ForkName.heze, + seq: ForkSeq.heze, + epoch: config.HEZE_FORK_EPOCH, + version: config.HEZE_FORK_VERSION, + prevVersion: config.GLOAS_FORK_VERSION, + prevForkName: ForkName.gloas, + }; /** Forks in order order of occurence, `phase0` first */ // Note: Downstream code relies on proper ordering. - const forks = {phase0, altair, bellatrix, capella, deneb, electra, fulu, gloas}; + const forks = {phase0, altair, bellatrix, capella, deneb, electra, fulu, gloas, heze}; // Prevents allocating an array on every getForkInfo() call const forksAscendingEpochOrder = Object.values(forks); diff --git a/packages/config/src/testUtils/config.ts b/packages/config/src/testUtils/config.ts index a3768bb9adf4..8ee0d16d0ccd 100644 --- a/packages/config/src/testUtils/config.ts +++ b/packages/config/src/testUtils/config.ts @@ -60,5 +60,17 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig { GLOAS_FORK_EPOCH: forkEpoch, BLOB_SCHEDULE: [], }); + case ForkName.heze: + return createChainForkConfig({ + ALTAIR_FORK_EPOCH: 0, + BELLATRIX_FORK_EPOCH: 0, + CAPELLA_FORK_EPOCH: 0, + DENEB_FORK_EPOCH: 0, + ELECTRA_FORK_EPOCH: 0, + FULU_FORK_EPOCH: 0, + GLOAS_FORK_EPOCH: 0, + HEZE_FORK_EPOCH: forkEpoch, + BLOB_SCHEDULE: [], + }); } } diff --git a/packages/config/test/e2e/ensure-config-is-synced.test.ts b/packages/config/test/e2e/ensure-config-is-synced.test.ts index 46e49f3bb8bc..c8999204590f 100644 --- a/packages/config/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/config/test/e2e/ensure-config-is-synced.test.ts @@ -15,13 +15,10 @@ import specTestsVersions from "../spec-tests-version.json" with {type: "json"}; const ignoredRemoteConfigFields: (keyof ChainConfig)[] = [ // BLOB_SCHEDULE is an array/JSON format that requires special parsing "BLOB_SCHEDULE" as keyof ChainConfig, - // EIP-7805 (Inclusion Lists) - not yet implemented in Lodestar + // HEZE (EIP-7805 Inclusion Lists) - timing params absent in the heze boilerplate (FOCIL feature, not yet implemented) "VIEW_FREEZE_CUTOFF_BPS" as keyof ChainConfig, "INCLUSION_LIST_SUBMISSION_DUE_BPS" as keyof ChainConfig, - "INCLUSION_LIST_DUE_BPS" as keyof ChainConfig, "PROPOSER_INCLUSION_LIST_CUTOFF_BPS" as keyof ChainConfig, - "MAX_REQUEST_INCLUSION_LIST" as keyof ChainConfig, - "MAX_BYTES_PER_INCLUSION_LIST" as keyof ChainConfig, // Networking params that may be in presets instead of chainConfig "ATTESTATION_SUBNET_COUNT" as keyof ChainConfig, "ATTESTATION_SUBNET_EXTRA_BITS" as keyof ChainConfig, @@ -34,8 +31,6 @@ const ignoredRemoteConfigFields: (keyof ChainConfig)[] = [ "CONSOLIDATION_CHURN_LIMIT_QUOTIENT" as keyof ChainConfig, "MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT_GLOAS" as keyof ChainConfig, // Future forks not yet implemented in Lodestar - "HEZE_FORK_VERSION" as keyof ChainConfig, - "HEZE_FORK_EPOCH" as keyof ChainConfig, "EIP7928_FORK_VERSION" as keyof ChainConfig, "EIP7928_FORK_EPOCH" as keyof ChainConfig, "EIP8025_FORK_VERSION" as keyof ChainConfig, @@ -49,6 +44,7 @@ const ignoredRemoteConfigFields: (keyof ChainConfig)[] = [ "ELECTRA_FORK_EPOCH", "FULU_FORK_EPOCH", "GLOAS_FORK_EPOCH", + "HEZE_FORK_EPOCH", // Terminal values are network-specific "TERMINAL_TOTAL_DIFFICULTY", "TERMINAL_BLOCK_HASH", diff --git a/packages/params/src/forkName.ts b/packages/params/src/forkName.ts index 439f4f0aa137..e7cca2f75764 100644 --- a/packages/params/src/forkName.ts +++ b/packages/params/src/forkName.ts @@ -10,6 +10,7 @@ export enum ForkName { electra = "electra", fulu = "fulu", gloas = "gloas", + heze = "heze", } /** @@ -24,6 +25,7 @@ export enum ForkSeq { electra = 5, fulu = 6, gloas = 7, + heze = 8, } function exclude(coll: T[], val: U[]): Exclude[] { @@ -127,6 +129,22 @@ export function isForkPostGloas(fork: ForkName): fork is ForkPostGloas { return isForkPostFulu(fork) && fork !== ForkName.fulu; } +export type ForkPreHeze = ForkPreGloas | ForkName.gloas; +export type ForkPostHeze = Exclude; +export const forkPostHeze = exclude(forkAll, [ + ForkName.phase0, + ForkName.altair, + ForkName.bellatrix, + ForkName.capella, + ForkName.deneb, + ForkName.electra, + ForkName.fulu, + ForkName.gloas, +]); +export function isForkPostHeze(fork: ForkName): fork is ForkPostHeze { + return isForkPostGloas(fork) && fork !== ForkName.gloas; +} + /* * Aliases only exported for backwards compatibility. This will be removed in * lodestar v2.0. The types and guards above should be used in all places as diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 34bc96977b4f..8a2870de5894 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -162,6 +162,7 @@ export const DOMAIN_BLS_TO_EXECUTION_CHANGE = Uint8Array.from([10, 0, 0, 0]); export const DOMAIN_BEACON_BUILDER = Uint8Array.from([11, 0, 0, 0]); export const DOMAIN_PTC_ATTESTER = Uint8Array.from([12, 0, 0, 0]); export const DOMAIN_PROPOSER_PREFERENCES = Uint8Array.from([13, 0, 0, 0]); +export const DOMAIN_INCLUSION_LIST_COMMITTEE = Uint8Array.from([14, 0, 0, 0]); // Application specific domains @@ -315,6 +316,9 @@ export const BYTES_PER_CELL = FIELD_ELEMENTS_PER_CELL * BYTES_PER_FIELD_ELEMENT; export const KZG_COMMITMENTS_GINDEX = 27; export const KZG_COMMITMENTS_SUBTREE_INDEX = KZG_COMMITMENTS_GINDEX - 2 ** KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH; +// HEZE +export const INCLUSION_LIST_COMMITTEE_SIZE = 16; + // Gloas Misc export const BUILDER_INDEX_FLAG = 2 ** 40; export const BUILDER_INDEX_SELF_BUILD = Infinity; diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index 4ec676b16471..a0473066d631 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -142,6 +142,10 @@ export const mainnetPreset: BeaconPreset = { FIELD_ELEMENTS_PER_EXT_BLOB: 8192, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4, + // HEZE + /////////// + INCLUSION_LIST_COMMITTEE_SIZE: 16, + // GLOAS PTC_SIZE: 512, MAX_PAYLOAD_ATTESTATIONS: 4, diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 9d46b81f02e9..6c26b0e6f070 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -143,6 +143,10 @@ export const minimalPreset: BeaconPreset = { FIELD_ELEMENTS_PER_EXT_BLOB: 8192, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4, + // HEZE + /////////// + INCLUSION_LIST_COMMITTEE_SIZE: 16, + // GLOAS PTC_SIZE: 16, MAX_PAYLOAD_ATTESTATIONS: 4, diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index e4451914fdfd..bcddd9b8c7c0 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -104,6 +104,10 @@ export type BeaconPreset = { FIELD_ELEMENTS_PER_EXT_BLOB: number; KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: number; + // HEZE + /////////// + INCLUSION_LIST_COMMITTEE_SIZE: number; + // GLOAS PTC_SIZE: number; MAX_PAYLOAD_ATTESTATIONS: number; @@ -219,6 +223,10 @@ export const beaconPresetTypes: BeaconPresetTypes = { FIELD_ELEMENTS_PER_EXT_BLOB: "number", KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: "number", + // HEZE + /////////// + INCLUSION_LIST_COMMITTEE_SIZE: "number", + // GLOAS PTC_SIZE: "number", MAX_PAYLOAD_ATTESTATIONS: "number", diff --git a/packages/state-transition/src/block/processExecutionPayloadBid.ts b/packages/state-transition/src/block/processExecutionPayloadBid.ts index c03513d00727..747a43d2849c 100644 --- a/packages/state-transition/src/block/processExecutionPayloadBid.ts +++ b/packages/state-transition/src/block/processExecutionPayloadBid.ts @@ -1,10 +1,10 @@ import {PublicKey, Signature, verify} from "@chainsafe/blst"; -import {BUILDER_INDEX_SELF_BUILD, ForkPostGloas, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {BeaconBlock, gloas, ssz} from "@lodestar/types"; +import {BUILDER_INDEX_SELF_BUILD, ForkPostGloas, ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {BeaconBlock, 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"; -import {CachedBeaconStateGloas} from "../types.js"; +import {CachedBeaconStateGloas, CachedBeaconStateHeze} from "../types.js"; import {canBuilderCoverBid, isActiveBuilder} from "../util/gloas.js"; import {getCurrentEpoch, getRandaoMix} from "../util/index.js"; @@ -84,7 +84,12 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block: state.builderPendingPayments.set(SLOTS_PER_EPOCH + (bid.slot % SLOTS_PER_EPOCH), pendingPaymentView); } - state.latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); + if (state.config.getForkSeq(state.slot) >= ForkSeq.heze) { + const hezeState = state as unknown as CachedBeaconStateHeze; + hezeState.latestExecutionPayloadBid = ssz.heze.ExecutionPayloadBid.toViewDU(bid as heze.ExecutionPayloadBid); + } else { + state.latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); + } } function verifyExecutionPayloadBidSignature( diff --git a/packages/state-transition/src/cache/stateCache.ts b/packages/state-transition/src/cache/stateCache.ts index 6054001a6027..020ca8cd994e 100644 --- a/packages/state-transition/src/cache/stateCache.ts +++ b/packages/state-transition/src/cache/stateCache.ts @@ -12,6 +12,7 @@ import { BeaconStateExecutions, BeaconStateFulu, BeaconStateGloas, + BeaconStateHeze, BeaconStatePhase0, } from "./types.js"; @@ -136,6 +137,7 @@ export type CachedBeaconStateDeneb = CachedBeaconState; export type CachedBeaconStateElectra = CachedBeaconState; export type CachedBeaconStateFulu = CachedBeaconState; export type CachedBeaconStateGloas = CachedBeaconState; +export type CachedBeaconStateHeze = CachedBeaconState; export type CachedBeaconStateAllForks = CachedBeaconState; export type CachedBeaconStateExecutions = CachedBeaconState; diff --git a/packages/state-transition/src/cache/types.ts b/packages/state-transition/src/cache/types.ts index 9a73c5b7e99f..2f01e0d73126 100644 --- a/packages/state-transition/src/cache/types.ts +++ b/packages/state-transition/src/cache/types.ts @@ -11,6 +11,7 @@ export type BeaconStateDeneb = CompositeViewDU>; export type BeaconStateFulu = CompositeViewDU>; export type BeaconStateGloas = CompositeViewDU>; +export type BeaconStateHeze = CompositeViewDU>; export type BeaconStateAllForks = CompositeViewDU>; export type BeaconStateExecutions = CompositeViewDU>; diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 5fd6b6be2316..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 { @@ -71,6 +73,7 @@ export type { BeaconStateExecutions, BeaconStateFulu, BeaconStateGloas, + BeaconStateHeze, // Non-cached states BeaconStatePhase0, CachedBeaconStateAllForks, @@ -82,6 +85,7 @@ export type { CachedBeaconStateExecutions, CachedBeaconStateFulu, CachedBeaconStateGloas, + CachedBeaconStateHeze, CachedBeaconStatePhase0, } from "./types.js"; export * from "./util/index.js"; diff --git a/packages/state-transition/src/lightClient/spec/utils.ts b/packages/state-transition/src/lightClient/spec/utils.ts index a571614e2e2e..ab7656e0fd7e 100644 --- a/packages/state-transition/src/lightClient/spec/utils.ts +++ b/packages/state-transition/src/lightClient/spec/utils.ts @@ -197,11 +197,18 @@ export function upgradeLightClientHeader( // Break if no further upgrades is required else fall through if (ForkSeq[targetFork] <= ForkSeq.fulu) break; + // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here case ForkName.gloas: // No changes to LightClientHeader in Gloas // Break if no further upgrades is required else fall through if (ForkSeq[targetFork] <= ForkSeq.gloas) break; + + case ForkName.heze: + // No changes to LightClientHeader in Heze + + // Break if no further upgrades is required else fall through + if (ForkSeq[targetFork] <= ForkSeq.heze) break; } return upgradedHeader; } diff --git a/packages/state-transition/src/signatureSets/executionPayloadBid.ts b/packages/state-transition/src/signatureSets/executionPayloadBid.ts index cebad9261b8e..4032e82c466a 100644 --- a/packages/state-transition/src/signatureSets/executionPayloadBid.ts +++ b/packages/state-transition/src/signatureSets/executionPayloadBid.ts @@ -1,14 +1,16 @@ import {BeaconConfig} from "@lodestar/config"; -import {DOMAIN_BEACON_BUILDER} from "@lodestar/params"; -import {Slot, gloas, ssz} from "@lodestar/types"; +import {DOMAIN_BEACON_BUILDER, ForkSeq} from "@lodestar/params"; +import {Slot, gloas, heze, ssz} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; export function getExecutionPayloadBidSigningRoot( config: BeaconConfig, stateSlot: Slot, - bid: gloas.ExecutionPayloadBid + bid: gloas.ExecutionPayloadBid | heze.ExecutionPayloadBid ): Uint8Array { const domain = config.getDomain(stateSlot, DOMAIN_BEACON_BUILDER); + const sszType = + config.getForkSeq(stateSlot) >= ForkSeq.heze ? ssz.heze.ExecutionPayloadBid : ssz.gloas.ExecutionPayloadBid; - return computeSigningRoot(ssz.gloas.ExecutionPayloadBid, bid, domain); + return computeSigningRoot(sszType, bid, domain); } diff --git a/packages/state-transition/src/slot/index.ts b/packages/state-transition/src/slot/index.ts index e373343fda3c..4022590901d3 100644 --- a/packages/state-transition/src/slot/index.ts +++ b/packages/state-transition/src/slot/index.ts @@ -10,6 +10,7 @@ export {upgradeStateToDeneb} from "./upgradeStateToDeneb.js"; export {upgradeStateToElectra} from "./upgradeStateToElectra.js"; export {upgradeStateToFulu} from "./upgradeStateToFulu.js"; export {upgradeStateToGloas} from "./upgradeStateToGloas.js"; +export {upgradeStateToHeze} from "./upgradeStateToHeze.js"; /** * Dial state to next slot. Common for all forks diff --git a/packages/state-transition/src/slot/upgradeStateToHeze.ts b/packages/state-transition/src/slot/upgradeStateToHeze.ts new file mode 100644 index 000000000000..412efe625aa0 --- /dev/null +++ b/packages/state-transition/src/slot/upgradeStateToHeze.ts @@ -0,0 +1,91 @@ +import {ssz} from "@lodestar/types"; +import {getCachedBeaconState} from "../cache/stateCache.js"; +import {CachedBeaconStateGloas, CachedBeaconStateHeze} from "../types.js"; + +/** + * Upgrade a state from Gloas to Heze. + * Spec: heze/fork.md `upgrade_to_heze`. + */ +export function upgradeStateToHeze(stateGloas: CachedBeaconStateGloas): CachedBeaconStateHeze { + const {config} = stateGloas; + + ssz.gloas.BeaconState.commitViewDU(stateGloas); + const stateHezeCloned = stateGloas; + + const stateHezeView = ssz.heze.BeaconState.defaultViewDU(); + + stateHezeView.genesisTime = stateHezeCloned.genesisTime; + stateHezeView.genesisValidatorsRoot = stateHezeCloned.genesisValidatorsRoot; + stateHezeView.slot = stateHezeCloned.slot; + stateHezeView.fork = ssz.phase0.Fork.toViewDU({ + previousVersion: stateGloas.fork.currentVersion, + currentVersion: config.HEZE_FORK_VERSION, + epoch: stateGloas.epochCtx.epoch, + }); + stateHezeView.latestBlockHeader = stateHezeCloned.latestBlockHeader; + stateHezeView.blockRoots = stateHezeCloned.blockRoots; + stateHezeView.stateRoots = stateHezeCloned.stateRoots; + stateHezeView.historicalRoots = stateHezeCloned.historicalRoots; + stateHezeView.eth1Data = stateHezeCloned.eth1Data; + stateHezeView.eth1DataVotes = stateHezeCloned.eth1DataVotes; + stateHezeView.eth1DepositIndex = stateHezeCloned.eth1DepositIndex; + stateHezeView.validators = stateHezeCloned.validators; + stateHezeView.balances = stateHezeCloned.balances; + stateHezeView.randaoMixes = stateHezeCloned.randaoMixes; + stateHezeView.slashings = stateHezeCloned.slashings; + stateHezeView.previousEpochParticipation = stateHezeCloned.previousEpochParticipation; + stateHezeView.currentEpochParticipation = stateHezeCloned.currentEpochParticipation; + stateHezeView.justificationBits = stateHezeCloned.justificationBits; + stateHezeView.previousJustifiedCheckpoint = stateHezeCloned.previousJustifiedCheckpoint; + stateHezeView.currentJustifiedCheckpoint = stateHezeCloned.currentJustifiedCheckpoint; + stateHezeView.finalizedCheckpoint = stateHezeCloned.finalizedCheckpoint; + stateHezeView.inactivityScores = stateHezeCloned.inactivityScores; + stateHezeView.currentSyncCommittee = stateHezeCloned.currentSyncCommittee; + stateHezeView.nextSyncCommittee = stateHezeCloned.nextSyncCommittee; + stateHezeView.latestBlockHash = stateHezeCloned.latestBlockHash; + stateHezeView.nextWithdrawalIndex = stateHezeCloned.nextWithdrawalIndex; + stateHezeView.nextWithdrawalValidatorIndex = stateHezeCloned.nextWithdrawalValidatorIndex; + stateHezeView.historicalSummaries = stateHezeCloned.historicalSummaries; + stateHezeView.depositRequestsStartIndex = stateHezeCloned.depositRequestsStartIndex; + stateHezeView.depositBalanceToConsume = stateHezeCloned.depositBalanceToConsume; + stateHezeView.exitBalanceToConsume = stateHezeCloned.exitBalanceToConsume; + stateHezeView.earliestExitEpoch = stateHezeCloned.earliestExitEpoch; + stateHezeView.consolidationBalanceToConsume = stateHezeCloned.consolidationBalanceToConsume; + stateHezeView.earliestConsolidationEpoch = stateHezeCloned.earliestConsolidationEpoch; + stateHezeView.pendingDeposits = stateHezeCloned.pendingDeposits; + stateHezeView.pendingPartialWithdrawals = stateHezeCloned.pendingPartialWithdrawals; + stateHezeView.pendingConsolidations = stateHezeCloned.pendingConsolidations; + stateHezeView.proposerLookahead = stateHezeCloned.proposerLookahead; + stateHezeView.builders = stateHezeCloned.builders; + stateHezeView.nextWithdrawalBuilderIndex = stateHezeCloned.nextWithdrawalBuilderIndex; + stateHezeView.executionPayloadAvailability = stateHezeCloned.executionPayloadAvailability; + stateHezeView.builderPendingPayments = stateHezeCloned.builderPendingPayments; + stateHezeView.builderPendingWithdrawals = stateHezeCloned.builderPendingWithdrawals; + + // [Modified in Heze:EIP7805] inclusion_list_bits = Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]() (default zero) + const oldBid = stateHezeCloned.latestExecutionPayloadBid; + const newBid = ssz.heze.ExecutionPayloadBid.defaultViewDU(); + newBid.parentBlockHash = oldBid.parentBlockHash; + newBid.parentBlockRoot = oldBid.parentBlockRoot; + newBid.blockHash = oldBid.blockHash; + newBid.prevRandao = oldBid.prevRandao; + newBid.feeRecipient = oldBid.feeRecipient; + newBid.gasLimit = oldBid.gasLimit; + newBid.builderIndex = oldBid.builderIndex; + newBid.slot = oldBid.slot; + newBid.value = oldBid.value; + newBid.executionPayment = oldBid.executionPayment; + newBid.blobKzgCommitments = oldBid.blobKzgCommitments; + newBid.executionRequestsRoot = oldBid.executionRequestsRoot; + stateHezeView.latestExecutionPayloadBid = newBid; + + stateHezeView.payloadExpectedWithdrawals = stateHezeCloned.payloadExpectedWithdrawals; + stateHezeView.ptcWindow = stateHezeCloned.ptcWindow; + + const stateHeze = getCachedBeaconState(stateHezeView, stateGloas); + stateHeze.commit(); + // biome-ignore lint/complexity/useLiteralKeys: It is a protected attribute + stateHeze["clearCache"](); + + return stateHeze; +} diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index e803ef90d765..554184953d46 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -16,6 +16,7 @@ import { upgradeStateToDeneb, upgradeStateToElectra, upgradeStateToGloas, + upgradeStateToHeze, } from "./slot/index.js"; import {upgradeStateToFulu} from "./slot/upgradeStateToFulu.js"; import { @@ -26,6 +27,7 @@ import { CachedBeaconStateDeneb, CachedBeaconStateElectra, CachedBeaconStateFulu, + CachedBeaconStateGloas, CachedBeaconStatePhase0, } from "./types.js"; import {computeEpochAtSlot} from "./util/index.js"; @@ -278,6 +280,9 @@ function processSlotsWithTransientCache( if (stateEpoch === config.GLOAS_FORK_EPOCH) { postState = upgradeStateToGloas(postState as CachedBeaconStateFulu) as CachedBeaconStateAllForks; } + if (stateEpoch === config.HEZE_FORK_EPOCH) { + postState = upgradeStateToHeze(postState as CachedBeaconStateGloas) as CachedBeaconStateAllForks; + } { const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.finalProcessEpoch}); diff --git a/packages/state-transition/src/stateView/interface.ts b/packages/state-transition/src/stateView/interface.ts index 75487c585845..9be5295027b9 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: electra.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/types.ts b/packages/state-transition/src/types.ts index 02a84f2b4c08..686c05d38e15 100644 --- a/packages/state-transition/src/types.ts +++ b/packages/state-transition/src/types.ts @@ -10,6 +10,7 @@ export type { CachedBeaconStateExecutions, CachedBeaconStateFulu, CachedBeaconStateGloas, + CachedBeaconStateHeze, CachedBeaconStatePhase0, } from "./cache/stateCache.js"; export type { @@ -22,6 +23,7 @@ export type { BeaconStateExecutions, BeaconStateFulu, BeaconStateGloas, + BeaconStateHeze, BeaconStatePhase0, ShufflingGetter, } from "./cache/types.js"; diff --git a/packages/state-transition/src/util/genesis.ts b/packages/state-transition/src/util/genesis.ts index 8ee6b9fda3a2..fc32c2562879 100644 --- a/packages/state-transition/src/util/genesis.ts +++ b/packages/state-transition/src/util/genesis.ts @@ -334,6 +334,12 @@ export function initializeBeaconStateFromEth1( stateGloas.fork.currentVersion = config.GLOAS_FORK_VERSION; } + if (fork >= ForkSeq.heze) { + const stateHeze = state as CompositeViewDU; + stateHeze.fork.previousVersion = config.HEZE_FORK_VERSION; + stateHeze.fork.currentVersion = config.HEZE_FORK_VERSION; + } + state.commit(); return state; diff --git a/packages/state-transition/test/unit/upgradeState.test.ts b/packages/state-transition/test/unit/upgradeState.test.ts index 573d819e4ab6..c43ae5630b74 100644 --- a/packages/state-transition/test/unit/upgradeState.test.ts +++ b/packages/state-transition/test/unit/upgradeState.test.ts @@ -94,5 +94,16 @@ function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig { FULU_FORK_EPOCH: 0, GLOAS_FORK_EPOCH: forkEpoch, }); + case ForkName.heze: + return createChainForkConfig({ + ALTAIR_FORK_EPOCH: 0, + BELLATRIX_FORK_EPOCH: 0, + CAPELLA_FORK_EPOCH: 0, + DENEB_FORK_EPOCH: 0, + ELECTRA_FORK_EPOCH: 0, + FULU_FORK_EPOCH: 0, + GLOAS_FORK_EPOCH: 0, + HEZE_FORK_EPOCH: forkEpoch, + }); } } diff --git a/packages/types/src/heze/index.ts b/packages/types/src/heze/index.ts new file mode 100644 index 000000000000..f34bd0a13167 --- /dev/null +++ b/packages/types/src/heze/index.ts @@ -0,0 +1,5 @@ +export * from "./types.js"; + +import * as ssz from "./sszTypes.js"; +import * as ts from "./types.js"; +export {ts, ssz}; diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts new file mode 100644 index 000000000000..274a5eb05ac7 --- /dev/null +++ b/packages/types/src/heze/sszTypes.ts @@ -0,0 +1,151 @@ +import {BitVectorType, ContainerType, ListCompositeType, VectorBasicType} from "@chainsafe/ssz"; +import {INCLUSION_LIST_COMMITTEE_SIZE, MAX_TRANSACTIONS_PER_PAYLOAD} from "@lodestar/params"; +import {ssz as bellatrixSsz} from "../bellatrix/index.js"; +import {ssz as denebSsz} from "../deneb/index.js"; +import {ssz as electraSsz} from "../electra/index.js"; +import {ssz as gloasSsz} from "../gloas/index.js"; +import {ssz as primitiveSsz} from "../primitive/index.js"; + +const {Slot, Root, BLSSignature, ValidatorIndex} = primitiveSsz; + +export const InclusionListCommittee = new VectorBasicType(ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE); + +// Per InclusionList container; bound is MAX_TRANSACTIONS_PER_PAYLOAD. +export const InclusionListTransactions = new ListCompositeType(bellatrixSsz.Transaction, MAX_TRANSACTIONS_PER_PAYLOAD); + +// Aggregated IL transactions surfaced in PayloadAttributes/EL: bounded by total committee output. +export const AggregatedInclusionListTransactions = new ListCompositeType( + bellatrixSsz.Transaction, + MAX_TRANSACTIONS_PER_PAYLOAD * INCLUSION_LIST_COMMITTEE_SIZE +); + +export const InclusionList = new ContainerType( + { + slot: Slot, + validatorIndex: ValidatorIndex, + inclusionListCommitteeRoot: Root, + transactions: InclusionListTransactions, + }, + {typeName: "InclusionList", jsonCase: "eth2"} +); + +export const SignedInclusionList = new ContainerType( + { + message: InclusionList, + signature: BLSSignature, + }, + {typeName: "SignedInclusionList", jsonCase: "eth2"} +); + +export const InclusionListByCommitteeIndicesRequest = new ContainerType( + { + slot: Slot, + committeeIndices: new BitVectorType(INCLUSION_LIST_COMMITTEE_SIZE), + }, + {typeName: "InclusionListByCommitteeIndicesRequest", jsonCase: "eth2"} +); + +export const ExecutionPayloadBid = new ContainerType( + { + ...gloasSsz.ExecutionPayloadBid.fields, + inclusionListBits: new BitVectorType(INCLUSION_LIST_COMMITTEE_SIZE), // [New in Heze:EIP7805] + }, + {typeName: "ExecutionPayloadBid", jsonCase: "eth2"} +); + +export const SignedExecutionPayloadBid = new ContainerType( + { + message: ExecutionPayloadBid, + signature: BLSSignature, + }, + {typeName: "SignedExecutionPayloadBid", jsonCase: "eth2"} +); + +export const DataColumnSidecar = gloasSsz.DataColumnSidecar; +export const DataColumnSidecars = gloasSsz.DataColumnSidecars; + +export const BeaconState = new ContainerType( + { + ...gloasSsz.BeaconState.fields, + latestExecutionPayloadBid: ExecutionPayloadBid, // [Modified in Heze:EIP7805] + }, + {typeName: "BeaconState", jsonCase: "eth2"} +); + +export const BeaconBlockBody = new ContainerType( + { + ...gloasSsz.BeaconBlockBody.fields, + signedExecutionPayloadBid: SignedExecutionPayloadBid, // [Modified in Heze:EIP7805] + }, + {typeName: "BeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true} +); + +export const BeaconBlock = new ContainerType( + { + ...gloasSsz.BeaconBlock.fields, + body: BeaconBlockBody, + }, + {typeName: "BeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true} +); + +export const SignedBeaconBlock = new ContainerType( + { + message: BeaconBlock, + signature: BLSSignature, + }, + {typeName: "SignedBeaconBlock", jsonCase: "eth2"} +); + +// Gloas does not define blinded block types (post-ePBS uses bid/envelope split). +// Heze inherits the electra blinded types for code paths still expecting them. +export const BlindedBeaconBlockBody = new ContainerType( + { + ...electraSsz.BlindedBeaconBlockBody.fields, + }, + {typeName: "BlindedBeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true} +); + +export const BlindedBeaconBlock = new ContainerType( + { + ...electraSsz.BlindedBeaconBlock.fields, + }, + {typeName: "BlindedBeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true} +); + +export const SignedBlindedBeaconBlock = new ContainerType( + { + ...electraSsz.SignedBlindedBeaconBlock.fields, + }, + {typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"} +); + +export const ExecutionPayload = new ContainerType( + { + ...gloasSsz.ExecutionPayload.fields, + }, + {typeName: "ExecutionPayload", jsonCase: "eth2"} +); + +export const ExecutionPayloadHeader = new ContainerType( + { + ...denebSsz.ExecutionPayloadHeader.fields, + }, + {typeName: "ExecutionPayloadHeader", jsonCase: "eth2"} +); + +// PayloadAttributes primarily for SSE event +export const PayloadAttributes = new ContainerType( + { + ...gloasSsz.PayloadAttributes.fields, + inclusionListTransactions: AggregatedInclusionListTransactions, + }, + {typeName: "PayloadAttributes", jsonCase: "eth2"} +); + +export const SSEPayloadAttributes = new ContainerType( + { + ...bellatrixSsz.SSEPayloadAttributesCommon.fields, + payloadAttributes: PayloadAttributes, + }, + {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} +); diff --git a/packages/types/src/heze/types.ts b/packages/types/src/heze/types.ts new file mode 100644 index 000000000000..62595e6a8308 --- /dev/null +++ b/packages/types/src/heze/types.ts @@ -0,0 +1,21 @@ +import {ValueOf} from "@chainsafe/ssz"; +import * as ssz from "./sszTypes.js"; + +export type InclusionListCommittee = ValueOf; +export type InclusionList = ValueOf; +export type SignedInclusionList = ValueOf; +export type InclusionListByCommitteeIndicesRequest = ValueOf; + +export type ExecutionPayloadBid = ValueOf; +export type SignedExecutionPayloadBid = ValueOf; + +export type BeaconState = ValueOf; +export type BeaconBlockBody = ValueOf; +export type BeaconBlock = ValueOf; +export type SignedBeaconBlock = ValueOf; +export type BlindedBeaconBlockBody = ValueOf; +export type BlindedBeaconBlock = ValueOf; +export type SignedBlindedBeaconBlock = ValueOf; +export type ExecutionPayload = ValueOf; +export type ExecutionPayloadHeader = ValueOf; +export type SSEPayloadAttributes = ValueOf; diff --git a/packages/types/src/sszTypes.ts b/packages/types/src/sszTypes.ts index c6763c66564d..e4bd286511c1 100644 --- a/packages/types/src/sszTypes.ts +++ b/packages/types/src/sszTypes.ts @@ -7,6 +7,7 @@ import {ssz as denebSsz} from "./deneb/index.js"; import {ssz as electraSsz} from "./electra/index.js"; import {ssz as fuluSsz} from "./fulu/index.js"; import {ssz as gloasSsz} from "./gloas/index.js"; +import {ssz as hezeSsz} from "./heze/index.js"; import {ssz as phase0Ssz} from "./phase0/index.js"; export * from "./primitive/sszTypes.js"; @@ -33,6 +34,17 @@ const typesByFork = { ...fuluSsz, ...gloasSsz, }, + [ForkName.heze]: { + ...phase0Ssz, + ...altairSsz, + ...bellatrixSsz, + ...capellaSsz, + ...denebSsz, + ...electraSsz, + ...fuluSsz, + ...gloasSsz, + ...hezeSsz, + }, }; // Export these types to ensure that each fork is a superset of the previous one (with overridden types obviously) @@ -46,6 +58,7 @@ export const deneb = typesByFork[ForkName.deneb]; export const electra = typesByFork[ForkName.electra]; export const fulu = typesByFork[ForkName.fulu]; export const gloas = typesByFork[ForkName.gloas]; +export const heze = typesByFork[ForkName.heze]; /** * A type of union of forks must accept as any parameter the UNION of all fork types. diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index e4cf1d2e7406..fe1ccb37d681 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -15,6 +15,7 @@ import {ts as deneb} from "./deneb/index.js"; import {ts as electra} from "./electra/index.js"; import {ts as fulu} from "./fulu/index.js"; import {ts as gloas} from "./gloas/index.js"; +import {ts as heze} from "./heze/index.js"; import {ts as phase0} from "./phase0/index.js"; import {Slot} from "./primitive/types.js"; @@ -25,6 +26,7 @@ export {ts as deneb} from "./deneb/index.js"; export {ts as electra} from "./electra/index.js"; export {ts as fulu} from "./fulu/index.js"; export {ts as gloas} from "./gloas/index.js"; +export {ts as heze} from "./heze/index.js"; export {ts as phase0} from "./phase0/index.js"; export * from "./primitive/types.js"; @@ -326,6 +328,47 @@ type TypesByFork = { DataColumnSidecar: gloas.DataColumnSidecar; DataColumnSidecars: gloas.DataColumnSidecars; }; + [ForkName.heze]: { + BeaconBlockHeader: phase0.BeaconBlockHeader; + SignedBeaconBlockHeader: phase0.SignedBeaconBlockHeader; + BeaconBlock: heze.BeaconBlock; + BeaconBlockBody: heze.BeaconBlockBody; + BeaconState: heze.BeaconState; + SignedBeaconBlock: heze.SignedBeaconBlock; + Metadata: fulu.Metadata; + Status: fulu.Status; + LightClientHeader: deneb.LightClientHeader; + LightClientBootstrap: electra.LightClientBootstrap; + LightClientUpdate: electra.LightClientUpdate; + LightClientFinalityUpdate: electra.LightClientFinalityUpdate; + LightClientOptimisticUpdate: electra.LightClientOptimisticUpdate; + LightClientStore: electra.LightClientStore; + BlindedBeaconBlock: electra.BlindedBeaconBlock; + BlindedBeaconBlockBody: electra.BlindedBeaconBlockBody; + SignedBlindedBeaconBlock: electra.SignedBlindedBeaconBlock; + ExecutionPayload: gloas.ExecutionPayload; + ExecutionPayloadHeader: deneb.ExecutionPayloadHeader; + BuilderBid: electra.BuilderBid; + SignedBuilderBid: electra.SignedBuilderBid; + SSEPayloadAttributes: heze.SSEPayloadAttributes; + BlockContents: fulu.BlockContents; + SignedBlockContents: fulu.SignedBlockContents; + ExecutionPayloadAndBlobsBundle: fulu.ExecutionPayloadAndBlobsBundle; + BlobsBundle: fulu.BlobsBundle; + SyncCommittee: altair.SyncCommittee; + SyncAggregate: altair.SyncAggregate; + SingleAttestation: electra.SingleAttestation; + Attestation: electra.Attestation; + IndexedAttestation: electra.IndexedAttestation; + IndexedAttestationBigint: electra.IndexedAttestationBigint; + AttesterSlashing: electra.AttesterSlashing; + AggregateAndProof: electra.AggregateAndProof; + SignedAggregateAndProof: electra.SignedAggregateAndProof; + ExecutionRequests: electra.ExecutionRequests; + ExecutionPayloadBid: heze.ExecutionPayloadBid; + DataColumnSidecar: gloas.DataColumnSidecar; + DataColumnSidecars: gloas.DataColumnSidecars; + }; }; export type TypesFor = K extends void diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index e2fcb3f7fff6..46b76c48d6de 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -102,6 +102,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Wed, 10 Jun 2026 21:31:18 -0700 Subject: [PATCH 02/22] chore: order heze params after gloas and validate heze specrefs/config Move heze constants/presets after gloas to match fork sequence, validate the heze fork version, inclusion-list config and committee-size values against the remote spec (ensure-config-is-synced), and wire ethspecify sources for the heze constant/preset/config references so they are no longer skipped. --- .../test/spec/presets/fork_choice.test.ts | 1 - .../test/e2e/ensure-config-is-synced.test.ts | 4 ---- packages/params/src/index.ts | 6 +++--- packages/params/src/presets/mainnet.ts | 7 +++---- packages/params/src/presets/minimal.ts | 7 +++---- packages/params/src/types.ts | 14 ++++++------- packages/validator/src/util/params.ts | 18 ++++++++--------- specrefs/.ethspecify.yml | 14 +------------ specrefs/configs.yml | 20 ++++++++++++++----- specrefs/constants.yml | 4 +++- specrefs/presets.yml | 4 +++- 11 files changed, 46 insertions(+), 53 deletions(-) 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 592637bec3be..708b395a0793 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -735,7 +735,6 @@ const forkChoiceTest = shouldSkip: (_testcase, name, _index) => name.includes("invalid_incorrect_proof") || // TODO GLOAS: These tests will be unskipped by https://github.com/ChainSafe/lodestar/pull/9233 - // heze inherits gloas fork-choice, so the same proposer-boost-dependent-roots scenarios apply. ((name.includes("gloas") || name.includes("heze")) && (name.includes("simple_attempted_reorg_without_enough_ffg_votes") || name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch") || diff --git a/packages/config/test/e2e/ensure-config-is-synced.test.ts b/packages/config/test/e2e/ensure-config-is-synced.test.ts index c8999204590f..de3dac1e493d 100644 --- a/packages/config/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/config/test/e2e/ensure-config-is-synced.test.ts @@ -15,10 +15,6 @@ import specTestsVersions from "../spec-tests-version.json" with {type: "json"}; const ignoredRemoteConfigFields: (keyof ChainConfig)[] = [ // BLOB_SCHEDULE is an array/JSON format that requires special parsing "BLOB_SCHEDULE" as keyof ChainConfig, - // HEZE (EIP-7805 Inclusion Lists) - timing params absent in the heze boilerplate (FOCIL feature, not yet implemented) - "VIEW_FREEZE_CUTOFF_BPS" as keyof ChainConfig, - "INCLUSION_LIST_SUBMISSION_DUE_BPS" as keyof ChainConfig, - "PROPOSER_INCLUSION_LIST_CUTOFF_BPS" as keyof ChainConfig, // Networking params that may be in presets instead of chainConfig "ATTESTATION_SUBNET_COUNT" as keyof ChainConfig, "ATTESTATION_SUBNET_EXTRA_BITS" as keyof ChainConfig, diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 8a2870de5894..e2a7d5919ae2 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -316,11 +316,11 @@ export const BYTES_PER_CELL = FIELD_ELEMENTS_PER_CELL * BYTES_PER_FIELD_ELEMENT; export const KZG_COMMITMENTS_GINDEX = 27; export const KZG_COMMITMENTS_SUBTREE_INDEX = KZG_COMMITMENTS_GINDEX - 2 ** KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH; -// HEZE -export const INCLUSION_LIST_COMMITTEE_SIZE = 16; - // Gloas Misc export const BUILDER_INDEX_FLAG = 2 ** 40; export const BUILDER_INDEX_SELF_BUILD = Infinity; export const BUILDER_PAYMENT_THRESHOLD_NUMERATOR = 6; export const BUILDER_PAYMENT_THRESHOLD_DENOMINATOR = 10; + +// HEZE +export const INCLUSION_LIST_COMMITTEE_SIZE = 16; diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index a0473066d631..627580c67b49 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -142,14 +142,13 @@ export const mainnetPreset: BeaconPreset = { FIELD_ELEMENTS_PER_EXT_BLOB: 8192, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4, - // HEZE - /////////// - INCLUSION_LIST_COMMITTEE_SIZE: 16, - // GLOAS PTC_SIZE: 512, MAX_PAYLOAD_ATTESTATIONS: 4, BUILDER_REGISTRY_LIMIT: 1099511627776, // 2**40 BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576, // 2**20 MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384, // 2**14 + + // HEZE + INCLUSION_LIST_COMMITTEE_SIZE: 16, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 6c26b0e6f070..81e18e8b018d 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -143,14 +143,13 @@ export const minimalPreset: BeaconPreset = { FIELD_ELEMENTS_PER_EXT_BLOB: 8192, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4, - // HEZE - /////////// - INCLUSION_LIST_COMMITTEE_SIZE: 16, - // GLOAS PTC_SIZE: 16, MAX_PAYLOAD_ATTESTATIONS: 4, BUILDER_REGISTRY_LIMIT: 1099511627776, // 2**40 BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576, // 2**20 MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16, // 2**4 + + // HEZE + INCLUSION_LIST_COMMITTEE_SIZE: 16, }; diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index bcddd9b8c7c0..3c50569bf6fd 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -104,16 +104,15 @@ export type BeaconPreset = { FIELD_ELEMENTS_PER_EXT_BLOB: number; KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: number; - // HEZE - /////////// - INCLUSION_LIST_COMMITTEE_SIZE: number; - // GLOAS PTC_SIZE: number; MAX_PAYLOAD_ATTESTATIONS: number; BUILDER_REGISTRY_LIMIT: number; BUILDER_PENDING_WITHDRAWALS_LIMIT: number; MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: number; + + // HEZE + INCLUSION_LIST_COMMITTEE_SIZE: number; }; /** @@ -223,16 +222,15 @@ export const beaconPresetTypes: BeaconPresetTypes = { FIELD_ELEMENTS_PER_EXT_BLOB: "number", KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: "number", - // HEZE - /////////// - INCLUSION_LIST_COMMITTEE_SIZE: "number", - // GLOAS PTC_SIZE: "number", MAX_PAYLOAD_ATTESTATIONS: "number", BUILDER_REGISTRY_LIMIT: "number", BUILDER_PENDING_WITHDRAWALS_LIMIT: "number", MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: "number", + + // HEZE + INCLUSION_LIST_COMMITTEE_SIZE: "number", }; type BeaconPresetTypes = { diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 46b76c48d6de..acc670bd69ef 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -142,12 +142,12 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record - name: HEZE_FORK_EPOCH#heze - sources: [] + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "HEZE_FORK_EPOCH:" spec: | HEZE_FORK_EPOCH: Epoch = 18446744073709551615 - name: HEZE_FORK_VERSION#heze - sources: [] + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "HEZE_FORK_VERSION:" spec: | HEZE_FORK_VERSION: Version = '0x08000000' @@ -383,7 +387,9 @@ - name: INCLUSION_LIST_DUE_BPS#heze - sources: [] + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "INCLUSION_LIST_DUE_BPS:" spec: | INCLUSION_LIST_DUE_BPS: uint64 = 6667 @@ -417,7 +423,9 @@ - name: MAX_BYTES_PER_INCLUSION_LIST#heze - sources: [] + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "MAX_BYTES_PER_INCLUSION_LIST:" spec: | MAX_BYTES_PER_INCLUSION_LIST = 8192 @@ -478,7 +486,9 @@ - name: MAX_REQUEST_INCLUSION_LIST#heze - sources: [] + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "MAX_REQUEST_INCLUSION_LIST:" spec: | MAX_REQUEST_INCLUSION_LIST = 16 diff --git a/specrefs/constants.yml b/specrefs/constants.yml index fc08509f0329..fe758a90b0ed 100644 --- a/specrefs/constants.yml +++ b/specrefs/constants.yml @@ -223,7 +223,9 @@ - name: DOMAIN_INCLUSION_LIST_COMMITTEE#heze - sources: [] + sources: + - file: packages/params/src/index.ts + search: export const DOMAIN_INCLUSION_LIST_COMMITTEE = spec: | DOMAIN_INCLUSION_LIST_COMMITTEE: DomainType = '0x0E000000' diff --git a/specrefs/presets.yml b/specrefs/presets.yml index d090b4239cc7..afe30f96d486 100644 --- a/specrefs/presets.yml +++ b/specrefs/presets.yml @@ -179,7 +179,9 @@ - name: INCLUSION_LIST_COMMITTEE_SIZE#heze - sources: [] + sources: + - file: packages/params/src/presets/mainnet.ts + search: "INCLUSION_LIST_COMMITTEE_SIZE:" spec: | INCLUSION_LIST_COMMITTEE_SIZE: uint64 = 16 From bd9dee282bc5cc6f1efd78ef09260eaba7d10e62 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:03:49 -0700 Subject: [PATCH 03/22] chore: order hezeForkRelevant after gloas in validator spec params --- packages/validator/src/util/params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index acc670bd69ef..40cd179a2e22 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -102,8 +102,8 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Wed, 10 Jun 2026 22:07:15 -0700 Subject: [PATCH 04/22] chore: validate heze container specrefs --- specrefs/.ethspecify.yml | 7 ------- specrefs/containers.yml | 20 +++++++++++++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml index 7e88a36b6c91..a646b4afd4e3 100644 --- a/specrefs/.ethspecify.yml +++ b/specrefs/.ethspecify.yml @@ -69,13 +69,6 @@ exceptions: - PartialDataColumnPartsMetadata#fulu - PartialDataColumnSidecar#fulu - # heze (not implemented) - - BeaconState#heze - - ExecutionPayloadBid#heze - - InclusionList#heze - - SignedExecutionPayloadBid#heze - - SignedInclusionList#heze - dataclasses: # phase0 - ForkChoiceNode#phase0 diff --git a/specrefs/containers.yml b/specrefs/containers.yml index c1ab68597329..19e831c1a839 100644 --- a/specrefs/containers.yml +++ b/specrefs/containers.yml @@ -636,7 +636,9 @@ - name: BeaconState#heze - sources: [] + sources: + - file: packages/types/src/heze/sszTypes.ts + search: export const BeaconState = spec: | class BeaconState(Container): @@ -1038,7 +1040,9 @@ - name: ExecutionPayloadBid#heze - sources: [] + sources: + - file: packages/types/src/heze/sszTypes.ts + search: export const ExecutionPayloadBid = spec: | class ExecutionPayloadBid(Container): @@ -1209,7 +1213,9 @@ - name: InclusionList#heze - sources: [] + sources: + - file: packages/types/src/heze/sszTypes.ts + search: export const InclusionList = spec: | class InclusionList(Container): @@ -1657,7 +1663,9 @@ - name: SignedExecutionPayloadBid#heze - sources: [] + sources: + - file: packages/types/src/heze/sszTypes.ts + search: export const SignedExecutionPayloadBid = spec: | class SignedExecutionPayloadBid(Container): @@ -1678,7 +1686,9 @@ - name: SignedInclusionList#heze - sources: [] + sources: + - file: packages/types/src/heze/sszTypes.ts + search: export const SignedInclusionList = spec: | class SignedInclusionList(Container): From e44ec09df2b1dbedd48d0e84237330be20e9cfca Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:25:12 -0700 Subject: [PATCH 05/22] fix: expose heze DOMAIN_INCLUSION_LIST_COMMITTEE and update forkName snapshot Add DOMAIN_INCLUSION_LIST_COMMITTEE to the config API specConstants so the e2e config test (which scans heze spec docs) sees it exposed, and refresh the forkName snapshot to include heze (shared by the browser test via the test/browser -> ./unit symlink). --- packages/beacon-node/src/api/impl/config/constants.ts | 4 ++++ .../params/test/unit/__snapshots__/forkName.test.ts.snap | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/packages/beacon-node/src/api/impl/config/constants.ts b/packages/beacon-node/src/api/impl/config/constants.ts index a5bf170e58e0..7aa5ec37daee 100644 --- a/packages/beacon-node/src/api/impl/config/constants.ts +++ b/packages/beacon-node/src/api/impl/config/constants.ts @@ -23,6 +23,7 @@ import { DOMAIN_BLS_TO_EXECUTION_CHANGE, DOMAIN_CONTRIBUTION_AND_PROOF, DOMAIN_DEPOSIT, + DOMAIN_INCLUSION_LIST_COMMITTEE, DOMAIN_PROPOSER_PREFERENCES, DOMAIN_PTC_ATTESTER, DOMAIN_RANDAO, @@ -140,6 +141,9 @@ export const specConstants = { BUILDER_INDEX_SELF_BUILD, BUILDER_PAYMENT_THRESHOLD_NUMERATOR, BUILDER_PAYMENT_THRESHOLD_DENOMINATOR, + + // heze + DOMAIN_INCLUSION_LIST_COMMITTEE, }; /** Convert single-byte numbers to hex strings for API spec compliance */ diff --git a/packages/params/test/unit/__snapshots__/forkName.test.ts.snap b/packages/params/test/unit/__snapshots__/forkName.test.ts.snap index 4991cf14f722..ce7035e9c447 100644 --- a/packages/params/test/unit/__snapshots__/forkName.test.ts.snap +++ b/packages/params/test/unit/__snapshots__/forkName.test.ts.snap @@ -10,6 +10,7 @@ exports[`forkName > should have valid allForks 1`] = ` "electra", "fulu", "gloas", + "heze", ] `; @@ -22,6 +23,7 @@ exports[`forkName > should have valid post-altair forks 1`] = ` "electra", "fulu", "gloas", + "heze", ] `; @@ -33,6 +35,7 @@ exports[`forkName > should have valid post-bellatrix forks 1`] = ` "electra", "fulu", "gloas", + "heze", ] `; @@ -43,6 +46,7 @@ exports[`forkName > should have valid post-capella forks 1`] = ` "electra", "fulu", "gloas", + "heze", ] `; @@ -52,5 +56,6 @@ exports[`forkName > should have valid post-deneb forks 1`] = ` "electra", "fulu", "gloas", + "heze", ] `; From 0bfc1207c82933d55bc519ef964dd74e7d1e86e4 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:02:59 -0700 Subject: [PATCH 06/22] fix: route heze bid gossip and SSE payload attributes through heze ssz - getGossipSSZType: dispatch SignedExecutionPayloadBid via sszTypesFor(fork) so heze bids include inclusionListBits instead of being decoded as gloas and rejected as invalid serialized bytes. - preparePayloadAttributes: set inclusionListTransactions to an empty list post-heze so the heze SSEPayloadAttributes container can be serialized. Aggregation pool wiring lands with the focil feature (TODO HEZE). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../beacon-node/src/chain/produceBlock/produceBlockBody.ts | 6 ++++++ packages/beacon-node/src/network/gossip/topic.ts | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index c9542d1519fb..9b4ef0ba63d1 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -48,6 +48,7 @@ import { electra, fulu, gloas, + heze, ssz, } from "@lodestar/types"; import {GWEI_TO_WEI, Logger, byteArrayEquals, fromHex, sleep, toHex, toPubkeyHex, toRootHex} from "@lodestar/utils"; @@ -935,6 +936,11 @@ function preparePayloadAttributes( ); } + if (ForkSeq[fork] >= ForkSeq.heze) { + // TODO HEZE: populate from inclusion list pool once IL aggregation is wired up. + (payloadAttributes as heze.SSEPayloadAttributes["payloadAttributes"]).inclusionListTransactions = []; + } + return payloadAttributes; } diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 2ad9b5173a65..bd252f331a8e 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -7,6 +7,7 @@ import { isForkPostAltair, isForkPostElectra, isForkPostFulu, + isForkPostGloas, } from "@lodestar/params"; import {Attestation, SingleAttestation, ssz, sszTypesFor} from "@lodestar/types"; import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js"; @@ -124,7 +125,7 @@ export function getGossipSSZType(topic: GossipTopic) { case GossipType.payload_attestation_message: return ssz.gloas.PayloadAttestationMessage; case GossipType.execution_payload_bid: - return ssz.gloas.SignedExecutionPayloadBid; + return isForkPostGloas(fork) ? sszTypesFor(fork).SignedExecutionPayloadBid : ssz.gloas.SignedExecutionPayloadBid; case GossipType.proposer_preferences: return ssz.gloas.SignedProposerPreferences; } From bdafb91a485e6975d9bde385393b2d7d366642d8 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 8 Jul 2026 20:01:03 -0700 Subject: [PATCH 07/22] fix: remove inclusion_list_bits from heze ExecutionPayloadBid Match spec-tests v1.7.0-alpha.11, where the IL bitlist was removed from the bid (consensus-specs #5371). It is re-added post-alpha.11 (#5410); that change will land on the alpha.12 branch instead. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/state-transition/src/slot/upgradeStateToHeze.ts | 1 - packages/state-transition/src/stateView/interface.ts | 1 - packages/types/src/heze/sszTypes.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/packages/state-transition/src/slot/upgradeStateToHeze.ts b/packages/state-transition/src/slot/upgradeStateToHeze.ts index 412efe625aa0..5ef384a020bf 100644 --- a/packages/state-transition/src/slot/upgradeStateToHeze.ts +++ b/packages/state-transition/src/slot/upgradeStateToHeze.ts @@ -62,7 +62,6 @@ export function upgradeStateToHeze(stateGloas: CachedBeaconStateGloas): CachedBe stateHezeView.builderPendingPayments = stateHezeCloned.builderPendingPayments; stateHezeView.builderPendingWithdrawals = stateHezeCloned.builderPendingWithdrawals; - // [Modified in Heze:EIP7805] inclusion_list_bits = Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]() (default zero) const oldBid = stateHezeCloned.latestExecutionPayloadBid; const newBid = ssz.heze.ExecutionPayloadBid.defaultViewDU(); newBid.parentBlockHash = oldBid.parentBlockHash; diff --git a/packages/state-transition/src/stateView/interface.ts b/packages/state-transition/src/stateView/interface.ts index 7387e71b1e4a..248a652c1cae 100644 --- a/packages/state-transition/src/stateView/interface.ts +++ b/packages/state-transition/src/stateView/interface.ts @@ -274,7 +274,6 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu { /** Heze+ state fields — use isStatePostHeze() guard */ export interface IBeaconStateViewHeze extends IBeaconStateViewGloas { forkName: ForkPostHeze; - /** Modified in heze: ExecutionPayloadBid carries `inclusion_list_bits`. */ latestExecutionPayloadBid: heze.ExecutionPayloadBid; } diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index 274a5eb05ac7..c3841b42776b 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -48,7 +48,6 @@ export const InclusionListByCommitteeIndicesRequest = new ContainerType( export const ExecutionPayloadBid = new ContainerType( { ...gloasSsz.ExecutionPayloadBid.fields, - inclusionListBits: new BitVectorType(INCLUSION_LIST_COMMITTEE_SIZE), // [New in Heze:EIP7805] }, {typeName: "ExecutionPayloadBid", jsonCase: "eth2"} ); From a61b7b8a9fe3fd663bddc5324e78a09330caa924 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:37:50 -0700 Subject: [PATCH 08/22] Revert "fix: remove inclusion_list_bits from heze ExecutionPayloadBid" This reverts commit bdafb91a485e6975d9bde385393b2d7d366642d8. inclusion_list_bits is re-added to the bid post-alpha.11 (consensus-specs #5410), so removing it was a regression against the latest spec. Add the Bitvector[INCLUSION_LIST_COMMITTEE_SIZE] field back and wait for the alpha.12 spec-tests, where the field is present again. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/state-transition/src/slot/upgradeStateToHeze.ts | 1 + packages/state-transition/src/stateView/interface.ts | 1 + packages/types/src/heze/sszTypes.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/state-transition/src/slot/upgradeStateToHeze.ts b/packages/state-transition/src/slot/upgradeStateToHeze.ts index 5ef384a020bf..412efe625aa0 100644 --- a/packages/state-transition/src/slot/upgradeStateToHeze.ts +++ b/packages/state-transition/src/slot/upgradeStateToHeze.ts @@ -62,6 +62,7 @@ export function upgradeStateToHeze(stateGloas: CachedBeaconStateGloas): CachedBe stateHezeView.builderPendingPayments = stateHezeCloned.builderPendingPayments; stateHezeView.builderPendingWithdrawals = stateHezeCloned.builderPendingWithdrawals; + // [Modified in Heze:EIP7805] inclusion_list_bits = Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]() (default zero) const oldBid = stateHezeCloned.latestExecutionPayloadBid; const newBid = ssz.heze.ExecutionPayloadBid.defaultViewDU(); newBid.parentBlockHash = oldBid.parentBlockHash; diff --git a/packages/state-transition/src/stateView/interface.ts b/packages/state-transition/src/stateView/interface.ts index 248a652c1cae..7387e71b1e4a 100644 --- a/packages/state-transition/src/stateView/interface.ts +++ b/packages/state-transition/src/stateView/interface.ts @@ -274,6 +274,7 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu { /** Heze+ state fields — use isStatePostHeze() guard */ export interface IBeaconStateViewHeze extends IBeaconStateViewGloas { forkName: ForkPostHeze; + /** Modified in heze: ExecutionPayloadBid carries `inclusion_list_bits`. */ latestExecutionPayloadBid: heze.ExecutionPayloadBid; } diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index c3841b42776b..274a5eb05ac7 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -48,6 +48,7 @@ export const InclusionListByCommitteeIndicesRequest = new ContainerType( export const ExecutionPayloadBid = new ContainerType( { ...gloasSsz.ExecutionPayloadBid.fields, + inclusionListBits: new BitVectorType(INCLUSION_LIST_COMMITTEE_SIZE), // [New in Heze:EIP7805] }, {typeName: "ExecutionPayloadBid", jsonCase: "eth2"} ); From 9dbac307145fde9e401a4823c940301fb21b362d Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 8 Jul 2026 21:53:51 -0700 Subject: [PATCH 09/22] refactor: address heze boilerplate review comments - Drop spurious `(EIP-7805)` from HEZE config comments to match the style of other forks - Remove heze redefinitions of ExecutionPayload, ExecutionPayloadHeader and the blinded block types: heze does not modify them per the alpha.12 spec, so inherit them from gloas/deneb/electra via the fork type registry (mirrors gloas, which defines none of these) - Type getExecutionPayloadBidSigningRoot's bid param with the top-level ExecutionPayloadBid instead of a gloas|heze union - Drop the `as unknown` double cast in processExecutionPayloadBid by widening through CachedBeaconStateAllForks Co-Authored-By: Claude Opus 4.8 (1M context) --- .../config/src/chainConfig/configs/mainnet.ts | 2 +- .../config/src/chainConfig/configs/minimal.ts | 2 +- packages/config/src/chainConfig/types.ts | 2 +- .../src/block/processExecutionPayloadBid.ts | 13 ++++--- .../src/signatureSets/executionPayloadBid.ts | 4 +- packages/types/src/heze/sszTypes.ts | 39 ------------------- packages/types/src/heze/types.ts | 5 --- 7 files changed, 12 insertions(+), 55 deletions(-) diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 809758c5a3e1..161a54a7b7f9 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -60,7 +60,7 @@ export const chainConfig: ChainConfig = { GLOAS_FORK_VERSION: b("0x07000000"), GLOAS_FORK_EPOCH: Infinity, - // HEZE (EIP-7805) + // HEZE HEZE_FORK_VERSION: b("0x08000000"), HEZE_FORK_EPOCH: Infinity, diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index f3cbae93a8ca..9c2328dc7385 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -53,7 +53,7 @@ export const chainConfig: ChainConfig = { // GLOAS GLOAS_FORK_VERSION: b("0x07000001"), GLOAS_FORK_EPOCH: Infinity, - // HEZE (EIP-7805) + // HEZE HEZE_FORK_VERSION: b("0x08000001"), HEZE_FORK_EPOCH: Infinity, diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 1c08f5e4f415..5f3040e185bd 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -50,7 +50,7 @@ export type ChainConfig = { // GLOAS GLOAS_FORK_VERSION: Uint8Array; GLOAS_FORK_EPOCH: number; - // HEZE (EIP-7805) + // HEZE HEZE_FORK_VERSION: Uint8Array; HEZE_FORK_EPOCH: number; diff --git a/packages/state-transition/src/block/processExecutionPayloadBid.ts b/packages/state-transition/src/block/processExecutionPayloadBid.ts index 31482c26ab41..9ecb8a8284de 100644 --- a/packages/state-transition/src/block/processExecutionPayloadBid.ts +++ b/packages/state-transition/src/block/processExecutionPayloadBid.ts @@ -15,7 +15,7 @@ import {canBuilderCoverBid, isActiveBuilder} from "../util/gloas.js"; import {getBlockRootAtSlot, getCurrentEpoch, getRandaoMix} from "../util/index.js"; export function processExecutionPayloadBid( - state: CachedBeaconStateGloas, + state: CachedBeaconStateGloas | CachedBeaconStateHeze, signedBid: gloas.SignedExecutionPayloadBid ): void { const bid = signedBid.message; @@ -47,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`); } @@ -106,15 +106,16 @@ export function processExecutionPayloadBid( } if (state.config.getForkSeq(state.slot) >= ForkSeq.heze) { - const hezeState = state as unknown as CachedBeaconStateHeze; - hezeState.latestExecutionPayloadBid = ssz.heze.ExecutionPayloadBid.toViewDU(bid as heze.ExecutionPayloadBid); + (state as CachedBeaconStateHeze).latestExecutionPayloadBid = ssz.heze.ExecutionPayloadBid.toViewDU( + bid as heze.ExecutionPayloadBid + ); } else { - state.latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); + (state as CachedBeaconStateGloas).latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); } } function verifyExecutionPayloadBidSignature( - state: CachedBeaconStateGloas, + state: CachedBeaconStateGloas | CachedBeaconStateHeze, pubkey: Uint8Array, signedBid: gloas.SignedExecutionPayloadBid ): boolean { diff --git a/packages/state-transition/src/signatureSets/executionPayloadBid.ts b/packages/state-transition/src/signatureSets/executionPayloadBid.ts index 4032e82c466a..76f80b017296 100644 --- a/packages/state-transition/src/signatureSets/executionPayloadBid.ts +++ b/packages/state-transition/src/signatureSets/executionPayloadBid.ts @@ -1,12 +1,12 @@ import {BeaconConfig} from "@lodestar/config"; import {DOMAIN_BEACON_BUILDER, ForkSeq} from "@lodestar/params"; -import {Slot, gloas, heze, ssz} from "@lodestar/types"; +import {ExecutionPayloadBid, Slot, ssz} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; export function getExecutionPayloadBidSigningRoot( config: BeaconConfig, stateSlot: Slot, - bid: gloas.ExecutionPayloadBid | heze.ExecutionPayloadBid + bid: ExecutionPayloadBid ): Uint8Array { const domain = config.getDomain(stateSlot, DOMAIN_BEACON_BUILDER); const sszType = diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index 274a5eb05ac7..d247591f775f 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -1,8 +1,6 @@ import {BitVectorType, ContainerType, ListCompositeType, VectorBasicType} from "@chainsafe/ssz"; import {INCLUSION_LIST_COMMITTEE_SIZE, MAX_TRANSACTIONS_PER_PAYLOAD} from "@lodestar/params"; import {ssz as bellatrixSsz} from "../bellatrix/index.js"; -import {ssz as denebSsz} from "../deneb/index.js"; -import {ssz as electraSsz} from "../electra/index.js"; import {ssz as gloasSsz} from "../gloas/index.js"; import {ssz as primitiveSsz} from "../primitive/index.js"; @@ -96,43 +94,6 @@ export const SignedBeaconBlock = new ContainerType( {typeName: "SignedBeaconBlock", jsonCase: "eth2"} ); -// Gloas does not define blinded block types (post-ePBS uses bid/envelope split). -// Heze inherits the electra blinded types for code paths still expecting them. -export const BlindedBeaconBlockBody = new ContainerType( - { - ...electraSsz.BlindedBeaconBlockBody.fields, - }, - {typeName: "BlindedBeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true} -); - -export const BlindedBeaconBlock = new ContainerType( - { - ...electraSsz.BlindedBeaconBlock.fields, - }, - {typeName: "BlindedBeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true} -); - -export const SignedBlindedBeaconBlock = new ContainerType( - { - ...electraSsz.SignedBlindedBeaconBlock.fields, - }, - {typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"} -); - -export const ExecutionPayload = new ContainerType( - { - ...gloasSsz.ExecutionPayload.fields, - }, - {typeName: "ExecutionPayload", jsonCase: "eth2"} -); - -export const ExecutionPayloadHeader = new ContainerType( - { - ...denebSsz.ExecutionPayloadHeader.fields, - }, - {typeName: "ExecutionPayloadHeader", jsonCase: "eth2"} -); - // PayloadAttributes primarily for SSE event export const PayloadAttributes = new ContainerType( { diff --git a/packages/types/src/heze/types.ts b/packages/types/src/heze/types.ts index 62595e6a8308..13aac8e143dd 100644 --- a/packages/types/src/heze/types.ts +++ b/packages/types/src/heze/types.ts @@ -13,9 +13,4 @@ export type BeaconState = ValueOf; export type BeaconBlockBody = ValueOf; export type BeaconBlock = ValueOf; export type SignedBeaconBlock = ValueOf; -export type BlindedBeaconBlockBody = ValueOf; -export type BlindedBeaconBlock = ValueOf; -export type SignedBlindedBeaconBlock = ValueOf; -export type ExecutionPayload = ValueOf; -export type ExecutionPayloadHeader = ValueOf; export type SSEPayloadAttributes = ValueOf; From bc973a2ea134521eae2ca22b69f8b6ab0cc3b187 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Fri, 24 Jul 2026 18:20:47 -0700 Subject: [PATCH 10/22] feat: update heze definitions to consensus-specs v1.7.0-alpha.12 - heze SSZ types now use ProgressiveContainerType (ExecutionPayloadBid 13 active fields, BeaconState 46) and ProgressiveList for inclusion list transactions - move INCLUSION_LIST_COMMITTEE_SIZE from ChainConfig to BeaconPreset and add MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE_HEZE / MAX_SIGNED_INCLUSION_LIST_SIZE - align heze TypesByFork entries with gloas (light client + attestation types) - run heze spec test suites; skip eip8148 fork vector, heze FCR and PTC/FOCIL fork choice handlers matching existing gloas skips - check name-based spec test skips before input deserialization so cross-fork vectors that cannot be parsed can be skipped Co-Authored-By: Claude Fable 5 --- .../spec/presets/fast_confirmation.test.ts | 1 + .../test/spec/presets/operations.test.ts | 5 +- .../test/spec/utils/forkChoiceTestRunner.ts | 2 +- .../test/spec/utils/specTestIterator.ts | 4 ++ .../beacon-node/test/utils/blocksAndData.ts | 4 +- .../config/src/chainConfig/configs/mainnet.ts | 2 - .../config/src/chainConfig/configs/minimal.ts | 2 - packages/config/src/chainConfig/types.ts | 4 +- packages/params/src/index.ts | 7 +-- packages/spec-test-util/src/single.ts | 7 +++ packages/types/src/heze/sszTypes.ts | 50 ++++++++++++------- packages/types/src/types.ts | 26 +++++----- packages/validator/src/util/params.ts | 2 + 13 files changed, 70 insertions(+), 46 deletions(-) diff --git a/packages/beacon-node/test/spec/presets/fast_confirmation.test.ts b/packages/beacon-node/test/spec/presets/fast_confirmation.test.ts index d0153c7c8173..147fed8c6815 100644 --- a/packages/beacon-node/test/spec/presets/fast_confirmation.test.ts +++ b/packages/beacon-node/test/spec/presets/fast_confirmation.test.ts @@ -729,6 +729,7 @@ specTestIterator( // presence/timeliness, get_node_for_root with PAYLOAD_STATUS_PENDING). Head/justified/ // finalized/proposer-head all match; only getConfirmedRoot diverges. /^gloas\/fast_confirmation\/.*/, + /^heze\/fast_confirmation\/.*/, ], } ); diff --git a/packages/beacon-node/test/spec/presets/operations.test.ts b/packages/beacon-node/test/spec/presets/operations.test.ts index 30f11711aec8..e2831471e54e 100644 --- a/packages/beacon-node/test/spec/presets/operations.test.ts +++ b/packages/beacon-node/test/spec/presets/operations.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, @@ -187,7 +187,8 @@ const operations: TestRunnerFn = (fork, deposit_request: ssz.electra.DepositRequest, consolidation_request: ssz.electra.ConsolidationRequest, payload_attestation: ssz.gloas.PayloadAttestation, - execution_payload_bid: ssz.gloas.SignedExecutionPayloadBid, + execution_payload_bid: + ForkSeq[fork] >= ForkSeq.heze ? ssz.heze.SignedExecutionPayloadBid : ssz.gloas.SignedExecutionPayloadBid, builder_deposit_request: ssz.gloas.BuilderDepositRequest, builder_exit_request: ssz.gloas.BuilderExitRequest, }, diff --git a/packages/beacon-node/test/spec/utils/forkChoiceTestRunner.ts b/packages/beacon-node/test/spec/utils/forkChoiceTestRunner.ts index 25a4a3ebb6ca..57953948de52 100644 --- a/packages/beacon-node/test/spec/utils/forkChoiceTestRunner.ts +++ b/packages/beacon-node/test/spec/utils/forkChoiceTestRunner.ts @@ -888,7 +888,7 @@ export const forkChoiceTestRunner = shouldSkip: (_testcase, name, _index) => name.includes("invalid_incorrect_proof") || // TODO GLOAS: These tests will be unskipped by https://github.com/ChainSafe/lodestar/pull/9233 - (name.includes("gloas") && + ((name.includes("gloas") || name.includes("heze")) && (name.includes("simple_attempted_reorg_without_enough_ffg_votes") || name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch") || name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_previous_epoch") || diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 29e2785ea04d..9d1e0d5da8db 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -97,6 +97,10 @@ export const defaultSkipOpts: SkipOpts = { // TODO-GLOAS: re-enable after gloas light client is implemented /\/gloas_fork$/, /\/heze_fork$/, + // TODO EIP-8148: this vector is generated by upgrade_to_eip8148 (pre=heze, post=eip8148 with + // the new validator_sweep_thresholds field) but is placed under heze/fork by the generator. + // Re-enable once eip8148 is implemented. + /^heze\/fork\/fork\/pyspec_tests\/fork_base_state$/, // 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/utils/blocksAndData.ts b/packages/beacon-node/test/utils/blocksAndData.ts index 8a55ddcfe566..a955f82905e6 100644 --- a/packages/beacon-node/test/utils/blocksAndData.ts +++ b/packages/beacon-node/test/utils/blocksAndData.ts @@ -17,7 +17,7 @@ import { isForkPostGloas, } from "@lodestar/params"; import {computeStartSlotAtEpoch, signedBlockToSignedHeader} from "@lodestar/state-transition"; -import {BeaconBlockBody, SignedBeaconBlock, Slot, deneb, fulu, gloas, ssz} from "@lodestar/types"; +import {BeaconBlockBody, SignedBeaconBlock, Slot, deneb, fulu, gloas, ssz, sszTypesFor} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {VersionedHashes} from "../../src/execution/index.js"; import {computeNodeIdFromPrivateKey} from "../../src/network/subnets/index.js"; @@ -186,7 +186,7 @@ function generateColumnSidecars( if (isForkPostGloas(forkName)) { (block.message.body as gloas.BeaconBlockBody).signedExecutionPayloadBid.message.blobKzgCommitments = kzgCommitments; - const beaconBlockRoot = ssz[forkName as ForkPostGloas].BeaconBlock.hashTreeRoot( + const beaconBlockRoot = sszTypesFor(forkName as ForkPostGloas).BeaconBlock.hashTreeRoot( block.message as SignedBeaconBlock["message"] ); columnSidecars = getGloasDataColumnSidecars( diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 71c0d19730c2..e69c38000fe6 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -202,8 +202,6 @@ export const chainConfig: ChainConfig = { MAX_REQUEST_INCLUSION_LIST: 16, // 2**13 (=8192) MAX_BYTES_PER_INCLUSION_LIST: 8192, - // 2**4 (= 16) - INCLUSION_LIST_COMMITTEE_SIZE: 16, // Gloas // 2**7 (= 128) payloads diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index f7d8fdd6eda7..ee57163f61cb 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -203,8 +203,6 @@ export const chainConfig: ChainConfig = { MAX_REQUEST_INCLUSION_LIST: 16, // 2**13 (=8192) MAX_BYTES_PER_INCLUSION_LIST: 8192, - // 2**4 (= 16) - INCLUSION_LIST_COMMITTEE_SIZE: 16, // Fast Confirmation Rule // --------------------------------------------------------------- diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 5f3040e185bd..89e5e1af8ef7 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -141,7 +141,6 @@ export type ChainConfig = { // HEZE MAX_REQUEST_INCLUSION_LIST: number; MAX_BYTES_PER_INCLUSION_LIST: number; - INCLUSION_LIST_COMMITTEE_SIZE: number; // Fast Confirmation Rule CONFIRMATION_BYZANTINE_THRESHOLD: number; @@ -184,7 +183,7 @@ export const chainConfigTypes: SpecTypes = { // GLOAS GLOAS_FORK_VERSION: "bytes", GLOAS_FORK_EPOCH: "number", - // HEZE (EIP-7805) + // HEZE HEZE_FORK_VERSION: "bytes", HEZE_FORK_EPOCH: "number", @@ -268,7 +267,6 @@ export const chainConfigTypes: SpecTypes = { // HEZE MAX_REQUEST_INCLUSION_LIST: "number", MAX_BYTES_PER_INCLUSION_LIST: "number", - INCLUSION_LIST_COMMITTEE_SIZE: "number", // Gloas MAX_REQUEST_PAYLOADS: "number", diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 8d287f08d28e..579166dc0cbd 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -125,6 +125,10 @@ export const { MAX_DATA_COLUMN_SIDECAR_SIZE, MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE, + + INCLUSION_LIST_COMMITTEE_SIZE, + MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE_HEZE, + MAX_SIGNED_INCLUSION_LIST_SIZE, } = activePreset; //////////// @@ -368,6 +372,3 @@ export const BUILDER_PAYMENT_THRESHOLD_NUMERATOR = 6; export const BUILDER_PAYMENT_THRESHOLD_DENOMINATOR = 10; export const BUILDER_DEPOSIT_REQUEST_TYPE = 0x03; export const BUILDER_EXIT_REQUEST_TYPE = 0x04; - -// HEZE -export const INCLUSION_LIST_COMMITTEE_SIZE = 16; diff --git a/packages/spec-test-util/src/single.ts b/packages/spec-test-util/src/single.ts index e839b01fad73..a195524365f4 100644 --- a/packages/spec-test-util/src/single.ts +++ b/packages/spec-test-util/src/single.ts @@ -115,6 +115,13 @@ export function describeDirectorySpecTest ? loadYaml(fs.readFileSync(metaFilePath, "utf8")) : undefined; + // Check name-based skips before loading inputs — some skipped vectors (e.g. cross-fork + // upgrade tests placed under an earlier fork's directory) cannot be deserialized at all. + if (options.shouldSkip?.(null as unknown as TestCase, testName, 0)) { + context.skip(); + return; + } + let testCase = loadInputFiles(testSubDirPath, options, meta); if (options.mapToTestCase) testCase = options.mapToTestCase(testCase); if (options.shouldSkip?.(testCase, testName, 0)) { diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index d247591f775f..9999918a7aa4 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -1,22 +1,27 @@ -import {BitVectorType, ContainerType, ListCompositeType, VectorBasicType} from "@chainsafe/ssz"; -import {INCLUSION_LIST_COMMITTEE_SIZE, MAX_TRANSACTIONS_PER_PAYLOAD} from "@lodestar/params"; -import {ssz as bellatrixSsz} from "../bellatrix/index.js"; +import { + BitVectorType, + ContainerType, + ProgressiveContainerType, + ProgressiveListCompositeType, + VectorBasicType, +} from "@chainsafe/ssz"; +import {INCLUSION_LIST_COMMITTEE_SIZE} from "@lodestar/params"; import {ssz as gloasSsz} from "../gloas/index.js"; import {ssz as primitiveSsz} from "../primitive/index.js"; const {Slot, Root, BLSSignature, ValidatorIndex} = primitiveSsz; -export const InclusionListCommittee = new VectorBasicType(ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE); +function activeFields(count: number): boolean[] { + return Array.from({length: count}, () => true); +} -// Per InclusionList container; bound is MAX_TRANSACTIONS_PER_PAYLOAD. -export const InclusionListTransactions = new ListCompositeType(bellatrixSsz.Transaction, MAX_TRANSACTIONS_PER_PAYLOAD); +export const InclusionListCommittee = new VectorBasicType(ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE); -// Aggregated IL transactions surfaced in PayloadAttributes/EL: bounded by total committee output. -export const AggregatedInclusionListTransactions = new ListCompositeType( - bellatrixSsz.Transaction, - MAX_TRANSACTIONS_PER_PAYLOAD * INCLUSION_LIST_COMMITTEE_SIZE -); +export const InclusionListTransactions = new ProgressiveListCompositeType(gloasSsz.Transaction, { + typeName: "InclusionListTransactions", +}); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#inclusionlist export const InclusionList = new ContainerType( { slot: Slot, @@ -27,6 +32,7 @@ export const InclusionList = new ContainerType( {typeName: "InclusionList", jsonCase: "eth2"} ); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#signedinclusionlist export const SignedInclusionList = new ContainerType( { message: InclusionList, @@ -35,6 +41,7 @@ export const SignedInclusionList = new ContainerType( {typeName: "SignedInclusionList", jsonCase: "eth2"} ); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/p2p-interface.md#inclusionlistbycommitteeindices-v1 export const InclusionListByCommitteeIndicesRequest = new ContainerType( { slot: Slot, @@ -43,17 +50,20 @@ export const InclusionListByCommitteeIndicesRequest = new ContainerType( {typeName: "InclusionListByCommitteeIndicesRequest", jsonCase: "eth2"} ); -export const ExecutionPayloadBid = new ContainerType( +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#executionpayloadbid +export const ExecutionPayloadBid = new ProgressiveContainerType( { ...gloasSsz.ExecutionPayloadBid.fields, inclusionListBits: new BitVectorType(INCLUSION_LIST_COMMITTEE_SIZE), // [New in Heze:EIP7805] }, + activeFields(13), {typeName: "ExecutionPayloadBid", jsonCase: "eth2"} ); +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#signedexecutionpayloadbid export const SignedExecutionPayloadBid = new ContainerType( { - message: ExecutionPayloadBid, + message: ExecutionPayloadBid, // [Modified in Heze:EIP7805] signature: BLSSignature, }, {typeName: "SignedExecutionPayloadBid", jsonCase: "eth2"} @@ -62,19 +72,22 @@ export const SignedExecutionPayloadBid = new ContainerType( export const DataColumnSidecar = gloasSsz.DataColumnSidecar; export const DataColumnSidecars = gloasSsz.DataColumnSidecars; -export const BeaconState = new ContainerType( +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#beaconstate +export const BeaconState = new ProgressiveContainerType( { ...gloasSsz.BeaconState.fields, latestExecutionPayloadBid: ExecutionPayloadBid, // [Modified in Heze:EIP7805] }, + activeFields(46), {typeName: "BeaconState", jsonCase: "eth2"} ); -export const BeaconBlockBody = new ContainerType( +export const BeaconBlockBody = new ProgressiveContainerType( { ...gloasSsz.BeaconBlockBody.fields, signedExecutionPayloadBid: SignedExecutionPayloadBid, // [Modified in Heze:EIP7805] }, + activeFields(13), {typeName: "BeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true} ); @@ -95,18 +108,19 @@ export const SignedBeaconBlock = new ContainerType( ); // PayloadAttributes primarily for SSE event +// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/fork-choice.md#modified-payloadattributes export const PayloadAttributes = new ContainerType( { ...gloasSsz.PayloadAttributes.fields, - inclusionListTransactions: AggregatedInclusionListTransactions, + inclusionListTransactions: InclusionListTransactions, // [New in Heze:EIP7805] }, {typeName: "PayloadAttributes", jsonCase: "eth2"} ); export const SSEPayloadAttributes = new ContainerType( { - ...bellatrixSsz.SSEPayloadAttributesCommon.fields, - payloadAttributes: PayloadAttributes, + ...gloasSsz.SSEPayloadAttributes.fields, + payloadAttributes: PayloadAttributes, // [Modified in Heze:EIP7805] }, {typeName: "SSEPayloadAttributes", jsonCase: "eth2"} ); diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index f868cbbc59b9..d2b530f41c96 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -337,12 +337,12 @@ type TypesByFork = { SignedBeaconBlock: heze.SignedBeaconBlock; Metadata: fulu.Metadata; Status: fulu.Status; - LightClientHeader: deneb.LightClientHeader; - LightClientBootstrap: electra.LightClientBootstrap; - LightClientUpdate: electra.LightClientUpdate; - LightClientFinalityUpdate: electra.LightClientFinalityUpdate; - LightClientOptimisticUpdate: electra.LightClientOptimisticUpdate; - LightClientStore: electra.LightClientStore; + LightClientHeader: gloas.LightClientHeader; + LightClientBootstrap: gloas.LightClientBootstrap; + LightClientUpdate: gloas.LightClientUpdate; + LightClientFinalityUpdate: gloas.LightClientFinalityUpdate; + LightClientOptimisticUpdate: gloas.LightClientOptimisticUpdate; + LightClientStore: gloas.LightClientStore; BlindedBeaconBlock: electra.BlindedBeaconBlock; BlindedBeaconBlockBody: electra.BlindedBeaconBlockBody; SignedBlindedBeaconBlock: electra.SignedBlindedBeaconBlock; @@ -358,13 +358,13 @@ type TypesByFork = { SyncCommittee: altair.SyncCommittee; SyncAggregate: altair.SyncAggregate; SingleAttestation: electra.SingleAttestation; - Attestation: electra.Attestation; - IndexedAttestation: electra.IndexedAttestation; - IndexedAttestationBigint: electra.IndexedAttestationBigint; - AttesterSlashing: electra.AttesterSlashing; - AggregateAndProof: electra.AggregateAndProof; - SignedAggregateAndProof: electra.SignedAggregateAndProof; - ExecutionRequests: electra.ExecutionRequests; + Attestation: gloas.Attestation; + IndexedAttestation: gloas.IndexedAttestation; + IndexedAttestationBigint: gloas.IndexedAttestationBigint; + AttesterSlashing: gloas.AttesterSlashing; + AggregateAndProof: gloas.AggregateAndProof; + SignedAggregateAndProof: gloas.SignedAggregateAndProof; + ExecutionRequests: gloas.ExecutionRequests; ExecutionPayloadBid: heze.ExecutionPayloadBid; DataColumnSidecar: gloas.DataColumnSidecar; DataColumnSidecars: gloas.DataColumnSidecars; diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 61d0f9b95c9e..ab79ff45ab6b 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -343,6 +343,8 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Fri, 24 Jul 2026 18:29:03 -0700 Subject: [PATCH 11/22] test: skip eip8148 fork in spec test iterator Note: the eip8148 upgrade vector (fork_base_state) is generated under heze/fork because eip8148 is not a testgen fork, so it is not covered by skippedForks and currently fails. To be investigated separately. Co-Authored-By: Claude Fable 5 --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 6 +----- packages/spec-test-util/src/single.ts | 7 ------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 9d1e0d5da8db..9524b00fb623 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -61,7 +61,7 @@ const coveredTestRunners = [ // ], // ``` export const defaultSkipOpts: SkipOpts = { - skippedForks: [], + skippedForks: ["eip8148"], skippedTestSuites: [ // Merge transition tests are skipped because we no longer support performing the merge transition. // All networks have already completed the merge, so this code path is no longer needed. @@ -97,10 +97,6 @@ export const defaultSkipOpts: SkipOpts = { // TODO-GLOAS: re-enable after gloas light client is implemented /\/gloas_fork$/, /\/heze_fork$/, - // TODO EIP-8148: this vector is generated by upgrade_to_eip8148 (pre=heze, post=eip8148 with - // the new validator_sweep_thresholds field) but is placed under heze/fork by the generator. - // Re-enable once eip8148 is implemented. - /^heze\/fork\/fork\/pyspec_tests\/fork_base_state$/, // 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/spec-test-util/src/single.ts b/packages/spec-test-util/src/single.ts index a195524365f4..e839b01fad73 100644 --- a/packages/spec-test-util/src/single.ts +++ b/packages/spec-test-util/src/single.ts @@ -115,13 +115,6 @@ export function describeDirectorySpecTest ? loadYaml(fs.readFileSync(metaFilePath, "utf8")) : undefined; - // Check name-based skips before loading inputs — some skipped vectors (e.g. cross-fork - // upgrade tests placed under an earlier fork's directory) cannot be deserialized at all. - if (options.shouldSkip?.(null as unknown as TestCase, testName, 0)) { - context.skip(); - return; - } - let testCase = loadInputFiles(testSubDirPath, options, meta); if (options.mapToTestCase) testCase = options.mapToTestCase(testCase); if (options.shouldSkip?.(testCase, testName, 0)) { From fdc65187297e4716a16b5cbf7ade2d221b53e750 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:18:53 -0700 Subject: [PATCH 12/22] test: skip broken eip8148 fork vector under heze/fork The fork_base_state vector in heze/fork/fork/pyspec_tests is a broken eip8148 upgrade vector that crashes input deserialization before any per-test skip applies. Removed upstream in consensus-specs#5440; unskip this suite on the next spec-tests bump. Co-Authored-By: Claude Fable 5 --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 9524b00fb623..4c5fdeae98a1 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -92,6 +92,11 @@ export const defaultSkipOpts: SkipOpts = { /^heze\/fork_choice_compliance\/.*/, // TODO-HEZE: re-enable after on_inclusion_list (FOCIL) fork choice is implemented. /^heze\/fork_choice\/on_inclusion_list\/.*$/, + // The fork_base_state vector in this suite is a broken eip8148 upgrade vector (post state has + // the eip8148-only validator_sweep_thresholds field) that crashes input deserialization before + // any per-test skip can apply. It was added by mistake and removed upstream in + // https://github.com/ethereum/consensus-specs/pull/5440 — unskip on the next spec-tests bump. + /^heze\/fork\/fork\/pyspec_tests$/, ], skippedTests: [ // TODO-GLOAS: re-enable after gloas light client is implemented From 4a803199579d2a158257a9089c132c65e1ff11ec Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:12:09 -0700 Subject: [PATCH 13/22] feat: add heze BlockContents type Gloas block production (#9595) returns `BlockContents` containing the execution payload envelope for the stateless self-build flow. Heze was still mapped to `fulu.BlockContents`, which has no envelope and carries a fulu block, so produceBlockV4 would have serialized the wrong shape at the heze boundary. --- packages/types/src/heze/sszTypes.ts | 8 ++++++++ packages/types/src/heze/types.ts | 1 + packages/types/src/types.ts | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index 9999918a7aa4..7a7fe8a4a3bd 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -107,6 +107,14 @@ export const SignedBeaconBlock = new ContainerType( {typeName: "SignedBeaconBlock", jsonCase: "eth2"} ); +export const BlockContents = new ContainerType( + { + ...gloasSsz.BlockContents.fields, + block: BeaconBlock, + }, + {typeName: "BlockContents", jsonCase: "eth2"} +); + // PayloadAttributes primarily for SSE event // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/fork-choice.md#modified-payloadattributes export const PayloadAttributes = new ContainerType( diff --git a/packages/types/src/heze/types.ts b/packages/types/src/heze/types.ts index 13aac8e143dd..83ae54f9960d 100644 --- a/packages/types/src/heze/types.ts +++ b/packages/types/src/heze/types.ts @@ -13,4 +13,5 @@ export type BeaconState = ValueOf; export type BeaconBlockBody = ValueOf; export type BeaconBlock = ValueOf; export type SignedBeaconBlock = ValueOf; +export type BlockContents = ValueOf; export type SSEPayloadAttributes = ValueOf; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index ea723410c066..04771502cd6c 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -351,7 +351,7 @@ type TypesByFork = { BuilderBid: electra.BuilderBid; SignedBuilderBid: electra.SignedBuilderBid; SSEPayloadAttributes: heze.SSEPayloadAttributes; - BlockContents: fulu.BlockContents; + BlockContents: heze.BlockContents; SignedBlockContents: fulu.SignedBlockContents; ExecutionPayloadAndBlobsBundle: fulu.ExecutionPayloadAndBlobsBundle; BlobsBundle: fulu.BlobsBundle; From 72257402a7dc0c61765592d62fe8189a2db45b01 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:12:09 -0700 Subject: [PATCH 14/22] fix: use heze bid size limit for execution_payload_bid gossip The heze preset raises MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE from 196932 to 196934 to cover the new inclusion_list_bits field. Without selecting the heze bound, valid bids in those last two bytes are rejected before deserialization. --- packages/beacon-node/src/network/gossip/topic.ts | 4 +++- .../beacon-node/test/unit/network/gossip/topic.test.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 7fb68342e7a1..3580b493ef0a 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -8,11 +8,13 @@ import { MAX_DATA_COLUMN_SIDECAR_SIZE, MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE, + MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE_HEZE, SYNC_COMMITTEE_SUBNET_COUNT, isForkPostAltair, isForkPostElectra, isForkPostFulu, isForkPostGloas, + isForkPostHeze, } from "@lodestar/params"; import {Attestation, SingleAttestation, ssz, sszTypesFor} from "@lodestar/types"; import {GossipAction, GossipActionError, GossipErrorCode} from "../../chain/errors/gossipValidation.js"; @@ -154,7 +156,7 @@ export function getGossipSSZMaxSize(topic: GossipTopic, maxPayloadSize: number, case GossipType.execution_payload: return maxPayloadSize; case GossipType.execution_payload_bid: - return MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE; + return isForkPostHeze(fork) ? MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE_HEZE : MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE; default: return (sszType ?? getGossipSSZType(topic)).maxSize; } diff --git a/packages/beacon-node/test/unit/network/gossip/topic.test.ts b/packages/beacon-node/test/unit/network/gossip/topic.test.ts index 88537cadd88c..40321f8c3709 100644 --- a/packages/beacon-node/test/unit/network/gossip/topic.test.ts +++ b/packages/beacon-node/test/unit/network/gossip/topic.test.ts @@ -9,6 +9,7 @@ import { MAX_DATA_COLUMN_SIDECAR_SIZE, MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE, + MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE_HEZE, ZERO_HASH, } from "@lodestar/params"; import {DataTransformSnappy} from "../../../../src/network/gossip/encoding.js"; @@ -281,6 +282,14 @@ describe("network / gossip / topic", () => { }); }); + it("should use the Heze bid size limit post-Heze", () => { + const boundary = {fork: ForkName.heze, epoch: config.HEZE_FORK_EPOCH}; + + expect( + getGossipSSZMaxSize({type: GossipType.execution_payload_bid, boundary, encoding}, config.MAX_PAYLOAD_SIZE) + ).toBe(MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE_HEZE); + }); + it("should cap Gloas progressive gossip objects below their theoretical SSZ max", () => { const boundary = {fork: ForkName.gloas, epoch: config.GLOAS_FORK_EPOCH}; From b2b0fa234aa0defc13665fe81e6dccce2a582b2a Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:12:09 -0700 Subject: [PATCH 15/22] fix: set inclusion list bits on heze self-build bid The heze block body requires a heze SignedExecutionPayloadBid, so the self-build path must initialize inclusionListBits or hashing/serializing the produced block reads an undefined bitvector. --- .../beacon-node/src/chain/produceBlock/produceBlockBody.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index eebc71b8ec8f..3e50b1d6963a 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -1,3 +1,4 @@ +import {BitArray} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {IForkChoice, ProtoBlock, getSafeExecutionBlockHash} from "@lodestar/fork-choice"; import { @@ -10,6 +11,7 @@ import { ForkPostGloas, ForkPreGloas, ForkSeq, + INCLUSION_LIST_COMMITTEE_SIZE, isForkPostAltair, isForkPostBellatrix, isForkPostGloas, @@ -354,6 +356,10 @@ export async function produceBlockBody( blobKzgCommitments: blobsBundle.commitments, executionRequestsRoot: ssz.gloas.ExecutionRequests.hashTreeRoot(executionRequests as gloas.ExecutionRequests), }; + if (ForkSeq[fork] >= ForkSeq.heze) { + // TODO HEZE: populate from inclusion list pool once IL aggregation is wired up. + (bid as heze.ExecutionPayloadBid).inclusionListBits = BitArray.fromBitLen(INCLUSION_LIST_COMMITTEE_SIZE); + } const signedBid: gloas.SignedExecutionPayloadBid = { message: bid, signature: G2_POINT_AT_INFINITY, From 350d13c7c384b379eab3934d0de7b8cd494f5a4d Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:28:44 -0700 Subject: [PATCH 16/22] feat: update heze definitions to consensus-specs v1.7.0-alpha.13 Most of the alpha.12 -> alpha.13 heze delta is cosmetic in the spec (uint64 -> Uint64, Bitvector -> BitVector). The functional changes: - Add MIN_SLOTS_FOR_INCLUSION_LISTS_REQUESTS (= 1), a new inclusion list req/resp serving window param. Marked non-critical in assertEqualParams to match the other MIN_EPOCHS_FOR_*_REQUESTS / MAX_REQUEST_* params. - Rename InclusionListByCommitteeIndices -> InclusionListsByIndices and add the inclusion_list_committee_root field to the request, renaming committee_indices to indices. - Point heze spec reference URLs at v1.7.0-alpha.13. - Unskip heze/fork/fork/pyspec_tests: the broken eip8148 upgrade vector was removed upstream in consensus-specs#5440 and alpha.13 adds real heze fork test vectors, which pass. - Skip heze/fork_choice/should_apply_proposer_boost alongside the gloas suite; the generator emits heze vectors too and Lodestar still applies the pre-gloas proposer boost. --- .../test/spec/utils/specTestIterator.ts | 6 +----- .../config/src/chainConfig/configs/mainnet.ts | 2 ++ .../config/src/chainConfig/configs/minimal.ts | 2 ++ packages/config/src/chainConfig/params.ts | 1 + packages/config/src/chainConfig/types.ts | 2 ++ packages/types/src/heze/sszTypes.ts | 21 ++++++++++--------- packages/types/src/heze/types.ts | 2 +- specrefs/configs.yml | 4 +++- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 5f08733182c3..02dcab19dac3 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -91,16 +91,12 @@ export const defaultSkipOpts: SkipOpts = { // New test suite added in v1.7.0-alpha.13 (consensus-specs #5441); Lodestar still applies // the pre-gloas proposer boost, so the head weight differs by the boost amount. /^gloas\/fork_choice\/should_apply_proposer_boost\/.*$/, + /^heze\/fork_choice\/should_apply_proposer_boost\/.*$/, // TODO GLOAS: enable this after gloas fork choice is ready /^gloas\/fork_choice_compliance\/.*/, /^heze\/fork_choice_compliance\/.*/, // TODO-HEZE: re-enable after on_inclusion_list (FOCIL) fork choice is implemented. /^heze\/fork_choice\/on_inclusion_list\/.*$/, - // The fork_base_state vector in this suite is a broken eip8148 upgrade vector (post state has - // the eip8148-only validator_sweep_thresholds field) that crashes input deserialization before - // any per-test skip can apply. It was added by mistake and removed upstream in - // https://github.com/ethereum/consensus-specs/pull/5440 — unskip on the next spec-tests bump. - /^heze\/fork\/fork\/pyspec_tests$/, ], skippedTests: [ // TODO-GLOAS: re-enable after gloas light client is implemented diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index e69c38000fe6..8d85d9f31868 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -200,6 +200,8 @@ export const chainConfig: ChainConfig = { // HEZE // 2**4 (= 16) MAX_REQUEST_INCLUSION_LIST: 16, + // 1 slots + MIN_SLOTS_FOR_INCLUSION_LISTS_REQUESTS: 1, // 2**13 (=8192) MAX_BYTES_PER_INCLUSION_LIST: 8192, diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index ee57163f61cb..22a0810cbbbb 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -201,6 +201,8 @@ export const chainConfig: ChainConfig = { // HEZE // 2**4 (= 16) MAX_REQUEST_INCLUSION_LIST: 16, + // 1 slots + MIN_SLOTS_FOR_INCLUSION_LISTS_REQUESTS: 1, // 2**13 (=8192) MAX_BYTES_PER_INCLUSION_LIST: 8192, diff --git a/packages/config/src/chainConfig/params.ts b/packages/config/src/chainConfig/params.ts index 96285f10a3cd..ddc1200c61fd 100644 --- a/packages/config/src/chainConfig/params.ts +++ b/packages/config/src/chainConfig/params.ts @@ -342,6 +342,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record = { // HEZE MAX_REQUEST_INCLUSION_LIST: "number", + MIN_SLOTS_FOR_INCLUSION_LISTS_REQUESTS: "number", MAX_BYTES_PER_INCLUSION_LIST: "number", // Gloas diff --git a/packages/types/src/heze/sszTypes.ts b/packages/types/src/heze/sszTypes.ts index 7a7fe8a4a3bd..011b5648da1c 100644 --- a/packages/types/src/heze/sszTypes.ts +++ b/packages/types/src/heze/sszTypes.ts @@ -21,7 +21,7 @@ export const InclusionListTransactions = new ProgressiveListCompositeType(gloasS typeName: "InclusionListTransactions", }); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#inclusionlist +// 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 +32,7 @@ export const InclusionList = new ContainerType( {typeName: "InclusionList", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#signedinclusionlist +// 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,16 +41,17 @@ export const SignedInclusionList = new ContainerType( {typeName: "SignedInclusionList", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/p2p-interface.md#inclusionlistbycommitteeindices-v1 -export const InclusionListByCommitteeIndicesRequest = new ContainerType( +// 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, - committeeIndices: new BitVectorType(INCLUSION_LIST_COMMITTEE_SIZE), + inclusionListCommitteeRoot: Root, + indices: new BitVectorType(INCLUSION_LIST_COMMITTEE_SIZE), }, - {typeName: "InclusionListByCommitteeIndicesRequest", jsonCase: "eth2"} + {typeName: "InclusionListsByIndicesRequest", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#executionpayloadbid +// 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, @@ -60,7 +61,7 @@ export const ExecutionPayloadBid = new ProgressiveContainerType( {typeName: "ExecutionPayloadBid", jsonCase: "eth2"} ); -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/beacon-chain.md#signedexecutionpayloadbid +// 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] @@ -72,7 +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.12/specs/heze/beacon-chain.md#beaconstate +// 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, @@ -116,7 +117,7 @@ export const BlockContents = new ContainerType( ); // PayloadAttributes primarily for SSE event -// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/heze/fork-choice.md#modified-payloadattributes +// 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, diff --git a/packages/types/src/heze/types.ts b/packages/types/src/heze/types.ts index 83ae54f9960d..546833262f80 100644 --- a/packages/types/src/heze/types.ts +++ b/packages/types/src/heze/types.ts @@ -4,7 +4,7 @@ import * as ssz from "./sszTypes.js"; export type InclusionListCommittee = ValueOf; export type InclusionList = ValueOf; export type SignedInclusionList = ValueOf; -export type InclusionListByCommitteeIndicesRequest = ValueOf; +export type InclusionListsByIndicesRequest = ValueOf; export type ExecutionPayloadBid = ValueOf; export type SignedExecutionPayloadBid = ValueOf; diff --git a/specrefs/configs.yml b/specrefs/configs.yml index 9690960e8930..37368ea1f579 100644 --- a/specrefs/configs.yml +++ b/specrefs/configs.yml @@ -612,7 +612,9 @@ - name: MIN_SLOTS_FOR_INCLUSION_LISTS_REQUESTS#heze - sources: [] + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "MIN_SLOTS_FOR_INCLUSION_LISTS_REQUESTS:" spec: | MIN_SLOTS_FOR_INCLUSION_LISTS_REQUESTS: Slot = 1 From b705a388ec66fb20824339375d8426a7368cf7b1 Mon Sep 17 00:00:00 2001 From: Lodekeeper <258435968+lodekeeper@users.noreply.github.com> Date: Tue, 4 Aug 2026 22:46:13 +0100 Subject: [PATCH 17/22] update ethspecify --- specrefs/.ethspecify.yml | 1 - specrefs/functions.yml | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml index c4f83b5f3539..3af0b8a8d08c 100644 --- a/specrefs/.ethspecify.yml +++ b/specrefs/.ethspecify.yml @@ -372,7 +372,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/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 a43ac9f1b6ec2643014cf665cb221450e77e19b1 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:20:56 -0700 Subject: [PATCH 18/22] test: skip slow gloas/heze historical_accumulator sanity slots gloas and heze take ~23-24s on the mainnet preset versus ~1-6s for every pre-gloas fork, which is 76-81% of the 30s sanity/slots timeout and a flake risk on slower runners. The suite steps SLOTS_PER_HISTORICAL_ROOT (8192) slots and every post-gloas slot writes into the equally wide executionPayloadAvailability bitvector. Skipped rather than raising the timeout so the regression stays visible. Tracked in https://github.com/ChainSafe/lodestar/issues/9771 --- packages/beacon-node/test/spec/utils/specTestIterator.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 02dcab19dac3..3d04b72ce53b 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -107,6 +107,12 @@ export const defaultSkipOpts: SkipOpts = { // Enable this after https://github.com/ChainSafe/lodestar/issues/9666 is resolved // The case name embeds the generation seed, so it changes whenever comptests are regenerated. /fork_choice_compliance\/block_tree_test\/pyspec_tests\/block_tree_test_17_381675768_1$/, + // TODO GLOAS: gloas/heze take ~23-24s on the mainnet preset (~7.5x pre-gloas) because every + // post-gloas slot writes into the SLOTS_PER_HISTORICAL_ROOT-wide executionPayloadAvailability + // bitvector, and this suite steps 8192 slots. That is 76-81% of the 30s sanity/slots timeout, + // so skip rather than raise the timeout and hide the regression. + // Enable this after https://github.com/ChainSafe/lodestar/issues/9771 is resolved + /^(gloas|heze)\/sanity\/slots\/pyspec_tests\/historical_accumulator$/, ], // TODO GLOAS: Investigate why networking tests are failing since alpha.5 skippedRunners: ["networking"], From d3c6af40a6b7ec03a89cc7aad4d15a24a6c229e0 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:44:40 -0700 Subject: [PATCH 19/22] refactor: address heze review feedback on bid types - getExecutionPayloadBidSigningRoot: select the bid ssz type via fork-aware sszTypesFor() instead of a manual heze/gloas ternary, matching the pattern in getGossipSSZType. Keeps the pre-gloas fallback so the "different fork" negative test still compares roots rather than throwing. - IBeaconStateViewHeze: drop the latestExecutionPayloadBid redeclaration. IBeaconStateViewGloas already declares it as ExecutionPayloadBid, which defaults to the ForkPostGloas union and therefore already covers heze. This follows latestExecutionPayloadHeader, declared once at bellatrix as a union and never re-narrowed in capella or deneb. Dropping it also removes the Omit/re-add of the field in IBeaconStateViewLatestFork, which only existed to undo the narrowing. The interface and isStatePostHeze guard stay: every fork from altair onwards has both, even when it adds no fields (IBeaconStateViewDeneb is forkName-only and isStatePostDeneb has no callers). --- .../src/signatureSets/executionPayloadBid.ts | 8 ++++---- packages/state-transition/src/stateView/interface.ts | 6 +----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/state-transition/src/signatureSets/executionPayloadBid.ts b/packages/state-transition/src/signatureSets/executionPayloadBid.ts index 76f80b017296..5c66a634d927 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, isForkPostGloas} from "@lodestar/params"; +import {ExecutionPayloadBid, Slot, ssz, sszTypesFor} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; export function getExecutionPayloadBidSigningRoot( @@ -8,9 +8,9 @@ export function getExecutionPayloadBidSigningRoot( stateSlot: Slot, bid: ExecutionPayloadBid ): Uint8Array { + const fork = config.getForkName(stateSlot); const domain = config.getDomain(stateSlot, DOMAIN_BEACON_BUILDER); - const sszType = - config.getForkSeq(stateSlot) >= ForkSeq.heze ? ssz.heze.ExecutionPayloadBid : ssz.gloas.ExecutionPayloadBid; + const sszType = isForkPostGloas(fork) ? sszTypesFor(fork).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 58472a831805..3e651daff2f2 100644 --- a/packages/state-transition/src/stateView/interface.ts +++ b/packages/state-transition/src/stateView/interface.ts @@ -40,7 +40,6 @@ import { electra, fulu, gloas, - heze, phase0, rewards, } from "@lodestar/types"; @@ -273,8 +272,6 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu { /** Heze+ state fields — use isStatePostHeze() guard */ export interface IBeaconStateViewHeze extends IBeaconStateViewGloas { forkName: ForkPostHeze; - /** Modified in heze: ExecutionPayloadBid carries `inclusion_list_bits`. */ - latestExecutionPayloadBid: heze.ExecutionPayloadBid; } /** @@ -285,11 +282,10 @@ export interface IBeaconStateViewHeze extends IBeaconStateViewGloas { */ export type IBeaconStateViewLatestFork = Omit< IBeaconStateViewHeze, - "forkName" | "latestExecutionPayloadHeader" | "latestExecutionPayloadBid" | "payloadBlockNumber" + "forkName" | "latestExecutionPayloadHeader" | "payloadBlockNumber" > & { forkName: ForkName; latestExecutionPayloadHeader: ExecutionPayloadHeader; - latestExecutionPayloadBid: ExecutionPayloadBid; payloadBlockNumber: number; }; From aac3f88b8c5f963cb28a72a87fd026b77eab8f0c Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Tue, 4 Aug 2026 18:44:40 -0700 Subject: [PATCH 20/22] refactor: drop spec reference comments from heze ssz types Match the convention in the other fork type modules: gloas, fulu, electra, capella and altair carry no consensus-specs URLs in sszTypes.ts. The [New in Heze:EIP7805] / [Modified in Heze:EIP7805] field markers are kept, since those mirror the spec annotations and mark what heze actually changes. --- packages/types/src/heze/sszTypes.ts | 7 ------- 1 file changed, 7 deletions(-) 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 d5bc30b153c664a6bfdd79b8fddf7c572dc9c7ea Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 5 Aug 2026 09:36:50 +0100 Subject: [PATCH 21/22] review --- .../src/chain/validation/executionPayloadBid.ts | 2 +- .../beacon-node/test/spec/presets/operations.test.ts | 7 ++++--- packages/builder/src/services/builderSigner.ts | 2 +- .../builder/test/unit/services/builderSigner.test.ts | 6 +++--- .../src/block/processExecutionPayloadBid.ts | 2 +- .../src/signatureSets/executionPayloadBid.ts | 12 ++++-------- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/beacon-node/src/chain/validation/executionPayloadBid.ts b/packages/beacon-node/src/chain/validation/executionPayloadBid.ts index feea410c0757..1f178cbdfd16 100644 --- a/packages/beacon-node/src/chain/validation/executionPayloadBid.ts +++ b/packages/beacon-node/src/chain/validation/executionPayloadBid.ts @@ -343,7 +343,7 @@ async function validateExecutionPayloadBid( // [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`. const signatureSet = createSingleSignatureSetFromComponents( PublicKey.fromBytes(builder.pubkey), - getExecutionPayloadBidSigningRoot(chain.config, state.slot, bid), + getExecutionPayloadBidSigningRoot(chain.config, bid), signedExecutionPayloadBid.signature ); diff --git a/packages/beacon-node/test/spec/presets/operations.test.ts b/packages/beacon-node/test/spec/presets/operations.test.ts index d3eebe5e724b..359fd024bd5e 100644 --- a/packages/beacon-node/test/spec/presets/operations.test.ts +++ b/packages/beacon-node/test/spec/presets/operations.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, ForkSeq, isForkPostGloas} from "@lodestar/params"; import {InputType} from "@lodestar/spec-test-util"; import { BeaconStateAllForks, @@ -192,8 +192,9 @@ const operations: TestRunnerFn = (fork, deposit_request: ssz.electra.DepositRequest, consolidation_request: ssz.electra.ConsolidationRequest, payload_attestation: ssz.gloas.PayloadAttestation, - execution_payload_bid: - ForkSeq[fork] >= ForkSeq.heze ? ssz.heze.SignedExecutionPayloadBid : ssz.gloas.SignedExecutionPayloadBid, + execution_payload_bid: isForkPostGloas(fork) + ? sszTypesFor(fork).SignedExecutionPayloadBid + : ssz.gloas.SignedExecutionPayloadBid, builder_deposit_request: ssz.gloas.BuilderDepositRequest, builder_exit_request: ssz.gloas.BuilderExitRequest, }, diff --git a/packages/builder/src/services/builderSigner.ts b/packages/builder/src/services/builderSigner.ts index 92c1479aaded..9ad9e77fbf05 100644 --- a/packages/builder/src/services/builderSigner.ts +++ b/packages/builder/src/services/builderSigner.ts @@ -24,7 +24,7 @@ export class BuilderSigner { } signExecutionPayloadBid(bid: gloas.ExecutionPayloadBid): gloas.SignedExecutionPayloadBid { - const signingRoot = getExecutionPayloadBidSigningRoot(this.config, bid.slot, bid); + const signingRoot = getExecutionPayloadBidSigningRoot(this.config, bid); return { message: bid, diff --git a/packages/builder/test/unit/services/builderSigner.test.ts b/packages/builder/test/unit/services/builderSigner.test.ts index aceeee74ae7d..583e66263c0d 100644 --- a/packages/builder/test/unit/services/builderSigner.test.ts +++ b/packages/builder/test/unit/services/builderSigner.test.ts @@ -39,7 +39,7 @@ describe("BuilderSigner", () => { expect( verify( - getExecutionPayloadBidSigningRoot(beaconConfig, bid.slot, bid), + getExecutionPayloadBidSigningRoot(beaconConfig, bid), publicKey, Signature.fromBytes(signedBid.signature, true) ) @@ -72,7 +72,7 @@ describe("BuilderSigner", () => { expect( verify( - getExecutionPayloadBidSigningRoot(beaconConfigOtherNetwork, bid.slot, bid), + getExecutionPayloadBidSigningRoot(beaconConfigOtherNetwork, bid), publicKey, Signature.fromBytes(signedBid.signature, true) ) @@ -106,7 +106,7 @@ describe("BuilderSigner", () => { expect( verify( - getExecutionPayloadBidSigningRoot(beaconConfigOtherFork, bid.slot, bid), + getExecutionPayloadBidSigningRoot(beaconConfigOtherFork, bid), publicKey, Signature.fromBytes(signedBid.signature, true) ) diff --git a/packages/state-transition/src/block/processExecutionPayloadBid.ts b/packages/state-transition/src/block/processExecutionPayloadBid.ts index b401f5d65948..7350b8e75bfa 100644 --- a/packages/state-transition/src/block/processExecutionPayloadBid.ts +++ b/packages/state-transition/src/block/processExecutionPayloadBid.ts @@ -122,7 +122,7 @@ function verifyExecutionPayloadBidSignature( pubkey: Uint8Array, signedBid: gloas.SignedExecutionPayloadBid ): boolean { - const signingRoot = getExecutionPayloadBidSigningRoot(state.config, state.slot, signedBid.message); + const signingRoot = getExecutionPayloadBidSigningRoot(state.config, signedBid.message); try { const publicKey = PublicKey.fromBytes(pubkey); diff --git a/packages/state-transition/src/signatureSets/executionPayloadBid.ts b/packages/state-transition/src/signatureSets/executionPayloadBid.ts index 5c66a634d927..393aa809b8c8 100644 --- a/packages/state-transition/src/signatureSets/executionPayloadBid.ts +++ b/packages/state-transition/src/signatureSets/executionPayloadBid.ts @@ -1,15 +1,11 @@ import {BeaconConfig} from "@lodestar/config"; import {DOMAIN_BEACON_BUILDER, isForkPostGloas} from "@lodestar/params"; -import {ExecutionPayloadBid, Slot, ssz, sszTypesFor} from "@lodestar/types"; +import {ExecutionPayloadBid, ssz, sszTypesFor} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; -export function getExecutionPayloadBidSigningRoot( - config: BeaconConfig, - stateSlot: Slot, - bid: ExecutionPayloadBid -): Uint8Array { - const fork = config.getForkName(stateSlot); - const domain = config.getDomain(stateSlot, DOMAIN_BEACON_BUILDER); +export function getExecutionPayloadBidSigningRoot(config: BeaconConfig, bid: ExecutionPayloadBid): Uint8Array { + const fork = config.getForkName(bid.slot); + const domain = config.getDomain(bid.slot, DOMAIN_BEACON_BUILDER); const sszType = isForkPostGloas(fork) ? sszTypesFor(fork).ExecutionPayloadBid : ssz.gloas.ExecutionPayloadBid; return computeSigningRoot(sszType, bid, domain); From d388ac1fbbfcb8660d0247a4ee95a8bc6b89e538 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 5 Aug 2026 10:02:11 +0100 Subject: [PATCH 22/22] remove type casts --- .../src/block/processExecutionPayloadBid.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/state-transition/src/block/processExecutionPayloadBid.ts b/packages/state-transition/src/block/processExecutionPayloadBid.ts index 7350b8e75bfa..4104d5bd7a76 100644 --- a/packages/state-transition/src/block/processExecutionPayloadBid.ts +++ b/packages/state-transition/src/block/processExecutionPayloadBid.ts @@ -107,11 +107,9 @@ 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.latestExecutionPayloadBid = ssz.heze.ExecutionPayloadBid.toViewDU(bid as heze.ExecutionPayloadBid); } else { - (state as CachedBeaconStateGloas).latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); + state.latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid); } return parentSlot;