@aztec/p2p 0.0.1-commit.9ef841308 → 0.0.1-commit.a89ec08

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 (207) hide show
  1. package/dest/client/factory.d.ts +2 -2
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +1 -2
  4. package/dest/client/p2p_client.d.ts +1 -1
  5. package/dest/client/p2p_client.d.ts.map +1 -1
  6. package/dest/client/p2p_client.js +4 -6
  7. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +6 -17
  8. package/dest/config.d.ts +6 -6
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +6 -6
  11. package/dest/index.d.ts +2 -1
  12. package/dest/index.d.ts.map +1 -1
  13. package/dest/index.js +1 -0
  14. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -4
  15. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  16. package/dest/mem_pools/attestation_pool/attestation_pool.js +4 -8
  17. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +6 -6
  18. package/dest/mem_pools/index.d.ts +2 -1
  19. package/dest/mem_pools/index.d.ts.map +1 -1
  20. package/dest/mem_pools/instrumentation.d.ts +2 -4
  21. package/dest/mem_pools/instrumentation.d.ts.map +1 -1
  22. package/dest/mem_pools/instrumentation.js +14 -16
  23. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +125 -0
  24. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -0
  25. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +596 -0
  26. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts +32 -0
  27. package/dest/mem_pools/tx_pool/eviction/eviction_manager.d.ts.map +1 -0
  28. package/dest/mem_pools/tx_pool/eviction/eviction_manager.js +112 -0
  29. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts +157 -0
  30. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.d.ts.map +1 -0
  31. package/dest/mem_pools/tx_pool/eviction/eviction_strategy.js +52 -0
  32. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +16 -0
  33. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -0
  34. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +123 -0
  35. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts +17 -0
  36. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.d.ts.map +1 -0
  37. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +84 -0
  38. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts +19 -0
  39. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.d.ts.map +1 -0
  40. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.js +78 -0
  41. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts +26 -0
  42. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.d.ts.map +1 -0
  43. package/dest/mem_pools/tx_pool/eviction/low_priority_eviction_rule.js +84 -0
  44. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts +25 -0
  45. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.d.ts.map +1 -0
  46. package/dest/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.js +57 -0
  47. package/dest/mem_pools/tx_pool/index.d.ts +3 -0
  48. package/dest/mem_pools/tx_pool/index.d.ts.map +1 -0
  49. package/dest/mem_pools/tx_pool/index.js +2 -0
  50. package/dest/mem_pools/tx_pool/priority.d.ts +12 -0
  51. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -0
  52. package/dest/mem_pools/tx_pool/priority.js +15 -0
  53. package/dest/mem_pools/tx_pool/tx_pool.d.ts +127 -0
  54. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -0
  55. package/dest/mem_pools/tx_pool/tx_pool.js +3 -0
  56. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +7 -0
  57. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -0
  58. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +402 -0
  59. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +5 -7
  60. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  61. package/dest/mem_pools/tx_pool_v2/interfaces.js +0 -1
  62. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +6 -5
  63. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  64. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +1 -5
  65. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  66. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  67. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +43 -26
  68. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +1 -1
  69. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  70. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +0 -3
  71. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -2
  72. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  73. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +1 -18
  74. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -1
  75. package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
  76. package/dest/msg_validators/attestation_validator/attestation_validator.js +4 -5
  77. package/dest/msg_validators/clock_tolerance.d.ts +1 -1
  78. package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
  79. package/dest/msg_validators/clock_tolerance.js +3 -4
  80. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +1 -1
  81. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  82. package/dest/msg_validators/proposal_validator/proposal_validator.js +5 -5
  83. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  84. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  85. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  86. package/dest/services/discv5/discV5_service.d.ts +1 -1
  87. package/dest/services/discv5/discV5_service.d.ts.map +1 -1
  88. package/dest/services/discv5/discV5_service.js +2 -4
  89. package/dest/services/libp2p/libp2p_service.d.ts +9 -7
  90. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  91. package/dest/services/libp2p/libp2p_service.js +59 -137
  92. package/dest/services/peer-manager/metrics.d.ts +1 -3
  93. package/dest/services/peer-manager/metrics.d.ts.map +1 -1
  94. package/dest/services/peer-manager/metrics.js +0 -6
  95. package/dest/services/peer-manager/peer_manager.d.ts +2 -6
  96. package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
  97. package/dest/services/peer-manager/peer_manager.js +9 -24
  98. package/dest/services/peer-manager/peer_scoring.d.ts +2 -5
  99. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  100. package/dest/services/peer-manager/peer_scoring.js +10 -28
  101. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +8 -11
  102. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  103. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +101 -82
  104. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +2 -3
  105. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  106. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +4 -5
  107. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  108. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +7 -13
  109. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +11 -19
  110. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  111. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +15 -52
  112. package/dest/services/reqresp/reqresp.d.ts +1 -1
  113. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  114. package/dest/services/reqresp/reqresp.js +3 -4
  115. package/dest/services/service.d.ts +1 -7
  116. package/dest/services/service.d.ts.map +1 -1
  117. package/dest/services/tx_collection/fast_tx_collection.d.ts +4 -1
  118. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  119. package/dest/services/tx_collection/fast_tx_collection.js +73 -57
  120. package/dest/services/tx_collection/missing_txs_tracker.d.ts +32 -0
  121. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +1 -0
  122. package/dest/services/tx_collection/missing_txs_tracker.js +27 -0
  123. package/dest/services/tx_collection/proposal_tx_collector.d.ts +7 -6
  124. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  125. package/dest/services/tx_collection/proposal_tx_collector.js +4 -4
  126. package/dest/services/tx_collection/slow_tx_collection.js +1 -1
  127. package/dest/services/tx_collection/tx_collection.d.ts +6 -3
  128. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  129. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  130. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  131. package/dest/test-helpers/mock-pubsub.d.ts +1 -6
  132. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  133. package/dest/test-helpers/mock-pubsub.js +1 -9
  134. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  135. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  136. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  137. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  138. package/dest/test-helpers/testbench-utils.js +2 -20
  139. package/dest/testbench/p2p_client_testbench_worker.js +15 -44
  140. package/dest/testbench/worker_client_manager.d.ts +1 -1
  141. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  142. package/dest/testbench/worker_client_manager.js +2 -2
  143. package/dest/util.d.ts +4 -9
  144. package/dest/util.d.ts.map +1 -1
  145. package/dest/util.js +9 -2
  146. package/package.json +14 -14
  147. package/src/client/factory.ts +2 -3
  148. package/src/client/p2p_client.ts +4 -6
  149. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +9 -19
  150. package/src/config.ts +10 -10
  151. package/src/index.ts +1 -0
  152. package/src/mem_pools/attestation_pool/attestation_pool.ts +7 -8
  153. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +6 -6
  154. package/src/mem_pools/index.ts +3 -0
  155. package/src/mem_pools/instrumentation.ts +13 -17
  156. package/src/mem_pools/tx_pool/README.md +270 -0
  157. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +746 -0
  158. package/src/mem_pools/tx_pool/eviction/eviction_manager.ts +132 -0
  159. package/src/mem_pools/tx_pool/eviction/eviction_strategy.ts +208 -0
  160. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +163 -0
  161. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +104 -0
  162. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_reorg_rule.ts +93 -0
  163. package/src/mem_pools/tx_pool/eviction/low_priority_eviction_rule.ts +106 -0
  164. package/src/mem_pools/tx_pool/eviction/nullifier_conflict_pre_add_rule.ts +75 -0
  165. package/src/mem_pools/tx_pool/index.ts +2 -0
  166. package/src/mem_pools/tx_pool/priority.ts +20 -0
  167. package/src/mem_pools/tx_pool/tx_pool.ts +141 -0
  168. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +321 -0
  169. package/src/mem_pools/tx_pool_v2/interfaces.ts +4 -7
  170. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +5 -11
  171. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +43 -29
  172. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +0 -3
  173. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +1 -19
  174. package/src/msg_validators/attestation_validator/README.md +1 -1
  175. package/src/msg_validators/attestation_validator/attestation_validator.ts +4 -5
  176. package/src/msg_validators/clock_tolerance.ts +3 -4
  177. package/src/msg_validators/proposal_validator/README.md +4 -4
  178. package/src/msg_validators/proposal_validator/proposal_validator.ts +5 -6
  179. package/src/msg_validators/tx_validator/metadata_validator.ts +4 -12
  180. package/src/services/discv5/discV5_service.ts +2 -4
  181. package/src/services/libp2p/libp2p_service.ts +71 -135
  182. package/src/services/peer-manager/metrics.ts +0 -7
  183. package/src/services/peer-manager/peer_manager.ts +9 -28
  184. package/src/services/peer-manager/peer_scoring.ts +5 -21
  185. package/src/services/reqresp/batch-tx-requester/README.md +7 -46
  186. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +111 -78
  187. package/src/services/reqresp/batch-tx-requester/interface.ts +1 -2
  188. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +6 -13
  189. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +24 -68
  190. package/src/services/reqresp/reqresp.ts +3 -5
  191. package/src/services/service.ts +0 -7
  192. package/src/services/tx_collection/fast_tx_collection.ts +83 -57
  193. package/src/services/tx_collection/missing_txs_tracker.ts +52 -0
  194. package/src/services/tx_collection/proposal_tx_collector.ts +13 -8
  195. package/src/services/tx_collection/slow_tx_collection.ts +1 -1
  196. package/src/services/tx_collection/tx_collection.ts +5 -3
  197. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  198. package/src/test-helpers/mock-pubsub.ts +0 -9
  199. package/src/test-helpers/reqresp-nodes.ts +1 -1
  200. package/src/test-helpers/testbench-utils.ts +3 -28
  201. package/src/testbench/p2p_client_testbench_worker.ts +15 -44
  202. package/src/testbench/worker_client_manager.ts +2 -2
  203. package/src/util.ts +13 -9
  204. package/dest/services/tx_collection/request_tracker.d.ts +0 -53
  205. package/dest/services/tx_collection/request_tracker.d.ts.map +0 -1
  206. package/dest/services/tx_collection/request_tracker.js +0 -84
  207. package/src/services/tx_collection/request_tracker.ts +0 -127
