Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: delete eth-log-hander #8598

Merged
merged 1 commit into from
Sep 18, 2024
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
7 changes: 1 addition & 6 deletions yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ interface MockRollupContractRead {
archiveAt: (args: readonly [bigint]) => Promise<`0x${string}`>;
}

class MockRollupContract {
constructor(public read: MockRollupContractRead, public address: `0x${string}`) {}
}

describe('Archiver', () => {
const rollupAddress = EthAddress.ZERO;
const inboxAddress = EthAddress.ZERO;
Expand Down Expand Up @@ -74,10 +70,9 @@ describe('Archiver', () => {

blocks = blockNumbers.map(x => L2Block.random(x, 4, x, x + 1, 2, 2));

const mockRollupRead = mock<MockRollupContractRead>({
((archiver as any).rollup as any).read = mock<MockRollupContractRead>({
archiveAt: (args: readonly [bigint]) => Promise.resolve(blocks[Number(args[0] - 1n)].archive.root.toString()),
});
(archiver as any).rollup = new MockRollupContract(mockRollupRead, rollupAddress.toString());
});

afterEach(async () => {
Expand Down
20 changes: 15 additions & 5 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { Fr } from '@aztec/foundation/fields';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { RunningPromise } from '@aztec/foundation/running-promise';
import { Timer } from '@aztec/foundation/timer';
import { RollupAbi } from '@aztec/l1-artifacts';
import { InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
import { type TelemetryClient } from '@aztec/telemetry-client';
import {
Expand All @@ -55,8 +55,12 @@ import {

import { type ArchiverDataStore } from './archiver_store.js';
import { type ArchiverConfig } from './config.js';
import { retrieveBlockFromRollup, retrieveL1ToL2Messages, retrieveL2ProofVerifiedEvents } from './data_retrieval.js';
import { getL1BlockTime } from './eth_log_handlers.js';
import {
getL1BlockTime,
retrieveBlockFromRollup,
retrieveL1ToL2Messages,
retrieveL2ProofVerifiedEvents,
} from './data_retrieval.js';
import { ArchiverInstrumentation } from './instrumentation.js';
import { type SingletonDataRetrieval } from './structs/data_retrieval.js';

Expand All @@ -77,6 +81,7 @@ export class Archiver implements ArchiveSource {
private runningPromise?: RunningPromise;

private rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>;
private inbox: GetContractReturnType<typeof InboxAbi, PublicClient<HttpTransport, Chain>>;

/**
* Creates a new instance of the Archiver.
Expand Down Expand Up @@ -104,6 +109,12 @@ export class Archiver implements ArchiveSource {
abi: RollupAbi,
client: publicClient,
});

this.inbox = getContract({
address: inboxAddress.toString(),
abi: InboxAbi,
client: publicClient,
});
}

/**
Expand Down Expand Up @@ -244,8 +255,7 @@ export class Archiver implements ArchiveSource {
// ********** Events that are processed per L2 block **********

const retrievedL1ToL2Messages = await retrieveL1ToL2Messages(
this.publicClient,
this.inboxAddress,
this.inbox,
blockUntilSynced,
messagesSynchedTo + 1n,
currentL1BlockNumber,
Expand Down
198 changes: 176 additions & 22 deletions yarn-project/archiver/src/archiver/data_retrieval.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import { type InboxLeaf, type L2Block } from '@aztec/circuit-types';
import { Fr, type Proof } from '@aztec/circuits.js';
import { EthAddress } from '@aztec/foundation/eth-address';
import { Body, InboxLeaf, L2Block } from '@aztec/circuit-types';
import { AppendOnlyTreeSnapshot, Fr, Header, Proof } from '@aztec/circuits.js';
import { type EthAddress } from '@aztec/foundation/eth-address';
import { type ViemSignature } from '@aztec/foundation/eth-signature';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { RollupAbi } from '@aztec/l1-artifacts';
import { numToUInt32BE } from '@aztec/foundation/serialize';
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';

import {
type Chain,
type GetContractEventsReturnType,
type GetContractReturnType,
type Hex,
type HttpTransport,
type PublicClient,
decodeFunctionData,
getAbiItem,
hexToBytes,
} from 'viem';

import {
getBlockProofFromSubmitProofTx,
getL2BlockProposedLogs,
getMessageSentLogs,
processL2BlockProposedLogs,
processMessageSentLogs,
} from './eth_log_handlers.js';
import { type DataRetrieval } from './structs/data_retrieval.js';
import { type L1Published } from './structs/published.js';
import { type L1Published, type L1PublishedData } from './structs/published.js';

/**
* Fetches new L2 blocks.
Expand All @@ -46,11 +44,12 @@ export async function retrieveBlockFromRollup(
if (searchStartBlock > searchEndBlock) {
break;
}
const l2BlockProposedLogs = await getL2BlockProposedLogs(
publicClient,
EthAddress.fromString(rollup.address),
searchStartBlock,
searchEndBlock,
const l2BlockProposedLogs = await rollup.getEvents.L2BlockProposed(
{},
{
fromBlock: searchStartBlock,
toBlock: searchEndBlock + 1n,
},
);

if (l2BlockProposedLogs.length === 0) {
Expand All @@ -69,6 +68,96 @@ export async function retrieveBlockFromRollup(
return retrievedBlocks;
}

/**
* Processes newly received L2BlockProposed logs.
* @param rollup - The rollup contract
* @param publicClient - The viem public client to use for transaction retrieval.
* @param logs - L2BlockProposed logs.
* @returns - An array blocks.
*/
export async function processL2BlockProposedLogs(
rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
publicClient: PublicClient,
logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
logger: DebugLogger,
): Promise<L1Published<L2Block>[]> {
const retrievedBlocks: L1Published<L2Block>[] = [];
for (const log of logs) {
const blockNum = log.args.blockNumber!;
const archive = log.args.archive!;
const archiveFromChain = await rollup.read.archiveAt([blockNum]);

// The value from the event and contract will match only if the block is in the chain.
if (archive === archiveFromChain) {
// TODO: Fetch blocks from calldata in parallel
const block = await getBlockFromRollupTx(publicClient, log.transactionHash!, blockNum);

const l1: L1PublishedData = {
blockNumber: log.blockNumber,
blockHash: log.blockHash,
timestamp: await getL1BlockTime(publicClient, log.blockNumber),
};

retrievedBlocks.push({ data: block, l1 });
} else {
logger.warn(
`Archive mismatch matching, ignoring block ${blockNum} with archive: ${archive}, expected ${archiveFromChain}`,
);
}
}

return retrievedBlocks;
}

export async function getL1BlockTime(publicClient: PublicClient, blockNumber: bigint): Promise<bigint> {
const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
return block.timestamp;
}

/**
* Gets block from the calldata of an L1 transaction.
* Assumes that the block was published from an EOA.
* TODO: Add retries and error management.
* @param publicClient - The viem public client to use for transaction retrieval.
* @param txHash - Hash of the tx that published it.
* @param l2BlockNum - L2 block number.
* @returns L2 block from the calldata, deserialized
*/
async function getBlockFromRollupTx(
publicClient: PublicClient,
txHash: `0x${string}`,
l2BlockNum: bigint,
): Promise<L2Block> {
const { input: data } = await publicClient.getTransaction({ hash: txHash });
const { functionName, args } = decodeFunctionData({
abi: RollupAbi,
data,
});

if (!(functionName === 'propose')) {
throw new Error(`Unexpected method called ${functionName}`);
}
const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex];

const header = Header.fromBuffer(Buffer.from(hexToBytes(headerHex)));
const blockBody = Body.fromBuffer(Buffer.from(hexToBytes(bodyHex)));

const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();

if (blockNumberFromHeader !== l2BlockNum) {
throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
}

const archive = AppendOnlyTreeSnapshot.fromBuffer(
Buffer.concat([
Buffer.from(hexToBytes(archiveRootHex)), // L2Block.archive.root
numToUInt32BE(Number(l2BlockNum + 1n)), // L2Block.archive.nextAvailableLeafIndex
]),
);

return new L2Block(archive, header, blockBody);
}

/**
* Fetch L1 to L2 messages.
* @param publicClient - The viem public client to use for transaction retrieval.
Expand All @@ -79,8 +168,7 @@ export async function retrieveBlockFromRollup(
* @returns An array of InboxLeaf and next eth block to search from.
*/
export async function retrieveL1ToL2Messages(
publicClient: PublicClient,
inboxAddress: EthAddress,
inbox: GetContractReturnType<typeof InboxAbi, PublicClient<HttpTransport, Chain>>,
blockUntilSynced: boolean,
searchStartBlock: bigint,
searchEndBlock: bigint,
Expand All @@ -90,12 +178,24 @@ export async function retrieveL1ToL2Messages(
if (searchStartBlock > searchEndBlock) {
break;
}
const messageSentLogs = await getMessageSentLogs(publicClient, inboxAddress, searchStartBlock, searchEndBlock);

const messageSentLogs = await inbox.getEvents.MessageSent(
{},
{
fromBlock: searchStartBlock,
toBlock: searchEndBlock + 1n,
},
);

if (messageSentLogs.length === 0) {
break;
}
const l1ToL2Messages = processMessageSentLogs(messageSentLogs);
retrievedL1ToL2Messages.push(...l1ToL2Messages);

for (const log of messageSentLogs) {
const { l2BlockNumber, index, hash } = log.args;
retrievedL1ToL2Messages.push(new InboxLeaf(l2BlockNumber!, index!, Fr.fromString(hash!)));
}

// handles the case when there are no new messages:
searchStartBlock = (messageSentLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n;
} while (blockUntilSynced && searchStartBlock <= searchEndBlock);
Expand Down Expand Up @@ -145,3 +245,57 @@ export async function retrieveL2ProofsFromRollup(
lastProcessedL1BlockNumber,
};
}

export type SubmitBlockProof = {
header: Header;
archiveRoot: Fr;
proverId: Fr;
aggregationObject: Buffer;
proof: Proof;
};

/**
* Gets block metadata (header and archive snapshot) from the calldata of an L1 transaction.
* Assumes that the block was published from an EOA.
* TODO: Add retries and error management.
* @param publicClient - The viem public client to use for transaction retrieval.
* @param txHash - Hash of the tx that published it.
* @param l2BlockNum - L2 block number.
* @returns L2 block metadata (header and archive) from the calldata, deserialized
*/
export async function getBlockProofFromSubmitProofTx(
publicClient: PublicClient,
txHash: `0x${string}`,
l2BlockNum: bigint,
expectedProverId: Fr,
): Promise<SubmitBlockProof> {
const { input: data } = await publicClient.getTransaction({ hash: txHash });
const { functionName, args } = decodeFunctionData({
abi: RollupAbi,
data,
});

if (!(functionName === 'submitBlockRootProof')) {
throw new Error(`Unexpected method called ${functionName}`);
}
const [headerHex, archiveHex, proverIdHex, aggregationObjectHex, proofHex] = args!;

const header = Header.fromBuffer(Buffer.from(hexToBytes(headerHex)));
const proverId = Fr.fromString(proverIdHex);

const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();
if (blockNumberFromHeader !== l2BlockNum) {
throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
}
if (!proverId.equals(expectedProverId)) {
throw new Error(`Prover ID mismatch: expected ${expectedProverId} but got ${proverId}`);
}

return {
header,
proverId,
aggregationObject: Buffer.from(hexToBytes(aggregationObjectHex)),
archiveRoot: Fr.fromString(archiveHex),
proof: Proof.fromBuffer(Buffer.from(hexToBytes(proofHex))),
};
}
Loading
Loading