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

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 (216) hide show
  1. package/dest/client/factory.d.ts +5 -5
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +40 -9
  4. package/dest/client/interface.d.ts +33 -15
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +33 -36
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +104 -139
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
  10. package/dest/config.d.ts +14 -5
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +11 -1
  13. package/dest/index.d.ts +2 -1
  14. package/dest/index.d.ts.map +1 -1
  15. package/dest/index.js +1 -0
  16. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +102 -88
  17. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/attestation_pool.js +436 -3
  19. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
  20. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
  21. package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +353 -87
  22. package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
  23. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
  24. package/dest/mem_pools/attestation_pool/index.js +1 -2
  25. package/dest/mem_pools/index.d.ts +3 -2
  26. package/dest/mem_pools/index.d.ts.map +1 -1
  27. package/dest/mem_pools/index.js +1 -1
  28. package/dest/mem_pools/interface.d.ts +5 -5
  29. package/dest/mem_pools/interface.d.ts.map +1 -1
  30. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +87 -0
  31. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -0
  32. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +180 -0
  33. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -1
  34. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/index.js +1 -0
  36. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +7 -3
  37. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  38. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +25 -3
  39. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +38 -5
  41. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +99 -0
  42. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -0
  43. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +332 -0
  44. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +1 -1
  45. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  46. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -0
  47. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  48. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  49. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +226 -516
  50. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
  51. package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
  52. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +3 -3
  53. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  54. package/dest/msg_validators/tx_validator/block_header_validator.d.ts +16 -3
  55. package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
  56. package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
  57. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +13 -3
  58. package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
  59. package/dest/msg_validators/tx_validator/double_spend_validator.js +4 -4
  60. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +20 -4
  61. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  62. package/dest/msg_validators/tx_validator/timestamp_validator.js +2 -2
  63. package/dest/services/dummy_service.d.ts +10 -2
  64. package/dest/services/dummy_service.d.ts.map +1 -1
  65. package/dest/services/dummy_service.js +6 -0
  66. package/dest/services/encoding.d.ts +2 -2
  67. package/dest/services/encoding.d.ts.map +1 -1
  68. package/dest/services/encoding.js +2 -2
  69. package/dest/services/gossipsub/index.d.ts +3 -0
  70. package/dest/services/gossipsub/index.d.ts.map +1 -0
  71. package/dest/services/gossipsub/index.js +2 -0
  72. package/dest/services/gossipsub/scoring.d.ts +21 -3
  73. package/dest/services/gossipsub/scoring.d.ts.map +1 -1
  74. package/dest/services/gossipsub/scoring.js +24 -7
  75. package/dest/services/gossipsub/topic_score_params.d.ts +161 -0
  76. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -0
  77. package/dest/services/gossipsub/topic_score_params.js +324 -0
  78. package/dest/services/libp2p/libp2p_service.d.ts +84 -35
  79. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  80. package/dest/services/libp2p/libp2p_service.js +368 -273
  81. package/dest/services/peer-manager/peer_scoring.d.ts +1 -1
  82. package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
  83. package/dest/services/peer-manager/peer_scoring.js +25 -2
  84. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -4
  85. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  86. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +8 -8
  87. package/dest/services/reqresp/interface.d.ts +10 -1
  88. package/dest/services/reqresp/interface.d.ts.map +1 -1
  89. package/dest/services/reqresp/interface.js +15 -1
  90. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +7 -5
  91. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
  92. package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +16 -11
  93. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +21 -10
  94. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
  95. package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +27 -11
  96. package/dest/services/reqresp/protocols/tx.d.ts +7 -1
  97. package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
  98. package/dest/services/reqresp/protocols/tx.js +20 -0
  99. package/dest/services/reqresp/reqresp.d.ts +1 -1
  100. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  101. package/dest/services/reqresp/reqresp.js +11 -4
  102. package/dest/services/service.d.ts +35 -1
  103. package/dest/services/service.d.ts.map +1 -1
  104. package/dest/services/tx_collection/config.d.ts +10 -4
  105. package/dest/services/tx_collection/config.d.ts.map +1 -1
  106. package/dest/services/tx_collection/config.js +19 -3
  107. package/dest/services/tx_collection/fast_tx_collection.d.ts +6 -5
  108. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  109. package/dest/services/tx_collection/fast_tx_collection.js +27 -17
  110. package/dest/services/tx_collection/file_store_tx_collection.d.ts +44 -0
  111. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -0
  112. package/dest/services/tx_collection/file_store_tx_collection.js +118 -0
  113. package/dest/services/tx_collection/file_store_tx_source.d.ts +27 -0
  114. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -0
  115. package/dest/services/tx_collection/file_store_tx_source.js +57 -0
  116. package/dest/services/tx_collection/index.d.ts +3 -2
  117. package/dest/services/tx_collection/index.d.ts.map +1 -1
  118. package/dest/services/tx_collection/index.js +1 -0
  119. package/dest/services/tx_collection/proposal_tx_collector.d.ts +12 -12
  120. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  121. package/dest/services/tx_collection/proposal_tx_collector.js +4 -5
  122. package/dest/services/tx_collection/slow_tx_collection.d.ts +3 -1
  123. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  124. package/dest/services/tx_collection/slow_tx_collection.js +48 -19
  125. package/dest/services/tx_collection/tx_collection.d.ts +17 -7
  126. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  127. package/dest/services/tx_collection/tx_collection.js +58 -2
  128. package/dest/services/tx_collection/tx_collection_sink.d.ts +15 -6
  129. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  130. package/dest/services/tx_collection/tx_collection_sink.js +13 -7
  131. package/dest/services/tx_file_store/config.d.ts +1 -3
  132. package/dest/services/tx_file_store/config.d.ts.map +1 -1
  133. package/dest/services/tx_file_store/config.js +0 -4
  134. package/dest/services/tx_file_store/tx_file_store.d.ts +3 -3
  135. package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
  136. package/dest/services/tx_provider.d.ts +3 -3
  137. package/dest/services/tx_provider.d.ts.map +1 -1
  138. package/dest/services/tx_provider.js +5 -4
  139. package/dest/test-helpers/make-test-p2p-clients.d.ts +3 -3
  140. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  141. package/dest/test-helpers/mock-pubsub.d.ts +27 -1
  142. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  143. package/dest/test-helpers/mock-pubsub.js +97 -2
  144. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  145. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  146. package/dest/test-helpers/reqresp-nodes.js +2 -1
  147. package/dest/test-helpers/testbench-utils.d.ts +38 -38
  148. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  149. package/dest/test-helpers/testbench-utils.js +124 -61
  150. package/dest/testbench/p2p_client_testbench_worker.js +2 -2
  151. package/package.json +14 -14
  152. package/src/client/factory.ts +68 -12
  153. package/src/client/interface.ts +39 -14
  154. package/src/client/p2p_client.ts +139 -162
  155. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  156. package/src/config.ts +26 -2
  157. package/src/index.ts +1 -0
  158. package/src/mem_pools/attestation_pool/attestation_pool.ts +488 -91
  159. package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +442 -102
  160. package/src/mem_pools/attestation_pool/index.ts +9 -2
  161. package/src/mem_pools/index.ts +4 -1
  162. package/src/mem_pools/interface.ts +4 -4
  163. package/src/mem_pools/tx_pool_v2/README.md +87 -16
  164. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +234 -0
  165. package/src/mem_pools/tx_pool_v2/index.ts +1 -0
  166. package/src/mem_pools/tx_pool_v2/interfaces.ts +4 -2
  167. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +53 -6
  168. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +417 -0
  169. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +3 -0
  170. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +234 -604
  171. package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +2 -2
  172. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +2 -2
  173. package/src/msg_validators/tx_validator/block_header_validator.ts +15 -3
  174. package/src/msg_validators/tx_validator/double_spend_validator.ts +11 -6
  175. package/src/msg_validators/tx_validator/timestamp_validator.ts +19 -14
  176. package/src/services/dummy_service.ts +12 -0
  177. package/src/services/encoding.ts +2 -2
  178. package/src/services/gossipsub/README.md +626 -0
  179. package/src/services/gossipsub/index.ts +2 -0
  180. package/src/services/gossipsub/scoring.ts +29 -5
  181. package/src/services/gossipsub/topic_score_params.ts +451 -0
  182. package/src/services/libp2p/libp2p_service.ts +370 -275
  183. package/src/services/peer-manager/peer_scoring.ts +25 -0
  184. package/src/services/reqresp/batch-tx-requester/README.md +7 -7
  185. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +11 -11
  186. package/src/services/reqresp/interface.ts +26 -1
  187. package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +23 -14
  188. package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +38 -15
  189. package/src/services/reqresp/protocols/tx.ts +22 -0
  190. package/src/services/reqresp/reqresp.ts +13 -3
  191. package/src/services/service.ts +40 -0
  192. package/src/services/tx_collection/config.ts +32 -6
  193. package/src/services/tx_collection/fast_tx_collection.ts +28 -26
  194. package/src/services/tx_collection/file_store_tx_collection.ts +152 -0
  195. package/src/services/tx_collection/file_store_tx_source.ts +70 -0
  196. package/src/services/tx_collection/index.ts +2 -1
  197. package/src/services/tx_collection/proposal_tx_collector.ts +12 -14
  198. package/src/services/tx_collection/slow_tx_collection.ts +55 -26
  199. package/src/services/tx_collection/tx_collection.ts +78 -12
  200. package/src/services/tx_collection/tx_collection_sink.ts +17 -7
  201. package/src/services/tx_file_store/config.ts +0 -6
  202. package/src/services/tx_file_store/tx_file_store.ts +4 -4
  203. package/src/services/tx_provider.ts +8 -7
  204. package/src/test-helpers/make-test-p2p-clients.ts +3 -3
  205. package/src/test-helpers/mock-pubsub.ts +133 -3
  206. package/src/test-helpers/reqresp-nodes.ts +2 -1
  207. package/src/test-helpers/testbench-utils.ts +122 -74
  208. package/src/testbench/p2p_client_testbench_worker.ts +2 -2
  209. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
  210. package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
  211. package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
  212. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
  213. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  214. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
  215. package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
  216. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
