-
-
Notifications
You must be signed in to change notification settings - Fork 443
Description
Summary
SeenPayloadEnvelopeInput (added in PR #8962) has significantly fewer pruning mechanisms than the equivalent SeenBlockInput cache. This means entries can accumulate and leak memory in several scenarios that the block input cache handles correctly.
Analysis
Pruning comparison: SeenBlockInput vs SeenPayloadEnvelopeInput
| Pruning mechanism | SeenBlockInput |
SeenPayloadEnvelopeInput |
|---|---|---|
After DB persist (.finally()) |
β
writeBlockInputToDb.ts:133 |
β
writePayloadEnvelopeInputToDb.ts:49 |
| On finalization event | β
onFinalized + pruneToMaxSize() |
β
onFinalized (no max-size cap) |
| On gossip validation error (REJECT/IGNORE) | β
gossipHandlers.ts:205 |
β Missing |
On processBlock error (gossip path) |
β
gossipHandlers.ts:492 |
β Missing |
| After range sync batch processing | β
range.ts:223 |
β N/A (no range sync for envelopes yet) |
| On unknown block removal (bad chain) | β
unknownBlock.ts:681 |
β N/A (no unknown envelope sync yet) |
pruneToMaxSize() cap |
β
MAX_BLOCK_INPUT_CACHE_SIZE |
β Missing β no upper bound |
Specific concerns
1. No pruneToMaxSize() β unbounded growth during non-finality
SeenBlockInput has MAX_BLOCK_INPUT_CACHE_SIZE = (MAX_LOOK_AHEAD_EPOCHS + 1) * SLOTS_PER_EPOCH and calls pruneToMaxSize() after every prune() and onFinalized(). SeenPayloadEnvelopeInput has no equivalent β during extended non-finality, entries accumulate with no upper bound besides finalization.
2. No pruning on importExecutionPayload failure
When processExecutionPayload fails (EL error, invalid signature, state transition error), the PayloadEnvelopeInput stays in the cache permanently (until finalization). The gossip handler catches the error but only logs it:
chain.processExecutionPayload(payloadInput, {validSignature: true}).catch((e) => {
chain.logger.debug("Error processing execution payload from gossip", ...);
// No prune! Entry stays in cache forever
});Compare with SeenBlockInput which explicitly prunes on block processing errors:
chain.seenBlockInputCache.prune(blockInput.blockRootHex);3. .finally() prunes even on DB write failure β data loss
persistPayloadEnvelopeInput prunes the in-memory cache in .finally(), which runs after .catch(). If the DB write fails (transient error), the envelope data is evicted from both memory and DB β irrecoverable. This is the same pattern as persistBlockInput (existing technical debt), but worth noting as it applies here too.
4. No pruning on gossip validation rejection
If validateGossipExecutionPayloadEnvelope passes but addPayloadEnvelope throws (e.g., duplicate envelope), the cache entry persists. The block input cache handles analogous cases by pruning on REJECT/IGNORE actions.
Suggested fixes
- Add
pruneToMaxSize()with a reasonable constant (e.g.,MAX_PAYLOAD_ENVELOPE_INPUT_CACHE_SIZE), called afterprune()andonFinalized() - Prune on
processExecutionPayloadfailure in the gossip handler (match block input pattern) - Consider retry-then-prune for DB write failures instead of unconditional
.finally()prune (applies to both block and payload envelope caches β tracked separately)
Related
- feat: add gloas execution payload envelope import pipelineΒ #8962 (introduces
SeenPayloadEnvelopeInput) - Queuing on api if execution payload envelope is received firstΒ #8915 (block + payload timing issue)
- Existing
persistBlockInputhas the same.finally()data-loss pattern (separate issue)
AI assisted