From aaf436f13082c43c0a05c9ea5c2376bc34304af1 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:20:35 +0000 Subject: [PATCH] fmt --- yarn-project/aztec-node/package.json | 1 + .../aztec-node/src/aztec-node/server.ts | 6 +- yarn-project/epoch-cache/src/factory.ts | 8 + yarn-project/epoch-cache/src/index.ts | 1 + yarn-project/p2p/package.json | 1 + yarn-project/p2p/src/client/index.ts | 3 + yarn-project/p2p/src/mocks/index.ts | 3 + .../p2p/src/services/libp2p/libp2p_service.ts | 159 +++++++++++++++++- .../reqresp/reqresp.integration.test.ts | 52 ++---- .../src/prover-coordination/factory.ts | 5 +- .../prover-node/src/prover-node.test.ts | 3 + yarn-project/validator-client/src/factory.ts | 14 +- yarn-project/yarn.lock | 1 + 13 files changed, 200 insertions(+), 57 deletions(-) create mode 100644 yarn-project/epoch-cache/src/factory.ts diff --git a/yarn-project/aztec-node/package.json b/yarn-project/aztec-node/package.json index f23417a5b7b..3e25d04baf2 100644 --- a/yarn-project/aztec-node/package.json +++ b/yarn-project/aztec-node/package.json @@ -69,6 +69,7 @@ "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", "@aztec/ethereum": "workspace:^", + "@aztec/epoch-cache": "workspace:^", "@aztec/foundation": "workspace:^", "@aztec/kv-store": "workspace:^", "@aztec/merkle-tree": "workspace:^", diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 60e2ce1ca22..7949e6db41d 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -54,6 +54,7 @@ import { type PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash'; +import { createEpochCache } from '@aztec/epoch-cache'; import { type L1ContractAddresses, createEthereumChain } from '@aztec/ethereum'; import { type ContractArtifact } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; @@ -167,6 +168,8 @@ export class AztecNodeService implements AztecNode, Traceable { log.warn(`Aztec node is accepting fake proofs`); } + const epochCache = await createEpochCache(config, config.l1Contracts.rollupAddress); + // create the tx pool and the p2p client, which will need the l2 block source const p2pClient = await createP2PClient( P2PClientType.Full, @@ -174,13 +177,14 @@ export class AztecNodeService implements AztecNode, Traceable { archiver, proofVerifier, worldStateSynchronizer, + epochCache, telemetry, ); // start both and wait for them to sync from the block source await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]); - const validatorClient = await createValidatorClient(config, rollupAddress, { p2pClient, telemetry, dateProvider }); + const validatorClient = createValidatorClient(config, { p2pClient, telemetry, dateProvider, epochCache }); // now create the sequencer const sequencer = config.disableValidator diff --git a/yarn-project/epoch-cache/src/factory.ts b/yarn-project/epoch-cache/src/factory.ts new file mode 100644 index 00000000000..639142989fe --- /dev/null +++ b/yarn-project/epoch-cache/src/factory.ts @@ -0,0 +1,8 @@ +import { type EthAddress } from '@aztec/foundation/eth-address'; + +import { type EpochCacheConfig } from './config.js'; +import { EpochCache } from './epoch_cache.js'; + +export function createEpochCache(config: EpochCacheConfig, rollupAddress: EthAddress): Promise { + return EpochCache.create(rollupAddress, config); +} diff --git a/yarn-project/epoch-cache/src/index.ts b/yarn-project/epoch-cache/src/index.ts index f6a7dba8382..ab91c67de81 100644 --- a/yarn-project/epoch-cache/src/index.ts +++ b/yarn-project/epoch-cache/src/index.ts @@ -1,2 +1,3 @@ export * from './epoch_cache.js'; export * from './config.js'; +export * from './factory.js'; diff --git a/yarn-project/p2p/package.json b/yarn-project/p2p/package.json index 69c44f33562..15946ac8b88 100644 --- a/yarn-project/p2p/package.json +++ b/yarn-project/p2p/package.json @@ -68,6 +68,7 @@ "dependencies": { "@aztec/circuit-types": "workspace:^", "@aztec/circuits.js": "workspace:^", + "@aztec/epoch-cache": "workspace:^", "@aztec/foundation": "workspace:^", "@aztec/kv-store": "workspace:^", "@aztec/telemetry-client": "workspace:^", diff --git a/yarn-project/p2p/src/client/index.ts b/yarn-project/p2p/src/client/index.ts index c2a01ff4294..d8b74d41866 100644 --- a/yarn-project/p2p/src/client/index.ts +++ b/yarn-project/p2p/src/client/index.ts @@ -4,6 +4,7 @@ import { P2PClientType, type WorldStateSynchronizer, } from '@aztec/circuit-types'; +import { type EpochCache } from '@aztec/epoch-cache'; import { createLogger } from '@aztec/foundation/log'; import { type AztecKVStore } from '@aztec/kv-store'; import { type DataStoreConfig } from '@aztec/kv-store/config'; @@ -39,6 +40,7 @@ export const createP2PClient = async ( l2BlockSource: L2BlockSource, proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, + epochCache: EpochCache, telemetry: TelemetryClient = new NoopTelemetryClient(), deps: P2PClientDeps = {}, ) => { @@ -75,6 +77,7 @@ export const createP2PClient = async ( peerId, mempools, l2BlockSource, + epochCache, proofVerifier, worldStateSynchronizer, store, diff --git a/yarn-project/p2p/src/mocks/index.ts b/yarn-project/p2p/src/mocks/index.ts index 56a98c4462d..ff5996d0908 100644 --- a/yarn-project/p2p/src/mocks/index.ts +++ b/yarn-project/p2p/src/mocks/index.ts @@ -5,6 +5,7 @@ import { type Tx, type WorldStateSynchronizer, } from '@aztec/circuit-types'; +import { type EpochCache } from '@aztec/epoch-cache'; import { type DataStoreConfig } from '@aztec/kv-store/config'; import { openTmpStore } from '@aztec/kv-store/lmdb'; import { type TelemetryClient } from '@aztec/telemetry-client'; @@ -101,6 +102,7 @@ export async function createTestLibP2PService( boostrapAddrs: string[] = [], l2BlockSource: L2BlockSource, worldStateSynchronizer: WorldStateSynchronizer, + epochCache: EpochCache, mempools: MemPools, telemetry: TelemetryClient, port: number = 0, @@ -132,6 +134,7 @@ export async function createTestLibP2PService( discoveryService, mempools, l2BlockSource, + epochCache, proofVerifier, worldStateSynchronizer, telemetry, diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index bfbf5b7df20..bc355221166 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -17,6 +17,7 @@ import { } from '@aztec/circuit-types'; import { P2PClientType } from '@aztec/circuit-types'; import { Fr } from '@aztec/circuits.js'; +import { type EpochCache } from '@aztec/epoch-cache'; import { createLogger } from '@aztec/foundation/log'; import { SerialQueue } from '@aztec/foundation/queue'; import { RunningPromise } from '@aztec/foundation/running-promise'; @@ -102,6 +103,7 @@ export class LibP2PService extends WithTracer implement private peerDiscoveryService: PeerDiscoveryService, private mempools: MemPools, private l2BlockSource: L2BlockSource, + private epochCache: EpochCache, private proofVerifier: ClientProtocolCircuitVerifier, private worldStateSynchronizer: WorldStateSynchronizer, private telemetry: TelemetryClient, @@ -153,7 +155,17 @@ export class LibP2PService extends WithTracer implement } // Add p2p topic validators - this.node.services.pubsub.topicValidators.set(Tx.p2pTopic, this.validatePropagatedTxFromMessage.bind(this)); + // As they are stored within a kv pair, there is no need to register them conditionally + // based on the client type + const topicValidators = { + [Tx.p2pTopic]: this.validatePropagatedTxFromMessage.bind(this), + [BlockAttestation.p2pTopic]: this.validatePropagatedAttestationFromMessage.bind(this), + [BlockProposal.p2pTopic]: this.validatePropagatedBlockFromMessage.bind(this), + [EpochProofQuote.p2pTopic]: this.validatePropagatedEpochProofQuoteFromMessage.bind(this), + }; + for (const [topic, validator] of Object.entries(topicValidators)) { + this.node.services.pubsub.topicValidators.set(topic, validator); + } // add GossipSub listener this.node.services.pubsub.addEventListener('gossipsub:message', async e => { @@ -215,6 +227,7 @@ export class LibP2PService extends WithTracer implement peerId: PeerId, mempools: MemPools, l2BlockSource: L2BlockSource, + epochCache: EpochCache, proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, store: AztecKVStore, @@ -329,6 +342,7 @@ export class LibP2PService extends WithTracer implement peerDiscoveryService, mempools, l2BlockSource, + epochCache, proofVerifier, worldStateSynchronizer, telemetry, @@ -537,6 +551,12 @@ export class LibP2PService extends WithTracer implement return true; } + /** + * Validate a tx from a peer. + * @param propagationSource - The peer ID of the peer that sent the tx. + * @param msg - The tx message. + * @returns True if the tx is valid, false otherwise. + */ private async validatePropagatedTxFromMessage( propagationSource: PeerId, msg: Message, @@ -551,11 +571,59 @@ export class LibP2PService extends WithTracer implement } /** - * Validate a tx that has been propagated from a peer. - * @param tx - The tx to validate. - * @param peerId - The peer ID of the peer that sent the tx. - * @returns True if the tx is valid, false otherwise. + * Validate an attestation from a peer. + * @param propagationSource - The peer ID of the peer that sent the attestation. + * @param msg - The attestation message. + * @returns True if the attestation is valid, false otherwise. + */ + private async validatePropagatedAttestationFromMessage( + propagationSource: PeerId, + msg: Message, + ): Promise { + const attestation = BlockAttestation.fromBuffer(Buffer.from(msg.data)); + const isValid = await this.validateAttestation(propagationSource, attestation); + this.logger.trace(`validatePropagatedAttestation: ${isValid}`, { + [Attributes.SLOT_NUMBER]: attestation.payload.header.globalVariables.slotNumber.toString(), + [Attributes.P2P_ID]: propagationSource.toString(), + }); + return isValid ? TopicValidatorResult.Accept : TopicValidatorResult.Reject; + } + + /** + * Validate a block proposal from a peer. + * @param propagationSource - The peer ID of the peer that sent the block. + * @param msg - The block proposal message. + * @returns True if the block proposal is valid, false otherwise. */ + private async validatePropagatedBlockFromMessage( + propagationSource: PeerId, + msg: Message, + ): Promise { + const block = BlockProposal.fromBuffer(Buffer.from(msg.data)); + const isValid = await this.validateBlockProposal(propagationSource, block); + this.logger.trace(`validatePropagatedBlock: ${isValid}`, { + [Attributes.SLOT_NUMBER]: block.payload.header.globalVariables.slotNumber.toString(), + [Attributes.P2P_ID]: propagationSource.toString(), + }); + return isValid ? TopicValidatorResult.Accept : TopicValidatorResult.Reject; + } + + /** + * Validate an epoch proof quote from a peer. + * @param propagationSource - The peer ID of the peer that sent the epoch proof quote. + * @param msg - The epoch proof quote message. + * @returns True if the epoch proof quote is valid, false otherwise. + */ + private validatePropagatedEpochProofQuoteFromMessage(propagationSource: PeerId, msg: Message): TopicValidatorResult { + const epochProofQuote = EpochProofQuote.fromBuffer(Buffer.from(msg.data)); + const isValid = this.validateEpochProofQuote(propagationSource, epochProofQuote); + this.logger.trace(`validatePropagatedEpochProofQuote: ${isValid}`, { + [Attributes.EPOCH_NUMBER]: epochProofQuote.payload.epochToProve.toString(), + [Attributes.P2P_ID]: propagationSource.toString(), + }); + return isValid ? TopicValidatorResult.Accept : TopicValidatorResult.Reject; + } + @trackSpan('Libp2pService.validatePropagatedTx', tx => ({ [Attributes.TX_HASH]: tx.getTxHash().toString(), })) @@ -687,6 +755,87 @@ export class LibP2PService extends WithTracer implement return true; } + /** + * Validate an attestation. + * + * @param attestation - The attestation to validate. + * @returns True if the attestation is valid, false otherwise. + */ + @trackSpan('Libp2pService.validateAttestation', (_peerId, attestation) => ({ + [Attributes.SLOT_NUMBER]: attestation.payload.header.globalVariables.slotNumber.toString(), + })) + public async validateAttestation(peerId: PeerId, attestation: BlockAttestation): Promise { + const { currentSlot, nextSlot } = await this.epochCache.getProposerInCurrentOrNextSlot(); + + // Check that the attestation is for the current or next slot + const slotNumberBigInt = attestation.payload.header.globalVariables.slotNumber.toBigInt(); + if (slotNumberBigInt !== currentSlot && slotNumberBigInt !== nextSlot) { + this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError); + return false; + } + + // Check that the attestation is from the somebody in the committee + const attester = attestation.getSender(); + if (!(await this.epochCache.isInCommittee(attester))) { + this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError); + return false; + } + + return true; + } + + /** + * Validate a block proposal. + * + * @param block - The block proposal to validate. + * @returns True if the block proposal is valid, false otherwise. + */ + @trackSpan('Libp2pService.validateBlockProposal', (_peerId, block) => ({ + [Attributes.SLOT_NUMBER]: block.payload.header.globalVariables.slotNumber.toString(), + })) + public async validateBlockProposal(peerId: PeerId, block: BlockProposal): Promise { + const { currentProposer, nextProposer, currentSlot, nextSlot } = + await this.epochCache.getProposerInCurrentOrNextSlot(); + + // Check that the attestation is for the current or next slot + const slotNumberBigInt = block.payload.header.globalVariables.slotNumber.toBigInt(); + if (slotNumberBigInt !== currentSlot && slotNumberBigInt !== nextSlot) { + this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError); + return false; + } + + // Check that the block proposal is from the current or next proposer + const proposer = block.getSender(); + if (!proposer.equals(currentProposer) && !proposer.equals(nextProposer)) { + this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError); + return false; + } + + return true; + } + + /** + * Validate an epoch proof quote. + * + * @param epochProofQuote - The epoch proof quote to validate. + * @returns True if the epoch proof quote is valid, false otherwise. + */ + @trackSpan('Libp2pService.validateEpochProofQuote', (_peerId, epochProofQuote) => ({ + [Attributes.EPOCH_NUMBER]: epochProofQuote.payload.epochToProve.toString(), + })) + public validateEpochProofQuote(peerId: PeerId, epochProofQuote: EpochProofQuote): boolean { + const { epoch } = this.epochCache.getEpochAndSlotNow(); + + // Check that the epoch proof quote is for the current epoch + const epochToProve = epochProofQuote.payload.epochToProve; + if (epochToProve !== epoch && epochToProve !== epoch - 1n) { + this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError); + return false; + } + + return true; + } + public getPeerScore(peerId: PeerId): number { return this.node.services.pubsub.score.score(peerId.toString()); } diff --git a/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts b/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts index 6b11f5b01c6..9cfeb77d092 100644 --- a/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts +++ b/yarn-project/p2p/src/services/reqresp/reqresp.integration.test.ts @@ -6,6 +6,7 @@ import { type WorldStateSynchronizer, mockTx, } from '@aztec/circuit-types'; +import { type EpochCache } from '@aztec/epoch-cache'; import { createLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; import { type AztecKVStore } from '@aztec/kv-store'; @@ -16,6 +17,7 @@ import { SignableENR } from '@chainsafe/enr'; import { describe, expect, it, jest } from '@jest/globals'; import { multiaddr } from '@multiformats/multiaddr'; import getPort from 'get-port'; +import { type MockProxy, mock } from 'jest-mock-extended'; import { generatePrivateKey } from 'viem/accounts'; import { createP2PClient } from '../../client/index.js'; @@ -29,13 +31,6 @@ import { convertToMultiaddr, createLibP2PPeerIdFromPrivateKey } from '../../util import { AZTEC_ENR_KEY, AZTEC_NET } from '../discv5/discV5_service.js'; import { PeerErrorSeverity } from '../peer-scoring/peer_scoring.js'; -/** - * Mockify helper for testing purposes. - */ -type Mockify = { - [P in keyof T]: ReturnType; -}; - const TEST_TIMEOUT = 80000; function generatePeerIdPrivateKeys(numberOfPeers: number): string[] { @@ -49,40 +44,11 @@ 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(), - markMinedAsPending: jest.fn(), - }, - attestationPool: { - addAttestations: jest.fn(), - deleteAttestations: jest.fn(), - deleteAttestationsForSlot: jest.fn(), - deleteAttestationsOlderThan: 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 txPool: MockProxy; + let attestationPool: MockProxy; + let epochProofQuotePool: MockProxy; + let epochCache: MockProxy; let l2BlockSource: MockL2BlockSource; let kvStore: AztecKVStore; let worldState: WorldStateSynchronizer; @@ -90,7 +56,10 @@ describe('Req Resp p2p client integration', () => { const logger = createLogger('p2p:test:client-integration'); beforeEach(() => { - ({ txPool, attestationPool, epochProofQuotePool } = makeMockPools()); + txPool = mock(); + attestationPool = mock(); + epochProofQuotePool = mock(); + epochCache = mock(); }); const getPorts = (numberOfPeers: number) => Promise.all(Array.from({ length: numberOfPeers }, () => getPort())); @@ -159,6 +128,7 @@ describe('Req Resp p2p client integration', () => { l2BlockSource, proofVerifier, worldState, + epochCache, undefined, deps, ); diff --git a/yarn-project/prover-node/src/prover-coordination/factory.ts b/yarn-project/prover-node/src/prover-coordination/factory.ts index 48194d44c47..88731deec2a 100644 --- a/yarn-project/prover-node/src/prover-coordination/factory.ts +++ b/yarn-project/prover-node/src/prover-coordination/factory.ts @@ -6,6 +6,7 @@ import { type WorldStateSynchronizer, createAztecNodeClient, } from '@aztec/circuit-types'; +import { type EpochCache } from '@aztec/epoch-cache'; import { createLogger } from '@aztec/foundation/log'; import { type DataStoreConfig } from '@aztec/kv-store/config'; import { createP2PClient } from '@aztec/p2p'; @@ -19,6 +20,7 @@ type ProverCoordinationDeps = { worldStateSynchronizer?: WorldStateSynchronizer; archiver?: Archiver | ArchiveSource; telemetry?: TelemetryClient; + epochCache?: EpochCache; }; /** @@ -41,7 +43,7 @@ export async function createProverCoordination( if (config.p2pEnabled) { log.info('Using prover coordination via p2p'); - if (!deps.archiver || !deps.worldStateSynchronizer || !deps.telemetry) { + if (!deps.archiver || !deps.worldStateSynchronizer || !deps.telemetry || !deps.epochCache) { throw new Error('Missing dependencies for p2p prover coordination'); } @@ -52,6 +54,7 @@ export async function createProverCoordination( deps.archiver, proofVerifier, deps.worldStateSynchronizer, + deps.epochCache, deps.telemetry, ); await p2pClient.start(); diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index 931396a2676..0aebc0a2f89 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -13,6 +13,7 @@ import { type WorldStateSynchronizer, } from '@aztec/circuit-types'; import { type ContractDataSource, EthAddress, Fr } from '@aztec/circuits.js'; +import { type EpochCache } from '@aztec/epoch-cache'; import { times } from '@aztec/foundation/collection'; import { Signature } from '@aztec/foundation/eth-signature'; import { makeBackoff, retry } from '@aztec/foundation/retry'; @@ -299,11 +300,13 @@ describe('prover-node', () => { txPool: new InMemoryTxPool(telemetryClient), epochProofQuotePool: new MemoryEpochProofQuotePool(telemetryClient), }; + const epochCache = mock(); const libp2pService = await createTestLibP2PService( P2PClientType.Prover, [bootnodeAddr], l2BlockSource, worldState, + epochCache, mempools, telemetryClient, port, diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index 3382163f408..3e4bf64acf7 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -1,5 +1,4 @@ -import { EpochCache, type EpochCacheConfig } from '@aztec/epoch-cache'; -import { type EthAddress } from '@aztec/foundation/eth-address'; +import { type EpochCache } from '@aztec/epoch-cache'; import { type DateProvider } from '@aztec/foundation/timer'; import { type P2P } from '@aztec/p2p'; import { type TelemetryClient } from '@aztec/telemetry-client'; @@ -9,13 +8,13 @@ import { generatePrivateKey } from 'viem/accounts'; import { type ValidatorClientConfig } from './config.js'; import { ValidatorClient } from './validator.js'; -export async function createValidatorClient( - config: ValidatorClientConfig & EpochCacheConfig, - rollupAddress: EthAddress, +export function createValidatorClient( + config: ValidatorClientConfig, deps: { p2pClient: P2P; telemetry: TelemetryClient; dateProvider: DateProvider; + epochCache: EpochCache; }, ) { if (config.disableValidator) { @@ -25,8 +24,5 @@ export async function createValidatorClient( config.validatorPrivateKey = generatePrivateKey(); } - // Create the epoch cache - const epochCache = await EpochCache.create(rollupAddress, config, deps); - - return ValidatorClient.new(config, epochCache, deps.p2pClient, deps.telemetry); + return ValidatorClient.new(config, deps.epochCache, deps.p2pClient, deps.telemetry); } diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 3bc9084e506..fe4e993bb65 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -928,6 +928,7 @@ __metadata: "@aztec/archiver": "workspace:^" "@aztec/circuit-types": "workspace:^" "@aztec/circuits.js": "workspace:^" + "@aztec/epoch-cache": "workspace:^" "@aztec/foundation": "workspace:^" "@aztec/kv-store": "workspace:^" "@aztec/telemetry-client": "workspace:^"