@@ -1,12 +1,15 @@
1
+ import { BlockNumber } from '@aztec/foundation/branded-types';
1
2
  import { ProtocolContractAddress } from '@aztec/protocol-contracts';
2
3
  import { computeFeePayerBalanceStorageSlot } from '@aztec/protocol-contracts/fee-juice';
3
4
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
4
5
  import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
5
6
  import { Tx, TxHash } from '@aztec/stdlib/tx';
6
7
  import { TxArchive } from './archive/index.js';
8
+ import { DeletedPool } from './deleted_pool.js';
7
9
  import { EvictionManager, FeePayerBalanceEvictionRule, FeePayerBalancePreAddRule, InvalidTxsAfterMiningRule, InvalidTxsAfterReorgRule, LowPriorityEvictionRule, LowPriorityPreAddRule, NullifierConflictRule } from './eviction/index.js';
8
10
  import { DEFAULT_TX_POOL_V2_CONFIG } from './interfaces.js';
9
- import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } from './tx_metadata.js';
11
+ import { buildTxMetaData, checkNullifierConflict } from './tx_metadata.js';
12
+ import { TxPoolIndices } from './tx_pool_indices.js';
10
13
  /**
11
14
  * Implementation of TxPoolV2 logic.
12
15
  *
@@ -18,19 +21,13 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
18
21
  // === Dependencies ===
19
22
  #l2BlockSource;
20
23
  #worldStateSynchronizer;
21
- #pendingTxValidator;
24
+ #createTxValidator;
22
25
  // === In-Memory Indices ===
23
- /** Primary metadata store: txHash -> TxMetaData */ #metadata = new Map();
24
- /** Nullifier to txHash index (pending txs only) */ #nullifierToTxHash = new Map();
25
- /** Fee payer to txHashes index (pending txs only) */ #feePayerToTxHashes = new Map();
26
- /**
27
- * Pending txHashes grouped by priority fee.
28
- * Outer map: priorityFee -> Set of txHashes at that fee level.
29
- */ #pendingByPriority = new Map();
30
- /** Protected transactions: txHash -> slotNumber. Includes txs we have and txs we expect to receive. */ #protectedTransactions = new Map();
26
+ #indices = new TxPoolIndices();
31
27
  // === Config & Services ===
32
28
  #config;
33
29
  #archive;
30
+ #deletedPool;
34
31
  #evictionManager;
35
32
  #log;
36
33
  #callbacks;
@@ -39,12 +36,13 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
39
36
  this.#txsDB = store.openMap('txs');
40
37
  this.#l2BlockSource = deps.l2BlockSource;
41
38
  this.#worldStateSynchronizer = deps.worldStateSynchronizer;
42
- this.#pendingTxValidator = deps.pendingTxValidator;
39
+ this.#createTxValidator = deps.createTxValidator;
43
40
  this.#config = {
44
41
  ...DEFAULT_TX_POOL_V2_CONFIG,
45
42
  ...config
46
43
  };
47
44
  this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
45
+ this.#deletedPool = new DeletedPool(store, this.#txsDB, log);
48
46
  this.#log = log;
49
47
  this.#callbacks = callbacks;
50
48
  // Setup eviction manager with rules
@@ -75,20 +73,32 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
75
73
  * Note: Protected status is lost on restart. All non-mined txs are rebuilt as pending
76
74
  * by running pre-add rules to resolve nullifier conflicts, balance checks, and pool size limits.
77
75
  */ async hydrateFromDatabase() {
78
- // Step 1: Load all transactions from DB
76
+ // Step 0: Hydrate deleted pool state
77
+ await this.#deletedPool.hydrateFromDatabase();
78
+ // Step 1: Load all transactions from DB (excluding soft-deleted)
79
79
  const { loaded, errors: deserializationErrors } = await this.#loadAllTxsFromDb();
80
80
  // Step 2: Check mined status for each tx
81
81
  await this.#markMinedStatusBatch(loaded.map((l)=>l.meta));
82
82
  // Step 3: Partition by mined status
83
- const { mined, nonMined } = this.#partitionByMinedStatus(loaded);
83
+ const mined = [];
84
+ const nonMined = [];
85
+ for (const entry of loaded){
86
+ if (entry.meta.minedL2BlockId !== undefined) {
87
+ mined.push(entry.meta);
88
+ } else {
89
+ nonMined.push(entry);
90
+ }
91
+ }
84
92
  // Step 4: Validate non-mined transactions
85
- const { valid, invalid } = await this.#validateNonMinedTxs(nonMined);
93
+ const { valid, invalid } = await this.#revalidateMetadata(nonMined.map((e)=>e.meta), 'on startup');
86
94
  // Step 5: Populate mined indices (these don't need conflict resolution)
87
- this.#populateMinedIndices(mined);
95
+ for (const meta of mined){
96
+ this.#indices.addMined(meta);
97
+ }
88
98
  // Step 6: Rebuild pending pool by running pre-add rules for each tx
89
99
  // This resolves nullifier conflicts, fee payer balance issues, and pool size limits
90
100
  const { rejected } = await this.#rebuildPendingPool(valid);
91
- // Step 7: Delete invalid and rejected txs from DB
101
+ // Step 7: Delete invalid and rejected txs from DB only (indices were never populated for these)
92
102
  const toDelete = [
93
103
  ...deserializationErrors,
94
104
  ...invalid,
@@ -108,7 +118,6 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
108
118
  const accepted = [];
109
119
  const ignored = [];
110
120
  const rejected = [];
111
- const newlyAdded = [];
112
121
  const acceptedPending = new Set();
113
122
  const poolAccess = this.#createPreAddPoolAccess();
114
123
  await this.#store.transactionAsync(async ()=>{
@@ -116,29 +125,30 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
116
125
  const txHash = tx.getTxHash();
117
126
  const txHashStr = txHash.toString();
118
127
  // Skip duplicates
119
- if (this.#isDuplicateTx(txHashStr)) {
128
+ if (this.#indices.has(txHashStr)) {
120
129
  ignored.push(txHash);
121
130
  continue;
122
131
  }
123
132
  // Check mined status first (applies to all paths)
124
133
  const minedBlockId = await this.#getMinedBlockId(txHash);
125
- const preProtectedSlot = this.#protectedTransactions.get(txHashStr);
134
+ const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
126
135
  if (minedBlockId) {
127
136
  // Already mined - add directly (protection already set if pre-protected)
128
- await this.#addNewMinedTx(tx, minedBlockId);
137
+ await this.#addTx(tx, {
138
+ mined: minedBlockId
139
+ }, opts);
129
140
  accepted.push(txHash);
130
- newlyAdded.push(tx);
131
141
  } else if (preProtectedSlot !== undefined) {
132
142
  // Pre-protected and not mined - add as protected (bypass validation)
133
- await this.#addNewProtectedTx(tx, preProtectedSlot);
143
+ await this.#addTx(tx, {
144
+ protected: preProtectedSlot
145
+ }, opts);
134
146
  accepted.push(txHash);
135
- newlyAdded.push(tx);
136
147
  } else {
137
148
  // Regular pending tx - validate and run pre-add rules
138
- const result = await this.#tryAddRegularPendingTx(tx, poolAccess, acceptedPending, ignored);
149
+ const result = await this.#tryAddRegularPendingTx(tx, opts, poolAccess, acceptedPending, ignored);
139
150
  if (result.status === 'accepted') {
140
151
  acceptedPending.add(txHashStr);
141
- newlyAdded.push(tx);
142
152
  } else if (result.status === 'rejected') {
143
153
  rejected.push(txHash);
144
154
  } else {
@@ -153,35 +163,29 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
153
163
  }
154
164
  // Run post-add eviction rules for pending txs
155
165
  if (acceptedPending.size > 0) {
156
- const feePayers = Array.from(acceptedPending).map((txHash)=>this.#metadata.get(txHash).feePayer);
166
+ const feePayers = Array.from(acceptedPending).map((txHash)=>this.#indices.getMetadata(txHash).feePayer);
157
167
  const uniqueFeePayers = new Set(feePayers);
158
168
  await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [
159
169
  ...uniqueFeePayers
160
170
  ]);
161
171
  }
162
- // Emit events
163
- if (newlyAdded.length > 0) {
164
- this.#callbacks.onTxsAdded(newlyAdded, opts);
165
- }
166
172
  return {
167
173
  accepted,
168
174
  ignored,
169
175
  rejected
170
176
  };
171
177
  }
172
- /** Validates and adds a regular pending tx. Returns status. */ async #tryAddRegularPendingTx(tx, poolAccess, acceptedPending, ignored) {
178
+ /** Validates and adds a regular pending tx. Returns status. */ async #tryAddRegularPendingTx(tx, opts, poolAccess, acceptedPending, ignored) {
173
179
  const txHash = tx.getTxHash();
174
180
  const txHashStr = txHash.toString();
175
- // Validate transaction
176
- const validationResult = await this.#pendingTxValidator.validateTx(tx);
177
- if (validationResult.result !== 'valid') {
178
- this.#log.info(`Rejecting tx ${txHashStr}: ${validationResult.reason?.join(', ')}`);
181
+ // Build metadata and validate using metadata
182
+ const meta = await buildTxMetaData(tx);
183
+ if (!await this.#validateMeta(meta)) {
179
184
  return {
180
185
  status: 'rejected'
181
186
  };
182
187
  }
183
- // Build metadata and run pre-add rules
184
- const meta = await buildTxMetaData(tx);
188
+ // Run pre-add rules
185
189
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
186
190
  if (preAddResult.shouldIgnore) {
187
191
  this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason}`);
@@ -189,17 +193,18 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
189
193
  status: 'ignored'
190
194
  };
191
195
  }
192
- // Evict conflicts (tracking intra-batch evictions)
196
+ // Evict conflicts
193
197
  for (const evictHashStr of preAddResult.txHashesToEvict){
194
198
  await this.#deleteTx(evictHashStr);
195
199
  this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`);
196
200
  if (acceptedPending.has(evictHashStr)) {
201
+ // Evicted tx was from this batch - mark as ignored in result
197
202
  acceptedPending.delete(evictHashStr);
198
203
  ignored.push(TxHash.fromString(evictHashStr));
199
204
  }
200
205
  }
201
206
  // Add the transaction
202
- await this.#addNewPendingTx(tx);
207
+ await this.#addTx(tx, 'pending', opts);
203
208
  return {
204
209
  status: 'accepted'
205
210
  };
@@ -207,62 +212,62 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
207
212
  async canAddPendingTx(tx) {
208
213
  const txHashStr = tx.getTxHash().toString();
209
214
  // Check if already in pool
210
- if (this.#metadata.has(txHashStr)) {
215
+ if (this.#indices.has(txHashStr)) {
211
216
  return 'ignored';
212
217
  }
213
- // Validate transaction
214
- const validationResult = await this.#pendingTxValidator.validateTx(tx);
215
- if (validationResult.result !== 'valid') {
218
+ // Build metadata and validate using metadata
219
+ const meta = await buildTxMetaData(tx);
220
+ const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
221
+ if (validationResult !== true) {
216
222
  return 'rejected';
217
223
  }
218
- // Build metadata and use pre-add rules
219
- const meta = await buildTxMetaData(tx);
224
+ // Use pre-add rules
220
225
  const poolAccess = this.#createPreAddPoolAccess();
221
226
  const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
222
227
  return preAddResult.shouldIgnore ? 'ignored' : 'accepted';
223
228
  }
224
229
  async addProtectedTxs(txs, block, opts) {
225
230
  const slotNumber = block.globalVariables.slotNumber;
226
- const newlyAdded = [];
227
231
  await this.#store.transactionAsync(async ()=>{
228
232
  for (const tx of txs){
229
233
  const txHash = tx.getTxHash();
230
234
  const txHashStr = txHash.toString();
231
- const isNew = !this.#metadata.has(txHashStr);
235
+ const isNew = !this.#indices.has(txHashStr);
232
236
  const minedBlockId = await this.#getMinedBlockId(txHash);
233
237
  if (isNew) {
234
- // New tx - add as mined or protected
238
+ // New tx - add as mined or protected (callback emitted by #addTx)
235
239
  if (minedBlockId) {
236
- await this.#addNewMinedTx(tx, minedBlockId);
237
- this.#protectedTransactions.set(txHashStr, slotNumber);
240
+ await this.#addTx(tx, {
241
+ mined: minedBlockId
242
+ }, opts);
243
+ this.#indices.setProtection(txHashStr, slotNumber);
238
244
  } else {
239
- await this.#addNewProtectedTx(tx, slotNumber);
245
+ await this.#addTx(tx, {
246
+ protected: slotNumber
247
+ }, opts);
240
248
  }
241
- newlyAdded.push(tx);
242
249
  } else {
243
250
  // Existing tx - update protection and mined status
244
- this.#updateProtection(txHashStr, slotNumber);
251
+ this.#indices.updateProtection(txHashStr, slotNumber);
245
252
  if (minedBlockId) {
246
- this.#markAsMined(this.#metadata.get(txHashStr), minedBlockId);
253
+ const meta = this.#indices.getMetadata(txHashStr);
254
+ this.#indices.markAsMined(meta, minedBlockId);
247
255
  }
248
256
  }
249
257
  }
250
258
  });
251
- if (newlyAdded.length > 0) {
252
- this.#callbacks.onTxsAdded(newlyAdded, opts);
253
- }
254
259
  }
255
260
  protectTxs(txHashes, block) {
256
261
  const slotNumber = block.globalVariables.slotNumber;
257
262
  const missing = [];
258
263
  for (const txHash of txHashes){
259
264
  const txHashStr = txHash.toString();
260
- if (this.#metadata.has(txHashStr)) {
261
- // Step 1a: Update protection for existing tx
262
- this.#updateProtection(txHashStr, slotNumber);
265
+ if (this.#indices.has(txHashStr)) {
266
+ // Update protection for existing tx
267
+ this.#indices.updateProtection(txHashStr, slotNumber);
263
268
  } else {
264
- // Step 1b: Pre-record protection for tx we don't have yet
265
- this.#protectedTransactions.set(txHashStr, slotNumber);
269
+ // Pre-record protection for tx we don't have yet
270
+ this.#indices.setProtection(txHashStr, slotNumber);
266
271
  missing.push(txHash);
267
272
  }
268
273
  }
@@ -271,25 +276,22 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
271
276
  async addMinedTxs(txs, block, opts) {
272
277
  // Step 1: Build block ID
273
278
  const blockId = await this.#buildBlockId(block);
274
- const newlyAdded = [];
275
279
  await this.#store.transactionAsync(async ()=>{
276
280
  for (const tx of txs){
277
281
  const txHashStr = tx.getTxHash().toString();
278
- const existingMeta = this.#metadata.get(txHashStr);
282
+ const existingMeta = this.#indices.getMetadata(txHashStr);
279
283
  if (existingMeta) {
280
- // Step 2a: Mark existing tx as mined
281
- this.#markAsMined(existingMeta, blockId);
284
+ // Mark existing tx as mined
285
+ this.#indices.markAsMined(existingMeta, blockId);
282
286
  } else {
283
- // Step 2b: Add new mined tx
284
- await this.#addNewMinedTx(tx, blockId);
285
- newlyAdded.push(tx);
287
+ // Add new mined tx (callback emitted by #addTx)
288
+ await this.#addTx(tx, {
289
+ mined: blockId
290
+ }, opts);
286
291
  }
292
+ await this.#deletedPool.clearIfMinedHigher(txHashStr, blockId.number);
287
293
  }
288
294
  });
289
- // Step 3: Emit events for newly added txs
290
- if (newlyAdded.length > 0) {
291
- this.#callbacks.onTxsAdded(newlyAdded, opts);
292
- }
293
295
  }
294
296
  async handleMinedBlock(block) {
295
297
  // Step 1: Build block ID
@@ -301,32 +303,34 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
301
303
  const feePayers = [];
302
304
  const found = [];
303
305
  for (const txHash of txHashes){
304
- const meta = this.#metadata.get(txHash.toString());
306
+ const meta = this.#indices.getMetadata(txHash.toString());
305
307
  if (meta) {
306
308
  feePayers.push(meta.feePayer);
307
309
  found.push(meta);
308
310
  }
309
311
  }
310
312
  // Step 4: Mark txs as mined (only those we have in the pool)
311
- this.#markTxsAsMined(found, blockId);
313
+ for (const meta of found){
314
+ this.#indices.markAsMined(meta, blockId);
315
+ await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
316
+ }
312
317
  // Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
313
318
  await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
314
- this.#callbacks.onTxsRemoved(txHashes.map((h)=>h.toBigInt()));
315
319
  this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
316
320
  }
317
321
  async prepareForSlot(slotNumber) {
318
322
  // Step 1: Find expired protected txs
319
- const expiredProtected = this.#findExpiredProtectedTxs(slotNumber);
323
+ const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
320
324
  // Step 2: Clear protection for all expired entries (including those without metadata)
321
- this.#clearProtection(expiredProtected);
325
+ this.#indices.clearProtection(expiredProtected);
322
326
  // Step 3: Filter to only txs that have metadata and are not mined
323
- const txsToRestore = this.#filterRestorable(expiredProtected);
327
+ const txsToRestore = this.#indices.filterRestorable(expiredProtected);
324
328
  if (txsToRestore.length === 0) {
325
329
  return;
326
330
  }
327
331
  this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
328
332
  // Step 4: Validate for pending pool
329
- const { valid, invalid } = await this.#validateForPending(txsToRestore);
333
+ const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
330
334
  // Step 5: Resolve nullifier conflicts and add winners to pending indices
331
335
  const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
332
336
  // Step 6: Delete invalid and evicted txs
@@ -345,58 +349,68 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
345
349
  }
346
350
  async handlePrunedBlocks(latestBlock) {
347
351
  // Step 1: Find transactions mined after the prune point
348
- const txsToUnmine = this.#findTxsMinedAfter(latestBlock.number);
352
+ const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
349
353
  if (txsToUnmine.length === 0) {
350
354
  this.#log.debug(`No transactions to un-mine for prune to block ${latestBlock.number}`);
351
355
  return;
352
356
  }
353
357
  this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
354
- // Step 2: Unmine - clear mined status from metadata
355
- this.#unmineTxs(txsToUnmine);
356
- // Step 3: Filter out protected txs (they'll be handled by prepareForSlot)
357
- const unprotectedTxs = this.#filterUnprotected(txsToUnmine);
358
+ // Step 2: Mark ALL un-mined txs with their original mined block number
359
+ // This ensures they get soft-deleted if removed later, and only hard-deleted
360
+ // when their original mined block is finalized
361
+ await this.#deletedPool.markFromPrunedBlock(txsToUnmine.map((m)=>({
362
+ txHash: m.txHash,
363
+ minedAtBlock: BlockNumber(m.minedL2BlockId.number)
364
+ })));
365
+ // Step 3: Unmine - clear mined status from metadata
366
+ for (const meta of txsToUnmine){
367
+ this.#indices.markAsUnmined(meta);
368
+ }
369
+ // Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
370
+ const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
358
371
  // Step 4: Validate for pending pool
359
- const { valid, invalid } = await this.#validateForPending(unprotectedTxs);
360
- // Step 5: Resolve nullifier conflicts and add winners to pending indices
372
+ const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
373
+ // Step 6: Resolve nullifier conflicts and add winners to pending indices
361
374
  const { toEvict } = this.#applyNullifierConflictResolution(valid);
362
- // Step 6: Delete invalid and evicted txs
375
+ // Step 7: Delete invalid and evicted txs
363
376
  await this.#deleteTxsBatch([
364
377
  ...invalid,
365
378
  ...toEvict
366
379
  ]);
367
- // Step 7: Run eviction rules for ALL pending txs (not just restored ones)
380
+ // Step 8: Run eviction rules for ALL pending txs (not just restored ones)
368
381
  // This handles cases like existing pending txs with invalid fee payer balances
369
382
  await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
370
383
  }
371
384
  async handleFailedExecution(txHashes) {
372
- // Step 1: Delete failed txs
385
+ // Delete failed txs
373
386
  await this.#deleteTxsBatch(txHashes.map((h)=>h.toString()));
374
387
  this.#log.info(`Deleted ${txHashes.length} failed txs`);
375
388
  }
376
389
  async handleFinalizedBlock(block) {
377
390
  const blockNumber = block.globalVariables.blockNumber;
378
- // Step 1: Find txs mined at or before finalized block
379
- const txsToFinalize = this.#findTxsMinedAtOrBefore(blockNumber);
380
- if (txsToFinalize.length === 0) {
381
- return;
382
- }
383
- // Step 2: Collect txs for archiving (before deletion)
391
+ // Step 1: Find mined txs at or before finalized block
392
+ const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
393
+ // Step 2: Collect mined txs for archiving (before deletion)
384
394
  const txsToArchive = [];
385
395
  if (this.#archive.isEnabled()) {
386
- for (const txHashStr of txsToFinalize){
396
+ for (const txHashStr of minedTxsToFinalize){
387
397
  const buffer = await this.#txsDB.getAsync(txHashStr);
388
398
  if (buffer) {
389
399
  txsToArchive.push(Tx.fromBuffer(buffer));
390
400
  }
391
401
  }
392
402
  }
393
- // Step 3: Delete from active pool
394
- await this.#deleteTxsBatch(txsToFinalize);
395
- // Step 4: Archive
403
+ // Step 3: Delete mined txs from active pool
404
+ await this.#deleteTxsBatch(minedTxsToFinalize);
405
+ // Step 4: Finalize soft-deleted txs
406
+ await this.#deletedPool.finalizeBlock(blockNumber);
407
+ // Step 5: Archive mined txs
396
408
  if (txsToArchive.length > 0) {
397
409
  await this.#archive.archiveTxs(txsToArchive);
398
410
  }
399
- this.#log.info(`Finalized ${txsToFinalize.length} txs from blocks up to ${blockNumber}`);
411
+ if (minedTxsToFinalize.length > 0) {
412
+ this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`);
413
+ }
400
414
  }
401
415
  // === Query Methods ===
402
416
  async getTxByHash(txHash) {
@@ -412,42 +426,40 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
412
426
  return results;
413
427
  }
414
428
  hasTxs(txHashes) {
415
- return txHashes.map((h)=>this.#metadata.has(h.toString()));
429
+ return txHashes.map((h)=>{
430
+ const hashStr = h.toString();
431
+ return this.#indices.has(hashStr) || this.#deletedPool.isSoftDeleted(hashStr);
432
+ });
416
433
  }
417
434
  getTxStatus(txHash) {
418
- const meta = this.#metadata.get(txHash.toString());
419
- if (!meta) {
420
- return undefined;
435
+ const txHashStr = txHash.toString();
436
+ const meta = this.#indices.getMetadata(txHashStr);
437
+ if (meta) {
438
+ return this.#indices.getTxState(meta);
421
439
  }
422
- return this.#getTxState(meta);
440
+ // Check if soft-deleted
441
+ if (this.#deletedPool.isSoftDeleted(txHashStr)) {
442
+ return 'deleted';
443
+ }
444
+ return undefined;
423
445
  }
424
446
  getPendingTxHashes() {
425
447
  return [
426
- ...this.#iteratePendingByPriority('desc')
448
+ ...this.#indices.iteratePendingByPriority('desc')
427
449
  ].map((hash)=>TxHash.fromString(hash));
428
450
  }
429
451
  getPendingTxCount() {
430
- let count = 0;
431
- for (const hashes of this.#pendingByPriority.values()){
432
- count += hashes.size;
433
- }
434
- return count;
452
+ return this.#indices.getPendingTxCount();
435
453
  }
436
454
  getMinedTxHashes() {
437
- const result = [];
438
- for (const [txHash, meta] of this.#metadata){
439
- if (meta.minedL2BlockId !== undefined) {
440
- result.push([
441
- TxHash.fromString(txHash),
442
- meta.minedL2BlockId
443
- ]);
444
- }
445
- }
446
- return result;
455
+ return this.#indices.getMinedTxs().map(([hash, blockId])=>[
456
+ TxHash.fromString(hash),
457
+ blockId
458
+ ]);
447
459
  }
448
460
  getMinedTxCount() {
449
461
  let count = 0;
450
- for (const meta of this.#metadata.values()){
462
+ for (const [, meta] of this.#indices.iterateMetadata()){
451
463
  if (meta.minedL2BlockId !== undefined) {
452
464
  count++;
453
465
  }
@@ -455,26 +467,16 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
455
467
  return count;
456
468
  }
457
469
  isEmpty() {
458
- return this.#metadata.size === 0;
470
+ return this.#indices.isEmpty();
459
471
  }
460
472
  getTxCount() {
461
- return this.#metadata.size;
473
+ return this.#indices.getTxCount();
462
474
  }
463
475
  getArchivedTxByHash(txHash) {
464
476
  return this.#archive.getTxByHash(txHash);
465
477
  }
466
478
  getLowestPriorityPending(limit) {
467
- if (limit <= 0) {
468
- return [];
469
- }
470
- const result = [];
471
- for (const hash of this.#iteratePendingByPriority('asc')){
472
- result.push(TxHash.fromString(hash));
473
- if (result.length >= limit) {
474
- break;
475
- }
476
- }
477
- return result;
479
+ return this.#indices.getLowestPriorityPending(limit).map((h)=>TxHash.fromString(h));
478
480
  }
479
481
  // === Configuration ===
480
482
  updateConfig(config) {
@@ -491,132 +493,84 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
491
493
  // === Pool Read Access ===
492
494
  getPoolReadAccess() {
493
495
  return {
494
- getMetadata: (txHash)=>this.#metadata.get(txHash),
495
- getTxHashByNullifier: (nullifier)=>this.#nullifierToTxHash.get(nullifier),
496
- getTxHashesByFeePayer: (feePayer)=>this.#feePayerToTxHashes.get(feePayer),
497
- getPendingTxCount: ()=>this.getPendingTxCount()
496
+ getMetadata: (txHash)=>this.#indices.getMetadata(txHash),
497
+ getTxHashByNullifier: (nullifier)=>this.#indices.getTxHashByNullifier(nullifier),
498
+ getTxHashesByFeePayer: (feePayer)=>this.#indices.getTxHashesByFeePayer(feePayer),
499
+ getPendingTxCount: ()=>this.#indices.getPendingTxCount()
498
500
  };
499
501
  }
500
502
  // === Metrics ===
501
503
  countTxs() {
502
- let pending = 0;
503
- let protected_ = 0;
504
- let mined = 0;
505
- for (const meta of this.#metadata.values()){
506
- const state = this.#getTxState(meta);
507
- if (state === 'pending') {
508
- pending++;
509
- } else if (state === 'protected') {
510
- protected_++;
511
- } else if (state === 'mined') {
512
- mined++;
513
- }
514
- }
515
- return {
516
- pending,
517
- protected: protected_,
518
- mined
519
- };
504
+ return this.#indices.countTxs();
520
505
  }
521
506
  // ============================================================================
522
- // PRIVATE QUERY IMPLEMENTATIONS
507
+ // PRIVATE HELPERS - Transaction Management
523
508
  // ============================================================================
524
509
  /**
525
- * Derives the transaction state from its metadata and protection status.
526
- * A transaction is:
527
- * - 'mined' if it has a minedL2BlockId
528
- * - 'protected' if it's in the protectedTransactions map (but not mined)
529
- * - 'pending' otherwise
530
- */ #getTxState(meta) {
531
- if (meta.minedL2BlockId !== undefined) {
532
- return 'mined';
533
- } else if (this.#protectedTransactions.has(meta.txHash)) {
534
- return 'protected';
510
+ * Adds a new transaction to the pool with the specified state.
511
+ * Emits onTxsAdded callback immediately after DB write.
512
+ */ async #addTx(tx, state, opts = {}) {
513
+ const txHashStr = tx.getTxHash().toString();
514
+ const meta = await buildTxMetaData(tx);
515
+ await this.#txsDB.set(txHashStr, tx.toBuffer());
516
+ this.#callbacks.onTxsAdded([
517
+ tx
518
+ ], opts);
519
+ if (state === 'pending') {
520
+ this.#indices.addPending(meta);
521
+ } else if ('protected' in state) {
522
+ this.#indices.addProtected(meta, state.protected);
535
523
  } else {
536
- return 'pending';
524
+ meta.minedL2BlockId = state.mined;
525
+ this.#indices.addMined(meta);
537
526
  }
527
+ const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
528
+ this.#log.verbose(`Added ${stateStr} tx ${txHashStr}`, {
529
+ eventName: 'tx-added-to-pool',
530
+ state: stateStr
531
+ });
532
+ return meta;
538
533
  }
539
534
  /**
540
- * Iterates pending transaction hashes in priority order.
541
- * @param order - 'desc' for highest priority first, 'asc' for lowest priority first
542
- */ *#iteratePendingByPriority(order) {
543
- // Use shared comparators, negating for descending order
544
- const feeCompareFn = order === 'desc' ? (a, b)=>compareFee(b, a) : (a, b)=>compareFee(a, b);
545
- const hashCompareFn = order === 'desc' ? (a, b)=>compareTxHash(b, a) : (a, b)=>compareTxHash(a, b);
546
- const sortedFees = [
547
- ...this.#pendingByPriority.keys()
548
- ].sort(feeCompareFn);
549
- for (const fee of sortedFees){
550
- const hashesAtFee = this.#pendingByPriority.get(fee);
551
- const sortedHashes = [
552
- ...hashesAtFee
553
- ].sort(hashCompareFn);
554
- for (const hash of sortedHashes){
555
- yield hash;
556
- }
535
+ * Deletes a transaction from both indices and DB.
536
+ * Emits onTxsRemoved callback immediately after DB delete.
537
+ */ /**
538
+ * Deletes a transaction from the pool.
539
+ * Delegates to DeletedPool which decides soft vs hard delete based on whether
540
+ * the tx is from a pruned block.
541
+ */ async #deleteTx(txHashStr) {
542
+ this.#indices.remove(txHashStr);
543
+ this.#callbacks.onTxsRemoved([
544
+ txHashStr
545
+ ]);
546
+ await this.#deletedPool.deleteTx(txHashStr);
547
+ }
548
+ /** Deletes a batch of transactions, emitting callbacks individually for each. */ async #deleteTxsBatch(txHashes) {
549
+ for (const txHashStr of txHashes){
550
+ await this.#deleteTx(txHashStr);
557
551
  }
558
552
  }
559
553
  // ============================================================================
560
- // HELPER FUNCTIONS - Pipeline Step Functions
554
+ // PRIVATE HELPERS - Validation & Conflict Resolution
561
555
  // ============================================================================
562
- // --- Finding & Filtering Steps ---
563
- /** Finds all transactions mined in blocks after the given block number */ #findTxsMinedAfter(blockNumber) {
564
- const result = [];
565
- for (const meta of this.#metadata.values()){
566
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number > blockNumber) {
567
- result.push(meta);
568
- }
569
- }
570
- return result;
571
- }
572
- /** Finds tx hashes mined at or before the given block number */ #findTxsMinedAtOrBefore(blockNumber) {
573
- const result = [];
574
- for (const [txHashStr, meta] of this.#metadata){
575
- if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number <= blockNumber) {
576
- result.push(txHashStr);
577
- }
578
- }
579
- return result;
580
- }
581
- /** Finds protected tx hashes from slots earlier than the given slot number */ #findExpiredProtectedTxs(slotNumber) {
582
- const result = [];
583
- for (const [txHashStr, protectedSlot] of this.#protectedTransactions){
584
- if (protectedSlot < slotNumber) {
585
- result.push(txHashStr);
586
- }
556
+ /** Validates transaction metadata, returning true if valid */ async #validateMeta(meta, validator, context) {
557
+ const txValidator = validator ?? await this.#createTxValidator();
558
+ const result = await txValidator.validateTx(meta);
559
+ if (result.result !== 'valid') {
560
+ const contextStr = context ? ` ${context}` : '';
561
+ this.#log.info(`Tx ${meta.txHash}${contextStr} failed validation: ${result.reason?.join(', ')}`);
562
+ return false;
587
563
  }
588
- return result;
589
- }
590
- /** Filters out transactions that are currently protected */ #filterUnprotected(txs) {
591
- return txs.filter((meta)=>!this.#protectedTransactions.has(meta.txHash));
564
+ return true;
592
565
  }
593
- /** Filters to transactions that have metadata and are not mined */ #filterRestorable(txHashes) {
594
- const result = [];
595
- for (const txHashStr of txHashes){
596
- const meta = this.#metadata.get(txHashStr);
597
- if (meta && meta.minedL2BlockId === undefined) {
598
- result.push(meta);
599
- }
600
- }
601
- return result;
602
- }
603
- // --- Validation & Conflict Resolution Steps ---
604
- /** Validates transactions for pending pool, returning valid and invalid groups */ async #validateForPending(txs) {
566
+ /** Validates metadata directly */ async #revalidateMetadata(metas, context) {
605
567
  const valid = [];
606
568
  const invalid = [];
607
- for (const meta of txs){
608
- const buffer = await this.#txsDB.getAsync(meta.txHash);
609
- if (!buffer) {
610
- this.#log.warn(`Tx ${meta.txHash} not found in DB during validation`);
611
- invalid.push(meta.txHash);
612
- continue;
613
- }
614
- const tx = Tx.fromBuffer(buffer);
615
- const result = await this.#pendingTxValidator.validateTx(tx);
616
- if (result.result === 'valid') {
569
+ const validator = await this.#createTxValidator();
570
+ for (const meta of metas){
571
+ if (await this.#validateMeta(meta, validator, context)) {
617
572
  valid.push(meta);
618
573
  } else {
619
- this.#log.info(`Tx ${meta.txHash} failed validation: ${result.reason?.join(', ')}`);
620
574
  invalid.push(meta.txHash);
621
575
  }
622
576
  }
@@ -633,7 +587,7 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
633
587
  const added = [];
634
588
  const toEvict = [];
635
589
  for (const meta of txs){
636
- const conflict = checkNullifierConflict(meta, (nullifier)=>this.#nullifierToTxHash.get(nullifier), (txHash)=>this.#metadata.get(txHash));
590
+ const conflict = checkNullifierConflict(meta, (nullifier)=>this.#indices.getTxHashByNullifier(nullifier), (txHash)=>this.#indices.getMetadata(txHash));
637
591
  if (conflict.shouldIgnore) {
638
592
  // Lower priority than existing - don't add, mark for deletion
639
593
  toEvict.push(meta.txHash);
@@ -642,13 +596,13 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
642
596
  toEvict.push(...conflict.txHashesToEvict);
643
597
  // Remove evicted from indices immediately for subsequent checks
644
598
  for (const evictHash of conflict.txHashesToEvict){
645
- const evictMeta = this.#metadata.get(evictHash);
599
+ const evictMeta = this.#indices.getMetadata(evictHash);
646
600
  if (evictMeta) {
647
- this.#removeFromPendingIndices(evictMeta);
601
+ this.#indices.removeFromPendingIndices(evictMeta);
648
602
  }
649
603
  }
650
604
  // Add to pending indices immediately so subsequent txs in the batch see this tx
651
- this.#addToPendingIndices(meta);
605
+ this.#indices.addToPendingIndices(meta);
652
606
  added.push(meta);
653
607
  }
654
608
  }
@@ -657,32 +611,10 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
657
611
  toEvict
658
612
  };
659
613
  }
660
- // --- State Transition Steps ---
661
- /** Clears the mined status from transactions, returning them for further processing */ #unmineTxs(txs) {
662
- for (const meta of txs){
663
- meta.minedL2BlockId = undefined;
664
- }
665
- return txs;
666
- }
667
- /** Removes protection from tx hashes and clears them from the protected map */ #clearProtection(txHashes) {
668
- for (const txHashStr of txHashes){
669
- this.#protectedTransactions.delete(txHashStr);
670
- }
671
- }
672
- // --- Batch Operation Steps ---
673
- /** Deletes a batch of transactions permanently */ async #deleteTxsBatch(txHashes) {
674
- if (txHashes.length === 0) {
675
- return;
676
- }
677
- await this.#store.transactionAsync(async ()=>{
678
- for (const txHashStr of txHashes){
679
- await this.#deleteTx(txHashStr);
680
- }
681
- });
682
- this.#callbacks.onTxsRemoved(txHashes);
683
- }
684
- // --- Block & Tx Info Steps ---
685
- /** Builds a block ID from a block header */ async #buildBlockId(block) {
614
+ // ============================================================================
615
+ // PRIVATE HELPERS - Block & Hydration
616
+ // ============================================================================
617
+ async #buildBlockId(block) {
686
618
  return {
687
619
  number: block.globalVariables.blockNumber,
688
620
  hash: (await block.hash()).toString()
@@ -698,40 +630,14 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
698
630
  hash: txEffect.l2BlockHash.toString()
699
631
  };
700
632
  }
701
- /** Marks a batch of transactions as mined */ #markTxsAsMined(metas, blockId) {
702
- for (const meta of metas){
703
- this.#markAsMined(meta, blockId);
704
- }
705
- }
706
- // --- Add Transaction Steps ---
707
- /** Persists a transaction to the database */ async #persistTx(txHashStr, tx) {
708
- await this.#txsDB.set(txHashStr, tx.toBuffer());
709
- }
710
- /** Adds a new transaction as protected, returning its metadata */ async #addNewProtectedTx(tx, slotNumber) {
711
- const txHashStr = tx.getTxHash().toString();
712
- const meta = await buildTxMetaData(tx);
713
- this.#protectedTransactions.set(txHashStr, slotNumber);
714
- await this.#persistTx(txHashStr, tx);
715
- this.#metadata.set(txHashStr, meta);
716
- // Don't add to pending indices since it's protected
717
- this.#log.verbose(`Added protected tx ${txHashStr} for slot ${slotNumber}`);
718
- return meta;
719
- }
720
- /** Adds a new transaction as mined, returning its metadata */ async #addNewMinedTx(tx, blockId) {
721
- const txHashStr = tx.getTxHash().toString();
722
- const meta = await buildTxMetaData(tx);
723
- meta.minedL2BlockId = blockId;
724
- await this.#persistTx(txHashStr, tx);
725
- this.#metadata.set(txHashStr, meta);
726
- // Don't add to pending indices since it's mined
727
- this.#log.verbose(`Added mined tx ${txHashStr} from block ${blockId.number}`);
728
- return meta;
729
- }
730
- // --- Hydration Steps ---
731
633
  /** Loads all transactions from the database, returning loaded txs and deserialization errors */ async #loadAllTxsFromDb() {
732
634
  const loaded = [];
733
635
  const errors = [];
734
636
  for await (const [txHashStr, buffer] of this.#txsDB.entriesAsync()){
637
+ // Skip soft-deleted transactions - they stay in DB but not in indices
638
+ if (this.#deletedPool.isSoftDeleted(txHashStr)) {
639
+ continue;
640
+ }
735
641
  try {
736
642
  const tx = Tx.fromBuffer(buffer);
737
643
  const meta = await buildTxMetaData(tx);
@@ -768,43 +674,6 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
768
674
  }
769
675
  }
770
676
  }
771
- /** Partitions transactions by mined status */ #partitionByMinedStatus(txs) {
772
- const mined = [];
773
- const nonMined = [];
774
- for (const entry of txs){
775
- if (entry.meta.minedL2BlockId !== undefined) {
776
- mined.push(entry.meta);
777
- } else {
778
- nonMined.push(entry);
779
- }
780
- }
781
- return {
782
- mined,
783
- nonMined
784
- };
785
- }
786
- /** Validates non-mined transactions, returning valid metadata and invalid hashes */ async #validateNonMinedTxs(txs) {
787
- const valid = [];
788
- const invalid = [];
789
- for (const { tx, meta } of txs){
790
- const result = await this.#pendingTxValidator.validateTx(tx);
791
- if (result.result === 'valid') {
792
- valid.push(meta);
793
- } else {
794
- this.#log.info(`Removing invalid tx ${meta.txHash} on startup: ${result.reason?.join(', ')}`);
795
- invalid.push(meta.txHash);
796
- }
797
- }
798
- return {
799
- valid,
800
- invalid
801
- };
802
- }
803
- /** Populates metadata index for mined transactions */ #populateMinedIndices(metas) {
804
- for (const meta of metas){
805
- this.#metadata.set(meta.txHash, meta);
806
- }
807
- }
808
677
  /**
809
678
  * Rebuilds the pending pool by processing each tx through pre-add rules.
810
679
  * Starts with an empty pending pool and adds txs one by one, resolving conflicts.
@@ -824,17 +693,17 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
824
693
  }
825
694
  // Evict any conflicting txs identified by pre-add rules
826
695
  for (const evictHashStr of preAddResult.txHashesToEvict){
827
- const evictMeta = this.#metadata.get(evictHashStr);
696
+ const evictMeta = this.#indices.getMetadata(evictHashStr);
828
697
  if (evictMeta) {
829
- this.#removeFromPendingIndices(evictMeta);
830
- this.#metadata.delete(evictHashStr);
698
+ this.#indices.removeFromPendingIndices(evictMeta);
699
+ this.#indices.remove(evictHashStr);
831
700
  rejected.push(evictHashStr);
832
701
  accepted.delete(evictHashStr);
833
702
  this.#log.debug(`Evicted tx ${evictHashStr} during rebuild due to conflict with ${meta.txHash}`);
834
703
  }
835
704
  }
836
- // Add to metadata and pending indices
837
- this.#addToIndices(meta);
705
+ // Add to indices
706
+ this.#indices.addPending(meta);
838
707
  accepted.add(meta.txHash);
839
708
  }
840
709
  this.#log.info(`Rebuilt pending pool: ${accepted.size} accepted, ${rejected.length} rejected`);
@@ -845,197 +714,38 @@ import { buildTxMetaData, checkNullifierConflict, compareFee, compareTxHash } fr
845
714
  rejected
846
715
  };
847
716
  }
848
- // --- Add Pending Tx Steps ---
849
- /** Checks if a tx is a duplicate (already in pool) */ #isDuplicateTx(txHashStr) {
850
- return this.#metadata.has(txHashStr);
851
- }
852
- /** Adds a new pending tx to the pool, returning its metadata */ async #addNewPendingTx(tx) {
853
- const txHashStr = tx.getTxHash().toString();
854
- const meta = await buildTxMetaData(tx);
855
- await this.#persistTx(txHashStr, tx);
856
- this.#addToIndices(meta);
857
- this.#log.verbose(`Added tx ${txHashStr} to pool`, {
858
- eventName: 'tx-added-to-pool',
859
- state: this.#getTxState(meta)
860
- });
861
- return meta;
862
- }
863
- // ============================================================================
864
- // HELPER FUNCTIONS - Index Management
865
- // ============================================================================
866
- #addToIndices(meta) {
867
- this.#metadata.set(meta.txHash, meta);
868
- if (this.#getTxState(meta) === 'pending') {
869
- this.#addToPendingIndices(meta);
870
- }
871
- // Protected and mined txs don't go into pending indices
872
- }
873
- #addToPendingIndices(meta) {
874
- // Add to nullifier index
875
- for (const nullifier of meta.nullifiers){
876
- this.#nullifierToTxHash.set(nullifier, meta.txHash);
877
- }
878
- // Add to fee payer index
879
- let feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
880
- if (!feePayerSet) {
881
- feePayerSet = new Set();
882
- this.#feePayerToTxHashes.set(meta.feePayer, feePayerSet);
883
- }
884
- feePayerSet.add(meta.txHash);
885
- // Add to priority bucket
886
- let prioritySet = this.#pendingByPriority.get(meta.priorityFee);
887
- if (!prioritySet) {
888
- prioritySet = new Set();
889
- this.#pendingByPriority.set(meta.priorityFee, prioritySet);
890
- }
891
- prioritySet.add(meta.txHash);
892
- }
893
- #removeFromPendingIndices(meta) {
894
- // Remove from nullifier index
895
- for (const nullifier of meta.nullifiers){
896
- this.#nullifierToTxHash.delete(nullifier);
897
- }
898
- // Remove from fee payer index
899
- const feePayerSet = this.#feePayerToTxHashes.get(meta.feePayer);
900
- if (feePayerSet) {
901
- feePayerSet.delete(meta.txHash);
902
- if (feePayerSet.size === 0) {
903
- this.#feePayerToTxHashes.delete(meta.feePayer);
904
- }
905
- }
906
- // Remove from priority map
907
- const hashSet = this.#pendingByPriority.get(meta.priorityFee);
908
- if (hashSet) {
909
- hashSet.delete(meta.txHash);
910
- if (hashSet.size === 0) {
911
- this.#pendingByPriority.delete(meta.priorityFee);
912
- }
913
- }
914
- }
915
- #updateProtection(txHashStr, slotNumber) {
916
- const currentSlot = this.#protectedTransactions.get(txHashStr);
917
- // Only update if not already protected at an equal or later slot
918
- if (currentSlot !== undefined && currentSlot >= slotNumber) {
919
- return;
920
- }
921
- // Remove from pending indices if transitioning from pending to protected
922
- if (currentSlot === undefined) {
923
- const meta = this.#metadata.get(txHashStr);
924
- if (meta) {
925
- this.#removeFromPendingIndices(meta);
926
- }
927
- }
928
- this.#protectedTransactions.set(txHashStr, slotNumber);
929
- }
930
- #markAsMined(meta, blockId) {
931
- meta.minedL2BlockId = blockId;
932
- // Safe to call unconditionally - removeFromPendingIndices is idempotent
933
- this.#removeFromPendingIndices(meta);
934
- }
935
- async #deleteTx(txHashStr) {
936
- const meta = this.#metadata.get(txHashStr);
937
- if (!meta) {
938
- return;
939
- }
940
- // Remove from all indices
941
- this.#metadata.delete(txHashStr);
942
- this.#protectedTransactions.delete(txHashStr);
943
- this.#removeFromPendingIndices(meta);
944
- // Remove from persistence
945
- await this.#txsDB.delete(txHashStr);
946
- }
947
717
  // ============================================================================
948
- // HELPER FUNCTIONS - Adapters
718
+ // PRIVATE HELPERS - Pool Access Adapters
949
719
  // ============================================================================
950
- /** Gets all pending transactions for a given fee payer. */ #getFeePayerPendingTxs(feePayer) {
951
- const txHashes = this.#feePayerToTxHashes.get(feePayer);
952
- if (!txHashes) {
953
- return [];
954
- }
955
- const result = [];
956
- for (const txHashStr of txHashes){
957
- const meta = this.#metadata.get(txHashStr);
958
- if (meta && this.#getTxState(meta) === 'pending') {
959
- result.push(meta);
960
- }
961
- }
962
- return result;
963
- }
964
- /**
965
- * Creates a PoolOperations adapter for use with the eviction manager.
966
- */ #createPoolOperations() {
720
+ #createPoolOperations() {
967
721
  return {
968
- getPendingTxs: ()=>{
969
- const result = [];
970
- for (const hashSet of this.#pendingByPriority.values()){
971
- for (const txHashStr of hashSet){
972
- const meta = this.#metadata.get(txHashStr);
973
- if (meta) {
974
- result.push(meta);
975
- }
976
- }
977
- }
978
- return result;
979
- },
980
- getPendingFeePayers: ()=>{
981
- return Array.from(this.#feePayerToTxHashes.keys());
982
- },
983
- getFeePayerPendingTxs: (feePayer)=>{
984
- return this.#getFeePayerPendingTxs(feePayer);
985
- },
986
- getPendingTxCount: ()=>{
987
- return this.getPendingTxCount();
988
- },
989
- getLowestPriorityPending: (limit)=>{
990
- return this.getLowestPriorityPending(limit).map((h)=>h.toString());
991
- },
992
- deleteTxs: async (txHashes)=>{
993
- await this.#store.transactionAsync(async ()=>{
994
- for (const txHashStr of txHashes){
995
- await this.#deleteTx(txHashStr);
996
- }
997
- });
998
- this.#callbacks.onTxsRemoved(txHashes);
999
- }
722
+ getPendingTxs: ()=>this.#indices.getPendingTxs(),
723
+ getPendingFeePayers: ()=>this.#indices.getPendingFeePayers(),
724
+ getFeePayerPendingTxs: (feePayer)=>this.#indices.getFeePayerPendingTxs(feePayer),
725
+ getPendingTxCount: ()=>this.#indices.getPendingTxCount(),
726
+ getLowestPriorityPending: (limit)=>this.#indices.getLowestPriorityPending(limit),
727
+ deleteTxs: (txHashes)=>this.#deleteTxsBatch(txHashes)
1000
728
  };
1001
729
  }
1002
- /**
1003
- * Creates a PreAddPoolAccess adapter for use with pre-add eviction rules.
1004
- * All methods work with strings and TxMetaData for efficiency.
1005
- */ #createPreAddPoolAccess() {
730
+ #createPreAddPoolAccess() {
1006
731
  return {
1007
732
  getMetadata: (txHashStr)=>{
1008
- const meta = this.#metadata.get(txHashStr);
1009
- if (!meta || this.#getTxState(meta) !== 'pending') {
733
+ const meta = this.#indices.getMetadata(txHashStr);
734
+ if (!meta || this.#indices.getTxState(meta) !== 'pending') {
1010
735
  return undefined;
1011
736
  }
1012
737
  return meta;
1013
738
  },
1014
- getTxHashByNullifier: (nullifier)=>{
1015
- return this.#nullifierToTxHash.get(nullifier);
1016
- },
739
+ getTxHashByNullifier: (nullifier)=>this.#indices.getTxHashByNullifier(nullifier),
1017
740
  getFeePayerBalance: async (feePayer)=>{
1018
741
  const db = this.#worldStateSynchronizer.getCommitted();
1019
742
  const publicStateSource = new DatabasePublicStateSource(db);
1020
743
  const balance = await publicStateSource.storageRead(ProtocolContractAddress.FeeJuice, await computeFeePayerBalanceStorageSlot(AztecAddress.fromString(feePayer)));
1021
744
  return balance.toBigInt();
1022
745
  },
1023
- getFeePayerPendingTxs: (feePayer)=>{
1024
- return this.#getFeePayerPendingTxs(feePayer);
1025
- },
1026
- getPendingTxCount: ()=>{
1027
- return this.getPendingTxCount();
1028
- },
1029
- getLowestPriorityPendingTx: ()=>{
1030
- // Iterate in ascending order to find the lowest priority
1031
- for (const txHashStr of this.#iteratePendingByPriority('asc')){
1032
- const meta = this.#metadata.get(txHashStr);
1033
- if (meta) {
1034
- return meta;
1035
- }
1036
- }
1037
- return undefined;
1038
- }
746
+ getFeePayerPendingTxs: (feePayer)=>this.#indices.getFeePayerPendingTxs(feePayer),
747
+ getPendingTxCount: ()=>this.#indices.getPendingTxCount(),
748
+ getLowestPriorityPendingTx: ()=>this.#indices.getLowestPriorityPendingTx()
1039
749
  };
1040
750
  }
1041
751
  }