Skip to content

feat(fast-inbox): introduce inboxRollingHash end to end, dual with inHash (A-1373) - #24600

Closed
spalladino wants to merge 10 commits into
spl/a-1372-msg-bundle-componentsfrom
spl/a-1373-inbox-rolling-hash
Closed

feat(fast-inbox): introduce inboxRollingHash end to end, dual with inHash (A-1373)#24600
spalladino wants to merge 10 commits into
spl/a-1372-msg-bundle-componentsfrom
spl/a-1373-inbox-rolling-hash

Conversation

@spalladino

@spalladino spalladino commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Stacked on #24587 (A-1372). Part of the AZIP-22 Fast Inbox work.

What

Introduces the inboxRollingHash — a truncated-to-field sha256 rolling chain over the L1→L2 message leaves — end to end, carried as a dual of the legacy inHash. The legacy inHash remains authoritative; the rolling hash is computed and threaded everywhere but not yet enforced on L1, so this change is behavior-preserving pre-flip.

Each link is h' = sha256ToField(h_be32 || leaf_be32) with the top byte of the digest zeroed, genesis value zero.

Changes

Circuits (noir)

  • Parity base computes the rolling chain over its real message leaves (start_rolling_hashend_rolling_hash, num_msgs), asserting trailing padding lanes are zero. Parity root asserts chunk continuity (children[i].start == children[i-1].end) and sums the counts.
  • Block and checkpoint rollup public inputs carry a {start, end} rolling-hash pair, propagated exactly like in_hash. Checkpoint merges assert right.start == left.end (decision 11 anchoring).
  • The checkpoint header gains inbox_rolling_hash immediately after in_hash.
  • The root rollup public inputs expose the {previous, end} pair sourced from the merged checkpoint public inputs, so the epoch's consumed chain segment is passed through to proof verification.

L1 (Solidity)

  • ProposedHeaderLib serializes the new header field (header 348 → 380 bytes).
  • PublicInputArgs gains previousInboxRollingHash / endInboxRollingHash; EpochProofLib places them at public-input positions 3 and 4 (header hashes, fees, constants and blob inputs shift by two). Both values are deliberately unvalidated until the Fast Inbox flip — for now they are pass-through only.

TypeScript

  • updateInboxRollingHash / accumulateInboxRollingHash mirror the circuit chain; getPreviousCheckpointInboxRollingHash sources the previous checkpoint's end value (returns zero for checkpoint ≤ 1). The sequencer, validator, and prover populate the header field; the orchestrator threads per-base start hashes; the prover-node publisher fills the two new PublicInputArgs.
  • RootRollupPublicInputs gains the pair with matching serialization, conversion, factories and viem types.

Constants

  • CHECKPOINT_HEADER_LENGTH 13 → 14, BLOCK_ROLLUP_PUBLIC_INPUTS_LENGTH 56 → 58, CHECKPOINT_ROLLUP_PUBLIC_INPUTS_LENGTH 149 → 151, ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH 111 → 113.
  • Circuit ABIs changed, so the base branch's committed pinned-build.tar.gz no longer matches the compiled circuits. It is dropped here rather than carried stale — bootstrap.sh recompiles from source whenever the pin is absent — and regenerated once the ABI settles. (feat(fast-inbox): message-bundle components in rollup-lib (A-1372) #24587 keeps the base pin untouched, so the artifacts stay pinned on the train until this PR.)

Testing

  • yarn build green; forge test green (870 passed); noir rollup-lib root suites green (176 passed).
  • stdlib serde, prover-node publisher, and the checkpoint-sub-tree / top-tree orchestrator suites green.
  • Cross-chain l1_to_l2 e2e confirms the header field round-trips through L1 and the archiver (decoded inboxRollingHash = 0 for the genesis checkpoint, as expected). This suite is registered flaky (.test_patterns.yml); the repeated-consumption cases exhibit a pre-existing nullifier-sync timing race unrelated to this change.

