Skip to content

refactor(fast-inbox): port Leila Wang node cleanups (#25422) - #185

Draft
spalladino wants to merge 1 commit into
spl/fi-n8-required-prefixfrom
spl/fi-n9-leila-cleanups
Draft

spalladino wants to merge 1 commit into
spl/fi-n8-required-prefixfrom
spl/fi-n9-leila-cleanups

Conversation

@spalladino

@spalladino spalladino commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Ports Leila Wang's compatible node-side cleanups from #25422 while preserving her Git authorship. The changes trim
redundant archiver sync work, carry the Inbox rolling hash incrementally, validate exact prover message spans, make
streaming state immutable, and require builder message arrays.

Stack position

This is rung 22 of 25 in the Fast Inbox node stack, stacked on spl/fi-n8-required-prefix. The single commit is
authored by Leila Wang leizciw@gmail.com, with Santiago Palladino as committer. It reconstructs the compatible node-side subset of her commit
504acdc761
("Cleanups.") from old PR #25422. #25422 is not a clean standalone branch — its history replays copies of
#25436–#25445, all of which are already in this stack — so the commit was rebuilt against the destination rather
than cherry-picked, and the authorship was carried across by hand so it survives in Git and not only here.

What it changes

The archiver's sync iteration reads the block sync point straight off the blocks store instead of going through
getArchiverSynchPoint, which also reads the message sync point that this call site immediately discards. Nothing in
the rebuilt recovery flow (N3c, FI-S01/S02/S10, N7, FI-S12) reads the discarded half, so the substitution is exact.

The lightweight checkpoint builder no longer retains the checkpoint's entire message array only to hash it once at
completion. It carries the incremental Inbox rolling hash instead: the constructor takes the hash rather than the
list, startNewCheckpoint seeds it with the previous checkpoint's, resumeCheckpoint folds the already-consumed
bundle into it, each fully built block extends it, completeCheckpoint reads it, and clone copies it. A mid-build
throw still leaves the hash consistent with the blocks actually built, because the extension happens after the block
is complete — the same invariant the old comment described, now expressed on the hash directly.

The checkpoint prover validates its supplied message list before it enqueues anything. The list must span exactly the
previous block header's L1-to-L2 leaf count to the final block's; previously the checkpoint's start count was
derived from the list length, so a short or long list silently misassigned every block's slice and was only caught
by a header mismatch after a block had been fully re-executed. A block whose leaf count falls below its parent's is
also rejected rather than producing a backwards slice.

Streaming checkpoint state in the sequencer's proposal job becomes immutable. buildBlocksForCheckpoint and
buildForcedEndpointBlock return the advanced state instead of writing through an aliased object, and
proposeCheckpoint threads the returned state into both preflights. The behaviour this encodes — a failed build
attempt leaves the cursor untouched, a forced tail block's advance is what the checkpoint publishes on — was already
true and tested after N3b; this makes it structural rather than a property of where the mutation happened to sit.

BlockBuilderOptions.l1ToL2Messages becomes required. Callers pass an empty array where they previously omitted the
field, and the ?? [] / ?.length ?? 0 fallbacks in the validator's checkpoint builder and the sequencer's job go
away. The type, both builders and every mock move in the same commit, so no intermediate state fails to compile.

Deliberately excluded

  • The two Noir files in 504acdc761 (block_rollup_public_inputs_composer.nr, append_only_tree.nr). Their
    content landed in B4/B5.
  • Leila's failure / catch / failSubTreeProofs(failure ?? …) production hunks, and the test expectation that
    whenSubTreeProofsReady() rejects with the underlying cause rather than the generic
    "did not complete block processing". Those depend on the deferred proof-lifecycle work and must not enter
    this stack. As a consequence the two new prover tests read the validation message off the error the prover logs,
    and assert the observable consequences separately (nothing enqueued for a bad span; exactly one block started before
    a backwards count is caught). See the caveat below.
  • Santiago's test-only follow-up 011ee1f0481800fe90cacd8c4659bf860e9023de (typed-mock cleanup and retry
    regression). It can land later as a separate Santiago-authored follow-up once its expectations are checked against
    the simplified stack. It is not in this branch and is not attributed to Leila.
  • The rename of the validator builder's "an empty or absent message list leaves the tree untouched" test: that test
    does not exist in this repo.

Nothing deferred crept in: the diff has no DutyBudget, no unverifiable / checkpoint-unverifiable / sentinel
encoding, no R − P send cutoff, no Rollup artifact library map, no proposal-size or fee-modifier size change, no
override-type or Noir-fixture cleanup, and no proof completion/lifetime or archive-after-close work.

Verification

yarn build, yarn format and yarn lint are all clean from yarn-project.

Suites run in full, base → final on this branch:

package base final
archiver (JEST_MAX_WORKERS=1) 23 suites / 682 23 / 682
prover-client (excluding the two bb_prover_* real-proving files) 16 / 256 passed / 6 skipped 16 / 256 / 6 skipped
prover-node 10 / 191 10 / 194
sequencer-client 12 / 305 / 1 skipped 12 / 305 / 1 skipped
stdlib 132 / 1120 / 1 skipped 132 / 1120 / 1 skipped
validator-client 11 / 317 / 3 skipped 11 / 317 / 3 skipped

Red/green on the prover's span validation. Both new failure cases were written first and fail on unchanged code:
with no validation the run proceeds past the bad slice and whenSubTreeProofsReady() never settles, so each case
fails on the 120s suite timeout. After the change both pass in single-digit milliseconds. The third new case —
slicing three messages across blocks at leaf counts 12/12/13 over a parent at 10 — passes on unchanged code and is
the control proving the correct slicing is preserved.

The immutable streaming state is a refactor, so the evidence is the passing pre-change sequencer-client baseline
(12 / 305 / 1 skipped, unchanged after) plus two targeted mutations:

  • advancing the cursor before the build instead of after reddens "re-offers the same messages to the next block
    when a build fails" — the direct proof that a failed attempt leaves the cursor untouched;
  • dropping streamingState = result.streamingState so the preflights read the initial state instead of the returned
    one reddens 13 cases, including every forced-endpoint-tail case — the proof that returning the state is doing
    real work rather than restating an alias.

E2E suites were not run and are left to CI. The bb_prover_* real-proving files were excluded from the prover-client
runs by instruction.

Recorded caveat (no blocking, per the standing instruction)

Excluding the error-propagation hunk from the deferred proof-lifecycle work means the prover's sub-tree promise still rejects with
Checkpoint <id> did not complete block processing rather than the validation error itself, because the
executeCheckpoint finally rejects first and first rejection wins. The two new tests therefore assert the
validation message through the error the prover logs, and assert the behavioural consequences (isFailed(),
onFailed, and how many blocks were enqueued) separately. When the deferred proof-lifecycle work lands, those assertions should move back onto
the rejection, as in Leila's original. It also makes the red side of the red/green a 120s timeout rather than a fast
message mismatch.

@spalladino
spalladino added this pull request to stack #188 September 12, 2026 04:52
@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.

2 participants