diff --git a/yarn-project/deploy_npm.sh b/yarn-project/deploy_npm.sh index b7154d1226c..03f33a893b5 100755 --- a/yarn-project/deploy_npm.sh +++ b/yarn-project/deploy_npm.sh @@ -104,5 +104,6 @@ deploy_package archiver deploy_package p2p deploy_package prover-client deploy_package sequencer-client +deploy_package prover-node deploy_package aztec-node deploy_package txe diff --git a/yarn-project/package.json b/yarn-project/package.json index f9a72eb8eea..afa095bbe01 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -47,6 +47,7 @@ "p2p-bootstrap", "protocol-contracts", "prover-client", + "prover-node", "rollup-provider", "sequencer-client", "scripts", diff --git a/yarn-project/prover-node/.eslintrc.cjs b/yarn-project/prover-node/.eslintrc.cjs new file mode 100644 index 00000000000..e659927475c --- /dev/null +++ b/yarn-project/prover-node/.eslintrc.cjs @@ -0,0 +1 @@ +module.exports = require('@aztec/foundation/eslint'); diff --git a/yarn-project/prover-node/README.md b/yarn-project/prover-node/README.md new file mode 100644 index 00000000000..e31824c879a --- /dev/null +++ b/yarn-project/prover-node/README.md @@ -0,0 +1 @@ +# Prover Node diff --git a/yarn-project/prover-node/package.json b/yarn-project/prover-node/package.json new file mode 100644 index 00000000000..efcd145e763 --- /dev/null +++ b/yarn-project/prover-node/package.json @@ -0,0 +1,85 @@ +{ + "name": "@aztec/prover-node", + "version": "0.1.0", + "type": "module", + "exports": { + ".": "./dest/index.js" + }, + "inherits": [ + "../package.common.json" + ], + "scripts": { + "build": "yarn clean && tsc -b", + "build:dev": "tsc -b --watch", + "clean": "rm -rf ./dest .tsbuildinfo", + "formatting": "run -T prettier --check ./src && run -T eslint ./src", + "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src", + "bb": "node --no-warnings ./dest/bb/index.js", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests" + }, + "jest": { + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" + }, + "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$", + "rootDir": "./src", + "transform": { + "^.+\\.tsx?$": [ + "@swc/jest", + { + "jsc": { + "parser": { + "syntax": "typescript", + "decorators": true + } + } + } + ] + }, + "extensionsToTreatAsEsm": [ + ".ts" + ], + "reporters": [ + [ + "default", + { + "summaryThreshold": 9999 + } + ] + ] + }, + "dependencies": { + "@aztec/archiver": "workspace:^", + "@aztec/circuit-types": "workspace:^", + "@aztec/circuits.js": "workspace:^", + "@aztec/foundation": "workspace:^", + "@aztec/kv-store": "workspace:^", + "@aztec/prover-client": "workspace:^", + "@aztec/sequencer-client": "workspace:^", + "@aztec/simulator": "workspace:^", + "@aztec/telemetry-client": "workspace:^", + "@aztec/world-state": "workspace:^", + "source-map-support": "^0.5.21", + "tslib": "^2.4.0" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/jest": "^29.5.0", + "@types/memdown": "^3.0.0", + "@types/node": "^18.7.23", + "@types/source-map-support": "^0.5.10", + "jest": "^29.5.0", + "jest-mock-extended": "^3.0.3", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "files": [ + "dest", + "src", + "!*.test.*" + ], + "types": "./dest/index.d.ts", + "engines": { + "node": ">=18" + } +} diff --git a/yarn-project/prover-node/src/config.ts b/yarn-project/prover-node/src/config.ts new file mode 100644 index 00000000000..59f99b68f60 --- /dev/null +++ b/yarn-project/prover-node/src/config.ts @@ -0,0 +1,25 @@ +import { type ArchiverConfig, getArchiverConfigFromEnv } from '@aztec/archiver'; +import { type ProverClientConfig, getProverEnvVars } from '@aztec/prover-client'; +import { type PublisherConfig, type TxSenderConfig, getTxSenderConfigFromEnv } from '@aztec/sequencer-client'; +import { type WorldStateConfig, getWorldStateConfigFromEnv } from '@aztec/world-state'; + +import { type TxProviderConfig, getTxProviderConfigFromEnv } from './tx-provider/config.js'; + +export type ProverNodeConfig = ArchiverConfig & + ProverClientConfig & + WorldStateConfig & + PublisherConfig & + TxSenderConfig & + TxProviderConfig; + +export function getProverNodeConfigFromEnv(): ProverNodeConfig { + const { PROOF_PUBLISH_RETRY_INTERVAL_MS } = process.env; + return { + ...getArchiverConfigFromEnv(), + ...getProverEnvVars(), + ...getWorldStateConfigFromEnv(), + ...getTxSenderConfigFromEnv('PROVER'), + ...getTxProviderConfigFromEnv(), + l1PublishRetryIntervalMS: PROOF_PUBLISH_RETRY_INTERVAL_MS ? +PROOF_PUBLISH_RETRY_INTERVAL_MS : 1_000, + }; +} diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts new file mode 100644 index 00000000000..51195cad31e --- /dev/null +++ b/yarn-project/prover-node/src/factory.ts @@ -0,0 +1,43 @@ +import { createArchiver } from '@aztec/archiver'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { createStore } from '@aztec/kv-store/utils'; +import { createProverClient } from '@aztec/prover-client'; +import { getL1Publisher } from '@aztec/sequencer-client'; +import { PublicProcessorFactory, createSimulationProvider } from '@aztec/simulator'; +import { type TelemetryClient } from '@aztec/telemetry-client'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; +import { createWorldStateSynchronizer } from '@aztec/world-state'; + +import { type ProverNodeConfig } from './config.js'; +import { ProverNode } from './prover-node.js'; +import { createTxProvider } from './tx-provider/factory.js'; + +/** Creates a new prover node given a config. */ +export async function createProverNode( + config: ProverNodeConfig, + telemetry: TelemetryClient = new NoopTelemetryClient(), + log = createDebugLogger('aztec:prover'), + storeLog = createDebugLogger('aztec:prover:lmdb'), +) { + const store = await createStore(config, config.l1Contracts.rollupAddress, storeLog); + + const archiver = await createArchiver(config, store, telemetry, { blockUntilSync: true }); + + const worldStateConfig = { ...config, worldStateProvenBlocksOnly: true }; + const worldStateSynchronizer = await createWorldStateSynchronizer(worldStateConfig, store, archiver); + await worldStateSynchronizer.start(); + + const simulationProvider = await createSimulationProvider(config, log); + + const prover = await createProverClient(config, worldStateSynchronizer, archiver); + + // REFACTOR: Move publisher out of sequencer package and into an L1-related package + const publisher = getL1Publisher(config); + + const latestWorldState = worldStateSynchronizer.getLatest(); + const publicProcessorFactory = new PublicProcessorFactory(latestWorldState, archiver, simulationProvider, telemetry); + + const txProvider = createTxProvider(config); + + return new ProverNode(prover!, publicProcessorFactory, publisher, archiver, txProvider); +} diff --git a/yarn-project/prover-node/src/index.ts b/yarn-project/prover-node/src/index.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/yarn-project/prover-node/src/job/block-proving-job.ts b/yarn-project/prover-node/src/job/block-proving-job.ts new file mode 100644 index 00000000000..64e6d7af0eb --- /dev/null +++ b/yarn-project/prover-node/src/job/block-proving-job.ts @@ -0,0 +1,123 @@ +import { + type BlockProver, + EmptyTxValidator, + type L2Block, + type L2BlockSource, + type ProcessedTx, + type Tx, + type TxHash, + type TxProvider, +} from '@aztec/circuit-types'; +import { type Fr } from '@aztec/circuits.js'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { type L1Publisher } from '@aztec/sequencer-client'; +import { type PublicProcessor, type PublicProcessorFactory } from '@aztec/simulator'; + +export class BlockProvingJob { + private state: BlockProvingJobState = 'initialized'; + private log = createDebugLogger('aztec:block-proving-job'); + + constructor( + private prover: BlockProver, + private publicProcessorFactory: PublicProcessorFactory, + private publisher: L1Publisher, + private l2BlockSource: L2BlockSource, + private txProvider: TxProvider, + ) {} + + public getState(): BlockProvingJobState { + return this.state; + } + + public async run(fromBlock: number, toBlock: number) { + if (fromBlock !== toBlock) { + throw new Error(`Block ranges are not yet supported`); + } + + this.log.info(`Starting block proving job`, { fromBlock, toBlock }); + this.state = 'started'; + + // TODO: Fast-forward world state to fromBlock and/or await fromBlock to be published to the unproven chain + + this.state = 'processing'; + + let historicalHeader = (await this.l2BlockSource.getBlock(fromBlock - 1))?.header; + for (let blockNumber = fromBlock; blockNumber <= toBlock; blockNumber++) { + const block = await this.getBlock(blockNumber); + const globalVariables = block.header.globalVariables; + const txHashes = block.body.txEffects.map(tx => tx.txHash); + const l1ToL2Messages: Fr[] = []; // TODO: grab L1 to L2 messages for this block + + this.log.debug(`Starting block processing`, { blockNumber: block.number, blockHash: block.hash().toString() }); + await this.prover.startNewBlock(txHashes.length, globalVariables, l1ToL2Messages); + const publicProcessor = await this.publicProcessorFactory.create(historicalHeader, globalVariables); + + const txs = await this.getTxs(txHashes); + const txCount = block.body.numberOfTxsIncludingPadded; + await this.processTxs(publicProcessor, txs, txCount); + + this.log.debug(`Processed all txs for block`, { blockNumber: block.number, blockHash: block.hash().toString() }); + await this.prover.setBlockCompleted(); + + historicalHeader = block.header; + } + + this.state = 'awaiting-prover'; + const { block, aggregationObject, proof } = await this.prover.finaliseBlock(); + this.log.info(`Finalised proof for block range`, { fromBlock, toBlock }); + + this.state = 'publishing-proof'; + await this.publisher.submitProof(block.header, block.archive.root, aggregationObject, proof); + this.log.info(`Submitted proof for block range`, { fromBlock, toBlock }); + + this.state = 'completed'; + } + + private async getBlock(blockNumber: number): Promise { + const block = await this.l2BlockSource.getBlock(blockNumber); + if (!block) { + throw new Error(`Block ${blockNumber} not found in L2 block source`); + } + return block; + } + + private async getTxs(txHashes: TxHash[]): Promise { + const txs = await Promise.all( + txHashes.map(txHash => this.txProvider.getTxByHash(txHash).then(tx => [txHash, tx] as const)), + ); + const notFound = txs.filter(([_, tx]) => !tx); + if (notFound.length) { + throw new Error(`Txs not found: ${notFound.map(([txHash]) => txHash.toString()).join(', ')}`); + } + return txs.map(([_, tx]) => tx!); + } + + private async processTxs( + publicProcessor: PublicProcessor, + txs: Tx[], + totalNumberOfTxs: number, + ): Promise { + const [processedTxs, failedTxs] = await publicProcessor.process( + txs, + totalNumberOfTxs, + this.prover, + new EmptyTxValidator(), + ); + + if (failedTxs.length) { + throw new Error( + `Failed to process txs: ${failedTxs.map(({ tx, error }) => `${tx.getTxHash()} (${error})`).join(', ')}`, + ); + } + + return processedTxs; + } +} + +export type BlockProvingJobState = + | 'initialized' + | 'started' + | 'processing' + | 'awaiting-prover' + | 'publishing-proof' + | 'completed'; diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts new file mode 100644 index 00000000000..13d7c0767ae --- /dev/null +++ b/yarn-project/prover-node/src/prover-node.ts @@ -0,0 +1,51 @@ +import { type L2BlockSource, type ProverClient, type TxProvider } from '@aztec/circuit-types'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { type L1Publisher } from '@aztec/sequencer-client'; +import { type PublicProcessorFactory } from '@aztec/simulator'; + +import { BlockProvingJob } from './job/block-proving-job.js'; + +export class ProverNode { + private log = createDebugLogger('aztec:prover-node'); + + constructor( + private prover: ProverClient, + private publicProcessorFactory: PublicProcessorFactory, + private publisher: L1Publisher, + private l2BlockSource: L2BlockSource, + private txProvider: TxProvider, + ) {} + + async stop() { + this.log.info('Stopping ProverNode'); + await this.prover.stop(); + await this.l2BlockSource.stop(); + // TODO: Should we stop the L1Publisher as well? + this.log.info('Stopped ProverNode'); + } + + /** + * Creates a proof for a block range. Returns once the proof has been submitted to L1. + */ + public prove(fromBlock: number, toBlock: number) { + return this.createProvingJob().run(fromBlock, toBlock); + } + + /** + * Starts a proving process and returns immediately. + */ + public startProof(fromBlock: number, toBlock: number) { + void this.createProvingJob().run(fromBlock, toBlock); + return Promise.resolve(); + } + + private createProvingJob() { + return new BlockProvingJob( + this.prover, + this.publicProcessorFactory, + this.publisher, + this.l2BlockSource, + this.txProvider, + ); + } +} diff --git a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts b/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts new file mode 100644 index 00000000000..90797602453 --- /dev/null +++ b/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts @@ -0,0 +1,10 @@ +import { type AztecNode, type Tx, type TxHash, type TxProvider } from '@aztec/circuit-types'; + +/** Implements TxProvider by querying an Aztec node for the txs. */ +export class AztecNodeTxProvider implements TxProvider { + constructor(private node: AztecNode) {} + + getTxByHash(txHash: TxHash): Promise { + return this.node.getTxByHash(txHash); + } +} diff --git a/yarn-project/prover-node/src/tx-provider/config.ts b/yarn-project/prover-node/src/tx-provider/config.ts new file mode 100644 index 00000000000..cd44131378e --- /dev/null +++ b/yarn-project/prover-node/src/tx-provider/config.ts @@ -0,0 +1,9 @@ +export type TxProviderConfig = { + txProviderNodeUrl: string | undefined; +}; + +export function getTxProviderConfigFromEnv(): TxProviderConfig { + return { + txProviderNodeUrl: process.env.TX_PROVIDER_NODE_URL, + }; +} diff --git a/yarn-project/prover-node/src/tx-provider/factory.ts b/yarn-project/prover-node/src/tx-provider/factory.ts new file mode 100644 index 00000000000..6e6b090acd2 --- /dev/null +++ b/yarn-project/prover-node/src/tx-provider/factory.ts @@ -0,0 +1,13 @@ +import { type TxProvider, createAztecNodeClient } from '@aztec/circuit-types'; + +import { AztecNodeTxProvider } from './aztec-node-tx-provider.js'; +import { type TxProviderConfig } from './config.js'; + +export function createTxProvider(config: TxProviderConfig): TxProvider { + if (config.txProviderNodeUrl) { + const node = createAztecNodeClient(config.txProviderNodeUrl); + return new AztecNodeTxProvider(node); + } else { + throw new Error(`Tx provider node URL is not set`); + } +} diff --git a/yarn-project/prover-node/src/tx-provider/index.ts b/yarn-project/prover-node/src/tx-provider/index.ts new file mode 100644 index 00000000000..bac271dd877 --- /dev/null +++ b/yarn-project/prover-node/src/tx-provider/index.ts @@ -0,0 +1,3 @@ +export * from './aztec-node-tx-provider.js'; +export * from './factory.js'; +export * from './config.js'; diff --git a/yarn-project/prover-node/tsconfig.json b/yarn-project/prover-node/tsconfig.json new file mode 100644 index 00000000000..9f241158c41 --- /dev/null +++ b/yarn-project/prover-node/tsconfig.json @@ -0,0 +1,41 @@ +{ + "extends": "..", + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "tsBuildInfoFile": ".tsbuildinfo" + }, + "references": [ + { + "path": "../archiver" + }, + { + "path": "../circuit-types" + }, + { + "path": "../circuits.js" + }, + { + "path": "../foundation" + }, + { + "path": "../kv-store" + }, + { + "path": "../prover-client" + }, + { + "path": "../sequencer-client" + }, + { + "path": "../simulator" + }, + { + "path": "../telemetry-client" + }, + { + "path": "../world-state" + } + ], + "include": ["src"] +} diff --git a/yarn-project/tsconfig.json b/yarn-project/tsconfig.json index 68777079e4a..e5706e34b3b 100644 --- a/yarn-project/tsconfig.json +++ b/yarn-project/tsconfig.json @@ -43,6 +43,7 @@ { "path": "p2p-bootstrap/tsconfig.json" }, { "path": "protocol-contracts/tsconfig.json" }, { "path": "prover-client/tsconfig.json" }, + { "path": "prover-node/tsconfig.json" }, { "path": "sequencer-client/tsconfig.json" }, { "path": "types/tsconfig.json" }, { "path": "world-state/tsconfig.json" }, diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 7349aebceeb..46a36131973 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -802,6 +802,34 @@ __metadata: languageName: unknown linkType: soft +"@aztec/prover-node@workspace:prover-node": + version: 0.0.0-use.local + resolution: "@aztec/prover-node@workspace:prover-node" + dependencies: + "@aztec/archiver": "workspace:^" + "@aztec/circuit-types": "workspace:^" + "@aztec/circuits.js": "workspace:^" + "@aztec/foundation": "workspace:^" + "@aztec/kv-store": "workspace:^" + "@aztec/prover-client": "workspace:^" + "@aztec/sequencer-client": "workspace:^" + "@aztec/simulator": "workspace:^" + "@aztec/telemetry-client": "workspace:^" + "@aztec/world-state": "workspace:^" + "@jest/globals": ^29.5.0 + "@types/jest": ^29.5.0 + "@types/memdown": ^3.0.0 + "@types/node": ^18.7.23 + "@types/source-map-support": ^0.5.10 + jest: ^29.5.0 + jest-mock-extended: ^3.0.3 + source-map-support: ^0.5.21 + ts-node: ^10.9.1 + tslib: ^2.4.0 + typescript: ^5.0.4 + languageName: unknown + linkType: soft + "@aztec/pxe@workspace:^, @aztec/pxe@workspace:pxe": version: 0.0.0-use.local resolution: "@aztec/pxe@workspace:pxe"