Skip to content

Commit 7255d1d

Browse files
spalladinoclaude
andauthored
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>
1 parent a7000eb commit 7255d1d

9 files changed

Lines changed: 194 additions & 30 deletions

File tree

yarn-project/foundation/src/config/env_var.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export type EnvVar =
211211
| 'SEQ_MAX_DA_BLOCK_GAS'
212212
| 'SEQ_MAX_L2_BLOCK_GAS'
213213
| 'SEQ_PER_BLOCK_ALLOCATION_MULTIPLIER'
214+
| 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET'
214215
| 'SEQ_PUBLISHER_PRIVATE_KEY'
215216
| 'SEQ_PUBLISHER_PRIVATE_KEYS'
216217
| 'SEQ_PUBLISHER_ADDRESSES'

yarn-project/sequencer-client/src/client/sequencer-client.test.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ describe('computeBlockLimits', () => {
2222
describe('L2 gas', () => {
2323
it('derives maxL2BlockGas from rollupManaLimit when not explicitly set', () => {
2424
const rollupManaLimit = 1_000_000;
25-
// Single block mode (maxNumberOfBlocks=1), default multiplier=2:
26-
// min(1_000_000, ceil(1_000_000 / 1 * 2)) = min(1_000_000, 2_000_000) = 1_000_000
25+
// Single block mode (maxNumberOfBlocks=1), default multiplier=1.2:
26+
// min(1_000_000, ceil(1_000_000 / 1 * 1.2)) = min(1_000_000, 1_200_000) = 1_000_000
2727
const result = computeBlockLimits(makeConfig(), rollupManaLimit, 12, log);
2828
expect(result.maxL2BlockGas).toBe(rollupManaLimit);
2929
});
@@ -43,8 +43,8 @@ describe('computeBlockLimits', () => {
4343
const daLimit = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
4444

4545
it('derives maxDABlockGas from DA checkpoint limit when not explicitly set', () => {
46-
// Single block mode (maxNumberOfBlocks=1), default multiplier=2:
47-
// min(daLimit, ceil(daLimit / 1 * 2)) = min(daLimit, daLimit * 2) = daLimit
46+
// Single block mode (maxNumberOfBlocks=1), default multiplier=1.2:
47+
// min(daLimit, ceil(daLimit / 1 * 1.2)) = min(daLimit, daLimit * 1.2) = daLimit
4848
const result = computeBlockLimits(makeConfig(), 1_000_000, 12, log);
4949
expect(result.maxDABlockGas).toBe(daLimit);
5050
});
@@ -78,14 +78,14 @@ describe('computeBlockLimits', () => {
7878
});
7979

8080
it('derives maxTxsPerBlock from maxTxsPerCheckpoint when per-block not set', () => {
81-
// Multi-block mode with maxNumberOfBlocks=5, multiplier=2:
82-
// min(100, ceil(100 / 5 * 2)) = min(100, 40) = 40
81+
// Multi-block mode with maxNumberOfBlocks=5, multiplier=1.2:
82+
// min(100, ceil(100 / 5 * 1.2)) = min(100, 24) = 24
8383
const config = makeConfig({
8484
maxTxsPerCheckpoint: 100,
8585
blockDurationMs: 8000,
8686
});
8787
const result = computeBlockLimits(config, 1_000_000, 12, log);
88-
expect(result.maxTxsPerBlock).toBe(40);
88+
expect(result.maxTxsPerBlock).toBe(24);
8989
});
9090
});
9191

@@ -97,14 +97,20 @@ describe('computeBlockLimits', () => {
9797
// timeReservedAtEnd = 8 + 19 = 27
9898
// timeAvailableForBlocks = 72 - 1 - 27 = 44
9999
// maxNumberOfBlocks = floor(44 / 8) = 5
100-
// With multiplier=2 and rollupManaLimit=1_000_000:
101-
// maxL2BlockGas = min(1_000_000, ceil(1_000_000 / 5 * 2)) = min(1_000_000, 400_000) = 400_000
100+
// With multiplier=1.2 and rollupManaLimit=1_000_000:
101+
// maxL2BlockGas = min(1_000_000, ceil(1_000_000 / 5 * 1.2)) = min(1_000_000, 240_000) = 240_000
102102
const config = makeConfig({ blockDurationMs: 8000 });
103103
const result = computeBlockLimits(config, 1_000_000, 12, log);
104-
expect(result.maxL2BlockGas).toBe(400_000);
104+
expect(result.maxL2BlockGas).toBe(240_000);
105105

106106
const daLimit = MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT;
107-
expect(result.maxDABlockGas).toBe(Math.min(daLimit, Math.ceil((daLimit / 5) * 2)));
107+
expect(result.maxDABlockGas).toBe(Math.min(daLimit, Math.ceil((daLimit / 5) * 1.2)));
108+
});
109+
110+
it('returns maxBlocksPerCheckpoint from timetable', () => {
111+
const config = makeConfig({ blockDurationMs: 8000 });
112+
const result = computeBlockLimits(config, 1_000_000, 12, log);
113+
expect(result.maxBlocksPerCheckpoint).toBe(5);
108114
});
109115
});
110116
});

yarn-project/sequencer-client/src/client/sequencer-client.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export class SequencerClient {
160160
const l1PublishingTimeBasedOnChain = isAnvilTestChain(config.l1ChainId) ? 1 : ethereumSlotDuration;
161161
const l1PublishingTime = config.l1PublishingTime ?? l1PublishingTimeBasedOnChain;
162162

163-
const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock } = computeBlockLimits(
163+
const { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint } = computeBlockLimits(
164164
config,
165165
rollupManaLimit,
166166
l1PublishingTime,
@@ -183,7 +183,7 @@ export class SequencerClient {
183183
deps.dateProvider,
184184
epochCache,
185185
rollupContract,
186-
{ ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock },
186+
{ ...config, l1PublishingTime, maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint },
187187
telemetryClient,
188188
log,
189189
);
@@ -257,7 +257,7 @@ export function computeBlockLimits(
257257
rollupManaLimit: number,
258258
l1PublishingTime: number,
259259
log: ReturnType<typeof createLogger>,
260-
): { maxL2BlockGas: number; maxDABlockGas: number; maxTxsPerBlock: number } {
260+
): { maxL2BlockGas: number; maxDABlockGas: number; maxTxsPerBlock: number; maxBlocksPerCheckpoint: number } {
261261
const maxNumberOfBlocks = new SequencerTimetable({
262262
ethereumSlotDuration: config.ethereumSlotDuration,
263263
aztecSlotDuration: config.aztecSlotDuration,
@@ -331,5 +331,5 @@ export function computeBlockLimits(
331331
multiplier,
332332
});
333333

334-
return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock };
334+
return { maxL2BlockGas, maxDABlockGas, maxTxsPerBlock, maxBlocksPerCheckpoint: maxNumberOfBlocks };
335335
}

yarn-project/sequencer-client/src/config.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ export const DefaultSequencerConfig = {
4040
minTxsPerBlock: 1,
4141
buildCheckpointIfEmpty: false,
4242
publishTxsWithProposals: false,
43-
perBlockAllocationMultiplier: 2,
43+
perBlockAllocationMultiplier: 1.2,
44+
redistributeCheckpointBudget: true,
4445
enforceTimeTable: true,
4546
attestationPropagationTime: DEFAULT_P2P_PROPAGATION_TIME,
4647
secondsBeforeInvalidatingBlockAsCommitteeMember: 144, // 12 L1 blocks
@@ -112,6 +113,15 @@ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
112113
' Values greater than one allow early blocks to use more than their even share, relying on checkpoint-level capping for later blocks.',
113114
...numberConfigHelper(DefaultSequencerConfig.perBlockAllocationMultiplier),
114115
},
116+
redistributeCheckpointBudget: {
117+
env: 'SEQ_REDISTRIBUTE_CHECKPOINT_BUDGET',
118+
description:
119+
'Redistribute remaining checkpoint budget evenly across remaining blocks instead of allowing a single block to consume the entire remaining budget.',
120+
...booleanConfigHelper(DefaultSequencerConfig.redistributeCheckpointBudget),
121+
},
122+
maxBlocksPerCheckpoint: {
123+
description: 'Computed max number of blocks per checkpoint from timetable.',
124+
},
115125
coinbase: {
116126
env: 'COINBASE',
117127
parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),

yarn-project/stdlib/src/interfaces/block-builder.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export type FullNodeBlockBuilderConfig = Pick<L1RollupConstants, 'l1GenesisTime'
6464
| 'maxTxsPerCheckpoint'
6565
| 'maxL2BlockGas'
6666
| 'maxDABlockGas'
67+
| 'redistributeCheckpointBudget'
68+
| 'perBlockAllocationMultiplier'
69+
| 'maxBlocksPerCheckpoint'
6770
>;
6871

6972
export const FullNodeBlockBuilderConfigKeys: (keyof FullNodeBlockBuilderConfig)[] = [
@@ -79,6 +82,9 @@ export const FullNodeBlockBuilderConfigKeys: (keyof FullNodeBlockBuilderConfig)[
7982
'maxL2BlockGas',
8083
'maxDABlockGas',
8184
'rollupManaLimit',
85+
'redistributeCheckpointBudget',
86+
'perBlockAllocationMultiplier',
87+
'maxBlocksPerCheckpoint',
8288
] as const;
8389

8490
/** Thrown when no valid transactions are available to include in a block after processing, and this is not the first block in a checkpoint. */

yarn-project/stdlib/src/interfaces/configs.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export interface SequencerConfig {
2727
maxDABlockGas?: number;
2828
/** Per-block gas budget multiplier for both L2 and DA gas. Budget = (checkpointLimit / maxBlocks) * multiplier. */
2929
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. */
33+
maxBlocksPerCheckpoint?: number;
3034
/** Recipient of block reward. */
3135
coinbase?: EthAddress;
3236
/** Address to receive fees. */
@@ -94,6 +98,8 @@ export const SequencerConfigSchema = zodFor<SequencerConfig>()(
9498
publishTxsWithProposals: z.boolean().optional(),
9599
maxDABlockGas: z.number().optional(),
96100
perBlockAllocationMultiplier: z.number().optional(),
101+
redistributeCheckpointBudget: z.boolean().optional(),
102+
maxBlocksPerCheckpoint: z.number().optional(),
97103
coinbase: schemas.EthAddress.optional(),
98104
feeRecipient: schemas.AztecAddress.optional(),
99105
acvmWorkingDirectory: z.string().optional(),
@@ -142,7 +148,9 @@ type SequencerConfigOptionalKeys =
142148
| 'maxTxsPerCheckpoint'
143149
| 'maxL2BlockGas'
144150
| 'maxDABlockGas'
145-
| 'perBlockAllocationMultiplier';
151+
| 'perBlockAllocationMultiplier'
152+
| 'redistributeCheckpointBudget'
153+
| 'maxBlocksPerCheckpoint';
146154

147155
export type ResolvedSequencerConfig = Prettify<
148156
Required<Omit<SequencerConfig, SequencerConfigOptionalKeys>> & Pick<SequencerConfig, SequencerConfigOptionalKeys>

yarn-project/validator-client/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,11 @@ L1 enforces gas and blob capacity per checkpoint. The node enforces these during
239239

240240
Per-block budgets prevent one block from consuming the entire checkpoint budget.
241241

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.
243243

244244
**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.
245245

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.
247247

248248
### Per-transaction enforcement
249249

@@ -259,7 +259,8 @@ Per-block budgets prevent one block from consuming the entire checkpoint budget.
259259
| `SEQ_MAX_DA_BLOCK_GAS` | *auto* | Per-block DA gas. Auto-derived from checkpoint DA limit / maxBlocks * multiplier. |
260260
| `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)`. |
261261
| `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. |
263264
| `VALIDATOR_MAX_L2_BLOCK_GAS` | *none* | Per-block L2 gas limit for validation. Proposals exceeding this are rejected. |
264265
| `VALIDATOR_MAX_DA_BLOCK_GAS` | *none* | Per-block DA gas limit for validation. Proposals exceeding this are rejected. |
265266
| `VALIDATOR_MAX_TX_PER_BLOCK` | *none* | Per-block tx count limit for validation. Proposals exceeding this are rejected. |

yarn-project/validator-client/src/checkpoint_builder.test.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ describe('CheckpointBuilder', () => {
8585
l1ChainId: 1,
8686
rollupVersion: 1,
8787
rollupManaLimit: 200_000_000,
88+
redistributeCheckpointBudget: false,
8889
...overrideConfig,
8990
};
9091

@@ -416,4 +417,127 @@ describe('CheckpointBuilder', () => {
416417
expect(capped.maxTransactions).toBeUndefined();
417418
});
418419
});
420+
421+
describe('redistributeCheckpointBudget', () => {
422+
it('evenly splits budget with multiplier=1', () => {
423+
const rollupManaLimit = 1_000_000;
424+
setupBuilder({
425+
redistributeCheckpointBudget: true,
426+
perBlockAllocationMultiplier: 1,
427+
maxBlocksPerCheckpoint: 5,
428+
rollupManaLimit,
429+
});
430+
431+
lightweightCheckpointBuilder.getBlocks.mockReturnValue([]);
432+
433+
const opts: PublicProcessorLimits = {};
434+
const capped = (checkpointBuilder as TestCheckpointBuilder).testCapLimits(opts);
435+
436+
// Fair share = ceil(1_000_000 / 5 * 1) = 200_000
437+
expect(capped.maxBlockGas!.l2Gas).toBe(200_000);
438+
});
439+
440+
it('computes fair share with multiplier=1.2, 5 max blocks, 2 existing', () => {
441+
const rollupManaLimit = 1_000_000;
442+
setupBuilder({
443+
redistributeCheckpointBudget: true,
444+
perBlockAllocationMultiplier: 1.2,
445+
maxBlocksPerCheckpoint: 5,
446+
rollupManaLimit,
447+
});
448+
449+
// 2 existing blocks used 400_000 mana total
450+
lightweightCheckpointBuilder.getBlocks.mockReturnValue([
451+
createMockBlock({ manaUsed: 200_000, txBlobFields: [10], blockBlobFieldCount: 20 }),
452+
createMockBlock({ manaUsed: 200_000, txBlobFields: [10], blockBlobFieldCount: 20 }),
453+
]);
454+
455+
const opts: PublicProcessorLimits = {};
456+
const capped = (checkpointBuilder as TestCheckpointBuilder).testCapLimits(opts);
457+
458+
// remainingMana = 600_000, remainingBlocks = 3, multiplier = 1.2
459+
// fairShare = ceil(600_000 / 3 * 1.2) = ceil(240_000) = 240_000
460+
expect(capped.maxBlockGas!.l2Gas).toBe(240_000);
461+
});
462+
463+
it('gives all remaining budget to last block (remainingBlocks=1)', () => {
464+
const rollupManaLimit = 1_000_000;
465+
setupBuilder({
466+
redistributeCheckpointBudget: true,
467+
perBlockAllocationMultiplier: 1.2,
468+
maxBlocksPerCheckpoint: 3,
469+
rollupManaLimit,
470+
});
471+
472+
// 2 existing blocks used 800_000 total
473+
lightweightCheckpointBuilder.getBlocks.mockReturnValue([
474+
createMockBlock({ manaUsed: 400_000, txBlobFields: [10], blockBlobFieldCount: 20 }),
475+
createMockBlock({ manaUsed: 400_000, txBlobFields: [10], blockBlobFieldCount: 20 }),
476+
]);
477+
478+
const opts: PublicProcessorLimits = {};
479+
const capped = (checkpointBuilder as TestCheckpointBuilder).testCapLimits(opts);
480+
481+
// remainingMana = 200_000, remainingBlocks = 1, multiplier = 1.2
482+
// fairShare = ceil(200_000 / 1 * 1.2) = 240_000. min(200_000, 240_000, 200_000) = 200_000
483+
expect(capped.maxBlockGas!.l2Gas).toBe(200_000);
484+
});
485+
486+
it('uses old behavior when redistributeCheckpointBudget is false', () => {
487+
const rollupManaLimit = 1_000_000;
488+
setupBuilder({
489+
redistributeCheckpointBudget: false,
490+
maxBlocksPerCheckpoint: 5,
491+
rollupManaLimit,
492+
});
493+
494+
lightweightCheckpointBuilder.getBlocks.mockReturnValue([
495+
createMockBlock({ manaUsed: 200_000, txBlobFields: [10], blockBlobFieldCount: 20 }),
496+
]);
497+
498+
const opts: PublicProcessorLimits = {};
499+
const capped = (checkpointBuilder as TestCheckpointBuilder).testCapLimits(opts);
500+
501+
// Old behavior: no fair share, just remaining budget = 800_000
502+
expect(capped.maxBlockGas!.l2Gas).toBe(800_000);
503+
});
504+
505+
it('redistributes DA gas across remaining blocks', () => {
506+
setupBuilder({
507+
redistributeCheckpointBudget: true,
508+
perBlockAllocationMultiplier: 1,
509+
maxBlocksPerCheckpoint: 4,
510+
});
511+
512+
lightweightCheckpointBuilder.getBlocks.mockReturnValue([]);
513+
514+
const opts: PublicProcessorLimits = {};
515+
const capped = (checkpointBuilder as TestCheckpointBuilder).testCapLimits(opts);
516+
517+
// fairShareDA = ceil(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT / 4 * 1)
518+
const expectedDA = Math.ceil(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT / 4);
519+
expect(capped.maxBlockGas!.daGas).toBe(expectedDA);
520+
});
521+
522+
it('redistributes tx count across remaining blocks', () => {
523+
setupBuilder({
524+
redistributeCheckpointBudget: true,
525+
perBlockAllocationMultiplier: 1,
526+
maxBlocksPerCheckpoint: 4,
527+
maxTxsPerCheckpoint: 100,
528+
});
529+
530+
// 1 existing block with 10 txs
531+
lightweightCheckpointBuilder.getBlocks.mockReturnValue([
532+
createMockBlock({ manaUsed: 0, txBlobFields: new Array(10).fill(1), blockBlobFieldCount: 20 }),
533+
]);
534+
535+
const opts: PublicProcessorLimits = {};
536+
const capped = (checkpointBuilder as TestCheckpointBuilder).testCapLimits(opts);
537+
538+
// remainingTxs = 90, remainingBlocks = 3, multiplier = 1
539+
// fairShareTxs = ceil(90 / 3 * 1) = 30
540+
expect(capped.maxTransactions).toBe(30);
541+
});
542+
});
419543
});

0 commit comments

Comments
 (0)