@aztec/p2p 5.0.0-rc.1 → 5.0.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 (103) hide show
  1. package/README.md +3 -5
  2. package/dest/bootstrap/bootstrap.d.ts +1 -1
  3. package/dest/bootstrap/bootstrap.d.ts.map +1 -1
  4. package/dest/bootstrap/bootstrap.js +20 -12
  5. package/dest/client/factory.d.ts +1 -1
  6. package/dest/client/factory.d.ts.map +1 -1
  7. package/dest/client/factory.js +2 -1
  8. package/dest/client/interface.d.ts +14 -2
  9. package/dest/client/interface.d.ts.map +1 -1
  10. package/dest/client/p2p_client.d.ts +4 -2
  11. package/dest/client/p2p_client.d.ts.map +1 -1
  12. package/dest/client/p2p_client.js +10 -2
  13. package/dest/config.d.ts +7 -1
  14. package/dest/config.d.ts.map +1 -1
  15. package/dest/config.js +10 -5
  16. package/dest/errors/reqresp.error.d.ts +13 -1
  17. package/dest/errors/reqresp.error.d.ts.map +1 -1
  18. package/dest/errors/reqresp.error.js +14 -0
  19. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +1 -1
  20. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/attestation_pool.js +6 -2
  22. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
  23. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +43 -0
  25. package/dest/mem_pools/instrumentation.d.ts +9 -4
  26. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  27. package/dest/mem_pools/instrumentation.js +11 -8
  28. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
  29. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +12 -1
  30. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +9 -1
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +21 -0
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +2 -1
  36. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +12 -4
  38. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +10 -2
  39. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +56 -8
  41. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +10 -6
  42. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  43. package/dest/msg_validators/proposal_validator/proposal_validator.js +25 -12
  44. package/dest/msg_validators/tx_validator/phases_validator.js +2 -2
  45. package/dest/services/discv5/discV5_service.d.ts +12 -2
  46. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  47. package/dest/services/discv5/discV5_service.js +84 -15
  48. package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
  49. package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
  50. package/dest/services/discv5/persisted_enr_store.js +145 -0
  51. package/dest/services/dummy_service.d.ts +6 -2
  52. package/dest/services/dummy_service.d.ts.map +1 -1
  53. package/dest/services/dummy_service.js +3 -0
  54. package/dest/services/encoding.d.ts +19 -6
  55. package/dest/services/encoding.d.ts.map +1 -1
  56. package/dest/services/encoding.js +25 -15
  57. package/dest/services/libp2p/libp2p_service.d.ts +11 -3
  58. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  59. package/dest/services/libp2p/libp2p_service.js +98 -34
  60. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  61. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  62. package/dest/services/peer-manager/peer_manager.js +21 -12
  63. package/dest/services/reqresp/config.d.ts +13 -1
  64. package/dest/services/reqresp/config.d.ts.map +1 -1
  65. package/dest/services/reqresp/config.js +11 -0
  66. package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
  67. package/dest/services/reqresp/reqresp.d.ts +1 -1
  68. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  69. package/dest/services/reqresp/reqresp.js +34 -3
  70. package/dest/services/service.d.ts +17 -1
  71. package/dest/services/service.d.ts.map +1 -1
  72. package/dest/test-helpers/testbench-utils.d.ts +2 -1
  73. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  74. package/dest/test-helpers/testbench-utils.js +3 -0
  75. package/package.json +16 -17
  76. package/src/bootstrap/bootstrap.ts +15 -9
  77. package/src/client/factory.ts +1 -0
  78. package/src/client/interface.ts +15 -0
  79. package/src/client/p2p_client.ts +14 -3
  80. package/src/config.ts +17 -4
  81. package/src/errors/reqresp.error.ts +19 -0
  82. package/src/mem_pools/attestation_pool/attestation_pool.ts +6 -2
  83. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -1
  84. package/src/mem_pools/instrumentation.ts +13 -11
  85. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
  86. package/src/mem_pools/tx_pool_v2/interfaces.ts +13 -0
  87. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +23 -0
  88. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +14 -4
  89. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +71 -7
  90. package/src/msg_validators/proposal_validator/proposal_validator.ts +22 -15
  91. package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
  92. package/src/services/discv5/discV5_service.ts +88 -9
  93. package/src/services/discv5/persisted_enr_store.ts +158 -0
  94. package/src/services/dummy_service.ts +6 -0
  95. package/src/services/encoding.ts +28 -20
  96. package/src/services/libp2p/libp2p_service.ts +107 -37
  97. package/src/services/peer-manager/peer_manager.ts +18 -11
  98. package/src/services/reqresp/README.md +2 -23
  99. package/src/services/reqresp/config.ts +17 -0
  100. package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
  101. package/src/services/reqresp/reqresp.ts +42 -2
  102. package/src/services/service.ts +19 -0
  103. package/src/test-helpers/testbench-utils.ts +4 -0
