@aztec/p2p 0.38.0 → 0.40.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/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +3 -1
- package/dest/config.d.ts +5 -0
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +4 -2
- package/dest/service/data_store.d.ts +27 -0
- package/dest/service/data_store.d.ts.map +1 -0
- package/dest/service/data_store.js +188 -0
- package/dest/service/discV5_service.d.ts +8 -6
- package/dest/service/discV5_service.d.ts.map +1 -1
- package/dest/service/discV5_service.js +35 -21
- package/dest/service/dummy_service.d.ts +3 -1
- package/dest/service/dummy_service.d.ts.map +1 -1
- package/dest/service/dummy_service.js +11 -1
- package/dest/service/libp2p_service.d.ts +28 -17
- package/dest/service/libp2p_service.d.ts.map +1 -1
- package/dest/service/libp2p_service.js +101 -174
- package/dest/service/peer_manager.d.ts +12 -0
- package/dest/service/peer_manager.d.ts.map +1 -0
- package/dest/service/peer_manager.js +22 -0
- package/dest/service/service.d.ts +5 -0
- package/dest/service/service.d.ts.map +1 -1
- package/dest/service/service.js +6 -2
- package/dest/service/tx_messages.d.ts +11 -57
- package/dest/service/tx_messages.d.ts.map +1 -1
- package/dest/service/tx_messages.js +14 -89
- package/package.json +29 -21
- package/src/bootstrap/bootstrap.ts +2 -0
- package/src/config.ts +9 -0
- package/src/service/data_store.ts +235 -0
- package/src/service/discV5_service.ts +37 -20
- package/src/service/dummy_service.ts +8 -1
- package/src/service/libp2p_service.ts +130 -194
- package/src/service/peer_manager.ts +26 -0
- package/src/service/service.ts +7 -0
- package/src/service/tx_messages.ts +18 -93
|
@@ -2,43 +2,43 @@ import { type Tx, type TxHash } from '@aztec/circuit-types';
|
|
|
2
2
|
import { SerialQueue } from '@aztec/foundation/fifo';
|
|
3
3
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
4
|
import { type AztecKVStore } from '@aztec/kv-store';
|
|
5
|
+
import { AztecLmdbStore } from '@aztec/kv-store/lmdb';
|
|
5
6
|
|
|
6
7
|
import { ENR } from '@chainsafe/enr';
|
|
8
|
+
import { type GossipsubEvents, gossipsub } from '@chainsafe/libp2p-gossipsub';
|
|
7
9
|
import { noise } from '@chainsafe/libp2p-noise';
|
|
8
10
|
import { yamux } from '@chainsafe/libp2p-yamux';
|
|
9
11
|
import { identify } from '@libp2p/identify';
|
|
10
|
-
import type { IncomingStreamData, PeerId, Stream } from '@libp2p/interface';
|
|
11
|
-
import type { ServiceMap } from '@libp2p/interface-libp2p';
|
|
12
|
+
import type { IncomingStreamData, PeerId, PubSub, Stream } from '@libp2p/interface';
|
|
12
13
|
import '@libp2p/kad-dht';
|
|
13
14
|
import { mplex } from '@libp2p/mplex';
|
|
14
15
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
15
|
-
import { createFromJSON, createSecp256k1PeerId
|
|
16
|
+
import { createFromJSON, createSecp256k1PeerId } from '@libp2p/peer-id-factory';
|
|
16
17
|
import { tcp } from '@libp2p/tcp';
|
|
17
18
|
import { pipe } from 'it-pipe';
|
|
18
|
-
import { type Libp2p,
|
|
19
|
+
import { type Libp2p, createLibp2p } from 'libp2p';
|
|
19
20
|
|
|
20
21
|
import { type P2PConfig } from '../config.js';
|
|
21
22
|
import { type TxPool } from '../tx_pool/index.js';
|
|
23
|
+
import { AztecDatastore } from './data_store.js';
|
|
22
24
|
import { KnownTxLookup } from './known_txs.js';
|
|
25
|
+
import { PeerManager } from './peer_manager.js';
|
|
23
26
|
import { AztecPeerDb, type AztecPeerStore } from './peer_store.js';
|
|
24
27
|
import type { P2PService, PeerDiscoveryService } from './service.js';
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
decodeTransactionsMessage,
|
|
33
|
-
getEncodedMessage,
|
|
34
|
-
} from './tx_messages.js';
|
|
28
|
+
import { AztecTxMessageCreator, fromTxMessage } from './tx_messages.js';
|
|
29
|
+
|
|
30
|
+
export interface PubSubLibp2p extends Libp2p {
|
|
31
|
+
services: {
|
|
32
|
+
pubsub: PubSub<GossipsubEvents>;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Create a libp2p peer ID from the private key if provided, otherwise creates a new random ID.
|
|
38
38
|
* @param privateKey - Optional peer ID private key as hex string
|
|
39
39
|
* @returns The peer ID.
|
|
40
40
|
*/
|
|
41
|
-
export async function createLibP2PPeerId(privateKey?: string) {
|
|
41
|
+
export async function createLibP2PPeerId(privateKey?: string): Promise<PeerId> {
|
|
42
42
|
if (!privateKey?.length) {
|
|
43
43
|
return await createSecp256k1PeerId();
|
|
44
44
|
}
|
|
@@ -49,31 +49,27 @@ export async function createLibP2PPeerId(privateKey?: string) {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
* Exports a given peer id to a string representation.
|
|
54
|
-
* @param peerId - The peerId instance to be converted.
|
|
55
|
-
* @returns The peer id as a string.
|
|
56
|
-
*/
|
|
57
|
-
export function exportLibP2PPeerIdToString(peerId: PeerId) {
|
|
58
|
-
return Buffer.from(exportToProtobuf(peerId)).toString('hex');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
52
|
/**
|
|
62
53
|
* Lib P2P implementation of the P2PService interface.
|
|
63
54
|
*/
|
|
64
55
|
export class LibP2PService implements P2PService {
|
|
65
56
|
private jobQueue: SerialQueue = new SerialQueue();
|
|
66
57
|
private knownTxLookup: KnownTxLookup = new KnownTxLookup();
|
|
58
|
+
private messageCreator: AztecTxMessageCreator;
|
|
59
|
+
private peerManager: PeerManager;
|
|
67
60
|
constructor(
|
|
68
61
|
private config: P2PConfig,
|
|
69
|
-
private node:
|
|
62
|
+
private node: PubSubLibp2p,
|
|
70
63
|
private peerDiscoveryService: PeerDiscoveryService,
|
|
71
64
|
private peerStore: AztecPeerStore,
|
|
72
65
|
private protocolId: string,
|
|
73
66
|
private txPool: TxPool,
|
|
74
67
|
private bootstrapPeerIds: PeerId[] = [],
|
|
75
68
|
private logger = createDebugLogger('aztec:libp2p_service'),
|
|
76
|
-
) {
|
|
69
|
+
) {
|
|
70
|
+
this.messageCreator = new AztecTxMessageCreator(config.txGossipVersion);
|
|
71
|
+
this.peerManager = new PeerManager(node, peerDiscoveryService, config, logger);
|
|
72
|
+
}
|
|
77
73
|
|
|
78
74
|
/**
|
|
79
75
|
* Starts the LibP2P service.
|
|
@@ -97,24 +93,18 @@ export class LibP2PService implements P2PService {
|
|
|
97
93
|
await this.addPeer(enr);
|
|
98
94
|
});
|
|
99
95
|
|
|
100
|
-
this.node.addEventListener('peer:
|
|
101
|
-
const peerId = evt.detail.id;
|
|
102
|
-
if (this.isBootstrapPeer(peerId)) {
|
|
103
|
-
this.logger.verbose(`Discovered bootstrap peer ${peerId.toString()}`);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
this.node.addEventListener('peer:connect', evt => {
|
|
96
|
+
this.node.addEventListener('peer:connect', async evt => {
|
|
108
97
|
const peerId = evt.detail;
|
|
109
|
-
this.handleNewConnection(peerId);
|
|
98
|
+
await this.handleNewConnection(peerId as PeerId);
|
|
110
99
|
});
|
|
111
100
|
|
|
112
|
-
this.node.addEventListener('peer:disconnect', evt => {
|
|
101
|
+
this.node.addEventListener('peer:disconnect', async evt => {
|
|
113
102
|
const peerId = evt.detail;
|
|
114
103
|
if (this.isBootstrapPeer(peerId)) {
|
|
115
104
|
this.logger.verbose(`Disconnect from bootstrap peer ${peerId.toString()}`);
|
|
116
105
|
} else {
|
|
117
106
|
this.logger.verbose(`Disconnected from transaction peer ${peerId.toString()}`);
|
|
107
|
+
await this.peerManager.updateDiscoveryService();
|
|
118
108
|
}
|
|
119
109
|
});
|
|
120
110
|
|
|
@@ -125,6 +115,17 @@ export class LibP2PService implements P2PService {
|
|
|
125
115
|
this.jobQueue.put(() => Promise.resolve(this.handleProtocolDial(incoming))),
|
|
126
116
|
);
|
|
127
117
|
this.logger.info(`Started P2P client with Peer ID ${this.node.peerId.toString()}`);
|
|
118
|
+
|
|
119
|
+
// Subscribe to standard topics by default
|
|
120
|
+
this.subscribeToTopic(this.messageCreator.getTopic());
|
|
121
|
+
|
|
122
|
+
// add gossipsub listener
|
|
123
|
+
this.node.services.pubsub.addEventListener('gossipsub:message', async e => {
|
|
124
|
+
const { msg } = e.detail;
|
|
125
|
+
this.logger.debug(`Received PUBSUB message.`);
|
|
126
|
+
|
|
127
|
+
await this.handleNewGossipMessage(msg.topic, msg.data);
|
|
128
|
+
});
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
/**
|
|
@@ -152,27 +153,15 @@ export class LibP2PService implements P2PService {
|
|
|
152
153
|
txPool: TxPool,
|
|
153
154
|
store: AztecKVStore,
|
|
154
155
|
) {
|
|
155
|
-
const {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
connectionEncryption: [noise()],
|
|
165
|
-
connectionManager: {
|
|
166
|
-
minConnections: minPeerCount,
|
|
167
|
-
maxConnections: maxPeerCount,
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const services: ServiceFactoryMap = {
|
|
172
|
-
identify: identify({
|
|
173
|
-
protocolPrefix: 'aztec',
|
|
174
|
-
}),
|
|
175
|
-
};
|
|
156
|
+
const {
|
|
157
|
+
tcpListenIp,
|
|
158
|
+
tcpListenPort,
|
|
159
|
+
minPeerCount,
|
|
160
|
+
maxPeerCount,
|
|
161
|
+
dataDirectory,
|
|
162
|
+
transactionProtocol: protocolId,
|
|
163
|
+
} = config;
|
|
164
|
+
const bindAddrTcp = `/ip4/${tcpListenIp}/tcp/${tcpListenPort}`;
|
|
176
165
|
|
|
177
166
|
// The autonat service seems quite problematic in that using it seems to cause a lot of attempts
|
|
178
167
|
// to dial ephemeral ports. I suspect that it works better if you can get the uPNPnat service to
|
|
@@ -188,11 +177,41 @@ export class LibP2PService implements P2PService {
|
|
|
188
177
|
// services.uPnPNAT = uPnPNATService();
|
|
189
178
|
// }
|
|
190
179
|
|
|
180
|
+
const datastore = new AztecDatastore(AztecLmdbStore.open(dataDirectory));
|
|
181
|
+
|
|
191
182
|
const node = await createLibp2p({
|
|
192
|
-
|
|
193
|
-
|
|
183
|
+
start: false,
|
|
184
|
+
peerId,
|
|
185
|
+
addresses: {
|
|
186
|
+
listen: [bindAddrTcp],
|
|
187
|
+
},
|
|
188
|
+
transports: [
|
|
189
|
+
tcp({
|
|
190
|
+
maxConnections: config.maxPeerCount,
|
|
191
|
+
}),
|
|
192
|
+
],
|
|
193
|
+
datastore,
|
|
194
|
+
streamMuxers: [yamux(), mplex()],
|
|
195
|
+
connectionEncryption: [noise()],
|
|
196
|
+
connectionManager: {
|
|
197
|
+
minConnections: minPeerCount,
|
|
198
|
+
maxConnections: maxPeerCount,
|
|
199
|
+
},
|
|
200
|
+
services: {
|
|
201
|
+
identify: identify({
|
|
202
|
+
protocolPrefix: 'aztec',
|
|
203
|
+
}),
|
|
204
|
+
pubsub: gossipsub({
|
|
205
|
+
allowPublishToZeroTopicPeers: true,
|
|
206
|
+
D: 6,
|
|
207
|
+
Dlo: 4,
|
|
208
|
+
Dhi: 12,
|
|
209
|
+
heartbeatInterval: 1_000,
|
|
210
|
+
mcacheLength: 5,
|
|
211
|
+
mcacheGossip: 3,
|
|
212
|
+
}),
|
|
213
|
+
},
|
|
194
214
|
});
|
|
195
|
-
const protocolId = config.transactionProtocol;
|
|
196
215
|
|
|
197
216
|
// Create an LMDB peer store
|
|
198
217
|
const peerDb = new AztecPeerDb(store);
|
|
@@ -208,6 +227,47 @@ export class LibP2PService implements P2PService {
|
|
|
208
227
|
return new LibP2PService(config, node, peerDiscoveryService, peerDb, protocolId, txPool, bootstrapPeerIds);
|
|
209
228
|
}
|
|
210
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Subscribes to a topic.
|
|
232
|
+
* @param topic - The topic to subscribe to.
|
|
233
|
+
*/
|
|
234
|
+
private subscribeToTopic(topic: string) {
|
|
235
|
+
if (!this.node.services.pubsub) {
|
|
236
|
+
throw new Error('Pubsub service not available.');
|
|
237
|
+
}
|
|
238
|
+
void this.node.services.pubsub.subscribe(topic);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Publishes data to a topic.
|
|
243
|
+
* @param topic - The topic to publish to.
|
|
244
|
+
* @param data - The data to publish.
|
|
245
|
+
* @returns The number of recipients the data was sent to.
|
|
246
|
+
*/
|
|
247
|
+
private async publishToTopic(topic: string, data: Uint8Array) {
|
|
248
|
+
if (!this.node.services.pubsub) {
|
|
249
|
+
throw new Error('Pubsub service not available.');
|
|
250
|
+
}
|
|
251
|
+
const result = await this.node.services.pubsub.publish(topic, data);
|
|
252
|
+
|
|
253
|
+
return result.recipients.length;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Handles a new gossip message that was received by the client.
|
|
258
|
+
* @param topic - The message's topic.
|
|
259
|
+
* @param data - The message data
|
|
260
|
+
*/
|
|
261
|
+
private async handleNewGossipMessage(topic: string, data: Uint8Array) {
|
|
262
|
+
if (topic !== this.messageCreator.getTopic()) {
|
|
263
|
+
// Invalid TX Topic, ignore
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const tx = fromTxMessage(Buffer.from(data));
|
|
268
|
+
await this.processTxFromPeer(tx);
|
|
269
|
+
}
|
|
270
|
+
|
|
211
271
|
/**
|
|
212
272
|
* Propagates the provided transaction to peers.
|
|
213
273
|
* @param tx - The transaction to propagate.
|
|
@@ -243,7 +303,7 @@ export class LibP2PService implements P2PService {
|
|
|
243
303
|
|
|
244
304
|
// add to peer store if not already known
|
|
245
305
|
if (!hasPeer) {
|
|
246
|
-
this.logger.info(`Discovered peer ${
|
|
306
|
+
this.logger.info(`Discovered peer ${peerIdStr}. Adding to libp2p peer list`);
|
|
247
307
|
let stream: Stream | undefined;
|
|
248
308
|
try {
|
|
249
309
|
stream = await this.node.dialProtocol(peerMultiAddr, this.protocolId);
|
|
@@ -268,7 +328,7 @@ export class LibP2PService implements P2PService {
|
|
|
268
328
|
if (!message.length) {
|
|
269
329
|
this.logger.verbose(`Ignoring 0 byte message from peer${peer.toString()}`);
|
|
270
330
|
}
|
|
271
|
-
await this.
|
|
331
|
+
// await this.processTransactionMessage(message, peer);
|
|
272
332
|
} catch (err) {
|
|
273
333
|
this.logger.error(
|
|
274
334
|
`Failed to handle received message from peer ${incomingStreamData.connection.remotePeer.toString()}`,
|
|
@@ -289,151 +349,27 @@ export class LibP2PService implements P2PService {
|
|
|
289
349
|
return { message: buffer, peer: incomingStreamData.connection.remotePeer };
|
|
290
350
|
}
|
|
291
351
|
|
|
292
|
-
private handleNewConnection(peerId: PeerId) {
|
|
352
|
+
private async handleNewConnection(peerId: PeerId) {
|
|
293
353
|
if (this.isBootstrapPeer(peerId)) {
|
|
294
354
|
this.logger.verbose(`Connected to bootstrap peer ${peerId.toString()}`);
|
|
295
355
|
} else {
|
|
296
356
|
this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
|
|
297
|
-
|
|
298
|
-
void this.jobQueue.put(async () => {
|
|
299
|
-
await this.sendTxHashesMessageToPeer(peerId);
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private async processMessage(message: Buffer, peerId: PeerId) {
|
|
305
|
-
const type = message.readUInt32BE(0);
|
|
306
|
-
const encodedMessage = getEncodedMessage(message);
|
|
307
|
-
switch (type) {
|
|
308
|
-
case Messages.POOLED_TRANSACTIONS:
|
|
309
|
-
await this.processReceivedTxs(encodedMessage, peerId);
|
|
310
|
-
return;
|
|
311
|
-
case Messages.POOLED_TRANSACTION_HASHES:
|
|
312
|
-
await this.processReceivedTxHashes(encodedMessage, peerId);
|
|
313
|
-
return;
|
|
314
|
-
case Messages.GET_TRANSACTIONS:
|
|
315
|
-
await this.processReceivedGetTransactionsRequest(encodedMessage, peerId);
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
throw new Error(`Unknown message type ${type}`);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
private async processReceivedTxHashes(encodedMessage: Buffer, peerId: PeerId) {
|
|
322
|
-
try {
|
|
323
|
-
const txHashes = decodeTransactionHashesMessage(encodedMessage);
|
|
324
|
-
this.logger.debug(`Received tx hash messages from ${peerId.toString()}`);
|
|
325
|
-
// we send a message requesting the transactions that we don't have from the set of received hashes
|
|
326
|
-
const requiredHashes = txHashes.filter(hash => !this.txPool.hasTx(hash));
|
|
327
|
-
if (!requiredHashes.length) {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
await this.sendGetTransactionsMessageToPeer(txHashes, peerId);
|
|
331
|
-
} catch (err) {
|
|
332
|
-
this.logger.error(`Failed to process received tx hashes`, err);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private async processReceivedGetTransactionsRequest(encodedMessage: Buffer, peerId: PeerId) {
|
|
337
|
-
try {
|
|
338
|
-
this.logger.debug(`Received get txs messages from ${peerId.toString()}`);
|
|
339
|
-
// get the transactions in the list that we have and return them
|
|
340
|
-
const removeUndefined = <S>(value: S | undefined): value is S => value != undefined;
|
|
341
|
-
const txHashes = decodeGetTransactionsRequestMessage(encodedMessage);
|
|
342
|
-
const txs = txHashes.map(x => this.txPool.getTxByHash(x)).filter(removeUndefined);
|
|
343
|
-
if (!txs.length) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
await this.sendTransactionsMessageToPeer(txs, peerId);
|
|
347
|
-
} catch (err) {
|
|
348
|
-
this.logger.error(`Failed to process get txs request`, err);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
private async processReceivedTxs(encodedMessage: Buffer, peerId: PeerId) {
|
|
353
|
-
try {
|
|
354
|
-
const txs = decodeTransactionsMessage(encodedMessage);
|
|
355
|
-
// Could optimize here and process all txs at once
|
|
356
|
-
// Propagation would need to filter and send custom tx set per peer
|
|
357
|
-
for (const tx of txs) {
|
|
358
|
-
await this.processTxFromPeer(tx, peerId);
|
|
359
|
-
}
|
|
360
|
-
} catch (err) {
|
|
361
|
-
this.logger.error(`Failed to process pooled transactions message`, err);
|
|
357
|
+
await this.peerManager.updateDiscoveryService();
|
|
362
358
|
}
|
|
363
359
|
}
|
|
364
360
|
|
|
365
|
-
private async processTxFromPeer(tx: Tx
|
|
361
|
+
private async processTxFromPeer(tx: Tx): Promise<void> {
|
|
366
362
|
const txHash = tx.getTxHash();
|
|
367
363
|
const txHashString = txHash.toString();
|
|
368
|
-
this.
|
|
369
|
-
this.logger.debug(`Received tx ${txHashString} from peer ${peerId.toString()}`);
|
|
364
|
+
this.logger.debug(`Received tx ${txHashString} from external peer.`);
|
|
370
365
|
await this.txPool.addTxs([tx]);
|
|
371
|
-
this.propagateTx(tx);
|
|
372
366
|
}
|
|
373
367
|
|
|
374
368
|
private async sendTxToPeers(tx: Tx) {
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
const txHashString = txHash.toString();
|
|
380
|
-
for (const peer of peers) {
|
|
381
|
-
try {
|
|
382
|
-
if (this.knownTxLookup.hasPeerSeenTx(peer, txHashString)) {
|
|
383
|
-
this.logger.debug(`Not sending tx ${txHashString} to peer ${peer.toString()} as they have already seen it`);
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
this.logger.debug(`Sending tx ${txHashString} to peer ${peer.toString()}`);
|
|
387
|
-
await this.sendRawMessageToPeer(payload, peer);
|
|
388
|
-
this.knownTxLookup.addPeerForTx(peer, txHashString);
|
|
389
|
-
} catch (err) {
|
|
390
|
-
this.logger.error(`Failed to send txs to peer ${peer.toString()}`, err);
|
|
391
|
-
continue;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
private async sendTxHashesMessageToPeer(peer: PeerId) {
|
|
397
|
-
try {
|
|
398
|
-
const hashes = this.txPool.getAllTxHashes();
|
|
399
|
-
if (!hashes.length) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
const message = createTransactionHashesMessage(hashes);
|
|
403
|
-
await this.sendRawMessageToPeer(new Uint8Array(message), peer);
|
|
404
|
-
} catch (err) {
|
|
405
|
-
this.logger.error(`Failed to send tx hashes to peer ${peer.toString()}`, err);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
private async sendGetTransactionsMessageToPeer(hashes: TxHash[], peer: PeerId) {
|
|
410
|
-
try {
|
|
411
|
-
const message = createGetTransactionsRequestMessage(hashes);
|
|
412
|
-
await this.sendRawMessageToPeer(new Uint8Array(message), peer);
|
|
413
|
-
} catch (err) {
|
|
414
|
-
this.logger.error(`Failed to send tx request to peer ${peer.toString()}`, err);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
private async sendTransactionsMessageToPeer(txs: Tx[], peer: PeerId) {
|
|
419
|
-
// don't filter out any transactions based on what we think the peer has seen,
|
|
420
|
-
// we have been explicitly asked for these transactions
|
|
421
|
-
const message = createTransactionsMessage(txs);
|
|
422
|
-
await this.sendRawMessageToPeer(message, peer);
|
|
423
|
-
for (const tx of txs) {
|
|
424
|
-
const hash = tx.getTxHash();
|
|
425
|
-
this.knownTxLookup.addPeerForTx(peer, hash.toString());
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
private async sendRawMessageToPeer(message: Uint8Array, peer: PeerId) {
|
|
430
|
-
const stream = await this.node.dialProtocol(peer, this.protocolId);
|
|
431
|
-
await pipe([message], stream);
|
|
432
|
-
await stream.close();
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
private getTxPeers() {
|
|
436
|
-
return this.node.getPeers().filter(peer => !this.isBootstrapPeer(peer));
|
|
369
|
+
const { data: txData } = this.messageCreator.createTxMessage(tx);
|
|
370
|
+
this.logger.debug(`Sending tx ${tx.getTxHash().toString()} to peers`);
|
|
371
|
+
const recipientsNum = await this.publishToTopic(this.messageCreator.getTopic(), txData);
|
|
372
|
+
this.logger.debug(`Sent tx ${tx.getTxHash().toString()} to ${recipientsNum} peers`);
|
|
437
373
|
}
|
|
438
374
|
|
|
439
375
|
private isBootstrapPeer(peer: PeerId) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
2
|
+
|
|
3
|
+
import { type Libp2p } from 'libp2p';
|
|
4
|
+
|
|
5
|
+
import { type P2PConfig } from '../config.js';
|
|
6
|
+
import { type PeerDiscoveryService, PeerDiscoveryState } from './service.js';
|
|
7
|
+
|
|
8
|
+
export class PeerManager {
|
|
9
|
+
constructor(
|
|
10
|
+
private libP2PNode: Libp2p,
|
|
11
|
+
private discV5Node: PeerDiscoveryService,
|
|
12
|
+
private config: P2PConfig,
|
|
13
|
+
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();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/service/service.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { Tx, TxHash } from '@aztec/circuit-types';
|
|
|
3
3
|
import type { ENR } from '@chainsafe/enr';
|
|
4
4
|
import type EventEmitter from 'events';
|
|
5
5
|
|
|
6
|
+
export enum PeerDiscoveryState {
|
|
7
|
+
RUNNING = 'running',
|
|
8
|
+
STOPPED = 'stopped',
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* The interface for a P2P service implementation.
|
|
8
13
|
*/
|
|
@@ -57,4 +62,6 @@ export interface PeerDiscoveryService extends EventEmitter {
|
|
|
57
62
|
*/
|
|
58
63
|
on(event: 'peer:discovered', listener: (enr: ENR) => void): this;
|
|
59
64
|
emit(event: 'peer:discovered', enr: ENR): boolean;
|
|
65
|
+
|
|
66
|
+
getStatus(): PeerDiscoveryState;
|
|
60
67
|
}
|
|
@@ -1,34 +1,26 @@
|
|
|
1
|
-
import { EncryptedTxL2Logs, Tx,
|
|
1
|
+
import { EncryptedTxL2Logs, Tx, UnencryptedTxL2Logs } from '@aztec/circuit-types';
|
|
2
2
|
import { PrivateKernelTailCircuitPublicInputs, Proof, PublicCallRequest } from '@aztec/circuits.js';
|
|
3
3
|
import { numToUInt32BE } from '@aztec/foundation/serialize';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* Enumeration of P2P message types.
|
|
7
|
-
*/
|
|
8
|
-
export enum Messages {
|
|
9
|
-
POOLED_TRANSACTIONS = 1,
|
|
10
|
-
POOLED_TRANSACTION_HASHES = 2,
|
|
11
|
-
GET_TRANSACTIONS = 3,
|
|
12
|
-
}
|
|
5
|
+
import { type SemVer } from 'semver';
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
* Create a P2P message from the message type and message data.
|
|
16
|
-
* @param type - The type of the message.
|
|
17
|
-
* @param messageData - The binary message data.
|
|
18
|
-
* @returns The encoded message.
|
|
19
|
-
*/
|
|
20
|
-
export function createMessage(type: Messages, messageData: Buffer) {
|
|
21
|
-
return Buffer.concat([numToUInt32BE(type), messageData]);
|
|
22
|
-
}
|
|
7
|
+
export const TX_MESSAGE_TOPIC = '';
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
9
|
+
export class AztecTxMessageCreator {
|
|
10
|
+
private readonly topic: string;
|
|
11
|
+
constructor(version: SemVer) {
|
|
12
|
+
this.topic = `/aztec/tx/${version.toString()}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
createTxMessage(tx: Tx) {
|
|
16
|
+
const messageData = toTxMessage(tx);
|
|
17
|
+
|
|
18
|
+
return { topic: this.topic, data: messageData };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getTopic() {
|
|
22
|
+
return this.topic;
|
|
23
|
+
}
|
|
32
24
|
}
|
|
33
25
|
|
|
34
26
|
/**
|
|
@@ -49,73 +41,6 @@ export function decodeTransactionsMessage(message: Buffer) {
|
|
|
49
41
|
return txs;
|
|
50
42
|
}
|
|
51
43
|
|
|
52
|
-
/**
|
|
53
|
-
* Create a POOLED_TRANSACTION_HASHES message.
|
|
54
|
-
* @param hashes - The transaction hashes to be sent.
|
|
55
|
-
* @returns The encoded message.
|
|
56
|
-
*/
|
|
57
|
-
export function createTransactionHashesMessage(hashes: TxHash[]) {
|
|
58
|
-
const messageData = hashes.map(x => x.buffer);
|
|
59
|
-
return createMessage(Messages.POOLED_TRANSACTION_HASHES, Buffer.concat(messageData));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Decode a POOLED_TRANSACTION_HASHESs message ito the original transaction hash objects.
|
|
64
|
-
* @param message - The binary message to be decoded.
|
|
65
|
-
* @returns - The array of transaction hashes originally encoded into the message.
|
|
66
|
-
*/
|
|
67
|
-
export function decodeTransactionHashesMessage(message: Buffer) {
|
|
68
|
-
let offset = 0;
|
|
69
|
-
const txHashes: TxHash[] = [];
|
|
70
|
-
while (offset < message.length) {
|
|
71
|
-
const slice = message.subarray(offset, offset + TxHash.SIZE);
|
|
72
|
-
if (slice.length < TxHash.SIZE) {
|
|
73
|
-
throw new Error(`Invalid message size when processing transaction hashes message`);
|
|
74
|
-
}
|
|
75
|
-
txHashes.push(new TxHash(slice));
|
|
76
|
-
offset += TxHash.SIZE;
|
|
77
|
-
}
|
|
78
|
-
return txHashes;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create a GET_TRANSACTIONS message from an array of transaction hashes.
|
|
83
|
-
* @param hashes - The hashes of the transactions to be requested.
|
|
84
|
-
* @returns The encoded message.
|
|
85
|
-
*/
|
|
86
|
-
export function createGetTransactionsRequestMessage(hashes: TxHash[]) {
|
|
87
|
-
const messageData = hashes.map(x => x.buffer);
|
|
88
|
-
return createMessage(Messages.GET_TRANSACTIONS, Buffer.concat(messageData));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Decode a GET_TRANSACTIONS message into the original transaction hash objects.
|
|
93
|
-
* @param message - The binary message to be decoded.
|
|
94
|
-
* @returns - The array of transaction hashes originally encoded into the message.
|
|
95
|
-
*/
|
|
96
|
-
export function decodeGetTransactionsRequestMessage(message: Buffer) {
|
|
97
|
-
// for the time being this payload is effectively the same as the POOLED_TRANSACTION_HASHES message
|
|
98
|
-
return decodeTransactionHashesMessage(message);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Decode the message type from a received message.
|
|
103
|
-
* @param message - The received message.
|
|
104
|
-
* @returns The decoded MessageType.
|
|
105
|
-
*/
|
|
106
|
-
export function decodeMessageType(message: Buffer) {
|
|
107
|
-
return message.readUInt32BE(0);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Return the encoded message (minus the header) from received message buffer.
|
|
112
|
-
* @param message - The complete received message.
|
|
113
|
-
* @returns The encoded message, without the header.
|
|
114
|
-
*/
|
|
115
|
-
export function getEncodedMessage(message: Buffer) {
|
|
116
|
-
return message.subarray(4);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
44
|
/**
|
|
120
45
|
* Creates a tx 'message' for sending to a peer.
|
|
121
46
|
* @param tx - The transaction to convert to a message.
|