Skip to content

fix: download proposer duties for the next epoch post-fulu#9380

Merged
wemeetagain merged 12 commits into
unstablefrom
te/cayman/proposer-preferences-api
May 19, 2026
Merged

fix: download proposer duties for the next epoch post-fulu#9380
wemeetagain merged 12 commits into
unstablefrom
te/cayman/proposer-preferences-api

Conversation

@twoeths

@twoeths twoeths commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes Lodestar's handling of proposer duties under the post-Fulu (EIP-7917) deterministic 1-epoch proposer lookahead. Surfaced while reviewing #9377: the validator was never querying currentEpoch + 1 proposer duties post-Fulu, and the BN's dep_root computation was wrong when serving duties for an epoch other than state.epoch.

Contains two related stories:

  1. BN-side bug fixes + lookahead support so getProposerDutiesV2 correctly serves currentEpoch + 1 (and currentEpoch + 2 near the boundary).
  2. Validator-side refactor to consume that lookahead through an event-driven model that mirrors AttestationDutiesService, instead of per-slot polling.

BN side

proposerShufflingDecisionRoot bug fix (state-transition/src/util/shuffling.ts)

Previously derived the decision slot from state.epoch, which gave the wrong dep_root whenever the state was one epoch off the requested epoch (e.g. serving state.epoch + 1 duties from the head state). Now takes proposalEpoch explicitly:

  • Pre-Fulu: dep_root(E) = block@(startSlot(E) - 1) — unchanged
  • Post-Fulu (MIN_SEED_LOOKAHEAD = 1): dep_root(E) = block@(startSlot(E - 1) - 1) — shifted back one epoch

getProposerDuties (beacon-node/src/api/impl/validator/index.ts)

Allows epoch === currentEpoch + 2 near the next-epoch boundary post-Fulu. The duties are served from the upcoming-epoch (currentEpoch + 1) checkpoint state's nextProposers, which is populated by the proposer_lookahead field. The existing nearNextEpoch gate (msToNextEpoch < prepareNextSlotLookAheadMs) determines availability.

Validator side

Original draft of this PR added a fork-aware pollBeaconProposers that, post-Fulu, polled nextEpoch every slot and nextEpoch + nextEpoch+1 at the boundary. That was functional but raised a fair concern in review: why fetch two epochs at the boundary, and why poll next-epoch every slot if its dep_root is stable post-Fulu?

The refactor (refactor(validator): event-driven proposer duties via SSE head events) replaces that with an attester-style model:

Trigger Action
clock.runEveryEpoch(epoch) Fetch epoch (+ epoch + 1 post-Fulu, using the EIP-7917 lookahead)
chainHeaderTracker.runOnNewHead(headEvent) Compare incoming dep_roots against cache; refetch only the affected epoch on mismatch
clock.runEverySlot(slot) Notify block production from cache; pre-Fulu only — schedule the 1s-before-boundary fetch for nextEpoch (its dep_root only stabilizes at the boundary and isn't exposed via SSE)

The SSE head event already carries everything needed for both forks via a nice coincidence in the dep_root math:

  • Pre-Fulu: currentDutyDependentRoot ≡ proposer_dep_root(currentEpoch)
  • Post-Fulu: previousDutyDependentRoot ≡ proposer_dep_root(currentEpoch), currentDutyDependentRoot ≡ proposer_dep_root(nextEpoch)

No spec/event changes required — the same fields the validator already uses for attester duties cover the post-Fulu proposer lookahead window.

A per-slot notification dedup (notifiedSlot / notifiedProposers) replaces the old "two-pass with differenceHex" pattern so any source of cache update (SSE refetch, cold-cache back-fill, epoch tick) only notifies newly discovered proposers and never duplicates createAndPublishBlock calls.

Results

In steady state, the validator now makes 2 proposer-duty calls per epoch (current + next epoch pre-fetch) plus refetches only on dep_root changes — matching the per-epoch cadence of AttestationDutiesService (which previously had been 32× more frequent).

Tests

  • 11 new BlockDutiesService unit tests covering: post-Fulu pre-fetch of next epoch, pre-Fulu vs post-Fulu fork detection, SSE-driven refetch on dep_root mismatch, no-op on dep_root match, cold-cache back-fill, pre-Fulu boundary scheduling + post-Fulu suppression, signer removal across epochs.
  • BN-side getProposerDuties tests updated to exercise the V2 path with a post-Fulu config.
  • E2E tests verified: proposerBoostReorg, finalizedSync, checkpointSync (Fulu fork crossings, reorgs, checkpoint sync) — all pass, all 30+ block proposals fire correctly, no new errors.

Known follow-ups (non-blocking)

  1. Genesis-state dep_root quirk (BN-side, cosmetic). At very early genesis, the BN's getProposerDuties returns genesisBlockRoot via the state.slot === decisionSlot fallback, but later returns state.getBlockRootAtSlot(0) for the same epoch — they're cosmetically different roots for the same logical block. The old code didn't observe this because it didn't pre-fetch nextEpoch until ~1s before the boundary; the new code pre-fetches at the start of epoch 0 and sees one or two spurious Proposer duties re-org warnings per VC at startup. Duties are correct — pure metric noise. Worth a small BN-side normalization or a "skip pre-fetch on first epoch tick" guard.

  2. Concurrent pollBeaconProposers race. If onNewHead and runEveryEpochTask race on the same epoch with asymmetric HTTP latencies, last-write-wins can briefly leave a stale dep_root cached. In practice the same BN serves both calls and returns identical payloads. Documented in a code comment; a per-epoch sequence number would harden it if it ever becomes a real problem.

  3. Gloas timing. BLOCK_DUTIES_LOOKAHEAD_BPS may want to flip from "1s before the boundary" to "1s after" post-Gloas. Existing TODO GLOAS: re-evaluate timing is preserved.

AI disclosure

Refactor designed and implemented with AI assistance.

wemeetagain and others added 5 commits May 18, 2026 17:56
Implements beacon-APIs PR ethereum/beacon-APIs#593 for the gloas fork.

Adds the operational ProposerPreferencesPool keyed (slot, dependent_root)
and pruned per slot tick, plumbs it into BeaconChain, and exposes it via
two new beacon API endpoints:

  GET  /eth/v1/beacon/pool/proposer_preferences (slot-filtered)
  POST /eth/v1/beacon/pool/proposer_preferences (JSON + SSZ bodies)

The POST handler validates each item via the existing gossip validator,
adds to the pool, publishes through gossip (network.publishProposerPreferences),
emits the new proposer_preferences SSE event, and reports per-item failures
with persistInvalidSszValue on REJECT-class errors. The gossip handler
mirrors the same persist-and-emit on a successfully validated incoming
message.

Also resolves the two // TODO GLOAS markers in validateExecutionPayloadBid:

  - IGNORE if no SignedProposerPreferences exists at (bid.slot,
    dependent_root), where dependent_root is derived from bid.parentBlockRoot
    via forkChoice.getAncestor(parentBlockRoot, computeStartSlotAtEpoch(bidEpoch) - 1).
  - REJECT if bid.fee_recipient != preferences.fee_recipient.
  - REJECT if bid.gas_limit != preferences.gas_limit.

The parentBlockRoot in-fork-choice IGNORE was hoisted to the top of the
validator so getAncestor can be called safely; its late copy is removed
as unreachable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds ProposerPreferencesService that runs every slot (gloas+) and submits
each local validator's SignedProposerPreferences within SLOTS_PER_EPOCH/4
slots of their proposal slot. Re-submits when the proposer dependent root
for an epoch shifts (reorg / dependent-root change), detected by comparing
the dependentRoot reported by BlockDutiesService against the one we last
submitted under. Submitted-slot tracking is updated only after the batch
POST succeeds so transient API failures retry naturally on the next tick.

Lifts BlockDutiesService ownership from BlockProposingService to validator.ts
so the new service can share the existing per-slot proposer-duty poll.
BlockDutiesService gains a setNotifyBlockProductionFn setter for late-binding
the block-production callback, and a public getProposersAtEpoch(epoch) getter
exposing the per-epoch {dependentRoot, data} cache.

Adds ValidatorStore.signProposerPreferences (mirrors signPayloadAttestation)
and SignableMessageType.PROPOSER_PREFERENCES (enum, type union,
requiresForkInfo, serialization switch) for remote-signer support.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for post-Fulu proposer duties and execution payload bid validation by implementing deterministic lookahead logic and updating the validator's polling mechanisms. Changes include the addition of getProposerDutiesV2, updates to the getShufflingDependentRoot utility, and adjustments to the SignedProposerPreferencesListType capacity. Feedback indicates that the new epoch-boundary polling logic for post-Fulu forks might be redundant, potentially causing duplicate API calls, and suggests simplifying the implementation to maintain coverage without overlap.

Comment thread packages/validator/src/services/blockDuties.ts Outdated
@twoeths

twoeths commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

the log is good for finalizedSync.test.ts

grep -e "Polling proposers\|Downloaded proposer duties" 1.txt 
Eph -1/4 1.131[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0xf0d8ba0b2cadf9b02171998212c1557fe7a3308639aa9baeac301d620086847c, count=8
Eph 0/0 0.020[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0xf0d8ba0b2cadf9b02171998212c1557fe7a3308639aa9baeac301d620086847c, count=8
Eph 0/1 0.007[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0xf0d8ba0b2cadf9b02171998212c1557fe7a3308639aa9baeac301d620086847c, count=8
Eph 0/2 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 0/3 0.007[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 0/4 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 0/5 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 0/6 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 0/7 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=0, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 0/7 1.833[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next 2 epochs nextEpoch=1, currentSlot=7
Eph 0/7 1.834[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/0 0.022[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=2, currentSlot=8
Eph 1/0 0.023[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/0 0.023[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/0 0.057[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/1 0.002[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=2, currentSlot=9
Eph 1/1 0.007[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/1 0.007[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/2 0.002[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=2, currentSlot=10
Eph 1/2 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/2 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/3 0.012[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=2, currentSlot=11
Eph 1/3 0.013[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/3 0.013[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/4 0.003[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=2, currentSlot=12
Eph 1/4 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/4 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/5 0.000[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=2, currentSlot=13
Eph 1/5 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/5 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/6 0.002[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=2, currentSlot=14
Eph 1/6 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/6 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 1/7 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=1, dependentRoot=0x3a6325c6f4092e2349f9abfaff01f21acfdda7a4b22a49b4953d4e138ded6282, count=8
Eph 1/7 1.834[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next 2 epochs nextEpoch=2, currentSlot=15
Eph 1/7 1.835[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/0 0.001[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=3, currentSlot=16
Eph 2/0 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/0 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/0 0.046[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/1 0.002[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=3, currentSlot=17
Eph 2/1 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/1 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/2 0.012[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=3, currentSlot=18
Eph 2/2 0.013[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/2 0.013[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/3 0.005[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=3, currentSlot=19
Eph 2/3 0.007[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/3 0.007[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/4 0.001[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=3, currentSlot=20
Eph 2/4 0.004[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/4 0.004[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/5 0.011[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=3, currentSlot=21
Eph 2/5 0.011[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/5 0.011[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/6 0.003[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=3, currentSlot=22
Eph 2/6 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/6 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 2/7 0.004[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=2, dependentRoot=0xb1a7e7d79055149aa168ee4afbdff359d7fd6b99c8294072e130ab99d0e9903f, count=8
Eph 2/7 1.833[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next 2 epochs nextEpoch=3, currentSlot=23
Eph 2/7 1.833[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/0 0.007[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=4, currentSlot=24
Eph 3/0 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/0 0.009[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/0 0.036[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/1 0.003[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=4, currentSlot=25
Eph 3/1 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/1 0.006[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/2 0.001[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=4, currentSlot=26
Eph 3/2 0.003[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/2 0.003[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/3 0.003[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=4, currentSlot=27
Eph 3/3 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/3 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/4 0.001[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=4, currentSlot=28
Eph 3/4 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/4 0.005[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/5 0.002[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=4, currentSlot=29
Eph 3/5 0.009[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/5 0.009[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/6 0.007[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=4, currentSlot=30
Eph 3/6 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/6 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 3/7 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=3, dependentRoot=0x8de25c67c0f8c5c2d5c75f19cf6b19f1108425374f7325ef86eb1c64c70e7e05, count=8
Eph 3/7 1.835[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next 2 epochs nextEpoch=4, currentSlot=31
Eph 3/7 1.835[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 4/0 0.025[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=5, currentSlot=32
Eph 4/0 0.025[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 4/0 0.025[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=5, dependentRoot=0x6de64c43ce4141eb7fcd235a1fe7a34261f65dacfd47a2dccf6141b4ca58453a, count=8
Eph 4/0 0.579[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=5, dependentRoot=0x6de64c43ce4141eb7fcd235a1fe7a34261f65dacfd47a2dccf6141b4ca58453a, count=8
Eph 4/1 0.006[FinalizedSyncVc-VAL-0-7] debug: Polling proposers for the next epoch nextEpoch=5, currentSlot=33
Eph 4/1 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=4, dependentRoot=0x228c81f67c5cb2ad5745d2408b0b3d01919a11011ff0a3ec910797213b2290c5, count=8
Eph 4/1 0.008[FinalizedSyncVc-VAL-0-7] debug: Downloaded proposer duties epoch=5, dependentRoot=0x6de64c43ce4141eb7fcd235a1fe7a34261f65dacfd47a2dccf6141b4ca58453a, count=8
grep -e "Downloaded proposer duties epoch=3" -rn 1.txt | wc -l
      17

8 for this epoch download, 8 for the previous epoch download (1-epoch look ahead) plus 1 at epoch boundary

no error found in the log

Base automatically changed from cayman/proposer-preferences-api to unstable May 19, 2026 09:45
@twoeths twoeths changed the title fix: review 9377 fix: download proposer duties for the next epoch post-fulu May 19, 2026
@github-actions

github-actions Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

⚠️ Performance Alert ⚠️

Possible performance regression was detected for some benchmarks.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold.

Benchmark suite Current: 2e58976 Previous: c98da75 Ratio
enrSubnets - fastDeserialize 4 bits 320.00 ns/op 95.000 ns/op 3.37
Full columns - reconstruct half of the blobs out of 20 699.30 us/op 181.66 us/op 3.85
Full benchmark results
Benchmark suite Current: 2e58976 Previous: c98da75 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.2259 ms/op 858.07 us/op 1.43
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 39.821 us/op 39.227 us/op 1.02
BLS verify - blst 756.43 us/op 747.44 us/op 1.01
BLS verifyMultipleSignatures 3 - blst 1.3881 ms/op 1.3366 ms/op 1.04
BLS verifyMultipleSignatures 8 - blst 2.1920 ms/op 2.1675 ms/op 1.01
BLS verifyMultipleSignatures 32 - blst 6.8917 ms/op 6.9137 ms/op 1.00
BLS verifyMultipleSignatures 64 - blst 13.373 ms/op 13.676 ms/op 0.98
BLS verifyMultipleSignatures 128 - blst 26.019 ms/op 26.085 ms/op 1.00
BLS deserializing 10000 signatures 632.11 ms/op 640.28 ms/op 0.99
BLS deserializing 100000 signatures 6.4112 s/op 6.3943 s/op 1.00
BLS verifyMultipleSignatures - same message - 3 - blst 767.60 us/op 762.16 us/op 1.01
BLS verifyMultipleSignatures - same message - 8 - blst 904.00 us/op 945.31 us/op 0.96
BLS verifyMultipleSignatures - same message - 32 - blst 1.4440 ms/op 1.5138 ms/op 0.95
BLS verifyMultipleSignatures - same message - 64 - blst 2.2408 ms/op 2.4516 ms/op 0.91
BLS verifyMultipleSignatures - same message - 128 - blst 3.9959 ms/op 4.0438 ms/op 0.99
BLS aggregatePubkeys 32 - blst 17.437 us/op 17.869 us/op 0.98
BLS aggregatePubkeys 128 - blst 61.270 us/op 63.711 us/op 0.96
getSlashingsAndExits - default max 52.527 us/op 44.888 us/op 1.17
getSlashingsAndExits - 2k 418.44 us/op 353.30 us/op 1.18
proposeBlockBody type=full, size=empty 750.88 us/op 775.79 us/op 0.97
isKnown best case - 1 super set check 393.00 ns/op 152.00 ns/op 2.59
isKnown normal case - 2 super set checks 386.00 ns/op 152.00 ns/op 2.54
isKnown worse case - 16 super set checks 397.00 ns/op 152.00 ns/op 2.61
validate api signedAggregateAndProof - struct 1.5671 ms/op 1.5167 ms/op 1.03
validate gossip signedAggregateAndProof - struct 1.5681 ms/op 1.5196 ms/op 1.03
batch validate gossip attestation - vc 640000 - chunk 32 105.58 us/op 109.35 us/op 0.97
batch validate gossip attestation - vc 640000 - chunk 64 93.457 us/op 93.878 us/op 1.00
batch validate gossip attestation - vc 640000 - chunk 128 91.277 us/op 82.425 us/op 1.11
batch validate gossip attestation - vc 640000 - chunk 256 88.711 us/op 82.828 us/op 1.07
bytes32 toHexString 508.00 ns/op 264.00 ns/op 1.92
bytes32 Buffer.toString(hex) 405.00 ns/op 160.00 ns/op 2.53
bytes32 Buffer.toString(hex) from Uint8Array 487.00 ns/op 222.00 ns/op 2.19
bytes32 Buffer.toString(hex) + 0x 406.00 ns/op 161.00 ns/op 2.52
Return object 10000 times 0.23680 ns/op 0.20140 ns/op 1.18
Throw Error 10000 times 3.4560 us/op 3.1299 us/op 1.10
toHex 96.299 ns/op 87.430 ns/op 1.10
Buffer.from 88.099 ns/op 78.997 ns/op 1.12
shared Buffer 60.521 ns/op 52.597 ns/op 1.15
fastMsgIdFn sha256 / 200 bytes 1.7380 us/op 1.3830 us/op 1.26
fastMsgIdFn h32 xxhash / 200 bytes 363.00 ns/op 142.00 ns/op 2.56
fastMsgIdFn h64 xxhash / 200 bytes 420.00 ns/op 196.00 ns/op 2.14
fastMsgIdFn sha256 / 1000 bytes 5.0500 us/op 4.4770 us/op 1.13
fastMsgIdFn h32 xxhash / 1000 bytes 454.00 ns/op 231.00 ns/op 1.97
fastMsgIdFn h64 xxhash / 1000 bytes 478.00 ns/op 239.00 ns/op 2.00
fastMsgIdFn sha256 / 10000 bytes 42.621 us/op 39.491 us/op 1.08
fastMsgIdFn h32 xxhash / 10000 bytes 1.5030 us/op 1.2100 us/op 1.24
fastMsgIdFn h64 xxhash / 10000 bytes 1.0570 us/op 781.00 ns/op 1.35
send data - 1000 256B messages 4.3276 ms/op 4.1251 ms/op 1.05
send data - 1000 512B messages 4.4740 ms/op 3.9533 ms/op 1.13
send data - 1000 1024B messages 4.8635 ms/op 5.2058 ms/op 0.93
send data - 1000 1200B messages 4.7615 ms/op 4.6482 ms/op 1.02
send data - 1000 2048B messages 6.1433 ms/op 4.9111 ms/op 1.25
send data - 1000 4096B messages 6.2062 ms/op 5.3853 ms/op 1.15
send data - 1000 16384B messages 22.700 ms/op 12.975 ms/op 1.75
send data - 1000 65536B messages 264.43 ms/op 186.62 ms/op 1.42
enrSubnets - fastDeserialize 64 bits 1.0300 us/op 681.00 ns/op 1.51
enrSubnets - ssz BitVector 64 bits 500.00 ns/op 256.00 ns/op 1.95
enrSubnets - fastDeserialize 4 bits 320.00 ns/op 95.000 ns/op 3.37
enrSubnets - ssz BitVector 4 bits 504.00 ns/op 264.00 ns/op 1.91
prioritizePeers score -10:0 att 32-0.1 sync 2-0 218.20 us/op 196.00 us/op 1.11
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 259.11 us/op 259.06 us/op 1.00
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 357.21 us/op 371.38 us/op 0.96
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 608.32 us/op 651.44 us/op 0.93
prioritizePeers score 0:0 att 64-1 sync 4-1 749.19 us/op 703.92 us/op 1.06
array of 16000 items push then shift 1.2375 us/op 1.1980 us/op 1.03
LinkedList of 16000 items push then shift 8.0340 ns/op 7.0720 ns/op 1.14
array of 16000 items push then pop 71.104 ns/op 66.817 ns/op 1.06
LinkedList of 16000 items push then pop 6.3240 ns/op 6.0110 ns/op 1.05
array of 24000 items push then shift 2.0332 us/op 1.9486 us/op 1.04
LinkedList of 24000 items push then shift 7.6240 ns/op 6.4540 ns/op 1.18
array of 24000 items push then pop 100.97 ns/op 94.851 ns/op 1.06
LinkedList of 24000 items push then pop 6.3180 ns/op 6.0580 ns/op 1.04
intersect bitArray bitLen 8 4.8090 ns/op 4.7710 ns/op 1.01
intersect array and set length 8 28.723 ns/op 29.578 ns/op 0.97
intersect bitArray bitLen 128 23.483 ns/op 25.149 ns/op 0.93
intersect array and set length 128 497.88 ns/op 505.54 ns/op 0.98
bitArray.getTrueBitIndexes() bitLen 128 1.3800 us/op 1.0220 us/op 1.35
bitArray.getTrueBitIndexes() bitLen 248 2.0770 us/op 1.6960 us/op 1.22
bitArray.getTrueBitIndexes() bitLen 512 4.0980 us/op 3.5210 us/op 1.16
Full columns - reconstruct all 6 blobs 134.15 us/op 118.54 us/op 1.13
Full columns - reconstruct half of the blobs out of 6 70.151 us/op 95.687 us/op 0.73
Full columns - reconstruct single blob out of 6 40.243 us/op 34.285 us/op 1.17
Half columns - reconstruct all 6 blobs 400.32 ms/op 385.70 ms/op 1.04
Half columns - reconstruct half of the blobs out of 6 199.31 ms/op 194.92 ms/op 1.02
Half columns - reconstruct single blob out of 6 71.794 ms/op 69.918 ms/op 1.03
Full columns - reconstruct all 10 blobs 193.41 us/op 187.48 us/op 1.03
Full columns - reconstruct half of the blobs out of 10 103.51 us/op 28.358 ms/op 0.00
Full columns - reconstruct single blob out of 10 31.834 us/op 26.853 us/op 1.19
Half columns - reconstruct all 10 blobs 660.79 ms/op 636.73 ms/op 1.04
Half columns - reconstruct half of the blobs out of 10 333.86 ms/op 322.72 ms/op 1.03
Half columns - reconstruct single blob out of 10 72.237 ms/op 69.804 ms/op 1.03
Full columns - reconstruct all 20 blobs 1.3777 ms/op 570.81 us/op 2.41
Full columns - reconstruct half of the blobs out of 20 699.30 us/op 181.66 us/op 3.85
Full columns - reconstruct single blob out of 20 32.463 us/op 98.816 us/op 0.33
Half columns - reconstruct all 20 blobs 1.3361 s/op 1.2640 s/op 1.06
Half columns - reconstruct half of the blobs out of 20 663.85 ms/op 642.70 ms/op 1.03
Half columns - reconstruct single blob out of 20 73.111 ms/op 71.514 ms/op 1.02
Set add up to 64 items then delete first 2.7256 us/op 1.9643 us/op 1.39
OrderedSet add up to 64 items then delete first 3.5011 us/op 3.3901 us/op 1.03
Set add up to 64 items then delete last 2.4625 us/op 2.1597 us/op 1.14
OrderedSet add up to 64 items then delete last 3.5342 us/op 3.2937 us/op 1.07
Set add up to 64 items then delete middle 2.2480 us/op 2.1450 us/op 1.05
OrderedSet add up to 64 items then delete middle 5.1208 us/op 4.7800 us/op 1.07
Set add up to 128 items then delete first 4.3426 us/op 4.3514 us/op 1.00
OrderedSet add up to 128 items then delete first 6.5093 us/op 6.5865 us/op 0.99
Set add up to 128 items then delete last 4.3481 us/op 3.9509 us/op 1.10
OrderedSet add up to 128 items then delete last 6.2771 us/op 5.8157 us/op 1.08
Set add up to 128 items then delete middle 4.0793 us/op 3.9594 us/op 1.03
OrderedSet add up to 128 items then delete middle 12.602 us/op 11.765 us/op 1.07
Set add up to 256 items then delete first 8.0486 us/op 7.9622 us/op 1.01
OrderedSet add up to 256 items then delete first 12.240 us/op 12.275 us/op 1.00
Set add up to 256 items then delete last 8.1787 us/op 7.7311 us/op 1.06
OrderedSet add up to 256 items then delete last 12.561 us/op 11.569 us/op 1.09
Set add up to 256 items then delete middle 8.0502 us/op 7.7164 us/op 1.04
OrderedSet add up to 256 items then delete middle 38.001 us/op 35.358 us/op 1.07
pass gossip attestations to forkchoice per slot 2.6484 ms/op 2.5932 ms/op 1.02
forkChoice updateHead vc 100000 bc 64 eq 0 432.16 us/op 427.71 us/op 1.01
forkChoice updateHead vc 600000 bc 64 eq 0 2.5899 ms/op 2.5811 ms/op 1.00
forkChoice updateHead vc 1000000 bc 64 eq 0 4.3346 ms/op 4.3176 ms/op 1.00
forkChoice updateHead vc 600000 bc 320 eq 0 2.6174 ms/op 2.6224 ms/op 1.00
forkChoice updateHead vc 600000 bc 1200 eq 0 2.6807 ms/op 2.5775 ms/op 1.04
forkChoice updateHead vc 600000 bc 7200 eq 0 3.3106 ms/op 2.8908 ms/op 1.15
forkChoice updateHead vc 600000 bc 64 eq 1000 3.1430 ms/op 3.1863 ms/op 0.99
forkChoice updateHead vc 600000 bc 64 eq 10000 3.2414 ms/op 3.2553 ms/op 1.00
forkChoice updateHead vc 600000 bc 64 eq 300000 7.3884 ms/op 7.1000 ms/op 1.04
computeDeltas 1400000 validators 0% inactive 13.892 ms/op 12.920 ms/op 1.08
computeDeltas 1400000 validators 10% inactive 13.002 ms/op 12.174 ms/op 1.07
computeDeltas 1400000 validators 20% inactive 11.895 ms/op 11.242 ms/op 1.06
computeDeltas 1400000 validators 50% inactive 8.9384 ms/op 8.5471 ms/op 1.05
computeDeltas 2100000 validators 0% inactive 20.934 ms/op 19.677 ms/op 1.06
computeDeltas 2100000 validators 10% inactive 19.565 ms/op 18.419 ms/op 1.06
computeDeltas 2100000 validators 20% inactive 17.820 ms/op 16.907 ms/op 1.05
computeDeltas 2100000 validators 50% inactive 13.470 ms/op 10.069 ms/op 1.34
altair processAttestation - 250000 vs - 7PWei normalcase 1.9577 ms/op 2.0061 ms/op 0.98
altair processAttestation - 250000 vs - 7PWei worstcase 2.5397 ms/op 3.4147 ms/op 0.74
altair processAttestation - setStatus - 1/6 committees join 90.188 us/op 99.043 us/op 0.91
altair processAttestation - setStatus - 1/3 committees join 184.40 us/op 191.07 us/op 0.97
altair processAttestation - setStatus - 1/2 committees join 286.11 us/op 268.81 us/op 1.06
altair processAttestation - setStatus - 2/3 committees join 375.23 us/op 349.75 us/op 1.07
altair processAttestation - setStatus - 4/5 committees join 514.88 us/op 475.41 us/op 1.08
altair processAttestation - setStatus - 100% committees join 610.40 us/op 564.04 us/op 1.08
altair processBlock - 250000 vs - 7PWei normalcase 3.7083 ms/op 3.4268 ms/op 1.08
altair processBlock - 250000 vs - 7PWei normalcase hashState 16.256 ms/op 13.746 ms/op 1.18
altair processBlock - 250000 vs - 7PWei worstcase 20.848 ms/op 19.828 ms/op 1.05
altair processBlock - 250000 vs - 7PWei worstcase hashState 41.006 ms/op 41.857 ms/op 0.98
phase0 processBlock - 250000 vs - 7PWei normalcase 1.4369 ms/op 1.4443 ms/op 0.99
phase0 processBlock - 250000 vs - 7PWei worstcase 17.085 ms/op 17.153 ms/op 1.00
altair processEth1Data - 250000 vs - 7PWei normalcase 299.66 us/op 295.14 us/op 1.02
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.4530 us/op 5.5160 us/op 0.63
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 21.427 us/op 20.558 us/op 1.04
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 6.1830 us/op 6.4840 us/op 0.95
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.8560 us/op 3.8930 us/op 0.99
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 93.719 us/op 92.807 us/op 1.01
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.4080 ms/op 1.3860 ms/op 1.02
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8511 ms/op 1.8257 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.8449 ms/op 1.8264 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.6408 ms/op 3.7148 ms/op 0.98
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.1001 ms/op 1.9492 ms/op 1.08
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 3.9886 ms/op 3.9747 ms/op 1.00
Tree 40 250000 create 359.78 ms/op 391.87 ms/op 0.92
Tree 40 250000 get(125000) 98.215 ns/op 94.740 ns/op 1.04
Tree 40 250000 set(125000) 1.0637 us/op 944.24 ns/op 1.13
Tree 40 250000 toArray() 9.3501 ms/op 11.613 ms/op 0.81
Tree 40 250000 iterate all - toArray() + loop 9.4273 ms/op 12.281 ms/op 0.77
Tree 40 250000 iterate all - get(i) 35.379 ms/op 33.127 ms/op 1.07
Array 250000 create 2.0898 ms/op 2.0754 ms/op 1.01
Array 250000 clone - spread 645.50 us/op 637.63 us/op 1.01
Array 250000 get(125000) 0.48200 ns/op 0.28400 ns/op 1.70
Array 250000 set(125000) 0.49700 ns/op 0.28700 ns/op 1.73
Array 250000 iterate all - loop 57.383 us/op 54.993 us/op 1.04
phase0 afterProcessEpoch - 250000 vs - 7PWei 53.134 ms/op 49.723 ms/op 1.07
Array.fill - length 1000000 2.1068 ms/op 2.2306 ms/op 0.94
Array push - length 1000000 7.8129 ms/op 7.7391 ms/op 1.01
Array.get 0.20481 ns/op 0.20844 ns/op 0.98
Uint8Array.get 0.22350 ns/op 0.23514 ns/op 0.95
phase0 beforeProcessEpoch - 250000 vs - 7PWei 14.736 ms/op 14.334 ms/op 1.03
altair processEpoch - mainnet_e81889 317.39 ms/op 294.77 ms/op 1.08
mainnet_e81889 - altair beforeProcessEpoch 35.924 ms/op 36.161 ms/op 0.99
mainnet_e81889 - altair processJustificationAndFinalization 6.3750 us/op 6.1160 us/op 1.04
mainnet_e81889 - altair processInactivityUpdates 3.4201 ms/op 3.4154 ms/op 1.00
mainnet_e81889 - altair processRewardsAndPenalties 19.185 ms/op 18.819 ms/op 1.02
mainnet_e81889 - altair processRegistryUpdates 759.00 ns/op 517.00 ns/op 1.47
mainnet_e81889 - altair processSlashings 352.00 ns/op 134.00 ns/op 2.63
mainnet_e81889 - altair processEth1DataReset 344.00 ns/op 126.00 ns/op 2.73
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.6511 ms/op 4.9716 ms/op 0.33
mainnet_e81889 - altair processSlashingsReset 942.00 ns/op 683.00 ns/op 1.38
mainnet_e81889 - altair processRandaoMixesReset 1.5790 us/op 1.4450 us/op 1.09
mainnet_e81889 - altair processHistoricalRootsUpdate 366.00 ns/op 131.00 ns/op 2.79
mainnet_e81889 - altair processParticipationFlagUpdates 689.00 ns/op 414.00 ns/op 1.66
mainnet_e81889 - altair processSyncCommitteeUpdates 315.00 ns/op 110.00 ns/op 2.86
mainnet_e81889 - altair afterProcessEpoch 39.512 ms/op 41.869 ms/op 0.94
capella processEpoch - mainnet_e217614 960.16 ms/op 945.06 ms/op 1.02
mainnet_e217614 - capella beforeProcessEpoch 66.881 ms/op 56.927 ms/op 1.17
mainnet_e217614 - capella processJustificationAndFinalization 6.9830 us/op 6.2030 us/op 1.13
mainnet_e217614 - capella processInactivityUpdates 14.388 ms/op 11.291 ms/op 1.27
mainnet_e217614 - capella processRewardsAndPenalties 97.715 ms/op 108.87 ms/op 0.90
mainnet_e217614 - capella processRegistryUpdates 4.5300 us/op 4.3050 us/op 1.05
mainnet_e217614 - capella processSlashings 344.00 ns/op 134.00 ns/op 2.57
mainnet_e217614 - capella processEth1DataReset 345.00 ns/op 131.00 ns/op 2.63
mainnet_e217614 - capella processEffectiveBalanceUpdates 5.6341 ms/op 13.208 ms/op 0.43
mainnet_e217614 - capella processSlashingsReset 931.00 ns/op 694.00 ns/op 1.34
mainnet_e217614 - capella processRandaoMixesReset 1.6000 us/op 1.2710 us/op 1.26
mainnet_e217614 - capella processHistoricalRootsUpdate 340.00 ns/op 145.00 ns/op 2.34
mainnet_e217614 - capella processParticipationFlagUpdates 655.00 ns/op 412.00 ns/op 1.59
mainnet_e217614 - capella afterProcessEpoch 107.03 ms/op 104.20 ms/op 1.03
phase0 processEpoch - mainnet_e58758 314.64 ms/op 323.04 ms/op 0.97
mainnet_e58758 - phase0 beforeProcessEpoch 67.895 ms/op 68.467 ms/op 0.99
mainnet_e58758 - phase0 processJustificationAndFinalization 6.7100 us/op 6.7740 us/op 0.99
mainnet_e58758 - phase0 processRewardsAndPenalties 15.853 ms/op 16.808 ms/op 0.94
mainnet_e58758 - phase0 processRegistryUpdates 2.5070 us/op 2.2270 us/op 1.13
mainnet_e58758 - phase0 processSlashings 366.00 ns/op 135.00 ns/op 2.71
mainnet_e58758 - phase0 processEth1DataReset 366.00 ns/op 360.00 ns/op 1.02
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 781.46 us/op 836.77 us/op 0.93
mainnet_e58758 - phase0 processSlashingsReset 1.0900 us/op 825.00 ns/op 1.32
mainnet_e58758 - phase0 processRandaoMixesReset 1.6130 us/op 1.4040 us/op 1.15
mainnet_e58758 - phase0 processHistoricalRootsUpdate 366.00 ns/op 144.00 ns/op 2.54
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.4530 us/op 1.2330 us/op 1.18
mainnet_e58758 - phase0 afterProcessEpoch 35.255 ms/op 34.899 ms/op 1.01
phase0 processEffectiveBalanceUpdates - 250000 normalcase 942.42 us/op 1.1465 ms/op 0.82
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.2531 ms/op 1.4874 ms/op 0.84
altair processInactivityUpdates - 250000 normalcase 10.573 ms/op 11.801 ms/op 0.90
altair processInactivityUpdates - 250000 worstcase 10.559 ms/op 12.066 ms/op 0.88
phase0 processRegistryUpdates - 250000 normalcase 2.4470 us/op 2.3070 us/op 1.06
phase0 processRegistryUpdates - 250000 badcase_full_deposits 141.96 us/op 148.38 us/op 0.96
phase0 processRegistryUpdates - 250000 worstcase 0.5 59.778 ms/op 55.068 ms/op 1.09
altair processRewardsAndPenalties - 250000 normalcase 13.436 ms/op 13.929 ms/op 0.96
altair processRewardsAndPenalties - 250000 worstcase 14.577 ms/op 13.833 ms/op 1.05
phase0 getAttestationDeltas - 250000 normalcase 5.0356 ms/op 13.634 ms/op 0.37
phase0 getAttestationDeltas - 250000 worstcase 5.0775 ms/op 5.1491 ms/op 0.99
phase0 processSlashings - 250000 worstcase 56.195 us/op 61.323 us/op 0.92
altair processSyncCommitteeUpdates - 250000 9.9329 ms/op 10.526 ms/op 0.94
BeaconState.hashTreeRoot - No change 403.00 ns/op 180.00 ns/op 2.24
BeaconState.hashTreeRoot - 1 full validator 78.250 us/op 70.505 us/op 1.11
BeaconState.hashTreeRoot - 32 full validator 753.67 us/op 882.17 us/op 0.85
BeaconState.hashTreeRoot - 512 full validator 6.3589 ms/op 6.3462 ms/op 1.00
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 101.17 us/op 98.762 us/op 1.02
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.4473 ms/op 2.5145 ms/op 0.58
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 14.143 ms/op 14.679 ms/op 0.96
BeaconState.hashTreeRoot - 1 balances 77.903 us/op 89.587 us/op 0.87
BeaconState.hashTreeRoot - 32 balances 560.26 us/op 702.19 us/op 0.80
BeaconState.hashTreeRoot - 512 balances 4.8393 ms/op 6.0376 ms/op 0.80
BeaconState.hashTreeRoot - 250000 balances 136.21 ms/op 165.20 ms/op 0.82
aggregationBits - 2048 els - zipIndexesInBitList 18.410 us/op 19.990 us/op 0.92
regular array get 100000 times 21.958 us/op 22.763 us/op 0.96
wrappedArray get 100000 times 21.922 us/op 22.755 us/op 0.96
arrayWithProxy get 100000 times 9.0187 ms/op 12.664 ms/op 0.71
ssz.Root.equals 20.236 ns/op 21.198 ns/op 0.95
byteArrayEquals 20.107 ns/op 20.860 ns/op 0.96
Buffer.compare 8.4180 ns/op 8.6840 ns/op 0.97
processSlot - 1 slots 8.9730 us/op 10.753 us/op 0.83
processSlot - 32 slots 2.0759 ms/op 2.2217 ms/op 0.93
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.5701 ms/op 4.6901 ms/op 0.76
getCommitteeAssignments - req 1 vs - 250000 vc 1.5849 ms/op 1.5928 ms/op 1.00
getCommitteeAssignments - req 100 vs - 250000 vc 3.2535 ms/op 3.2958 ms/op 0.99
getCommitteeAssignments - req 1000 vs - 250000 vc 3.4843 ms/op 3.5218 ms/op 0.99
findModifiedValidators - 10000 modified validators 539.87 ms/op 874.23 ms/op 0.62
findModifiedValidators - 1000 modified validators 465.01 ms/op 527.29 ms/op 0.88
findModifiedValidators - 100 modified validators 253.58 ms/op 284.99 ms/op 0.89
findModifiedValidators - 10 modified validators 148.41 ms/op 167.80 ms/op 0.88
findModifiedValidators - 1 modified validators 180.42 ms/op 167.05 ms/op 1.08
findModifiedValidators - no difference 157.30 ms/op 160.02 ms/op 0.98
migrate state 1500000 validators, 3400 modified, 2000 new 3.7361 s/op 3.5157 s/op 1.06
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 5.6200 ns/op 3.6200 ns/op 1.55
state getBlockRootAtSlot - 250000 vs - 7PWei 406.18 ns/op 435.64 ns/op 0.93
computeProposerIndex 100000 validators 1.3336 ms/op 1.3091 ms/op 1.02
getNextSyncCommitteeIndices 1000 validators 2.8338 ms/op 2.7620 ms/op 1.03
getNextSyncCommitteeIndices 10000 validators 25.118 ms/op 24.481 ms/op 1.03
getNextSyncCommitteeIndices 100000 validators 87.347 ms/op 81.655 ms/op 1.07
computeProposers - vc 250000 544.07 us/op 531.02 us/op 1.02
computeEpochShuffling - vc 250000 38.854 ms/op 37.160 ms/op 1.05
getNextSyncCommittee - vc 250000 9.4047 ms/op 9.1172 ms/op 1.03
nodejs block root to RootHex using toHex 89.953 ns/op 90.470 ns/op 0.99
nodejs block root to RootHex using toRootHex 55.354 ns/op 54.451 ns/op 1.02
nodejs fromHex(blob) 862.84 us/op 820.13 us/op 1.05
nodejs fromHexInto(blob) 651.54 us/op 608.45 us/op 1.07
nodejs block root to RootHex using the deprecated toHexString 518.51 ns/op 438.69 ns/op 1.18
nodejs byteArrayEquals 32 bytes (block root) 26.719 ns/op 25.069 ns/op 1.07
nodejs byteArrayEquals 48 bytes (pubkey) 39.111 ns/op 36.554 ns/op 1.07
nodejs byteArrayEquals 96 bytes (signature) 37.059 ns/op 33.146 ns/op 1.12
nodejs byteArrayEquals 1024 bytes 44.015 ns/op 39.367 ns/op 1.12
nodejs byteArrayEquals 131072 bytes (blob) 1.8142 us/op 1.7041 us/op 1.06
browser block root to RootHex using toHex 149.77 ns/op 139.57 ns/op 1.07
browser block root to RootHex using toRootHex 135.84 ns/op 127.16 ns/op 1.07
browser fromHex(blob) 1.6876 ms/op 1.6078 ms/op 1.05
browser fromHexInto(blob) 655.75 us/op 644.45 us/op 1.02
browser block root to RootHex using the deprecated toHexString 363.27 ns/op 337.98 ns/op 1.07
browser byteArrayEquals 32 bytes (block root) 28.712 ns/op 28.704 ns/op 1.00
browser byteArrayEquals 48 bytes (pubkey) 40.628 ns/op 40.486 ns/op 1.00
browser byteArrayEquals 96 bytes (signature) 75.526 ns/op 75.864 ns/op 1.00
browser byteArrayEquals 1024 bytes 774.97 ns/op 776.86 ns/op 1.00
browser byteArrayEquals 131072 bytes (blob) 97.926 us/op 97.370 us/op 1.01

by benchmarkbot/action

@wemeetagain wemeetagain marked this pull request as ready for review May 19, 2026 18:30
@wemeetagain wemeetagain requested a review from a team as a code owner May 19, 2026 18:30
@codecov

codecov Bot commented May 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.56%. Comparing base (c98da75) to head (287db24).

Additional details and impacted files
@@            Coverage Diff            @@
##           unstable    #9380   +/-   ##
=========================================
  Coverage     52.56%   52.56%           
=========================================
  Files           848      848           
  Lines         60986    60970   -16     
  Branches       4495     4491    -4     
=========================================
- Hits          32059    32051    -8     
+ Misses        28863    28857    -6     
+ Partials         64       62    -2     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 287db244a3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/beacon-node/src/api/impl/validator/index.ts
@wemeetagain wemeetagain merged commit c5efeb6 into unstable May 19, 2026
24 of 26 checks passed
@wemeetagain wemeetagain deleted the te/cayman/proposer-preferences-api branch May 19, 2026 21:23
const res = await this.api.validator.getProposerDuties({epoch});
// Post-Fulu the proposer dependent root changed (deterministic proposer lookahead)
const res = isForkPostFulu(this.config.getForkName(computeStartSlotAtEpoch(epoch)))
? await this.api.validator.getProposerDutiesV2({epoch})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means we will use v2 on mainnet with the next release, this could be problematic as this endpoint was added to the spec retroactively after fulu went live on mainnet, it might be fine but we should check if all clients implement the api already

ideally, we should avoid using v2 until after gloas

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this is a valid concern
added fallback mechanism in #9518

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but this is different from what I mean, we shouldn't be calling getProposerDutiesV2 at all from the vc before gloas

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nflaig pushed a commit that referenced this pull request Jun 17, 2026
**Motivation**

- got this error from vero
```
consensus-1  | Jun-09 21:45:56.328[rest]            error: Req req-qi getProposerDuties error - Can only get block root in the past currentSlot=14518127 slot=14518143
consensus-1  | Error: Can only get block root in the past currentSlot=14518127 slot=14518143
consensus-1  |     at getBlockRootAtSlot (file:///usr/app/packages/state-transition/src/util/blockRoot.ts:21:11)
consensus-1  |     at BeaconStateView.getBlockRootAtSlot (file:///usr/app/packages/state-transition/src/stateView/beaconStateView.ts:157:12)
consensus-1  |     at proposerShufflingDecisionRoot (file:///usr/app/packages/state-transition/src/util/shuffling.ts:36:16)
consensus-1  |     at Object.getProposerDuties (file:///usr/app/packages/beacon-node/src/api/impl/validator/index.ts:1298:9)
consensus-1  |     at processTicksAndRejections (node:internal/process/task_queues:104:5)
consensus-1  |     at Object.<anonymous> (file:///usr/app/packages/api/src/utils/server/handler.ts:105:22)
```

- #9380 was too strict, vero still querying `get_proposer_duties()` v1

**Description**

- this is how it was broken for unstable and how it worked for v1.43
```
###
lodestar unstable

vero requested for v1 at epoch 453692, lodestar detected it's not v2 
  => fork is phase0 => decision epoch = 453692 => decision slot = 14 518 143 => throw error

###
lodestar v1.43 did not care about requested epoch

  => based on state slot 14518127, decisionSlot is 14.518.111, which is luckily correct for requested epoch 453692 (previous slot of epoch 453691)

```

=> fallback to`get_proposer_duties()` v1, which is how it worked for
v1.43


**AI Assistance Disclosure**
- created with the help of Claude

Co-authored-by: twoeths <twoeths@users.noreply.github.com>
nflaig added a commit that referenced this pull request Jun 17, 2026
per
#9380 (comment),
we cannot use getProposerDutiesV2 before gloas as clients might not have
it implemented yet
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants