Skip to content

perf(state-transition): add caches for indexed pending deposits by pubkey and verified counts#9247

Open
guha-rahul wants to merge 1 commit intoChainSafe:unstablefrom
guha-rahul:perf/pending-deposit-sig-cache
Open

perf(state-transition): add caches for indexed pending deposits by pubkey and verified counts#9247
guha-rahul wants to merge 1 commit intoChainSafe:unstablefrom
guha-rahul:perf/pending-deposit-sig-cache

Conversation

@guha-rahul
Copy link
Copy Markdown
Contributor

Motivation

Gloas deposit request processing needs to determine whether a pubkey already has a valid pending validator deposit. The previous approach requires scanning state.pendingDeposits and BLS-verifying every pending deposit signature to build a global set of pending validator pubkeys . The pr reeduces the overhead by caching pubkey-indexed pending deposit lookups and verified counts on the epoch cache.

Description

This PR replaces the eager pending-validator pubkey lookup in Gloas deposit request processing with two caches stored on state.epochCtx:

  • pubkey -> pending deposit indices index
  • pubkey -> verified pending deposit count

With these caches, lodestar can jump directly to the pending deposits for one pubkey and verify only those entries instead of scanning and verifying the full queue. The caches are updated when new pending deposits are appended.

Addresses #9181

AI Assistance Disclosure

Claude was used for this optimisation

@guha-rahul guha-rahul requested a review from a team as a code owner April 21, 2026 15:40
Copy link
Copy Markdown
Contributor

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

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 replaces a naive pending validator cache with a more robust indexing system in EpochCache to optimize deposit processing. It introduces pendingDepositPubkeyIndex and pendingValidatorVerifiedCount to track deposits and their signature validity. Feedback focuses on performance optimizations, including removing redundant type casting and hex conversions in processDepositRequest, caching hex representations of public keys to avoid repeated conversions during index initialization, and exploring more efficient cloning strategies for the new cache maps.

Comment on lines +123 to +124
const stateGloas = state as CachedBeaconStateGloas;
const pubkeyHex = toPubkeyHex(pubkey);
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.

medium

The variables stateGloas and pubkeyHex are already defined and computed in the previous if (fork >= ForkSeq.gloas) block (lines 89-90). You should move their definitions to the function scope or merge the logic to avoid redundant type casting and hex conversion, which can be expensive when processing many deposit requests.

Suggested change
const stateGloas = state as CachedBeaconStateGloas;
const pubkeyHex = toPubkeyHex(pubkey);
const stateGloas = state as CachedBeaconStateGloas;
const pubkeyHex = toPubkeyHex(pubkey);
const pendingDepositPubkeyIndex = ensurePendingDepositPubkeyIndex(stateGloas);
const newPendingDepositIndex = state.pendingDeposits.length - 1;
const bucket = pendingDepositPubkeyIndex.get(pubkeyHex);
if (bucket) {
bucket.push(newPendingDepositIndex);
} else {
pendingDepositPubkeyIndex.set(pubkeyHex, [newPendingDepositIndex]);
}
const verifiedPendingValidatorCount = getPendingValidatorVerifiedCount(stateGloas);
const cachedCount = verifiedPendingValidatorCount.get(pubkeyHex);
if (
cachedCount !== undefined &&
isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)
) {
verifiedPendingValidatorCount.set(pubkeyHex, cachedCount + 1);
}

Comment on lines +152 to +153
for (let i = 0; i < state.pendingDeposits.length; i++) {
const pubkeyHex = toPubkeyHex(state.pendingDeposits.getReadonly(i).pubkey);
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.

medium

Iterating over all pending deposits and converting every pubkey to hex can be a performance bottleneck if the queue is large. While this is only done when the cache is initialized, consider if the hex representation can be cached or if PendingDeposit can store it to avoid repeated toPubkeyHex calls.

Comment on lines +616 to +621
pendingDepositPubkeyIndex:
this.pendingDepositPubkeyIndex !== null
? new Map(
Array.from(this.pendingDepositPubkeyIndex, ([pubkeyHex, indexes]) => [pubkeyHex, [...indexes]] as const)
)
: null,
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.

medium

Deep cloning the pendingDepositPubkeyIndex map and all its internal arrays on every state clone (which happens per block) might be expensive if there are many pending deposits. While necessary for state isolation since the arrays are mutated in-place, consider if a more efficient copy-on-write structure or a shallow copy with lazy array cloning could be used if performance becomes an issue.

Copy link
Copy Markdown

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

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: 2c9b7b4e65

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +616 to +620
pendingDepositPubkeyIndex:
this.pendingDepositPubkeyIndex !== null
? new Map(
Array.from(this.pendingDepositPubkeyIndex, ([pubkeyHex, indexes]) => [pubkeyHex, [...indexes]] as const)
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stop deep-copying deposit index in generic state clones

EpochCache.clone() now deep-copies pendingDepositPubkeyIndex for every state.clone(), but state.clone() is on the hot path for each block/slot transition (via getCachedBeaconState().clone() calling epochCtx.clone()). Once this cache is initialized, clone cost becomes O(number of pending deposits) even when no deposit requests are being processed, which can significantly slow production block processing with large pending queues. This cache should be rebuilt lazily in the envelope path (or copied copy-on-write), not eagerly copied in every generic clone.

Useful? React with 👍 / 👎.

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.

1 participant