@@ -170,37 +170,6 @@ 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
-
204
173
  ## Key Files
205
174
 
206
175
  | File | Description |
@@ -210,16 +179,15 @@ RequestTracker.finish()
210
179
  | `peer_collection.ts` | Manages peer classification (dumb/smart/bad) and rate limiting |
211
180
  | `interface.ts` | Type definitions for dependencies |
212
181
  | `../protocols/block_txs/` | Wire protocol definitions (`BlockTxsRequest`, `BlockTxsResponse`, `BitVector`) |
213
- | `../../tx_collection/request_tracker.ts` | Centralized deadline, missing tx tracking, and cancellation signal |
214
182
 
215
183
  ## Stopping Conditions
216
184
 
217
- The `BatchTxRequester` stops when any of these conditions are met, all managed by the `RequestTracker`:
185
+ The `BatchTxRequester` stops when any of these conditions are met:
218
186
 
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
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
223
191
 
224
192
  ## Configuration
225
193
 
@@ -260,15 +228,11 @@ Request to peer fails
260
228
  ## Usage Example
261
229
 
262
230
  ```typescript
263
- const requestTracker = RequestTracker.create(
264
- missingTxHashes, // TxHash[] - what we need
265
- new Date(Date.now() + 5_000), // deadline
266
- );
267
-
268
231
  const requester = new BatchTxRequester(
269
- requestTracker, // IRequestTracker - tracks missing txs, deadline, and cancellation
232
+ missingTxHashes, // TxHash[] - what we need
270
233
  blockTxsSource, // BlockTxsSource - the proposal or block we need txs for
271
234
  pinnedPeer, // PeerId | undefined - peer expected to have the txs
235
+ timeoutMs, // number - how long to try
272
236
  p2pService, // BatchTxRequesterLibP2PService
273
237
  );
274
238
 
@@ -309,8 +273,6 @@ const txs = await BatchTxRequester.collectAllTxs(requester.run());
309
273
  │ 1. Try RPC nodes first (fast) │ │ Periodic polling of RPC nodes │
310
274
  │ 2. Fall back to BatchTxRequester │ │ and peers for missing txs │
311
275
  │ │ │ │
312
- │ Creates RequestTracker per │ │ │
313
- │ request with deadline │ │ │
314
276
  └───────────────────┬───────────────┘ └─────────────────────────────────────┘
315
277
 
316
278
  │ For 'proposal' and 'block' requests
@@ -319,7 +281,6 @@ const txs = await BatchTxRequester.collectAllTxs(requester.run());
319
281
  │ BatchTxRequester │
320
282
  │ │
321
283
  │ Aggressive parallel fetching from multiple peers │
322
- │ Shares RequestTracker with FastTxCollection for unified cancellation │
323
284
  │ Uses BLOCK_TXS sub-protocol for efficient batching │
324
285
  └───────────────────┬─────────────────────────────────────────────────────────┘
325
286
 
@@ -1,14 +1,16 @@
1
1
  import { chunkWrapAround } from '@aztec/foundation/collection';
2
+ import { TimeoutError } from '@aztec/foundation/error';
2
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
3
4
  import { FifoMemoryQueue, type ISemaphore, Semaphore } from '@aztec/foundation/queue';
4
5
  import { sleep } from '@aztec/foundation/sleep';
5
- import { DateProvider } from '@aztec/foundation/timer';
6
+ import { DateProvider, executeTimeout } from '@aztec/foundation/timer';
6
7
  import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
7
8
  import { Tx, TxArray, TxHash } from '@aztec/stdlib/tx';
8
9
 
9
10
  import type { PeerId } from '@libp2p/interface';
11
+ import { peerIdFromString } from '@libp2p/peer-id';
10
12
 
11
- import type { IRequestTracker } from '../../tx_collection/request_tracker.js';
13
+ import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_tracker.js';
12
14
  import { ReqRespSubProtocol } from '.././interface.js';
13
15
  import { BlockTxsRequest, BlockTxsResponse, type BlockTxsSource } from '.././protocols/index.js';
14
16
  import { ReqRespStatus } from '.././status.js';
@@ -41,14 +43,16 @@ import { BatchRequestTxValidator, type IBatchRequestTxValidator } from './tx_val
41
43
  * - Is the peer which was unable to send us successful response N times in a row
42
44
  * */
43
45
  export class BatchTxRequester {
44
- private readonly requestTracker: IRequestTracker;
45
46
  private readonly blockTxsSource: BlockTxsSource;
46
47
  private readonly pinnedPeer: PeerId | undefined;
48
+ private readonly timeoutMs: number;
47
49
  private readonly p2pService: BatchTxRequesterLibP2PService;
48
50
  private readonly logger: Logger;
51
+ private readonly dateProvider: DateProvider;
49
52
  private readonly opts: BatchTxRequesterOptions;
50
53
  private readonly peers: IPeerCollection;
51
54
  private readonly txsMetadata: ITxMetadataCollection;
55
+ private readonly deadline: number;
52
56
  private readonly smartRequesterSemaphore: ISemaphore;
53
57
  private readonly txQueue: FifoMemoryQueue<Tx>;
54
58
  private readonly txValidator: IBatchRequestTxValidator;
@@ -57,19 +61,21 @@ export class BatchTxRequester {
57
61
  private readonly txBatchSize: number;
58
62
 
59
63
  constructor(
60
- requestTracker: IRequestTracker,
64
+ missingTxsTracker: IMissingTxsTracker,
61
65
  blockTxsSource: BlockTxsSource,
62
66
  pinnedPeer: PeerId | undefined,
67
+ timeoutMs: number,
63
68
  p2pService: BatchTxRequesterLibP2PService,
64
69
  logger?: Logger,
65
70
  dateProvider?: DateProvider,
66
71
  opts?: BatchTxRequesterOptions,
67
72
  ) {
68
- this.requestTracker = requestTracker;
69
73
  this.blockTxsSource = blockTxsSource;
70
74
  this.pinnedPeer = pinnedPeer;
75
+ this.timeoutMs = timeoutMs;
71
76
  this.p2pService = p2pService;
72
77
  this.logger = logger ?? createLogger('p2p:reqresp_batch');
78
+ this.dateProvider = dateProvider ?? new DateProvider();
73
79
  this.opts = opts ?? {};
74
80
 
75
81
  this.smartParallelWorkerCount =
@@ -77,22 +83,24 @@ export class BatchTxRequester {
77
83
  this.dumbParallelWorkerCount =
78
84
  this.opts.dumbParallelWorkerCount ?? DEFAULT_BATCH_TX_REQUESTER_DUMB_PARALLEL_WORKER_COUNT;
79
85
  this.txBatchSize = this.opts.txBatchSize ?? DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE;
86
+ this.deadline = this.dateProvider.now() + this.timeoutMs;
80
87
  this.txQueue = new FifoMemoryQueue(this.logger);
81
88
  this.txValidator = this.opts.txValidator ?? new BatchRequestTxValidator(this.p2pService.txValidatorConfig);
82
89
 
83
90
  if (this.opts.peerCollection) {
84
91
  this.peers = this.opts.peerCollection;
85
92
  } else {
93
+ const initialPeers = this.p2pService.connectionSampler.getPeerListSortedByConnectionCountAsc();
86
94
  const badPeerThreshold = this.opts.badPeerThreshold ?? DEFAULT_BATCH_TX_REQUESTER_BAD_PEER_THRESHOLD;
87
95
  this.peers = new PeerCollection(
88
- this.p2pService.connectionSampler,
96
+ initialPeers,
89
97
  this.pinnedPeer,
90
- dateProvider ?? new DateProvider(),
98
+ this.dateProvider,
91
99
  badPeerThreshold,
92
100
  this.p2pService.peerScoring,
93
101
  );
94
102
  }
95
- this.txsMetadata = new MissingTxMetadataCollection(requestTracker, this.txBatchSize);
103
+ this.txsMetadata = new MissingTxMetadataCollection(missingTxsTracker, this.txBatchSize);
96
104
  this.smartRequesterSemaphore = this.opts.semaphore ?? new Semaphore(0);
97
105
  }
98
106
 
@@ -100,30 +108,40 @@ export class BatchTxRequester {
100
108
  * Fetches all missing transactions and yields them one by one
101
109
  * */
102
110
  public async *run(): AsyncGenerator<Tx, Tx | undefined, unknown> {
111
+ // Our timeout is represented in milliseconds but queue expects seconds
112
+ // We also want to make sure we wait at least 1 second in case of very low timeouts
113
+ const timeoutQueueAfter = Math.max(Math.ceil(this.timeoutMs / 1_000), 1);
103
114
  try {
104
115
  if (this.txsMetadata.getMissingTxHashes().size === 0) {
105
116
  return undefined;
106
117
  }
107
118
 
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(() => {
119
+ // Start workers in background
120
+ const workersPromise = executeTimeout(
121
+ () => Promise.allSettled([this.smartRequester(), this.dumbRequester(), this.pinnedPeerRequester()]),
122
+ this.timeoutMs,
123
+ ).finally(() => {
114
124
  this.txQueue.end();
115
125
  });
116
126
 
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.
119
127
  while (true) {
120
- const tx = await this.txQueue.get();
128
+ const tx = await this.txQueue.get(timeoutQueueAfter);
121
129
 
130
+ // null indicates that the queue has ended
122
131
  if (tx === null) {
123
132
  break;
124
133
  }
125
134
 
126
135
  yield tx;
136
+
137
+ if (this.shouldStop()) {
138
+ // Drain queue before ending
139
+ let remaining;
140
+ while ((remaining = this.txQueue.getImmediate()) !== undefined) {
141
+ yield remaining;
142
+ }
143
+ break;
144
+ }
127
145
  }
128
146
 
129
147
  this.unlockSmartRequesterSemaphores();
@@ -209,6 +227,7 @@ export class BatchTxRequester {
209
227
  * Starts dumb worker loops
210
228
  * */
211
229
  private async dumbRequester() {
230
+ const nextPeerIndex = this.makeRoundRobinIndexer();
212
231
  const nextBatchIndex = this.makeRoundRobinIndexer();
213
232
 
214
233
  // Chunk missing tx hashes into batches of txBatchSize, wrapping around to ensure no peer gets less than txBatchSize
@@ -244,9 +263,15 @@ export class BatchTxRequester {
244
263
  return { blockRequest, txs };
245
264
  };
246
265
 
247
- const workerCount = this.dumbParallelWorkerCount;
266
+ const nextPeer = () => {
267
+ const peers = this.peers.getDumbPeersToQuery();
268
+ const idx = nextPeerIndex(() => peers.length);
269
+ return idx === undefined ? undefined : peerIdFromString(peers[idx]);
270
+ };
271
+
272
+ const workerCount = Math.min(this.dumbParallelWorkerCount, this.peers.getAllPeers().size);
248
273
  const workers = Array.from({ length: workerCount }, (_, index) =>
249
- this.dumbWorkerLoop(this.peers.nextDumbPeerToQuery.bind(this.peers), makeRequest, index + 1),
274
+ this.dumbWorkerLoop(nextPeer, makeRequest, index + 1),
250
275
  );
251
276
 
252
277
  await Promise.allSettled(workers);
@@ -307,6 +332,14 @@ export class BatchTxRequester {
307
332
  * Starts smart worker loops
308
333
  * */
309
334
  private async smartRequester() {
335
+ const nextPeerIndex = this.makeRoundRobinIndexer();
336
+
337
+ const nextPeer = () => {
338
+ const peers = this.peers.getSmartPeersToQuery();
339
+ const idx = nextPeerIndex(() => peers.length);
340
+ return idx === undefined ? undefined : peerIdFromString(peers[idx]);
341
+ };
342
+
310
343
  const makeRequest = (pid: PeerId) => {
311
344
  const txs = this.txsMetadata.getTxsToRequestFromThePeer(pid);
312
345
  const blockRequest = BlockTxsRequest.fromTxsSourceAndMissingTxs(this.blockTxsSource, txs);
@@ -317,8 +350,9 @@ export class BatchTxRequester {
317
350
  return { blockRequest, txs };
318
351
  };
319
352
 
320
- const workers = Array.from({ length: this.smartParallelWorkerCount }, (_, index) =>
321
- this.smartWorkerLoop(this.peers.nextSmartPeerToQuery.bind(this.peers), makeRequest, index + 1),
353
+ const workers = Array.from(
354
+ { length: Math.min(this.smartParallelWorkerCount, this.peers.getAllPeers().size) },
355
+ (_, index) => this.smartWorkerLoop(nextPeer, makeRequest, index + 1),
322
356
  );
323
357
 
324
358
  await Promise.allSettled(workers);
@@ -344,10 +378,7 @@ export class BatchTxRequester {
344
378
  ) {
345
379
  try {
346
380
  this.logger.trace(`Smart worker ${workerIndex} started`);
347
- await Promise.race([this.smartRequesterSemaphore.acquire(), this.requestTracker.cancellationToken]);
348
- if (this.requestTracker.checkCancelled()) {
349
- return;
350
- }
381
+ await executeTimeout((_: AbortSignal) => this.smartRequesterSemaphore.acquire(), this.timeoutMs);
351
382
  this.logger.trace(`Smart worker ${workerIndex} acquired semaphore`);
352
383
 
353
384
  while (!this.shouldStop()) {
@@ -356,25 +387,30 @@ export class BatchTxRequester {
356
387
  if (weRanOutOfPeersToQuery) {
357
388
  this.logger.debug(`Worker loop smart: No more peers to query`);
358
389
 
359
- // If we have rate limited peers wait for them.
360
- const nextSmartPeerDelay = this.peers.getNextSmartPeerAvailabilityDelayMs();
361
- const thereAreSomeRateLimitedSmartPeers = nextSmartPeerDelay !== undefined;
362
- if (thereAreSomeRateLimitedSmartPeers) {
363
- await this.sleepClampedToDeadline(nextSmartPeerDelay);
364
- continue;
390
+ // If there are no more dumb peers to query then none of our peers can become smart,
391
+ // thus we can simply exit this worker
392
+ const noMoreDumbPeersToQuery = this.peers.getDumbPeersToQuery().length === 0;
393
+ if (noMoreDumbPeersToQuery) {
394
+ // These might be either smart peers that will get unblocked after _some time_
395
+ const nextSmartPeerDelay = this.peers.getNextSmartPeerAvailabilityDelayMs();
396
+ const thereAreSomeRateLimitedSmartPeers = nextSmartPeerDelay !== undefined;
397
+ if (thereAreSomeRateLimitedSmartPeers) {
398
+ await this.sleepClampedToDeadline(nextSmartPeerDelay);
399
+ continue;
400
+ }
401
+
402
+ this.logger.debug(`Worker loop smart: No more smart peers to query killing ${workerIndex}`);
403
+ break;
365
404
  }
366
405
 
406
+ // Otherwise there are still some dumb peers that could become smart.
367
407
  // We end up here when all known smart peers became temporarily unavailable via combination of
368
408
  // (bad, in-flight, or rate-limited) or in some weird scenario all current smart peers turn bad which is permanent
369
- // but there are dumb peers that could be promoted
370
- // or new peer can join as dumb and be promoted later
409
+ // but dumb peers still exist that could become smart.
371
410
  //
372
411
  // When a dumb peer responds with valid txIndices, it gets
373
412
  // promoted to smart and releases the semaphore, waking this worker.
374
- await Promise.race([this.smartRequesterSemaphore.acquire(), this.requestTracker.cancellationToken]);
375
- if (this.requestTracker.checkCancelled()) {
376
- break;
377
- }
413
+ await executeTimeout((_: AbortSignal) => this.smartRequesterSemaphore.acquire(), this.timeoutMs);
378
414
  this.logger.debug(`Worker loop smart: acquired next smart peer`);
379
415
  continue;
380
416
  }
@@ -401,7 +437,11 @@ export class BatchTxRequester {
401
437
  });
402
438
  }
403
439
  } catch (err: any) {
404
- this.logger.error(`Smart worker ${workerIndex} encountered an error: ${err}`);
440
+ if (err instanceof TimeoutError) {
441
+ this.logger.debug(`Smart worker ${workerIndex} timed out waiting for semaphore`);
442
+ } else {
443
+ this.logger.error(`Smart worker ${workerIndex} encountered an error: ${err}`);
444
+ }
405
445
  } finally {
406
446
  this.logger.debug(`Smart worker ${workerIndex} finished`);
407
447
  }
@@ -449,18 +489,9 @@ export class BatchTxRequester {
449
489
  * this implies we will query these peers couple of more times and give them a chance to "redeem" themselves before completely ignoring them
450
490
  */
451
491
  private handleFailResponseFromPeer(peerId: PeerId, responseStatus: ReqRespStatus) {
492
+ //TODO: Should we ban these peers?
452
493
  if (responseStatus === ReqRespStatus.FAILURE || responseStatus === ReqRespStatus.UNKNOWN) {
453
494
  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);
464
495
  return;
465
496
  }
466
497
 
@@ -514,9 +545,6 @@ export class BatchTxRequester {
514
545
  });
515
546
 
516
547
  if (hasInvalidTx) {
517
- this.logger.warn(`Penalizing peer ${peerId.toString()} for sending invalid transactions in batch response`, {
518
- peerId,
519
- });
520
548
  this.peers.penalisePeer(peerId, PeerErrorSeverity.LowToleranceError);
521
549
  } else {
522
550
  // If we have received successful response from the peer, they have "redeemed" themselves and not considered bad anymore
@@ -553,9 +581,10 @@ export class BatchTxRequester {
553
581
  return;
554
582
  }
555
583
 
556
- const hasArchiveRootMismatch = this.blockTxsSource.archive.toString() !== response.archiveRoot.toString();
557
- if (hasArchiveRootMismatch) {
558
- this.handleArchiveRootMismatch(peerId, response);
584
+ // If block response is invalid we still want to query this peer in the future
585
+ // Because they sent successful response, so they might become smart peer in the future
586
+ // Or send us needed txs
587
+ if (!this.isBlockResponseValid(response)) {
559
588
  return;
560
589
  }
561
590
 
@@ -570,28 +599,18 @@ export class BatchTxRequester {
570
599
  this.markTxsPeerHas(peerId, response);
571
600
 
572
601
  // Unblock smart workers
573
- this.smartRequesterSemaphore.release();
574
- }
575
-
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);
602
+ if (this.peers.getSmartPeersToQuery().length <= this.smartParallelWorkerCount) {
603
+ this.smartRequesterSemaphore.release();
585
604
  }
605
+ }
586
606
 
587
- this.peers.markPeerDumb(peerId);
588
- this.txsMetadata.clearPeerData(peerId);
607
+ private isBlockResponseValid(response: BlockTxsResponse): boolean {
608
+ const archiveRootsMatch = this.blockTxsSource.archive.toString() === response.archiveRoot.toString();
609
+ const peerHasSomeTxsFromProposal = !response.txIndices.isEmpty();
610
+ return archiveRootsMatch && peerHasSomeTxsFromProposal;
589
611
  }
590
612
 
591
613
  private peerHasSomeTxsWeAreMissing(_peerId: PeerId, response: BlockTxsResponse): boolean {
592
- if (response.txIndices.isEmpty()) {
593
- return false;
594
- }
595
614
  const txsPeerHas = new Set(this.extractHashesPeerHasFromResponse(response).map(h => h.toString()));
596
615
  return this.txsMetadata.getMissingTxHashes().intersection(txsPeerHas).size > 0;
597
616
  }
@@ -640,14 +659,27 @@ export class BatchTxRequester {
640
659
  }
641
660
 
642
661
  /*
643
- * Checks if the BatchTxRequester should stop fetching missing txs.
644
- * Delegates to requestTracker which covers: deadline hit, all txs fetched, or external cancellation. */
662
+ * @returns true if all missing txs have been fetched */
663
+ private fetchedAllTxs() {
664
+ return this.txsMetadata.getMissingTxHashes().size == 0;
665
+ }
666
+
667
+ /*
668
+ * Checks if the BatchTxRequester should stop fetching missing txs
669
+ * Conditions for stopping are:
670
+ * - There have been no missing transactions to start with
671
+ * - All transactions have been fetched
672
+ * - The deadline has been hit (no more time to fetch)
673
+ * - This process has been cancelled via abortSignal
674
+ *
675
+ * @returns true if BatchTxRequester should stop, otherwise false*/
645
676
  private shouldStop() {
646
- if (this.requestTracker.checkCancelled()) {
677
+ const aborted = this.opts.abortSignal?.aborted ?? false;
678
+ if (aborted) {
647
679
  this.unlockSmartRequesterSemaphores();
648
680
  }
649
681
 
650
- return this.requestTracker.checkCancelled();
682
+ return aborted || this.fetchedAllTxs() || this.dateProvider.now() > this.deadline;
651
683
  }
652
684
 
653
685
  /*
@@ -665,9 +697,10 @@ export class BatchTxRequester {
665
697
  * This ensures we don't sleep past the deadline.
666
698
  * */
667
699
  private async sleepClampedToDeadline(durationMs: number) {
668
- if (this.requestTracker.checkCancelled()) {
669
- return;
700
+ const remaining = this.deadline - this.dateProvider.now();
701
+ const thereIsTimeRemaining = remaining > 0;
702
+ if (thereIsTimeRemaining) {
703
+ await sleep(Math.min(durationMs, remaining));
670
704
  }
671
- await Promise.race([sleep(durationMs), this.requestTracker.cancellationToken]);
672
705
  }
673
706
  }
@@ -23,8 +23,6 @@ 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;
28
26
  }
29
27
 
30
28
  /**
@@ -49,6 +47,7 @@ export interface BatchTxRequesterOptions {
49
47
  //Injectable for testing purposes
50
48
  semaphore?: ISemaphore;
51
49
  peerCollection?: IPeerCollection;
50
+ abortSignal?: AbortSignal;
52
51
  /** Optional tx validator for testing - if not provided, one is created from p2pService.txValidatorConfig */
53
52
  txValidator?: IBatchRequestTxValidator;
54
53
  }
@@ -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 { IRequestTracker } from '../../tx_collection/request_tracker.js';
5
+ import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_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 requestTracker: IRequestTracker,
44
+ private missingTxsTracker: IMissingTxsTracker,
45
45
  private readonly txBatchSize: number = DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE,
46
46
  ) {
47
- requestTracker.missingTxHashes.forEach(hash => this.txMetadata.set(hash, new MissingTxMetadata(hash)));
47
+ missingTxsTracker.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.requestTracker.missingTxHashes;
68
+ return this.missingTxsTracker.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.requestTracker.isMissing(txHash.toString());
131
+ return !this.missingTxsTracker.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.requestTracker.markFetched(tx);
147
+ return this.missingTxsTracker.markFetched(tx);
148
148
  }
149
149
 
150
150
  public markPeerHas(peerId: PeerId, txHash: TxHash[]) {
@@ -158,11 +158,4 @@ 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
- }
168
161
  }