Skip to content

Commit

Permalink
Merge de0c384 into e3ea298
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind authored Sep 17, 2024
2 parents e3ea298 + de0c384 commit d49dde1
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 82 deletions.
7 changes: 5 additions & 2 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
uint256 l2ToL1TreeMinHeight = min + 1;
OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight);

emit L2BlockProposed(blockNumber);
emit L2BlockProposed(blockNumber, _archive);

// Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber.
if (blockNumber <= assumeProvenThroughBlockNumber) {
Expand Down Expand Up @@ -390,7 +390,10 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
* @return bytes32 - The archive root of the block
*/
function archiveAt(uint256 _blockNumber) external view override(IRollup) returns (bytes32) {
return blocks[_blockNumber].archive;
if (_blockNumber <= tips.pendingBlockNumber) {
return blocks[_blockNumber].archive;
}
return bytes32(0);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface ITestRollup {
}

interface IRollup {
event L2BlockProposed(uint256 indexed blockNumber);
event L2BlockProposed(uint256 indexed blockNumber, bytes32 indexed archive);
event L2ProofVerified(uint256 indexed blockNumber, bytes32 indexed proverId);
event PrunedPending(uint256 provenBlockNumber, uint256 pendingBlockNumber);

Expand Down
99 changes: 75 additions & 24 deletions yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Fr } from '@aztec/foundation/fields';
import { sleep } from '@aztec/foundation/sleep';
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';

import { jest } from '@jest/globals';
import { type MockProxy, mock } from 'jest-mock-extended';
import {
type Chain,
Expand All @@ -26,6 +27,14 @@ import { type ArchiverDataStore } from './archiver_store.js';
import { type ArchiverInstrumentation } from './instrumentation.js';
import { MemoryArchiverStore } from './memory_archiver_store/memory_archiver_store.js';

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 All @@ -39,6 +48,7 @@ describe('Archiver', () => {
let now: number;

let archiver: Archiver;
let blocks: L2Block[];

beforeEach(() => {
now = +new Date();
Expand All @@ -47,16 +57,11 @@ describe('Archiver', () => {
timestamp: args.blockNumber * 1000n + BigInt(now),
})) as any,
});

instrumentation = mock({ isEnabled: () => true });
archiverStore = new MemoryArchiverStore(1000);
proverId = Fr.random();
});

afterEach(async () => {
await archiver?.stop();
});

it('can start, sync and stop and handle l1 to l2 messages and logs', async () => {
archiver = new Archiver(
publicClient,
rollupAddress,
Expand All @@ -67,18 +72,30 @@ describe('Archiver', () => {
instrumentation,
);

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

const mockRollupRead = 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 () => {
await archiver?.stop();
});

it('can start, sync and stop and handle l1 to l2 messages and logs', async () => {
let latestBlockNum = await archiver.getBlockNumber();
expect(latestBlockNum).toEqual(0);

const blocks = blockNumbers.map(x => L2Block.random(x, 4, x, x + 1, 2, 2));
blocks.forEach((b, i) => (b.header.globalVariables.timestamp = new Fr(now + 1000 * (i + 1))));
const rollupTxs = blocks.map(makeRollupTx);

publicClient.getBlockNumber.mockResolvedValueOnce(2500n).mockResolvedValueOnce(2600n).mockResolvedValueOnce(2700n);

mockGetLogs({
messageSent: [makeMessageSentEvent(98n, 1n, 0n), makeMessageSentEvent(99n, 1n, 1n)],
L2BlockProposed: [makeL2BlockProposedEvent(101n, 1n)],
L2BlockProposed: [makeL2BlockProposedEvent(101n, 1n, blocks[0].archive.root.toString())],
proofVerified: [makeProofVerifiedEvent(102n, 1n, proverId)],
});

Expand All @@ -89,7 +106,10 @@ describe('Archiver', () => {
makeMessageSentEvent(2505n, 2n, 2n),
makeMessageSentEvent(2506n, 3n, 1n),
],
L2BlockProposed: [makeL2BlockProposedEvent(2510n, 2n), makeL2BlockProposedEvent(2520n, 3n)],
L2BlockProposed: [
makeL2BlockProposedEvent(2510n, 2n, blocks[1].archive.root.toString()),
makeL2BlockProposedEvent(2520n, 3n, blocks[2].archive.root.toString()),
],
});

publicClient.getTransaction.mockResolvedValueOnce(rollupTxs[0]);
Expand Down Expand Up @@ -168,45 +188,76 @@ describe('Archiver', () => {
}, 10_000);

it('does not sync past current block number', async () => {
let latestBlockNum = await archiver.getBlockNumber();
expect(latestBlockNum).toEqual(0);

const numL2BlocksInTest = 2;
archiver = new Archiver(
publicClient,
rollupAddress,
inboxAddress,
registryAddress,
archiverStore,
1000,
instrumentation,
);

const rollupTxs = blocks.map(makeRollupTx);

// Here we set the current L1 block number to 102. L1 to L2 messages after this should not be read.
publicClient.getBlockNumber.mockResolvedValue(102n);

mockGetLogs({
messageSent: [makeMessageSentEvent(66n, 1n, 0n), makeMessageSentEvent(68n, 1n, 1n)],
L2BlockProposed: [
makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString()),
makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString()),
],
});

mockGetLogs({});

rollupTxs.slice(0, numL2BlocksInTest).forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx));

await archiver.start(false);

while ((await archiver.getBlockNumber()) !== numL2BlocksInTest) {
await sleep(100);
}

latestBlockNum = await archiver.getBlockNumber();
expect(latestBlockNum).toEqual(numL2BlocksInTest);
}, 10_000);

it('ignores block 3 because it have been pruned (simulate pruning)', async () => {
const loggerSpy = jest.spyOn((archiver as any).log, 'warn');

let latestBlockNum = await archiver.getBlockNumber();
expect(latestBlockNum).toEqual(0);

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

const rollupTxs = blocks.map(makeRollupTx);

// Here we set the current L1 block number to 102. L1 to L2 messages after this should not be read.
publicClient.getBlockNumber.mockResolvedValue(102n);

const badArchive = Fr.random().toString();

mockGetLogs({
messageSent: [makeMessageSentEvent(66n, 1n, 0n), makeMessageSentEvent(68n, 1n, 1n)],
L2BlockProposed: [makeL2BlockProposedEvent(70n, 1n), makeL2BlockProposedEvent(80n, 2n)],
L2BlockProposed: [
makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString()),
makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString()),
makeL2BlockProposedEvent(90n, 3n, badArchive),
],
});

mockGetLogs({});

rollupTxs.slice(0, numL2BlocksInTest).forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx));
rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx));

