@aztec/p2p 0.0.1-commit.43c09e3f → 0.0.1-commit.4ad48494d

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 (230) hide show
  1. package/dest/client/factory.d.ts +5 -5
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +44 -10
  4. package/dest/client/interface.d.ts +37 -15
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +35 -36
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +114 -138
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
  10. package/dest/config.d.ts +23 -5
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +16 -1
  13. package/dest/index.d.ts +2 -1
  14. package/dest/index.d.ts.map +1 -1
  15. package/dest/index.js +1 -0
  16. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +104 -88
  17. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool.js +441 -3
  19. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
  20. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +353 -87
  22. package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
  23. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/index.js +1 -2
  25. package/dest/mem_pools/index.d.ts +3 -2
  26. package/dest/mem_pools/index.d.ts.map +1 -1
  27. package/dest/mem_pools/index.js +1 -1
  28. package/dest/mem_pools/interface.d.ts +5 -5
  29. package/dest/mem_pools/interface.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +102 -0
  31. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -0
  32. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +242 -0
  33. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  34. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -0
  36. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +3 -1
  37. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +3 -1
  38. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +1 -1
  39. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +3 -1
  41. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -1
  42. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/index.js +1 -0
  44. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +11 -3
  45. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  46. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  47. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +27 -3
  48. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  49. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +39 -5
  50. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +105 -0
  51. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -0
  52. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +345 -0
  53. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
  54. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  55. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +12 -2
  56. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +4 -2
  57. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  58. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +259 -520
  59. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
  60. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  61. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +3 -3
  62. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  63. package/dest/msg_validators/tx_validator/block_header_validator.d.ts +16 -3
  64. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
  65. package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
  66. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +13 -3
  67. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
  68. package/dest/msg_validators/tx_validator/double_spend_validator.js +4 -4
  69. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +20 -4
  70. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  71. package/dest/msg_validators/tx_validator/timestamp_validator.js +2 -2
  72. package/dest/services/dummy_service.d.ts +10 -2
  73. package/dest/services/dummy_service.d.ts.map +1 -1
  74. package/dest/services/dummy_service.js +6 -0
  75. package/dest/services/encoding.d.ts +2 -2
  76. package/dest/services/encoding.d.ts.map +1 -1
  77. package/dest/services/encoding.js +2 -2
  78. package/dest/services/gossipsub/index.d.ts +3 -0
  79. package/dest/services/gossipsub/index.d.ts.map +1 -0
  80. package/dest/services/gossipsub/index.js +2 -0
  81. package/dest/services/gossipsub/scoring.d.ts +21 -3
  82. package/dest/services/gossipsub/scoring.d.ts.map +1 -1
  83. package/dest/services/gossipsub/scoring.js +24 -7
  84. package/dest/services/gossipsub/topic_score_params.d.ts +161 -0
  85. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -0
  86. package/dest/services/gossipsub/topic_score_params.js +324 -0
  87. package/dest/services/libp2p/libp2p_service.d.ts +84 -35
  88. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  89. package/dest/services/libp2p/libp2p_service.js +368 -273
  90. package/dest/services/peer-manager/peer_scoring.d.ts +1 -1
  91. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  92. package/dest/services/peer-manager/peer_scoring.js +25 -2
  93. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -4
  94. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  95. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +8 -8
  96. package/dest/services/reqresp/interface.d.ts +10 -1
  97. package/dest/services/reqresp/interface.d.ts.map +1 -1
  98. package/dest/services/reqresp/interface.js +15 -1
  99. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +7 -5
  100. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  101. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +16 -11
  102. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +21 -10
  103. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  104. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +27 -11
  105. package/dest/services/reqresp/protocols/tx.d.ts +7 -1
  106. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  107. package/dest/services/reqresp/protocols/tx.js +20 -0
  108. package/dest/services/reqresp/reqresp.d.ts +1 -1
  109. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  110. package/dest/services/reqresp/reqresp.js +11 -4
  111. package/dest/services/service.d.ts +35 -1
  112. package/dest/services/service.d.ts.map +1 -1
  113. package/dest/services/tx_collection/config.d.ts +22 -4
  114. package/dest/services/tx_collection/config.d.ts.map +1 -1
  115. package/dest/services/tx_collection/config.js +49 -3
  116. package/dest/services/tx_collection/fast_tx_collection.d.ts +6 -5
  117. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  118. package/dest/services/tx_collection/fast_tx_collection.js +27 -17
  119. package/dest/services/tx_collection/file_store_tx_collection.d.ts +53 -0
  120. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -0
  121. package/dest/services/tx_collection/file_store_tx_collection.js +165 -0
  122. package/dest/services/tx_collection/file_store_tx_source.d.ts +28 -0
  123. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -0
  124. package/dest/services/tx_collection/file_store_tx_source.js +59 -0
  125. package/dest/services/tx_collection/index.d.ts +3 -2
  126. package/dest/services/tx_collection/index.d.ts.map +1 -1
  127. package/dest/services/tx_collection/index.js +1 -0
  128. package/dest/services/tx_collection/proposal_tx_collector.d.ts +12 -12
  129. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  130. package/dest/services/tx_collection/proposal_tx_collector.js +4 -5
  131. package/dest/services/tx_collection/slow_tx_collection.d.ts +6 -2
  132. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  133. package/dest/services/tx_collection/slow_tx_collection.js +55 -23
  134. package/dest/services/tx_collection/tx_collection.d.ts +19 -7
  135. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  136. package/dest/services/tx_collection/tx_collection.js +75 -3
  137. package/dest/services/tx_collection/tx_collection_sink.d.ts +15 -6
  138. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  139. package/dest/services/tx_collection/tx_collection_sink.js +13 -7
  140. package/dest/services/tx_file_store/config.d.ts +1 -3
  141. package/dest/services/tx_file_store/config.d.ts.map +1 -1
  142. package/dest/services/tx_file_store/config.js +0 -4
  143. package/dest/services/tx_file_store/tx_file_store.d.ts +4 -3
  144. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
  145. package/dest/services/tx_file_store/tx_file_store.js +8 -5
  146. package/dest/services/tx_provider.d.ts +3 -3
  147. package/dest/services/tx_provider.d.ts.map +1 -1
  148. package/dest/services/tx_provider.js +5 -4
  149. package/dest/test-helpers/make-test-p2p-clients.d.ts +3 -3
  150. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  151. package/dest/test-helpers/mock-pubsub.d.ts +27 -1
  152. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  153. package/dest/test-helpers/mock-pubsub.js +97 -2
  154. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  155. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  156. package/dest/test-helpers/reqresp-nodes.js +2 -1
  157. package/dest/test-helpers/testbench-utils.d.ts +40 -38
  158. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  159. package/dest/test-helpers/testbench-utils.js +128 -59
  160. package/dest/testbench/p2p_client_testbench_worker.js +2 -2
  161. package/package.json +14 -14
  162. package/src/client/factory.ts +81 -13
  163. package/src/client/interface.ts +45 -14
  164. package/src/client/p2p_client.ts +151 -161
  165. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  166. package/src/config.ts +34 -2
  167. package/src/index.ts +1 -0
  168. package/src/mem_pools/attestation_pool/attestation_pool.ts +496 -91
  169. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +442 -102
  170. package/src/mem_pools/attestation_pool/index.ts +9 -2
  171. package/src/mem_pools/index.ts +4 -1
  172. package/src/mem_pools/interface.ts +4 -4
  173. package/src/mem_pools/tx_pool_v2/README.md +103 -16
  174. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +310 -0
  175. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +3 -0
  176. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +1 -1
  177. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +1 -1
  178. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +3 -1
  179. package/src/mem_pools/tx_pool_v2/index.ts +1 -0
  180. package/src/mem_pools/tx_pool_v2/interfaces.ts +10 -2
  181. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +57 -6
  182. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +433 -0
  183. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +10 -1
  184. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +266 -607
  185. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +2 -2
  186. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +2 -2
  187. package/src/msg_validators/tx_validator/block_header_validator.ts +15 -3
  188. package/src/msg_validators/tx_validator/double_spend_validator.ts +11 -6
  189. package/src/msg_validators/tx_validator/timestamp_validator.ts +19 -14
  190. package/src/services/dummy_service.ts +12 -0
  191. package/src/services/encoding.ts +2 -2
  192. package/src/services/gossipsub/README.md +626 -0
  193. package/src/services/gossipsub/index.ts +2 -0
  194. package/src/services/gossipsub/scoring.ts +29 -5
  195. package/src/services/gossipsub/topic_score_params.ts +451 -0
  196. package/src/services/libp2p/libp2p_service.ts +370 -275
  197. package/src/services/peer-manager/peer_scoring.ts +25 -0
  198. package/src/services/reqresp/batch-tx-requester/README.md +7 -7
  199. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +11 -11
  200. package/src/services/reqresp/interface.ts +26 -1
  201. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +23 -14
  202. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +38 -15
  203. package/src/services/reqresp/protocols/tx.ts +22 -0
  204. package/src/services/reqresp/reqresp.ts +13 -3
  205. package/src/services/service.ts +40 -0
  206. package/src/services/tx_collection/config.ts +74 -6
  207. package/src/services/tx_collection/fast_tx_collection.ts +28 -26
  208. package/src/services/tx_collection/file_store_tx_collection.ts +198 -0
  209. package/src/services/tx_collection/file_store_tx_source.ts +73 -0
  210. package/src/services/tx_collection/index.ts +2 -1
  211. package/src/services/tx_collection/proposal_tx_collector.ts +12 -14
  212. package/src/services/tx_collection/slow_tx_collection.ts +64 -30
  213. package/src/services/tx_collection/tx_collection.ts +109 -13
  214. package/src/services/tx_collection/tx_collection_sink.ts +17 -7
  215. package/src/services/tx_file_store/config.ts +0 -6
  216. package/src/services/tx_file_store/tx_file_store.ts +9 -7
  217. package/src/services/tx_provider.ts +8 -7
  218. package/src/test-helpers/make-test-p2p-clients.ts +3 -3
  219. package/src/test-helpers/mock-pubsub.ts +133 -3
  220. package/src/test-helpers/reqresp-nodes.ts +2 -1
  221. package/src/test-helpers/testbench-utils.ts +127 -71
  222. package/src/testbench/p2p_client_testbench_worker.ts +2 -2
  223. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
  224. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
  225. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
  226. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
  227. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  228. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
  229. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
  230. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
