Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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. This is `undefined` if the transaction hasn't been included in a proven epoch yet.
Comment thread
nventuro marked this conversation as resolved.
Outdated

### [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.nr] Removed `get_random_bytes`

The `get_random_bytes` unconstrained function has been removed from `aztec::utils::random`. If you were using it, you can replace it with direct calls to the `random` oracle from `aztec::oracle::random` and convert to bytes yourself.
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
8 changes: 6 additions & 2 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 @@ -880,10 +880,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, block] = await Promise.all([
this.getProvenBlockNumber(),
this.getCheckpointedL2BlockNumber(),
this.getFinalizedL2BlockNumber(),
this.getBlock(blockNumber),

Copy link
Copy Markdown
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.

]);

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

const epochNumber = block ? getEpochAtSlot(block.slot, this.l1Constants) : undefined;

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

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 @@ -388,6 +393,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 @@ -710,6 +710,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,7 @@ 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 type { 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 +13,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 +69,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 +104,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 +140,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 +186,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 +253,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 +297,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
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,17 @@ describe('e2e_cross_chain_messaging token_bridge_private', () => {
await crossChainTestHarness.expectPrivateBalanceOnL2(ownerAddress, bridgeAmount - withdrawAmount);

// 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(l2TxReceipt);
await t.advanceToEpochProven(l2TxReceipt);

const l2ToL1MessageResult = await computeL2ToL1MembershipWitness(aztecNode, epoch, l2ToL1Message);
const l2ToL1MessageResult = (await computeL2ToL1MembershipWitness(aztecNode, l2ToL1Message, l2TxReceipt.txHash))!;

// Check balance before and after exit.
expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount);
await crossChainTestHarness.withdrawFundsFromBridgeOnL1(
withdrawAmount,
epoch,
l2ToL1MessageResult!.leafIndex,
l2ToL1MessageResult!.siblingPath,
l2ToL1MessageResult.epochNumber,
l2ToL1MessageResult.leafIndex,
l2ToL1MessageResult.siblingPath,
);
expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount + withdrawAmount);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,17 @@ describe('e2e_cross_chain_messaging token_bridge_public', () => {
await crossChainTestHarness.expectPublicBalanceOnL2(ownerAddress, afterBalance - withdrawAmount);

// 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(l2TxReceipt);
await t.advanceToEpochProven(l2TxReceipt);

const l2ToL1MessageResult = await computeL2ToL1MembershipWitness(aztecNode, epoch, l2ToL1Message);
const l2ToL1MessageResult = (await computeL2ToL1MembershipWitness(aztecNode, l2ToL1Message, l2TxReceipt.txHash))!;

// Check balance before and after exit.
expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount);
await crossChainTestHarness.withdrawFundsFromBridgeOnL1(
withdrawAmount,
epoch,
l2ToL1MessageResult!.leafIndex,
l2ToL1MessageResult!.siblingPath,
l2ToL1MessageResult.epochNumber,
l2ToL1MessageResult.leafIndex,
l2ToL1MessageResult.siblingPath,
);
expect(await crossChainTestHarness.getL1BalanceOf(ethAccount)).toBe(l1TokenBalance - bridgeAmount + withdrawAmount);
}, 120_000);
Expand Down
Loading