@aztec/p2p 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dest/client/index.d.ts.map +1 -1
  2. package/dest/client/index.js +5 -7
  3. package/dest/client/p2p_client.d.ts.map +1 -1
  4. package/dest/client/p2p_client.js +2 -3
  5. package/dest/service/discV5_service.d.ts +5 -2
  6. package/dest/service/discV5_service.d.ts.map +1 -1
  7. package/dest/service/discV5_service.js +26 -9
  8. package/dest/service/dummy_service.d.ts +4 -1
  9. package/dest/service/dummy_service.d.ts.map +1 -1
  10. package/dest/service/dummy_service.js +7 -1
  11. package/dest/service/libp2p_service.d.ts +6 -14
  12. package/dest/service/libp2p_service.d.ts.map +1 -1
  13. package/dest/service/libp2p_service.js +49 -91
  14. package/dest/service/peer_manager.d.ts +25 -3
  15. package/dest/service/peer_manager.d.ts.map +1 -1
  16. package/dest/service/peer_manager.js +152 -12
  17. package/dest/service/service.d.ts +12 -6
  18. package/dest/service/service.d.ts.map +1 -1
  19. package/dest/service/service.js +1 -1
  20. package/dest/tx_pool/aztec_kv_tx_pool.d.ts +2 -1
  21. package/dest/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  22. package/dest/tx_pool/aztec_kv_tx_pool.js +11 -6
  23. package/dest/tx_pool/instrumentation.d.ts +23 -0
  24. package/dest/tx_pool/instrumentation.d.ts.map +1 -0
  25. package/dest/tx_pool/instrumentation.js +48 -0
  26. package/dest/tx_pool/memory_tx_pool.d.ts +3 -1
  27. package/dest/tx_pool/memory_tx_pool.d.ts.map +1 -1
  28. package/dest/tx_pool/memory_tx_pool.js +6 -2
  29. package/package.json +15 -6
  30. package/src/client/index.ts +4 -6
  31. package/src/client/p2p_client.ts +1 -2
  32. package/src/service/discV5_service.ts +28 -11
  33. package/src/service/dummy_service.ts +10 -1
  34. package/src/service/libp2p_service.ts +52 -101
  35. package/src/service/peer_manager.ts +187 -12
  36. package/src/service/service.ts +14 -7
  37. package/src/tx_pool/aztec_kv_tx_pool.ts +12 -3
  38. package/src/tx_pool/instrumentation.ts +58 -0
  39. package/src/tx_pool/memory_tx_pool.ts +8 -1
  40. package/dest/service/known_txs.d.ts +0 -31
  41. package/dest/service/known_txs.d.ts.map +0 -1
  42. package/dest/service/known_txs.js +0 -52
  43. package/src/service/known_txs.ts +0 -56
@@ -1,17 +1,16 @@
1
- import { type Tx, type TxHash } from '@aztec/circuit-types';
1
+ import { type Tx } from '@aztec/circuit-types';
2
2
  import { SerialQueue } from '@aztec/foundation/fifo';
3
3
  import { createDebugLogger } from '@aztec/foundation/log';
4
- import { AztecLmdbStore } from '@aztec/kv-store/lmdb';
4
+ import { RunningPromise } from '@aztec/foundation/running-promise';
5
+ import type { AztecKVStore } from '@aztec/kv-store';
5
6
 
6
- import { ENR } from '@chainsafe/enr';
7
7
  import { type GossipsubEvents, gossipsub } from '@chainsafe/libp2p-gossipsub';
8
8
  import { noise } from '@chainsafe/libp2p-noise';
9
9
  import { yamux } from '@chainsafe/libp2p-yamux';
10
10
  import { identify } from '@libp2p/identify';
11
- import type { PeerId, PubSub, Stream } from '@libp2p/interface';
11
+ import type { PeerId, PubSub } from '@libp2p/interface';
12
12
  import '@libp2p/kad-dht';
13
13
  import { mplex } from '@libp2p/mplex';
14
- import { peerIdFromString } from '@libp2p/peer-id';
15
14
  import { createFromJSON, createSecp256k1PeerId } from '@libp2p/peer-id-factory';
16
15
  import { tcp } from '@libp2p/tcp';
17
16
  import { type Libp2p, createLibp2p } from 'libp2p';
