@aztec/p2p 0.75.0 → 0.76.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 +9 -4
- package/dest/client/factory.d.ts +4 -2
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +4 -4
- package/dest/config.d.ts +30 -14
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +30 -14
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +2 -2
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +1 -1
- package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +2 -2
- 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 +1 -1
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts +2 -2
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts.map +1 -1
- package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.js +1 -1
- package/dest/services/discv5/discV5_service.d.ts +5 -1
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +65 -18
- package/dest/services/dummy_service.d.ts +1 -0
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +2 -1
- package/dest/services/libp2p/libp2p_logger.d.ts +7 -0
- package/dest/services/libp2p/libp2p_logger.d.ts.map +1 -0
- package/dest/services/libp2p/libp2p_logger.js +67 -0
- package/dest/services/libp2p/libp2p_service.d.ts +3 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +42 -10
- package/dest/services/reqresp/interface.d.ts +9 -0
- package/dest/services/reqresp/interface.d.ts.map +1 -1
- package/dest/services/reqresp/interface.js +1 -1
- package/dest/services/reqresp/protocols/goodbye.js +2 -2
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limiter.js +4 -2
- package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
- package/dest/services/reqresp/reqresp.d.ts +7 -2
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +90 -21
- package/dest/services/reqresp/status.d.ts +31 -0
- package/dest/services/reqresp/status.d.ts.map +1 -0
- package/dest/services/reqresp/status.js +52 -0
- package/dest/services/service.d.ts +1 -0
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/types.d.ts +1 -7
- package/dest/services/types.d.ts.map +1 -1
- package/dest/services/types.js +2 -10
- package/dest/test-helpers/generate-peer-id-private-keys.d.ts +7 -0
- package/dest/test-helpers/generate-peer-id-private-keys.d.ts.map +1 -0
- package/dest/test-helpers/generate-peer-id-private-keys.js +15 -0
- package/dest/test-helpers/get-ports.d.ts +7 -0
- package/dest/test-helpers/get-ports.d.ts.map +1 -0
- package/dest/test-helpers/get-ports.js +8 -0
- package/dest/test-helpers/index.d.ts +6 -0
- package/dest/test-helpers/index.d.ts.map +1 -0
- package/dest/test-helpers/index.js +6 -0
- package/dest/test-helpers/make-enrs.d.ts +16 -0
- package/dest/test-helpers/make-enrs.d.ts.map +1 -0
- package/dest/test-helpers/make-enrs.js +35 -0
- package/dest/test-helpers/make-test-p2p-clients.d.ts +37 -0
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -0
- package/dest/test-helpers/make-test-p2p-clients.js +71 -0
- package/dest/{mocks/index.d.ts → test-helpers/reqresp-nodes.d.ts} +6 -5
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -0
- package/dest/test-helpers/reqresp-nodes.js +183 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -0
- package/dest/testbench/p2p_client_testbench_worker.js +125 -0
- package/dest/versioning.d.ts +12 -0
- package/dest/versioning.d.ts.map +1 -0
- package/dest/versioning.js +38 -0
- package/package.json +10 -8
- package/src/bootstrap/bootstrap.ts +9 -3
- package/src/client/factory.ts +12 -5
- package/src/config.ts +56 -29
- package/src/msg_validators/attestation_validator/attestation_validator.ts +3 -3
- package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +3 -3
- package/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts +3 -3
- package/src/services/discv5/discV5_service.ts +67 -18
- package/src/services/dummy_service.ts +2 -0
- package/src/services/libp2p/libp2p_logger.ts +78 -0
- package/src/services/libp2p/libp2p_service.ts +47 -10
- package/src/services/reqresp/interface.ts +11 -0
- package/src/services/reqresp/protocols/goodbye.ts +1 -1
- package/src/services/reqresp/rate-limiter/rate_limiter.ts +3 -1
- package/src/services/reqresp/rate-limiter/rate_limits.ts +2 -2
- package/src/services/reqresp/reqresp.ts +120 -25
- package/src/services/reqresp/status.ts +59 -0
- package/src/services/service.ts +2 -0
- package/src/services/types.ts +2 -10
- package/src/test-helpers/generate-peer-id-private-keys.ts +15 -0
- package/src/test-helpers/get-ports.ts +8 -0
- package/src/test-helpers/index.ts +5 -0
- package/src/test-helpers/make-enrs.ts +44 -0
- package/src/test-helpers/make-test-p2p-clients.ts +124 -0
- package/src/{mocks/index.ts → test-helpers/reqresp-nodes.ts} +10 -5
- package/src/testbench/README.md +20 -0
- package/src/testbench/p2p_client_testbench_worker.ts +156 -0
- package/src/testbench/scripts/run_testbench.sh +7 -0
- package/src/versioning.ts +50 -0
- package/dest/mocks/index.d.ts.map +0 -1
- package/dest/mocks/index.js +0 -181
|
@@ -200,9 +200,11 @@ export class RequestResponseRateLimiter {
|
|
|
200
200
|
|
|
201
201
|
switch (rateLimitStatus) {
|
|
202
202
|
case RateLimitStatus.DeniedPeer:
|
|
203
|
-
|
|
203
|
+
// Hitting a peer specific limit, we should lightly penalise the peer
|
|
204
|
+
this.peerScoring.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
204
205
|
return false;
|
|
205
206
|
case RateLimitStatus.DeniedGlobal:
|
|
207
|
+
// Hitting a global limit, we should not penalise the peer
|
|
206
208
|
return false;
|
|
207
209
|
default:
|
|
208
210
|
return true;
|
|
@@ -25,11 +25,11 @@ export const DEFAULT_RATE_LIMITS: ReqRespSubProtocolRateLimits = {
|
|
|
25
25
|
[ReqRespSubProtocol.TX]: {
|
|
26
26
|
peerLimit: {
|
|
27
27
|
quotaTimeMs: 1000,
|
|
28
|
-
quotaCount:
|
|
28
|
+
quotaCount: 10,
|
|
29
29
|
},
|
|
30
30
|
globalLimit: {
|
|
31
31
|
quotaTimeMs: 1000,
|
|
32
|
-
quotaCount:
|
|
32
|
+
quotaCount: 20,
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
[ReqRespSubProtocol.BLOCK]: {
|
|
@@ -22,7 +22,8 @@ import { ConnectionSampler } from './connection-sampler/connection_sampler.js';
|
|
|
22
22
|
import {
|
|
23
23
|
DEFAULT_SUB_PROTOCOL_HANDLERS,
|
|
24
24
|
DEFAULT_SUB_PROTOCOL_VALIDATORS,
|
|
25
|
-
type
|
|
25
|
+
type ReqRespResponse,
|
|
26
|
+
ReqRespSubProtocol,
|
|
26
27
|
type ReqRespSubProtocolHandlers,
|
|
27
28
|
type ReqRespSubProtocolValidators,
|
|
28
29
|
type SubProtocolMap,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
} from './interface.js';
|
|
31
32
|
import { ReqRespMetrics } from './metrics.js';
|
|
32
33
|
import { RequestResponseRateLimiter } from './rate-limiter/rate_limiter.js';
|
|
34
|
+
import { ReqRespStatus, ReqRespStatusError, parseStatusChunk, prettyPrintReqRespStatus } from './status.js';
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
37
|
* The Request Response Service
|
|
@@ -190,10 +192,17 @@ export class ReqResp {
|
|
|
190
192
|
this.logger.trace(`Sending request to peer: ${peer.toString()}`);
|
|
191
193
|
const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffer);
|
|
192
194
|
|
|
195
|
+
if (response && response.status !== ReqRespStatus.SUCCESS) {
|
|
196
|
+
this.logger.debug(
|
|
197
|
+
`Request to peer ${peer.toString()} failed with status ${prettyPrintReqRespStatus(response.status)}`,
|
|
198
|
+
);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
193
202
|
// If we get a response, return it, otherwise we iterate onto the next peer
|
|
194
203
|
// We do not consider it a success if we have an empty buffer
|
|
195
|
-
if (response && response.length > 0) {
|
|
196
|
-
const object = subProtocolMap[subProtocol].response.fromBuffer(response);
|
|
204
|
+
if (response && response.data.length > 0) {
|
|
205
|
+
const object = subProtocolMap[subProtocol].response.fromBuffer(response.data);
|
|
197
206
|
// The response validator handles peer punishment within
|
|
198
207
|
const isValid = await responseValidator(request, object, peer);
|
|
199
208
|
if (!isValid) {
|
|
@@ -311,8 +320,22 @@ export class ReqResp {
|
|
|
311
320
|
for (const index of indices) {
|
|
312
321
|
const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffers[index]);
|
|
313
322
|
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
// Check the status of the response buffer
|
|
324
|
+
if (response && response.status !== ReqRespStatus.SUCCESS) {
|
|
325
|
+
this.logger.debug(
|
|
326
|
+
`Request to peer ${peer.toString()} failed with status ${prettyPrintReqRespStatus(
|
|
327
|
+
response.status,
|
|
328
|
+
)}`,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// If we hit a rate limit or some failure, we remove the peer and return the results,
|
|
332
|
+
// they will be split among remaining peers and the new sampled peer
|
|
333
|
+
batchSampler.removePeerAndReplace(peer);
|
|
334
|
+
return { peer, results: peerResults };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (response && response.data.length > 0) {
|
|
338
|
+
const object = subProtocolMap[subProtocol].response.fromBuffer(response.data);
|
|
316
339
|
const isValid = await responseValidator(requests[index], object, peer);
|
|
317
340
|
|
|
318
341
|
if (isValid) {
|
|
@@ -394,7 +417,7 @@ export class ReqResp {
|
|
|
394
417
|
peerId: PeerId,
|
|
395
418
|
subProtocol: ReqRespSubProtocol,
|
|
396
419
|
payload: Buffer,
|
|
397
|
-
): Promise<
|
|
420
|
+
): Promise<ReqRespResponse | undefined> {
|
|
398
421
|
let stream: Stream | undefined;
|
|
399
422
|
try {
|
|
400
423
|
this.metrics.recordRequestSent(subProtocol);
|
|
@@ -402,8 +425,8 @@ export class ReqResp {
|
|
|
402
425
|
stream = await this.connectionSampler.dialProtocol(peerId, subProtocol);
|
|
403
426
|
|
|
404
427
|
// Open the stream with a timeout
|
|
405
|
-
const result = await executeTimeout<
|
|
406
|
-
(): Promise<
|
|
428
|
+
const result = await executeTimeout<ReqRespResponse>(
|
|
429
|
+
(): Promise<ReqRespResponse> => pipe([payload], stream!, this.readMessage.bind(this)),
|
|
407
430
|
this.individualRequestTimeoutMs,
|
|
408
431
|
() => new IndividualReqRespTimeoutError(),
|
|
409
432
|
);
|
|
@@ -447,7 +470,15 @@ export class ReqResp {
|
|
|
447
470
|
* Categorize the error and log it.
|
|
448
471
|
*/
|
|
449
472
|
private categorizeError(e: any, peerId: PeerId, subProtocol: ReqRespSubProtocol): PeerErrorSeverity | undefined {
|
|
450
|
-
// Non
|
|
473
|
+
// Non punishable errors - we do not expect a response for goodbye messages
|
|
474
|
+
if (subProtocol === ReqRespSubProtocol.GOODBYE) {
|
|
475
|
+
this.logger.debug('Error encountered on goodbye sub protocol, no penalty', {
|
|
476
|
+
peerId: peerId.toString(),
|
|
477
|
+
subProtocol,
|
|
478
|
+
});
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
|
|
451
482
|
// We do not punish a collective timeout, as the node triggers this interupt, independent of the peer's behaviour
|
|
452
483
|
const logTags = {
|
|
453
484
|
peerId: peerId.toString(),
|
|
@@ -492,14 +523,45 @@ export class ReqResp {
|
|
|
492
523
|
|
|
493
524
|
/**
|
|
494
525
|
* Read a message returned from a stream into a single buffer
|
|
526
|
+
*
|
|
527
|
+
* The message is split into two components
|
|
528
|
+
* - The first chunk should contain a control byte, indicating the status of the response see `ReqRespStatus`
|
|
529
|
+
* - The second chunk should contain the response data
|
|
495
530
|
*/
|
|
496
|
-
private async readMessage(source: AsyncIterable<Uint8ArrayList>): Promise<
|
|
531
|
+
private async readMessage(source: AsyncIterable<Uint8ArrayList>): Promise<ReqRespResponse> {
|
|
532
|
+
let statusBuffer: ReqRespStatus | undefined;
|
|
497
533
|
const chunks: Uint8Array[] = [];
|
|
498
|
-
|
|
499
|
-
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
for await (const chunk of source) {
|
|
537
|
+
if (statusBuffer === undefined) {
|
|
538
|
+
const firstChunkBuffer = chunk.subarray();
|
|
539
|
+
statusBuffer = parseStatusChunk(firstChunkBuffer);
|
|
540
|
+
} else {
|
|
541
|
+
chunks.push(chunk.subarray());
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const messageData = Buffer.concat(chunks);
|
|
546
|
+
const message: Buffer = this.snappyTransform.inboundTransformNoTopic(messageData);
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
status: statusBuffer ?? ReqRespStatus.UNKNOWN,
|
|
550
|
+
data: message,
|
|
551
|
+
};
|
|
552
|
+
} catch (e: any) {
|
|
553
|
+
this.logger.debug(`Reading message failed: ${e.message}`);
|
|
554
|
+
|
|
555
|
+
let status = ReqRespStatus.UNKNOWN;
|
|
556
|
+
if (e instanceof ReqRespStatusError) {
|
|
557
|
+
status = e.status;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
status,
|
|
562
|
+
data: Buffer.from([]),
|
|
563
|
+
};
|
|
500
564
|
}
|
|
501
|
-
const messageData = Buffer.concat(chunks);
|
|
502
|
-
return this.snappyTransform.inboundTransformNoTopic(messageData);
|
|
503
565
|
}
|
|
504
566
|
|
|
505
567
|
/**
|
|
@@ -525,35 +587,68 @@ export class ReqResp {
|
|
|
525
587
|
private async streamHandler(protocol: ReqRespSubProtocol, { stream, connection }: IncomingStreamData) {
|
|
526
588
|
this.metrics.recordRequestReceived(protocol);
|
|
527
589
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
this.
|
|
590
|
+
try {
|
|
591
|
+
// Store a reference to from this for the async generator
|
|
592
|
+
if (!this.rateLimiter.allow(protocol, connection.remotePeer)) {
|
|
593
|
+
this.logger.warn(`Rate limit exceeded for ${protocol} from ${connection.remotePeer}`);
|
|
531
594
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
595
|
+
throw new ReqRespStatusError(ReqRespStatus.RATE_LIMIT_EXCEEDED);
|
|
596
|
+
}
|
|
536
597
|
|
|
537
|
-
|
|
538
|
-
|
|
598
|
+
const handler = this.subProtocolHandlers[protocol];
|
|
599
|
+
const transform = this.snappyTransform;
|
|
600
|
+
|
|
601
|
+
this.logger.info(`Stream handler for ${protocol}`);
|
|
539
602
|
|
|
540
|
-
try {
|
|
541
603
|
await pipe(
|
|
542
604
|
stream,
|
|
543
605
|
async function* (source: any) {
|
|
544
606
|
for await (const chunkList of source) {
|
|
545
607
|
const msg = Buffer.from(chunkList.subarray());
|
|
546
608
|
const response = await handler(connection.remotePeer, msg);
|
|
609
|
+
|
|
610
|
+
if (protocol === ReqRespSubProtocol.GOODBYE) {
|
|
611
|
+
// Don't respond
|
|
612
|
+
await stream.close();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Send success code first, then the response
|
|
617
|
+
const successChunk = Buffer.from([ReqRespStatus.SUCCESS]);
|
|
618
|
+
yield new Uint8Array(successChunk);
|
|
619
|
+
|
|
547
620
|
yield new Uint8Array(transform.outboundTransformNoTopic(response));
|
|
548
621
|
}
|
|
549
622
|
},
|
|
550
623
|
stream,
|
|
551
624
|
);
|
|
552
625
|
} catch (e: any) {
|
|
553
|
-
this.logger.warn(e);
|
|
626
|
+
this.logger.warn('Reqresp Response error: ', e);
|
|
554
627
|
this.metrics.recordResponseError(protocol);
|
|
628
|
+
|
|
629
|
+
// If we receive a known error, we use the error status in the response chunk, otherwise we categorize as unknown
|
|
630
|
+
let errorStatus = ReqRespStatus.UNKNOWN;
|
|
631
|
+
if (e instanceof ReqRespStatusError) {
|
|
632
|
+
errorStatus = e.status;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const sendErrorChunk = this.sendErrorChunk(errorStatus);
|
|
636
|
+
|
|
637
|
+
// Return and yield the response chunk
|
|
638
|
+
await pipe(
|
|
639
|
+
stream,
|
|
640
|
+
async function* (_source: any) {
|
|
641
|
+
yield* sendErrorChunk;
|
|
642
|
+
},
|
|
643
|
+
stream,
|
|
644
|
+
);
|
|
555
645
|
} finally {
|
|
556
646
|
await stream.close();
|
|
557
647
|
}
|
|
558
648
|
}
|
|
649
|
+
|
|
650
|
+
private async *sendErrorChunk(error: ReqRespStatus): AsyncIterable<Uint8Array> {
|
|
651
|
+
const errorChunk = Buffer.from([error]);
|
|
652
|
+
yield new Uint8Array(errorChunk);
|
|
653
|
+
}
|
|
559
654
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error codes for the ReqResp protocol
|
|
3
|
+
*/
|
|
4
|
+
export enum ReqRespStatus {
|
|
5
|
+
SUCCESS = 0,
|
|
6
|
+
RATE_LIMIT_EXCEEDED = 1,
|
|
7
|
+
BADLY_FORMED_REQUEST = 2,
|
|
8
|
+
UNKNOWN = 127,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ReqRespStatusError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* The status code
|
|
14
|
+
*/
|
|
15
|
+
status: ReqRespStatus;
|
|
16
|
+
|
|
17
|
+
constructor(status: ReqRespStatus) {
|
|
18
|
+
super(`ReqResp Error: ${prettyPrintReqRespStatus(status)}`);
|
|
19
|
+
this.status = status;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parse the status chunk
|
|
25
|
+
* @param chunk
|
|
26
|
+
* @returns
|
|
27
|
+
*
|
|
28
|
+
* @throws ReqRespStatusError if the chunk is not valid
|
|
29
|
+
*/
|
|
30
|
+
export function parseStatusChunk(chunk: Uint8Array): ReqRespStatus {
|
|
31
|
+
if (chunk.length !== 1) {
|
|
32
|
+
throw new ReqRespStatusError(ReqRespStatus.UNKNOWN);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const status = chunk[0];
|
|
36
|
+
// Check if status is a valid ReqRespStatus value
|
|
37
|
+
if (!(status in ReqRespStatus)) {
|
|
38
|
+
throw new ReqRespStatusError(ReqRespStatus.UNKNOWN);
|
|
39
|
+
}
|
|
40
|
+
return status as ReqRespStatus;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pretty print the ReqResp status
|
|
45
|
+
* @param status
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
export function prettyPrintReqRespStatus(status: ReqRespStatus) {
|
|
49
|
+
switch (status) {
|
|
50
|
+
case ReqRespStatus.SUCCESS:
|
|
51
|
+
return 'SUCCESS';
|
|
52
|
+
case ReqRespStatus.RATE_LIMIT_EXCEEDED:
|
|
53
|
+
return 'RATE_LIMIT_EXCEEDED';
|
|
54
|
+
case ReqRespStatus.BADLY_FORMED_REQUEST:
|
|
55
|
+
return 'BADLY_FORMED_REQUEST';
|
|
56
|
+
case ReqRespStatus.UNKNOWN:
|
|
57
|
+
return 'UNKNOWN';
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/services/service.ts
CHANGED
package/src/services/types.ts
CHANGED
|
@@ -29,16 +29,8 @@ export enum GossipSubEvent {
|
|
|
29
29
|
/***************************************************
|
|
30
30
|
* Types
|
|
31
31
|
***************************************************/
|
|
32
|
+
|
|
32
33
|
/**
|
|
33
34
|
* Aztec network specific types
|
|
34
35
|
*/
|
|
35
|
-
export const AZTEC_ENR_KEY = '
|
|
36
|
-
|
|
37
|
-
export enum AztecENR {
|
|
38
|
-
devnet = 0x01,
|
|
39
|
-
testnet = 0x02,
|
|
40
|
-
mainnet = 0x03,
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// TODO: Make this an env var
|
|
44
|
-
export const AZTEC_NET = AztecENR.devnet;
|
|
36
|
+
export const AZTEC_ENR_KEY = 'aztec';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { generatePrivateKey } from 'viem/accounts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a list of peer id private keys
|
|
5
|
+
* @param numberOfPeers - The number of peer id private keys to generate
|
|
6
|
+
* @returns A list of peer id private keys
|
|
7
|
+
*/
|
|
8
|
+
export function generatePeerIdPrivateKeys(numberOfPeers: number): string[] {
|
|
9
|
+
const peerIdPrivateKeys: string[] = [];
|
|
10
|
+
for (let i = 0; i < numberOfPeers; i++) {
|
|
11
|
+
// magic number is multiaddr prefix: https://multiformats.io/multiaddr/
|
|
12
|
+
peerIdPrivateKeys.push('08021220' + generatePrivateKey().slice(2, 68));
|
|
13
|
+
}
|
|
14
|
+
return peerIdPrivateKeys;
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import getPort from 'get-port';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get a list of ports for a given number of peers
|
|
5
|
+
* @param numberOfPeers - The number of peers to get ports for
|
|
6
|
+
* @returns A list of ports
|
|
7
|
+
*/
|
|
8
|
+
export const getPorts = (numberOfPeers: number) => Promise.all(Array.from({ length: numberOfPeers }, () => getPort()));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ChainConfig } from '@aztec/circuit-types/config';
|
|
2
|
+
|
|
3
|
+
import { SignableENR } from '@chainsafe/enr';
|
|
4
|
+
import { multiaddr } from '@multiformats/multiaddr';
|
|
5
|
+
|
|
6
|
+
import { convertToMultiaddr, createLibP2PPeerIdFromPrivateKey } from '../util.js';
|
|
7
|
+
import { setAztecEnrKey } from '../versioning.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Make a list of ENRs for a given list of p2p private keys and ports
|
|
11
|
+
* @param p2pPrivateKeys - The private keys of the p2p nodes
|
|
12
|
+
* @param ports - The ports of the p2p nodes
|
|
13
|
+
* @returns A list of ENRs
|
|
14
|
+
*/
|
|
15
|
+
export async function makeEnrs(p2pPrivateKeys: string[], ports: number[], config: ChainConfig) {
|
|
16
|
+
return await Promise.all(
|
|
17
|
+
p2pPrivateKeys.map((pk, i) => {
|
|
18
|
+
return makeEnr(pk, ports[i], config);
|
|
19
|
+
}),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Make an ENR for a given p2p private key and port
|
|
25
|
+
* @param p2pPrivateKey - The private key of the p2p node
|
|
26
|
+
* @param port - The port of the p2p node
|
|
27
|
+
* @returns The ENR of the p2p node
|
|
28
|
+
*/
|
|
29
|
+
export async function makeEnr(p2pPrivateKey: string, port: number, config: ChainConfig) {
|
|
30
|
+
const peerId = await createLibP2PPeerIdFromPrivateKey(p2pPrivateKey);
|
|
31
|
+
const enr = SignableENR.createFromPeerId(peerId);
|
|
32
|
+
|
|
33
|
+
const udpAnnounceAddress = `127.0.0.1:${port}`;
|
|
34
|
+
const tcpAnnounceAddress = `127.0.0.1:${port}`;
|
|
35
|
+
const udpPublicAddr = multiaddr(convertToMultiaddr(udpAnnounceAddress, 'udp'));
|
|
36
|
+
const tcpPublicAddr = multiaddr(convertToMultiaddr(tcpAnnounceAddress, 'tcp'));
|
|
37
|
+
|
|
38
|
+
// ENRS must include the network and a discoverable address (udp for discv5)
|
|
39
|
+
setAztecEnrKey(enr, config);
|
|
40
|
+
enr.setLocationMultiaddr(udpPublicAddr);
|
|
41
|
+
enr.setLocationMultiaddr(tcpPublicAddr);
|
|
42
|
+
|
|
43
|
+
return enr.encodeTxt();
|
|
44
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { MockL2BlockSource } from '@aztec/archiver/test';
|
|
2
|
+
import { P2PClientType, type WorldStateSynchronizer } from '@aztec/circuit-types';
|
|
3
|
+
import { type EpochCache } from '@aztec/epoch-cache';
|
|
4
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { type DataStoreConfig } from '@aztec/kv-store/config';
|
|
6
|
+
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
|
|
7
|
+
|
|
8
|
+
import { createP2PClient } from '../client/index.js';
|
|
9
|
+
import { type P2PClient } from '../client/p2p_client.js';
|
|
10
|
+
import { type P2PConfig } from '../config.js';
|
|
11
|
+
import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js';
|
|
12
|
+
import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js';
|
|
13
|
+
import { type TxPool } from '../mem_pools/tx_pool/index.js';
|
|
14
|
+
import { generatePeerIdPrivateKeys } from '../test-helpers/generate-peer-id-private-keys.js';
|
|
15
|
+
import { getPorts } from './get-ports.js';
|
|
16
|
+
import { makeEnrs } from './make-enrs.js';
|
|
17
|
+
import { AlwaysFalseCircuitVerifier, AlwaysTrueCircuitVerifier } from './reqresp-nodes.js';
|
|
18
|
+
|
|
19
|
+
interface MakeTestP2PClientOptions {
|
|
20
|
+
mockAttestationPool: AttestationPool;
|
|
21
|
+
mockEpochProofQuotePool: EpochProofQuotePool;
|
|
22
|
+
mockTxPool: TxPool;
|
|
23
|
+
mockEpochCache: EpochCache;
|
|
24
|
+
mockWorldState: WorldStateSynchronizer;
|
|
25
|
+
alwaysTrueVerifier?: boolean;
|
|
26
|
+
p2pBaseConfig: P2PConfig;
|
|
27
|
+
p2pConfigOverrides?: Partial<P2PConfig>;
|
|
28
|
+
logger?: Logger;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Creates a single P2P client for testing purposes.
|
|
33
|
+
* @param peerIdPrivateKey - The private key of the peer.
|
|
34
|
+
* @param port - The port to run the client on.
|
|
35
|
+
* @param peers - The peers to connect to.
|
|
36
|
+
* @param options - The options for the client.
|
|
37
|
+
* @returns The created client.
|
|
38
|
+
*/
|
|
39
|
+
export async function makeTestP2PClient(
|
|
40
|
+
peerIdPrivateKey: string,
|
|
41
|
+
port: number,
|
|
42
|
+
peers: string[],
|
|
43
|
+
{
|
|
44
|
+
alwaysTrueVerifier = true,
|
|
45
|
+
p2pBaseConfig,
|
|
46
|
+
p2pConfigOverrides = {},
|
|
47
|
+
mockAttestationPool,
|
|
48
|
+
mockEpochProofQuotePool,
|
|
49
|
+
mockTxPool,
|
|
50
|
+
mockEpochCache,
|
|
51
|
+
mockWorldState,
|
|
52
|
+
logger = createLogger('p2p-test-client'),
|
|
53
|
+
}: MakeTestP2PClientOptions,
|
|
54
|
+
) {
|
|
55
|
+
const addr = `127.0.0.1:${port}`;
|
|
56
|
+
const listenAddr = `0.0.0.0:${port}`;
|
|
57
|
+
|
|
58
|
+
// Filter nodes so that we only dial active peers
|
|
59
|
+
|
|
60
|
+
const config: P2PConfig & DataStoreConfig = {
|
|
61
|
+
...p2pBaseConfig,
|
|
62
|
+
p2pEnabled: true,
|
|
63
|
+
peerIdPrivateKey,
|
|
64
|
+
tcpListenAddress: listenAddr, // run on port 0
|
|
65
|
+
udpListenAddress: listenAddr,
|
|
66
|
+
tcpAnnounceAddress: addr,
|
|
67
|
+
udpAnnounceAddress: addr,
|
|
68
|
+
bootstrapNodes: peers,
|
|
69
|
+
peerCheckIntervalMS: 1000,
|
|
70
|
+
maxPeerCount: 10,
|
|
71
|
+
...p2pConfigOverrides,
|
|
72
|
+
} as P2PConfig & DataStoreConfig;
|
|
73
|
+
|
|
74
|
+
const l2BlockSource = new MockL2BlockSource();
|
|
75
|
+
await l2BlockSource.createBlocks(100);
|
|
76
|
+
|
|
77
|
+
const proofVerifier = alwaysTrueVerifier ? new AlwaysTrueCircuitVerifier() : new AlwaysFalseCircuitVerifier();
|
|
78
|
+
const kvStore = await openTmpStore('test');
|
|
79
|
+
const deps = {
|
|
80
|
+
txPool: mockTxPool as unknown as TxPool,
|
|
81
|
+
attestationPool: mockAttestationPool as unknown as AttestationPool,
|
|
82
|
+
epochProofQuotePool: mockEpochProofQuotePool as unknown as EpochProofQuotePool,
|
|
83
|
+
store: kvStore,
|
|
84
|
+
logger,
|
|
85
|
+
};
|
|
86
|
+
const client = await createP2PClient(
|
|
87
|
+
P2PClientType.Full,
|
|
88
|
+
config,
|
|
89
|
+
l2BlockSource,
|
|
90
|
+
proofVerifier,
|
|
91
|
+
mockWorldState,
|
|
92
|
+
mockEpochCache,
|
|
93
|
+
undefined,
|
|
94
|
+
deps,
|
|
95
|
+
);
|
|
96
|
+
await client.start();
|
|
97
|
+
|
|
98
|
+
return client;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates a number of P2P clients for testing purposes.
|
|
103
|
+
* @param numberOfPeers - The number of clients to create.
|
|
104
|
+
* @param options - The options for the clients.
|
|
105
|
+
* @returns The created clients.
|
|
106
|
+
*/
|
|
107
|
+
export async function makeTestP2PClients(numberOfPeers: number, testConfig: MakeTestP2PClientOptions) {
|
|
108
|
+
const clients: P2PClient[] = [];
|
|
109
|
+
const peerIdPrivateKeys = generatePeerIdPrivateKeys(numberOfPeers);
|
|
110
|
+
|
|
111
|
+
const ports = await getPorts(numberOfPeers);
|
|
112
|
+
const peerEnrs = await makeEnrs(peerIdPrivateKeys, ports, testConfig.p2pBaseConfig);
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < numberOfPeers; i++) {
|
|
115
|
+
const client = await makeTestP2PClient(peerIdPrivateKeys[i], ports[i], peerEnrs, {
|
|
116
|
+
...testConfig,
|
|
117
|
+
logger: createLogger(`p2p:${i}`),
|
|
118
|
+
});
|
|
119
|
+
clients.push(client);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
await Promise.all(clients.map(client => client.isReady()));
|
|
123
|
+
return clients;
|
|
124
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Tx,
|
|
6
6
|
type WorldStateSynchronizer,
|
|
7
7
|
} from '@aztec/circuit-types';
|
|
8
|
+
import { type ChainConfig, emptyChainConfig } from '@aztec/circuit-types/config';
|
|
8
9
|
import { type EpochCache } from '@aztec/epoch-cache';
|
|
9
10
|
import { timesParallel } from '@aztec/foundation/collection';
|
|
10
11
|
import { type DataStoreConfig } from '@aztec/kv-store/config';
|
|
@@ -105,6 +106,7 @@ export async function createTestLibP2PService<T extends P2PClientType>(
|
|
|
105
106
|
telemetry: TelemetryClient,
|
|
106
107
|
port: number = 0,
|
|
107
108
|
peerId?: PeerId,
|
|
109
|
+
chainConfig: ChainConfig = emptyChainConfig,
|
|
108
110
|
) {
|
|
109
111
|
peerId = peerId ?? (await createSecp256k1PeerId());
|
|
110
112
|
const config = {
|
|
@@ -114,10 +116,11 @@ export async function createTestLibP2PService<T extends P2PClientType>(
|
|
|
114
116
|
udpListenAddress: `0.0.0.0:${port}`,
|
|
115
117
|
bootstrapNodes: boostrapAddrs,
|
|
116
118
|
peerCheckIntervalMS: 1000,
|
|
117
|
-
minPeerCount: 1,
|
|
118
119
|
maxPeerCount: 5,
|
|
119
120
|
p2pEnabled: true,
|
|
120
121
|
peerIdPrivateKey: Buffer.from(peerId.privateKey!).toString('hex'),
|
|
122
|
+
bootstrapNodeEnrVersionCheck: false,
|
|
123
|
+
...chainConfig,
|
|
121
124
|
} as P2PConfig & DataStoreConfig;
|
|
122
125
|
const discoveryService = new DiscV5Service(peerId, config, telemetry);
|
|
123
126
|
const proofVerifier = new AlwaysTrueCircuitVerifier();
|
|
@@ -230,15 +233,15 @@ export class AlwaysFalseCircuitVerifier implements ClientProtocolCircuitVerifier
|
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
// Bootnodes
|
|
233
|
-
export function createBootstrapNodeConfig(privateKey: string, port: number): BootnodeConfig {
|
|
236
|
+
export function createBootstrapNodeConfig(privateKey: string, port: number, chainConfig: ChainConfig): BootnodeConfig {
|
|
234
237
|
return {
|
|
235
238
|
udpListenAddress: `0.0.0.0:${port}`,
|
|
236
239
|
udpAnnounceAddress: `127.0.0.1:${port}`,
|
|
237
240
|
peerIdPrivateKey: privateKey,
|
|
238
|
-
minPeerCount: 10,
|
|
239
241
|
maxPeerCount: 100,
|
|
240
242
|
dataDirectory: undefined,
|
|
241
243
|
dataStoreMapSizeKB: 0,
|
|
244
|
+
...chainConfig,
|
|
242
245
|
};
|
|
243
246
|
}
|
|
244
247
|
|
|
@@ -246,17 +249,19 @@ export function createBootstrapNodeFromPrivateKey(
|
|
|
246
249
|
privateKey: string,
|
|
247
250
|
port: number,
|
|
248
251
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
252
|
+
chainConfig: ChainConfig = emptyChainConfig,
|
|
249
253
|
): Promise<BootstrapNode> {
|
|
250
|
-
const config = createBootstrapNodeConfig(privateKey, port);
|
|
254
|
+
const config = createBootstrapNodeConfig(privateKey, port, chainConfig);
|
|
251
255
|
return startBootstrapNode(config, telemetry);
|
|
252
256
|
}
|
|
253
257
|
|
|
254
258
|
export async function createBootstrapNode(
|
|
255
259
|
port: number,
|
|
256
260
|
telemetry: TelemetryClient = getTelemetryClient(),
|
|
261
|
+
chainConfig: ChainConfig = emptyChainConfig,
|
|
257
262
|
): Promise<BootstrapNode> {
|
|
258
263
|
const peerId = await createSecp256k1PeerId();
|
|
259
|
-
const config = createBootstrapNodeConfig(Buffer.from(peerId.privateKey!).toString('hex'), port);
|
|
264
|
+
const config = createBootstrapNodeConfig(Buffer.from(peerId.privateKey!).toString('hex'), port, chainConfig);
|
|
260
265
|
|
|
261
266
|
return startBootstrapNode(config, telemetry);
|
|
262
267
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## P2P Test bench
|
|
2
|
+
|
|
3
|
+
A testbench that runs only the P2P client on a number of worker threads, with the purpose of monitoring and testing the performance of the P2P client.
|
|
4
|
+
|
|
5
|
+
### Running the testbench
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
./run_testbench.sh <outputfile>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will produce a LONG series of logs that can be used for further analysis.
|
|
12
|
+
|
|
13
|
+
## TODO
|
|
14
|
+
|
|
15
|
+
- Strongly parameterizing the testbench scripts
|
|
16
|
+
- Add traffic shaping options to the testbench
|
|
17
|
+
- Add log parsing step that can categorize a report in json of the propoagation of the message
|
|
18
|
+
- Add multiple different tx sizes
|
|
19
|
+
- Create ci pipeline that can run analysis on the logs and compare against previous runs
|
|
20
|
+
- Create a series of markdown reports detailing what each parameter change does and include graphs to compare performance
|