Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [Aztec.js] `TxReceipt` now includes `epochNumber`

`TxReceipt` now includes an `epochNumber` field that indicates which epoch the transaction was included in.

### [Aztec.js] `computeL2ToL1MembershipWitness` signature changed

The function signature has changed to resolve the epoch internally from a transaction hash, rather than requiring the caller to pass the epoch number.

**Migration:**

```diff
- const witness = await computeL2ToL1MembershipWitness(aztecNode, epochNumber, messageHash);
- // epoch was passed in by the caller
+ const witness = await computeL2ToL1MembershipWitness(aztecNode, messageHash, txHash);
+ // epoch is now available on the returned witness
+ const epoch = witness.epochNumber;
```

The return type `L2ToL1MembershipWitness` now includes `epochNumber`. An optional `messageIndexInTx` parameter can be passed as the fourth argument to disambiguate when a transaction emits multiple identical L2-to-L1 messages.

**Impact**: All call sites that compute L2-to-L1 membership witnesses must update to the new argument order and extract `epochNumber` from the result instead of passing it in.

### [Aztec.js] `getPublicEvents` now returns an object instead of an array

`getPublicEvents` now returns a `GetPublicEventsResult<T>` object with `events` and `maxLogsHit` fields instead of a plain array. This enables pagination through large result sets using the new `afterLog` filter option.
Expand Down
16 changes: 3 additions & 13 deletions docs/examples/ts/token_bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { AztecAddress, EthAddress } from "@aztec/aztec.js/addresses";
import { Fr } from "@aztec/aztec.js/fields";
import { createAztecNodeClient } from "@aztec/aztec.js/node";
import { createExtendedL1Client } from "@aztec/ethereum/client";
import { RollupContract } from "@aztec/ethereum/contracts";
import { deployL1Contract } from "@aztec/ethereum/deploy-l1-contract";
import { CheckpointNumber } from "@aztec/foundation/branded-types";
import { sha256ToField } from "@aztec/foundation/crypto/sha256";
import {
computeL2ToL1MessageHash,
Expand Down Expand Up @@ -41,10 +39,6 @@ console.log(`Account: ${account.address.toString()}\n`);
const nodeInfo = await node.getNodeInfo();
const registryAddress = nodeInfo.l1ContractAddresses.registryAddress.toString();
const inboxAddress = nodeInfo.l1ContractAddresses.inboxAddress.toString();
const rollupAddress = nodeInfo.l1ContractAddresses.rollupAddress.toString();

// Create rollup contract instance for querying epoch information
const rollup = new RollupContract(l1Client, rollupAddress);
// docs:end:setup

// docs:start:deploy_l1_contracts
Expand Down Expand Up @@ -308,15 +302,11 @@ while (provenBlockNumber < exitReceipt.blockNumber!) {

console.log("Block proven!\n");

// Get the epoch for the exit block's checkpoint
// In Aztec, checkpoint number equals block number (1:1 mapping)
const epoch = await rollup.getEpochNumberForCheckpoint(
CheckpointNumber.fromBlockNumber(exitReceipt.blockNumber!),
);
// Compute the membership witness using the message hash and the L2 tx hash
const witness = await computeL2ToL1MembershipWitness(node, msgLeaf, exitReceipt.txHash);
const epoch = witness!.epochNumber;
console.log(` Epoch for block ${exitReceipt.blockNumber}: ${epoch}`);

// Compute the membership witness using the message hash and epoch
const witness = await computeL2ToL1MembershipWitness(node, epoch, msgLeaf);
const siblingPathHex = witness!.siblingPath
.toBufferArray()
.map((buf: Buffer) => `0x${buf.toString("hex")}` as `0x${string}`);
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/archiver/src/modules/data_source_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export abstract class ArchiverDataSourceBase
}

public getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
return this.store.getSettledTxReceipt(txHash);
return this.store.getSettledTxReceipt(txHash, this.l1Constants);
}

