@aztec/p2p 0.42.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.
- package/dest/bootstrap/bootstrap.d.ts +1 -1
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +8 -4
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +24 -21
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +2 -3
- package/dest/config.d.ts +9 -22
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -9
- 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 +38 -12
- 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 +5 -14
- package/dest/service/libp2p_service.d.ts.map +1 -1
- package/dest/service/libp2p_service.js +56 -97
- 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/dest/util.d.ts +21 -0
- package/dest/util.d.ts.map +1 -0
- package/dest/util.js +59 -0
- package/package.json +15 -6
- package/src/bootstrap/bootstrap.ts +11 -5
- package/src/client/index.ts +28 -21
- package/src/client/p2p_client.ts +1 -2
- package/src/config.ts +19 -39
- package/src/service/discV5_service.ts +45 -15
- package/src/service/dummy_service.ts +10 -1
- package/src/service/libp2p_service.ts +60 -108
- 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/src/util.ts +62 -0
- package/dest/service/ip_query.d.ts +0 -2
- package/dest/service/ip_query.d.ts.map +0 -1
- package/dest/service/ip_query.js +0 -6
- 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/ip_query.ts +0 -5
- package/src/service/known_txs.ts +0 -56
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
2
|
-
import {
|
|
2
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
3
3
|
|
|
4
4
|
import { Discv5, type Discv5EventEmitter } from '@chainsafe/discv5';
|
|
5
|
-
import {
|
|
5
|
+
import { ENR, SignableENR } from '@chainsafe/enr';
|
|
6
6
|
import type { PeerId } from '@libp2p/interface';
|
|
7
7
|
import { multiaddr } from '@multiformats/multiaddr';
|
|
8
8
|
import EventEmitter from 'events';
|
|
9
9
|
|
|
10
10
|
import type { P2PConfig } from '../config.js';
|
|
11
|
+
import { convertToMultiaddr } from '../util.js';
|
|
11
12
|
import { type PeerDiscoveryService, PeerDiscoveryState } from './service.js';
|
|
12
13
|
|
|
13
14
|
export const AZTEC_ENR_KEY = 'aztec_network';
|
|
14
15
|
|
|
16
|
+
const delayBeforeStart = 2000; // 2sec
|
|
17
|
+
|
|
15
18
|
export enum AztecENR {
|
|
16
19
|
devnet = 0x01,
|
|
17
20
|
testnet = 0x02,
|
|
@@ -31,26 +34,33 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
31
34
|
/** This instance's ENR */
|
|
32
35
|
private enr: SignableENR;
|
|
33
36
|
|
|
34
|
-
private runningPromise: RunningPromise;
|
|
35
|
-
|
|
36
37
|
private currentState = PeerDiscoveryState.STOPPED;
|
|
37
38
|
|
|
38
39
|
private bootstrapNodes: string[];
|
|
40
|
+
private bootstrapNodePeerIds: PeerId[] = [];
|
|
41
|
+
|
|
42
|
+
private startTime = 0;
|
|
39
43
|
|
|
40
44
|
constructor(private peerId: PeerId, config: P2PConfig, private logger = createDebugLogger('aztec:discv5_service')) {
|
|
41
45
|
super();
|
|
42
|
-
const {
|
|
43
|
-
config;
|
|
46
|
+
const { tcpAnnounceAddress, udpAnnounceAddress, udpListenAddress, bootstrapNodes } = config;
|
|
44
47
|
this.bootstrapNodes = bootstrapNodes;
|
|
45
48
|
// create ENR from PeerId
|
|
46
49
|
this.enr = SignableENR.createFromPeerId(peerId);
|
|
47
50
|
// Add aztec identification to ENR
|
|
48
51
|
this.enr.set(AZTEC_ENR_KEY, Uint8Array.from([AZTEC_NET]));
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if (!tcpAnnounceAddress) {
|
|
54
|
+
throw new Error('You need to provide at least a TCP announce address.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const multiAddrTcp = multiaddr(`${convertToMultiaddr(tcpAnnounceAddress, 'tcp')}/p2p/${peerId.toString()}`);
|
|
58
|
+
// if no udp announce address is provided, use the tcp announce address
|
|
59
|
+
const multiAddrUdp = multiaddr(
|
|
60
|
+
`${convertToMultiaddr(udpAnnounceAddress || tcpAnnounceAddress, 'udp')}/p2p/${peerId.toString()}`,
|
|
61
|
+
);
|
|
52
62
|
|
|
53
|
-
const listenMultiAddrUdp = multiaddr(
|
|
63
|
+
const listenMultiAddrUdp = multiaddr(convertToMultiaddr(udpListenAddress, 'udp'));
|
|
54
64
|
|
|
55
65
|
// set location multiaddr in ENR record
|
|
56
66
|
this.enr.setLocationMultiaddr(multiAddrUdp);
|
|
@@ -75,18 +85,18 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
75
85
|
const multiAddrUdp = await enr.getFullMultiaddr('udp');
|
|
76
86
|
this.logger.debug(`ENR multiaddr: ${multiAddrTcp?.toString()}, ${multiAddrUdp?.toString()}`);
|
|
77
87
|
});
|
|
78
|
-
|
|
79
|
-
this.runningPromise = new RunningPromise(async () => {
|
|
80
|
-
await this.discv5.findRandomNode();
|
|
81
|
-
}, config.p2pPeerCheckIntervalMS);
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
public async start(): Promise<void> {
|
|
91
|
+
// Do this conversion once since it involves an async function call
|
|
92
|
+
this.bootstrapNodePeerIds = await Promise.all(this.bootstrapNodes.map(enr => ENR.decodeTxt(enr).peerId()));
|
|
85
93
|
if (this.currentState === PeerDiscoveryState.RUNNING) {
|
|
86
94
|
throw new Error('DiscV5Service already started');
|
|
87
95
|
}
|
|
88
96
|
this.logger.info('Starting DiscV5');
|
|
89
97
|
await this.discv5.start();
|
|
98
|
+
this.startTime = Date.now();
|
|
99
|
+
|
|
90
100
|
this.logger.info('DiscV5 started');
|
|
91
101
|
this.currentState = PeerDiscoveryState.RUNNING;
|
|
92
102
|
|
|
@@ -101,8 +111,25 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
101
111
|
this.logger.error(`Error adding bootnode ENRs: ${e}`);
|
|
102
112
|
}
|
|
103
113
|
}
|
|
114
|
+
}
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
public async runRandomNodesQuery(): Promise<void> {
|
|
117
|
+
if (this.currentState !== PeerDiscoveryState.RUNNING) {
|
|
118
|
+
throw new Error('DiscV5Service not running');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// First, wait some time before starting the peer discovery
|
|
122
|
+
// reference: https://github.com/ChainSafe/lodestar/issues/3423
|
|
123
|
+
const msSinceStart = Date.now() - this.startTime;
|
|
124
|
+
if (Date.now() - this.startTime <= delayBeforeStart) {
|
|
125
|
+
await sleep(delayBeforeStart - msSinceStart);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await this.discv5.findRandomNode();
|
|
130
|
+
} catch (err) {
|
|
131
|
+
this.logger.error(`Error running discV5 random node query: ${err}`);
|
|
132
|
+
}
|
|
106
133
|
}
|
|
107
134
|
|
|
108
135
|
public getAllPeers(): ENR[] {
|
|
@@ -121,8 +148,11 @@ export class DiscV5Service extends EventEmitter implements PeerDiscoveryService
|
|
|
121
148
|
return this.currentState;
|
|
122
149
|
}
|
|
123
150
|
|
|
151
|
+
public isBootstrapPeer(peerId: PeerId): boolean {
|
|
152
|
+
return this.bootstrapNodePeerIds.some(node => node.equals(peerId));
|
|
153
|
+
}
|
|
154
|
+
|
|
124
155
|
public async stop(): Promise<void> {
|
|
125
|
-
await this.runningPromise.stop();
|
|
126
156
|
await this.discv5.stop();
|
|
127
157
|
this.currentState = PeerDiscoveryState.STOPPED;
|
|
128
158
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Tx, TxHash } from '@aztec/circuit-types';
|
|
2
2
|
|
|
3
|
+
import type { PeerId } from '@libp2p/interface';
|
|
3
4
|
import EventEmitter from 'events';
|
|
4
5
|
|
|
5
6
|
import { type P2PService, type PeerDiscoveryService, PeerDiscoveryState } from './service.js';
|
|
@@ -66,6 +67,14 @@ export class DummyPeerDiscoveryService extends EventEmitter implements PeerDisco
|
|
|
66
67
|
return [];
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
public runRandomNodesQuery(): Promise<void> {
|
|
71
|
+
return Promise.resolve();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public isBootstrapPeer(_: PeerId): boolean {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
69
78
|
public getStatus(): PeerDiscoveryState {
|
|
70
79
|
return this.currentState;
|
|
71
80
|
}
|
|
@@ -1,25 +1,24 @@
|
|
|
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';
|
|
18
17
|
|
|
19
18
|
import { type P2PConfig } from '../config.js';
|
|
20
19
|
import { type TxPool } from '../tx_pool/index.js';
|
|
20
|
+
import { convertToMultiaddr } from '../util.js';
|
|
21
21
|
import { AztecDatastore } from './data_store.js';
|
|
22
|
-
import { KnownTxLookup } from './known_txs.js';
|
|
23
22
|
import { PeerManager } from './peer_manager.js';
|
|
24
23
|
import type { P2PService, PeerDiscoveryService } from './service.js';
|
|
25
24
|
import { AztecTxMessageCreator, fromTxMessage } from './tx_messages.js';
|
|
@@ -29,7 +28,6 @@ export interface PubSubLibp2p extends Libp2p {
|
|
|
29
28
|
pubsub: PubSub<GossipsubEvents>;
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
|
-
|
|
33
31
|
/**
|
|
34
32
|
* Create a libp2p peer ID from the private key if provided, otherwise creates a new random ID.
|
|
35
33
|
* @param privateKey - Optional peer ID private key as hex string
|
|
@@ -51,16 +49,14 @@ export async function createLibP2PPeerId(privateKey?: string): Promise<PeerId> {
|
|
|
51
49
|
*/
|
|
52
50
|
export class LibP2PService implements P2PService {
|
|
53
51
|
private jobQueue: SerialQueue = new SerialQueue();
|
|
54
|
-
private knownTxLookup: KnownTxLookup = new KnownTxLookup();
|
|
55
52
|
private messageCreator: AztecTxMessageCreator;
|
|
56
53
|
private peerManager: PeerManager;
|
|
54
|
+
private discoveryRunningPromise?: RunningPromise;
|
|
57
55
|
constructor(
|
|
58
56
|
private config: P2PConfig,
|
|
59
57
|
private node: PubSubLibp2p,
|
|
60
58
|
private peerDiscoveryService: PeerDiscoveryService,
|
|
61
|
-
private protocolId: string,
|
|
62
59
|
private txPool: TxPool,
|
|
63
|
-
private bootstrapPeerIds: PeerId[] = [],
|
|
64
60
|
private logger = createDebugLogger('aztec:libp2p_service'),
|
|
65
61
|
) {
|
|
66
62
|
this.messageCreator = new AztecTxMessageCreator(config.txGossipVersion);
|
|
@@ -72,53 +68,42 @@ export class LibP2PService implements P2PService {
|
|
|
72
68
|
* @returns An empty promise.
|
|
73
69
|
*/
|
|
74
70
|
public async start() {
|
|
71
|
+
// Check if service is already started
|
|
75
72
|
if (this.node.status === 'started') {
|
|
76
73
|
throw new Error('P2P service already started');
|
|
77
74
|
}
|
|
78
|
-
const { enableNat, tcpListenIp, tcpListenPort, announceTcpHostname, announcePort } = this.config;
|
|
79
|
-
this.logger.info(`Starting P2P node on ${tcpListenIp}:${tcpListenPort}`);
|
|
80
|
-
if (announceTcpHostname) {
|
|
81
|
-
this.logger.info(`Announcing at ${announceTcpHostname}/tcp/${announcePort ?? tcpListenPort}`);
|
|
82
|
-
}
|
|
83
|
-
if (enableNat) {
|
|
84
|
-
this.logger.info(`Enabling NAT in libp2p module`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// handle discovered peers from external discovery service
|
|
88
|
-
this.peerDiscoveryService.on('peer:discovered', async (enr: ENR) => {
|
|
89
|
-
await this.addPeer(enr);
|
|
90
|
-
});
|
|
91
75
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.logger.verbose(`Disconnect from bootstrap peer ${peerId.toString()}`);
|
|
101
|
-
} else {
|
|
102
|
-
this.logger.verbose(`Disconnected from transaction peer ${peerId.toString()}`);
|
|
103
|
-
await this.peerManager.updateDiscoveryService();
|
|
104
|
-
}
|
|
105
|
-
});
|
|
76
|
+
// Log listen & announce addresses
|
|
77
|
+
const { tcpListenAddress, tcpAnnounceAddress } = this.config;
|
|
78
|
+
this.logger.info(`Starting P2P node on ${tcpListenAddress}`);
|
|
79
|
+
if (!tcpAnnounceAddress) {
|
|
80
|
+
throw new Error('Announce address not provided.');
|
|
81
|
+
}
|
|
82
|
+
const announceTcpMultiaddr = convertToMultiaddr(tcpAnnounceAddress, 'tcp');
|
|
83
|
+
this.logger.info(`Announcing at ${announceTcpMultiaddr}`);
|
|
106
84
|
|
|
85
|
+
// Start job queue, peer discovery service and libp2p node
|
|
107
86
|
this.jobQueue.start();
|
|
108
87
|
await this.peerDiscoveryService.start();
|
|
109
88
|
await this.node.start();
|
|
110
89
|
this.logger.info(`Started P2P client with Peer ID ${this.node.peerId.toString()}`);
|
|
111
90
|
|
|
112
|
-
// Subscribe to standard topics by default
|
|
91
|
+
// Subscribe to standard GossipSub topics by default
|
|
113
92
|
this.subscribeToTopic(this.messageCreator.getTopic());
|
|
114
93
|
|
|
115
|
-
// add
|
|
94
|
+
// add GossipSub listener
|
|
116
95
|
this.node.services.pubsub.addEventListener('gossipsub:message', async e => {
|
|
117
96
|
const { msg } = e.detail;
|
|
118
97
|
this.logger.debug(`Received PUBSUB message.`);
|
|
119
98
|
|
|
120
99
|
await this.jobQueue.put(() => this.handleNewGossipMessage(msg.topic, msg.data));
|
|
121
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();
|
|
122
107
|
}
|
|
123
108
|
|
|
124
109
|
/**
|
|
@@ -128,8 +113,12 @@ export class LibP2PService implements P2PService {
|
|
|
128
113
|
public async stop() {
|
|
129
114
|
this.logger.debug('Stopping job queue...');
|
|
130
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();
|
|
131
120
|
this.logger.debug('Stopping LibP2P...');
|
|
132
|
-
await this.
|
|
121
|
+
await this.stopLibP2P();
|
|
133
122
|
this.logger.info('LibP2P service stopped');
|
|
134
123
|
}
|
|
135
124
|
|
|
@@ -146,8 +135,12 @@ export class LibP2PService implements P2PService {
|
|
|
146
135
|
txPool: TxPool,
|
|
147
136
|
store: AztecKVStore,
|
|
148
137
|
) {
|
|
149
|
-
const {
|
|
150
|
-
const bindAddrTcp =
|
|
138
|
+
const { tcpListenAddress, tcpAnnounceAddress, minPeerCount, maxPeerCount } = config;
|
|
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');
|
|
142
|
+
|
|
143
|
+
const datastore = new AztecDatastore(store);
|
|
151
144
|
|
|
152
145
|
// The autonat service seems quite problematic in that using it seems to cause a lot of attempts
|
|
153
146
|
// to dial ephemeral ports. I suspect that it works better if you can get the uPNPnat service to
|
|
@@ -163,17 +156,24 @@ export class LibP2PService implements P2PService {
|
|
|
163
156
|
// services.uPnPNAT = uPnPNATService();
|
|
164
157
|
// }
|
|
165
158
|
|
|
166
|
-
const datastore = new AztecDatastore(store);
|
|
167
|
-
|
|
168
159
|
const node = await createLibp2p({
|
|
169
160
|
start: false,
|
|
170
161
|
peerId,
|
|
171
162
|
addresses: {
|
|
172
163
|
listen: [bindAddrTcp],
|
|
164
|
+
announce: [announceAddrTcp],
|
|
173
165
|
},
|
|
174
166
|
transports: [
|
|
175
167
|
tcp({
|
|
176
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
|
+
},
|
|
177
177
|
}),
|
|
178
178
|
],
|
|
179
179
|
datastore,
|
|
@@ -199,15 +199,7 @@ export class LibP2PService implements P2PService {
|
|
|
199
199
|
},
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
let bootstrapPeerIds: PeerId[] = [];
|
|
204
|
-
if (config.bootstrapNodes.length) {
|
|
205
|
-
bootstrapPeerIds = await Promise.all(
|
|
206
|
-
config.bootstrapNodes.map(bootnodeEnr => ENR.decodeTxt(bootnodeEnr).peerId()),
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return new LibP2PService(config, node, peerDiscoveryService, protocolId, txPool, bootstrapPeerIds);
|
|
202
|
+
return new LibP2PService(config, node, peerDiscoveryService, txPool);
|
|
211
203
|
}
|
|
212
204
|
|
|
213
205
|
/**
|
|
@@ -259,71 +251,31 @@ export class LibP2PService implements P2PService {
|
|
|
259
251
|
void this.jobQueue.put(() => Promise.resolve(this.sendTxToPeers(tx)));
|
|
260
252
|
}
|
|
261
253
|
|
|
262
|
-
/**
|
|
263
|
-
* Handles the settling of a new batch of transactions.
|
|
264
|
-
* @param txHashes - The hashes of the newly settled transactions.
|
|
265
|
-
*/
|
|
266
|
-
public settledTxs(txHashes: TxHash[]): void {
|
|
267
|
-
this.knownTxLookup.handleSettledTxs(txHashes.map(x => x.toString()));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private async addPeer(enr: ENR) {
|
|
271
|
-
const peerMultiAddr = await enr.getFullMultiaddr('tcp');
|
|
272
|
-
if (!peerMultiAddr) {
|
|
273
|
-
// No TCP address, can't connect
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
const peerIdStr = peerMultiAddr.getPeerId();
|
|
277
|
-
|
|
278
|
-
if (!peerIdStr) {
|
|
279
|
-
this.logger.debug(`Peer ID not found in discovered node's multiaddr: ${peerMultiAddr}`);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// check if peer is already known
|
|
284
|
-
const peerId = peerIdFromString(peerIdStr);
|
|
285
|
-
const hasPeer = await this.node.peerStore.has(peerId);
|
|
286
|
-
|
|
287
|
-
// add to peer store if not already known
|
|
288
|
-
if (!hasPeer) {
|
|
289
|
-
this.logger.info(`Discovered peer ${peerIdStr}. Adding to libp2p peer list`);
|
|
290
|
-
let stream: Stream | undefined;
|
|
291
|
-
try {
|
|
292
|
-
stream = await this.node.dialProtocol(peerMultiAddr, this.protocolId);
|
|
293
|
-
} catch (err) {
|
|
294
|
-
this.logger.error(`Failed to dial peer ${peerIdStr}`, err);
|
|
295
|
-
} finally {
|
|
296
|
-
if (stream) {
|
|
297
|
-
await stream.close();
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private async handleNewConnection(peerId: PeerId) {
|
|
304
|
-
if (this.isBootstrapPeer(peerId)) {
|
|
305
|
-
this.logger.verbose(`Connected to bootstrap peer ${peerId.toString()}`);
|
|
306
|
-
} else {
|
|
307
|
-
this.logger.verbose(`Connected to transaction peer ${peerId.toString()}`);
|
|
308
|
-
await this.peerManager.updateDiscoveryService();
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
254
|
private async processTxFromPeer(tx: Tx): Promise<void> {
|
|
313
255
|
const txHash = tx.getTxHash();
|
|
314
256
|
const txHashString = txHash.toString();
|
|
315
|
-
this.logger.
|
|
257
|
+
this.logger.verbose(`Received tx ${txHashString} from external peer.`);
|
|
316
258
|
await this.txPool.addTxs([tx]);
|
|
317
259
|
}
|
|
318
260
|
|
|
319
261
|
private async sendTxToPeers(tx: Tx) {
|
|
320
262
|
const { data: txData } = this.messageCreator.createTxMessage(tx);
|
|
321
|
-
this.logger.
|
|
263
|
+
this.logger.verbose(`Sending tx ${tx.getTxHash().toString()} to peers`);
|
|
322
264
|
const recipientsNum = await this.publishToTopic(this.messageCreator.getTopic(), txData);
|
|
323
|
-
this.logger.
|
|
265
|
+
this.logger.verbose(`Sent tx ${tx.getTxHash().toString()} to ${recipientsNum} peers`);
|
|
324
266
|
}
|
|
325
267
|
|
|
326
|
-
|
|
327
|
-
|
|
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
|
+
}
|
|
328
280
|
}
|
|
329
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
|
*/
|