diff --git a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol index c07841302e6a..86ee6b7e32e2 100644 --- a/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol +++ b/l1-contracts/src/core/interfaces/messagebridge/IInbox.sol @@ -18,7 +18,8 @@ interface IInbox { uint256 recipientVersion, uint32 deadline, uint64 fee, - bytes32 content + bytes32 content, + bytes32 secretHash ); event L1ToL2MessageCancelled(bytes32 indexed entryKey); diff --git a/l1-contracts/src/core/messagebridge/Inbox.sol b/l1-contracts/src/core/messagebridge/Inbox.sol index 45e328aabdbd..97f92a180fd8 100644 --- a/l1-contracts/src/core/messagebridge/Inbox.sol +++ b/l1-contracts/src/core/messagebridge/Inbox.sol @@ -84,7 +84,8 @@ contract Inbox is IInbox { message.recipient.version, message.deadline, message.fee, - message.content + message.content, + message.secretHash ); return key; diff --git a/l1-contracts/test/Inbox.t.sol b/l1-contracts/test/Inbox.t.sol index 9de6cbb8563a..f2ea0563def9 100644 --- a/l1-contracts/test/Inbox.t.sol +++ b/l1-contracts/test/Inbox.t.sol @@ -20,7 +20,8 @@ contract InboxTest is Test { uint256 recipientVersion, uint32 deadline, uint64 fee, - bytes32 content + bytes32 content, + bytes32 secretHash ); event L1ToL2MessageCancelled(bytes32 indexed entryKey); @@ -65,7 +66,8 @@ contract InboxTest is Test { _message.recipient.version, _message.deadline, _message.fee, - _message.content + _message.content, + _message.secretHash ); // event we will get bytes32 entryKey = inbox.sendL2Message{value: _message.fee}( diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index 1d3ca7e4f4ae..b72402e8229d 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -27,7 +27,8 @@ contract TokenPortalTest is Test { uint256 recipientVersion, uint32 deadline, uint64 fee, - bytes32 content + bytes32 content, + bytes32 secretHash ); Registry internal registry; @@ -89,7 +90,8 @@ contract TokenPortalTest is Test { expectedMessage.recipient.version, expectedMessage.deadline, expectedMessage.fee, - expectedMessage.content + expectedMessage.content, + expectedMessage.secretHash ); // Perform op diff --git a/yarn-project/archiver/README.md b/yarn-project/archiver/README.md index 4f180b229cfc..c8088c6c191b 100644 --- a/yarn-project/archiver/README.md +++ b/yarn-project/archiver/README.md @@ -2,8 +2,9 @@ Archiver is a service which is used to fetch data on-chain data and present them in a nice-to-consume form. The on-chain data specifically are the following events: 1. `L2BlockProcessed` event emitted on Rollup contract, -2. `UnverifiedData` event emitted on UnverifiedDataEmitter contract and -3. `ContractDeployment` event emitted on UnverifiedDataEmitter contract as well. +2. `UnverifiedData` event emitted on UnverifiedDataEmitter contract, +3. `ContractDeployment` event emitted on UnverifiedDataEmitter contract, +4. `MessageAdded` event emitted on Inbox contract, The interfaces defining how the data can be consumed from the archiver are `L2BlockSource`, `UnverifiedDataSource` and `ContractDataSource`. @@ -11,4 +12,4 @@ The interfaces defining how the data can be consumed from the archiver are `L2Bl To install dependencies and build the package run `yarn install` followed by `yarn build`. To run test execute `yarn test`. -To start the service export `ETHEREUM_HOST` (defaults to `http://127.0.0.1:8545/`), `ARCHIVER_POLLING_INTERVAL` (defaults to `1000 ms`), `ROLLUP_CONTRACT_ADDRESS`, `UNVERIFIED_DATA_EMITTER_ADDRESS` environmental variables and start the service with `yarn start`. +To start the service export `ETHEREUM_HOST` (defaults to `http://127.0.0.1:8545/`), `ARCHIVER_POLLING_INTERVAL` (defaults to `1000 ms`), `ROLLUP_CONTRACT_ADDRESS`, `INBOX_CONTRACT_ADDRESS`, `UNVERIFIED_DATA_EMITTER_ADDRESS` environmental variables and start the service with `yarn start`. diff --git a/yarn-project/archiver/src/archiver/archiver.test.ts b/yarn-project/archiver/src/archiver/archiver.test.ts index e3742e82705d..d28e5118ad32 100644 --- a/yarn-project/archiver/src/archiver/archiver.test.ts +++ b/yarn-project/archiver/src/archiver/archiver.test.ts @@ -1,4 +1,4 @@ -import { RollupAbi, UnverifiedDataEmitterAbi } from '@aztec/l1-artifacts'; +import { InboxAbi, RollupAbi, UnverifiedDataEmitterAbi } from '@aztec/l1-artifacts'; import { ContractData, ContractPublicData, EncodedContractFunction, L2Block } from '@aztec/types'; import { MockProxy, mock } from 'jest-mock-extended'; import { Chain, HttpTransport, Log, PublicClient, Transaction, encodeFunctionData, toHex } from 'viem'; @@ -12,6 +12,7 @@ import { ArchiverDataStore, MemoryArchiverStore } from './archiver_store.js'; describe('Archiver', () => { const rollupAddress = '0x0000000000000000000000000000000000000000'; + const inboxAddress = '0x0000000000000000000000000000000000000000'; const unverifiedDataEmitterAddress = '0x0000000000000000000000000000000000000001'; let publicClient: MockProxy>; let archiverStore: ArchiverDataStore; @@ -25,6 +26,7 @@ describe('Archiver', () => { const archiver = new Archiver( publicClient, EthAddress.fromString(rollupAddress), + EthAddress.fromString(inboxAddress), EthAddress.fromString(unverifiedDataEmitterAddress), archiverStore, 1000, @@ -38,13 +40,16 @@ describe('Archiver', () => { const rollupTxs = [1, 2, 3].map(makeRollupTx); publicClient.getBlockNumber.mockResolvedValue(2500n); + // logs should be created in order of how archiver syncs. publicClient.getLogs .mockResolvedValueOnce([makeL2BlockProcessedEvent(100n, 1n)]) .mockResolvedValueOnce([makeUnverifiedDataEvent(102n, 1n)]) .mockResolvedValueOnce([makeContractDeployedEvent(104n, 1n)]) + .mockResolvedValueOnce([makeL1ToL2MessageAddedEvent(101n)]) .mockResolvedValueOnce([makeL2BlockProcessedEvent(1100n, 2n), makeL2BlockProcessedEvent(1150n, 3n)]) .mockResolvedValueOnce([makeUnverifiedDataEvent(1100n, 2n)]) .mockResolvedValueOnce([makeContractDeployedEvent(1102n, 2n)]) + .mockResolvedValueOnce([makeL1ToL2MessageAddedEvent(1101n)]) .mockResolvedValue([]); rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx)); @@ -66,6 +71,9 @@ describe('Archiver', () => { latestUnverifiedDataBlockNum = await archiver.getLatestUnverifiedDataBlockNum(); expect(latestUnverifiedDataBlockNum).toEqual(2); + // there are only 2 l1ToL2 messages in the store + expect((await archiver.getPendingL1ToL2Messages(10)).length).toEqual(2); + await archiver.stop(); }, 10_000); }); @@ -129,6 +137,29 @@ function makeContractDeployedEvent(l1BlockNum: bigint, l2BlockNum: bigint) { } as Log; } +/** + * Makes a fake L1ToL2 MessageAdded event for testing purposes. + * @param l1BlockNum - L1 block number. + * @returns An L2BlockProcessed event log. + */ +function makeL1ToL2MessageAddedEvent(l1BlockNum: bigint) { + return { + blockNumber: l1BlockNum, + args: { + sender: EthAddress.random().toString(), + senderChainId: 1n, + recipient: AztecAddress.random().toString(), + recipientVersion: 1n, + content: '0x' + randomBytes(32).toString('hex'), + secretHash: '0x' + randomBytes(32).toString('hex'), + deadline: 100, + fee: 1n, + entryKey: '0x' + randomBytes(32).toString('hex'), + }, + transactionHash: `0x${l1BlockNum}`, + } as Log; +} + /** * Makes a fake rollup tx for testing purposes. * @param blockNum - L2Block number. diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 55e4f14709dd..2b8e9aa049e0 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -2,7 +2,7 @@ import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/running-promise'; import { EthAddress } from '@aztec/foundation/eth-address'; import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { INITIAL_L2_BLOCK_NUM } from '@aztec/types'; +import { INITIAL_L2_BLOCK_NUM, L1ToL2Message, L1ToL2MessageSource } from '@aztec/types'; import { ContractData, ContractPublicData, @@ -16,7 +16,12 @@ import { import { Chain, HttpTransport, PublicClient, createPublicClient, http } from 'viem'; import { localhost } from 'viem/chains'; import { ArchiverConfig } from './config.js'; -import { retrieveBlocks, retrieveNewContractData, retrieveUnverifiedData } from './data_retrieval.js'; +import { + retrieveBlocks, + retrieveNewContractData, + retrieveUnverifiedData, + retrieveNewPendingL1ToL2Messages, +} from './data_retrieval.js'; import { ArchiverDataStore, MemoryArchiverStore } from './archiver_store.js'; /** @@ -24,7 +29,7 @@ import { ArchiverDataStore, MemoryArchiverStore } from './archiver_store.js'; * Responsible for handling robust L1 polling so that other components do not need to * concern themselves with it. */ -export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDataSource { +export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDataSource, L1ToL2MessageSource { /** * A promise in which we will be continually fetching new L2 blocks. */ @@ -39,6 +44,7 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa * Creates a new instance of the Archiver. * @param publicClient - A client for interacting with the Ethereum node. * @param rollupAddress - Ethereum address of the rollup contract. + * @param inboxAddress - Ethereum address of the inbox contract. * @param unverifiedDataEmitterAddress - Ethereum address of the unverifiedDataEmitter contract. * @param pollingIntervalMs - The interval for polling for rollup logs (in milliseconds). * @param store - An archiver data store for storage & retrieval of blocks, unverified data & contract data. @@ -47,6 +53,7 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa constructor( private readonly publicClient: PublicClient, private readonly rollupAddress: EthAddress, + private readonly inboxAddress: EthAddress, private readonly unverifiedDataEmitterAddress: EthAddress, private readonly store: ArchiverDataStore, private readonly pollingIntervalMs = 10_000, @@ -68,6 +75,7 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa const archiver = new Archiver( publicClient, config.rollupContract, + config.inboxContract, config.unverifiedDataEmitterContract, archiverStore, config.archiverPollingInterval, @@ -130,6 +138,13 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa currentBlockNumber, this.nextL2BlockFromBlock, ); + const retrievedPendingL1ToL2Messages = await retrieveNewPendingL1ToL2Messages( + this.publicClient, + this.inboxAddress, + blockUntilSynced, + currentBlockNumber, + this.nextL2BlockFromBlock, + ); if (retrievedBlocks.retrievedData.length === 0) { return; @@ -151,6 +166,9 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa } }); + // store l1 to l2 messages for which we have retrieved rollups + await this.store.addPendingL1ToL2Messages(retrievedPendingL1ToL2Messages.retrievedData); + // store retrieved rollup blocks await this.store.addL2Blocks(retrievedBlocks.retrievedData); @@ -259,4 +277,13 @@ export class Archiver implements L2BlockSource, UnverifiedDataSource, ContractDa public getLatestUnverifiedDataBlockNum(): Promise { return this.store.getLatestUnverifiedDataBlockNum(); } + + /** + * Gets the `take` amount of pending L1 to L2 messages. + * @param take - The number of messages to return. + * @returns The requested L1 to L2 messages. + */ + getPendingL1ToL2Messages(take: number): Promise { + return this.store.getPendingL1ToL2Messages(take); + } } diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index 4d215c6261d8..33cb86e99789 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -1,4 +1,12 @@ -import { ContractPublicData, L2Block, UnverifiedData, INITIAL_L2_BLOCK_NUM, ContractData } from '@aztec/types'; +import { + ContractPublicData, + L2Block, + UnverifiedData, + INITIAL_L2_BLOCK_NUM, + ContractData, + L1ToL2Message, +} from '@aztec/types'; +import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; /** @@ -9,6 +17,8 @@ export interface ArchiverDataStore { addL2Blocks(blocks: L2Block[]): Promise; getL2Blocks(from: number, take: number): Promise; addUnverifiedData(data: UnverifiedData[]): Promise; + addPendingL1ToL2Messages(messages: L1ToL2Message[]): Promise; + getPendingL1ToL2Messages(take: number): Promise; getUnverifiedData(from: number, take: number): Promise; addL2ContractPublicData(data: ContractPublicData[], blockNum: number): Promise; getL2ContractPublicData(contractAddress: AztecAddress): Promise; @@ -40,6 +50,11 @@ export class MemoryArchiverStore implements ArchiverDataStore { */ private contractPublicData: (ContractPublicData[] | undefined)[] = []; + /** + * An array containing all the pending L1 to L2 messages + */ + private pendingL1ToL2Messages: L1ToL2Message[] = []; + constructor() {} /** @@ -62,6 +77,16 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(true); } + /** + * Append new pending L1 to L2 messages to the store's list. + * @param messages - The L1 to L2 messages to be added to the store. + * @returns True if the operation is successful (always in this implementation). + */ + public addPendingL1ToL2Messages(messages: L1ToL2Message[]): Promise { + this.pendingL1ToL2Messages.push(...messages); + return Promise.resolve(true); + } + /** * Store new Contract Public Data from an L2 block to the store's list. * @param data - List of contracts' data to be added. @@ -91,6 +116,17 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(this.l2Blocks.slice(startIndex, endIndex)); } + /** + * Gets the `take` amount of pending L1 to L2 messages. + * @param take - The number of messages to return (by default NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP). + * @returns The requested L1 to L2 messages. + */ + public getPendingL1ToL2Messages(take: number = NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP): Promise { + // todo: @rahul https://github.com/AztecProtocol/aztec-packages/issues/529 - change this so that sequencer actually actually consumes messages sorted by fee or another value + // upon consumption, the messages are removed from the store + return Promise.resolve(this.pendingL1ToL2Messages.slice(0, take)); + } + /** * Gets the `take` amount of unverified data starting from `from`. * @param from - Number of the L2 block to which corresponds the first `unverifiedData` to be returned. diff --git a/yarn-project/archiver/src/archiver/config.ts b/yarn-project/archiver/src/archiver/config.ts index 513877a7bbf9..4a6bd936c6a7 100644 --- a/yarn-project/archiver/src/archiver/config.ts +++ b/yarn-project/archiver/src/archiver/config.ts @@ -22,12 +22,18 @@ export interface ArchiverConfig extends L1Addresses { * @returns The archiver configuration. */ export function getConfigEnvVars(): ArchiverConfig { - const { ETHEREUM_HOST, ARCHIVER_POLLING_INTERVAL, ROLLUP_CONTRACT_ADDRESS, UNVERIFIED_DATA_EMITTER_ADDRESS } = - process.env; + const { + ETHEREUM_HOST, + ARCHIVER_POLLING_INTERVAL, + ROLLUP_CONTRACT_ADDRESS, + INBOX_CONTRACT_ADDRESS, + UNVERIFIED_DATA_EMITTER_ADDRESS, + } = process.env; return { rpcUrl: ETHEREUM_HOST || 'http://127.0.0.1:8545/', archiverPollingInterval: ARCHIVER_POLLING_INTERVAL ? +ARCHIVER_POLLING_INTERVAL : 1_000, rollupContract: ROLLUP_CONTRACT_ADDRESS ? EthAddress.fromString(ROLLUP_CONTRACT_ADDRESS) : EthAddress.ZERO, + inboxContract: INBOX_CONTRACT_ADDRESS ? EthAddress.fromString(INBOX_CONTRACT_ADDRESS) : EthAddress.ZERO, unverifiedDataEmitterContract: UNVERIFIED_DATA_EMITTER_ADDRESS ? EthAddress.fromString(UNVERIFIED_DATA_EMITTER_ADDRESS) : EthAddress.ZERO, diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index 10aae461ef1d..174e38454b1c 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -2,13 +2,29 @@ import { PublicClient } from 'viem'; import { getContractDeploymentLogs, getL2BlockProcessedLogs, + getPendingL1ToL2MessageLogs, getUnverifiedDataLogs, processBlockLogs, processContractDeploymentLogs, + processPendingL1ToL2MessageAddedLogs, processUnverifiedDataLogs, } from './eth_log_handlers.js'; import { EthAddress } from '@aztec/foundation/eth-address'; -import { ContractPublicData, L2Block, UnverifiedData } from '@aztec/types'; +import { ContractPublicData, L1ToL2Message, L2Block, UnverifiedData } from '@aztec/types'; + +/** + * Data retreived from logs + */ +type DataRetrieval = { + /** + * The next block number. + */ + nextEthBlockNumber: bigint; + /** + * The data returned. + */ + retrievedData: T[]; +}; /** * Fetches new L2 Blocks. @@ -18,6 +34,7 @@ import { ContractPublicData, L2Block, UnverifiedData } from '@aztec/types'; * @param currentBlockNumber - Latest available block number in the ETH node. * @param searchStartBlock - The block number to use for starting the search. * @param expectedNextRollupNumber - The next rollup id that we expect to find. + * @returns An array of L2 Blocks and the next eth block to search from */ export async function retrieveBlocks( publicClient: PublicClient, @@ -26,7 +43,7 @@ export async function retrieveBlocks( currentBlockNumber: bigint, searchStartBlock: bigint, expectedNextRollupNumber: bigint, -) { +): Promise> { const retrievedBlocks: L2Block[] = []; do { if (searchStartBlock > currentBlockNumber) { @@ -55,6 +72,7 @@ export async function retrieveBlocks( * @param currentBlockNumber - Latest available block number in the ETH node. * @param searchStartBlock - The block number to use for starting the search. * @param expectedNextRollupNumber - The next rollup id that we expect to find. + * @returns An array of UnverifiedData and the next eth block to search from. */ export async function retrieveUnverifiedData( publicClient: PublicClient, @@ -63,7 +81,7 @@ export async function retrieveUnverifiedData( currentBlockNumber: bigint, searchStartBlock: bigint, expectedNextRollupNumber: bigint, -) { +): Promise> { const newUnverifiedDataChunks: UnverifiedData[] = []; do { if (searchStartBlock > currentBlockNumber) { @@ -95,7 +113,7 @@ export async function retrieveUnverifiedData( * @param blockUntilSynced - If true, blocks until the archiver has fully synced. * @param currentBlockNumber - Latest available block number in the ETH node. * @param searchStartBlock - The block number to use for starting the search. - * @returns An array of ContractPublicData and their equivalent L2 Block number. + * @returns An array of ContractPublicData and their equivalent L2 Block number along with the next eth block to search from.. */ export async function retrieveNewContractData( publicClient: PublicClient, @@ -103,7 +121,7 @@ export async function retrieveNewContractData( blockUntilSynced: boolean, currentBlockNumber: bigint, searchStartBlock: bigint, -) { +): Promise> { let retrievedNewContracts: [ContractPublicData[], number][] = []; do { if (searchStartBlock > currentBlockNumber) { @@ -122,3 +140,33 @@ export async function retrieveNewContractData( } while (blockUntilSynced && searchStartBlock <= currentBlockNumber); return { nextEthBlockNumber: searchStartBlock, retrievedData: retrievedNewContracts }; } + +/** + * Fetch new pending L1 to L2 messages. + * @param publicClient - The viem public client to use for transaction retrieval. + * @param inboxAddress - The address of the inbox contract to fetch messages from. + * @param blockUntilSynced - If true, blocks until the archiver has fully synced. + * @param currentBlockNumber - Latest available block number in the ETH node. + * @param searchStartBlock - The block number to use for starting the search. + * @returns An array of L1ToL2Message and next eth block to search from. + */ +export async function retrieveNewPendingL1ToL2Messages( + publicClient: PublicClient, + inboxAddress: EthAddress, + blockUntilSynced: boolean, + currentBlockNumber: bigint, + searchStartBlock: bigint, +): Promise> { + const retrievedNewL1ToL2Messages: L1ToL2Message[] = []; + do { + if (searchStartBlock > currentBlockNumber) { + break; + } + const newL1ToL2MessageLogs = await getPendingL1ToL2MessageLogs(publicClient, inboxAddress, searchStartBlock); + const newL1ToL2Messages = processPendingL1ToL2MessageAddedLogs(newL1ToL2MessageLogs); + retrievedNewL1ToL2Messages.push(...newL1ToL2Messages); + // handles the case when there are no new messages: + searchStartBlock = (newL1ToL2MessageLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n; + } while (blockUntilSynced && searchStartBlock <= currentBlockNumber); + return { nextEthBlockNumber: searchStartBlock, retrievedData: retrievedNewL1ToL2Messages }; +} diff --git a/yarn-project/archiver/src/archiver/eth_log_handlers.ts b/yarn-project/archiver/src/archiver/eth_log_handlers.ts index 316648af41f1..4390e18776a3 100644 --- a/yarn-project/archiver/src/archiver/eth_log_handlers.ts +++ b/yarn-project/archiver/src/archiver/eth_log_handlers.ts @@ -1,16 +1,46 @@ import { Hex, Log, PublicClient, decodeFunctionData, getAbiItem, getAddress, hexToBytes } from 'viem'; -import { RollupAbi, UnverifiedDataEmitterAbi } from '@aztec/l1-artifacts'; +import { InboxAbi, RollupAbi, UnverifiedDataEmitterAbi } from '@aztec/l1-artifacts'; +import { Fr } from '@aztec/foundation/fields'; import { BufferReader, ContractData, ContractPublicData, EncodedContractFunction, + L1ToL2Message, + L1Actor, + L2Actor, L2Block, UnverifiedData, } from '@aztec/types'; import { EthAddress } from '@aztec/foundation/eth-address'; import { AztecAddress } from '@aztec/foundation/aztec-address'; +/** + * Processes newly received MessageAdded (L1 to L2) logs. + * @param logs - MessageAdded logs. + * @returns Array of all Pending L1 to L2 messages that were processed + */ +export function processPendingL1ToL2MessageAddedLogs( + logs: Log[], +): L1ToL2Message[] { + const l1ToL2Messages: L1ToL2Message[] = []; + for (const log of logs) { + const { sender, senderChainId, recipient, recipientVersion, content, secretHash, deadline, fee, entryKey } = + log.args; + l1ToL2Messages.push( + new L1ToL2Message( + new L1Actor(EthAddress.fromString(sender), Number(senderChainId)), + new L2Actor(AztecAddress.fromString(recipient), Number(recipientVersion)), + Fr.fromString(content), + Fr.fromString(secretHash), + deadline, + Number(fee), + Fr.fromString(entryKey), + ), + ); + } + return l1ToL2Messages; +} /** * Processes newly received UnverifiedData logs. * @param logs - ContractDeployment logs. @@ -175,7 +205,7 @@ export async function getContractDeploymentLogs( publicClient: PublicClient, unverifiedDataEmitterAddress: EthAddress, fromBlock: bigint, -): Promise { +): Promise[]> { const abiItem = getAbiItem({ abi: UnverifiedDataEmitterAbi, name: 'ContractDeployment', @@ -186,3 +216,26 @@ export async function getContractDeploymentLogs( fromBlock, }); } + +/** + * Get relevant `MessageAdded` logs emitted by Inbox on chain. + * @param publicClient - The viem public client to use for transaction retrieval. + * @param inboxAddress - The address of the inbox contract. + * @param fromBlock - First block to get logs from (inclusive). + * @returns An array of `MessageAdded` logs. + */ +export async function getPendingL1ToL2MessageLogs( + publicClient: PublicClient, + inboxAddress: EthAddress, + fromBlock: bigint, +): Promise[]> { + const abiItem = getAbiItem({ + abi: InboxAbi, + name: 'MessageAdded', + }); + return await publicClient.getLogs({ + address: getAddress(inboxAddress.toString()), + event: abiItem, + fromBlock, + }); +} diff --git a/yarn-project/archiver/src/index.ts b/yarn-project/archiver/src/index.ts index cab552e72069..f7c81b4fa915 100644 --- a/yarn-project/archiver/src/index.ts +++ b/yarn-project/archiver/src/index.ts @@ -15,7 +15,7 @@ const log = createLogger('aztec:archiver_init'); // eslint-disable-next-line require-await async function main() { const config = getConfigEnvVars(); - const { rpcUrl, rollupContract, unverifiedDataEmitterContract } = config; + const { rpcUrl, rollupContract, inboxContract, unverifiedDataEmitterContract } = config; const publicClient = createPublicClient({ chain: localhost, @@ -24,7 +24,13 @@ async function main() { const archiverStore = new MemoryArchiverStore(); - const archiver = new Archiver(publicClient, rollupContract, unverifiedDataEmitterContract, archiverStore); + const archiver = new Archiver( + publicClient, + rollupContract, + inboxContract, + unverifiedDataEmitterContract, + archiverStore, + ); const shutdown = async () => { await archiver.stop(); diff --git a/yarn-project/end-to-end/src/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/integration_l1_publisher.test.ts index d7c8dc9f0814..745ab2bf6843 100644 --- a/yarn-project/end-to-end/src/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/integration_l1_publisher.test.ts @@ -137,6 +137,7 @@ describe('L1Publisher integration', () => { chainId: config.chainId, requiredConfirmations: 1, rollupContract: EthAddress.fromString(rollupAddress), + inboxContract: EthAddress.fromString(inboxAddress), unverifiedDataEmitterContract: EthAddress.fromString(unverifiedDataEmitterAddress), publisherPrivateKey: hexStringToBuffer(sequencerPK), retryIntervalMs: 100, diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index 364518d47c3c..4a4032c8b32b 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -20,6 +20,7 @@ export function getConfigEnvVars(): SequencerClientConfig { SEQ_TX_POLLING_INTERVAL, SEQ_MAX_TX_PER_BLOCK, ROLLUP_CONTRACT_ADDRESS, + INBOX_CONTRACT_ADDRESS, UNVERIFIED_DATA_EMITTER_ADDRESS, } = process.env; @@ -30,6 +31,7 @@ export function getConfigEnvVars(): SequencerClientConfig { retryIntervalMs: SEQ_RETRY_INTERVAL ? +SEQ_RETRY_INTERVAL : 1_000, transactionPollingInterval: SEQ_TX_POLLING_INTERVAL ? +SEQ_TX_POLLING_INTERVAL : 1_000, rollupContract: ROLLUP_CONTRACT_ADDRESS ? EthAddress.fromString(ROLLUP_CONTRACT_ADDRESS) : EthAddress.ZERO, + inboxContract: INBOX_CONTRACT_ADDRESS ? EthAddress.fromString(INBOX_CONTRACT_ADDRESS) : EthAddress.ZERO, unverifiedDataEmitterContract: UNVERIFIED_DATA_EMITTER_ADDRESS ? EthAddress.fromString(UNVERIFIED_DATA_EMITTER_ADDRESS) : EthAddress.ZERO, diff --git a/yarn-project/types/src/l1_addresses.ts b/yarn-project/types/src/l1_addresses.ts index eca12b70e893..12501a289dac 100644 --- a/yarn-project/types/src/l1_addresses.ts +++ b/yarn-project/types/src/l1_addresses.ts @@ -9,6 +9,11 @@ export interface L1Addresses { */ rollupContract: EthAddress; + /** + * Inbox contract address. + */ + inboxContract: EthAddress; + /** * UnverifiedDataEmitter contract address. */ diff --git a/yarn-project/types/src/l1_to_l2_message.ts b/yarn-project/types/src/l1_to_l2_message.ts index 2943b68ba02c..81644b0a6527 100644 --- a/yarn-project/types/src/l1_to_l2_message.ts +++ b/yarn-project/types/src/l1_to_l2_message.ts @@ -5,6 +5,18 @@ import { serializeToBuffer } from '@aztec/circuits.js/utils'; import { sha256 } from '@aztec/foundation/crypto'; import { toBigIntBE, toBufferBE } from '@aztec/foundation/bigint-buffer'; +/** + * Interface of classes allowing for the retrieval of L1 to L2 messages. + */ +export interface L1ToL2MessageSource { + /** + * Gets the `take` amount of pending L1 to L2 messages. + * @param take - The number of messages to return. + * @returns The requested L1 to L2 messages. + */ + getPendingL1ToL2Messages(take: number): Promise; +} + /** * The format of an L1 to L2 Message. */ @@ -34,9 +46,18 @@ export class L1ToL2Message { * The fee for the message. */ public readonly fee: number, - ) {} + /** + * The entry key for the message - optional. + * If not provided, it will be calculated by hashing the message (similar to the Inbox contract) + */ + public readonly entryKey?: Fr, + ) { + if (!entryKey) { + this.entryKey = this.hash(); + } + } - // TODO: sha256 hash of the message packed the same as solidity + // TODO: (#646) - sha256 hash of the message packed the same as solidity hash(): Fr { const buf = this.toBuffer(); const temp = toBigIntBE(sha256(buf));