@aztec/p2p 0.0.1-commit.181e2d196 → 0.0.1-commit.1a421b1a1

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 (120) hide show
  1. package/dest/client/factory.d.ts +2 -2
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +8 -2
  4. package/dest/client/p2p_client.d.ts +1 -1
  5. package/dest/client/p2p_client.d.ts.map +1 -1
  6. package/dest/client/p2p_client.js +8 -26
  7. package/dest/config.d.ts +18 -2
  8. package/dest/config.d.ts.map +1 -1
  9. package/dest/config.js +56 -7
  10. package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
  11. package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
  12. package/dest/mem_pools/tx_pool/priority.js +4 -3
  13. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
  14. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  15. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
  16. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
  17. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  18. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
  19. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  20. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
  21. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
  22. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  23. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  24. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +3 -1
  25. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  26. package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
  27. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +17 -9
  28. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +26 -9
  30. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
  31. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  32. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +26 -43
  33. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +1 -1
  34. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +10 -2
  36. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +5 -4
  37. package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
  38. package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
  39. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +5 -4
  40. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
  41. package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
  42. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +12 -9
  43. package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
  44. package/dest/msg_validators/proposal_validator/proposal_validator.js +46 -44
  45. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +1 -1
  46. package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
  47. package/dest/msg_validators/tx_validator/allowed_public_setup.js +21 -32
  48. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
  49. package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
  50. package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
  51. package/dest/msg_validators/tx_validator/factory.d.ts +15 -4
  52. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  53. package/dest/msg_validators/tx_validator/factory.js +13 -6
  54. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
  55. package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
  56. package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
  57. package/dest/msg_validators/tx_validator/gas_validator.d.ts +13 -4
  58. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  59. package/dest/msg_validators/tx_validator/gas_validator.js +39 -9
  60. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  61. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  62. package/dest/msg_validators/tx_validator/index.js +1 -0
  63. package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
  64. package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
  65. package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
  66. package/dest/msg_validators/tx_validator/phases_validator.d.ts +1 -1
  67. package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
  68. package/dest/msg_validators/tx_validator/phases_validator.js +21 -1
  69. package/dest/services/libp2p/libp2p_service.d.ts +1 -1
  70. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  71. package/dest/services/libp2p/libp2p_service.js +41 -16
  72. package/dest/test-helpers/make-test-p2p-clients.d.ts +1 -1
  73. package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
  74. package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
  75. package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
  76. package/dest/test-helpers/testbench-utils.d.ts +1 -1
  77. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  78. package/dest/test-helpers/testbench-utils.js +2 -1
  79. package/dest/testbench/p2p_client_testbench_worker.js +2 -1
  80. package/dest/testbench/worker_client_manager.d.ts +3 -1
  81. package/dest/testbench/worker_client_manager.d.ts.map +1 -1
  82. package/dest/testbench/worker_client_manager.js +6 -2
  83. package/dest/util.d.ts +1 -1
  84. package/package.json +14 -14
  85. package/src/client/factory.ts +8 -1
  86. package/src/client/p2p_client.ts +5 -26
  87. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
  88. package/src/config.ts +75 -4
  89. package/src/mem_pools/tx_pool/priority.ts +6 -3
  90. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
  91. package/src/mem_pools/tx_pool_v2/README.md +9 -1
  92. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
  93. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
  94. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
  95. package/src/mem_pools/tx_pool_v2/interfaces.ts +3 -0
  96. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +41 -11
  97. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +29 -43
  98. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +11 -2
  99. package/src/msg_validators/proposal_validator/block_proposal_validator.ts +13 -3
  100. package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +19 -6
  101. package/src/msg_validators/proposal_validator/proposal_validator.ts +57 -48
  102. package/src/msg_validators/tx_validator/allowed_public_setup.ts +16 -35
  103. package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
  104. package/src/msg_validators/tx_validator/factory.ts +15 -2
  105. package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
  106. package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
  107. package/src/msg_validators/tx_validator/index.ts +1 -0
  108. package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
  109. package/src/msg_validators/tx_validator/phases_validator.ts +29 -0
  110. package/src/services/libp2p/libp2p_service.ts +39 -15
  111. package/src/test-helpers/make-test-p2p-clients.ts +1 -1
  112. package/src/test-helpers/reqresp-nodes.ts +1 -1
  113. package/src/test-helpers/testbench-utils.ts +1 -0
  114. package/src/testbench/p2p_client_testbench_worker.ts +2 -1
  115. package/src/testbench/worker_client_manager.ts +13 -5
  116. package/src/util.ts +1 -1
  117. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -24
  118. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
  119. package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -378
  120. package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -373
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/p2p",
3
- "version": "0.0.1-commit.181e2d196",
3
+ "version": "0.0.1-commit.1a421b1a1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -67,17 +67,17 @@
67
67
  ]
