Skip to content

fix(sequencer): reserve blob space for a transaction-less checkpoint tail block - #25436

Draft
spalladino wants to merge 2 commits into
spl/fi2-review-fixesfrom
spl/fi2-f16-rescue-tail-blob
Draft

fix(sequencer): reserve blob space for a transaction-less checkpoint tail block#25436
spalladino wants to merge 2 commits into
spl/fi2-review-fixesfrom
spl/fi2-f16-rescue-tail-blob

Conversation

@spalladino

Copy link
Copy Markdown
Contributor

Background

The L1 Inbox stores messages sent from Ethereum to Aztec and groups them into buckets. A checkpoint may consume
messages incrementally across several L2 blocks, but its final message count must equal the end of a live Inbox
bucket before the checkpoint can be published. If the scheduled block-building sub-slots finish before that
boundary is reached, the proposer can add a transaction-less tail block that consumes the remaining messages.

The problem

A transaction-less block still serializes block-end metadata into the checkpoint's blobs. The existing packing
calculation reserved space for the block currently being built, but not for a possible tail block.

The test captures the failure with the protocol's actual blob geometry:

  1. A checkpoint has six blobs of 4,096 fields, for 24,576 fields in total. Its one-field checkpoint-end marker
    leaves 24,575 fields for blocks.
  2. A transaction-less block serializes to seven fields, or 224 bytes.
  3. Suppose the existing block has used 24,551 fields and the checkpoint may still contain two more blocks.
  4. The old calculation reserved seven fields for the current block and offered the remaining 17 fields to its
    transactions: 24,575 - 24,551 - 7 = 17.
  5. If those 17 fields are used, the current block brings usage to 24,575. If message consumption then stops at a
    prefix that is not a live bucket endpoint, the required seven-field tail no longer fits.
  6. The proposer cannot produce a publishable checkpoint and loses the slot, even though omitting seven transaction
    fields would have left enough blob capacity for the tail.

What this changes

While another block can still follow, proposer-side packing now reserves two sets of block-end fields: seven for
the block being built and seven for a possible transaction-less tail. In the example, the transaction allowance is
therefore 10 fields:

24,575 - 24,551 - 7 - 7 = 10

After using those 10 fields and writing the current block's seven fields, exactly seven fields remain. A real tail
block can consume that reservation, bringing block data to 24,575 fields; the separately accounted checkpoint-end
marker brings the encoded checkpoint to the full 24,576-field capacity.

The reservation is released when building the last block the checkpoint is allowed to contain, because no tail can
follow it. The transaction allowance is also floored at zero when the available capacity is smaller than the
required overhead.

What this does not do

This changes only the local proposer's transaction-packing policy. A validator re-executing another node's proposal
does not apply the reservation, so the change cannot make an otherwise valid peer proposal fail validation.

The reservation protects blob capacity only. It does not reserve execution time, guarantee that the tail can be
built before the duty ends, or make an unavailable Inbox endpoint reachable. It does not change message limits,
per-block limits, the maximum checkpoint block count, checkpoint timing, or total blob capacity.

Testing

Unit tests serialize a real transaction-less L2Block and pin its size at seven fields and 224 bytes. They cover
near-capacity packing, the exact-fit case, being one field short, a zero transaction allowance, releasing the
reservation on the last possible block, consuming it with a real tail, and counting the checkpoint-end marker only
once. A separate case confirms that validator re-execution does not reserve tail space.

The tests exercise checkpoint-builder accounting; they do not simulate the full timed proposer flow or prove that
an extra block can always finish within the slot.

@spalladino
spalladino added this pull request to stack #25417 September 9, 2026 21:01
@spalladino
spalladino force-pushed the spl/fi2-f16-rescue-tail-blob branch from 128ccf5 to b60c9f6 Compare September 9, 2026 21:15
@spalladino
spalladino removed this pull request from stack #25417 September 9, 2026 21:16
@spalladino
spalladino changed the base branch from spl/fi2-message-only-docs to spl/fi2-review-fixes September 9, 2026 21:16
@spalladino
spalladino added this pull request to stack #25441 September 9, 2026 21:16
@spalladino
spalladino force-pushed the spl/fi2-f16-rescue-tail-blob branch from b60c9f6 to 22b4c21 Compare September 10, 2026 02:46
@spalladino
spalladino force-pushed the spl/fi2-f16-rescue-tail-blob branch 2 times, most recently from ce37387 to 0807d63 Compare September 10, 2026 05:11
@spalladino
spalladino removed this pull request from stack #25441 September 10, 2026 11:49
@spalladino
spalladino added this pull request to stack #25448 September 10, 2026 11:49
@spalladino
spalladino removed this pull request from stack #25448 September 10, 2026 12:09
@spalladino
spalladino changed the base branch from spl/fi2-review-fixes to spl/fi2-message-only-docs September 10, 2026 12:10
@spalladino
spalladino changed the base branch from spl/fi2-message-only-docs to spl/fi2-review-fixes September 10, 2026 12:10
@spalladino
spalladino added this pull request to stack #25449 September 10, 2026 12:11
@spalladino
spalladino force-pushed the spl/fi2-f16-rescue-tail-blob branch from 0807d63 to 3be9f66 Compare September 10, 2026 12:48
@spalladino
spalladino removed this pull request from stack #25449 September 10, 2026 13:04
@spalladino
spalladino changed the base branch from spl/fi2-review-fixes to spl/fi2-message-only-docs September 10, 2026 13:05
@spalladino
spalladino changed the base branch from spl/fi2-message-only-docs to spl/fi2-review-fixes September 10, 2026 13:05
@spalladino
spalladino added this pull request to stack #25451 September 10, 2026 13:05
@spalladino
spalladino force-pushed the spl/fi2-f16-rescue-tail-blob branch from 3be9f66 to a29d703 Compare September 10, 2026 13:50
@spalladino
spalladino removed this pull request from stack #25451 September 10, 2026 13:53
@spalladino
spalladino changed the base branch from spl/fi2-review-fixes to spl/fi2-message-only-docs September 10, 2026 13:54
@spalladino
spalladino changed the base branch from spl/fi2-message-only-docs to spl/fi2-review-fixes September 10, 2026 13:54
@spalladino
spalladino added this pull request to stack #25452 September 10, 2026 13:54
@spalladino
spalladino marked this pull request as draft September 10, 2026 14:22
spalladino and others added 2 commits September 10, 2026 11:27
…tail block

When a proposer runs out of sub-slots with the consumption cursor at a message prefix that is not a live L1
Inbox bucket end, it appends one transaction-less block to reach one so the checkpoint can be published at all.
That block still writes its own block-end fields, but checkpoint blob accounting only ever reserved the current
block's end fields, so ordinary packing could fill the checkpoint until the tail no longer fits.

Packing now holds back one block's worth of end fields — `getNumBlockEndBlobFields()`, measured at 7 fields and
224 bytes for a real transaction-less block — while the checkpoint can still gain another block, releases it on
the last block the checkpoint can hold, and lets the actual tail consume it by writing its own end fields. The
checkpoint end marker is still deducted once from the total, so a tail appended to an existing checkpoint costs
seven fields rather than eight. The transaction allowance is floored at zero so a full checkpoint reports no
room instead of a negative one.

This is local proposer packing policy: re-executing a peer's proposal reserves nothing, so no otherwise valid
proposal becomes rejectable. Reserving blob space does not reserve build time or guarantee the extra block can
be built. Message, per-block, block-count, timing and total blob limits are unchanged.
The tail reservation guard already requires isBuildingProposal, so the
hoisted remainingBlocks and its `: 1` fallback bought nothing: inline the
count into the guard and let the fair-share divisor keep the local it had
before. `Math.max(1, n) > 1` and `n > 1` agree, so packing is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@spalladino
spalladino force-pushed the spl/fi2-f16-rescue-tail-blob branch from a29d703 to e546fe1 Compare September 10, 2026 14:27
@spalladino
spalladino removed this pull request from stack #25452 September 10, 2026 14:31
@spalladino
spalladino changed the base branch from spl/fi2-review-fixes to spl/fi2-message-only-docs September 10, 2026 14:31
@spalladino
spalladino changed the base branch from spl/fi2-message-only-docs to spl/fi2-review-fixes September 10, 2026 14:31
@spalladino
spalladino added this pull request to stack #25453 September 10, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant