From ed5b2a3e6923aed2c80ad5fd0ac847e58377f0cc Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 8 Jan 2025 19:19:10 -0300 Subject: [PATCH] fix: Prover node aborts execution at epoch end Prover node aborts all jobs once it hits the end of the next epoch to be proven. Fixes #10802 --- .../archiver/src/archiver/archiver.ts | 4 +- .../archiver/src/test/mock_l2_block_source.ts | 7 +- yarn-project/aztec.js/src/index.ts | 1 - .../src/epoch-helpers/index.test.ts | 5 +- .../circuit-types/src/epoch-helpers/index.ts | 42 ++--- .../src/interfaces/archiver.test.ts | 9 + .../circuit-types/src/interfaces/archiver.ts | 2 + .../src/interfaces/prover-node.ts | 11 ++ .../circuit-types/src/l2_block_source.ts | 6 + yarn-project/circuits.js/src/structs/proof.ts | 5 + ...block_root_or_block_merge_public_inputs.ts | 4 + .../src/structs/rollup/root_rollup.ts | 19 +++ .../trees/append_only_tree_snapshot.ts | 4 + .../end-to-end/src/e2e_block_building.test.ts | 15 +- .../end-to-end/src/e2e_epochs.test.ts | 60 +++++-- yarn-project/end-to-end/src/fixtures/utils.ts | 3 +- .../ethereum/src/deploy_l1_contracts.ts | 2 +- yarn-project/ethereum/src/test/tx_delayer.ts | 6 +- yarn-project/prover-node/package.json | 3 +- .../src/job/epoch-proving-job.test.ts | 155 ++++++++++++++++++ .../prover-node/src/job/epoch-proving-job.ts | 81 +++++++-- .../prover-node/src/prover-node.test.ts | 3 + yarn-project/prover-node/src/prover-node.ts | 40 +++-- yarn-project/prover-node/src/test/index.ts | 11 ++ yarn-project/sequencer-client/package.json | 3 +- .../sequencer-client/src/publisher/index.ts | 1 - .../src/sequencer/sequencer.ts | 30 ++-- .../sequencer-client/src/test/index.ts | 23 +++ .../{publisher => test}/test-l1-publisher.ts | 2 +- 29 files changed, 454 insertions(+), 103 deletions(-) create mode 100644 yarn-project/prover-node/src/job/epoch-proving-job.test.ts create mode 100644 yarn-project/prover-node/src/test/index.ts create mode 100644 yarn-project/sequencer-client/src/test/index.ts rename yarn-project/sequencer-client/src/{publisher => test}/test-l1-publisher.ts (92%) diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 7bbab882dbf..d81f412252a 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -508,8 +508,8 @@ export class Archiver implements ArchiveSource, Traceable { return Promise.resolve(); } - public getL1Constants(): L1RollupConstants { - return this.l1constants; + public getL1Constants(): Promise { + return Promise.resolve(this.l1constants); } public getRollupAddress(): Promise { diff --git a/yarn-project/archiver/src/test/mock_l2_block_source.ts b/yarn-project/archiver/src/test/mock_l2_block_source.ts index 5cd61233e69..4f1ea8f15bb 100644 --- a/yarn-project/archiver/src/test/mock_l2_block_source.ts +++ b/yarn-project/archiver/src/test/mock_l2_block_source.ts @@ -1,4 +1,5 @@ import { + type L1RollupConstants, L2Block, L2BlockHash, type L2BlockSource, @@ -6,8 +7,8 @@ import { type TxHash, TxReceipt, TxStatus, + getSlotRangeForEpoch, } from '@aztec/circuit-types'; -import { getSlotRangeForEpoch } from '@aztec/circuit-types'; import { type BlockHeader, EthAddress } from '@aztec/circuits.js'; import { DefaultL1ContractsConfig } from '@aztec/ethereum'; import { createLogger } from '@aztec/foundation/log'; @@ -187,6 +188,10 @@ export class MockL2BlockSource implements L2BlockSource { throw new Error('Method not implemented.'); } + getL1Constants(): Promise { + throw new Error('Method not implemented.'); + } + /** * Starts the block source. In this mock implementation, this is a noop. * @returns A promise that signals the initialization of the l2 block source on completion. diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 4164b5000a8..be601cc31ac 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -125,7 +125,6 @@ export { mockEpochProofQuote, mockTx, type AztecNode, - type EpochConstants, type LogFilter, type PXE, type PartialAddress, diff --git a/yarn-project/circuit-types/src/epoch-helpers/index.test.ts b/yarn-project/circuit-types/src/epoch-helpers/index.test.ts index 4fa879b7f6c..e3d38c9bcee 100644 --- a/yarn-project/circuit-types/src/epoch-helpers/index.test.ts +++ b/yarn-project/circuit-types/src/epoch-helpers/index.test.ts @@ -1,12 +1,11 @@ -import { type EpochConstants, getTimestampRangeForEpoch } from './index.js'; +import { type L1RollupConstants, getTimestampRangeForEpoch } from './index.js'; describe('EpochHelpers', () => { - let constants: EpochConstants; + let constants: Omit; const l1GenesisTime = 1734440000n; beforeEach(() => { constants = { - l1GenesisBlock: 10n, l1GenesisTime: l1GenesisTime, epochDuration: 4, slotDuration: 24, diff --git a/yarn-project/circuit-types/src/epoch-helpers/index.ts b/yarn-project/circuit-types/src/epoch-helpers/index.ts index 90a22f1a4b5..e72acb74397 100644 --- a/yarn-project/circuit-types/src/epoch-helpers/index.ts +++ b/yarn-project/circuit-types/src/epoch-helpers/index.ts @@ -1,3 +1,7 @@ +import { type ZodFor, schemas } from '@aztec/foundation/schemas'; + +import { z } from 'zod'; + export type L1RollupConstants = { l1StartBlock: bigint; l1GenesisTime: bigint; @@ -14,30 +18,29 @@ export const EmptyL1RollupConstants: L1RollupConstants = { ethereumSlotDuration: 1, }; -// REFACTOR: Merge this type with L1RollupConstants -export type EpochConstants = { - l1GenesisBlock: bigint; - l1GenesisTime: bigint; - epochDuration: number; - slotDuration: number; - ethereumSlotDuration: number; -}; +export const L1RollupConstantsSchema = z.object({ + l1StartBlock: schemas.BigInt, + l1GenesisTime: schemas.BigInt, + slotDuration: z.number(), + epochDuration: z.number(), + ethereumSlotDuration: z.number(), +}) satisfies ZodFor; /** Returns the slot number for a given timestamp. */ -export function getSlotAtTimestamp(ts: bigint, constants: Pick) { +export function getSlotAtTimestamp(ts: bigint, constants: Pick) { return ts < constants.l1GenesisTime ? 0n : (ts - constants.l1GenesisTime) / BigInt(constants.slotDuration); } /** Returns the epoch number for a given timestamp. */ export function getEpochNumberAtTimestamp( ts: bigint, - constants: Pick, + constants: Pick, ) { return getSlotAtTimestamp(ts, constants) / BigInt(constants.epochDuration); } /** Returns the range of L2 slots (inclusive) for a given epoch number. */ -export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick) { +export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick) { const startSlot = epochNumber * BigInt(constants.epochDuration); return [startSlot, startSlot + BigInt(constants.epochDuration) - 1n]; } @@ -48,7 +51,7 @@ export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick, + constants: Pick, ) { const [startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, constants); const ethereumSlotsPerL2Slot = constants.slotDuration / constants.ethereumSlotDuration; @@ -59,18 +62,3 @@ export function getTimestampRangeForEpoch( BigInt((ethereumSlotsPerL2Slot - 1) * constants.ethereumSlotDuration), ]; } - -/** - * Returns the range of L1 blocks (inclusive) for a given epoch number. - * @remarks This assumes no time warp has happened. - */ -export function getL1BlockRangeForEpoch( - epochNumber: bigint, - constants: Pick, -) { - const epochDurationInL1Blocks = BigInt(constants.epochDuration) * BigInt(constants.slotDuration); - return [ - epochNumber * epochDurationInL1Blocks + constants.l1GenesisBlock, - (epochNumber + 1n) * epochDurationInL1Blocks + constants.l1GenesisBlock - 1n, - ]; -} diff --git a/yarn-project/circuit-types/src/interfaces/archiver.test.ts b/yarn-project/circuit-types/src/interfaces/archiver.test.ts index f526d9944fe..de3c342927d 100644 --- a/yarn-project/circuit-types/src/interfaces/archiver.test.ts +++ b/yarn-project/circuit-types/src/interfaces/archiver.test.ts @@ -21,6 +21,7 @@ import { readFileSync } from 'fs'; import omit from 'lodash.omit'; import { resolve } from 'path'; +import { EmptyL1RollupConstants, type L1RollupConstants } from '../epoch-helpers/index.js'; import { type InBlock, randomInBlock } from '../in_block.js'; import { L2Block } from '../l2_block.js'; import { type L2Tips } from '../l2_block_source.js'; @@ -248,6 +249,11 @@ describe('ArchiverApiSchema', () => { privateFunctions: [], }); }); + + it('getL1Constants', async () => { + const result = await context.client.getL1Constants(); + expect(result).toEqual(EmptyL1RollupConstants); + }); }); class MockArchiver implements ArchiverApi { @@ -388,4 +394,7 @@ class MockArchiver implements ArchiverApi { addContractClass(_contractClass: ContractClassPublic): Promise { return Promise.resolve(); } + getL1Constants(): Promise { + return Promise.resolve(EmptyL1RollupConstants); + } } diff --git a/yarn-project/circuit-types/src/interfaces/archiver.ts b/yarn-project/circuit-types/src/interfaces/archiver.ts index fe67917ee68..e610fb7de81 100644 --- a/yarn-project/circuit-types/src/interfaces/archiver.ts +++ b/yarn-project/circuit-types/src/interfaces/archiver.ts @@ -10,6 +10,7 @@ import { type ApiSchemaFor, optional, schemas } from '@aztec/foundation/schemas' import { z } from 'zod'; +import { L1RollupConstantsSchema } from '../epoch-helpers/index.js'; import { inBlockSchemaFor } from '../in_block.js'; import { L2Block } from '../l2_block.js'; import { type L2BlockSource, L2TipsSchema } from '../l2_block_source.js'; @@ -77,4 +78,5 @@ export const ArchiverApiSchema: ApiSchemaFor = { .function() .args(schemas.AztecAddress, schemas.FunctionSelector) .returns(optional(z.string())), + getL1Constants: z.function().args().returns(L1RollupConstantsSchema), }; diff --git a/yarn-project/circuit-types/src/interfaces/prover-node.ts b/yarn-project/circuit-types/src/interfaces/prover-node.ts index a27924353c8..7fca59b4b1c 100644 --- a/yarn-project/circuit-types/src/interfaces/prover-node.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-node.ts @@ -15,10 +15,21 @@ const EpochProvingJobState = [ 'publishing-proof', 'completed', 'failed', + 'stopped', + 'timed-out', ] as const; export type EpochProvingJobState = (typeof EpochProvingJobState)[number]; +export const EpochProvingJobTerminalState: EpochProvingJobState[] = [ + 'completed', + 'failed', + 'stopped', + 'timed-out', +] as const; + +export type EpochProvingJobTerminalState = (typeof EpochProvingJobTerminalState)[number]; + /** JSON RPC public interface to a prover node. */ export interface ProverNodeApi { getJobs(): Promise<{ uuid: string; status: EpochProvingJobState }[]>; diff --git a/yarn-project/circuit-types/src/l2_block_source.ts b/yarn-project/circuit-types/src/l2_block_source.ts index 5a62f159b38..3012eb0c4e1 100644 --- a/yarn-project/circuit-types/src/l2_block_source.ts +++ b/yarn-project/circuit-types/src/l2_block_source.ts @@ -2,6 +2,7 @@ import { type BlockHeader, type EthAddress } from '@aztec/circuits.js'; import { z } from 'zod'; +import { type L1RollupConstants } from './epoch-helpers/index.js'; import { type InBlock } from './in_block.js'; import { type L2Block } from './l2_block.js'; import { type TxHash } from './tx/tx_hash.js'; @@ -106,6 +107,11 @@ export interface L2BlockSource { * Returns the tips of the L2 chain. */ getL2Tips(): Promise; + + /** + * Returns the rollup constants for the current chain. + */ + getL1Constants(): Promise; } /** diff --git a/yarn-project/circuits.js/src/structs/proof.ts b/yarn-project/circuits.js/src/structs/proof.ts index ec6af85223c..de3942dab7c 100644 --- a/yarn-project/circuits.js/src/structs/proof.ts +++ b/yarn-project/circuits.js/src/structs/proof.ts @@ -99,6 +99,11 @@ export class Proof { this.buffer.length === EMPTY_PROOF_SIZE && this.buffer.every(byte => byte === 0) && this.numPublicInputs === 0 ); } + + /** Returns an empty proof. */ + static empty() { + return makeEmptyProof(); + } } /** diff --git a/yarn-project/circuits.js/src/structs/rollup/block_root_or_block_merge_public_inputs.ts b/yarn-project/circuits.js/src/structs/rollup/block_root_or_block_merge_public_inputs.ts index 895824df0e0..0731d0869c2 100644 --- a/yarn-project/circuits.js/src/structs/rollup/block_root_or_block_merge_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/rollup/block_root_or_block_merge_public_inputs.ts @@ -168,4 +168,8 @@ export class FeeRecipient { } return { recipient: this.recipient.toString(), value: this.value.toString() }; } + + static random() { + return new FeeRecipient(EthAddress.random(), Fr.random()); + } } diff --git a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts index b5eab3be568..f494e7e1f2c 100644 --- a/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts +++ b/yarn-project/circuits.js/src/structs/rollup/root_rollup.ts @@ -1,3 +1,4 @@ +import { makeTuple } from '@aztec/foundation/array'; import { Fr } from '@aztec/foundation/fields'; import { bufferSchemaFor } from '@aztec/foundation/schemas'; import { BufferReader, type Tuple, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; @@ -184,4 +185,22 @@ export class RootRollupPublicInputs { static get schema() { return bufferSchemaFor(RootRollupPublicInputs); } + + /** Creates a random instance. */ + static random() { + return new RootRollupPublicInputs( + AppendOnlyTreeSnapshot.random(), + AppendOnlyTreeSnapshot.random(), + Fr.random(), + Fr.random(), + Fr.random(), + Fr.random(), + Fr.random(), + makeTuple(AZTEC_MAX_EPOCH_DURATION, FeeRecipient.random), + Fr.random(), + Fr.random(), + Fr.random(), + makeTuple(AZTEC_MAX_EPOCH_DURATION, BlockBlobPublicInputs.empty), + ); + } } diff --git a/yarn-project/circuits.js/src/structs/trees/append_only_tree_snapshot.ts b/yarn-project/circuits.js/src/structs/trees/append_only_tree_snapshot.ts index 5a97560f2e9..90d070338a1 100644 --- a/yarn-project/circuits.js/src/structs/trees/append_only_tree_snapshot.ts +++ b/yarn-project/circuits.js/src/structs/trees/append_only_tree_snapshot.ts @@ -88,4 +88,8 @@ export class AppendOnlyTreeSnapshot { public equals(other: this) { return this.root.equals(other.root) && this.nextAvailableLeafIndex === other.nextAvailableLeafIndex; } + + static random() { + return new AppendOnlyTreeSnapshot(Fr.random(), Math.floor(Math.random() * 1000)); + } } diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index 4421e007e25..148b090eab8 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -29,7 +29,8 @@ import { type TestDateProvider } from '@aztec/foundation/timer'; import { StatefulTestContract, StatefulTestContractArtifact } from '@aztec/noir-contracts.js/StatefulTest'; import { TestContract } from '@aztec/noir-contracts.js/Test'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; -import { type Sequencer, type SequencerClient, SequencerState } from '@aztec/sequencer-client'; +import { type SequencerClient, SequencerState } from '@aztec/sequencer-client'; +import { type TestSequencerClient } from '@aztec/sequencer-client/test'; import { PublicProcessorFactory, type PublicTxResult, PublicTxSimulator, type WorldStateDB } from '@aztec/simulator'; import { type TelemetryClient } from '@aztec/telemetry-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; @@ -59,7 +60,7 @@ describe('e2e_block_building', () => { const artifact = StatefulTestContractArtifact; beforeAll(async () => { - let sequencerClient; + let sequencerClient: SequencerClient | undefined; ({ teardown, pxe, @@ -70,8 +71,7 @@ describe('e2e_block_building', () => { dateProvider, cheatCodes, } = await setup(2)); - // Bypass accessibility modifiers in sequencer - sequencer = sequencerClient! as unknown as TestSequencerClient; + sequencer = sequencerClient! as TestSequencerClient; }); afterEach(() => aztecNode.setConfig({ minTxsPerBlock: 1 })); @@ -610,13 +610,6 @@ async function sendAndWait(calls: ContractFunctionInteraction[]) { ); } -type TestSequencer = Omit & { - publicProcessorFactory: PublicProcessorFactory; - timeTable: Record; - processTxTime: number; -}; -type TestSequencerClient = Omit & { sequencer: TestSequencer }; - const TEST_PUBLIC_TX_SIMULATION_DELAY_MS = 300; class TestPublicTxSimulator extends PublicTxSimulator { 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 539cd573cc0..14850cb1509 100644 --- a/yarn-project/end-to-end/src/e2e_epochs.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs.test.ts @@ -1,8 +1,15 @@ +import { type Logger, getTimestampRangeForEpoch, retryUntil, sleep } from '@aztec/aztec.js'; // eslint-disable-next-line no-restricted-imports -import { type EpochConstants, type Logger, getTimestampRangeForEpoch, retryUntil } from '@aztec/aztec.js'; +import { type L1RollupConstants } from '@aztec/circuit-types'; +import { Proof } from '@aztec/circuits.js'; +import { RootRollupPublicInputs } from '@aztec/circuits.js/rollup'; import { RollupContract } from '@aztec/ethereum/contracts'; import { type Delayer, waitUntilL1Timestamp } from '@aztec/ethereum/test'; +import { promiseWithResolvers } from '@aztec/foundation/promise'; +import { type TestProverNode } from '@aztec/prover-node/test'; +import { type TestL1Publisher, type TestSequencerClient } from '@aztec/sequencer-client/test'; +import { jest } from '@jest/globals'; import { type PublicClient } from 'viem'; import { type EndToEndContext, setup } from './fixtures/utils.js'; @@ -14,7 +21,7 @@ describe('e2e_epochs', () => { let context: EndToEndContext; let l1Client: PublicClient; let rollup: RollupContract; - let constants: EpochConstants; + let constants: L1RollupConstants; let logger: Logger; let proverDelayer: Delayer; let sequencerDelayer: Delayer; @@ -80,11 +87,8 @@ describe('e2e_epochs', () => { logger.info(msg); }, 200); - // 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 - // do it if we see the this pattern popping up in more places. - proverDelayer = (context.proverNode as any).publisher.delayer; - sequencerDelayer = (context.sequencer as any).sequencer.publisher.delayer; + proverDelayer = ((context.proverNode as TestProverNode).publisher as TestL1Publisher).delayer!; + sequencerDelayer = ((context.sequencer as TestSequencerClient).sequencer.publisher as TestL1Publisher).delayer!; expect(proverDelayer).toBeDefined(); expect(sequencerDelayer).toBeDefined(); @@ -92,16 +96,17 @@ describe('e2e_epochs', () => { constants = { epochDuration: EPOCH_DURATION_IN_L2_SLOTS, slotDuration: L1_BLOCK_TIME_IN_S * L2_SLOT_DURATION_IN_L1_SLOTS, - l1GenesisBlock: await rollup.getL1StartBlock(), + l1StartBlock: await rollup.getL1StartBlock(), l1GenesisTime: await rollup.getL1GenesisTime(), ethereumSlotDuration: L1_BLOCK_TIME_IN_S, }; - logger.info(`L2 genesis at L1 block ${constants.l1GenesisBlock} (timestamp ${constants.l1GenesisTime})`); + logger.info(`L2 genesis at L1 block ${constants.l1StartBlock} (timestamp ${constants.l1GenesisTime})`); }); afterEach(async () => { clearInterval(handle); + jest.restoreAllMocks(); await context.teardown(); }); @@ -156,7 +161,7 @@ describe('e2e_epochs', () => { logger.info(`Test succeeded`); }); - it('submits proof claim alone if there is no txs to build a block', async () => { + it('submits proof claim alone if there are no txs to build a block', async () => { context.sequencer?.updateSequencerConfig({ minTxsPerBlock: 1 }); await waitUntilEpochStarts(1); const blockNumberAtEndOfEpoch0 = Number(await rollup.getBlockNumber()); @@ -166,4 +171,39 @@ describe('e2e_epochs', () => { expect(l2BlockNumber).toEqual(blockNumberAtEndOfEpoch0); logger.info(`Test succeeded`); }); + + it('aborts proving if end of next epoch is reached', async () => { + // Inject a delay in prover node proving equal to the length of an epoch, to make sure deadline will be hit + const epochProverManager = (context.proverNode as TestProverNode).prover; + const originalCreate = epochProverManager.createEpochProver.bind(epochProverManager); + const finaliseEpochPromise = promiseWithResolvers(); + jest.spyOn(epochProverManager, 'createEpochProver').mockImplementation(() => { + const prover = originalCreate(); + jest.spyOn(prover, 'finaliseEpoch').mockImplementation(async () => { + const seconds = L1_BLOCK_TIME_IN_S * L2_SLOT_DURATION_IN_L1_SLOTS * EPOCH_DURATION_IN_L2_SLOTS; + logger.warn(`Finalise epoch: sleeping ${seconds}s.`); + await sleep(L1_BLOCK_TIME_IN_S * L2_SLOT_DURATION_IN_L1_SLOTS * EPOCH_DURATION_IN_L2_SLOTS * 1000); + logger.warn(`Finalise epoch: returning.`); + finaliseEpochPromise.resolve(); + return { publicInputs: RootRollupPublicInputs.random(), proof: Proof.empty() }; + }); + return prover; + }); + + await waitUntilEpochStarts(1); + logger.info(`Starting epoch 1`); + const proverTxCount = proverDelayer.getTxs().length; + + await waitUntilEpochStarts(2); + logger.info(`Starting epoch 2`); + + // No proof for epoch zero should have landed during epoch one + expect(l2ProvenBlockNumber).toEqual(0); + + // Wait until the prover job finalises (and a bit more) and check that it aborted and never attempted to submit a tx + logger.info(`Awaiting finalise epoch`); + await finaliseEpochPromise.promise; + await sleep(1000); + expect(proverDelayer.getTxs().length - proverTxCount).toEqual(0); + }); }); diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index 85ae078728f..ee058a45855 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -45,7 +45,8 @@ import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types'; import { ProtocolContractAddress, protocolContractTreeRoot } from '@aztec/protocol-contracts'; import { type ProverNode, type ProverNodeConfig, createProverNode } from '@aztec/prover-node'; import { type PXEService, type PXEServiceConfig, createPXEService, getPXEServiceConfig } from '@aztec/pxe'; -import { type SequencerClient, TestL1Publisher } from '@aztec/sequencer-client'; +import { type SequencerClient } from '@aztec/sequencer-client'; +import { TestL1Publisher } from '@aztec/sequencer-client/test'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { createAndStartTelemetryClient, getConfigEnvVars as getTelemetryConfig } from '@aztec/telemetry-client/start'; diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 657c9ff7145..4b20b83798f 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -404,7 +404,7 @@ export const deployL1Contracts = async ( logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs); const slashFactoryAddress = await deployer.deploy(l1Artifacts.slashFactory, [rollupAddress.toString()]); - logger.info(`Deployed SlashFactory at ${slashFactoryAddress}`); + logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`); await deployer.waitForDeployments(); logger.verbose(`All core contracts have been deployed`); diff --git a/yarn-project/ethereum/src/test/tx_delayer.ts b/yarn-project/ethereum/src/test/tx_delayer.ts index afb117f4ea2..c2ecd25fbcd 100644 --- a/yarn-project/ethereum/src/test/tx_delayer.ts +++ b/yarn-project/ethereum/src/test/tx_delayer.ts @@ -27,7 +27,7 @@ export function waitUntilBlock(client: T, blockNumber: number return currentBlockNumber >= BigInt(blockNumber); }, `Wait until L1 block ${blockNumber}`, - 60, + 120, 0.1, ); } @@ -52,7 +52,7 @@ export function waitUntilL1Timestamp(client: T, timestamp: num return currentTs >= BigInt(timestamp); }, `Wait until L1 timestamp ${timestamp}`, - 60, + 120, 0.1, ); } @@ -144,7 +144,7 @@ export function withDelayer( return Promise.resolve(txHash); } else { const txHash = await client.sendRawTransaction(...args); - logger.debug(`Sent tx immediately ${txHash}`); + logger.verbose(`Sent tx immediately ${txHash}`); delayer.txs.push(txHash); return txHash; } diff --git a/yarn-project/prover-node/package.json b/yarn-project/prover-node/package.json index 13a872b8864..f7cc6c7838a 100644 --- a/yarn-project/prover-node/package.json +++ b/yarn-project/prover-node/package.json @@ -4,7 +4,8 @@ "type": "module", "exports": { ".": "./dest/index.js", - "./config": "./dest/config.js" + "./config": "./dest/config.js", + "./test": "./dest/test/index.js" }, "inherits": [ "../package.common.json" diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.test.ts b/yarn-project/prover-node/src/job/epoch-proving-job.test.ts new file mode 100644 index 00000000000..4576c377f2b --- /dev/null +++ b/yarn-project/prover-node/src/job/epoch-proving-job.test.ts @@ -0,0 +1,155 @@ +import { + type EpochProver, + type L1ToL2MessageSource, + L2Block, + type L2BlockSource, + type MerkleTreeWriteOperations, + type ProcessedTx, + type Tx, + type WorldStateSynchronizer, +} from '@aztec/circuit-types'; +import { BlockHeader, Proof } from '@aztec/circuits.js'; +import { RootRollupPublicInputs } from '@aztec/circuits.js/rollup'; +import { times } from '@aztec/foundation/collection'; +import { sleep } from '@aztec/foundation/sleep'; +import { type L1Publisher } from '@aztec/sequencer-client'; +import { type PublicProcessor, type PublicProcessorFactory } from '@aztec/simulator'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; + +import { type MockProxy, mock } from 'jest-mock-extended'; + +import { ProverNodeMetrics } from '../metrics.js'; +import { EpochProvingJob } from './epoch-proving-job.js'; + +describe('epoch-proving-job', () => { + // Dependencies + let prover: MockProxy; + let publisher: MockProxy; + let l2BlockSource: MockProxy; + let l1ToL2MessageSource: MockProxy; + let worldState: MockProxy; + let publicProcessorFactory: MockProxy; + let metrics: ProverNodeMetrics; + + // Created by a dependency + let db: MockProxy; + let publicProcessor: MockProxy; + + // Objects + let publicInputs: RootRollupPublicInputs; + let proof: Proof; + let blocks: L2Block[]; + let txs: Tx[]; + let header: BlockHeader; + let epochNumber: number; + + // Constants + const NUM_BLOCKS = 3; + const TXS_PER_BLOCK = 2; + const NUM_TXS = NUM_BLOCKS * TXS_PER_BLOCK; + + // Subject factory + const createJob = (opts: { deadline?: Date; parallelBlockLimit?: number } = {}) => + new EpochProvingJob( + worldState, + BigInt(epochNumber), + blocks, + txs, + prover, + publicProcessorFactory, + publisher, + l2BlockSource, + l1ToL2MessageSource, + metrics, + opts.deadline, + { parallelBlockLimit: opts.parallelBlockLimit ?? 32 }, + ); + + beforeEach(() => { + prover = mock(); + publisher = mock(); + l2BlockSource = mock(); + l1ToL2MessageSource = mock(); + worldState = mock(); + publicProcessorFactory = mock(); + db = mock(); + publicProcessor = mock(); + metrics = new ProverNodeMetrics(new NoopTelemetryClient()); + + publicInputs = RootRollupPublicInputs.random(); + proof = Proof.empty(); + header = BlockHeader.empty(); + epochNumber = 1; + blocks = times(NUM_BLOCKS, i => L2Block.random(i + 1, TXS_PER_BLOCK)); + txs = times(NUM_TXS, i => + mock({ + getTxHash: () => blocks[i % NUM_BLOCKS].body.txEffects[i % TXS_PER_BLOCK].txHash, + }), + ); + + l1ToL2MessageSource.getL1ToL2Messages.mockResolvedValue([]); + l2BlockSource.getBlockHeader.mockResolvedValue(header); + publicProcessorFactory.create.mockReturnValue(publicProcessor); + worldState.fork.mockResolvedValue(db); + prover.finaliseEpoch.mockResolvedValue({ publicInputs, proof }); + publisher.submitEpochProof.mockResolvedValue(true); + publicProcessor.process.mockImplementation((txs: Iterable) => + Promise.resolve([Array.from(txs).map(tx => mock({ hash: tx.getTxHash() })), [], []]), + ); + }); + + it('works', async () => { + const job = createJob(); + await job.run(); + + expect(job.getState()).toEqual('completed'); + expect(db.close).toHaveBeenCalledTimes(NUM_BLOCKS); + expect(publicProcessor.process).toHaveBeenCalledTimes(NUM_BLOCKS); + expect(publisher.submitEpochProof).toHaveBeenCalledWith( + expect.objectContaining({ epochNumber, proof, publicInputs }), + ); + }); + + it('fails if fails to process txs for a block', async () => { + publicProcessor.process.mockImplementation((txs: Iterable) => + Promise.resolve([[], Array.from(txs).map(tx => ({ error: new Error('Failed to process tx'), tx })), []]), + ); + + const job = createJob(); + await job.run(); + + expect(job.getState()).toEqual('failed'); + expect(publisher.submitEpochProof).not.toHaveBeenCalled(); + }); + + it('fails if does not process all txs for a block', async () => { + publicProcessor.process.mockImplementation((_txs: Iterable) => Promise.resolve([[], [], []])); + + const job = createJob(); + await job.run(); + + expect(job.getState()).toEqual('failed'); + expect(publisher.submitEpochProof).not.toHaveBeenCalled(); + }); + + it('times out if deadline is hit', async () => { + prover.startNewBlock.mockImplementation(() => sleep(200)); + const deadline = new Date(Date.now() + 100); + const job = createJob({ deadline }); + await job.run(); + + expect(job.getState()).toEqual('timed-out'); + expect(publisher.submitEpochProof).not.toHaveBeenCalled(); + }); + + it('halts if stopped externally', async () => { + prover.startNewBlock.mockImplementation(() => sleep(200)); + const job = createJob(); + void job.run(); + await sleep(100); + await job.stop(); + + expect(job.getState()).toEqual('stopped'); + expect(publisher.submitEpochProof).not.toHaveBeenCalled(); + }); +}); diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.ts b/yarn-project/prover-node/src/job/epoch-proving-job.ts index f434421dca3..1f22fe0477c 100644 --- a/yarn-project/prover-node/src/job/epoch-proving-job.ts +++ b/yarn-project/prover-node/src/job/epoch-proving-job.ts @@ -1,6 +1,7 @@ import { type EpochProver, type EpochProvingJobState, + EpochProvingJobTerminalState, type ForkMerkleTreeOperations, type L1ToL2MessageSource, type L2Block, @@ -31,6 +32,7 @@ export class EpochProvingJob implements Traceable { private uuid: string; private runPromise: Promise | undefined; + private deadlineTimeoutHandler: NodeJS.Timeout | undefined; public readonly tracer: Tracer; @@ -45,6 +47,7 @@ export class EpochProvingJob implements Traceable { private l2BlockSource: L2BlockSource, private l1ToL2MessageSource: L1ToL2MessageSource, private metrics: ProverNodeMetrics, + private deadline: Date | undefined, private config: { parallelBlockLimit: number } = { parallelBlockLimit: 32 }, private cleanUp: (job: EpochProvingJob) => Promise = () => Promise.resolve(), ) { @@ -67,6 +70,8 @@ export class EpochProvingJob implements Traceable { return { [Attributes.EPOCH_NUMBER]: Number(this.epochNumber) }; }) public async run() { + this.scheduleDeadlineStop(); + const epochNumber = Number(this.epochNumber); const epochSizeBlocks = this.blocks.length; const epochSizeTxs = this.blocks.reduce((total, current) => total + current.body.numberOfTxsIncludingPadded, 0); @@ -78,9 +83,9 @@ export class EpochProvingJob implements Traceable { epochNumber, uuid: this.uuid, }); - this.state = 'processing'; - const timer = new Timer(); + this.progressState('processing'); + const timer = new Timer(); const { promise, resolve } = promiseWithResolvers(); this.runPromise = promise; @@ -88,6 +93,8 @@ export class EpochProvingJob implements Traceable { this.prover.startNewEpoch(epochNumber, fromBlock, epochSizeBlocks); await asyncPool(this.config.parallelBlockLimit, this.blocks, async block => { + this.checkState(); + const globalVariables = block.header.globalVariables; const txs = this.getTxs(block); const l1ToL2Messages = await this.getL1ToL2Messages(block); @@ -104,6 +111,7 @@ export class EpochProvingJob implements Traceable { uuid: this.uuid, ...globalVariables, }); + // Start block proving await this.prover.startNewBlock(globalVariables, l1ToL2Messages); @@ -123,32 +131,73 @@ export class EpochProvingJob implements Traceable { await this.prover.setBlockCompleted(block.number, block.header); }); - this.state = 'awaiting-prover'; + this.progressState('awaiting-prover'); const { publicInputs, proof } = await this.prover.finaliseEpoch(); this.log.info(`Finalised proof for epoch ${epochNumber}`, { epochNumber, uuid: this.uuid, duration: timer.ms() }); - this.state = 'publishing-proof'; - await this.publisher.submitEpochProof({ fromBlock, toBlock, epochNumber, publicInputs, proof }); - this.log.info(`Submitted proof for epoch`, { epochNumber, uuid: this.uuid }); + this.progressState('publishing-proof'); + const success = await this.publisher.submitEpochProof({ fromBlock, toBlock, epochNumber, publicInputs, proof }); + if (!success) { + throw new Error('Failed to submit epoch proof to L1'); + } + this.log.info(`Submitted proof for epoch`, { epochNumber, uuid: this.uuid }); this.state = 'completed'; this.metrics.recordProvingJob(timer, epochSizeBlocks, epochSizeTxs); - } catch (err) { + } catch (err: any) { + if (err && err.name === 'HaltExecutionError') { + this.log.warn(`Halted execution of epoch ${epochNumber} prover job`, { uuid: this.uuid, epochNumber }); + return; + } this.log.error(`Error running epoch ${epochNumber} prover job`, err, { uuid: this.uuid, epochNumber }); this.state = 'failed'; } finally { + clearTimeout(this.deadlineTimeoutHandler); await this.cleanUp(this); resolve(); } } - public async stop() { + private progressState(state: EpochProvingJobState) { + this.checkState(); + this.state = state; + } + + private checkState() { + if (this.state === 'timed-out' || this.state === 'stopped' || this.state === 'failed') { + throw new HaltExecutionError(this.state); + } + } + + public async stop(state: EpochProvingJobState = 'stopped') { + this.state = state; this.prover.cancel(); + // TODO(palla/prover): Stop the publisher as well if (this.runPromise) { await this.runPromise; } } + private scheduleDeadlineStop() { + const deadline = this.deadline; + if (deadline) { + const timeout = deadline.getTime() - Date.now(); + if (timeout <= 0) { + throw new Error('Cannot start job with deadline in the past'); + } + + this.deadlineTimeoutHandler = setTimeout(() => { + if (EpochProvingJobTerminalState.includes(this.state)) { + return; + } + this.log.warn('Stopping job due to deadline hit', { uuid: this.uuid, epochNumber: this.epochNumber }); + this.stop('timed-out').catch(err => { + this.log.error('Error stopping job', err, { uuid: this.uuid, epochNumber: this.epochNumber }); + }); + }, timeout); + } + } + /* Returns the header for the given block number, or undefined for block zero. */ private getBlockHeader(blockNumber: number) { if (blockNumber === 0) { @@ -167,16 +216,28 @@ export class EpochProvingJob implements Traceable { } private async processTxs(publicProcessor: PublicProcessor, txs: Tx[]): Promise { - const [processedTxs, failedTxs] = await publicProcessor.process(txs); + const { deadline } = this; + const [processedTxs, failedTxs] = await publicProcessor.process(txs, { deadline }); if (failedTxs.length) { throw new Error( - `Failed to process txs: ${failedTxs.map(({ tx, error }) => `${tx.getTxHash()} (${error})`).join(', ')}`, + `Txs failed processing: ${failedTxs.map(({ tx, error }) => `${tx.getTxHash()} (${error})`).join(', ')}`, ); } + if (processedTxs.length !== txs.length) { + throw new Error(`Failed to process all txs: processed ${processedTxs.length} out of ${txs.length}`); + } + return processedTxs; } } +class HaltExecutionError extends Error { + constructor(state: EpochProvingJobState) { + super(`Halted execution due to state ${state}`); + this.name = 'HaltExecutionError'; + } +} + export { type EpochProvingJobState }; diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index 0e5068ecded..8d3359149b2 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -1,4 +1,5 @@ import { + EmptyL1RollupConstants, type EpochProofClaim, EpochProofQuote, EpochProofQuotePayload, @@ -150,6 +151,7 @@ describe('prover-node', () => { // Archiver returns a bunch of fake blocks l2BlockSource.getBlocksForEpoch.mockResolvedValue(blocks); + l2BlockSource.getL1Constants.mockResolvedValue(EmptyL1RollupConstants); // Coordination plays along and returns a tx whenever requested mockCoordination.getTxByHash.mockImplementation(hash => @@ -472,6 +474,7 @@ describe('prover-node', () => { class TestProverNode extends ProverNode { protected override doCreateEpochProvingJob( epochNumber: bigint, + _deadline: Date | undefined, _blocks: L2Block[], _txs: Tx[], _publicProcessorFactory: PublicProcessorFactory, diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 4f341ae58d7..3fc7e9a07ee 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -13,11 +13,13 @@ import { type Tx, type TxHash, type WorldStateSynchronizer, + getTimestampRangeForEpoch, tryStop, } from '@aztec/circuit-types'; import { type ContractDataSource } from '@aztec/circuits.js'; import { asyncPool } from '@aztec/foundation/async-pool'; import { compact } from '@aztec/foundation/collection'; +import { memoize } from '@aztec/foundation/decorators'; import { TimeoutError } from '@aztec/foundation/error'; import { createLogger } from '@aztec/foundation/log'; import { retryUntil } from '@aztec/foundation/retry'; @@ -64,19 +66,19 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr public readonly tracer: Tracer; constructor( - private readonly prover: EpochProverManager, - private readonly publisher: L1Publisher, - private readonly l2BlockSource: L2BlockSource & Maybe, - private readonly l1ToL2MessageSource: L1ToL2MessageSource, - private readonly contractDataSource: ContractDataSource, - private readonly worldState: WorldStateSynchronizer, - private readonly coordination: ProverCoordination & Maybe, - private readonly quoteProvider: QuoteProvider, - private readonly quoteSigner: QuoteSigner, - private readonly claimsMonitor: ClaimsMonitor, - private readonly epochsMonitor: EpochMonitor, - private readonly bondManager: BondManager, - private readonly telemetryClient: TelemetryClient, + protected readonly prover: EpochProverManager, + protected readonly publisher: L1Publisher, + protected readonly l2BlockSource: L2BlockSource & Maybe, + protected readonly l1ToL2MessageSource: L1ToL2MessageSource, + protected readonly contractDataSource: ContractDataSource, + protected readonly worldState: WorldStateSynchronizer, + protected readonly coordination: ProverCoordination & Maybe, + protected readonly quoteProvider: QuoteProvider, + protected readonly quoteSigner: QuoteSigner, + protected readonly claimsMonitor: ClaimsMonitor, + protected readonly epochsMonitor: EpochMonitor, + protected readonly bondManager: BondManager, + protected readonly telemetryClient: TelemetryClient, options: Partial = {}, ) { this.options = { @@ -289,11 +291,19 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr return Promise.resolve(); }; - const job = this.doCreateEpochProvingJob(epochNumber, blocks, txs, publicProcessorFactory, cleanUp); + const [_, endTimestamp] = getTimestampRangeForEpoch(epochNumber + 1n, await this.getL1Constants()); + const deadline = new Date(Number(endTimestamp) * 1000); + + const job = this.doCreateEpochProvingJob(epochNumber, deadline, blocks, txs, publicProcessorFactory, cleanUp); this.jobs.set(job.getId(), job); return job; } + @memoize + private getL1Constants() { + return this.l2BlockSource.getL1Constants(); + } + @trackSpan('ProverNode.gatherEpochData', epochNumber => ({ [Attributes.EPOCH_NUMBER]: Number(epochNumber) })) private async gatherEpochData(epochNumber: bigint) { // Gather blocks for this epoch and their txs @@ -379,6 +389,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr /** Extracted for testing purposes. */ protected doCreateEpochProvingJob( epochNumber: bigint, + deadline: Date | undefined, blocks: L2Block[], txs: Tx[], publicProcessorFactory: PublicProcessorFactory, @@ -395,6 +406,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr this.l2BlockSource, this.l1ToL2MessageSource, this.metrics, + deadline, { parallelBlockLimit: this.options.maxParallelBlocksPerEpoch }, cleanUp, ); diff --git a/yarn-project/prover-node/src/test/index.ts b/yarn-project/prover-node/src/test/index.ts new file mode 100644 index 00000000000..76154d9969a --- /dev/null +++ b/yarn-project/prover-node/src/test/index.ts @@ -0,0 +1,11 @@ +import { type EpochProverManager } from '@aztec/circuit-types'; +import { type L1Publisher } from '@aztec/sequencer-client'; + +import { ProverNode } from '../prover-node.js'; + +class TestProverNode_ extends ProverNode { + public override prover!: EpochProverManager; + public override publisher!: L1Publisher; +} + +export type TestProverNode = TestProverNode_; diff --git a/yarn-project/sequencer-client/package.json b/yarn-project/sequencer-client/package.json index fb874975daa..4d0abeb0287 100644 --- a/yarn-project/sequencer-client/package.json +++ b/yarn-project/sequencer-client/package.json @@ -4,7 +4,8 @@ "type": "module", "exports": { ".": "./dest/index.js", - "./config": "./dest/config.js" + "./config": "./dest/config.js", + "./test": "./dest/test/index.js" }, "typedocOptions": { "entryPoints": [ diff --git a/yarn-project/sequencer-client/src/publisher/index.ts b/yarn-project/sequencer-client/src/publisher/index.ts index f0a2f3b5092..af6d71529a6 100644 --- a/yarn-project/sequencer-client/src/publisher/index.ts +++ b/yarn-project/sequencer-client/src/publisher/index.ts @@ -1,2 +1 @@ export { L1Publisher, L1SubmitEpochProofArgs } from './l1-publisher.js'; -export * from './test-l1-publisher.js'; diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index a953e51e110..d64b35847cc 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -85,7 +85,7 @@ export class Sequencer { private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions(); private maxBlockSizeInBytes: number = 1024 * 1024; private maxBlockGas: Gas = new Gas(10e9, 10e9); - private processTxTime: number = 12; + protected processTxTime: number = 12; private metrics: SequencerMetrics; private isFlushing: boolean = false; @@ -97,22 +97,22 @@ export class Sequencer { protected enforceTimeTable: boolean = false; constructor( - private publisher: L1Publisher, - private validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive - private globalsBuilder: GlobalVariableBuilder, - private p2pClient: P2P, - private worldState: WorldStateSynchronizer, - private slasherClient: SlasherClient, - private blockBuilderFactory: BlockBuilderFactory, - private l2BlockSource: L2BlockSource, - private l1ToL2MessageSource: L1ToL2MessageSource, - private publicProcessorFactory: PublicProcessorFactory, - private contractDataSource: ContractDataSource, + protected publisher: L1Publisher, + protected validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive + protected globalsBuilder: GlobalVariableBuilder, + protected p2pClient: P2P, + protected worldState: WorldStateSynchronizer, + protected slasherClient: SlasherClient, + protected blockBuilderFactory: BlockBuilderFactory, + protected l2BlockSource: L2BlockSource, + protected l1ToL2MessageSource: L1ToL2MessageSource, + protected publicProcessorFactory: PublicProcessorFactory, + protected contractDataSource: ContractDataSource, protected l1Constants: SequencerRollupConstants, - private dateProvider: DateProvider, + protected dateProvider: DateProvider, telemetry: TelemetryClient, - private config: SequencerConfig = {}, - private log = createLogger('sequencer'), + protected config: SequencerConfig = {}, + protected log = createLogger('sequencer'), ) { this.updateConfig(config); this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer'); diff --git a/yarn-project/sequencer-client/src/test/index.ts b/yarn-project/sequencer-client/src/test/index.ts new file mode 100644 index 00000000000..a7f466b485b --- /dev/null +++ b/yarn-project/sequencer-client/src/test/index.ts @@ -0,0 +1,23 @@ +import { type PublicProcessorFactory } from '@aztec/simulator'; + +import { SequencerClient } from '../client/sequencer-client.js'; +import { type L1Publisher } from '../publisher/l1-publisher.js'; +import { Sequencer } from '../sequencer/sequencer.js'; +import { type SequencerState } from '../sequencer/utils.js'; + +class TestSequencer_ extends Sequencer { + public override publicProcessorFactory!: PublicProcessorFactory; + public override timeTable!: Record; + public override processTxTime!: number; + public override publisher!: L1Publisher; +} + +export type TestSequencer = TestSequencer_; + +class TestSequencerClient_ extends SequencerClient { + public override sequencer!: TestSequencer; +} + +export type TestSequencerClient = TestSequencerClient_; + +export * from './test-l1-publisher.js'; diff --git a/yarn-project/sequencer-client/src/publisher/test-l1-publisher.ts b/yarn-project/sequencer-client/src/test/test-l1-publisher.ts similarity index 92% rename from yarn-project/sequencer-client/src/publisher/test-l1-publisher.ts rename to yarn-project/sequencer-client/src/test/test-l1-publisher.ts index f60dd608138..a28b352bb66 100644 --- a/yarn-project/sequencer-client/src/publisher/test-l1-publisher.ts +++ b/yarn-project/sequencer-client/src/test/test-l1-publisher.ts @@ -3,7 +3,7 @@ import { type Delayer, withDelayer } from '@aztec/ethereum/test'; import { type Chain, type HttpTransport, type PrivateKeyAccount, type WalletClient } from 'viem'; -import { L1Publisher } from './l1-publisher.js'; +import { L1Publisher } from '../publisher/l1-publisher.js'; export class TestL1Publisher extends L1Publisher { public delayer: Delayer | undefined;