68
68
  },
69
69
  "dependencies": {
70
- "@aztec/constants": "0.0.1-commit.181e2d196",
71
- "@aztec/epoch-cache": "0.0.1-commit.181e2d196",
72
- "@aztec/ethereum": "0.0.1-commit.181e2d196",
73
- "@aztec/foundation": "0.0.1-commit.181e2d196",
74
- "@aztec/kv-store": "0.0.1-commit.181e2d196",
75
- "@aztec/noir-contracts.js": "0.0.1-commit.181e2d196",
76
- "@aztec/noir-protocol-circuits-types": "0.0.1-commit.181e2d196",
77
- "@aztec/protocol-contracts": "0.0.1-commit.181e2d196",
78
- "@aztec/simulator": "0.0.1-commit.181e2d196",
79
- "@aztec/stdlib": "0.0.1-commit.181e2d196",
80
- "@aztec/telemetry-client": "0.0.1-commit.181e2d196",
70
+ "@aztec/constants": "0.0.1-commit.1a421b1a1",
71
+ "@aztec/epoch-cache": "0.0.1-commit.1a421b1a1",
72
+ "@aztec/ethereum": "0.0.1-commit.1a421b1a1",
73
+ "@aztec/foundation": "0.0.1-commit.1a421b1a1",
74
+ "@aztec/kv-store": "0.0.1-commit.1a421b1a1",
75
+ "@aztec/noir-contracts.js": "0.0.1-commit.1a421b1a1",
76
+ "@aztec/noir-protocol-circuits-types": "0.0.1-commit.1a421b1a1",
77
+ "@aztec/protocol-contracts": "0.0.1-commit.1a421b1a1",
78
+ "@aztec/simulator": "0.0.1-commit.1a421b1a1",
79
+ "@aztec/stdlib": "0.0.1-commit.1a421b1a1",
80
+ "@aztec/telemetry-client": "0.0.1-commit.1a421b1a1",
81
81
  "@chainsafe/libp2p-gossipsub": "13.0.0",
82
82
  "@chainsafe/libp2p-noise": "^15.0.0",
83
83
  "@chainsafe/libp2p-yamux": "^6.0.2",
@@ -104,8 +104,8 @@
104
104
  "xxhash-wasm": "^1.1.0"
105
105
  },
106
106
  "devDependencies": {
107
- "@aztec/archiver": "0.0.1-commit.181e2d196",
108
- "@aztec/world-state": "0.0.1-commit.181e2d196",
107
+ "@aztec/archiver": "0.0.1-commit.1a421b1a1",
108
+ "@aztec/world-state": "0.0.1-commit.1a421b1a1",
109
109
  "@jest/globals": "^30.0.0",
110
110
  "@types/jest": "^30.0.0",
111
111
  "@types/node": "^22.15.17",
@@ -3,12 +3,12 @@ import { BlockNumber } from '@aztec/foundation/branded-types';
3
3
  import { type Logger, createLogger } from '@aztec/foundation/log';
4
4
  import { DateProvider } from '@aztec/foundation/timer';
5
5
  import type { AztecAsyncKVStore } from '@aztec/kv-store';
6
- import type { DataStoreConfig } from '@aztec/kv-store/config';
7
6
  import { AztecLMDBStoreV2, createStore } from '@aztec/kv-store/lmdb-v2';
8
7
  import type { L2BlockSource } from '@aztec/stdlib/block';
9
8
  import type { ChainConfig } from '@aztec/stdlib/config';
10
9
  import type { ContractDataSource } from '@aztec/stdlib/contract';
11
10
  import type { AztecNode, ClientProtocolCircuitVerifier, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
11
+ import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
12
12
  import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
13
13
 
14
14
  import { P2PClient } from '../client/p2p_client.js';
@@ -87,10 +87,16 @@ export async function createP2PClient(
87
87
  // We accept transactions if they are not expired by the next slot and block number (checked based on the ExpirationTimestamp field)
88
88
  const currentBlockNumber = await archiver.getBlockNumber();
89
89
  const { ts: nextSlotTimestamp } = epochCache.getEpochAndSlotInNextL1Slot();
90
+ const l1Constants = await archiver.getL1Constants();
90
91
  return createTxValidatorForTransactionsEnteringPendingTxPool(
91
92
  worldStateSynchronizer,
92
93
  nextSlotTimestamp,
93
94
  BlockNumber(currentBlockNumber + 1),
95
+ {
96
+ rollupManaLimit: l1Constants.rollupManaLimit,
97
+ maxBlockL2Gas: config.validateMaxL2BlockGas,
98
+ maxBlockDAGas: config.validateMaxDABlockGas,
99
+ },
94
100
  );
95
101
  },
96
102
  },
@@ -100,6 +106,7 @@ export async function createP2PClient(
100
106
  archivedTxLimit: config.archivedTxLimit,
101
107
  minTxPoolAgeMs: config.minTxPoolAgeMs,
102
108
  dropTransactionsProbability: config.dropTransactionsProbability,
109
+ priceBumpPercentage: config.priceBumpPercentage,
103
110
  },
104
111
  dateProvider,
105
112
  );
@@ -18,7 +18,6 @@ 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
22
  import { type BlockProposal, CheckpointAttestation, type CheckpointProposal, type TopicType } from '@aztec/stdlib/p2p';
24
23
  import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
@@ -111,27 +110,6 @@ export class P2PClient extends WithTracer implements P2P {
111
110
  this.telemetry,
112
111
  );
113
112
 
114
- // Default to collecting all txs when we see a valid proposal
115
- // This can be overridden by the validator client to validate, and it will call getTxsForBlockProposal on its own
116
- // Note: Validators do NOT attest to individual blocks - attestations are only for checkpoint proposals.
117
- // TODO(palla/txs): We should not trigger a request for txs on a proposal before fully validating it. We need to bring
118
- // validator-client code into here so we can validate a proposal is reasonable.
119
- this.registerBlockProposalHandler(async (block, sender) => {
120
- this.log.debug(`Received block proposal from ${sender.toString()}`);
121
- // TODO(palla/txs): Need to subtract validatorReexecuteDeadlineMs from this deadline (see ValidatorClient.getReexecutionDeadline)
122
- const constants = this.txCollection.getConstants();
123
- const nextSlotTimestampSeconds = Number(getTimestampForSlot(SlotNumber(block.slotNumber + 1), constants));
124
- const deadline = new Date(nextSlotTimestampSeconds * 1000);
125
- const parentBlock = await this.l2BlockSource.getBlockHeaderByArchive(block.blockHeader.lastArchive.root);
126
- if (!parentBlock) {
127
- this.log.debug(`Cannot collect txs for proposal as parent block not found`);
128
- return false;
129
- }
130
- const blockNumber = BlockNumber(parentBlock.getBlockNumber() + 1);
131
- await this.txProvider.getTxsForBlockProposal(block, blockNumber, { pinnedPeer: sender, deadline });
132
- return true;
133
- });
134
-
135
113
  this.l2Tips = new L2TipsKVStore(store, 'p2p_client');
136
114
  this.synchedLatestSlot = store.openSingleton('p2p_pool_last_l2_slot');
137
115
  }
@@ -701,10 +679,11 @@ export class P2PClient extends WithTracer implements P2P {
701
679
  if (oldCheckpointNumber <= CheckpointNumber.ZERO) {
702
680
  return false;
703
681
  }
704
- const isEpochPrune = oldCheckpointNumber !== newCheckpoint.number;
705
- this.log.info(
706
- `Detected epoch prune: ${isEpochPrune}. Old checkpoint: ${oldCheckpointNumber}, new checkpoint: ${newCheckpoint.number}`,
707
- );
682
+ const newCheckpointNumber = newCheckpoint.number;
683
+ const isEpochPrune = oldCheckpointNumber !== newCheckpointNumber;
684
+ if (isEpochPrune) {
685
+ this.log.info(`Detected epoch prune to ${newCheckpointNumber}`, { oldCheckpointNumber, newCheckpointNumber });
686
+ }
708
687
  return isEpochPrune;
709
688
  }
