diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 7e61ce6671d9..5f5379ba26f5 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -247,7 +247,8 @@ export async function importBlock( this.forkChoice.notifyPtcMessages( toRootHex(payloadAttestation.data.beaconBlockRoot), ptcIndices, - payloadAttestation.data.payloadPresent + payloadAttestation.data.payloadPresent, + payloadAttestation.data.blobDataAvailable ); } } catch (e) { diff --git a/packages/beacon-node/src/chain/errors/blockError.ts b/packages/beacon-node/src/chain/errors/blockError.ts index 7eb02a60bb40..01de8b27883c 100644 --- a/packages/beacon-node/src/chain/errors/blockError.ts +++ b/packages/beacon-node/src/chain/errors/blockError.ts @@ -70,6 +70,8 @@ export enum BlockErrorCode { TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS", /** Bid parent block root does not match block parent root */ BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH", + /** Parent execution payload has not been seen */ + PARENT_PAYLOAD_UNKNOWN = "BLOCK_ERROR_PARENT_PAYLOAD_UNKNOWN", } type ExecutionErrorStatus = Exclude< @@ -114,7 +116,8 @@ export type BlockErrorType = | {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionErrorStatus; errorMessage: string} | {code: BlockErrorCode.DATA_UNAVAILABLE} | {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number} - | {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}; + | {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex} + | {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentBlockHash: RootHex}; export class BlockGossipError extends GossipActionError {} diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index f7addf9a5794..8570e7751086 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -153,6 +153,17 @@ export async function validateGossipBlock( }); } + // [IGNORE] The block's parent execution payload (defined by bid.parent_block_hash) has been seen + // (via gossip or non-gossip sources) (a client MAY queue blocks for processing once the parent + // payload is retrieved). + const parentBlockHashHex = toRootHex(bid.parentBlockHash); + if (!chain.forkChoice.hasExecutionPayload(parentBlockHashHex)) { + throw new BlockGossipError(GossipAction.IGNORE, { + code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN, + parentBlockHash: parentBlockHashHex, + }); + } + // TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation // This requires execution engine integration to verify the parent block hash } diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index b022a1866470..effd9ae2ca6a 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -859,7 +859,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand chain.forkChoice.notifyPtcMessages( toRootHex(payloadAttestationMessage.data.beaconBlockRoot), [validationResult.validatorCommitteeIndex], - payloadAttestationMessage.data.payloadPresent + payloadAttestationMessage.data.payloadPresent, + payloadAttestationMessage.data.blobDataAvailable ); }, [GossipType.execution_payload_bid]: async ({ diff --git a/packages/beacon-node/test/spec/presets/operations.test.ts b/packages/beacon-node/test/spec/presets/operations.test.ts index 6b8d7a90300a..9a8a1cd6068e 100644 --- a/packages/beacon-node/test/spec/presets/operations.test.ts +++ b/packages/beacon-node/test/spec/presets/operations.test.ts @@ -17,7 +17,7 @@ import {AttesterSlashing, altair, bellatrix, capella, electra, gloas, phase0, ss import {createCachedBeaconStateTest} from "../../utils/cachedBeaconState.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {expectEqualBeaconState, inputTypeSszTreeViewDU} from "../utils/expectEqualBeaconState.js"; -import {specTestIterator} from "../utils/specTestIterator.js"; +import {defaultSkipOpts, specTestIterator} from "../utils/specTestIterator.js"; import {BaseSpecTest, RunnerType, TestRunnerFn, shouldVerify} from "../utils/types.js"; // Define above to re-use in sync_aggregate and sync_aggregate_random @@ -187,6 +187,15 @@ const operations: TestRunnerFn = (fork, }; }; -specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), { - operations: {type: RunnerType.default, fn: operations}, -}); +specTestIterator( + path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), + { + operations: {type: RunnerType.default, fn: operations}, + }, + { + ...defaultSkipOpts, + // builder_voluntary_exit__success: spec test vector bug — missing voluntary_exit input yield + // See: https://github.com/ethereum/consensus-specs/pull/4908 + skippedTests: [...(defaultSkipOpts.skippedTests ?? []), /builder_voluntary_exit__success/], + } +); diff --git a/packages/fork-choice/src/forkChoice/errors.ts b/packages/fork-choice/src/forkChoice/errors.ts index c79309b17547..f97df72bf087 100644 --- a/packages/fork-choice/src/forkChoice/errors.ts +++ b/packages/fork-choice/src/forkChoice/errors.ts @@ -57,6 +57,10 @@ export enum InvalidAttestationCode { * The attestation data index is invalid for a Gloas block (must be 0 or 1). */ INVALID_DATA_INDEX = "INVALID_DATA_INDEX", + /** + * Attestation index=1 (full node) but the payload is not known/available. + */ + UNKNOWN_PAYLOAD_STATUS = "UNKNOWN_PAYLOAD_STATUS", } export type InvalidAttestation = @@ -69,7 +73,8 @@ export type InvalidAttestation = | {code: InvalidAttestationCode.INVALID_TARGET; attestation: RootHex; local: RootHex} | {code: InvalidAttestationCode.ATTESTS_TO_FUTURE_BLOCK; block: Slot; attestation: Slot} | {code: InvalidAttestationCode.FUTURE_SLOT; attestationSlot: Slot; latestPermissibleSlot: Slot} - | {code: InvalidAttestationCode.INVALID_DATA_INDEX; index: number}; + | {code: InvalidAttestationCode.INVALID_DATA_INDEX; index: number} + | {code: InvalidAttestationCode.UNKNOWN_PAYLOAD_STATUS; beaconBlockRoot: RootHex}; export enum ForkChoiceErrorCode { INVALID_ATTESTATION = "FORKCHOICE_ERROR_INVALID_ATTESTATION", diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 2d0cef22540c..5a515ec6040e 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -895,7 +895,7 @@ export class ForkChoice implements IForkChoice { let payloadStatus: PayloadStatus; // We need to retrieve block to check if it's Gloas and to compare slot - // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#new-is_supporting_vote + // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#new-is_supporting_vote const block = this.getBlockHexDefaultStatus(blockRootHex); if (block && isGloasBlock(block)) { @@ -964,8 +964,13 @@ export class ForkChoice implements IForkChoice { * Updates the PTC votes for multiple validators attesting to a block * Spec: gloas/fork-choice.md#new-on_payload_attestation_message */ - notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void { - this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent); + notifyPtcMessages( + blockRoot: RootHex, + ptcIndices: number[], + payloadPresent: boolean, + blobDataAvailable: boolean + ): void { + this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent, blobDataAvailable); } /** @@ -1056,6 +1061,14 @@ export class ForkChoice implements IForkChoice { return this.protoArray.hasBlock(blockRoot); } + /** + * Check if an execution payload with the given block hash has been seen. + * Spec: gloas/p2p-interface.md — parent execution payload must have been seen + */ + hasExecutionPayload(executionPayloadBlockHash: RootHex): boolean { + return this.protoArray.hasExecutionPayload(executionPayloadBlockHash); + } + /** * Returns a MUTABLE `ProtoBlock` if the block is known **and** a descendant of the finalized root. */ @@ -1673,6 +1686,22 @@ export class ForkChoice implements IForkChoice { }); } + // [New in Gloas:EIP7732] + // If attesting for a full node (index=1), the payload must be known (locally available) + // Spec: gloas/fork-choice.md#modified-validate_on_attestation + if (isGloasBlock(block) && attestationData.index === 1) { + const fullVariant = this.protoArray.getNodeIndexByRootAndStatus(beaconBlockRootHex, PayloadStatus.FULL); + if (fullVariant === undefined) { + throw new ForkChoiceError({ + code: ForkChoiceErrorCode.INVALID_ATTESTATION, + err: { + code: InvalidAttestationCode.UNKNOWN_PAYLOAD_STATUS, + beaconBlockRoot: beaconBlockRootHex, + }, + }); + } + } + this.validatedAttestationDatas.add(attDataRoot); } diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index 26e7f08c2be5..db9a277a3750 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -84,7 +84,7 @@ export interface IForkChoice { * ## Specification * * Modified for Gloas to return ProtoNode instead of just root: - * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#modified-get_ancestor + * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#modified-get_ancestor * * Pre-Gloas: Returns (root, PAYLOAD_STATUS_FULL) * Gloas: Returns (root, payloadStatus) based on actual node state @@ -183,20 +183,26 @@ export interface IForkChoice { * * ## Specification * - * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/gloas/fork-choice.md#new-notify_ptc_messages + * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#new-on_payload_attestation_message * * @param blockRoot - The beacon block root being attested * @param ptcIndices - Array of PTC committee indices that voted - * @param payloadPresent - Whether validators attest the payload is present + * @param payloadPresent - Whether validators attest the payload is present (timeliness) + * @param blobDataAvailable - Whether validators attest blob data is available */ - notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void; + notifyPtcMessages( + blockRoot: RootHex, + ptcIndices: number[], + payloadPresent: boolean, + blobDataAvailable: boolean + ): void; /** * Notify fork choice that an execution payload has arrived (Gloas fork) * Creates the FULL variant of a Gloas block when the payload becomes available * * ## Specification * - * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#new-on_execution_payload + * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#new-on_execution_payload * * @param blockRoot - The beacon block root for which the payload arrived * @param executionPayloadBlockHash - The block hash of the execution payload @@ -228,6 +234,11 @@ export interface IForkChoice { */ hasBlockUnsafe(blockRoot: Root): boolean; hasBlockHexUnsafe(blockRoot: RootHex): boolean; + /** + * Check if an execution payload with the given block hash has been seen. + * For Gloas: checks if any block has a FULL variant with matching execution payload block hash. + */ + hasExecutionPayload(executionPayloadBlockHash: RootHex): boolean; getSlotsPresent(windowStart: number): number; /** * Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root. diff --git a/packages/fork-choice/src/protoArray/interface.ts b/packages/fork-choice/src/protoArray/interface.ts index 5c42e6f33c03..87952a894183 100644 --- a/packages/fork-choice/src/protoArray/interface.ts +++ b/packages/fork-choice/src/protoArray/interface.ts @@ -41,9 +41,9 @@ export enum ExecutionStatus { * Spec: gloas/fork-choice.md#constants */ export enum PayloadStatus { - PENDING = 0, - EMPTY = 1, - FULL = 2, + EMPTY = 0, + FULL = 1, + PENDING = 2, } /** diff --git a/packages/fork-choice/src/protoArray/protoArray.ts b/packages/fork-choice/src/protoArray/protoArray.ts index 683ba6b38145..e525c46593e9 100644 --- a/packages/fork-choice/src/protoArray/protoArray.ts +++ b/packages/fork-choice/src/protoArray/protoArray.ts @@ -21,6 +21,12 @@ import { */ const PAYLOAD_TIMELY_THRESHOLD = Math.floor(PTC_SIZE / 2); +/** + * Threshold for payload data availability (>50% of PTC must vote) + * Spec: gloas/fork-choice.md (DATA_AVAILABILITY_TIMELY_THRESHOLD = PTC_SIZE // 2) + */ +const DATA_AVAILABILITY_TIMELY_THRESHOLD = Math.floor(PTC_SIZE / 2); + export const DEFAULT_PRUNE_THRESHOLD = 0; type ProposerBoost = {root: RootHex; score: number}; @@ -29,11 +35,12 @@ const ZERO_HASH_HEX = toRootHex(Buffer.alloc(32, 0)); /** Pre-Gloas: single element, FULL index (for backward compatibility) */ type PreGloasVariantIndex = number; /** - * Post-Gloas: array length is 2 or 3 - * - Length 2: [PENDING_INDEX, EMPTY_INDEX] when payload hasn't arrived yet - * - Length 3: [PENDING_INDEX, EMPTY_INDEX, FULL_INDEX] when payload has arrived + * Post-Gloas: always length 3, indexed by PayloadStatus enum value + * - [EMPTY_INDEX, FULL_INDEX | undefined, PENDING_INDEX] + * - PayloadStatus.EMPTY=0, PayloadStatus.FULL=1, PayloadStatus.PENDING=2 + * - FULL starts as undefined until execution payload arrives */ -type GloasVariantIndices = [number, number] | [number, number, number]; +type GloasVariantIndices = [number, number | undefined, number]; type VariantIndices = PreGloasVariantIndex | GloasVariantIndices; export class ProtoArray { @@ -48,10 +55,10 @@ export class ProtoArray { /** * Maps block root to array of node indices for each payload status variant * - * Array structure: [PENDING, EMPTY, FULL] where indices correspond to PayloadStatus enum values - * - number[0] = PENDING variant index (PayloadStatus.PENDING = 0) - * - number[1] = EMPTY variant index (PayloadStatus.EMPTY = 1) - * - number[2] = FULL variant index (PayloadStatus.FULL = 2) + * Array structure: [EMPTY, FULL, PENDING] where indices correspond to PayloadStatus enum values + * - number[0] = EMPTY variant index (PayloadStatus.EMPTY = 0) + * - number[1] = FULL variant index (PayloadStatus.FULL = 1) + * - number[2] = PENDING variant index (PayloadStatus.PENDING = 2) * * Note: undefined array elements indicate that variant doesn't exist for this block */ @@ -61,14 +68,24 @@ export class ProtoArray { private previousProposerBoost: ProposerBoost | null = null; /** - * PTC (Payload Timeliness Committee) votes per block as bitvectors + * Payload timeliness votes per block as bitvectors * Maps block root to BitArray of PTC_SIZE bits (512 mainnet, 2 minimal) - * Spec: gloas/fork-choice.md#modified-store (line 148) + * Spec: gloas/fork-choice.md#modified-store (payload_timeliness_vote) * * Bit i is set if PTC member i voted payload_present=true * Used by is_payload_timely() to determine if payload is timely */ - private ptcVotes = new Map(); + private payloadTimelinessVotes = new Map(); + + /** + * Payload data availability votes per block as bitvectors + * Maps block root to BitArray of PTC_SIZE bits (512 mainnet, 2 minimal) + * Spec: gloas/fork-choice.md#modified-store (payload_data_availability_vote) + * + * Bit i is set if PTC member i voted blob_data_available=true + * Used by is_payload_data_available() to determine if blob data is available + */ + private payloadDataAvailabilityVotes = new Map(); constructor({ pruneThreshold, @@ -439,11 +456,11 @@ export class ProtoArray { // Check if parent exists by getting variants array const parentVariants = this.indices.get(block.parentRoot); if (parentVariants != null) { - const anyParentIndex = Array.isArray(parentVariants) ? parentVariants[0] : parentVariants; + const anyParentIndex = Array.isArray(parentVariants) ? parentVariants[PayloadStatus.EMPTY] : parentVariants; const anyParentNode = this.nodes[anyParentIndex]; if (!isGloasBlock(anyParentNode)) { - // Fork transition: parent is pre-Gloas, so it only has FULL variant at variants[0] + // Fork transition: parent is pre-Gloas, so it only has FULL variant parentIndex = anyParentIndex; } else { // Both blocks are Gloas: determine which parent payload status to extend @@ -479,9 +496,9 @@ export class ProtoArray { const emptyIndex = this.nodes.length; this.nodes.push(emptyNode); - // Store both variants in the indices array - // [PENDING, EMPTY, undefined] - FULL will be added later if payload arrives - this.indices.set(block.blockRoot, [pendingIndex, emptyIndex]); + // Store variants in the indices array indexed by PayloadStatus enum values + // [EMPTY=0, FULL=1(undefined), PENDING=2] - FULL will be added when payload arrives + this.indices.set(block.blockRoot, [emptyIndex, undefined, pendingIndex]); // Update bestChild pointers if (parentIndex !== undefined) { @@ -495,9 +512,20 @@ export class ProtoArray { // Update bestChild for PENDING → EMPTY edge this.maybeUpdateBestChildAndDescendant(pendingIndex, emptyIndex, currentSlot, proposerBoostRoot); - // Initialize PTC votes for this block (all false initially) - // Spec: gloas/fork-choice.md#modified-on_block (line 645) - this.ptcVotes.set(block.blockRoot, BitArray.fromBitLen(PTC_SIZE)); + // Initialize both vote maps for this block + // Spec: gloas/fork-choice.md#modified-on_block (all false for new blocks) + // Spec: gloas/fork-choice.md#modified-get_forkchoice_store (all true for anchor block) + const timelinessVotes = BitArray.fromBitLen(PTC_SIZE); + const dataAvailabilityVotes = BitArray.fromBitLen(PTC_SIZE); + if (block.blockRoot === this.finalizedRoot) { + // Anchor block: set all bits to true per get_forkchoice_store spec + for (let i = 0; i < PTC_SIZE; i++) { + timelinessVotes.set(i, true); + dataAvailabilityVotes.set(i, true); + } + } + this.payloadTimelinessVotes.set(block.blockRoot, timelinessVotes); + this.payloadDataAvailabilityVotes.set(block.blockRoot, dataAvailabilityVotes); } else { // Pre-Gloas: Only create FULL node (payload embedded in block) const node: ProtoNode = { @@ -612,11 +640,18 @@ export class ProtoArray { * * @param blockRoot - The beacon block root being attested * @param ptcIndices - Array of PTC committee indices that voted (0..PTC_SIZE-1) - * @param payloadPresent - Whether the validators attest the payload is present + * @param payloadPresent - Whether the validators attest the payload is present (timeliness) + * @param blobDataAvailable - Whether the validators attest blob data is available */ - notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void { - const votes = this.ptcVotes.get(blockRoot); - if (votes === undefined) { + notifyPtcMessages( + blockRoot: RootHex, + ptcIndices: number[], + payloadPresent: boolean, + blobDataAvailable: boolean + ): void { + const timelinessVotes = this.payloadTimelinessVotes.get(blockRoot); + const dataAvailabilityVotes = this.payloadDataAvailabilityVotes.get(blockRoot); + if (timelinessVotes === undefined || dataAvailabilityVotes === undefined) { // Block not found or not a Gloas block, ignore return; } @@ -626,7 +661,8 @@ export class ProtoArray { throw new Error(`Invalid PTC index: ${ptcIndex}, must be 0..${PTC_SIZE - 1}`); } - votes.set(ptcIndex, payloadPresent); + timelinessVotes.set(ptcIndex, payloadPresent); + dataAvailabilityVotes.set(ptcIndex, blobDataAvailable); } } @@ -642,7 +678,7 @@ export class ProtoArray { * @param blockRoot - The beacon block root to check */ isPayloadTimely(blockRoot: RootHex): boolean { - const votes = this.ptcVotes.get(blockRoot); + const votes = this.payloadTimelinessVotes.get(blockRoot); if (votes === undefined) { // Block not found or not a Gloas block return false; @@ -660,6 +696,35 @@ export class ProtoArray { return yesVotes > PAYLOAD_TIMELY_THRESHOLD; } + /** + * Check if blob data for a block is available + * Spec: gloas/fork-choice.md#new-is_payload_data_available + * + * Returns true if: + * 1. Block has data availability votes tracked + * 2. Payload is locally available (FULL variant exists in proto array) + * 3. More than DATA_AVAILABILITY_TIMELY_THRESHOLD (>50% of PTC) voted blob_data_available=true + * + * @param blockRoot - The beacon block root to check + */ + isPayloadDataAvailable(blockRoot: RootHex): boolean { + const votes = this.payloadDataAvailabilityVotes.get(blockRoot); + if (votes === undefined) { + // Block not found or not a Gloas block + return false; + } + + // If payload is not locally available, blob data is not considered available + const fullNodeIndex = this.getNodeIndexByRootAndStatus(blockRoot, PayloadStatus.FULL); + if (fullNodeIndex === undefined) { + return false; + } + + // Count votes for blob_data_available=true + const yesVotes = bitCount(votes.uint8Array); + return yesVotes > DATA_AVAILABILITY_TIMELY_THRESHOLD; + } + /** * Check if parent node is FULL * Spec: gloas/fork-choice.md#new-is_parent_node_full @@ -684,8 +749,8 @@ export class ProtoArray { * @param proposerBoostRoot - Current proposer boost root (from ForkChoice) */ shouldExtendPayload(blockRoot: RootHex, proposerBoostRoot: RootHex | null): boolean { - // Condition 1: Payload is timely - if (this.isPayloadTimely(blockRoot)) { + // Condition 1: Payload is timely AND blob data is available + if (this.isPayloadTimely(blockRoot) && this.isPayloadDataAvailable(blockRoot)) { return true; } @@ -1083,7 +1148,8 @@ export class ProtoArray { this.indices.delete(root); // Prune PTC votes for this block to prevent memory leak // Spec: gloas/fork-choice.md (implicit - finalized blocks don't need PTC votes) - this.ptcVotes.delete(root); + this.payloadTimelinessVotes.delete(root); + this.payloadDataAvailabilityVotes.delete(root); } // Store nodes prior to finalization @@ -1391,13 +1457,13 @@ export class ProtoArray { * ### Specification * * Modified for Gloas to return node identifier instead of just root: - * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#modified-get_ancestor + * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#modified-get_ancestor * * Pre-Gloas: Returns (root, PAYLOAD_STATUS_FULL) * Gloas: Returns (root, payloadStatus) based on actual node state */ getAncestor(blockRoot: RootHex, ancestorSlot: Slot): ProtoNode { - // Get any variant to check the block (use variants[0]) + // Get any variant to check the block (EMPTY for Gloas, FULL for pre-Gloas) const variantOrArr = this.indices.get(blockRoot); if (variantOrArr == null) { throw new ForkChoiceError({ @@ -1406,13 +1472,11 @@ export class ProtoArray { }); } - const blockIndex = Array.isArray(variantOrArr) ? variantOrArr[0] : variantOrArr; + const blockIndex = Array.isArray(variantOrArr) ? variantOrArr[PayloadStatus.EMPTY] : variantOrArr; const block = this.nodes[blockIndex]; - // If block is at or before queried slot, return PENDING variant (or FULL for pre-Gloas) + // If block is at or before queried slot, return any variant (EMPTY for Gloas, FULL for pre-Gloas) if (block.slot <= ancestorSlot) { - // For pre-Gloas: only FULL exists at variants[0] - // For Gloas: PENDING is at variants[0] return block; } @@ -1428,7 +1492,7 @@ export class ProtoArray { }); } - let parentIndex = Array.isArray(parentVariants) ? parentVariants[0] : parentVariants; + let parentIndex = Array.isArray(parentVariants) ? parentVariants[PayloadStatus.EMPTY] : parentVariants; let parentBlock = this.nodes[parentIndex]; // Walk backwards while parent.slot > ancestorSlot @@ -1444,7 +1508,7 @@ export class ProtoArray { }); } - parentIndex = Array.isArray(nextParentVariants) ? nextParentVariants[0] : nextParentVariants; + parentIndex = Array.isArray(nextParentVariants) ? nextParentVariants[PayloadStatus.EMPTY] : nextParentVariants; parentBlock = this.nodes[parentIndex]; } @@ -1671,6 +1735,36 @@ export class ProtoArray { return this.getDefaultNodeIndex(blockRoot) !== undefined; } + /** + * Check if an execution payload with the given block hash has been seen. + * Looks through all nodes with FULL variant status for a matching executionPayloadBlockHash. + * Used for Gloas gossip validation: parent execution payload must have been seen. + * + * TODO GLOAS: Add secondary index (Set by executionPayloadBlockHash) for O(1) lookups. + * Current O(n) linear scan is acceptable during development but should be optimized before mainnet. + */ + hasExecutionPayload(executionPayloadBlockHash: RootHex): boolean { + for (const [, variantOrArr] of this.indices) { + if (!Array.isArray(variantOrArr)) { + // Pre-Gloas: check directly + const node = this.nodes[variantOrArr]; + if (node?.executionPayloadBlockHash === executionPayloadBlockHash) { + return true; + } + } else { + // Gloas: check FULL variant if it exists + const fullIndex = variantOrArr[PayloadStatus.FULL]; + if (fullIndex !== undefined) { + const node = this.nodes[fullIndex]; + if (node?.executionPayloadBlockHash === executionPayloadBlockHash) { + return true; + } + } + } + } + return false; + } + /** * Return ProtoNode for blockRoot with explicit payload status * diff --git a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts index c2ab249d3093..e773976c712f 100644 --- a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts @@ -161,7 +161,7 @@ describe("Forkchoice", () => { bestDescendant: undefined, parent: 0, weight: 0, - payloadStatus: 2, // Pre-Gloas blocks always have PAYLOAD_STATUS_FULL + payloadStatus: PayloadStatus.FULL, // Pre-Gloas blocks always have PAYLOAD_STATUS_FULL }); }); diff --git a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts index 9e8b79261ad0..30ed1916d648 100644 --- a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts +++ b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts @@ -304,7 +304,7 @@ describe("executionStatus / invalidate all postmerge chain", () => { // findHead returns ProtoNode // For pre-Gloas blocks, this should have blockRoot "0" and payloadStatus FULL (2) expect(fcHead.blockRoot).toBe("0"); - expect(fcHead.payloadStatus).toBe(2); // PayloadStatus.FULL + expect(fcHead.payloadStatus).toBe(PayloadStatus.FULL); }); }); diff --git a/packages/fork-choice/test/unit/protoArray/gloas.test.ts b/packages/fork-choice/test/unit/protoArray/gloas.test.ts index b1e5bb84d684..6a6a6d5dcfc3 100644 --- a/packages/fork-choice/test/unit/protoArray/gloas.test.ts +++ b/packages/fork-choice/test/unit/protoArray/gloas.test.ts @@ -347,7 +347,7 @@ describe("Gloas Fork Choice", () => { expect(protoArray.isPayloadTimely("0x02")).toBe(false); // Vote yes from validators at indices 0, 1, 2 - protoArray.notifyPtcMessages("0x02", [0, 1, 2], true); + protoArray.notifyPtcMessages("0x02", [0, 1, 2], true, true); // Still not timely (need >50% of PTC_SIZE) expect(protoArray.isPayloadTimely("0x02")).toBe(false); @@ -357,15 +357,15 @@ describe("Gloas Fork Choice", () => { const block = createTestBlock(gloasForkSlot, "0x02", genesisRoot, genesisRoot); protoArray.onBlock(block, gloasForkSlot, null); - expect(() => protoArray.notifyPtcMessages("0x02", [-1], true)).toThrow(/Invalid PTC index/); - expect(() => protoArray.notifyPtcMessages("0x02", [PTC_SIZE], true)).toThrow(/Invalid PTC index/); - expect(() => protoArray.notifyPtcMessages("0x02", [PTC_SIZE + 1], true)).toThrow(/Invalid PTC index/); - expect(() => protoArray.notifyPtcMessages("0x02", [0, 1, PTC_SIZE], true)).toThrow(/Invalid PTC index/); + expect(() => protoArray.notifyPtcMessages("0x02", [-1], true, true)).toThrow(/Invalid PTC index/); + expect(() => protoArray.notifyPtcMessages("0x02", [PTC_SIZE], true, true)).toThrow(/Invalid PTC index/); + expect(() => protoArray.notifyPtcMessages("0x02", [PTC_SIZE + 1], true, true)).toThrow(/Invalid PTC index/); + expect(() => protoArray.notifyPtcMessages("0x02", [0, 1, PTC_SIZE], true, true)).toThrow(/Invalid PTC index/); }); it("notifyPtcMessages() handles unknown block gracefully", () => { // Should not throw for unknown block - expect(() => protoArray.notifyPtcMessages("0x99", [0], true)).not.toThrow(); + expect(() => protoArray.notifyPtcMessages("0x99", [0], true, true)).not.toThrow(); }); it("isPayloadTimely() returns false when payload not locally available", () => { @@ -375,7 +375,7 @@ describe("Gloas Fork Choice", () => { // Vote yes from majority of PTC const threshold = Math.floor(PTC_SIZE / 2) + 1; const indices = Array.from({length: threshold}, (_, i) => i); - protoArray.notifyPtcMessages("0x02", indices, true); + protoArray.notifyPtcMessages("0x02", indices, true, true); // Without execution payload (no FULL variant), should return false expect(protoArray.isPayloadTimely("0x02")).toBe(false); @@ -391,7 +391,7 @@ describe("Gloas Fork Choice", () => { // Vote yes from majority of PTC (>50%) const threshold = Math.floor(PTC_SIZE / 2) + 1; const indices = Array.from({length: threshold}, (_, i) => i); - protoArray.notifyPtcMessages("0x02", indices, true); + protoArray.notifyPtcMessages("0x02", indices, true, true); // Should now be timely expect(protoArray.isPayloadTimely("0x02")).toBe(true); @@ -407,7 +407,7 @@ describe("Gloas Fork Choice", () => { // Vote yes from exactly 50% (not >50%) const threshold = Math.floor(PTC_SIZE / 2); const indices = Array.from({length: threshold}, (_, i) => i); - protoArray.notifyPtcMessages("0x02", indices, true); + protoArray.notifyPtcMessages("0x02", indices, true, true); // Should not be timely (need >50%, not >=50%) expect(protoArray.isPayloadTimely("0x02")).toBe(false); @@ -424,16 +424,16 @@ describe("Gloas Fork Choice", () => { const threshold = Math.floor(PTC_SIZE / 2) + 1; // Vote yes from indices 0..threshold-1 const yesIndices = Array.from({length: threshold}, (_, i) => i); - protoArray.notifyPtcMessages("0x02", yesIndices, true); + protoArray.notifyPtcMessages("0x02", yesIndices, true, true); // Vote no from indices threshold..PTC_SIZE-1 const noIndices = Array.from({length: PTC_SIZE - threshold}, (_, i) => i + threshold); - protoArray.notifyPtcMessages("0x02", noIndices, false); + protoArray.notifyPtcMessages("0x02", noIndices, false, false); // Should be timely (threshold met) expect(protoArray.isPayloadTimely("0x02")).toBe(true); // Change some yes votes to no - protoArray.notifyPtcMessages("0x02", [0, 1], false); + protoArray.notifyPtcMessages("0x02", [0, 1], false, false); // Should no longer be timely expect(protoArray.isPayloadTimely("0x02")).toBe(false); @@ -451,7 +451,7 @@ describe("Gloas Fork Choice", () => { expect(protoArray.isPayloadTimely("0x02")).toBe(false); // notifyPtcMessages should be no-op - expect(() => protoArray.notifyPtcMessages("0x02", [0], true)).not.toThrow(); + expect(() => protoArray.notifyPtcMessages("0x02", [0], true, true)).not.toThrow(); }); }); diff --git a/packages/state-transition/src/block/processDepositRequest.ts b/packages/state-transition/src/block/processDepositRequest.ts index 1bd12e5cb282..767b9d372f03 100644 --- a/packages/state-transition/src/block/processDepositRequest.ts +++ b/packages/state-transition/src/block/processDepositRequest.ts @@ -1,13 +1,13 @@ import {FAR_FUTURE_EPOCH, ForkSeq, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params"; import {BLSPubkey, Bytes32, UintNum64, electra, ssz} from "@lodestar/types"; import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js"; -import {findBuilderIndexByPubkey, isBuilderWithdrawalCredential} from "../util/gloas.js"; +import {findBuilderIndexByPubkey, isBuilderWithdrawalCredential, isPendingValidator} from "../util/gloas.js"; import {computeEpochAtSlot, isValidatorKnown} from "../util/index.js"; import {isValidDepositSignature} from "./processDeposit.js"; /** * Apply a deposit for a builder. Either increases balance for existing builder or adds new builder to registry. - * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/beacon-chain.md#new-apply_deposit_for_builder + * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/beacon-chain.md#new-apply_deposit_for_builder */ export function applyDepositForBuilder( state: CachedBeaconStateGloas, @@ -93,8 +93,9 @@ export function processDepositRequest( const isValidator = isValidatorKnown(state, validatorIndex); const isBuilderPrefix = isBuilderWithdrawalCredential(withdrawalCredentials); - // Route to builder if it's an existing builder OR has builder prefix and is not a validator - if (isBuilder || (isBuilderPrefix && !isValidator)) { + // Route to builder if it's an existing builder OR has builder prefix and is not a validator/pending validator + // Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/beacon-chain.md#modified-process_deposit_request + if (isBuilder || (isBuilderPrefix && !isValidator && !isPendingValidator(state.config, stateGloas, pubkey))) { // Apply builder deposits immediately applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot); return; diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index e2b54f5e183a..f60ad245a0ec 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -80,7 +80,7 @@ export function processWithdrawals( applyWithdrawals(state, expectedWithdrawals); if (fork >= ForkSeq.electra) { - // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/electra/beacon-chain.md#new-update_pending_partial_withdrawals + // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/electra/beacon-chain.md#new-update_pending_partial_withdrawals const stateElectra = state as CachedBeaconStateElectra; stateElectra.pendingPartialWithdrawals = stateElectra.pendingPartialWithdrawals.sliceFrom( processedPartialWithdrawalsCount @@ -105,13 +105,13 @@ export function processWithdrawals( } } // Update the nextWithdrawalIndex - // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_index + // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/capella/beacon-chain.md#new-update_next_withdrawal_index const latestWithdrawal = expectedWithdrawals.at(-1); if (latestWithdrawal) { state.nextWithdrawalIndex = latestWithdrawal.index + 1; } - // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-update_next_withdrawal_validator_index + // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/capella/beacon-chain.md#new-update_next_withdrawal_validator_index // Update the nextWithdrawalValidatorIndex if (latestWithdrawal && expectedWithdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) { // All slots filled, nextWithdrawalValidatorIndex should be validatorIndex having next turn @@ -418,7 +418,7 @@ export function getExpectedWithdrawals( const expectedWithdrawals: capella.Withdrawal[] = []; // Separate maps to track balances after applying withdrawals - // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/capella/beacon-chain.md#new-get_balance_after_withdrawals + // https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/capella/beacon-chain.md#new-get_balance_after_withdrawals const builderBalanceAfterWithdrawals = new Map(); const validatorBalanceAfterWithdrawals = new Map(); // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) diff --git a/packages/state-transition/src/slot/upgradeStateToGloas.ts b/packages/state-transition/src/slot/upgradeStateToGloas.ts index 64403823fa8e..3b5d36b8f700 100644 --- a/packages/state-transition/src/slot/upgradeStateToGloas.ts +++ b/packages/state-transition/src/slot/upgradeStateToGloas.ts @@ -82,7 +82,7 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea /** * Applies any pending deposits for builders to onboard builders during the fork transition - * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.2/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits + * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits */ function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void { // Track pubkeys of new validators to keep their deposits pending diff --git a/packages/state-transition/src/util/gloas.ts b/packages/state-transition/src/util/gloas.ts index 1a3ce4206046..581a07b0f11c 100644 --- a/packages/state-transition/src/util/gloas.ts +++ b/packages/state-transition/src/util/gloas.ts @@ -1,3 +1,4 @@ +import {BeaconConfig} from "@lodestar/config"; import { BUILDER_INDEX_FLAG, BUILDER_PAYMENT_THRESHOLD_DENOMINATOR, @@ -8,14 +9,44 @@ import { MIN_DEPOSIT_AMOUNT, SLOTS_PER_EPOCH, } from "@lodestar/params"; -import {BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types"; +import {BLSPubkey, BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types"; import {AttestationData} from "@lodestar/types/phase0"; import {byteArrayEquals} from "@lodestar/utils"; +import {isValidDepositSignature} from "../block/processDeposit.js"; import {CachedBeaconStateGloas} from "../types.js"; import {getBlockRootAtSlot} from "./blockRoot.js"; import {computeEpochAtSlot} from "./epoch.js"; import {RootCache} from "./rootCache.js"; +/** + * Check if a pending deposit with a valid signature is in the queue for the given pubkey. + * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/beacon-chain.md#new-is_pending_validator + * + * Note: This function naively revalidates deposit signatures on every call. + * Implementations SHOULD cache verification results to avoid repeated work. + * TODO GLOAS: Cache deposit signature validation results to avoid repeated BLS verification + */ +export function isPendingValidator(config: BeaconConfig, state: CachedBeaconStateGloas, pubkey: BLSPubkey): boolean { + for (let i = 0; i < state.pendingDeposits.length; i++) { + const pendingDeposit = state.pendingDeposits.getReadonly(i); + if (!byteArrayEquals(pendingDeposit.pubkey, pubkey)) { + continue; + } + if ( + isValidDepositSignature( + config, + pendingDeposit.pubkey, + pendingDeposit.withdrawalCredentials, + pendingDeposit.amount, + pendingDeposit.signature + ) + ) { + return true; + } + } + return false; +} + export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return withdrawalCredentials[0] === BUILDER_WITHDRAWAL_PREFIX; } diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index 64e5f698bb98..b68272d24f00 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -98,7 +98,7 @@ export function getMaxEffectiveBalance(withdrawalCredentials: Uint8Array): numbe /** * Check if validator is partially withdrawable. - * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/electra/beacon-chain.md#modified-is_partially_withdrawable_validator + * https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/electra/beacon-chain.md#modified-is_partially_withdrawable_validator */ export function isPartiallyWithdrawableValidator(fork: ForkSeq, validator: phase0.Validator, balance: number): boolean { const isPostElectra = fork >= ForkSeq.electra; diff --git a/spec-tests-version.json b/spec-tests-version.json index 74088e7fb373..fd897522492f 100644 --- a/spec-tests-version.json +++ b/spec-tests-version.json @@ -1,6 +1,6 @@ { "ethereumConsensusSpecsTests": { - "specVersion": "v1.7.0-alpha.2", + "specVersion": "v1.7.0-alpha.3", "specTestsRepoUrl": "https://github.com/ethereum/consensus-specs", "outputDirBase": "spec-tests", "testsToDownload": [ diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml index c8e2c05236fd..3d386420a59a 100644 --- a/specrefs/.ethspecify.yml +++ b/specrefs/.ethspecify.yml @@ -1,4 +1,4 @@ -version: v1.7.0-alpha.2 +version: v1.7.0-alpha.3 style: full specrefs: @@ -47,10 +47,16 @@ exceptions: - PAYLOAD_STATUS_FULL#gloas - PAYLOAD_STATUS_PENDING#gloas - PTC_TIMELINESS_INDEX#gloas + - DOMAIN_INCLUSION_LIST_COMMITTEE#heze containers: # gloas - ForkChoiceNode#gloas + - BeaconState#heze + - ExecutionPayloadBid#heze + - InclusionList#heze + - SignedExecutionPayloadBid#heze + - SignedInclusionList#heze dataclasses: # phase0 @@ -69,6 +75,10 @@ exceptions: - ExpectedWithdrawals#gloas - LatestMessage#gloas - Store#gloas + - GetInclusionListResponse#heze + - InclusionListStore#heze + - PayloadAttributes#heze + - Store#heze functions: # phase0 @@ -304,6 +314,27 @@ exceptions: - validate_merge_block#gloas - validate_on_attestation#gloas - verify_data_column_sidecar#gloas + - compute_fork_version#heze + - get_forkchoice_store#heze + - get_inclusion_list_bits#heze + - get_inclusion_list_committee#heze + - get_inclusion_list_committee_assignment#heze + - get_inclusion_list_signature#heze + - get_inclusion_list_store#heze + - get_inclusion_list_submission_due_ms#heze + - get_inclusion_list_transactions#heze + - get_proposer_inclusion_list_cutoff_ms#heze + - get_view_freeze_cutoff_ms#heze + - is_inclusion_list_bits_inclusive#heze + - is_payload_inclusion_list_satisfied#heze + - is_valid_inclusion_list_signature#heze + - on_execution_payload#heze + - on_inclusion_list#heze + - prepare_execution_payload#heze + - process_inclusion_list#heze + - record_payload_inclusion_list_satisfaction#heze + - should_extend_payload#heze + - upgrade_to_heze#heze custom_types: # phase0 @@ -321,3 +352,15 @@ exceptions: # gloas - PayloadStatus#gloas + + configs: + - HEZE_FORK_EPOCH#heze + - HEZE_FORK_VERSION#heze + - INCLUSION_LIST_SUBMISSION_DUE_BPS#heze + - MAX_BYTES_PER_INCLUSION_LIST#heze + - MAX_REQUEST_INCLUSION_LIST#heze + - PROPOSER_INCLUSION_LIST_CUTOFF_BPS#heze + - VIEW_FREEZE_CUTOFF_BPS#heze + + presets: + - INCLUSION_LIST_COMMITTEE_SIZE#heze diff --git a/specrefs/configs.yml b/specrefs/configs.yml index 4b7d8a573a9e..c767599dfc58 100644 --- a/specrefs/configs.yml +++ b/specrefs/configs.yml @@ -80,15 +80,6 @@ ATTESTATION_SUBNET_EXTRA_BITS = 0 -- name: ATTESTATION_SUBNET_PREFIX_BITS#phase0 - sources: - - file: packages/params/src/index.ts - search: export const ATTESTATION_SUBNET_PREFIX_BITS = - spec: | - - ATTESTATION_SUBNET_PREFIX_BITS: int = 6 - - - name: BALANCE_PER_ADDITIONAL_CUSTODY_GROUP#fulu sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -333,6 +324,20 @@ GLOAS_FORK_VERSION: Version = '0x07000000' +- name: HEZE_FORK_EPOCH#heze + sources: [] + spec: | + + HEZE_FORK_EPOCH: Epoch = 18446744073709551615 + + +- name: HEZE_FORK_VERSION#heze + sources: [] + spec: | + + HEZE_FORK_VERSION: Version = '0x08000000' + + - name: INACTIVITY_SCORE_BIAS#altair sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -351,6 +356,13 @@ INACTIVITY_SCORE_RECOVERY_RATE: uint64 = 16 +- name: INCLUSION_LIST_SUBMISSION_DUE_BPS#heze + sources: [] + spec: | + + INCLUSION_LIST_SUBMISSION_DUE_BPS: uint64 = 6667 + + - name: MAXIMUM_GOSSIP_CLOCK_DISPARITY#phase0 sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -378,6 +390,13 @@ MAX_BLOBS_PER_BLOCK_ELECTRA: uint64 = 9 +- name: MAX_BYTES_PER_INCLUSION_LIST#heze + sources: [] + spec: | + + MAX_BYTES_PER_INCLUSION_LIST = 8192 + + - name: MAX_PAYLOAD_SIZE#phase0 sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -405,24 +424,6 @@ MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: Gwei = 256000000000 -- name: MAX_REQUEST_BLOB_SIDECARS#deneb - sources: - - file: packages/config/src/chainConfig/configs/mainnet.ts - search: "MAX_REQUEST_BLOB_SIDECARS:" - spec: | - - MAX_REQUEST_BLOB_SIDECARS = 768 - - -- name: MAX_REQUEST_BLOB_SIDECARS_ELECTRA#electra - sources: - - file: packages/config/src/chainConfig/configs/mainnet.ts - search: "MAX_REQUEST_BLOB_SIDECARS_ELECTRA:" - spec: | - - MAX_REQUEST_BLOB_SIDECARS_ELECTRA = 1152 - - - name: MAX_REQUEST_BLOCKS#phase0 sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -441,13 +442,11 @@ MAX_REQUEST_BLOCKS_DENEB = 128 -- name: MAX_REQUEST_DATA_COLUMN_SIDECARS#fulu - sources: - - file: packages/config/src/chainConfig/configs/mainnet.ts - search: "MAX_REQUEST_DATA_COLUMN_SIDECARS:" +- name: MAX_REQUEST_INCLUSION_LIST#heze + sources: [] spec: | - - MAX_REQUEST_DATA_COLUMN_SIDECARS = 16384 + + MAX_REQUEST_INCLUSION_LIST = 16 - name: MAX_REQUEST_PAYLOADS#gloas @@ -495,15 +494,6 @@ MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS = 4096 -- name: MIN_EPOCHS_FOR_BLOCK_REQUESTS#phase0 - sources: - - file: packages/config/src/chainConfig/configs/mainnet.ts - search: "MIN_EPOCHS_FOR_BLOCK_REQUESTS:" - spec: | - - MIN_EPOCHS_FOR_BLOCK_REQUESTS = 33024 - - - name: MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS#fulu sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -576,6 +566,13 @@ PAYLOAD_ATTESTATION_DUE_BPS: uint64 = 7500 +- name: PROPOSER_INCLUSION_LIST_CUTOFF_BPS#heze + sources: [] + spec: | + + PROPOSER_INCLUSION_LIST_CUTOFF_BPS: uint64 = 9167 + + - name: PROPOSER_REORG_CUTOFF_BPS#phase0 sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -639,15 +636,6 @@ SECONDS_PER_ETH1_BLOCK: uint64 = 14 -- name: SECONDS_PER_SLOT#phase0 - sources: - - file: packages/config/src/chainConfig/configs/mainnet.ts - search: "SECONDS_PER_SLOT:" - spec: | - - SECONDS_PER_SLOT: uint64 = 12 - - - name: SHARD_COMMITTEE_PERIOD#phase0 sources: - file: packages/config/src/chainConfig/configs/mainnet.ts @@ -728,3 +716,10 @@ VALIDATOR_CUSTODY_REQUIREMENT = 8 + +- name: VIEW_FREEZE_CUTOFF_BPS#heze + sources: [] + spec: | + + VIEW_FREEZE_CUTOFF_BPS: uint64 = 7500 + diff --git a/specrefs/constants.yml b/specrefs/constants.yml index 2c642759d5f5..0d23d38d2be2 100644 --- a/specrefs/constants.yml +++ b/specrefs/constants.yml @@ -215,6 +215,13 @@ DOMAIN_DEPOSIT: DomainType = '0x03000000' +- name: DOMAIN_INCLUSION_LIST_COMMITTEE#heze + sources: [] + spec: | + + DOMAIN_INCLUSION_LIST_COMMITTEE: DomainType = '0x0E000000' + + - name: DOMAIN_PROPOSER_PREFERENCES#gloas sources: - file: packages/params/src/index.ts @@ -436,22 +443,22 @@ - name: PAYLOAD_STATUS_EMPTY#gloas sources: [] spec: | - - PAYLOAD_STATUS_EMPTY: PayloadStatus = 1 + + PAYLOAD_STATUS_EMPTY: PayloadStatus = 0 - name: PAYLOAD_STATUS_FULL#gloas sources: [] spec: | - - PAYLOAD_STATUS_FULL: PayloadStatus = 2 + + PAYLOAD_STATUS_FULL: PayloadStatus = 1 - name: PAYLOAD_STATUS_PENDING#gloas sources: [] spec: | - - PAYLOAD_STATUS_PENDING: PayloadStatus = 0 + + PAYLOAD_STATUS_PENDING: PayloadStatus = 2 - name: PRIMITIVE_ROOT_OF_UNITY#deneb diff --git a/specrefs/containers.yml b/specrefs/containers.yml index 168b0648c45d..3f7ce8b6f803 100644 --- a/specrefs/containers.yml +++ b/specrefs/containers.yml @@ -631,6 +631,59 @@ payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] +- name: BeaconState#heze + sources: [] + spec: | + + class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # [Modified in Heze:EIP7805] + latest_execution_payload_bid: ExecutionPayloadBid + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + deposit_requests_start_index: uint64 + deposit_balance_to_consume: Gwei + exit_balance_to_consume: Gwei + earliest_exit_epoch: Epoch + consolidation_balance_to_consume: Gwei + earliest_consolidation_epoch: Epoch + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] + pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] + proposer_lookahead: Vector[ValidatorIndex, (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH] + builders: List[Builder, BUILDER_REGISTRY_LIMIT] + next_withdrawal_builder_index: BuilderIndex + execution_payload_availability: Bitvector[SLOTS_PER_HISTORICAL_ROOT] + builder_pending_payments: Vector[BuilderPendingPayment, 2 * SLOTS_PER_EPOCH] + builder_pending_withdrawals: List[BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT] + latest_block_hash: Hash32 + payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + + - name: BlobIdentifier#deneb sources: - file: packages/types/src/deneb/sszTypes.ts @@ -948,6 +1001,26 @@ blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] +- name: ExecutionPayloadBid#heze + sources: [] + spec: | + + class ExecutionPayloadBid(Container): + parent_block_hash: Hash32 + parent_block_root: Root + block_hash: Hash32 + prev_randao: Bytes32 + fee_recipient: ExecutionAddress + gas_limit: uint64 + builder_index: BuilderIndex + slot: Slot + value: Gwei + execution_payment: Gwei + blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + # [New in Heze:EIP7805] + inclusion_list_bits: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE] + + - name: ExecutionPayloadEnvelope#gloas sources: - file: packages/types/src/gloas/sszTypes.ts @@ -1108,6 +1181,17 @@ state_summary_root: Root +- name: InclusionList#heze + sources: [] + spec: | + + class InclusionList(Container): + slot: Slot + validator_index: ValidatorIndex + inclusion_list_committee_root: Root + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + + - name: IndexedAttestation#phase0 sources: - file: packages/types/src/phase0/sszTypes.ts @@ -1511,6 +1595,16 @@ signature: BLSSignature +- name: SignedExecutionPayloadBid#heze + sources: [] + spec: | + + class SignedExecutionPayloadBid(Container): + # [Modified in Heze:EIP7805] + message: ExecutionPayloadBid + signature: BLSSignature + + - name: SignedExecutionPayloadEnvelope#gloas sources: - file: packages/types/src/gloas/sszTypes.ts @@ -1522,6 +1616,15 @@ signature: BLSSignature +- name: SignedInclusionList#heze + sources: [] + spec: | + + class SignedInclusionList(Container): + message: InclusionList + signature: BLSSignature + + - name: SignedProposerPreferences#gloas sources: - file: packages/types/src/gloas/sszTypes.ts diff --git a/specrefs/dataclasses.yml b/specrefs/dataclasses.yml index 0549fbe66d68..559fe34b21e3 100644 --- a/specrefs/dataclasses.yml +++ b/specrefs/dataclasses.yml @@ -68,6 +68,14 @@ processed_sweep_withdrawals_count: uint64 +- name: GetInclusionListResponse#heze + sources: [] + spec: | + + class GetInclusionListResponse(object): + inclusion_list_transactions: Sequence[Transaction] + + - name: GetPayloadResponse#bellatrix sources: - file: packages/beacon-node/src/execution/engine/types.ts @@ -140,6 +148,19 @@ execution_requests: Sequence[bytes] +- name: InclusionListStore#heze + sources: [] + spec: | + + class InclusionListStore(object): + inclusion_lists: DefaultDict[Tuple[Slot, Root], Set[InclusionList]] = field( + default_factory=lambda: defaultdict(set) + ) + equivocators: DefaultDict[Tuple[Slot, Root], Set[ValidatorIndex]] = field( + default_factory=lambda: defaultdict(set) + ) + + - name: LatestMessage#phase0 sources: [] spec: | @@ -289,6 +310,20 @@ parent_beacon_block_root: Root +- name: PayloadAttributes#heze + sources: [] + spec: | + + class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress + withdrawals: Sequence[Withdrawal] + parent_beacon_block_root: Root + # [New in Heze:EIP7805] + inclusion_list_transactions: Sequence[Transaction] + + - name: Store#phase0 sources: - file: packages/fork-choice/src/forkChoice/store.ts @@ -315,7 +350,7 @@ - name: Store#gloas sources: [] spec: | - + class Store(object): time: uint64 genesis_time: uint64 @@ -334,7 +369,41 @@ latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict) # [New in Gloas:EIP7732] - execution_payload_states: Dict[Root, BeaconState] = field(default_factory=dict) + payload_states: Dict[Root, BeaconState] = field(default_factory=dict) + # [New in Gloas:EIP7732] + payload_timeliness_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field(default_factory=dict) # [New in Gloas:EIP7732] - ptc_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field(default_factory=dict) + payload_data_availability_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field( + default_factory=dict + ) + + +- name: Store#heze + sources: [] + spec: | + + class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + unrealized_justified_checkpoint: Checkpoint + unrealized_finalized_checkpoint: Checkpoint + proposer_boost_root: Root + equivocating_indices: Set[ValidatorIndex] + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + block_timeliness: Dict[Root, Vector[boolean, NUM_BLOCK_TIMELINESS_DEADLINES]] = field( + default_factory=dict + ) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict) + payload_states: Dict[Root, BeaconState] = field(default_factory=dict) + payload_timeliness_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field(default_factory=dict) + payload_data_availability_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field( + default_factory=dict + ) + # [New in Heze:EIP7805] + payload_inclusion_list_satisfaction: Dict[Root, boolean] = field(default_factory=dict) diff --git a/specrefs/functions.yml b/specrefs/functions.yml index 7c53864f5fb6..f371d86c3c7d 100644 --- a/specrefs/functions.yml +++ b/specrefs/functions.yml @@ -440,6 +440,19 @@ return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) +- name: compute_attestation_subnet_prefix_bits#phase0 + sources: + - file: packages/params/src/index.ts + search: "export const ATTESTATION_SUBNET_PREFIX_BITS =" + spec: | + + def compute_attestation_subnet_prefix_bits() -> uint64: + """ + Return the number of NodeId bits to use when mapping to a subscribed subnet. + """ + return uint64(ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS) + + - name: compute_balance_weighted_acceptance#gloas sources: [] spec: | @@ -845,6 +858,33 @@ return GENESIS_FORK_VERSION +- name: compute_fork_version#heze + sources: [] + spec: | + + def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= HEZE_FORK_EPOCH: + return HEZE_FORK_VERSION + if epoch >= GLOAS_FORK_EPOCH: + return GLOAS_FORK_VERSION + if epoch >= FULU_FORK_EPOCH: + return FULU_FORK_VERSION + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION + + - name: compute_matrix#fulu sources: - file: packages/beacon-node/src/util/dataColumns.ts @@ -873,6 +913,59 @@ return matrix +- name: compute_max_request_blob_sidecars#deneb + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "MAX_REQUEST_BLOB_SIDECARS:" + spec: | + + def compute_max_request_blob_sidecars() -> uint64: + """ + Return the maximum number of blob sidecars in a single request. + """ + return uint64(MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK) + + +- name: compute_max_request_blob_sidecars#electra + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "MAX_REQUEST_BLOB_SIDECARS_ELECTRA:" + spec: | + + def compute_max_request_blob_sidecars() -> uint64: + """ + Return the maximum number of blob sidecars in a single request. + """ + # [Modified in Electra:EIP7691] + return uint64(MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA) + + +- name: compute_max_request_data_column_sidecars#fulu + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "MAX_REQUEST_DATA_COLUMN_SIDECARS:" + spec: | + + def compute_max_request_data_column_sidecars() -> uint64: + """ + Return the maximum number of data column sidecars in a single request. + """ + return uint64(MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS) + + +- name: compute_min_epochs_for_block_requests#phase0 + sources: + - file: packages/config/src/chainConfig/configs/mainnet.ts + search: "MIN_EPOCHS_FOR_BLOCK_REQUESTS:" + spec: | + + def compute_min_epochs_for_block_requests() -> uint64: + """ + Return the minimum epoch range over which a node must serve blocks. + """ + return uint64(MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2) + + - name: compute_new_state_root#phase0 sources: - file: packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts @@ -1206,16 +1299,17 @@ - file: packages/beacon-node/src/network/subnets/util.ts search: export function computeSubscribedSubnetByIndex( spec: | - + def compute_subscribed_subnet(node_id: NodeID, epoch: Epoch, index: int) -> SubnetID: - node_id_prefix = node_id >> (NODE_ID_BITS - ATTESTATION_SUBNET_PREFIX_BITS) + prefix_bits = int(compute_attestation_subnet_prefix_bits()) + node_id_prefix = node_id >> (NODE_ID_BITS - prefix_bits) node_offset = node_id % EPOCHS_PER_SUBNET_SUBSCRIPTION permutation_seed = hash( uint_to_bytes(uint64((epoch + node_offset) // EPOCHS_PER_SUBNET_SUBSCRIPTION)) ) permutated_prefix = compute_shuffled_index( node_id_prefix, - 1 << ATTESTATION_SUBNET_PREFIX_BITS, + 1 << prefix_bits, permutation_seed, ) return SubnetID((permutated_prefix + index) % ATTESTATION_SUBNET_COUNT) @@ -1256,10 +1350,10 @@ - file: packages/state-transition/src/util/slot.ts search: export function computeTimeAtSlot( spec: | - + def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) + return uint64(state.genesis_time + slots_since_genesis * SLOT_DURATION_MS // 1000) - name: compute_weak_subjectivity_period#phase0 @@ -3193,7 +3287,7 @@ - name: get_forkchoice_store#phase0 sources: [] spec: | - + def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: assert anchor_block.state_root == hash_tree_root(anchor_state) anchor_root = hash_tree_root(anchor_block) @@ -3202,7 +3296,7 @@ finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) proposer_boost_root = Root() return Store( - time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), + time=uint64(anchor_state.genesis_time + SLOT_DURATION_MS * anchor_state.slot // 1000), genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, @@ -3220,7 +3314,7 @@ - name: get_forkchoice_store#gloas sources: [] spec: | - + def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: assert anchor_block.state_root == hash_tree_root(anchor_state) anchor_root = hash_tree_root(anchor_block) @@ -3229,7 +3323,7 @@ finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) proposer_boost_root = Root() return Store( - time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), + time=uint64(anchor_state.genesis_time + SLOT_DURATION_MS * anchor_state.slot // 1000), genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, @@ -3239,12 +3333,57 @@ equivocating_indices=set(), blocks={anchor_root: copy(anchor_block)}, block_states={anchor_root: copy(anchor_state)}, + # [New in Gloas:EIP7732] + block_timeliness={anchor_root: [True, True]}, checkpoint_states={justified_checkpoint: copy(anchor_state)}, unrealized_justifications={anchor_root: justified_checkpoint}, # [New in Gloas:EIP7732] - execution_payload_states={anchor_root: copy(anchor_state)}, - ptc_vote={anchor_root: Vector[boolean, PTC_SIZE]()}, + payload_states={anchor_root: copy(anchor_state)}, + # [New in Gloas:EIP7732] + payload_timeliness_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + # [New in Gloas:EIP7732] + payload_data_availability_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + ) + + +- name: get_forkchoice_store#heze + sources: [] + spec: | + + def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + SLOT_DURATION_MS * anchor_state.slot // 1000), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + unrealized_justified_checkpoint=justified_checkpoint, + unrealized_finalized_checkpoint=finalized_checkpoint, + proposer_boost_root=proposer_boost_root, + equivocating_indices=set(), + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, block_timeliness={anchor_root: [True, True]}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + unrealized_justifications={anchor_root: justified_checkpoint}, + payload_states={anchor_root: copy(anchor_state)}, + payload_timeliness_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + payload_data_availability_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + # [New in Heze:EIP7805] + payload_inclusion_list_satisfaction={anchor_root: True}, ) @@ -3423,6 +3562,133 @@ return rewards, penalties +- name: get_inclusion_list_bits#heze + sources: [] + spec: | + + def get_inclusion_list_bits( + store: InclusionListStore, state: BeaconState, slot: Slot + ) -> Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]: + """ + Return a ``Bitvector`` over inclusion list committee indices with bits set + for valid, non-equivocating inclusion list submissions for the given ``slot``. + """ + inclusion_list_committee = get_inclusion_list_committee(state, slot) + inclusion_list_committee_root = hash_tree_root(inclusion_list_committee) + key = (slot, inclusion_list_committee_root) + + validator_indices = [ + inclusion_list.validator_index + for inclusion_list in store.inclusion_lists[key] + if inclusion_list.validator_index not in store.equivocators[key] + ] + + return Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]( + validator_index in validator_indices for validator_index in inclusion_list_committee + ) + + +- name: get_inclusion_list_committee#heze + sources: [] + spec: | + + def get_inclusion_list_committee( + state: BeaconState, slot: Slot + ) -> Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]: + """ + Get the inclusion list committee for the given ``slot``. + """ + epoch = compute_epoch_at_slot(slot) + indices: List[ValidatorIndex] = [] + # Concatenate all committees for this slot in order + committees_per_slot = get_committee_count_per_slot(state, epoch) + for i in range(committees_per_slot): + committee = get_beacon_committee(state, slot, CommitteeIndex(i)) + indices.extend(committee) + return Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]( + [indices[i % len(indices)] for i in range(INCLUSION_LIST_COMMITTEE_SIZE)] + ) + + +- name: get_inclusion_list_committee_assignment#heze + sources: [] + spec: | + + def get_inclusion_list_committee_assignment( + state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex + ) -> Optional[Slot]: + """ + Returns the slot during the requested epoch in which the validator with + index ``validator_index`` is a member of the inclusion list committee. + Returns None if no assignment is found. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + if validator_index in get_inclusion_list_committee(state, Slot(slot)): + return Slot(slot) + return None + + +- name: get_inclusion_list_signature#heze + sources: [] + spec: | + + def get_inclusion_list_signature( + state: BeaconState, inclusion_list: InclusionList, privkey: int + ) -> BLSSignature: + domain = get_domain( + state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(inclusion_list.slot) + ) + signing_root = compute_signing_root(inclusion_list, domain) + return bls.Sign(privkey, signing_root) + + +- name: get_inclusion_list_store#heze + sources: [] + spec: | + + def get_inclusion_list_store() -> InclusionListStore: + # `cached_or_new_inclusion_list_store` is implementation and context dependent. + # It returns the cached `InclusionListStore`; if none exists, + # it initializes a new instance, caches it and returns it. + inclusion_list_store = cached_or_new_inclusion_list_store() + + return inclusion_list_store + + +- name: get_inclusion_list_submission_due_ms#heze + sources: [] + spec: | + + def get_inclusion_list_submission_due_ms(epoch: Epoch) -> uint64: + return get_slot_component_duration_ms(INCLUSION_LIST_SUBMISSION_DUE_BPS) + + +- name: get_inclusion_list_transactions#heze + sources: [] + spec: | + + def get_inclusion_list_transactions( + store: InclusionListStore, state: BeaconState, slot: Slot + ) -> Sequence[Transaction]: + inclusion_list_committee = get_inclusion_list_committee(state, slot) + inclusion_list_committee_root = hash_tree_root(inclusion_list_committee) + key = (slot, inclusion_list_committee_root) + + inclusion_list_transactions = [ + transaction + for inclusion_list in store.inclusion_lists[key] + if inclusion_list.validator_index not in store.equivocators[key] + for transaction in inclusion_list.transactions + ] + + # Deduplicate inclusion list transactions. Order does not need to be preserved. + return list(set(inclusion_list_transactions)) + + - name: get_index_for_new_builder#gloas sources: [] spec: | @@ -3754,13 +4020,13 @@ - name: get_node_children#gloas sources: [] spec: | - + def get_node_children( store: Store, blocks: Dict[Root, BeaconBlock], node: ForkChoiceNode ) -> Sequence[ForkChoiceNode]: if node.payload_status == PAYLOAD_STATUS_PENDING: children = [ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_EMPTY)] - if node.root in store.execution_payload_states: + if node.root in store.payload_states: children.append(ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_FULL)) return children else: @@ -4011,6 +4277,14 @@ return head_root +- name: get_proposer_inclusion_list_cutoff_ms#heze + sources: [] + spec: | + + def get_proposer_inclusion_list_cutoff_ms(epoch: Epoch) -> uint64: + return get_slot_component_duration_ms(PROPOSER_INCLUSION_LIST_CUTOFF_BPS) + + - name: get_proposer_preferences_signature#gloas sources: [] spec: | @@ -4173,9 +4447,9 @@ - file: packages/state-transition/src/util/slot.ts search: export function getSlotsSinceGenesis( spec: | - + def get_slots_since_genesis(store: Store) -> int: - return (store.time - store.genesis_time) // SECONDS_PER_SLOT + return (store.time - store.genesis_time) * 1000 // SLOT_DURATION_MS - name: get_source_deltas#phase0 @@ -4605,6 +4879,14 @@ return withdrawals, withdrawal_index, processed_count +- name: get_view_freeze_cutoff_ms#heze + sources: [] + spec: | + + def get_view_freeze_cutoff_ms(epoch: Epoch) -> uint64: + return get_slot_component_duration_ms(VIEW_FREEZE_CUTOFF_BPS) + + - name: get_voting_source#phase0 sources: [] spec: | @@ -5413,6 +5695,30 @@ return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY +- name: is_inclusion_list_bits_inclusive#heze + sources: [] + spec: | + + def is_inclusion_list_bits_inclusive( + store: InclusionListStore, + state: BeaconState, + slot: Slot, + inclusion_list_bits: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE], + ) -> bool: + """ + Return ``True`` if and only if ``inclusion_list_bits`` is a superset of + the locally observed inclusion list bits for the given ``slot``. + """ + local_inclusion_list_bits = get_inclusion_list_bits(store, state, slot) + + return all( + inclusion_bit or not local_inclusion_bit + for inclusion_bit, local_inclusion_bit in zip( + inclusion_list_bits, local_inclusion_list_bits + ) + ) + + - name: is_merge_transition_block#bellatrix sources: [] spec: | @@ -5547,24 +5853,89 @@ ) +- name: is_payload_data_available#gloas + sources: + - file: packages/fork-choice/src/protoArray/protoArray.ts + search: "isPayloadDataAvailable(blockRoot: RootHex)" + spec: | + + def is_payload_data_available(store: Store, root: Root) -> bool: + """ + Return whether the blob data for the beacon block with root ``root`` + was voted as present by the PTC, and was locally determined to be available. + """ + # The beacon block root must be known + assert root in store.payload_data_availability_vote + + # If the payload is not locally available, the blob data + # is not considered available regardless of the PTC vote + if root not in store.payload_states: + return False + + return sum(store.payload_data_availability_vote[root]) > DATA_AVAILABILITY_TIMELY_THRESHOLD + + +- name: is_payload_inclusion_list_satisfied#heze + sources: [] + spec: | + + def is_payload_inclusion_list_satisfied(store: Store, root: Root) -> bool: + """ + Return whether the execution payload for the beacon block with root ``root`` + satisfied the inclusion list constraints, and was locally determined to be available. + """ + # The beacon block root must be known + assert root in store.payload_inclusion_list_satisfaction + + # If the payload is not locally available, the payload + # is not considered to satisfy the inclusion list constraints + if root not in store.payload_states: + return False + + return store.payload_inclusion_list_satisfaction[root] + + - name: is_payload_timely#gloas sources: [] spec: | - + def is_payload_timely(store: Store, root: Root) -> bool: """ Return whether the execution payload for the beacon block with root ``root`` was voted as present by the PTC, and was locally determined to be available. """ # The beacon block root must be known - assert root in store.ptc_vote + assert root in store.payload_timeliness_vote # If the payload is not locally available, the payload # is not considered available regardless of the PTC vote - if root not in store.execution_payload_states: + if root not in store.payload_states: return False - return sum(store.ptc_vote[root]) > PAYLOAD_TIMELY_THRESHOLD + return sum(store.payload_timeliness_vote[root]) > PAYLOAD_TIMELY_THRESHOLD + + +- name: is_pending_validator#gloas + sources: + - file: packages/state-transition/src/util/gloas.ts + search: "export function isPendingValidator(" + spec: | + + def is_pending_validator(state: BeaconState, pubkey: BLSPubkey) -> bool: + """ + Check if a pending deposit with a valid signature is in the queue for the given pubkey. + """ + for pending_deposit in state.pending_deposits: + if pending_deposit.pubkey != pubkey: + continue + if is_valid_deposit_signature( + pending_deposit.pubkey, + pending_deposit.withdrawal_credentials, + pending_deposit.amount, + pending_deposit.signature, + ): + return True + return False - name: is_proposer#phase0 @@ -5653,11 +6024,11 @@ - name: is_supporting_vote#gloas sources: [] spec: | - + def is_supporting_vote(store: Store, node: ForkChoiceNode, message: LatestMessage) -> bool: """ - Returns whether a vote for ``message.root`` supports the chain containing the beacon block ``node.root`` with the - payload contents indicated by ``node.payload_status`` as head during slot ``node.slot``. + Returns whether the vote ``message`` supports the chain containing the + forkchoice node ``node``. """ block = store.blocks[node.root] if node.root == message.root: @@ -5737,6 +6108,24 @@ return True +- name: is_valid_inclusion_list_signature#heze + sources: [] + spec: | + + def is_valid_inclusion_list_signature( + state: BeaconState, signed_inclusion_list: SignedInclusionList + ) -> bool: + """ + Check if ``signed_inclusion_list`` has a valid signature. + """ + message = signed_inclusion_list.message + index = message.validator_index + pubkey = state.validators[index].pubkey + domain = get_domain(state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(message.slot)) + signing_root = compute_signing_root(message, domain) + return bls.Verify(pubkey, signing_root, signed_inclusion_list.signature) + + - name: is_valid_indexed_attestation#phase0 sources: - file: packages/state-transition/src/block/isValidIndexedAttestation.ts @@ -6404,7 +6793,7 @@ - name: on_block#gloas sources: [] spec: | - + def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: """ Run ``on_block`` upon receiving a new block. @@ -6419,8 +6808,8 @@ parent_bid = parent_block.body.signed_execution_payload_bid.message # Make a copy of the state to avoid mutability issues if is_parent_node_full(store, block): - assert block.parent_root in store.execution_payload_states - state = copy(store.execution_payload_states[block.parent_root]) + assert block.parent_root in store.payload_states + state = copy(store.payload_states[block.parent_root]) else: assert bid.parent_block_hash == parent_bid.parent_block_hash state = copy(store.block_states[block.parent_root]) @@ -6449,7 +6838,8 @@ # Add new state for this block to the store store.block_states[block_root] = state # Add a new PTC voting for this block to the store - store.ptc_vote[block_root] = [False] * PTC_SIZE + store.payload_timeliness_vote[block_root] = [False] * PTC_SIZE + store.payload_data_availability_vote[block_root] = [False] * PTC_SIZE # Notify the store about the payload_attestations in the block notify_ptc_messages(store, state, block.body.payload_attestations) @@ -6467,7 +6857,7 @@ - name: on_execution_payload#gloas sources: [] spec: | - + def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None: """ Run ``on_execution_payload`` upon receiving a new execution payload. @@ -6487,13 +6877,65 @@ process_execution_payload(state, signed_envelope, EXECUTION_ENGINE) # Add new state for this payload to the store - store.execution_payload_states[envelope.beacon_block_root] = state + store.payload_states[envelope.beacon_block_root] = state + + +- name: on_execution_payload#heze + sources: [] + spec: | + + def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None: + """ + Run ``on_execution_payload`` upon receiving a new execution payload. + """ + envelope = signed_envelope.message + # The corresponding beacon block root needs to be known + assert envelope.beacon_block_root in store.block_states + + # Check if blob data is available + # If not, this payload MAY be queued and subsequently considered when blob data becomes available + assert is_data_available(envelope.beacon_block_root) + + # Make a copy of the state to avoid mutability issues + state = copy(store.block_states[envelope.beacon_block_root]) + + # Process the execution payload + process_execution_payload(state, signed_envelope, EXECUTION_ENGINE) + + # [New in Heze:EIP7805] + # Check if this payload satisfies the inclusion list constraints + # If not, add this payload to the store as inclusion list constraints unsatisfied + record_payload_inclusion_list_satisfaction( + store, state, envelope.beacon_block_root, envelope.payload, EXECUTION_ENGINE + ) + + # Add new state for this payload to the store + store.payload_states[envelope.beacon_block_root] = state + + +- name: on_inclusion_list#heze + sources: [] + spec: | + + def on_inclusion_list(store: Store, signed_inclusion_list: SignedInclusionList) -> None: + """ + Run ``on_inclusion_list`` upon receiving a new inclusion list. + """ + inclusion_list = signed_inclusion_list.message + + seconds_since_genesis = store.time - store.genesis_time + time_into_slot_ms = seconds_to_milliseconds(seconds_since_genesis) % SLOT_DURATION_MS + epoch = get_current_store_epoch(store) + view_freeze_cutoff_ms = get_view_freeze_cutoff_ms(epoch) + is_before_view_freeze_cutoff = time_into_slot_ms < view_freeze_cutoff_ms + + process_inclusion_list(get_inclusion_list_store(), inclusion_list, is_before_view_freeze_cutoff) - name: on_payload_attestation_message#gloas sources: [] spec: | - + def on_payload_attestation_message( store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool = False ) -> None: @@ -6525,10 +6967,12 @@ signature=ptc_message.signature, ), ) - # Update the ptc vote for the block + # Update the votes for the block ptc_index = ptc.index(ptc_message.validator_index) - ptc_vote = store.ptc_vote[data.beacon_block_root] - ptc_vote[ptc_index] = data.payload_present + payload_timeliness_vote = store.payload_timeliness_vote[data.beacon_block_root] + payload_timeliness_vote[ptc_index] = data.payload_present + payload_data_availability_vote = store.payload_data_availability_vote[data.beacon_block_root] + payload_data_availability_vote[ptc_index] = data.blob_data_available - name: on_tick#phase0 @@ -6537,13 +6981,15 @@ search: '^\s+private onTick\(' regex: true spec: | - + def on_tick(store: Store, time: uint64) -> None: # If the ``store.time`` falls behind, while loop catches up slot by slot # to ensure that every previous slot is processed with ``on_tick_per_slot`` - tick_slot = (time - store.genesis_time) // SECONDS_PER_SLOT + tick_slot = (time - store.genesis_time) * 1000 // SLOT_DURATION_MS while get_current_slot(store) < tick_slot: - previous_time = store.genesis_time + (get_current_slot(store) + 1) * SECONDS_PER_SLOT + previous_time = ( + store.genesis_time + (get_current_slot(store) + 1) * SLOT_DURATION_MS // 1000 + ) on_tick_per_slot(store, previous_time) on_tick_per_slot(store, time) @@ -6805,6 +7251,37 @@ ) +- name: prepare_execution_payload#heze + sources: [] + spec: | + + def prepare_execution_payload( + state: BeaconState, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine, + ) -> Optional[PayloadId]: + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_time_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + withdrawals=get_expected_withdrawals(state).withdrawals, + parent_beacon_block_root=hash_tree_root(state.latest_block_header), + # [New in Heze:EIP7805] + inclusion_list_transactions=get_inclusion_list_transactions( + get_inclusion_list_store(), state, Slot(state.slot - 1) + ), + ) + return execution_engine.notify_forkchoice_updated( + head_block_hash=state.latest_block_hash, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) + + - name: process_attestation#phase0 sources: - file: packages/state-transition/src/block/processAttestationPhase0.ts @@ -7456,7 +7933,7 @@ - file: packages/state-transition/src/block/processDepositRequest.ts search: export function processDepositRequest( spec: | - + def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None: # [New in Gloas:EIP7732] builder_pubkeys = [b.pubkey for b in state.builders] @@ -7467,8 +7944,11 @@ # already exists with this pubkey, apply the deposit to their balance is_builder = deposit_request.pubkey in builder_pubkeys is_validator = deposit_request.pubkey in validator_pubkeys - is_builder_prefix = is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) - if is_builder or (is_builder_prefix and not is_validator): + if is_builder or ( + is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) + and not is_validator + and not is_pending_validator(state, deposit_request.pubkey) + ): # Apply builder deposits immediately apply_deposit_for_builder( state, @@ -8180,6 +8660,35 @@ ) +- name: process_inclusion_list#heze + sources: [] + spec: | + + def process_inclusion_list( + store: InclusionListStore, inclusion_list: InclusionList, is_before_view_freeze_cutoff: bool + ) -> None: + key = (inclusion_list.slot, inclusion_list.inclusion_list_committee_root) + + # Ignore `inclusion_list` from equivocators. + if inclusion_list.validator_index in store.equivocators[key]: + return + + for stored_inclusion_list in store.inclusion_lists[key]: + if stored_inclusion_list.validator_index != inclusion_list.validator_index: + continue + + if stored_inclusion_list != inclusion_list: + store.equivocators[key].add(inclusion_list.validator_index) + store.inclusion_lists[key].remove(stored_inclusion_list) + + # Whether it was an equivocation or not, we have processed this `inclusion_list`. + return + + # Only store `inclusion_list` if it arrived before the view freeze cutoff. + if is_before_view_freeze_cutoff: + store.inclusion_lists[key].add(inclusion_list) + + - name: process_justification_and_finalization#phase0 sources: - file: packages/state-transition/src/epoch/processJustificationAndFinalization.ts @@ -9485,6 +9994,26 @@ ] +- name: record_payload_inclusion_list_satisfaction#heze + sources: [] + spec: | + + def record_payload_inclusion_list_satisfaction( + store: Store, + state: BeaconState, + root: Root, + payload: ExecutionPayload, + execution_engine: ExecutionEngine, + ) -> None: + inclusion_list_transactions = get_inclusion_list_transactions( + get_inclusion_list_store(), state, Slot(state.slot - 1) + ) + is_inclusion_list_satisfied = execution_engine.is_inclusion_list_satisfied( + payload, inclusion_list_transactions + ) + store.payload_inclusion_list_satisfaction[root] = is_inclusion_list_satisfied + + - name: recover_matrix#fulu sources: - file: packages/beacon-node/src/util/blobs.ts @@ -9593,11 +10122,29 @@ - name: should_extend_payload#gloas sources: [] spec: | - + def should_extend_payload(store: Store, root: Root) -> bool: proposer_root = store.proposer_boost_root return ( - is_payload_timely(store, root) + (is_payload_timely(store, root) and is_payload_data_available(store, root)) + or proposer_root == Root() + or store.blocks[proposer_root].parent_root != root + or is_parent_node_full(store, store.blocks[proposer_root]) + ) + + +- name: should_extend_payload#heze + sources: [] + spec: | + + def should_extend_payload(store: Store, root: Root) -> bool: + # [New in Heze:EIP7805] + if not is_payload_inclusion_list_satisfied(store, root): + return False + + proposer_root = store.proposer_boost_root + return ( + (is_payload_timely(store, root) and is_payload_data_available(store, root)) or proposer_root == Root() or store.blocks[proposer_root].parent_root != root or is_parent_node_full(store, store.blocks[proposer_root]) @@ -9945,7 +10492,7 @@ - name: update_latest_messages#gloas sources: [] spec: | - + def update_latest_messages( store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation ) -> None: @@ -9957,8 +10504,11 @@ ] for i in non_equivocating_attesting_indices: if i not in store.latest_messages or slot > store.latest_messages[i].slot: + # [Modified in Gloas:EIP7732] store.latest_messages[i] = LatestMessage( - slot=slot, root=beacon_block_root, payload_present=payload_present + slot=slot, + root=beacon_block_root, + payload_present=payload_present, ) @@ -10906,6 +11456,85 @@ return post +- name: upgrade_to_heze#heze + sources: [] + spec: | + + def upgrade_to_heze(pre: gloas.BeaconState) -> BeaconState: + epoch = gloas.get_current_epoch(pre) + latest_execution_payload_bid = ExecutionPayloadBid( + parent_block_hash=pre.latest_execution_payload_bid.parent_block_hash, + parent_block_root=pre.latest_execution_payload_bid.parent_block_root, + block_hash=pre.latest_execution_payload_bid.block_hash, + prev_randao=pre.latest_execution_payload_bid.prev_randao, + fee_recipient=pre.latest_execution_payload_bid.fee_recipient, + gas_limit=pre.latest_execution_payload_bid.gas_limit, + builder_index=pre.latest_execution_payload_bid.builder_index, + slot=pre.latest_execution_payload_bid.slot, + value=pre.latest_execution_payload_bid.value, + execution_payment=pre.latest_execution_payload_bid.execution_payment, + blob_kzg_commitments=pre.latest_execution_payload_bid.blob_kzg_commitments, + # [New in Heze:EIP7805] + inclusion_list_bits=Bitvector[INCLUSION_LIST_COMMITTEE_SIZE](), + ) + + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in Heze:EIP7805] + current_version=HEZE_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # [Modified in Heze:EIP7805] + latest_execution_payload_bid=latest_execution_payload_bid, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + deposit_requests_start_index=pre.deposit_requests_start_index, + deposit_balance_to_consume=pre.deposit_balance_to_consume, + exit_balance_to_consume=pre.exit_balance_to_consume, + earliest_exit_epoch=pre.earliest_exit_epoch, + consolidation_balance_to_consume=pre.consolidation_balance_to_consume, + earliest_consolidation_epoch=pre.earliest_consolidation_epoch, + pending_deposits=pre.pending_deposits, + pending_partial_withdrawals=pre.pending_partial_withdrawals, + pending_consolidations=pre.pending_consolidations, + proposer_lookahead=pre.proposer_lookahead, + builders=pre.builders, + next_withdrawal_builder_index=pre.next_withdrawal_builder_index, + execution_payload_availability=pre.execution_payload_availability, + builder_pending_payments=pre.builder_pending_payments, + builder_pending_withdrawals=pre.builder_pending_withdrawals, + latest_block_hash=pre.latest_block_hash, + payload_expected_withdrawals=pre.payload_expected_withdrawals, + ) + + return post + + - name: validate_light_client_update#altair sources: - file: packages/light-client/src/spec/validateLightClientUpdate.ts @@ -11089,7 +11718,7 @@ - name: validate_on_attestation#gloas sources: [] spec: | - + def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: target = attestation.data.target @@ -11117,6 +11746,10 @@ assert attestation.data.index in [0, 1] if block_slot == attestation.data.slot: assert attestation.data.index == 0 + # [New in Gloas:EIP7732] + # If attesting for a full node, the payload must be known + if attestation.data.index == 1: + assert attestation.data.beacon_block_root in store.payload_states # LMD vote must be consistent with FFG vote target assert target.root == get_checkpoint_block( diff --git a/specrefs/presets.yml b/specrefs/presets.yml index d936edd1bfbb..d090b4239cc7 100644 --- a/specrefs/presets.yml +++ b/specrefs/presets.yml @@ -178,6 +178,13 @@ INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: uint64 = 16777216 +- name: INCLUSION_LIST_COMMITTEE_SIZE#heze + sources: [] + spec: | + + INCLUSION_LIST_COMMITTEE_SIZE: uint64 = 16 + + - name: KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH#fulu sources: - file: packages/params/src/presets/mainnet.ts