@aztec/p2p 0.85.0 → 0.86.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 +6 -3
- package/dest/client/p2p_client.d.ts +41 -3
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +58 -18
- package/dest/config.d.ts +13 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +15 -3
- package/dest/enr/generate-enr.d.ts +1 -1
- package/dest/enr/generate-enr.d.ts.map +1 -1
- package/dest/enr/generate-enr.js +2 -2
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +11 -11
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +2 -2
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +4 -4
- package/dest/mem_pools/attestation_pool/mocks.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/mocks.js +2 -2
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +3 -0
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +18 -0
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +3 -0
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/memory_tx_pool.js +9 -0
- package/dest/mem_pools/tx_pool/tx_pool.d.ts +17 -0
- package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +57 -0
- package/dest/msg_validators/attestation_validator/attestation_validator.js +1 -1
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +1 -0
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +6 -1
- package/dest/services/discv5/discV5_service.d.ts +2 -1
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +22 -7
- package/dest/services/dummy_service.d.ts +2 -2
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +2 -2
- package/dest/services/libp2p/libp2p_service.d.ts +3 -1
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +28 -12
- package/dest/services/peer-manager/peer_manager.d.ts +21 -2
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +63 -18
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +2 -2
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +17 -1
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -1
- package/dest/services/reqresp/connection-sampler/connection_sampler.js +84 -36
- package/dest/services/reqresp/reqresp.d.ts +2 -2
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +7 -2
- package/dest/services/reqresp/status.d.ts +2 -1
- package/dest/services/reqresp/status.d.ts.map +1 -1
- package/dest/services/reqresp/status.js +3 -0
- package/dest/services/service.d.ts +4 -4
- package/dest/services/service.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.d.ts +6 -1
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +19 -2
- package/dest/testbench/p2p_client_testbench_worker.js +4 -1
- package/dest/util.d.ts.map +1 -1
- package/dest/util.js +5 -1
- package/package.json +12 -14
- package/src/bootstrap/bootstrap.ts +8 -4
- package/src/client/p2p_client.ts +212 -131
- package/src/config.ts +34 -4
- package/src/enr/generate-enr.ts +2 -2
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +11 -15
- package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +2 -2
- package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +4 -4
- package/src/mem_pools/attestation_pool/mocks.ts +3 -3
- package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +21 -0
- package/src/mem_pools/tx_pool/memory_tx_pool.ts +11 -0
- package/src/mem_pools/tx_pool/tx_pool.ts +20 -0
- package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +43 -0
- package/src/msg_validators/attestation_validator/attestation_validator.ts +1 -1
- package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +10 -1
- package/src/services/discv5/discV5_service.ts +32 -6
- package/src/services/dummy_service.ts +2 -2
- package/src/services/libp2p/libp2p_service.ts +37 -12
- package/src/services/peer-manager/peer_manager.ts +79 -22
- package/src/services/reqresp/connection-sampler/batch_connection_sampler.ts +2 -2
- package/src/services/reqresp/connection-sampler/connection_sampler.ts +82 -41
- package/src/services/reqresp/reqresp.ts +12 -6
- package/src/services/reqresp/status.ts +3 -0
- package/src/services/service.ts +4 -4
- package/src/test-helpers/make-test-p2p-clients.ts +20 -2
- package/src/testbench/p2p_client_testbench_worker.ts +3 -0
- package/src/util.ts +6 -1
|
@@ -15,12 +15,14 @@ import {
|
|
|
15
15
|
type Gossipable,
|
|
16
16
|
P2PClientType,
|
|
17
17
|
PeerErrorSeverity,
|
|
18
|
-
|
|
18
|
+
TopicType,
|
|
19
|
+
createTopicString,
|
|
19
20
|
getTopicTypeForClientType,
|
|
20
21
|
metricsTopicStrToLabels,
|
|
21
22
|
} from '@aztec/stdlib/p2p';
|
|
22
23
|
import { DatabasePublicStateSource, MerkleTreeId } from '@aztec/stdlib/trees';
|
|
23
24
|
import { Tx, type TxHash, type TxValidationResult } from '@aztec/stdlib/tx';
|
|
25
|
+
import { compressComponentVersions } from '@aztec/stdlib/versioning';
|
|
24
26
|
import { Attributes, OtelMetricsAdapter, type TelemetryClient, WithTracer, trackSpan } from '@aztec/telemetry-client';
|
|
25
27
|
|
|
26
28
|
import type { ENR } from '@chainsafe/enr';
|
|
@@ -57,6 +59,7 @@ import {
|
|
|
57
59
|
} from '../../msg_validators/tx_validator/index.js';
|
|
58
60
|
import { GossipSubEvent } from '../../types/index.js';
|
|
59
61
|
import { type PubSubLibp2p, convertToMultiaddr } from '../../util.js';
|
|
62
|
+
import { getVersions } from '../../versioning.js';
|
|
60
63
|
import { AztecDatastore } from '../data_store.js';
|
|
61
64
|
import { SnappyTransform, fastMsgIdFn, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
|
|
62
65
|
import { gossipScoreThresholds } from '../gossipsub/scoring.js';
|
|
@@ -95,6 +98,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
95
98
|
private attestationValidator: AttestationValidator;
|
|
96
99
|
private blockProposalValidator: BlockProposalValidator;
|
|
97
100
|
|
|
101
|
+
private protocolVersion = '';
|
|
102
|
+
private topicStrings: Record<TopicType, string> = {} as Record<TopicType, string>;
|
|
103
|
+
|
|
98
104
|
// Request and response sub service
|
|
99
105
|
public reqresp: ReqResp;
|
|
100
106
|
|
|
@@ -127,6 +133,17 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
127
133
|
) {
|
|
128
134
|
super(telemetry, 'LibP2PService');
|
|
129
135
|
|
|
136
|
+
const versions = getVersions(config);
|
|
137
|
+
this.protocolVersion = compressComponentVersions(versions);
|
|
138
|
+
logger.info(`Started libp2p service with protocol version ${this.protocolVersion}`);
|
|
139
|
+
|
|
140
|
+
this.topicStrings[TopicType.tx] = createTopicString(TopicType.tx, this.protocolVersion);
|
|
141
|
+
this.topicStrings[TopicType.block_proposal] = createTopicString(TopicType.block_proposal, this.protocolVersion);
|
|
142
|
+
this.topicStrings[TopicType.block_attestation] = createTopicString(
|
|
143
|
+
TopicType.block_attestation,
|
|
144
|
+
this.protocolVersion,
|
|
145
|
+
);
|
|
146
|
+
|
|
130
147
|
const peerScoring = new PeerScoring(config);
|
|
131
148
|
this.reqresp = new ReqResp(config, node, peerScoring);
|
|
132
149
|
|
|
@@ -198,6 +215,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
198
215
|
peerDiscovery.push(bootstrap({ list: bootstrapNodes }));
|
|
199
216
|
}
|
|
200
217
|
|
|
218
|
+
const versions = getVersions(config);
|
|
219
|
+
const protocolVersion = compressComponentVersions(versions);
|
|
220
|
+
|
|
221
|
+
const txTopic = createTopicString(TopicType.tx, protocolVersion);
|
|
222
|
+
const blockProposalTopic = createTopicString(TopicType.block_proposal, protocolVersion);
|
|
223
|
+
const blockAttestationTopic = createTopicString(TopicType.block_attestation, protocolVersion);
|
|
224
|
+
|
|
201
225
|
const node = await createLibp2p({
|
|
202
226
|
start: false,
|
|
203
227
|
peerId,
|
|
@@ -251,24 +275,24 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
251
275
|
fastMsgIdFn: fastMsgIdFn,
|
|
252
276
|
dataTransform: new SnappyTransform(),
|
|
253
277
|
metricsRegister: otelMetricsAdapter,
|
|
254
|
-
metricsTopicStrToLabel: metricsTopicStrToLabels(),
|
|
278
|
+
metricsTopicStrToLabel: metricsTopicStrToLabels(protocolVersion),
|
|
255
279
|
asyncValidation: true,
|
|
256
280
|
scoreThresholds: gossipScoreThresholds,
|
|
257
281
|
scoreParams: createPeerScoreParams({
|
|
258
282
|
// IPColocation factor can be disabled for local testing - default to -5
|
|
259
283
|
IPColocationFactorWeight: config.debugDisableColocationPenalty ? 0 : -5.0,
|
|
260
284
|
topics: {
|
|
261
|
-
[
|
|
285
|
+
[txTopic]: createTopicScoreParams({
|
|
262
286
|
topicWeight: 1,
|
|
263
287
|
invalidMessageDeliveriesWeight: -20,
|
|
264
288
|
invalidMessageDeliveriesDecay: 0.5,
|
|
265
289
|
}),
|
|
266
|
-
[
|
|
290
|
+
[blockAttestationTopic]: createTopicScoreParams({
|
|
267
291
|
topicWeight: 1,
|
|
268
292
|
invalidMessageDeliveriesWeight: -20,
|
|
269
293
|
invalidMessageDeliveriesDecay: 0.5,
|
|
270
294
|
}),
|
|
271
|
-
[
|
|
295
|
+
[blockProposalTopic]: createTopicScoreParams({
|
|
272
296
|
topicWeight: 1,
|
|
273
297
|
invalidMessageDeliveriesWeight: -20,
|
|
274
298
|
invalidMessageDeliveriesDecay: 0.5,
|
|
@@ -318,13 +342,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
318
342
|
// Start job queue, peer discovery service and libp2p node
|
|
319
343
|
this.jobQueue.start();
|
|
320
344
|
|
|
321
|
-
await this.peerManager.
|
|
345
|
+
await this.peerManager.initializePeers();
|
|
322
346
|
await this.peerDiscoveryService.start();
|
|
323
347
|
await this.node.start();
|
|
324
348
|
|
|
325
349
|
// Subscribe to standard GossipSub topics by default
|
|
326
350
|
for (const topic of getTopicTypeForClientType(this.clientType)) {
|
|
327
|
-
this.subscribeToTopic(
|
|
351
|
+
this.subscribeToTopic(this.topicStrings[topic]);
|
|
328
352
|
}
|
|
329
353
|
|
|
330
354
|
// Create request response protocol handlers
|
|
@@ -434,7 +458,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
434
458
|
sendBatchRequest<SubProtocol extends ReqRespSubProtocol>(
|
|
435
459
|
protocol: SubProtocol,
|
|
436
460
|
requests: InstanceType<SubProtocolMap[SubProtocol]['request']>[],
|
|
437
|
-
): Promise<InstanceType<SubProtocolMap[SubProtocol]['response']>
|
|
461
|
+
): Promise<(InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined)[]> {
|
|
438
462
|
return this.reqresp.sendBatchRequest(protocol, requests);
|
|
439
463
|
}
|
|
440
464
|
|
|
@@ -483,13 +507,13 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
483
507
|
* @param data - The message data
|
|
484
508
|
*/
|
|
485
509
|
protected async handleNewGossipMessage(msg: Message, msgId: string, source: PeerId) {
|
|
486
|
-
if (msg.topic ===
|
|
510
|
+
if (msg.topic === this.topicStrings[TopicType.tx]) {
|
|
487
511
|
await this.handleGossipedTx(msg, msgId, source);
|
|
488
512
|
}
|
|
489
|
-
if (msg.topic ===
|
|
513
|
+
if (msg.topic === this.topicStrings[TopicType.block_attestation] && this.clientType === P2PClientType.Full) {
|
|
490
514
|
await this.processAttestationFromPeer(msg, msgId, source);
|
|
491
515
|
}
|
|
492
|
-
if (msg.topic
|
|
516
|
+
if (msg.topic === this.topicStrings[TopicType.block_proposal]) {
|
|
493
517
|
await this.processBlockFromPeer(msg, msgId, source);
|
|
494
518
|
}
|
|
495
519
|
|
|
@@ -884,6 +908,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
884
908
|
public async validateBlockProposal(peerId: PeerId, block: BlockProposal): Promise<boolean> {
|
|
885
909
|
const severity = await this.blockProposalValidator.validate(block);
|
|
886
910
|
if (severity) {
|
|
911
|
+
this.logger.debug(`Penalizing peer ${peerId} for block proposal validation failure`);
|
|
887
912
|
this.peerManager.penalizePeer(peerId, severity);
|
|
888
913
|
return false;
|
|
889
914
|
}
|
|
@@ -901,7 +926,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
901
926
|
const identifier = await message.p2pMessageIdentifier().then(i => i.toString());
|
|
902
927
|
this.logger.trace(`Sending message ${identifier}`, { p2pMessageIdentifier: identifier });
|
|
903
928
|
|
|
904
|
-
const recipientsNum = await this.publishToTopic(parent.p2pTopic, message.toBuffer());
|
|
929
|
+
const recipientsNum = await this.publishToTopic(this.topicStrings[parent.p2pTopic], message.toBuffer());
|
|
905
930
|
this.logger.debug(`Sent message ${identifier} to ${recipientsNum} peers`, {
|
|
906
931
|
p2pMessageIdentifier: identifier,
|
|
907
932
|
sourcePeer: this.node.peerId.toString(),
|
|
@@ -41,8 +41,10 @@ export class PeerManager {
|
|
|
41
41
|
private heartbeatCounter: number = 0;
|
|
42
42
|
private displayPeerCountsPeerHeartbeat: number = 0;
|
|
43
43
|
private timedOutPeers: Map<string, TimedOutPeer> = new Map();
|
|
44
|
-
private trustedPeers: Set<
|
|
44
|
+
private trustedPeers: Set<string> = new Set();
|
|
45
45
|
private trustedPeersInitialized: boolean = false;
|
|
46
|
+
private privatePeers: Set<string> = new Set();
|
|
47
|
+
private privatePeersInitialized: boolean = false;
|
|
46
48
|
|
|
47
49
|
private metrics: PeerManagerMetrics;
|
|
48
50
|
private handlers: {
|
|
@@ -87,14 +89,34 @@ export class PeerManager {
|
|
|
87
89
|
*
|
|
88
90
|
* This function is called when the peer manager is initialized.
|
|
89
91
|
*/
|
|
90
|
-
async
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
async initializePeers() {
|
|
93
|
+
if (this.config.trustedPeers) {
|
|
94
|
+
const trustedPeersEnrs: ENR[] = this.config.trustedPeers.map(enr => ENR.decodeTxt(enr));
|
|
95
|
+
await Promise.all(trustedPeersEnrs.map(enr => enr.peerId()))
|
|
96
|
+
.then(peerIds => peerIds.forEach(peerId => this.trustedPeers.add(peerId.toString())))
|
|
97
|
+
.finally(() => {
|
|
98
|
+
this.trustedPeersInitialized = true;
|
|
99
|
+
})
|
|
100
|
+
.catch(e => this.logger.error('Error initializing trusted peers', e));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (this.config.privatePeers) {
|
|
104
|
+
const privatePeersEnrs: ENR[] = this.config.privatePeers.map(enr => ENR.decodeTxt(enr));
|
|
105
|
+
await Promise.all(privatePeersEnrs.map(enr => enr.peerId()))
|
|
106
|
+
.then(peerIds =>
|
|
107
|
+
peerIds.forEach(peerId => {
|
|
108
|
+
this.trustedPeers.add(peerId.toString());
|
|
109
|
+
this.privatePeers.add(peerId.toString());
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
.finally(() => {
|
|
113
|
+
if (!this.config.trustedPeers) {
|
|
114
|
+
this.trustedPeersInitialized = true;
|
|
115
|
+
}
|
|
116
|
+
this.privatePeersInitialized = true;
|
|
117
|
+
})
|
|
118
|
+
.catch(e => this.logger.error('Error initializing private peers', e));
|
|
119
|
+
}
|
|
98
120
|
}
|
|
99
121
|
|
|
100
122
|
get tracer() {
|
|
@@ -165,7 +187,7 @@ export class PeerManager {
|
|
|
165
187
|
this.logger.warn('Trusted peers not initialized, returning false');
|
|
166
188
|
return false;
|
|
167
189
|
}
|
|
168
|
-
return this.trustedPeers.has(peerId);
|
|
190
|
+
return this.trustedPeers.has(peerId.toString());
|
|
169
191
|
}
|
|
170
192
|
|
|
171
193
|
/**
|
|
@@ -173,9 +195,47 @@ export class PeerManager {
|
|
|
173
195
|
* @param peerId - The peer ID to add to trusted peers.
|
|
174
196
|
*/
|
|
175
197
|
public addTrustedPeer(peerId: PeerId): void {
|
|
176
|
-
|
|
198
|
+
const peerIdStr = peerId.toString();
|
|
199
|
+
|
|
200
|
+
this.trustedPeers.add(peerIdStr);
|
|
201
|
+
this.trustedPeersInitialized = true;
|
|
202
|
+
this.logger.verbose(`Added trusted peer ${peerIdStr}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Adds a peer to the private peers set.
|
|
207
|
+
* @param peerId - The peer ID to add to private peers.
|
|
208
|
+
*/
|
|
209
|
+
public addPrivatePeer(peerId: PeerId): void {
|
|
210
|
+
const peerIdStr = peerId.toString();
|
|
211
|
+
|
|
212
|
+
this.trustedPeers.add(peerIdStr);
|
|
213
|
+
this.privatePeers.add(peerIdStr);
|
|
177
214
|
this.trustedPeersInitialized = true;
|
|
178
|
-
this.
|
|
215
|
+
this.privatePeersInitialized = true;
|
|
216
|
+
this.logger.verbose(`Added private peer ${peerIdStr}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Checks if a peer is private.
|
|
221
|
+
* @param peerId - The peer ID.
|
|
222
|
+
* @returns True if the peer is private, false otherwise.
|
|
223
|
+
*/
|
|
224
|
+
private isPrivatePeer(peerId: PeerId): boolean {
|
|
225
|
+
if (!this.privatePeersInitialized) {
|
|
226
|
+
this.logger.warn('Private peers not initialized, returning false');
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return this.privatePeers.has(peerId.toString());
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Checks if a peer is protected (either trusted or private).
|
|
234
|
+
* @param peerId - The peer ID.
|
|
235
|
+
* @returns True if the peer is protected, false otherwise.
|
|
236
|
+
*/
|
|
237
|
+
private isProtectedPeer(peerId: PeerId): boolean {
|
|
238
|
+
return this.isTrustedPeer(peerId) || this.isPrivatePeer(peerId);
|
|
179
239
|
}
|
|
180
240
|
|
|
181
241
|
/**
|
|
@@ -241,15 +301,16 @@ export class PeerManager {
|
|
|
241
301
|
const connections = this.libP2PNode.getConnections();
|
|
242
302
|
|
|
243
303
|
const healthyConnections = this.prioritizePeers(
|
|
244
|
-
this.
|
|
304
|
+
this.pruneUnhealthyPeers(this.getNonProtectedPeers(this.pruneDuplicatePeers(connections))),
|
|
245
305
|
);
|
|
246
306
|
|
|
247
307
|
// Calculate how many connections we're looking to make
|
|
248
308
|
const peersToConnect = this.config.maxPeerCount - healthyConnections.length - this.trustedPeers.size;
|
|
249
309
|
|
|
250
310
|
const logLevel = this.heartbeatCounter % this.displayPeerCountsPeerHeartbeat === 0 ? 'info' : 'debug';
|
|
251
|
-
this.logger[logLevel](`Connected to ${healthyConnections.length} peers`, {
|
|
252
|
-
|
|
311
|
+
this.logger[logLevel](`Connected to ${healthyConnections.length + this.trustedPeers.size} peers`, {
|
|
312
|
+
discoveredConnections: healthyConnections.length,
|
|
313
|
+
protectedConnections: this.trustedPeers.size,
|
|
253
314
|
maxPeerCount: this.config.maxPeerCount,
|
|
254
315
|
cachedPeers: this.cachedPeers.size,
|
|
255
316
|
...this.peerScoring.getStats(),
|
|
@@ -302,18 +363,14 @@ export class PeerManager {
|
|
|
302
363
|
}
|
|
303
364
|
}
|
|
304
365
|
|
|
305
|
-
private
|
|
306
|
-
return connections.filter(conn => !this.
|
|
366
|
+
private getNonProtectedPeers(connections: Connection[]): Connection[] {
|
|
367
|
+
return connections.filter(conn => !this.isProtectedPeer(conn.remotePeer));
|
|
307
368
|
}
|
|
308
369
|
|
|
309
370
|
private pruneUnhealthyPeers(connections: Connection[]): Connection[] {
|
|
310
371
|
const connectedHealthyPeers: Connection[] = [];
|
|
311
372
|
|
|
312
373
|
for (const peer of connections) {
|
|
313
|
-
if (this.isTrustedPeer(peer.remotePeer)) {
|
|
314
|
-
this.logger.debug(`Not pruning trusted peer ${peer.remotePeer.toString()}`);
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
374
|
const score = this.peerScoring.getScoreState(peer.remotePeer.toString());
|
|
318
375
|
switch (score) {
|
|
319
376
|
case PeerScoreState.Banned:
|
|
@@ -526,7 +583,7 @@ export class PeerManager {
|
|
|
526
583
|
|
|
527
584
|
// Remove the oldest peers
|
|
528
585
|
for (const [key, value] of this.cachedPeers.entries()) {
|
|
529
|
-
if (this.
|
|
586
|
+
if (this.isProtectedPeer(value.peerId)) {
|
|
530
587
|
this.logger.debug(`Not pruning trusted peer ${key}`);
|
|
531
588
|
continue;
|
|
532
589
|
}
|
|
@@ -70,11 +70,11 @@ export class BatchConnectionSampler {
|
|
|
70
70
|
|
|
71
71
|
if (newPeer) {
|
|
72
72
|
this.batch[index] = newPeer;
|
|
73
|
-
this.logger.trace(
|
|
73
|
+
this.logger.trace('Replaced peer', { peerId, newPeer });
|
|
74
74
|
} else {
|
|
75
75
|
// If we couldn't get a replacement, remove the peer and compact the array
|
|
76
76
|
this.batch.splice(index, 1);
|
|
77
|
-
this.logger.trace(
|
|
77
|
+
this.logger.trace('Removed peer', { peerId });
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -17,7 +17,7 @@ export class RandomSampler {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* A class that samples peers from the libp2p node and returns a peer that we don't already have a connection open to.
|
|
20
|
+
* A class that samples peers from the libp2p node and returns a peer that we don't already have a reqresp connection open to.
|
|
21
21
|
* If we already have a connection open, we try to sample a different peer.
|
|
22
22
|
* We do this MAX_SAMPLE_ATTEMPTS times, if we still don't find a peer we just go for it.
|
|
23
23
|
*
|
|
@@ -69,32 +69,65 @@ export class ConnectionSampler {
|
|
|
69
69
|
getPeer(excluding?: Map<string, boolean>): PeerId | undefined {
|
|
70
70
|
// In libp2p getPeers performs a shallow copy, so this array can be sliced from safetly
|
|
71
71
|
const peers = this.libp2p.getPeers();
|
|
72
|
+
const { peer } = this.getPeerFromList(peers, excluding);
|
|
73
|
+
return peer;
|
|
74
|
+
}
|
|
72
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Samples a peer from a list of peers, excluding those that have active (reqresp) connections or are in the exclusion list
|
|
78
|
+
*
|
|
79
|
+
* @param peers - The list of peers to sample from
|
|
80
|
+
* @param excluding - The peers to exclude from the sampling
|
|
81
|
+
* @returns - A peer from the list, or undefined if no peers are available,
|
|
82
|
+
* - a boolean indicating if the peer has active connections, and
|
|
83
|
+
* - all sampled peers - to enable optional resampling
|
|
84
|
+
*
|
|
85
|
+
* @dev The provided list peers, should be mutated by this function. This allows batch sampling
|
|
86
|
+
* to be performed without making extra copies of the list.
|
|
87
|
+
*/
|
|
88
|
+
getPeerFromList(
|
|
89
|
+
peers: PeerId[],
|
|
90
|
+
excluding?: Map<string, boolean>,
|
|
91
|
+
): {
|
|
92
|
+
peer: PeerId | undefined;
|
|
93
|
+
sampledPeers: PeerId[];
|
|
94
|
+
} {
|
|
73
95
|
if (peers.length === 0) {
|
|
74
|
-
return undefined;
|
|
96
|
+
return { peer: undefined, sampledPeers: [] };
|
|
75
97
|
}
|
|
76
98
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
(excluding?.get(peers[randomIndex]?.toString()) ?? false))
|
|
87
|
-
) {
|
|
99
|
+
const sampledPeers: PeerId[] = [];
|
|
100
|
+
// Try to find a peer that has no active connections and is not in the exclusion list
|
|
101
|
+
for (let attempts = 0; attempts < MAX_SAMPLE_ATTEMPTS && peers.length > 0; attempts++) {
|
|
102
|
+
const randomIndex = this.sampler.random(peers.length);
|
|
103
|
+
const peer = peers[randomIndex];
|
|
104
|
+
const hasActiveConnections = (this.activeConnectionsCount.get(peer) ?? 0) > 0;
|
|
105
|
+
const isExcluded = excluding?.get(peer.toString()) ?? false;
|
|
106
|
+
|
|
107
|
+
// Remove this peer from consideration
|
|
88
108
|
peers.splice(randomIndex, 1);
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
|
|
110
|
+
// If peer is suitable (no active connections and not excluded), return it
|
|
111
|
+
if (!hasActiveConnections && !isExcluded) {
|
|
112
|
+
this.logger.trace('Sampled peer', {
|
|
113
|
+
attempts,
|
|
114
|
+
peer,
|
|
115
|
+
});
|
|
116
|
+
return { peer, sampledPeers };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Keep track of peers that have active reqresp channels, batch sampling will use these to resample
|
|
120
|
+
sampledPeers.push(peer);
|
|
91
121
|
}
|
|
92
122
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
123
|
+
// If we've exhausted our attempts or peers list is empty, return the last peer if available
|
|
124
|
+
const lastPeer = peers.length > 0 ? peers[this.sampler.random(peers.length)] : undefined;
|
|
125
|
+
|
|
126
|
+
this.logger.trace('Sampled peer', {
|
|
127
|
+
attempts: MAX_SAMPLE_ATTEMPTS,
|
|
128
|
+
peer: lastPeer?.toString(),
|
|
96
129
|
});
|
|
97
|
-
return
|
|
130
|
+
return { peer: lastPeer, sampledPeers };
|
|
98
131
|
}
|
|
99
132
|
|
|
100
133
|
/**
|
|
@@ -105,34 +138,41 @@ export class ConnectionSampler {
|
|
|
105
138
|
*/
|
|
106
139
|
samplePeersBatch(numberToSample: number): PeerId[] {
|
|
107
140
|
const peers = this.libp2p.getPeers();
|
|
108
|
-
|
|
109
|
-
const peersWithConnections: PeerId[] = []; // Hold onto peers with active connections incase we need to sample more
|
|
141
|
+
this.logger.debug('Sampling peers batch', { numberToSample, peers });
|
|
110
142
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
143
|
+
// Only sample as many peers as we have available
|
|
144
|
+
numberToSample = Math.min(numberToSample, peers.length);
|
|
145
|
+
|
|
146
|
+
const batch: PeerId[] = [];
|
|
147
|
+
const withActiveConnections: Set<PeerId> = new Set();
|
|
148
|
+
for (let i = 0; i < numberToSample; i++) {
|
|
149
|
+
const { peer, sampledPeers } = this.getPeerFromList(peers, undefined);
|
|
150
|
+
if (peer) {
|
|
151
|
+
batch.push(peer);
|
|
152
|
+
}
|
|
153
|
+
if (sampledPeers.length > 0) {
|
|
154
|
+
sampledPeers.forEach(peer => withActiveConnections.add(peer));
|
|
119
155
|
}
|
|
120
156
|
}
|
|
157
|
+
const lengthWithoutConnections = batch.length;
|
|
121
158
|
|
|
122
159
|
// If we still need more peers, sample from those with connections
|
|
123
|
-
while (
|
|
124
|
-
const randomIndex = this.sampler.random(
|
|
125
|
-
|
|
126
|
-
|
|
160
|
+
while (batch.length < numberToSample && withActiveConnections.size > 0) {
|
|
161
|
+
const randomIndex = this.sampler.random(withActiveConnections.size);
|
|
162
|
+
|
|
163
|
+
const peer = Array.from(withActiveConnections)[randomIndex];
|
|
164
|
+
withActiveConnections.delete(peer);
|
|
165
|
+
batch.push(peer);
|
|
127
166
|
}
|
|
128
167
|
|
|
129
|
-
this.logger.trace(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
168
|
+
this.logger.trace('Batch sampled peers', {
|
|
169
|
+
length: batch.length,
|
|
170
|
+
peers: batch,
|
|
171
|
+
withoutConnections: lengthWithoutConnections,
|
|
172
|
+
withConnections: numberToSample - lengthWithoutConnections,
|
|
133
173
|
});
|
|
134
174
|
|
|
135
|
-
return
|
|
175
|
+
return batch;
|
|
136
176
|
}
|
|
137
177
|
|
|
138
178
|
// Set of passthrough functions to keep track of active connections
|
|
@@ -155,8 +195,9 @@ export class ConnectionSampler {
|
|
|
155
195
|
const updatedActiveConnectionsCount = (this.activeConnectionsCount.get(peerId) ?? 0) + 1;
|
|
156
196
|
this.activeConnectionsCount.set(peerId, updatedActiveConnectionsCount);
|
|
157
197
|
|
|
158
|
-
this.logger.trace(
|
|
198
|
+
this.logger.trace('Dialed protocol', {
|
|
159
199
|
streamId: stream.id,
|
|
200
|
+
protocol,
|
|
160
201
|
peerId: peerId.toString(),
|
|
161
202
|
activeConnectionsCount: updatedActiveConnectionsCount,
|
|
162
203
|
});
|
|
@@ -181,7 +222,7 @@ export class ConnectionSampler {
|
|
|
181
222
|
const updatedActiveConnectionsCount = (this.activeConnectionsCount.get(peerId) ?? 1) - 1;
|
|
182
223
|
this.activeConnectionsCount.set(peerId, updatedActiveConnectionsCount);
|
|
183
224
|
|
|
184
|
-
this.logger.trace(
|
|
225
|
+
this.logger.trace('Closing connection', {
|
|
185
226
|
streamId,
|
|
186
227
|
peerId: peerId.toString(),
|
|
187
228
|
protocol: stream.protocol,
|
|
@@ -207,7 +248,7 @@ export class ConnectionSampler {
|
|
|
207
248
|
// Check if we have lost track of accounting
|
|
208
249
|
if (this.activeConnectionsCount.get(peerId) === 0) {
|
|
209
250
|
await this.close(streamId);
|
|
210
|
-
this.logger.debug(
|
|
251
|
+
this.logger.debug('Cleaned up stale connection', { streamId, peerId: peerId.toString() });
|
|
211
252
|
}
|
|
212
253
|
} catch (error) {
|
|
213
254
|
this.logger.error(`Error cleaning up stale connection ${streamId}`, { error });
|
|
@@ -196,7 +196,7 @@ export class ReqResp {
|
|
|
196
196
|
this.logger.trace(`Sending request to peer: ${peer.toString()}`);
|
|
197
197
|
const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffer);
|
|
198
198
|
|
|
199
|
-
if (response
|
|
199
|
+
if (response.status !== ReqRespStatus.SUCCESS) {
|
|
200
200
|
this.logger.debug(
|
|
201
201
|
`Request to peer ${peer.toString()} failed with status ${prettyPrintReqRespStatus(response.status)}`,
|
|
202
202
|
);
|
|
@@ -264,9 +264,9 @@ export class ReqResp {
|
|
|
264
264
|
timeoutMs = 10000,
|
|
265
265
|
maxPeers = Math.min(10, requests.length),
|
|
266
266
|
maxRetryAttempts = 3,
|
|
267
|
-
): Promise<InstanceType<SubProtocolMap[SubProtocol]['response']>[]> {
|
|
267
|
+
): Promise<(InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined)[]> {
|
|
268
268
|
const responseValidator = this.subProtocolValidators[subProtocol];
|
|
269
|
-
const responses: InstanceType<SubProtocolMap[SubProtocol]['response']>[] = new Array(requests.length);
|
|
269
|
+
const responses: (InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined)[] = new Array(requests.length);
|
|
270
270
|
const requestBuffers = requests.map(req => req.toBuffer());
|
|
271
271
|
|
|
272
272
|
const requestFunction = async () => {
|
|
@@ -325,7 +325,7 @@ export class ReqResp {
|
|
|
325
325
|
const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffers[index]);
|
|
326
326
|
|
|
327
327
|
// Check the status of the response buffer
|
|
328
|
-
if (response
|
|
328
|
+
if (response.status !== ReqRespStatus.SUCCESS) {
|
|
329
329
|
this.logger.debug(
|
|
330
330
|
`Request to peer ${peer.toString()} failed with status ${prettyPrintReqRespStatus(
|
|
331
331
|
response.status,
|
|
@@ -378,7 +378,7 @@ export class ReqResp {
|
|
|
378
378
|
};
|
|
379
379
|
|
|
380
380
|
try {
|
|
381
|
-
return await executeTimeout<InstanceType<SubProtocolMap[SubProtocol]['response']>[]>(
|
|
381
|
+
return await executeTimeout<(InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined)[]>(
|
|
382
382
|
requestFunction,
|
|
383
383
|
timeoutMs,
|
|
384
384
|
() => new CollectiveReqRespTimeoutError(),
|
|
@@ -421,7 +421,7 @@ export class ReqResp {
|
|
|
421
421
|
peerId: PeerId,
|
|
422
422
|
subProtocol: ReqRespSubProtocol,
|
|
423
423
|
payload: Buffer,
|
|
424
|
-
): Promise<ReqRespResponse
|
|
424
|
+
): Promise<ReqRespResponse> {
|
|
425
425
|
let stream: Stream | undefined;
|
|
426
426
|
try {
|
|
427
427
|
this.metrics.recordRequestSent(subProtocol);
|
|
@@ -439,6 +439,12 @@ export class ReqResp {
|
|
|
439
439
|
} catch (e: any) {
|
|
440
440
|
this.metrics.recordRequestError(subProtocol);
|
|
441
441
|
this.handleResponseError(e, peerId, subProtocol);
|
|
442
|
+
|
|
443
|
+
// If there is an exception, we return an unknown response
|
|
444
|
+
return {
|
|
445
|
+
status: ReqRespStatus.FAILURE,
|
|
446
|
+
data: Buffer.from([]),
|
|
447
|
+
};
|
|
442
448
|
} finally {
|
|
443
449
|
// Only close the stream if we created it
|
|
444
450
|
if (stream) {
|
|
@@ -5,6 +5,7 @@ export enum ReqRespStatus {
|
|
|
5
5
|
SUCCESS = 0,
|
|
6
6
|
RATE_LIMIT_EXCEEDED = 1,
|
|
7
7
|
BADLY_FORMED_REQUEST = 2,
|
|
8
|
+
FAILURE = 126,
|
|
8
9
|
UNKNOWN = 127,
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -53,6 +54,8 @@ export function prettyPrintReqRespStatus(status: ReqRespStatus) {
|
|
|
53
54
|
return 'RATE_LIMIT_EXCEEDED';
|
|
54
55
|
case ReqRespStatus.BADLY_FORMED_REQUEST:
|
|
55
56
|
return 'BADLY_FORMED_REQUEST';
|
|
57
|
+
case ReqRespStatus.FAILURE:
|
|
58
|
+
return 'FAILURE';
|
|
56
59
|
case ReqRespStatus.UNKNOWN:
|
|
57
60
|
return 'UNKNOWN';
|
|
58
61
|
}
|
package/src/services/service.ts
CHANGED
|
@@ -56,7 +56,7 @@ export interface P2PService {
|
|
|
56
56
|
sendBatchRequest<Protocol extends ReqRespSubProtocol>(
|
|
57
57
|
protocol: Protocol,
|
|
58
58
|
requests: InstanceType<SubProtocolMap[Protocol]['request']>[],
|
|
59
|
-
): Promise<InstanceType<SubProtocolMap[Protocol]['response']>
|
|
59
|
+
): Promise<(InstanceType<SubProtocolMap[Protocol]['response']> | undefined)[]>;
|
|
60
60
|
|
|
61
61
|
// Leaky abstraction: fix https://github.com/AztecProtocol/aztec-packages/issues/7963
|
|
62
62
|
registerBlockReceivedCallback(callback: (block: BlockProposal) => Promise<BlockAttestation | undefined>): void;
|
|
@@ -81,10 +81,10 @@ export interface PeerDiscoveryService extends EventEmitter {
|
|
|
81
81
|
stop(): Promise<void>;
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* Gets all
|
|
85
|
-
* @returns An array of
|
|
84
|
+
* Gets all KadValues.
|
|
85
|
+
* @returns An array of ENRs.
|
|
86
86
|
*/
|
|
87
|
-
|
|
87
|
+
getKadValues(): ENR[];
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Runs findRandomNode query.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { MockL2BlockSource } from '@aztec/archiver/test';
|
|
2
2
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
3
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
4
5
|
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
5
6
|
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
|
|
6
7
|
import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
@@ -57,6 +58,7 @@ export async function makeTestP2PClient(
|
|
|
57
58
|
p2pEnabled: true,
|
|
58
59
|
peerIdPrivateKey,
|
|
59
60
|
p2pIp: `127.0.0.1`,
|
|
61
|
+
listenAddress: `127.0.0.1`,
|
|
60
62
|
p2pPort: port,
|
|
61
63
|
bootstrapNodes: peers,
|
|
62
64
|
peerCheckIntervalMS: 1000,
|
|
@@ -101,7 +103,16 @@ export async function makeTestP2PClients(numberOfPeers: number, testConfig: Make
|
|
|
101
103
|
const clients: P2PClient[] = [];
|
|
102
104
|
const peerIdPrivateKeys = generatePeerIdPrivateKeys(numberOfPeers);
|
|
103
105
|
|
|
104
|
-
|
|
106
|
+
let ports = [];
|
|
107
|
+
while (true) {
|
|
108
|
+
try {
|
|
109
|
+
ports = await getPorts(numberOfPeers);
|
|
110
|
+
break;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
await sleep(1000);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
105
116
|
const peerEnrs = await makeEnrs(peerIdPrivateKeys, ports, testConfig.p2pBaseConfig);
|
|
106
117
|
|
|
107
118
|
for (let i = 0; i < numberOfPeers; i++) {
|
|
@@ -113,5 +124,12 @@ export async function makeTestP2PClients(numberOfPeers: number, testConfig: Make
|
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
await Promise.all(clients.map(client => client.isReady()));
|
|
116
|
-
return clients
|
|
127
|
+
return clients.map((client, index) => {
|
|
128
|
+
return {
|
|
129
|
+
client,
|
|
130
|
+
peerPrivateKey: peerIdPrivateKeys[index],
|
|
131
|
+
port: ports[index],
|
|
132
|
+
enr: peerEnrs[index],
|
|
133
|
+
};
|
|
134
|
+
});
|
|
117
135
|
}
|