710
689
 
@@ -3,11 +3,11 @@ import { SecretValue } from '@aztec/foundation/config';
3
3
  import { createLogger } from '@aztec/foundation/log';
4
4
  import { sleep } from '@aztec/foundation/sleep';
5
5
  import { DateProvider, Timer, executeTimeout } from '@aztec/foundation/timer';
6
- import type { DataStoreConfig } from '@aztec/kv-store/config';
7
6
  import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
8
7
  import type { L2BlockSource } from '@aztec/stdlib/block';
9
8
  import type { ContractDataSource } from '@aztec/stdlib/contract';
10
9
  import type { ClientProtocolCircuitVerifier } from '@aztec/stdlib/interfaces/server';
10
+ import type { DataStoreConfig } from '@aztec/stdlib/kv-store';
11
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';
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,
@@ -10,7 +11,6 @@ import {
10
11
  secretStringConfigHelper,
11
12
  } from '@aztec/foundation/config';
12
13
  import { Fr } from '@aztec/foundation/curves/bn254';
13
- import { type DataStoreConfig, dataConfigMappings } from '@aztec/kv-store/config';
14
14
  import { FunctionSelector } from '@aztec/stdlib/abi/function-selector';
15
15
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
16
16
  import {
@@ -20,6 +20,7 @@ import {
20
20
  chainConfigMappings,
21
21
  sharedSequencerConfigMappings,
22
22
  } from '@aztec/stdlib/config';
23
+ import { type DataStoreConfig, dataConfigMappings } from '@aztec/stdlib/kv-store';
23
24
 
24
25
  import {
25
26
  type BatchTxRequesterConfig,
@@ -39,6 +40,15 @@ export interface P2PConfig
39
40
  TxCollectionConfig,
40
41
  TxFileStoreConfig,
41
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 L2 gas per block for validation. When set, txs exceeding this limit are rejected. */
47
+ validateMaxL2BlockGas?: number;
48
+
49
+ /** Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. */
50
+ validateMaxDABlockGas?: number;
51
+
42
52
  /** A flag dictating whether the P2P subsystem should be enabled. */
43
53
  p2pEnabled: boolean;
44
54
 
@@ -190,11 +200,30 @@ export interface P2PConfig
190
200
 
191
201
  /** Minimum age (ms) a transaction must have been in the pool before it's eligible for block building. */
192
202
  minTxPoolAgeMs: number;
203
+
204
+ /** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
205
+ priceBumpPercentage: bigint;
193
206
  }
194
207
 
195
208
  export const DEFAULT_P2P_PORT = 40400;
196
209
 
197
210
  export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
211
+ validateMaxTxsPerBlock: {
212
+ env: 'VALIDATOR_MAX_TX_PER_BLOCK',
213
+ description:
214
+ 'Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set.',
215
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
216
+ },
217
+ validateMaxL2BlockGas: {
218
+ env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
219
+ description: 'Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected.',
220
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
221
+ },
222
+ validateMaxDABlockGas: {
223
+ env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
224
+ description: 'Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected.',
225
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
226
+ },
198
227
  p2pEnabled: {
199
228
  env: 'P2P_ENABLED',
200
229
  description: 'A flag dictating whether the P2P subsystem should be enabled.',
@@ -397,9 +426,9 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
397
426
  env: 'TX_PUBLIC_SETUP_ALLOWLIST',
398
427
  parseEnv: (val: string) => parseAllowList(val),
399
428
  description:
400
- 'Additional entries to extend the default setup allow list. Format: I:address:selector,C:classId:selector',
429
+ '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 +.',
401
430
  printDefault: () =>
402
- 'Default: AuthRegistry._set_authorized, FeeJuice._increase_public_balance, Token._increase_public_balance, Token.transfer_in_public',
431
+ 'Default: AuthRegistry._set_authorized, AuthRegistry.set_authorized, FeeJuice._increase_public_balance',
403
432
  },
404
433
  maxPendingTxCount: {
405
434
  env: 'P2P_MAX_PENDING_TX_COUNT',
@@ -465,6 +494,12 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
465
494
  description: 'Minimum age (ms) a transaction must have been in the pool before it is eligible for block building.',
466
495
  ...numberConfigHelper(2_000),
467
496
  },
497
+ priceBumpPercentage: {
498
+ env: 'P2P_RPC_PRICE_BUMP_PERCENTAGE',
499
+ description:
500
+ 'Minimum percentage fee increase required to replace an existing tx via RPC. Even at 0%, replacement still requires paying at least 1 unit more.',
501
+ ...bigintConfigHelper(10n),
502
+ },
468
503
  ...sharedSequencerConfigMappings,
469
504
  ...p2pReqRespConfigMappings,
470
505
  ...batchTxRequesterConfigMappings,
@@ -522,12 +557,45 @@ export const bootnodeConfigMappings = pickConfigMappings(
522
557
  bootnodeConfigKeys,
523
558
  );
524
559
 
560
+ /**
561
+ * Parses a `+`-separated flags string into validation properties for an allow list entry.
562
+ * Supported flags: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength).
563
+ */
564
+ function parseFlags(
565
+ flags: string,
566
+ entry: string,
567
+ ): { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } {
568
+ const result: { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } = {};
569
+ for (const flag of flags.split('+')) {
570
+ if (flag === 'os') {
571
+ result.onlySelf = true;
572
+ } else if (flag === 'rn') {
573
+ result.rejectNullMsgSender = true;
574
+ } else if (flag.startsWith('cl=')) {
575
+ const n = parseInt(flag.slice(3), 10);
576
+ if (isNaN(n) || n < 0) {
577
+ throw new Error(
578
+ `Invalid allow list entry "${entry}": invalid calldataLength in flag "${flag}". Expected a non-negative integer.`,
579
+ );
580
+ }
581
+ result.calldataLength = n;
582
+ } else {
583
+ throw new Error(`Invalid allow list entry "${entry}": unknown flag "${flag}". Supported flags: os, rn, cl=N.`);
584
+ }
585
+ }
586
+ return result;
587
+ }
588
+
525
589
  /**
526
590
  * Parses a string to a list of allowed elements.
527
591
  * Each entry is expected to be of one of the following formats:
528
592
  * `I:${address}:${selector}` — instance (contract address) with function selector
529
593
  * `C:${classId}:${selector}` — class with function selector
530
594
  *
595
+ * An optional flags segment can be appended after the selector:
596
+ * `I:${address}:${selector}:${flags}` or `C:${classId}:${selector}:${flags}`
597
+ * where flags is a `+`-separated list of: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength).
598
+ *
531
599
  * @param value The string to parse
532
600
  * @returns A list of allowed elements
533
601
  */
@@ -543,7 +611,7 @@ export function parseAllowList(value: string): AllowedElement[] {
543
611
  if (!trimmed) {
544
612
  continue;
545
613
  }
546
- const [typeString, identifierString, selectorString] = trimmed.split(':');
614
+ const [typeString, identifierString, selectorString, flagsString] = trimmed.split(':');
547
615
 
548
616
  if (!selectorString) {
549
617
  throw new Error(
@@ -552,16 +620,19 @@ export function parseAllowList(value: string): AllowedElement[] {
552
620
  }
553
621
 
554
622
  const selector = FunctionSelector.fromString(selectorString);
623
+ const flags = flagsString ? parseFlags(flagsString, trimmed) : {};
555
624
 
556
625
  if (typeString === 'I') {
557
626
  entries.push({
558
627
  address: AztecAddress.fromString(identifierString),
559
628
  selector,
629
+ ...flags,
560
630
  });
561
631
  } else if (typeString === 'C') {
562
632
  entries.push({
563
633
  classId: Fr.fromHexString(identifierString),
564
634
  selector,
635
+ ...flags,
565
636
  });
566
637
  } else {
567
638
  throw new Error(
@@ -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,12 @@ 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 priority fees, 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;
18
+ const { maxPriorityFeesPerGas: priorityFees, maxFeesPerGas } = tx.getGasSettings();
19
+ const totalFees =
20
+ minBigint(maxFeesPerGas.feePerDaGas, priorityFees.feePerDaGas) +
21
+ minBigint(maxFeesPerGas.feePerL2Gas, priorityFees.feePerL2Gas);
19
22
  return totalFees;
20
23
  }
@@ -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
@@ -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
  /**
@@ -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
  });
@@ -15,11 +15,12 @@ export class NullifierConflictRule implements PreAddRule {
15
15
 
16
16
  private log = createLogger('p2p:tx_pool_v2:nullifier_conflict_rule');
17
17
 
18
- check(incomingMeta: TxMetaData, poolAccess: PreAddPoolAccess, _context?: PreAddContext): Promise<PreAddResult> {
18
+ check(incomingMeta: TxMetaData, poolAccess: PreAddPoolAccess, context?: PreAddContext): Promise<PreAddResult> {
19
19
  const result = checkNullifierConflict(
20
20
  incomingMeta,
21
21
  nullifier => poolAccess.getTxHashByNullifier(nullifier),
22
22
  txHash => poolAccess.getMetadata(txHash),
23
+ context?.priceBumpPercentage,
23
24
  );
24
25
 
25
26
  if (result.shouldIgnore) {
@@ -46,6 +46,8 @@ export type TxPoolV2Config = {
46
46
  evictedTxCacheSize: number;
47
47
  /** The probability (0-1) that a transaction is discarded. 0 disables dropping. For testing purposes only. */
48
48
  dropTransactionsProbability: number;
49
+ /** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
50
+ priceBumpPercentage: bigint;
49
51
  };
50
52
 
51
53
  /**
@@ -57,6 +59,7 @@ export const DEFAULT_TX_POOL_V2_CONFIG: TxPoolV2Config = {
57
59
  minTxPoolAgeMs: 2_000,
58
60
  evictedTxCacheSize: 10_000,
59
61
  dropTransactionsProbability: 0,
62
+ priceBumpPercentage: 10n,
60
63
  };
61
64
 
62
65
  /**
@@ -158,13 +158,13 @@ export function txHashFromBigInt(value: bigint): string {
158
158
  }
159
159
 
160
160
  /** Minimal fields required for priority comparison. */
161
- type PriorityComparable = Pick<TxMetaData, 'txHashBigInt' | 'priorityFee'>;
161
+ export type PriorityComparable = Pick<TxMetaData, 'txHash' | 'txHashBigInt' | 'priorityFee'>;
162
162
 
163
163
  /**
164
164
  * Compares two priority fees in ascending order.
165
165
  * Returns negative if a < b, positive if a > b, 0 if equal.
166
166
  */
167
- export function compareFee(a: bigint, b: bigint): number {
167
+ export function compareFee(a: bigint, b: bigint): -1 | 0 | 1 {
168
168
  return a < b ? -1 : a > b ? 1 : 0;
169
169
  }
170
170
 
@@ -173,7 +173,7 @@ export function compareFee(a: bigint, b: bigint): number {
173
173
  * Uses field element comparison for deterministic ordering.
174
174
  * Returns negative if a < b, positive if a > b, 0 if equal.
175
175
  */
176
- export function compareTxHash(a: bigint, b: bigint): number {
176
+ export function compareTxHash(a: bigint, b: bigint): -1 | 0 | 1 {
177
177
  return Fr.cmpAsBigInt(a, b);
178
178
  }
179
179
 
@@ -182,7 +182,7 @@ export function compareTxHash(a: bigint, b: bigint): number {
182
182
  * Returns negative if a < b, positive if a > b, 0 if equal.
183
183
  * Use with sort() for ascending order, or negate/reverse for descending.
184
184
  */
185
- export function comparePriority(a: PriorityComparable, b: PriorityComparable): number {
185
+ export function comparePriority(a: PriorityComparable, b: PriorityComparable): -1 | 0 | 1 {
186
186
  const feeComparison = compareFee(a.priorityFee, b.priorityFee);
187
187
  if (feeComparison !== 0) {
188
188
  return feeComparison;
@@ -190,21 +190,38 @@ export function comparePriority(a: PriorityComparable, b: PriorityComparable): n
190
190
  return compareTxHash(a.txHashBigInt, b.txHashBigInt);
191
191
  }
192
192
 
193
+ /**
194
+ * Returns the minimum fee required to replace an existing tx with the given price bump percentage.
195
+ * Uses integer arithmetic: `existingFee + existingFee * priceBumpPercentage / 100`.
196
+ */
197
+ export function getMinimumPriceBumpFee(existingFee: bigint, priceBumpPercentage: bigint): bigint {
198
+ const bump = (existingFee * priceBumpPercentage) / 100n;
199
+ // Ensure the minimum bump is at least 1, so that replacement always requires
200
+ // paying strictly more — even with 0% bump or zero existing fee.
201
+ const effectiveBump = bump > 0n ? bump : 1n;
202
+ return existingFee + effectiveBump;
203
+ }
204
+
193
205
  /**
194
206
  * Checks for nullifier conflicts between an incoming transaction and existing pool state.
195
207
  *
196
208
  * When the incoming tx shares nullifiers with existing pending txs:
197
- * - If the incoming tx has strictly higher priority, mark conflicting txs for eviction
198
- * - If any conflicting tx has equal or higher priority, ignore the incoming tx
209
+ * - If the incoming tx meets or exceeds the required priority, mark conflicting txs for eviction
210
+ * - Otherwise, ignore the incoming tx
211
+ *
212
+ * When `priceBumpPercentage` is provided (RPC path), uses fee-only comparison with the
213
+ * percentage bump instead of `comparePriority`.
199
214
  *
200
215
  * @param incomingMeta - Metadata for the incoming transaction
201
216
  * @param getTxHashByNullifier - Accessor to find which tx uses a nullifier
202
217
  * @param getMetadata - Accessor to get metadata for a tx hash
218
+ * @param priceBumpPercentage - Optional percentage bump required for fee-based replacement
203
219
  */
204
220
  export function checkNullifierConflict(
205
221
  incomingMeta: TxMetaData,
206
222
  getTxHashByNullifier: (nullifier: string) => string | undefined,
207
223
  getMetadata: (txHash: string) => TxMetaData | undefined,
224
+ priceBumpPercentage?: bigint,
208
225
  ): PreAddResult {
209
226
  const txHashesToEvict: string[] = [];
210
227
 
@@ -225,19 +242,32 @@ export function checkNullifierConflict(
225
242
  continue;
226
243
  }
227
244
 
228
- // If incoming tx has strictly higher priority, mark for eviction
229
- // Otherwise, ignore incoming tx (ties go to existing tx)
230
- // Use comparePriority for deterministic ordering (includes txHash as tiebreaker)
231
- if (comparePriority(incomingMeta, conflictingMeta) > 0) {
245
+ // When price bump is set (RPC path), require the incoming fee to meet the bumped threshold.
246
+ // Otherwise (P2P path), use full comparePriority with tx hash tiebreaker.
247
+ const isHigherPriority =
248
+ priceBumpPercentage !== undefined
249
+ ? incomingMeta.priorityFee >= getMinimumPriceBumpFee(conflictingMeta.priorityFee, priceBumpPercentage)
250
+ : comparePriority(incomingMeta, conflictingMeta) > 0;
251
+
252
+ if (isHigherPriority) {
232
253
  txHashesToEvict.push(conflictingHashStr);
233
254
  } else {
255
+ const minimumFee =
256
+ priceBumpPercentage !== undefined
257
+ ? getMinimumPriceBumpFee(conflictingMeta.priorityFee, priceBumpPercentage)
258
+ : undefined;
234
259
  return {
235
260
  shouldIgnore: true,
236
261
  txHashesToEvict: [],
237
262
  reason: {
238
263
  code: TxPoolRejectionCode.NULLIFIER_CONFLICT,
239
- message: `Nullifier conflict with existing tx ${conflictingHashStr}`,
264
+ message:
265
+ minimumFee !== undefined
266
+ ? `Nullifier conflict with existing tx ${conflictingHashStr}. Minimum required fee: ${minimumFee}, got: ${incomingMeta.priorityFee}`
267
+ : `Nullifier conflict with existing tx ${conflictingHashStr}`,
240
268
  conflictingTxHash: conflictingHashStr,
269
+ minimumPriceBumpFee: minimumFee,
270
+ txPriorityFee: minimumFee !== undefined ? incomingMeta.priorityFee : undefined,
241
271
  },
242
272
  };
243
273
  }