diff --git a/yarn-project/archiver/package.json b/yarn-project/archiver/package.json index 9514bf2b7e45..c1571c1429c4 100644 --- a/yarn-project/archiver/package.json +++ b/yarn-project/archiver/package.json @@ -2,7 +2,10 @@ "name": "@aztec/archiver", "version": "0.1.0", "type": "module", - "exports": "./dest/index.js", + "exports": { + ".": "./dest/index.js", + "./data-retrieval": "./dest/archiver/data_retrieval.js" + }, "typedocOptions": { "entryPoints": [ "./src/index.ts" diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index bff5c815f344..67084e8967af 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -1,5 +1,5 @@ import { type Body, type InboxLeaf } from '@aztec/circuit-types'; -import { type AppendOnlyTreeSnapshot, Fr, type Header } from '@aztec/circuits.js'; +import { type AppendOnlyTreeSnapshot, Fr, type Header, type Proof } from '@aztec/circuits.js'; import { type EthAddress } from '@aztec/foundation/eth-address'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { RollupAbi } from '@aztec/l1-artifacts'; @@ -7,6 +7,7 @@ import { RollupAbi } from '@aztec/l1-artifacts'; import { type PublicClient, getAbiItem } from 'viem'; import { + getBlockProofFromSubmitProofTx, getL2BlockProcessedLogs, getMessageSentLogs, getTxsPublishedLogs, @@ -147,7 +148,7 @@ export async function retrieveL2ProofVerifiedEvents( rollupAddress: EthAddress, searchStartBlock: bigint, searchEndBlock?: bigint, -): Promise<{ l1BlockNumber: bigint; l2BlockNumber: bigint; proverId: Fr }[]> { +): Promise<{ l1BlockNumber: bigint; l2BlockNumber: bigint; proverId: Fr; txHash: `0x${string}` }[]> { const logs = await publicClient.getLogs({ address: rollupAddress.toString(), fromBlock: searchStartBlock, @@ -160,5 +161,27 @@ export async function retrieveL2ProofVerifiedEvents( l1BlockNumber: log.blockNumber, l2BlockNumber: log.args.blockNumber, proverId: Fr.fromString(log.args.proverId), + txHash: log.transactionHash, })); } + +/** Retrieve submitted proofs from the rollup contract */ +export async function retrieveL2ProofsFromRollup( + publicClient: PublicClient, + rollupAddress: EthAddress, + searchStartBlock: bigint, + searchEndBlock?: bigint, +): Promise> { + const logs = await retrieveL2ProofVerifiedEvents(publicClient, rollupAddress, searchStartBlock, searchEndBlock); + const retrievedData: { proof: Proof; proverId: Fr; l2BlockNumber: bigint; txHash: `0x${string}` }[] = []; + const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n; + + for (const { txHash, proverId, l2BlockNumber } of logs) { + const proofData = await getBlockProofFromSubmitProofTx(publicClient, txHash, l2BlockNumber, proverId); + retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, l2BlockNumber, txHash }); + } + return { + retrievedData, + lastProcessedL1BlockNumber, + }; +} diff --git a/yarn-project/archiver/src/archiver/eth_log_handlers.ts b/yarn-project/archiver/src/archiver/eth_log_handlers.ts index dd62d60f55c8..2a3474f3be25 100644 --- a/yarn-project/archiver/src/archiver/eth_log_handlers.ts +++ b/yarn-project/archiver/src/archiver/eth_log_handlers.ts @@ -1,5 +1,5 @@ import { Body, InboxLeaf } from '@aztec/circuit-types'; -import { AppendOnlyTreeSnapshot, Header } from '@aztec/circuits.js'; +import { AppendOnlyTreeSnapshot, Header, Proof } from '@aztec/circuits.js'; import { type EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { numToUInt32BE } from '@aztec/foundation/serialize'; @@ -257,3 +257,57 @@ export function getMessageSentLogs( toBlock: toBlock + 1n, // the toBlock argument in getLogs is exclusive }); } + +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 { + const { input: data } = await publicClient.getTransaction({ hash: txHash }); + const { functionName, args } = decodeFunctionData({ + abi: RollupAbi, + data, + }); + + if (!(functionName === 'submitProof')) { + 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))), + }; +} diff --git a/yarn-project/aztec/package.json b/yarn-project/aztec/package.json index 9cc80536ed15..6d1aec9bcd55 100644 --- a/yarn-project/aztec/package.json +++ b/yarn-project/aztec/package.json @@ -48,6 +48,7 @@ "@aztec/noir-protocol-circuits-types": "workspace:^", "@aztec/p2p": "workspace:^", "@aztec/p2p-bootstrap": "workspace:^", + "@aztec/proof-verifier": "workspace:^", "@aztec/protocol-contracts": "workspace:^", "@aztec/prover-client": "workspace:^", "@aztec/prover-node": "workspace:^", diff --git a/yarn-project/aztec/src/cli/aztec_start_options.ts b/yarn-project/aztec/src/cli/aztec_start_options.ts index fd40c45a09dc..d2c670c15545 100644 --- a/yarn-project/aztec/src/cli/aztec_start_options.ts +++ b/yarn-project/aztec/src/cli/aztec_start_options.ts @@ -9,6 +9,7 @@ import { isBooleanConfigValue, } from '@aztec/foundation/config'; import { bootnodeConfigMappings, p2pConfigMappings } from '@aztec/p2p'; +import { proofVerifierConfigMappings } from '@aztec/proof-verifier'; import { proverClientConfigMappings } from '@aztec/prover-client'; import { proverNodeConfigMappings } from '@aztec/prover-node'; import { allPxeConfigMappings } from '@aztec/pxe'; @@ -295,6 +296,15 @@ export const aztecStartOptions: { [key: string]: AztecStartOption[] } = { }, ...getOptions('bot', botConfigMappings), ], + 'PROOF VERIFIER': [ + { + flag: '--proof-verifier', + description: 'Starts Aztec Proof Verifier with options', + defaultValue: undefined, + envVar: undefined, + }, + ...getOptions('proofVerifier', proofVerifierConfigMappings), + ], TXE: [ { flag: '--txe', diff --git a/yarn-project/aztec/src/cli/cmds/start_proof_verifier.ts b/yarn-project/aztec/src/cli/cmds/start_proof_verifier.ts new file mode 100644 index 000000000000..89477caaffb8 --- /dev/null +++ b/yarn-project/aztec/src/cli/cmds/start_proof_verifier.ts @@ -0,0 +1,26 @@ +import { type ServerList } from '@aztec/foundation/json-rpc/server'; +import { type LogFn } from '@aztec/foundation/log'; +import { ProofVerifier, proofVerifierConfigMappings } from '@aztec/proof-verifier'; +import { createAndStartTelemetryClient, telemetryClientConfigMappings } from '@aztec/telemetry-client/start'; + +import { extractRelevantOptions } from '../util.js'; + +export async function startProofVerifier( + options: any, + signalHandlers: (() => Promise)[], + userLog: LogFn, +): Promise { + const services: ServerList = []; + + const config = extractRelevantOptions(options, proofVerifierConfigMappings, 'proofVerifier'); + + const telemetryConfig = extractRelevantOptions(options, telemetryClientConfigMappings, 'tel'); + const telemetry = await createAndStartTelemetryClient(telemetryConfig); + const proofVerifier = await ProofVerifier.new(config, telemetry); + + userLog('Starting proof verifier'); + proofVerifier.start(); + + signalHandlers.push(() => proofVerifier.stop()); + return services; +} diff --git a/yarn-project/aztec/terraform/proof-verifier/main.tf b/yarn-project/aztec/terraform/proof-verifier/main.tf new file mode 100644 index 000000000000..d7d3465598b2 --- /dev/null +++ b/yarn-project/aztec/terraform/proof-verifier/main.tf @@ -0,0 +1,209 @@ +terraform { + backend "s3" { + bucket = "aztec-terraform" + region = "eu-west-2" + } + required_providers { + aws = { + source = "hashicorp/aws" + version = "3.74.2" + } + } +} + +# Define provider and region +provider "aws" { + region = "eu-west-2" +} + +data "terraform_remote_state" "aztec2_iac" { + backend = "s3" + config = { + bucket = "aztec-terraform" + key = "aztec2/iac" + region = "eu-west-2" + } +} + +data "terraform_remote_state" "setup_iac" { + backend = "s3" + config = { + bucket = "aztec-terraform" + key = "setup/setup-iac" + region = "eu-west-2" + } +} + +resource "aws_cloudwatch_log_group" "aztec-proof-verifier-log-group" { + name = "/fargate/service/${var.DEPLOY_TAG}/aztec-proof-verifier" + retention_in_days = 14 +} + +resource "aws_service_discovery_service" "aztec-proof-verifier" { + name = "${var.DEPLOY_TAG}-aztec-proof-verifier" + + health_check_custom_config { + failure_threshold = 1 + } + + dns_config { + namespace_id = data.terraform_remote_state.setup_iac.outputs.local_service_discovery_id + + dns_records { + ttl = 60 + type = "A" + } + + dns_records { + ttl = 60 + type = "SRV" + } + + routing_policy = "MULTIVALUE" + } + + # Terraform just fails if this resource changes and you have registered instances. + provisioner "local-exec" { + when = destroy + command = "${path.module}/../servicediscovery-drain.sh ${self.id}" + } +} + +# Create a fleet. +data "template_file" "user_data" { + template = <> /etc/ecs/ecs.config +echo 'ECS_INSTANCE_ATTRIBUTES={"group": "${var.DEPLOY_TAG}-proof-verifier"}' >> /etc/ecs/ecs.config +EOF +} + +resource "aws_launch_template" "proof_verifier_launch_template" { + name = "${var.DEPLOY_TAG}-launch-template" + image_id = "ami-0cd4858f2b923aa6b" + instance_type = "m7a.2xlarge" // 8 cores, 32 GB + vpc_security_group_ids = [data.terraform_remote_state.setup_iac.outputs.security_group_private_id] + + iam_instance_profile { + name = data.terraform_remote_state.setup_iac.outputs.ecs_instance_profile_name + } + + key_name = data.terraform_remote_state.setup_iac.outputs.ecs_instance_key_pair_name + + user_data = base64encode(data.template_file.user_data.rendered) + + tag_specifications { + resource_type = "instance" + tags = { + Name = "${var.DEPLOY_TAG}-proof-verifier" + prometheus = "" + } + } +} + +resource "aws_ec2_fleet" "proof_verifier_fleet" { + launch_template_config { + launch_template_specification { + launch_template_id = aws_launch_template.proof_verifier_launch_template.id + version = aws_launch_template.proof_verifier_launch_template.latest_version + } + + override { + subnet_id = data.terraform_remote_state.setup_iac.outputs.subnet_az1_private_id + availability_zone = "eu-west-2a" + max_price = "0.15" + } + + override { + subnet_id = data.terraform_remote_state.setup_iac.outputs.subnet_az2_private_id + availability_zone = "eu-west-2b" + max_price = "0.15" + } + } + + target_capacity_specification { + default_target_capacity_type = "on-demand" + total_target_capacity = 1 + spot_target_capacity = 0 + on_demand_target_capacity = 1 + } + + terminate_instances = true + terminate_instances_with_expiration = true +} + +resource "aws_ecs_task_definition" "aztec-proof-verifier" { + family = "${var.DEPLOY_TAG}-aztec-proof-verifier" + network_mode = "awsvpc" + requires_compatibilities = ["EC2"] + execution_role_arn = data.terraform_remote_state.setup_iac.outputs.ecs_task_execution_role_arn + task_role_arn = data.terraform_remote_state.aztec2_iac.outputs.cloudwatch_logging_ecs_role_arn + + container_definitions = jsonencode([ + { + name = "${var.DEPLOY_TAG}-aztec-proof-verifier" + image = "${var.DOCKERHUB_ACCOUNT}/aztec:${var.DEPLOY_TAG}" + command = ["start", "--proof-verifier"] + essential = true + cpu = 8192, + memoryReservation = 32768, + portMappings = [] + environment = [ + { name = "PROOF_VERIFIER_L1_START_BLOCK", value = "15918000" }, + { name = "PROOF_VERIFIER_POLL_INTERVAL_MS", value = var.PROOF_VERIFIER_POLL_INTERVAL_MS }, + { name = "ETHEREUM_HOST", value = var.ETHEREUM_HOST }, + { name = "L1_CHAIN_ID", value = var.L1_CHAIN_ID }, + { name = "ROLLUP_CONTRACT_ADDRESS", value = var.ROLLUP_CONTRACT_ADDRESS }, + { name = "LOG_LEVEL", value = var.LOG_LEVEL }, + { name = "NETWORK", value = var.DEPLOY_TAG }, + { name = "LOG_JSON", value = "1" } + ] + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.aztec-proof-verifier-log-group.name + "awslogs-region" = "eu-west-2" + "awslogs-stream-prefix" = "ecs" + } + } + } + ]) +} + +resource "aws_ecs_service" "aztec-proof-verifier" { + name = "${var.DEPLOY_TAG}-aztec-proof-verifier" + cluster = data.terraform_remote_state.setup_iac.outputs.ecs_cluster_id + launch_type = "EC2" + desired_count = 1 + deployment_maximum_percent = 100 + deployment_minimum_healthy_percent = 0 + force_new_deployment = true + enable_execute_command = true + + network_configuration { + subnets = [ + data.terraform_remote_state.setup_iac.outputs.subnet_az1_private_id, + data.terraform_remote_state.setup_iac.outputs.subnet_az2_private_id + ] + security_groups = [data.terraform_remote_state.setup_iac.outputs.security_group_private_id] + } + + # load_balancer { + # target_group_arn = aws_alb_target_group.bot_http.arn + # container_name = "${var.DEPLOY_TAG}-aztec-proof-verifier" + # container_port = 80 + # } + + service_registries { + registry_arn = aws_service_discovery_service.aztec-proof-verifier.arn + container_name = "${var.DEPLOY_TAG}-aztec-proof-verifier" + container_port = 80 + } + + placement_constraints { + type = "memberOf" + expression = "attribute:group == ${var.DEPLOY_TAG}-proof-verifier" + } + + task_definition = aws_ecs_task_definition.aztec-proof-verifier.family +} diff --git a/yarn-project/aztec/terraform/proof-verifier/variables.tf b/yarn-project/aztec/terraform/proof-verifier/variables.tf new file mode 100644 index 000000000000..2063f6ee9a2e --- /dev/null +++ b/yarn-project/aztec/terraform/proof-verifier/variables.tf @@ -0,0 +1,33 @@ +variable "DEPLOY_TAG" { + type = string +} + +variable "DOCKERHUB_ACCOUNT" { + type = string +} + +variable "API_KEY" { + type = string +} + +variable "LOG_LEVEL" { + type = string + default = "verbose" +} + +variable "ETHEREUM_HOST" { + type = string +} + +variable "L1_CHAIN_ID" { + type = number +} + +variable "ROLLUP_CONTRACT_ADDRESS" { + type = string +} + +variable "PROOF_VERIFIER_POLL_INTERVAL_MS" { + type = "number" + default = 10000 +} diff --git a/yarn-project/aztec/tsconfig.json b/yarn-project/aztec/tsconfig.json index 72f82e9d8050..570da13289f6 100644 --- a/yarn-project/aztec/tsconfig.json +++ b/yarn-project/aztec/tsconfig.json @@ -66,6 +66,9 @@ { "path": "../p2p-bootstrap" }, + { + "path": "../proof-verifier" + }, { "path": "../protocol-contracts" }, diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 2d9a50293513..72f292abbe98 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -107,5 +107,7 @@ export type EnvVar = | 'VALIDATOR_DISABLED' | 'PROVER_NODE_DISABLE_AUTOMATIC_PROVING' | 'PROVER_NODE_MAX_PENDING_JOBS' + | 'PROOF_VERIFIER_POLL_INTERVAL_MS' + | 'PROOF_VERIFIER_L1_START_BLOCK' | 'LOG_LEVEL' | 'DEBUG'; diff --git a/yarn-project/package.json b/yarn-project/package.json index 5707c688028b..316150e46fdc 100644 --- a/yarn-project/package.json +++ b/yarn-project/package.json @@ -49,6 +49,7 @@ "noir-protocol-circuits-types", "p2p", "p2p-bootstrap", + "proof-verifier", "protocol-contracts", "prover-client", "prover-node", diff --git a/yarn-project/proof-verifier/.eslintrc.cjs b/yarn-project/proof-verifier/.eslintrc.cjs new file mode 100644 index 000000000000..e659927475c0 --- /dev/null +++ b/yarn-project/proof-verifier/.eslintrc.cjs @@ -0,0 +1 @@ +module.exports = require('@aztec/foundation/eslint'); diff --git a/yarn-project/proof-verifier/package.json b/yarn-project/proof-verifier/package.json new file mode 100644 index 000000000000..1a6c5f42afd7 --- /dev/null +++ b/yarn-project/proof-verifier/package.json @@ -0,0 +1,73 @@ +{ + "name": "@aztec/proof-verifier", + "type": "module", + "exports": { + ".": "./dest/index.js" + }, + "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", + "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests" + }, + "engines": { + "node": ">=18" + }, + "files": [ + "dest", + "src", + "!*.test.*" + ], + "dependencies": { + "@aztec/archiver": "workspace:^", + "@aztec/bb-prover": "workspace:^", + "@aztec/circuit-types": "workspace:^", + "@aztec/circuits.js": "workspace:^", + "@aztec/ethereum": "workspace:^", + "@aztec/foundation": "workspace:^", + "@aztec/noir-protocol-circuits-types": "workspace:^", + "@aztec/telemetry-client": "workspace:^", + "viem": "^2.7.15" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/jest": "^29.5.0", + "@types/node": "^18.7.23", + "jest": "^29.5.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "jest": { + "extensionsToTreatAsEsm": [ + ".ts" + ], + "transform": { + "^.+\\.tsx?$": [ + "@swc/jest", + { + "jsc": { + "parser": { + "syntax": "typescript", + "decorators": true + } + } + } + ] + }, + "moduleNameMapper": { + "^(\\.{1,2}/.*)\\.[cm]?js$": "$1" + }, + "reporters": [ + [ + "default", + { + "summaryThreshold": 9999 + } + ] + ], + "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$", + "rootDir": "./src" + } +} diff --git a/yarn-project/proof-verifier/src/config.ts b/yarn-project/proof-verifier/src/config.ts new file mode 100644 index 000000000000..0b6e12986012 --- /dev/null +++ b/yarn-project/proof-verifier/src/config.ts @@ -0,0 +1,74 @@ +import { EthAddress } from '@aztec/circuits.js'; +import { + type ConfigMappingsType, + booleanConfigHelper, + getConfigFromMappings, + numberConfigHelper, +} from '@aztec/foundation/config'; +import { type TelemetryClientConfig, telemetryClientConfigMappings } from '@aztec/telemetry-client/start'; + +export type ProofVerifierConfig = { + /** The URL to an L1 node */ + l1Url: string; + /** The L1 chain ID */ + l1ChainId: number; + /** Start block number */ + l1StartBlock: bigint; + /** The address of the Rollup contract */ + rollupAddress: EthAddress; + /** How often to poll L1 for proof submission */ + pollIntervalMs: number; + /** The path to the bb binary */ + bbBinaryPath: string; + /** Where bb stores temporary files */ + bbWorkingDirectory: string; + /** Whether to skip cleanup of bb temporary files */ + bbSkipCleanup: boolean; +} & TelemetryClientConfig; + +export const proofVerifierConfigMappings: ConfigMappingsType = { + ...telemetryClientConfigMappings, + l1Url: { + env: 'ETHEREUM_HOST', + description: 'The URL to an L1 node', + }, + l1ChainId: { + env: 'L1_CHAIN_ID', + parseEnv: (val: string) => +val, + defaultValue: 31337, + description: 'The chain ID of the ethereum host.', + }, + l1StartBlock: { + env: 'PROOF_VERIFIER_L1_START_BLOCK', + parseEnv: (val: string) => BigInt(val), + description: 'Start block number', + defaultValue: 1n, + }, + rollupAddress: { + env: 'ROLLUP_CONTRACT_ADDRESS', + description: 'The address of the Rollup contract', + parseEnv: EthAddress.fromString, + }, + pollIntervalMs: { + env: 'PROOF_VERIFIER_POLL_INTERVAL_MS', + description: 'How often to poll L1 for proof submission', + ...numberConfigHelper(10_000), + }, + bbBinaryPath: { + env: 'BB_BINARY_PATH', + description: 'The path to the bb binary', + }, + bbWorkingDirectory: { + env: 'BB_WORKING_DIRECTORY', + description: 'Where bb stores temporary files', + }, + bbSkipCleanup: { + env: 'BB_SKIP_CLEANUP', + description: 'Whether to skip cleanup of bb temporary files', + ...booleanConfigHelper(false), + }, +}; + +export function getProofVerifierConfigFromEnv(): ProofVerifierConfig { + return getConfigFromMappings(proofVerifierConfigMappings); +} diff --git a/yarn-project/proof-verifier/src/index.ts b/yarn-project/proof-verifier/src/index.ts new file mode 100644 index 000000000000..4b3ddaa6d8f4 --- /dev/null +++ b/yarn-project/proof-verifier/src/index.ts @@ -0,0 +1,2 @@ +export * from './config.js'; +export * from './proof_verifier.js'; diff --git a/yarn-project/proof-verifier/src/proof_verifier.ts b/yarn-project/proof-verifier/src/proof_verifier.ts new file mode 100644 index 000000000000..bad8f9ade2f0 --- /dev/null +++ b/yarn-project/proof-verifier/src/proof_verifier.ts @@ -0,0 +1,84 @@ +import { retrieveL2ProofsFromRollup } from '@aztec/archiver/data-retrieval'; +import { BBCircuitVerifier } from '@aztec/bb-prover'; +import { createEthereumChain } from '@aztec/ethereum'; +import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; +import { RunningPromise } from '@aztec/foundation/running-promise'; +import { Attributes, Metrics, type TelemetryClient, type UpDownCounter, ValueType } from '@aztec/telemetry-client'; + +import { type PublicClient, createPublicClient, http } from 'viem'; + +import { type ProofVerifierConfig } from './config.js'; + +export class ProofVerifier { + private runningPromise: RunningPromise; + private synchedToL1Block = 0n; + + private proofVerified: UpDownCounter; + + constructor( + private config: ProofVerifierConfig, + private client: PublicClient, + private verifier: BBCircuitVerifier, + telemetryClient: TelemetryClient, + private logger: DebugLogger, + ) { + this.runningPromise = new RunningPromise(this.work.bind(this), config.pollIntervalMs); + this.proofVerified = telemetryClient.getMeter('ProofVerifier').createUpDownCounter(Metrics.PROOF_VERIFIER_COUNT, { + valueType: ValueType.INT, + description: 'The number of proofs verified by the block verifier bot', + }); + this.synchedToL1Block = config.l1StartBlock - 1n; + } + + static async new(config: ProofVerifierConfig, telemetryClient: TelemetryClient): Promise { + const logger = createDebugLogger('aztec:block-verifier-bot'); + const verifier = await BBCircuitVerifier.new(config, [], logger); + const client = createPublicClient({ + chain: createEthereumChain(config.l1Url, config.l1ChainId).chainInfo, + transport: http(config.l1Url), + }); + + return new ProofVerifier(config, client, verifier, telemetryClient, logger); + } + + start() { + this.runningPromise.start(); + } + + async stop() { + await this.runningPromise.stop(); + } + + private async work() { + const startBlock = this.synchedToL1Block + 1n; + this.logger.debug(`Fetching proofs from L1 block ${startBlock}`); + const { lastProcessedL1BlockNumber, retrievedData } = await retrieveL2ProofsFromRollup( + this.client, + this.config.rollupAddress, + startBlock, + ); + + for (const { l2BlockNumber, txHash, proof, proverId } of retrievedData) { + try { + await this.verifier.verifyProofForCircuit('RootRollupArtifact', proof); + this.logger.info(`Verified proof for L2 block proverId=${proverId} l2Block=${l2BlockNumber} l1Tx=${txHash}`); + + this.proofVerified.add(1, { + [Attributes.ROLLUP_PROVER_ID]: proverId.toString(), + [Attributes.OK]: true, + }); + } catch (err) { + this.logger.warn( + `Failed to verify proof for L2 block proverId=${proverId} l2Block=${l2BlockNumber} l1Tx=${txHash}`, + ); + + this.proofVerified.add(1, { + [Attributes.ROLLUP_PROVER_ID]: proverId.toString(), + [Attributes.OK]: false, + }); + } + } + + this.synchedToL1Block = lastProcessedL1BlockNumber; + } +} diff --git a/yarn-project/proof-verifier/tsconfig.json b/yarn-project/proof-verifier/tsconfig.json new file mode 100644 index 000000000000..283f554f5701 --- /dev/null +++ b/yarn-project/proof-verifier/tsconfig.json @@ -0,0 +1,35 @@ +{ + "extends": "..", + "compilerOptions": { + "outDir": "dest", + "rootDir": "src", + "tsBuildInfoFile": ".tsbuildinfo" + }, + "references": [ + { + "path": "../archiver" + }, + { + "path": "../bb-prover" + }, + { + "path": "../circuit-types" + }, + { + "path": "../circuits.js" + }, + { + "path": "../ethereum" + }, + { + "path": "../foundation" + }, + { + "path": "../noir-protocol-circuits-types" + }, + { + "path": "../telemetry-client" + } + ], + "include": ["src"] +} diff --git a/yarn-project/telemetry-client/src/metrics.ts b/yarn-project/telemetry-client/src/metrics.ts index 48d8deda753b..094044f416da 100644 --- a/yarn-project/telemetry-client/src/metrics.ts +++ b/yarn-project/telemetry-client/src/metrics.ts @@ -71,3 +71,5 @@ export const WORLD_STATE_FORK_DURATION = 'aztec.world_state.fork.duration'; export const WORLD_STATE_SYNC_DURATION = 'aztec.world_state.sync.duration'; export const WORLD_STATE_MERKLE_TREE_SIZE = 'aztec.world_state.merkle_tree_size'; export const WORLD_STATE_DB_SIZE = 'aztec.world_state.db_size'; + +export const PROOF_VERIFIER_COUNT = 'aztec.proof_verifier.count'; diff --git a/yarn-project/tsconfig.json b/yarn-project/tsconfig.json index 52f60af50fb9..45e18ab81290 100644 --- a/yarn-project/tsconfig.json +++ b/yarn-project/tsconfig.json @@ -53,7 +53,8 @@ { "path": "scripts/tsconfig.json" }, { "path": "entrypoints/tsconfig.json" }, { "path": "cli/tsconfig.json" }, - { "path": "cli-wallet/tsconfig.json" } + { "path": "cli-wallet/tsconfig.json" }, + { "path": "proof-verifier/tsconfig.json" } ], "files": ["./@types/jest/index.d.ts"], "exclude": ["node_modules", "**/node_modules", "**/.*/"] diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index a5f52210f28f..ba988a6a5ff0 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -250,6 +250,7 @@ __metadata: "@aztec/noir-protocol-circuits-types": "workspace:^" "@aztec/p2p": "workspace:^" "@aztec/p2p-bootstrap": "workspace:^" + "@aztec/proof-verifier": "workspace:^" "@aztec/protocol-contracts": "workspace:^" "@aztec/prover-client": "workspace:^" "@aztec/prover-node": "workspace:^" @@ -873,6 +874,28 @@ __metadata: languageName: unknown linkType: soft +"@aztec/proof-verifier@workspace:^, @aztec/proof-verifier@workspace:proof-verifier": + version: 0.0.0-use.local + resolution: "@aztec/proof-verifier@workspace:proof-verifier" + dependencies: + "@aztec/archiver": "workspace:^" + "@aztec/bb-prover": "workspace:^" + "@aztec/circuit-types": "workspace:^" + "@aztec/circuits.js": "workspace:^" + "@aztec/ethereum": "workspace:^" + "@aztec/foundation": "workspace:^" + "@aztec/noir-protocol-circuits-types": "workspace:^" + "@aztec/telemetry-client": "workspace:^" + "@jest/globals": ^29.5.0 + "@types/jest": ^29.5.0 + "@types/node": ^18.7.23 + jest: ^29.5.0 + ts-node: ^10.9.1 + typescript: ^5.0.4 + viem: ^2.7.15 + languageName: unknown + linkType: soft + "@aztec/protocol-contracts@workspace:^, @aztec/protocol-contracts@workspace:protocol-contracts": version: 0.0.0-use.local resolution: "@aztec/protocol-contracts@workspace:protocol-contracts"