Skip to content

feat!: improve L2ToL1MessageWitness API#21231

Merged
nventuro merged 13 commits intomerge-train/fairiesfrom
nico/f-388-feature-support-unique-message-identifier-in
Mar 12, 2026
Merged

feat!: improve L2ToL1MessageWitness API#21231
nventuro merged 13 commits intomerge-train/fairiesfrom
nico/f-388-feature-support-unique-message-identifier-in

Conversation

@nventuro
Copy link
Contributor

@nventuro nventuro commented Mar 7, 2026

The old API had two issues:

  • it required knowledge of the epoch for the tx in which the message was included, which is hard to acquire
  • it did not support duplicate messages (i.e. same hash) in the epoch at all - it only found the first one

This changes things so that users no longer need to know the epoch, and instead query with (message, txHash). If more than one message is found in the tx matching message, we throw and request an additional optional param messageIndexInTx. If this is passed, then we assert that the message at that index indeed matches message.

Most users should not need to pass the index, but it is there in case it is needed. It is expected that apps would know the messages they're interested in, and can look at indices from the tx effects.

Doing this requires fetching the epoch as well as checkpoint, block and tx indices from the node. There was no API for checkpoint data, so I exposed getCheckpointsDataForEpoch so that we can find the checkpoint's index in the epoch. I also added the epoch number to the tx receipt.


I used Claude heavily here as I don't really know my way around the node and archiver code, but I think the result makes sense.

Closes #20874

nventuro and others added 3 commits March 6, 2026 23:32
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactors `computeL2ToL1MembershipWitness` to take a `txHash` instead of requiring the caller to pass the epoch number. The epoch, checkpoint index, block index, and tx index are now resolved internally via the node. Also adds `epochNumber` to `L2ToL1MembershipWitness` type, adds `getCheckpointsDataForEpoch` to `AztecNode`, and supports explicit `messageIndexInTx` for disambiguating duplicate messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nventuro nventuro requested a review from spalladino March 7, 2026 01:29
@nventuro nventuro requested a review from a team as a code owner March 7, 2026 01:29
this.getProvenBlockNumber(),
this.getCheckpointedL2BlockNumber(),
this.getFinalizedL2BlockNumber(),
this.getBlock(blockNumber),
Copy link
Contributor

Choose a reason for hiding this comment

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

This call is expensive, since it deserializes all txs in a block. I'd prefer if we could instead store the slot along with the IndexedTxEffect, so we get it automatically when we do getTxEffect.

