diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 52073645cb17..11c03219f6d7 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -3,9 +3,10 @@ import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz"; import {BeaconConfig} from "@lodestar/config"; import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; -import {ForkSeq, GENESIS_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {ForkSeq, GENESIS_SLOT, SLOTS_PER_EPOCH, isForkPostElectra} from "@lodestar/params"; import { BeaconStateAllForks, + BeaconStateElectra, CachedBeaconStateAllForks, EffectiveBalanceIncrements, EpochShuffling, @@ -350,6 +351,16 @@ export class BeaconChain implements IBeaconChain { this.serializedCache = new SerializedCache(); this.archiver = new Archiver(db, this, logger, signal, opts, metrics); + + // Stop polling eth1 data if anchor state is in Electra AND deposit_requests_start_index is reached + const anchorStateFork = this.config.getForkName(anchorState.slot); + if (isForkPostElectra(anchorStateFork)) { + const {eth1DepositIndex, depositRequestsStartIndex} = anchorState as BeaconStateElectra; + if (eth1DepositIndex === Number(depositRequestsStartIndex)) { + this.eth1.stopPollingEth1Data(); + } + } + // always run PrepareNextSlotScheduler except for fork_choice spec tests if (!opts?.disablePrepareNextSlot) { new PrepareNextSlotScheduler(this, this.config, metrics, this.logger, signal); @@ -1143,16 +1154,6 @@ export class BeaconChain implements IBeaconChain { if (headState === null) { this.logger.verbose("Head state is null"); } - - // TODO-Electra: Deprecating eth1Data poll requires a check on a finalized checkpoint state. - // Will resolve this later - // if (cpEpoch >= (this.config.ELECTRA_FORK_EPOCH ?? Infinity)) { - // // finalizedState can be safely casted to Electra state since cp is already post-Electra - // if (finalizedState.eth1DepositIndex >= (finalizedState as CachedBeaconStateElectra).depositRequestsStartIndex) { - // // Signal eth1 to stop polling eth1Data - // this.eth1.stopPollingEth1Data(); - // } - // } } async updateBeaconProposerData(epoch: Epoch, proposers: ProposerPreparationData[]): Promise { diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index f78c1842bd78..588818925aa3 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -1,7 +1,8 @@ import {routes} from "@lodestar/api"; import {ChainForkConfig} from "@lodestar/config"; -import {ForkExecution, ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {ForkExecution, ForkSeq, SLOTS_PER_EPOCH, isForkPostElectra} from "@lodestar/params"; import { + BeaconStateElectra, CachedBeaconStateAllForks, CachedBeaconStateExecutions, StateHashTreeRootSource, @@ -214,6 +215,10 @@ export class PrepareNextSlotScheduler { this.metrics?.precomputeNextEpochTransition.waste.inc(); } this.metrics?.precomputeNextEpochTransition.hits.set(previousHits ?? 0); + + // Check if we can stop polling eth1 data + this.stopEth1Polling(); + this.logger.verbose("Completed PrepareNextSlotScheduler epoch transition", { nextEpoch, headSlot, @@ -240,4 +245,27 @@ export class PrepareNextSlotScheduler { state.hashTreeRoot(); hashTreeRootTimer?.(); } + + /** + * Stop eth1 data polling after eth1_deposit_index has reached deposit_requests_start_index in Electra as described in EIP-6110 + */ + stopEth1Polling(): void { + // Only continue if eth1 is still polling and finalized checkpoint is in Electra. State regen is expensive + if (this.chain.eth1.isPollingEth1Data()) { + const finalizedCheckpoint = this.chain.forkChoice.getFinalizedCheckpoint(); + const checkpointFork = this.config.getForkInfoAtEpoch(finalizedCheckpoint.epoch).name; + + if (isForkPostElectra(checkpointFork)) { + const finalizedState = this.chain.getStateByCheckpoint(finalizedCheckpoint)?.state; + + if ( + finalizedState !== undefined && + finalizedState.eth1DepositIndex === Number((finalizedState as BeaconStateElectra).depositRequestsStartIndex) + ) { + // Signal eth1 to stop polling eth1Data + this.chain.eth1.stopPollingEth1Data(); + } + } + } + } } diff --git a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts index 815f5aeb8938..e0b9e9d61f68 100644 --- a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts +++ b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts @@ -88,7 +88,6 @@ export class Eth1DepositDataTracker { this.depositsCache = new Eth1DepositsCache(opts, config, db); this.eth1DataCache = new Eth1DataCache(config, db); this.eth1FollowDistance = config.ETH1_FOLLOW_DISTANCE; - // TODO Electra: fix scenario where node starts post-Electra and `stopPolling` will always be false this.stopPolling = false; this.forcedEth1DataVote = opts.forcedEth1DataVote @@ -118,7 +117,10 @@ export class Eth1DepositDataTracker { } } - // TODO Electra: Figure out how an elegant way to stop eth1data polling + isPollingEth1Data(): boolean { + return !this.stopPolling; + } + stopPollingEth1Data(): void { this.stopPolling = true; } diff --git a/packages/beacon-node/src/eth1/index.ts b/packages/beacon-node/src/eth1/index.ts index f8c28afc5aed..81b2ab6d7b91 100644 --- a/packages/beacon-node/src/eth1/index.ts +++ b/packages/beacon-node/src/eth1/index.ts @@ -105,6 +105,10 @@ export class Eth1ForBlockProduction implements IEth1ForBlockProduction { this.eth1MergeBlockTracker.startPollingMergeBlock(); } + isPollingEth1Data(): boolean { + return this.eth1DepositDataTracker?.isPollingEth1Data() ?? false; + } + stopPollingEth1Data(): void { this.eth1DepositDataTracker?.stopPollingEth1Data(); } @@ -139,6 +143,10 @@ export class Eth1ForBlockProductionDisabled implements IEth1ForBlockProduction { return null; } + isPollingEth1Data(): boolean { + return false; + } + startPollingMergeBlock(): void { // Ignore } diff --git a/packages/beacon-node/src/eth1/interface.ts b/packages/beacon-node/src/eth1/interface.ts index ccfd3568e479..c247e30a691c 100644 --- a/packages/beacon-node/src/eth1/interface.ts +++ b/packages/beacon-node/src/eth1/interface.ts @@ -63,6 +63,8 @@ export interface IEth1ForBlockProduction { */ startPollingMergeBlock(): void; + isPollingEth1Data(): boolean; + /** * Should stop polling eth1Data after a Electra block is finalized AND deposit_requests_start_index is reached */ diff --git a/packages/beacon-node/src/eth1/utils/eth1Vote.ts b/packages/beacon-node/src/eth1/utils/eth1Vote.ts index e1cd47301c22..35b7df0fa120 100644 --- a/packages/beacon-node/src/eth1/utils/eth1Vote.ts +++ b/packages/beacon-node/src/eth1/utils/eth1Vote.ts @@ -1,6 +1,6 @@ import {ChainForkConfig} from "@lodestar/config"; -import {EPOCHS_PER_ETH1_VOTING_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {BeaconStateAllForks, computeTimeAtSlot} from "@lodestar/state-transition"; +import {EPOCHS_PER_ETH1_VOTING_PERIOD, SLOTS_PER_EPOCH, isForkPostElectra} from "@lodestar/params"; +import {BeaconStateAllForks, BeaconStateElectra, computeTimeAtSlot} from "@lodestar/state-transition"; import {RootHex, phase0} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; @@ -15,6 +15,14 @@ export async function getEth1VotesToConsider( state: BeaconStateAllForks, eth1DataGetter: Eth1DataGetter ): Promise { + const fork = config.getForkName(state.slot); + if (isForkPostElectra(fork)) { + const {eth1DepositIndex, depositRequestsStartIndex} = state as BeaconStateElectra; + if (eth1DepositIndex === Number(depositRequestsStartIndex)) { + return state.eth1DataVotes.getAllReadonly(); + } + } + const periodStart = votingPeriodStartTime(config, state); const {SECONDS_PER_ETH1_BLOCK, ETH1_FOLLOW_DISTANCE} = config;