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
13 changes: 13 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,17 @@ jobs:
name: "Test"
command: cond_spot_run_tests end-to-end e2e_nested_contract.test.ts

e2e-l1-to-l2-messaging:
docker:
- image: aztecprotocol/alpine-build-image
resource_class: small
steps:
- *checkout
- *setup_env
- run:
name: "Test"
command: cond_spot_run_tests end-to-end e2e_l1_to_l2_msg.test.ts

e2e-l2-to-l1-messaging:
docker:
- image: aztecprotocol/alpine-build-image
Expand Down Expand Up @@ -601,6 +612,7 @@ workflows:
- e2e-nested-contract: *e2e_test
- e2e-public-token-contract: *e2e_test
- e2e-l2-to-l1-messaging: *e2e_test
- e2e-l1-to-l2-messaging: *e2e_test
- integration-l1-publisher: *e2e_test
- e2e-p2p: *e2e_test

Expand All @@ -612,6 +624,7 @@ workflows:
- e2e-nested-contract
- e2e-public-token-contract
- e2e-l2-to-l1-messaging
- e2e-l1-to-l2-messaging
- integration-l1-publisher
- e2e-p2p
<<: *defaults
28 changes: 14 additions & 14 deletions l1-contracts/src/core/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,34 @@ pragma solidity >=0.8.18;
*/
library Errors {
// Inbox
error Inbox__DeadlineBeforeNow();
error Inbox__NotPastDeadline();
error Inbox__PastDeadline();
error Inbox__FeeTooHigh();
error Inbox__FailedToWithdrawFees();
error Inbox__Unauthorized();
error Inbox__NothingToConsume(bytes32 entryKey);
error Inbox__DeadlineBeforeNow(); // 0xbf94a5dc
error Inbox__NotPastDeadline(); //0x3218ad9e
error Inbox__PastDeadline(); // 0x1eb114ea
error Inbox__FeeTooHigh(); // 0x6f478f42
error Inbox__FailedToWithdrawFees(); // 0xbc66d464
error Inbox__Unauthorized(); // 0xe5336a6b
error Inbox__NothingToConsume(bytes32 entryKey); // 0xdd7e995e
error Inbox__IncompatibleEntryArguments(
bytes32 entryKey,
uint64 storedFee,
uint64 feePassed,
uint32 storedDeadline,
uint32 deadlinePassed
);
); // 0xd483d8f2

// Outbox
error Outbox__Unauthorized();
error Outbox__InvalidChainId();
error Outbox__NothingToConsume(bytes32 entryKey);
error Outbox__Unauthorized(); // 0x2c9490c2
error Outbox__InvalidChainId(); // 0x577ec7c4
error Outbox__NothingToConsume(bytes32 entryKey); // 0xfb4fb506
error Outbox__IncompatibleEntryArguments(
bytes32 entryKey,
uint64 storedFee,
uint64 feePassed,
uint32 storedDeadline,
uint32 deadlinePassed
);
); // 0xad1b401b

