Skip to content

fix(sequencer): reject a production config that cannot clear a mandatory Inbox backlog - #175

Draft
spalladino wants to merge 3 commits into
spl/fi-s05-remining-fixturefrom
spl/fi-s03-capacity-floor
Draft

spalladino wants to merge 3 commits into
spl/fi-s05-remining-fixturefrom
spl/fi-s03-capacity-floor

Conversation

@spalladino

@spalladino spalladino commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

validateNetworkConsensusConfig already refuses a generated network profile whose maxBlocksPerCheckpoint
sits below MIN_BLOCKS_FOR_INBOX_CATCHUP (4 here, ceil(1024/256)). That check only sees the configured cap.
A running proposer is bounded by the smaller of that cap and what its own slot timings derive, so a node whose
timings shrink the derived count below the floor can never reach a mandatory streaming-Inbox endpoint: L1
rejects every publication it makes and it loses all of its slots, while its configuration looks valid.

This PR checks the effective count — min(maxBlocksPerCheckpoint, timetable.getMaxBlocksPerCheckpoint())
at sequencer startup and again before a runtime config update is committed. A rejected update leaves the
previous config and timetable in place, reusing the candidate/commit structure updateConfig already has.

Sandbox and e2e profiles deliberately run one or two blocks per slot against an Inbox nobody floods, so they
get a warning rather than a rejection. The exemption is a threshold on the Ethereum slot duration, not a
declaration that a node is a development one, so it also covers a real network running short Ethereum slots —
which faster L1 slots do not make safe, since the per-block message cap is unchanged. The JSDoc says so where
the exemption is taken, and the boundary is pinned in both directions by tests.

The exemption is boundary-inclusive (<= FAST_PROFILE_ETHEREUM_SLOT_DURATION), unlike the budget-clamping
isFastLocalProfile predicate, which is strictly below. DEFAULT_L1_BLOCK_TIME sits at exactly that boundary,
deliberately, so single-node e2e runs keep the production timing budgets; at that cadence a 16s L2 slot derives
2 block opportunities, and a strictly-below test would reject every default-cadence e2e run. The inclusive form
is implemented directly rather than ported and then corrected.

stdlib/src/timetable/budgets.ts gains the exported isFastLocalProfile predicate, replacing the inline
isFastProfile local inside resolveTimingBudgets. That is a pure extract-function refactor: same comparison,
same semantics.

Finally, the two e2e suites that need a propagation budget to satisfy the new floor get one.
MULTI_VALIDATOR_BLOCK_PRODUCTION_TIMING leaves attestationPropagationTime per-test; at the default of 2 its
24s slot derives 3 block opportunities against a floor of 4, so the new check refuses the config and every case
fails in setup. multi-node/block-production/proof_boundary and multi-node/block-production/simple were both
running on that default; their siblings already pin 1 and 0.5, and 1 derives 4 here. Gossip is mocked in both
suites, so the shorter budget costs them nothing. simple's budget is set at its own
setupSimpleBlockProduction call rather than inside the shared helper, because high_tps goes through the same
helper and deliberately overrides the cadence to 36s/6s.

Stack position

This is rung 12 of 25 in the Fast Inbox node stack, stacked on spl/fi-s05-remining-fixture.

Sources

Reconstructs the final state of four commits from AztecProtocol/aztec-packages, replacing the mixed old PR
#25440 with a scoped rung:

  • af8ccfc50587460efe04381be73f95c0e2cb315c — the production check and isFastLocalProfile.
  • a87164c8dc624cdf392aa5356118cab2cc16356b — documenting how far the exemption reaches, plus the boundary test.
  • d03049fb2fe7e32010b1e3a9850a426b7d2b0610 — corrects the exemption to the inclusive boundary and moves the
    strict-rejection pin one second above it. Supersedes af8ccfc505 on that point; implemented directly.
  • 97504b8c6dfebb75bbc2d0167ea32d0423840b31 — the proof_boundary fixture budget.

No production timing is retuned. Two e2e fixtures set the same timing value:
attestationPropagationTime: 1 in proof_boundary, as sourced from 97504b8c6d, and in simple, as a
destination-only correction for the same missed default. Neither e2e suite was run locally.

Tests

Red first, then green, for each behavior:

  • The six new rejection/boundary cases fail against the unchanged sequencer (6 failed, 3 passed — the three
    passing ones are the intended warn-path controls), and pass after the check lands.
  • Flipping the exemption from <= to < reddens exactly one test, warns rather than rejects at the fast-profile boundary, which is the case d03049fb2f exists to protect.

Run locally from yarn-project:

  • yarn build — green.
  • yarn format, yarn lint — clean.
  • yarn workspace @aztec-labs/sequencer-client test — 12 suites, 302 passed / 1 skipped (base was 293 passed /
    1 skipped; the 9 new tests are the whole delta).
  • yarn workspace @aztec-labs/stdlib test src/timetable/ src/config/network-consensus-config.test.ts — 3 suites,
    67 passed.

The archiver suite was not re-run: this diff does not touch the archiver, and the only shared-package change is
the behavior-identical extraction of isFastLocalProfile.

