@aztec/p2p 0.0.1-commit.808bf7f90 → 0.0.1-commit.8227e42
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 -20
- package/dest/client/interface.d.ts +3 -10
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +2 -10
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +2 -57
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +5 -5
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +9 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +12 -0
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +2 -2
- 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_impl.d.ts +2 -2
- 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 +1 -6
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
- package/dest/msg_validators/tx_validator/factory.d.ts +114 -6
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +219 -58
- package/dest/msg_validators/tx_validator/gas_validator.d.ts +58 -3
- package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/gas_validator.js +72 -35
- 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/nullifier_cache.d.ts +14 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
- package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
- package/dest/services/dummy_service.d.ts +2 -3
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +1 -4
- package/dest/services/libp2p/libp2p_service.d.ts +10 -7
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +57 -70
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -1
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +14 -37
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +17 -11
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/peer_collection.js +49 -15
- package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
- package/dest/services/service.d.ts +2 -2
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_provider.d.ts +3 -3
- package/dest/services/tx_provider.d.ts.map +1 -1
- package/dest/services/tx_provider.js +4 -4
- package/dest/test-helpers/testbench-utils.d.ts +2 -2
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/package.json +14 -14
- package/src/client/factory.ts +13 -33
- package/src/client/interface.ts +2 -10
- package/src/client/p2p_client.ts +2 -76
- package/src/mem_pools/tx_pool_v2/interfaces.ts +4 -4
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +13 -0
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +1 -1
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +2 -8
- package/src/msg_validators/tx_validator/README.md +115 -0
- package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
- package/src/msg_validators/tx_validator/factory.ts +353 -77
- package/src/msg_validators/tx_validator/gas_validator.ts +84 -29
- package/src/msg_validators/tx_validator/index.ts +1 -0
- package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
- package/src/services/dummy_service.ts +1 -5
- package/src/services/libp2p/libp2p_service.ts +70 -79
- package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +14 -42
- package/src/services/reqresp/batch-tx-requester/peer_collection.ts +63 -24
- package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
- package/src/services/service.ts +1 -1
- package/src/services/tx_provider.ts +2 -2
- package/src/test-helpers/testbench-utils.ts +1 -1
package/src/client/factory.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
3
4
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
4
5
|
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
5
6
|
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
6
7
|
import { AztecLMDBStoreV2, createStore } from '@aztec/kv-store/lmdb-v2';
|
|
7
|
-
import type {
|
|
8
|
+
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
8
9
|
import type { ChainConfig } from '@aztec/stdlib/config';
|
|
9
10
|
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
10
11
|
import type { AztecNode, ClientProtocolCircuitVerifier, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
11
12
|
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
12
|
-
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
13
13
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
14
14
|
|
|
15
15
|
import { P2PClient } from '../client/p2p_client.js';
|
|
@@ -17,11 +17,8 @@ import type { P2PConfig } from '../config.js';
|
|
|
17
17
|
import { AttestationPool, type AttestationPoolApi } from '../mem_pools/attestation_pool/attestation_pool.js';
|
|
18
18
|
import type { MemPools } from '../mem_pools/interface.js';
|
|
19
19
|
import type { TxPoolV2 } from '../mem_pools/tx_pool_v2/interfaces.js';
|
|
20
|
-
import type { TxMetaData } from '../mem_pools/tx_pool_v2/tx_metadata.js';
|
|
21
20
|
import { AztecKVTxPoolV2 } from '../mem_pools/tx_pool_v2/tx_pool_v2.js';
|
|
22
|
-
import {
|
|
23
|
-
import { BlockHeaderTxValidator } from '../msg_validators/tx_validator/block_header_validator.js';
|
|
24
|
-
import { DoubleSpendTxValidator } from '../msg_validators/tx_validator/double_spend_validator.js';
|
|
21
|
+
import { createTxValidatorForTransactionsEnteringPendingTxPool } from '../msg_validators/index.js';
|
|
25
22
|
import { DummyP2PService } from '../services/dummy_service.js';
|
|
26
23
|
import { LibP2PService } from '../services/index.js';
|
|
27
24
|
import { createFileStoreTxSources } from '../services/tx_collection/file_store_tx_source.js';
|
|
@@ -80,32 +77,6 @@ export async function createP2PClient<T extends P2PClientType>(
|
|
|
80
77
|
const rollupAddress = inputConfig.l1Contracts.rollupAddress.toString().toLowerCase().replace(/^0x/, '');
|
|
81
78
|
const txFileStoreBasePath = `aztec-${inputConfig.l1ChainId}-${inputConfig.rollupVersion}-0x${rollupAddress}`;
|
|
82
79
|
|
|
83
|
-
/** Validator factory for pool re-validation (double-spend + block header only). */
|
|
84
|
-
const createPoolTxValidator = async () => {
|
|
85
|
-
await worldStateSynchronizer.syncImmediate();
|
|
86
|
-
return new AggregateTxValidator<TxMetaData>(
|
|
87
|
-
new DoubleSpendTxValidator<TxMetaData>(
|
|
88
|
-
{
|
|
89
|
-
nullifiersExist: async (nullifiers: Buffer[]) => {
|
|
90
|
-
const merkleTree = worldStateSynchronizer.getCommitted();
|
|
91
|
-
const indices = await merkleTree.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, nullifiers);
|
|
92
|
-
return indices.map(index => index !== undefined);
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
bindings,
|
|
96
|
-
),
|
|
97
|
-
new BlockHeaderTxValidator<TxMetaData>(
|
|
98
|
-
{
|
|
99
|
-
getArchiveIndices: (archives: BlockHash[]) => {
|
|
100
|
-
const merkleTree = worldStateSynchronizer.getCommitted();
|
|
101
|
-
return merkleTree.findLeafIndices(MerkleTreeId.ARCHIVE, archives);
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
bindings,
|
|
105
|
-
),
|
|
106
|
-
);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
80
|
const txPool =
|
|
110
81
|
deps.txPool ??
|
|
111
82
|
new AztecKVTxPoolV2(
|
|
@@ -114,7 +85,16 @@ export async function createP2PClient<T extends P2PClientType>(
|
|
|
114
85
|
{
|
|
115
86
|
l2BlockSource: archiver,
|
|
116
87
|
worldStateSynchronizer,
|
|
117
|
-
createTxValidator:
|
|
88
|
+
createTxValidator: async () => {
|
|
89
|
+
// We accept transactions if they are not expired by the next slot and block number (checked based on the ExpirationTimestamp field)
|
|
90
|
+
const currentBlockNumber = await archiver.getBlockNumber();
|
|
91
|
+
const { ts: nextSlotTimestamp } = epochCache.getEpochAndSlotInNextL1Slot();
|
|
92
|
+
return createTxValidatorForTransactionsEnteringPendingTxPool(
|
|
93
|
+
worldStateSynchronizer,
|
|
94
|
+
nextSlotTimestamp,
|
|
95
|
+
BlockNumber(currentBlockNumber + 1),
|
|
96
|
+
);
|
|
97
|
+
},
|
|
118
98
|
},
|
|
119
99
|
telemetry,
|
|
120
100
|
{
|
package/src/client/interface.ts
CHANGED
|
@@ -140,14 +140,6 @@ export type P2P<T extends P2PClientType = P2PClientType.Full> = P2PApiFull<T> &
|
|
|
140
140
|
*/
|
|
141
141
|
hasTxsInPool(txHashes: TxHash[]): Promise<boolean[]>;
|
|
142
142
|
|
|
143
|
-
/**
|
|
144
|
-
* Returns transactions in the transaction pool by hash, requesting from the network if not found.
|
|
145
|
-
* @param txHashes - Hashes of tx to return.
|
|
146
|
-
* @param pinnedPeerId - An optional peer id that will be used to request the tx from (in addition to other random peers).
|
|
147
|
-
* @returns An array of tx or undefined.
|
|
148
|
-
*/
|
|
149
|
-
getTxsByHash(txHashes: TxHash[], pinnedPeerId: PeerId | undefined): Promise<(Tx | undefined)[]>;
|
|
150
|
-
|
|
151
143
|
/**
|
|
152
144
|
* Returns an archived transaction from the transaction pool by its hash.
|
|
153
145
|
* @param txHash - Hash of tx to return.
|
|
@@ -224,8 +216,8 @@ export type P2P<T extends P2PClientType = P2PClientType.Full> = P2PApiFull<T> &
|
|
|
224
216
|
|
|
225
217
|
updateP2PConfig(config: Partial<P2PConfig>): Promise<void>;
|
|
226
218
|
|
|
227
|
-
/** Validates a set of txs. */
|
|
228
|
-
|
|
219
|
+
/** Validates a set of txs received in a block proposal. */
|
|
220
|
+
validateTxsReceivedInBlockProposal(txs: Tx[]): Promise<void>;
|
|
229
221
|
|
|
230
222
|
/** Clears the db. */
|
|
231
223
|
clear(): Promise<void>;
|
package/src/client/p2p_client.ts
CHANGED
|
@@ -44,7 +44,6 @@ import {
|
|
|
44
44
|
type ReqRespSubProtocolHandler,
|
|
45
45
|
type ReqRespSubProtocolValidators,
|
|
46
46
|
} from '../services/reqresp/interface.js';
|
|
47
|
-
import { chunkTxHashesRequest } from '../services/reqresp/protocols/tx.js';
|
|
48
47
|
import type {
|
|
49
48
|
DuplicateAttestationInfo,
|
|
50
49
|
DuplicateProposalInfo,
|
|
@@ -433,36 +432,6 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
433
432
|
this.p2pService.registerDuplicateAttestationCallback(callback);
|
|
434
433
|
}
|
|
435
434
|
|
|
436
|
-
/**
|
|
437
|
-
* Uses the batched Request Response protocol to request a set of transactions from the network.
|
|
438
|
-
*/
|
|
439
|
-
private async requestTxsByHash(txHashes: TxHash[], pinnedPeerId: PeerId | undefined): Promise<Tx[]> {
|
|
440
|
-
const timeoutMs = 8000; // Longer timeout for now
|
|
441
|
-
const maxRetryAttempts = 10; // Keep retrying within the timeout
|
|
442
|
-
const requests = chunkTxHashesRequest(txHashes);
|
|
443
|
-
const maxPeers = Math.min(Math.ceil(requests.length / 3), 10);
|
|
444
|
-
|
|
445
|
-
const txBatches = await this.p2pService.sendBatchRequest(
|
|
446
|
-
ReqRespSubProtocol.TX,
|
|
447
|
-
requests,
|
|
448
|
-
pinnedPeerId,
|
|
449
|
-
timeoutMs,
|
|
450
|
-
maxPeers,
|
|
451
|
-
maxRetryAttempts,
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
const txs = txBatches.flat();
|
|
455
|
-
if (txs.length > 0) {
|
|
456
|
-
await this.txPool.addPendingTxs(txs);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const txHashesStr = txHashes.map(tx => tx.toString()).join(', ');
|
|
460
|
-
this.log.debug(`Requested txs ${txHashesStr} (${txs.length} / ${txHashes.length}) from peers`);
|
|
461
|
-
|
|
462
|
-
// We return all transactions, even the not found ones to the caller, such they can handle missing items themselves.
|
|
463
|
-
return txs;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
435
|
public async getPendingTxs(limit?: number, after?: TxHash): Promise<Tx[]> {
|
|
467
436
|
if (limit !== undefined && limit <= 0) {
|
|
468
437
|
throw new TypeError('limit must be greater than 0');
|
|
@@ -530,49 +499,6 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
530
499
|
return this.txPool.hasTxs(txHashes);
|
|
531
500
|
}
|
|
532
501
|
|
|
533
|
-
/**
|
|
534
|
-
* Returns transactions in the transaction pool by hash.
|
|
535
|
-
* If a transaction is not in the pool, it will be requested from the network.
|
|
536
|
-
* @param txHashes - Hashes of the transactions to look for.
|
|
537
|
-
* @returns The txs found, or undefined if not found in the order requested.
|
|
538
|
-
*/
|
|
539
|
-
async getTxsByHash(txHashes: TxHash[], pinnedPeerId: PeerId | undefined): Promise<(Tx | undefined)[]> {
|
|
540
|
-
const txs = await Promise.all(txHashes.map(txHash => this.txPool.getTxByHash(txHash)));
|
|
541
|
-
const missingTxHashes = txs
|
|
542
|
-
.map((tx, index) => [tx, index] as const)
|
|
543
|
-
.filter(([tx, _index]) => !tx)
|
|
544
|
-
.map(([_tx, index]) => txHashes[index]);
|
|
545
|
-
|
|
546
|
-
if (missingTxHashes.length === 0) {
|
|
547
|
-
return txs as Tx[];
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
const missingTxs = await this.requestTxsByHash(missingTxHashes, pinnedPeerId);
|
|
551
|
-
// TODO: optimize
|
|
552
|
-
// Merge the found txs in order
|
|
553
|
-
const mergingTxs = txHashes.map(txHash => {
|
|
554
|
-
// Is it in the txs list from the mempool?
|
|
555
|
-
for (const tx of txs) {
|
|
556
|
-
if (tx !== undefined && tx.getTxHash().equals(txHash)) {
|
|
557
|
-
return tx;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Is it in the fetched missing txs?
|
|
562
|
-
// Note: this is an O(n^2) operation, but we expect the number of missing txs to be small.
|
|
563
|
-
for (const tx of missingTxs) {
|
|
564
|
-
if (tx.getTxHash().equals(txHash)) {
|
|
565
|
-
return tx;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// Otherwise return undefined
|
|
570
|
-
return undefined;
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
return mergingTxs;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
502
|
/**
|
|
577
503
|
* Returns an archived transaction in the transaction pool by its hash.
|
|
578
504
|
* @param txHash - Hash of the archived transaction to look for.
|
|
@@ -839,8 +765,8 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
839
765
|
this.log.debug(`Moved from state ${P2PClientState[oldState]} to ${P2PClientState[this.currentState]}`);
|
|
840
766
|
}
|
|
841
767
|
|
|
842
|
-
public
|
|
843
|
-
return this.p2pService.
|
|
768
|
+
public validateTxsReceivedInBlockProposal(txs: Tx[]): Promise<void> {
|
|
769
|
+
return this.p2pService.validateTxsReceivedInBlockProposal(txs);
|
|
844
770
|
}
|
|
845
771
|
|
|
846
772
|
/**
|
|
@@ -110,12 +110,12 @@ export interface TxPoolV2 extends TypedEventEmitter<TxPoolV2Events> {
|
|
|
110
110
|
addPendingTxs(txs: Tx[], opts?: { source?: string; feeComparisonOnly?: boolean }): Promise<AddTxsResult>;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Checks if
|
|
114
|
-
*
|
|
113
|
+
* Checks if the pool would accept a transaction without modifying state.
|
|
114
|
+
* Used as a pre-check before expensive proof verification.
|
|
115
115
|
* @param tx - Transaction to check
|
|
116
|
-
* @returns
|
|
116
|
+
* @returns 'accepted' if the pool would accept, 'ignored' if already in pool or undesirable
|
|
117
117
|
*/
|
|
118
|
-
canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'
|
|
118
|
+
canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'>;
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* Adds transactions as immediately protected for a given slot.
|
|
@@ -2,6 +2,7 @@ import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
|
2
2
|
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
|
+
import { Gas } from '@aztec/stdlib/gas';
|
|
5
6
|
import type { Tx } from '@aztec/stdlib/tx';
|
|
6
7
|
|
|
7
8
|
import { getFeePayerBalanceDelta } from '../../msg_validators/tx_validator/fee_payer_balance.js';
|
|
@@ -12,6 +13,8 @@ import { type PreAddResult, TxPoolRejectionCode } from './eviction/interfaces.js
|
|
|
12
13
|
export type TxMetaValidationData = {
|
|
13
14
|
getNonEmptyNullifiers(): Fr[];
|
|
14
15
|
expirationTimestamp: bigint;
|
|
16
|
+
/** Whether the tx has public calls. Used to select the correct L2 gas minimum. */
|
|
17
|
+
forPublic?: unknown;
|
|
15
18
|
constants: {
|
|
16
19
|
anchorBlockHeader: {
|
|
17
20
|
hash(): Promise<BlockHash>;
|
|
@@ -19,6 +22,9 @@ export type TxMetaValidationData = {
|
|
|
19
22
|
blockNumber: BlockNumber;
|
|
20
23
|
};
|
|
21
24
|
};
|
|
25
|
+
txContext: {
|
|
26
|
+
gasSettings: { gasLimits: Gas };
|
|
27
|
+
};
|
|
22
28
|
};
|
|
23
29
|
};
|
|
24
30
|
|
|
@@ -105,11 +111,15 @@ export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
|
|
|
105
111
|
data: {
|
|
106
112
|
getNonEmptyNullifiers: () => nullifierFrs,
|
|
107
113
|
expirationTimestamp,
|
|
114
|
+
forPublic: !!tx.data.forPublic,
|
|
108
115
|
constants: {
|
|
109
116
|
anchorBlockHeader: {
|
|
110
117
|
hash: () => Promise.resolve(anchorBlockHeaderHashFr),
|
|
111
118
|
globalVariables: { blockNumber: anchorBlockNumber },
|
|
112
119
|
},
|
|
120
|
+
txContext: {
|
|
121
|
+
gasSettings: { gasLimits: tx.data.constants.txContext.gasSettings.gasLimits },
|
|
122
|
+
},
|
|
113
123
|
},
|
|
114
124
|
},
|
|
115
125
|
};
|
|
@@ -237,6 +247,9 @@ export function stubTxMetaValidationData(overrides: { expirationTimestamp?: bigi
|
|
|
237
247
|
hash: () => Promise.resolve(new BlockHash(Fr.ZERO)),
|
|
238
248
|
globalVariables: { blockNumber: BlockNumber(0) },
|
|
239
249
|
},
|
|
250
|
+
txContext: {
|
|
251
|
+
gasSettings: { gasLimits: Gas.empty() },
|
|
252
|
+
},
|
|
240
253
|
},
|
|
241
254
|
};
|
|
242
255
|
}
|
|
@@ -74,7 +74,7 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
|
|
|
74
74
|
return this.#queue.put(() => this.#impl.addPendingTxs(txs, opts));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'
|
|
77
|
+
canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
|
|
78
78
|
return this.#queue.put(() => this.#impl.canAddPendingTx(tx));
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -327,7 +327,7 @@ export class TxPoolV2Impl {
|
|
|
327
327
|
return { status: 'accepted' };
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'
|
|
330
|
+
async canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
|
|
331
331
|
const txHashStr = tx.getTxHash().toString();
|
|
332
332
|
|
|
333
333
|
// Check if already in pool
|
|
@@ -335,14 +335,8 @@ export class TxPoolV2Impl {
|
|
|
335
335
|
return 'ignored';
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
-
// Build metadata and
|
|
338
|
+
// Build metadata and check pre-add rules
|
|
339
339
|
const meta = await buildTxMetaData(tx);
|
|
340
|
-
const validationResult = await this.#validateMeta(meta, undefined, 'can add pending');
|
|
341
|
-
if (validationResult !== true) {
|
|
342
|
-
return 'rejected';
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Use pre-add rules
|
|
346
340
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
347
341
|
const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
|
|
348
342
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Transaction Validation
|
|
2
|
+
|
|
3
|
+
This module defines the transaction validators and the factory functions that assemble them for each entry point into the system.
|
|
4
|
+
|
|
5
|
+
## Validation Strategy
|
|
6
|
+
|
|
7
|
+
Transactions enter the system through different paths. **Unsolicited** transactions (gossip and RPC) are fully validated before acceptance. **Solicited** transactions (req/resp and block proposals) are only checked for well-formedness because we must store them for block re-execution — they may ultimately be invalid, which is caught during block building and reported as part of block validation/attestation.
|
|
8
|
+
|
|
9
|
+
When solicited transactions fail to be mined, they may be migrated to the pending pool. At that point, the pool runs the state-dependent checks that were skipped on initial receipt.
|
|
10
|
+
|
|
11
|
+
## Entry Points
|
|
12
|
+
|
|
13
|
+
### 1. Gossip (libp2p pubsub)
|
|
14
|
+
|
|
15
|
+
**Factory**: `createFirstStageTxValidationsForGossipedTransactions` + `createSecondStageTxValidationsForGossipedTransactions`
|
|
16
|
+
**Called from**: `LibP2PService.handleGossipedTx()` in `libp2p_service.ts`
|
|
17
|
+
|
|
18
|
+
Unsolicited transactions from any peer. Fully validated in two stages with a pool pre-check in between to avoid wasting CPU on proof verification for transactions the pool would reject:
|
|
19
|
+
|
|
20
|
+
| Step | What runs | On failure |
|
|
21
|
+
|------|-----------|------------|
|
|
22
|
+
| **Stage 1** (fast) | TxPermitted, Data, Metadata, Timestamp, DoubleSpend, Gas, Phases, BlockHeader | Penalize peer, reject tx |
|
|
23
|
+
| **Pool pre-check** | `canAddPendingTx` — checks for duplicates, pool capacity | Ignore tx (no penalty) |
|
|
24
|
+
| **Stage 2** (slow) | Proof verification | Penalize peer, reject tx |
|
|
25
|
+
| **Pool add** | `addPendingTxs` | Accept, ignore, or reject |
|
|
26
|
+
|
|
27
|
+
Each stage-1 and stage-2 validator is paired with a `PeerErrorSeverity`. If a validator fails, the sending peer is penalized with that severity. The `doubleSpendValidator` has special handling: its severity is determined by how recently the nullifier appeared (recent = high tolerance, old = low tolerance).
|
|
28
|
+
|
|
29
|
+
### 2. JSON-RPC
|
|
30
|
+
|
|
31
|
+
**Factory**: `createTxValidatorForAcceptingTxsOverRPC`
|
|
32
|
+
**Called from**: `AztecNodeService.isValidTx()` in `aztec-node/server.ts`
|
|
33
|
+
|
|
34
|
+
Unsolicited transactions from a local wallet/PXE. Runs the full set of checks as a single aggregate validator:
|
|
35
|
+
|
|
36
|
+
- TxPermitted, Size, Data, Metadata, Timestamp, DoubleSpend, Phases, BlockHeader
|
|
37
|
+
- Gas (optional — skipped when `skipFeeEnforcement` is set)
|
|
38
|
+
- Proof verification (optional — skipped for simulations when no verifier is provided)
|
|
39
|
+
|
|
40
|
+
### 3. Req/resp and block proposals
|
|
41
|
+
|
|
42
|
+
**Factories**: `createTxValidatorForReqResponseReceivedTxs`, `createTxValidatorForBlockProposalReceivedTxs`
|
|
43
|
+
**Called from**: `LibP2PService.validateRequestedTx()`, `LibP2PService.validateTxsReceivedInBlockProposal()`, and `BatchRequestTxValidator` in `batch-tx-requester/tx_validator.ts`
|
|
44
|
+
|
|
45
|
+
Solicited transactions — we requested these from peers or received them as part of a block proposal we need to validate. We must accept them for re-execution even if they are invalid against the current state. Only well-formedness is checked:
|
|
46
|
+
|
|
47
|
+
- Metadata, Size, Data, Proof
|
|
48
|
+
|
|
49
|
+
State-dependent checks are deferred to either the block building validator (for txs included in blocks) or the pending pool migration validator (for unmined txs migrating to pending).
|
|
50
|
+
|
|
51
|
+
### 4. Block building
|
|
52
|
+
|
|
53
|
+
**Factory**: `createTxValidatorForBlockBuilding`
|
|
54
|
+
**Called from**: `CheckpointBuilder.makeBlockBuilderDeps()` in `validator-client/checkpoint_builder.ts`
|
|
55
|
+
|
|
56
|
+
Transactions already in the pool, about to be sequenced into a block. Re-validates against the current state of the block being built. **This is where invalid txs that entered via req/resp or block proposals are caught** — their invalidity is reported as part of block validation/attestation.
|
|
57
|
+
|
|
58
|
+
Runs:
|
|
59
|
+
- Timestamp, DoubleSpend, Phases, Gas, BlockHeader
|
|
60
|
+
|
|
61
|
+
Does **not** run:
|
|
62
|
+
- Proof, Data — already verified on entry (by gossip, RPC, or req/resp validators)
|
|
63
|
+
|
|
64
|
+
### 5. Pending pool migration
|
|
65
|
+
|
|
66
|
+
**Factory**: `createTxValidatorForTransactionsEnteringPendingTxPool`
|
|
67
|
+
**Called from**: `TxPoolV2Impl` (injected as the `createTxValidator` factory via `TxPoolV2Dependencies`)
|
|
68
|
+
|
|
69
|
+
When transactions that arrived via req/resp or block proposals fail to be mined, they may need to be included in our pending pool. These txs only had well-formedness checks on receipt, so the pool runs the state-dependent checks they missed before accepting them.
|
|
70
|
+
|
|
71
|
+
This validator is invoked on **every** transaction potentially entering the pending pool:
|
|
72
|
+
- `addPendingTxs` — validating each tx before adding
|
|
73
|
+
- `prepareForSlot` — unprotecting txs back to pending after a slot ends
|
|
74
|
+
- `handlePrunedBlocks` — unmining txs from pruned blocks back to pending
|
|
75
|
+
- Startup hydration — revalidating persisted non-mined txs on node restart
|
|
76
|
+
|
|
77
|
+
Runs:
|
|
78
|
+
- DoubleSpend, BlockHeader, GasLimits, Timestamp
|
|
79
|
+
|
|
80
|
+
Operates on `TxMetaData` (pre-built by the pool) rather than full `Tx` objects.
|
|
81
|
+
|
|
82
|
+
## Individual Validators
|
|
83
|
+
|
|
84
|
+
| Validator | What it checks | Benchmarked verification duration |
|
|
85
|
+
|-----------|---------------|---------------|
|
|
86
|
+
| `TxPermittedValidator` | Whether the system is accepting transactions (controlled by config flag) | 1.56 us |
|
|
87
|
+
| `DataTxValidator` | Transaction data integrity — correct structure, non-empty fields | 4.10–18.18 ms |
|
|
88
|
+
| `SizeTxValidator` | Transaction does not exceed maximum size limits | 2.28 us |
|
|
89
|
+
| `MetadataTxValidator` | Chain ID, rollup version, protocol contracts hash, VK tree root | 4.18 us |
|
|
90
|
+
| `TimestampTxValidator` | Transaction has not expired (expiration timestamp vs next slot) | 1.56 us |
|
|
91
|
+
| `DoubleSpendTxValidator` | Nullifiers do not already exist in the nullifier tree | 106.08 us |
|
|
92
|
+
| `GasTxValidator` | Gas limits are within bounds (delegates to `GasLimitsValidator`), max fee per gas meets current block fees, and fee payer has sufficient FeeJuice balance | 1.02 ms |
|
|
93
|
+
| `GasLimitsValidator` | Gas limits are >= fixed minimums and <= AVM max processable L2 gas. Used standalone in pool migration; also called internally by `GasTxValidator` | 3–10 us |
|
|
94
|
+
| `PhasesTxValidator` | Public function calls in setup phase are on the allow list | 10.12–13.12 us |
|
|
95
|
+
| `BlockHeaderTxValidator` | Transaction's anchor block hash exists in the archive tree | 98.88 us |
|
|
96
|
+
| `TxProofValidator` | Client proof verifies correctly | ~250ms |
|
|
97
|
+
|
|
98
|
+
## Validator Coverage by Entry Point
|
|
99
|
+
|
|
100
|
+
| Validator | Gossip | RPC | Req/resp | Block building | Pool migration |
|
|
101
|
+
|-----------|--------|-----|----------|----------------|----------------|
|
|
102
|
+
| TxPermitted | Stage 1 | Yes | — | — | — |
|
|
103
|
+
| Data | Stage 1 | Yes | Yes | — | — |
|
|
104
|
+
| Size | — | Yes | Yes | — | — |
|
|
105
|
+
| Metadata | Stage 1 | Yes | Yes | — | — |
|
|
106
|
+
| Timestamp | Stage 1 | Yes | — | Yes | Yes |
|
|
107
|
+
| DoubleSpend | Stage 1 | Yes | — | Yes | Yes |
|
|
108
|
+
| Gas (balance + limits) | Stage 1 | Optional* | — | Yes | — |
|
|
109
|
+
| GasLimits (standalone) | — | — | — | — | Yes |
|
|
110
|
+
| Phases | Stage 1 | Yes | — | Yes | — |
|
|
111
|
+
| BlockHeader | Stage 1 | Yes | — | Yes | Yes |
|
|
112
|
+
| Proof | Stage 2 | Optional** | Yes | — | — |
|
|
113
|
+
|
|
114
|
+
\* Gas balance check is skipped when `skipFeeEnforcement` is set (testing/dev). `GasTxValidator` internally delegates to `GasLimitsValidator` as its first step, so gas limits are checked wherever `GasTxValidator` runs. Pool migration uses `GasLimitsValidator` standalone because it doesn't need the balance or fee-per-gas checks.
|
|
115
|
+
\** Proof verification is skipped for simulations (no verifier provided).
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { TxValidationResult, TxValidator } from '@aztec/stdlib/tx';
|
|
2
2
|
|
|
3
3
|
export class AggregateTxValidator<T> implements TxValidator<T> {
|
|
4
|
-
|
|
4
|
+
readonly validators: TxValidator<T>[];
|
|
5
5
|
constructor(...validators: TxValidator<T>[]) {
|
|
6
6
|
if (validators.length === 0) {
|
|
7
7
|
throw new Error('At least one validator must be provided');
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
this
|
|
10
|
+
this.validators = validators;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async validateTx(tx: T): Promise<TxValidationResult> {
|
|
14
14
|
const aggregate: { result: string; reason?: string[] } = { result: 'valid', reason: [] };
|
|
15
|
-
for (const validator of this
|
|
15
|
+
for (const validator of this.validators) {
|
|
16
16
|
const result = await validator.validateTx(tx);
|
|
17
17
|
if (result.result === 'invalid') {
|
|
18
18
|
aggregate.result = 'invalid';
|