From 2132bc254ef3dbeaec27be98acb85a98b20385bb Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:46:02 +0800 Subject: [PATCH] feat(prover): perform prover coordination via p2p layer (#9325) fixes: #9264 --- scripts/run_native_testnet.sh | 2 +- .../aztec-node/src/aztec-node/server.ts | 4 +- yarn-project/bb-prover/package.json | 5 +- .../src/interfaces/prover-coordination.ts | 5 + .../end-to-end/src/e2e_p2p/p2p_network.ts | 2 +- .../end-to-end/src/fixtures/setup_p2p_test.ts | 29 ---- .../src/fixtures/snapshot_manager.ts | 10 +- yarn-project/p2p/package.json | 7 +- yarn-project/p2p/src/client/index.ts | 49 +------ .../p2p/src/client/p2p_client.test.ts | 2 +- yarn-project/p2p/src/client/p2p_client.ts | 46 ++++++- yarn-project/p2p/src/config.ts | 2 +- yarn-project/p2p/src/mocks/index.ts | 128 +++++++++++++++++- yarn-project/p2p/src/service/peer_scoring.ts | 8 +- ...on.test.ts => reqresp.integration.test.ts} | 106 +++++++-------- yarn-project/p2p/src/util.ts | 53 ++++++++ yarn-project/prover-node/package.json | 2 + yarn-project/prover-node/src/config.ts | 4 + yarn-project/prover-node/src/factory.ts | 16 ++- .../src/prover-coordination/factory.ts | 56 +++++++- .../prover-node/src/prover-node.test.ts | 99 +++++++++++++- yarn-project/prover-node/src/prover-node.ts | 1 + .../prover-node/src/quote-provider/http.ts | 2 +- yarn-project/prover-node/tsconfig.json | 6 + yarn-project/yarn.lock | 3 + 25 files changed, 479 insertions(+), 168 deletions(-) rename yarn-project/p2p/src/service/reqresp/{p2p_client.integration.test.ts => reqresp.integration.test.ts} (80%) diff --git a/scripts/run_native_testnet.sh b/scripts/run_native_testnet.sh index 4f8f6fbe1f7..3f72b261ee6 100755 --- a/scripts/run_native_testnet.sh +++ b/scripts/run_native_testnet.sh @@ -27,7 +27,7 @@ Options: ' # Default values -TEST_SCRIPT=./test-transfer.sh +TEST_SCRIPT="./test-transfer.sh" PROVER_SCRIPT="\"./prover-node.sh 8078 false\"" NUM_VALIDATORS=3 INTERLEAVED=false diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index b56eb13940e..cc0173596b6 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -118,7 +118,7 @@ export class AztecNodeService implements AztecNode { } addEpochProofQuote(quote: EpochProofQuote): Promise { - return Promise.resolve(this.p2pClient.broadcastEpochProofQuote(quote)); + return Promise.resolve(this.p2pClient.addEpochProofQuote(quote)); } getEpochProofQuotes(epoch: bigint): Promise { @@ -397,7 +397,7 @@ export class AztecNodeService implements AztecNode { * @returns - The tx if it exists. */ public getTxByHash(txHash: TxHash) { - return Promise.resolve(this.p2pClient!.getTxByHash(txHash)); + return Promise.resolve(this.p2pClient!.getTxByHashFromPool(txHash)); } /** diff --git a/yarn-project/bb-prover/package.json b/yarn-project/bb-prover/package.json index efdeb93cc5c..9081ad3164e 100644 --- a/yarn-project/bb-prover/package.json +++ b/yarn-project/bb-prover/package.json @@ -3,7 +3,10 @@ "version": "0.1.0", "type": "module", "exports": { - ".": "./dest/index.js" + ".": "./dest/index.js", + "./prover": "./dest/prover/index.js", + "./verifier": "./dest/verifier/index.js", + "./test": "./dest/test/index.js" }, "bin": { "bb-cli": "./dest/bb/index.js" diff --git a/yarn-project/circuit-types/src/interfaces/prover-coordination.ts b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts index 01918a34d39..6413faedeb2 100644 --- a/yarn-project/circuit-types/src/interfaces/prover-coordination.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts @@ -16,4 +16,9 @@ export interface ProverCoordination { * @param quote - The quote to store */ addEpochProofQuote(quote: EpochProofQuote): Promise; + + /** + * Stops the coordination service. + */ + stop(): Promise; } 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 13e51b6db2d..84a2aa53b8f 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 @@ -4,13 +4,13 @@ import { AZTEC_SLOT_DURATION, ETHEREUM_SLOT_DURATION, EthAddress } from '@aztec/ import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { RollupAbi } from '@aztec/l1-artifacts'; import { type BootstrapNode } from '@aztec/p2p'; +import { createBootstrapNodeFromPrivateKey } from '@aztec/p2p/mocks'; import getPort from 'get-port'; import { getContract } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import { - createBootstrapNodeFromPrivateKey, createValidatorConfig, generateNodePrivateKeys, generatePeerIdPrivateKeys, diff --git a/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts b/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts index 13152cf557d..48e89c6512d 100644 --- a/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts +++ b/yarn-project/end-to-end/src/fixtures/setup_p2p_test.ts @@ -4,7 +4,6 @@ import { type AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node'; import { type SentTx, createDebugLogger } from '@aztec/aztec.js'; import { type AztecAddress } from '@aztec/circuits.js'; -import { type BootnodeConfig, BootstrapNode, createLibP2PPeerId } from '@aztec/p2p'; import { type PXEService } from '@aztec/pxe'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; @@ -121,31 +120,3 @@ export async function createValidatorConfig( return nodeConfig; } - -export function createBootstrapNodeConfig(privateKey: string, port: number): BootnodeConfig { - return { - udpListenAddress: `0.0.0.0:${port}`, - udpAnnounceAddress: `127.0.0.1:${port}`, - peerIdPrivateKey: privateKey, - minPeerCount: 10, - maxPeerCount: 100, - }; -} - -export function createBootstrapNodeFromPrivateKey(privateKey: string, port: number): Promise { - const config = createBootstrapNodeConfig(privateKey, port); - return startBootstrapNode(config); -} - -export async function createBootstrapNode(port: number): Promise { - const peerId = await createLibP2PPeerId(); - const config = createBootstrapNodeConfig(Buffer.from(peerId.privateKey!).toString('hex'), port); - - return startBootstrapNode(config); -} - -async function startBootstrapNode(config: BootnodeConfig) { - const bootstrapNode = new BootstrapNode(); - await bootstrapNode.start(config); - return bootstrapNode; -} diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index bf7ce4b501e..d8a9502d340 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -256,6 +256,14 @@ async function createAndSyncProverNode( aztecNodeConfig: AztecNodeConfig, aztecNode: AztecNode, ) { + // Disable stopping the aztec node as the prover coordination test will kill it otherwise + // This is only required when stopping the prover node for testing + const aztecNodeWithoutStop = { + addEpochProofQuote: aztecNode.addEpochProofQuote.bind(aztecNode), + getTxByHash: aztecNode.getTxByHash.bind(aztecNode), + stop: () => Promise.resolve(), + }; + // Creating temp store and archiver for simulated prover node const archiverConfig = { ...aztecNodeConfig, dataDirectory: undefined }; const archiver = await createArchiver(archiverConfig, new NoopTelemetryClient(), { blockUntilSync: true }); @@ -277,7 +285,7 @@ async function createAndSyncProverNode( proverTargetEscrowAmount: 2000n, }; const proverNode = await createProverNode(proverConfig, { - aztecNodeTxProvider: aztecNode, + aztecNodeTxProvider: aztecNodeWithoutStop, archiver: archiver as Archiver, }); await proverNode.start(); diff --git a/yarn-project/p2p/package.json b/yarn-project/p2p/package.json index 14e66ce63ef..284973b6c85 100644 --- a/yarn-project/p2p/package.json +++ b/yarn-project/p2p/package.json @@ -2,7 +2,11 @@ "name": "@aztec/p2p", "version": "0.0.0", "type": "module", - "exports": "./dest/index.js", + "exports": { + ".": "./dest/index.js", + "./mocks": "./dest/mocks/index.js", + "./bootstrap": "./dest/bootstrap/bootstrap.js" + }, "typedocOptions": { "entryPoints": [ "./src/index.ts" @@ -93,6 +97,7 @@ "@jest/globals": "^29.5.0", "@types/jest": "^29.5.0", "@types/node": "^18.14.6", + "get-port": "^7.1.0", "it-drain": "^3.0.5", "it-length": "^3.0.6", "jest": "^29.5.0", diff --git a/yarn-project/p2p/src/client/index.ts b/yarn-project/p2p/src/client/index.ts index 1dd56d7484a..e6a4d273f0e 100644 --- a/yarn-project/p2p/src/client/index.ts +++ b/yarn-project/p2p/src/client/index.ts @@ -16,7 +16,7 @@ import { AztecKVTxPool, type TxPool } from '../mem_pools/tx_pool/index.js'; import { DiscV5Service } from '../service/discV5_service.js'; import { DummyP2PService } from '../service/dummy_service.js'; import { LibP2PService, createLibP2PPeerId } from '../service/index.js'; -import { getPublicIp, resolveAddressIfNecessary, splitAddressPort } from '../util.js'; +import { configureP2PClientAddresses } from '../util.js'; export * from './p2p_client.js'; @@ -67,50 +67,3 @@ export const createP2PClient = async ( } return new P2PClient(store, l2BlockSource, mempools, p2pService, config.keepProvenTxsInPoolFor, telemetry); }; - -async function configureP2PClientAddresses(_config: P2PConfig & DataStoreConfig): Promise { - const config = { ..._config }; - const { - tcpAnnounceAddress: configTcpAnnounceAddress, - udpAnnounceAddress: configUdpAnnounceAddress, - queryForIp, - } = config; - - config.tcpAnnounceAddress = configTcpAnnounceAddress - ? await resolveAddressIfNecessary(configTcpAnnounceAddress) - : undefined; - config.udpAnnounceAddress = configUdpAnnounceAddress - ? await resolveAddressIfNecessary(configUdpAnnounceAddress) - : undefined; - - // create variable for re-use if needed - let publicIp; - - // check if no announce IP was provided - const splitTcpAnnounceAddress = splitAddressPort(configTcpAnnounceAddress || '', true); - if (splitTcpAnnounceAddress.length == 2 && splitTcpAnnounceAddress[0] === '') { - if (queryForIp) { - publicIp = await getPublicIp(); - const tcpAnnounceAddress = `${publicIp}:${splitTcpAnnounceAddress[1]}`; - config.tcpAnnounceAddress = tcpAnnounceAddress; - } else { - throw new Error( - `Invalid announceTcpAddress provided: ${configTcpAnnounceAddress}. Expected format: :`, - ); - } - } - - const splitUdpAnnounceAddress = splitAddressPort(configUdpAnnounceAddress || '', true); - if (splitUdpAnnounceAddress.length == 2 && splitUdpAnnounceAddress[0] === '') { - // If announceUdpAddress is not provided, use announceTcpAddress - if (!queryForIp && config.tcpAnnounceAddress) { - config.udpAnnounceAddress = config.tcpAnnounceAddress; - } else if (queryForIp) { - const udpPublicIp = publicIp || (await getPublicIp()); - const udpAnnounceAddress = `${udpPublicIp}:${splitUdpAnnounceAddress[1]}`; - config.udpAnnounceAddress = udpAnnounceAddress; - } - } - - return config; -} diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index ae0adba6a2e..8c9cd44e0d6 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -194,7 +194,7 @@ describe('In-Memory P2P Client', () => { ]; for (const quote of proofQuotes) { - client.broadcastEpochProofQuote(quote); + await client.addEpochProofQuote(quote); } expect(epochProofQuotePool.addQuote).toBeCalledTimes(proofQuotes.length); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 85fca19d464..f47d6e36140 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -16,7 +16,7 @@ import { Attributes, type TelemetryClient, WithTracer, trackSpan } from '@aztec/ import { type ENR } from '@chainsafe/enr'; -import { getP2PConfigEnvVars } from '../config.js'; +import { getP2PConfigFromEnv } from '../config.js'; import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { type MemPools } from '../mem_pools/interface.js'; @@ -77,11 +77,11 @@ export interface P2P { getEpochProofQuotes(epoch: bigint): Promise; /** - * Broadcasts an EpochProofQuote to other peers. + * Adds an EpochProofQuote to the pool and broadcasts an EpochProofQuote to other peers. * * @param quote - the quote to broadcast */ - broadcastEpochProofQuote(quote: EpochProofQuote): void; + addEpochProofQuote(quote: EpochProofQuote): Promise; /** * Registers a callback from the validator client that determines how to behave when @@ -130,7 +130,14 @@ export interface P2P { * @param txHash - Hash of tx to return. * @returns A single tx or undefined. */ - getTxByHash(txHash: TxHash): Tx | undefined; + getTxByHashFromPool(txHash: TxHash): Tx | undefined; + + /** + * Returns a transaction in the transaction pool by its hash, requesting it from the network if it is not found. + * @param txHash - Hash of tx to return. + * @returns A single tx or undefined. + */ + getTxByHash(txHash: TxHash): Promise; /** * Returns whether the given tx hash is flagged as pending or mined. @@ -217,7 +224,7 @@ export class P2PClient extends WithTracer implements P2P { ) { super(telemetryClient, 'P2PClient'); - const { blockCheckIntervalMS: checkInterval, l2QueueSize: p2pL2QueueSize } = getP2PConfigEnvVars(); + const { blockCheckIntervalMS: checkInterval, l2QueueSize: p2pL2QueueSize } = getP2PConfigFromEnv(); const l2DownloaderOpts = { maxQueueSize: p2pL2QueueSize, pollIntervalMS: checkInterval }; // TODO(palla/prover-node): This effectively downloads blocks twice from the archiver, which is an issue // if the archiver is remote. We should refactor this so the downloader keeps a single queue and handles @@ -234,18 +241,29 @@ export class P2PClient extends WithTracer implements P2P { } #assertIsReady() { + // this.log.info('Checking if p2p client is ready, current state: ', this.currentState); if (!this.isReady()) { throw new Error('P2P client not ready'); } } + /** + * Adds an EpochProofQuote to the pool and broadcasts an EpochProofQuote to other peers. + * @param quote - the quote to broadcast + */ + addEpochProofQuote(quote: EpochProofQuote): Promise { + this.epochProofQuotePool.addQuote(quote); + this.broadcastEpochProofQuote(quote); + return Promise.resolve(); + } + getEpochProofQuotes(epoch: bigint): Promise { return Promise.resolve(this.epochProofQuotePool.getQuotes(epoch)); } broadcastEpochProofQuote(quote: EpochProofQuote): void { this.#assertIsReady(); - this.epochProofQuotePool.addQuote(quote); + this.log.info('Broadcasting epoch proof quote', quote.toViemArgs()); return this.p2pService.propagate(quote); } @@ -406,10 +424,24 @@ export class P2PClient extends WithTracer implements P2P { * @param txHash - Hash of the transaction to look for in the pool. * @returns A single tx or undefined. */ - getTxByHash(txHash: TxHash): Tx | undefined { + getTxByHashFromPool(txHash: TxHash): Tx | undefined { return this.txPool.getTxByHash(txHash); } + /** + * Returns a transaction in the transaction pool by its hash. + * If the transaction is not in the pool, it will be requested from the network. + * @param txHash - Hash of the transaction to look for in the pool. + * @returns A single tx or undefined. + */ + getTxByHash(txHash: TxHash): Promise { + const tx = this.txPool.getTxByHash(txHash); + if (tx) { + return Promise.resolve(tx); + } + return this.requestTxByHash(txHash); + } + /** * Verifies the 'tx' and, if valid, adds it to local tx pool and forwards it to other peers. * @param tx - The tx to verify. diff --git a/yarn-project/p2p/src/config.ts b/yarn-project/p2p/src/config.ts index 88ff1af0939..351e28de8b0 100644 --- a/yarn-project/p2p/src/config.ts +++ b/yarn-project/p2p/src/config.ts @@ -302,7 +302,7 @@ export const p2pConfigMappings: ConfigMappingsType = { * Gets the config values for p2p client from environment variables. * @returns The config values for p2p client. */ -export function getP2PConfigEnvVars(): P2PConfig { +export function getP2PConfigFromEnv(): P2PConfig { return getConfigFromMappings(p2pConfigMappings); } diff --git a/yarn-project/p2p/src/mocks/index.ts b/yarn-project/p2p/src/mocks/index.ts index 2610d691203..5127a8c6238 100644 --- a/yarn-project/p2p/src/mocks/index.ts +++ b/yarn-project/p2p/src/mocks/index.ts @@ -1,11 +1,27 @@ -import { type ClientProtocolCircuitVerifier, type Tx } from '@aztec/circuit-types'; +import { + type ClientProtocolCircuitVerifier, + type L2BlockSource, + type Tx, + type WorldStateSynchronizer, +} from '@aztec/circuit-types'; +import { type DataStoreConfig } from '@aztec/kv-store/utils'; +import { type TelemetryClient } from '@aztec/telemetry-client'; +import { gossipsub } from '@chainsafe/libp2p-gossipsub'; import { noise } from '@chainsafe/libp2p-noise'; import { yamux } from '@chainsafe/libp2p-yamux'; import { bootstrap } from '@libp2p/bootstrap'; +import { identify } from '@libp2p/identify'; +import { type PeerId } from '@libp2p/interface'; import { tcp } from '@libp2p/tcp'; +import getPort from 'get-port'; import { type Libp2p, type Libp2pOptions, createLibp2p } from 'libp2p'; +import { BootstrapNode } from '../bootstrap/bootstrap.js'; +import { type BootnodeConfig, type P2PConfig } from '../config.js'; +import { type MemPools } from '../mem_pools/interface.js'; +import { DiscV5Service } from '../service/discV5_service.js'; +import { LibP2PService, createLibP2PPeerId } from '../service/libp2p_service.js'; import { type PeerManager } from '../service/peer_manager.js'; import { type P2PReqRespConfig } from '../service/reqresp/config.js'; import { pingHandler, statusHandler } from '../service/reqresp/handlers.js'; @@ -18,20 +34,35 @@ import { noopValidator, } from '../service/reqresp/interface.js'; import { ReqResp } from '../service/reqresp/reqresp.js'; +import { type PubSubLibp2p } from '../util.js'; /** * Creates a libp2p node, pre configured. * @param boostrapAddrs - an optional list of bootstrap addresses * @returns Lip2p node */ -export async function createLibp2pNode(boostrapAddrs: string[] = []): Promise { +export async function createLibp2pNode( + boostrapAddrs: string[] = [], + peerId?: PeerId, + port?: number, + enableGossipSub: boolean = false, + start: boolean = true, +): Promise { + port = port ?? (await getPort()); const options: Libp2pOptions = { + start, addresses: { - listen: ['/ip4/0.0.0.0/tcp/0'], + listen: [`/ip4/0.0.0.0/tcp/${port}`], + announce: [`/ip4/0.0.0.0/tcp/${port}`], }, connectionEncryption: [noise()], streamMuxers: [yamux()], transports: [tcp()], + services: { + identify: identify({ + protocolPrefix: 'aztec', + }), + }, }; if (boostrapAddrs.length > 0) { @@ -42,9 +73,65 @@ export async function createLibp2pNode(boostrapAddrs: string[] = []): Promise
  • => { + const stopPromises = []; for (const node of nodes) { - await node.req.stop(); - await node.p2p.stop(); + stopPromises.push(node.req.stop()); + stopPromises.push(node.p2p.stop()); } + await Promise.all(stopPromises); }; // Create a req resp node, exposing the underlying p2p node @@ -132,3 +221,32 @@ export class AlwaysFalseCircuitVerifier implements ClientProtocolCircuitVerifier return Promise.resolve(false); } } + +// Bootnodes +export function createBootstrapNodeConfig(privateKey: string, port: number): BootnodeConfig { + return { + udpListenAddress: `0.0.0.0:${port}`, + udpAnnounceAddress: `127.0.0.1:${port}`, + peerIdPrivateKey: privateKey, + minPeerCount: 10, + maxPeerCount: 100, + }; +} + +export function createBootstrapNodeFromPrivateKey(privateKey: string, port: number): Promise { + const config = createBootstrapNodeConfig(privateKey, port); + return startBootstrapNode(config); +} + +export async function createBootstrapNode(port: number): Promise { + const peerId = await createLibP2PPeerId(); + const config = createBootstrapNodeConfig(Buffer.from(peerId.privateKey!).toString('hex'), port); + + return startBootstrapNode(config); +} + +async function startBootstrapNode(config: BootnodeConfig) { + const bootstrapNode = new BootstrapNode(); + await bootstrapNode.start(config); + return bootstrapNode; +} diff --git a/yarn-project/p2p/src/service/peer_scoring.ts b/yarn-project/p2p/src/service/peer_scoring.ts index ef6db2d3404..d59cb10b182 100644 --- a/yarn-project/p2p/src/service/peer_scoring.ts +++ b/yarn-project/p2p/src/service/peer_scoring.ts @@ -32,14 +32,14 @@ export class PeerScoring { peerPenalties: { [key in PeerErrorSeverity]: number }; constructor(config: P2PConfig) { - const orderedValues = config.peerPenaltyValues.sort((a, b) => a - b); + const orderedValues = config.peerPenaltyValues?.sort((a, b) => a - b); this.peerPenalties = { [PeerErrorSeverity.HighToleranceError]: - orderedValues[0] ?? DefaultPeerPenalties[PeerErrorSeverity.LowToleranceError], + orderedValues?.[0] ?? DefaultPeerPenalties[PeerErrorSeverity.LowToleranceError], [PeerErrorSeverity.MidToleranceError]: - orderedValues[1] ?? DefaultPeerPenalties[PeerErrorSeverity.MidToleranceError], + orderedValues?.[1] ?? DefaultPeerPenalties[PeerErrorSeverity.MidToleranceError], [PeerErrorSeverity.LowToleranceError]: - orderedValues[2] ?? DefaultPeerPenalties[PeerErrorSeverity.HighToleranceError], + orderedValues?.[2] ?? DefaultPeerPenalties[PeerErrorSeverity.HighToleranceError], }; } diff --git a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts b/yarn-project/p2p/src/service/reqresp/reqresp.integration.test.ts similarity index 80% rename from yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts rename to yarn-project/p2p/src/service/reqresp/reqresp.integration.test.ts index b91aa6aa703..cb88b9a4ac5 100644 --- a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts +++ b/yarn-project/p2p/src/service/reqresp/reqresp.integration.test.ts @@ -1,16 +1,15 @@ // An integration test for the p2p client to test req resp protocols import { MockL2BlockSource } from '@aztec/archiver/test'; import { type ClientProtocolCircuitVerifier, type WorldStateSynchronizer, mockTx } from '@aztec/circuit-types'; -import { EthAddress } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; -import { getRandomPort } from '@aztec/foundation/testing'; import { type AztecKVStore } from '@aztec/kv-store'; import { type DataStoreConfig, openTmpStore } from '@aztec/kv-store/utils'; import { SignableENR } from '@chainsafe/enr'; import { describe, expect, it, jest } from '@jest/globals'; import { multiaddr } from '@multiformats/multiaddr'; +import getPort from 'get-port'; import { generatePrivateKey } from 'viem/accounts'; import { createP2PClient } from '../../client/index.js'; @@ -45,25 +44,49 @@ function generatePeerIdPrivateKeys(numberOfPeers: number): string[] { const NUMBER_OF_PEERS = 2; +// Mock the mempools +const makeMockPools = () => { + return { + txPool: { + addTxs: jest.fn(() => {}), + getTxByHash: jest.fn().mockReturnValue(undefined), + deleteTxs: jest.fn(), + getAllTxs: jest.fn().mockReturnValue([]), + getAllTxHashes: jest.fn().mockReturnValue([]), + getMinedTxHashes: jest.fn().mockReturnValue([]), + getPendingTxHashes: jest.fn().mockReturnValue([]), + getTxStatus: jest.fn().mockReturnValue(undefined), + markAsMined: jest.fn(), + }, + attestationPool: { + addAttestations: jest.fn(), + deleteAttestations: jest.fn(), + deleteAttestationsForSlot: jest.fn(), + getAttestationsForSlot: jest.fn().mockReturnValue(undefined), + }, + epochProofQuotePool: { + addQuote: jest.fn(), + getQuotes: jest.fn().mockReturnValue([]), + deleteQuotesToEpoch: jest.fn(), + }, + }; +}; + describe('Req Resp p2p client integration', () => { let txPool: Mockify; let attestationPool: Mockify; let epochProofQuotePool: Mockify; - let blockSource: MockL2BlockSource; + let l2BlockSource: MockL2BlockSource; let kvStore: AztecKVStore; - let worldStateSynchronizer: WorldStateSynchronizer; + let worldState: WorldStateSynchronizer; let proofVerifier: ClientProtocolCircuitVerifier; - let bootNodePort: number; const logger = createDebugLogger('p2p-client-integration-test'); - const getPorts = async (numberOfPeers: number) => { - const ports = []; - for (let i = 0; i < numberOfPeers; i++) { - const port = (await getRandomPort()) || bootNodePort + i + 1; - ports.push(port); - } - return ports; - }; + beforeEach(() => { + ({ txPool, attestationPool, epochProofQuotePool } = makeMockPools()); + }); + + const getPorts = (numberOfPeers: number) => Promise.all(Array.from({ length: numberOfPeers }, () => getPort())); const createClients = async (numberOfPeers: number, alwaysTrueVerifier: boolean = true): Promise => { const clients: P2PClient[] = []; @@ -77,11 +100,14 @@ describe('Req Resp p2p client integration', () => { const enr = SignableENR.createFromPeerId(peerId); const udpAnnounceAddress = `127.0.0.1:${ports[i]}`; - const publicAddr = multiaddr(convertToMultiaddr(udpAnnounceAddress, 'udp')); + const tcpAnnounceAddress = `127.0.0.1:${ports[i]}`; + const udpPublicAddr = multiaddr(convertToMultiaddr(udpAnnounceAddress, 'udp')); + const tcpPublicAddr = multiaddr(convertToMultiaddr(tcpAnnounceAddress, 'tcp')); // ENRS must include the network and a discoverable address (udp for discv5) enr.set(AZTEC_ENR_KEY, Uint8Array.from([AZTEC_NET])); - enr.setLocationMultiaddr(publicAddr); + enr.setLocationMultiaddr(udpPublicAddr); + enr.setLocationMultiaddr(tcpPublicAddr); return enr.encodeTxt(); }), @@ -103,47 +129,14 @@ describe('Req Resp p2p client integration', () => { udpListenAddress: listenAddr, tcpAnnounceAddress: addr, udpAnnounceAddress: addr, - l2QueueSize: 1, bootstrapNodes: [...otherNodes], - blockCheckIntervalMS: 1000, peerCheckIntervalMS: 1000, - transactionProtocol: '', minPeerCount: 1, maxPeerCount: 10, - keepProvenTxsInPoolFor: 0, - queryForIp: false, - l1ChainId: 31337, - dataDirectory: undefined, - l1Contracts: { rollupAddress: EthAddress.ZERO }, - }; - - txPool = { - addTxs: jest.fn(() => {}), - getTxByHash: jest.fn().mockReturnValue(undefined), - deleteTxs: jest.fn(), - getAllTxs: jest.fn().mockReturnValue([]), - getAllTxHashes: jest.fn().mockReturnValue([]), - getMinedTxHashes: jest.fn().mockReturnValue([]), - getPendingTxHashes: jest.fn().mockReturnValue([]), - getTxStatus: jest.fn().mockReturnValue(undefined), - markAsMined: jest.fn(), - }; - - attestationPool = { - addAttestations: jest.fn(), - deleteAttestations: jest.fn(), - deleteAttestationsForSlot: jest.fn(), - getAttestationsForSlot: jest.fn().mockReturnValue(undefined), - }; - - epochProofQuotePool = { - addQuote: jest.fn(), - getQuotes: jest.fn().mockReturnValue([]), - deleteQuotesToEpoch: jest.fn(), - }; + } as P2PConfig & DataStoreConfig; - blockSource = new MockL2BlockSource(); - blockSource.createBlocks(100); + l2BlockSource = new MockL2BlockSource(); + l2BlockSource.createBlocks(100); proofVerifier = alwaysTrueVerifier ? new AlwaysTrueCircuitVerifier() : new AlwaysFalseCircuitVerifier(); kvStore = openTmpStore(); @@ -153,7 +146,7 @@ describe('Req Resp p2p client integration', () => { epochProofQuotePool: epochProofQuotePool as unknown as EpochProofQuotePool, store: kvStore, }; - const client = await createP2PClient(config, blockSource, proofVerifier, worldStateSynchronizer, undefined, deps); + const client = await createP2PClient(config, l2BlockSource, proofVerifier, worldState, undefined, deps); await client.start(); clients.push(client); @@ -173,8 +166,7 @@ describe('Req Resp p2p client integration', () => { await sleep(1000); }; - // TODO: re-enable all in file with https://github.com/AztecProtocol/aztec-packages/issues/8707 is fixed - it.skip( + it( 'Returns undefined if unable to find a transaction from another peer', async () => { // We want to create a set of nodes and request transaction from them @@ -197,7 +189,7 @@ describe('Req Resp p2p client integration', () => { TEST_TIMEOUT, ); - it.skip( + it( 'Can request a transaction from another peer', async () => { // We want to create a set of nodes and request transaction from them @@ -223,7 +215,7 @@ describe('Req Resp p2p client integration', () => { TEST_TIMEOUT, ); - it.skip( + it( 'Will penalize peers that send invalid proofs', async () => { // We want to create a set of nodes and request transaction from them @@ -255,7 +247,7 @@ describe('Req Resp p2p client integration', () => { TEST_TIMEOUT, ); - it.skip( + it( 'Will penalize peers that send the wrong transaction', async () => { // We want to create a set of nodes and request transaction from them diff --git a/yarn-project/p2p/src/util.ts b/yarn-project/p2p/src/util.ts index 0c2008d6d3b..0550ad5ec92 100644 --- a/yarn-project/p2p/src/util.ts +++ b/yarn-project/p2p/src/util.ts @@ -1,7 +1,11 @@ +import { type DataStoreConfig } from '@aztec/kv-store/utils'; + import type { GossipSub } from '@chainsafe/libp2p-gossipsub'; import { resolve } from 'dns/promises'; import type { Libp2p } from 'libp2p'; +import { type P2PConfig } from './config.js'; + export interface PubSubLibp2p extends Libp2p { services: { pubsub: GossipSub; @@ -88,3 +92,52 @@ function addressToMultiAddressType(address: string): 'ip4' | 'ip6' | 'dns' { return 'dns'; } } + +export async function configureP2PClientAddresses( + _config: P2PConfig & DataStoreConfig, +): Promise { + const config = { ..._config }; + const { + tcpAnnounceAddress: configTcpAnnounceAddress, + udpAnnounceAddress: configUdpAnnounceAddress, + queryForIp, + } = config; + + config.tcpAnnounceAddress = configTcpAnnounceAddress + ? await resolveAddressIfNecessary(configTcpAnnounceAddress) + : undefined; + config.udpAnnounceAddress = configUdpAnnounceAddress + ? await resolveAddressIfNecessary(configUdpAnnounceAddress) + : undefined; + + // create variable for re-use if needed + let publicIp; + + // check if no announce IP was provided + const splitTcpAnnounceAddress = splitAddressPort(configTcpAnnounceAddress || '', true); + if (splitTcpAnnounceAddress.length == 2 && splitTcpAnnounceAddress[0] === '') { + if (queryForIp) { + publicIp = await getPublicIp(); + const tcpAnnounceAddress = `${publicIp}:${splitTcpAnnounceAddress[1]}`; + config.tcpAnnounceAddress = tcpAnnounceAddress; + } else { + throw new Error( + `Invalid announceTcpAddress provided: ${configTcpAnnounceAddress}. Expected format: :`, + ); + } + } + + const splitUdpAnnounceAddress = splitAddressPort(configUdpAnnounceAddress || '', true); + if (splitUdpAnnounceAddress.length == 2 && splitUdpAnnounceAddress[0] === '') { + // If announceUdpAddress is not provided, use announceTcpAddress + if (!queryForIp && config.tcpAnnounceAddress) { + config.udpAnnounceAddress = config.tcpAnnounceAddress; + } else if (queryForIp) { + const udpPublicIp = publicIp || (await getPublicIp()); + const udpAnnounceAddress = `${udpPublicIp}:${splitUdpAnnounceAddress[1]}`; + config.udpAnnounceAddress = udpAnnounceAddress; + } + } + + return config; +} diff --git a/yarn-project/prover-node/package.json b/yarn-project/prover-node/package.json index b7130d53215..27c11339930 100644 --- a/yarn-project/prover-node/package.json +++ b/yarn-project/prover-node/package.json @@ -53,12 +53,14 @@ }, "dependencies": { "@aztec/archiver": "workspace:^", + "@aztec/bb-prover": "workspace:^", "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", "@aztec/ethereum": "workspace:^", "@aztec/foundation": "workspace:^", "@aztec/kv-store": "workspace:^", "@aztec/l1-artifacts": "workspace:^", + "@aztec/p2p": "workspace:^", "@aztec/prover-client": "workspace:^", "@aztec/sequencer-client": "workspace:^", "@aztec/simulator": "workspace:^", diff --git a/yarn-project/prover-node/src/config.ts b/yarn-project/prover-node/src/config.ts index 8049b0d494e..ce0f8676065 100644 --- a/yarn-project/prover-node/src/config.ts +++ b/yarn-project/prover-node/src/config.ts @@ -5,6 +5,7 @@ import { getConfigFromMappings, numberConfigHelper, } from '@aztec/foundation/config'; +import { type P2PConfig, getP2PConfigFromEnv, p2pConfigMappings } from '@aztec/p2p'; import { type ProverClientConfig, getProverEnvVars, proverClientConfigMappings } from '@aztec/prover-client'; import { type PublisherConfig, @@ -25,6 +26,7 @@ import { export type ProverNodeConfig = ArchiverConfig & ProverClientConfig & + P2PConfig & WorldStateConfig & PublisherConfig & TxSenderConfig & @@ -77,6 +79,7 @@ const quoteProviderConfigMappings: ConfigMappingsType = { export const proverNodeConfigMappings: ConfigMappingsType = { ...archiverConfigMappings, ...proverClientConfigMappings, + ...p2pConfigMappings, ...worldStateConfigMappings, ...getPublisherConfigMappings('PROVER'), ...getTxSenderConfigMappings('PROVER'), @@ -90,6 +93,7 @@ export function getProverNodeConfigFromEnv(): ProverNodeConfig { return { ...getArchiverConfigFromEnv(), ...getProverEnvVars(), + ...getP2PConfigFromEnv(), ...getWorldStateConfigFromEnv(), ...getPublisherConfigFromEnv('PROVER'), ...getTxSenderConfigFromEnv('PROVER'), diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 6d376676761..3d9cec10579 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -1,5 +1,5 @@ import { type Archiver, createArchiver } from '@aztec/archiver'; -import { type AztecNode } from '@aztec/circuit-types'; +import { type ProverCoordination } from '@aztec/circuit-types'; import { createEthereumChain } from '@aztec/ethereum'; import { Buffer32 } from '@aztec/foundation/buffer'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; @@ -29,7 +29,7 @@ export async function createProverNode( deps: { telemetry?: TelemetryClient; log?: DebugLogger; - aztecNodeTxProvider?: AztecNode; + aztecNodeTxProvider?: ProverCoordination; archiver?: Archiver; } = {}, ) { @@ -49,7 +49,15 @@ export async function createProverNode( // REFACTOR: Move publisher out of sequencer package and into an L1-related package const publisher = new L1Publisher(config, telemetry); - const txProvider = deps.aztecNodeTxProvider ? deps.aztecNodeTxProvider : createProverCoordination(config); + // If config.p2pEnabled is true, createProverCoordination will create a p2p client where quotes will be shared and tx's requested + // If config.p2pEnabled is false, createProverCoordination request information from the AztecNode + const proverCoordination = await createProverCoordination(config, { + aztecNodeTxProvider: deps.aztecNodeTxProvider, + worldStateSynchronizer, + archiver, + telemetry, + }); + const quoteProvider = createQuoteProvider(config); const quoteSigner = createQuoteSigner(config); @@ -72,7 +80,7 @@ export async function createProverNode( archiver, archiver, worldStateSynchronizer, - txProvider, + proverCoordination, simulationProvider, quoteProvider, quoteSigner, diff --git a/yarn-project/prover-node/src/prover-coordination/factory.ts b/yarn-project/prover-node/src/prover-coordination/factory.ts index e48720a329c..1c44d2673dc 100644 --- a/yarn-project/prover-node/src/prover-coordination/factory.ts +++ b/yarn-project/prover-node/src/prover-coordination/factory.ts @@ -1,9 +1,59 @@ -import { type ProverCoordination, createAztecNodeClient } from '@aztec/circuit-types'; +import { type ArchiveSource, type Archiver } from '@aztec/archiver'; +import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover'; +import { type ProverCoordination, type WorldStateSynchronizer, createAztecNodeClient } from '@aztec/circuit-types'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { createP2PClient } from '@aztec/p2p'; +import { type TelemetryClient } from '@aztec/telemetry-client'; -import { type ProverCoordinationConfig } from './config.js'; +import { type ProverNodeConfig } from '../config.js'; + +// We return a reference to the P2P client so that the prover node can stop the service when it shuts down. +type ProverCoordinationDeps = { + aztecNodeTxProvider?: ProverCoordination; + worldStateSynchronizer?: WorldStateSynchronizer; + archiver?: Archiver | ArchiveSource; + telemetry?: TelemetryClient; +}; + +/** + * Creates a prover coordination service. + * If p2p is enabled, prover coordination is done via p2p. + * If an Aztec node URL is provided, prover coordination is done via the Aztec node over http. + * If an aztec node is provided, it is returned directly. + */ +export async function createProverCoordination( + config: ProverNodeConfig, + deps: ProverCoordinationDeps, +): Promise { + const log = createDebugLogger('aztec:createProverCoordination'); + + if (deps.aztecNodeTxProvider) { + log.info('Using prover coordination via aztec node'); + return deps.aztecNodeTxProvider; + } + + if (config.p2pEnabled) { + log.info('Using prover coordination via p2p'); + + if (!deps.archiver || !deps.worldStateSynchronizer || !deps.telemetry) { + throw new Error('Missing dependencies for p2p prover coordination'); + } + + const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier(); + const p2pClient = await createP2PClient( + config, + deps.archiver, + proofVerifier, + deps.worldStateSynchronizer, + deps.telemetry, + ); + await p2pClient.start(); + + return p2pClient; + } -export function createProverCoordination(config: ProverCoordinationConfig): ProverCoordination { if (config.proverCoordinationNodeUrl) { + log.info('Using prover coordination via node url'); return createAztecNodeClient(config.proverCoordinationNodeUrl); } else { throw new Error(`Aztec Node URL for Tx Provider is not set.`); diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index cafedf3d077..bca781310f4 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -15,10 +15,20 @@ import { type ContractDataSource, EthAddress } from '@aztec/circuits.js'; import { times } from '@aztec/foundation/collection'; import { Signature } from '@aztec/foundation/eth-signature'; import { sleep } from '@aztec/foundation/sleep'; +import { openTmpStore } from '@aztec/kv-store/utils'; +import { + type BootstrapNode, + InMemoryAttestationPool, + InMemoryTxPool, + MemoryEpochProofQuotePool, + P2PClient, +} from '@aztec/p2p'; +import { createBootstrapNode, createTestLibP2PService } from '@aztec/p2p/mocks'; import { type L1Publisher } from '@aztec/sequencer-client'; import { type PublicProcessorFactory, type SimulationProvider } from '@aztec/simulator'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; +import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; import { type BondManager } from './bond/bond-manager.js'; @@ -37,7 +47,7 @@ describe('prover-node', () => { let l1ToL2MessageSource: MockProxy; let contractDataSource: MockProxy; let worldState: MockProxy; - let coordination: MockProxy; + let coordination: MockProxy | ProverCoordination; let simulator: MockProxy; let quoteProvider: MockProxy; let quoteSigner: MockProxy; @@ -277,6 +287,93 @@ describe('prover-node', () => { }); }); + // Things to test + // - Another aztec node receives the proof quote via p2p + // - The prover node can get the it is missing via p2p, or it has them in it's mempool + describe('Using a p2p coordination', () => { + let bootnode: BootstrapNode; + let p2pClient: P2PClient; + let otherP2PClient: P2PClient; + + const createP2PClient = async (bootnodeAddr: string, port: number) => { + const mempools = { + txPool: new InMemoryTxPool(telemetryClient), + attestationPool: new InMemoryAttestationPool(telemetryClient), + epochProofQuotePool: new MemoryEpochProofQuotePool(telemetryClient), + }; + const libp2pService = await createTestLibP2PService( + [bootnodeAddr], + l2BlockSource, + worldState, + mempools, + telemetryClient, + port, + ); + const kvStore = openTmpStore(); + return new P2PClient(kvStore, l2BlockSource, mempools, libp2pService, 0, telemetryClient); + }; + + beforeEach(async () => { + bootnode = await createBootstrapNode(40400); + await sleep(1000); + + const bootnodeAddr = bootnode.getENR().encodeTxt(); + p2pClient = await createP2PClient(bootnodeAddr, 8080); + otherP2PClient = await createP2PClient(bootnodeAddr, 8081); + + // Set the p2p client to be the coordination method + coordination = p2pClient; + + await Promise.all([p2pClient.start(), otherP2PClient.start()]); + + // Sleep to enable peer discovery + await sleep(3000); + }, 10000); + + afterEach(async () => { + await bootnode.stop(); + await p2pClient?.stop(); + await otherP2PClient.stop(); + }); + + describe('with mocked monitors', () => { + let claimsMonitor: MockProxy; + let epochMonitor: MockProxy; + + beforeEach(() => { + claimsMonitor = mock(); + epochMonitor = mock(); + + proverNode = createProverNode(claimsMonitor, epochMonitor); + }); + + afterEach(async () => { + await proverNode.stop(); + }); + + it('Should send a proof quote via p2p to another node', async () => { + // Check that the p2p client receives the quote (casted as any to access private property) + const p2pEpochReceivedSpy = jest.spyOn((otherP2PClient as any).p2pService, 'processEpochProofQuoteFromPeer'); + + // Check the other node's pool has no quotes yet + const peerInitialState = await otherP2PClient.getEpochProofQuotes(10n); + expect(peerInitialState.length).toEqual(0); + + await proverNode.handleEpochCompleted(10n); + + // Wait for message to be propagated + await sleep(1000); + + // Check the other node received a quote via p2p + expect(p2pEpochReceivedSpy).toHaveBeenCalledTimes(1); + + // We should be able to retreive the quote from the other node + const peerFinalStateQuotes = await otherP2PClient.getEpochProofQuotes(10n); + expect(peerFinalStateQuotes[0]).toEqual(toExpectedQuote(10n)); + }); + }); + }); + class TestProverNode extends ProverNode { protected override doCreateEpochProvingJob( epochNumber: bigint, diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 33d669e5e3d..3582d357f28 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -169,6 +169,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { this.publisher.interrupt(); await Promise.all(Array.from(this.jobs.values()).map(job => job.stop())); await this.worldState.stop(); + await this.coordination.stop(); this.log.info('Stopped ProverNode'); } diff --git a/yarn-project/prover-node/src/quote-provider/http.ts b/yarn-project/prover-node/src/quote-provider/http.ts index 210f397d374..2e3a6ce6798 100644 --- a/yarn-project/prover-node/src/quote-provider/http.ts +++ b/yarn-project/prover-node/src/quote-provider/http.ts @@ -27,7 +27,7 @@ export class HttpQuoteProvider implements QuoteProvider { const data = await response.json(); if (!data.basisPointFee || !data.bondAmount) { - throw new Error(`Missing required fields in response: ${JSON.stringify(data)}`); + throw new Error(`Missing required fields (basisPointFee | bondAmount) in response: ${JSON.stringify(data)}`); } const basisPointFee = Number(data.basisPointFee); diff --git a/yarn-project/prover-node/tsconfig.json b/yarn-project/prover-node/tsconfig.json index b5e774e1b8a..93d30fb1eb8 100644 --- a/yarn-project/prover-node/tsconfig.json +++ b/yarn-project/prover-node/tsconfig.json @@ -9,6 +9,9 @@ { "path": "../archiver" }, + { + "path": "../bb-prover" + }, { "path": "../circuit-types" }, @@ -27,6 +30,9 @@ { "path": "../l1-artifacts" }, + { + "path": "../p2p" + }, { "path": "../prover-client" }, diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index dd7db7e86c5..567d78868ad 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -869,6 +869,7 @@ __metadata: "@multiformats/multiaddr": 12.1.14 "@types/jest": ^29.5.0 "@types/node": ^18.14.6 + get-port: ^7.1.0 interface-datastore: ^8.2.11 interface-store: ^5.1.8 it-drain: ^3.0.5 @@ -967,12 +968,14 @@ __metadata: resolution: "@aztec/prover-node@workspace:prover-node" dependencies: "@aztec/archiver": "workspace:^" + "@aztec/bb-prover": "workspace:^" "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" "@aztec/ethereum": "workspace:^" "@aztec/foundation": "workspace:^" "@aztec/kv-store": "workspace:^" "@aztec/l1-artifacts": "workspace:^" + "@aztec/p2p": "workspace:^" "@aztec/prover-client": "workspace:^" "@aztec/sequencer-client": "workspace:^" "@aztec/simulator": "workspace:^"