Left out

  • e2e is left to CI. proof_boundary and simple are multi-validator suites and expensive to run locally, so
    their budget changes ship unverified locally by design.
  • None of the seven deferred work families is imported: no DutyBudget, no unverifiable / checkpoint-unverifiable
    status or sentinel encoding 9, no R − P hard gossip cutoff, no generic Rollup artifact library map, no
    generic proposal-size/fee-modifier fix, no generic override-type or Noir-fixture cleanup.
  • Developer/operator documentation for this behavior is owned by aztec-packages and is not in this stack.

Survey: does this check break anything else in CI?

97504b8c6d is evidence that this check broke an existing e2e suite upstream, so every e2e timing profile with
an Ethereum slot duration above the exemption boundary was evaluated against the destination's own
ProposerTimetable. Profiles at or below 8s are exempt and warn regardless.

Setup E / S / D / P Derived Verdict
fixtures.ts automine & pipelining 4 / 12 / 3 / 2 2 warns (exempt)
single-node default (DEFAULT_L1_BLOCK_TIME) 8 / 16 / 3 / 2 2 warns (exempt, boundary)
MULTI_VALIDATOR_BLOCK_PRODUCTION_TIMING, P defaulted — proof_boundary and simple before this PR 12 / 24 / 4 / 2 3 rejected
same profile, P = 1 — proof_boundary and simple (both fixed here), high_tps 12 / 24 / 4 / 1 4 ok
same profile, P = 0.5 — first_slot 12 / 24 / 4 / 0.5 4 ok
WIDE_SLOT_TIMINGsetupBlockProductionWithProver and friends 12 / 72 / 5.5 / 2 11 ok
p2p/reqresp/utils.ts 12 / 36 / 6 / 2 4 ok
invalid-attestations/invalidate_block.parallel 12 / 36 / 6 / 2 4 ok
bench/bench_build_block.test.ts 12 / 600 / 200 / 2 1 predicted to be rejected

The upstream commit 97504b8c6d states that proof_boundary was the only suite on that profile not setting an
attestationPropagationTime. That is not accurate: reading the upstream tree at that SHA,
multi-node/block-production/simple.test.ts had exactly the same gap and was missed. It passes no setupOpts
at all, so it runs on an unconsidered default of 2 rather than a deliberate value, and it derives 3 against the
floor. This PR fixes both suites, verified with the same derivation against the real ProposerTimetable:
simple derives 3 without the budget and 4 with attestationPropagationTime: 1.

One suite is deliberately left alone. bench/bench_build_block.test.ts runs 600s slots with 200s blocks and
derives a single block opportunity by design — its own comment explains the geometry is chosen to measure pure
build time rather than a deadline-truncated block. The floor and that benchmark's intent are in direct tension,
which is a design decision to take rather than something to patch here. The benchmark was not run, so this is a
deterministic prediction from the reported timetable calculation rather than observed red test output. The stack
should not be called fully green until CI confirms or the conflict is resolved.

All spartan/ deployment profiles were checked too and all clear the floor: 36s/6s derives 4, 36s/3s derives 5,
72s/6s derives 10, 72s/5.5s derives 11, 24s/3s derives 5.

…ory Inbox backlog

validateNetworkConsensusConfig applies MIN_BLOCKS_FOR_INBOX_CATCHUP to a generated profile's configured
maxBlocksPerCheckpoint, but a running proposer is bounded by the smaller of that cap and what its own slot
timings derive. Timings that shrink the derived count below the floor leave a proposer whose publications L1
always rejects, losing every one of its slots. Check the effective count at startup and before committing a
config update; a rejected update leaves the previous config and timetable in place.

Sandbox and e2e profiles deliberately run one or two blocks per slot against an Inbox nobody floods, so they
warn instead of failing. The exemption is boundary-inclusive: the single-node e2e default runs at exactly
FAST_PROFILE_ETHEREUM_SLOT_DURATION so that it keeps the production timing budgets, and at that cadence a 16s
L2 slot derives 2 block opportunities. isFastLocalProfile names the strictly-below budget-clamping condition
that resolveTimingBudgets already used.
…nbox floor

MULTI_VALIDATOR_BLOCK_PRODUCTION_TIMING leaves attestationPropagationTime per-test, and proof_boundary was
one of the suites on the profile not setting one. At the default of 2 its 24s slot derives 3 block
opportunities against a floor of 4, so the new capacity check refuses the config and every case fails in
setupTest. Its two siblings already pin 1 and 0.5; 1 derives 4 here.
simple passes no setupOpts, so MULTI_VALIDATOR_BLOCK_PRODUCTION_TIMING leaves attestationPropagationTime at
its default of 2 and its 24s slot derives 3 block opportunities against a floor of 4, which the capacity check
refuses at node creation. Set it at this call site rather than in setupSimpleBlockProduction: high_tps shares
that helper and deliberately overrides the cadence to 36s/6s.
@spalladino
spalladino added this pull request to stack #188 September 12, 2026 04:52
@spalladino spalladino changed the title spl/fi s03 capacity floor fix(sequencer): reject a production config that cannot clear a mandatory Inbox backlog Sep 12, 2026
@spalladino spalladino added the ci-draft Run CI on this draft PR label Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on this draft PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant