@aztec/p2p 0.0.1-commit.684755437 → 0.0.1-commit.6b113946b
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/README.md +129 -3
- package/dest/client/factory.d.ts +1 -1
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +19 -7
- 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 +22 -10
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +6 -5
- package/dest/config.d.ts +13 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +20 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +3 -3
- package/dest/mem_pools/attestation_pool/attestation_pool.js +3 -3
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +6 -6
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.js +2 -1
- package/dest/mem_pools/tx_pool/priority.d.ts +2 -2
- package/dest/mem_pools/tx_pool/priority.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/priority.js +4 -4
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +3 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +2 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +7 -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 -2
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +7 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -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.js +3 -0
- 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 +19 -5
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.d.ts.map +1 -1
- package/dest/msg_validators/attestation_validator/attestation_validator.js +5 -4
- package/dest/msg_validators/clock_tolerance.d.ts +1 -1
- package/dest/msg_validators/clock_tolerance.d.ts.map +1 -1
- package/dest/msg_validators/clock_tolerance.js +4 -3
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.d.ts.map +1 -1
- package/dest/msg_validators/proposal_validator/proposal_validator.js +5 -5
- package/dest/msg_validators/tx_validator/factory.d.ts +23 -4
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +28 -8
- 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/phases_validator.d.ts +21 -1
- package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/phases_validator.js +27 -0
- package/dest/services/encoding.d.ts +5 -1
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +7 -1
- package/dest/services/libp2p/libp2p_service.d.ts +2 -9
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +37 -30
- 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 +4 -2
- package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +11 -8
- 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 +66 -65
- package/dest/services/reqresp/batch-tx-requester/interface.d.ts +3 -2
- package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +5 -4
- package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/missing_txs.js +13 -7
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +3 -1
- package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
- package/dest/services/reqresp/batch-tx-requester/peer_collection.js +3 -0
- package/dest/services/reqresp/reqresp.d.ts +1 -1
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +17 -9
- package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -4
- package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/fast_tx_collection.js +57 -73
- package/dest/services/tx_collection/proposal_tx_collector.d.ts +6 -7
- package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
- package/dest/services/tx_collection/proposal_tx_collector.js +4 -4
- package/dest/services/tx_collection/request_tracker.d.ts +53 -0
- package/dest/services/tx_collection/request_tracker.d.ts.map +1 -0
- package/dest/services/tx_collection/request_tracker.js +84 -0
- package/dest/services/tx_collection/slow_tx_collection.js +1 -1
- package/dest/services/tx_collection/tx_collection.d.ts +3 -6
- package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
- 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 +22 -3
- package/dest/testbench/p2p_client_testbench_worker.d.ts +1 -1
- package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
- package/dest/testbench/p2p_client_testbench_worker.js +6 -5
- package/package.json +14 -14
- package/src/client/factory.ts +34 -11
- package/src/client/p2p_client.ts +22 -12
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +6 -7
- package/src/config.ts +33 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +3 -3
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +6 -6
- package/src/mem_pools/tx_pool/eviction/fee_payer_balance_eviction_rule.ts +2 -1
- package/src/mem_pools/tx_pool/priority.ts +4 -4
- package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +3 -1
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +2 -1
- package/src/mem_pools/tx_pool_v2/interfaces.ts +6 -4
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +11 -1
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +13 -1
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +20 -5
- package/src/msg_validators/attestation_validator/README.md +49 -0
- package/src/msg_validators/attestation_validator/attestation_validator.ts +5 -4
- package/src/msg_validators/clock_tolerance.ts +4 -3
- package/src/msg_validators/proposal_validator/README.md +123 -0
- package/src/msg_validators/proposal_validator/proposal_validator.ts +6 -5
- package/src/msg_validators/tx_validator/README.md +5 -1
- package/src/msg_validators/tx_validator/factory.ts +36 -3
- package/src/msg_validators/tx_validator/gas_validator.ts +41 -8
- package/src/msg_validators/tx_validator/phases_validator.ts +30 -0
- package/src/services/encoding.ts +9 -1
- package/src/services/libp2p/libp2p_service.ts +34 -33
- package/src/services/peer-manager/peer_manager.ts +5 -2
- package/src/services/reqresp/README.md +229 -0
- package/src/services/reqresp/batch-tx-requester/README.md +46 -7
- package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +61 -69
- package/src/services/reqresp/batch-tx-requester/interface.ts +2 -1
- package/src/services/reqresp/batch-tx-requester/missing_txs.ts +13 -6
- package/src/services/reqresp/batch-tx-requester/peer_collection.ts +5 -0
- package/src/services/reqresp/reqresp.ts +19 -11
- package/src/services/tx_collection/fast_tx_collection.ts +57 -83
- package/src/services/tx_collection/proposal_tx_collector.ts +8 -13
- package/src/services/tx_collection/request_tracker.ts +127 -0
- package/src/services/tx_collection/slow_tx_collection.ts +1 -1
- package/src/services/tx_collection/tx_collection.ts +3 -5
- package/src/test-helpers/testbench-utils.ts +29 -3
- package/src/testbench/p2p_client_testbench_worker.ts +6 -8
- package/dest/services/tx_collection/missing_txs_tracker.d.ts +0 -32
- package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +0 -1
- package/dest/services/tx_collection/missing_txs_tracker.js +0 -27
- package/src/services/tx_collection/missing_txs_tracker.ts +0 -52
package/src/client/p2p_client.ts
CHANGED
|
@@ -669,31 +669,41 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
669
669
|
}
|
|
670
670
|
|
|
671
671
|
/**
|
|
672
|
-
* Returns true if the prune
|
|
673
|
-
* If the
|
|
674
|
-
* If they differ, the prune spans across checkpoints (epoch prune).
|
|
672
|
+
* Returns true if the prune is an epoch prune (new checkpoint number is less than old).
|
|
673
|
+
* If the checkpoint number stays the same or increases, the prune is within a checkpoint.
|
|
675
674
|
*/
|
|
676
675
|
private async isEpochPrune(newCheckpoint: CheckpointId): Promise<boolean> {
|
|
677
676
|
const tips = await this.l2Tips.getL2Tips();
|
|
678
677
|
const oldCheckpointNumber = tips.checkpointed.checkpoint.number;
|
|
679
|
-
if (oldCheckpointNumber <= CheckpointNumber.
|
|
678
|
+
if (oldCheckpointNumber <= CheckpointNumber.INITIAL) {
|
|
680
679
|
return false;
|
|
681
680
|
}
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
681
|
+
const newCheckpointNumber = newCheckpoint.number;
|
|
682
|
+
// We check that the new checkpoint number is less than the old checkpoint number in order to consider it an epoch prune.
|
|
683
|
+
// To be more certain that it is an epoch prune, we will check that at least 2 checkpoints were removed.
|
|
684
|
+
// This means we should avoid thinking checkpoints removed by L1 re-orgs are epoch prunes
|
|
685
|
+
const thresholdForEpochPrune = CheckpointNumber(oldCheckpointNumber - 2);
|
|
686
|
+
const isEpochPrune = newCheckpointNumber <= thresholdForEpochPrune;
|
|
687
|
+
if (isEpochPrune) {
|
|
688
|
+
this.log.info(`Detected epoch prune to ${newCheckpointNumber}`, {
|
|
689
|
+
oldCheckpointNumber,
|
|
690
|
+
newCheckpointNumber,
|
|
691
|
+
thresholdForEpochPrune,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
686
694
|
return isEpochPrune;
|
|
687
695
|
}
|
|
688
696
|
|
|
689
697
|
/** Checks if the slot has changed and calls prepareForSlot if so. */
|
|
690
698
|
private async maybeCallPrepareForSlot(): Promise<void> {
|
|
691
|
-
|
|
692
|
-
|
|
699
|
+
// If we have a pending checkpoint available, we want to prepare the target slot - otherwise we prepare the current slot
|
|
700
|
+
// Knowledege of pending checkpoints is in the PR above
|
|
701
|
+
const { targetSlot } = this.epochCache.getTargetAndNextSlot();
|
|
702
|
+
if (targetSlot <= this.lastSlotProcessed) {
|
|
693
703
|
return;
|
|
694
704
|
}
|
|
695
|
-
this.lastSlotProcessed =
|
|
696
|
-
await this.txPool.prepareForSlot(
|
|
705
|
+
this.lastSlotProcessed = targetSlot;
|
|
706
|
+
await this.txPool.prepareForSlot(targetSlot);
|
|
697
707
|
}
|
|
698
708
|
|
|
699
709
|
private async startServiceIfSynched() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MockL2BlockSource } from '@aztec/archiver/test';
|
|
2
|
+
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
3
|
import { SecretValue } from '@aztec/foundation/config';
|
|
3
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
5
|
import { sleep } from '@aztec/foundation/sleep';
|
|
@@ -14,12 +15,13 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
|
|
|
14
15
|
|
|
15
16
|
import type { PeerId } from '@libp2p/interface';
|
|
16
17
|
import { peerIdFromString } from '@libp2p/peer-id';
|
|
18
|
+
import { mock } from 'jest-mock-extended';
|
|
17
19
|
|
|
18
20
|
import type { P2PConfig } from '../../../config.js';
|
|
19
21
|
import { BatchTxRequesterCollector, SendBatchRequestCollector } from '../../../services/index.js';
|
|
20
22
|
import type { IBatchRequestTxValidator } from '../../../services/reqresp/batch-tx-requester/tx_validator.js';
|
|
21
23
|
import { RateLimitStatus } from '../../../services/reqresp/rate-limiter/rate_limiter.js';
|
|
22
|
-
import {
|
|
24
|
+
import { RequestTracker } from '../../../services/tx_collection/request_tracker.js';
|
|
23
25
|
import {
|
|
24
26
|
AlwaysTrueCircuitVerifier,
|
|
25
27
|
BENCHMARK_CONSTANTS,
|
|
@@ -27,7 +29,6 @@ import {
|
|
|
27
29
|
InMemoryTxPool,
|
|
28
30
|
UNLIMITED_RATE_LIMIT_QUOTA,
|
|
29
31
|
calculateInternalTimeout,
|
|
30
|
-
createMockEpochCache,
|
|
31
32
|
createMockWorldStateSynchronizer,
|
|
32
33
|
} from '../../../test-helpers/index.js';
|
|
33
34
|
import { createP2PClient } from '../../index.js';
|
|
@@ -98,7 +99,7 @@ function sendMessage(message: WorkerResponse): Promise<void> {
|
|
|
98
99
|
async function startClient(config: P2PConfig, clientIndex: number) {
|
|
99
100
|
txPool = new InMemoryTxPool();
|
|
100
101
|
attestationPool = new InMemoryAttestationPool();
|
|
101
|
-
const epochCache =
|
|
102
|
+
const epochCache = mock<EpochCache>();
|
|
102
103
|
const worldState = createMockWorldStateSynchronizer();
|
|
103
104
|
const l2BlockSource = new MockL2BlockSource();
|
|
104
105
|
const proofVerifier = new AlwaysTrueCircuitVerifier();
|
|
@@ -213,10 +214,9 @@ async function runCollector(cmd: Extract<WorkerCommand, { type: 'RUN_COLLECTOR'
|
|
|
213
214
|
const fetched = await executeTimeout(
|
|
214
215
|
(_signal: AbortSignal) =>
|
|
215
216
|
collector.collectTxs(
|
|
216
|
-
|
|
217
|
+
RequestTracker.create(parsedTxHashes, new Date(Date.now() + internalTimeoutMs)),
|
|
217
218
|
parsedProposal,
|
|
218
219
|
pinnedPeer,
|
|
219
|
-
internalTimeoutMs,
|
|
220
220
|
),
|
|
221
221
|
timeoutMs,
|
|
222
222
|
() => new Error(`Collector timed out after ${timeoutMs}ms`),
|
|
@@ -231,10 +231,9 @@ async function runCollector(cmd: Extract<WorkerCommand, { type: 'RUN_COLLECTOR'
|
|
|
231
231
|
const fetched = await executeTimeout(
|
|
232
232
|
(_signal: AbortSignal) =>
|
|
233
233
|
collector.collectTxs(
|
|
234
|
-
|
|
234
|
+
RequestTracker.create(parsedTxHashes, new Date(Date.now() + internalTimeoutMs)),
|
|
235
235
|
parsedProposal,
|
|
236
236
|
pinnedPeer,
|
|
237
|
-
internalTimeoutMs,
|
|
238
237
|
),
|
|
239
238
|
timeoutMs,
|
|
240
239
|
() => new Error(`Collector timed out after ${timeoutMs}ms`),
|
package/src/config.ts
CHANGED
|
@@ -43,6 +43,15 @@ export interface P2PConfig
|
|
|
43
43
|
/** Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. */
|
|
44
44
|
validateMaxTxsPerBlock?: number;
|
|
45
45
|
|
|
46
|
+
/** Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set. */
|
|
47
|
+
validateMaxTxsPerCheckpoint?: number;
|
|
48
|
+
|
|
49
|
+
/** Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected. */
|
|
50
|
+
validateMaxL2BlockGas?: number;
|
|
51
|
+
|
|
52
|
+
/** Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected. */
|
|
53
|
+
validateMaxDABlockGas?: number;
|
|
54
|
+
|
|
46
55
|
/** A flag dictating whether the P2P subsystem should be enabled. */
|
|
47
56
|
p2pEnabled: boolean;
|
|
48
57
|
|
|
@@ -61,6 +70,9 @@ export interface P2PConfig
|
|
|
61
70
|
/** The frequency in which to check for new peers. */
|
|
62
71
|
peerCheckIntervalMS: number;
|
|
63
72
|
|
|
73
|
+
/** How long to ban a peer after it fails MAX_DIAL_ATTEMPTS dials. */
|
|
74
|
+
peerFailedBanTimeMs: number;
|
|
75
|
+
|
|
64
76
|
/** Size of queue of L2 blocks to store. */
|
|
65
77
|
l2QueueSize: number;
|
|
66
78
|
|
|
@@ -208,6 +220,22 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
|
|
|
208
220
|
'Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set.',
|
|
209
221
|
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
210
222
|
},
|
|
223
|
+
validateMaxTxsPerCheckpoint: {
|
|
224
|
+
env: 'VALIDATOR_MAX_TX_PER_CHECKPOINT',
|
|
225
|
+
description:
|
|
226
|
+
'Maximum transactions per checkpoint for validation. Used as fallback for maxTxsPerBlock when that is not set.',
|
|
227
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
228
|
+
},
|
|
229
|
+
validateMaxL2BlockGas: {
|
|
230
|
+
env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
|
|
231
|
+
description: 'Maximum L2 gas per block for validation. When set, txs exceeding this limit are rejected.',
|
|
232
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
233
|
+
},
|
|
234
|
+
validateMaxDABlockGas: {
|
|
235
|
+
env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
|
|
236
|
+
description: 'Maximum DA gas per block for validation. When set, txs exceeding this limit are rejected.',
|
|
237
|
+
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
|
|
238
|
+
},
|
|
211
239
|
p2pEnabled: {
|
|
212
240
|
env: 'P2P_ENABLED',
|
|
213
241
|
description: 'A flag dictating whether the P2P subsystem should be enabled.',
|
|
@@ -238,6 +266,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
|
|
|
238
266
|
description: 'The frequency in which to check for new peers.',
|
|
239
267
|
...numberConfigHelper(30_000),
|
|
240
268
|
},
|
|
269
|
+
peerFailedBanTimeMs: {
|
|
270
|
+
env: 'P2P_PEER_FAILED_BAN_TIME_MS',
|
|
271
|
+
description: 'How long to ban a peer after it fails maximum dial attempts.',
|
|
272
|
+
...numberConfigHelper(5 * 60 * 1000),
|
|
273
|
+
},
|
|
241
274
|
l2QueueSize: {
|
|
242
275
|
env: 'P2P_L2_QUEUE_SIZE',
|
|
243
276
|
description: 'Size of queue of L2 blocks to store.',
|
|
@@ -26,10 +26,10 @@ export type TryAddResult = {
|
|
|
26
26
|
count: number;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
export const MAX_CHECKPOINT_PROPOSALS_PER_SLOT =
|
|
30
|
-
export const MAX_BLOCK_PROPOSALS_PER_POSITION =
|
|
29
|
+
export const MAX_CHECKPOINT_PROPOSALS_PER_SLOT = 2;
|
|
30
|
+
export const MAX_BLOCK_PROPOSALS_PER_POSITION = 2;
|
|
31
31
|
/** Maximum attestations a single signer can make per slot before being rejected. */
|
|
32
|
-
export const MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER =
|
|
32
|
+
export const MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER = 2;
|
|
33
33
|
|
|
34
34
|
/** Public API interface for attestation pools. Used for typing mocks and test implementations. */
|
|
35
35
|
export type AttestationPoolApi = Pick<
|
|
@@ -446,12 +446,12 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
446
446
|
const result2 = await ap.tryAddBlockProposal(proposal2);
|
|
447
447
|
expect(result2.count).toBe(2);
|
|
448
448
|
|
|
449
|
-
//
|
|
449
|
+
// Third proposal for same position should be rejected (cap is 2)
|
|
450
450
|
const proposal3 = await mockBlockProposalWithIndex(signers[2], slotNumber, indexWithinCheckpoint);
|
|
451
451
|
const result3 = await ap.tryAddBlockProposal(proposal3);
|
|
452
452
|
|
|
453
|
-
expect(result3.added).toBe(
|
|
454
|
-
expect(result3.count).toBe(
|
|
453
|
+
expect(result3.added).toBe(false);
|
|
454
|
+
expect(result3.count).toBe(2);
|
|
455
455
|
});
|
|
456
456
|
|
|
457
457
|
it('should return added=false when exceeding capacity', async () => {
|
|
@@ -666,12 +666,12 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
666
666
|
const result2 = await ap.tryAddCheckpointProposal(proposal2);
|
|
667
667
|
expect(result2.count).toBe(2);
|
|
668
668
|
|
|
669
|
-
//
|
|
669
|
+
// Third proposal for same slot should be rejected (cap is 2)
|
|
670
670
|
const proposal3 = await mockCheckpointProposalCoreForPool(signers[2], slotNumber);
|
|
671
671
|
const result3 = await ap.tryAddCheckpointProposal(proposal3);
|
|
672
672
|
|
|
673
|
-
expect(result3.added).toBe(
|
|
674
|
-
expect(result3.count).toBe(
|
|
673
|
+
expect(result3.added).toBe(false);
|
|
674
|
+
expect(result3.count).toBe(2);
|
|
675
675
|
});
|
|
676
676
|
|
|
677
677
|
it('should not count attestations as proposals for duplicate detection', async () => {
|
|
@@ -32,10 +32,11 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
|
|
|
32
32
|
|
|
33
33
|
if (context.event === EvictionEvent.BLOCK_MINED) {
|
|
34
34
|
const blockNumber = context.block.getBlockNumber();
|
|
35
|
+
const blockHash = await context.block.hash();
|
|
35
36
|
// Ensure world state is synced to this block before accessing the snapshot.
|
|
36
37
|
// This handles the race where a block is added to the archiver
|
|
37
38
|
// but the world state hasn't synced it yet.
|
|
38
|
-
await this.worldState.syncImmediate(blockNumber);
|
|
39
|
+
await this.worldState.syncImmediate(blockNumber, blockHash);
|
|
39
40
|
return await this.evictForFeePayers(context.feePayers, this.worldState.getSnapshot(blockNumber), txPool);
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { minBigint } from '@aztec/foundation/bigint';
|
|
1
2
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
2
3
|
import type { Tx } from '@aztec/stdlib/tx';
|
|
3
4
|
|
|
@@ -11,10 +12,9 @@ export function getPendingTxPriority(tx: Tx): string {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* Returns the priority of a tx.
|
|
15
|
+
* Returns the priority of a tx based on the L2 priority fee only, capped by the max fees per gas.
|
|
15
16
|
*/
|
|
16
17
|
export function getTxPriorityFee(tx: Tx): bigint {
|
|
17
|
-
const priorityFees = tx.getGasSettings()
|
|
18
|
-
|
|
19
|
-
return totalFees;
|
|
18
|
+
const { maxPriorityFeesPerGas: priorityFees, maxFeesPerGas } = tx.getGasSettings();
|
|
19
|
+
return minBigint(maxFeesPerGas.feePerL2Gas, priorityFees.feePerL2Gas);
|
|
20
20
|
}
|
|
@@ -204,7 +204,9 @@ export function describeTxPool(getTxPool: () => TxPool) {
|
|
|
204
204
|
|
|
205
205
|
it('returns pending tx hashes sorted by priority', async () => {
|
|
206
206
|
const withPriorityFee = (tx: Tx, fee: number) => {
|
|
207
|
-
unfreeze(tx.data.constants.txContext.gasSettings)
|
|
207
|
+
const gs = unfreeze(tx.data.constants.txContext.gasSettings);
|
|
208
|
+
gs.maxPriorityFeesPerGas = new GasFees(fee, fee);
|
|
209
|
+
gs.maxFeesPerGas = new GasFees(fee, fee);
|
|
208
210
|
return tx;
|
|
209
211
|
};
|
|
210
212
|
|
|
@@ -29,7 +29,8 @@ export class FeePayerBalanceEvictionRule implements EvictionRule {
|
|
|
29
29
|
|
|
30
30
|
if (context.event === EvictionEvent.BLOCK_MINED) {
|
|
31
31
|
const blockNumber = context.block.getBlockNumber();
|
|
32
|
-
await
|
|
32
|
+
const blockHash = await context.block.hash();
|
|
33
|
+
await this.worldState.syncImmediate(blockNumber, blockHash);
|
|
33
34
|
return await this.evictForFeePayers(context.feePayers, this.worldState.getSnapshot(blockNumber), pool);
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -72,6 +72,8 @@ export type TxPoolV2Dependencies = {
|
|
|
72
72
|
worldStateSynchronizer: WorldStateSynchronizer;
|
|
73
73
|
/** Factory that creates a validator for re-validating pool transactions using metadata */
|
|
74
74
|
createTxValidator: () => Promise<TxValidator<TxMetaData>>;
|
|
75
|
+
/** Checks whether a tx's setup-phase calls are on the allow list. Precomputed at receipt time. */
|
|
76
|
+
checkAllowedSetupCalls: (tx: Tx) => Promise<boolean>;
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
/**
|
|
@@ -158,10 +160,10 @@ export interface TxPoolV2 extends TypedEventEmitter<TxPoolV2Events> {
|
|
|
158
160
|
handleMinedBlock(block: L2Block): Promise<void>;
|
|
159
161
|
|
|
160
162
|
/**
|
|
161
|
-
* Prepares the pool for a new slot
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
163
|
+
* Prepares the pool for a new slot by unprotecting transactions from earlier
|
|
164
|
+
* slots and re-validating them before returning to pending state.
|
|
165
|
+
* @param slotNumber - The pipeline slot we are building for (i.e. the slot
|
|
166
|
+
* the resulting blocks will target on L1).
|
|
165
167
|
*/
|
|
166
168
|
prepareForSlot(slotNumber: SlotNumber): Promise<void>;
|
|
167
169
|
|
|
@@ -67,6 +67,9 @@ export type TxMetaData = {
|
|
|
67
67
|
/** Timestamp by which the transaction must be included (for expiration checks) */
|
|
68
68
|
readonly expirationTimestamp: bigint;
|
|
69
69
|
|
|
70
|
+
/** Whether the tx's setup-phase calls pass the allow list check. Computed at receipt time. */
|
|
71
|
+
readonly allowedSetupCalls: boolean;
|
|
72
|
+
|
|
70
73
|
/** Validator-compatible data, providing the same access patterns as Tx.data */
|
|
71
74
|
readonly data: TxMetaValidationData;
|
|
72
75
|
|
|
@@ -84,8 +87,12 @@ export type TxState = 'pending' | 'protected' | 'mined' | 'deleted';
|
|
|
84
87
|
* Builds TxMetaData from a full Tx object.
|
|
85
88
|
* Extracts all relevant fields for efficient in-memory storage and querying.
|
|
86
89
|
* Fr values are captured in closures for zero-cost re-validation.
|
|
90
|
+
*
|
|
91
|
+
* @param allowedSetupCalls - Whether the tx's setup-phase calls pass the allow list.
|
|
92
|
+
* For gossip/RPC txs this is always `true` (already validated by PhasesTxValidator).
|
|
93
|
+
* For req/resp txs this should be computed by the caller using the phases validator.
|
|
87
94
|
*/
|
|
88
|
-
export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
|
|
95
|
+
export async function buildTxMetaData(tx: Tx, allowedSetupCalls: boolean = true): Promise<TxMetaData> {
|
|
89
96
|
const txHashObj = tx.getTxHash();
|
|
90
97
|
const txHash = txHashObj.toString();
|
|
91
98
|
const txHashBigInt = txHashObj.toBigInt();
|
|
@@ -112,6 +119,7 @@ export async function buildTxMetaData(tx: Tx): Promise<TxMetaData> {
|
|
|
112
119
|
feeLimit,
|
|
113
120
|
nullifiers,
|
|
114
121
|
expirationTimestamp,
|
|
122
|
+
allowedSetupCalls,
|
|
115
123
|
receivedAt: 0,
|
|
116
124
|
estimatedSizeBytes,
|
|
117
125
|
data: {
|
|
@@ -304,6 +312,7 @@ export function stubTxMetaData(
|
|
|
304
312
|
nullifiers?: string[];
|
|
305
313
|
expirationTimestamp?: bigint;
|
|
306
314
|
anchorBlockHeaderHash?: string;
|
|
315
|
+
allowedSetupCalls?: boolean;
|
|
307
316
|
} = {},
|
|
308
317
|
): TxMetaData {
|
|
309
318
|
const txHashBigInt = Fr.fromHexString(txHash).toBigInt();
|
|
@@ -320,6 +329,7 @@ export function stubTxMetaData(
|
|
|
320
329
|
feeLimit: overrides.feeLimit ?? 100n,
|
|
321
330
|
nullifiers: overrides.nullifiers ?? [`0x${normalizedTxHash.slice(2)}null1`],
|
|
322
331
|
expirationTimestamp,
|
|
332
|
+
allowedSetupCalls: overrides.allowedSetupCalls ?? true,
|
|
323
333
|
receivedAt: 0,
|
|
324
334
|
estimatedSizeBytes: 0,
|
|
325
335
|
data: stubTxMetaValidationData({ expirationTimestamp }),
|
|
@@ -11,7 +11,14 @@ import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-clien
|
|
|
11
11
|
import EventEmitter from 'node:events';
|
|
12
12
|
|
|
13
13
|
import { PoolInstrumentation, PoolName } from '../instrumentation.js';
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
AddTxsResult,
|
|
16
|
+
PoolReadAccess,
|
|
17
|
+
TxPoolV2,
|
|
18
|
+
TxPoolV2Config,
|
|
19
|
+
TxPoolV2Dependencies,
|
|
20
|
+
TxPoolV2Events,
|
|
21
|
+
} from './interfaces.js';
|
|
15
22
|
import type { TxState } from './tx_metadata.js';
|
|
16
23
|
import { TxPoolV2Impl } from './tx_pool_v2_impl.js';
|
|
17
24
|
|
|
@@ -165,6 +172,11 @@ export class AztecKVTxPoolV2 extends (EventEmitter as new () => TypedEventEmitte
|
|
|
165
172
|
return this.#queue.put(() => Promise.resolve(this.#impl.getLowestPriorityPending(limit)));
|
|
166
173
|
}
|
|
167
174
|
|
|
175
|
+
/** Returns read-only access to the pool. Used for testing. */
|
|
176
|
+
getPoolReadAccess(): PoolReadAccess {
|
|
177
|
+
return this.#impl.getPoolReadAccess();
|
|
178
|
+
}
|
|
179
|
+
|
|
168
180
|
// === Configuration ===
|
|
169
181
|
|
|
170
182
|
updateConfig(config: Partial<TxPoolV2Config>): Promise<void> {
|
|
@@ -62,6 +62,7 @@ export class TxPoolV2Impl {
|
|
|
62
62
|
#l2BlockSource: L2BlockSource;
|
|
63
63
|
#worldStateSynchronizer: WorldStateSynchronizer;
|
|
64
64
|
#createTxValidator: TxPoolV2Dependencies['createTxValidator'];
|
|
65
|
+
#checkAllowedSetupCalls: TxPoolV2Dependencies['checkAllowedSetupCalls'];
|
|
65
66
|
|
|
66
67
|
// === In-Memory Indices ===
|
|
67
68
|
#indices: TxPoolIndices = new TxPoolIndices();
|
|
@@ -93,6 +94,7 @@ export class TxPoolV2Impl {
|
|
|
93
94
|
this.#l2BlockSource = deps.l2BlockSource;
|
|
94
95
|
this.#worldStateSynchronizer = deps.worldStateSynchronizer;
|
|
95
96
|
this.#createTxValidator = deps.createTxValidator;
|
|
97
|
+
this.#checkAllowedSetupCalls = deps.checkAllowedSetupCalls;
|
|
96
98
|
|
|
97
99
|
this.#config = { ...DEFAULT_TX_POOL_V2_CONFIG, ...config };
|
|
98
100
|
this.#archive = new TxArchive(archiveStore, this.#config.archivedTxLimit, log);
|
|
@@ -354,6 +356,7 @@ export class TxPoolV2Impl {
|
|
|
354
356
|
|
|
355
357
|
// Check if already in pool
|
|
356
358
|
if (this.#indices.has(txHashStr)) {
|
|
359
|
+
this.#log.verbose(`canAddPendingTx: tx ${txHashStr} already in pool`);
|
|
357
360
|
return 'ignored';
|
|
358
361
|
}
|
|
359
362
|
|
|
@@ -362,26 +365,37 @@ export class TxPoolV2Impl {
|
|
|
362
365
|
const poolAccess = this.#createPreAddPoolAccess();
|
|
363
366
|
const preAddResult = await this.#evictionManager.runPreAddRules(meta, poolAccess);
|
|
364
367
|
|
|
365
|
-
|
|
368
|
+
if (preAddResult.shouldIgnore) {
|
|
369
|
+
this.#log.verbose(`canAddPendingTx: tx ${txHashStr} ignored by pre-add rule`, {
|
|
370
|
+
reason: preAddResult.reason?.message ?? 'no reason provided',
|
|
371
|
+
});
|
|
372
|
+
return 'ignored';
|
|
373
|
+
}
|
|
374
|
+
return 'accepted';
|
|
366
375
|
}
|
|
367
376
|
|
|
368
377
|
async addProtectedTxs(txs: Tx[], block: BlockHeader, opts: { source?: string }): Promise<void> {
|
|
369
378
|
const slotNumber = block.globalVariables.slotNumber;
|
|
370
379
|
|
|
380
|
+
// Precompute setup-call allow-list flags outside the store transaction
|
|
381
|
+
const allowedFlags = await Promise.all(txs.map(tx => this.#checkAllowedSetupCalls(tx)));
|
|
382
|
+
|
|
371
383
|
await this.#store.transactionAsync(async () => {
|
|
372
|
-
for (
|
|
384
|
+
for (let i = 0; i < txs.length; i++) {
|
|
385
|
+
const tx = txs[i];
|
|
373
386
|
const txHash = tx.getTxHash();
|
|
374
387
|
const txHashStr = txHash.toString();
|
|
375
388
|
const isNew = !this.#indices.has(txHashStr);
|
|
376
389
|
const minedBlockId = await this.#getMinedBlockId(txHash);
|
|
377
390
|
|
|
378
391
|
if (isNew) {
|
|
392
|
+
const meta = await buildTxMetaData(tx, allowedFlags[i]);
|
|
379
393
|
// New tx - add as mined or protected (callback emitted by #addTx)
|
|
380
394
|
if (minedBlockId) {
|
|
381
|
-
await this.#addTx(tx, { mined: minedBlockId }, opts);
|
|
395
|
+
await this.#addTx(tx, { mined: minedBlockId }, opts, meta);
|
|
382
396
|
this.#indices.setProtection(txHashStr, slotNumber);
|
|
383
397
|
} else {
|
|
384
|
-
await this.#addTx(tx, { protected: slotNumber }, opts);
|
|
398
|
+
await this.#addTx(tx, { protected: slotNumber }, opts, meta);
|
|
385
399
|
}
|
|
386
400
|
} else {
|
|
387
401
|
// Existing tx - update protection and mined status
|
|
@@ -976,7 +990,8 @@ export class TxPoolV2Impl {
|
|
|
976
990
|
|
|
977
991
|
try {
|
|
978
992
|
const tx = Tx.fromBuffer(buffer);
|
|
979
|
-
const
|
|
993
|
+
const allowedSetupCalls = await this.#checkAllowedSetupCalls(tx);
|
|
994
|
+
const meta = await buildTxMetaData(tx, allowedSetupCalls);
|
|
980
995
|
loaded.push({ tx, meta });
|
|
981
996
|
} catch (err) {
|
|
982
997
|
this.#log.warn(`Failed to deserialize tx ${txHashStr}, deleting`, { err });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Attestation Validation
|
|
2
|
+
|
|
3
|
+
This module validates `CheckpointAttestation` gossipsub messages. Attestations are signatures from committee members endorsing a checkpoint proposal.
|
|
4
|
+
|
|
5
|
+
**Topic**: `checkpoint_attestation` | **Snappy size limit**: 5 KB
|
|
6
|
+
|
|
7
|
+
## Stage 1: AttestationValidator (Gossipsub Validation)
|
|
8
|
+
|
|
9
|
+
| # | Rule | Consequence | Severity | File |
|
|
10
|
+
|---|------|-------------|----------|------|
|
|
11
|
+
| 1 | **Slot timeliness**: `currentSlot` or `nextSlot`. Previous slot within 500ms: IGNORE. Older: REJECT. | REJECT or IGNORE | HighToleranceError | `attestation_validator.ts` |
|
|
12
|
+
| 2 | **Attester signature**: `getSender()` must recover valid address | REJECT | LowToleranceError | same |
|
|
13
|
+
| 3 | **Attester in committee**: recovered address in committee for slot | REJECT | HighToleranceError | same |
|
|
14
|
+
| 4 | **Proposer exists**: `getProposerAttesterAddressInSlot` must return defined | REJECT | HighToleranceError | same |
|
|
15
|
+
| 5 | **Proposer signature**: `getProposer()` must recover valid address | REJECT | LowToleranceError | same |
|
|
16
|
+
| 6 | **Proposer matches expected**: recovered proposer = expected for slot | REJECT | HighToleranceError | same |
|
|
17
|
+
| 7 | **NoCommitteeError**: committee unavailable | REJECT | LowToleranceError | same |
|
|
18
|
+
|
|
19
|
+
**Fisherman mode extension** (`FishermanAttestationValidator`): if a checkpoint proposal for the same archive exists in pool, the attestation's `ConsensusPayload` must `.equals()` the stored proposal's payload. On mismatch: REJECT + LowToleranceError.
|
|
20
|
+
|
|
21
|
+
## Stage 2: Pool Admission
|
|
22
|
+
|
|
23
|
+
| # | Rule | Consequence |
|
|
24
|
+
|---|------|-------------|
|
|
25
|
+
| 8 | Sender recoverable (pool-side) | Silent drop |
|
|
26
|
+
| 9 | Not a duplicate (same slot + proposalId + signer) | IGNORE |
|
|
27
|
+
| 10 | Per-signer cap: `MAX_ATTESTATIONS_PER_SLOT_AND_SIGNER` = 2 | IGNORE |
|
|
28
|
+
|
|
29
|
+
Own attestations added via `addOwnCheckpointAttestations` bypass the per-signer cap.
|
|
30
|
+
|
|
31
|
+
## Stage 3: Equivocation Detection
|
|
32
|
+
|
|
33
|
+
When a signer's attestation count for a slot reaches exactly 2 (different proposals): `duplicateAttestationCallback` fires -> `WANT_TO_SLASH_EVENT` with `OffenseType.DUPLICATE_ATTESTATION`. Attestation still ACCEPTED and rebroadcast. Callback fires once (not again at count 3+).
|
|
34
|
+
|
|
35
|
+
## Validation at L1 Checkpoint Submission (Archiver)
|
|
36
|
+
|
|
37
|
+
| Rule | Consequence | File |
|
|
38
|
+
|------|-------------|------|
|
|
39
|
+
| Each attestation must have recoverable signature (or address-only is allowed but does not count toward quorum) | Checkpoint rejected as invalid | `archiver/src/modules/validation.ts` |
|
|
40
|
+
| Attestation at index `i` must correspond to committee member at index `i` | Checkpoint rejected as invalid | same |
|
|
41
|
+
| Valid attestation count >= floor(committee * 2/3) + 1 | Checkpoint rejected as invalid | same |
|
|
42
|
+
| No committee / escape hatch open | Accepted unconditionally | same |
|
|
43
|
+
|
|
44
|
+
Note: `skipValidateCheckpointAttestations` config flag bypasses all archiver attestation validation.
|
|
45
|
+
|
|
46
|
+
## Gossipsub Topic Scoring
|
|
47
|
+
|
|
48
|
+
P3 enabled with expected messages per slot = `targetCommitteeSize`. Conservative threshold (30% of convergence value). Max P3 penalty = -34 per topic.
|
|
49
|
+
|
|
@@ -23,13 +23,14 @@ export class CheckpointAttestationValidator implements P2PValidator<CheckpointAt
|
|
|
23
23
|
const slotNumber = message.payload.header.slotNumber;
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
// Use target slots since proposals target pipeline slots (slot + 1 when pipelining)
|
|
27
|
+
const { targetSlot, nextSlot } = this.epochCache.getTargetAndNextSlot();
|
|
27
28
|
|
|
28
|
-
if (slotNumber !==
|
|
29
|
+
if (slotNumber !== targetSlot && slotNumber !== nextSlot) {
|
|
29
30
|
// Check if message is for previous slot and within clock tolerance
|
|
30
|
-
if (!isWithinClockTolerance(slotNumber,
|
|
31
|
+
if (!isWithinClockTolerance(slotNumber, targetSlot, this.epochCache)) {
|
|
31
32
|
this.logger.warn(
|
|
32
|
-
`Checkpoint attestation slot ${slotNumber} is not current (${
|
|
33
|
+
`Checkpoint attestation slot ${slotNumber} is not current (${targetSlot}) or next (${nextSlot}) slot`,
|
|
33
34
|
);
|
|
34
35
|
return { result: 'reject', severity: PeerErrorSeverity.HighToleranceError };
|
|
35
36
|
}
|
|
@@ -36,10 +36,11 @@ export function isWithinClockTolerance(
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// Check how far we are into the current slot (in milliseconds)
|
|
39
|
-
const { ts: slotStartTs, nowMs
|
|
39
|
+
const { ts: slotStartTs, nowMs } = epochCache.getEpochAndSlotNow();
|
|
40
|
+
const targetSlot = epochCache.getTargetSlot();
|
|
40
41
|
|
|
41
|
-
// Sanity check: ensure the epoch cache's
|
|
42
|
-
if (
|
|
42
|
+
// Sanity check: ensure the epoch cache's target slot matches the expected current slot
|
|
43
|
+
if (targetSlot !== currentSlot) {
|
|
43
44
|
return false;
|
|
44
45
|
}
|
|
45
46
|
|