@@ -20,7 +19,6 @@ import { type P2PConfig } from '../config.js';
20
19
  import { type TxPool } from '../tx_pool/index.js';
21
20
  import { convertToMultiaddr } from '../util.js';
22
21
  import { AztecDatastore } from './data_store.js';
23
- import { KnownTxLookup } from './known_txs.js';
24
22
  import { PeerManager } from './peer_manager.js';
25
23
  import type { P2PService, PeerDiscoveryService } from './service.js';
26
24
  import { AztecTxMessageCreator, fromTxMessage } from './tx_messages.js';
@@ -30,7 +28,6 @@ export interface PubSubLibp2p extends Libp2p {
30
28
  pubsub: PubSub<GossipsubEvents>;
31
29
  };
32
30
  }
33
-
34
31
  /**
35
32
  * Create a libp2p peer ID from the private key if provided, otherwise creates a new random ID.
36
33
  * @param privateKey - Optional peer ID private key as hex string
@@ -52,16 +49,14 @@ export async function createLibP2PPeerId(privateKey?: string): Promise<PeerId> {
52
49
  */
53
50
  export class LibP2PService implements P2PService {
54
51
  private jobQueue: SerialQueue = new SerialQueue();
55
- private knownTxLookup: KnownTxLookup = new KnownTxLookup();
56
52
  private messageCreator: AztecTxMessageCreator;
57
53
  private peerManager: PeerManager;
54
+ private discoveryRunningPromise?: RunningPromise;
58
55
  constructor(
59
56
  private config: P2PConfig,
60
57
  private node: PubSubLibp2p,
61
58
  private peerDiscoveryService: PeerDiscoveryService,
62
- private protocolId: string,
63
59
  private txPool: TxPool,
64
- private bootstrapPeerIds: PeerId[] = [],
65
60
  private logger = createDebugLogger('aztec:libp2p_service'),
66
61
  ) {
67
62
  this.messageCreator = new AztecTxMessageCreator(config.txGossipVersion);
@@ -73,54 +68,42 @@ export class LibP2PService implements P2PService {
73
68
  * @returns An empty promise.
74
69
  */
75
70
  public async start() {
71
+ // Check if service is already started
76
72
  if (this.node.status === 'started') {
77
73
  throw new Error('P2P service already started');
78
74
  }
75
+
76
+ // Log listen & announce addresses
79
77
  const { tcpListenAddress, tcpAnnounceAddress } = this.config;
80
78
  this.logger.info(`Starting P2P node on ${tcpListenAddress}`);
81
-
82
79
  if (!tcpAnnounceAddress) {
83
80
  throw new Error('Announce address not provided.');
84
81
  }
85
-
86
82
  const announceTcpMultiaddr = convertToMultiaddr(tcpAnnounceAddress, 'tcp');
87
-
88
83
  this.logger.info(`Announcing at ${announceTcpMultiaddr}`);
89
- // handle discovered peers from external discovery service
90
- this.peerDiscoveryService.on('peer:discovered', async (enr: ENR) => {
91
- await this.addPeer(enr);
92
- });
93
-
94
- this.node.addEventListener('peer:connect', async evt => {
95
- const peerId = evt.detail;
96
- await this.handleNewConnection(peerId as PeerId);
97
- });
98
-
99
- this.node.addEventListener('peer:disconnect', async evt => {
100
- const peerId = evt.detail;
101
- if (this.isBootstrapPeer(peerId)) {
102
- this.logger.info(`Disconnect from bootstrap peer ${peerId.toString()}`);
103
- } else {
104
- this.logger.info(`Disconnected from transaction peer ${peerId.toString()}`);
105
- await this.peerManager.updateDiscoveryService();
106
- }
107
- });
108
84
 
85
+ // Start job queue, peer discovery service and libp2p node
109
86
  this.jobQueue.start();
110
87
  await this.peerDiscoveryService.start();
111
88
  await this.node.start();
112
89
  this.logger.info(`Started P2P client with Peer ID ${this.node.peerId.toString()}`);
113
90
 
114
- // Subscribe to standard topics by default
91
+ // Subscribe to standard GossipSub topics by default
115
92
  this.subscribeToTopic(this.messageCreator.getTopic());
116
93
 
117
- // add gossipsub listener
94
+ // add GossipSub listener
118
95
  this.node.services.pubsub.addEventListener('gossipsub:message', async e => {
119
96
  const { msg } = e.detail;
120
97
  this.logger.debug(`Received PUBSUB message.`);
121
98
 
122
99
  await this.jobQueue.put(() => this.handleNewGossipMessage(msg.topic, msg.data));
123
100
  });
101
+
102
+ // Start running promise for peer discovery
103
+ this.discoveryRunningPromise = new RunningPromise(() => {
104
+ this.peerManager.discover();
105
+ }, this.config.p2pPeerCheckIntervalMS);
106
+ this.discoveryRunningPromise.start();
124
107
  }
125
108
 
126
109
  /**
@@ -130,8 +113,12 @@ export class LibP2PService implements P2PService {
130
113
  public async stop() {
131
114
  this.logger.debug('Stopping job queue...');
132
115
  await this.jobQueue.end();
116
+ this.logger.debug('Stopping running promise...');
117
+ await this.discoveryRunningPromise?.stop();
118
+ this.logger.debug('Stopping peer discovery service...');
119
+ await this.peerDiscoveryService.stop();
133
120
  this.logger.debug('Stopping LibP2P...');
134
- await this.node.stop();
121
+ await this.stopLibP2P();
135
122
  this.logger.info('LibP2P service stopped');
136
123
  }
137
124
 
@@ -146,11 +133,14 @@ export class LibP2PService implements P2PService {
146
133
  peerDiscoveryService: PeerDiscoveryService,
147
134
  peerId: PeerId,
148
135
  txPool: TxPool,
136
+ store: AztecKVStore,
149
137
  ) {
150
- const { tcpListenAddress, minPeerCount, maxPeerCount, transactionProtocol: protocolId } = config;
138
+ const { tcpListenAddress, tcpAnnounceAddress, minPeerCount, maxPeerCount } = config;
151
139
  const bindAddrTcp = convertToMultiaddr(tcpListenAddress, 'tcp');
140
+ // We know tcpAnnounceAddress cannot be null here because we set it or throw when setting up the service.
141
+ const announceAddrTcp = convertToMultiaddr(tcpAnnounceAddress!, 'tcp');
152
142
 
153
- const datastore = new AztecDatastore(AztecLmdbStore.open());
143
+ const datastore = new AztecDatastore(store);
154
144
 
155
145
  // The autonat service seems quite problematic in that using it seems to cause a lot of attempts
156
146
  // to dial ephemeral ports. I suspect that it works better if you can get the uPNPnat service to
@@ -171,10 +161,19 @@ export class LibP2PService implements P2PService {
171
161
  peerId,
172
162
  addresses: {
173
163
  listen: [bindAddrTcp],
164
+ announce: [announceAddrTcp],
174
165
  },
175
166
  transports: [
176
167
  tcp({
177
168
  maxConnections: config.maxPeerCount,
169
+ // socket option: the maximum length of the queue of pending connections
170
+ // https://nodejs.org/dist/latest-v18.x/docs/api/net.html#serverlisten
171
+ // it's not safe if we increase this number
172
+ backlog: 5,
173
+ closeServerOnMaxConnections: {
174
+ closeAbove: maxPeerCount ?? Infinity,
175
+ listenBelow: maxPeerCount ?? Infinity,
176
+ },
178
177
  }),
179
178
  ],
180
179
  datastore,
@@ -200,15 +199,7 @@ export class LibP2PService implements P2PService {
200
199
  },
201
200
  });
202
201
 
203
- // extract bootstrap node peer IDs
204
- let bootstrapPeerIds: PeerId[] = [];
205
- if (config.bootstrapNodes.length) {
206
- bootstrapPeerIds = await Promise.all(
207
- config.bootstrapNodes.map(bootnodeEnr => ENR.decodeTxt(bootnodeEnr).peerId()),
208
- );
209
- }
210
-
211
- return new LibP2PService(config, node, peerDiscoveryService, protocolId, txPool, bootstrapPeerIds);
202
+ return new LibP2PService(config, node, peerDiscoveryService, txPool);
212
203
  }
213
204
 
214
205
  /**
@@ -260,71 +251,31 @@ export class LibP2PService implements P2PService {
260
251
  void this.jobQueue.put(() => Promise.resolve(this.sendTxToPeers(tx)));
261
252
  }
262
253
 
263
- /**
264
- * Handles the settling of a new batch of transactions.
265
- * @param txHashes - The hashes of the newly settled transactions.
266
- */
267
- public settledTxs(txHashes: TxHash[]): void {
268
- this.knownTxLookup.handleSettledTxs(txHashes.map(x => x.toString()));
269
- }
270
-
271
- private async addPeer(enr: ENR) {
272
- const peerMultiAddr = await enr.getFullMultiaddr('tcp');
273
- if (!peerMultiAddr) {
274
- // No TCP address, can't connect
275
- return;
276
- }
277
- const peerIdStr = peerMultiAddr.getPeerId();
278
-
279
- if (!peerIdStr) {
280
- this.logger.debug(`Peer ID not found in discovered node's multiaddr: ${peerMultiAddr}`);
281
- return;
282
- }
283
-
284
- // check if peer is already known
285
- const peerId = peerIdFromString(peerIdStr);
286
- const hasPeer = await this.node.peerStore.has(peerId);
287
-
288
- // add to peer store if not already known
289
- if (!hasPeer) {
290
- this.logger.info(`Discovered peer ${peerIdStr}. Adding to libp2p peer list`);
291
- let stream: Stream | undefined;
292
- try {
293
- stream = await this.node.dialProtocol(peerMultiAddr, this.protocolId);
294
- } catch (err) {
295
- this.logger.debug(`Failed to dial peer ${peerIdStr}: ${err}`);
296
- } finally {
297
- if (stream) {
298
- await stream.close();
299
- }
300
- }
301
- }
302
- }
303
-
304
- private async handleNewConnection(peerId: PeerId) {
305
- if (this.isBootstrapPeer(peerId)) {
306
- this.logger.info(`Connected to bootstrap peer ${peerId.toString()}`);
307
- } else {
308
- this.logger.info(`Connected to transaction peer ${peerId.toString()}`);
309
- await this.peerManager.updateDiscoveryService();
310
- }
311
- }
312
-
313
254
  private async processTxFromPeer(tx: Tx): Promise<void> {
314
255
  const txHash = tx.getTxHash();
315
256
  const txHashString = txHash.toString();
316
- this.logger.debug(`Received tx ${txHashString} from external peer.`);
257
+ this.logger.verbose(`Received tx ${txHashString} from external peer.`);
317
258
  await this.txPool.addTxs([tx]);
318
259
  }
319
260
 
320
261
  private async sendTxToPeers(tx: Tx) {
321
262
  const { data: txData } = this.messageCreator.createTxMessage(tx);
322
- this.logger.debug(`Sending tx ${tx.getTxHash().toString()} to peers`);
263
+ this.logger.verbose(`Sending tx ${tx.getTxHash().toString()} to peers`);
323
264
  const recipientsNum = await this.publishToTopic(this.messageCreator.getTopic(), txData);
324
- this.logger.debug(`Sent tx ${tx.getTxHash().toString()} to ${recipientsNum} peers`);
265
+ this.logger.verbose(`Sent tx ${tx.getTxHash().toString()} to ${recipientsNum} peers`);
325
266
  }
326
267
 
327
- private isBootstrapPeer(peer: PeerId) {
328
- return this.bootstrapPeerIds.some(bootstrapPeer => bootstrapPeer.equals(peer));
268
+ // Libp2p seems to hang sometimes if new peers are initiating connections.
269
+ private async stopLibP2P() {
270
+ const TIMEOUT_MS = 5000; // 5 seconds timeout
271
+ const timeout = new Promise((resolve, reject) => {
272
+ setTimeout(() => reject(new Error('Timeout during libp2p.stop()')), TIMEOUT_MS);
273
+ });
274
+ try {
275
+ await Promise.race([this.node.stop(), timeout]);
276
+ this.logger.debug('Libp2p stopped');
277
+ } catch (error) {
278
+ this.logger.error('Error during stop or timeout:', error);
279
+ }
329
280
  }
330
281
  }
@@ -1,26 +1,201 @@
1
1
  import { createDebugLogger } from '@aztec/foundation/log';
2
2
 
3
+ import { type ENR } from '@chainsafe/enr';
4
+ import { type PeerId } from '@libp2p/interface';
5
+ import { type Multiaddr } from '@multiformats/multiaddr';
3
6
  import { type Libp2p } from 'libp2p';
4
7
 
5
8
  import { type P2PConfig } from '../config.js';
6
- import { type PeerDiscoveryService, PeerDiscoveryState } from './service.js';
9
+ import { type PeerDiscoveryService } from './service.js';
10
+
11
+ const MAX_DIAL_ATTEMPTS = 3;
12
+ const MAX_CACHED_PEERS = 100;
13
+
14
+ type CachedPeer = {
15
+ peerId: PeerId;
16
+ enr: ENR;
17
+ multiaddrTcp: Multiaddr;
18
+ dialAttempts: number;
19
+ };
7
20
 
8
21
  export class PeerManager {
22
+ private cachedPeers: Map<string, CachedPeer> = new Map();
9
23
  constructor(
10
24
  private libP2PNode: Libp2p,
11
- private discV5Node: PeerDiscoveryService,
25
+ private peerDiscoveryService: PeerDiscoveryService,
12
26
  private config: P2PConfig,
13
27
  private logger = createDebugLogger('aztec:p2p:peer_manager'),
14
- ) {}
15
-
16
- async updateDiscoveryService() {
17
- const peerCount = this.libP2PNode.getPeers().length;
18
- if (peerCount >= this.config.maxPeerCount && this.discV5Node.getStatus() === PeerDiscoveryState.RUNNING) {
19
- this.logger.debug('Max peer count reached, stopping discovery service');
20
- await this.discV5Node.stop();
21
- } else if (peerCount <= this.config.minPeerCount && this.discV5Node.getStatus() === PeerDiscoveryState.STOPPED) {
22
- this.logger.debug('Min peer count reached, starting discovery service');
23
- await this.discV5Node.start();
28
+ ) {
29
+ // Handle new established connections
30
+ this.libP2PNode.addEventListener('peer:connect', evt => {
31
+ const peerId = evt.detail;
32
+ if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
33
+ this.logger.debug(`Connected to bootstrap peer ${peerId.toString()}`);
34
+ } else {
35
+ this.logger.debug(`Connected to transaction peer ${peerId.toString()}`);
36
+ }
37
+ });
38
+
39
+ // Handle lost connections
40
+ this.libP2PNode.addEventListener('peer:disconnect', evt => {
41
+ const peerId = evt.detail;
42
+ if (this.peerDiscoveryService.isBootstrapPeer(peerId)) {
43
+ this.logger.debug(`Disconnected from bootstrap peer ${peerId.toString()}`);
44
+ } else {
45
+ this.logger.debug(`Disconnected from transaction peer ${peerId.toString()}`);
46
+ }
47
+ });
48
+
49
+ // Handle Discovered peers
50
+ this.peerDiscoveryService.on('peer:discovered', async (enr: ENR) => {
51
+ await this.handleDiscoveredPeer(enr);
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Discovers peers.
57
+ */
58
+ public discover() {
59
+ // Get current connections
60
+ const connections = this.libP2PNode.getConnections();
61
+
62
+ // Calculate how many connections we're looking to make
63
+ const peersToConnect = this.config.maxPeerCount - connections.length;
64
+
65
+ this.logger.debug(
66
+ `Connections: ${connections.length}, Peers to connect: ${peersToConnect}, maxPeerCount: ${this.config.maxPeerCount}, cachedPeers: ${this.cachedPeers.size}`,
67
+ );
68
+
69
+ // Exit if no peers to connect
70
+ if (peersToConnect <= 0) {
71
+ return;
72
+ }
73
+
74
+ const cachedPeersToDial: CachedPeer[] = [];
75
+
76
+ const pendingDials = new Set(
77
+ this.libP2PNode
78
+ .getDialQueue()
79
+ .map(pendingDial => pendingDial.peerId?.toString())
80
+ .filter(Boolean) as string[],
81
+ );
82
+
83
+ for (const [id, peerData] of this.cachedPeers.entries()) {
84
+ // if already dialling or connected to, remove from cache
85
+ if (pendingDials.has(id) || connections.some(conn => conn.remotePeer.equals(peerData.peerId))) {
86
+ this.cachedPeers.delete(id);
87
+ } else {
88
+ // cachedPeersToDial.set(id, enr);
89
+ cachedPeersToDial.push(peerData);
90
+ }
91
+ }
92
+
93
+ // reverse to dial older entries first
94
+ cachedPeersToDial.reverse();
95
+
96
+ for (const peer of cachedPeersToDial) {
97
+ this.cachedPeers.delete(peer.peerId.toString());
98
+ void this.dialPeer(peer);
99
+ }
100
+
101
+ // if we need more peers, start randomNodesQuery
102
+ if (peersToConnect > 0) {
103
+ this.logger.debug('Running random nodes query');
104
+ void this.peerDiscoveryService.runRandomNodesQuery();
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Handles a discovered peer.
110
+ * @param enr - The discovered peer's ENR.
111
+ */
112
+ private async handleDiscoveredPeer(enr: ENR) {
113
+ // TODO: Will be handling peer scoring here
114
+
115
+ // check if peer is already connected
116
+ const [peerId, multiaddrTcp] = await Promise.all([enr.peerId(), enr.getFullMultiaddr('tcp')]);
117
+
118
+ this.logger.debug(`Handling discovered peer ${peerId.toString()}, ${multiaddrTcp?.toString()}`);
119
+
120
+ // throw if no tcp addr in multiaddr
121
+ if (!multiaddrTcp) {
122
+ this.logger.debug(`No TCP address in discovered node's multiaddr: ${enr.toString()}`);
123
+ return;
124
+ }
125
+ const connections = this.libP2PNode.getConnections();
126
+ if (connections.some(conn => conn.remotePeer.equals(peerId))) {
127
+ this.logger.debug(`Already connected to peer ${peerId.toString()}`);
128
+ return;
129
+ }
130
+
131
+ // check if peer is already in cache
132
+ const id = peerId.toString();
133
+ if (this.cachedPeers.has(id)) {
134
+ this.logger.debug(`Already in cache ${id}`);
135
+ return;
136
+ }
137
+
138
+ // create cached peer object
139
+ const cachedPeer: CachedPeer = {
140
+ peerId,
141
+ enr,
142
+ multiaddrTcp,
143
+ dialAttempts: 0,
144
+ };
145
+
146
+ // Determine if we should dial immediately or not
147
+ if (this.shouldDialPeer()) {
148
+ this.logger.debug(`Dialing peer ${id}`);
149
+ void this.dialPeer(cachedPeer);
150
+ } else {
151
+ this.logger.debug(`Caching peer ${id}`);
152
+ this.cachedPeers.set(id, cachedPeer);
153
+ // Prune set of cached peers
154
+ this.pruneCachedPeers();
155
+ }
156
+ }
157
+
158
+ async dialPeer(peer: CachedPeer) {
159
+ const id = peer.peerId.toString();
160
+ await this.libP2PNode.peerStore.merge(peer.peerId, { multiaddrs: [peer.multiaddrTcp] });
161
+
162
+ this.logger.debug(`Dialing peer ${id}`);
163
+ try {
164
+ await this.libP2PNode.dial(peer.multiaddrTcp);
165
+ } catch {
166
+ this.logger.debug(`Failed to dial peer ${id}`);
167
+ peer.dialAttempts++;
168
+ if (peer.dialAttempts < MAX_DIAL_ATTEMPTS) {
169
+ this.cachedPeers.set(id, peer);
170
+ } else {
171
+ this.cachedPeers.delete(id);
172
+ }
173
+ }
174
+ }
175
+
176
+ private shouldDialPeer(): boolean {
177
+ const connections = this.libP2PNode.getConnections().length;
178
+ this.logger.debug(`Connections: ${connections}, maxPeerCount: ${this.config.maxPeerCount}`);
179
+ if (connections >= this.config.maxPeerCount) {
180
+ this.logger.debug('Not dialing peer, maxPeerCount reached');
181
+ return false;
182
+ }
183
+ return true;
184
+ }
185
+
186
+ private pruneCachedPeers() {
187
+ let peersToDelete = this.cachedPeers.size - MAX_CACHED_PEERS;
188
+ if (peersToDelete <= 0) {
189
+ return;
190
+ }
191
+
192
+ // Remove the oldest peers
193
+ for (const key of this.cachedPeers.keys()) {
194
+ this.cachedPeers.delete(key);
195
+ peersToDelete--;
196
+ if (peersToDelete <= 0) {
197
+ break;
198
+ }
24
199
  }
25
200
  }
26
201
  }
@@ -1,6 +1,7 @@
1
- import type { Tx, TxHash } from '@aztec/circuit-types';
1
+ import type { Tx } from '@aztec/circuit-types';
2
2
 
3
3
  import type { ENR } from '@chainsafe/enr';
4
+ import type { PeerId } from '@libp2p/interface';
4
5
  import type EventEmitter from 'events';
5
6
 
6
7
  export enum PeerDiscoveryState {
@@ -29,12 +30,6 @@ export interface P2PService {
29
30
  * @param tx - The transaction to be propagated.
30
31
  */
31
32
  propagateTx(tx: Tx): void;
32
-
33
- /**
34
- * Called upon receipt of settled transactions.
35
- * @param txHashes - The hashes of the settled transactions.
36
- */
37
- settledTxs(txHashes: TxHash[]): void;
38
33
  }
39
34
 
40
35
  /**
@@ -57,6 +52,18 @@ export interface PeerDiscoveryService extends EventEmitter {
57
52
  */
58
53
  getAllPeers(): ENR[];
59
54
 
55
+ /**
56
+ * Runs findRandomNode query.
57
+ */
58
+ runRandomNodesQuery(): Promise<void>;
59
+
60
+ /**
61
+ * Checks if the given peer is a bootstrap peer.
62
+ * @param peerId - The peer ID to check.
63
+ * @returns True if the peer is a bootstrap peer.
64
+ */
65
+ isBootstrapPeer(peerId: PeerId): boolean;
66
+
60
67
  /**
61
68
  * Event emitted when a new peer is discovered.
62
69
  */
@@ -2,7 +2,9 @@ import { Tx, TxHash } from '@aztec/circuit-types';
2
2
  import { type TxAddedToPoolStats } from '@aztec/circuit-types/stats';
3
3
  import { type Logger, createDebugLogger } from '@aztec/foundation/log';
4
4
  import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
5
+ import { type TelemetryClient } from '@aztec/telemetry-client';
5
6
 
7
+ import { TxPoolInstrumentation } from './instrumentation.js';
6
8
  import { type TxPool } from './tx_pool.js';
7
9
 
8
10
  /**
@@ -18,15 +20,18 @@ export class AztecKVTxPool implements TxPool {
18
20
 
19
21
  #log: Logger;
20
22
 
23
+ #metrics: TxPoolInstrumentation;
24
+
21
25
  /**
22
26
  * Class constructor for in-memory TxPool. Initiates our transaction pool as a JS Map.
23
27
  * @param store - A KV store.
24
28
  * @param log - A logger.
25
29
  */
26
- constructor(store: AztecKVStore, log = createDebugLogger('aztec:tx_pool')) {
30
+ constructor(store: AztecKVStore, telemetry: TelemetryClient, log = createDebugLogger('aztec:tx_pool')) {
27
31
  this.#txs = store.openMap('txs');
28
32
  this.#store = store;
29
33
  this.#log = log;
34
+ this.#metrics = new TxPoolInstrumentation(telemetry, 'AztecKVTxPool');
30
35
  }
31
36
 
32
37
  /**
@@ -44,8 +49,8 @@ export class AztecKVTxPool implements TxPool {
44
49
  * @param txs - An array of txs to be added to the pool.
45
50
  * @returns Empty promise.
46
51
  */
47
- public async addTxs(txs: Tx[]): Promise<void> {
48
- const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
52
+ public addTxs(txs: Tx[]): Promise<void> {
53
+ const txHashes = txs.map(tx => tx.getTxHash());
49
54
  return this.#store.transaction(() => {
50
55
  for (const [i, tx] of txs.entries()) {
51
56
  const txHash = txHashes[i];
@@ -56,6 +61,8 @@ export class AztecKVTxPool implements TxPool {
56
61
 
57
62
  void this.#txs.set(txHash.toString(), tx.toBuffer());
58
63
  }
64
+
65
+ this.#metrics.recordTxs(txs);
59
66
  });
60
67
  }
61
68
 
@@ -69,6 +76,8 @@ export class AztecKVTxPool implements TxPool {
69
76
  for (const hash of txHashes) {
70
77
  void this.#txs.delete(hash.toString());
71
78
  }
79
+
80
+ this.#metrics.removeTxs(txHashes.length);
72
81
  });
73
82
  }
74
83
 
@@ -0,0 +1,58 @@
1
+ import { type Tx } from '@aztec/circuit-types';
2
+ import { type Histogram, Metrics, type TelemetryClient, type UpDownCounter } from '@aztec/telemetry-client';
3
+
4
+ /**
5
+ * Instrumentation class for the TxPool.
6
+ */
7
+ export class TxPoolInstrumentation {
8
+ /** The number of txs in the mempool */
9
+ private txInMempool: UpDownCounter;
10
+ /** Tracks tx size */
11
+ private txSize: Histogram;
12
+
13
+ constructor(telemetry: TelemetryClient, name: string) {
14
+ const meter = telemetry.getMeter(name);
15
+ this.txInMempool = meter.createUpDownCounter(Metrics.MEMPOOL_TX_COUNT, {
16
+ description: 'The current number of transactions in the mempool',
17
+ });
18
+
19
+ this.txSize = meter.createHistogram(Metrics.MEMPOOL_TX_SIZE, {
20
+ unit: 'By',
21
+ description: 'The size of transactions in the mempool',
22
+ advice: {
23
+ explicitBucketBoundaries: [
24
+ 5_000, // 5KB
25
+ 10_000,
26
+ 20_000,
27
+ 50_000,
28
+ 75_000,
29
+ 100_000, // 100KB
30
+ 200_000,
31
+ ],
32
+ },
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Updates the metrics with the new transactions.
38
+ * @param txs - The transactions to record
39
+ */
40
+ public recordTxs(txs: Tx[]) {
41
+ for (const tx of txs) {
42
+ this.txSize.record(tx.getSize());
43
+ }
44
+
45
+ this.txInMempool.add(txs.length);
46
+ }
47
+
48
+ /**
49
+ * Updates the metrics by removing transactions from the mempool.
50
+ * @param count - The number of transactions to remove from the mempool
51
+ */
52
+ public removeTxs(count = 1) {
53
+ if (count < 0) {
54
+ throw new Error('Count must be positive');
55
+ }
56
+ this.txInMempool.add(-1 * count);
57
+ }
58
+ }
@@ -1,7 +1,9 @@
1
1
  import { Tx, TxHash } from '@aztec/circuit-types';
2
2
  import { type TxAddedToPoolStats } from '@aztec/circuit-types/stats';
3
3
  import { createDebugLogger } from '@aztec/foundation/log';
4
+ import { type TelemetryClient } from '@aztec/telemetry-client';
4
5
 
6
+ import { TxPoolInstrumentation } from './instrumentation.js';
5
7
  import { type TxPool } from './tx_pool.js';
6
8
 
7
9
  /**
@@ -13,12 +15,15 @@ export class InMemoryTxPool implements TxPool {
13
15
  */
14
16
  private txs: Map<bigint, Tx>;
15
17
 
18
+ private metrics: TxPoolInstrumentation;
19
+
16
20
  /**
17
21
  * Class constructor for in-memory TxPool. Initiates our transaction pool as a JS Map.
18
22
  * @param log - A logger.
19
23
  */
20
- constructor(private log = createDebugLogger('aztec:tx_pool')) {
24
+ constructor(telemetry: TelemetryClient, private log = createDebugLogger('aztec:tx_pool')) {
21
25
  this.txs = new Map<bigint, Tx>();
26
+ this.metrics = new TxPoolInstrumentation(telemetry, 'InMemoryTxPool');
22
27
  }
23
28
 
24
29
  /**
@@ -37,6 +42,7 @@ export class InMemoryTxPool implements TxPool {
37
42
  * @returns Empty promise.
38
43
  */
39
44
  public addTxs(txs: Tx[]): Promise<void> {
45
+ this.metrics.recordTxs(txs);
40
46
  for (const tx of txs) {
41
47
  const txHash = tx.getTxHash();
42
48
  this.log.debug(`Adding tx with id ${txHash.toString()}`, {
@@ -54,6 +60,7 @@ export class InMemoryTxPool implements TxPool {
54
60
  * @returns The number of transactions that was deleted from the pool.
55
61
  */
56
62
  public deleteTxs(txHashes: TxHash[]): Promise<void> {
63
+ this.metrics.removeTxs(txHashes.length);
57
64
  for (const txHash of txHashes) {
58
65
  this.txs.delete(txHash.toBigInt());
59
66
  }