@aztec/p2p 0.0.1-commit.c949de6bc → 0.0.1-commit.cb6bed7c2
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 +2 -2
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +2 -1
- package/dest/client/p2p_client.d.ts +1 -1
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +0 -24
- 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/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/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 +17 -9
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +26 -9
- 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 -43
- 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 +5 -1
- 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/libp2p/libp2p_service.d.ts +1 -1
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +16 -10
- 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/test-helpers/make-test-p2p-clients.d.ts +1 -1
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +2 -1
- 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 +2 -1
- package/src/client/p2p_client.ts +0 -22
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
- 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/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/interfaces.ts +3 -0
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +41 -11
- 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 +8 -1
- 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/libp2p/libp2p_service.ts +15 -6
- package/src/services/peer-manager/metrics.ts +7 -0
- package/src/services/peer-manager/peer_manager.ts +2 -1
- package/src/test-helpers/make-test-p2p-clients.ts +1 -1
- package/src/test-helpers/reqresp-nodes.ts +1 -1
- package/src/testbench/p2p_client_testbench_worker.ts +2 -1
- 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/client/p2p_client.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -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) {
|
|
@@ -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):
|
|
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):
|
|
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):
|
|
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
|
|
198
|
-
* -
|
|
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
|
-
//
|
|
229
|
-
// Otherwise,
|
|
230
|
-
|
|
231
|
-
|
|
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:
|
|
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
|
}
|