@aztec/p2p 0.0.1-commit.7ac86ea28 → 0.0.1-commit.7b86788
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 +4 -5
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +15 -26
- package/dest/client/interface.d.ts +6 -13
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +5 -13
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +3 -58
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -2
- package/dest/config.d.ts +2 -5
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +0 -5
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +2 -0
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +10 -6
- package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/index.js +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +7 -5
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +26 -4
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +48 -7
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +9 -10
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +2 -2
- 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_impl.d.ts +2 -2
- 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 +173 -146
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
- package/dest/msg_validators/tx_validator/factory.d.ts +114 -6
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +219 -58
- package/dest/msg_validators/tx_validator/gas_validator.d.ts +58 -3
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +73 -36
- package/dest/msg_validators/tx_validator/index.d.ts +2 -1
- package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/index.js +1 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
- package/dest/services/dummy_service.d.ts +2 -3
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +1 -4
- package/dest/services/encoding.d.ts +2 -2
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +7 -7
- package/dest/services/libp2p/libp2p_service.d.ts +15 -13
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +64 -80
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -1
- 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 +14 -37
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +17 -11
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/peer_collection.js +49 -15
- package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
- package/dest/services/reqresp/reqresp.d.ts +1 -1
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +2 -1
- package/dest/services/service.d.ts +2 -2
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_provider.d.ts +3 -3
- package/dest/services/tx_provider.d.ts.map +1 -1
- package/dest/services/tx_provider.js +4 -4
- package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +1 -2
- package/dest/test-helpers/mock-pubsub.d.ts +2 -3
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +2 -2
- package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +2 -2
- package/dest/test-helpers/testbench-utils.d.ts +2 -2
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +5 -5
- package/package.json +14 -14
- package/src/client/factory.ts +22 -46
- package/src/client/interface.ts +5 -19
- package/src/client/p2p_client.ts +4 -88
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -2
- package/src/config.ts +1 -9
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -0
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -2
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +10 -6
- package/src/mem_pools/tx_pool_v2/index.ts +1 -1
- package/src/mem_pools/tx_pool_v2/interfaces.ts +7 -4
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +65 -10
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +11 -11
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +1 -1
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +179 -148
- package/src/msg_validators/tx_validator/README.md +115 -0
- package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
- package/src/msg_validators/tx_validator/factory.ts +353 -77
- package/src/msg_validators/tx_validator/gas_validator.ts +90 -27
- package/src/msg_validators/tx_validator/index.ts +1 -0
- package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
- package/src/services/dummy_service.ts +1 -5
- package/src/services/encoding.ts +5 -6
- package/src/services/libp2p/libp2p_service.ts +75 -90
- package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +14 -42
- package/src/services/reqresp/batch-tx-requester/peer_collection.ts +63 -24
- package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
- package/src/services/reqresp/reqresp.ts +3 -1
- package/src/services/service.ts +1 -1
- package/src/services/tx_provider.ts +2 -2
- package/src/test-helpers/make-test-p2p-clients.ts +0 -2
- package/src/test-helpers/mock-pubsub.ts +3 -6
- package/src/test-helpers/reqresp-nodes.ts +2 -5
- package/src/test-helpers/testbench-utils.ts +1 -1
- package/src/testbench/p2p_client_testbench_worker.ts +2 -6
|
@@ -187,6 +187,30 @@ export class TxPoolV2Impl {
|
|
|
187
187
|
const errors = new Map<string, TxPoolRejectionError>();
|
|
188
188
|
const acceptedPending = new Set<string>();
|
|
189
189
|
|
|
190
|
+
// Phase 1: Pre-compute all throwable I/O outside the transaction.
|
|
191
|
+
// If any pre-computation throws, the entire call fails before mutations happen.
|
|
192
|
+
const precomputed = new Map<string, { meta: TxMetaData; minedBlockId: L2BlockId | undefined; isValid: boolean }>();
|
|
193
|
+
|
|
194
|
+
const validator = await this.#createTxValidator();
|
|
195
|
+
|
|
196
|
+
for (const tx of txs) {
|
|
197
|
+
const txHash = tx.getTxHash();
|
|
198
|
+
const txHashStr = txHash.toString();
|
|
199
|
+
|
|
200
|
+
const meta = await buildTxMetaData(tx);
|
|
201
|
+
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
202
|
+
|
|
203
|
+
// Validate non-mined txs (mined and pre-protected txs bypass validation inside the transaction)
|
|
204
|
+
let isValid = true;
|
|
205
|
+
if (!minedBlockId) {
|
|
206
|
+
isValid = await this.#validateMeta(meta, validator);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
precomputed.set(txHashStr, { meta, minedBlockId, isValid });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Phase 2: Apply mutations inside the transaction using only pre-computed results,
|
|
213
|
+
// in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
|
|
190
214
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
191
215
|
const preAddContext: PreAddContext | undefined =
|
|
192
216
|
opts.feeComparisonOnly !== undefined ? { feeComparisonOnly: opts.feeComparisonOnly } : undefined;
|
|
@@ -202,22 +226,25 @@ export class TxPoolV2Impl {
|
|
|
202
226
|
continue;
|
|
203
227
|
}
|
|
204
228
|
|
|
205
|
-
|
|
206
|
-
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
229
|
+
const { meta, minedBlockId, isValid } = precomputed.get(txHashStr)!;
|
|
207
230
|
const preProtectedSlot = this.#indices.getProtectionSlot(txHashStr);
|
|
208
231
|
|
|
209
232
|
if (minedBlockId) {
|
|
210
233
|
// Already mined - add directly (protection already set if pre-protected)
|
|
211
|
-
await this.#addTx(tx, { mined: minedBlockId }, opts);
|
|
234
|
+
await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
|
|
212
235
|
accepted.push(txHash);
|
|
213
236
|
} else if (preProtectedSlot !== undefined) {
|
|
214
237
|
// Pre-protected and not mined - add as protected (bypass validation)
|
|
215
|
-
await this.#addTx(tx, { protected: preProtectedSlot }, opts);
|
|
238
|
+
await this.#addTx(tx, { protected: preProtectedSlot }, opts, meta);
|
|
216
239
|
accepted.push(txHash);
|
|
240
|
+
} else if (!isValid) {
|
|
241
|
+
// Failed pre-computed validation
|
|
242
|
+
rejected.push(txHash);
|
|
217
243
|
} else {
|
|
218
|
-
// Regular pending tx -
|
|
244
|
+
// Regular pending tx - run pre-add rules using pre-computed metadata
|
|
219
245
|
const result = await this.#tryAddRegularPendingTx(
|
|
220
246
|
tx,
|
|
247
|
+
meta,
|
|
221
248
|
opts,
|
|
222
249
|
poolAccess,
|
|
223
250
|
acceptedPending,
|
|
@@ -227,13 +254,18 @@ export class TxPoolV2Impl {
|
|
|
227
254
|
);
|
|
228
255
|
if (result.status === 'accepted') {
|
|
229
256
|
acceptedPending.add(txHashStr);
|
|
230
|
-
} else if (result.status === 'rejected') {
|
|
231
|
-
rejected.push(txHash);
|
|
232
257
|
} else {
|
|
233
258
|
ignored.push(txHash);
|
|
234
259
|
}
|
|
235
260
|
}
|
|
236
261
|
}
|
|
262
|
+
|
|
263
|
+
// Run post-add eviction rules for pending txs (inside transaction for atomicity)
|
|
264
|
+
if (acceptedPending.size > 0) {
|
|
265
|
+
const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
|
|
266
|
+
const uniqueFeePayers = new Set<string>(feePayers);
|
|
267
|
+
await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
|
|
268
|
+
}
|
|
237
269
|
});
|
|
238
270
|
|
|
239
271
|
// Build final accepted list for pending txs (excludes intra-batch evictions)
|
|
@@ -249,37 +281,24 @@ export class TxPoolV2Impl {
|
|
|
249
281
|
this.#instrumentation.recordRejected(rejected.length);
|
|
250
282
|
}
|
|
251
283
|
|
|
252
|
-
// Run post-add eviction rules for pending txs
|
|
253
|
-
if (acceptedPending.size > 0) {
|
|
254
|
-
const feePayers = Array.from(acceptedPending).map(txHash => this.#indices.getMetadata(txHash)!.feePayer);
|
|
255
|
-
const uniqueFeePayers = new Set<string>(feePayers);
|
|
256
|
-
await this.#evictionManager.evictAfterNewTxs(Array.from(acceptedPending), [...uniqueFeePayers]);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
284
|
return { accepted, ignored, rejected, ...(errors.size > 0 ? { errors } : {}) };
|
|
260
285
|
}
|
|
261
286
|
|
|
262
|
-
/**
|
|
287
|
+
/** Adds a validated pending tx, running pre-add rules and evicting conflicts. */
|
|
263
288
|
async #tryAddRegularPendingTx(
|
|
264
289
|
tx: Tx,
|
|
290
|
+
precomputedMeta: TxMetaData,
|
|
265
291
|
opts: { source?: string },
|
|
266
292
|
poolAccess: PreAddPoolAccess,
|
|
267
293
|
acceptedPending: Set<string>,
|
|
268
294
|
ignored: TxHash[],
|
|
269
295
|
errors: Map<string, TxPoolRejectionError>,
|
|
270
296
|
preAddContext?: PreAddContext,
|
|
271
|
-
): Promise<{ status: 'accepted' | 'ignored'
|
|
272
|
-
const
|
|
273
|
-
const txHashStr = txHash.toString();
|
|
274
|
-
|
|
275
|
-
// Build metadata and validate using metadata
|
|
276
|
-
const meta = await buildTxMetaData(tx);
|
|
277
|
-
if (!(await this.#validateMeta(meta))) {
|
|
278
|
-
return { status: 'rejected' };
|
|
279
|
-
}
|
|
297
|
+
): Promise<{ status: 'accepted' | 'ignored' }> {
|
|
298
|
+
const txHashStr = tx.getTxHash().toString();
|
|
280
299
|
|
|
281
300
|
// Run pre-add rules
|
|
282
|
-
const preAddResult = await this.#evictionManager.runPreAddRules(
|
|
301
|
+
const preAddResult = await this.#evictionManager.runPreAddRules(precomputedMeta, poolAccess, preAddContext);
|
|
283
302
|
|
|
284
303
|
if (preAddResult.shouldIgnore) {
|
|
285
304
|
this.#log.debug(`Ignoring tx ${txHashStr}: ${preAddResult.reason?.message ?? 'unknown reason'}`);
|
|
@@ -316,12 +335,18 @@ export class TxPoolV2Impl {
|
|
|
316
335
|
}
|
|
317
336
|
}
|
|
318
337
|
|
|
338
|
+
// Randomly drop the transaction for testing purposes (report as accepted so it propagates)
|
|
339
|
+
if (this.#config.dropTransactionsProbability > 0 && Math.random() < this.#config.dropTransactionsProbability) {
|
|
340
|
+
this.#log.debug(`Dropping tx ${txHashStr} (simulated drop for testing)`);
|
|
341
|
+
return { status: 'accepted' };
|
|
342
|
+
}
|
|
343
|
+
|
|
319
344
|
// Add the transaction
|
|
320
|
-
await this.#addTx(tx, 'pending', opts);
|
|
345
|
+
await this.#addTx(tx, 'pending', opts, precomputedMeta);
|
|
321
346
|
return { status: 'accepted' };
|
|
322
347
|
}
|
|
323
348
|
|
|
324
|
-
async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'
|
|
349
|
+
async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
|
|
325
350
|
const txHashStr = tx.getTxHash().toString();
|
|
326
351
|
|
|
327
352
|
// Check if already in pool
|
|
@@ -329,14 +354,8 @@ export class TxPoolV2Impl {
|
|
|
329
354
|
return 'ignored';
|
|
330
355
|
}
|
|
331
356
|
|
|
332
|
-
// Build metadata and
|
|
357
|
+
// Build metadata and check pre-add rules
|
|
333
358
|
const meta = await buildTxMetaData(tx);
|
|
334
|
-
const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
|
|
335
|
-
if (validationResult !== true) {
|
|
336
|
-
return 'rejected';
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Use pre-add rules
|
|
340
359
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
341
360
|
const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
|
|
342
361
|
|
|
@@ -379,33 +398,35 @@ export class TxPoolV2Impl {
|
|
|
379
398
|
let softDeletedHits = 0;
|
|
380
399
|
let missingPreviouslyEvicted = 0;
|
|
381
400
|
|
|
382
|
-
|
|
383
|
-
const
|
|
401
|
+
await this.#store.transactionAsync(async () => {
|
|
402
|
+
for (const txHash of txHashes) {
|
|
403
|
+
const txHashStr = txHash.toString();
|
|
384
404
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
405
|
+
if (this.#indices.has(txHashStr)) {
|
|
406
|
+
// Update protection for existing tx
|
|
407
|
+
this.#indices.updateProtection(txHashStr, slotNumber);
|
|
408
|
+
} else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
|
|
409
|
+
// Resurrect soft-deleted tx as protected
|
|
410
|
+
const buffer = await this.#txsDB.getAsync(txHashStr);
|
|
411
|
+
if (buffer) {
|
|
412
|
+
const tx = Tx.fromBuffer(buffer);
|
|
413
|
+
await this.#addTx(tx, { protected: slotNumber });
|
|
414
|
+
softDeletedHits++;
|
|
415
|
+
} else {
|
|
416
|
+
// Data missing despite soft-delete flag — treat as truly missing
|
|
417
|
+
this.#indices.setProtection(txHashStr, slotNumber);
|
|
418
|
+
missing.push(txHash);
|
|
419
|
+
}
|
|
395
420
|
} else {
|
|
396
|
-
//
|
|
421
|
+
// Truly missing — pre-record protection for tx we don't have yet
|
|
397
422
|
this.#indices.setProtection(txHashStr, slotNumber);
|
|
398
423
|
missing.push(txHash);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.#indices.setProtection(txHashStr, slotNumber);
|
|
403
|
-
missing.push(txHash);
|
|
404
|
-
if (this.#evictedTxHashes.has(txHashStr)) {
|
|
405
|
-
missingPreviouslyEvicted++;
|
|
424
|
+
if (this.#evictedTxHashes.has(txHashStr)) {
|
|
425
|
+
missingPreviouslyEvicted++;
|
|
426
|
+
}
|
|
406
427
|
}
|
|
407
428
|
}
|
|
408
|
-
}
|
|
429
|
+
});
|
|
409
430
|
|
|
410
431
|
// Record metrics
|
|
411
432
|
if (softDeletedHits > 0) {
|
|
@@ -466,56 +487,60 @@ export class TxPoolV2Impl {
|
|
|
466
487
|
}
|
|
467
488
|
}
|
|
468
489
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
490
|
+
await this.#store.transactionAsync(async () => {
|
|
491
|
+
// Step 4: Mark txs as mined (only those we have in the pool)
|
|
492
|
+
for (const meta of found) {
|
|
493
|
+
this.#indices.markAsMined(meta, blockId);
|
|
494
|
+
await this.#deletedPool.clearIfMinedHigher(meta.txHash, blockId.number);
|
|
495
|
+
}
|
|
474
496
|
|
|
475
|
-
|
|
476
|
-
|
|
497
|
+
// Step 5: Run post-event eviction rules (inside transaction for atomicity)
|
|
498
|
+
await this.#evictionManager.evictAfterNewBlock(block.header, nullifiers, feePayers);
|
|
499
|
+
});
|
|
477
500
|
|
|
478
501
|
this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
|
|
479
502
|
}
|
|
480
503
|
|
|
481
504
|
async prepareForSlot(slotNumber: SlotNumber): Promise<void> {
|
|
482
|
-
|
|
483
|
-
|
|
505
|
+
await this.#store.transactionAsync(async () => {
|
|
506
|
+
// Step 0: Clean up slot-deleted txs from previous slots
|
|
507
|
+
await this.#deletedPool.cleanupSlotDeleted(slotNumber);
|
|
484
508
|
|
|
485
|
-
|
|
486
|
-
|
|
509
|
+
// Step 1: Find expired protected txs
|
|
510
|
+
const expiredProtected = this.#indices.findExpiredProtectedTxs(slotNumber);
|
|
487
511
|
|
|
488
|
-
|
|
489
|
-
|
|
512
|
+
// Step 2: Clear protection for all expired entries (including those without metadata)
|
|
513
|
+
this.#indices.clearProtection(expiredProtected);
|
|
490
514
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
515
|
+
// Step 3: Filter to only txs that have metadata and are not mined
|
|
516
|
+
const txsToRestore = this.#indices.filterRestorable(expiredProtected);
|
|
517
|
+
if (txsToRestore.length === 0) {
|
|
518
|
+
this.#log.debug(`Preparing for slot ${slotNumber}, no txs to unprotect`);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
497
521
|
|
|
498
|
-
|
|
522
|
+
this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
|
|
499
523
|
|
|
500
|
-
|
|
501
|
-
|
|
524
|
+
// Step 4: Validate for pending pool
|
|
525
|
+
const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, 'during prepareForSlot');
|
|
502
526
|
|
|
503
|
-
|
|
504
|
-
|
|
527
|
+
// Step 5: Resolve nullifier conflicts and add winners to pending indices
|
|
528
|
+
const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
|
|
505
529
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
530
|
+
// Step 6: Delete invalid txs and evict conflict losers
|
|
531
|
+
await this.#deleteTxsBatch(invalid);
|
|
532
|
+
await this.#evictTxs(toEvict, 'NullifierConflict');
|
|
509
533
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
534
|
+
// Step 7: Run eviction rules (enforce pool size limit)
|
|
535
|
+
if (added.length > 0) {
|
|
536
|
+
const feePayers = added.map(meta => meta.feePayer);
|
|
537
|
+
const uniqueFeePayers = new Set<string>(feePayers);
|
|
538
|
+
await this.#evictionManager.evictAfterNewTxs(
|
|
539
|
+
added.map(m => m.txHash),
|
|
540
|
+
[...uniqueFeePayers],
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
519
544
|
}
|
|
520
545
|
|
|
521
546
|
async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
|
|
@@ -528,57 +553,60 @@ export class TxPoolV2Impl {
|
|
|
528
553
|
|
|
529
554
|
this.#log.info(`Handling prune to block ${latestBlock.number}: un-mining ${txsToUnmine.length} txs`);
|
|
530
555
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
556
|
+
await this.#store.transactionAsync(async () => {
|
|
557
|
+
// Step 2: Mark ALL un-mined txs with their original mined block number
|
|
558
|
+
// This ensures they get soft-deleted if removed later, and only hard-deleted
|
|
559
|
+
// when their original mined block is finalized
|
|
560
|
+
await this.#deletedPool.markFromPrunedBlock(
|
|
561
|
+
txsToUnmine.map(m => ({
|
|
562
|
+
txHash: m.txHash,
|
|
563
|
+
minedAtBlock: BlockNumber(m.minedL2BlockId!.number),
|
|
564
|
+
})),
|
|
565
|
+
);
|
|
540
566
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
567
|
+
// Step 3: Unmine - clear mined status from metadata
|
|
568
|
+
for (const meta of txsToUnmine) {
|
|
569
|
+
this.#indices.markAsUnmined(meta);
|
|
570
|
+
}
|
|
545
571
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
572
|
+
// If deleteAllTxs is set (epoch prune), delete all un-mined txs and return early
|
|
573
|
+
if (options?.deleteAllTxs) {
|
|
574
|
+
const allTxHashes = txsToUnmine.map(m => m.txHash);
|
|
575
|
+
await this.#deleteTxsBatch(allTxHashes);
|
|
576
|
+
this.#log.info(
|
|
577
|
+
`Handled prune to block ${latestBlock.number} with deleteAllTxs: deleted ${allTxHashes.length} txs`,
|
|
578
|
+
);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
555
581
|
|
|
556
|
-
|
|
557
|
-
|
|
582
|
+
// Step 4: Filter out protected txs (they'll be handled by prepareForSlot)
|
|
583
|
+
const unprotectedTxs = this.#indices.filterUnprotected(txsToUnmine);
|
|
558
584
|
|
|
559
|
-
|
|
560
|
-
|
|
585
|
+
// Step 5: Validate for pending pool
|
|
586
|
+
const { valid, invalid } = await this.#revalidateMetadata(unprotectedTxs, 'during handlePrunedBlocks');
|
|
561
587
|
|
|
562
|
-
|
|
563
|
-
|
|
588
|
+
// Step 6: Resolve nullifier conflicts and add winners to pending indices
|
|
589
|
+
const { toEvict } = this.#applyNullifierConflictResolution(valid);
|
|
564
590
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
591
|
+
// Step 7: Delete invalid txs and evict conflict losers
|
|
592
|
+
await this.#deleteTxsBatch(invalid);
|
|
593
|
+
await this.#evictTxs(toEvict, 'NullifierConflict');
|
|
568
594
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
595
|
+
this.#log.info(
|
|
596
|
+
`Handled prune to block ${latestBlock.number}: ${valid.length} txs restored to pending, ${invalid.length} invalid, ${toEvict.length} evicted due to nullifier conflicts`,
|
|
597
|
+
{ txHashesRestored: valid.map(m => m.txHash), txHashesInvalid: invalid, txHashesEvicted: toEvict },
|
|
598
|
+
);
|
|
573
599
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
600
|
+
// Step 8: Run eviction rules for ALL pending txs (not just restored ones)
|
|
601
|
+
// This handles cases like existing pending txs with invalid fee payer balances
|
|
602
|
+
await this.#evictionManager.evictAfterChainPrune(latestBlock.number);
|
|
603
|
+
});
|
|
577
604
|
}
|
|
578
605
|
|
|
579
606
|
async handleFailedExecution(txHashes: TxHash[]): Promise<void> {
|
|
580
|
-
|
|
581
|
-
|
|
607
|
+
await this.#store.transactionAsync(async () => {
|
|
608
|
+
await this.#deleteTxsBatch(txHashes.map(h => h.toString()));
|
|
609
|
+
});
|
|
582
610
|
|
|
583
611
|
this.#log.info(`Deleted ${txHashes.length} failed txs`, { txHashes: txHashes.map(h => h.toString()) });
|
|
584
612
|
}
|
|
@@ -589,27 +617,29 @@ export class TxPoolV2Impl {
|
|
|
589
617
|
// Step 1: Find mined txs at or before finalized block
|
|
590
618
|
const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(blockNumber);
|
|
591
619
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
620
|
+
await this.#store.transactionAsync(async () => {
|
|
621
|
+
// Step 2: Collect mined txs for archiving (before deletion)
|
|
622
|
+
const txsToArchive: Tx[] = [];
|
|
623
|
+
if (this.#archive.isEnabled()) {
|
|
624
|
+
for (const txHashStr of minedTxsToFinalize) {
|
|
625
|
+
const buffer = await this.#txsDB.getAsync(txHashStr);
|
|
626
|
+
if (buffer) {
|
|
627
|
+
txsToArchive.push(Tx.fromBuffer(buffer));
|
|
628
|
+
}
|
|
599
629
|
}
|
|
600
630
|
}
|
|
601
|
-
}
|
|
602
631
|
|
|
603
|
-
|
|
604
|
-
|
|
632
|
+
// Step 3: Delete mined txs from active pool
|
|
633
|
+
await this.#deleteTxsBatch(minedTxsToFinalize);
|
|
605
634
|
|
|
606
|
-
|
|
607
|
-
|
|
635
|
+
// Step 4: Finalize soft-deleted txs
|
|
636
|
+
await this.#deletedPool.finalizeBlock(blockNumber);
|
|
608
637
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
638
|
+
// Step 5: Archive mined txs
|
|
639
|
+
if (txsToArchive.length > 0) {
|
|
640
|
+
await this.#archive.archiveTxs(txsToArchive);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
613
643
|
|
|
614
644
|
if (minedTxsToFinalize.length > 0) {
|
|
615
645
|
this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${blockNumber}`, {
|
|
@@ -754,9 +784,10 @@ export class TxPoolV2Impl {
|
|
|
754
784
|
tx: Tx,
|
|
755
785
|
state: 'pending' | { protected: SlotNumber } | { mined: L2BlockId },
|
|
756
786
|
opts: { source?: string } = {},
|
|
787
|
+
precomputedMeta?: TxMetaData,
|
|
757
788
|
): Promise<TxMetaData> {
|
|
758
789
|
const txHashStr = tx.getTxHash().toString();
|
|
759
|
-
const meta = await buildTxMetaData(tx);
|
|
790
|
+
const meta = precomputedMeta ?? (await buildTxMetaData(tx));
|
|
760
791
|
meta.receivedAt = this.#dateProvider.now();
|
|
761
792
|
|
|
762
793
|
await this.#txsDB.set(txHashStr, tx.toBuffer());
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Transaction Validation
|
|
2
|
+
|
|
3
|
+
This module defines the transaction validators and the factory functions that assemble them for each entry point into the system.
|
|
4
|
+
|
|
5
|
+
## Validation Strategy
|
|
6
|
+
|
|
7
|
+
Transactions enter the system through different paths. **Unsolicited** transactions (gossip and RPC) are fully validated before acceptance. **Solicited** transactions (req/resp and block proposals) are only checked for well-formedness because we must store them for block re-execution — they may ultimately be invalid, which is caught during block building and reported as part of block validation/attestation.
|
|
8
|
+
|
|
9
|
+
When solicited transactions fail to be mined, they may be migrated to the pending pool. At that point, the pool runs the state-dependent checks that were skipped on initial receipt.
|
|
10
|
+
|
|
11
|
+
## Entry Points
|
|
12
|
+
|
|
13
|
+
### 1. Gossip (libp2p pubsub)
|
|
14
|
+
|
|
15
|
+
**Factory**: `createFirstStageTxValidationsForGossipedTransactions` + `createSecondStageTxValidationsForGossipedTransactions`
|
|
16
|
+
**Called from**: `LibP2PService.handleGossipedTx()` in `libp2p_service.ts`
|
|
17
|
+
|
|
18
|
+
Unsolicited transactions from any peer. Fully validated in two stages with a pool pre-check in between to avoid wasting CPU on proof verification for transactions the pool would reject:
|
|
19
|
+
|
|
20
|
+
| Step | What runs | On failure |
|
|
21
|
+
|------|-----------|------------|
|
|
22
|
+
| **Stage 1** (fast) | TxPermitted, Data, Metadata, Timestamp, DoubleSpend, Gas, Phases, BlockHeader | Penalize peer, reject tx |
|
|
23
|
+
| **Pool pre-check** | `canAddPendingTx` — checks for duplicates, pool capacity | Ignore tx (no penalty) |
|
|
24
|
+
| **Stage 2** (slow) | Proof verification | Penalize peer, reject tx |
|
|
25
|
+
| **Pool add** | `addPendingTxs` | Accept, ignore, or reject |
|
|
26
|
+
|
|
27
|
+
Each stage-1 and stage-2 validator is paired with a `PeerErrorSeverity`. If a validator fails, the sending peer is penalized with that severity. The `doubleSpendValidator` has special handling: its severity is determined by how recently the nullifier appeared (recent = high tolerance, old = low tolerance).
|
|
28
|
+
|
|
29
|
+
### 2. JSON-RPC
|
|
30
|
+
|
|
31
|
+
**Factory**: `createTxValidatorForAcceptingTxsOverRPC`
|
|
32
|
+
**Called from**: `AztecNodeService.isValidTx()` in `aztec-node/server.ts`
|
|
33
|
+
|
|
34
|
+
Unsolicited transactions from a local wallet/PXE. Runs the full set of checks as a single aggregate validator:
|
|
35
|
+
|
|
36
|
+
- TxPermitted, Size, Data, Metadata, Timestamp, DoubleSpend, Phases, BlockHeader
|
|
37
|
+
- Gas (optional — skipped when `skipFeeEnforcement` is set)
|
|
38
|
+
- Proof verification (optional — skipped for simulations when no verifier is provided)
|
|
39
|
+
|
|
40
|
+
### 3. Req/resp and block proposals
|
|
41
|
+
|
|
42
|
+
**Factories**: `createTxValidatorForReqResponseReceivedTxs`, `createTxValidatorForBlockProposalReceivedTxs`
|
|
43
|
+
**Called from**: `LibP2PService.validateRequestedTx()`, `LibP2PService.validateTxsReceivedInBlockProposal()`, and `BatchRequestTxValidator` in `batch-tx-requester/tx_validator.ts`
|
|
44
|
+
|
|
45
|
+
Solicited transactions — we requested these from peers or received them as part of a block proposal we need to validate. We must accept them for re-execution even if they are invalid against the current state. Only well-formedness is checked:
|
|
46
|
+
|
|
47
|
+
- Metadata, Size, Data, Proof
|
|
48
|
+
|
|
49
|
+
State-dependent checks are deferred to either the block building validator (for txs included in blocks) or the pending pool migration validator (for unmined txs migrating to pending).
|
|
50
|
+
|
|
51
|
+
### 4. Block building
|
|
52
|
+
|
|
53
|
+
**Factory**: `createTxValidatorForBlockBuilding`
|
|
54
|
+
**Called from**: `CheckpointBuilder.makeBlockBuilderDeps()` in `validator-client/checkpoint_builder.ts`
|
|
55
|
+
|
|
56
|
+
Transactions already in the pool, about to be sequenced into a block. Re-validates against the current state of the block being built. **This is where invalid txs that entered via req/resp or block proposals are caught** — their invalidity is reported as part of block validation/attestation.
|
|
57
|
+
|
|
58
|
+
Runs:
|
|
59
|
+
- Timestamp, DoubleSpend, Phases, Gas, BlockHeader
|
|
60
|
+
|
|
61
|
+
Does **not** run:
|
|
62
|
+
- Proof, Data — already verified on entry (by gossip, RPC, or req/resp validators)
|
|
63
|
+
|
|
64
|
+
### 5. Pending pool migration
|
|
65
|
+
|
|
66
|
+
**Factory**: `createTxValidatorForTransactionsEnteringPendingTxPool`
|
|
67
|
+
**Called from**: `TxPoolV2Impl` (injected as the `createTxValidator` factory via `TxPoolV2Dependencies`)
|
|
68
|
+
|
|
69
|
+
When transactions that arrived via req/resp or block proposals fail to be mined, they may need to be included in our pending pool. These txs only had well-formedness checks on receipt, so the pool runs the state-dependent checks they missed before accepting them.
|
|
70
|
+
|
|
71
|
+
This validator is invoked on **every** transaction potentially entering the pending pool:
|
|
72
|
+
- `addPendingTxs` — validating each tx before adding
|
|
73
|
+
- `prepareForSlot` — unprotecting txs back to pending after a slot ends
|
|
74
|
+
- `handlePrunedBlocks` — unmining txs from pruned blocks back to pending
|
|
75
|
+
- Startup hydration — revalidating persisted non-mined txs on node restart
|
|
76
|
+
|
|
77
|
+
Runs:
|
|
78
|
+
- DoubleSpend, BlockHeader, GasLimits, Timestamp
|
|
79
|
+
|
|
80
|
+
Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
|
|
81
|
+
|
|
82
|
+
## Individual Validators
|
|
83
|
+
|
|
84
|
+
| Validator | What it checks | Benchmarked verification duration |
|
|
85
|
+
|-----------|---------------|---------------|
|
|
86
|
+
| `TxPermittedValidator` | Whether the system is accepting transactions (controlled by config flag) | 1.56 us |
|
|
87
|
+
| `DataTxValidator` | Transaction data integrity — correct structure, non-empty fields | 4.10–18.18 ms |
|
|
88
|
+
| `SizeTxValidator` | Transaction does not exceed maximum size limits | 2.28 us |
|
|
89
|
+
| `MetadataTxValidator` | Chain ID, rollup version, protocol contracts hash, VK tree root | 4.18 us |
|
|
90
|
+
| `TimestampTxValidator` | Transaction has not expired (expiration timestamp vs next slot) | 1.56 us |
|
|
91
|
+
| `DoubleSpendTxValidator` | Nullifiers do not already exist in the nullifier tree | 106.08 us |
|
|
92
|
+
| `GasTxValidator` | Gas limits are within bounds (delegates to `GasLimitsValidator`), max fee per gas meets current block fees, and fee payer has sufficient FeeJuice balance | 1.02 ms |
|
|
93
|
+
| `GasLimitsValidator` | Gas limits are >= fixed minimums and <= AVM max processable L2 gas. Used standalone in pool migration; also called internally by `GasTxValidator` | 3–10 us |
|
|
94
|
+
| `PhasesTxValidator` | Public function calls in setup phase are on the allow list | 10.12–13.12 us |
|
|
95
|
+
| `BlockHeaderTxValidator` | Transaction's anchor block hash exists in the archive tree | 98.88 us |
|
|
96
|
+
| `TxProofValidator` | Client proof verifies correctly | ~250ms |
|
|
97
|
+
|
|
98
|
+
## Validator Coverage by Entry Point
|
|
99
|
+
|
|
100
|
+
| Validator | Gossip | RPC | Req/resp | Block building | Pool migration |
|
|
101
|
+
|-----------|--------|-----|----------|----------------|----------------|
|
|
102
|
+
| TxPermitted | Stage 1 | Yes | — | — | — |
|
|
103
|
+
| Data | Stage 1 | Yes | Yes | — | — |
|
|
104
|
+
| Size | — | Yes | Yes | — | — |
|
|
105
|
+
| Metadata | Stage 1 | Yes | Yes | — | — |
|
|
106
|
+
| Timestamp | Stage 1 | Yes | — | Yes | Yes |
|
|
107
|
+
| DoubleSpend | Stage 1 | Yes | — | Yes | Yes |
|
|
108
|
+
| Gas (balance + limits) | Stage 1 | Optional* | — | Yes | — |
|
|
109
|
+
| GasLimits (standalone) | — | — | — | — | Yes |
|
|
110
|
+
| Phases | Stage 1 | Yes | — | Yes | — |
|
|
111
|
+
| BlockHeader | Stage 1 | Yes | — | Yes | Yes |
|
|
112
|
+
| Proof | Stage 2 | Optional** | Yes | — | — |
|
|
113
|
+
|
|
114
|
+
\* Gas balance check is skipped when `skipFeeEnforcement` is set (testing/dev). `GasTxValidator` internally delegates to `GasLimitsValidator` as its first step, so gas limits are checked wherever `GasTxValidator` runs. Pool migration uses `GasLimitsValidator` standalone because it doesn't need the balance or fee-per-gas checks.
|
|
115
|
+
\** Proof verification is skipped for simulations (no verifier provided).
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { TxValidationResult, TxValidator } from '@aztec/stdlib/tx';
|
|
2
2
|
|
|
3
3
|
export class AggregateTxValidator<T> implements TxValidator<T> {
|
|
4
|
-
|
|
4
|
+
readonly validators: TxValidator<T>[];
|
|
5
5
|
constructor(...validators: TxValidator<T>[]) {
|
|
6
6
|
if (validators.length === 0) {
|
|
7
7
|
throw new Error('At least one validator must be provided');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
this
|
|
10
|
+
this.validators = validators;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async validateTx(tx: T): Promise<TxValidationResult> {
|
|
14
14
|
const aggregate: { result: string; reason?: string[] } = { result: 'valid', reason: [] };
|
|
15
|
-
for (const validator of this
|
|
15
|
+
for (const validator of this.validators) {
|
|
16
16
|
const result = await validator.validateTx(tx);
|
|
17
17
|
if (result.result === 'invalid') {
|
|
18
18
|
aggregate.result = 'invalid';
|