@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.
Files changed (102) hide show
  1. package/dest/bootstrap/bootstrap.d.ts.map +1 -1
  2. package/dest/bootstrap/bootstrap.js +9 -4
  3. package/dest/client/factory.d.ts +4 -2
  4. package/dest/client/factory.d.ts.map +1 -1
  5. package/dest/client/factory.js +4 -4
  6. package/dest/config.d.ts +30 -14
  7. package/dest/config.d.ts.map +1 -1
  8. package/dest/config.js +30 -14
  9. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +2 -2
  10. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  11. package/dest/msg_validators/attestation_validator/attestation_validator.js +1 -1
  12. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts +2 -2
  13. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.d.ts.map +1 -1
  14. package/dest/msg_validators/block_proposal_validator/block_proposal_validator.js +1 -1
  15. package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts +2 -2
  16. package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.d.ts.map +1 -1
  17. package/dest/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.js +1 -1
  18. package/dest/services/discv5/discV5_service.d.ts +5 -1
  19. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  20. package/dest/services/discv5/discV5_service.js +65 -18
  21. package/dest/services/dummy_service.d.ts +1 -0
  22. package/dest/services/dummy_service.d.ts.map +1 -1
  23. package/dest/services/dummy_service.js +2 -1
  24. package/dest/services/libp2p/libp2p_logger.d.ts +7 -0
  25. package/dest/services/libp2p/libp2p_logger.d.ts.map +1 -0
  26. package/dest/services/libp2p/libp2p_logger.js +67 -0
  27. package/dest/services/libp2p/libp2p_service.d.ts +3 -3
  28. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  29. package/dest/services/libp2p/libp2p_service.js +42 -10
  30. package/dest/services/reqresp/interface.d.ts +9 -0
  31. package/dest/services/reqresp/interface.d.ts.map +1 -1
  32. package/dest/services/reqresp/interface.js +1 -1
  33. package/dest/services/reqresp/protocols/goodbye.js +2 -2
  34. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  35. package/dest/services/reqresp/rate-limiter/rate_limiter.js +4 -2
  36. package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
  37. package/dest/services/reqresp/reqresp.d.ts +7 -2
  38. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  39. package/dest/services/reqresp/reqresp.js +90 -21
  40. package/dest/services/reqresp/status.d.ts +31 -0
  41. package/dest/services/reqresp/status.d.ts.map +1 -0
  42. package/dest/services/reqresp/status.js +52 -0
  43. package/dest/services/service.d.ts +1 -0
  44. package/dest/services/service.d.ts.map +1 -1
  45. package/dest/services/types.d.ts +1 -7
  46. package/dest/services/types.d.ts.map +1 -1
  47. package/dest/services/types.js +2 -10
  48. package/dest/test-helpers/generate-peer-id-private-keys.d.ts +7 -0
  49. package/dest/test-helpers/generate-peer-id-private-keys.d.ts.map +1 -0
  50. package/dest/test-helpers/generate-peer-id-private-keys.js +15 -0
  51. package/dest/test-helpers/get-ports.d.ts +7 -0
  52. package/dest/test-helpers/get-ports.d.ts.map +1 -0
  53. package/dest/test-helpers/get-ports.js +8 -0
  54. package/dest/test-helpers/index.d.ts +6 -0
  55. package/dest/test-helpers/index.d.ts.map +1 -0
  56. package/dest/test-helpers/index.js +6 -0
  57. package/dest/test-helpers/make-enrs.d.ts +16 -0
  58. package/dest/test-helpers/make-enrs.d.ts.map +1 -0
  59. package/dest/test-helpers/make-enrs.js +35 -0
  60. package/dest/test-helpers/make-test-p2p-clients.d.ts +37 -0
  61. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -0
  62. package/dest/test-helpers/make-test-p2p-clients.js +71 -0
  63. package/dest/{mocks/index.d.ts → test-helpers/reqresp-nodes.d.ts} +6 -5
  64. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -0
  65. package/dest/test-helpers/reqresp-nodes.js +183 -0
  66. package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -0
  67. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -0
  68. package/dest/testbench/p2p_client_testbench_worker.js +125 -0
  69. package/dest/versioning.d.ts +12 -0
  70. package/dest/versioning.d.ts.map +1 -0
  71. package/dest/versioning.js +38 -0
  72. package/package.json +10 -8
  73. package/src/bootstrap/bootstrap.ts +9 -3
  74. package/src/client/factory.ts +12 -5
  75. package/src/config.ts +56 -29
  76. package/src/msg_validators/attestation_validator/attestation_validator.ts +3 -3
  77. package/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +3 -3
  78. package/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts +3 -3
  79. package/src/services/discv5/discV5_service.ts +67 -18
  80. package/src/services/dummy_service.ts +2 -0
  81. package/src/services/libp2p/libp2p_logger.ts +78 -0
  82. package/src/services/libp2p/libp2p_service.ts +47 -10
  83. package/src/services/reqresp/interface.ts +11 -0
  84. package/src/services/reqresp/protocols/goodbye.ts +1 -1
  85. package/src/services/reqresp/rate-limiter/rate_limiter.ts +3 -1
  86. package/src/services/reqresp/rate-limiter/rate_limits.ts +2 -2
  87. package/src/services/reqresp/reqresp.ts +120 -25
  88. package/src/services/reqresp/status.ts +59 -0
  89. package/src/services/service.ts +2 -0
  90. package/src/services/types.ts +2 -10
  91. package/src/test-helpers/generate-peer-id-private-keys.ts +15 -0
  92. package/src/test-helpers/get-ports.ts +8 -0
  93. package/src/test-helpers/index.ts +5 -0
  94. package/src/test-helpers/make-enrs.ts +44 -0
  95. package/src/test-helpers/make-test-p2p-clients.ts +124 -0
  96. package/src/{mocks/index.ts → test-helpers/reqresp-nodes.ts} +10 -5
  97. package/src/testbench/README.md +20 -0
  98. package/src/testbench/p2p_client_testbench_worker.ts +156 -0
  99. package/src/testbench/scripts/run_testbench.sh +7 -0
  100. package/src/versioning.ts +50 -0
  101. package/dest/mocks/index.d.ts.map +0 -1
  102. 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