public isPendingChainInvalid(): Promise<boolean> {
Expand Down
14 changes: 11 additions & 3 deletions yarn-project/archiver/src/store/block_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
serializeValidateCheckpointResult,
} from '@aztec/stdlib/block';
import { type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
import { CheckpointHeader } from '@aztec/stdlib/rollup';
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
import {
Expand Down Expand Up @@ -871,7 +871,10 @@ export class BlockStore {
* @param txHash - The hash of a tx we try to get the receipt for.
* @returns The requested tx receipt (or undefined if not found).
*/
async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
async getSettledTxReceipt(
txHash: TxHash,
l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
): Promise<TxReceipt | undefined> {
const txEffect = await this.getTxEffect(txHash);
if (!txEffect) {
return undefined;
Expand All @@ -880,10 +883,11 @@ export class BlockStore {
const blockNumber = BlockNumber(txEffect.l2BlockNumber);

// Use existing archiver methods to determine finalization level
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber] = await Promise.all([
const [provenBlockNumber, checkpointedBlockNumber, finalizedBlockNumber, blockData] = await Promise.all([
this.getProvenBlockNumber(),
this.getCheckpointedL2BlockNumber(),
this.getFinalizedL2BlockNumber(),
this.getBlockData(blockNumber),
]);

let status: TxStatus;
Expand All @@ -897,6 +901,9 @@ export class BlockStore {
status = TxStatus.PROPOSED;
}

const epochNumber =
blockData && l1Constants ? getEpochAtSlot(blockData.header.globalVariables.slotNumber, l1Constants) : undefined;

return new TxReceipt(
txHash,
status,
Expand All @@ -905,6 +912,7 @@ export class BlockStore {
txEffect.data.transactionFee.toBigInt(),
txEffect.l2BlockHash,
blockNumber,
epochNumber,
);
}

Expand Down
7 changes: 5 additions & 2 deletions yarn-project/archiver/src/store/kv_archiver_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,11 @@ export class KVArchiverDataStore implements ContractDataSource {
* @param txHash - The hash of a tx we try to get the receipt for.
* @returns The requested tx receipt (or undefined if not found).
*/
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
return this.#blockStore.getSettledTxReceipt(txHash);
getSettledTxReceipt(
txHash: TxHash,
l1Constants?: Pick<L1RollupConstants, 'epochDuration'>,
): Promise<TxReceipt | undefined> {
return this.#blockStore.getSettledTxReceipt(txHash, l1Constants);
}

/**
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/archiver/src/test/mock_l2_block_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import {
} from '@aztec/stdlib/block';
import { Checkpoint, type CheckpointData, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
import { EmptyL1RollupConstants, type L1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
import {
EmptyL1RollupConstants,
type L1RollupConstants,
getEpochAtSlot,
getSlotRangeForEpoch,
} from '@aztec/stdlib/epoch-helpers';
import { computeCheckpointOutHash } from '@aztec/stdlib/messaging';
import { CheckpointHeader } from '@aztec/stdlib/rollup';
import { type BlockHeader, TxExecutionResult, TxHash, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
Expand Down Expand Up @@ -394,6 +399,7 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
txEffect.transactionFee.toBigInt(),
await block.hash(),
block.number,
getEpochAtSlot(block.slot, EmptyL1RollupConstants),
);
}
}
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
}

public getCheckpointsDataForEpoch(epochNumber: EpochNumber) {
return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
}

/**
* Method to fetch the current min L2 fees.
* @returns The current min L2 fees.
Expand Down
7 changes: 3 additions & 4 deletions yarn-project/aztec/src/testing/epoch_test_settler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { type EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
import type { Logger } from '@aztec/foundation/log';
import { EpochMonitor } from '@aztec/prover-node';
import type { EthAddress, L2BlockSource } from '@aztec/stdlib/block';
import { computeL2ToL1MembershipWitnessFromMessagesInEpoch } from '@aztec/stdlib/messaging';
import { computeEpochOutHash } from '@aztec/stdlib/messaging';

export class EpochTestSettler {
private rollupCheatCodes: RollupCheatCodes;
Expand Down Expand Up @@ -51,9 +51,8 @@ export class EpochTestSettler {
messagesInEpoch[checkpointIndex].push(block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs));
}

const [firstMessage] = messagesInEpoch.flat(3);
if (firstMessage) {
const { root: outHash } = computeL2ToL1MembershipWitnessFromMessagesInEpoch(messagesInEpoch, firstMessage);
const outHash = computeEpochOutHash(messagesInEpoch);
if (!outHash.isZero()) {
await this.rollupCheatCodes.insertOutbox(epoch, outHash.toBigInt());
} else {
this.log.info(`No L2 to L1 messages in epoch ${epoch}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Fr } from '@aztec/aztec.js/fields';
import { createLogger } from '@aztec/aztec.js/log';
import { createAztecNodeClient, waitForNode } from '@aztec/aztec.js/node';
import { createExtendedL1Client } from '@aztec/ethereum/client';
import { RollupContract } from '@aztec/ethereum/contracts';
import { deployL1Contract } from '@aztec/ethereum/deploy-l1-contract';
import {
FeeAssetHandlerAbi,
Expand Down Expand Up @@ -224,19 +223,15 @@ describe('e2e_cross_chain_messaging token_bridge_tutorial_test', () => {
// docs:end:l2-withdraw

// docs:start:l1-withdraw
const rollup = new RollupContract(l1Client, l1ContractAddresses.rollupAddress.toString());
const block = await node.getBlock(l2TxReceipt.blockNumber!);
const epoch = await rollup.getEpochNumberForCheckpoint(block!.checkpointNumber);

const result = await computeL2ToL1MembershipWitness(node, epoch, l2ToL1Message);
const result = await computeL2ToL1MembershipWitness(node, l2ToL1Message, l2TxReceipt.txHash);
if (!result) {
throw new Error('L2 to L1 message not found');
}

await l1PortalManager.withdrawFunds(
withdrawAmount,
EthAddress.fromString(ownerEthAddress),
epoch,
result.epochNumber,
result.leafIndex,
result.siblingPath,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { BatchCall } from '@aztec/aztec.js/contracts';
import { Fr } from '@aztec/aztec.js/fields';
import type { Wallet } from '@aztec/aztec.js/wallet';
import { OutboxContract, RollupContract, type ViemL2ToL1Msg } from '@aztec/ethereum/contracts';
import { EpochNumber } from '@aztec/foundation/branded-types';
import { OutboxAbi } from '@aztec/l1-artifacts';
import { TestContract } from '@aztec/noir-test-contracts.js/Test';
import { computeL2ToL1MessageHash } from '@aztec/stdlib/hash';
Expand All @@ -13,6 +12,7 @@ import {
computeL2ToL1MembershipWitness,
getL2ToL1MessageLeafId,
} from '@aztec/stdlib/messaging';
import type { TxHash } from '@aztec/stdlib/tx';

import { type Hex, decodeEventLog } from 'viem';

Expand Down Expand Up @@ -68,17 +68,17 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => {
const blockNumber = txReceipt.blockNumber!;

// Advance the epoch until the tx is proven since the messages are inserted to the outbox when the epoch is proven.
const epoch = await t.advanceToEpochProven(txReceipt);
await t.advanceToEpochProven(txReceipt);

// Check that the block contains the 2 messages.
const block = (await aztecNode.getBlock(blockNumber))!;
const l2ToL1Messages = block.body.txEffects.flatMap(txEffect => txEffect.l2ToL1Msgs);
expect(l2ToL1Messages).toStrictEqual([computeMessageLeaf(messages[0]), computeMessageLeaf(messages[1])]);

// Consume messages[0].
await expectConsumeMessageToSucceed(epoch, messages[0]);
await expectConsumeMessageToSucceed(messages[0], txReceipt.txHash);
// Consume messages[1].
await expectConsumeMessageToSucceed(epoch, messages[1]);
await expectConsumeMessageToSucceed(messages[1], txReceipt.txHash);
});

// When the block contains a tx with no messages, the zero txOutHash is skipped and won't be included in the top tree.
Expand All @@ -103,10 +103,10 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => {
expect(noMessageReceipt.blockNumber).toEqual(withMessageReceipt.blockNumber);

// Advance the epoch until the tx is proven since the messages are inserted to the outbox when the epoch is proven.
const epoch = await t.advanceToEpochProven(withMessageReceipt);
await t.advanceToEpochProven(withMessageReceipt);

// Consume the message.
await expectConsumeMessageToSucceed(epoch, message);
await expectConsumeMessageToSucceed(message, withMessageReceipt.txHash);
});

it('2 txs (balanced), one with 3 messages (unbalanced), one with 4 messages (balanced)', async () => {
Expand Down Expand Up @@ -139,25 +139,25 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => {
}

// Advance the epoch until the tx is proven since the messages are inserted to the outbox when the epoch is proven.
const epoch = await t.advanceToEpochProven(l2TxReceipt1);
await t.advanceToEpochProven(l2TxReceipt1);

// Consume messages in tx0.
{
// Consume messages[0], which is in the subtree of height 2.
const msg = tx0.messages[0];
await expectConsumeMessageToSucceed(epoch, msg);
await expectConsumeMessageToSucceed(msg, l2TxReceipt0.txHash);
}
{
// Consume messages[2], which is in the subtree of height 1.
const msg = tx0.messages[2];
await expectConsumeMessageToSucceed(epoch, msg);
await expectConsumeMessageToSucceed(msg, l2TxReceipt0.txHash);
}

// Consume messages in tx1.
{
// Consume messages[2], which is in the subtree of height 2.
const msg = tx1.messages[0];
await expectConsumeMessageToSucceed(epoch, msg);
await expectConsumeMessageToSucceed(msg, l2TxReceipt1.txHash);
}
});

Expand Down Expand Up @@ -185,32 +185,32 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => {
expect(l2TxReceipt2.blockNumber).toEqual(blockNumber);

// Advance the epoch until the tx is proven since the messages are inserted to the outbox when the epoch is proven.
const epoch = await t.advanceToEpochProven(l2TxReceipt2);
await t.advanceToEpochProven(l2TxReceipt2);

// Consume messages in tx0.
{
// Consume messages[0], which is in the subtree of height 2.
const msg = tx0.messages[0];
await expectConsumeMessageToSucceed(epoch, msg);
await expectConsumeMessageToSucceed(msg, l2TxReceipt0.txHash);
}
{
// Consume messages[2], which is in the subtree of height 1.
const msg = tx0.messages[2];
await expectConsumeMessageToSucceed(epoch, msg);
await expectConsumeMessageToSucceed(msg, l2TxReceipt0.txHash);
}

// Consume messages in tx1.
{
// Consume messages[0], which is the tx subtree root.
const msg = tx1.messages[0];
await expectConsumeMessageToSucceed(epoch, msg);
await expectConsumeMessageToSucceed(msg, l2TxReceipt1.txHash);
}

// Consume messages in tx2.
{
// Consume messages[1], which is in the subtree of height 1.
const msg = tx2.messages[1];
await expectConsumeMessageToSucceed(epoch, msg);
await expectConsumeMessageToSucceed(msg, l2TxReceipt2.txHash);
}
});

Expand Down Expand Up @@ -252,9 +252,10 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => {
return { recipients, contents, messages };
}

async function expectConsumeMessageToSucceed(epoch: EpochNumber, msg: ReturnType<typeof makeL2ToL1Message>) {
async function expectConsumeMessageToSucceed(msg: ReturnType<typeof makeL2ToL1Message>, l2TxHash: TxHash) {
const msgLeaf = computeMessageLeaf(msg);
const witness = (await computeL2ToL1MembershipWitness(aztecNode, epoch, msgLeaf))!;
const result = (await computeL2ToL1MembershipWitness(aztecNode, msgLeaf, l2TxHash))!;
const { epochNumber: epoch, ...witness } = result;
const leafId = getL2ToL1MessageLeafId(witness);

const txHash = await outbox.consume(
Expand Down Expand Up @@ -295,22 +296,17 @@ describe('e2e_cross_chain_messaging l2_to_l1', () => {
expect(topics.args.leafId).toBe(leafId);

// Ensure we cannot consume the same message again.
await expectConsumeMessageToFail(epoch, msg, witness);
await expectConsumeMessageToFail(msg, result);
}

async function expectConsumeMessageToFail(
epoch: EpochNumber,
msg: ReturnType<typeof makeL2ToL1Message>,
witness?: L2ToL1MembershipWitness,
witness: L2ToL1MembershipWitness,
) {
if (!witness) {
const msgLeaf = computeMessageLeaf(msg);
witness = (await computeL2ToL1MembershipWitness(aztecNode, epoch, msgLeaf))!;
}
await expect(
outbox.consume(
msg,
epoch,
witness.epochNumber,
witness.leafIndex,
witness.siblingPath.toFields().map(f => f.toString()),
),
Expand Down
Loading
Loading