You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(sequencer): redistribute checkpoint budget evenly across remaining blocks (#21378)
Update the per-block budgets so that, on every block, the limits are
further adjusted to `remainingCheckpointBudget / remainingBlocks *
multiplier`. This prevents the last blocks from starvation. Also adjusts
the multiplier from 2x to 1.2x.
## Visualization
Using the
https://github.com/AztecProtocol/explorations/tree/main/block-distribution-simulator
### Before this PR
No redistribution, 2x multiplier
<img width="1544" height="737" alt="Screenshot From 2026-03-11 15-50-38"
src="https://github.com/user-attachments/assets/fda36d04-5d9e-456a-9ced-4649fa58d724"
/>
### After this PR
Redistribution enabled, 1.2x multiplier
<img width="1544" height="737" alt="Screenshot From 2026-03-11 15-50-49"
src="https://github.com/user-attachments/assets/2bc196f3-77fa-47bf-9294-4eb4199f8f93"
/>
### With no multiplier
For comparison purposes only, note the lower gas utilization
<img width="1544" height="737" alt="Screenshot From 2026-03-11 15-50-59"
src="https://github.com/user-attachments/assets/0facbc36-65e3-446e-abaf-eb7f637b87c9"
/>
## Summary
- Adds `SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET` (default: true) to
distribute remaining checkpoint budget evenly across remaining blocks
instead of letting one block consume it all. Fair share per block is
`ceil(remainingBudget / remainingBlocks * multiplier)`, applied to all
four dimensions (L2 gas, DA gas, blob fields, tx count).
- Changes default `perBlockAllocationMultiplier` from 2 to 1.2 for
smoother distribution.
- Wires `maxBlocksPerCheckpoint` from the timetable through to the
checkpoint builder config.
## Test plan
- Existing `capLimitsByCheckpointBudgets` tests pass with
`redistributeCheckpointBudget: false` (old behavior)
- New tests cover: even split with multiplier=1, fair share with
multiplier=1.2, last block gets all remaining, disabled flag falls back
to old behavior, DA gas and tx count redistribution
- `computeBlockLimits` tests updated for new default multiplier and
`maxBlocksPerCheckpoint` return value
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
/** Per-block gas budget multiplier for both L2 and DA gas. Budget = (checkpointLimit / maxBlocks) * multiplier. */
29
29
perBlockAllocationMultiplier?: number;
30
+
/** Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget. */
31
+
redistributeCheckpointBudget?: boolean;
32
+
/** Computed max number of blocks per checkpoint from timetable. */
Copy file name to clipboardExpand all lines: yarn-project/validator-client/README.md
+4-3Lines changed: 4 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -239,11 +239,11 @@ L1 enforces gas and blob capacity per checkpoint. The node enforces these during
239
239
240
240
Per-block budgets prevent one block from consuming the entire checkpoint budget.
241
241
242
-
**Proposer**: `computeBlockLimits()` derives budgets at startup as `min(checkpointLimit, ceil(checkpointLimit / maxBlocks * multiplier))`, where `maxBlocks` comes from the timetable and `multiplier` defaults to 2. The multiplier greater than 1 allows early blocks to use more than their even share of the checkpoint budget, since different blocks hit different limit dimensions (L2 gas, DA gas, blob fields) — a strict even split would waste capacity. Operators can override via `SEQ_MAX_L2_BLOCK_GAS` / `SEQ_MAX_DA_BLOCK_GAS` / `SEQ_MAX_TX_PER_BLOCK` (capped at checkpoint limits). Per-block TX limits follow the same derivation pattern when `SEQ_MAX_TX_PER_CHECKPOINT` is set.
242
+
**Proposer**: `computeBlockLimits()` derives budgets at startup as `min(checkpointLimit, ceil(checkpointLimit / maxBlocks * multiplier))`, where `maxBlocks` comes from the timetable and `multiplier` defaults to 1.2. The multiplier greater than 1 allows early blocks to use more than their even share of the checkpoint budget, since different blocks hit different limit dimensions (L2 gas, DA gas, blob fields) — a strict even split would waste capacity. Operators can override via `SEQ_MAX_L2_BLOCK_GAS` / `SEQ_MAX_DA_BLOCK_GAS` / `SEQ_MAX_TX_PER_BLOCK` (capped at checkpoint limits). Per-block TX limits follow the same derivation pattern when `SEQ_MAX_TX_PER_CHECKPOINT` is set.
243
243
244
244
**Validator**: Optionally enforces per-block limits via `VALIDATOR_MAX_L2_BLOCK_GAS`, `VALIDATOR_MAX_DA_BLOCK_GAS`, and `VALIDATOR_MAX_TX_PER_BLOCK`. When set, these are passed to `buildBlock` during re-execution and to `validateCheckpoint` for final validation. When unset, no per-block limit is enforced for that dimension (checkpoint-level protocol limits still apply). These are independent of the `SEQ_` vars so operators can tune proposer and validation limits separately.
245
245
246
-
**Checkpoint-level capping**: `CheckpointBuilder.capLimitsByCheckpointBudgets()` always runs before tx processing, capping per-block limits by `checkpointBudget - sum(used by prior blocks)` for all three gas dimensions and for transaction count (when `SEQ_MAX_TX_PER_CHECKPOINT` is set). This applies to both proposer and validator paths.
246
+
**Checkpoint-level capping**: `CheckpointBuilder.capLimitsByCheckpointBudgets()` always runs before tx processing, capping per-block limits by the remaining checkpoint budget. When `SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET` is enabled (default: true), the remaining budget is distributed evenly across remaining blocks with the multiplier applied: `min(perBlockLimit, ceil(remainingBudget / remainingBlocks * multiplier), remainingBudget)`. This prevents early blocks from consuming the entire checkpoint budget, producing smoother distribution. When disabled, each block can consume up to the full remaining budget, ie caps by `checkpointBudget - sum(used by prior blocks)`. This applies to all four dimensions (L2 gas, DA gas, blob fields, transaction count). Validators always cap by the total remaining.
247
247
248
248
### Per-transaction enforcement
249
249
@@ -259,7 +259,8 @@ Per-block budgets prevent one block from consuming the entire checkpoint budget.
259
259
|`SEQ_MAX_DA_BLOCK_GAS`|*auto*| Per-block DA gas. Auto-derived from checkpoint DA limit / maxBlocks * multiplier. |
260
260
|`SEQ_MAX_TX_PER_BLOCK`|*none*| Per-block tx count. If `SEQ_MAX_TX_PER_CHECKPOINT` is set and per-block is not, derived as `ceil(checkpointLimit / maxBlocks * multiplier)`. |
261
261
|`SEQ_MAX_TX_PER_CHECKPOINT`|*none*| Total txs across all blocks in a checkpoint. When set, per-block tx limit is derived from it (unless explicitly overridden) and checkpoint-level capping is enforced. |
262
-
|`SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER`| 2 | Multiplier for per-block budget computation. |
262
+
|`SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER`| 1.2 | Multiplier for per-block budget computation. |
263
+
|`SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET`| true | Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing one block to consume it all. |
263
264
|`VALIDATOR_MAX_L2_BLOCK_GAS`|*none*| Per-block L2 gas limit for validation. Proposals exceeding this are rejected. |
264
265
|`VALIDATOR_MAX_DA_BLOCK_GAS`|*none*| Per-block DA gas limit for validation. Proposals exceeding this are rejected. |
265
266
|`VALIDATOR_MAX_TX_PER_BLOCK`|*none*| Per-block tx count limit for validation. Proposals exceeding this are rejected. |
0 commit comments