await archiver.start(false);

// Wait until block 3 is processed. If this won't happen the test will fail with timeout.
while ((await archiver.getBlockNumber()) !== numL2BlocksInTest) {
await sleep(100);
}

latestBlockNum = await archiver.getBlockNumber();
expect(latestBlockNum).toEqual(numL2BlocksInTest);
const errorMessage = `Archive mismatch matching, ignoring block ${3} with archive: ${badArchive}, expected ${blocks[2].archive.root.toString()}`;
expect(loggerSpy).toHaveBeenCalledWith(errorMessage);
}, 10_000);

// logs should be created in order of how archiver syncs.
Expand All @@ -228,10 +279,10 @@ describe('Archiver', () => {
* @param l2BlockNum - L2 Block number.
* @returns An L2BlockProposed event log.
*/
function makeL2BlockProposedEvent(l1BlockNum: bigint, l2BlockNum: bigint) {
function makeL2BlockProposedEvent(l1BlockNum: bigint, l2BlockNum: bigint, archive: `0x${string}`) {
return {
blockNumber: l1BlockNum,
args: { blockNumber: l2BlockNum },
args: { blockNumber: l2BlockNum, archive },
transactionHash: `0x${l2BlockNum}`,
} as Log<bigint, number, false, undefined, true, typeof RollupAbi, 'L2BlockProposed'>;
}
Expand Down
27 changes: 20 additions & 7 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ import {
} from '@aztec/types/contracts';

import groupBy from 'lodash.groupby';
import { type Chain, type HttpTransport, type PublicClient, createPublicClient, getContract, http } from 'viem';
import {
type Chain,
type GetContractReturnType,
type HttpTransport,
type PublicClient,
createPublicClient,
getContract,
http,
} from 'viem';

import { type ArchiverDataStore } from './archiver_store.js';
import { type ArchiverConfig } from './config.js';
Expand All @@ -68,6 +76,8 @@ export class Archiver implements ArchiveSource {
*/
private runningPromise?: RunningPromise;

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

/**
* Creates a new instance of the Archiver.
* @param publicClient - A client for interacting with the Ethereum node.
Expand All @@ -88,7 +98,13 @@ export class Archiver implements ArchiveSource {
private readonly instrumentation: ArchiverInstrumentation,
private readonly l1StartBlock: bigint = 0n,
private readonly log: DebugLogger = createDebugLogger('aztec:archiver'),
) {}
) {
this.rollup = getContract({
address: rollupAddress.toString(),
abi: RollupAbi,
client: publicClient,
});
}

/**
* Creates a new instance of the Archiver and blocks until it syncs from chain.
Expand Down Expand Up @@ -245,17 +261,14 @@ export class Archiver implements ArchiveSource {

await this.store.addL1ToL2Messages(retrievedL1ToL2Messages);

// Read all data from chain and then write to our stores at the end
const nextExpectedL2BlockNum = BigInt((await this.store.getSynchedL2BlockNumber()) + 1);

this.log.debug(`Retrieving blocks from ${blocksSynchedTo + 1n} to ${currentL1BlockNumber}`);
const retrievedBlocks = await retrieveBlockFromRollup(
this.rollup,
this.publicClient,
this.rollupAddress,
blockUntilSynced,
blocksSynchedTo + 1n,
currentL1BlockNumber,
nextExpectedL2BlockNum,
this.log,
);

// Add the body
Expand Down
28 changes: 17 additions & 11 deletions yarn-project/archiver/src/archiver/data_retrieval.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { type InboxLeaf, type L2Block } from '@aztec/circuit-types';
import { Fr, type Proof } from '@aztec/circuits.js';
import { type EthAddress } from '@aztec/foundation/eth-address';
import { EthAddress } from '@aztec/foundation/eth-address';
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
import { RollupAbi } from '@aztec/l1-artifacts';

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

import {
getBlockProofFromSubmitProofTx,
Expand All @@ -27,38 +34,37 @@ import { type L1Published } from './structs/published.js';
* @returns An array of block; as well as the next eth block to search from.
*/
export async function retrieveBlockFromRollup(
rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
publicClient: PublicClient,
rollupAddress: EthAddress,
blockUntilSynced: boolean,
searchStartBlock: bigint,
searchEndBlock: bigint,
expectedNextL2BlockNum: bigint,
logger: DebugLogger = createDebugLogger('aztec:archiver'),
): Promise<L1Published<L2Block>[]> {
const retrievedBlocks: L1Published<L2Block>[] = [];
do {
if (searchStartBlock > searchEndBlock) {
break;
}
const L2BlockProposedLogs = await getL2BlockProposedLogs(
const l2BlockProposedLogs = await getL2BlockProposedLogs(
publicClient,
rollupAddress,
EthAddress.fromString(rollup.address),
searchStartBlock,
searchEndBlock,
);
if (L2BlockProposedLogs.length === 0) {

if (l2BlockProposedLogs.length === 0) {
break;
}

const lastLog = L2BlockProposedLogs[L2BlockProposedLogs.length - 1];
const lastLog = l2BlockProposedLogs[l2BlockProposedLogs.length - 1];
logger.debug(
`Got L2 block processed logs for ${L2BlockProposedLogs[0].blockNumber}-${lastLog.blockNumber} between ${searchStartBlock}-${searchEndBlock} L1 blocks`,
`Got L2 block processed logs for ${l2BlockProposedLogs[0].blockNumber}-${lastLog.blockNumber} between ${searchStartBlock}-${searchEndBlock} L1 blocks`,
);

const newBlocks = await processL2BlockProposedLogs(publicClient, expectedNextL2BlockNum, L2BlockProposedLogs);
const newBlocks = await processL2BlockProposedLogs(rollup, publicClient, l2BlockProposedLogs, logger);
retrievedBlocks.push(...newBlocks);
searchStartBlock = lastLog.blockNumber! + 1n;
expectedNextL2BlockNum += BigInt(newBlocks.length);
} while (blockUntilSynced && searchStartBlock <= searchEndBlock);
return retrievedBlocks;
}
Expand Down
Loading

0 comments on commit d49dde1

Please sign in to comment.