Alternatively, we can use getBlockDataFromBlockStorage(this.#blocks.getAsync(blockNumber)) to get just the block header instead of the entire block.


const [messagesInEpoch, block, txEffect, checkpointsData] = await Promise.all([
node.getL2ToL1Messages(epochNumber),
node.getBlock(blockNumber),
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's get the block header only rather than the whole block

Comment on lines +129 to +134
const [messagesInEpoch, block, txEffect, checkpointsData] = await Promise.all([
node.getL2ToL1Messages(epochNumber),
node.getBlock(blockNumber),
node.getTxEffect(txHash),
node.getCheckpointsDataForEpoch(epochNumber),
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels like we're getting a ton of data from the node, when we actually need a lot less. I'd prefer if instead we stored the indices needed in the IndexedTxEffect. We are already storing the txIndexInBlock, we could just add the ones for block and checkpoint if needed.

Actually, why don't we just expose a method getL2ToL1MembershipWitness and perform the computeL2ToL1MembershipWitnessFromMessagesInEpoch on the node directly, so we don't need to send whole blocks and checkpoints down the wire? It'd also mimic the API we have for getL1ToL2MessageMembershipWitness.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered that, but this seemed like a more disruptive change since we'd bake in a bunch of new behavior into the node. E.g. my handling of the optional msgIndexInTx feels a bit out of place there.

Fine with taking that approach if you think it's better though. If not, adding stuff to IndexedTxEffect seems like a good plan.

@spalladino
Copy link
Contributor

If more than one message is found in the tx matching message, we throw and request an additional optional param messageIndexInTx.

Do you think it'd be possible to tweak the private kernels so L2-to-L1 messages are unique, by hashing them with a counter or something? Not for alpha, of course. We made this change back in the day for L1-to-L2 messages and it made things so much easier.

Copy link
Contributor

@spalladino spalladino left a comment

Choose a reason for hiding this comment

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

Given the timelines, I think you're right in that it's better to keep the node changes to a minimum for now. Let's just make the small changes pointed out and ship it, and avoid bundling the complexity of computing the merkle tree on the fly on the node, or the database schema changes.

Let's also log an issue to revisit this later. We have several options, not all mutually exclusive:

  • Computing the merkle tree on the node if it's cheap enough. We could also precompute it or cache it for future accesses.
  • Add the missing indices (blockIndexWithinCheckpoint, etc) to the IndexedTxEffect so we can rapidly identify the leaf of the tree we need.
  • Add a missing indexWithinEpoch to the CheckpointData, so we can combine that with the tx's indexWithinBlock and the block's indexWithinCheckpoint to identify the leaf.

Apologies for the back and forth!

nventuro and others added 2 commits March 11, 2026 21:11
Avoids deserializing all transactions in the block when only the slot
number is needed to compute the epoch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@nventuro nventuro requested a review from spalladino March 11, 2026 22:04
@nventuro nventuro enabled auto-merge (squash) March 11, 2026 22:04
@nventuro nventuro added the claudebox Owned by claudebox. it can push to this PR. label Mar 12, 2026
@nventuro nventuro disabled auto-merge March 12, 2026 15:29
nventuro and others added 2 commits March 12, 2026 17:12
Co-authored-by: Santiago Palladino <santiago@aztecprotocol.com>
@nventuro nventuro enabled auto-merge (squash) March 12, 2026 20:13
@nventuro nventuro merged commit a8b2277 into merge-train/fairies Mar 12, 2026
10 checks passed
@nventuro nventuro deleted the nico/f-388-feature-support-unique-message-identifier-in branch March 12, 2026 23:07
@AztecBot
Copy link
Collaborator

❌ Failed to cherry-pick to v4 due to conflicts. (🤖) View backport run.

AztecBot pushed a commit that referenced this pull request Mar 12, 2026
Cherry-pick of a8b2277 from next. Conflicts left as-is for review.
AztecBot pushed a commit that referenced this pull request Mar 12, 2026
Cherry-pick of a8b2277 with conflicts preserved for review.
sklppy88 added a commit that referenced this pull request Mar 13, 2026
## Summary

PR #21231 changed the `computeL2ToL1MembershipWitness` API signature
from `(node, epoch, message)` to `(node, message, txHash)`, but the
`example_swap` docs example (added in PR #20233) was written against the
old API. This caused the merge-train CI to fail with `TypeError: Cannot
read properties of undefined (reading 'siblingPath')` because the
function received wrong arguments and returned `undefined`.

## Changes

- Removed `RollupContract` import and usage (no longer needed to
manually compute epoch)
- Updated both `computeL2ToL1MembershipWitness` calls to use new
signature: `(node, message, txHash)`
- Get epoch numbers from the returned witness (`witness.epochNumber`)
instead of computing manually

ClaudeBox log: https://claudebox.work/s/5eb93c1235ed3384?run=2

Co-authored-by: esau <152162806+sklppy88@users.noreply.github.com>
github-merge-queue bot pushed a commit that referenced this pull request Mar 13, 2026
BEGIN_COMMIT_OVERRIDE
fix: skip oracle version check for pinned protocol contracts (#21349)
fix: not reusing tags of partially reverted txs (#20817)
feat: move storage_slot from partial commitment to completion hash
(#21351)
feat: offchain reception (#20893)
fix: handle workspace members in needsRecompile crate collection
(#21284)
fix(aztec-nr): return Option from decode functions and fix event
commitment capacity (#21264)
fix: handle bad note lengths on compute_note_hash_and_nullifier (#21271)
fix: address review feedback from PRs #21284 and #21237 (#21369)
fix: claim contract & improve nullif docs (#21234)
feat!: auto-enqueue public init nullifier for contracts with public
functions (#20775)
fix: search for all note nonces instead of just the one for the note
index (#21438)
fix: set anvilSlotsInAnEpoch in e2e_offchain_payment to prevent
finalization race (#21452)
fix: complete legacy oracle mappings for all pinned contracts (#21404)
fix: correct inverted constrained encryption check in message delivery
(#21399)
feat!: improve L2ToL1MessageWitness API (#21231)
END_COMMIT_OVERRIDE
nventuro added a commit that referenced this pull request Mar 13, 2026
…21463)

## Summary

Backport of #21231
to `v4`.

Cherry-pick of merge commit `a8b2277a50` with two conflicts resolved:
- `docs/docs-developers/docs/resources/migration_notes.md` — accepted
incoming migration notes entries
- `yarn-project/archiver/src/store/block_store.ts` — included
`getEpochAtSlot` import

### Commit structure
1. **Cherry-pick commit** — raw cherry-pick with conflict markers
preserved for review
2. **Conflict resolution commit** — resolved the two conflicts above

No additional build fixes were needed — the code compiles cleanly on v4.

ClaudeBox log: https://claudebox.work/s/8bd8c8724c80cbb2?run=1
AztecBot added a commit that referenced this pull request Mar 14, 2026
BEGIN_COMMIT_OVERRIDE
fix: orchestrator enqueue yield (#21286)
chore: default multiplier (#21482)
chore: update yarn.lock (#21479)
chore: backport #21231 feat!: improve L2ToL1MessageWitness API to v4
(#21463)
feat(rpc): add package version to RPC response headers (#21526)
feat(ethereum): check VK tree root and protocol contracts hash in rollup
compatibility (#21537)
feat: add public log filtering by tag (#21561)
END_COMMIT_OVERRIDE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-to-v4 claudebox Owned by claudebox. it can push to this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants