@aztec/p2p 0.0.1-commit.858058eac → 0.0.1-commit.85d7d01

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 (243) hide show
  1. package/dest/client/factory.d.ts +6 -6
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +23 -30
  4. package/dest/client/interface.d.ts +14 -19
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +9 -18
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +52 -72
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +6 -7
  10. package/dest/config.d.ts +13 -6
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +5 -5
  13. package/dest/errors/tx-pool.error.d.ts +8 -0
  14. package/dest/errors/tx-pool.error.d.ts.map +1 -0
  15. package/dest/errors/tx-pool.error.js +9 -0
  16. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
  17. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool.js +5 -0
  19. package/dest/mem_pools/attestation_pool/mocks.d.ts +2 -2
  20. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/mocks.js +2 -2
  22. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +3 -3
  23. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +30 -13
  24. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +91 -20
  26. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +3 -3
  27. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +18 -9
  29. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  30. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +5 -2
  32. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +3 -3
  33. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +12 -4
  35. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +2 -2
  36. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/eviction/index.js +1 -1
  38. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +48 -5
  39. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +8 -0
  41. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +7 -5
  42. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +7 -5
  43. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
  44. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  45. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +14 -6
  46. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +4 -4
  47. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  48. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +14 -4
  49. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +3 -3
  50. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  51. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  52. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
  53. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  54. package/dest/mem_pools/tx_pool_v2/index.js +1 -1
  55. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts +15 -0
  56. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts.map +1 -0
  57. package/dest/mem_pools/tx_pool_v2/instrumentation.js +43 -0
  58. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +20 -6
  59. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  60. package/dest/mem_pools/tx_pool_v2/interfaces.js +4 -1
  61. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +34 -8
  62. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  63. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +76 -10
  64. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +12 -3
  65. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  66. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +36 -14
  67. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +9 -4
  68. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  69. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +11 -6
  70. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +13 -5
  71. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  72. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +297 -143
  73. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
  74. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  75. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
  76. package/dest/msg_validators/tx_validator/factory.d.ts +114 -6
  77. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  78. package/dest/msg_validators/tx_validator/factory.js +219 -58
  79. package/dest/msg_validators/tx_validator/gas_validator.d.ts +58 -3
  80. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  81. package/dest/msg_validators/tx_validator/gas_validator.js +73 -36
  82. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  83. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  84. package/dest/msg_validators/tx_validator/index.js +1 -0
  85. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
  86. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
  87. package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
  88. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +2 -2
  89. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  90. package/dest/msg_validators/tx_validator/timestamp_validator.js +6 -6
  91. package/dest/services/dummy_service.d.ts +4 -4
  92. package/dest/services/dummy_service.d.ts.map +1 -1
  93. package/dest/services/dummy_service.js +4 -4
  94. package/dest/services/encoding.d.ts +2 -2
  95. package/dest/services/encoding.d.ts.map +1 -1
  96. package/dest/services/encoding.js +9 -8
  97. package/dest/services/gossipsub/topic_score_params.d.ts +18 -6
  98. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  99. package/dest/services/gossipsub/topic_score_params.js +32 -10
  100. package/dest/services/libp2p/libp2p_service.d.ts +16 -13
  101. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  102. package/dest/services/libp2p/libp2p_service.js +69 -81
  103. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -3
  104. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  105. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +19 -46
  106. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +2 -6
  107. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  108. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +10 -13
  109. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  110. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +25 -46
  111. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +17 -11
  112. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  113. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +49 -15
  114. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
  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 +2 -1
  118. package/dest/services/service.d.ts +5 -3
  119. package/dest/services/service.d.ts.map +1 -1
  120. package/dest/services/tx_collection/config.d.ts +13 -1
  121. package/dest/services/tx_collection/config.d.ts.map +1 -1
  122. package/dest/services/tx_collection/config.js +30 -0
  123. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -1
  124. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  125. package/dest/services/tx_collection/fast_tx_collection.js +39 -33
  126. package/dest/services/tx_collection/file_store_tx_collection.d.ts +38 -29
  127. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -1
  128. package/dest/services/tx_collection/file_store_tx_collection.js +126 -77
  129. package/dest/services/tx_collection/file_store_tx_source.d.ts +16 -6
  130. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  131. package/dest/services/tx_collection/file_store_tx_source.js +49 -16
  132. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  133. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  134. package/dest/services/tx_collection/instrumentation.js +2 -1
  135. package/dest/services/tx_collection/missing_txs_tracker.d.ts +32 -0
  136. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +1 -0
  137. package/dest/services/tx_collection/missing_txs_tracker.js +27 -0
  138. package/dest/services/tx_collection/proposal_tx_collector.d.ts +7 -6
  139. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  140. package/dest/services/tx_collection/proposal_tx_collector.js +5 -4
  141. package/dest/services/tx_collection/slow_tx_collection.d.ts +5 -3
  142. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  143. package/dest/services/tx_collection/slow_tx_collection.js +17 -12
  144. package/dest/services/tx_collection/tx_collection.d.ts +9 -6
  145. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  146. package/dest/services/tx_collection/tx_collection.js +26 -10
  147. package/dest/services/tx_collection/tx_collection_sink.d.ts +6 -5
  148. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  149. package/dest/services/tx_collection/tx_collection_sink.js +13 -22
  150. package/dest/services/tx_collection/tx_source.d.ts +8 -3
  151. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  152. package/dest/services/tx_collection/tx_source.js +19 -2
  153. package/dest/services/tx_file_store/tx_file_store.d.ts +3 -2
  154. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
  155. package/dest/services/tx_file_store/tx_file_store.js +9 -6
  156. package/dest/services/tx_provider.d.ts +3 -3
  157. package/dest/services/tx_provider.d.ts.map +1 -1
  158. package/dest/services/tx_provider.js +4 -4
  159. package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
  160. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  161. package/dest/test-helpers/make-test-p2p-clients.js +1 -2
  162. package/dest/test-helpers/mock-pubsub.d.ts +4 -4
  163. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  164. package/dest/test-helpers/mock-pubsub.js +8 -2
  165. package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
  166. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  167. package/dest/test-helpers/reqresp-nodes.js +2 -2
  168. package/dest/test-helpers/testbench-utils.d.ts +8 -3
  169. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  170. package/dest/test-helpers/testbench-utils.js +7 -1
  171. package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -2
  172. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  173. package/dest/testbench/p2p_client_testbench_worker.js +13 -13
  174. package/dest/util.d.ts +2 -2
  175. package/dest/util.d.ts.map +1 -1
  176. package/package.json +14 -14
  177. package/src/client/factory.ts +39 -48
  178. package/src/client/interface.ts +17 -20
  179. package/src/client/p2p_client.ts +60 -104
  180. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +19 -10
  181. package/src/config.ts +10 -10
  182. package/src/errors/tx-pool.error.ts +12 -0
  183. package/src/mem_pools/attestation_pool/attestation_pool.ts +8 -0
  184. package/src/mem_pools/attestation_pool/mocks.ts +2 -1
  185. package/src/mem_pools/tx_pool/README.md +1 -1
  186. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +3 -3
  187. package/src/mem_pools/tx_pool_v2/README.md +43 -27
  188. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +109 -22
  189. package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +21 -8
  190. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +5 -2
  191. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +18 -4
  192. package/src/mem_pools/tx_pool_v2/eviction/index.ts +4 -0
  193. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +49 -4
  194. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +5 -5
  195. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +5 -5
  196. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +14 -9
  197. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +24 -6
  198. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +3 -3
  199. package/src/mem_pools/tx_pool_v2/index.ts +1 -1
  200. package/src/mem_pools/tx_pool_v2/instrumentation.ts +69 -0
  201. package/src/mem_pools/tx_pool_v2/interfaces.ts +21 -6
  202. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +107 -17
  203. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +43 -16
  204. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +18 -7
  205. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +326 -138
  206. package/src/msg_validators/tx_validator/README.md +115 -0
  207. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
  208. package/src/msg_validators/tx_validator/factory.ts +353 -77
  209. package/src/msg_validators/tx_validator/gas_validator.ts +90 -27
  210. package/src/msg_validators/tx_validator/index.ts +1 -0
  211. package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
  212. package/src/msg_validators/tx_validator/timestamp_validator.ts +7 -7
  213. package/src/services/dummy_service.ts +6 -6
  214. package/src/services/encoding.ts +7 -7
  215. package/src/services/gossipsub/README.md +29 -14
  216. package/src/services/gossipsub/topic_score_params.ts +49 -13
  217. package/src/services/libp2p/libp2p_service.ts +80 -90
  218. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +20 -48
  219. package/src/services/reqresp/batch-tx-requester/interface.ts +1 -5
  220. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +23 -71
  221. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +63 -24
  222. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
  223. package/src/services/reqresp/reqresp.ts +3 -1
  224. package/src/services/service.ts +11 -2
  225. package/src/services/tx_collection/config.ts +42 -0
  226. package/src/services/tx_collection/fast_tx_collection.ts +51 -30
  227. package/src/services/tx_collection/file_store_tx_collection.ts +143 -93
  228. package/src/services/tx_collection/file_store_tx_source.ts +64 -17
  229. package/src/services/tx_collection/instrumentation.ts +7 -1
  230. package/src/services/tx_collection/missing_txs_tracker.ts +52 -0
  231. package/src/services/tx_collection/proposal_tx_collector.ts +8 -7
  232. package/src/services/tx_collection/slow_tx_collection.ts +17 -13
  233. package/src/services/tx_collection/tx_collection.ts +45 -14
  234. package/src/services/tx_collection/tx_collection_sink.ts +15 -29
  235. package/src/services/tx_collection/tx_source.ts +22 -3
  236. package/src/services/tx_file_store/tx_file_store.ts +6 -4
  237. package/src/services/tx_provider.ts +2 -2
  238. package/src/test-helpers/make-test-p2p-clients.ts +0 -2
  239. package/src/test-helpers/mock-pubsub.ts +13 -6
  240. package/src/test-helpers/reqresp-nodes.ts +2 -5
  241. package/src/test-helpers/testbench-utils.ts +11 -3
  242. package/src/testbench/p2p_client_testbench_worker.ts +22 -19
  243. package/src/util.ts +7 -1
@@ -1,5 +1,6 @@
1
1
  import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { Logger } from '@aztec/foundation/log';
3
+ import type { DateProvider } from '@aztec/foundation/timer';
3
4
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
4
5
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
5
6
  import { computeFeePayerBalanceStorageSlot } from '@aztec/protocol-contracts/fee-juice';
@@ -8,6 +9,7 @@ import type { L2Block, L2BlockId, L2BlockSource } from '@aztec/stdlib/block';
8
9
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
9
10
  import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
10
11
  import { BlockHeader, Tx, TxHash, type TxValidator } from '@aztec/stdlib/tx';
12
+ import type { TelemetryClient } from '@aztec/telemetry-client';
11
13
 
12
14
  import { TxArchive } from './archive/index.js';
13
15
  import { DeletedPool } from './deleted_pool.js';
@@ -21,8 +23,12 @@ import {
21
23
  LowPriorityPreAddRule,
22
24
  NullifierConflictRule,
23
25
  type PoolOperations,
26
+ type PreAddContext,
24
27
  type PreAddPoolAccess,
28
+ TxPoolRejectionCode,
29
+ type TxPoolRejectionError,
25
30
  } from './eviction/index.js';
31
+ import { TxPoolV2Instrumentation } from './instrumentation.js';
26
32
  import {
27
33
  type AddTxsResult,
28
34
  DEFAULT_TX_POOL_V2_CONFIG,
@@ -64,6 +70,9 @@ export class TxPoolV2Impl {
64
70
  #archive: TxArchive;
65
71
  #deletedPool: DeletedPool;
66
72
  #evictionManager: EvictionManager;
73
+ #dateProvider: DateProvider;
74
+ #instrumentation: TxPoolV2Instrumentation;
75
+ #evictedTxHashes: Set<string> = new Set();
67
76
  #log: Logger;
68
77
  #callbacks: TxPoolV2Callbacks;
69
78
 
@@ -72,7 +81,9 @@ export class TxPoolV2Impl {
72
81
  archiveStore: AztecAsyncKVStore,
73
82
  deps: TxPoolV2Dependencies,
74
83
  callbacks: TxPoolV2Callbacks,
84
+ telemetry: TelemetryClient,
75
85
  config: Partial<TxPoolV2Config> = {},
86
+ dateProvider: DateProvider,
76
87
  log: Logger,
77
88
  ) {
78
89
  this.#store = store;
@@ -85,6 +96,8 @@ export class TxPoolV2Impl {
85
96
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
86
97
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
87
98
  this.#deletedPool = new DeletedPool(store, this.#txsDB, log);
99
+ this.#dateProvider = dateProvider;
100
+ this.#instrumentation = new TxPoolV2Instrumentation(telemetry, () => this.#indices.getTotalMetadataBytes());
88
101
  this.#log = log;
89
102
  this.#callbacks = callbacks;
90
103
 
@@ -164,16 +177,43 @@ export class TxPoolV2Impl {
164
177
  await this.#txsDB.delete(txHashStr);
165
178
  }
166
179
  });
167
- this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`);
180
+ this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`, { txHashes: toDelete });
168
181
  }
169
182
 
170
- async addPendingTxs(txs: Tx[], opts: { source?: string }): Promise<AddTxsResult> {
183
+ async addPendingTxs(txs: Tx[], opts: { source?: string; feeComparisonOnly?: boolean }): Promise<AddTxsResult> {
171
184
  const accepted: TxHash[] = [];
172
185
  const ignored: TxHash[] = [];
173
186
  const rejected: TxHash[] = [];
187
+ const errors = new Map<string, TxPoolRejectionError>();
174
188
  const acceptedPending = new Set<string>();
175
189
 
190
+ // Phase 1: Pre-compute all throwable I/O outside the transaction.
191
+ // If any pre-computation throws, the entire call fails before mutations happen.
192
+ const precomputed = new Map<string, { meta: TxMetaData; minedBlockId: L2BlockId | undefined; isValid: boolean }>();
193
+
194
+ const validator = await this.#createTxValidator();
195
+
196
+ for (const tx of txs) {
197
+ const txHash = tx.getTxHash();
198
+ const txHashStr = txHash.toString();
199
+
200
+ const meta = await buildTxMetaData(tx);
201
+ const minedBlockId = await this.#getMinedBlockId(txHash);
202
+
203
+ // Validate non-mined txs (mined and pre-protected txs bypass validation inside the transaction)
204
+ let isValid = true;
205
+ if (!minedBlockId) {
206
+ isValid = await this.#validateMeta(meta, validator);
207
+ }
208
+
209
+ precomputed.set(txHashStr, { meta, minedBlockId, isValid });
210
+ }
211
+
212
+ // Phase 2: Apply mutations inside the transaction using only pre-computed results,
213
+ // in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
176
214
  const poolAccess = this.#createPreAddPoolAccess();
215
+ const preAddContext: PreAddContext | undefined =
216
+ opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
177
217
 
178
218
  await this.#store.transactionAsync(async () => {
179
219
  for (const tx of txs) {
@@ -186,30 +226,46 @@ export class TxPoolV2Impl {
186
226
  continue;
187
227
  }
188
228
 
189
- // Check mined status first (applies to all paths)
190
- const minedBlockId = await this.#getMinedBlockId(txHash);
229
+ const { meta, minedBlockId, isValid } = precomputed.get(txHashStr)!;
191
230
  const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
192
231
 
193
232
  if (minedBlockId) {
194
233
  // Already mined - add directly (protection already set if pre-protected)
195
- await this.#addTx(tx, { mined: minedBlockId }, opts);
234
+ await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
196
235
  accepted.push(txHash);
197
236
  } else if (preProtectedSlot !== undefined) {
198
237
  // Pre-protected and not mined - add as protected (bypass validation)
199
- await this.#addTx(tx, { protected: preProtectedSlot }, opts);
238
+ await this.#addTx(tx, { protected: preProtectedSlot }, opts, meta);
200
239
  accepted.push(txHash);
240
+ } else if (!isValid) {
241
+ // Failed pre-computed validation
242
+ rejected.push(txHash);
201
243
  } else {
202
- // Regular pending tx - validate and run pre-add rules
203
- const result = await this.#tryAddRegularPendingTx(tx, opts, poolAccess, acceptedPending, ignored);
244
+ // Regular pending tx - run pre-add rules using pre-computed metadata
245
+ const result = await this.#tryAddRegularPendingTx(
246
+ tx,
247
+ meta,
248
+ opts,
249
+ poolAccess,
250
+ acceptedPending,
251
+ ignored,
252
+ errors,
253
+ preAddContext,
254
+ );
204
255
  if (result.status === 'accepted') {
205
256
  acceptedPending.add(txHashStr);
206
- } else if (result.status === 'rejected') {
207
- rejected.push(txHash);
208
257
  } else {
209
258
  ignored.push(txHash);
210
259
  }
211
260
  }
212
261
  }
262
+
263
+ // Run post-add eviction rules for pending txs (inside transaction for atomicity)
264
+ if (acceptedPending.size > 0) {
265
+ const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
266
+ const uniqueFeePayers = new Set<string>(feePayers);
267
+ await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
268
+ }
213
269
  });
214
270
 
215
271
  // Build final accepted list for pending txs (excludes intra-batch evictions)
@@ -217,58 +273,80 @@ export class TxPoolV2Impl {
217
273
  accepted.push(TxHash.fromString(txHashStr));
218
274
  }
219
275
 
220
- // Run post-add eviction rules for pending txs
221
- if (acceptedPending.size > 0) {
222
- const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
223
- const uniqueFeePayers = new Set<string>(feePayers);
224
- await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
276
+ // Record metrics
277
+ if (ignored.length > 0) {
278
+ this.#instrumentation.recordIgnored(ignored.length);
279
+ }
280
+ if (rejected.length > 0) {
281
+ this.#instrumentation.recordRejected(rejected.length);
225
282
  }
226
283
 
227
- return { accepted, ignored, rejected };
284
+ return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
228
285
  }
229
286
 
230
- /** Validates and adds a regular pending tx. Returns status. */
287
+ /** Adds a validated pending tx, running pre-add rules and evicting conflicts. */
231
288
  async #tryAddRegularPendingTx(
232
289
  tx: Tx,
290
+ precomputedMeta: TxMetaData,
233
291
  opts: { source?: string },
234
292
  poolAccess: PreAddPoolAccess,
235
293
  acceptedPending: Set<string>,
236
294
  ignored: TxHash[],
237
- ): Promise<{ status: 'accepted' | 'ignored' | 'rejected' }> {
238
- const txHash = tx.getTxHash();
239
- const txHashStr = txHash.toString();
240
-
241
- // Build metadata and validate using metadata
242
- const meta = await buildTxMetaData(tx);
243
- if (!(await this.#validateMeta(meta))) {
244
- return { status: 'rejected' };
245
- }
295
+ errors: Map<string, TxPoolRejectionError>,
296
+ preAddContext?: PreAddContext,
297
+ ): Promise<{ status: 'accepted' | 'ignored' }> {
298
+ const txHashStr = tx.getTxHash().toString();
246
299
 
247
300
  // Run pre-add rules
248
- const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
301
+ const preAddResult = await this.#evictionManager.runPreAddRules(precomputedMeta, poolAccess, preAddContext);
249
302
 
250
303
  if (preAddResult.shouldIgnore) {
251
- this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason}`);
304
+ this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`);
305
+ if (preAddResult.reason && preAddResult.reason.code !== TxPoolRejectionCode.INTERNAL_ERROR) {
306
+ errors.set(txHashStr, preAddResult.reason);
307
+ }
252
308
  return { status: 'ignored' };
253
309
  }
254
310
 
255
- // Evict conflicts
256
- for (const evictHashStr of preAddResult.txHashesToEvict) {
257
- await this.#deleteTx(evictHashStr);
258
- this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`);
259
- if (acceptedPending.has(evictHashStr)) {
260
- // Evicted tx was from this batch - mark as ignored in result
261
- acceptedPending.delete(evictHashStr);
262
- ignored.push(TxHash.fromString(evictHashStr));
311
+ // Evict conflicts, grouped by rule name for metrics
312
+ if (preAddResult.evictions && preAddResult.evictions.length > 0) {
313
+ const byReason = new Map<string, string[]>();
314
+ for (const { txHash: evictHash, reason } of preAddResult.evictions) {
315
+ const group = byReason.get(reason);
316
+ if (group) {
317
+ group.push(evictHash);
318
+ } else {
319
+ byReason.set(reason, [evictHash]);
320
+ }
321
+ }
322
+ for (const [reason, hashes] of byReason) {
323
+ await this.#evictTxs(hashes, reason);
324
+ }
325
+ for (const evictHashStr of preAddResult.txHashesToEvict) {
326
+ this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`, {
327
+ evictedTxHash: evictHashStr,
328
+ replacementTxHash: txHashStr,
329
+ });
330
+ if (acceptedPending.has(evictHashStr)) {
331
+ // Evicted tx was from this batch - mark as ignored in result
332
+ acceptedPending.delete(evictHashStr);
333
+ ignored.push(TxHash.fromString(evictHashStr));
334
+ }
263
335
  }
264
336
  }
265
337
 
338
+ // Randomly drop the transaction for testing purposes (report as accepted so it propagates)
339
+ if (this.#config.dropTransactionsProbability > 0 && Math.random() < this.#config.dropTransactionsProbability) {
340
+ this.#log.debug(`Dropping tx ${txHashStr} (simulated drop for testing)`);
341
+ return { status: 'accepted' };
342
+ }
343
+
266
344
  // Add the transaction
267
- await this.#addTx(tx, 'pending', opts);
345
+ await this.#addTx(tx, 'pending', opts, precomputedMeta);
268
346
  return { status: 'accepted' };
269
347
  }
270
348
 
271
- async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored' | 'rejected'> {
349
+ async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
272
350
  const txHashStr = tx.getTxHash().toString();
273
351
 
274
352
  // Check if already in pool
@@ -276,14 +354,8 @@ export class TxPoolV2Impl {
276
354
  return 'ignored';
277
355
  }
278
356
 
279
- // Build metadata and validate using metadata
357
+ // Build metadata and check pre-add rules
280
358
  const meta = await buildTxMetaData(tx);
281
- const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
282
- if (validationResult !== true) {
283
- return 'rejected';
284
- }
285
-
286
- // Use pre-add rules
287
359
  const poolAccess = this.#createPreAddPoolAccess();
288
360
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
289
361
 
@@ -320,22 +392,57 @@ export class TxPoolV2Impl {
320
392
  });
321
393
  }
322
394
 
323
- protectTxs(txHashes: TxHash[], block: BlockHeader): TxHash[] {
395
+ async protectTxs(txHashes: TxHash[], block: BlockHeader): Promise<TxHash[]> {
324
396
  const slotNumber = block.globalVariables.slotNumber;
325
397
  const missing: TxHash[] = [];
398
+ let softDeletedHits = 0;
399
+ let missingPreviouslyEvicted = 0;
326
400
 
327
- for (const txHash of txHashes) {
328
- const txHashStr = txHash.toString();
401
+ await this.#store.transactionAsync(async () => {
402
+ for (const txHash of txHashes) {
403
+ const txHashStr = txHash.toString();
329
404
 
330
- if (this.#indices.has(txHashStr)) {
331
- // Update protection for existing tx
332
- this.#indices.updateProtection(txHashStr, slotNumber);
333
- } else {
334
- // Pre-record protection for tx we don't have yet
335
- this.#indices.setProtection(txHashStr, slotNumber);
336
- missing.push(txHash);
405
+ if (this.#indices.has(txHashStr)) {
406
+ // Update protection for existing tx
407
+ this.#indices.updateProtection(txHashStr, slotNumber);
408
+ } else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
409
+ // Resurrect soft-deleted tx as protected
410
+ const buffer = await this.#txsDB.getAsync(txHashStr);
411
+ if (buffer) {
412
+ const tx = Tx.fromBuffer(buffer);
413
+ await this.#addTx(tx, { protected: slotNumber });
414
+ softDeletedHits++;
415
+ } else {
416
+ // Data missing despite soft-delete flag — treat as truly missing
417
+ this.#indices.setProtection(txHashStr, slotNumber);
418
+ missing.push(txHash);
419
+ }
420
+ } else {
421
+ // Truly missing — pre-record protection for tx we don't have yet
422
+ this.#indices.setProtection(txHashStr, slotNumber);
423
+ missing.push(txHash);
424
+ if (this.#evictedTxHashes.has(txHashStr)) {
425
+ missingPreviouslyEvicted++;
426
+ }
427
+ }
337
428
  }
429
+ });
430
+
431
+ // Record metrics
432
+ if (softDeletedHits > 0) {
433
+ this.#instrumentation.recordSoftDeletedHits(softDeletedHits);
434
+ }
435
+ if (missing.length > 0) {
436
+ this.#log.debug(`protectTxs missing tx hashes: ${missing.map(h => h.toString()).join(', ')}`);
437
+ this.#instrumentation.recordMissingOnProtect(missing.length);
338
438
  }
439
+ if (missingPreviouslyEvicted > 0) {
440
+ this.#instrumentation.recordMissingPreviouslyEvicted(missingPreviouslyEvicted);
441
+ }
442
+
443
+ this.#log.info(
444
+ `Protected ${txHashes.length} txs, missing: ${missing.length}, soft-deleted hits: ${softDeletedHits}`,
445
+ );
339
446
 
340
447
  return missing;
341
448
  }
@@ -380,54 +487,63 @@ export class TxPoolV2Impl {
380
487
  }
381
488
  }
382
489
 
383
- // Step 4: Mark txs as mined (only those we have in the pool)
384
- for (const meta of found) {
385
- this.#indices.markAsMined(meta, blockId);
386
- await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
387
- }
490
+ await this.#store.transactionAsync(async () => {
491
+ // Step 4: Mark txs as mined (only those we have in the pool)
492
+ for (const meta of found) {
493
+ this.#indices.markAsMined(meta, blockId);
494
+ await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
495
+ }
388
496
 
389
- // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
390
- await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
497
+ // Step 5: Run post-event eviction rules (inside transaction for atomicity)
498
+ await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
499
+ });
391
500
 
392
501
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
393
502
  }
394
503
 
395
504
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
396
- // Step 1: Find expired protected txs
397
- const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
505
+ await this.#store.transactionAsync(async () => {
506
+ // Step 0: Clean up slot-deleted txs from previous slots
507
+ await this.#deletedPool.cleanupSlotDeleted(slotNumber);
398
508
 
399
- // Step 2: Clear protection for all expired entries (including those without metadata)
400
- this.#indices.clearProtection(expiredProtected);
509
+ // Step 1: Find expired protected txs
510
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
401
511
 
402
- // Step 3: Filter to only txs that have metadata and are not mined
403
- const txsToRestore = this.#indices.filterRestorable(expiredProtected);
404
- if (txsToRestore.length === 0) {
405
- return;
406
- }
512
+ // Step 2: Clear protection for all expired entries (including those without metadata)
513
+ this.#indices.clearProtection(expiredProtected);
407
514
 
408
- this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
515
+ // Step 3: Filter to only txs that have metadata and are not mined
516
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
517
+ if (txsToRestore.length === 0) {
518
+ this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
519
+ return;
520
+ }
409
521
 
410
- // Step 4: Validate for pending pool
411
- const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
522
+ this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
412
523
 
413
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
414
- const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
524
+ // Step 4: Validate for pending pool
525
+ const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
415
526
 
416
- // Step 6: Delete invalid and evicted txs
417
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
527
+ // Step 5: Resolve nullifier conflicts and add winners to pending indices
528
+ const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
418
529
 
419
- // Step 7: Run eviction rules (enforce pool size limit)
420
- if (added.length > 0) {
421
- const feePayers = added.map(meta => meta.feePayer);
422
- const uniqueFeePayers = new Set<string>(feePayers);
423
- await this.#evictionManager.evictAfterNewTxs(
424
- added.map(m => m.txHash),
425
- [...uniqueFeePayers],
426
- );
427
- }
530
+ // Step 6: Delete invalid txs and evict conflict losers
531
+ await this.#deleteTxsBatch(invalid);
532
+ await this.#evictTxs(toEvict, 'NullifierConflict');
533
+
534
+ // Step 7: Run eviction rules (enforce pool size limit)
535
+ if (added.length > 0) {
536
+ const feePayers = added.map(meta => meta.feePayer);
537
+ const uniqueFeePayers = new Set<string>(feePayers);
538
+ await this.#evictionManager.evictAfterNewTxs(
539
+ added.map(m => m.txHash),
540
+ [...uniqueFeePayers],
541
+ );
542
+ }
543
+ });
428
544
  }
429
545
 
430
- async handlePrunedBlocks(latestBlock: L2BlockId): Promise<void> {
546
+ async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
431
547
  // Step 1: Find transactions mined after the prune point
432
548
  const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
433
549
  if (txsToUnmine.length === 0) {
@@ -437,43 +553,62 @@ export class TxPoolV2Impl {
437
553
 
438
554
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
439
555
 
440
- // Step 2: Mark ALL un-mined txs with their original mined block number
441
- // This ensures they get soft-deleted if removed later, and only hard-deleted
442
- // when their original mined block is finalized
443
- await this.#deletedPool.markFromPrunedBlock(
444
- txsToUnmine.map(m => ({
445
- txHash: m.txHash,
446
- minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
447
- })),
448
- );
556
+ await this.#store.transactionAsync(async () => {
557
+ // Step 2: Mark ALL un-mined txs with their original mined block number
558
+ // This ensures they get soft-deleted if removed later, and only hard-deleted
559
+ // when their original mined block is finalized
560
+ await this.#deletedPool.markFromPrunedBlock(
561
+ txsToUnmine.map(m => ({
562
+ txHash: m.txHash,
563
+ minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
564
+ })),
565
+ );
449
566
 
450
- // Step 3: Unmine - clear mined status from metadata
451
- for (const meta of txsToUnmine) {
452
- this.#indices.markAsUnmined(meta);
453
- }
567
+ // Step 3: Unmine - clear mined status from metadata
568
+ for (const meta of txsToUnmine) {
569
+ this.#indices.markAsUnmined(meta);
570
+ }
454
571
 
455
- // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
456
- const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
572
+ // If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
573
+ if (options?.deleteAllTxs) {
574
+ const allTxHashes = txsToUnmine.map(m => m.txHash);
575
+ await this.#deleteTxsBatch(allTxHashes);
576
+ this.#log.info(
577
+ `Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
578
+ );
579
+ return;
580
+ }
581
+
582
+ // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
583
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
457
584
 
458
- // Step 4: Validate for pending pool
459
- const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
585
+ // Step 5: Validate for pending pool
586
+ const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
460
587
 
461
- // Step 6: Resolve nullifier conflicts and add winners to pending indices
462
- const { toEvict } = this.#applyNullifierConflictResolution(valid);
588
+ // Step 6: Resolve nullifier conflicts and add winners to pending indices
589
+ const { toEvict } = this.#applyNullifierConflictResolution(valid);
463
590
 
464
- // Step 7: Delete invalid and evicted txs
465
- await this.#deleteTxsBatch([...invalid, ...toEvict]);
591
+ // Step 7: Delete invalid txs and evict conflict losers
592
+ await this.#deleteTxsBatch(invalid);
593
+ await this.#evictTxs(toEvict, 'NullifierConflict');
466
594
 
467
- // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
468
- // This handles cases like existing pending txs with invalid fee payer balances
469
- await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
595
+ this.#log.info(
596
+ `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
597
+ { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
598
+ );
599
+
600
+ // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
601
+ // This handles cases like existing pending txs with invalid fee payer balances
602
+ await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
603
+ });
470
604
  }
471
605
 
472
606
  async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
473
- // Delete failed txs
474
- await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
607
+ await this.#store.transactionAsync(async () => {
608
+ await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
609
+ });
475
610
 
476
- this.#log.info(`Deleted ${txHashes.length} failed txs`);
611
+ this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
477
612
  }
478
613
 
479
614
  async handleFinalizedBlock(block: BlockHeader): Promise<void> {
@@ -482,30 +617,34 @@ export class TxPoolV2Impl {
482
617
  // Step 1: Find mined txs at or before finalized block
483
618
  const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
484
619
 
485
- // Step 2: Collect mined txs for archiving (before deletion)
486
- const txsToArchive: Tx[] = [];
487
- if (this.#archive.isEnabled()) {
488
- for (const txHashStr of minedTxsToFinalize) {
489
- const buffer = await this.#txsDB.getAsync(txHashStr);
490
- if (buffer) {
491
- txsToArchive.push(Tx.fromBuffer(buffer));
620
+ await this.#store.transactionAsync(async () => {
621
+ // Step 2: Collect mined txs for archiving (before deletion)
622
+ const txsToArchive: Tx[] = [];
623
+ if (this.#archive.isEnabled()) {
624
+ for (const txHashStr of minedTxsToFinalize) {
625
+ const buffer = await this.#txsDB.getAsync(txHashStr);
626
+ if (buffer) {
627
+ txsToArchive.push(Tx.fromBuffer(buffer));
628
+ }
492
629
  }
493
630
  }
494
- }
495
631
 
496
- // Step 3: Delete mined txs from active pool
497
- await this.#deleteTxsBatch(minedTxsToFinalize);
632
+ // Step 3: Delete mined txs from active pool
633
+ await this.#deleteTxsBatch(minedTxsToFinalize);
498
634
 
499
- // Step 4: Finalize soft-deleted txs
500
- await this.#deletedPool.finalizeBlock(blockNumber);
635
+ // Step 4: Finalize soft-deleted txs
636
+ await this.#deletedPool.finalizeBlock(blockNumber);
501
637
 
502
- // Step 5: Archive mined txs
503
- if (txsToArchive.length > 0) {
504
- await this.#archive.archiveTxs(txsToArchive);
505
- }
638
+ // Step 5: Archive mined txs
639
+ if (txsToArchive.length > 0) {
640
+ await this.#archive.archiveTxs(txsToArchive);
641
+ }
642
+ });
506
643
 
507
644
  if (minedTxsToFinalize.length > 0) {
508
- this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`);
645
+ this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
646
+ txHashes: minedTxsToFinalize,
647
+ });
509
648
  }
510
649
  }
511
650
 
@@ -549,6 +688,13 @@ export class TxPoolV2Impl {
549
688
  return [...this.#indices.iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
550
689
  }
551
690
 
691
+ getEligiblePendingTxHashes(): TxHash[] {
692
+ const maxReceivedAt = this.#dateProvider.now() - this.#config.minTxPoolAgeMs;
693
+ return [...this.#indices.iterateEligiblePendingByPriority('desc', maxReceivedAt)].map(hash =>
694
+ TxHash.fromString(hash),
695
+ );
696
+ }
697
+
552
698
  getPendingTxCount(): number {
553
699
  return this.#indices.getPendingTxCount();
554
700
  }
@@ -593,6 +739,9 @@ export class TxPoolV2Impl {
593
739
  this.#config.archivedTxLimit = config.archivedTxLimit;
594
740
  this.#archive.updateLimit(config.archivedTxLimit);
595
741
  }
742
+ if (config.minTxPoolAgeMs !== undefined) {
743
+ this.#config.minTxPoolAgeMs = config.minTxPoolAgeMs;
744
+ }
596
745
  // Update eviction rules with new config
597
746
  this.#evictionManager.updateConfig(config);
598
747
  }
@@ -610,8 +759,17 @@ export class TxPoolV2Impl {
610
759
 
611
760
  // === Metrics ===
612
761
 
613
- countTxs(): { pending: number; protected: number; mined: number } {
614
- return this.#indices.countTxs();
762
+ countTxs(): {
763
+ pending: number;
764
+ protected: number;
765
+ mined: number;
766
+ softDeleted: number;
767
+ totalMetadataBytes: number;
768
+ } {
769
+ return {
770
+ ...this.#indices.countTxs(),
771
+ softDeleted: this.#deletedPool.getSoftDeletedCount(),
772
+ };
615
773
  }
616
774
 
617
775
  // ============================================================================
@@ -626,11 +784,14 @@ export class TxPoolV2Impl {
626
784
  tx: Tx,
627
785
  state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
628
786
  opts: { source?: string } = {},
787
+ precomputedMeta?: TxMetaData,
629
788
  ): Promise<TxMetaData> {
630
789
  const txHashStr = tx.getTxHash().toString();
631
- const meta = await buildTxMetaData(tx);
790
+ const meta = precomputedMeta ?? (await buildTxMetaData(tx));
791
+ meta.receivedAt = this.#dateProvider.now();
632
792
 
633
793
  await this.#txsDB.set(txHashStr, tx.toBuffer());
794
+ await this.#deletedPool.clearSoftDeleted(txHashStr);
634
795
  this.#callbacks.onTxsAdded([tx], opts);
635
796
 
636
797
  if (state === 'pending') {
@@ -643,9 +804,11 @@ export class TxPoolV2Impl {
643
804
  }
644
805
 
645
806
  const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
646
- this.#log.verbose(`Added ${stateStr} tx ${txHashStr}`, {
807
+ this.#log.debug(`Added tx ${txHashStr} as ${stateStr}`, {
647
808
  eventName: 'tx-added-to-pool',
809
+ txHash: txHashStr,
648
810
  state: stateStr,
811
+ source: opts.source,
649
812
  });
650
813
 
651
814
  return meta;
@@ -673,6 +836,29 @@ export class TxPoolV2Impl {
673
836
  }
674
837
  }
675
838
 
839
+ /** Evicts transactions: records eviction metric with reason, caches hashes, then deletes. */
840
+ async #evictTxs(txHashes: string[], reason: string): Promise<void> {
841
+ if (txHashes.length === 0) {
842
+ return;
843
+ }
844
+ this.#instrumentation.recordEvictions(txHashes.length, reason);
845
+ for (const txHashStr of txHashes) {
846
+ this.#log.debug(`Evicting tx ${txHashStr}`, { txHash: txHashStr, reason });
847
+ this.#addToEvictedCache(txHashStr);
848
+ }
849
+ await this.#deleteTxsBatch(txHashes);
850
+ }
851
+
852
+ /** Adds a tx hash to the bounded evicted cache, evicting the oldest entry if at capacity. */
853
+ #addToEvictedCache(txHashStr: string): void {
854
+ if (this.#evictedTxHashes.size >= this.#config.evictedTxCacheSize) {
855
+ // FIFO eviction: remove the first (oldest) entry
856
+ const oldest = this.#evictedTxHashes.values().next().value!;
857
+ this.#evictedTxHashes.delete(oldest);
858
+ }
859
+ this.#evictedTxHashes.add(txHashStr);
860
+ }
861
+
676
862
  // ============================================================================
677
863
  // PRIVATE HELPERS - Validation & Conflict Resolution
678
864
  // ============================================================================
@@ -828,7 +1014,9 @@ export class TxPoolV2Impl {
828
1014
  if (preAddResult.shouldIgnore) {
829
1015
  // Transaction rejected - mark for deletion from DB
830
1016
  rejected.push(meta.txHash);
831
- this.#log.debug(`Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason}`);
1017
+ this.#log.debug(
1018
+ `Rejected tx ${meta.txHash} during rebuild: ${preAddResult.reason?.message ?? 'unknown reason'}`,
1019
+ );
832
1020
  continue;
833
1021
  }
834
1022
 
@@ -864,7 +1052,7 @@ export class TxPoolV2Impl {
864
1052
  getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
865
1053
  getPendingTxCount: () => this.#indices.getPendingTxCount(),
866
1054
  getLowestPriorityPending: (limit: number) => this.#indices.getLowestPriorityPending(limit),
867
- deleteTxs: (txHashes: string[]) => this.#deleteTxsBatch(txHashes),
1055
+ deleteTxs: (txHashes: string[], reason?: string) => this.#evictTxs(txHashes, reason ?? 'unknown'),
868
1056
  };
869
1057
  }
870
1058