@@ -1,5 +1,6 @@
1
- import { SlotNumber } from '@aztec/foundation/branded-types';
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';
@@ -10,6 +11,7 @@ import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
10
11
  import { BlockHeader, Tx, TxHash, type TxValidator } from '@aztec/stdlib/tx';
11
12
 
12
13
  import { TxArchive } from './archive/index.js';
14
+ import { DeletedPool } from './deleted_pool.js';
13
15
  import {
14
16
  EvictionManager,
15
17
  FeePayerBalanceEvictionRule,
@@ -29,14 +31,8 @@ import {
29
31
  type TxPoolV2Config,
30
32
  type TxPoolV2Dependencies,
31
33
  } from './interfaces.js';
32
- import {
33
- type TxMetaData,
34
- type TxState,
35
- buildTxMetaData,
36
- checkNullifierConflict,
37
- compareFee,
38
- compareTxHash,
39
- } from './tx_metadata.js';
34
+ import { type TxMetaData, type TxState, buildTxMetaData, checkNullifierConflict } from './tx_metadata.js';
35
+ import { TxPoolIndices } from './tx_pool_indices.js';
40
36
 
41
37
  /**
42
38
  * Callbacks for the implementation to notify the outer class about events and metrics.
@@ -59,27 +55,17 @@ export class TxPoolV2Impl {
59
55
  // === Dependencies ===
60
56
  #l2BlockSource: L2BlockSource;
61
57
  #worldStateSynchronizer: WorldStateSynchronizer;
62
- #pendingTxValidator: TxValidator<Tx>;
58
+ #createTxValidator: TxPoolV2Dependencies['createTxValidator'];
63
59
 
64
60
  // === In-Memory Indices ===
65
- /** Primary metadata store: txHash -> TxMetaData */
66
- #metadata: Map<string, TxMetaData> = new Map();
67
- /** Nullifier to txHash index (pending txs only) */
68
- #nullifierToTxHash: Map<string, string> = new Map();
69
- /** Fee payer to txHashes index (pending txs only) */
70
- #feePayerToTxHashes: Map<string, Set<string>> = new Map();
71
- /**
72
- * Pending txHashes grouped by priority fee.
73
- * Outer map: priorityFee -> Set of txHashes at that fee level.
74
- */
75
- #pendingByPriority: Map<bigint, Set<string>> = new Map();
76
- /** Protected transactions: txHash -> slotNumber. Includes txs we have and txs we expect to receive. */
77
- #protectedTransactions: Map<string, SlotNumber> = new Map();
61
+ #indices: TxPoolIndices = new TxPoolIndices();
78
62
 
79
63
  // === Config & Services ===
80
64
  #config: TxPoolV2Config;
81
65
  #archive: TxArchive;
66
+ #deletedPool: DeletedPool;
82
67
  #evictionManager: EvictionManager;
68
+ #dateProvider: DateProvider;
83
69
  #log: Logger;
84
70
  #callbacks: TxPoolV2Callbacks;
85
71
 
@@ -89,6 +75,7 @@ export class TxPoolV2Impl {
89
75
  deps: TxPoolV2Dependencies,
90
76
  callbacks: TxPoolV2Callbacks,
91
77
  config: Partial<TxPoolV2Config> = {},
78
+ dateProvider: DateProvider,
92
79
  log: Logger,
93
80
  ) {
94
81
  this.#store = store;
@@ -96,10 +83,12 @@ export class TxPoolV2Impl {
96
83
 
97
84
  this.#l2BlockSource = deps.l2BlockSource;
98
85
  this.#worldStateSynchronizer = deps.worldStateSynchronizer;
99
- this.#pendingTxValidator = deps.pendingTxValidator;
86
+ this.#createTxValidator = deps.createTxValidator;
100
87
 
101
88
  this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
102
89
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
90
+ this.#deletedPool = new DeletedPool(store, this.#txsDB, log);
91
+ this.#dateProvider = dateProvider;
103
92
  this.#log = log;
104
93
  this.#callbacks = callbacks;
105
94
 
@@ -134,26 +123,42 @@ export class TxPoolV2Impl {
134
123
  * by running pre-add rules to resolve nullifier conflicts, balance checks, and pool size limits.
135
124
  */
136
125
  async hydrateFromDatabase(): Promise<void> {
137
- // Step 1: Load all transactions from DB
126
+ // Step 0: Hydrate deleted pool state
127
+ await this.#deletedPool.hydrateFromDatabase();
128
+
129
+ // Step 1: Load all transactions from DB (excluding soft-deleted)
138
130
  const { loaded, errors: deserializationErrors } = await this.#loadAllTxsFromDb();
139
131
 
140
132
  // Step 2: Check mined status for each tx
141
133
  await this.#markMinedStatusBatch(loaded.map(l => l.meta));
142
134
 
143
135
  // Step 3: Partition by mined status
144
- const { mined, nonMined } = this.#partitionByMinedStatus(loaded);
136
+ const mined: TxMetaData[] = [];
137
+ const nonMined: { tx: Tx; meta: TxMetaData }[] = [];
138
+ for (const entry of loaded) {
139
+ if (entry.meta.minedL2BlockId !== undefined) {
140
+ mined.push(entry.meta);
141
+ } else {
142
+ nonMined.push(entry);
143
+ }
144
+ }
145
145
 
146
146
  // Step 4: Validate non-mined transactions
147
- const { valid, invalid } = await this.#validateNonMinedTxs(nonMined);
147
+ const { valid, invalid } = await this.#revalidateMetadata(
148
+ nonMined.map(e => e.meta),
149
+ 'on startup',
150
+ );
148
151
 
149
152
  // Step 5: Populate mined indices (these don't need conflict resolution)
150
- this.#populateMinedIndices(mined);
153
+ for (const meta of mined) {
154
+ this.#indices.addMined(meta);
155
+ }
151
156
 
152
157
  // Step 6: Rebuild pending pool by running pre-add rules for each tx
153
158
  // This resolves nullifier conflicts, fee payer balance issues, and pool size limits
154
159
  const { rejected } = await this.#rebuildPendingPool(valid);
155
160
 
156
- // Step 7: Delete invalid and rejected txs from DB
161
+ // Step 7: Delete invalid and rejected txs from DB only (indices were never populated for these)
157
162
  const toDelete = [...deserializationErrors, ...invalid, ...rejected];
158
163
  if (toDelete.length === 0) {
159
164
  return;
@@ -163,14 +168,13 @@ export class TxPoolV2Impl {
163
168
  await this.#txsDB.delete(txHashStr);
164
169
  }
165
170
  });
166
- this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`);
171
+ this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`, { txHashes: toDelete });
167
172
  }
168
173
 
169
174
  async addPendingTxs(txs: Tx[], opts: { source?: string }): Promise<AddTxsResult> {
170
175
  const accepted: TxHash[] = [];
171
176
  const ignored: TxHash[] = [];
172
177
  const rejected: TxHash[] = [];
173
- const newlyAdded: Tx[] = [];
174
178
  const acceptedPending = new Set<string>();
175
179
 
176
180
  const poolAccess = this.#createPreAddPoolAccess();
@@ -181,31 +185,28 @@ export class TxPoolV2Impl {
181
185
  const txHashStr = txHash.toString();
182
186
 
183
187
  // Skip duplicates
184
- if (this.#isDuplicateTx(txHashStr)) {
188
+ if (this.#indices.has(txHashStr)) {
185
189
  ignored.push(txHash);
186
190
  continue;
187
191
  }
188
192
 
189
193
  // Check mined status first (applies to all paths)
190
194
  const minedBlockId = await this.#getMinedBlockId(txHash);
191
- const preProtectedSlot = this.#protectedTransactions.get(txHashStr);
195
+ const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
192
196
 
193
197
  if (minedBlockId) {
194
198
  // Already mined - add directly (protection already set if pre-protected)
195
- await this.#addNewMinedTx(tx, minedBlockId);
199
+ await this.#addTx(tx, { mined: minedBlockId }, opts);
196
200
  accepted.push(txHash);
197
- newlyAdded.push(tx);
198
201
  } else if (preProtectedSlot !== undefined) {
199
202
  // Pre-protected and not mined - add as protected (bypass validation)
200
- await this.#addNewProtectedTx(tx, preProtectedSlot);
203
+ await this.#addTx(tx, { protected: preProtectedSlot }, opts);
201
204
  accepted.push(txHash);
202
- newlyAdded.push(tx);
203
205
  } else {
204
206
  // Regular pending tx - validate and run pre-add rules
205
- const result = await this.#tryAddRegularPendingTx(tx, poolAccess, acceptedPending, ignored);
207
+ const result = await this.#tryAddRegularPendingTx(tx, opts, poolAccess, acceptedPending, ignored);
206
208
  if (result.status === 'accepted') {
207
209
  acceptedPending.add(txHashStr);
208
- newlyAdded.push(tx);
209
210
  } else if (result.status === 'rejected') {
210
211
  rejected.push(txHash);
211
212
  } else {
@@ -222,22 +223,18 @@ export class TxPoolV2Impl {
222
223
 
223
224
  // Run post-add eviction rules for pending txs
224
225
  if (acceptedPending.size > 0) {
225
- const feePayers = Array.from(acceptedPending).map(txHash => this.#metadata.get(txHash)!.feePayer);
226
+ const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
226
227
  const uniqueFeePayers = new Set<string>(feePayers);
227
228
  await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
228
229
  }
229
230
 
230
- // Emit events
231
- if (newlyAdded.length > 0) {
232
- this.#callbacks.onTxsAdded(newlyAdded, opts);
233
- }
234
-
235
231
  return { accepted, ignored, rejected };
236
232
  }
237
233
 
238
234
  /** Validates and adds a regular pending tx. Returns status. */
239
235
  async #tryAddRegularPendingTx(
240
236
  tx: Tx,
237
+ opts: { source?: string },
241
238
  poolAccess: PreAddPoolAccess,
242
239
  acceptedPending: Set<string>,
243
240
  ignored: TxHash[],
@@ -245,15 +242,13 @@ export class TxPoolV2Impl {
245
242
  const txHash = tx.getTxHash();
246
243
  const txHashStr = txHash.toString();
247
244
 
248
- // Validate transaction
249
- const validationResult = await this.#pendingTxValidator.validateTx(tx);
250
- if (validationResult.result !== 'valid') {
251
- this.#log.info(`Rejecting tx ${txHashStr}: ${validationResult.reason?.join(', ')}`);
245
+ // Build metadata and validate using metadata
246
+ const meta = await buildTxMetaData(tx);
247
+ if (!(await this.#validateMeta(meta))) {
252
248
  return { status: 'rejected' };
253
249
  }
254
250
 
255
- // Build metadata and run pre-add rules
256
- const meta = await buildTxMetaData(tx);
251
+ // Run pre-add rules
257
252
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
258
253
 
259
254
  if (preAddResult.shouldIgnore) {
@@ -261,18 +256,22 @@ export class TxPoolV2Impl {
261
256
  return { status: 'ignored' };
262
257
  }
263
258
 
264
- // Evict conflicts (tracking intra-batch evictions)
259
+ // Evict conflicts
265
260
  for (const evictHashStr of preAddResult.txHashesToEvict) {
266
261
  await this.#deleteTx(evictHashStr);
267
- this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`);
262
+ this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`, {
263
+ evictedTxHash: evictHashStr,
264
+ replacementTxHash: txHashStr,
265
+ });
268
266
  if (acceptedPending.has(evictHashStr)) {
267
+ // Evicted tx was from this batch - mark as ignored in result
269
268
  acceptedPending.delete(evictHashStr);
270
269
  ignored.push(TxHash.fromString(evictHashStr));
271
270
  }
272
271
  }
273
272
 
274
273
  // Add the transaction
275
- await this.#addNewPendingTx(tx);
274
+ await this.#addTx(tx, 'pending', opts);
276
275
  return { status: 'accepted' };
277
276
  }
278
277
 
@@ -280,18 +279,18 @@ export class TxPoolV2Impl {
280
279
  const txHashStr = tx.getTxHash().toString();
281
280
 
282
281
  // Check if already in pool
283
- if (this.#metadata.has(txHashStr)) {
282
+ if (this.#indices.has(txHashStr)) {
284
283
  return 'ignored';
285
284
  }
286
285
 
287
- // Validate transaction
288
- const validationResult = await this.#pendingTxValidator.validateTx(tx);
289
- if (validationResult.result !== 'valid') {
286
+ // Build metadata and validate using metadata
287
+ const meta = await buildTxMetaData(tx);
288
+ const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
289
+ if (validationResult !== true) {
290
290
  return 'rejected';
291
291
  }
292
292
 
293
- // Build metadata and use pre-add rules
294
- const meta = await buildTxMetaData(tx);
293
+ // Use pre-add rules
295
294
  const poolAccess = this.#createPreAddPoolAccess();
296
295
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
297
296
 
@@ -300,37 +299,32 @@ export class TxPoolV2Impl {
300
299
 
301
300
  async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
302
301
  const slotNumber = block.globalVariables.slotNumber;
303
- const newlyAdded: Tx[] = [];
304
302
 
305
303
  await this.#store.transactionAsync(async () => {
306
304
  for (const tx of txs) {
307
305
  const txHash = tx.getTxHash();
308
306
  const txHashStr = txHash.toString();
309
- const isNew = !this.#metadata.has(txHashStr);
307
+ const isNew = !this.#indices.has(txHashStr);
310
308
  const minedBlockId = await this.#getMinedBlockId(txHash);
311
309
 
312
310
  if (isNew) {
313
- // New tx - add as mined or protected
311
+ // New tx - add as mined or protected (callback emitted by #addTx)
314
312
  if (minedBlockId) {
315
- await this.#addNewMinedTx(tx, minedBlockId);
316
- this.#protectedTransactions.set(txHashStr, slotNumber);
313
+ await this.#addTx(tx, { mined: minedBlockId }, opts);
314
+ this.#indices.setProtection(txHashStr, slotNumber);
317
315
  } else {
318
- await this.#addNewProtectedTx(tx, slotNumber);
316
+ await this.#addTx(tx, { protected: slotNumber }, opts);
319
317
  }
320
- newlyAdded.push(tx);
321
318
  } else {
322
319
  // Existing tx - update protection and mined status
323
- this.#updateProtection(txHashStr, slotNumber);
320
+ this.#indices.updateProtection(txHashStr, slotNumber);
324
321
  if (minedBlockId) {
325
- this.#markAsMined(this.#metadata.get(txHashStr)!, minedBlockId);
322
+ const meta = this.#indices.getMetadata(txHashStr)!;
323
+ this.#indices.markAsMined(meta, minedBlockId);
326
324
  }
327
325
  }
328
326
  }
329
327
  });
330
-
331
- if (newlyAdded.length > 0) {
332
- this.#callbacks.onTxsAdded(newlyAdded, opts);
333
- }
334
328
  }
335
329
 
336
330
  protectTxs(txHashes: TxHash[], block: BlockHeader): TxHash[] {
@@ -340,12 +334,12 @@ export class TxPoolV2Impl {
340
334
  for (const txHash of txHashes) {
341
335
  const txHashStr = txHash.toString();
342
336
 
343
- if (this.#metadata.has(txHashStr)) {
344
- // Step 1a: Update protection for existing tx
345
- this.#updateProtection(txHashStr, slotNumber);
337
+ if (this.#indices.has(txHashStr)) {
338
+ // Update protection for existing tx
339
+ this.#indices.updateProtection(txHashStr, slotNumber);
346
340
  } else {
347
- // Step 1b: Pre-record protection for tx we don't have yet
348
- this.#protectedTransactions.set(txHashStr, slotNumber);
341
+ // Pre-record protection for tx we don't have yet
342
+ this.#indices.setProtection(txHashStr, slotNumber);
349
343
  missing.push(txHash);
350
344
  }
351
345
  }
@@ -356,28 +350,22 @@ export class TxPoolV2Impl {
356
350
  async addMinedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
357
351
  // Step 1: Build block ID
358
352
  const blockId = await this.#buildBlockId(block);
359
- const newlyAdded: Tx[] = [];
360
353
 
361
354
  await this.#store.transactionAsync(async () => {
362
355
  for (const tx of txs) {
363
356
  const txHashStr = tx.getTxHash().toString();
364
- const existingMeta = this.#metadata.get(txHashStr);
357
+ const existingMeta = this.#indices.getMetadata(txHashStr);
365
358
 
366
359
  if (existingMeta) {
367
- // Step 2a: Mark existing tx as mined
368
- this.#markAsMined(existingMeta, blockId);
360
+ // Mark existing tx as mined
361
+ this.#indices.markAsMined(existingMeta, blockId);
369
362
  } else {
370
- // Step 2b: Add new mined tx
371
- await this.#addNewMinedTx(tx, blockId);
372
- newlyAdded.push(tx);
363
+ // Add new mined tx (callback emitted by #addTx)
364
+ await this.#addTx(tx, { mined: blockId }, opts);
373
365
  }
366
+ await this.#deletedPool.clearIfMinedHigher(txHashStr, blockId.number);
374
367
  }
375
368
  });
376
-
377
- // Step 3: Emit events for newly added txs
378
- if (newlyAdded.length > 0) {
379
- this.#callbacks.onTxsAdded(newlyAdded, opts);
380
- }
381
369
  }
382
370
 
383
371
  async handleMinedBlock(block: L2Block): Promise<void> {
@@ -392,7 +380,7 @@ export class TxPoolV2Impl {
392
380
  const feePayers: string[] = [];
393
381
  const found: TxMetaData[] = [];
394
382
  for (const txHash of txHashes) {
395
- const meta = this.#metadata.get(txHash.toString());
383
+ const meta = this.#indices.getMetadata(txHash.toString());
396
384
  if (meta) {
397
385
  feePayers.push(meta.feePayer);
398
386
  found.push(meta);
@@ -400,24 +388,29 @@ export class TxPoolV2Impl {
400
388
  }
401
389
 
402
390
  // Step 4: Mark txs as mined (only those we have in the pool)
403
- this.#markTxsAsMined(found, blockId);
391
+ for (const meta of found) {
392
+ this.#indices.markAsMined(meta, blockId);
393
+ await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
394
+ }
404
395
 
405
396
  // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
406
397
  await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
407
398
 
408
- this.#callbacks.onTxsRemoved(txHashes.map(h => h.toBigInt()));
409
399
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
410
400
  }
411
401
 
412
402
  async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
403
+ // Step 0: Clean up slot-deleted txs from previous slots
404
+ await this.#deletedPool.cleanupSlotDeleted(slotNumber);
405
+
413
406
  // Step 1: Find expired protected txs
414
- const expiredProtected = this.#findExpiredProtectedTxs(slotNumber);
407
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
415
408
 
416
409
  // Step 2: Clear protection for all expired entries (including those without metadata)
417
- this.#clearProtection(expiredProtected);
410
+ this.#indices.clearProtection(expiredProtected);
418
411
 
419
412
  // Step 3: Filter to only txs that have metadata and are not mined
420
- const txsToRestore = this.#filterRestorable(expiredProtected);
413
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
421
414
  if (txsToRestore.length === 0) {
422
415
  return;
423
416
  }
@@ -425,7 +418,7 @@ export class TxPoolV2Impl {
425
418
  this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
426
419
 
427
420
  // Step 4: Validate for pending pool
428
- const { valid, invalid } = await this.#validateForPending(txsToRestore);
421
+ const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
429
422
 
430
423
  // Step 5: Resolve nullifier conflicts and add winners to pending indices
431
424
  const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
@@ -446,7 +439,7 @@ export class TxPoolV2Impl {
446
439
 
447
440
  async handlePrunedBlocks(latestBlock: L2BlockId): Promise<void> {
448
441
  // Step 1: Find transactions mined after the prune point
449
- const txsToUnmine = this.#findTxsMinedAfter(latestBlock.number);
442
+ const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
450
443
  if (txsToUnmine.length === 0) {
451
444
  this.#log.debug(`No transactions to un-mine for prune to block ${latestBlock.number}`);
452
445
  return;
@@ -454,46 +447,60 @@ export class TxPoolV2Impl {
454
447
 
455
448
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
456
449
 
457
- // Step 2: Unmine - clear mined status from metadata
458
- this.#unmineTxs(txsToUnmine);
450
+ // Step 2: Mark ALL un-mined txs with their original mined block number
451
+ // This ensures they get soft-deleted if removed later, and only hard-deleted
452
+ // when their original mined block is finalized
453
+ await this.#deletedPool.markFromPrunedBlock(
454
+ txsToUnmine.map(m => ({
455
+ txHash: m.txHash,
456
+ minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
457
+ })),
458
+ );
459
+
460
+ // Step 3: Unmine - clear mined status from metadata
461
+ for (const meta of txsToUnmine) {
462
+ this.#indices.markAsUnmined(meta);
463
+ }
459
464
 
460
- // Step 3: Filter out protected txs (they'll be handled by prepareForSlot)
461
- const unprotectedTxs = this.#filterUnprotected(txsToUnmine);
465
+ // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
466
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
462
467
 
463
468
  // Step 4: Validate for pending pool
464
- const { valid, invalid } = await this.#validateForPending(unprotectedTxs);
469
+ const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
465
470
 
466
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
471
+ // Step 6: Resolve nullifier conflicts and add winners to pending indices
467
472
  const { toEvict } = this.#applyNullifierConflictResolution(valid);
468
473
 
469
- // Step 6: Delete invalid and evicted txs
474
+ // Step 7: Delete invalid and evicted txs
470
475
  await this.#deleteTxsBatch([...invalid, ...toEvict]);
471
476
 
472
- // Step 7: Run eviction rules for ALL pending txs (not just restored ones)
477
+ this.#log.info(
478
+ `Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
479
+ { txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
480
+ );
481
+
482
+ // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
473
483
  // This handles cases like existing pending txs with invalid fee payer balances
474
484
  await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
475
485
  }
476
486
 
477
487
  async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
478
- // Step 1: Delete failed txs
488
+ // Delete failed txs
479
489
  await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
480
490
 
481
- this.#log.info(`Deleted ${txHashes.length} failed txs`);
491
+ this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
482
492
  }
483
493
 
484
494
  async handleFinalizedBlock(block: BlockHeader): Promise<void> {
485
495
  const blockNumber = block.globalVariables.blockNumber;
486
496
 
487
- // Step 1: Find txs mined at or before finalized block
488
- const txsToFinalize = this.#findTxsMinedAtOrBefore(blockNumber);
489
- if (txsToFinalize.length === 0) {
490
- return;
491
- }
497
+ // Step 1: Find mined txs at or before finalized block
498
+ const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
492
499
 
493
- // Step 2: Collect txs for archiving (before deletion)
500
+ // Step 2: Collect mined txs for archiving (before deletion)
494
501
  const txsToArchive: Tx[] = [];
495
502
  if (this.#archive.isEnabled()) {
496
- for (const txHashStr of txsToFinalize) {
503
+ for (const txHashStr of minedTxsToFinalize) {
497
504
  const buffer = await this.#txsDB.getAsync(txHashStr);
498
505
  if (buffer) {
499
506
  txsToArchive.push(Tx.fromBuffer(buffer));
@@ -501,15 +508,22 @@ export class TxPoolV2Impl {
501
508
  }
502
509
  }
503
510
 
504
- // Step 3: Delete from active pool
505
- await this.#deleteTxsBatch(txsToFinalize);
511
+ // Step 3: Delete mined txs from active pool
512
+ await this.#deleteTxsBatch(minedTxsToFinalize);
506
513
 
507
- // Step 4: Archive
514
+ // Step 4: Finalize soft-deleted txs
515
+ await this.#deletedPool.finalizeBlock(blockNumber);
516
+
517
+ // Step 5: Archive mined txs
508
518
  if (txsToArchive.length > 0) {
509
519
  await this.#archive.archiveTxs(txsToArchive);
510
520
  }
511
521
 
512
- this.#log.info(`Finalized ${txsToFinalize.length} txs from blocks up to ${blockNumber}`);
522
+ if (minedTxsToFinalize.length > 0) {
523
+ this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
524
+ txHashes: minedTxsToFinalize,
525
+ });
526
+ }
513
527
  }
514
528
 
515
529
  // === Query Methods ===
@@ -529,42 +543,47 @@ export class TxPoolV2Impl {
529
543
  }
530
544
 
531
545
  hasTxs(txHashes: TxHash[]): boolean[] {
532
- return txHashes.map(h => this.#metadata.has(h.toString()));
546
+ return txHashes.map(h => {
547
+ const hashStr = h.toString();
548
+ return this.#indices.has(hashStr) || this.#deletedPool.isSoftDeleted(hashStr);
549
+ });
533
550
  }
534
551
 
535
552
  getTxStatus(txHash: TxHash): TxState | undefined {
536
- const meta = this.#metadata.get(txHash.toString());
537
- if (!meta) {
538
- return undefined;
553
+ const txHashStr = txHash.toString();
554
+ const meta = this.#indices.getMetadata(txHashStr);
555
+ if (meta) {
556
+ return this.#indices.getTxState(meta);
557
+ }
558
+ // Check if soft-deleted
559
+ if (this.#deletedPool.isSoftDeleted(txHashStr)) {
560
+ return 'deleted';
539
561
  }
540
- return this.#getTxState(meta);
562
+ return undefined;
541
563
  }
542
564
 
543
565
  getPendingTxHashes(): TxHash[] {
544
- return [...this.#iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
566
+ return [...this.#indices.iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
567
+ }
568
+
569
+ getEligiblePendingTxHashes(): TxHash[] {
570
+ const maxReceivedAt = this.#dateProvider.now() - this.#config.minTxPoolAgeMs;
571
+ return [...this.#indices.iterateEligiblePendingByPriority('desc', maxReceivedAt)].map(hash =>
572
+ TxHash.fromString(hash),
573
+ );
545
574
  }
546
575
 
547
576
  getPendingTxCount(): number {
548
- let count = 0;
549
- for (const hashes of this.#pendingByPriority.values()) {
550
- count += hashes.size;
551
- }
552
- return count;
577
+ return this.#indices.getPendingTxCount();
553
578
  }
554
579
 
555
580
  getMinedTxHashes(): [TxHash, L2BlockId][] {
556
- const result: [TxHash, L2BlockId][] = [];
557
- for (const [txHash, meta] of this.#metadata) {
558
- if (meta.minedL2BlockId !== undefined) {
559
- result.push([TxHash.fromString(txHash), meta.minedL2BlockId]);
560
- }
561
- }
562
- return result;
581
+ return this.#indices.getMinedTxs().map(([hash, blockId]) => [TxHash.fromString(hash), blockId]);
563
582
  }
564
583
 
565
584
  getMinedTxCount(): number {
566
585
  let count = 0;
567
- for (const meta of this.#metadata.values()) {
586
+ for (const [, meta] of this.#indices.iterateMetadata()) {
568
587
  if (meta.minedL2BlockId !== undefined) {
569
588
  count++;
570
589
  }
@@ -573,11 +592,11 @@ export class TxPoolV2Impl {
573
592
  }
574
593
 
575
594
  isEmpty(): boolean {
576
- return this.#metadata.size === 0;
595
+ return this.#indices.isEmpty();
577
596
  }
578
597
 
579
598
  getTxCount(): number {
580
- return this.#metadata.size;
599
+ return this.#indices.getTxCount();
581
600
  }
582
601
 
583
602
  getArchivedTxByHash(txHash: TxHash): Promise<Tx | undefined> {
@@ -585,18 +604,7 @@ export class TxPoolV2Impl {
585
604
  }
586
605
 
587
606
  getLowestPriorityPending(limit: number): TxHash[] {
588
- if (limit <= 0) {
589
- return [];
590
- }
591
-
592
- const result: TxHash[] = [];
593
- for (const hash of this.#iteratePendingByPriority('asc')) {
594
- result.push(TxHash.fromString(hash));
595
- if (result.length >= limit) {
596
- break;
597
- }
598
- }
599
- return result;
607
+ return this.#indices.getLowestPriorityPending(limit).map(h => TxHash.fromString(h));
600
608
  }
601
609
 
602
610
  // === Configuration ===
@@ -609,6 +617,9 @@ export class TxPoolV2Impl {
609
617
  this.#config.archivedTxLimit = config.archivedTxLimit;
610
618
  this.#archive.updateLimit(config.archivedTxLimit);
611
619
  }
620
+ if (config.minTxPoolAgeMs !== undefined) {
621
+ this.#config.minTxPoolAgeMs = config.minTxPoolAgeMs;
622
+ }
612
623
  // Update eviction rules with new config
613
624
  this.#evictionManager.updateConfig(config);
614
625
  }
@@ -617,159 +628,111 @@ export class TxPoolV2Impl {
617
628
 
618
629
  getPoolReadAccess(): PoolReadAccess {
619
630
  return {
620
- getMetadata: (txHash: string) => this.#metadata.get(txHash),
621
- getTxHashByNullifier: (nullifier: string) => this.#nullifierToTxHash.get(nullifier),
622
- getTxHashesByFeePayer: (feePayer: string) => this.#feePayerToTxHashes.get(feePayer),
623
- getPendingTxCount: () => this.getPendingTxCount(),
631
+ getMetadata: (txHash: string) => this.#indices.getMetadata(txHash),
632
+ getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
633
+ getTxHashesByFeePayer: (feePayer: string) => this.#indices.getTxHashesByFeePayer(feePayer),
634
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
624
635
  };
625
636
  }
626
637
 
627
638
  // === Metrics ===
628
639
 
629
640
  countTxs(): { pending: number; protected: number; mined: number } {
630
- let pending = 0;
631
- let protected_ = 0;
632
- let mined = 0;
633
-
634
- for (const meta of this.#metadata.values()) {
635
- const state = this.#getTxState(meta);
636
- if (state === 'pending') {
637
- pending++;
638
- } else if (state === 'protected') {
639
- protected_++;
640
- } else if (state === 'mined') {
641
- mined++;
642
- }
643
- }
644
-
645
- return { pending, protected: protected_, mined };
641
+ return this.#indices.countTxs();
646
642
  }
647
643
 
648
644
  // ============================================================================
649
- // PRIVATE QUERY IMPLEMENTATIONS
645
+ // PRIVATE HELPERS - Transaction Management
650
646
  // ============================================================================
651
647
 
652
648
  /**
653
- * Derives the transaction state from its metadata and protection status.
654
- * A transaction is:
655
- * - 'mined' if it has a minedL2BlockId
656
- * - 'protected' if it's in the protectedTransactions map (but not mined)
657
- * - 'pending' otherwise
649
+ * Adds a new transaction to the pool with the specified state.
650
+ * Emits onTxsAdded callback immediately after DB write.
658
651
  */
659
- #getTxState(meta: TxMetaData): TxState {
660
- if (meta.minedL2BlockId !== undefined) {
661
- return 'mined';
662
- } else if (this.#protectedTransactions.has(meta.txHash)) {
663
- return 'protected';
664
- } else {
665
- return 'pending';
666
- }
667
- }
652
+ async #addTx(
653
+ tx: Tx,
654
+ state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
655
+ opts: { source?: string } = {},
656
+ ): Promise<TxMetaData> {
657
+ const txHashStr = tx.getTxHash().toString();
658
+ const meta = await buildTxMetaData(tx);
659
+ meta.receivedAt = this.#dateProvider.now();
668
660
 
669
- /**
670
- * Iterates pending transaction hashes in priority order.
671
- * @param order - 'desc' for highest priority first, 'asc' for lowest priority first
672
- */
673
- *#iteratePendingByPriority(order: 'asc' | 'desc'): Generator<string> {
674
- // Use shared comparators, negating for descending order
675
- const feeCompareFn =
676
- order === 'desc' ? (a: bigint, b: bigint) => compareFee(b, a) : (a: bigint, b: bigint) => compareFee(a, b);
677
- const hashCompareFn =
678
- order === 'desc' ? (a: string, b: string) => compareTxHash(b, a) : (a: string, b: string) => compareTxHash(a, b);
679
-
680
- const sortedFees = [...this.#pendingByPriority.keys()].sort(feeCompareFn);
681
-
682
- for (const fee of sortedFees) {
683
- const hashesAtFee = this.#pendingByPriority.get(fee)!;
684
- const sortedHashes = [...hashesAtFee].sort(hashCompareFn);
685
- for (const hash of sortedHashes) {
686
- yield hash;
687
- }
688
- }
689
- }
661
+ await this.#txsDB.set(txHashStr, tx.toBuffer());
662
+ await this.#deletedPool.clearSoftDeleted(txHashStr);
663
+ this.#callbacks.onTxsAdded([tx], opts);
690
664
 
691
- // ============================================================================
692
- // HELPER FUNCTIONS - Pipeline Step Functions
693
- // ============================================================================
665
+ if (state === 'pending') {
666
+ this.#indices.addPending(meta);
667
+ } else if ('protected' in state) {
668
+ this.#indices.addProtected(meta, state.protected);
669
+ } else {
670
+ meta.minedL2BlockId = state.mined;
671
+ this.#indices.addMined(meta);
672
+ }
694
673
 
695
- // --- Finding & Filtering Steps ---
674
+ const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
675
+ this.#log.verbose(`Added ${stateStr} tx ${txHashStr}`, {
676
+ eventName: 'tx-added-to-pool',
677
+ state: stateStr,
678
+ });
696
679
 
697
- /** Finds all transactions mined in blocks after the given block number */
698
- #findTxsMinedAfter(blockNumber: number): TxMetaData[] {
699
- const result: TxMetaData[] = [];
700
- for (const meta of this.#metadata.values()) {
701
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number > blockNumber) {
702
- result.push(meta);
703
- }
704
- }
705
- return result;
680
+ return meta;
706
681
  }
707
682
 
708
- /** Finds tx hashes mined at or before the given block number */
709
- #findTxsMinedAtOrBefore(blockNumber: number): string[] {
710
- const result: string[] = [];
711
- for (const [txHashStr, meta] of this.#metadata) {
712
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number <= blockNumber) {
713
- result.push(txHashStr);
714
- }
715
- }
716
- return result;
683
+ /**
684
+ * Deletes a transaction from both indices and DB.
685
+ * Emits onTxsRemoved callback immediately after DB delete.
686
+ */
687
+ /**
688
+ * Deletes a transaction from the pool.
689
+ * Delegates to DeletedPool which decides soft vs hard delete based on whether
690
+ * the tx is from a pruned block.
691
+ */
692
+ async #deleteTx(txHashStr: string): Promise<void> {
693
+ this.#indices.remove(txHashStr);
694
+ this.#callbacks.onTxsRemoved([txHashStr]);
695
+ await this.#deletedPool.deleteTx(txHashStr);
717
696
  }
718
697
 
719
- /** Finds protected tx hashes from slots earlier than the given slot number */
720
- #findExpiredProtectedTxs(slotNumber: SlotNumber): string[] {
721
- const result: string[] = [];
722
- for (const [txHashStr, protectedSlot] of this.#protectedTransactions) {
723
- if (protectedSlot < slotNumber) {
724
- result.push(txHashStr);
725
- }
698
+ /** Deletes a batch of transactions, emitting callbacks individually for each. */
699
+ async #deleteTxsBatch(txHashes: string[]): Promise<void> {
700
+ for (const txHashStr of txHashes) {
701
+ await this.#deleteTx(txHashStr);
726
702
  }
727
- return result;
728
703
  }
729
704
 
730
- /** Filters out transactions that are currently protected */
731
- #filterUnprotected(txs: TxMetaData[]): TxMetaData[] {
732
- return txs.filter(meta => !this.#protectedTransactions.has(meta.txHash));
733
- }
705
+ // ============================================================================
706
+ // PRIVATE HELPERS - Validation & Conflict Resolution
707
+ // ============================================================================
734
708
 
735
- /** Filters to transactions that have metadata and are not mined */
736
- #filterRestorable(txHashes: string[]): TxMetaData[] {
737
- const result: TxMetaData[] = [];
738
- for (const txHashStr of txHashes) {
739
- const meta = this.#metadata.get(txHashStr);
740
- if (meta && meta.minedL2BlockId === undefined) {
741
- result.push(meta);
742
- }
709
+ /** Validates transaction metadata, returning true if valid */
710
+ async #validateMeta(meta: TxMetaData, validator?: TxValidator<TxMetaData>, context?: string): Promise<boolean> {
711
+ const txValidator = validator ?? (await this.#createTxValidator());
712
+ const result = await txValidator.validateTx(meta);
713
+ if (result.result !== 'valid') {
714
+ const contextStr = context ? ` ${context}` : '';
715
+ this.#log.info(`Tx ${meta.txHash}${contextStr} failed validation: ${result.reason?.join(', ')}`);
716
+ return false;
743
717
  }
744
- return result;
718
+ return true;
745
719
  }
746
720
 
747
- // --- Validation & Conflict Resolution Steps ---
748
-
749
- /** Validates transactions for pending pool, returning valid and invalid groups */
750
- async #validateForPending(txs: TxMetaData[]): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
721
+ /** Validates metadata directly */
722
+ async #revalidateMetadata(
723
+ metas: TxMetaData[],
724
+ context?: string,
725
+ ): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
751
726
  const valid: TxMetaData[] = [];
752
727
  const invalid: string[] = [];
753
-
754
- for (const meta of txs) {
755
- const buffer = await this.#txsDB.getAsync(meta.txHash);
756
- if (!buffer) {
757
- this.#log.warn(`Tx ${meta.txHash} not found in DB during validation`);
758
- invalid.push(meta.txHash);
759
- continue;
760
- }
761
-
762
- const tx = Tx.fromBuffer(buffer);
763
- const result = await this.#pendingTxValidator.validateTx(tx);
764
-
765
- if (result.result === 'valid') {
728
+ const validator = await this.#createTxValidator();
729
+ for (const meta of metas) {
730
+ if (await this.#validateMeta(meta, validator, context)) {
766
731
  valid.push(meta);
767
732
  } else {
768
- this.#log.info(`Tx ${meta.txHash} failed validation: ${result.reason?.join(', ')}`);
769
733
  invalid.push(meta.txHash);
770
734
  }
771
735
  }
772
-
773
736
  return { valid, invalid };
774
737
  }
775
738
 
@@ -785,8 +748,8 @@ export class TxPoolV2Impl {
785
748
  for (const meta of txs) {
786
749
  const conflict = checkNullifierConflict(
787
750
  meta,
788
- nullifier => this.#nullifierToTxHash.get(nullifier),
789
- txHash => this.#metadata.get(txHash),
751
+ nullifier => this.#indices.getTxHashByNullifier(nullifier),
752
+ txHash => this.#indices.getMetadata(txHash),
790
753
  );
791
754
  if (conflict.shouldIgnore) {
792
755
  // Lower priority than existing - don't add, mark for deletion
@@ -796,13 +759,13 @@ export class TxPoolV2Impl {
796
759
  toEvict.push(...conflict.txHashesToEvict);
797
760
  // Remove evicted from indices immediately for subsequent checks
798
761
  for (const evictHash of conflict.txHashesToEvict) {
799
- const evictMeta = this.#metadata.get(evictHash);
762
+ const evictMeta = this.#indices.getMetadata(evictHash);
800
763
  if (evictMeta) {
801
- this.#removeFromPendingIndices(evictMeta);
764
+ this.#indices.removeFromPendingIndices(evictMeta);
802
765
  }
803
766
  }
804
767
  // Add to pending indices immediately so subsequent txs in the batch see this tx
805
- this.#addToPendingIndices(meta);
768
+ this.#indices.addToPendingIndices(meta);
806
769
  added.push(meta);
807
770
  }
808
771
  }
@@ -810,43 +773,10 @@ export class TxPoolV2Impl {
810
773
  return { added, toEvict };
811
774
  }
812
775
 
813
- // --- State Transition Steps ---
814
-
815
- /** Clears the mined status from transactions, returning them for further processing */
816
- #unmineTxs(txs: TxMetaData[]): TxMetaData[] {
817
- for (const meta of txs) {
818
- meta.minedL2BlockId = undefined;
819
- }
820
- return txs;
821
- }
822
-
823
- /** Removes protection from tx hashes and clears them from the protected map */
824
- #clearProtection(txHashes: string[]): void {
825
- for (const txHashStr of txHashes) {
826
- this.#protectedTransactions.delete(txHashStr);
827
- }
828
- }
829
-
830
- // --- Batch Operation Steps ---
831
-
832
- /** Deletes a batch of transactions permanently */
833
- async #deleteTxsBatch(txHashes: string[]): Promise<void> {
834
- if (txHashes.length === 0) {
835
- return;
836
- }
837
-
838
- await this.#store.transactionAsync(async () => {
839
- for (const txHashStr of txHashes) {
840
- await this.#deleteTx(txHashStr);
841
- }
842
- });
843
-
844
- this.#callbacks.onTxsRemoved(txHashes);
845
- }
846
-
847
- // --- Block & Tx Info Steps ---
776
+ // ============================================================================
777
+ // PRIVATE HELPERS - Block & Hydration
778
+ // ============================================================================
848
779
 
849
- /** Builds a block ID from a block header */
850
780
  async #buildBlockId(block: BlockHeader): Promise<L2BlockId> {
851
781
  return {
852
782
  number: block.globalVariables.blockNumber,
@@ -866,50 +796,6 @@ export class TxPoolV2Impl {
866
796
  };
867
797
  }
868
798
 
869
- /** Marks a batch of transactions as mined */
870
- #markTxsAsMined(metas: TxMetaData[], blockId: L2BlockId): void {
871
- for (const meta of metas) {
872
- this.#markAsMined(meta, blockId);
873
- }
874
- }
875
-
876
- // --- Add Transaction Steps ---
877
-
878
- /** Persists a transaction to the database */
879
- async #persistTx(txHashStr: string, tx: Tx): Promise<void> {
880
- await this.#txsDB.set(txHashStr, tx.toBuffer());
881
- }
882
-
883
- /** Adds a new transaction as protected, returning its metadata */
884
- async #addNewProtectedTx(tx: Tx, slotNumber: SlotNumber): Promise<TxMetaData> {
885
- const txHashStr = tx.getTxHash().toString();
886
- const meta = await buildTxMetaData(tx);
887
-
888
- this.#protectedTransactions.set(txHashStr, slotNumber);
889
- await this.#persistTx(txHashStr, tx);
890
- this.#metadata.set(txHashStr, meta);
891
- // Don't add to pending indices since it's protected
892
-
893
- this.#log.verbose(`Added protected tx ${txHashStr} for slot ${slotNumber}`);
894
- return meta;
895
- }
896
-
897
- /** Adds a new transaction as mined, returning its metadata */
898
- async #addNewMinedTx(tx: Tx, blockId: L2BlockId): Promise<TxMetaData> {
899
- const txHashStr = tx.getTxHash().toString();
900
- const meta = await buildTxMetaData(tx);
901
- meta.minedL2BlockId = blockId;
902
-
903
- await this.#persistTx(txHashStr, tx);
904
- this.#metadata.set(txHashStr, meta);
905
- // Don't add to pending indices since it's mined
906
-
907
- this.#log.verbose(`Added mined tx ${txHashStr} from block ${blockId.number}`);
908
- return meta;
909
- }
910
-
911
- // --- Hydration Steps ---
912
-
913
799
  /** Loads all transactions from the database, returning loaded txs and deserialization errors */
914
800
  async #loadAllTxsFromDb(): Promise<{
915
801
  loaded: { tx: Tx; meta: TxMetaData }[];
@@ -919,6 +805,11 @@ export class TxPoolV2Impl {
919
805
  const errors: string[] = [];
920
806
 
921
807
  for await (const [txHashStr, buffer] of this.#txsDB.entriesAsync()) {
808
+ // Skip soft-deleted transactions - they stay in DB but not in indices
809
+ if (this.#deletedPool.isSoftDeleted(txHashStr)) {
810
+ continue;
811
+ }
812
+
922
813
  try {
923
814
  const tx = Tx.fromBuffer(buffer);
924
815
  const meta = await buildTxMetaData(tx);
@@ -949,50 +840,6 @@ export class TxPoolV2Impl {
949
840
  }
950
841
  }
951
842
 
952
- /** Partitions transactions by mined status */
953
- #partitionByMinedStatus(txs: { tx: Tx; meta: TxMetaData }[]): {
954
- mined: TxMetaData[];
955
- nonMined: { tx: Tx; meta: TxMetaData }[];
956
- } {
957
- const mined: TxMetaData[] = [];
958
- const nonMined: { tx: Tx; meta: TxMetaData }[] = [];
959
-
960
- for (const entry of txs) {
961
- if (entry.meta.minedL2BlockId !== undefined) {
962
- mined.push(entry.meta);
963
- } else {
964
- nonMined.push(entry);
965
- }
966
- }
967
-
968
- return { mined, nonMined };
969
- }
970
-
971
- /** Validates non-mined transactions, returning valid metadata and invalid hashes */
972
- async #validateNonMinedTxs(txs: { tx: Tx; meta: TxMetaData }[]): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
973
- const valid: TxMetaData[] = [];
974
- const invalid: string[] = [];
975
-
976
- for (const { tx, meta } of txs) {
977
- const result = await this.#pendingTxValidator.validateTx(tx);
978
- if (result.result === 'valid') {
979
- valid.push(meta);
980
- } else {
981
- this.#log.info(`Removing invalid tx ${meta.txHash} on startup: ${result.reason?.join(', ')}`);
982
- invalid.push(meta.txHash);
983
- }
984
- }
985
-
986
- return { valid, invalid };
987
- }
988
-
989
- /** Populates metadata index for mined transactions */
990
- #populateMinedIndices(metas: TxMetaData[]): void {
991
- for (const meta of metas) {
992
- this.#metadata.set(meta.txHash, meta);
993
- }
994
- }
995
-
996
843
  /**
997
844
  * Rebuilds the pending pool by processing each tx through pre-add rules.
998
845
  * Starts with an empty pending pool and adds txs one by one, resolving conflicts.
@@ -1016,18 +863,18 @@ export class TxPoolV2Impl {
1016
863
 
1017
864
  // Evict any conflicting txs identified by pre-add rules
1018
865
  for (const evictHashStr of preAddResult.txHashesToEvict) {
1019
- const evictMeta = this.#metadata.get(evictHashStr);
866
+ const evictMeta = this.#indices.getMetadata(evictHashStr);
1020
867
  if (evictMeta) {
1021
- this.#removeFromPendingIndices(evictMeta);
1022
- this.#metadata.delete(evictHashStr);
868
+ this.#indices.removeFromPendingIndices(evictMeta);
869
+ this.#indices.remove(evictHashStr);
1023
870
  rejected.push(evictHashStr);
1024
871
  accepted.delete(evictHashStr);
1025
872
  this.#log.debug(`Evicted tx ${evictHashStr} during rebuild due to conflict with ${meta.txHash}`);
1026
873
  }
1027
874
  }
1028
875
 
1029
- // Add to metadata and pending indices
1030
- this.#addToIndices(meta);
876
+ // Add to indices
877
+ this.#indices.addPending(meta);
1031
878
  accepted.add(meta.txHash);
1032
879
  }
1033
880
 
@@ -1035,207 +882,32 @@ export class TxPoolV2Impl {
1035
882
  return { accepted: [...accepted], rejected };
1036
883
  }
1037
884
 
1038
- // --- Add Pending Tx Steps ---
1039
-
1040
- /** Checks if a tx is a duplicate (already in pool) */
1041
- #isDuplicateTx(txHashStr: string): boolean {
1042
- return this.#metadata.has(txHashStr);
1043
- }
1044
-
1045
- /** Adds a new pending tx to the pool, returning its metadata */
1046
- async #addNewPendingTx(tx: Tx): Promise<TxMetaData> {
1047
- const txHashStr = tx.getTxHash().toString();
1048
- const meta = await buildTxMetaData(tx);
1049
-
1050
- await this.#persistTx(txHashStr, tx);
1051
- this.#addToIndices(meta);
1052
-
1053
- this.#log.verbose(`Added tx ${txHashStr} to pool`, {
1054
- eventName: 'tx-added-to-pool',
1055
- state: this.#getTxState(meta),
1056
- });
1057
-
1058
- return meta;
1059
- }
1060
-
1061
- // ============================================================================
1062
- // HELPER FUNCTIONS - Index Management
1063
- // ============================================================================
1064
-
1065
- #addToIndices(meta: TxMetaData): void {
1066
- this.#metadata.set(meta.txHash, meta);
1067
-
1068
- if (this.#getTxState(meta) === 'pending') {
1069
- this.#addToPendingIndices(meta);
1070
- }
1071
- // Protected and mined txs don't go into pending indices
1072
- }
1073
-
1074
- #addToPendingIndices(meta: TxMetaData): void {
1075
- // Add to nullifier index
1076
- for (const nullifier of meta.nullifiers) {
1077
- this.#nullifierToTxHash.set(nullifier, meta.txHash);
1078
- }
1079
-
1080
- // Add to fee payer index
1081
- let feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
1082
- if (!feePayerSet) {
1083
- feePayerSet = new Set();
1084
- this.#feePayerToTxHashes.set(meta.feePayer, feePayerSet);
1085
- }
1086
- feePayerSet.add(meta.txHash);
1087
-
1088
- // Add to priority bucket
1089
- let prioritySet = this.#pendingByPriority.get(meta.priorityFee);
1090
- if (!prioritySet) {
1091
- prioritySet = new Set();
1092
- this.#pendingByPriority.set(meta.priorityFee, prioritySet);
1093
- }
1094
- prioritySet.add(meta.txHash);
1095
- }
1096
-
1097
- #removeFromPendingIndices(meta: TxMetaData): void {
1098
- // Remove from nullifier index
1099
- for (const nullifier of meta.nullifiers) {
1100
- this.#nullifierToTxHash.delete(nullifier);
1101
- }
1102
-
1103
- // Remove from fee payer index
1104
- const feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
1105
- if (feePayerSet) {
1106
- feePayerSet.delete(meta.txHash);
1107
- if (feePayerSet.size === 0) {
1108
- this.#feePayerToTxHashes.delete(meta.feePayer);
1109
- }
1110
- }
1111
-
1112
- // Remove from priority map
1113
- const hashSet = this.#pendingByPriority.get(meta.priorityFee);
1114
- if (hashSet) {
1115
- hashSet.delete(meta.txHash);
1116
- if (hashSet.size === 0) {
1117
- this.#pendingByPriority.delete(meta.priorityFee);
1118
- }
1119
- }
1120
- }
1121
-
1122
- #updateProtection(txHashStr: string, slotNumber: SlotNumber): void {
1123
- const currentSlot = this.#protectedTransactions.get(txHashStr);
1124
-
1125
- // Only update if not already protected at an equal or later slot
1126
- if (currentSlot !== undefined && currentSlot >= slotNumber) {
1127
- return;
1128
- }
1129
-
1130
- // Remove from pending indices if transitioning from pending to protected
1131
- if (currentSlot === undefined) {
1132
- const meta = this.#metadata.get(txHashStr);
1133
- if (meta) {
1134
- this.#removeFromPendingIndices(meta);
1135
- }
1136
- }
1137
-
1138
- this.#protectedTransactions.set(txHashStr, slotNumber);
1139
- }
1140
-
1141
- #markAsMined(meta: TxMetaData, blockId: L2BlockId): void {
1142
- meta.minedL2BlockId = blockId;
1143
- // Safe to call unconditionally - removeFromPendingIndices is idempotent
1144
- this.#removeFromPendingIndices(meta);
1145
- }
1146
-
1147
- async #deleteTx(txHashStr: string): Promise<void> {
1148
- const meta = this.#metadata.get(txHashStr);
1149
- if (!meta) {
1150
- return;
1151
- }
1152
-
1153
- // Remove from all indices
1154
- this.#metadata.delete(txHashStr);
1155
- this.#protectedTransactions.delete(txHashStr);
1156
- this.#removeFromPendingIndices(meta);
1157
-
1158
- // Remove from persistence
1159
- await this.#txsDB.delete(txHashStr);
1160
- }
1161
-
1162
885
  // ============================================================================
1163
- // HELPER FUNCTIONS - Adapters
886
+ // PRIVATE HELPERS - Pool Access Adapters
1164
887
  // ============================================================================
1165
888
 
1166
- /** Gets all pending transactions for a given fee payer. */
1167
- #getFeePayerPendingTxs(feePayer: string): TxMetaData[] {
1168
- const txHashes = this.#feePayerToTxHashes.get(feePayer);
1169
- if (!txHashes) {
1170
- return [];
1171
- }
1172
- const result: TxMetaData[] = [];
1173
- for (const txHashStr of txHashes) {
1174
- const meta = this.#metadata.get(txHashStr);
1175
- if (meta && this.#getTxState(meta) === 'pending') {
1176
- result.push(meta);
1177
- }
1178
- }
1179
- return result;
1180
- }
1181
-
1182
- /**
1183
- * Creates a PoolOperations adapter for use with the eviction manager.
1184
- */
1185
889
  #createPoolOperations(): PoolOperations {
1186
890
  return {
1187
- getPendingTxs: (): TxMetaData[] => {
1188
- const result: TxMetaData[] = [];
1189
- for (const hashSet of this.#pendingByPriority.values()) {
1190
- for (const txHashStr of hashSet) {
1191
- const meta = this.#metadata.get(txHashStr);
1192
- if (meta) {
1193
- result.push(meta);
1194
- }
1195
- }
1196
- }
1197
- return result;
1198
- },
1199
- getPendingFeePayers: (): string[] => {
1200
- return Array.from(this.#feePayerToTxHashes.keys());
1201
- },
1202
- getFeePayerPendingTxs: (feePayer: string): TxMetaData[] => {
1203
- return this.#getFeePayerPendingTxs(feePayer);
1204
- },
1205
- getPendingTxCount: (): number => {
1206
- return this.getPendingTxCount();
1207
- },
1208
- getLowestPriorityPending: (limit: number): string[] => {
1209
- return this.getLowestPriorityPending(limit).map(h => h.toString());
1210
- },
1211
- deleteTxs: async (txHashes: string[]): Promise<void> => {
1212
- await this.#store.transactionAsync(async () => {
1213
- for (const txHashStr of txHashes) {
1214
- await this.#deleteTx(txHashStr);
1215
- }
1216
- });
1217
- this.#callbacks.onTxsRemoved(txHashes);
1218
- },
891
+ getPendingTxs: () => this.#indices.getPendingTxs(),
892
+ getPendingFeePayers: () => this.#indices.getPendingFeePayers(),
893
+ getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
894
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
895
+ getLowestPriorityPending: (limit: number) => this.#indices.getLowestPriorityPending(limit),
896
+ deleteTxs: (txHashes: string[]) => this.#deleteTxsBatch(txHashes),
1219
897
  };
1220
898
  }
1221
899
 
1222
- /**
1223
- * Creates a PreAddPoolAccess adapter for use with pre-add eviction rules.
1224
- * All methods work with strings and TxMetaData for efficiency.
1225
- */
1226
900
  #createPreAddPoolAccess(): PreAddPoolAccess {
1227
901
  return {
1228
- getMetadata: (txHashStr: string): TxMetaData | undefined => {
1229
- const meta = this.#metadata.get(txHashStr);
1230
- if (!meta || this.#getTxState(meta) !== 'pending') {
902
+ getMetadata: (txHashStr: string) => {
903
+ const meta = this.#indices.getMetadata(txHashStr);
904
+ if (!meta || this.#indices.getTxState(meta) !== 'pending') {
1231
905
  return undefined;
1232
906
  }
1233
907
  return meta;
1234
908
  },
1235
- getTxHashByNullifier: (nullifier: string): string | undefined => {
1236
- return this.#nullifierToTxHash.get(nullifier);
1237
- },
1238
- getFeePayerBalance: async (feePayer: string): Promise<bigint> => {
909
+ getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
910
+ getFeePayerBalance: async (feePayer: string) => {
1239
911
  const db = this.#worldStateSynchronizer.getCommitted();
1240
912
  const publicStateSource = new DatabasePublicStateSource(db);
1241
913
  const balance = await publicStateSource.storageRead(
@@ -1244,22 +916,9 @@ export class TxPoolV2Impl {
1244
916
  );
1245
917
  return balance.toBigInt();
1246
918
  },
1247
- getFeePayerPendingTxs: (feePayer: string): TxMetaData[] => {
1248
- return this.#getFeePayerPendingTxs(feePayer);
1249
- },
1250
- getPendingTxCount: (): number => {
1251
- return this.getPendingTxCount();
1252
- },
1253
- getLowestPriorityPendingTx: (): TxMetaData | undefined => {
1254
- // Iterate in ascending order to find the lowest priority
1255
- for (const txHashStr of this.#iteratePendingByPriority('asc')) {
1256
- const meta = this.#metadata.get(txHashStr);
1257
- if (meta) {
1258
- return meta;
1259
- }
1260
- }
1261
- return undefined;
1262
- },
919
+ getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
920
+ getPendingTxCount: () => this.#indices.getPendingTxCount(),
921
+ getLowestPriorityPendingTx: () => this.#indices.getLowestPriorityPendingTx(),
1263
922
  };
1264
923
  }
1265
924
  }