@aztec/p2p 4.0.4 → 4.1.0-nightly.20260312
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 +1 -1
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +8 -2
- 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 +23 -9
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +75 -31
- 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 +13 -4
- 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_v2_impl.d.ts +1 -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 +2 -1
- package/dest/msg_validators/proposal_validator/block_proposal_validator.d.ts +5 -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 +5 -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 +12 -9
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +46 -44
- 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/factory.d.ts +15 -4
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +13 -6
- 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/gas_validator.d.ts +13 -4
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +39 -9
- 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/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 +21 -12
- package/dest/test-helpers/testbench-utils.d.ts +1 -1
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +2 -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 +4 -1
- package/package.json +14 -14
- package/src/client/factory.ts +7 -0
- package/src/client/p2p_client.ts +0 -22
- package/src/config.ts +105 -32
- 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 +37 -7
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +3 -1
- package/src/msg_validators/proposal_validator/block_proposal_validator.ts +13 -3
- package/src/msg_validators/proposal_validator/checkpoint_proposal_validator.ts +19 -6
- package/src/msg_validators/proposal_validator/proposal_validator.ts +57 -48
- 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/factory.ts +15 -2
- package/src/msg_validators/tx_validator/fee_payer_balance.ts +6 -2
- package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
- package/src/msg_validators/tx_validator/index.ts +1 -0
- package/src/msg_validators/tx_validator/phases_validator.ts +51 -26
- package/src/services/libp2p/libp2p_service.ts +21 -10
- package/src/test-helpers/testbench-utils.ts +1 -0
- package/src/testbench/p2p_client_testbench_worker.ts +1 -0
- package/src/testbench/worker_client_manager.ts +11 -4
- package/dest/msg_validators/proposal_validator/proposal_validator_test_suite.d.ts +0 -24
- 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 -378
- package/src/msg_validators/proposal_validator/proposal_validator_test_suite.ts +0 -373
|
@@ -44,6 +44,8 @@ export type TxPoolV2Config = {
|
|
|
44
44
|
minTxPoolAgeMs: number;
|
|
45
45
|
/** Maximum number of evicted tx hashes to remember for metrics tracking */
|
|
46
46
|
evictedTxCacheSize: number;
|
|
47
|
+
/** Minimum percentage fee increase required to replace an existing tx via RPC (0 = no bump). */
|
|
48
|
+
priceBumpPercentage: bigint;
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
/**
|
|
@@ -54,6 +56,7 @@ export const DEFAULT_TX_POOL_V2_CONFIG: TxPoolV2Config = {
|
|
|
54
56
|
archivedTxLimit: 0, // 0 = disabled
|
|
55
57
|
minTxPoolAgeMs: 2_000,
|
|
56
58
|
evictedTxCacheSize: 10_000,
|
|
59
|
+
priceBumpPercentage: 10n,
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
/**
|
|
@@ -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
|
}
|
|
@@ -213,7 +213,9 @@ export class TxPoolV2Impl {
|
|
|
213
213
|
// in-memory reads, and buffered DB writes. Nothing here can throw an unhandled exception.
|
|
214
214
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
215
215
|
const preAddContext: PreAddContext | undefined =
|
|
216
|
-
opts.feeComparisonOnly !== undefined
|
|
216
|
+
opts.feeComparisonOnly !== undefined
|
|
217
|
+
? { feeComparisonOnly: opts.feeComparisonOnly, priceBumpPercentage: this.#config.priceBumpPercentage }
|
|
218
|
+
: undefined;
|
|
217
219
|
|
|
218
220
|
await this.#store.transactionAsync(async () => {
|
|
219
221
|
for (const tx of txs) {
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
-
import type { BlockProposal, P2PValidator } from '@aztec/stdlib/p2p';
|
|
2
|
+
import type { BlockProposal, P2PValidator, ValidationResult } from '@aztec/stdlib/p2p';
|
|
3
3
|
|
|
4
4
|
import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
|
|
5
5
|
|
|
6
|
-
export class BlockProposalValidator
|
|
6
|
+
export class BlockProposalValidator implements P2PValidator<BlockProposal> {
|
|
7
|
+
private proposalValidator: ProposalValidator;
|
|
8
|
+
|
|
7
9
|
constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
|
|
8
|
-
|
|
10
|
+
this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:block_proposal_validator');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async validate(proposal: BlockProposal): Promise<ValidationResult> {
|
|
14
|
+
const headerResult = await this.proposalValidator.validate(proposal);
|
|
15
|
+
if (headerResult.result !== 'accept') {
|
|
16
|
+
return headerResult;
|
|
17
|
+
}
|
|
18
|
+
return this.proposalValidator.validateTxs(proposal);
|
|
9
19
|
}
|
|
10
20
|
}
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
-
import type { CheckpointProposal, P2PValidator } from '@aztec/stdlib/p2p';
|
|
2
|
+
import type { CheckpointProposal, P2PValidator, ValidationResult } from '@aztec/stdlib/p2p';
|
|
3
3
|
|
|
4
4
|
import { ProposalValidator } from '../proposal_validator/proposal_validator.js';
|
|
5
5
|
|
|
6
|
-
export class CheckpointProposalValidator
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
{
|
|
6
|
+
export class CheckpointProposalValidator implements P2PValidator<CheckpointProposal> {
|
|
7
|
+
private proposalValidator: ProposalValidator;
|
|
8
|
+
|
|
10
9
|
constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean; maxTxsPerBlock?: number }) {
|
|
11
|
-
|
|
10
|
+
this.proposalValidator = new ProposalValidator(epochCache, opts, 'p2p:checkpoint_proposal_validator');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async validate(proposal: CheckpointProposal): Promise<ValidationResult> {
|
|
14
|
+
const headerResult = await this.proposalValidator.validate(proposal);
|
|
15
|
+
if (headerResult.result !== 'accept') {
|
|
16
|
+
return headerResult;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const blockProposal = proposal.getBlockProposal();
|
|
20
|
+
if (blockProposal) {
|
|
21
|
+
return this.proposalValidator.validateTxs(blockProposal);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { result: 'accept' };
|
|
12
25
|
}
|
|
13
26
|
}
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
2
|
import { NoCommitteeError } from '@aztec/ethereum/contracts';
|
|
3
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type BlockProposal,
|
|
6
|
+
type CheckpointProposalCore,
|
|
7
|
+
PeerErrorSeverity,
|
|
8
|
+
type ValidationResult,
|
|
9
|
+
} from '@aztec/stdlib/p2p';
|
|
5
10
|
|
|
6
11
|
import { isWithinClockTolerance } from '../clock_tolerance.js';
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
/** Validates header-level and tx-level fields of block and checkpoint proposals. */
|
|
14
|
+
export class ProposalValidator {
|
|
15
|
+
private epochCache: EpochCacheInterface;
|
|
16
|
+
private logger: Logger;
|
|
17
|
+
private txsPermitted: boolean;
|
|
18
|
+
private maxTxsPerBlock?: number;
|
|
13
19
|
|
|
14
20
|
constructor(
|
|
15
21
|
epochCache: EpochCacheInterface,
|
|
@@ -22,7 +28,8 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
|
|
|
22
28
|
this.logger = createLogger(loggerName);
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
/** Validates header-level fields: slot, signature, and proposer. */
|
|
32
|
+
public async validate(proposal: BlockProposal | CheckpointProposalCore): Promise<ValidationResult> {
|
|
26
33
|
try {
|
|
27
34
|
// Slot check
|
|
28
35
|
const { currentSlot, nextSlot } = this.epochCache.getCurrentAndNextSlot();
|
|
@@ -44,38 +51,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
|
|
|
44
51
|
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
// Transactions permitted check
|
|
48
|
-
const embeddedTxCount = proposal.txs?.length ?? 0;
|
|
49
|
-
if (!this.txsPermitted && (proposal.txHashes.length > 0 || embeddedTxCount > 0)) {
|
|
50
|
-
this.logger.warn(
|
|
51
|
-
`Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when transactions are not permitted`,
|
|
52
|
-
);
|
|
53
|
-
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Max txs per block check
|
|
57
|
-
if (this.maxTxsPerBlock !== undefined && proposal.txHashes.length > this.maxTxsPerBlock) {
|
|
58
|
-
this.logger.warn(
|
|
59
|
-
`Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when max is ${this.maxTxsPerBlock}`,
|
|
60
|
-
);
|
|
61
|
-
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Embedded txs must be listed in txHashes
|
|
65
|
-
const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
|
|
66
|
-
const missingTxHashes =
|
|
67
|
-
embeddedTxCount > 0
|
|
68
|
-
? proposal.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
|
|
69
|
-
: [];
|
|
70
|
-
if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
|
|
71
|
-
this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
|
|
72
|
-
embeddedTxCount,
|
|
73
|
-
txHashesLength: proposal.txHashes.length,
|
|
74
|
-
missingTxHashes,
|
|
75
|
-
});
|
|
76
|
-
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
54
|
// Proposer check
|
|
80
55
|
const expectedProposer = await this.epochCache.getProposerAttesterAddressInSlot(slotNumber);
|
|
81
56
|
if (expectedProposer !== undefined && !proposer.equals(expectedProposer)) {
|
|
@@ -86,15 +61,6 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
|
|
|
86
61
|
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
87
62
|
}
|
|
88
63
|
|
|
89
|
-
// Validate tx hashes for all txs embedded in the proposal
|
|
90
|
-
if (!(await Promise.all(proposal.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
|
|
91
|
-
this.logger.warn(`Penalizing peer for invalid tx hashes in proposal`, {
|
|
92
|
-
proposer,
|
|
93
|
-
slotNumber,
|
|
94
|
-
});
|
|
95
|
-
return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
64
|
return { result: 'accept' };
|
|
99
65
|
} catch (e) {
|
|
100
66
|
if (e instanceof NoCommitteeError) {
|
|
@@ -103,4 +69,47 @@ export abstract class ProposalValidator<TProposal extends BlockProposal | Checkp
|
|
|
103
69
|
throw e;
|
|
104
70
|
}
|
|
105
71
|
}
|
|
72
|
+
|
|
73
|
+
/** Validates transaction-related fields of a block proposal. */
|
|
74
|
+
public async validateTxs(proposal: BlockProposal): Promise<ValidationResult> {
|
|
75
|
+
// Transactions permitted check
|
|
76
|
+
const embeddedTxCount = proposal.txs?.length ?? 0;
|
|
77
|
+
if (!this.txsPermitted && (proposal.txHashes.length > 0 || embeddedTxCount > 0)) {
|
|
78
|
+
this.logger.warn(
|
|
79
|
+
`Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when transactions are not permitted`,
|
|
80
|
+
);
|
|
81
|
+
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Max txs per block check
|
|
85
|
+
if (this.maxTxsPerBlock !== undefined && proposal.txHashes.length > this.maxTxsPerBlock) {
|
|
86
|
+
this.logger.warn(
|
|
87
|
+
`Penalizing peer for proposal with ${proposal.txHashes.length} transaction(s) when max is ${this.maxTxsPerBlock}`,
|
|
88
|
+
);
|
|
89
|
+
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Embedded txs must be listed in txHashes
|
|
93
|
+
const hashSet = new Set(proposal.txHashes.map(h => h.toString()));
|
|
94
|
+
const missingTxHashes =
|
|
95
|
+
embeddedTxCount > 0
|
|
96
|
+
? proposal.txs!.filter(tx => !hashSet.has(tx.getTxHash().toString())).map(tx => tx.getTxHash().toString())
|
|
97
|
+
: [];
|
|
98
|
+
if (embeddedTxCount > 0 && missingTxHashes.length > 0) {
|
|
99
|
+
this.logger.warn('Penalizing peer for embedded transaction(s) not included in txHashes', {
|
|
100
|
+
embeddedTxCount,
|
|
101
|
+
txHashesLength: proposal.txHashes.length,
|
|
102
|
+
missingTxHashes,
|
|
103
|
+
});
|
|
104
|
+
return { result: 'reject', severity: PeerErrorSeverity.MidToleranceError };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate tx hashes for all txs embedded in the proposal
|
|
108
|
+
if (!(await Promise.all(proposal.txs?.map(tx => tx.validateTxHash()) ?? [])).every(v => v)) {
|
|
109
|
+
this.logger.warn(`Penalizing peer for invalid tx hashes in proposal`);
|
|
110
|
+
return { result: 'reject', severity: PeerErrorSeverity.LowToleranceError };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { result: 'accept' };
|
|
114
|
+
}
|
|
106
115
|
}
|
|
@@ -1,35 +1,30 @@
|
|
|
1
|
-
import { FPCContract } from '@aztec/noir-contracts.js/FPC';
|
|
2
|
-
import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token';
|
|
3
1
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
4
|
-
import {
|
|
2
|
+
import { AuthRegistryArtifact } from '@aztec/protocol-contracts/auth-registry';
|
|
3
|
+
import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
|
|
5
4
|
import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
import { buildAllowedElement } from './allowed_setup_helpers.js';
|
|
7
|
+
|
|
8
|
+
let defaultAllowedSetupFunctions: AllowedElement[] | undefined;
|
|
9
|
+
|
|
10
|
+
/** Returns the default list of functions allowed to run in the setup phase of a transaction. */
|
|
8
11
|
export async function getDefaultAllowedSetupFunctions(): Promise<AllowedElement[]> {
|
|
9
12
|
if (defaultAllowedSetupFunctions === undefined) {
|
|
10
|
-
defaultAllowedSetupFunctions = [
|
|
11
|
-
// needed for authwit support
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'),
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
classId: (await getContractClassFromArtifact(FPCContract.artifact)).id,
|
|
29
|
-
// We can't restrict the selector because public functions get routed via dispatch.
|
|
30
|
-
// selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'),
|
|
31
|
-
},
|
|
32
|
-
];
|
|
13
|
+
defaultAllowedSetupFunctions = await Promise.all([
|
|
14
|
+
// AuthRegistry: needed for authwit support via private path (set_authorized_private enqueues _set_authorized)
|
|
15
|
+
buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, '_set_authorized', {
|
|
16
|
+
onlySelf: true,
|
|
17
|
+
rejectNullMsgSender: true,
|
|
18
|
+
}),
|
|
19
|
+
// AuthRegistry: needed for authwit support via public path (PublicFeePaymentMethod calls set_authorized directly)
|
|
20
|
+
buildAllowedElement(AuthRegistryArtifact, { address: ProtocolContractAddress.AuthRegistry }, 'set_authorized', {
|
|
21
|
+
rejectNullMsgSender: true,
|
|
22
|
+
}),
|
|
23
|
+
// FeeJuice: needed for claiming on the same tx as a spend (claim_and_end_setup enqueues this)
|
|
24
|
+
buildAllowedElement(FeeJuiceArtifact, { address: ProtocolContractAddress.FeeJuice }, '_increase_public_balance', {
|
|
25
|
+
onlySelf: true,
|
|
26
|
+
}),
|
|
27
|
+
]);
|
|
33
28
|
}
|
|
34
29
|
return defaultAllowedSetupFunctions;
|
|
35
30
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
|
+
import { FunctionSelector, countArgumentsSize, getAllFunctionAbis } from '@aztec/stdlib/abi';
|
|
3
|
+
import type { ContractArtifact } from '@aztec/stdlib/abi';
|
|
4
|
+
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
5
|
+
import type { AllowedElement } from '@aztec/stdlib/interfaces/server';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds an AllowedElement from a contract artifact, deriving both the function selector
|
|
9
|
+
* and calldata length from the artifact instead of hardcoding signature strings.
|
|
10
|
+
*/
|
|
11
|
+
export async function buildAllowedElement(
|
|
12
|
+
artifact: ContractArtifact,
|
|
13
|
+
target: { address: AztecAddress } | { classId: Fr },
|
|
14
|
+
functionName: string,
|
|
15
|
+
opts?: { onlySelf?: boolean; rejectNullMsgSender?: boolean },
|
|
16
|
+
): Promise<AllowedElement> {
|
|
17
|
+
const allFunctions = getAllFunctionAbis(artifact);
|
|
18
|
+
const fn = allFunctions.find(f => f.name === functionName);
|
|
19
|
+
if (!fn) {
|
|
20
|
+
throw new Error(`Unknown function ${functionName} in artifact ${artifact.name}`);
|
|
21
|
+
}
|
|
22
|
+
const selector = await FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
|
|
23
|
+
const calldataLength = 1 + countArgumentsSize(fn);
|
|
24
|
+
return {
|
|
25
|
+
...target,
|
|
26
|
+
selector,
|
|
27
|
+
calldataLength,
|
|
28
|
+
...(opts?.onlySelf ? { onlySelf: true } : {}),
|
|
29
|
+
...(opts?.rejectNullMsgSender ? { rejectNullMsgSender: true } : {}),
|
|
30
|
+
} as AllowedElement;
|
|
31
|
+
}
|
|
@@ -97,6 +97,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
|
|
|
97
97
|
txsPermitted: boolean,
|
|
98
98
|
allowedInSetup: AllowedElement[] = [],
|
|
99
99
|
bindings?: LoggerBindings,
|
|
100
|
+
gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
|
|
100
101
|
): Record<string, TransactionValidator> {
|
|
101
102
|
const merkleTree = worldStateSynchronizer.getCommitted();
|
|
102
103
|
|
|
@@ -158,6 +159,7 @@ export function createFirstStageTxValidationsForGossipedTransactions(
|
|
|
158
159
|
ProtocolContractAddress.FeeJuice,
|
|
159
160
|
gasFees,
|
|
160
161
|
bindings,
|
|
162
|
+
gasLimitOpts,
|
|
161
163
|
),
|
|
162
164
|
severity: PeerErrorSeverity.MidToleranceError,
|
|
163
165
|
},
|
|
@@ -278,6 +280,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
|
|
|
278
280
|
timestamp,
|
|
279
281
|
blockNumber,
|
|
280
282
|
txsPermitted,
|
|
283
|
+
rollupManaLimit,
|
|
284
|
+
maxBlockL2Gas,
|
|
285
|
+
maxBlockDAGas,
|
|
281
286
|
}: {
|
|
282
287
|
l1ChainId: number;
|
|
283
288
|
rollupVersion: number;
|
|
@@ -287,6 +292,9 @@ export function createTxValidatorForAcceptingTxsOverRPC(
|
|
|
287
292
|
timestamp: UInt64;
|
|
288
293
|
blockNumber: BlockNumber;
|
|
289
294
|
txsPermitted: boolean;
|
|
295
|
+
rollupManaLimit: number;
|
|
296
|
+
maxBlockL2Gas?: number;
|
|
297
|
+
maxBlockDAGas?: number;
|
|
290
298
|
},
|
|
291
299
|
bindings?: LoggerBindings,
|
|
292
300
|
): TxValidator<Tx> {
|
|
@@ -317,7 +325,11 @@ export function createTxValidatorForAcceptingTxsOverRPC(
|
|
|
317
325
|
|
|
318
326
|
if (!skipFeeEnforcement) {
|
|
319
327
|
validators.push(
|
|
320
|
-
new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings
|
|
328
|
+
new GasTxValidator(new DatabasePublicStateSource(db), ProtocolContractAddress.FeeJuice, gasFees, bindings, {
|
|
329
|
+
rollupManaLimit,
|
|
330
|
+
maxBlockL2Gas,
|
|
331
|
+
maxBlockDAGas,
|
|
332
|
+
}),
|
|
321
333
|
);
|
|
322
334
|
}
|
|
323
335
|
|
|
@@ -403,6 +415,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
|
|
|
403
415
|
worldStateSynchronizer: WorldStateSynchronizer,
|
|
404
416
|
timestamp: bigint,
|
|
405
417
|
blockNumber: BlockNumber,
|
|
418
|
+
gasLimitOpts: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
|
|
406
419
|
bindings?: LoggerBindings,
|
|
407
420
|
): Promise<TxValidator<TxMetaData>> {
|
|
408
421
|
await worldStateSynchronizer.syncImmediate();
|
|
@@ -419,7 +432,7 @@ export async function createTxValidatorForTransactionsEnteringPendingTxPool(
|
|
|
419
432
|
},
|
|
420
433
|
};
|
|
421
434
|
return new AggregateTxValidator<TxMetaData>(
|
|
422
|
-
new GasLimitsValidator<TxMetaData>(bindings),
|
|
435
|
+
new GasLimitsValidator<TxMetaData>({ ...gasLimitOpts, bindings }),
|
|
423
436
|
new TimestampTxValidator<TxMetaData>({ timestamp, blockNumber }, bindings),
|
|
424
437
|
new DoubleSpendTxValidator<TxMetaData>(nullifierSource, bindings),
|
|
425
438
|
new BlockHeaderTxValidator<TxMetaData>(archiveSource, bindings),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { FeeJuiceArtifact } from '@aztec/protocol-contracts/fee-juice';
|
|
1
2
|
import { getCallRequestsWithCalldataByPhase } from '@aztec/simulator/server';
|
|
2
|
-
import { FunctionSelector } from '@aztec/stdlib/abi';
|
|
3
|
+
import { FunctionSelector, getAllFunctionAbis } from '@aztec/stdlib/abi';
|
|
3
4
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
4
5
|
import { type Tx, TxExecutionPhase } from '@aztec/stdlib/tx';
|
|
5
6
|
|
|
@@ -8,7 +9,10 @@ export type FeePayerBalanceDelta = {
|
|
|
8
9
|
claimAmount: bigint;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
const increasePublicBalanceSelectorPromise =
|
|
12
|
+
const increasePublicBalanceSelectorPromise = (() => {
|
|
13
|
+
const fn = getAllFunctionAbis(FeeJuiceArtifact).find(f => f.name === '_increase_public_balance')!;
|
|
14
|
+
return FunctionSelector.fromNameAndParameters(fn.name, fn.parameters);
|
|
15
|
+
})();
|
|
12
16
|
|
|
13
17
|
export function getTxFeeLimit(tx: Tx): bigint {
|
|
14
18
|
return tx.data.constants.txContext.gasSettings.getFeeLimit().toBigInt();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT,
|
|
2
3
|
MAX_PROCESSABLE_L2_GAS,
|
|
3
4
|
PRIVATE_TX_L2_GAS_OVERHEAD,
|
|
4
5
|
PUBLIC_TX_L2_GAS_OVERHEAD,
|
|
@@ -49,16 +50,31 @@ export interface HasGasLimitData {
|
|
|
49
50
|
*/
|
|
50
51
|
export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidator<T> {
|
|
51
52
|
#log: Logger;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
#effectiveMaxL2Gas: number;
|
|
54
|
+
#effectiveMaxDAGas: number;
|
|
55
|
+
#rollupManaLimit: number;
|
|
56
|
+
#maxBlockL2Gas: number;
|
|
57
|
+
#maxBlockDAGas: number;
|
|
58
|
+
|
|
59
|
+
constructor(opts?: {
|
|
60
|
+
rollupManaLimit?: number;
|
|
61
|
+
maxBlockL2Gas?: number;
|
|
62
|
+
maxBlockDAGas?: number;
|
|
63
|
+
bindings?: LoggerBindings;
|
|
64
|
+
}) {
|
|
65
|
+
this.#log = createLogger('sequencer:tx_validator:tx_gas', opts?.bindings);
|
|
66
|
+
this.#rollupManaLimit = opts?.rollupManaLimit ?? Infinity;
|
|
67
|
+
this.#maxBlockL2Gas = opts?.maxBlockL2Gas ?? Infinity;
|
|
68
|
+
this.#maxBlockDAGas = opts?.maxBlockDAGas ?? Infinity;
|
|
69
|
+
this.#effectiveMaxL2Gas = Math.min(MAX_PROCESSABLE_L2_GAS, this.#rollupManaLimit, this.#maxBlockL2Gas);
|
|
70
|
+
this.#effectiveMaxDAGas = Math.min(MAX_PROCESSABLE_DA_GAS_PER_CHECKPOINT, this.#maxBlockDAGas);
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
validateTx(tx: T): Promise<TxValidationResult> {
|
|
58
74
|
return Promise.resolve(this.validateGasLimit(tx));
|
|
59
75
|
}
|
|
60
76
|
|
|
61
|
-
/** Checks gas limits are >= fixed minimums and <=
|
|
77
|
+
/** Checks gas limits are >= fixed minimums and <= effective max gas (L2 and DA). */
|
|
62
78
|
validateGasLimit(tx: T): TxValidationResult {
|
|
63
79
|
const gasLimits = tx.data.constants.txContext.gasSettings.gasLimits;
|
|
64
80
|
const minGasLimits = new Gas(
|
|
@@ -74,10 +90,21 @@ export class GasLimitsValidator<T extends HasGasLimitData> implements TxValidato
|
|
|
74
90
|
return { result: 'invalid', reason: [TX_ERROR_INSUFFICIENT_GAS_LIMIT] };
|
|
75
91
|
}
|
|
76
92
|
|
|
77
|
-
if (gasLimits.l2Gas >
|
|
78
|
-
this.#log.verbose(`Rejecting transaction due to the gas limit
|
|
93
|
+
if (gasLimits.l2Gas > this.#effectiveMaxL2Gas) {
|
|
94
|
+
this.#log.verbose(`Rejecting transaction due to the L2 gas limit being higher than the effective maximum`, {
|
|
79
95
|
gasLimits,
|
|
80
|
-
|
|
96
|
+
effectiveMaxL2Gas: this.#effectiveMaxL2Gas,
|
|
97
|
+
rollupManaLimit: this.#rollupManaLimit,
|
|
98
|
+
maxBlockL2Gas: this.#maxBlockL2Gas,
|
|
99
|
+
});
|
|
100
|
+
return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (gasLimits.daGas > this.#effectiveMaxDAGas) {
|
|
104
|
+
this.#log.verbose(`Rejecting transaction due to the DA gas limit being higher than the effective maximum`, {
|
|
105
|
+
gasLimits,
|
|
106
|
+
effectiveMaxDAGas: this.#effectiveMaxDAGas,
|
|
107
|
+
maxBlockDAGas: this.#maxBlockDAGas,
|
|
81
108
|
});
|
|
82
109
|
return { result: 'invalid', reason: [TX_ERROR_GAS_LIMIT_TOO_HIGH] };
|
|
83
110
|
}
|
|
@@ -106,21 +133,27 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
106
133
|
#publicDataSource: PublicStateSource;
|
|
107
134
|
#feeJuiceAddress: AztecAddress;
|
|
108
135
|
#gasFees: GasFees;
|
|
136
|
+
#gasLimitOpts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number };
|
|
109
137
|
|
|
110
138
|
constructor(
|
|
111
139
|
publicDataSource: PublicStateSource,
|
|
112
140
|
feeJuiceAddress: AztecAddress,
|
|
113
141
|
gasFees: GasFees,
|
|
114
142
|
private bindings?: LoggerBindings,
|
|
143
|
+
opts?: { rollupManaLimit?: number; maxBlockL2Gas?: number; maxBlockDAGas?: number },
|
|
115
144
|
) {
|
|
116
145
|
this.#log = createLogger('sequencer:tx_validator:tx_gas', bindings);
|
|
117
146
|
this.#publicDataSource = publicDataSource;
|
|
118
147
|
this.#feeJuiceAddress = feeJuiceAddress;
|
|
119
148
|
this.#gasFees = gasFees;
|
|
149
|
+
this.#gasLimitOpts = opts;
|
|
120
150
|
}
|
|
121
151
|
|
|
122
152
|
async validateTx(tx: Tx): Promise<TxValidationResult> {
|
|
123
|
-
const gasLimitValidation = new GasLimitsValidator(
|
|
153
|
+
const gasLimitValidation = new GasLimitsValidator({
|
|
154
|
+
...this.#gasLimitOpts,
|
|
155
|
+
bindings: this.bindings,
|
|
156
|
+
}).validateGasLimit(tx);
|
|
124
157
|
if (gasLimitValidation.result === 'invalid') {
|
|
125
158
|
return Promise.resolve(gasLimitValidation);
|
|
126
159
|
}
|
|
@@ -8,6 +8,7 @@ export * from './gas_validator.js';
|
|
|
8
8
|
export * from './phases_validator.js';
|
|
9
9
|
export * from './test_utils.js';
|
|
10
10
|
export * from './allowed_public_setup.js';
|
|
11
|
+
export * from './allowed_setup_helpers.js';
|
|
11
12
|
export * from './archive_cache.js';
|
|
12
13
|
export * from './tx_permitted_validator.js';
|
|
13
14
|
export * from './timestamp_validator.js';
|