From 0026464232f41dfe3528b290ca7a324d6b87965d Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Tue, 7 Jan 2025 11:17:49 -0300 Subject: [PATCH] Extract chain monitor and add to p2p tests --- .../aztec.js/src/utils/chain_monitor.ts | 74 +++++++++++++++++++ yarn-project/aztec.js/src/utils/index.ts | 1 + .../end-to-end/src/e2e_epochs.test.ts | 46 +++--------- .../end-to-end/src/e2e_p2p/p2p_network.ts | 8 +- yarn-project/ethereum/src/contracts/rollup.ts | 16 +++- 5 files changed, 106 insertions(+), 39 deletions(-) create mode 100644 yarn-project/aztec.js/src/utils/chain_monitor.ts diff --git a/yarn-project/aztec.js/src/utils/chain_monitor.ts b/yarn-project/aztec.js/src/utils/chain_monitor.ts new file mode 100644 index 000000000000..7f27fb38f477 --- /dev/null +++ b/yarn-project/aztec.js/src/utils/chain_monitor.ts @@ -0,0 +1,74 @@ +import { type RollupContract } from '@aztec/ethereum'; +import { createLogger } from '@aztec/foundation/log'; + +import { type PublicClient } from 'viem'; + +/** Utility class that polls the chain on quick intervals and logs new L1 blocks, L2 blocks, and L2 proofs. */ +export class ChainMonitor { + private readonly l1Client: PublicClient; + private handle: NodeJS.Timeout | undefined; + + /** Current L1 block number */ + public l1BlockNumber!: number; + /** Current L2 block number */ + public l2BlockNumber!: number; + /** Current L2 proven block number */ + public l2ProvenBlockNumber!: number; + + constructor( + private readonly rollup: RollupContract, + private logger = createLogger('aztecjs:utils:chain_monitor'), + private readonly intervalMs = 200, + ) { + this.l1Client = rollup.client; + } + + start() { + if (this.handle) { + throw new Error('Chain monitor already started'); + } + this.handle = setInterval(() => this.run(), this.intervalMs); + } + + stop() { + if (this.handle) { + clearInterval(this.handle!); + this.handle = undefined; + } + } + + async run() { + const newL1BlockNumber = Number(await this.l1Client.getBlockNumber({ cacheTime: 0 })); + if (this.l1BlockNumber === newL1BlockNumber) { + return; + } + this.l1BlockNumber = newL1BlockNumber; + + const block = await this.l1Client.getBlock({ blockNumber: BigInt(newL1BlockNumber), includeTransactions: false }); + const timestamp = block.timestamp; + const timestampString = new Date(Number(timestamp) * 1000).toTimeString().split(' ')[0]; + + let msg = `L1 block ${newL1BlockNumber} mined at ${timestampString}`; + + const newL2BlockNumber = Number(await this.rollup.getBlockNumber()); + if (this.l2BlockNumber !== newL2BlockNumber) { + const epochNumber = await this.rollup.getEpochNumber(BigInt(newL2BlockNumber)); + msg += ` with new L2 block ${newL2BlockNumber} for epoch ${epochNumber}`; + this.l2BlockNumber = newL2BlockNumber; + } + + const newL2ProvenBlockNumber = Number(await this.rollup.getProvenBlockNumber()); + if (this.l2ProvenBlockNumber !== newL2ProvenBlockNumber) { + const epochNumber = await this.rollup.getEpochNumber(BigInt(newL2ProvenBlockNumber)); + msg += ` with proof up to L2 block ${newL2ProvenBlockNumber} for epoch ${epochNumber}`; + this.l2ProvenBlockNumber = newL2ProvenBlockNumber; + } + + this.logger.info(msg, { + l1Timestamp: timestamp, + l1BlockNumber: this.l1BlockNumber, + l2BlockNumber: this.l2BlockNumber, + l2ProvenBlockNumber: this.l2ProvenBlockNumber, + }); + } +} diff --git a/yarn-project/aztec.js/src/utils/index.ts b/yarn-project/aztec.js/src/utils/index.ts index 68a1b4d12fad..a2ed02d5d9ef 100644 --- a/yarn-project/aztec.js/src/utils/index.ts +++ b/yarn-project/aztec.js/src/utils/index.ts @@ -8,3 +8,4 @@ export * from './node.js'; export * from './anvil_test_watcher.js'; export * from './field_compressed_string.js'; export * from './portal_manager.js'; +export * from './chain_monitor.js'; diff --git a/yarn-project/end-to-end/src/e2e_epochs.test.ts b/yarn-project/end-to-end/src/e2e_epochs.test.ts index 539cd573cc00..2f87bb4e9fdc 100644 --- a/yarn-project/end-to-end/src/e2e_epochs.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs.test.ts @@ -1,5 +1,6 @@ // eslint-disable-next-line no-restricted-imports import { type EpochConstants, type Logger, getTimestampRangeForEpoch, retryUntil } from '@aztec/aztec.js'; +import { ChainMonitor } from '@aztec/aztec.js/utils'; import { RollupContract } from '@aztec/ethereum/contracts'; import { type Delayer, waitUntilL1Timestamp } from '@aztec/ethereum/test'; @@ -18,11 +19,7 @@ describe('e2e_epochs', () => { let logger: Logger; let proverDelayer: Delayer; let sequencerDelayer: Delayer; - - let l2BlockNumber: number = 0; - let l2ProvenBlockNumber: number = 0; - let l1BlockNumber: number; - let handle: NodeJS.Timeout; + let monitor: ChainMonitor; const EPOCH_DURATION_IN_L2_SLOTS = 4; const L2_SLOT_DURATION_IN_L1_SLOTS = 2; @@ -52,33 +49,8 @@ describe('e2e_epochs', () => { rollup = RollupContract.getFromConfig(context.config); // Loop that tracks L1 and L2 block numbers and logs whenever there's a new one. - // We could refactor this out to an utility if we want to use this in other tests. - handle = setInterval(async () => { - const newL1BlockNumber = Number(await l1Client.getBlockNumber({ cacheTime: 0 })); - if (l1BlockNumber === newL1BlockNumber) { - return; - } - const block = await l1Client.getBlock({ blockNumber: BigInt(newL1BlockNumber), includeTransactions: false }); - const timestamp = block.timestamp; - l1BlockNumber = newL1BlockNumber; - - let msg = `L1 block ${newL1BlockNumber} mined at ${timestamp}`; - - const newL2BlockNumber = Number(await rollup.getBlockNumber()); - if (l2BlockNumber !== newL2BlockNumber) { - const epochNumber = await rollup.getEpochNumber(BigInt(newL2BlockNumber)); - msg += ` with new L2 block ${newL2BlockNumber} for epoch ${epochNumber}`; - l2BlockNumber = newL2BlockNumber; - } - - const newL2ProvenBlockNumber = Number(await rollup.getProvenBlockNumber()); - if (l2ProvenBlockNumber !== newL2ProvenBlockNumber) { - const epochNumber = await rollup.getEpochNumber(BigInt(newL2ProvenBlockNumber)); - msg += ` with proof up to L2 block ${newL2ProvenBlockNumber} for epoch ${epochNumber}`; - l2ProvenBlockNumber = newL2ProvenBlockNumber; - } - logger.info(msg); - }, 200); + monitor = new ChainMonitor(rollup, logger); + monitor.start(); // The "as any" cast sucks, but it saves us from having to define test-only types for the provernode // and sequencer that are exactly like the real ones but with the publisher exposed. We should @@ -101,7 +73,7 @@ describe('e2e_epochs', () => { }); afterEach(async () => { - clearInterval(handle); + monitor.stop(); await context.teardown(); }); @@ -115,12 +87,12 @@ describe('e2e_epochs', () => { /** Waits until the given L2 block number is mined. */ const waitUntilL2BlockNumber = async (target: number) => { - await retryUntil(() => Promise.resolve(target === l2BlockNumber), `Wait until L2 block ${target}`, 60, 0.1); + await retryUntil(() => Promise.resolve(target === monitor.l2BlockNumber), `Wait until L2 block ${target}`, 60, 0.1); }; /** Waits until the given L2 block number is marked as proven. */ - const waitUntilProvenL2BlockNumber = async (target: number) => { - await retryUntil(() => Promise.resolve(target === l2ProvenBlockNumber), `Wait proven L2 block ${target}`, 60, 0.1); + const waitUntilProvenL2BlockNumber = async (t: number) => { + await retryUntil(() => Promise.resolve(t === monitor.l2ProvenBlockNumber), `Wait proven L2 block ${t}`, 60, 0.1); }; it('does not allow submitting proof after epoch end', async () => { @@ -163,7 +135,7 @@ describe('e2e_epochs', () => { logger.info(`Starting epoch 1 after L2 block ${blockNumberAtEndOfEpoch0}`); await waitUntilProvenL2BlockNumber(blockNumberAtEndOfEpoch0); - expect(l2BlockNumber).toEqual(blockNumberAtEndOfEpoch0); + expect(monitor.l2BlockNumber).toEqual(blockNumberAtEndOfEpoch0); logger.info(`Test succeeded`); }); }); diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index e7a8f8fd56ba..7a56506c702f 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -1,7 +1,8 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; import { type AztecNodeConfig, type AztecNodeService } from '@aztec/aztec-node'; import { type AccountWalletWithSecretKey } from '@aztec/aztec.js'; -import { L1TxUtils, getL1ContractsConfigEnvVars } from '@aztec/ethereum'; +import { ChainMonitor } from '@aztec/aztec.js/utils'; +import { L1TxUtils, RollupContract, getL1ContractsConfigEnvVars } from '@aztec/ethereum'; import { EthCheatCodesWithState } from '@aztec/ethereum/test'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; @@ -38,6 +39,7 @@ export class P2PNetworkTest { private baseAccount; public logger: Logger; + public monitor!: ChainMonitor; public ctx!: SubsystemsContext; public attesterPrivateKeys: `0x${string}`[] = []; @@ -308,6 +310,9 @@ export class P2PNetworkTest { stallTimeMs: 1000, }, ); + + this.monitor = new ChainMonitor(RollupContract.getFromL1ContractsValues(this.ctx.deployL1ContractsValues)); + this.monitor.start(); } async stopNodes(nodes: AztecNodeService[]) { @@ -325,6 +330,7 @@ export class P2PNetworkTest { } async teardown() { + this.monitor.stop(); await this.bootstrapNode.stop(); await this.snapshotManager.teardown(); if (this.cleanupInterval) { diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 69bc1065653d..cbaf9d689e09 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -1,3 +1,4 @@ +import { AztecAddress } from '@aztec/foundation/aztec-address'; import { memoize } from '@aztec/foundation/decorators'; import { RollupAbi } from '@aztec/l1-artifacts'; @@ -12,16 +13,21 @@ import { http, } from 'viem'; +import { type DeployL1Contracts } from '../deploy_l1_contracts.js'; import { createEthereumChain } from '../ethereum_chain.js'; import { type L1ReaderConfig } from '../l1_reader.js'; export class RollupContract { private readonly rollup: GetContractReturnType>; - constructor(client: PublicClient, address: Hex) { + constructor(public readonly client: PublicClient, address: Hex) { this.rollup = getContract({ address, abi: RollupAbi, client }); } + public get address() { + return AztecAddress.fromString(this.rollup.address); + } + @memoize getL1StartBlock() { return this.rollup.read.L1_BLOCK_AT_GENESIS(); @@ -69,6 +75,14 @@ export class RollupContract { return this.rollup.read.getEpochForBlock([BigInt(blockNumber)]); } + static getFromL1ContractsValues(deployL1ContractsValues: DeployL1Contracts) { + const { + publicClient, + l1ContractAddresses: { rollupAddress }, + } = deployL1ContractsValues; + return new RollupContract(publicClient, rollupAddress.toString()); + } + static getFromConfig(config: L1ReaderConfig) { const client = createPublicClient({ transport: http(config.l1RpcUrl),