// Rollup
error Rollup__InvalidStateHash(bytes32 expected, bytes32 actual);
error Rollup__InvalidProof();
error Rollup__InvalidStateHash(bytes32 expected, bytes32 actual); // 0xa3cfaab3
error Rollup__InvalidProof(); // 0xa5b2ba17
}
2 changes: 2 additions & 0 deletions l1-contracts/test/portals/TokenPortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ contract TokenPortalTest is Test {
// Perform op
bytes32 entryKey = tokenPortal.depositToAztec{value: bid}(to, amount, deadline, secretHash);

assertEq(entryKey, expectedEntryKey, "returned entry key and calculated entryKey should match");

// Check that the message is in the inbox
DataStructures.Entry memory entry = inbox.get(entryKey);
assertEq(entry.count, 1);
Expand Down
12 changes: 6 additions & 6 deletions yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ describe('Archiver', () => {
const blocks = [1, 2, 3].map(x => L2Block.random(x));
const rollupTxs = blocks.map(makeRollupTx);

publicClient.getBlockNumber.mockResolvedValue(2500n);
publicClient.getBlockNumber.mockResolvedValueOnce(2500n).mockResolvedValueOnce(2501n).mockResolvedValueOnce(2502n);
// logs should be created in order of how archiver syncs.
publicClient.getLogs
.mockResolvedValueOnce([makeL2BlockProcessedEvent(100n, 1n)])
.mockResolvedValueOnce([makeL1ToL2MessageAddedEvent(100n)])
.mockResolvedValueOnce([makeL2BlockProcessedEvent(101n, 1n)])
.mockResolvedValueOnce([makeUnverifiedDataEvent(102n, blocks[0])])
.mockResolvedValueOnce([makeContractDeployedEvent(104n, blocks[0])])
.mockResolvedValueOnce([makeL1ToL2MessageAddedEvent(101n)])
.mockResolvedValueOnce([makeL2BlockProcessedEvent(1100n, 2n), makeL2BlockProcessedEvent(1150n, 3n)])
.mockResolvedValueOnce([makeContractDeployedEvent(103n, blocks[0])])
.mockResolvedValueOnce([makeL1ToL2MessageAddedEvent(1000n)])
.mockResolvedValueOnce([makeL2BlockProcessedEvent(1101n, 2n), makeL2BlockProcessedEvent(1150n, 3n)])
.mockResolvedValueOnce([makeUnverifiedDataEvent(1100n, blocks[1])])
.mockResolvedValueOnce([makeContractDeployedEvent(1102n, blocks[1])])
.mockResolvedValueOnce([makeL1ToL2MessageAddedEvent(1101n)])
.mockResolvedValue([]);
rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx));

Expand Down
39 changes: 28 additions & 11 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa
*/
private nextL2BlockFromBlock = 0n;

/**
* Last Processed Block Number
*/
private lastProcessedBlockNumber = 0n;

/**
* Creates a new instance of the Archiver.
* @param publicClient - A client for interacting with the Ethereum node.
Expand Down Expand Up @@ -116,6 +121,28 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa
*/
private async sync(blockUntilSynced: boolean) {
const currentBlockNumber = await this.publicClient.getBlockNumber();
if (currentBlockNumber <= this.lastProcessedBlockNumber) {
this.log(`No new blocks to process, current block number: ${currentBlockNumber}`);
return;
}

// ********** Events that are processed inbetween blocks **********

// Process l1ToL2Messages, these are consumed as time passes, not each block

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.

ah good catch!

const retrievedPendingL1ToL2Messages = await retrieveNewPendingL1ToL2Messages(
this.publicClient,
this.inboxAddress,
blockUntilSynced,
currentBlockNumber,
this.lastProcessedBlockNumber + 1n, // + 1 to prevent re including messages from the last processed block
);
// TODO: optimise this - there could be messages in confirmed that are also in pending. No need to modify storage then.
// Store l1 to l2 messages
this.log('Adding pending l1 to l2 messages to store');
await this.store.addPendingL1ToL2Messages(retrievedPendingL1ToL2Messages.retrievedData);
this.lastProcessedBlockNumber = currentBlockNumber;

// ********** Events that are processed per block **********

// The sequencer publishes unverified data first
// Read all data from chain and then write to our stores at the end
Expand Down Expand Up @@ -154,13 +181,6 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa
this.nextL2BlockFromBlock,
blockHashMapping,
);
const retrievedPendingL1ToL2Messages = await retrieveNewPendingL1ToL2Messages(
this.publicClient,
this.inboxAddress,
blockUntilSynced,
currentBlockNumber,
this.nextL2BlockFromBlock,
);

if (retrievedBlocks.retrievedData.length === 0) {
return;
Expand All @@ -182,12 +202,9 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa
}
});

// TODO: optimise this - there could be messages in confirmed that are also in pending. No need to modify storage then.
// store new pending l1 to l2 messages for which we have retrieved rollups
await this.store.addPendingL1ToL2Messages(retrievedPendingL1ToL2Messages.retrievedData);
// from retrieved L2Blocks, confirm L1 to L2 messages that have been published
// from each l2block fetch all messageKeys in a flattened array:
const messageKeysToRemove = retrievedBlocks.retrievedData.map(l2block => l2block.newL2ToL1Msgs).flat();
const messageKeysToRemove = retrievedBlocks.retrievedData.map(l2block => l2block.newL1ToL2Messages).flat();
await this.store.confirmL1ToL2Messages(messageKeysToRemove);

// store retrieved rollup blocks
Expand Down
16 changes: 1 addition & 15 deletions yarn-project/aztec-node/src/aztec-node/aztec-node.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CONTRACT_TREE_HEIGHT, L1_TO_L2_MESSAGES_TREE_HEIGHT, PRIVATE_DATA_TREE_HEIGHT } from '@aztec/circuits.js';
import { AztecAddress } from '@aztec/foundation/aztec-address';
import { ContractPublicData, ContractData, L2Block, MerkleTreeId, L1ToL2Message } from '@aztec/types';
import { ContractPublicData, ContractData, L2Block, MerkleTreeId, L1ToL2MessageAndIndex } from '@aztec/types';
import { SiblingPath } from '@aztec/merkle-tree';
import { Tx, TxHash } from '@aztec/types';
import { UnverifiedData } from '@aztec/types';
Expand Down Expand Up @@ -123,17 +123,3 @@ export interface AztecNode {
*/
getTreeRoots(): Promise<Record<MerkleTreeId, Fr>>;
}