@@ -10,12 +10,18 @@ import type { Libp2p } from 'libp2p';
10
10
  import { pipeline } from 'node:stream/promises';
11
11
  import type { Uint8ArrayList } from 'uint8arraylist';
12
12
 
13
- import { IndividualReqRespTimeoutError } from '../../errors/reqresp.error.js';
14
- import { OversizedSnappyResponseError, SnappyTransform } from '../encoding.js';
13
+ import { IndividualReqRespTimeoutError, OversizedReqRespRequestError } from '../../errors/reqresp.error.js';
14
+ import {
15
+ DEFAULT_MAX_RESPONSE_SIZE_KB,
16
+ OversizedSnappyResponseError,
17
+ ResponseSizeLimitExceededError,
18
+ SnappyTransform,
19
+ } from '../encoding.js';
15
20
  import type { PeerScoring } from '../peer-manager/peer_scoring.js';
16
21
  import {
17
22
  DEFAULT_INDIVIDUAL_REQUEST_TIMEOUT_MS,
18
23
  DEFAULT_REQRESP_DIAL_TIMEOUT_MS,
24
+ MAX_REQRESP_REQUEST_SIZE_BYTES,
19
25
  type P2PReqRespConfig,
20
26
  } from './config.js';
21
27
  import { ConnectionSampler, RandomSampler } from './connection-sampler/connection_sampler.js';
@@ -207,6 +213,11 @@ export class ReqResp implements ReqRespInterface {
207
213
  payload: Buffer,
208
214
  dialTimeout: number = this.dialTimeoutMs,
209
215
  ): Promise<ReqRespResponse> {
216
+ // Thrown before the try block so the generic error handling below does not penalize the peer for our own bug.
217
+ if (payload.length > MAX_REQRESP_REQUEST_SIZE_BYTES) {
218
+ throw new OversizedReqRespRequestError(subProtocol, payload.length, MAX_REQRESP_REQUEST_SIZE_BYTES);
219
+ }
220
+
210
221
  let stream: Stream | undefined;
211
222
  try {
212
223
  this.metrics.recordRequestSent(subProtocol);
@@ -291,6 +302,14 @@ export class ReqResp implements ReqRespInterface {
291
302
  * - The first chunk should contain a control byte, indicating the status of the response see `ReqRespStatus`
292
303
  * - The second chunk should contain the response data
293
304
  *
305
+ * To bound memory usage we abort reception as soon as the accumulated compressed bytes exceed a
306
+ * bound derived from `maxSizeKb`, before the full stream has been buffered. Without this a peer we
307
+ * dialed could stream data up to the request timeout, forcing us to buffer arbitrarily much (the
308
+ * `maxSizeKb` guard in `inboundTransformData` only runs after the whole compressed payload is
309
+ * concatenated, so it does nothing to cap reception memory). Snappy can expand incompressible input
310
+ * to ~1.17x its size, so we allow 2x `maxSizeKb`: comfortably above the worst-case expansion of a
311
+ * legitimate max-size response, while still capping buffered memory at twice the permitted size.
312
+ *
294
313
  * @param source - The async iterable source of data chunks
295
314
  * @param maxSizeKb - Optional maximum expected size in KB for the decompressed response
296
315
  */
@@ -298,10 +317,20 @@ export class ReqResp implements ReqRespInterface {
298
317
  let status: ReqRespStatus | undefined;
299
318
  const chunks: Uint8Array[] = [];
300
319
 
320
+ const maxAccumulatedBytes = (maxSizeKb ?? DEFAULT_MAX_RESPONSE_SIZE_KB) * 1024 * 2;
321
+ let accumulatedBytes = 0;
322
+
301
323
  try {
302
324
  for await (const chunk of source) {
303
325
  const statusParsed = status !== undefined;
304
326
  if (statusParsed) {
327
+ // Use `byteLength` (the logical length) rather than `chunk.subarray().length`: calling
328
+ // `subarray()` on a `Uint8ArrayList` consolidates its backing buffers into a single copy,
329
+ // which would materialize the very chunk we are trying to reject.
330
+ accumulatedBytes += chunk.byteLength;
331
+ if (accumulatedBytes > maxAccumulatedBytes) {
332
+ throw new ResponseSizeLimitExceededError(accumulatedBytes, maxAccumulatedBytes);
333
+ }
305
334
  chunks.push(chunk.subarray());
306
335
  continue;
307
336
  }
@@ -440,6 +469,9 @@ export class ReqResp implements ReqRespInterface {
440
469
  await pipeline(
441
470
  stream.source,
442
471
  async function* (source: any) {
472
+ // Req/resp is one-request-one-response: an honest sender writes a single payload and half-closes.
473
+ // Process only the first chunk and return, so extra frames a peer queues on the same stream cannot
474
+ // drive further handler invocations that bypass the per-stream rate-limit check in streamHandler.
443
475
  for await (const chunk of source) {
444
476
  const response = await handler(connection.remotePeer, chunk.subarray());
445
477
 
@@ -454,6 +486,7 @@ export class ReqResp implements ReqRespInterface {
454
486
 
455
487
  yield SUCCESS;
456
488
  yield snappy.outboundTransformData(response);
489
+ return;
457
490
  }
458
491
  },
459
492
  stream.sink,
@@ -564,6 +597,13 @@ export class ReqResp implements ReqRespInterface {
564
597
  return PeerErrorSeverity.LowToleranceError;
565
598
  }
566
599
 
600
+ // Response exceeded the reception buffer bound: the peer streamed more compressed data than the
601
+ // per-request size limit allows. Same protocol violation as above, penalize harshly.
602
+ if (e instanceof ResponseSizeLimitExceededError) {
603
+ this.logger.warn(`Oversized response from peer ${peerId.toString()} in ${subProtocol}: ${e.message}`, logTags);
604
+ return PeerErrorSeverity.LowToleranceError;
605
+ }
606
+
567
607
  return this.categorizeConnectionErrors(e, peerId, subProtocol);
568
608
  }
569
609
 
@@ -58,6 +58,19 @@ export type DuplicateProposalInfo = {
58
58
  */
59
59
  export type P2PDuplicateProposalCallback = (info: DuplicateProposalInfo) => void;
60
60
 
61
+ /** Minimal info passed to the oversized proposal callback. */
62
+ export type OversizedProposalInfo = {
63
+ slot: SlotNumber;
64
+ proposer: EthAddress;
65
+ };
66
+
67
+ /**
68
+ * Callback for when a block proposal whose index lands at or beyond the consensus per-checkpoint block
69
+ * limit is stored and re-broadcast as slashing evidence. May fire multiple times per (slot, proposer)
70
+ * if the proposer signed several oversized proposals; consumers are expected to dedup.
71
+ */
72
+ export type P2POversizedProposalCallback = (info: OversizedProposalInfo) => void;
73
+
61
74
  /** Minimal info passed to the duplicate attestation callback. */
62
75
  export type DuplicateAttestationInfo = {
63
76
  slot: SlotNumber;
@@ -108,6 +121,12 @@ export interface P2PService {
108
121
  */
109
122
  registerDuplicateProposalCallback(callback: P2PDuplicateProposalCallback): void;
110
123
 
124
+ /**
125
+ * Registers a callback invoked when an oversized block proposal (index at or beyond the consensus
126
+ * per-checkpoint block limit) is stored and re-broadcast as slashing evidence.
127
+ */
128
+ registerOversizedProposalCallback(callback: P2POversizedProposalCallback): void;
129
+
111
130
  /**
112
131
  * Registers a callback invoked when a duplicate attestation is detected (equivocation).
113
132
  * A validator signing attestations for different proposals at the same slot.
@@ -171,6 +171,10 @@ export class InMemoryTxPool extends EventEmitter implements TxPoolV2 {
171
171
  return Promise.resolve(this.txsByHash.size);
172
172
  }
173
173
 
174
+ async hasEligiblePendingTxs(minCount: number): Promise<boolean> {
175
+ return (await this.getPendingTxCount()) >= minCount;
176
+ }
177
+
174
178
  getMinedTxHashes(): Promise<[TxHash, L2BlockId][]> {
175
179
  return Promise.resolve([]);
176
180
  }