@aztec/p2p 0.0.1-commit.358457c → 0.0.1-commit.3895657bc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/factory.d.ts +5 -6
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +7 -6
- package/dest/client/interface.d.ts +4 -4
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +4 -4
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +1 -25
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -2
- package/dest/config.d.ts +20 -11
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +66 -32
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +5 -1
- package/dest/mem_pools/instrumentation.d.ts +4 -2
- package/dest/mem_pools/instrumentation.d.ts.map +1 -1
- package/dest/mem_pools/instrumentation.js +16 -14
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +2 -0
- package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +7 -1
- package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +8 -6
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
- package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -2
- package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/index.js +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +3 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +32 -10
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +62 -16
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +26 -44
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +3 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +41 -23
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +6 -4
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/block_proposal_validator.js +10 -2
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts +6 -4
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/checkpoint_proposal_validator.js +16 -2
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +13 -8
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +48 -36
- package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts +2 -1
- package/dest/msg_validators/tx_validator/allowed_public_setup.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/allowed_public_setup.js +24 -20
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts +17 -0
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/allowed_setup_helpers.js +24 -0
- package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts +1 -1
- package/dest/msg_validators/tx_validator/fee_payer_balance.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/fee_payer_balance.js +6 -2
- package/dest/msg_validators/tx_validator/index.d.ts +2 -1
- package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/index.js +1 -0
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.js +4 -4
- package/dest/msg_validators/tx_validator/phases_validator.d.ts +2 -2
- package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/phases_validator.js +44 -23
- package/dest/services/encoding.d.ts +2 -2
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +7 -7
- package/dest/services/libp2p/libp2p_service.d.ts +6 -7
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +22 -19
- package/dest/services/peer-manager/metrics.d.ts +3 -1
- package/dest/services/peer-manager/metrics.d.ts.map +1 -1
- package/dest/services/peer-manager/metrics.js +6 -0
- package/dest/services/peer-manager/peer_manager.d.ts +1 -1
- package/dest/services/peer-manager/peer_manager.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_manager.js +2 -1
- package/dest/services/reqresp/reqresp.d.ts +1 -1
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +2 -1
- package/dest/test-helpers/make-test-p2p-clients.d.ts +5 -6
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +1 -2
- package/dest/test-helpers/mock-pubsub.d.ts +2 -3
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +2 -2
- package/dest/test-helpers/reqresp-nodes.d.ts +2 -3
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +2 -2
- package/dest/testbench/p2p_client_testbench_worker.js +7 -6
- package/dest/testbench/worker_client_manager.d.ts +3 -1
- package/dest/testbench/worker_client_manager.d.ts.map +1 -1
- package/dest/testbench/worker_client_manager.js +6 -2
- package/dest/util.d.ts +1 -1
- package/package.json +14 -14
- package/src/client/factory.ts +10 -14
- package/src/client/interface.ts +3 -9
- package/src/client/p2p_client.ts +2 -34
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +2 -3
- package/src/config.ts +91 -34
- package/src/mem_pools/attestation_pool/attestation_pool.ts +5 -4
- package/src/mem_pools/instrumentation.ts +17 -13
- package/src/mem_pools/tx_pool_v2/README.md +9 -1
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +3 -0
- package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +11 -1
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +15 -6
- package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +2 -1
- package/src/mem_pools/tx_pool_v2/index.ts +1 -1
- package/src/mem_pools/tx_pool_v2/interfaces.ts +3 -0
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +91 -19
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +29 -43
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +3 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +48 -21
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +14 -4
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +20 -7
- package/src/msg_validators/proposal_validator/proposal_validator.ts +63 -40
- package/src/msg_validators/tx_validator/allowed_public_setup.ts +22 -27
- package/src/msg_validators/tx_validator/allowed_setup_helpers.ts +31 -0
- package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
- package/src/msg_validators/tx_validator/index.ts +1 -0
- package/src/msg_validators/tx_validator/metadata_validator.ts +12 -4
- package/src/msg_validators/tx_validator/phases_validator.ts +51 -26
- package/src/services/encoding.ts +5 -6
- package/src/services/libp2p/libp2p_service.ts +20 -17
- package/src/services/peer-manager/metrics.ts +7 -0
- package/src/services/peer-manager/peer_manager.ts +2 -1
- package/src/services/reqresp/reqresp.ts +3 -1
- package/src/test-helpers/make-test-p2p-clients.ts +1 -3
- package/src/test-helpers/mock-pubsub.ts +3 -6
- package/src/test-helpers/reqresp-nodes.ts +3 -6
- package/src/testbench/p2p_client_testbench_worker.ts +4 -7
- package/src/testbench/worker_client_manager.ts +13 -5
- package/src/util.ts +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -23
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts.map +0 -1
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.js +0 -212
- package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -230
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,
|
|
@@ -38,7 +39,10 @@ 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
|
+
|
|
42
46
|
/** A flag dictating whether the P2P subsystem should be enabled. */
|
|
43
47
|
p2pEnabled: boolean;
|
|
44
48
|
|
|
@@ -150,8 +154,8 @@ export interface P2PConfig
|
|
|
150
154
|
/** The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. */
|
|
151
155
|
p2pStoreMapSizeKb?: number;
|
|
152
156
|
|
|
153
|
-
/**
|
|
154
|
-
|
|
157
|
+
/** Additional entries to extend the default setup allow list. */
|
|
158
|
+
txPublicSetupAllowListExtend: AllowedElement[];
|
|
155
159
|
|
|
156
160
|
/** The maximum number of pending txs before evicting lower priority txs. */
|
|
157
161
|
maxPendingTxCount: number;
|
|
@@ -190,11 +194,20 @@ export interface P2PConfig
|
|
|
190
194
|
|
|
191
195
|
/** Minimum age (ms) a transaction must have been in the pool before it's eligible for block building. */
|
|
192
196
|
minTxPoolAgeMs: number;
|
|
197
|
+
|
|
198
|
+
/** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
|
|
199
|
+
priceBumpPercentage: bigint;
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
export const DEFAULT_P2P_PORT = 40400;
|
|
196
203
|
|
|
197
204
|
export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
|
|
205
|
+
validateMaxTxsPerBlock: {
|
|
206
|
+
env: 'VALIDATOR_MAX_TX_PER_BLOCK',
|
|
207
|
+
description:
|
|
208
|
+
'Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set.',
|
|
209
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
210
|
+
},
|
|
198
211
|
p2pEnabled: {
|
|
199
212
|
env: 'P2P_ENABLED',
|
|
200
213
|
description: 'A flag dictating whether the P2P subsystem should be enabled.',
|
|
@@ -393,12 +406,13 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
|
|
|
393
406
|
parseEnv: (val: string | undefined) => (val ? +val : undefined),
|
|
394
407
|
description: 'The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb.',
|
|
395
408
|
},
|
|
396
|
-
|
|
409
|
+
txPublicSetupAllowListExtend: {
|
|
397
410
|
env: 'TX_PUBLIC_SETUP_ALLOWLIST',
|
|
398
411
|
parseEnv: (val: string) => parseAllowList(val),
|
|
399
|
-
description:
|
|
412
|
+
description:
|
|
413
|
+
'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
414
|
printDefault: () =>
|
|
401
|
-
'AuthRegistry
|
|
415
|
+
'Default: AuthRegistry._set_authorized, AuthRegistry.set_authorized, FeeJuice._increase_public_balance',
|
|
402
416
|
},
|
|
403
417
|
maxPendingTxCount: {
|
|
404
418
|
env: 'P2P_MAX_PENDING_TX_COUNT',
|
|
@@ -464,6 +478,12 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
|
|
|
464
478
|
description: 'Minimum age (ms) a transaction must have been in the pool before it is eligible for block building.',
|
|
465
479
|
...numberConfigHelper(2_000),
|
|
466
480
|
},
|
|
481
|
+
priceBumpPercentage: {
|
|
482
|
+
env: 'P2P_RPC_PRICE_BUMP_PERCENTAGE',
|
|
483
|
+
description:
|
|
484
|
+
'Minimum percentage fee increase required to replace an existing tx via RPC. Even at 0%, replacement still requires paying at least 1 unit more.',
|
|
485
|
+
...bigintConfigHelper(10n),
|
|
486
|
+
},
|
|
467
487
|
...sharedSequencerConfigMappings,
|
|
468
488
|
...p2pReqRespConfigMappings,
|
|
469
489
|
...batchTxRequesterConfigMappings,
|
|
@@ -521,13 +541,44 @@ export const bootnodeConfigMappings = pickConfigMappings(
|
|
|
521
541
|
bootnodeConfigKeys,
|
|
522
542
|
);
|
|
523
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Parses a `+`-separated flags string into validation properties for an allow list entry.
|
|
546
|
+
* Supported flags: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength).
|
|
547
|
+
*/
|
|
548
|
+
function parseFlags(
|
|
549
|
+
flags: string,
|
|
550
|
+
entry: string,
|
|
551
|
+
): { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } {
|
|
552
|
+
const result: { onlySelf?: boolean; rejectNullMsgSender?: boolean; calldataLength?: number } = {};
|
|
553
|
+
for (const flag of flags.split('+')) {
|
|
554
|
+
if (flag === 'os') {
|
|
555
|
+
result.onlySelf = true;
|
|
556
|
+
} else if (flag === 'rn') {
|
|
557
|
+
result.rejectNullMsgSender = true;
|
|
558
|
+
} else if (flag.startsWith('cl=')) {
|
|
559
|
+
const n = parseInt(flag.slice(3), 10);
|
|
560
|
+
if (isNaN(n) || n < 0) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`Invalid allow list entry "${entry}": invalid calldataLength in flag "${flag}". Expected a non-negative integer.`,
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
result.calldataLength = n;
|
|
566
|
+
} else {
|
|
567
|
+
throw new Error(`Invalid allow list entry "${entry}": unknown flag "${flag}". Supported flags: os, rn, cl=N.`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
|
|
524
573
|
/**
|
|
525
574
|
* Parses a string to a list of allowed elements.
|
|
526
|
-
* Each
|
|
527
|
-
* `I:${address}`
|
|
528
|
-
* `
|
|
529
|
-
*
|
|
530
|
-
*
|
|
575
|
+
* Each entry is expected to be of one of the following formats:
|
|
576
|
+
* `I:${address}:${selector}` — instance (contract address) with function selector
|
|
577
|
+
* `C:${classId}:${selector}` — class with function selector
|
|
578
|
+
*
|
|
579
|
+
* An optional flags segment can be appended after the selector:
|
|
580
|
+
* `I:${address}:${selector}:${flags}` or `C:${classId}:${selector}:${flags}`
|
|
581
|
+
* where flags is a `+`-separated list of: `os` (onlySelf), `rn` (rejectNullMsgSender), `cl=N` (calldataLength).
|
|
531
582
|
*
|
|
532
583
|
* @param value The string to parse
|
|
533
584
|
* @returns A list of allowed elements
|
|
@@ -540,31 +591,37 @@ export function parseAllowList(value: string): AllowedElement[] {
|
|
|
540
591
|
}
|
|
541
592
|
|
|
542
593
|
for (const val of value.split(',')) {
|
|
543
|
-
const
|
|
544
|
-
|
|
594
|
+
const trimmed = val.trim();
|
|
595
|
+
if (!trimmed) {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
const [typeString, identifierString, selectorString, flagsString] = trimmed.split(':');
|
|
599
|
+
|
|
600
|
+
if (!selectorString) {
|
|
601
|
+
throw new Error(
|
|
602
|
+
`Invalid allow list entry "${trimmed}": selector is required. Expected format: I:address:selector or C:classId:selector`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const selector = FunctionSelector.fromString(selectorString);
|
|
607
|
+
const flags = flagsString ? parseFlags(flagsString, trimmed) : {};
|
|
545
608
|
|
|
546
609
|
if (typeString === 'I') {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
} else {
|
|
553
|
-
entries.push({
|
|
554
|
-
address: AztecAddress.fromString(identifierString),
|
|
555
|
-
});
|
|
556
|
-
}
|
|
610
|
+
entries.push({
|
|
611
|
+
address: AztecAddress.fromString(identifierString),
|
|
612
|
+
selector,
|
|
613
|
+
...flags,
|
|
614
|
+
});
|
|
557
615
|
} else if (typeString === 'C') {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
616
|
+
entries.push({
|
|
617
|
+
classId: Fr.fromHexString(identifierString),
|
|
618
|
+
selector,
|
|
619
|
+
...flags,
|
|
620
|
+
});
|
|
621
|
+
} else {
|
|
622
|
+
throw new Error(
|
|
623
|
+
`Invalid allow list entry "${trimmed}": unknown type "${typeString}". Expected "I" (instance) or "C" (class).`,
|
|
624
|
+
);
|
|
568
625
|
}
|
|
569
626
|
}
|
|
570
627
|
|
|
@@ -359,11 +359,10 @@ export class AttestationPool {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
const address = sender.toString();
|
|
362
|
+
const ownKey = this.getAttestationKey(slotNumber, proposalId, address);
|
|
362
363
|
|
|
363
|
-
await this.checkpointAttestations.set(
|
|
364
|
-
|
|
365
|
-
attestation.toBuffer(),
|
|
366
|
-
);
|
|
364
|
+
await this.checkpointAttestations.set(ownKey, attestation.toBuffer());
|
|
365
|
+
this.metrics.trackMempoolItemAdded(ownKey);
|
|
367
366
|
|
|
368
367
|
this.log.debug(`Added own checkpoint attestation for slot ${slotNumber} from ${address}`, {
|
|
369
368
|
signature: attestation.signature.toString(),
|
|
@@ -429,6 +428,7 @@ export class AttestationPool {
|
|
|
429
428
|
const attestationEndKey = new Fr(oldestSlot).toString();
|
|
430
429
|
for await (const key of this.checkpointAttestations.keysAsync({ end: attestationEndKey })) {
|
|
431
430
|
await this.checkpointAttestations.delete(key);
|
|
431
|
+
this.metrics.trackMempoolItemRemoved(key);
|
|
432
432
|
numberOfAttestations++;
|
|
433
433
|
}
|
|
434
434
|
|
|
@@ -526,6 +526,7 @@ export class AttestationPool {
|
|
|
526
526
|
|
|
527
527
|
// Add the attestation
|
|
528
528
|
await this.checkpointAttestations.set(key, attestation.toBuffer());
|
|
529
|
+
this.metrics.trackMempoolItemAdded(key);
|
|
529
530
|
|
|
530
531
|
// Track this attestation in the per-signer-per-slot index for duplicate detection
|
|
531
532
|
const slotSignerKey = this.getSlotSignerKey(slotNumber, signerAddress);
|
|
@@ -73,7 +73,7 @@ export class PoolInstrumentation<PoolObject extends Gossipable> {
|
|
|
73
73
|
private defaultAttributes;
|
|
74
74
|
private meter: Meter;
|
|
75
75
|
|
|
76
|
-
private
|
|
76
|
+
private mempoolItemAddedTimestamp: Map<bigint | string, number> = new Map<bigint | string, number>();
|
|
77
77
|
|
|
78
78
|
constructor(
|
|
79
79
|
telemetry: TelemetryClient,
|
|
@@ -114,22 +114,26 @@ export class PoolInstrumentation<PoolObject extends Gossipable> {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
public transactionsAdded(transactions: Tx[]) {
|
|
117
|
-
|
|
118
|
-
for (const transaction of transactions) {
|
|
119
|
-
this.txAddedTimestamp.set(transaction.txHash.toBigInt(), timestamp);
|
|
120
|
-
}
|
|
117
|
+
transactions.forEach(tx => this.trackMempoolItemAdded(tx.txHash.toBigInt()));
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
public transactionsRemoved(hashes: Iterable<bigint> | Iterable<string>) {
|
|
124
|
-
const timestamp = Date.now();
|
|
125
121
|
for (const hash of hashes) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
this.trackMempoolItemRemoved(BigInt(hash));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public trackMempoolItemAdded(key: bigint | string): void {
|
|
127
|
+
this.mempoolItemAddedTimestamp.set(key, Date.now());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public trackMempoolItemRemoved(key: bigint | string): void {
|
|
131
|
+
const timestamp = Date.now();
|
|
132
|
+
const addedAt = this.mempoolItemAddedTimestamp.get(key);
|
|
133
|
+
if (addedAt !== undefined) {
|
|
134
|
+
this.mempoolItemAddedTimestamp.delete(key);
|
|
135
|
+
if (addedAt < timestamp) {
|
|
136
|
+
this.minedDelay.record(timestamp - addedAt);
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
}
|
|
@@ -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
|
|
@@ -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
|
-
| {
|
|
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
|
-
//
|
|
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
|
-
?
|
|
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: ${
|
|
79
|
-
minimumPriorityFee:
|
|
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,
|
|
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) {
|
|
@@ -7,6 +7,6 @@ export {
|
|
|
7
7
|
type PoolReadAccess,
|
|
8
8
|
DEFAULT_TX_POOL_V2_CONFIG,
|
|
9
9
|
} from './interfaces.js';
|
|
10
|
-
export { type TxMetaData, type TxState, buildTxMetaData, comparePriority } from './tx_metadata.js';
|
|
10
|
+
export { type TxMetaData, type TxState, buildTxMetaData, comparePriority, stubTxMetaData } from './tx_metadata.js';
|
|
11
11
|
export { TxArchive } from './archive/index.js';
|
|
12
12
|
export { DeletedPool } from './deleted_pool.js';
|
|
@@ -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
|
/**
|
|
@@ -3,7 +3,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
3
3
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
4
4
|
import { BlockHash, type L2BlockId } from '@aztec/stdlib/block';
|
|
5
5
|
import { Gas } from '@aztec/stdlib/gas';
|
|
6
|
-
import type
|
|
6
|
+
import { type Tx, TxHash } from '@aztec/stdlib/tx';
|
|
7
7
|
|
|
8
8
|
import { getFeePayerBalanceDelta } from '../../msg_validators/tx_validator/fee_payer_balance.js';
|
|
9
9
|
import { getTxPriorityFee } from '../tx_pool/priority.js';
|
|
@@ -40,6 +40,9 @@ export type TxMetaData = {
|
|
|
40
40
|
/** The transaction hash as hex string */
|
|
41
41
|
readonly txHash: string;
|
|
42
42
|
|
|
43
|
+
/** The transaction hash as bigint (for efficient Fr conversion in comparisons) */
|
|
44
|
+
readonly txHashBigInt: bigint;
|
|
45
|
+
|
|
43
46
|
/** Block ID (number and hash) in which the transaction was mined (undefined if not mined) */
|
|
44
47
|
minedL2BlockId?: L2BlockId;
|
|
45
48
|
|
|
@@ -83,7 +86,9 @@ export type TxState = 'pending' | 'protected' | 'mined' | 'deleted';
|
|
|
83
86
|
* Fr values are captured in closures for zero-cost re-validation.
|
|
84
87
|
*/
|
|
85
88
|
export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
|
|
86
|
-
const
|
|
89
|
+
const txHashObj = tx.getTxHash();
|
|
90
|
+
const txHash = txHashObj.toString();
|
|
91
|
+
const txHashBigInt = txHashObj.toBigInt();
|
|
87
92
|
const nullifierFrs = tx.data.getNonEmptyNullifiers();
|
|
88
93
|
const nullifiers = nullifierFrs.map(n => n.toString());
|
|
89
94
|
const anchorBlockHeaderHashFr = await tx.data.constants.anchorBlockHeader.hash();
|
|
@@ -99,6 +104,7 @@ export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
|
|
|
99
104
|
|
|
100
105
|
return {
|
|
101
106
|
txHash,
|
|
107
|
+
txHashBigInt,
|
|
102
108
|
anchorBlockHeaderHash,
|
|
103
109
|
priorityFee,
|
|
104
110
|
feePayer,
|
|
@@ -134,11 +140,11 @@ const HEX_STRING_BYTES = 98;
|
|
|
134
140
|
const BIGINT_BYTES = 32;
|
|
135
141
|
const FR_BYTES = 80;
|
|
136
142
|
// Fixed cost: object shell + txHash + anchorBlockHeaderHash + feePayer (3 hex strings)
|
|
137
|
-
// + priorityFee + claimAmount + feeLimit + includeByTimestamp (
|
|
143
|
+
// + txHashBigInt + priorityFee + claimAmount + feeLimit + includeByTimestamp (5 bigints)
|
|
138
144
|
// + receivedAt (number, 8 bytes) + estimatedSizeBytes (number, 8 bytes)
|
|
139
145
|
// + data closure object (~OBJECT_OVERHEAD + anchorBlockHeaderHashFr Fr + anchorBlockNumber number)
|
|
140
146
|
const FIXED_METADATA_BYTES =
|
|
141
|
-
OBJECT_OVERHEAD + 3 * HEX_STRING_BYTES +
|
|
147
|
+
OBJECT_OVERHEAD + 3 * HEX_STRING_BYTES + 5 * BIGINT_BYTES + 8 + 8 + OBJECT_OVERHEAD + FR_BYTES + 8;
|
|
142
148
|
|
|
143
149
|
/** Estimates the in-memory size of a TxMetaData object based on the number of nullifiers. */
|
|
144
150
|
function estimateTxMetaDataSize(nullifierCount: number): number {
|
|
@@ -146,14 +152,19 @@ function estimateTxMetaDataSize(nullifierCount: number): number {
|
|
|
146
152
|
return FIXED_METADATA_BYTES + nullifierCount * (HEX_STRING_BYTES + FR_BYTES);
|
|
147
153
|
}
|
|
148
154
|
|
|
155
|
+
/** Converts a txHash bigint back to the canonical 0x-prefixed 64-char hex string. */
|
|
156
|
+
export function txHashFromBigInt(value: bigint): string {
|
|
157
|
+
return TxHash.fromBigInt(value).toString();
|
|
158
|
+
}
|
|
159
|
+
|
|
149
160
|
/** Minimal fields required for priority comparison. */
|
|
150
|
-
type PriorityComparable = Pick<TxMetaData, 'txHash' | 'priorityFee'>;
|
|
161
|
+
export type PriorityComparable = Pick<TxMetaData, 'txHash' | 'txHashBigInt' | 'priorityFee'>;
|
|
151
162
|
|
|
152
163
|
/**
|
|
153
164
|
* Compares two priority fees in ascending order.
|
|
154
165
|
* Returns negative if a < b, positive if a > b, 0 if equal.
|
|
155
166
|
*/
|
|
156
|
-
export function compareFee(a: bigint, b: bigint):
|
|
167
|
+
export function compareFee(a: bigint, b: bigint): -1 | 0 | 1 {
|
|
157
168
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
158
169
|
}
|
|
159
170
|
|
|
@@ -162,10 +173,8 @@ export function compareFee(a: bigint, b: bigint): number {
|
|
|
162
173
|
* Uses field element comparison for deterministic ordering.
|
|
163
174
|
* Returns negative if a < b, positive if a > b, 0 if equal.
|
|
164
175
|
*/
|
|
165
|
-
export function compareTxHash(a:
|
|
166
|
-
|
|
167
|
-
const fieldB = Fr.fromHexString(b);
|
|
168
|
-
return fieldA.cmp(fieldB);
|
|
176
|
+
export function compareTxHash(a: bigint, b: bigint): -1 | 0 | 1 {
|
|
177
|
+
return Fr.cmpAsBigInt(a, b);
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
/**
|
|
@@ -173,29 +182,46 @@ export function compareTxHash(a: string, b: string): number {
|
|
|
173
182
|
* Returns negative if a < b, positive if a > b, 0 if equal.
|
|
174
183
|
* Use with sort() for ascending order, or negate/reverse for descending.
|
|
175
184
|
*/
|
|
176
|
-
export function comparePriority(a: PriorityComparable, b: PriorityComparable):
|
|
185
|
+
export function comparePriority(a: PriorityComparable, b: PriorityComparable): -1 | 0 | 1 {
|
|
177
186
|
const feeComparison = compareFee(a.priorityFee, b.priorityFee);
|
|
178
187
|
if (feeComparison !== 0) {
|
|
179
188
|
return feeComparison;
|
|
180
189
|
}
|
|
181
|
-
return compareTxHash(a.
|
|
190
|
+
return compareTxHash(a.txHashBigInt, b.txHashBigInt);
|
|
191
|
+
}
|
|
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;
|
|
182
203
|
}
|
|
183
204
|
|
|
184
205
|
/**
|
|
185
206
|
* Checks for nullifier conflicts between an incoming transaction and existing pool state.
|
|
186
207
|
*
|
|
187
208
|
* When the incoming tx shares nullifiers with existing pending txs:
|
|
188
|
-
* - If the incoming tx
|
|
189
|
-
* -
|
|
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`.
|
|
190
214
|
*
|
|
191
215
|
* @param incomingMeta - Metadata for the incoming transaction
|
|
192
216
|
* @param getTxHashByNullifier - Accessor to find which tx uses a nullifier
|
|
193
217
|
* @param getMetadata - Accessor to get metadata for a tx hash
|
|
218
|
+
* @param priceBumpPercentage - Optional percentage bump required for fee-based replacement
|
|
194
219
|
*/
|
|
195
220
|
export function checkNullifierConflict(
|
|
196
221
|
incomingMeta: TxMetaData,
|
|
197
222
|
getTxHashByNullifier: (nullifier: string) => string | undefined,
|
|
198
223
|
getMetadata: (txHash: string) => TxMetaData | undefined,
|
|
224
|
+
priceBumpPercentage?: bigint,
|
|
199
225
|
): PreAddResult {
|
|
200
226
|
const txHashesToEvict: string[] = [];
|
|
201
227
|
|
|
@@ -216,19 +242,32 @@ export function checkNullifierConflict(
|
|
|
216
242
|
continue;
|
|
217
243
|
}
|
|
218
244
|
|
|
219
|
-
//
|
|
220
|
-
// Otherwise,
|
|
221
|
-
|
|
222
|
-
|
|
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) {
|
|
223
253
|
txHashesToEvict.push(conflictingHashStr);
|
|
224
254
|
} else {
|
|
255
|
+
const minimumFee =
|
|
256
|
+
priceBumpPercentage !== undefined
|
|
257
|
+
? getMinimumPriceBumpFee(conflictingMeta.priorityFee, priceBumpPercentage)
|
|
258
|
+
: undefined;
|
|
225
259
|
return {
|
|
226
260
|
shouldIgnore: true,
|
|
227
261
|
txHashesToEvict: [],
|
|
228
262
|
reason: {
|
|
229
263
|
code: TxPoolRejectionCode.NULLIFIER_CONFLICT,
|
|
230
|
-
message:
|
|
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}`,
|
|
231
268
|
conflictingTxHash: conflictingHashStr,
|
|
269
|
+
minimumPriceBumpFee: minimumFee,
|
|
270
|
+
txPriorityFee: minimumFee !== undefined ? incomingMeta.priorityFee : undefined,
|
|
232
271
|
},
|
|
233
272
|
};
|
|
234
273
|
}
|
|
@@ -253,3 +292,36 @@ export function stubTxMetaValidationData(overrides: { expirationTimestamp?: bigi
|
|
|
253
292
|
},
|
|
254
293
|
};
|
|
255
294
|
}
|
|
295
|
+
|
|
296
|
+
/** Creates a stub TxMetaData for tests. All fields have sensible defaults and can be overridden. */
|
|
297
|
+
export function stubTxMetaData(
|
|
298
|
+
txHash: string,
|
|
299
|
+
overrides: {
|
|
300
|
+
priorityFee?: bigint;
|
|
301
|
+
feePayer?: string;
|
|
302
|
+
claimAmount?: bigint;
|
|
303
|
+
feeLimit?: bigint;
|
|
304
|
+
nullifiers?: string[];
|
|
305
|
+
expirationTimestamp?: bigint;
|
|
306
|
+
anchorBlockHeaderHash?: string;
|
|
307
|
+
} = {},
|
|
308
|
+
): TxMetaData {
|
|
309
|
+
const txHashBigInt = Fr.fromHexString(txHash).toBigInt();
|
|
310
|
+
// Normalize to canonical zero-padded hex so txHashFromBigInt(txHashBigInt) === normalizedTxHash
|
|
311
|
+
const normalizedTxHash = txHashFromBigInt(txHashBigInt);
|
|
312
|
+
const expirationTimestamp = overrides.expirationTimestamp ?? 0n;
|
|
313
|
+
return {
|
|
314
|
+
txHash: normalizedTxHash,
|
|
315
|
+
txHashBigInt,
|
|
316
|
+
anchorBlockHeaderHash: overrides.anchorBlockHeaderHash ?? '0x1234',
|
|
317
|
+
priorityFee: overrides.priorityFee ?? 100n,
|
|
318
|
+
feePayer: overrides.feePayer ?? '0xfeepayer',
|
|
319
|
+
claimAmount: overrides.claimAmount ?? 0n,
|
|
320
|
+
feeLimit: overrides.feeLimit ?? 100n,
|
|
321
|
+
nullifiers: overrides.nullifiers ?? [`0x${normalizedTxHash.slice(2)}null1`],
|
|
322
|
+
expirationTimestamp,
|
|
323
|
+
receivedAt: 0,
|
|
324
|
+
estimatedSizeBytes: 0,
|
|
325
|
+
data: stubTxMetaValidationData({ expirationTimestamp }),
|
|
326
|
+
};
|
|
327
|
+
}
|