Skip to content
Closed
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
73 changes: 73 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,79 @@ 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.

### Two separate init nullifiers for private and public

Contract initialization now emits two separate nullifiers instead of one: a **private init nullifier** and a **public init nullifier**. Each nullifier gates its respective execution domain:

- Private external functions check the private init nullifier.
- Public external functions check the public init nullifier.

**How initializers work:**

- **Private initializers** emit the private init nullifier. If the contract has any external public functions, the protocol auto-enqueues a public call to emit the public init nullifier.
- **Public initializers** emit both nullifiers directly.
- Contracts with no public functions only emit the private init nullifier.

**`only_self` functions no longer have init checks.** They behave as if marked `noinitcheck`.

**External functions called during private initialization must be `#[only_self]`.** Init nullifiers are emitted at the end of the initializer, so any external functions called on the initializing contract (e.g. via `enqueue_self` or `call_self`) during initialization will fail the init check unless they skip it.

**Breaking change for deployment:** If your contract has external public functions and a private initializer, the class must be registered onchain before initialization. You can no longer pass `skipClassPublication: true`, because the auto-enqueued public call requires the class to be available.

```diff
const deployed = await MyContract.deploy(wallet, ...args).send({
- skipClassPublication: true,
}).deployed();
```

### [Aztec.nr] Made `compute_note_hash_for_nullification` unconstrained

This function shouldn't have been constrained in the first place, as constrained computation of `HintedNote` nullifiers is dangerous (constrained computation of nullifiers can be performed only on the `ConfirmedNote` type). If you were calling this from a constrained function, consider using `compute_confirmed_note_hash_for_nullification` instead. Unconstrained usage is safe.

### [Aztec.nr] Changes to standard note hash computation

Note hashes used to be computed with the storage slot being the last value of the preimage, it is now the first. This is to make it easier to ensure all note hashes have proper domain separation.

This change requires no input from your side unless you were testing or relying on hardcoded note hashes.

### [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.

```diff
- const events = await getPublicEvents<MyEvent>(node, MyContract.events.MyEvent, filter);
+ const { events } = await getPublicEvents<MyEvent>(node, MyContract.events.MyEvent, filter);
```

The `maxLogsHit` flag indicates whether the log limit was reached, meaning more results may be available. You can use `afterLog` in the filter to fetch the next page.

### [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.

### [Aztec.js] `simulate()`, `send()`, and deploy return types changed to always return objects

All SDK interaction methods now return structured objects that include offchain output alongside the primary result. This affects `.simulate()`, `.send()`, deploy `.send()`, and `Wallet.sendTx()`.
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
Loading
Loading