- this.peerScoring.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
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: 5,
28
+ quotaCount: 10,
29
29
  },
30
30
  globalLimit: {
31
31
  quotaTimeMs: 1000,
32
- quotaCount: 10,
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 ReqRespSubProtocol,
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
- if (response && response.length > 0) {
315
- const object = subProtocolMap[subProtocol].response.fromBuffer(response);
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<Buffer | undefined> {
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<Buffer>(
406
- (): Promise<Buffer> => pipe([payload], stream!, this.readMessage.bind(this)),
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 pubishable errors
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<Buffer> {
531
+ private async readMessage(source: AsyncIterable<Uint8ArrayList>): Promise<ReqRespResponse> {
532
+ let statusBuffer: ReqRespStatus | undefined;
497
533
  const chunks: Uint8Array[] = [];
498
- for await (const chunk of source) {
499
- chunks.push(chunk.subarray());
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
- // Store a reference to from this for the async generator
529
- if (!this.rateLimiter.allow(protocol, connection.remotePeer)) {
530
- this.logger.warn(`Rate limit exceeded for ${protocol} from ${connection.remotePeer}`);
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
- // TODO(#8483): handle changing peer scoring for failed rate limit, maybe differentiate between global and peer limits here when punishing
533
- await stream.close();
534
- return;
535
- }
595
+ throw new ReqRespStatusError(ReqRespStatus.RATE_LIMIT_EXCEEDED);
596
+ }
536
597
 
537
- const handler = this.subProtocolHandlers[protocol];
538
- const transform = this.snappyTransform;
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
+ }
@@ -106,4 +106,6 @@ export interface PeerDiscoveryService extends EventEmitter {
106
106
  getStatus(): PeerDiscoveryState;
107
107
 
108
108
  getEnr(): ENR | undefined;
109
+
110
+ bootstrapNodes: string[];
109
111
  }
@@ -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 = 'aztec_network';
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,5 @@
1
+ export * from './generate-peer-id-private-keys.js';
2
+ export * from './get-ports.js';
3
+ export * from './make-enrs.js';
4
+ export * from './make-test-p2p-clients.js';
5
+ export * from './reqresp-nodes.js';
@@ -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