@aztec/p2p 0.0.1-commit.2e2504e2 → 0.0.1-commit.2eb6648a
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.
- package/dest/client/factory.d.ts +3 -3
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +2 -2
- package/dest/client/interface.d.ts +9 -2
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +4 -3
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +11 -5
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
- package/dest/config.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +94 -87
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +411 -3
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +351 -85
- package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
- package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/index.js +1 -2
- package/dest/mem_pools/index.d.ts +2 -2
- package/dest/mem_pools/index.d.ts.map +1 -1
- package/dest/mem_pools/index.js +1 -1
- package/dest/mem_pools/interface.d.ts +3 -3
- package/dest/mem_pools/interface.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +5 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +99 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +332 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +193 -486
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
- package/dest/services/dummy_service.d.ts +6 -2
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +3 -0
- package/dest/services/libp2p/libp2p_service.d.ts +74 -33
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +299 -228
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -4
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +8 -8
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +6 -4
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.js +16 -11
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +15 -10
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +12 -11
- package/dest/services/service.d.ts +18 -1
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/config.d.ts +3 -3
- package/dest/services/tx_collection/config.js +3 -3
- package/dest/services/tx_collection/fast_tx_collection.d.ts +4 -5
- package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/fast_tx_collection.js +10 -14
- package/dest/services/tx_collection/index.d.ts +1 -1
- package/dest/services/tx_collection/proposal_tx_collector.d.ts +12 -12
- package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
- package/dest/services/tx_collection/proposal_tx_collector.js +4 -5
- package/dest/test-helpers/testbench-utils.d.ts +10 -16
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +32 -30
- package/dest/testbench/p2p_client_testbench_worker.js +1 -1
- package/package.json +14 -14
- package/src/client/factory.ts +3 -4
- package/src/client/interface.ts +13 -1
- package/src/client/p2p_client.ts +20 -8
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
- package/src/mem_pools/attestation_pool/attestation_pool.ts +444 -90
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +436 -100
- package/src/mem_pools/attestation_pool/index.ts +9 -2
- package/src/mem_pools/index.ts +1 -1
- package/src/mem_pools/interface.ts +2 -2
- package/src/mem_pools/tx_pool_v2/README.md +28 -7
- package/src/mem_pools/tx_pool_v2/interfaces.ts +2 -0
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +2 -1
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +417 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +3 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +185 -568
- package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +2 -2
- package/src/services/dummy_service.ts +6 -0
- package/src/services/libp2p/libp2p_service.ts +304 -230
- package/src/services/reqresp/batch-tx-requester/README.md +7 -7
- package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +11 -11
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +22 -13
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +21 -15
- package/src/services/service.ts +20 -0
- package/src/services/tx_collection/config.ts +6 -6
- package/src/services/tx_collection/fast_tx_collection.ts +14 -24
- package/src/services/tx_collection/index.ts +1 -1
- package/src/services/tx_collection/proposal_tx_collector.ts +12 -14
- package/src/test-helpers/testbench-utils.ts +18 -39
- package/src/testbench/p2p_client_testbench_worker.ts +1 -1
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
- package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
- package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
|
@@ -29,14 +29,8 @@ import {
|
|
|
29
29
|
type TxPoolV2Config,
|
|
30
30
|
type TxPoolV2Dependencies,
|
|
31
31
|
} from './interfaces.js';
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
type TxState,
|
|
35
|
-
buildTxMetaData,
|
|
36
|
-
checkNullifierConflict,
|
|
37
|
-
compareFee,
|
|
38
|
-
compareTxHash,
|
|
39
|
-
} from './tx_metadata.js';
|
|
32
|
+
import { type TxMetaData, type TxState, buildTxMetaData, checkNullifierConflict } from './tx_metadata.js';
|
|
33
|
+
import { TxPoolIndices } from './tx_pool_indices.js';
|
|
40
34
|
|
|
41
35
|
/**
|
|
42
36
|
* Callbacks for the implementation to notify the outer class about events and metrics.
|
|
@@ -62,19 +56,7 @@ export class TxPoolV2Impl {
|
|
|
62
56
|
#pendingTxValidator: TxValidator<Tx>;
|
|
63
57
|
|
|
64
58
|
// === In-Memory Indices ===
|
|
65
|
-
|
|
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();
|
|
59
|
+
#indices: TxPoolIndices = new TxPoolIndices();
|
|
78
60
|
|
|
79
61
|
// === Config & Services ===
|
|
80
62
|
#config: TxPoolV2Config;
|
|
@@ -141,19 +123,29 @@ export class TxPoolV2Impl {
|
|
|
141
123
|
await this.#markMinedStatusBatch(loaded.map(l => l.meta));
|
|
142
124
|
|
|
143
125
|
// Step 3: Partition by mined status
|
|
144
|
-
const
|
|
126
|
+
const mined: TxMetaData[] = [];
|
|
127
|
+
const nonMined: { tx: Tx; meta: TxMetaData }[] = [];
|
|
128
|
+
for (const entry of loaded) {
|
|
129
|
+
if (entry.meta.minedL2BlockId !== undefined) {
|
|
130
|
+
mined.push(entry.meta);
|
|
131
|
+
} else {
|
|
132
|
+
nonMined.push(entry);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
145
135
|
|
|
146
136
|
// Step 4: Validate non-mined transactions
|
|
147
|
-
const { valid, invalid } = await this.#
|
|
137
|
+
const { valid, invalid } = await this.#validateTxBatch(nonMined, 'on startup');
|
|
148
138
|
|
|
149
139
|
// Step 5: Populate mined indices (these don't need conflict resolution)
|
|
150
|
-
|
|
140
|
+
for (const meta of mined) {
|
|
141
|
+
this.#indices.addMined(meta);
|
|
142
|
+
}
|
|
151
143
|
|
|
152
144
|
// Step 6: Rebuild pending pool by running pre-add rules for each tx
|
|
153
145
|
// This resolves nullifier conflicts, fee payer balance issues, and pool size limits
|
|
154
146
|
const { rejected } = await this.#rebuildPendingPool(valid);
|
|
155
147
|
|
|
156
|
-
// Step 7: Delete invalid and rejected txs from DB
|
|
148
|
+
// Step 7: Delete invalid and rejected txs from DB only (indices were never populated for these)
|
|
157
149
|
const toDelete = [...deserializationErrors, ...invalid, ...rejected];
|
|
158
150
|
if (toDelete.length === 0) {
|
|
159
151
|
return;
|
|
@@ -170,7 +162,6 @@ export class TxPoolV2Impl {
|
|
|
170
162
|
const accepted: TxHash[] = [];
|
|
171
163
|
const ignored: TxHash[] = [];
|
|
172
164
|
const rejected: TxHash[] = [];
|
|
173
|
-
const newlyAdded: Tx[] = [];
|
|
174
165
|
const acceptedPending = new Set<string>();
|
|
175
166
|
|
|
176
167
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
@@ -181,31 +172,28 @@ export class TxPoolV2Impl {
|
|
|
181
172
|
const txHashStr = txHash.toString();
|
|
182
173
|
|
|
183
174
|
// Skip duplicates
|
|
184
|
-
if (this.#
|
|
175
|
+
if (this.#indices.has(txHashStr)) {
|
|
185
176
|
ignored.push(txHash);
|
|
186
177
|
continue;
|
|
187
178
|
}
|
|
188
179
|
|
|
189
180
|
// Check mined status first (applies to all paths)
|
|
190
181
|
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
191
|
-
const preProtectedSlot = this.#
|
|
182
|
+
const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
|
|
192
183
|
|
|
193
184
|
if (minedBlockId) {
|
|
194
185
|
// Already mined - add directly (protection already set if pre-protected)
|
|
195
|
-
await this.#
|
|
186
|
+
await this.#addTx(tx, { mined: minedBlockId }, opts);
|
|
196
187
|
accepted.push(txHash);
|
|
197
|
-
newlyAdded.push(tx);
|
|
198
188
|
} else if (preProtectedSlot !== undefined) {
|
|
199
189
|
// Pre-protected and not mined - add as protected (bypass validation)
|
|
200
|
-
await this.#
|
|
190
|
+
await this.#addTx(tx, { protected: preProtectedSlot }, opts);
|
|
201
191
|
accepted.push(txHash);
|
|
202
|
-
newlyAdded.push(tx);
|
|
203
192
|
} else {
|
|
204
193
|
// Regular pending tx - validate and run pre-add rules
|
|
205
|
-
const result = await this.#tryAddRegularPendingTx(tx, poolAccess, acceptedPending, ignored);
|
|
194
|
+
const result = await this.#tryAddRegularPendingTx(tx, opts, poolAccess, acceptedPending, ignored);
|
|
206
195
|
if (result.status === 'accepted') {
|
|
207
196
|
acceptedPending.add(txHashStr);
|
|
208
|
-
newlyAdded.push(tx);
|
|
209
197
|
} else if (result.status === 'rejected') {
|
|
210
198
|
rejected.push(txHash);
|
|
211
199
|
} else {
|
|
@@ -222,22 +210,18 @@ export class TxPoolV2Impl {
|
|
|
222
210
|
|
|
223
211
|
// Run post-add eviction rules for pending txs
|
|
224
212
|
if (acceptedPending.size > 0) {
|
|
225
|
-
const feePayers = Array.from(acceptedPending).map(txHash => this.#
|
|
213
|
+
const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
|
|
226
214
|
const uniqueFeePayers = new Set<string>(feePayers);
|
|
227
215
|
await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
|
|
228
216
|
}
|
|
229
217
|
|
|
230
|
-
// Emit events
|
|
231
|
-
if (newlyAdded.length > 0) {
|
|
232
|
-
this.#callbacks.onTxsAdded(newlyAdded, opts);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
218
|
return { accepted, ignored, rejected };
|
|
236
219
|
}
|
|
237
220
|
|
|
238
221
|
/** Validates and adds a regular pending tx. Returns status. */
|
|
239
222
|
async #tryAddRegularPendingTx(
|
|
240
223
|
tx: Tx,
|
|
224
|
+
opts: { source?: string },
|
|
241
225
|
poolAccess: PreAddPoolAccess,
|
|
242
226
|
acceptedPending: Set<string>,
|
|
243
227
|
ignored: TxHash[],
|
|
@@ -246,9 +230,7 @@ export class TxPoolV2Impl {
|
|
|
246
230
|
const txHashStr = txHash.toString();
|
|
247
231
|
|
|
248
232
|
// Validate transaction
|
|
249
|
-
|
|
250
|
-
if (validationResult.result !== 'valid') {
|
|
251
|
-
this.#log.info(`Rejecting tx ${txHashStr}: ${validationResult.reason?.join(', ')}`);
|
|
233
|
+
if (!(await this.#validateTx(tx))) {
|
|
252
234
|
return { status: 'rejected' };
|
|
253
235
|
}
|
|
254
236
|
|
|
@@ -261,18 +243,19 @@ export class TxPoolV2Impl {
|
|
|
261
243
|
return { status: 'ignored' };
|
|
262
244
|
}
|
|
263
245
|
|
|
264
|
-
// Evict conflicts
|
|
246
|
+
// Evict conflicts
|
|
265
247
|
for (const evictHashStr of preAddResult.txHashesToEvict) {
|
|
266
248
|
await this.#deleteTx(evictHashStr);
|
|
267
249
|
this.#log.debug(`Evicted tx ${evictHashStr} due to higher-fee tx ${txHashStr}`);
|
|
268
250
|
if (acceptedPending.has(evictHashStr)) {
|
|
251
|
+
// Evicted tx was from this batch - mark as ignored in result
|
|
269
252
|
acceptedPending.delete(evictHashStr);
|
|
270
253
|
ignored.push(TxHash.fromString(evictHashStr));
|
|
271
254
|
}
|
|
272
255
|
}
|
|
273
256
|
|
|
274
257
|
// Add the transaction
|
|
275
|
-
await this.#
|
|
258
|
+
await this.#addTx(tx, 'pending', opts);
|
|
276
259
|
return { status: 'accepted' };
|
|
277
260
|
}
|
|
278
261
|
|
|
@@ -280,11 +263,11 @@ export class TxPoolV2Impl {
|
|
|
280
263
|
const txHashStr = tx.getTxHash().toString();
|
|
281
264
|
|
|
282
265
|
// Check if already in pool
|
|
283
|
-
if (this.#
|
|
266
|
+
if (this.#indices.has(txHashStr)) {
|
|
284
267
|
return 'ignored';
|
|
285
268
|
}
|
|
286
269
|
|
|
287
|
-
// Validate transaction
|
|
270
|
+
// Validate transaction (no logging for dry-run check)
|
|
288
271
|
const validationResult = await this.#pendingTxValidator.validateTx(tx);
|
|
289
272
|
if (validationResult.result !== 'valid') {
|
|
290
273
|
return 'rejected';
|
|
@@ -300,37 +283,32 @@ export class TxPoolV2Impl {
|
|
|
300
283
|
|
|
301
284
|
async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
|
|
302
285
|
const slotNumber = block.globalVariables.slotNumber;
|
|
303
|
-
const newlyAdded: Tx[] = [];
|
|
304
286
|
|
|
305
287
|
await this.#store.transactionAsync(async () => {
|
|
306
288
|
for (const tx of txs) {
|
|
307
289
|
const txHash = tx.getTxHash();
|
|
308
290
|
const txHashStr = txHash.toString();
|
|
309
|
-
const isNew = !this.#
|
|
291
|
+
const isNew = !this.#indices.has(txHashStr);
|
|
310
292
|
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
311
293
|
|
|
312
294
|
if (isNew) {
|
|
313
|
-
// New tx - add as mined or protected
|
|
295
|
+
// New tx - add as mined or protected (callback emitted by #addTx)
|
|
314
296
|
if (minedBlockId) {
|
|
315
|
-
await this.#
|
|
316
|
-
this.#
|
|
297
|
+
await this.#addTx(tx, { mined: minedBlockId }, opts);
|
|
298
|
+
this.#indices.setProtection(txHashStr, slotNumber);
|
|
317
299
|
} else {
|
|
318
|
-
await this.#
|
|
300
|
+
await this.#addTx(tx, { protected: slotNumber }, opts);
|
|
319
301
|
}
|
|
320
|
-
newlyAdded.push(tx);
|
|
321
302
|
} else {
|
|
322
303
|
// Existing tx - update protection and mined status
|
|
323
|
-
this.#updateProtection(txHashStr, slotNumber);
|
|
304
|
+
this.#indices.updateProtection(txHashStr, slotNumber);
|
|
324
305
|
if (minedBlockId) {
|
|
325
|
-
this.#
|
|
306
|
+
const meta = this.#indices.getMetadata(txHashStr)!;
|
|
307
|
+
this.#indices.markAsMined(meta, minedBlockId);
|
|
326
308
|
}
|
|
327
309
|
}
|
|
328
310
|
}
|
|
329
311
|
});
|
|
330
|
-
|
|
331
|
-
if (newlyAdded.length > 0) {
|
|
332
|
-
this.#callbacks.onTxsAdded(newlyAdded, opts);
|
|
333
|
-
}
|
|
334
312
|
}
|
|
335
313
|
|
|
336
314
|
protectTxs(txHashes: TxHash[], block: BlockHeader): TxHash[] {
|
|
@@ -340,12 +318,12 @@ export class TxPoolV2Impl {
|
|
|
340
318
|
for (const txHash of txHashes) {
|
|
341
319
|
const txHashStr = txHash.toString();
|
|
342
320
|
|
|
343
|
-
if (this.#
|
|
344
|
-
//
|
|
345
|
-
this.#updateProtection(txHashStr, slotNumber);
|
|
321
|
+
if (this.#indices.has(txHashStr)) {
|
|
322
|
+
// Update protection for existing tx
|
|
323
|
+
this.#indices.updateProtection(txHashStr, slotNumber);
|
|
346
324
|
} else {
|
|
347
|
-
//
|
|
348
|
-
this.#
|
|
325
|
+
// Pre-record protection for tx we don't have yet
|
|
326
|
+
this.#indices.setProtection(txHashStr, slotNumber);
|
|
349
327
|
missing.push(txHash);
|
|
350
328
|
}
|
|
351
329
|
}
|
|
@@ -356,28 +334,21 @@ export class TxPoolV2Impl {
|
|
|
356
334
|
async addMinedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
|
|
357
335
|
// Step 1: Build block ID
|
|
358
336
|
const blockId = await this.#buildBlockId(block);
|
|
359
|
-
const newlyAdded: Tx[] = [];
|
|
360
337
|
|
|
361
338
|
await this.#store.transactionAsync(async () => {
|
|
362
339
|
for (const tx of txs) {
|
|
363
340
|
const txHashStr = tx.getTxHash().toString();
|
|
364
|
-
const existingMeta = this.#
|
|
341
|
+
const existingMeta = this.#indices.getMetadata(txHashStr);
|
|
365
342
|
|
|
366
343
|
if (existingMeta) {
|
|
367
|
-
//
|
|
368
|
-
this.#markAsMined(existingMeta, blockId);
|
|
344
|
+
// Mark existing tx as mined
|
|
345
|
+
this.#indices.markAsMined(existingMeta, blockId);
|
|
369
346
|
} else {
|
|
370
|
-
//
|
|
371
|
-
await this.#
|
|
372
|
-
newlyAdded.push(tx);
|
|
347
|
+
// Add new mined tx (callback emitted by #addTx)
|
|
348
|
+
await this.#addTx(tx, { mined: blockId }, opts);
|
|
373
349
|
}
|
|
374
350
|
}
|
|
375
351
|
});
|
|
376
|
-
|
|
377
|
-
// Step 3: Emit events for newly added txs
|
|
378
|
-
if (newlyAdded.length > 0) {
|
|
379
|
-
this.#callbacks.onTxsAdded(newlyAdded, opts);
|
|
380
|
-
}
|
|
381
352
|
}
|
|
382
353
|
|
|
383
354
|
async handleMinedBlock(block: L2Block): Promise<void> {
|
|
@@ -392,7 +363,7 @@ export class TxPoolV2Impl {
|
|
|
392
363
|
const feePayers: string[] = [];
|
|
393
364
|
const found: TxMetaData[] = [];
|
|
394
365
|
for (const txHash of txHashes) {
|
|
395
|
-
const meta = this.#
|
|
366
|
+
const meta = this.#indices.getMetadata(txHash.toString());
|
|
396
367
|
if (meta) {
|
|
397
368
|
feePayers.push(meta.feePayer);
|
|
398
369
|
found.push(meta);
|
|
@@ -400,24 +371,25 @@ export class TxPoolV2Impl {
|
|
|
400
371
|
}
|
|
401
372
|
|
|
402
373
|
// Step 4: Mark txs as mined (only those we have in the pool)
|
|
403
|
-
|
|
374
|
+
for (const meta of found) {
|
|
375
|
+
this.#indices.markAsMined(meta, blockId);
|
|
376
|
+
}
|
|
404
377
|
|
|
405
378
|
// Step 5: Run eviction rules (remove pending txs with conflicting nullifiers/expired timestamps)
|
|
406
379
|
await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
|
|
407
380
|
|
|
408
|
-
this.#callbacks.onTxsRemoved(txHashes.map(h => h.toBigInt()));
|
|
409
381
|
this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
|
|
410
382
|
}
|
|
411
383
|
|
|
412
384
|
async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
|
|
413
385
|
// Step 1: Find expired protected txs
|
|
414
|
-
const expiredProtected = this.#findExpiredProtectedTxs(slotNumber);
|
|
386
|
+
const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
|
|
415
387
|
|
|
416
388
|
// Step 2: Clear protection for all expired entries (including those without metadata)
|
|
417
|
-
this.#clearProtection(expiredProtected);
|
|
389
|
+
this.#indices.clearProtection(expiredProtected);
|
|
418
390
|
|
|
419
391
|
// Step 3: Filter to only txs that have metadata and are not mined
|
|
420
|
-
const txsToRestore = this.#filterRestorable(expiredProtected);
|
|
392
|
+
const txsToRestore = this.#indices.filterRestorable(expiredProtected);
|
|
421
393
|
if (txsToRestore.length === 0) {
|
|
422
394
|
return;
|
|
423
395
|
}
|
|
@@ -425,7 +397,7 @@ export class TxPoolV2Impl {
|
|
|
425
397
|
this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
|
|
426
398
|
|
|
427
399
|
// Step 4: Validate for pending pool
|
|
428
|
-
const { valid, invalid } = await this.#
|
|
400
|
+
const { valid, invalid } = await this.#loadAndValidateTxs(txsToRestore, 'during prepareForSlot');
|
|
429
401
|
|
|
430
402
|
// Step 5: Resolve nullifier conflicts and add winners to pending indices
|
|
431
403
|
const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
|
|
@@ -446,7 +418,7 @@ export class TxPoolV2Impl {
|
|
|
446
418
|
|
|
447
419
|
async handlePrunedBlocks(latestBlock: L2BlockId): Promise<void> {
|
|
448
420
|
// Step 1: Find transactions mined after the prune point
|
|
449
|
-
const txsToUnmine = this.#findTxsMinedAfter(latestBlock.number);
|
|
421
|
+
const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
|
|
450
422
|
if (txsToUnmine.length === 0) {
|
|
451
423
|
this.#log.debug(`No transactions to un-mine for prune to block ${latestBlock.number}`);
|
|
452
424
|
return;
|
|
@@ -455,13 +427,15 @@ export class TxPoolV2Impl {
|
|
|
455
427
|
this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
|
|
456
428
|
|
|
457
429
|
// Step 2: Unmine - clear mined status from metadata
|
|
458
|
-
|
|
430
|
+
for (const meta of txsToUnmine) {
|
|
431
|
+
this.#indices.markAsUnmined(meta);
|
|
432
|
+
}
|
|
459
433
|
|
|
460
434
|
// Step 3: Filter out protected txs (they'll be handled by prepareForSlot)
|
|
461
|
-
const unprotectedTxs = this.#filterUnprotected(txsToUnmine);
|
|
435
|
+
const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
|
|
462
436
|
|
|
463
437
|
// Step 4: Validate for pending pool
|
|
464
|
-
const { valid, invalid } = await this.#
|
|
438
|
+
const { valid, invalid } = await this.#loadAndValidateTxs(unprotectedTxs, 'during handlePrunedBlocks');
|
|
465
439
|
|
|
466
440
|
// Step 5: Resolve nullifier conflicts and add winners to pending indices
|
|
467
441
|
const { toEvict } = this.#applyNullifierConflictResolution(valid);
|
|
@@ -475,7 +449,7 @@ export class TxPoolV2Impl {
|
|
|
475
449
|
}
|
|
476
450
|
|
|
477
451
|
async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
|
|
478
|
-
//
|
|
452
|
+
// Delete failed txs
|
|
479
453
|
await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
|
|
480
454
|
|
|
481
455
|
this.#log.info(`Deleted ${txHashes.length} failed txs`);
|
|
@@ -485,7 +459,7 @@ export class TxPoolV2Impl {
|
|
|
485
459
|
const blockNumber = block.globalVariables.blockNumber;
|
|
486
460
|
|
|
487
461
|
// Step 1: Find txs mined at or before finalized block
|
|
488
|
-
const txsToFinalize = this.#findTxsMinedAtOrBefore(blockNumber);
|
|
462
|
+
const txsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
|
|
489
463
|
if (txsToFinalize.length === 0) {
|
|
490
464
|
return;
|
|
491
465
|
}
|
|
@@ -529,42 +503,32 @@ export class TxPoolV2Impl {
|
|
|
529
503
|
}
|
|
530
504
|
|
|
531
505
|
hasTxs(txHashes: TxHash[]): boolean[] {
|
|
532
|
-
return txHashes.map(h => this.#
|
|
506
|
+
return txHashes.map(h => this.#indices.has(h.toString()));
|
|
533
507
|
}
|
|
534
508
|
|
|
535
509
|
getTxStatus(txHash: TxHash): TxState | undefined {
|
|
536
|
-
const meta = this.#
|
|
510
|
+
const meta = this.#indices.getMetadata(txHash.toString());
|
|
537
511
|
if (!meta) {
|
|
538
512
|
return undefined;
|
|
539
513
|
}
|
|
540
|
-
return this.#getTxState(meta);
|
|
514
|
+
return this.#indices.getTxState(meta);
|
|
541
515
|
}
|
|
542
516
|
|
|
543
517
|
getPendingTxHashes(): TxHash[] {
|
|
544
|
-
return [...this.#iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
|
|
518
|
+
return [...this.#indices.iteratePendingByPriority('desc')].map(hash => TxHash.fromString(hash));
|
|
545
519
|
}
|
|
546
520
|
|
|
547
521
|
getPendingTxCount(): number {
|
|
548
|
-
|
|
549
|
-
for (const hashes of this.#pendingByPriority.values()) {
|
|
550
|
-
count += hashes.size;
|
|
551
|
-
}
|
|
552
|
-
return count;
|
|
522
|
+
return this.#indices.getPendingTxCount();
|
|
553
523
|
}
|
|
554
524
|
|
|
555
525
|
getMinedTxHashes(): [TxHash, L2BlockId][] {
|
|
556
|
-
|
|
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;
|
|
526
|
+
return this.#indices.getMinedTxs().map(([hash, blockId]) => [TxHash.fromString(hash), blockId]);
|
|
563
527
|
}
|
|
564
528
|
|
|
565
529
|
getMinedTxCount(): number {
|
|
566
530
|
let count = 0;
|
|
567
|
-
for (const meta of this.#
|
|
531
|
+
for (const [, meta] of this.#indices.iterateMetadata()) {
|
|
568
532
|
if (meta.minedL2BlockId !== undefined) {
|
|
569
533
|
count++;
|
|
570
534
|
}
|
|
@@ -573,11 +537,11 @@ export class TxPoolV2Impl {
|
|
|
573
537
|
}
|
|
574
538
|
|
|
575
539
|
isEmpty(): boolean {
|
|
576
|
-
return this.#
|
|
540
|
+
return this.#indices.isEmpty();
|
|
577
541
|
}
|
|
578
542
|
|
|
579
543
|
getTxCount(): number {
|
|
580
|
-
return this.#
|
|
544
|
+
return this.#indices.getTxCount();
|
|
581
545
|
}
|
|
582
546
|
|
|
583
547
|
getArchivedTxByHash(txHash: TxHash): Promise<Tx | undefined> {
|
|
@@ -585,18 +549,7 @@ export class TxPoolV2Impl {
|
|
|
585
549
|
}
|
|
586
550
|
|
|
587
551
|
getLowestPriorityPending(limit: number): TxHash[] {
|
|
588
|
-
|
|
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;
|
|
552
|
+
return this.#indices.getLowestPriorityPending(limit).map(h => TxHash.fromString(h));
|
|
600
553
|
}
|
|
601
554
|
|
|
602
555
|
// === Configuration ===
|
|
@@ -617,155 +570,118 @@ export class TxPoolV2Impl {
|
|
|
617
570
|
|
|
618
571
|
getPoolReadAccess(): PoolReadAccess {
|
|
619
572
|
return {
|
|
620
|
-
getMetadata: (txHash: string) => this.#
|
|
621
|
-
getTxHashByNullifier: (nullifier: string) => this.#
|
|
622
|
-
getTxHashesByFeePayer: (feePayer: string) => this.#
|
|
623
|
-
getPendingTxCount: () => this.getPendingTxCount(),
|
|
573
|
+
getMetadata: (txHash: string) => this.#indices.getMetadata(txHash),
|
|
574
|
+
getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
|
|
575
|
+
getTxHashesByFeePayer: (feePayer: string) => this.#indices.getTxHashesByFeePayer(feePayer),
|
|
576
|
+
getPendingTxCount: () => this.#indices.getPendingTxCount(),
|
|
624
577
|
};
|
|
625
578
|
}
|
|
626
579
|
|
|
627
580
|
// === Metrics ===
|
|
628
581
|
|
|
629
582
|
countTxs(): { pending: number; protected: number; mined: number } {
|
|
630
|
-
|
|
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 };
|
|
583
|
+
return this.#indices.countTxs();
|
|
646
584
|
}
|
|
647
585
|
|
|
648
586
|
// ============================================================================
|
|
649
|
-
// PRIVATE
|
|
587
|
+
// PRIVATE HELPERS - Transaction Management
|
|
650
588
|
// ============================================================================
|
|
651
589
|
|
|
652
590
|
/**
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* - 'mined' if it has a minedL2BlockId
|
|
656
|
-
* - 'protected' if it's in the protectedTransactions map (but not mined)
|
|
657
|
-
* - 'pending' otherwise
|
|
591
|
+
* Adds a new transaction to the pool with the specified state.
|
|
592
|
+
* Emits onTxsAdded callback immediately after DB write.
|
|
658
593
|
*/
|
|
659
|
-
#
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
594
|
+
async #addTx(
|
|
595
|
+
tx: Tx,
|
|
596
|
+
state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
|
|
597
|
+
opts: { source?: string } = {},
|
|
598
|
+
): Promise<TxMetaData> {
|
|
599
|
+
const txHashStr = tx.getTxHash().toString();
|
|
600
|
+
const meta = await buildTxMetaData(tx);
|
|
601
|
+
|
|
602
|
+
await this.#txsDB.set(txHashStr, tx.toBuffer());
|
|
603
|
+
this.#callbacks.onTxsAdded([tx], opts);
|
|
604
|
+
|
|
605
|
+
if (state === 'pending') {
|
|
606
|
+
this.#indices.addPending(meta);
|
|
607
|
+
} else if ('protected' in state) {
|
|
608
|
+
this.#indices.addProtected(meta, state.protected);
|
|
664
609
|
} else {
|
|
665
|
-
|
|
610
|
+
meta.minedL2BlockId = state.mined;
|
|
611
|
+
this.#indices.addMined(meta);
|
|
666
612
|
}
|
|
613
|
+
|
|
614
|
+
const stateStr = typeof state === 'string' ? state : Object.keys(state)[0];
|
|
615
|
+
this.#log.verbose(`Added ${stateStr} tx ${txHashStr}`, {
|
|
616
|
+
eventName: 'tx-added-to-pool',
|
|
617
|
+
state: stateStr,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
return meta;
|
|
667
621
|
}
|
|
668
622
|
|
|
669
623
|
/**
|
|
670
|
-
*
|
|
671
|
-
*
|
|
624
|
+
* Deletes a transaction from both indices and DB.
|
|
625
|
+
* Emits onTxsRemoved callback immediately after DB delete.
|
|
672
626
|
*/
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const hashesAtFee = this.#pendingByPriority.get(fee)!;
|
|
684
|
-
const sortedHashes = [...hashesAtFee].sort(hashCompareFn);
|
|
685
|
-
for (const hash of sortedHashes) {
|
|
686
|
-
yield hash;
|
|
687
|
-
}
|
|
627
|
+
async #deleteTx(txHashStr: string): Promise<void> {
|
|
628
|
+
this.#indices.remove(txHashStr);
|
|
629
|
+
await this.#txsDB.delete(txHashStr);
|
|
630
|
+
this.#callbacks.onTxsRemoved([txHashStr]);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/** Deletes a batch of transactions, emitting callbacks individually for each. */
|
|
634
|
+
async #deleteTxsBatch(txHashes: string[]): Promise<void> {
|
|
635
|
+
for (const txHashStr of txHashes) {
|
|
636
|
+
await this.#deleteTx(txHashStr);
|
|
688
637
|
}
|
|
689
638
|
}
|
|
690
639
|
|
|
691
640
|
// ============================================================================
|
|
692
|
-
//
|
|
641
|
+
// PRIVATE HELPERS - Validation & Conflict Resolution
|
|
693
642
|
// ============================================================================
|
|
694
643
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
result.push(meta);
|
|
703
|
-
}
|
|
644
|
+
/** Validates a single transaction, returning true if valid */
|
|
645
|
+
async #validateTx(tx: Tx, context?: string): Promise<boolean> {
|
|
646
|
+
const result = await this.#pendingTxValidator.validateTx(tx);
|
|
647
|
+
if (result.result !== 'valid') {
|
|
648
|
+
const contextStr = context ? ` ${context}` : '';
|
|
649
|
+
this.#log.info(`Tx ${tx.getTxHash()}${contextStr} failed validation: ${result.reason?.join(', ')}`);
|
|
650
|
+
return false;
|
|
704
651
|
}
|
|
705
|
-
return
|
|
652
|
+
return true;
|
|
706
653
|
}
|
|
707
654
|
|
|
708
|
-
/**
|
|
709
|
-
#
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
if (meta.minedL2BlockId !== undefined && meta.minedL2BlockId.number <= blockNumber) {
|
|
713
|
-
result.push(txHashStr);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
return result;
|
|
717
|
-
}
|
|
655
|
+
/** Loads transactions from DB, returning loaded txs and missing hashes */
|
|
656
|
+
async #loadTxsFromDb(metas: TxMetaData[]): Promise<{ loaded: { tx: Tx; meta: TxMetaData }[]; missing: string[] }> {
|
|
657
|
+
const loaded: { tx: Tx; meta: TxMetaData }[] = [];
|
|
658
|
+
const missing: string[] = [];
|
|
718
659
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
660
|
+
for (const meta of metas) {
|
|
661
|
+
const buffer = await this.#txsDB.getAsync(meta.txHash);
|
|
662
|
+
if (!buffer) {
|
|
663
|
+
this.#log.warn(`Tx ${meta.txHash} not found in DB`);
|
|
664
|
+
missing.push(meta.txHash);
|
|
665
|
+
continue;
|
|
725
666
|
}
|
|
667
|
+
loaded.push({ tx: Tx.fromBuffer(buffer), meta });
|
|
726
668
|
}
|
|
727
|
-
return result;
|
|
728
|
-
}
|
|
729
669
|
|
|
730
|
-
|
|
731
|
-
#filterUnprotected(txs: TxMetaData[]): TxMetaData[] {
|
|
732
|
-
return txs.filter(meta => !this.#protectedTransactions.has(meta.txHash));
|
|
670
|
+
return { loaded, missing };
|
|
733
671
|
}
|
|
734
672
|
|
|
735
|
-
/**
|
|
736
|
-
#
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (meta && meta.minedL2BlockId === undefined) {
|
|
741
|
-
result.push(meta);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
return result;
|
|
745
|
-
}
|
|
746
|
-
|
|
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[] }> {
|
|
673
|
+
/** Validates a batch of transactions, returning valid and invalid groups */
|
|
674
|
+
async #validateTxBatch(
|
|
675
|
+
txs: { tx: Tx; meta: TxMetaData }[],
|
|
676
|
+
context?: string,
|
|
677
|
+
): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
|
|
751
678
|
const valid: TxMetaData[] = [];
|
|
752
679
|
const invalid: string[] = [];
|
|
753
680
|
|
|
754
|
-
for (const meta of txs) {
|
|
755
|
-
|
|
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') {
|
|
681
|
+
for (const { tx, meta } of txs) {
|
|
682
|
+
if (await this.#validateTx(tx, context)) {
|
|
766
683
|
valid.push(meta);
|
|
767
684
|
} else {
|
|
768
|
-
this.#log.info(`Tx ${meta.txHash} failed validation: ${result.reason?.join(', ')}`);
|
|
769
685
|
invalid.push(meta.txHash);
|
|
770
686
|
}
|
|
771
687
|
}
|
|
@@ -773,6 +689,16 @@ export class TxPoolV2Impl {
|
|
|
773
689
|
return { valid, invalid };
|
|
774
690
|
}
|
|
775
691
|
|
|
692
|
+
/** Loads transactions from DB and validates them */
|
|
693
|
+
async #loadAndValidateTxs(
|
|
694
|
+
metas: TxMetaData[],
|
|
695
|
+
context?: string,
|
|
696
|
+
): Promise<{ valid: TxMetaData[]; invalid: string[] }> {
|
|
697
|
+
const { loaded, missing } = await this.#loadTxsFromDb(metas);
|
|
698
|
+
const { valid, invalid } = await this.#validateTxBatch(loaded, context);
|
|
699
|
+
return { valid, invalid: [...missing, ...invalid] };
|
|
700
|
+
}
|
|
701
|
+
|
|
776
702
|
/**
|
|
777
703
|
* Resolves nullifier conflicts between incoming txs and existing pending txs.
|
|
778
704
|
* Modifies the pending indices during iteration to maintain consistent state
|
|
@@ -785,8 +711,8 @@ export class TxPoolV2Impl {
|
|
|
785
711
|
for (const meta of txs) {
|
|
786
712
|
const conflict = checkNullifierConflict(
|
|
787
713
|
meta,
|
|
788
|
-
nullifier => this.#
|
|
789
|
-
txHash => this.#
|
|
714
|
+
nullifier => this.#indices.getTxHashByNullifier(nullifier),
|
|
715
|
+
txHash => this.#indices.getMetadata(txHash),
|
|
790
716
|
);
|
|
791
717
|
if (conflict.shouldIgnore) {
|
|
792
718
|
// Lower priority than existing - don't add, mark for deletion
|
|
@@ -796,13 +722,13 @@ export class TxPoolV2Impl {
|
|
|
796
722
|
toEvict.push(...conflict.txHashesToEvict);
|
|
797
723
|
// Remove evicted from indices immediately for subsequent checks
|
|
798
724
|
for (const evictHash of conflict.txHashesToEvict) {
|
|
799
|
-
const evictMeta = this.#
|
|
725
|
+
const evictMeta = this.#indices.getMetadata(evictHash);
|
|
800
726
|
if (evictMeta) {
|
|
801
|
-
this.#removeFromPendingIndices(evictMeta);
|
|
727
|
+
this.#indices.removeFromPendingIndices(evictMeta);
|
|
802
728
|
}
|
|
803
729
|
}
|
|
804
730
|
// Add to pending indices immediately so subsequent txs in the batch see this tx
|
|
805
|
-
this.#addToPendingIndices(meta);
|
|
731
|
+
this.#indices.addToPendingIndices(meta);
|
|
806
732
|
added.push(meta);
|
|
807
733
|
}
|
|
808
734
|
}
|
|
@@ -810,43 +736,10 @@ export class TxPoolV2Impl {
|
|
|
810
736
|
return { added, toEvict };
|
|
811
737
|
}
|
|
812
738
|
|
|
813
|
-
//
|
|
814
|
-
|
|
815
|
-
|
|
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 ---
|
|
739
|
+
// ============================================================================
|
|
740
|
+
// PRIVATE HELPERS - Block & Hydration
|
|
741
|
+
// ============================================================================
|
|
848
742
|
|
|
849
|
-
/** Builds a block ID from a block header */
|
|
850
743
|
async #buildBlockId(block: BlockHeader): Promise<L2BlockId> {
|
|
851
744
|
return {
|
|
852
745
|
number: block.globalVariables.blockNumber,
|
|
@@ -866,50 +759,6 @@ export class TxPoolV2Impl {
|
|
|
866
759
|
};
|
|
867
760
|
}
|
|
868
761
|
|
|
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
762
|
/** Loads all transactions from the database, returning loaded txs and deserialization errors */
|
|
914
763
|
async #loadAllTxsFromDb(): Promise<{
|
|
915
764
|
loaded: { tx: Tx; meta: TxMetaData }[];
|
|
@@ -949,50 +798,6 @@ export class TxPoolV2Impl {
|
|
|
949
798
|
}
|
|
950
799
|
}
|
|
951
800
|
|
|
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
801
|
/**
|
|
997
802
|
* Rebuilds the pending pool by processing each tx through pre-add rules.
|
|
998
803
|
* Starts with an empty pending pool and adds txs one by one, resolving conflicts.
|
|
@@ -1016,18 +821,18 @@ export class TxPoolV2Impl {
|
|
|
1016
821
|
|
|
1017
822
|
// Evict any conflicting txs identified by pre-add rules
|
|
1018
823
|
for (const evictHashStr of preAddResult.txHashesToEvict) {
|
|
1019
|
-
const evictMeta = this.#
|
|
824
|
+
const evictMeta = this.#indices.getMetadata(evictHashStr);
|
|
1020
825
|
if (evictMeta) {
|
|
1021
|
-
this.#removeFromPendingIndices(evictMeta);
|
|
1022
|
-
this.#
|
|
826
|
+
this.#indices.removeFromPendingIndices(evictMeta);
|
|
827
|
+
this.#indices.remove(evictHashStr);
|
|
1023
828
|
rejected.push(evictHashStr);
|
|
1024
829
|
accepted.delete(evictHashStr);
|
|
1025
830
|
this.#log.debug(`Evicted tx ${evictHashStr} during rebuild due to conflict with ${meta.txHash}`);
|
|
1026
831
|
}
|
|
1027
832
|
}
|
|
1028
833
|
|
|
1029
|
-
// Add to
|
|
1030
|
-
this.#
|
|
834
|
+
// Add to indices
|
|
835
|
+
this.#indices.addPending(meta);
|
|
1031
836
|
accepted.add(meta.txHash);
|
|
1032
837
|
}
|
|
1033
838
|
|
|
@@ -1035,207 +840,32 @@ export class TxPoolV2Impl {
|
|
|
1035
840
|
return { accepted: [...accepted], rejected };
|
|
1036
841
|
}
|
|
1037
842
|
|
|
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
843
|
// ============================================================================
|
|
1062
|
-
//
|
|
844
|
+
// PRIVATE HELPERS - Pool Access Adapters
|
|
1063
845
|
// ============================================================================
|
|
1064
846
|
|
|
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
|
-
// ============================================================================
|
|
1163
|
-
// HELPER FUNCTIONS - Adapters
|
|
1164
|
-
// ============================================================================
|
|
1165
|
-
|
|
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
847
|
#createPoolOperations(): PoolOperations {
|
|
1186
848
|
return {
|
|
1187
|
-
getPendingTxs: ()
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
-
},
|
|
849
|
+
getPendingTxs: () => this.#indices.getPendingTxs(),
|
|
850
|
+
getPendingFeePayers: () => this.#indices.getPendingFeePayers(),
|
|
851
|
+
getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
|
|
852
|
+
getPendingTxCount: () => this.#indices.getPendingTxCount(),
|
|
853
|
+
getLowestPriorityPending: (limit: number) => this.#indices.getLowestPriorityPending(limit),
|
|
854
|
+
deleteTxs: (txHashes: string[]) => this.#deleteTxsBatch(txHashes),
|
|
1219
855
|
};
|
|
1220
856
|
}
|
|
1221
857
|
|
|
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
858
|
#createPreAddPoolAccess(): PreAddPoolAccess {
|
|
1227
859
|
return {
|
|
1228
|
-
getMetadata: (txHashStr: string)
|
|
1229
|
-
const meta = this.#
|
|
1230
|
-
if (!meta || this.#getTxState(meta) !== 'pending') {
|
|
860
|
+
getMetadata: (txHashStr: string) => {
|
|
861
|
+
const meta = this.#indices.getMetadata(txHashStr);
|
|
862
|
+
if (!meta || this.#indices.getTxState(meta) !== 'pending') {
|
|
1231
863
|
return undefined;
|
|
1232
864
|
}
|
|
1233
865
|
return meta;
|
|
1234
866
|
},
|
|
1235
|
-
getTxHashByNullifier: (nullifier: string)
|
|
1236
|
-
|
|
1237
|
-
},
|
|
1238
|
-
getFeePayerBalance: async (feePayer: string): Promise<bigint> => {
|
|
867
|
+
getTxHashByNullifier: (nullifier: string) => this.#indices.getTxHashByNullifier(nullifier),
|
|
868
|
+
getFeePayerBalance: async (feePayer: string) => {
|
|
1239
869
|
const db = this.#worldStateSynchronizer.getCommitted();
|
|
1240
870
|
const publicStateSource = new DatabasePublicStateSource(db);
|
|
1241
871
|
const balance = await publicStateSource.storageRead(
|
|
@@ -1244,22 +874,9 @@ export class TxPoolV2Impl {
|
|
|
1244
874
|
);
|
|
1245
875
|
return balance.toBigInt();
|
|
1246
876
|
},
|
|
1247
|
-
getFeePayerPendingTxs: (feePayer: string)
|
|
1248
|
-
|
|
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
|
-
},
|
|
877
|
+
getFeePayerPendingTxs: (feePayer: string) => this.#indices.getFeePayerPendingTxs(feePayer),
|
|
878
|
+
getPendingTxCount: () => this.#indices.getPendingTxCount(),
|
|
879
|
+
getLowestPriorityPendingTx: () => this.#indices.getLowestPriorityPendingTx(),
|
|
1263
880
|
};
|
|
1264
881
|
}
|
|
1265
882
|
}
|