node, cli: opt-in aggregator tunables for stuck-chain operation - #904
Closed
ch4r10t33r wants to merge 1 commit into
Closed
node, cli: opt-in aggregator tunables for stuck-chain operation#904ch4r10t33r wants to merge 1 commit into
ch4r10t33r wants to merge 1 commit into
Conversation
Three new operator flags surfaced by #899 for handling stalled-chain scenarios. Each defaults to the pre-existing behaviour so non-aggregator nodes and standard devnet aggregators see no change unless the flag is set explicitly. 1. `--gossip-attestation-max-age-slots N` (default 0 = off). Drops gossip attestations whose `data.slot + N < current_slot`. Symmetric to the implicit future-side bound `GOSSIP_DISPARITY_INTERVALS` (`attestationIsTooFarInFuture`); we add an `AttestationTooOld` error variant rather than re-using the future-side variant because the pending-attestation buffer's replay logic should not retry too-old entries. Block-included attestations skip the check (consistent with the future-side bound). Useful on devnets where stale peers re-broadcast attestations from finalisation-orphaned slots and pile them into the aggregator input set faster than the aggregator can drain them. 2. `--max-unfinalized-attestation-age-slots N` (default 0 = off). Adds a periodic non-finalisation prune that runs once per aggregator interval. The existing `pruneStaleAttestationData(finalized_slot)` only runs when finalisation advances; on a chain that never finalises the retained maps grow unboundedly, slowing every aggregate pass. The new `pruneStaleAttestationDataByHeadAge(head_slot, max_age_slots)` evicts `attestation_signatures` and aggregated_payloads entries with `data.slot + N < head_slot` regardless of finalisation state. New helper `prunePayloadMapByDataSlot` is a sibling of the existing target-slot-based prune. No-op when N=0. 3. `--aggregate-concurrent-limit N` (default 1). Surfaces the `aggregate_group.concurrent_limit` knob currently hardcoded to `.limited(1)` (the #873 invariant). Default `1` preserves the historical behaviour exactly; larger values let a slow pass not skip the next interval on hosts where each pass is well under one slot (after #900's slot window + #903's ThinLTO + `--rayon-threads`). Plumbed end-to-end: CLI args → `NodeOptions` → `NodeOpts` → `ChainOpts` → `BeamChain`. The `Io.Threaded.init` call now reads `opts.aggregate_concurrent_limit` instead of the literal `.limited(1)`. Related: #899
ch4r10t33r
marked this pull request as draft
May 20, 2026 19:31
Contributor
Author
|
Closing this PR — the analysis I built it on is stale once #900 lands the slot window. Why I'm closingThe thesis was "aggregators suffocate on accumulated
That collapses the case for each of the three flags here:
What remains useful from this series
If a memory cap is wanted laterA bounded |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three opt-in operator flags for stalled-chain aggregator behaviour, surfaced by #899. Every flag defaults to the pre-existing behaviour — non-aggregator nodes and healthy-chain aggregators see no change unless the flag is set.
1. `--gossip-attestation-max-age-slots N` (default 0 = off)
Drops gossip attestations whose `data.slot + N < current_slot`. Symmetric to the implicit future-side bound `GOSSIP_DISPARITY_INTERVALS` (`BeamChain.attestationIsTooFarInFuture`).
Implementation:
Useful on devnets where stale peers re-broadcast attestations from finalisation-orphaned slots and pile them into the aggregator input set faster than the aggregator can drain them.
2. `--max-unfinalized-attestation-age-slots N` (default 0 = off)
Periodic non-finalisation prune. Today's `pruneStaleAttestationData(finalized_slot)` only runs from `processFinalizationAdvancement`, so on a chain that never finalises (devnet partition, gossip drop, validator-set issue) the retained maps grow unboundedly and slow every aggregate pass.
The new `ForkChoice.pruneStaleAttestationDataByHeadAge(head_slot, max_age_slots)` evicts `attestation_signatures` and aggregated_payloads entries with `data.slot + N < head_slot` regardless of finalisation state. Called from `submitAggregateOnInterval` once per slot. Finalisation-based pruning is unchanged. No-op when N=0.
New helper `prunePayloadMapByDataSlot` is a sibling of the existing `prunePayloadMapBySlot` that filters by `AttestationData.slot` instead of `target.slot` (the head-age window is independent of where the chain's target checkpoint sits).
3. `--aggregate-concurrent-limit N` (default 1)
Surfaces the `aggregate_group.concurrent_limit` knob currently hardcoded to `.limited(1)` (the #873 invariant). Default `1` preserves the historical behaviour exactly. Larger values let a slow pass not skip the next interval on hosts where each pass is well under one slot (after #900's slot window + #903's ThinLTO + `--rayon-threads`).
Plumbing
CLI args → `pkgs/cli/src/node.zig:NodeOptions` → `pkgs/node/src/node.zig:NodeOpts` → `pkgs/node/src/chain.zig:ChainOpts` → `BeamChain`. The `std.Io.Threaded.init` call in `BeamChain.init` now reads `opts.aggregate_concurrent_limit` instead of the literal `.limited(1)`.
Test plan
Related