/**
* L1AndL2Message and Index (in the merkle tree) as one type
*/
export type L1ToL2MessageAndIndex = {
/**
* The message.
*/
message: L1ToL2Message;
/**
* the index in the L1 to L2 Message tree.
*/
index: bigint;
};
5 changes: 3 additions & 2 deletions yarn-project/aztec-node/src/aztec-node/http-node.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AztecNode, L1ToL2MessageAndIndex } from '@aztec/aztec-node';
import { AztecNode } from '@aztec/aztec-node';
import {
AztecAddress,
CONTRACT_TREE_HEIGHT,
Expand All @@ -15,6 +15,7 @@ import {
ContractData,
ContractPublicData,
EncodedContractFunction,
L1ToL2MessageAndIndex,
L1ToL2Message,
L2Block,
MerkleTreeId,
Expand Down Expand Up @@ -247,7 +248,7 @@ export class HttpNode implements AztecNode {
* @returns the message (or throws if not found)
*/
async getL1ToL2MessageAndIndex(messageKey: Fr): Promise<L1ToL2MessageAndIndex> {
const url = new URL(`${this.baseUrl}/l1-l2-message-and-index`);
const url = new URL(`${this.baseUrl}/l1-l2-message`);
url.searchParams.append('messageKey', messageKey.toString());
const response = await (await fetch(url.toString())).json();
return Promise.resolve({
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
L2BlockSource,
MerkleTreeId,
L1ToL2MessageSource,
L1ToL2MessageAndIndex,
} from '@aztec/types';
import { SiblingPath } from '@aztec/merkle-tree';
import { InMemoryTxPool, P2P, createP2PClient } from '@aztec/p2p';
Expand All @@ -31,7 +32,7 @@ import {
PRIVATE_DATA_TREE_HEIGHT,
} from '@aztec/circuits.js';
import { PrimitivesWasm } from '@aztec/barretenberg.js/wasm';
import { AztecNode, L1ToL2MessageAndIndex } from './aztec-node.js';
import { AztecNode } from './aztec-node.js';

export const createMemDown = () => (memdown as any)() as MemDown<any, any>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const logger = createDebugLogger('aztec:e2e_l1_to_l2_msg');
const config = getConfigEnvVars();

// NOTE: this tests is just a scaffold, it is awaiting functionality to come from the aztec-node around indexing messages in the contract
describe.skip('e2e_l1_to_l2_msg', () => {
describe('e2e_l1_to_l2_msg', () => {
let node: AztecNodeService;
let aztecRpcServer: AztecRPCServer;
let accounts: AztecAddress[];
Expand Down Expand Up @@ -62,6 +62,7 @@ describe.skip('e2e_l1_to_l2_msg', () => {
config.publisherPrivateKey = Buffer.from(privKey!);
config.rollupContract = rollupAddress;
config.inboxContract = inboxAddress;
config.archiverPollingInterval = 1000;
config.unverifiedDataEmitterContract = unverifiedDataEmitterAddress;

// Deploy portal contracts
Expand Down Expand Up @@ -109,11 +110,9 @@ describe.skip('e2e_l1_to_l2_msg', () => {
const deployContract = async (initialBalance = 0n, owner = { x: 0n, y: 0n }) => {
logger(`Deploying L2 Token contract...`);
const deployer = new ContractDeployer(NonNativeTokenContractAbi, aztecRpcServer);
const tx = deployer
.deploy(initialBalance, owner, {
portalContract: tokenPortalAddress,
})
.send();
const tx = deployer.deploy(initialBalance, owner).send({
portalContract: tokenPortalAddress,
});
const receipt = await tx.getReceipt();
contract = new Contract(receipt.contractAddress!, NonNativeTokenContractAbi, aztecRpcServer);
await contract.attach(tokenPortalAddress);
Expand All @@ -124,14 +123,14 @@ describe.skip('e2e_l1_to_l2_msg', () => {
return contract;
};

it('Should be able to consume an L1 Message and mint a non native token on L2', async () => {
const initialBalance = 1n;
const ownerAddress = accounts[0];
const owner = await aztecRpcServer.getAccountPublicKey(ownerAddress);
const deployedContract = await deployContract(initialBalance, pointToPublicKey(owner));
it('Milestone 2.2: L1->L2 Calls', async () => {
const initialBalance = 10n;
const [ownerAddress, receiver] = accounts;
const ownerPub = await aztecRpcServer.getAccountPublicKey(ownerAddress);
const deployedL2Contract = await deployContract(initialBalance, pointToPublicKey(ownerPub));
await expectBalance(accounts[0], initialBalance);

const l2TokenAddress = deployedContract.address.toString() as `0x${string}`;
const l2TokenAddress = deployedL2Contract.address.toString() as `0x${string}`;

logger('Initializing the TokenPortal contract');
await tokenPortal.write.initialize(
Expand All @@ -148,8 +147,7 @@ describe.skip('e2e_l1_to_l2_msg', () => {
const claimSecretHash = computeSecretMessageHash(wasm, secret);
logger('Generated claim secret: ', claimSecretHash);

// Mint some PortalERCjh20 token on l1
logger('Minting tokens on L2');
logger('Minting tokens on L1');
await underlyingERC20.write.mint([ethAccount.toString(), 1000000n], {} as any);
await underlyingERC20.write.approve([tokenPortalAddress.toString(), 1000n], {} as any);

Expand All @@ -158,31 +156,44 @@ describe.skip('e2e_l1_to_l2_msg', () => {
const deadline = 2 ** 32 - 1; // max uint32 - 1

logger('Sending messages to L1 portal');
const returnedMessageKey = await tokenPortal.write.depositToAztec(
[l2TokenAddress, 100n, deadline, secretString],
{} as any,
);

const messageKeyFr = Fr.fromBuffer(Buffer.from(returnedMessageKey, 'hex'));

// Wait for the rollup to process the message
// TODO: not implemented

// Force the node to consume the message
// TODO: not implemented
const args = [ownerAddress.toString(), 100n, deadline, secretString] as const;
const { result: messageKeyHex } = await tokenPortal.simulate.depositToAztec(args, {
account: ethAccount.toString(),
} as any);
await tokenPortal.write.depositToAztec(args, {} as any);
const messageKey = Fr.fromString(messageKeyHex);

// Wait for the archiver to process the message
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
await delay(5000); /// waiting 5 seconds.

// send a transfer tx to force through rollup with the message included
const transferAmount = 1n;
const transferTx = contract.methods
.transfer(
transferAmount,
pointToPublicKey(await aztecRpcServer.getAccountPublicKey(ownerAddress)),
pointToPublicKey(await aztecRpcServer.getAccountPublicKey(receiver)),
)
.send({ from: accounts[0] });

await transferTx.isMined(0, 0.1);
const transferReceipt = await transferTx.getReceipt();

expect(transferReceipt.status).toBe(TxStatus.MINED);

logger('Consuming messages on L2');
// Call the mint tokens function on the noir contract
const mintAmount = 100n;

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.

question - should we add expect() on methods in the archiver? See if the archiver actually processed everything correctly (i.e. added to pending store and later added to confirmed store)?

@Maddiaa0 Maddiaa0 May 26, 2023

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sounds like a good to have, ill add this. edit: this seems more of a unit test thing rather than integration test thing, ill not do it here

logger('Consuming messages on L2');
const tx = deployedContract.methods
.mint(mintAmount, pointToPublicKey(owner), messageKeyFr, secret)
const consumptionTx = deployedL2Contract.methods
.mint(mintAmount, pointToPublicKey(ownerPub), messageKey, secret)
.send({ from: ownerAddress });

await tx.isMined(0, 0.1);
const receipt = await tx.getReceipt();
await consumptionTx.isMined(0, 0.1);
const consumptionReceipt = await consumptionTx.getReceipt();

expect(receipt.status).toBe(TxStatus.MINED);
await expectBalance(ownerAddress, mintAmount);
});
expect(consumptionReceipt.status).toBe(TxStatus.MINED);
await expectBalance(ownerAddress, mintAmount + initialBalance - transferAmount);
}, 80_000);
});
2 changes: 1 addition & 1 deletion yarn-project/rollup-provider/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function appFactory(node: AztecNode, prefix: string) {
ctx.status = 200;
});

router.get('/l1-l2-message-and-index', async (ctx: Koa.Context) => {
router.get('/l1-l2-message', async (ctx: Koa.Context) => {

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

great catch

const key = ctx.query.messageKey!;
const messageAndindex = await node.getL1ToL2MessageAndIndex(Fr.fromString(key as string));
ctx.set('content-type', 'application/json');
Expand Down
Loading