@aztec/p2p 0.43.0 → 0.45.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.
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +5 -7
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +2 -3
- package/dest/service/discV5_service.d.ts +5 -2
- package/dest/service/discV5_service.d.ts.map +1 -1
- package/dest/service/discV5_service.js +26 -9
- package/dest/service/dummy_service.d.ts +4 -1
- package/dest/service/dummy_service.d.ts.map +1 -1
- package/dest/service/dummy_service.js +7 -1
- package/dest/service/libp2p_service.d.ts +6 -14
- package/dest/service/libp2p_service.d.ts.map +1 -1
- package/dest/service/libp2p_service.js +49 -91
- package/dest/service/peer_manager.d.ts +25 -3
- package/dest/service/peer_manager.d.ts.map +1 -1
- package/dest/service/peer_manager.js +152 -12
- package/dest/service/service.d.ts +12 -6
- package/dest/service/service.d.ts.map +1 -1
- package/dest/service/service.js +1 -1
- package/dest/tx_pool/aztec_kv_tx_pool.d.ts +2 -1
- package/dest/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
- package/dest/tx_pool/aztec_kv_tx_pool.js +11 -6
- package/dest/tx_pool/instrumentation.d.ts +23 -0
- package/dest/tx_pool/instrumentation.d.ts.map +1 -0
- package/dest/tx_pool/instrumentation.js +48 -0
- package/dest/tx_pool/memory_tx_pool.d.ts +3 -1
- package/dest/tx_pool/memory_tx_pool.d.ts.map +1 -1
- package/dest/tx_pool/memory_tx_pool.js +6 -2
- package/package.json +15 -6
- package/src/client/index.ts +4 -6
- package/src/client/p2p_client.ts +1 -2
- package/src/service/discV5_service.ts +28 -11
- package/src/service/dummy_service.ts +10 -1
- package/src/service/libp2p_service.ts +52 -101
- package/src/service/peer_manager.ts +187 -12
- package/src/service/service.ts +14 -7
- package/src/tx_pool/aztec_kv_tx_pool.ts +12 -3
- package/src/tx_pool/instrumentation.ts +58 -0
- package/src/tx_pool/memory_tx_pool.ts +8 -1
- package/dest/service/known_txs.d.ts +0 -31
- package/dest/service/known_txs.d.ts.map +0 -1
- package/dest/service/known_txs.js +0 -52
- package/src/service/known_txs.ts +0 -56
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import { type Tx
|
|
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 {
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
265
|
+
this.logger.verbose(`Sent tx ${tx.getTxHash().toString()} to ${recipientsNum} peers`);
|
|
325
266
|
}
|
|
326
267
|
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
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
|
|
25
|
+
private peerDiscoveryService: PeerDiscoveryService,
|
|
12
26
|
private config: P2PConfig,
|
|
13
27
|
private logger = createDebugLogger('aztec:p2p:peer_manager'),
|
|
14
|
-
) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
}
|
package/src/service/service.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Tx
|
|
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
|
|
48
|
-
const txHashes =
|
|
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
|
}
|