diff --git a/packages/api/package.json b/packages/api/package.json index 618a6bf890eb..8e61ec2bf9d2 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -75,7 +75,7 @@ }, "dependencies": { "@chainsafe/persistent-merkle-tree": "^1.2.1", - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/config": "workspace:^", "@lodestar/params": "workspace:^", "@lodestar/types": "workspace:^", diff --git a/packages/api/src/beacon/routes/validator.ts b/packages/api/src/beacon/routes/validator.ts index 6382a452f1c3..c7922108c322 100644 --- a/packages/api/src/beacon/routes/validator.ts +++ b/packages/api/src/beacon/routes/validator.ts @@ -780,7 +780,8 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions // <- tranformation ); }, - deserialize(data, {executionPayloadBlinded, version}) { + deserialize(data, meta) { + const {executionPayloadBlinded, version} = meta as ProduceBlockV3Meta; return executionPayloadBlinded ? getPostBellatrixForkTypes(version).BlindedBeaconBlock.deserialize(data) : isForkPostDeneb(version) diff --git a/packages/api/src/utils/types.ts b/packages/api/src/utils/types.ts index aef18b47d940..78588373226e 100644 --- a/packages/api/src/utils/types.ts +++ b/packages/api/src/utils/types.ts @@ -118,7 +118,10 @@ export type ResponseDataCodec = { toJson: (data: T, meta: M) => unknown; // server fromJson: (data: unknown, meta: M) => T; // client serialize: (data: T, meta: M) => Uint8Array; // server - deserialize: (data: Uint8Array, meta: M) => T; // client + // `meta` is typed as `any` so SSZ `Type.deserialize(data, opts?)` satisfies the shape. + // Route-specific deserializers (e.g. `WithVersion`) cast to their expected meta internally. + // biome-ignore lint/suspicious/noExplicitAny: contravariant workaround for ssz Type.deserialize(opts?) + deserialize: (data: Uint8Array, meta: any) => T; // client }; export type ResponseMetadataCodec = { diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 7e0bcd6c1c15..2b7df08b80d5 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -118,7 +118,7 @@ "@chainsafe/prometheus-gc-stats": "^1.0.0", "@chainsafe/pubkey-index-map": "^3.0.0", "@chainsafe/snappy-wasm": "^0.5.0", - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@chainsafe/threads": "^1.11.3", "@crate-crypto/node-eth-kzg": "0.9.1", "@fastify/bearer-auth": "^10.0.1", diff --git a/packages/beacon-node/src/chain/validation/payloadAttestationMessage.ts b/packages/beacon-node/src/chain/validation/payloadAttestationMessage.ts index 2877c3041bd6..c44751b49074 100644 --- a/packages/beacon-node/src/chain/validation/payloadAttestationMessage.ts +++ b/packages/beacon-node/src/chain/validation/payloadAttestationMessage.ts @@ -18,7 +18,8 @@ export async function validateApiPayloadAttestationMessage( chain: IBeaconChain, payloadAttestationMessage: gloas.PayloadAttestationMessage ): Promise { - return validatePayloadAttestationMessage(chain, payloadAttestationMessage); + const prioritizeBls = true; + return validatePayloadAttestationMessage(chain, payloadAttestationMessage, prioritizeBls); } export async function validateGossipPayloadAttestationMessage( @@ -30,7 +31,8 @@ export async function validateGossipPayloadAttestationMessage( async function validatePayloadAttestationMessage( chain: IBeaconChain, - payloadAttestationMessage: gloas.PayloadAttestationMessage + payloadAttestationMessage: gloas.PayloadAttestationMessage, + prioritizeBls = false ): Promise { const {data, validatorIndex} = payloadAttestationMessage; const epoch = computeEpochAtSlot(data.slot); @@ -102,7 +104,7 @@ async function validatePayloadAttestationMessage( payloadAttestationMessage.signature ); - if (!(await chain.bls.verifySignatureSets([signatureSet]))) { + if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) { throw new PayloadAttestationError(GossipAction.REJECT, { code: PayloadAttestationErrorCode.INVALID_SIGNATURE, }); diff --git a/packages/beacon-node/test/spec/presets/epoch_processing.test.ts b/packages/beacon-node/test/spec/presets/epoch_processing.test.ts index 67a5f226957d..ec90d3ff75a0 100644 --- a/packages/beacon-node/test/spec/presets/epoch_processing.test.ts +++ b/packages/beacon-node/test/spec/presets/epoch_processing.test.ts @@ -7,6 +7,7 @@ import { CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateFulu, + CachedBeaconStateGloas, EpochTransitionCache, beforeProcessEpoch, } from "@lodestar/state-transition"; @@ -51,6 +52,9 @@ const epochTransitionFns: Record = { const fork = state.config.getForkSeq(state.slot); epochFns.processProposerLookahead(fork, state as CachedBeaconStateFulu, epochTransitionCache); }, + ptc_window: (state, epochTransitionCache) => { + epochFns.processPtcWindow(state as CachedBeaconStateGloas, epochTransitionCache); + }, builder_pending_payments: epochFns.processBuilderPendingPayments as EpochTransitionFn, }; 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 598f323be2f9..4795a3979714 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -588,11 +588,11 @@ const forkChoiceTest = name.includes("voting_source_beyond_two_epoch") || name.includes("justified_update_always_if_better") || name.includes("justified_update_not_realized_finality") || - // TODO GLOAS: Requires should_apply_proposer_boost (gloas/fork-choice.md#new-should_apply_proposer_boost) - // which conditionally suppresses proposer boost when the parent is weak and from the previous slot. - // Pre-Gloas forks always apply boost; Gloas adds is_head_weak + equivocation checks. + // TODO GLOAS: These tests will be unskipped by https://github.com/ChainSafe/lodestar/pull/9233 (name.includes("gloas") && - name.includes("include_votes_another_empty_chain_with_enough_ffg_votes_previous_epoch")) || + (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_without_enough_ffg_votes_current_epoch"))) || // TODO GLOAS: These two tests are affected by the wrong proposer boost cutoff time from the // consensus-specs and thus have wrong expectation of proposer boost. Our implementation // should pass these two tests after https://github.com/ethereum/consensus-specs/pull/5095 diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index cd2e27aa1c17..583a84006bb5 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -73,12 +73,12 @@ export const defaultSkipOpts: SkipOpts = { /^fulu\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^.+\/light_client\/data_collection\/.*/, /^gloas\/ssz_static\/ForkChoiceNode.*$/, + // Ignore the partial data column container additions for now. Unskip them when + // cell level DAS is ready + /^fulu\/ssz_static\/PartialDataColumn(Header|PartsMetadata|Sidecar)\/.*$/, + /^gloas\/ssz_static\/PartialDataColumn(Header|PartsMetadata|Sidecar)\/.*$/, ], - skippedTests: [ - // TODO GLOAS: broken in v1.7.0-alpha.3 due to missing voluntary_exit.ssz_snappy input file - // Fixed by https://github.com/ethereum/consensus-specs/pull/5005 in v1.7.0-alpha.4 - /^gloas\/operations\/voluntary_exit\/pyspec_tests\/builder_voluntary_exit__success$/, - ], + skippedTests: [], skippedRunners: [], }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 1a67fa2dc890..b829153a1a64 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -64,7 +64,7 @@ "@chainsafe/enr": "^6.0.1", "@chainsafe/persistent-merkle-tree": "^1.2.1", "@chainsafe/pubkey-index-map": "^3.0.0", - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@chainsafe/threads": "^1.11.3", "@libp2p/crypto": "^5.1.13", "@libp2p/interface": "^3.1.0", diff --git a/packages/config/package.json b/packages/config/package.json index 258fb8d8a56c..ebdce5a59ecc 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -66,7 +66,7 @@ ], "dependencies": { "@chainsafe/as-sha256": "^1.2.0", - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/params": "workspace:^", "@lodestar/spec-test-util": "workspace:^", "@lodestar/types": "workspace:^", diff --git a/packages/db/package.json b/packages/db/package.json index 6f3392e5733d..1f996e640a1a 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -43,7 +43,7 @@ "check-readme": "pnpm exec ts-node ../../scripts/check_readme.ts" }, "dependencies": { - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/config": "workspace:^", "@lodestar/utils": "workspace:^", "classic-level": "^1.4.1" diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index dba9bf737867..528c0120eb00 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -39,7 +39,7 @@ "check-readme": "pnpm exec ts-node ../../scripts/check_readme.ts" }, "dependencies": { - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/config": "workspace:^", "@lodestar/params": "workspace:^", "@lodestar/state-transition": "workspace:^", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index 684e59df7c52..bb1580c5e1cf 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -66,7 +66,7 @@ "@chainsafe/bls": "8.2.0", "@chainsafe/blst": "^2.2.0", "@chainsafe/persistent-merkle-tree": "^1.2.1", - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/api": "workspace:^", "@lodestar/config": "workspace:^", "@lodestar/params": "workspace:^", diff --git a/packages/logger/package.json b/packages/logger/package.json index 6aaa2bc29193..ecbdcba1a724 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -74,7 +74,7 @@ "winston-transport": "^4.5.0" }, "devDependencies": { - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@chainsafe/threads": "^1.11.3", "@lodestar/test-utils": "workspace:^", "@types/triple-beam": "^1.3.2" diff --git a/packages/reqresp/package.json b/packages/reqresp/package.json index 86e8c66af80e..76c7ef499020 100644 --- a/packages/reqresp/package.json +++ b/packages/reqresp/package.json @@ -56,7 +56,7 @@ "uint8arraylist": "^2.4.7" }, "devDependencies": { - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@libp2p/crypto": "^5.1.13", "@libp2p/logger": "^6.2.2", "@libp2p/peer-id": "^6.0.4", diff --git a/packages/spec-test-util/package.json b/packages/spec-test-util/package.json index c4b8264e2a22..98d3b34f1474 100644 --- a/packages/spec-test-util/package.json +++ b/packages/spec-test-util/package.json @@ -60,7 +60,7 @@ "vitest": "catalog:" }, "peerDependencies": { - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/types": "workspace:^", "vitest": "catalog:" } diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index e6499e578fd9..ee618fd55b3e 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -65,7 +65,7 @@ "@chainsafe/persistent-merkle-tree": "^1.2.1", "@chainsafe/persistent-ts": "^1.0.0", "@chainsafe/pubkey-index-map": "^3.0.0", - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@chainsafe/swap-or-not-shuffle": "^1.2.1", "@lodestar/config": "workspace:^", "@lodestar/params": "workspace:^", diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index d87c998d1e8a..cf0805be7bfc 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -32,10 +32,10 @@ import { calculateShufflingDecisionRoot, computeEpochShuffling, } from "../util/epochShuffling.js"; +import {getPtcWindowEpochCacheData} from "../util/gloas.js"; import { computeActivationExitEpoch, computeEpochAtSlot, - computePayloadTimelinessCommitteesForEpoch, computeProposers, computeSyncPeriodAtEpoch, getActivationChurnLimit, @@ -56,7 +56,7 @@ import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalanc import {EffectiveBalanceIncrements, getEffectiveBalanceIncrementsWithLen} from "./effectiveBalanceIncrements.js"; import {EpochTransitionCache} from "./epochTransitionCache.js"; import {PubkeyCache, createPubkeyCache, syncPubkeys} from "./pubkeyCache.js"; -import {CachedBeaconStateAllForks, CachedBeaconStateFulu} from "./stateCache.js"; +import {CachedBeaconStateAllForks, CachedBeaconStateFulu, CachedBeaconStateGloas} from "./stateCache.js"; import { SyncCommitteeCache, SyncCommitteeCacheEmpty, @@ -226,11 +226,12 @@ export class EpochCache { /** TODO: Indexed SyncCommitteeCache */ nextSyncCommitteeIndexed: SyncCommitteeCache; - // TODO GLOAS: See if we need to cache PTC for next epoch // PTC for previous epoch, required for slot N block validating slot N-1 attestations previousPayloadTimelinessCommittees: Uint32Array[]; // PTC for current epoch, computed eagerly at epoch transition payloadTimelinessCommittees: Uint32Array[]; + // PTC for next epoch, precomputed from the ptc window for future duty serving + nextPayloadTimelinessCommittees: Uint32Array[]; // TODO: Helper stats syncPeriod: SyncPeriod; @@ -270,6 +271,7 @@ export class EpochCache { nextSyncCommitteeIndexed: SyncCommitteeCache; previousPayloadTimelinessCommittees: Uint32Array[]; payloadTimelinessCommittees: Uint32Array[]; + nextPayloadTimelinessCommittees: Uint32Array[]; epoch: Epoch; syncPeriod: SyncPeriod; }) { @@ -301,6 +303,7 @@ export class EpochCache { this.nextSyncCommitteeIndexed = data.nextSyncCommitteeIndexed; this.previousPayloadTimelinessCommittees = data.previousPayloadTimelinessCommittees; this.payloadTimelinessCommittees = data.payloadTimelinessCommittees; + this.nextPayloadTimelinessCommittees = data.nextPayloadTimelinessCommittees; this.epoch = data.epoch; this.syncPeriod = data.syncPeriod; } @@ -451,25 +454,13 @@ export class EpochCache { nextSyncCommitteeIndexed = new SyncCommitteeCacheEmpty(); } - // Compute PTC for all slots in the prev/current epoch + // Copy previous/current epoch PTC slices from state.ptcWindow once, then serve hot-path lookups from epochCtx. let previousPayloadTimelinessCommittees: Uint32Array[] = []; let payloadTimelinessCommittees: Uint32Array[] = []; + let nextPayloadTimelinessCommittees: Uint32Array[] = []; if (currentEpoch >= config.GLOAS_FORK_EPOCH) { - payloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch( - state, - currentEpoch, - currentShuffling.committees, - effectiveBalanceIncrements - ); - - if (!isGenesis && previousEpoch >= config.GLOAS_FORK_EPOCH) { - previousPayloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch( - state, - previousEpoch, - previousShuffling.committees, - effectiveBalanceIncrements - ); - } + ({previousPayloadTimelinessCommittees, payloadTimelinessCommittees, nextPayloadTimelinessCommittees} = + getPtcWindowEpochCacheData(state as CachedBeaconStateGloas)); } // Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute everytime the @@ -546,6 +537,7 @@ export class EpochCache { nextSyncCommitteeIndexed, previousPayloadTimelinessCommittees, payloadTimelinessCommittees, + nextPayloadTimelinessCommittees, epoch: currentEpoch, syncPeriod: computeSyncPeriodAtEpoch(currentEpoch), }); @@ -592,6 +584,7 @@ export class EpochCache { nextSyncCommitteeIndexed: this.nextSyncCommitteeIndexed, previousPayloadTimelinessCommittees: this.previousPayloadTimelinessCommittees, payloadTimelinessCommittees: this.payloadTimelinessCommittees, + nextPayloadTimelinessCommittees: this.nextPayloadTimelinessCommittees, epoch: this.epoch, syncPeriod: this.syncPeriod, }); @@ -695,21 +688,26 @@ export class EpochCache { /** * At fork boundary, this runs post-fork logic and it happens after `upgradeState*` is called. */ - finalProcessEpoch(state: CachedBeaconStateAllForks): void { + finalProcessEpoch(state: CachedBeaconStateAllForks, epochTransitionCache: EpochTransitionCache): void { // this.epoch was updated at the end of afterProcessEpoch() const upcomingEpoch = this.epoch; const epochAfterUpcoming = upcomingEpoch + 1; this.proposersPrevEpoch = this.proposers; if (upcomingEpoch >= this.config.GLOAS_FORK_EPOCH) { - // Shift and compute current epoch PTC eagerly for all slots - this.previousPayloadTimelinessCommittees = this.payloadTimelinessCommittees; - this.payloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch( - state, - upcomingEpoch, - this.currentShuffling.committees, - this.effectiveBalanceIncrements - ); + if (epochTransitionCache.nextEpochPayloadTimelinessCommittees) { + // shift arrays from transition cache + this.previousPayloadTimelinessCommittees = this.payloadTimelinessCommittees; + this.payloadTimelinessCommittees = this.nextPayloadTimelinessCommittees; + this.nextPayloadTimelinessCommittees = epochTransitionCache.nextEpochPayloadTimelinessCommittees; + } else { + // Fork boundary: processPtcWindow didn't run, read from freshly initialized state.ptcWindow + ({ + previousPayloadTimelinessCommittees: this.previousPayloadTimelinessCommittees, + payloadTimelinessCommittees: this.payloadTimelinessCommittees, + nextPayloadTimelinessCommittees: this.nextPayloadTimelinessCommittees, + } = getPtcWindowEpochCacheData(state as CachedBeaconStateGloas)); + } } if (upcomingEpoch >= this.config.FULU_FORK_EPOCH) { // Populate proposer cache with lookahead from state @@ -1034,12 +1032,16 @@ export class EpochCache { throw new Error("Payload Timeliness Committee is not available before gloas fork"); } + if (epoch === this.epoch - 1) { + return this.previousPayloadTimelinessCommittees[slot % SLOTS_PER_EPOCH]; + } + if (epoch === this.epoch) { return this.payloadTimelinessCommittees[slot % SLOTS_PER_EPOCH]; } - if (epoch === this.epoch - 1 && this.previousPayloadTimelinessCommittees.length > 0) { - return this.previousPayloadTimelinessCommittees[slot % SLOTS_PER_EPOCH]; + if (epoch === this.epoch + 1) { + return this.nextPayloadTimelinessCommittees[slot % SLOTS_PER_EPOCH]; } throw new Error(`Payload Timeliness Committee is not available for slot=${slot}`); diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index 01d7e94ff153..0cc2e3276118 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -158,6 +158,12 @@ export interface EpochTransitionCache { */ nextShuffling: EpochShuffling | null; + /** + * Pre-computed PTC for epoch N + MIN_SEED_LOOKAHEAD + 1, populated by processPtcWindow (Gloas+). + * Used by finalProcessEpoch to shift PTC arrays in epoch cache without reading from state. + */ + nextEpochPayloadTimelinessCommittees: Uint32Array[] | null; + /** * Altair specific, this is total active balances for the next epoch. * This is only used in `afterProcessEpoch` to compute base reward and sync participant reward. @@ -502,6 +508,7 @@ export function beforeProcessEpoch( indicesToEject, nextShufflingActiveIndices, nextShuffling: null, + nextEpochPayloadTimelinessCommittees: null, // to be updated in processEffectiveBalanceUpdates nextEpochTotalActiveBalanceByIncrement: 0, isActivePrevEpoch, diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index 47aa812ef792..40579a72095c 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -28,6 +28,7 @@ import {processParticipationRecordUpdates} from "./processParticipationRecordUpd import {processPendingConsolidations} from "./processPendingConsolidations.js"; import {processPendingDeposits} from "./processPendingDeposits.js"; import {processProposerLookahead} from "./processProposerLookahead.js"; +import {processPtcWindow} from "./processPtcWindow.js"; import {processRandaoMixesReset} from "./processRandaoMixesReset.js"; import {processRegistryUpdates} from "./processRegistryUpdates.js"; import {processRewardsAndPenalties} from "./processRewardsAndPenalties.js"; @@ -55,6 +56,7 @@ export { processPendingDeposits, processPendingConsolidations, processProposerLookahead, + processPtcWindow, processBuilderPendingPayments, }; @@ -81,6 +83,7 @@ export enum EpochTransitionStep { processPendingDeposits = "processPendingDeposits", processPendingConsolidations = "processPendingConsolidations", processProposerLookahead = "processProposerLookahead", + processPtcWindow = "processPtcWindow", processBuilderPendingPayments = "processBuilderPendingPayments", } @@ -211,4 +214,10 @@ export function processEpoch( processProposerLookahead(fork, state as CachedBeaconStateFulu, cache); timer?.(); } + + if (fork >= ForkSeq.gloas) { + const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.processPtcWindow}); + processPtcWindow(state as CachedBeaconStateGloas, cache); + timer?.(); + } } diff --git a/packages/state-transition/src/epoch/processPtcWindow.ts b/packages/state-transition/src/epoch/processPtcWindow.ts new file mode 100644 index 000000000000..bdb629b00978 --- /dev/null +++ b/packages/state-transition/src/epoch/processPtcWindow.ts @@ -0,0 +1,38 @@ +import {MIN_SEED_LOOKAHEAD} from "@lodestar/params"; +import {ssz} from "@lodestar/types"; +import {CachedBeaconStateGloas, EpochTransitionCache} from "../types.js"; +import {computeEpochShuffling} from "../util/epochShuffling.js"; +import {computePayloadTimelinessCommitteesForEpoch} from "../util/seed.js"; + +/** + * Update the `ptc_window` field in the beacon state by shifting out the oldest epoch's + * PTC entries and appending newly computed entries for the next lookahead epoch. + * Stashes the computed PTCs in the transition cache for finalProcessEpoch to shift + * into the epoch cache without reading from state. + * + * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.4/specs/gloas/beacon-chain.md#new-process_ptc_window + */ +export function processPtcWindow(state: CachedBeaconStateGloas, cache: EpochTransitionCache): void { + const nextEpoch = state.epochCtx.epoch + MIN_SEED_LOOKAHEAD + 1; + const nextEpochShuffling = + cache.nextShuffling ?? computeEpochShuffling(state, cache.nextShufflingActiveIndices, nextEpoch); + cache.nextShuffling = nextEpochShuffling; + + const newNextPayloadTimelinessCommittees = computePayloadTimelinessCommitteesForEpoch( + state, + nextEpoch, + nextEpochShuffling.committees, + state.epochCtx.effectiveBalanceIncrements + ); + + // Stash for finalProcessEpoch to shift into epoch cache + cache.nextEpochPayloadTimelinessCommittees = newNextPayloadTimelinessCommittees; + + // Write shifted window to state: current(N) + next(N+1) + newlyComputed(N+2) + // From the perspective of upcoming epoch N+1, this is previous + current + next + state.ptcWindow = ssz.gloas.PtcWindow.toViewDU([ + ...state.epochCtx.payloadTimelinessCommittees, + ...state.epochCtx.nextPayloadTimelinessCommittees, + ...newNextPayloadTimelinessCommittees, + ]); +} diff --git a/packages/state-transition/src/slot/upgradeStateToGloas.ts b/packages/state-transition/src/slot/upgradeStateToGloas.ts index 64403823fa8e..a269dba199c1 100644 --- a/packages/state-transition/src/slot/upgradeStateToGloas.ts +++ b/packages/state-transition/src/slot/upgradeStateToGloas.ts @@ -5,7 +5,7 @@ import {isValidDepositSignature} from "../block/processDeposit.js"; import {applyDepositForBuilder} from "../block/processDepositRequest.js"; import {getCachedBeaconState} from "../cache/stateCache.js"; import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js"; -import {isBuilderWithdrawalCredential} from "../util/gloas.js"; +import {initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js"; import {isValidatorKnown} from "../util/index.js"; /** @@ -61,6 +61,7 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea stateGloasView.pendingPartialWithdrawals = stateGloasCloned.pendingPartialWithdrawals; stateGloasView.pendingConsolidations = stateGloasCloned.pendingConsolidations; stateGloasView.proposerLookahead = stateGloasCloned.proposerLookahead; + stateGloasView.ptcWindow = ssz.gloas.PtcWindow.toViewDU(initializePtcWindow(stateFulu)); for (let i = 0; i < SLOTS_PER_HISTORICAL_ROOT; i++) { stateGloasView.executionPayloadAvailability.set(i, true); diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index 39ddd2e2c6c0..e803ef90d765 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -282,7 +282,7 @@ function processSlotsWithTransientCache( { const timer = metrics?.epochTransitionStepTime.startTimer({step: EpochTransitionStep.finalProcessEpoch}); // last step to prepare epoch data that depends on the upgraded state, for example proposerLookahead of BeaconStateFulu - postState.epochCtx.finalProcessEpoch(postState); + postState.epochCtx.finalProcessEpoch(postState, epochTransitionCache); timer?.(); } diff --git a/packages/state-transition/src/util/gloas.ts b/packages/state-transition/src/util/gloas.ts index 1a3ce4206046..6f1115697ed8 100644 --- a/packages/state-transition/src/util/gloas.ts +++ b/packages/state-transition/src/util/gloas.ts @@ -6,15 +6,20 @@ import { EFFECTIVE_BALANCE_INCREMENT, FAR_FUTURE_EPOCH, MIN_DEPOSIT_AMOUNT, + MIN_SEED_LOOKAHEAD, + PTC_SIZE, SLOTS_PER_EPOCH, } from "@lodestar/params"; import {BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types"; import {AttestationData} from "@lodestar/types/phase0"; import {byteArrayEquals} from "@lodestar/utils"; -import {CachedBeaconStateGloas} from "../types.js"; +import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js"; import {getBlockRootAtSlot} from "./blockRoot.js"; import {computeEpochAtSlot} from "./epoch.js"; +import {computeEpochShuffling} from "./epochShuffling.js"; import {RootCache} from "./rootCache.js"; +import {computePayloadTimelinessCommitteesForEpoch} from "./seed.js"; +import {getActiveValidatorIndices} from "./validator.js"; export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return withdrawalCredentials[0] === BUILDER_WITHDRAWAL_PREFIX; @@ -170,3 +175,45 @@ export function isAttestationSameSlotRootCache(rootCache: RootCache, data: Attes export function isParentBlockFull(state: CachedBeaconStateGloas): boolean { return byteArrayEquals(state.latestExecutionPayloadBid.blockHash, state.latestBlockHash); } + +export function initializePtcWindow(state: CachedBeaconStateFulu): Uint32Array[] { + const ptcWindow: Uint32Array[] = Array.from({length: SLOTS_PER_EPOCH}, () => new Uint32Array(PTC_SIZE)); + const currentEpoch = state.epochCtx.epoch; + + for (let epochOffset = 0; epochOffset <= MIN_SEED_LOOKAHEAD; epochOffset++) { + const epoch = currentEpoch + epochOffset; + const shuffling = + state.epochCtx.getShufflingAtEpochOrNull(epoch) ?? + computeEpochShuffling(state, getActiveValidatorIndices(state, epoch), epoch); + + ptcWindow.push( + ...computePayloadTimelinessCommitteesForEpoch( + state, + epoch, + shuffling.committees, + state.epochCtx.effectiveBalanceIncrements + ) + ); + } + + return ptcWindow; +} + +export function getPtcWindowEpochCacheData(state: CachedBeaconStateGloas): { + previousPayloadTimelinessCommittees: Uint32Array[]; + payloadTimelinessCommittees: Uint32Array[]; + nextPayloadTimelinessCommittees: Uint32Array[]; +} { + const toUint32Arrays = (views: ReturnType) => + views.map((v) => Uint32Array.from(v.getAll())); + + const previousPtcWindow = state.ptcWindow.getReadonlyByRange(0, SLOTS_PER_EPOCH); + const currentPtcWindow = state.ptcWindow.getReadonlyByRange(SLOTS_PER_EPOCH, SLOTS_PER_EPOCH); + const nextPtcWindow = state.ptcWindow.getReadonlyByRange(2 * SLOTS_PER_EPOCH, SLOTS_PER_EPOCH); + + return { + previousPayloadTimelinessCommittees: toUint32Arrays(previousPtcWindow), + payloadTimelinessCommittees: toUint32Arrays(currentPtcWindow), + nextPayloadTimelinessCommittees: toUint32Arrays(nextPtcWindow), + }; +} diff --git a/packages/types/package.json b/packages/types/package.json index d0b7b6c9a6a9..b71a07792463 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -81,7 +81,7 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/params": "workspace:^", "ethereum-cryptography": "^2.0.0" }, diff --git a/packages/types/src/gloas/sszTypes.ts b/packages/types/src/gloas/sszTypes.ts index b9bf89ad583f..47e142b4e415 100644 --- a/packages/types/src/gloas/sszTypes.ts +++ b/packages/types/src/gloas/sszTypes.ts @@ -1,9 +1,17 @@ -import {BitVectorType, ContainerType, ListBasicType, ListCompositeType, VectorCompositeType} from "@chainsafe/ssz"; +import { + BitVectorType, + ContainerType, + ListBasicType, + ListCompositeType, + VectorBasicType, + VectorCompositeType, +} from "@chainsafe/ssz"; import { BUILDER_PENDING_WITHDRAWALS_LIMIT, BUILDER_REGISTRY_LIMIT, HISTORICAL_ROOTS_LIMIT, MAX_PAYLOAD_ATTESTATIONS, + MIN_SEED_LOOKAHEAD, NUMBER_OF_COLUMNS, PTC_SIZE, SLOTS_PER_EPOCH, @@ -66,6 +74,12 @@ export const BuilderPendingPayment = new ContainerType( {typeName: "BuilderPendingPayment", jsonCase: "eth2"} ); +export const PayloadTimelinessCommittee = new VectorBasicType(ValidatorIndex, PTC_SIZE); +export const PtcWindow = new VectorCompositeType( + PayloadTimelinessCommittee, + (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH +); + export const PayloadAttestationData = new ContainerType( { beaconBlockRoot: Root, @@ -263,6 +277,7 @@ export const BeaconState = new ContainerType( builderPendingWithdrawals: new ListCompositeType(BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT), // New in GLOAS:EIP7732 latestBlockHash: Bytes32, // New in GLOAS:EIP7732 payloadExpectedWithdrawals: capellaSsz.Withdrawals, // New in GLOAS:EIP7732 + ptcWindow: PtcWindow, // New in GLOAS:EIP7732 }, {typeName: "BeaconState", jsonCase: "eth2"} ); diff --git a/packages/types/src/gloas/types.ts b/packages/types/src/gloas/types.ts index f90e74bbf416..ecd506932544 100644 --- a/packages/types/src/gloas/types.ts +++ b/packages/types/src/gloas/types.ts @@ -4,6 +4,8 @@ import * as ssz from "./sszTypes.js"; export type Builder = ValueOf; export type BuilderPendingWithdrawal = ValueOf; export type BuilderPendingPayment = ValueOf; +export type PayloadTimelinessCommittee = ValueOf; +export type PtcWindow = ValueOf; export type PayloadAttestationData = ValueOf; export type PayloadAttestation = ValueOf; export type PayloadAttestationMessage = ValueOf; diff --git a/packages/utils/package.json b/packages/utils/package.json index faccf973259c..69077c457082 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -53,7 +53,7 @@ "js-yaml": "^4.1.0" }, "devDependencies": { - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@types/js-yaml": "^4.0.5", "@types/yargs": "^17.0.24", "prom-client": "^15.1.0" diff --git a/packages/validator/package.json b/packages/validator/package.json index ef12a2c00833..95fb868ce0d4 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -49,7 +49,7 @@ ], "dependencies": { "@chainsafe/blst": "^2.2.0", - "@chainsafe/ssz": "^1.2.2", + "@chainsafe/ssz": "^1.4.0", "@lodestar/api": "workspace:^", "@lodestar/config": "workspace:^", "@lodestar/db": "workspace:^", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e740e4365a9..00f4b98eaff2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -180,8 +180,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/config': specifier: workspace:^ version: link:../config @@ -247,8 +247,8 @@ importers: specifier: ^0.5.0 version: 0.5.0 '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@chainsafe/threads': specifier: ^1.11.3 version: 1.11.3 @@ -449,8 +449,8 @@ importers: specifier: ^3.0.0 version: 3.0.0 '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@chainsafe/threads': specifier: ^1.11.3 version: 1.11.3 @@ -582,8 +582,8 @@ importers: specifier: ^1.2.0 version: 1.2.0 '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/params': specifier: workspace:^ version: link:../params @@ -600,8 +600,8 @@ importers: packages/db: dependencies: '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/config': specifier: workspace:^ version: link:../config @@ -680,8 +680,8 @@ importers: packages/fork-choice: dependencies: '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/config': specifier: workspace:^ version: link:../config @@ -710,8 +710,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/api': specifier: workspace:^ version: link:../api @@ -772,8 +772,8 @@ importers: version: 4.6.0 devDependencies: '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@chainsafe/threads': specifier: ^1.11.3 version: 1.11.3 @@ -924,8 +924,8 @@ importers: version: 2.4.8 devDependencies: '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@libp2p/crypto': specifier: ^5.1.13 version: 5.1.13 @@ -948,8 +948,8 @@ importers: packages/spec-test-util: dependencies: '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/types': specifier: workspace:^ version: link:../types @@ -984,8 +984,8 @@ importers: specifier: ^3.0.0 version: 3.0.0 '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@chainsafe/swap-or-not-shuffle': specifier: ^1.2.1 version: 1.2.1 @@ -1043,8 +1043,8 @@ importers: packages/types: dependencies: '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/params': specifier: workspace:^ version: link:../params @@ -1071,8 +1071,8 @@ importers: version: 4.1.1 devDependencies: '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@types/js-yaml': specifier: ^4.0.5 version: 4.0.5 @@ -1089,8 +1089,8 @@ importers: specifier: ^2.2.0 version: 2.2.0 '@chainsafe/ssz': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^1.4.0 + version: 1.4.0 '@lodestar/api': specifier: workspace:^ version: link:../api @@ -1505,6 +1505,9 @@ packages: '@chainsafe/persistent-merkle-tree@1.2.1': resolution: {integrity: sha512-AOSEVLfaqwb9eTCKuY1ri0DrRxVQ3Rh+we1VBj1GahUGfEdE8OC3Vkbca7Up6RoI9Ip9FLnI31Y7AjKH9ZqAGA==} + '@chainsafe/persistent-merkle-tree@1.2.5': + resolution: {integrity: sha512-uyv3/nHkD8vNXjdez6q89rhXwGDUp3YX2mBbOwB7/xelgZ9E7hc3HQgOuq1EdfM9Vyh09jiwimXF/hskmymOfQ==} + '@chainsafe/persistent-ts@1.0.0': resolution: {integrity: sha512-Xwu59vDQwJWcF4QbIdi9gvRVnkLBOc7Y5JUpINS4TVRtp4omhjEsqO4rFSCUhC8opyg1HcNSQEjL4IgYLGouuw==} @@ -1576,8 +1579,8 @@ packages: '@chainsafe/ssz@0.11.1': resolution: {integrity: sha512-cB8dBkgGN6ZoeOKuk+rIRHKN0L5i9JLGeC0Lui71QX0TuLcQKwgbfkUexpyJxnGFatWf8yeJxlOjozMn/OTP0g==} - '@chainsafe/ssz@1.2.2': - resolution: {integrity: sha512-kIA3fJO6h2RsQndsNBlCSQYB4xfdZGMQvNPKPgbiB0mysV6okuxeJU3Nyl16xDCKv3tqej76eGYHcyjMVt7V1w==} + '@chainsafe/ssz@1.4.0': + resolution: {integrity: sha512-DRje073CIU0lvIdNKmW89jAo4UAidoKpgYco5wvQve8WK1DUk/YS/54c5FX/e/iiKiJ1mo44CNCg4xiKWY3/yQ==} '@chainsafe/swap-or-not-shuffle-darwin-arm64@1.2.1': resolution: {integrity: sha512-kTewLZe1KqMAJ1gHfagOxo0BI4kTcMOAkGJ7pRLFM5ZkL2P7sh3/4ixnjdbtMkO207vMZEZL3fxJSSs14kg9Kg==} @@ -7941,6 +7944,12 @@ snapshots: '@chainsafe/hashtree': 1.0.2 '@noble/hashes': 1.8.0 + '@chainsafe/persistent-merkle-tree@1.2.5': + dependencies: + '@chainsafe/as-sha256': 1.2.0 + '@chainsafe/hashtree': 1.0.2 + '@noble/hashes': 1.8.0 + '@chainsafe/persistent-ts@1.0.0': {} '@chainsafe/prometheus-gc-stats@1.0.2(prom-client@15.1.3)': @@ -7989,10 +7998,10 @@ snapshots: '@chainsafe/as-sha256': 0.4.1 '@chainsafe/persistent-merkle-tree': 0.6.1 - '@chainsafe/ssz@1.2.2': + '@chainsafe/ssz@1.4.0': dependencies: '@chainsafe/as-sha256': 1.2.0 - '@chainsafe/persistent-merkle-tree': 1.2.1 + '@chainsafe/persistent-merkle-tree': 1.2.5 '@chainsafe/swap-or-not-shuffle-darwin-arm64@1.2.1': optional: true diff --git a/spec-tests-version.json b/spec-tests-version.json index fd897522492f..8e29f2192c02 100644 --- a/spec-tests-version.json +++ b/spec-tests-version.json @@ -1,6 +1,6 @@ { "ethereumConsensusSpecsTests": { - "specVersion": "v1.7.0-alpha.3", + "specVersion": "v1.7.0-alpha.4", "specTestsRepoUrl": "https://github.com/ethereum/consensus-specs", "outputDirBase": "spec-tests", "testsToDownload": [ diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml index 2c1f0d75b862..41b05a145e51 100644 --- a/specrefs/.ethspecify.yml +++ b/specrefs/.ethspecify.yml @@ -1,4 +1,4 @@ -version: v1.7.0-alpha.3 +version: v1.7.0-alpha.4 style: full specrefs: @@ -54,6 +54,12 @@ exceptions: containers: # gloas - ForkChoiceNode#gloas + - PartialDataColumnHeader#gloas + + # fulu (not implemented yet) + - PartialDataColumnHeader#fulu + - PartialDataColumnPartsMetadata#fulu + - PartialDataColumnSidecar#fulu # heze (not implemented) - BeaconState#heze @@ -65,6 +71,7 @@ exceptions: dataclasses: # phase0 - LatestMessage#phase0 + - Seen#phase0 # bellatrix - OptimisticStore#bellatrix @@ -93,6 +100,15 @@ exceptions: functions: # phase0 - bytes_to_uint64#phase0 + - compute_time_at_slot_ms#phase0 + - is_not_from_future_slot#phase0 + - is_within_slot_range#phase0 + - validate_attester_slashing_gossip#phase0 + - validate_beacon_aggregate_and_proof_gossip#phase0 + - validate_beacon_attestation_gossip#phase0 + - validate_beacon_block_gossip#phase0 + - validate_proposer_slashing_gossip#phase0 + - validate_voluntary_exit_gossip#phase0 - compute_fork_version#phase0 - compute_proposer_score#phase0 - get_aggregate_signature#phase0 @@ -259,6 +275,8 @@ exceptions: - vanishing_polynomialcoeff#fulu - verify_cell_kzg_proof_batch#fulu - verify_cell_kzg_proof_batch_impl#fulu + - verify_partial_data_column_header_inclusion_proof#fulu + - verify_partial_data_column_sidecar_kzg_proofs#fulu # gloas - add_builder_to_registry#gloas diff --git a/specrefs/containers.yml b/specrefs/containers.yml index 3f7ce8b6f803..f4008da2944f 100644 --- a/specrefs/containers.yml +++ b/specrefs/containers.yml @@ -572,7 +572,7 @@ - file: packages/types/src/gloas/sszTypes.ts search: export const BeaconState = spec: | - + class BeaconState(Container): genesis_time: uint64 genesis_validators_root: Root @@ -629,12 +629,14 @@ latest_block_hash: Hash32 # [New in Gloas:EIP7732] payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + # [New in Gloas:EIP7732] + ptc_window: Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH] - name: BeaconState#heze sources: [] spec: | - + class BeaconState(Container): genesis_time: uint64 genesis_validators_root: Root @@ -682,6 +684,7 @@ builder_pending_withdrawals: List[BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT] latest_block_hash: Hash32 payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + ptc_window: Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH] - name: BlobIdentifier#deneb diff --git a/specrefs/functions.yml b/specrefs/functions.yml index e0e194c8702a..709db3a6a3fc 100644 --- a/specrefs/functions.yml +++ b/specrefs/functions.yml @@ -1100,6 +1100,29 @@ return (committee_weight * PROPOSER_SCORE_BOOST) // 100 +- name: compute_ptc#gloas + sources: + - file: packages/state-transition/src/util/seed.ts + search: export function computePayloadTimelinessCommitteeForSlot( + spec: | + + def compute_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]: + """ + Get the payload timeliness committee for the given ``slot``. + """ + epoch = compute_epoch_at_slot(slot) + seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(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 compute_balance_weighted_selection( + state, indices, seed, size=PTC_SIZE, shuffle_indices=False + ) + + - name: compute_pulled_up_tip#phase0 sources: - file: packages/fork-choice/src/forkChoice/forkChoice.ts @@ -4330,28 +4353,25 @@ search: '^\s+getPayloadTimelinessCommittee\(' regex: true spec: | - + def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]: """ Get the payload timeliness committee for the given ``slot``. """ epoch = compute_epoch_at_slot(slot) - seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(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 compute_balance_weighted_selection( - state, indices, seed, size=PTC_SIZE, shuffle_indices=False - ) + state_epoch = get_current_epoch(state) + if epoch < state_epoch: + assert epoch + 1 == state_epoch + return state.ptc_window[slot % SLOTS_PER_EPOCH] + assert epoch <= state_epoch + MIN_SEED_LOOKAHEAD + offset = (epoch - state_epoch + 1) * SLOTS_PER_EPOCH + return state.ptc_window[offset + slot % SLOTS_PER_EPOCH] - name: get_ptc_assignment#gloas sources: [] spec: | - + def get_ptc_assignment( state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex ) -> Optional[Slot]: @@ -4360,8 +4380,8 @@ index ``validator_index`` is a member of the PTC. Returns None if no assignment is found. """ - next_epoch = Epoch(get_current_epoch(state) + 1) - assert epoch <= next_epoch + max_epoch = Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD) + assert epoch <= max_epoch start_slot = compute_start_slot_at_epoch(epoch) for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): @@ -4659,18 +4679,23 @@ - name: get_upcoming_proposal_slots#gloas sources: [] spec: | - + def get_upcoming_proposal_slots( state: BeaconState, validator_index: ValidatorIndex ) -> Sequence[Slot]: """ - Get the slots in the next epoch for which ``validator_index`` is proposing. + Get the future slots in the current epoch and the slots in the next + epoch for which ``validator_index`` is proposing. """ - return [ - Slot(compute_start_slot_at_epoch(get_current_epoch(state) + Epoch(1)) + offset) - for offset, proposer_index in enumerate(state.proposer_lookahead[SLOTS_PER_EPOCH:]) - if validator_index == proposer_index - ] + current_epoch_start_slot = compute_start_slot_at_epoch(get_current_epoch(state)) + upcoming_proposal_slots = [] + for offset, proposer_index in enumerate(state.proposer_lookahead): + slot = Slot(current_epoch_start_slot + offset) + if slot <= state.slot: + continue + if validator_index == proposer_index: + upcoming_proposal_slots.append(slot) + return upcoming_proposal_slots - name: get_validator_activation_churn_limit#deneb @@ -5138,6 +5163,34 @@ return lookahead +- name: initialize_ptc_window#gloas + sources: + - file: packages/state-transition/src/util/gloas.ts + search: export function initializePtcWindow( + spec: | + + def initialize_ptc_window( + state: BeaconState, + ) -> Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH]: + """ + Return the cached PTC window starting from the current epoch. + Used to initialize the ``ptc_window`` field in the beacon state at genesis and after forks. + """ + empty_previous_epoch = [ + Vector[ValidatorIndex, PTC_SIZE]([ValidatorIndex(0) for _ in range(PTC_SIZE)]) + for _ in range(SLOTS_PER_EPOCH) + ] + + ptcs = [] + current_epoch = get_current_epoch(state) + for e in range(1 + MIN_SEED_LOOKAHEAD): + epoch = Epoch(current_epoch + e) + start_slot = compute_start_slot_at_epoch(epoch) + ptcs += [compute_ptc(state, Slot(start_slot + i)) for i in range(SLOTS_PER_EPOCH)] + + return empty_previous_epoch + ptcs + + - name: initiate_builder_exit#gloas sources: - file: packages/state-transition/src/util/gloas.ts @@ -6275,12 +6328,21 @@ - name: is_valid_proposal_slot#gloas sources: [] spec: | - + def is_valid_proposal_slot(state: BeaconState, preferences: ProposerPreferences) -> bool: """ - Check if the validator is the proposer for the given slot in the next epoch. + Check if the validator is the proposer for the given slot in the current or + next epoch. """ - index = SLOTS_PER_EPOCH + preferences.proposal_slot % SLOTS_PER_EPOCH + current_epoch = get_current_epoch(state) + proposal_epoch = compute_epoch_at_slot(preferences.proposal_slot) + if proposal_epoch < current_epoch: + return False + if proposal_epoch > current_epoch + Epoch(1): + return False + + index = (proposal_epoch - current_epoch) * SLOTS_PER_EPOCH + index += preferences.proposal_slot % SLOTS_PER_EPOCH return state.proposer_lookahead[index] == preferences.validator_index @@ -6931,7 +6993,7 @@ - name: on_payload_attestation_message#gloas sources: [] spec: | - + def on_payload_attestation_message( store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool = False ) -> None: @@ -6942,6 +7004,7 @@ # The beacon block root must be known data = ptc_message.data # PTC attestation must be for a known block. If block is unknown, delay consideration until the block is found + assert data.beacon_block_root in store.block_states state = store.block_states[data.beacon_block_root] ptc = get_ptc(state, data.slot) # PTC votes can only change the vote for their assigned beacon block, return early otherwise @@ -8139,7 +8202,7 @@ - name: process_epoch#gloas sources: [] spec: | - + def process_epoch(state: BeaconState) -> None: process_justification_and_finalization(state) process_inactivity_updates(state) @@ -8158,6 +8221,8 @@ process_participation_flag_updates(state) process_sync_committee_updates(state) process_proposer_lookahead(state) + # [New in Gloas:EIP7732] + process_ptc_window(state) - name: process_eth1_data#phase0 @@ -9210,6 +9275,26 @@ slash_validator(state, header_1.proposer_index) +- name: process_ptc_window#gloas + sources: + - file: packages/state-transition/src/epoch/processPtcWindow.ts + search: export function processPtcWindow( + spec: | + + def process_ptc_window(state: BeaconState) -> None: + """ + Update the cached PTC window. + """ + # Shift all epochs forward by one + state.ptc_window[: len(state.ptc_window) - SLOTS_PER_EPOCH] = state.ptc_window[SLOTS_PER_EPOCH:] + # Fill in the last epoch + next_epoch = Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD + 1) + start_slot = compute_start_slot_at_epoch(next_epoch) + state.ptc_window[len(state.ptc_window) - SLOTS_PER_EPOCH :] = [ + compute_ptc(state, Slot(slot)) for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH) + ] + + - name: process_randao#phase0 sources: - file: packages/state-transition/src/block/processRandao.ts @@ -11377,7 +11462,7 @@ - file: packages/state-transition/src/slot/upgradeStateToGloas.ts search: export function upgradeStateToGloas( spec: | - + def upgrade_to_gloas(pre: fulu.BeaconState) -> BeaconState: epoch = fulu.get_current_epoch(pre) @@ -11444,6 +11529,8 @@ latest_block_hash=pre.latest_execution_payload_header.block_hash, # [New in Gloas:EIP7732] payload_expected_withdrawals=[], + # [New in Gloas:EIP7732] + ptc_window=initialize_ptc_window(pre), ) # [New in Gloas:EIP7732] @@ -11455,7 +11542,7 @@ - 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( @@ -11526,6 +11613,7 @@ builder_pending_withdrawals=pre.builder_pending_withdrawals, latest_block_hash=pre.latest_block_hash, payload_expected_withdrawals=pre.payload_expected_withdrawals, + ptc_window=pre.ptc_window, ) return post