Skip to content

Degrade Gloas envelope-by-range serving and validation gracefully instead of poisoning peer scores - #17274

Open
qu0b wants to merge 1 commit into
OffchainLabs:glamsterdam-devnet-7from
qu0b:qu0b/fix/envelope-range-serving
Open

Degrade Gloas envelope-by-range serving and validation gracefully instead of poisoning peer scores#17274
qu0b wants to merge 1 commit into
OffchainLabs:glamsterdam-devnet-7from
qu0b:qu0b/fix/envelope-range-serving

Conversation

@qu0b

@qu0b qu0b commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What

Two hardening fixes to ExecutionPayloadEnvelopesByRange, one on each side of the wire:

  • Server (streamCanonicalEnvelopes): payload reconstruction legitimately fails when the execution client no longer holds the required data (pruned block access lists, EL syncing/rewound). Today that returns a generic server error for the whole request. Now the server falls back to per-payload reconstruction and serves the longest contiguous prefix, answering ResourceUnavailable only when nothing in the range is reconstructable.
  • Requester (SendExecutionPayloadEnvelopesByRangeRequest): a mid-response parent-hash discontinuity (the serving peer reorged mid-stream during non-finality) is currently treated as ErrInvalidFetchedData. Now the requester keeps the already-validated contiguous prefix instead of discarding the whole response.

Why

Both paths punish honest peers with bad-responder downscoring, and five strikes means a goodbye/ban. In production on glamsterdam-devnet-7 this was self-reinforcing isolation at the worst possible moment: nodes serving envelope ranges from a degraded EL got banned by every requester, and syncing requesters banned their own last remaining sync sources ("bad responses scorer ... got 5, threshold 5") — a direct contributor to permanent initial-sync wedges observed on two production nodes. An envelope range a peer cannot fully serve, or a response that stops chaining because of a reorg, is a resource/availability condition, not evidence of a faulty peer; scoring it as malice removes exactly the peers a recovering node needs.

Testing

Sync-package envelope/by-range tests green, including 3 new regression subtests (server prefix-degradation, empty-range ResourceUnavailable, requester contiguous-prefix) that fail without this fix.

🤖 Generated with Claude Code

https://claude.ai/code/session_01F6buHMiS3KiNHcnpUx32FU

@potuz

potuz commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

I don't understand this:

serves the longest contiguous prefix

The only example other than an EL bug is that we no longer hold the older data, but this by definition will not be therefore a prefix of the requested range.

@qu0b

qu0b commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

You're right for that case — pruned older data leaves a suffix, so the prefix fallback finds nothing and the request degrades to ResourceUnavailable. That degradation is the main point of the PR: today this answers a server error, requesters count it as a bad response (5 strikes → goodbye), and a node whose EL pruned data gets disconnected by every peer that asks.

The prefix case is a different failure we also hit in production: an EL behind its CL (rewound after a crash, or still syncing behind a checkpoint-synced beacon). There reconstruction fails for blocks newer than the EL head, so what's servable is exactly a prefix of the range.

So: EL behind → serve the prefix it has; data pruned → ResourceUnavailable; either way, no server error and no peer penalty for an honest node.

…tead of poisoning peer scores

Serving execution_payload_envelopes_by_range requires reconstructing full
envelopes from execution-client data that may legitimately be gone: blinded
envelopes are stored without transactions/withdrawals/BAL, and
engine_getPayloadBodiesByHashV2 may return null blockAccessList once the EL
prunes it (or null bodies entirely while the EL is syncing). Since OffchainLabs#17174
any such gap fails the whole request with a server error, which requesting
peers score as a bad response; after a handful of requests every peer
disconnects and the serving node is isolated - exactly when it needs peers.
Observed on glamsterdam-devnet-7 prysm-geth-1: "block access list
unavailable" against a healthy geth, then "missing required field
'parentHash'" against a rewound one; a Lighthouse peer disconnected us with
"bad responses scorer: peer exceeded bad responses threshold: got 5".

Serve side: on batch reconstruction failure, retry per payload and serve the
longest contiguous prefix; if nothing is reconstructable, respond with
resource-unavailable (already used for out-of-retention ranges) instead of a
server error.

Request side: a mid-response parent-hash discontinuity is expected when the
serving peer reorgs mid-response during non-finality and is not proof of a
faulty peer. Keep the contiguous prefix instead of discarding the response
with ErrInvalidFetchedData and downscoring the peer. All other validations
(slot range, monotonic slots, count, SSZ) remain fatal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qu0b
qu0b force-pushed the qu0b/fix/envelope-range-serving branch from 9ff9671 to 2dd78f3 Compare July 30, 2026 11:56
@qu0b

qu0b commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Done — removed the per-payload prefix fallback. When the EL fails to reconstruct (batch error or a missing payload), the request now answers resource_unavailable instead of an internal error. Regression test updated to pin that. The requester-side change (keep the contiguous prefix on a mid-response parent-hash discontinuity instead of downscoring) is unchanged — that one is about serving-peer reorgs, not EL data.

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.

2 participants