Comment on lines +146 to +155
const start = baseParityIndex * NUM_MSGS_PER_BASE_PARITY;
const realMessages = this.l1ToL2Messages.slice(start, start + NUM_MSGS_PER_BASE_PARITY);
const messages = padArrayEnd(realMessages, Fr.ZERO, NUM_MSGS_PER_BASE_PARITY);
// Thread the rolling hash: this base's start is the chain value after all real messages in earlier bases, so the
// four bases chain sequentially and the parity root ends at the checkpoint's rolling hash. Only real (non-padding)
// messages are absorbed, matching the proposer's `accumulateInboxRollingHash`.
const startRollingHash = accumulateInboxRollingHash(
this.startInboxRollingHash,
this.l1ToL2Messages.slice(0, start),
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. Aren't the this.l1ToL2Messages already padded? And shouldn't realMessages drop the pads? Or is that left for a future PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No — this.l1ToL2Messages is unpadded here. It's the array handed to CheckpointSubTreeOrchestrator.start, which comes from l1ToL2MessageSource.getL1ToL2Messages(checkpointNumber) — the archiver's message_store.getL1ToL2Messages, which returns only the real messages (e.g. archiver-sync.test.ts expects length 0 for an empty checkpoint). Padding happens downstream in two places: per-base in this very method (padArrayEnd to NUM_MSGS_PER_BASE_PARITY, unchanged from before this PR) and inside appendL1ToL2MessagesToTree for the world-state insert. So realMessages is exactly the real slice, realMessages.length is the true per-base num_msgs, and the rolling hash accumulates over real messages only — matching the proposer's accumulateInboxRollingHash over the same raw archiver list. (In #24603 this class additionally gains an explicit paddedL1ToL2Messages for the sponge/first-block bundle, padded once in the constructor, which keeps the two representations clearly separated.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? How were those inputs generated before for the tests? Or are these new tests?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The committed crates/rollup-*/Prover.toml sample inputs (which CI nargo executes) had to be regenerated because this PR changes the rollup public-input ABIs. Previously they were regenerated by running the heavyweight single-node prover e2e (end-to-end/src/single-node/prover/server/full.test.ts) with AZTEC_GENERATE_TEST_DATA=1 — and that test's dump list has several of the affected circuits commented out (rollup-block-merge, rollup-block-root-first-empty-tx, …), so it couldn't refresh everything this PR touches. The new file is a lightweight prover-client-level harness: it drives the simulated orchestrator through a handful of scenarios chosen to produce every changed circuit's inputs and dumps them via the same updateProtocolCircuitSampleInputs mechanism. It's describe.skip unless AZTEC_GENERATE_TEST_DATA=1, so it's a no-op in normal CI. Follow-up worth considering: dedup with (or replace) the e2e dump list so there's one canonical regeneration path.

@spalladino
spalladino force-pushed the spl/a-1373-inbox-rolling-hash branch from c84a6cc to c818a3d Compare July 9, 2026 00:40
@spalladino
spalladino force-pushed the spl/a-1372-msg-bundle-components branch from e5d2b0e to 1e17c26 Compare July 13, 2026 21:51
@spalladino
spalladino force-pushed the spl/a-1373-inbox-rolling-hash branch from fe246df to b76ab03 Compare July 13, 2026 21:52
@spalladino
spalladino force-pushed the spl/a-1372-msg-bundle-components branch from 1e17c26 to cc40d0b Compare July 14, 2026 19:55
@spalladino
spalladino force-pushed the spl/a-1373-inbox-rolling-hash branch from b76ab03 to d76fe1b Compare July 14, 2026 20:01
@spalladino spalladino changed the title feat: introduce inboxRollingHash end to end, dual with inHash (A-1373) feat(fast-inbox): introduce inboxRollingHash end to end, dual with inHash (A-1373) Jul 17, 2026
@spalladino
spalladino force-pushed the spl/a-1372-msg-bundle-components branch from cc40d0b to 1073d5b Compare July 17, 2026 19:38
@spalladino
spalladino force-pushed the spl/a-1373-inbox-rolling-hash branch 2 times, most recently from 401cfcb to ada3771 Compare July 19, 2026 14:05
@spalladino
spalladino force-pushed the spl/a-1372-msg-bundle-components branch from 1073d5b to 40ddee2 Compare July 19, 2026 14:05
@AztecBot

AztecBot commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Flakey Tests

🤖 says: This CI run detected 1 tests that failed, but were tolerated due to a .test_patterns.yml entry.

\033FLAKED\033 (8;;http://ci.aztec-labs.com/8d8dd6bfc6a3303b�8d8dd6bfc6a3303b8;;�):  yarn-project/end-to-end/scripts/run_test.sh simple src/single-node/proving/multi_proof.test.ts (86s) (code: 0) group:e2e-p2p-epoch-flakes

Adds the Fast Inbox (AZIP-22) rolling-hash commitment as a dual of the
legacy inHash, flowing through parity circuits, block/checkpoint rollup
public inputs, the checkpoint header, L1, and the node/prover.

Circuits (noir):
- Parity base absorbs only real leaves into a truncated-to-field sha256
  chain (accumulate_inbox_rolling_hash, guarded by num_msgs +
  assert_trailing_zeros); merkle roots keep absorbing padding. Parity
  public inputs gain start_rolling_hash / end_rolling_hash / num_msgs;
  parity root threads the four base segments sequentially and sums counts.
- BlockRollupPublicInputs and CheckpointRollupPublicInputs carry a
  {start,end} pair propagated exactly like in_hash (first block root sets
  it, merges take left's, validate asserts right's is zero). Checkpoint
  merge asserts right.start == left.end (decision 11). CheckpointHeader
  gains inbox_rolling_hash (the end value) immediately after in_hash.
- Root rollup public inputs are unchanged: exposing the epoch pair is
  inseparable from the L1 epoch-proof anchoring that is out of scope here
  (FI-08/FI-14), so it is deferred with the rest of L1 anchoring.

TS: stdlib inbox_rolling_hash mirror (unit-tested against the FI-02
vectors), CheckpointHeader / parity / block / checkpoint PI classes,
noir-protocol-circuits-types conversions, orchestrator threading of
per-base start hashes + counts, and node header population at the
sequencer / prover / validator sites (sourced from the previous
checkpoint header via getPreviousCheckpointInboxRollingHash).

L1: ProposedHeader + ProposedHeaderLib.hash pack inboxRollingHash after
inHash (no new propose checks); test fixtures + headerHash regenerated.
Add the {previous, end} inbox rolling-hash pair to RootRollupPublicInputs so
the epoch's consumed chain segment is carried through to proof verification.
The root rollup sources the pair from the merged checkpoint public inputs;
continuity within the range is already enforced by the checkpoint merges.

On L1, PublicInputArgs gains previousInboxRollingHash/endInboxRollingHash and
EpochProofLib places them at public-input positions 3 and 4 (header hashes,
fees, constants and blob inputs shift by two). Both values are deliberately
unvalidated until the Fast Inbox flip, when they will be checked against the
per-checkpoint records written at propose; for now they are pass-through only.

Also fixes two ivc-integration benchmark generators whose hand-built parity
base inputs were missing start_rolling_hash/num_msgs.
…1373)

Exposing the inbox rolling hash on the parity-root and rollup public inputs
changed the ABI of the block-root-first variants, block-merge, checkpoint-merge
and root circuits, but their committed crates/rollup-*/Prover.toml sample inputs
were not refreshed. CI runs `nargo execute` against these fixtures, so all six
failed to deserialize (e.g. missing parity_root.public_inputs.start_rolling_hash
and previous_rollups[].public_inputs.start_inbox_rolling_hash).

Regenerate the six stale fixtures. The circuits push their serialized inputs via
pushTestData when run through the prover, so a new prover-client test drives
representative epochs through the simulated orchestrator and dumps the captured
inputs, replacing the dead orchestrator_single_checkpoint.test.ts regeneration
path. The suite is skipped unless AZTEC_GENERATE_TEST_DATA=1 is set.
A-1373 changes the rollup public-input ABIs, so the base branch's committed
pinned-build.tar.gz no longer matches the compiled circuits. Remove it rather than
carry a stale archive: bootstrap recompiles from source whenever the pin is absent,
and the pin is regenerated once the ABI settles.
getEpochProofPublicInputs sources the fee recipient/value from the supplied
checkpoint headers but never validated that they hash to the stored headers, so
a mismatch only reverted on the on-chain submit path instead of when an
off-chain caller assembles the public inputs. Call verifyHeaders in the getter
as well, and import ProposedHeaderLib in Rollup.t.sol so the test that exercises
this path compiles.
…h header (A-1373)

The checkpoint header now carries inboxRollingHash, changing the checkpoint
hash preimage. The stored format is unchanged (read-defaultable), so only the
snapshot is regenerated; no PXE_DATA_SCHEMA_VERSION bump.
…ng hash (A-1373)

The proposal handler, sequencer job, and prover node now source the parent
checkpoint's inboxRollingHash. Serve zeroed parent headers from the mocks so
scenarios beyond the genesis checkpoint resolve their chain start: the
validator suite stubs the proposed-checkpoint fallback (getCheckpointData
doubles as the already-published existence check), the sequencer suite stubs
getCheckpointData, and the prover-node suite serves synthetic ancestors below
the mined window.
… submission (A-1373)

submitEpochRootProof verified the supplied checkpoint headers and then hit
getEpochProofPublicInputs, which verified them again, hashing every header in
the epoch twice per submission. Split the assembly into a private
computeEpochProofPublicInputs that takes the headers as already verified, and
keep the header check on the externally reachable getter, which is the entry
point off-chain callers use and must not trust caller-supplied fee fields.
…n checkpoint prover (A-1373)

The offline rerun helper rebuilds each CheckpointProver from a downloaded job; it now
supplies the checkpoint's chain-start rolling hash alongside the previous block header.
@spalladino

Copy link
Copy Markdown
Contributor Author

Superseded by #25036.

The Fast Inbox stack has been regrouped from 20 per-issue PRs (plus 2 umbrellas) down to 3 area PRs plus an umbrella, and rebased onto merge-train/spartan including #25007 (the noir-projects fnd//labs/ split). This PR's diff is preserved verbatim as a single squashed commit in #25036, and this description is preserved in that commit's message.

New structure: #25036 (circuits + L1) → #25037 (node + flip) → #25038 (cleanup), with #25039 as the full-stack umbrella. The original branch for this PR is left on the remote as a recovery point.

Closing here to cut the rebase and review overhead of maintaining 22 PRs; the work is not abandoned.

@spalladino spalladino closed this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants