@aztec/p2p 0.0.1-commit.ff7989d6c → 0.0.1-commit.fff30aa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +129 -3
  2. package/dest/client/factory.d.ts +4 -5
  3. package/dest/client/factory.d.ts.map +1 -1
  4. package/dest/client/factory.js +29 -28
  5. package/dest/client/interface.d.ts +6 -13
  6. package/dest/client/interface.d.ts.map +1 -1
  7. package/dest/client/p2p_client.d.ts +5 -13
  8. package/dest/client/p2p_client.d.ts.map +1 -1
  9. package/dest/client/p2p_client.js +19 -88
  10. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -2
  11. package/dest/config.d.ts +32 -11
  12. package/dest/config.d.ts.map +1 -1
  13. package/dest/config.js +85 -31
  14. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  15. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  16. package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
  17. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  18. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  19. package/dest/mem_pools/tx_pool/priority.js +4 -4
  20. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  21. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  22. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
  24. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -2
  26. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
  27. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  28. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +2 -0
  29. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
  30. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  31. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +2 -2
  32. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
  33. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  34. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
  35. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
  36. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  37. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  38. package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
  39. package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/index.js +1 -1
  41. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +9 -7
  42. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/interfaces.js +1 -1
  44. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +46 -8
  45. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  46. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +81 -17
  47. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  48. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  49. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +9 -10
  50. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +5 -3
  51. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  52. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
  53. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -2
  54. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  55. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +50 -40
  56. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +6 -4
  57. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  58. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  59. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +6 -4
  60. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  61. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  62. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -8
  63. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  64. package/dest/msg_validators/proposal_validator/proposal_validator.js +48 -36
  65. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
  66. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  67. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
  68. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
  69. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  70. package/dest/msg_validators/tx_validator/allowed_public_setup.js +24 -20
  71. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  72. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  73. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  74. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts +9 -0
  75. package/dest/msg_validators/tx_validator/contract_instance_validator.d.ts.map +1 -0
  76. package/dest/msg_validators/tx_validator/contract_instance_validator.js +48 -0
  77. package/dest/msg_validators/tx_validator/data_validator.d.ts +1 -1
  78. package/dest/msg_validators/tx_validator/data_validator.d.ts.map +1 -1
  79. package/dest/msg_validators/tx_validator/data_validator.js +35 -2
  80. package/dest/msg_validators/tx_validator/factory.d.ts +133 -6
  81. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  82. package/dest/msg_validators/tx_validator/factory.js +247 -60
  83. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
  84. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
  85. package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
  86. package/dest/msg_validators/tx_validator/gas_validator.d.ts +67 -3
  87. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  88. package/dest/msg_validators/tx_validator/gas_validator.js +104 -37
  89. package/dest/msg_validators/tx_validator/index.d.ts +3 -1
  90. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  91. package/dest/msg_validators/tx_validator/index.js +2 -0
  92. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
  93. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
  94. package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
  95. package/dest/msg_validators/tx_validator/phases_validator.d.ts +22 -2
  96. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  97. package/dest/msg_validators/tx_validator/phases_validator.js +72 -24
  98. package/dest/services/dummy_service.d.ts +2 -3
  99. package/dest/services/dummy_service.d.ts.map +1 -1
  100. package/dest/services/dummy_service.js +1 -4
  101. package/dest/services/encoding.d.ts +6 -2
  102. package/dest/services/encoding.d.ts.map +1 -1
  103. package/dest/services/encoding.js +14 -8
  104. package/dest/services/libp2p/libp2p_service.d.ts +15 -13
  105. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  106. package/dest/services/libp2p/libp2p_service.js +92 -92
  107. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
  108. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +5 -4
  109. package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
  110. package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -8
  111. package/dest/services/reqresp/reqresp.d.ts +1 -1
  112. package/dest/services/reqresp/reqresp.d.ts.map +1 -1
  113. package/dest/services/reqresp/reqresp.js +17 -9
  114. package/dest/services/service.d.ts +2 -2
  115. package/dest/services/service.d.ts.map +1 -1
  116. package/dest/services/tx_collection/file_store_tx_source.d.ts +5 -4
  117. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  118. package/dest/services/tx_collection/file_store_tx_source.js +39 -29
  119. package/dest/services/tx_collection/tx_source.d.ts +6 -5
  120. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  121. package/dest/services/tx_collection/tx_source.js +9 -7
  122. package/dest/services/tx_provider.d.ts +3 -3
  123. package/dest/services/tx_provider.d.ts.map +1 -1
  124. package/dest/services/tx_provider.js +4 -4
  125. package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
  126. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  127. package/dest/test-helpers/make-test-p2p-clients.js +1 -2
  128. package/dest/test-helpers/mock-pubsub.d.ts +2 -3
  129. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  130. package/dest/test-helpers/mock-pubsub.js +2 -2
  131. package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
  132. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  133. package/dest/test-helpers/reqresp-nodes.js +2 -2
  134. package/dest/test-helpers/testbench-utils.d.ts +2 -2
  135. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  136. package/dest/test-helpers/testbench-utils.js +2 -1
  137. package/dest/testbench/p2p_client_testbench_worker.js +7 -6
  138. package/dest/testbench/worker_client_manager.d.ts +3 -1
  139. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  140. package/dest/testbench/worker_client_manager.js +4 -1
  141. package/package.json +14 -14
  142. package/src/client/factory.ts +49 -46
  143. package/src/client/interface.ts +5 -19
  144. package/src/client/p2p_client.ts +20 -118
  145. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -2
  146. package/src/config.ts +124 -34
  147. package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
  148. package/src/mem_pools/tx_pool/priority.ts +4 -4
  149. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  150. package/src/mem_pools/tx_pool_v2/README.md +9 -1
  151. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +3 -2
  152. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -0
  153. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
  154. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +2 -2
  155. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
  156. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
  157. package/src/mem_pools/tx_pool_v2/index.ts +1 -1
  158. package/src/mem_pools/tx_pool_v2/interfaces.ts +9 -7
  159. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +113 -18
  160. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +11 -11
  161. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +14 -2
  162. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +57 -39
  163. package/src/msg_validators/attestation_validator/README.md +49 -0
  164. package/src/msg_validators/proposal_validator/README.md +123 -0
  165. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +14 -4
  166. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +20 -7
  167. package/src/msg_validators/proposal_validator/proposal_validator.ts +63 -40
  168. package/src/msg_validators/tx_validator/README.md +119 -0
  169. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
  170. package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
  171. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  172. package/src/msg_validators/tx_validator/contract_instance_validator.ts +56 -0
  173. package/src/msg_validators/tx_validator/data_validator.ts +42 -1
  174. package/src/msg_validators/tx_validator/factory.ts +394 -78
  175. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  176. package/src/msg_validators/tx_validator/gas_validator.ts +123 -27
  177. package/src/msg_validators/tx_validator/index.ts +2 -0
  178. package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
  179. package/src/msg_validators/tx_validator/phases_validator.ts +82 -27
  180. package/src/services/dummy_service.ts +1 -5
  181. package/src/services/encoding.ts +14 -7
  182. package/src/services/libp2p/libp2p_service.ts +106 -101
  183. package/src/services/reqresp/README.md +229 -0
  184. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
  185. package/src/services/reqresp/rate-limiter/rate_limiter.ts +13 -9
  186. package/src/services/reqresp/reqresp.ts +19 -11
  187. package/src/services/service.ts +1 -1
  188. package/src/services/tx_collection/file_store_tx_source.ts +43 -31
  189. package/src/services/tx_collection/tx_source.ts +8 -7
  190. package/src/services/tx_provider.ts +2 -2
  191. package/src/test-helpers/make-test-p2p-clients.ts +0 -2
  192. package/src/test-helpers/mock-pubsub.ts +3 -6
  193. package/src/test-helpers/reqresp-nodes.ts +2 -5
  194. package/src/test-helpers/testbench-utils.ts +2 -1
  195. package/src/testbench/p2p_client_testbench_worker.ts +3 -6
  196. package/src/testbench/worker_client_manager.ts +11 -4
  197. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -23
  198. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
  199. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -212
  200. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
@@ -18,15 +18,8 @@ import {
18
18
  type L2TipsStore,
19
19
  } from '@aztec/stdlib/block';
20
20
  import type { ContractDataSource } from '@aztec/stdlib/contract';
21
- import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
22
21
  import { type PeerInfo, tryStop } from '@aztec/stdlib/interfaces/server';
23
- import {
24
- type BlockProposal,
25
- CheckpointAttestation,
26
- type CheckpointProposal,
27
- type P2PClientType,
28
- type TopicType,
29
- } from '@aztec/stdlib/p2p';
22
+ import { type BlockProposal, CheckpointAttestation, type CheckpointProposal, type TopicType } from '@aztec/stdlib/p2p';
30
23
  import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
31
24
  import { Attributes, type TelemetryClient, WithTracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
32
25
 
@@ -44,7 +37,6 @@ import {
44
37
  type ReqRespSubProtocolHandler,
45
38
  type ReqRespSubProtocolValidators,
46
39
  } from '../services/reqresp/interface.js';
47
- import { chunkTxHashesRequest } from '../services/reqresp/protocols/tx.js';
48
40
  import type {
49
41
  DuplicateAttestationInfo,
50
42
  DuplicateProposalInfo,
@@ -60,10 +52,7 @@ import { type P2P, P2PClientState, type P2PSyncState } from './interface.js';
60
52
  /**
61
53
  * The P2P client implementation.
62
54
  */
63
- export class P2PClient<T extends P2PClientType = P2PClientType.Full>
64
- extends WithTracer
65
- implements P2P, P2P<P2PClientType.Prover>
66
- {
55
+ export class P2PClient extends WithTracer implements P2P {
67
56
  /** The JS promise that will be running to keep the client's data in sync. Can be interrupted if the client is stopped. */
68
57
  private runningPromise!: Promise<void>;
69
58
 
@@ -95,7 +84,6 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
95
84
  private slotMonitor: RunningPromise | undefined;
96
85
 
97
86
  constructor(
98
- _clientType: T,
99
87
  private store: AztecAsyncKVStore,
100
88
  private l2BlockSource: L2BlockSource & ContractDataSource,
101
89
  mempools: MemPools,
@@ -122,27 +110,6 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
122
110
  this.telemetry,
123
111
  );
124
112
 
125
- // Default to collecting all txs when we see a valid proposal
126
- // This can be overridden by the validator client to validate, and it will call getTxsForBlockProposal on its own
127
- // Note: Validators do NOT attest to individual blocks - attestations are only for checkpoint proposals.
128
- // TODO(palla/txs): We should not trigger a request for txs on a proposal before fully validating it. We need to bring
129
- // validator-client code into here so we can validate a proposal is reasonable.
130
- this.registerBlockProposalHandler(async (block, sender) => {
131
- this.log.debug(`Received block proposal from ${sender.toString()}`);
132
- // TODO(palla/txs): Need to subtract validatorReexecuteDeadlineMs from this deadline (see ValidatorClient.getReexecutionDeadline)
133
- const constants = this.txCollection.getConstants();
134
- const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(block.slotNumber + 1), constants));
135
- const deadline = new Date(nextSlotTimestampSeconds * 1000);
136
- const parentBlock = await this.l2BlockSource.getBlockHeaderByArchive(block.blockHeader.lastArchive.root);
137
- if (!parentBlock) {
138
- this.log.debug(`Cannot collect txs for proposal as parent block not found`);
139
- return false;
140
- }
141
- const blockNumber = BlockNumber(parentBlock.getBlockNumber() + 1);
142
- await this.txProvider.getTxsForBlockProposal(block, blockNumber, { pinnedPeer: sender, deadline });
143
- return true;
144
- });
145
-
146
113
  this.l2Tips = new L2TipsKVStore(store, 'p2p_client');
147
114
  this.synchedLatestSlot = store.openSingleton('p2p_pool_last_l2_slot');
148
115
  }
@@ -433,36 +400,6 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
433
400
  this.p2pService.registerDuplicateAttestationCallback(callback);
434
401
  }
435
402
 
436
- /**
437
- * Uses the batched Request Response protocol to request a set of transactions from the network.
438
- */
439
- private async requestTxsByHash(txHashes: TxHash[], pinnedPeerId: PeerId | undefined): Promise<Tx[]> {
440
- const timeoutMs = 8000; // Longer timeout for now
441
- const maxRetryAttempts = 10; // Keep retrying within the timeout
442
- const requests = chunkTxHashesRequest(txHashes);
443
- const maxPeers = Math.min(Math.ceil(requests.length / 3), 10);
444
-
445
- const txBatches = await this.p2pService.sendBatchRequest(
446
- ReqRespSubProtocol.TX,
447
- requests,
448
- pinnedPeerId,
449
- timeoutMs,
450
- maxPeers,
451
- maxRetryAttempts,
452
- );
453
-
454
- const txs = txBatches.flat();
455
- if (txs.length > 0) {
456
- await this.txPool.addPendingTxs(txs);
457
- }
458
-
459
- const txHashesStr = txHashes.map(tx => tx.toString()).join(', ');
460
- this.log.debug(`Requested txs ${txHashesStr} (${txs.length} / ${txHashes.length}) from peers`);
461
-
462
- // We return all transactions, even the not found ones to the caller, such they can handle missing items themselves.
463
- return txs;
464
- }
465
-
466
403
  public async getPendingTxs(limit?: number, after?: TxHash): Promise<Tx[]> {
467
404
  if (limit !== undefined && limit <= 0) {
468
405
  throw new TypeError('limit must be greater than 0');
@@ -530,49 +467,6 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
530
467
  return this.txPool.hasTxs(txHashes);
531
468
  }
532
469
 
533
- /**
534
- * Returns transactions in the transaction pool by hash.
535
- * If a transaction is not in the pool, it will be requested from the network.
536
- * @param txHashes - Hashes of the transactions to look for.
537
- * @returns The txs found, or undefined if not found in the order requested.
538
- */
539
- async getTxsByHash(txHashes: TxHash[], pinnedPeerId: PeerId | undefined): Promise<(Tx | undefined)[]> {
540
- const txs = await Promise.all(txHashes.map(txHash => this.txPool.getTxByHash(txHash)));
541
- const missingTxHashes = txs
542
- .map((tx, index) => [tx, index] as const)
543
- .filter(([tx, _index]) => !tx)
544
- .map(([_tx, index]) => txHashes[index]);
545
-
546
- if (missingTxHashes.length === 0) {
547
- return txs as Tx[];
548
- }
549
-
550
- const missingTxs = await this.requestTxsByHash(missingTxHashes, pinnedPeerId);
551
- // TODO: optimize
552
- // Merge the found txs in order
553
- const mergingTxs = txHashes.map(txHash => {
554
- // Is it in the txs list from the mempool?
555
- for (const tx of txs) {
556
- if (tx !== undefined && tx.getTxHash().equals(txHash)) {
557
- return tx;
558
- }
559
- }
560
-
561
- // Is it in the fetched missing txs?
562
- // Note: this is an O(n^2) operation, but we expect the number of missing txs to be small.
563
- for (const tx of missingTxs) {
564
- if (tx.getTxHash().equals(txHash)) {
565
- return tx;
566
- }
567
- }
568
-
569
- // Otherwise return undefined
570
- return undefined;
571
- });
572
-
573
- return mergingTxs;
574
- }
575
-
576
470
  /**
577
471
  * Returns an archived transaction in the transaction pool by its hash.
578
472
  * @param txHash - Hash of the archived transaction to look for.
@@ -775,20 +669,28 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
775
669
  }
776
670
 
777
671
  /**
778
- * Returns true if the prune crossed a checkpoint boundary.
779
- * If the old and new checkpoint numbers are the same, the prune is within a single checkpoint.
780
- * If they differ, the prune spans across checkpoints (epoch prune).
672
+ * Returns true if the prune is an epoch prune (new checkpoint number is less than old).
673
+ * If the checkpoint number stays the same or increases, the prune is within a checkpoint.
781
674
  */
782
675
  private async isEpochPrune(newCheckpoint: CheckpointId): Promise<boolean> {
783
676
  const tips = await this.l2Tips.getL2Tips();
784
677
  const oldCheckpointNumber = tips.checkpointed.checkpoint.number;
785
- if (oldCheckpointNumber <= CheckpointNumber.ZERO) {
678
+ if (oldCheckpointNumber <= CheckpointNumber.INITIAL) {
786
679
  return false;
787
680
  }
788
- const isEpochPrune = oldCheckpointNumber !== newCheckpoint.number;
789
- this.log.info(
790
- `Detected epoch prune: ${isEpochPrune}. Old checkpoint: ${oldCheckpointNumber}, new checkpoint: ${newCheckpoint.number}`,
791
- );
681
+ const newCheckpointNumber = newCheckpoint.number;
682
+ // We check that the new checkpoint number is less than the old checkpoint number in order to consider it an epoch prune.
683
+ // To be more certain that it is an epoch prune, we will check that at least 2 checkpoints were removed.
684
+ // This means we should avoid thinking checkpoints removed by L1 re-orgs are epoch prunes
685
+ const thresholdForEpochPrune = CheckpointNumber(oldCheckpointNumber - 2);
686
+ const isEpochPrune = newCheckpointNumber <= thresholdForEpochPrune;
687
+ if (isEpochPrune) {
688
+ this.log.info(`Detected epoch prune to ${newCheckpointNumber}`, {
689
+ oldCheckpointNumber,
690
+ newCheckpointNumber,
691
+ thresholdForEpochPrune,
692
+ });
693
+ }
792
694
  return isEpochPrune;
793
695
  }
794
696
 
@@ -839,8 +741,8 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
839
741
  this.log.debug(`Moved from state ${P2PClientState[oldState]} to ${P2PClientState[this.currentState]}`);
840
742
  }
841
743
 
842
- public validate(txs: Tx[]): Promise<void> {
843
- return this.p2pService.validate(txs);
744
+ public validateTxsReceivedInBlockProposal(txs: Tx[]): Promise<void> {
745
+ return this.p2pService.validateTxsReceivedInBlockProposal(txs);
844
746
  }
845
747
 
846
748
  /**
@@ -8,7 +8,7 @@ import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
8
8
  import type { L2BlockSource } from '@aztec/stdlib/block';
9
9
  import type { ContractDataSource } from '@aztec/stdlib/contract';
10
10
  import type { ClientProtocolCircuitVerifier } from '@aztec/stdlib/interfaces/server';
11
- import { P2PClientType, PeerErrorSeverity } from '@aztec/stdlib/p2p';
11
+ import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
12
12
  import type { Tx, TxValidationResult } from '@aztec/stdlib/tx';
13
13
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
14
14
 
@@ -114,7 +114,6 @@ async function startClient(config: P2PConfig, clientIndex: number) {
114
114
  };
115
115
 
116
116
  client = await createP2PClient(
117
- P2PClientType.Full,
118
117
  config as P2PConfig & DataStoreConfig,
119
118
  l2BlockSource as L2BlockSource & ContractDataSource,
120
119
  proofVerifier as ClientProtocolCircuitVerifier,
package/src/config.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type ConfigMappingsType,
3
3
  SecretValue,
4
+ bigintConfigHelper,
4
5
  booleanConfigHelper,
5
6
  getConfigFromMappings,
6
7
  getDefaultConfig,
@@ -38,7 +39,19 @@ export interface P2PConfig
38
39
  ChainConfig,
39
40
  TxCollectionConfig,
40
41
  TxFileStoreConfig,
41
- Pick<SequencerConfig, 'blockDurationMs' | 'expectedBlockProposalsPerSlot'> {
42
+ Pick<SequencerConfig, 'blockDurationMs' | 'expectedBlockProposalsPerSlot' | 'maxTxsPerBlock'> {
43
+ /** Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. */
44
+ validateMaxTxsPerBlock?: number;
45
+
46
+ /** Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. */
47
+ validateMaxTxsPerCheckpoint?: number;
48
+
49
+ /** Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected. */
50
+ validateMaxL2BlockGas?: number;
51
+
52
+ /** Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. */
53
+ validateMaxDABlockGas?: number;
54
+
42
55
  /** A flag dictating whether the P2P subsystem should be enabled. */
43
56
  p2pEnabled: boolean;
44
57
 
@@ -150,8 +163,8 @@ export interface P2PConfig
150
163
  /** The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. */
151
164
  p2pStoreMapSizeKb?: number;
152
165
 
153
- /** Which calls are allowed in the public setup phase of a tx. */
154
- txPublicSetupAllowList: AllowedElement[];
166
+ /** Additional entries to extend the default setup allow list. */
167
+ txPublicSetupAllowListExtend: AllowedElement[];
155
168
 
156
169
  /** The maximum number of pending txs before evicting lower priority txs. */
157
170
  maxPendingTxCount: number;
@@ -173,7 +186,10 @@ export interface P2PConfig
173
186
  /** Whether transactions are disabled for this node. This means transactions will be rejected at the RPC and P2P layers. */
174
187
  disableTransactions: boolean;
175
188
 
176
- /** The probability that a transaction is discarded (0 = disabled). - For testing purposes only */
189
+ /** True to simulate discarding transactions. - For testing purposes only*/
190
+ dropTransactions: boolean;
191
+
192
+ /** The probability that a transaction is discarded. - For testing purposes only */
177
193
  dropTransactionsProbability: number;
178
194
 
179
195
  /** Whether to delete transactions from the pool after a reorg instead of moving them back to pending. */
@@ -190,11 +206,36 @@ export interface P2PConfig
190
206
 
191
207
  /** Minimum age (ms) a transaction must have been in the pool before it's eligible for block building. */
192
208
  minTxPoolAgeMs: number;
209
+
210
+ /** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
211
+ priceBumpPercentage: bigint;
193
212
  }
194
213
 
195
214
  export const DEFAULT_P2P_PORT = 40400;
196
215
 
197
216
  export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
217
+ validateMaxTxsPerBlock: {
218
+ env: 'VALIDATOR_MAX_TX_PER_BLOCK',
219
+ description:
220
+ 'Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set.',
221
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
222
+ },
223
+ validateMaxTxsPerCheckpoint: {
224
+ env: 'VALIDATOR_MAX_TX_PER_CHECKPOINT',
225
+ description:
226
+ 'Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set.',
227
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
228
+ },
229
+ validateMaxL2BlockGas: {
230
+ env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
231
+ description: 'Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected.',
232
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
233
+ },
234
+ validateMaxDABlockGas: {
235
+ env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
236
+ description: 'Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected.',
237
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
238
+ },
198
239
  p2pEnabled: {
199
240
  env: 'P2P_ENABLED',
200
241
  description: 'A flag dictating whether the P2P subsystem should be enabled.',
@@ -393,12 +434,13 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
393
434
  parseEnv: (val: string | undefined) => (val ? +val : undefined),
394
435
  description: 'The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb.',
395
436
  },
396
- txPublicSetupAllowList: {
437
+ txPublicSetupAllowListExtend: {
397
438
  env: 'TX_PUBLIC_SETUP_ALLOWLIST',
398
439
  parseEnv: (val: string) => parseAllowList(val),
399
- description: 'The list of functions calls allowed to run in setup',
440
+ description:
441
+ 'Additional entries to extend the default setup allow list. Format: I:address:selector[:flags],C:classId:selector[:flags]. Flags: os (onlySelf), rn (rejectNullMsgSender), cl=N (calldataLength), joined with +.',
400
442
  printDefault: () =>
401
- 'AuthRegistry, FeeJuice.increase_public_balance, Token.increase_public_balance, FPC.prepare_fee',
443
+ 'Default: AuthRegistry._set_authorized, AuthRegistry.set_authorized, FeeJuice._increase_public_balance',
402
444
  },
403
445
  maxPendingTxCount: {
404
446
  env: 'P2P_MAX_PENDING_TX_COUNT',
@@ -427,6 +469,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
427
469
  description: 'Number of auth attempts to allow before peer is banned. Number is inclusive',
428
470
  ...numberConfigHelper(3),
429
471
  },
472
+ dropTransactions: {
473
+ env: 'P2P_DROP_TX',
474
+ description: 'True to simulate discarding transactions. - For testing purposes only',
475
+ ...booleanConfigHelper(false),
476
+ },
430
477
  dropTransactionsProbability: {
431
478
  env: 'P2P_DROP_TX_CHANCE',
432
479
  description: 'The probability that a transaction is discarded (0 - 1). - For testing purposes only',
@@ -464,6 +511,12 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
464
511
  description: 'Minimum age (ms) a transaction must have been in the pool before it is eligible for block building.',
465
512
  ...numberConfigHelper(2_000),
466
513
  },
514
+ priceBumpPercentage: {
515
+ env: 'P2P_RPC_PRICE_BUMP_PERCENTAGE',
516
+ description:
517
+ 'Minimum percentage fee increase required to replace an existing tx via RPC. Even at 0%, replacement still requires paying at least 1 unit more.',
518
+ ...bigintConfigHelper(10n),
519
+ },
467
520
  ...sharedSequencerConfigMappings,
468
521
  ...p2pReqRespConfigMappings,
469
522
  ...batchTxRequesterConfigMappings,
@@ -521,13 +574,44 @@ export const bootnodeConfigMappings = pickConfigMappings(
521
574
  bootnodeConfigKeys,
522
575
  );
523
576
 
577
+ /**
578
+ * Parses a `+`-separated flags string into validation properties for an allow list entry.
579
+ * Supported flags: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength).
580
+ */
581
+ function parseFlags(
582
+ flags: string,
583
+ entry: string,
584
+ ): { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } {
585
+ const result: { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } = {};
586
+ for (const flag of flags.split('+')) {
587
+ if (flag === 'os') {
588
+ result.onlySelf = true;
589
+ } else if (flag === 'rn') {
590
+ result.rejectNullMsgSender = true;
591
+ } else if (flag.startsWith('cl=')) {
592
+ const n = parseInt(flag.slice(3), 10);
593
+ if (isNaN(n) || n < 0) {
594
+ throw new Error(
595
+ `Invalid allow list entry "${entry}": invalid calldataLength in flag "${flag}". Expected a non-negative integer.`,
596
+ );
597
+ }
598
+ result.calldataLength = n;
599
+ } else {
600
+ throw new Error(`Invalid allow list entry "${entry}": unknown flag "${flag}". Supported flags: os, rn, cl=N.`);
601
+ }
602
+ }
603
+ return result;
604
+ }
605
+
524
606
  /**
525
607
  * Parses a string to a list of allowed elements.
526
- * Each encoded is expected to be of one of the following formats
527
- * `I:${address}`
528
- * `I:${address}:${selector}`
529
- * `C:${classId}`
530
- * `C:${classId}:${selector}`
608
+ * Each entry is expected to be of one of the following formats:
609
+ * `I:${address}:${selector}` — instance (contract address) with function selector
610
+ * `C:${classId}:${selector}` — class with function selector
611
+ *
612
+ * An optional flags segment can be appended after the selector:
613
+ * `I:${address}:${selector}:${flags}` or `C:${classId}:${selector}:${flags}`
614
+ * where flags is a `+`-separated list of: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength).
531
615
  *
532
616
  * @param value The string to parse
533
617
  * @returns A list of allowed elements
@@ -540,31 +624,37 @@ export function parseAllowList(value: string): AllowedElement[] {
540
624
  }
541
625
 
542
626
  for (const val of value.split(',')) {
543
- const [typeString, identifierString, selectorString] = val.split(':');
544
- const selector = selectorString !== undefined ? FunctionSelector.fromString(selectorString) : undefined;
627
+ const trimmed = val.trim();
628
+ if (!trimmed) {
629
+ continue;
630
+ }
631
+ const [typeString, identifierString, selectorString, flagsString] = trimmed.split(':');
632
+
633
+ if (!selectorString) {
634
+ throw new Error(
635
+ `Invalid allow list entry "${trimmed}": selector is required. Expected format: I:address:selector or C:classId:selector`,
636
+ );
637
+ }
638
+
639
+ const selector = FunctionSelector.fromString(selectorString);
640
+ const flags = flagsString ? parseFlags(flagsString, trimmed) : {};
545
641
 
546
642
  if (typeString === 'I') {
547
- if (selector) {
548
- entries.push({
549
- address: AztecAddress.fromString(identifierString),
550
- selector,
551
- });
552
- } else {
553
- entries.push({
554
- address: AztecAddress.fromString(identifierString),
555
- });
556
- }
643
+ entries.push({
644
+ address: AztecAddress.fromString(identifierString),
645
+ selector,
646
+ ...flags,
647
+ });
557
648
  } else if (typeString === 'C') {
558
- if (selector) {
559
- entries.push({
560
- classId: Fr.fromHexString(identifierString),
561
- selector,
562
- });
563
- } else {
564
- entries.push({
565
- classId: Fr.fromHexString(identifierString),
566
- });
567
- }
649
+ entries.push({
650
+ classId: Fr.fromHexString(identifierString),
651
+ selector,
652
+ ...flags,
653
+ });
654
+ } else {
655
+ throw new Error(
656
+ `Invalid allow list entry "${trimmed}": unknown type "${typeString}". Expected "I" (instance) or "C" (class).`,
657
+ );
568
658
  }
569
659
  }
570
660
 
@@ -32,10 +32,11 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
32
32
 
33
33
  if (context.event === EvictionEvent.BLOCK_MINED) {
34
34
  const blockNumber = context.block.getBlockNumber();
35
+ const blockHash = await context.block.hash();
35
36
  // Ensure world state is synced to this block before accessing the snapshot.
36
37
  // This handles the race where a block is added to the archiver
37
38
  // but the world state hasn't synced it yet.
38
- await this.worldState.syncImmediate(blockNumber);
39
+ await this.worldState.syncImmediate(blockNumber, blockHash);
39
40
  return await this.evictForFeePayers(context.feePayers, this.worldState.getSnapshot(blockNumber), txPool);
40
41
  }
41
42
 
@@ -1,3 +1,4 @@
1
+ import { minBigint } from '@aztec/foundation/bigint';
1
2
  import { Buffer32 } from '@aztec/foundation/buffer';
2
3
  import type { Tx } from '@aztec/stdlib/tx';
3
4
 
@@ -11,10 +12,9 @@ export function getPendingTxPriority(tx: Tx): string {
11
12
  }
12
13
 
13
14
  /**
14
- * Returns the priority of a tx.
15
+ * Returns the priority of a tx based on the L2 priority fee only, capped by the max fees per gas.
15
16
  */
16
17
  export function getTxPriorityFee(tx: Tx): bigint {
17
- const priorityFees = tx.getGasSettings().maxPriorityFeesPerGas;
18
- const totalFees = priorityFees.feePerDaGas + priorityFees.feePerL2Gas;
19
- return totalFees;
18
+ const { maxPriorityFeesPerGas: priorityFees, maxFeesPerGas } = tx.getGasSettings();
19
+ return minBigint(maxFeesPerGas.feePerL2Gas, priorityFees.feePerL2Gas);
20
20
  }
@@ -204,7 +204,9 @@ export function describeTxPool(getTxPool: () => TxPool) {
204
204
 
205
205
  it('returns pending tx hashes sorted by priority', async () => {
206
206
  const withPriorityFee = (tx: Tx, fee: number) => {
207
- unfreeze(tx.data.constants.txContext.gasSettings).maxPriorityFeesPerGas = new GasFees(fee, fee);
207
+ const gs = unfreeze(tx.data.constants.txContext.gasSettings);
208
+ gs.maxPriorityFeesPerGas = new GasFees(fee, fee);
209
+ gs.maxFeesPerGas = new GasFees(fee, fee);
208
210
  return tx;
209
211
  };
210
212
 
@@ -158,7 +158,7 @@ Checked before adding a transaction to the pending pool:
158
158
 
159
159
  | Rule | Purpose |
160
160
  |------|---------|
161
- | `NullifierConflictRule` | Handles transactions with conflicting nullifiers. Higher priority tx wins. |
161
+ | `NullifierConflictRule` | Handles transactions with conflicting nullifiers. Higher priority tx wins. For RPC submissions, a configurable price bump percentage is required. |
162
162
  | `FeePayerBalancePreAddRule` | Ensures fee payer has sufficient balance for all their pending txs. |
163
163
  | `LowPriorityPreAddRule` | Rejects txs when pool is full and new tx has lowest priority. |
164
164
 
@@ -233,6 +233,14 @@ await pool.updateConfig({
233
233
  });
234
234
  ```
235
235
 
236
+ ### Price Bump (RPC Transaction Replacement)
237
+
238
+ When a transaction is submitted via RPC and clashes on nullifiers with an existing pool transaction, the incoming tx must pay at least `priceBumpPercentage`% more in priority fee (i.e. `>= existingFee + existingFee * bump / 100`) to replace it. This prevents spam via small fee increments. The same bump applies when the pool is full and the incoming tx needs to evict the lowest-priority tx.
239
+
240
+ - **Env var**: `P2P_RPC_PRICE_BUMP_PERCENTAGE` (default: 10)
241
+ - **Scope**: RPC submissions only. P2P gossip uses `comparePriority` (fee + hash tiebreaker) with no bump.
242
+ - Even with a 0% bump, a replacement tx must pay at least 1 unit more than the existing fee.
243
+
236
244
  ## Return Values
237
245
 
238
246
  ### AddTxsResult
@@ -29,12 +29,13 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
29
29
 
30
30
  if (context.event === EvictionEvent.BLOCK_MINED) {
31
31
  const blockNumber = context.block.getBlockNumber();
32
- await this.worldState.syncImmediate(blockNumber);
32
+ const blockHash = await context.block.hash();
33
+ await this.worldState.syncImmediate(blockNumber, blockHash);
33
34
  return await this.evictForFeePayers(context.feePayers, this.worldState.getSnapshot(blockNumber), pool);
34
35
  }
35
36
 
36
37
  if (context.event === EvictionEvent.CHAIN_PRUNED) {
37
- await this.worldState.syncImmediate(context.blockNumber);
38
+ await this.worldState.syncImmediate();
38
39
  const feePayers = pool.getPendingFeePayers();
39
40
  return await this.evictForFeePayers(feePayers, this.worldState.getSnapshot(context.blockNumber), pool);
40
41
  }
@@ -35,6 +35,7 @@ export class FeePayerBalancePreAddRule implements PreAddRule {
35
35
  // Create combined list with incoming tx
36
36
  const allTxs: Array<{
37
37
  txHash: string;
38
+ txHashBigInt: bigint;
38
39
  priorityFee: bigint;
39
40
  feeLimit: bigint;
40
41
  claimAmount: bigint;
@@ -42,6 +43,7 @@ export class FeePayerBalancePreAddRule implements PreAddRule {
42
43
  }> = [
43
44
  ...existingTxs.map(t => ({
44
45
  txHash: t.txHash,
46
+ txHashBigInt: t.txHashBigInt,
45
47
  priorityFee: t.priorityFee,
46
48
  feeLimit: t.feeLimit,
47
49
  claimAmount: t.claimAmount,
@@ -49,6 +51,7 @@ export class FeePayerBalancePreAddRule implements PreAddRule {
49
51
  })),
50
52
  {
51
53
  txHash: incomingMeta.txHash,
54
+ txHashBigInt: incomingMeta.txHashBigInt,
52
55
  priorityFee: incomingMeta.priorityFee,
53
56
  feeLimit: incomingMeta.feeLimit,
54
57
  claimAmount: incomingMeta.claimAmount,
@@ -100,7 +100,15 @@ export type TxPoolRejectionError =
100
100
  availableBalance: bigint;
101
101
  feeLimit: bigint;
102
102
  }
103
- | { code: typeof TxPoolRejectionCode.NULLIFIER_CONFLICT; message: string; conflictingTxHash: string }
103
+ | {
104
+ code: typeof TxPoolRejectionCode.NULLIFIER_CONFLICT;
105
+ message: string;
106
+ conflictingTxHash: string;
107
+ /** Minimum fee needed to replace the conflicting tx (only set when price bump applies). */
108
+ minimumPriceBumpFee?: bigint;
109
+ /** Incoming tx's priority fee. */
110
+ txPriorityFee?: bigint;
111
+ }
104
112
  | { code: typeof TxPoolRejectionCode.INTERNAL_ERROR; message: string };
105
113
 
106
114
  /**
@@ -121,6 +129,8 @@ export interface PreAddResult {
121
129
  export interface PreAddContext {
122
130
  /** If true, compare priority fee only (no tx hash tiebreaker). Used for RPC submissions. */
123
131
  feeComparisonOnly?: boolean;
132
+ /** Percentage-based price bump required for tx replacement. Only set for RPC submissions. */
133
+ priceBumpPercentage?: bigint;
124
134
  }
125
135
 
126
136
  /**
@@ -45,8 +45,8 @@ export class InvalidTxsAfterReorgRule implements EvictionRule {
45
45
  txsByBlockHash.get(blockHashStr)!.push(meta.txHash);
46
46
  }
47
47
 
48
- // Ensure world state is synced to this block before accessing the snapshot
49
- await this.worldState.syncImmediate(context.blockNumber);
48
+ // Sync without a block number to ensure the world state processes the prune event.
49
+ await this.worldState.syncImmediate();
50
50
  const db = this.worldState.getSnapshot(context.blockNumber);
51
51
 
52
52
  // Check which blocks exist in the archive
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from '@aztec/foundation/log';
2
2
 
3
- import { type TxMetaData, comparePriority } from '../tx_metadata.js';
3
+ import { type TxMetaData, comparePriority, getMinimumPriceBumpFee } from '../tx_metadata.js';
4
4
  import {
5
5
  type EvictionConfig,
6
6
  type PreAddContext,
@@ -48,10 +48,14 @@ export class LowPriorityPreAddRule implements PreAddRule {
48
48
  }
49
49
 
50
50
  // Compare incoming tx against lowest priority tx.
51
- // feeOnly mode (RPC): use strict fee comparison only — avoids churn from hash ordering
52
- // Default (gossip): use full comparePriority (fee + tx hash tiebreaker) for determinism
51
+ // feeOnly mode (RPC): use strict fee comparison only — avoids churn from hash ordering.
52
+ // When price bump is also set, require the bumped fee threshold.
53
+ // Default (gossip): use full comparePriority (fee + tx hash tiebreaker) for determinism.
53
54
  const isHigherPriority = context?.feeComparisonOnly
54
- ? incomingMeta.priorityFee > lowestPriorityMeta.priorityFee
55
+ ? context.priceBumpPercentage !== undefined
56
+ ? incomingMeta.priorityFee >=
57
+ getMinimumPriceBumpFee(lowestPriorityMeta.priorityFee, context.priceBumpPercentage)
58
+ : incomingMeta.priorityFee > lowestPriorityMeta.priorityFee
55
59
  : comparePriority(incomingMeta, lowestPriorityMeta) > 0;
56
60
 
57
61
  if (isHigherPriority) {
@@ -66,6 +70,11 @@ export class LowPriorityPreAddRule implements PreAddRule {
66
70
  }
67
71
 
68
72
  // Incoming tx has equal or lower priority - ignore it (it would be evicted anyway)
73
+ const minimumFee =
74
+ context?.feeComparisonOnly && context.priceBumpPercentage !== undefined
75
+ ? getMinimumPriceBumpFee(lowestPriorityMeta.priorityFee, context.priceBumpPercentage)
76
+ : lowestPriorityMeta.priorityFee + 1n;
77
+
69
78
  this.log.debug(
70
79
  `Pool at capacity (${currentCount}/${this.maxPoolSize}), ignoring ${incomingMeta.txHash} ` +
71
80
  `(priority ${incomingMeta.priorityFee}) - lower than existing minimum (priority ${lowestPriorityMeta.priorityFee})`,
@@ -75,8 +84,8 @@ export class LowPriorityPreAddRule implements PreAddRule {
75
84
  txHashesToEvict: [],
76
85
  reason: {
77
86
  code: TxPoolRejectionCode.LOW_PRIORITY_FEE,
78
- message: `Tx does not meet minimum priority fee. Required: ${lowestPriorityMeta.priorityFee + 1n}, got: ${incomingMeta.priorityFee}`,
79
- minimumPriorityFee: lowestPriorityMeta.priorityFee + 1n,
87
+ message: `Tx does not meet minimum priority fee. Required: ${minimumFee}, got: ${incomingMeta.priorityFee}`,
88
+ minimumPriorityFee: minimumFee,
80
89
  txPriorityFee: incomingMeta.priorityFee,
81
90
  },
82
91
  });