@aztec/p2p 0.0.1-commit.6d63667d → 0.0.1-commit.858058eac

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 (198) hide show
  1. package/dest/client/factory.d.ts +5 -5
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +40 -9
  4. package/dest/client/interface.d.ts +33 -15
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +33 -36
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +104 -139
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
  10. package/dest/config.d.ts +13 -4
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +11 -1
  13. package/dest/index.d.ts +2 -1
  14. package/dest/index.d.ts.map +1 -1
  15. package/dest/index.js +1 -0
  16. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +102 -88
  17. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool.js +436 -3
  19. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
  20. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +353 -87
  22. package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
  23. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/index.js +1 -2
  25. package/dest/mem_pools/index.d.ts +3 -2
  26. package/dest/mem_pools/index.d.ts.map +1 -1
  27. package/dest/mem_pools/index.js +1 -1
  28. package/dest/mem_pools/interface.d.ts +5 -5
  29. package/dest/mem_pools/interface.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +87 -0
  31. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -0
  32. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +180 -0
  33. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -1
  34. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/index.js +1 -0
  36. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +3 -3
  37. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +25 -3
  39. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +36 -4
  41. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  42. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +77 -74
  44. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
  45. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  46. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +3 -3
  47. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  48. package/dest/msg_validators/tx_validator/block_header_validator.d.ts +16 -3
  49. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
  50. package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
  51. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +13 -3
  52. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
  53. package/dest/msg_validators/tx_validator/double_spend_validator.js +4 -4
  54. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +20 -4
  55. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  56. package/dest/msg_validators/tx_validator/timestamp_validator.js +2 -2
  57. package/dest/services/dummy_service.d.ts +10 -2
  58. package/dest/services/dummy_service.d.ts.map +1 -1
  59. package/dest/services/dummy_service.js +6 -0
  60. package/dest/services/encoding.d.ts +2 -2
  61. package/dest/services/encoding.d.ts.map +1 -1
  62. package/dest/services/encoding.js +2 -2
  63. package/dest/services/gossipsub/index.d.ts +3 -0
  64. package/dest/services/gossipsub/index.d.ts.map +1 -0
  65. package/dest/services/gossipsub/index.js +2 -0
  66. package/dest/services/gossipsub/scoring.d.ts +21 -3
  67. package/dest/services/gossipsub/scoring.d.ts.map +1 -1
  68. package/dest/services/gossipsub/scoring.js +24 -7
  69. package/dest/services/gossipsub/topic_score_params.d.ts +161 -0
  70. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -0
  71. package/dest/services/gossipsub/topic_score_params.js +324 -0
  72. package/dest/services/libp2p/libp2p_service.d.ts +84 -35
  73. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  74. package/dest/services/libp2p/libp2p_service.js +364 -265
  75. package/dest/services/peer-manager/peer_scoring.d.ts +1 -1
  76. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  77. package/dest/services/peer-manager/peer_scoring.js +25 -2
  78. package/dest/services/reqresp/interface.d.ts +10 -1
  79. package/dest/services/reqresp/interface.d.ts.map +1 -1
  80. package/dest/services/reqresp/interface.js +15 -1
  81. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +4 -3
  82. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  83. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +7 -1
  84. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  85. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +15 -0
  86. package/dest/services/reqresp/protocols/tx.d.ts +7 -1
  87. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  88. package/dest/services/reqresp/protocols/tx.js +20 -0
  89. package/dest/services/reqresp/reqresp.d.ts +1 -1
  90. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  91. package/dest/services/reqresp/reqresp.js +11 -4
  92. package/dest/services/service.d.ts +35 -1
  93. package/dest/services/service.d.ts.map +1 -1
  94. package/dest/services/tx_collection/config.d.ts +7 -1
  95. package/dest/services/tx_collection/config.d.ts.map +1 -1
  96. package/dest/services/tx_collection/config.js +16 -0
  97. package/dest/services/tx_collection/fast_tx_collection.d.ts +3 -1
  98. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  99. package/dest/services/tx_collection/fast_tx_collection.js +17 -3
  100. package/dest/services/tx_collection/file_store_tx_collection.d.ts +44 -0
  101. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -0
  102. package/dest/services/tx_collection/file_store_tx_collection.js +118 -0
  103. package/dest/services/tx_collection/file_store_tx_source.d.ts +27 -0
  104. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -0
  105. package/dest/services/tx_collection/file_store_tx_source.js +57 -0
  106. package/dest/services/tx_collection/index.d.ts +2 -1
  107. package/dest/services/tx_collection/index.d.ts.map +1 -1
  108. package/dest/services/tx_collection/index.js +1 -0
  109. package/dest/services/tx_collection/slow_tx_collection.d.ts +3 -1
  110. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  111. package/dest/services/tx_collection/slow_tx_collection.js +48 -19
  112. package/dest/services/tx_collection/tx_collection.d.ts +17 -7
  113. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  114. package/dest/services/tx_collection/tx_collection.js +58 -2
  115. package/dest/services/tx_collection/tx_collection_sink.d.ts +15 -6
  116. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  117. package/dest/services/tx_collection/tx_collection_sink.js +13 -7
  118. package/dest/services/tx_file_store/config.d.ts +1 -3
  119. package/dest/services/tx_file_store/config.d.ts.map +1 -1
  120. package/dest/services/tx_file_store/config.js +0 -4
  121. package/dest/services/tx_file_store/tx_file_store.d.ts +3 -3
  122. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
  123. package/dest/services/tx_provider.d.ts +3 -3
  124. package/dest/services/tx_provider.d.ts.map +1 -1
  125. package/dest/services/tx_provider.js +5 -4
  126. package/dest/test-helpers/make-test-p2p-clients.d.ts +3 -3
  127. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  128. package/dest/test-helpers/mock-pubsub.d.ts +27 -1
  129. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  130. package/dest/test-helpers/mock-pubsub.js +97 -2
  131. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  132. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  133. package/dest/test-helpers/reqresp-nodes.js +2 -1
  134. package/dest/test-helpers/testbench-utils.d.ts +38 -38
  135. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  136. package/dest/test-helpers/testbench-utils.js +124 -61
  137. package/dest/testbench/p2p_client_testbench_worker.js +2 -2
  138. package/package.json +14 -14
  139. package/src/client/factory.ts +68 -12
  140. package/src/client/interface.ts +39 -14
  141. package/src/client/p2p_client.ts +139 -162
  142. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  143. package/src/config.ts +26 -2
  144. package/src/index.ts +1 -0
  145. package/src/mem_pools/attestation_pool/attestation_pool.ts +488 -91
  146. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +442 -102
  147. package/src/mem_pools/attestation_pool/index.ts +9 -2
  148. package/src/mem_pools/index.ts +4 -1
  149. package/src/mem_pools/interface.ts +4 -4
  150. package/src/mem_pools/tx_pool_v2/README.md +59 -9
  151. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +234 -0
  152. package/src/mem_pools/tx_pool_v2/index.ts +1 -0
  153. package/src/mem_pools/tx_pool_v2/interfaces.ts +2 -2
  154. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +51 -5
  155. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +90 -77
  156. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +2 -2
  157. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +2 -2
  158. package/src/msg_validators/tx_validator/block_header_validator.ts +15 -3
  159. package/src/msg_validators/tx_validator/double_spend_validator.ts +11 -6
  160. package/src/msg_validators/tx_validator/timestamp_validator.ts +19 -14
  161. package/src/services/dummy_service.ts +12 -0
  162. package/src/services/encoding.ts +2 -2
  163. package/src/services/gossipsub/README.md +626 -0
  164. package/src/services/gossipsub/index.ts +2 -0
  165. package/src/services/gossipsub/scoring.ts +29 -5
  166. package/src/services/gossipsub/topic_score_params.ts +451 -0
  167. package/src/services/libp2p/libp2p_service.ts +360 -269
  168. package/src/services/peer-manager/peer_scoring.ts +25 -0
  169. package/src/services/reqresp/interface.ts +26 -1
  170. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +4 -3
  171. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +17 -0
  172. package/src/services/reqresp/protocols/tx.ts +22 -0
  173. package/src/services/reqresp/reqresp.ts +13 -3
  174. package/src/services/service.ts +40 -0
  175. package/src/services/tx_collection/config.ts +26 -0
  176. package/src/services/tx_collection/fast_tx_collection.ts +14 -2
  177. package/src/services/tx_collection/file_store_tx_collection.ts +152 -0
  178. package/src/services/tx_collection/file_store_tx_source.ts +70 -0
  179. package/src/services/tx_collection/index.ts +1 -0
  180. package/src/services/tx_collection/slow_tx_collection.ts +55 -26
  181. package/src/services/tx_collection/tx_collection.ts +78 -12
  182. package/src/services/tx_collection/tx_collection_sink.ts +17 -7
  183. package/src/services/tx_file_store/config.ts +0 -6
  184. package/src/services/tx_file_store/tx_file_store.ts +4 -4
  185. package/src/services/tx_provider.ts +8 -7
  186. package/src/test-helpers/make-test-p2p-clients.ts +3 -3
  187. package/src/test-helpers/mock-pubsub.ts +133 -3
  188. package/src/test-helpers/reqresp-nodes.ts +2 -1
  189. package/src/test-helpers/testbench-utils.ts +122 -74
  190. package/src/testbench/p2p_client_testbench_worker.ts +2 -2
  191. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
  192. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
  193. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
  194. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
  195. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  196. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
  197. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
  198. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
@@ -14,6 +14,21 @@ import type { PeerId } from '@libp2p/interface';
14
14
 
15
15
  import type { P2PConfig } from '../../config.js';
16
16
 
17
+ /**
18
+ * Application-level peer penalties.
19
+ *
20
+ * These scores are multiplied by appSpecificWeight (10) when contributing to gossipsub score.
21
+ * The values are designed to align with gossipsub thresholds:
22
+ *
23
+ * - LowToleranceError (50): 1 error → app score -50 → gossipsub -500 → gossipThreshold
24
+ * - MidToleranceError (10): 5 errors → app score -50 → gossipsub -500 → gossipThreshold
25
+ * - HighToleranceError (2): 25 errors → app score -50 → gossipsub -500 → gossipThreshold
26
+ *
27
+ * Examples of each severity:
28
+ * - LowToleranceError: Invalid messages, deserialization errors, manipulation attempts
29
+ * - MidToleranceError: Hash mismatches, protocol violations
30
+ * - HighToleranceError: Rate limit exceeded, failed responses, transient errors
31
+ */
17
32
  const DefaultPeerPenalties = {
18
33
  [PeerErrorSeverity.LowToleranceError]: 50,
19
34
  [PeerErrorSeverity.MidToleranceError]: 10,
@@ -26,6 +41,16 @@ export enum PeerScoreState {
26
41
  Healthy,
27
42
  }
28
43
 
44
+ /**
45
+ * Score thresholds for peer states.
46
+ *
47
+ * These values align with gossipsub thresholds when multiplied by appSpecificWeight (10):
48
+ * - MIN_SCORE_BEFORE_DISCONNECT (-50) × 10 = -500 = gossipThreshold
49
+ * - MIN_SCORE_BEFORE_BAN (-100) × 10 = -1000 = publishThreshold
50
+ *
51
+ * This ensures that when a peer is disconnected at the application level,
52
+ * they also stop receiving gossip, and when banned, they cannot publish.
53
+ */
29
54
  // TODO: move into config / constants
30
55
  const MIN_SCORE_BEFORE_BAN = -100;
31
56
  const MIN_SCORE_BEFORE_DISCONNECT = -50;
@@ -1,5 +1,6 @@
1
1
  import { Fr } from '@aztec/foundation/curves/bn254';
2
2
  import { L2Block } from '@aztec/stdlib/block';
3
+ import { MAX_L2_BLOCK_SIZE_KB } from '@aztec/stdlib/p2p';
3
4
  import { TxArray, TxHashArray } from '@aztec/stdlib/tx';
4
5
 
5
6
  import type { PeerId } from '@libp2p/interface';
@@ -7,8 +8,13 @@ import type { PeerId } from '@libp2p/interface';
7
8
  import type { P2PReqRespConfig } from './config.js';
8
9
  import type { ConnectionSampler } from './connection-sampler/connection_sampler.js';
9
10
  import { AuthRequest, AuthResponse } from './protocols/auth.js';
10
- import { BlockTxsRequest, BlockTxsResponse } from './protocols/block_txs/block_txs_reqresp.js';
11
+ import {
12
+ BlockTxsRequest,
13
+ BlockTxsResponse,
14
+ calculateBlockTxsResponseSize,
15
+ } from './protocols/block_txs/block_txs_reqresp.js';
11
16
  import { StatusMessage } from './protocols/status.js';
17
+ import { calculateTxResponseSize } from './protocols/tx.js';
12
18
  import type { ReqRespStatus } from './status.js';
13
19
 
14
20
  /*
@@ -211,6 +217,25 @@ export const subProtocolMap = {
211
217
  },
212
218
  };
213
219
 
220
+ /**
221
+ * Type for a function that calculates the expected response size in KB for a given request.
222
+ */
223
+ export type ExpectedResponseSizeCalculator = (requestBuffer: Buffer) => number;
224
+
225
+ /**
226
+ * Map of sub-protocols to their expected response size calculators.
227
+ * These are used to validate that responses don't exceed expected sizes based on request parameters.
228
+ */
229
+ export const subProtocolSizeCalculators: Record<ReqRespSubProtocol, ExpectedResponseSizeCalculator> = {
230
+ [ReqRespSubProtocol.TX]: calculateTxResponseSize,
231
+ [ReqRespSubProtocol.BLOCK_TXS]: calculateBlockTxsResponseSize,
232
+ [ReqRespSubProtocol.BLOCK]: () => MAX_L2_BLOCK_SIZE_KB,
233
+ [ReqRespSubProtocol.STATUS]: () => 1,
234
+ [ReqRespSubProtocol.PING]: () => 1,
235
+ [ReqRespSubProtocol.AUTH]: () => 1,
236
+ [ReqRespSubProtocol.GOODBYE]: () => 1, // No response expected, but provide minimal limit
237
+ };
238
+
214
239
  export interface ReqRespInterface {
215
240
  start(
216
241
  subProtocolHandlers: Partial<ReqRespSubProtocolHandlers>,
@@ -4,7 +4,8 @@ import { TxArray } from '@aztec/stdlib/tx';
4
4
 
5
5
  import type { PeerId } from '@libp2p/interface';
6
6
 
7
- import type { AttestationPool, TxPool } from '../../../../mem_pools/index.js';
7
+ import type { AttestationPoolApi } from '../../../../mem_pools/attestation_pool/attestation_pool.js';
8
+ import type { TxPoolV2 } from '../../../../mem_pools/tx_pool_v2/interfaces.js';
8
9
  import type { ReqRespSubProtocolHandler } from '../../interface.js';
9
10
  import { ReqRespStatus, ReqRespStatusError } from '../../status.js';
10
11
  import { BitVector } from './bitvector.js';
@@ -18,9 +19,9 @@ import { BlockTxsRequest, BlockTxsResponse } from './block_txs_reqresp.js';
18
19
  * @returns the BlockTxs request handler
19
20
  */
20
21
  export function reqRespBlockTxsHandler(
21
- attestationPool: AttestationPool,
22
+ attestationPool: AttestationPoolApi,
22
23
  archiver: L2BlockSource,
23
- txPool: TxPool,
24
+ txPool: TxPoolV2,
24
25
  ): ReqRespSubProtocolHandler {
25
26
  /**
26
27
  * Handler for block txs requests
@@ -1,5 +1,6 @@
1
1
  import { Fr } from '@aztec/foundation/curves/bn254';
2
2
  import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
3
+ import { MAX_TX_SIZE_KB } from '@aztec/stdlib/p2p';
3
4
  import { TxArray, type TxHash, TxHashArray } from '@aztec/stdlib/tx';
4
5
 
5
6
  import { BitVector } from './bitvector.js';
@@ -125,3 +126,19 @@ export class BlockTxsResponse {
125
126
  return new BlockTxsResponse(Fr.ZERO, new TxArray(), BitVector.init(0, []));
126
127
  }
127
128
  }
129
+
130
+ /**
131
+ * Calculate the expected response size for a BLOCK_TXS request.
132
+ * @param requestBuffer - The serialized request buffer containing BlockTxsRequest
133
+ * @returns Expected response size in KB
134
+ */
135
+ export function calculateBlockTxsResponseSize(requestBuffer: Buffer): number {
136
+ try {
137
+ const request = BlockTxsRequest.fromBuffer(requestBuffer);
138
+ const requestedTxCount = request.txIndices.getTrueIndices().length;
139
+ return requestedTxCount * MAX_TX_SIZE_KB + 1; // +1 KB overhead for serialization
140
+ } catch {
141
+ // If we can't parse the request, fall back to allowing a single transaction response
142
+ return MAX_TX_SIZE_KB + 1;
143
+ }
144
+ }
@@ -1,4 +1,5 @@
1
1
  import { chunk } from '@aztec/foundation/collection';
2
+ import { MAX_TX_SIZE_KB } from '@aztec/stdlib/p2p';
2
3
  import { TxArray, TxHash, TxHashArray } from '@aztec/stdlib/tx';
3
4
 
4
5
  import type { PeerId } from '@libp2p/interface';
@@ -55,3 +56,24 @@ export function reqRespTxHandler(mempools: MemPools): ReqRespSubProtocolHandler
55
56
  export function chunkTxHashesRequest(hashes: TxHash[], chunkSize = 1): Array<TxHashArray> {
56
57
  return chunk(hashes, chunkSize).map(chunk => new TxHashArray(...chunk));
57
58
  }
59
+
60
+ /**
61
+ * Calculate the expected response size for a TX request.
62
+ * @param requestBuffer - The serialized request buffer containing TxHashArray
63
+ * @returns Expected response size in KB
64
+ */
65
+ export function calculateTxResponseSize(requestBuffer: Buffer): number {
66
+ try {
67
+ const txHashes = TxHashArray.fromBuffer(requestBuffer);
68
+ // TxHashArray.fromBuffer returns empty array on parse failure, so check for that
69
+ if (txHashes.length === 0 && requestBuffer.length > 0) {
70
+ // If we got an empty array but had a non-empty buffer, parsing likely failed
71
+ // Fall back to allowing a single transaction response
72
+ return MAX_TX_SIZE_KB + 1;
73
+ }
74
+ return Math.max(txHashes.length, 1) * MAX_TX_SIZE_KB + 1; // +1 KB overhead, at least 1 tx
75
+ } catch {
76
+ // If we can't parse the request, fall back to allowing a single transaction response
77
+ return MAX_TX_SIZE_KB + 1;
78
+ }
79
+ }
@@ -36,6 +36,7 @@ import {
36
36
  type ReqRespSubProtocolValidators,
37
37
  type SubProtocolMap,
38
38
  responseFromBuffer,
39
+ subProtocolSizeCalculators,
39
40
  } from './interface.js';
40
41
  import { ReqRespMetrics } from './metrics.js';
41
42
  import {
@@ -437,6 +438,9 @@ export class ReqResp implements ReqRespInterface {
437
438
  try {
438
439
  this.metrics.recordRequestSent(subProtocol);
439
440
 
441
+ // Calculate expected response size based on the request payload
442
+ const expectedSizeKb = subProtocolSizeCalculators[subProtocol](payload);
443
+
440
444
  this.logger.trace(`Sending request to peer ${peerId.toString()} on sub protocol ${subProtocol}`);
441
445
  stream = await this.connectionSampler.dialProtocol(peerId, subProtocol, dialTimeout);
442
446
  this.logger.trace(
@@ -444,11 +448,14 @@ export class ReqResp implements ReqRespInterface {
444
448
  );
445
449
 
446
450
  const timeoutErr = new IndividualReqRespTimeoutError();
451
+ // Create a wrapper to pass the expected size to readMessage
452
+ const readMessageWithSizeLimit = (source: AsyncIterable<Uint8ArrayList>) =>
453
+ this.readMessage(source, expectedSizeKb);
447
454
  const [_, resp] = await executeTimeout(
448
455
  signal =>
449
456
  Promise.all([
450
457
  pipeline([payload], stream!.sink, { signal }),
451
- pipeline(stream!.source, this.readMessage.bind(this), { signal }),
458
+ pipeline(stream!.source, readMessageWithSizeLimit, { signal }),
452
459
  ]),
453
460
  this.individualRequestTimeoutMs,
454
461
  () => timeoutErr,
@@ -510,8 +517,11 @@ export class ReqResp implements ReqRespInterface {
510
517
  * The message is split into two components
511
518
  * - The first chunk should contain a control byte, indicating the status of the response see `ReqRespStatus`
512
519
  * - The second chunk should contain the response data
520
+ *
521
+ * @param source - The async iterable source of data chunks
522
+ * @param maxSizeKb - Optional maximum expected size in KB for the decompressed response
513
523
  */
514
- private async readMessage(source: AsyncIterable<Uint8ArrayList>): Promise<ReqRespResponse> {
524
+ private async readMessage(source: AsyncIterable<Uint8ArrayList>, maxSizeKb?: number): Promise<ReqRespResponse> {
515
525
  let status: ReqRespStatus | undefined;
516
526
  const chunks: Uint8Array[] = [];
517
527
 
@@ -536,7 +546,7 @@ export class ReqResp implements ReqRespInterface {
536
546
  }
537
547
 
538
548
  const messageData = Buffer.concat(chunks);
539
- const message: Buffer = this.snappyTransform.inboundTransformData(messageData);
549
+ const message: Buffer = this.snappyTransform.inboundTransformData(messageData, undefined, maxSizeKb);
540
550
 
541
551
  return {
542
552
  status: status ?? ReqRespStatus.UNKNOWN,
@@ -1,3 +1,4 @@
1
+ import type { SlotNumber } from '@aztec/foundation/branded-types';
1
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
2
3
  import type { PeerInfo } from '@aztec/stdlib/interfaces/server';
3
4
  import type { BlockProposal, CheckpointAttestation, CheckpointProposalCore, Gossipable } from '@aztec/stdlib/p2p';
@@ -43,6 +44,32 @@ export type P2PCheckpointReceivedCallback = (
43
44
 
44
45
  export type AuthReceivedCallback = (peerId: PeerId, authRequest: AuthRequest) => Promise<AuthResponse | undefined>;
45
46
 
47
+ /** Minimal info passed to the duplicate proposal callback. */
48
+ export type DuplicateProposalInfo = {
49
+ slot: SlotNumber;
50
+ proposer: EthAddress;
51
+ type: 'checkpoint' | 'block';
52
+ };
53
+
54
+ /**
55
+ * Callback for when a duplicate proposal is detected (equivocation).
56
+ * Invoked on the first duplicate (when count goes from 1 to 2).
57
+ */
58
+ export type P2PDuplicateProposalCallback = (info: DuplicateProposalInfo) => void;
59
+
60
+ /** Minimal info passed to the duplicate attestation callback. */
61
+ export type DuplicateAttestationInfo = {
62
+ slot: SlotNumber;
63
+ attester: EthAddress;
64
+ };
65
+
66
+ /**
67
+ * Callback for when a duplicate attestation is detected (equivocation).
68
+ * A validator signing attestations for different proposals at the same slot.
69
+ * Invoked on the first duplicate (when count goes from 1 to 2).
70
+ */
71
+ export type P2PDuplicateAttestationCallback = (info: DuplicateAttestationInfo) => void;
72
+
46
73
  /**
47
74
  * The interface for a P2P service implementation.
48
75
  */
@@ -86,6 +113,19 @@ export interface P2PService {
86
113
 
87
114
  registerCheckpointReceivedCallback(callback: P2PCheckpointReceivedCallback): void;
88
115
 
116
+ /**
117
+ * Registers a callback invoked when a duplicate proposal is detected (equivocation).
118
+ * The callback is triggered on the first duplicate (when count goes from 1 to 2).
119
+ */
120
+ registerDuplicateProposalCallback(callback: P2PDuplicateProposalCallback): void;
121
+
122
+ /**
123
+ * Registers a callback invoked when a duplicate attestation is detected (equivocation).
124
+ * A validator signing attestations for different proposals at the same slot.
125
+ * The callback is triggered on the first duplicate (when count goes from 1 to 2).
126
+ */
127
+ registerDuplicateAttestationCallback(callback: P2PDuplicateAttestationCallback): void;
128
+
89
129
  getEnr(): ENR | undefined;
90
130
 
91
131
  getPeers(includePending?: boolean): PeerInfo[];
@@ -31,6 +31,12 @@ export type TxCollectionConfig = {
31
31
  txCollectionNodeRpcMaxBatchSize: number;
32
32
  /** Which collector implementation to use for missing txs collection */
33
33
  txCollectionMissingTxsCollectorType: MissingTxsCollectorType;
34
+ /** A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection */
35
+ txCollectionFileStoreUrls: string[];
36
+ /** Delay in ms before file store collection starts after slow collection is triggered */
37
+ txCollectionFileStoreSlowDelayMs: number;
38
+ /** Delay in ms before file store collection starts after fast collection is triggered */
39
+ txCollectionFileStoreFastDelayMs: number;
34
40
  };
35
41
 
36
42
  export const txCollectionConfigMappings: ConfigMappingsType<TxCollectionConfig> = {
@@ -95,4 +101,24 @@ export const txCollectionConfigMappings: ConfigMappingsType<TxCollectionConfig>
95
101
  description: 'Which collector implementation to use for missing txs collection (new or old)',
96
102
  ...enumConfigHelper(['new', 'old'] as const, 'new'),
97
103
  },
104
+ txCollectionFileStoreUrls: {
105
+ env: 'TX_COLLECTION_FILE_STORE_URLS',
106
+ description: 'A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection',
107
+ parseEnv: (val: string) =>
108
+ val
109
+ .split(',')
110
+ .map(url => url.trim())
111
+ .filter(url => url.length > 0),
112
+ defaultValue: [],
113
+ },
114
+ txCollectionFileStoreSlowDelayMs: {
115
+ env: 'TX_COLLECTION_FILE_STORE_SLOW_DELAY_MS',
116
+ description: 'Delay before file store collection starts after slow collection',
117
+ ...numberConfigHelper(24_000),
118
+ },
119
+ txCollectionFileStoreFastDelayMs: {
120
+ env: 'TX_COLLECTION_FILE_STORE_FAST_DELAY_MS',
121
+ description: 'Delay before file store collection starts after fast collection',
122
+ ...numberConfigHelper(2_000),
123
+ },
98
124
  };
@@ -20,7 +20,7 @@ import {
20
20
  SendBatchRequestCollector,
21
21
  } from './proposal_tx_collector.js';
22
22
  import type { FastCollectionRequest, FastCollectionRequestInput } from './tx_collection.js';
23
- import type { TxCollectionSink } from './tx_collection_sink.js';
23
+ import type { TxAddContext, TxCollectionSink } from './tx_collection_sink.js';
24
24
  import type { TxSource } from './tx_source.js';
25
25
 
26
26
  export class FastTxCollection {
@@ -77,7 +77,7 @@ export class FastTxCollection {
77
77
  // This promise is used to await for the collection to finish during the main collectFast method.
78
78
  // It gets resolved in `foundTxs` when all txs have been collected, or rejected if the request is aborted or hits the deadline.
79
79
  const promise = promiseWithResolvers<void>();
80
- setTimeout(() => promise.reject(new TimeoutError(`Timed out while collecting txs`)), timeout);
80
+ const timeoutTimer = setTimeout(() => promise.reject(new TimeoutError(`Timed out while collecting txs`)), timeout);
81
81
 
82
82
  const request: FastCollectionRequest = {
83
83
  ...input,
@@ -89,6 +89,7 @@ export class FastTxCollection {
89
89
  };
90
90
 
91
91
  const [duration] = await elapsed(() => this.collectFast(request, { ...opts }));
92
+ clearTimeout(timeoutTimer);
92
93
 
93
94
  this.log.verbose(
94
95
  `Collected ${request.foundTxs.size} txs out of ${txHashes.length} for ${input.type} at slot ${blockInfo.slotNumber}`,
@@ -234,6 +235,7 @@ export class FastTxCollection {
234
235
  method: 'fast-node-rpc',
235
236
  ...request.blockInfo,
236
237
  },
238
+ this.getAddContext(request),
237
239
  );
238
240
 
239
241
  // Clear from the active requests the txs we just requested
@@ -287,6 +289,7 @@ export class FastTxCollection {
287
289
  },
288
290
  Array.from(request.missingTxHashes).map(txHash => TxHash.fromString(txHash)),
289
291
  { description: `reqresp for slot ${slotNumber}`, method: 'fast-req-resp', ...opts, ...request.blockInfo },
292
+ this.getAddContext(request),
290
293
  );
291
294
  } catch (err) {
292
295
  this.log.error(`Error sending fast reqresp request for txs`, err, {
@@ -296,6 +299,15 @@ export class FastTxCollection {
296
299
  }
297
300
  }
298
301
 
302
+ /** Returns the TxAddContext for the given request, used by the sink to add txs to the pool correctly. */
303
+ private getAddContext(request: FastCollectionRequest): TxAddContext {
304
+ if (request.type === 'proposal') {
305
+ return { type: 'proposal', blockHeader: request.blockProposal.blockHeader };
306
+ } else {
307
+ return { type: 'mined', block: request.block };
308
+ }
309
+ }
310
+
299
311
  /**
300
312
  * Handle txs by marking them as found for the requests that are waiting for them, and resolves the request if all its txs have been found.
301
313
  * Called internally and from the main tx collection manager whenever the tx pool emits a tx-added event.
@@ -0,0 +1,152 @@
1
+ import { type Logger, createLogger } from '@aztec/foundation/log';
2
+ import { FifoMemoryQueue } from '@aztec/foundation/queue';
3
+ import { Tx, TxHash } from '@aztec/stdlib/tx';
4
+
5
+ import type { FileStoreTxSource } from './file_store_tx_source.js';
6
+ import type { TxAddContext, TxCollectionSink } from './tx_collection_sink.js';
7
+
8
+ // Internal constants (not configurable by node operators)
9
+ const FILE_STORE_DOWNLOAD_CONCURRENCY = 5; // Max concurrent downloads
10
+
11
+ /**
12
+ * Collects txs from file stores as a fallback after P2P methods have been tried.
13
+ * Runs in parallel to slow/fast collection. The delay before starting file store
14
+ * collection is managed by the TxCollection orchestrator, not this class.
15
+ */
16
+ export class FileStoreTxCollection {
17
+ /** Map from tx hash to add context for txs queued for download. */
18
+ private pendingTxs = new Map<string, TxAddContext>();
19
+
20
+ /**
21
+ * Tracks tx hashes found elsewhere, even before startCollecting is called.
22
+ * Needed because the orchestrator delays startCollecting via a real sleep, but foundTxs
23
+ * may arrive during that delay — before the hashes are added to pendingTxs.
24
+ */
25
+ private foundTxHashes = new Set<string>();
26
+
27
+ /** Queue of tx hashes to be downloaded. */
28
+ private downloadQueue = new FifoMemoryQueue<TxHash>();
29
+
30
+ /** Worker promises for concurrent downloads. */
31
+ private workers: Promise<void>[] = [];
32
+
33
+ /** Whether the collection has been started. */
34
+ private started = false;
35
+
36
+ constructor(
37
+ private readonly fileStoreSources: FileStoreTxSource[],
38
+ private readonly txCollectionSink: TxCollectionSink,
39
+ private readonly log: Logger = createLogger('p2p:file_store_tx_collection'),
40
+ ) {}
41
+
42
+ /** Starts the file store collection workers. */
43
+ public start() {
44
+ if (this.fileStoreSources.length === 0) {
45
+ this.log.debug('No file store sources configured, skipping file store collection');
46
+ return;
47
+ }
48
+
49
+ this.started = true;
50
+ this.downloadQueue = new FifoMemoryQueue<TxHash>();
51
+
52
+ // Start concurrent download workers
53
+ for (let i = 0; i < FILE_STORE_DOWNLOAD_CONCURRENCY; i++) {
54
+ this.workers.push(this.downloadQueue.process(txHash => this.processDownload(txHash)));
55
+ }
56
+
57
+ this.log.info(`Started file store tx collection with ${this.fileStoreSources.length} sources`, {
58
+ sources: this.fileStoreSources.map(s => s.getInfo()),
59
+ concurrency: FILE_STORE_DOWNLOAD_CONCURRENCY,
60
+ });
61
+ }
62
+
63
+ /** Stops all collection activity. */
64
+ public async stop() {
65
+ if (!this.started) {
66
+ return;
67
+ }
68
+ this.started = false;
69
+ this.downloadQueue.end();
70
+ await Promise.all(this.workers);
71
+ this.workers = [];
72
+ this.pendingTxs.clear();
73
+ this.foundTxHashes.clear();
74
+ }
75
+
76
+ /** Remove the given tx hashes from pending. */
77
+ public stopCollecting(txHashes: TxHash[]) {
78
+ for (const txHash of txHashes) {
79
+ const hashStr = txHash.toString();
80
+ this.pendingTxs.delete(hashStr);
81
+ }
82
+ }
83
+
84
+ /** Clears all pending state. Items already in the download queue will still be processed but won't be re-queued. */
85
+ public clearPending() {
86
+ this.pendingTxs.clear();
87
+ this.foundTxHashes.clear();
88
+ }
89
+
90
+ /** Queue the given tx hashes for file store collection. */
91
+ public startCollecting(txHashes: TxHash[], context: TxAddContext) {
92
+ for (const txHash of txHashes) {
93
+ const hashStr = txHash.toString();
94
+ if (!this.pendingTxs.has(hashStr) && !this.foundTxHashes.has(hashStr)) {
95
+ this.pendingTxs.set(hashStr, context);
96
+ this.downloadQueue.put(txHash);
97
+ }
98
+ }
99
+ }
100
+
101
+ /** Stop tracking txs that were found elsewhere. */
102
+ public foundTxs(txs: Tx[]) {
103
+ for (const tx of txs) {
104
+ const hashStr = tx.getTxHash().toString();
105
+ this.pendingTxs.delete(hashStr);
106
+ this.foundTxHashes.add(hashStr);
107
+ }
108
+ }
109
+
110
+ /** Processes a single tx hash from the download queue. */
111
+ private async processDownload(txHash: TxHash) {
112
+ const hashStr = txHash.toString();
113
+ const context = this.pendingTxs.get(hashStr);
114
+
115
+ // Skip if already found by another method
116
+ if (!context) {
117
+ return;
118
+ }
119
+
120
+ await this.downloadTx(txHash, context);
121
+ this.pendingTxs.delete(hashStr);
122
+ }
123
+
124
+ /** Attempt to download a tx from file stores (round-robin). */
125
+ private async downloadTx(txHash: TxHash, context: TxAddContext) {
126
+ const startIndex = Math.floor(Math.random() * this.fileStoreSources.length);
127
+ for (let i = startIndex; i < startIndex + this.fileStoreSources.length; i++) {
128
+ const source = this.fileStoreSources[i % this.fileStoreSources.length];
129
+
130
+ try {
131
+ const result = await this.txCollectionSink.collect(
132
+ hashes => source.getTxsByHash(hashes),
133
+ [txHash],
134
+ {
135
+ description: `file-store ${source.getInfo()}`,
136
+ method: 'file-store',
137
+ fileStore: source.getInfo(),
138
+ },
139
+ context,
140
+ );
141
+
142
+ if (result.txs.length > 0) {
143
+ return;
144
+ }
145
+ } catch (err) {
146
+ this.log.trace(`Failed to download tx ${txHash} from ${source.getInfo()}`, { err });
147
+ }
148
+ }
149
+
150
+ this.log.trace(`Tx ${txHash} not found in any file store`);
151
+ }
152
+ }
@@ -0,0 +1,70 @@
1
+ import { type Logger, createLogger } from '@aztec/foundation/log';
2
+ import { type ReadOnlyFileStore, createReadOnlyFileStore } from '@aztec/stdlib/file-store';
3
+ import { Tx, type TxHash } from '@aztec/stdlib/tx';
4
+
5
+ import type { TxSource } from './tx_source.js';
6
+
7
+ /** TxSource implementation that downloads txs from a file store. */
8
+ export class FileStoreTxSource implements TxSource {
9
+ private constructor(
10
+ private readonly fileStore: ReadOnlyFileStore,
11
+ private readonly baseUrl: string,
12
+ private readonly log: Logger,
13
+ ) {}
14
+
15
+ /**
16
+ * Creates a FileStoreTxSource from a URL.
17
+ * @param url - The file store URL (s3://, gs://, file://, http://, https://).
18
+ * @param log - Optional logger.
19
+ * @returns The FileStoreTxSource instance, or undefined if creation fails.
20
+ */
21
+ public static async create(
22
+ url: string,
23
+ log: Logger = createLogger('p2p:file_store_tx_source'),
24
+ ): Promise<FileStoreTxSource | undefined> {
25
+ try {
26
+ const fileStore = await createReadOnlyFileStore(url, log);
27
+ if (!fileStore) {
28
+ log.warn(`Failed to create file store for URL: ${url}`);
29
+ return undefined;
30
+ }
31
+ return new FileStoreTxSource(fileStore, url, log);
32
+ } catch (err) {
33
+ log.warn(`Error creating file store for URL: ${url}`, { error: err });
34
+ return undefined;
35
+ }
36
+ }
37
+
38
+ public getInfo(): string {
39
+ return `file-store:${this.baseUrl}`;
40
+ }
41
+
42
+ public getTxsByHash(txHashes: TxHash[]): Promise<(Tx | undefined)[]> {
43
+ return Promise.all(
44
+ txHashes.map(async txHash => {
45
+ const path = `txs/${txHash.toString()}.bin`;
46
+ try {
47
+ const buffer = await this.fileStore.read(path);
48
+ return Tx.fromBuffer(buffer);
49
+ } catch {
50
+ // Tx not found or error reading - return undefined
51
+ return undefined;
52
+ }
53
+ }),
54
+ );
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Creates FileStoreTxSource instances from URLs.
60
+ * @param urls - Array of file store URLs.
61
+ * @param log - Optional logger.
62
+ * @returns Array of successfully created FileStoreTxSource instances.
63
+ */
64
+ export async function createFileStoreTxSources(
65
+ urls: string[],
66
+ log: Logger = createLogger('p2p:file_store_tx_source'),
67
+ ): Promise<FileStoreTxSource[]> {
68
+ const sources = await Promise.all(urls.map(url => FileStoreTxSource.create(url, log)));
69
+ return sources.filter((s): s is FileStoreTxSource => s !== undefined);
70
+ }
@@ -5,3 +5,4 @@ export {
5
5
  BatchTxRequesterCollector,
6
6
  SendBatchRequestCollector,
7
7
  } from './proposal_tx_collector.js';
8
+ export { FileStoreTxSource, createFileStoreTxSources } from './file_store_tx_source.js';