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
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 <[email protected]>
@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 <[email protected]>
@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 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.

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