@aztec/p2p 0.0.1-commit.db765a8 → 0.0.1-commit.df81a97b5

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 (208) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +2 -2
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +21 -8
  5. package/dest/client/p2p_client.d.ts +1 -1
  6. package/dest/client/p2p_client.d.ts.map +1 -1
  7. package/dest/client/p2p_client.js +22 -34
  8. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +3 -3
  9. package/dest/config.d.ts +24 -2
  10. package/dest/config.d.ts.map +1 -1
  11. package/dest/config.js +66 -7
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +3 -3
  13. package/dest/mem_pools/attestation_pool/attestation_pool.js +3 -3
  14. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +6 -6
  15. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  16. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  17. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  18. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  19. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  20. package/dest/mem_pools/tx_pool/priority.js +4 -4
  21. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  22. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  23. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  24. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  25. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +2 -1
  27. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
  28. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
  30. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
  32. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
  33. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  35. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +9 -5
  36. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +25 -10
  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 +33 -10
  41. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  42. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +26 -43
  44. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
  45. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  46. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
  47. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  48. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  49. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +21 -6
  50. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -1
  51. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  52. package/dest/msg_validators/attestation_validator/attestation_validator.js +5 -4
  53. package/dest/msg_validators/clock_tolerance.d.ts +1 -1
  54. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  55. package/dest/msg_validators/clock_tolerance.js +4 -3
  56. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +5 -4
  57. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  58. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  59. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +5 -4
  60. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  61. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  62. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +12 -9
  63. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  64. package/dest/msg_validators/proposal_validator/proposal_validator.js +51 -49
  65. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +1 -1
  66. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  67. package/dest/msg_validators/tx_validator/allowed_public_setup.js +21 -32
  68. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  69. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  70. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  71. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  72. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  73. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  74. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  75. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  76. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  77. package/dest/msg_validators/tx_validator/factory.d.ts +23 -4
  78. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  79. package/dest/msg_validators/tx_validator/factory.js +36 -10
  80. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
  81. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
  82. package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
  83. package/dest/msg_validators/tx_validator/gas_validator.d.ts +13 -4
  84. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  85. package/dest/msg_validators/tx_validator/gas_validator.js +39 -9
  86. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  87. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  88. package/dest/msg_validators/tx_validator/index.js +1 -0
  89. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  90. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  91. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  92. package/dest/msg_validators/tx_validator/phases_validator.d.ts +21 -1
  93. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  94. package/dest/msg_validators/tx_validator/phases_validator.js +49 -2
  95. package/dest/services/encoding.d.ts +5 -1
  96. package/dest/services/encoding.d.ts.map +1 -1
  97. package/dest/services/encoding.js +7 -1
  98. package/dest/services/libp2p/libp2p_service.d.ts +4 -9
  99. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  100. package/dest/services/libp2p/libp2p_service.js +125 -68
  101. package/dest/services/peer-manager/peer_manager.d.ts +1 -1
  102. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  103. package/dest/services/peer-manager/peer_manager.js +4 -2
  104. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +11 -8
  105. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  106. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +69 -65
  107. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +3 -2
  108. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  109. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +5 -4
  110. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  111. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +13 -7
  112. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +3 -1
  113. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  114. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +3 -0
  115. package/dest/services/reqresp/reqresp.d.ts +1 -1
  116. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  117. package/dest/services/reqresp/reqresp.js +17 -9
  118. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -4
  119. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  120. package/dest/services/tx_collection/fast_tx_collection.js +57 -73
  121. package/dest/services/tx_collection/proposal_tx_collector.d.ts +6 -7
  122. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  123. package/dest/services/tx_collection/proposal_tx_collector.js +4 -4
  124. package/dest/services/tx_collection/request_tracker.d.ts +53 -0
  125. package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
  126. package/dest/services/tx_collection/request_tracker.js +84 -0
  127. package/dest/services/tx_collection/slow_tx_collection.js +1 -1
  128. package/dest/services/tx_collection/tx_collection.d.ts +3 -6
  129. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  130. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  131. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  132. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  133. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  134. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  135. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  136. package/dest/test-helpers/testbench-utils.js +22 -3
  137. package/dest/testbench/p2p_client_testbench_worker.js +5 -4
  138. package/dest/testbench/worker_client_manager.d.ts +3 -1
  139. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  140. package/dest/testbench/worker_client_manager.js +6 -2
  141. package/dest/util.d.ts +1 -1
  142. package/package.json +14 -14
  143. package/src/client/factory.ts +36 -12
  144. package/src/client/p2p_client.ts +22 -34
  145. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +4 -6
  146. package/src/config.ts +92 -4
  147. package/src/mem_pools/attestation_pool/attestation_pool.ts +3 -3
  148. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +6 -6
  149. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  150. package/src/mem_pools/tx_pool/priority.ts +4 -4
  151. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  152. package/src/mem_pools/tx_pool_v2/README.md +9 -1
  153. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  154. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
  155. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
  156. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
  157. package/src/mem_pools/tx_pool_v2/interfaces.ts +9 -4
  158. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +52 -12
  159. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +29 -43
  160. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +13 -1
  161. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +23 -6
  162. package/src/msg_validators/attestation_validator/README.md +49 -0
  163. package/src/msg_validators/attestation_validator/attestation_validator.ts +5 -4
  164. package/src/msg_validators/clock_tolerance.ts +4 -3
  165. package/src/msg_validators/proposal_validator/README.md +123 -0
  166. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +13 -3
  167. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +19 -6
  168. package/src/msg_validators/proposal_validator/proposal_validator.ts +63 -53
  169. package/src/msg_validators/tx_validator/README.md +5 -1
  170. package/src/msg_validators/tx_validator/allowed_public_setup.ts +16 -35
  171. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  172. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  173. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  174. package/src/msg_validators/tx_validator/factory.ts +43 -3
  175. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  176. package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
  177. package/src/msg_validators/tx_validator/index.ts +1 -0
  178. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  179. package/src/msg_validators/tx_validator/phases_validator.ts +60 -1
  180. package/src/services/encoding.ts +9 -1
  181. package/src/services/libp2p/libp2p_service.ts +118 -78
  182. package/src/services/peer-manager/peer_manager.ts +5 -2
  183. package/src/services/reqresp/README.md +229 -0
  184. package/src/services/reqresp/batch-tx-requester/README.md +46 -7
  185. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +64 -69
  186. package/src/services/reqresp/batch-tx-requester/interface.ts +2 -1
  187. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +13 -6
  188. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +5 -0
  189. package/src/services/reqresp/reqresp.ts +19 -11
  190. package/src/services/tx_collection/fast_tx_collection.ts +57 -83
  191. package/src/services/tx_collection/proposal_tx_collector.ts +8 -13
  192. package/src/services/tx_collection/request_tracker.ts +127 -0
  193. package/src/services/tx_collection/slow_tx_collection.ts +1 -1
  194. package/src/services/tx_collection/tx_collection.ts +3 -5
  195. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  196. package/src/test-helpers/reqresp-nodes.ts +1 -1
  197. package/src/test-helpers/testbench-utils.ts +29 -3
  198. package/src/testbench/p2p_client_testbench_worker.ts +5 -6
  199. package/src/testbench/worker_client_manager.ts +13 -5
  200. package/src/util.ts +1 -1
  201. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -24
  202. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
  203. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -378
  204. package/dest/services/tx_collection/missing_txs_tracker.d.ts +0 -32
  205. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +0 -1
  206. package/dest/services/tx_collection/missing_txs_tracker.js +0 -27
  207. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -373
  208. package/src/services/tx_collection/missing_txs_tracker.ts +0 -52
@@ -170,6 +170,37 @@ class BlockTxsResponse {
170
170
 
171
171
  The `BitVector` is a compact representation where each bit corresponds to a transaction index in the block proposal. This allows efficient capability advertisement without repeating full hashes.
172
172
 
173
+ ## Cancellation
174
+
175
+ All cancellation is managed by a single `RequestTracker` instance, shared across the entire collection
176
+ flow. The `RequestTracker` owns the deadline, tracks which txs are still missing, and exposes a
177
+ `cancellationToken` promise that resolves when the request should stop (deadline hit, all txs fetched,
178
+ or external `cancel()` call).
179
+
180
+ Cancellation propagates from the deepest stack level upward:
181
+
182
+ ```
183
+ RequestTracker.finish()
184
+ ├── resolves cancellationToken promise
185
+
186
+ ├── BatchTxRequester workers (deepest)
187
+ │ ├── shouldStop() checks requestTracker.cancelled → exit loop
188
+ │ ├── sleepClampedToDeadline races sleep vs cancellationToken → wakes
189
+ │ └── semaphore.acquire races vs cancellationToken → wakes
190
+ │ │
191
+ │ ▼ workers settle → txQueue.end() → generator returns
192
+
193
+ ├── Node collection loops
194
+ │ ├── notFinished() checks requestTracker.cancelled → exit loop
195
+ │ └── inter-retry sleep races vs cancellationToken → wakes
196
+ │ │
197
+ │ ▼ all node loops settle
198
+
199
+ └── collectFast (outermost)
200
+ awaits Promise.allSettled([reqresp, nodes]) → settles after inner tasks
201
+ finally: requestTracker.cancel() (idempotent), cleanup
202
+ ```
203
+
173
204
  ## Key Files
174
205
 
175
206
  | File | Description |
@@ -179,15 +210,16 @@ The `BitVector` is a compact representation where each bit corresponds to a tran
179
210
  | `peer_collection.ts` | Manages peer classification (dumb/smart/bad) and rate limiting |
180
211
  | `interface.ts` | Type definitions for dependencies |
181
212
  | `../protocols/block_txs/` | Wire protocol definitions (`BlockTxsRequest`, `BlockTxsResponse`, `BitVector`) |
213
+ | `../../tx_collection/request_tracker.ts` | Centralized deadline, missing tx tracking, and cancellation signal |
182
214
 
183
215
  ## Stopping Conditions
184
216
 
185
- The `BatchTxRequester` stops when any of these conditions are met:
217
+ The `BatchTxRequester` stops when any of these conditions are met, all managed by the `RequestTracker`:
186
218
 
187
- 1. **All transactions fetched** - Success!
188
- 2. **Deadline exceeded** - Timeout configured by caller
189
- 3. **Abort signal** - External cancellation
190
- 4. **No transactions to fetch** - Nothing was missing
219
+ 1. **All transactions fetched** - `markFetched()` removes the last missing tx, triggering `finish()`
220
+ 2. **Deadline exceeded** - `setTimeout` in `RequestTracker` fires, triggering `finish()`
221
+ 3. **External cancellation** - `RequestTracker.cancel()` called (e.g., from `stop()`, `stopCollectingForBlocksUpTo`)
222
+ 4. **No transactions to fetch** - Empty hash set at construction, `RequestTracker` finishes immediately
191
223
 
192
224
  ## Configuration
193
225
 
@@ -228,11 +260,15 @@ Request to peer fails
228
260
  ## Usage Example
229
261
 
230
262
  ```typescript
263
+ const requestTracker = RequestTracker.create(
264
+ missingTxHashes, // TxHash[] - what we need
265
+ new Date(Date.now() + 5_000), // deadline
266
+ );
267
+
231
268
  const requester = new BatchTxRequester(
232
- missingTxHashes, // TxHash[] - what we need
269
+ requestTracker, // IRequestTracker - tracks missing txs, deadline, and cancellation
233
270
  blockTxsSource, // BlockTxsSource - the proposal or block we need txs for
234
271
  pinnedPeer, // PeerId | undefined - peer expected to have the txs
235
- timeoutMs, // number - how long to try
236
272
  p2pService, // BatchTxRequesterLibP2PService
237
273
  );
238
274
 
@@ -273,6 +309,8 @@ const txs = await BatchTxRequester.collectAllTxs(requester.run());
273
309
  │ 1. Try RPC nodes first (fast) │ │ Periodic polling of RPC nodes │
274
310
  │ 2. Fall back to BatchTxRequester │ │ and peers for missing txs │
275
311
  │ │ │ │
312
+ │ Creates RequestTracker per │ │ │
313
+ │ request with deadline │ │ │
276
314
  └───────────────────┬───────────────┘ └─────────────────────────────────────┘
277
315
 
278
316
  │ For 'proposal' and 'block' requests
@@ -281,6 +319,7 @@ const txs = await BatchTxRequester.collectAllTxs(requester.run());
281
319
  │ BatchTxRequester │
282
320
  │ │
283
321
  │ Aggressive parallel fetching from multiple peers │
322
+ │ Shares RequestTracker with FastTxCollection for unified cancellation │
284
323
  │ Uses BLOCK_TXS sub-protocol for efficient batching │
285
324
  └───────────────────┬─────────────────────────────────────────────────────────┘
286
325
 
@@ -1,15 +1,14 @@
1
1
  import { chunkWrapAround } from '@aztec/foundation/collection';
2
- import { TimeoutError } from '@aztec/foundation/error';
3
2
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
3
  import { FifoMemoryQueue, type ISemaphore, Semaphore } from '@aztec/foundation/queue';
5
4
  import { sleep } from '@aztec/foundation/sleep';
6
- import { DateProvider, executeTimeout } from '@aztec/foundation/timer';
5
+ import { DateProvider } from '@aztec/foundation/timer';
7
6
  import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
8
7
  import { Tx, TxArray, TxHash } from '@aztec/stdlib/tx';
9
8
 
10
9
  import type { PeerId } from '@libp2p/interface';
11
10
 
12
- import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_tracker.js';
11
+ import type { IRequestTracker } from '../../tx_collection/request_tracker.js';
13
12
  import { ReqRespSubProtocol } from '.././interface.js';
14
13
  import { BlockTxsRequest, BlockTxsResponse, type BlockTxsSource } from '.././protocols/index.js';
15
14
  import { ReqRespStatus } from '.././status.js';
@@ -42,16 +41,14 @@ import { BatchRequestTxValidator, type IBatchRequestTxValidator } from './tx_val
42
41
  * - Is the peer which was unable to send us successful response N times in a row
43
42
  * */
44
43
  export class BatchTxRequester {
44
+ private readonly requestTracker: IRequestTracker;
45
45
  private readonly blockTxsSource: BlockTxsSource;
46
46
  private readonly pinnedPeer: PeerId | undefined;
47
- private readonly timeoutMs: number;
48
47
  private readonly p2pService: BatchTxRequesterLibP2PService;
49
48
  private readonly logger: Logger;
50
- private readonly dateProvider: DateProvider;
51
49
  private readonly opts: BatchTxRequesterOptions;
52
50
  private readonly peers: IPeerCollection;
53
51
  private readonly txsMetadata: ITxMetadataCollection;
54
- private readonly deadline: number;
55
52
  private readonly smartRequesterSemaphore: ISemaphore;
56
53
  private readonly txQueue: FifoMemoryQueue<Tx>;
57
54
  private readonly txValidator: IBatchRequestTxValidator;
@@ -60,21 +57,19 @@ export class BatchTxRequester {
60
57
  private readonly txBatchSize: number;
61
58
 
62
59
  constructor(
63
- missingTxsTracker: IMissingTxsTracker,
60
+ requestTracker: IRequestTracker,
64
61
  blockTxsSource: BlockTxsSource,
65
62
  pinnedPeer: PeerId | undefined,
66
- timeoutMs: number,
67
63
  p2pService: BatchTxRequesterLibP2PService,
68
64
  logger?: Logger,
69
65
  dateProvider?: DateProvider,
70
66
  opts?: BatchTxRequesterOptions,
71
67
  ) {
68
+ this.requestTracker = requestTracker;
72
69
  this.blockTxsSource = blockTxsSource;
73
70
  this.pinnedPeer = pinnedPeer;
74
- this.timeoutMs = timeoutMs;
75
71
  this.p2pService = p2pService;
76
72
  this.logger = logger ?? createLogger('p2p:reqresp_batch');
77
- this.dateProvider = dateProvider ?? new DateProvider();
78
73
  this.opts = opts ?? {};
79
74
 
80
75
  this.smartParallelWorkerCount =
@@ -82,7 +77,6 @@ export class BatchTxRequester {
82
77
  this.dumbParallelWorkerCount =
83
78
  this.opts.dumbParallelWorkerCount ?? DEFAULT_BATCH_TX_REQUESTER_DUMB_PARALLEL_WORKER_COUNT;
84
79
  this.txBatchSize = this.opts.txBatchSize ?? DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE;
85
- this.deadline = this.dateProvider.now() + this.timeoutMs;
86
80
  this.txQueue = new FifoMemoryQueue(this.logger);
87
81
  this.txValidator = this.opts.txValidator ?? new BatchRequestTxValidator(this.p2pService.txValidatorConfig);
88
82
 
@@ -93,12 +87,12 @@ export class BatchTxRequester {
93
87
  this.peers = new PeerCollection(
94
88
  this.p2pService.connectionSampler,
95
89
  this.pinnedPeer,
96
- this.dateProvider,
90
+ dateProvider ?? new DateProvider(),
97
91
  badPeerThreshold,
98
92
  this.p2pService.peerScoring,
99
93
  );
100
94
  }
101
- this.txsMetadata = new MissingTxMetadataCollection(missingTxsTracker, this.txBatchSize);
95
+ this.txsMetadata = new MissingTxMetadataCollection(requestTracker, this.txBatchSize);
102
96
  this.smartRequesterSemaphore = this.opts.semaphore ?? new Semaphore(0);
103
97
  }
104
98
 
@@ -106,40 +100,30 @@ export class BatchTxRequester {
106
100
  * Fetches all missing transactions and yields them one by one
107
101
  * */
108
102
  public async *run(): AsyncGenerator<Tx, Tx | undefined, unknown> {
109
- // Our timeout is represented in milliseconds but queue expects seconds
110
- // We also want to make sure we wait at least 1 second in case of very low timeouts
111
- const timeoutQueueAfter = Math.max(Math.ceil(this.timeoutMs / 1_000), 1);
112
103
  try {
113
104
  if (this.txsMetadata.getMissingTxHashes().size === 0) {
114
105
  return undefined;
115
106
  }
116
107
 
117
- // Start workers in background
118
- const workersPromise = executeTimeout(
119
- () => Promise.allSettled([this.smartRequester(), this.dumbRequester(), this.pinnedPeerRequester()]),
120
- this.timeoutMs,
121
- ).finally(() => {
108
+ // Start workers in background. Workers stop themselves via requestTracker.checkCancelled().
109
+ const workersPromise = Promise.allSettled([
110
+ this.smartRequester(),
111
+ this.dumbRequester(),
112
+ this.pinnedPeerRequester(),
113
+ ]).finally(() => {
122
114
  this.txQueue.end();
123
115
  });
124
116
 
117
+ // Yield txs as workers put them on the queue. The queue's end() drains remaining items
118
+ // before returning null, so we don't lose any txs.
125
119
  while (true) {
126
- const tx = await this.txQueue.get(timeoutQueueAfter);
120
+ const tx = await this.txQueue.get();
127
121
 
128
- // null indicates that the queue has ended
129
122
  if (tx === null) {
130
123
  break;
131
124
  }
132
125
 
133
126
  yield tx;
134
-
135
- if (this.shouldStop()) {
136
- // Drain queue before ending
137
- let remaining;
138
- while ((remaining = this.txQueue.getImmediate()) !== undefined) {
139
- yield remaining;
140
- }
141
- break;
142
- }
143
127
  }
144
128
 
145
129
  this.unlockSmartRequesterSemaphores();
@@ -360,7 +344,10 @@ export class BatchTxRequester {
360
344
  ) {
361
345
  try {
362
346
  this.logger.trace(`Smart worker ${workerIndex} started`);
363
- await executeTimeout((_: AbortSignal) => this.smartRequesterSemaphore.acquire(), this.timeoutMs);
347
+ await Promise.race([this.smartRequesterSemaphore.acquire(), this.requestTracker.cancellationToken]);
348
+ if (this.requestTracker.checkCancelled()) {
349
+ return;
350
+ }
364
351
  this.logger.trace(`Smart worker ${workerIndex} acquired semaphore`);
365
352
 
366
353
  while (!this.shouldStop()) {
@@ -384,7 +371,10 @@ export class BatchTxRequester {
384
371
  //
385
372
  // When a dumb peer responds with valid txIndices, it gets
386
373
  // promoted to smart and releases the semaphore, waking this worker.
387
- await executeTimeout((_: AbortSignal) => this.smartRequesterSemaphore.acquire(), this.timeoutMs);
374
+ await Promise.race([this.smartRequesterSemaphore.acquire(), this.requestTracker.cancellationToken]);
375
+ if (this.requestTracker.checkCancelled()) {
376
+ break;
377
+ }
388
378
  this.logger.debug(`Worker loop smart: acquired next smart peer`);
389
379
  continue;
390
380
  }
@@ -411,11 +401,7 @@ export class BatchTxRequester {
411
401
  });
412
402
  }
413
403
  } catch (err: any) {
414
- if (err instanceof TimeoutError) {
415
- this.logger.debug(`Smart worker ${workerIndex} timed out waiting for semaphore`);
416
- } else {
417
- this.logger.error(`Smart worker ${workerIndex} encountered an error: ${err}`);
418
- }
404
+ this.logger.error(`Smart worker ${workerIndex} encountered an error: ${err}`);
419
405
  } finally {
420
406
  this.logger.debug(`Smart worker ${workerIndex} finished`);
421
407
  }
@@ -463,9 +449,18 @@ export class BatchTxRequester {
463
449
  * this implies we will query these peers couple of more times and give them a chance to "redeem" themselves before completely ignoring them
464
450
  */
465
451
  private handleFailResponseFromPeer(peerId: PeerId, responseStatus: ReqRespStatus) {
466
- //TODO: Should we ban these peers?
467
452
  if (responseStatus === ReqRespStatus.FAILURE || responseStatus === ReqRespStatus.UNKNOWN) {
468
453
  this.peers.penalisePeer(peerId, PeerErrorSeverity.HighToleranceError);
454
+ this.peers.markPeerDumb(peerId);
455
+ this.txsMetadata.clearPeerData(peerId);
456
+ return;
457
+ }
458
+
459
+ // NOT_FOUND means the peer pruned its block proposal — it can no longer serve
460
+ // index-based requests, but this is a legitimate state so we don't penalize.
461
+ if (responseStatus === ReqRespStatus.NOT_FOUND) {
462
+ this.peers.markPeerDumb(peerId);
463
+ this.txsMetadata.clearPeerData(peerId);
469
464
  return;
470
465
  }
471
466
 
@@ -519,6 +514,9 @@ export class BatchTxRequester {
519
514
  });
520
515
 
521
516
  if (hasInvalidTx) {
517
+ this.logger.warn(`Penalizing peer ${peerId.toString()} for sending invalid transactions in batch response`, {
518
+ peerId,
519
+ });
522
520
  this.peers.penalisePeer(peerId, PeerErrorSeverity.LowToleranceError);
523
521
  } else {
524
522
  // If we have received successful response from the peer, they have "redeemed" themselves and not considered bad anymore
@@ -555,10 +553,9 @@ export class BatchTxRequester {
555
553
  return;
556
554
  }
557
555
 
558
- // If block response is invalid we still want to query this peer in the future
559
- // Because they sent successful response, so they might become smart peer in the future
560
- // Or send us needed txs
561
- if (!this.isBlockResponseValid(response)) {
556
+ const hasArchiveRootMismatch = this.blockTxsSource.archive.toString() !== response.archiveRoot.toString();
557
+ if (hasArchiveRootMismatch) {
558
+ this.handleArchiveRootMismatch(peerId, response);
562
559
  return;
563
560
  }
564
561
 
@@ -576,13 +573,25 @@ export class BatchTxRequester {
576
573
  this.smartRequesterSemaphore.release();
577
574
  }
578
575
 
579
- private isBlockResponseValid(response: BlockTxsResponse): boolean {
580
- const archiveRootsMatch = this.blockTxsSource.archive.toString() === response.archiveRoot.toString();
581
- const peerHasSomeTxsFromProposal = !response.txIndices.isEmpty();
582
- return archiveRootsMatch && peerHasSomeTxsFromProposal;
576
+ /**
577
+ * Handles an archive root mismatch between local state and peer response.
578
+ *
579
+ * - Response archive is Fr.ZERO (peer pruned proposal, legitimate): marks peer dumb.
580
+ * - Non-zero archive mismatch (malicious response): penalises + marks dumb.
581
+ */
582
+ private handleArchiveRootMismatch(peerId: PeerId, response: BlockTxsResponse): void {
583
+ if (!response.archiveRoot.isZero()) {
584
+ this.peers.penalisePeer(peerId, PeerErrorSeverity.LowToleranceError);
585
+ }
586
+
587
+ this.peers.markPeerDumb(peerId);
588
+ this.txsMetadata.clearPeerData(peerId);
583
589
  }
584
590
 
585
591
  private peerHasSomeTxsWeAreMissing(_peerId: PeerId, response: BlockTxsResponse): boolean {
592
+ if (response.txIndices.isEmpty()) {
593
+ return false;
594
+ }
586
595
  const txsPeerHas = new Set(this.extractHashesPeerHasFromResponse(response).map(h => h.toString()));
587
596
  return this.txsMetadata.getMissingTxHashes().intersection(txsPeerHas).size > 0;
588
597
  }
@@ -631,27 +640,14 @@ export class BatchTxRequester {
631
640
  }
632
641
 
633
642
  /*
634
- * @returns true if all missing txs have been fetched */
635
- private fetchedAllTxs() {
636
- return this.txsMetadata.getMissingTxHashes().size == 0;
637
- }
638
-
639
- /*
640
- * Checks if the BatchTxRequester should stop fetching missing txs
641
- * Conditions for stopping are:
642
- * - There have been no missing transactions to start with
643
- * - All transactions have been fetched
644
- * - The deadline has been hit (no more time to fetch)
645
- * - This process has been cancelled via abortSignal
646
- *
647
- * @returns true if BatchTxRequester should stop, otherwise false*/
643
+ * Checks if the BatchTxRequester should stop fetching missing txs.
644
+ * Delegates to requestTracker which covers: deadline hit, all txs fetched, or external cancellation. */
648
645
  private shouldStop() {
649
- const aborted = this.opts.abortSignal?.aborted ?? false;
650
- if (aborted) {
646
+ if (this.requestTracker.checkCancelled()) {
651
647
  this.unlockSmartRequesterSemaphores();
652
648
  }
653
649
 
654
- return aborted || this.fetchedAllTxs() || this.dateProvider.now() > this.deadline;
650
+ return this.requestTracker.checkCancelled();
655
651
  }
656
652
 
657
653
  /*
@@ -669,10 +665,9 @@ export class BatchTxRequester {
669
665
  * This ensures we don't sleep past the deadline.
670
666
  * */
671
667
  private async sleepClampedToDeadline(durationMs: number) {
672
- const remaining = this.deadline - this.dateProvider.now();
673
- const thereIsTimeRemaining = remaining > 0;
674
- if (thereIsTimeRemaining) {
675
- await sleep(Math.min(durationMs, remaining));
668
+ if (this.requestTracker.checkCancelled()) {
669
+ return;
676
670
  }
671
+ await Promise.race([sleep(durationMs), this.requestTracker.cancellationToken]);
677
672
  }
678
673
  }
@@ -23,6 +23,8 @@ export interface ITxMetadataCollection {
23
23
  alreadyFetched(txHash: TxHash): boolean;
24
24
  // Returns true if tx was marked as fetched, false if it was already marked as fetched
25
25
  markPeerHas(peerId: PeerId, txHashes: TxHash[]): void;
26
+ /** Remove all tx metadata associations for a peer (e.g. on demotion from smart to dumb). */
27
+ clearPeerData(peerId: PeerId): void;
26
28
  }
27
29
 
28
30
  /**
@@ -47,7 +49,6 @@ export interface BatchTxRequesterOptions {
47
49
  //Injectable for testing purposes
48
50
  semaphore?: ISemaphore;
49
51
  peerCollection?: IPeerCollection;
50
- abortSignal?: AbortSignal;
51
52
  /** Optional tx validator for testing - if not provided, one is created from p2pService.txValidatorConfig */
52
53
  txValidator?: IBatchRequestTxValidator;
53
54
  }
@@ -2,7 +2,7 @@ import { type Tx, TxHash } from '@aztec/stdlib/tx';
2
2
 
3
3
  import type { PeerId } from '@libp2p/interface';
4
4
 
5
- import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_tracker.js';
5
+ import type { IRequestTracker } from '../../tx_collection/request_tracker.js';
6
6
  import { DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE } from './config.js';
7
7
  import type { ITxMetadataCollection } from './interface.js';
8
8
 
@@ -41,10 +41,10 @@ export class MissingTxMetadataCollection implements ITxMetadataCollection {
41
41
  private txMetadata = new Map<string, MissingTxMetadata>();
42
42
 
43
43
  constructor(
44
- private missingTxsTracker: IMissingTxsTracker,
44
+ private requestTracker: IRequestTracker,
45
45
  private readonly txBatchSize: number = DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE,
46
46
  ) {
47
- missingTxsTracker.missingTxHashes.forEach(hash => this.txMetadata.set(hash, new MissingTxMetadata(hash)));
47
+ requestTracker.missingTxHashes.forEach(hash => this.txMetadata.set(hash, new MissingTxMetadata(hash)));
48
48
  }
49
49
 
50
50
  public getPrioritizingNotInFlightAndLowerRequestCount(txs: string[]): MissingTxMetadata[] {
@@ -65,7 +65,7 @@ export class MissingTxMetadataCollection implements ITxMetadataCollection {
65
65
  }
66
66
 
67
67
  public getMissingTxHashes(): Set<string> {
68
- return this.missingTxsTracker.missingTxHashes;
68
+ return this.requestTracker.missingTxHashes;
69
69
  }
70
70
 
71
71
  public getTxsPeerHas(peer: PeerId): Set<string> {
@@ -128,7 +128,7 @@ export class MissingTxMetadataCollection implements ITxMetadataCollection {
128
128
  }
129
129
 
130
130
  public alreadyFetched(txHash: TxHash): boolean {
131
- return !this.missingTxsTracker.isMissing(txHash.toString());
131
+ return !this.requestTracker.isMissing(txHash.toString());
132
132
  }
133
133
 
134
134
  public markFetched(peerId: PeerId, tx: Tx): boolean {
@@ -144,7 +144,7 @@ export class MissingTxMetadataCollection implements ITxMetadataCollection {
144
144
  }
145
145
 
146
146
  txMeta.peers.add(peerId.toString());
147
- return this.missingTxsTracker.markFetched(tx);
147
+ return this.requestTracker.markFetched(tx);
148
148
  }
149
149
 
150
150
  public markPeerHas(peerId: PeerId, txHash: TxHash[]) {
@@ -158,4 +158,11 @@ export class MissingTxMetadataCollection implements ITxMetadataCollection {
158
158
  }
159
159
  });
160
160
  }
161
+
162
+ public clearPeerData(peerId: PeerId) {
163
+ const peerIdStr = peerId.toString();
164
+ for (const txMeta of this.txMetadata.values()) {
165
+ txMeta.peers.delete(peerIdStr);
166
+ }
167
+ }
161
168
  }
@@ -12,6 +12,7 @@ export const RATE_LIMIT_EXCEEDED_PEER_CACHE_TTL = 1000; // 1s
12
12
 
13
13
  export interface IPeerCollection {
14
14
  markPeerSmart(peerId: PeerId): void;
15
+ markPeerDumb(peerId: PeerId): void;
15
16
 
16
17
  /** Sample next peer in round-robin fashion. No smart peers if returns undefined */
17
18
  nextSmartPeerToQuery(): PeerId | undefined;
@@ -57,6 +58,10 @@ export class PeerCollection implements IPeerCollection {
57
58
  this.smartPeers.add(peerId.toString());
58
59
  }
59
60
 
61
+ public markPeerDumb(peerId: PeerId): void {
62
+ this.smartPeers.delete(peerId.toString());
63
+ }
64
+
60
65
  // We keep track of all peers that are queried for peer sampling algorithm
61
66
  private queriedSmartPeers: Set<string> = new Set<string>();
62
67
  private queriedDumbPeers: Set<string> = new Set<string>();
@@ -16,7 +16,7 @@ import {
16
16
  IndividualReqRespTimeoutError,
17
17
  InvalidResponseError,
18
18
  } from '../../errors/reqresp.error.js';
19
- import { SnappyTransform } from '../encoding.js';
19
+ import { OversizedSnappyResponseError, SnappyTransform } from '../encoding.js';
20
20
  import type { PeerScoring } from '../peer-manager/peer_scoring.js';
21
21
  import {
22
22
  DEFAULT_INDIVIDUAL_REQUEST_TIMEOUT_MS,
@@ -320,7 +320,7 @@ export class ReqResp implements ReqRespInterface {
320
320
  };
321
321
 
322
322
  for (const index of indices) {
323
- this.logger.info(`Sending request ${index} to peer ${peerAsString}`);
323
+ this.logger.trace(`Sending request ${index} to peer ${peerAsString}`);
324
324
  const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffers[index]);
325
325
 
326
326
  // Check the status of the response buffer
@@ -553,16 +553,10 @@ export class ReqResp implements ReqRespInterface {
553
553
  data: message,
554
554
  };
555
555
  } catch (e: any) {
556
+ // All errors (invalid status bytes, oversized snappy responses, corrupt data, etc.)
557
+ // are re-thrown so the caller can penalize the peer via handleResponseError.
556
558
  this.logger.debug(`Reading message failed: ${e.message}`);
557
-
558
- let status = ReqRespStatus.UNKNOWN;
559
- if (e instanceof ReqRespStatusError) {
560
- status = e.status;
561
- }
562
-
563
- return {
564
- status,
565
- };
559
+ throw e;
566
560
  }
567
561
  }
568
562
 
@@ -780,6 +774,20 @@ export class ReqResp implements ReqRespInterface {
780
774
  return undefined;
781
775
  }
782
776
 
777
+ // Invalid status byte: the peer sent a status byte that doesn't match any known status code.
778
+ // This is a protocol violation, penalize harshly.
779
+ if (e instanceof ReqRespStatusError) {
780
+ this.logger.warn(`Invalid status byte from peer ${peerId.toString()} in ${subProtocol}: ${e.message}`, logTags);
781
+ return PeerErrorSeverity.LowToleranceError;
782
+ }
783
+
784
+ // Oversized snappy response: the peer is sending data that exceeds the allowed size.
785
+ // This is a protocol violation that wastes bandwidth, so penalize harshly.
786
+ if (e instanceof OversizedSnappyResponseError) {
787
+ this.logger.warn(`Oversized response from peer ${peerId.toString()} in ${subProtocol}: ${e.message}`, logTags);
788
+ return PeerErrorSeverity.LowToleranceError;
789
+ }
790
+
783
791
  return this.categorizeConnectionErrors(e, peerId, subProtocol);
784
792
  }
785
793