refactor(sequencer): reuse the resolved range when a block ends on the endpoint - #181
Draft
spalladino wants to merge 1 commit into
Draft
spalladino wants to merge 1 commit into
spalladino wants to merge 1 commit into
Conversation
…e endpoint A block that ends exactly on the endpoint the resolver read takes that snapshot as its bundle instead of reading the same prefix again. The resolver reads `[cursor, endpointTotal)` and checks it against the cursor's rolling hash, so at that end it is the range `readStreamingRange` would produce. An end short of or past the endpoint still gets its own read, so no endpoint hash is ever carried into a different prefix. Also corrects the selection JSDoc: ending at the further of the lookup and the safe local step describes a non-final block; the final block lands on the resolved boundary and may consume fewer messages than the local log alone would allow.
spalladino
added this pull request to stack #188
September 12, 2026 04:52
spalladino
removed this pull request from stack #188
September 15, 2026 22:42
spalladino
added this pull request to stack #217
September 15, 2026 22:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a streaming block has to consult L1,
selectStreamingBundleresolves a live Inboxbucket end and then decides where the block actually stops: the final block lands on the
resolved endpoint, any other block takes the further of that endpoint and the safe local
step. Whatever it picks, the block then re-read the prefix from the local log, even when the
end it picked was the endpoint the resolver had just read.
The resolver already reads
[cursor, endpointTotal)from one snapshot and checks both endsof it — the start against the cursor's rolling hash, the end against the live bucket's hash.
When selection stops exactly at
endpointTotal, that range is by construction the range there-read would return, so the block now takes the resolver's snapshot as its bundle. A block
whose end falls short of or past the endpoint still reads its own range.
Why the equality guard is the safety property, not an optimization
The bundle carries both the messages and the position the cursor advances to, and that
position's rolling hash is what the block signs. Handing back the endpoint's range for a
block that ends somewhere else would consume the wrong messages and sign the hash at the
endpoint instead of the hash at
end. So the reuse is only sound becauseend === endpointTotalproves the two ranges have identical bounds:resolveEndpointcallsgetL1ToL2MessageRange(cursor.totalMessageCount, endpointTotal)and returns that result as
range, withendpointset torange.end.range.end.totalMessageCountat therequested upper bound (an empty range returns
start, whose count equals it), soendpointTotalin the job is exactly the bound the range was read at.start.rollingHashdisagrees with thecursor, which is the one check
readStreamingRangeadds on top of the read.The only difference that remains is the moment of the read, and the reuse is the stricter
option there: one snapshot is used for both the endpoint authentication and the bundle,
rather than two reads that could straddle a local log change. The forced tail-block path in
the same file already consumed
resolved.range.messagesdirectly, so this makes the ordinaryselection path consistent with it.
JSDoc correction
The method's doc said the block "then ends at the further of what the lookup allows and the
safe local step… a block that consulted L1 may legitimately end inside a bucket". That is
only true of a non-final block. The final block ends exactly on the resolved boundary, so
when the last live boundary within reach sits behind the safe local step it consumes fewer
messages than the local log alone would allow — the opposite of "never consumes less". The
paragraph now states both cases; "Nothing is retained: the next attempt decides again" is
unchanged.
Stack position
This is rung 18 of 25 in the Fast Inbox node stack, stacked on
spl/fi-s09-drop-bucket-hint.Source
Reconstructs the final range-read and comment hunks of
5397c7d61f("refactor: drop unusedstreaming Inbox plumbing") from old PR #25440. The destination carried the source's exact
pre-image for both hunks, so the upstream wording and condition port over unchanged.
This completes the four-way split of
5397c7d61fin this stack: its archiver hunks landedas FI-S10, its pre-gossip bucket-hint hunks as FI-S09, and its range-reuse hunks here. The
fourth part — the
ethereum/src/contracts/chain_state_override.tsoverride-type derivationand the
rollup_fixture_builder.nrfixed-message-subtree fixture deletion — belongs tothe deferred override-type and Noir-fixture cleanup and stays out of this stack entirely.
Validation
This changes which range a block returns, so red/green applies. Three tests were added to
checkpoint_proposal_job.test.ts, one per case, asserting the bounds of every localmessage-range read alongside the bundle sizes and signed prefixes:
[256, 300)isread once. On the unchanged code it was read twice, so this case is the red one: it fails
before the change and passes after.
and reads
[700, 1024)then[700, 956), signing the hash at 956.local step reaches 768, so the block reads
[700, 750)then[700, 768)and signs 768.The last two pass on the unchanged code by design: they are the controls that catch the
mutation that matters. Making the reuse unconditional — returning
resolved.rangewith noequality guard, which is the change that would sign a wrong hash — turns both of them red
along with three pre-existing selection tests (five failures in total): the short block
consumes 324 messages instead of 256 + 68, and the past-the-endpoint block consumes 50
instead of 68 and signs the boundary at 750 rather than 768. The mutation was reverted.
yarn workspace @aztec-labs/sequencer-client test(full package): 12 suites, 302 passed, 1skipped at the base; 12 suites, 305 passed, 1 skipped after — the three added tests and
nothing else.
yarn build,yarn formatandyarn lintare clean fromyarn-project.No e2e suites were run; they are left to CI.