@aztec/p2p 0.0.1-commit.b9865e97 → 0.0.1-commit.be03c316
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/README.md +7 -6
- package/dest/bootstrap/bootstrap.d.ts +1 -1
- package/dest/bootstrap/bootstrap.d.ts.map +1 -1
- package/dest/bootstrap/bootstrap.js +20 -12
- package/dest/client/factory.d.ts +2 -2
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +10 -5
- package/dest/client/interface.d.ts +36 -8
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +23 -12
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +38 -35
- package/dest/config.d.ts +19 -13
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +27 -26
- package/dest/errors/reqresp.error.d.ts +13 -1
- package/dest/errors/reqresp.error.d.ts.map +1 -1
- package/dest/errors/reqresp.error.js +14 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +4 -2
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +9 -2
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +1 -1
- 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 +43 -0
- package/dest/mem_pools/instrumentation.d.ts +9 -4
- package/dest/mem_pools/instrumentation.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.js +11 -8
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -3
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/deleted_pool.js +6 -6
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +30 -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_pool_indices.d.ts +15 -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 +31 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +9 -3
- 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 +19 -8
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +17 -4
- 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 +133 -34
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +6 -5
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +24 -26
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts +4 -4
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.js +2 -2
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +4 -3
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/block_proposal_validator.js +2 -2
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +4 -3
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +8 -2
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +19 -5
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +66 -30
- package/dest/msg_validators/tx_validator/factory.d.ts +9 -11
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +15 -6
- package/dest/msg_validators/tx_validator/gas_validator.d.ts +12 -7
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +15 -15
- package/dest/msg_validators/tx_validator/phases_validator.js +2 -2
- package/dest/services/discv5/discV5_service.d.ts +12 -2
- package/dest/services/discv5/discV5_service.d.ts.map +1 -1
- package/dest/services/discv5/discV5_service.js +84 -15
- package/dest/services/discv5/persisted_enr_store.d.ts +38 -0
- package/dest/services/discv5/persisted_enr_store.d.ts.map +1 -0
- package/dest/services/discv5/persisted_enr_store.js +145 -0
- 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/encoding.d.ts +19 -6
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +25 -15
- package/dest/services/gossipsub/topic_score_params.d.ts +6 -23
- package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
- package/dest/services/gossipsub/topic_score_params.js +4 -31
- package/dest/services/libp2p/libp2p_service.d.ts +11 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +135 -53
- package/dest/services/peer-manager/peer_manager.d.ts +15 -2
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +46 -18
- package/dest/services/peer-manager/peer_scoring.d.ts +28 -1
- package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_scoring.js +70 -3
- package/dest/services/reqresp/config.d.ts +13 -1
- package/dest/services/reqresp/config.d.ts.map +1 -1
- package/dest/services/reqresp/config.js +11 -0
- package/dest/services/reqresp/rate-limiter/rate_limits.js +3 -3
- 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 +34 -3
- package/dest/services/service.d.ts +17 -1
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.d.ts +1 -1
- package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_source.js +4 -1
- package/dest/test-helpers/testbench-utils.d.ts +4 -1
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +9 -0
- package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +19 -2
- package/package.json +16 -17
- package/src/bootstrap/bootstrap.ts +15 -9
- package/src/client/factory.ts +9 -5
- package/src/client/interface.ts +29 -6
- package/src/client/p2p_client.ts +46 -48
- package/src/config.ts +50 -38
- package/src/errors/reqresp.error.ts +19 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +12 -2
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -1
- package/src/mem_pools/instrumentation.ts +13 -11
- package/src/mem_pools/tx_pool_v2/deleted_pool.ts +7 -7
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +1 -1
- package/src/mem_pools/tx_pool_v2/interfaces.ts +28 -4
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +35 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +22 -8
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +158 -33
- package/src/msg_validators/attestation_validator/attestation_validator.ts +26 -26
- package/src/msg_validators/attestation_validator/fisherman_attestation_validator.ts +4 -3
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +4 -2
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +10 -2
- package/src/msg_validators/proposal_validator/proposal_validator.ts +69 -30
- package/src/msg_validators/tx_validator/README.md +2 -0
- package/src/msg_validators/tx_validator/factory.ts +19 -13
- package/src/msg_validators/tx_validator/gas_validator.ts +16 -20
- package/src/msg_validators/tx_validator/phases_validator.ts +2 -2
- package/src/services/discv5/discV5_service.ts +88 -9
- package/src/services/discv5/persisted_enr_store.ts +158 -0
- package/src/services/dummy_service.ts +6 -0
- package/src/services/encoding.ts +28 -20
- package/src/services/gossipsub/README.md +33 -11
- package/src/services/gossipsub/topic_score_params.ts +9 -50
- package/src/services/libp2p/libp2p_service.ts +159 -56
- package/src/services/peer-manager/peer_manager.ts +48 -17
- package/src/services/peer-manager/peer_scoring.ts +75 -3
- package/src/services/reqresp/README.md +2 -23
- package/src/services/reqresp/config.ts +17 -0
- package/src/services/reqresp/rate-limiter/rate_limits.ts +3 -3
- package/src/services/reqresp/reqresp.ts +42 -2
- package/src/services/service.ts +19 -0
- package/src/services/tx_collection/tx_source.ts +2 -1
- package/src/test-helpers/testbench-utils.ts +12 -0
- package/src/testbench/p2p_client_testbench_worker.ts +22 -2
- package/dest/msg_validators/clock_tolerance.d.ts +0 -32
- package/dest/msg_validators/clock_tolerance.d.ts.map +0 -1
- package/dest/msg_validators/clock_tolerance.js +0 -90
- package/src/msg_validators/clock_tolerance.ts +0 -121
|
@@ -51,10 +51,18 @@ const FINALIZE_BLOCK_CHUNK_SIZE = 100;
|
|
|
51
51
|
/**
|
|
52
52
|
* Callbacks for the implementation to notify the outer class about events and metrics.
|
|
53
53
|
*/
|
|
54
|
+
/** A tx that has just transitioned to mined, with the time it spent in the pool. */
|
|
55
|
+
export interface MinedTxInfo {
|
|
56
|
+
txHash: string;
|
|
57
|
+
/** Wall-clock ms from receipt into the pool to being marked mined. Undefined when the
|
|
58
|
+
* receive time is unknown (e.g. a tx hydrated from the DB on restart, receivedAt === 0). */
|
|
59
|
+
minedDelayMs?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
export interface TxPoolV2Callbacks {
|
|
55
63
|
onTxsAdded: (txs: Tx[], opts: { source?: string }) => void;
|
|
56
64
|
onTxsRemoved: (txHashes: string[] | bigint[]) => void;
|
|
57
|
-
onTxsMined: (
|
|
65
|
+
onTxsMined: (minedTxs: MinedTxInfo[]) => void;
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
/**
|
|
@@ -65,7 +73,10 @@ export interface TxPoolV2Callbacks {
|
|
|
65
73
|
export class TxPoolV2Impl {
|
|
66
74
|
// === Persistence ===
|
|
67
75
|
#store: AztecAsyncKVStore;
|
|
76
|
+
/** Tx data without its proof. Proofs live in #proofsDB so a tx can be read without loading its proof. */
|
|
68
77
|
#txsDB: AztecAsyncMap<string, Buffer>;
|
|
78
|
+
/** Tx proofs, keyed by tx hash. Written and deleted in lockstep with #txsDB. */
|
|
79
|
+
#proofsDB: AztecAsyncMap<string, Buffer>;
|
|
69
80
|
|
|
70
81
|
// === Dependencies ===
|
|
71
82
|
#l2BlockSource: L2BlockSource;
|
|
@@ -99,6 +110,7 @@ export class TxPoolV2Impl {
|
|
|
99
110
|
) {
|
|
100
111
|
this.#store = store;
|
|
101
112
|
this.#txsDB = store.openMap('txs');
|
|
113
|
+
this.#proofsDB = store.openMap('tx_proofs');
|
|
102
114
|
|
|
103
115
|
this.#l2BlockSource = deps.l2BlockSource;
|
|
104
116
|
this.#worldStateSynchronizer = deps.worldStateSynchronizer;
|
|
@@ -108,7 +120,7 @@ export class TxPoolV2Impl {
|
|
|
108
120
|
this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
|
|
109
121
|
this.#evictedTxHashes = FifoSet.withLimit<string>(this.#config.evictedTxCacheSize);
|
|
110
122
|
this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
|
|
111
|
-
this.#deletedPool = new DeletedPool(store, this.#
|
|
123
|
+
this.#deletedPool = new DeletedPool(store, txHashStr => this.#deleteTxData(txHashStr), log);
|
|
112
124
|
this.#dateProvider = dateProvider;
|
|
113
125
|
this.#instrumentation = new TxPoolV2Instrumentation(telemetry, () => this.#indices.getTotalMetadataBytes());
|
|
114
126
|
this.#log = log;
|
|
@@ -216,7 +228,7 @@ export class TxPoolV2Impl {
|
|
|
216
228
|
}
|
|
217
229
|
await this.#store.transactionAsync(async () => {
|
|
218
230
|
for (const txHashStr of toDelete) {
|
|
219
|
-
await this.#
|
|
231
|
+
await this.#deleteTxData(txHashStr);
|
|
220
232
|
}
|
|
221
233
|
});
|
|
222
234
|
this.#log.info(`Deleted ${toDelete.length} invalid/rejected transactions on startup`, { txHashes: toDelete });
|
|
@@ -462,10 +474,11 @@ export class TxPoolV2Impl {
|
|
|
462
474
|
// Update protection for existing tx
|
|
463
475
|
this.#indices.updateProtection(txHashStr, slotNumber);
|
|
464
476
|
} else if (this.#deletedPool.isSoftDeleted(txHashStr)) {
|
|
465
|
-
// Resurrect soft-deleted tx as protected
|
|
477
|
+
// Resurrect soft-deleted tx as protected. Load the proof too: #addTx rewrites both entries, so re-adding
|
|
478
|
+
// a proofless tx would wipe the stored proof.
|
|
466
479
|
const buffer = await this.#txsDB.getAsync(txHashStr);
|
|
467
480
|
if (buffer) {
|
|
468
|
-
const tx =
|
|
481
|
+
const tx = await this.#loadTxWithProof(txHashStr, buffer);
|
|
469
482
|
await this.#addTx(tx, { protected: slotNumber });
|
|
470
483
|
softDeletedHits++;
|
|
471
484
|
} else {
|
|
@@ -555,7 +568,15 @@ export class TxPoolV2Impl {
|
|
|
555
568
|
});
|
|
556
569
|
|
|
557
570
|
if (found.length > 0) {
|
|
558
|
-
|
|
571
|
+
// receivedAt is 0 for txs hydrated from the DB on restart (true receive time lost) — leave
|
|
572
|
+
// their delay undefined so the metric isn't polluted by epoch-sized values.
|
|
573
|
+
const now = this.#dateProvider.now();
|
|
574
|
+
this.#callbacks.onTxsMined(
|
|
575
|
+
found.map(m => ({
|
|
576
|
+
txHash: m.txHash,
|
|
577
|
+
minedDelayMs: m.receivedAt > 0 ? now - m.receivedAt : undefined,
|
|
578
|
+
})),
|
|
579
|
+
);
|
|
559
580
|
}
|
|
560
581
|
|
|
561
582
|
this.#log.info(`Marked ${found.length} txs as mined in block ${blockId.number}`);
|
|
@@ -580,29 +601,62 @@ export class TxPoolV2Impl {
|
|
|
580
601
|
}
|
|
581
602
|
|
|
582
603
|
this.#log.info(`Preparing for slot ${slotNumber}: unprotecting ${txsToRestore.length} txs`);
|
|
604
|
+
await this.#restoreUnprotectedToPending(txsToRestore, 'during prepareForSlot');
|
|
605
|
+
});
|
|
606
|
+
}
|
|
583
607
|
|
|
584
|
-
|
|
585
|
-
|
|
608
|
+
async unprotectTxs(txHashes: TxHash[], slotNumber: SlotNumber): Promise<void> {
|
|
609
|
+
const hashStrs = txHashes.map(h => h.toString());
|
|
586
610
|
|
|
587
|
-
|
|
588
|
-
|
|
611
|
+
await this.#store.transactionAsync(async () => {
|
|
612
|
+
// Only release entries still recorded at this exact slot. A tx that another, still-live proposal
|
|
613
|
+
// raised to a higher slot via updateProtection must keep that protection. Mined txs have no
|
|
614
|
+
// protection entry and so are never matched here.
|
|
615
|
+
const matching = this.#indices.findProtectedTxsAtSlot(hashStrs, slotNumber);
|
|
616
|
+
if (matching.length === 0) {
|
|
617
|
+
this.#log.debug(`Unprotecting txs for slot ${slotNumber}: no matching protections to release`);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
589
620
|
|
|
590
|
-
|
|
591
|
-
await this.#deleteTxsBatch(invalid);
|
|
592
|
-
await this.#evictTxs(toEvict, 'NullifierConflict');
|
|
621
|
+
this.#indices.clearProtection(matching);
|
|
593
622
|
|
|
594
|
-
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
const uniqueFeePayers = new Set<string>(feePayers);
|
|
598
|
-
await this.#evictionManager.evictAfterNewTxs(
|
|
599
|
-
added.map(m => m.txHash),
|
|
600
|
-
[...uniqueFeePayers],
|
|
601
|
-
);
|
|
623
|
+
const txsToRestore = this.#indices.filterRestorable(matching);
|
|
624
|
+
if (txsToRestore.length === 0) {
|
|
625
|
+
return;
|
|
602
626
|
}
|
|
627
|
+
|
|
628
|
+
this.#log.info(`Unprotecting ${txsToRestore.length} txs from failed proposal at slot ${slotNumber}`);
|
|
629
|
+
await this.#restoreUnprotectedToPending(txsToRestore, 'during unprotectTxs');
|
|
603
630
|
});
|
|
604
631
|
}
|
|
605
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Returns just-unprotected txs to the pending pool: revalidates them, resolves nullifier
|
|
635
|
+
* conflicts, deletes losers, then enforces the pool size limit. Must run inside a store
|
|
636
|
+
* transaction; callers clear the protection entries before invoking.
|
|
637
|
+
*/
|
|
638
|
+
async #restoreUnprotectedToPending(txsToRestore: TxMetaData[], context: string): Promise<void> {
|
|
639
|
+
// Step 1: Validate for pending pool
|
|
640
|
+
const { valid, invalid } = await this.#revalidateMetadata(txsToRestore, context);
|
|
641
|
+
|
|
642
|
+
// Step 2: Resolve nullifier conflicts and add winners to pending indices
|
|
643
|
+
const { added, toEvict } = this.#applyNullifierConflictResolution(valid);
|
|
644
|
+
|
|
645
|
+
// Step 3: Delete invalid txs and evict conflict losers
|
|
646
|
+
await this.#deleteTxsBatch(invalid);
|
|
647
|
+
await this.#evictTxs(toEvict, 'NullifierConflict');
|
|
648
|
+
|
|
649
|
+
// Step 4: Run eviction rules (enforce pool size limit)
|
|
650
|
+
if (added.length > 0) {
|
|
651
|
+
const feePayers = added.map(meta => meta.feePayer);
|
|
652
|
+
const uniqueFeePayers = new Set<string>(feePayers);
|
|
653
|
+
await this.#evictionManager.evictAfterNewTxs(
|
|
654
|
+
added.map(m => m.txHash),
|
|
655
|
+
[...uniqueFeePayers],
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
606
660
|
async handlePrunedBlocks(latestBlock: L2BlockId, options?: { deleteAllTxs?: boolean }): Promise<void> {
|
|
607
661
|
// Step 1: Find transactions mined after the prune point
|
|
608
662
|
const txsToUnmine = this.#indices.findTxsMinedAfter(latestBlock.number);
|
|
@@ -674,8 +728,12 @@ export class TxPoolV2Impl {
|
|
|
674
728
|
async handleFinalizedBlock(block: BlockHeader): Promise<void> {
|
|
675
729
|
const blockNumber = block.globalVariables.blockNumber;
|
|
676
730
|
|
|
677
|
-
//
|
|
678
|
-
|
|
731
|
+
// Hold finalized txs for a configurable margin behind the finalized tip so a prover still
|
|
732
|
+
// proving an epoch with already-finalized blocks isn't starved of its txs. 0 deletes at the finalized tip.
|
|
733
|
+
const cutoffBlock = await this.#finalizationCutoffBlock(block);
|
|
734
|
+
|
|
735
|
+
// Step 1: Find mined txs at or before the cutoff block
|
|
736
|
+
const minedTxsToFinalize = this.#indices.findTxsMinedAtOrBefore(cutoffBlock);
|
|
679
737
|
|
|
680
738
|
// Step 2: Archive in chunks if archiving is enabled. Hydrating an entire epoch's worth of
|
|
681
739
|
// mined txs at once would OOM under load. When archiving is disabled there is no need to hydrate the txs at all.
|
|
@@ -699,32 +757,84 @@ export class TxPoolV2Impl {
|
|
|
699
757
|
// transaction. Only tx hashes are touched here, so memory is bounded and atomicity is preserved.
|
|
700
758
|
await this.#store.transactionAsync(async () => {
|
|
701
759
|
await this.#deleteTxsBatch(minedTxsToFinalize);
|
|
702
|
-
await this.#deletedPool.finalizeBlock(
|
|
760
|
+
await this.#deletedPool.finalizeBlock(cutoffBlock);
|
|
703
761
|
});
|
|
704
762
|
|
|
705
763
|
if (minedTxsToFinalize.length > 0) {
|
|
706
|
-
this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${
|
|
764
|
+
this.#log.info(`Finalized ${minedTxsToFinalize.length} mined txs from blocks up to ${cutoffBlock}`, {
|
|
707
765
|
txHashes: minedTxsToFinalize,
|
|
766
|
+
finalizedBlockNumber: blockNumber,
|
|
767
|
+
cutoffBlock,
|
|
708
768
|
});
|
|
709
769
|
}
|
|
710
770
|
}
|
|
711
771
|
|
|
772
|
+
/**
|
|
773
|
+
* Resolves the block up to which finalized txs may be deleted, given `keepFinalizedTxsForSlots`.
|
|
774
|
+
* With a margin of 0 this is just the finalized block. Otherwise we keep txs whose block is within
|
|
775
|
+
* the margin (in slots) of the finalized tip: take the target slot `finalizedSlot - margin`, find the
|
|
776
|
+
* latest checkpoint at or before it with a single reverse range query (`limit: 1`), and use that
|
|
777
|
+
* checkpoint's last block as the cutoff. This rounds to a checkpoint boundary, so it can retain slightly
|
|
778
|
+
* more than the configured margin but never less, and never deletes past the finalized block.
|
|
779
|
+
*/
|
|
780
|
+
async #finalizationCutoffBlock(block: BlockHeader): Promise<BlockNumber> {
|
|
781
|
+
const keepSlots = this.#config.keepFinalizedTxsForSlots;
|
|
782
|
+
if (keepSlots <= 0) {
|
|
783
|
+
return block.globalVariables.blockNumber;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// The goal here really is to ensure we keep transactions long enough for them to be proven.
|
|
787
|
+
// So we could be smart and calculate the number of slots until the end of the proof submission window.
|
|
788
|
+
// Our approach here is a lot simpler however and we just keep transactions for the configured number
|
|
789
|
+
// of slots past L1 finalisation. This means we may keep transactions in the mined pool for longer
|
|
790
|
+
// than strictly necessary.
|
|
791
|
+
const targetSlot = block.getSlot() - keepSlots;
|
|
792
|
+
if (targetSlot < 0) {
|
|
793
|
+
// The margin reaches past genesis: keep everything.
|
|
794
|
+
return BlockNumber(0);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const [checkpoint] = await this.#l2BlockSource.getCheckpointsData({
|
|
798
|
+
fromSlot: SlotNumber(targetSlot),
|
|
799
|
+
limit: 1,
|
|
800
|
+
reverse: true,
|
|
801
|
+
});
|
|
802
|
+
// No checkpoint at or before the target slot (e.g. early chain): keep everything.
|
|
803
|
+
return checkpoint ? BlockNumber(checkpoint.startBlock + checkpoint.blockCount - 1) : BlockNumber(0);
|
|
804
|
+
}
|
|
805
|
+
|
|
712
806
|
// === Query Methods ===
|
|
713
807
|
|
|
714
|
-
async getTxByHash(txHash: TxHash): Promise<Tx | undefined> {
|
|
715
|
-
const
|
|
716
|
-
|
|
808
|
+
async getTxByHash(txHash: TxHash, opts: { includeProof?: boolean } = {}): Promise<Tx | undefined> {
|
|
809
|
+
const txHashStr = txHash.toString();
|
|
810
|
+
const buffer = await this.#txsDB.getAsync(txHashStr);
|
|
811
|
+
if (!buffer) {
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
814
|
+
return opts.includeProof === false ? Tx.fromBuffer(buffer) : this.#loadTxWithProof(txHashStr, buffer);
|
|
717
815
|
}
|
|
718
816
|
|
|
719
|
-
async getTxsByHash(txHashes: TxHash[]): Promise<(Tx | undefined)[]> {
|
|
817
|
+
async getTxsByHash(txHashes: TxHash[], opts: { includeProof?: boolean } = {}): Promise<(Tx | undefined)[]> {
|
|
720
818
|
const results: (Tx | undefined)[] = [];
|
|
721
819
|
for (const h of txHashes) {
|
|
722
|
-
|
|
723
|
-
results.push(buffer ? Tx.fromBuffer(buffer) : undefined);
|
|
820
|
+
results.push(await this.getTxByHash(h, opts));
|
|
724
821
|
}
|
|
725
822
|
return results;
|
|
726
823
|
}
|
|
727
824
|
|
|
825
|
+
/** Deserializes a tx blob loaded from #txsDB together with its separately-stored proof. */
|
|
826
|
+
async #loadTxWithProof(txHashStr: string, buffer: Buffer): Promise<Tx> {
|
|
827
|
+
const proofBuffer = await this.#proofsDB.getAsync(txHashStr);
|
|
828
|
+
if (!proofBuffer) {
|
|
829
|
+
// #addTx always writes a proof entry (an empty proof serializes to a 4-byte sentinel), so a missing entry
|
|
830
|
+
// means the blob/proof invariant is broken. Return the proofless tx but flag it, since a consumer that needs
|
|
831
|
+
// the proof (eg serving peers) would otherwise fail far away from the root cause.
|
|
832
|
+
this.#log.warn(`Missing stored proof for tx ${txHashStr}`, { txHash: txHashStr });
|
|
833
|
+
return Tx.fromBuffer(buffer);
|
|
834
|
+
}
|
|
835
|
+
return Tx.fromBuffers(buffer, proofBuffer);
|
|
836
|
+
}
|
|
837
|
+
|
|
728
838
|
hasTxs(txHashes: TxHash[]): boolean[] {
|
|
729
839
|
return txHashes.map(h => {
|
|
730
840
|
const hashStr = h.toString();
|
|
@@ -760,6 +870,11 @@ export class TxPoolV2Impl {
|
|
|
760
870
|
return this.#indices.getPendingTxCount();
|
|
761
871
|
}
|
|
762
872
|
|
|
873
|
+
hasEligiblePendingTxs(minCount: number): boolean {
|
|
874
|
+
const maxReceivedAt = this.#dateProvider.now() - this.#config.minTxPoolAgeMs;
|
|
875
|
+
return this.#indices.hasEligiblePendingTxs(maxReceivedAt, minCount);
|
|
876
|
+
}
|
|
877
|
+
|
|
763
878
|
getMinedTxHashes(): [TxHash, L2BlockId][] {
|
|
764
879
|
return this.#indices.getMinedTxs().map(([hash, blockId]) => [TxHash.fromString(hash), blockId]);
|
|
765
880
|
}
|
|
@@ -803,6 +918,9 @@ export class TxPoolV2Impl {
|
|
|
803
918
|
if (config.minTxPoolAgeMs !== undefined) {
|
|
804
919
|
this.#config.minTxPoolAgeMs = config.minTxPoolAgeMs;
|
|
805
920
|
}
|
|
921
|
+
if (config.keepFinalizedTxsForSlots !== undefined) {
|
|
922
|
+
this.#config.keepFinalizedTxsForSlots = config.keepFinalizedTxsForSlots;
|
|
923
|
+
}
|
|
806
924
|
// Update eviction rules with new config
|
|
807
925
|
this.#evictionManager.updateConfig(config);
|
|
808
926
|
}
|
|
@@ -851,7 +969,8 @@ export class TxPoolV2Impl {
|
|
|
851
969
|
const meta = precomputedMeta ?? (await buildTxMetaData(tx));
|
|
852
970
|
meta.receivedAt = this.#dateProvider.now();
|
|
853
971
|
|
|
854
|
-
await this.#txsDB.set(txHashStr, tx.toBuffer());
|
|
972
|
+
await this.#txsDB.set(txHashStr, tx.withoutProof().toBuffer());
|
|
973
|
+
await this.#proofsDB.set(txHashStr, tx.chonkProof.toBuffer());
|
|
855
974
|
await this.#deletedPool.clearSoftDeleted(txHashStr);
|
|
856
975
|
this.#callbacks.onTxsAdded([tx], opts);
|
|
857
976
|
|
|
@@ -890,6 +1009,12 @@ export class TxPoolV2Impl {
|
|
|
890
1009
|
await this.#deletedPool.deleteTx(txHashStr);
|
|
891
1010
|
}
|
|
892
1011
|
|
|
1012
|
+
/** Hard-deletes a tx's stored data (tx blob and proof) from the DB. */
|
|
1013
|
+
async #deleteTxData(txHashStr: string): Promise<void> {
|
|
1014
|
+
await this.#txsDB.delete(txHashStr);
|
|
1015
|
+
await this.#proofsDB.delete(txHashStr);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
893
1018
|
/** Deletes a batch of transactions, emitting callbacks individually for each. */
|
|
894
1019
|
async #deleteTxsBatch(txHashes: string[]): Promise<void> {
|
|
895
1020
|
for (const txHashStr of txHashes) {
|
|
@@ -1078,7 +1203,7 @@ export class TxPoolV2Impl {
|
|
|
1078
1203
|
const publicStateSource = new DatabasePublicStateSource(db);
|
|
1079
1204
|
const balance = await publicStateSource.storageRead(
|
|
1080
1205
|
ProtocolContractAddress.FeeJuice,
|
|
1081
|
-
await computeFeePayerBalanceStorageSlot(AztecAddress.
|
|
1206
|
+
await computeFeePayerBalanceStorageSlot(AztecAddress.fromStringUnsafe(feePayer)),
|
|
1082
1207
|
);
|
|
1083
1208
|
return balance.toBigInt();
|
|
1084
1209
|
},
|
|
@@ -9,29 +9,27 @@ import {
|
|
|
9
9
|
type ValidationResult,
|
|
10
10
|
hasValidSignatureContext,
|
|
11
11
|
} from '@aztec/stdlib/p2p';
|
|
12
|
-
|
|
13
|
-
import { PipeliningWindow, isWithinClockTolerance } from '../clock_tolerance.js';
|
|
12
|
+
import type { ConsensusTimetable } from '@aztec/stdlib/timetable';
|
|
14
13
|
|
|
15
14
|
export class CheckpointAttestationValidator implements P2PValidator<CheckpointAttestation> {
|
|
16
15
|
protected epochCache: EpochCacheInterface;
|
|
17
16
|
protected logger: Logger;
|
|
18
|
-
private readonly
|
|
17
|
+
private readonly timetable: ConsensusTimetable;
|
|
19
18
|
protected readonly signatureContext: CoordinationSignatureContext;
|
|
19
|
+
private readonly clockDisparityMs: number;
|
|
20
20
|
|
|
21
21
|
constructor(
|
|
22
22
|
epochCache: EpochCacheInterface,
|
|
23
|
+
timetable: ConsensusTimetable,
|
|
23
24
|
opts: {
|
|
24
|
-
l1PublishingTime?: number;
|
|
25
|
-
p2pPropagationTime?: number;
|
|
26
25
|
signatureContext: CoordinationSignatureContext;
|
|
26
|
+
clockDisparityMs: number;
|
|
27
27
|
},
|
|
28
28
|
) {
|
|
29
29
|
this.epochCache = epochCache;
|
|
30
|
-
this.
|
|
31
|
-
l1PublishingTime: opts.l1PublishingTime,
|
|
32
|
-
p2pPropagationTime: opts.p2pPropagationTime,
|
|
33
|
-
});
|
|
30
|
+
this.timetable = timetable;
|
|
34
31
|
this.signatureContext = opts.signatureContext;
|
|
32
|
+
this.clockDisparityMs = opts.clockDisparityMs;
|
|
35
33
|
this.logger = createLogger('p2p:checkpoint-attestation-validator');
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -50,23 +48,25 @@ export class CheckpointAttestationValidator implements P2PValidator<CheckpointAt
|
|
|
50
48
|
return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
// Accept attestations whose explicit per-slot receive window contains the current wall-clock time.
|
|
52
|
+
// The window spans the build-frame start (attestation_receive_start) to the attestation deadline
|
|
53
|
+
// (target_slot_start + S - 2E), widened by the configured clock-disparity tolerance on both ends, so
|
|
54
|
+
// it covers the build slot, the target slot, and clock-disparity grace. This is the sole arrival
|
|
55
|
+
// gate; attestations are otherwise attributed by content, not by current/next slot.
|
|
56
|
+
const startSeconds = this.timetable.getAttestationReceiveStart(slotNumber);
|
|
57
|
+
const deadlineSeconds = this.timetable.getAttestationDeadline(slotNumber);
|
|
58
|
+
const nowMs = Number(this.epochCache.getEpochAndSlotNow().nowMs);
|
|
59
|
+
if (
|
|
60
|
+
nowMs < startSeconds * 1000 - this.clockDisparityMs ||
|
|
61
|
+
nowMs > deadlineSeconds * 1000 + this.clockDisparityMs
|
|
62
|
+
) {
|
|
63
|
+
this.logger.warn(`Checkpoint attestation slot ${slotNumber} is outside its receive window`, {
|
|
64
|
+
slotNumber,
|
|
65
|
+
nowMs,
|
|
66
|
+
windowStartSeconds: startSeconds,
|
|
67
|
+
windowDeadlineSeconds: deadlineSeconds,
|
|
68
|
+
});
|
|
69
|
+
return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// Verify the signature is valid
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
PeerErrorSeverity,
|
|
6
6
|
type ValidationResult,
|
|
7
7
|
} from '@aztec/stdlib/p2p';
|
|
8
|
+
import type { ConsensusTimetable } from '@aztec/stdlib/timetable';
|
|
8
9
|
import { Attributes, Metrics, type TelemetryClient, createUpDownCounterWithDefault } from '@aztec/telemetry-client';
|
|
9
10
|
|
|
10
11
|
import type { AttestationPoolApi } from '../../mem_pools/attestation_pool/attestation_pool.js';
|
|
@@ -23,15 +24,15 @@ export class FishermanAttestationValidator extends CheckpointAttestationValidato
|
|
|
23
24
|
|
|
24
25
|
constructor(
|
|
25
26
|
epochCache: EpochCacheInterface,
|
|
27
|
+
timetable: ConsensusTimetable,
|
|
26
28
|
private attestationPool: AttestationPoolApi,
|
|
27
29
|
telemetryClient: TelemetryClient,
|
|
28
30
|
opts: {
|
|
29
|
-
l1PublishingTime?: number;
|
|
30
|
-
p2pPropagationTime?: number;
|
|
31
31
|
signatureContext: CoordinationSignatureContext;
|
|
32
|
+
clockDisparityMs: number;
|
|
32
33
|
},
|
|
33
34
|
) {
|
|
34
|
-
super(epochCache, opts);
|
|
35
|
+
super(epochCache, timetable, opts);
|
|
35
36
|
this.logger = this.logger.createChild('[FISHERMAN]');
|
|
36
37
|
|
|
37
38
|
const meter = telemetryClient.getMeter('FishermanAttestationValidator');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
2
|
import type { BlockProposal, CoordinationSignatureContext, P2PValidator, ValidationResult } from '@aztec/stdlib/p2p';
|
|
3
|
+
import type { ConsensusTimetable } from '@aztec/stdlib/timetable';
|
|
3
4
|
|
|
4
5
|
import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
|
|
5
6
|
|
|
@@ -8,16 +9,17 @@ export class BlockProposalValidator implements P2PValidator<BlockProposal> {
|
|
|
8
9
|
|
|
9
10
|
constructor(
|
|
10
11
|
epochCache: EpochCacheInterface,
|
|
12
|
+
timetable: ConsensusTimetable,
|
|
11
13
|
opts: {
|
|
12
14
|
txsPermitted: boolean;
|
|
13
15
|
maxTxsPerBlock?: number;
|
|
14
16
|
maxBlocksPerCheckpoint?: number;
|
|
15
|
-
p2pPropagationTime?: number;
|
|
16
17
|
skipSlotValidation?: boolean;
|
|
17
18
|
signatureContext: CoordinationSignatureContext;
|
|
19
|
+
clockDisparityMs: number;
|
|
18
20
|
},
|
|
19
21
|
) {
|
|
20
|
-
this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:block_proposal_validator');
|
|
22
|
+
this.proposalValidator = new ProposalValidator(epochCache, timetable, opts, 'p2p:block_proposal_validator');
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
async validate(proposal: BlockProposal): Promise<ValidationResult> {
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
P2PValidator,
|
|
6
6
|
ValidationResult,
|
|
7
7
|
} from '@aztec/stdlib/p2p';
|
|
8
|
+
import type { ConsensusTimetable } from '@aztec/stdlib/timetable';
|
|
8
9
|
|
|
9
10
|
import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
|
|
10
11
|
|
|
@@ -13,16 +14,17 @@ export class CheckpointProposalValidator implements P2PValidator<CheckpointPropo
|
|
|
13
14
|
|
|
14
15
|
constructor(
|
|
15
16
|
epochCache: EpochCacheInterface,
|
|
17
|
+
timetable: ConsensusTimetable,
|
|
16
18
|
opts: {
|
|
17
19
|
txsPermitted: boolean;
|
|
18
20
|
maxTxsPerBlock?: number;
|
|
19
21
|
maxBlocksPerCheckpoint?: number;
|
|
20
|
-
p2pPropagationTime?: number;
|
|
21
22
|
skipSlotValidation?: boolean;
|
|
22
23
|
signatureContext: CoordinationSignatureContext;
|
|
24
|
+
clockDisparityMs: number;
|
|
23
25
|
},
|
|
24
26
|
) {
|
|
25
|
-
this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:checkpoint_proposal_validator');
|
|
27
|
+
this.proposalValidator = new ProposalValidator(epochCache, timetable, opts, 'p2p:checkpoint_proposal_validator');
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
async validate(proposal: CheckpointProposal): Promise<ValidationResult> {
|
|
@@ -33,6 +35,12 @@ export class CheckpointProposalValidator implements P2PValidator<CheckpointPropo
|
|
|
33
35
|
|
|
34
36
|
const blockProposal = proposal.getBlockProposal();
|
|
35
37
|
if (blockProposal) {
|
|
38
|
+
// The terminal block is carried inside the checkpoint proposal rather than gossiped as a
|
|
39
|
+
// standalone block proposal, so apply the per-checkpoint block ceiling to it here too.
|
|
40
|
+
const indexResult = this.proposalValidator.validateBlockIndexWithinCheckpoint(blockProposal);
|
|
41
|
+
if (indexResult.result !== 'accept') {
|
|
42
|
+
return indexResult;
|
|
43
|
+
}
|
|
36
44
|
return this.proposalValidator.validateTxs(blockProposal);
|
|
37
45
|
}
|
|
38
46
|
|