@aztec/p2p 1.0.0 → 1.1.2
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/bootstrap/bootstrap.js +1 -1
- package/dest/client/factory.js +1 -1
- package/dest/client/p2p_client.js +1 -1
- package/dest/config.d.ts +2 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +11 -4
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +17 -10
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +2 -2
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/memory_tx_pool.js +2 -2
- package/dest/mem_pools/tx_pool/tx_pool.d.ts +3 -2
- package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +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 +10 -3
- package/dest/msg_validators/tx_validator/factory.d.ts +2 -1
- package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/factory.js +3 -2
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts +2 -0
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.js +13 -7
- package/dest/msg_validators/tx_validator/phases_validator.d.ts +3 -2
- package/dest/msg_validators/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/phases_validator.js +4 -4
- package/dest/services/libp2p/instrumentation.d.ts +8 -1
- package/dest/services/libp2p/instrumentation.d.ts.map +1 -1
- package/dest/services/libp2p/instrumentation.js +130 -2
- package/dest/services/libp2p/libp2p_service.d.ts +8 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +34 -14
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/make-test-p2p-clients.js +2 -1
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +3 -2
- package/dest/testbench/p2p_client_testbench_worker.js +6 -0
- package/dest/testbench/worker_client_manager.d.ts.map +1 -1
- package/dest/testbench/worker_client_manager.js +2 -1
- package/dest/util.d.ts +3 -2
- package/dest/util.d.ts.map +1 -1
- package/dest/util.js +6 -5
- package/package.json +13 -13
- package/src/bootstrap/bootstrap.ts +1 -1
- package/src/client/factory.ts +1 -1
- package/src/client/p2p_client.ts +1 -1
- package/src/config.ts +2 -1
- package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +22 -12
- package/src/mem_pools/tx_pool/memory_tx_pool.ts +3 -3
- package/src/mem_pools/tx_pool/tx_pool.ts +3 -2
- package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +8 -4
- package/src/msg_validators/tx_validator/factory.ts +4 -1
- package/src/msg_validators/tx_validator/metadata_validator.ts +20 -9
- package/src/msg_validators/tx_validator/phases_validator.ts +3 -2
- package/src/services/libp2p/instrumentation.ts +122 -3
- package/src/services/libp2p/libp2p_service.ts +41 -15
- package/src/test-helpers/make-test-p2p-clients.ts +2 -1
- package/src/test-helpers/reqresp-nodes.ts +3 -2
- package/src/testbench/p2p_client_testbench_worker.ts +1 -0
- package/src/testbench/worker_client_manager.ts +2 -1
- package/src/util.ts +8 -7
|
@@ -8,7 +8,7 @@ import type { MerkleTreeReadOperations, WorldStateSynchronizer } from '@aztec/st
|
|
|
8
8
|
import { ClientIvcProof } from '@aztec/stdlib/proofs';
|
|
9
9
|
import type { TxAddedToPoolStats } from '@aztec/stdlib/stats';
|
|
10
10
|
import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
|
|
11
|
-
import { Tx, TxHash } from '@aztec/stdlib/tx';
|
|
11
|
+
import { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
|
|
12
12
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
13
13
|
|
|
14
14
|
import assert from 'assert';
|
|
@@ -129,8 +129,14 @@ export class AztecKVTxPool implements TxPool {
|
|
|
129
129
|
}
|
|
130
130
|
return true;
|
|
131
131
|
}
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Marks transactions as mined in a block and updates the pool state accordingly.
|
|
134
|
+
* Removes the transactions from the pending set and adds them to the mined set.
|
|
135
|
+
* Also evicts any transactions that become invalid after the block is mined.
|
|
136
|
+
* @param txHashes - Array of transaction hashes that were mined
|
|
137
|
+
* @param blockHeader - The header of the block the transactions were mined in
|
|
138
|
+
*/
|
|
139
|
+
public async markAsMined(txHashes: TxHash[], blockHeader: BlockHeader): Promise<void> {
|
|
134
140
|
if (txHashes.length === 0) {
|
|
135
141
|
return Promise.resolve();
|
|
136
142
|
}
|
|
@@ -142,7 +148,7 @@ export class AztecKVTxPool implements TxPool {
|
|
|
142
148
|
let pendingTxSize = (await this.#pendingTxSize.getAsync()) ?? 0;
|
|
143
149
|
for (const hash of txHashes) {
|
|
144
150
|
const key = hash.toString();
|
|
145
|
-
await this.#minedTxHashToBlock.set(key, blockNumber);
|
|
151
|
+
await this.#minedTxHashToBlock.set(key, blockHeader.globalVariables.blockNumber);
|
|
146
152
|
|
|
147
153
|
const tx = await this.getPendingTxByHash(hash);
|
|
148
154
|
if (tx) {
|
|
@@ -155,7 +161,7 @@ export class AztecKVTxPool implements TxPool {
|
|
|
155
161
|
}
|
|
156
162
|
await this.#pendingTxSize.set(pendingTxSize);
|
|
157
163
|
|
|
158
|
-
await this.evictInvalidTxsAfterMining(txHashes,
|
|
164
|
+
await this.evictInvalidTxsAfterMining(txHashes, blockHeader, minedNullifiers, minedFeePayers);
|
|
159
165
|
});
|
|
160
166
|
// We update this after the transaction above. This ensures that the non-evictable transactions are not evicted
|
|
161
167
|
// until any that have been mined are marked as such.
|
|
@@ -535,15 +541,15 @@ export class AztecKVTxPool implements TxPool {
|
|
|
535
541
|
* Eviction criteria includes:
|
|
536
542
|
* - txs with nullifiers that are already included in the mined block
|
|
537
543
|
* - txs with an insufficient fee payer balance
|
|
538
|
-
* - txs with
|
|
544
|
+
* - txs with an expiration timestamp lower than that of the mined block
|
|
539
545
|
*
|
|
540
546
|
* @param minedTxHashes - The tx hashes of the txs mined in the block.
|
|
541
|
-
* @param
|
|
547
|
+
* @param blockHeader - The header of the mined block.
|
|
542
548
|
* @returns The total number of txs evicted from the pool.
|
|
543
549
|
*/
|
|
544
550
|
private async evictInvalidTxsAfterMining(
|
|
545
551
|
minedTxHashes: TxHash[],
|
|
546
|
-
|
|
552
|
+
blockHeader: BlockHeader,
|
|
547
553
|
minedNullifiers: Set<string>,
|
|
548
554
|
minedFeePayers: Set<string>,
|
|
549
555
|
): Promise<number> {
|
|
@@ -551,6 +557,8 @@ export class AztecKVTxPool implements TxPool {
|
|
|
551
557
|
return 0;
|
|
552
558
|
}
|
|
553
559
|
|
|
560
|
+
const { blockNumber, timestamp } = blockHeader.globalVariables;
|
|
561
|
+
|
|
554
562
|
// Wait for world state to be synced to at least the mined block number
|
|
555
563
|
await this.#worldStateSynchronizer.syncImmediate(blockNumber);
|
|
556
564
|
|
|
@@ -582,10 +590,12 @@ export class AztecKVTxPool implements TxPool {
|
|
|
582
590
|
continue;
|
|
583
591
|
}
|
|
584
592
|
|
|
585
|
-
// Evict pending txs with
|
|
586
|
-
const
|
|
587
|
-
if (
|
|
588
|
-
this.#log.verbose(
|
|
593
|
+
// Evict pending txs with an expiration timestamp less than or equal to the mined block timestamp
|
|
594
|
+
const includeByTimestamp = tx.data.rollupValidationRequests.includeByTimestamp;
|
|
595
|
+
if (includeByTimestamp.isSome && includeByTimestamp.value <= timestamp) {
|
|
596
|
+
this.#log.verbose(
|
|
597
|
+
`Evicting tx ${txHash} from pool due to the tx being expired (includeByTimestamp: ${includeByTimestamp.value}, mined block timestamp: ${timestamp})`,
|
|
598
|
+
);
|
|
589
599
|
txsToEvict.push(TxHash.fromString(txHash));
|
|
590
600
|
continue;
|
|
591
601
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
2
|
import type { TxAddedToPoolStats } from '@aztec/stdlib/stats';
|
|
3
|
-
import { Tx, TxHash } from '@aztec/stdlib/tx';
|
|
3
|
+
import { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
|
|
4
4
|
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
5
5
|
|
|
6
6
|
import { PoolInstrumentation, PoolName, type PoolStatsCallback } from '../instrumentation.js';
|
|
@@ -47,10 +47,10 @@ export class InMemoryTxPool implements TxPool {
|
|
|
47
47
|
return Promise.resolve(this.txs.size === 0);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
public markAsMined(txHashes: TxHash[],
|
|
50
|
+
public markAsMined(txHashes: TxHash[], blockHeader: BlockHeader): Promise<void> {
|
|
51
51
|
const keys = txHashes.map(x => x.toBigInt());
|
|
52
52
|
for (const key of keys) {
|
|
53
|
-
this.minedTxs.set(key, blockNumber);
|
|
53
|
+
this.minedTxs.set(key, blockHeader.globalVariables.blockNumber);
|
|
54
54
|
this.pendingTxs.delete(key);
|
|
55
55
|
}
|
|
56
56
|
return Promise.resolve();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Tx, TxHash } from '@aztec/stdlib/tx';
|
|
1
|
+
import type { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx';
|
|
2
2
|
|
|
3
3
|
export type TxPoolOptions = {
|
|
4
4
|
maxTxPoolSize?: number;
|
|
@@ -48,8 +48,9 @@ export interface TxPool {
|
|
|
48
48
|
/**
|
|
49
49
|
* Marks the set of txs as mined, as opposed to pending.
|
|
50
50
|
* @param txHashes - Hashes of the txs to flag as mined.
|
|
51
|
+
* @param blockHeader - The header of the mined block.
|
|
51
52
|
*/
|
|
52
|
-
markAsMined(txHashes: TxHash[],
|
|
53
|
+
markAsMined(txHashes: TxHash[], blockHeader: BlockHeader): Promise<void>;
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Moves mined txs back to the pending set in the case of a reorg.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { unfreeze } from '@aztec/foundation/types';
|
|
2
2
|
import { GasFees } from '@aztec/stdlib/gas';
|
|
3
3
|
import { mockTx } from '@aztec/stdlib/testing';
|
|
4
|
-
import type
|
|
4
|
+
import { BlockHeader, GlobalVariables, type Tx } from '@aztec/stdlib/tx';
|
|
5
5
|
|
|
6
6
|
import type { TxPool } from './tx_pool.js';
|
|
7
7
|
|
|
@@ -12,6 +12,10 @@ import type { TxPool } from './tx_pool.js';
|
|
|
12
12
|
export function describeTxPool(getTxPool: () => TxPool) {
|
|
13
13
|
let pool: TxPool;
|
|
14
14
|
|
|
15
|
+
const minedBlockHeader = BlockHeader.empty({
|
|
16
|
+
globalVariables: GlobalVariables.empty({ blockNumber: 1, timestamp: 0n }),
|
|
17
|
+
});
|
|
18
|
+
|
|
15
19
|
beforeEach(() => {
|
|
16
20
|
pool = getTxPool();
|
|
17
21
|
});
|
|
@@ -43,7 +47,7 @@ export function describeTxPool(getTxPool: () => TxPool) {
|
|
|
43
47
|
const tx2 = await mockTx(2);
|
|
44
48
|
|
|
45
49
|
await pool.addTxs([tx1, tx2]);
|
|
46
|
-
await pool.markAsMined([await tx1.getTxHash()],
|
|
50
|
+
await pool.markAsMined([await tx1.getTxHash()], minedBlockHeader);
|
|
47
51
|
|
|
48
52
|
await expect(pool.getTxByHash(await tx1.getTxHash())).resolves.toEqual(tx1);
|
|
49
53
|
await expect(pool.getTxStatus(await tx1.getTxHash())).resolves.toEqual('mined');
|
|
@@ -57,7 +61,7 @@ export function describeTxPool(getTxPool: () => TxPool) {
|
|
|
57
61
|
const tx2 = await mockTx(2);
|
|
58
62
|
|
|
59
63
|
await pool.addTxs([tx1, tx2]);
|
|
60
|
-
await pool.markAsMined([await tx1.getTxHash()],
|
|
64
|
+
await pool.markAsMined([await tx1.getTxHash()], minedBlockHeader);
|
|
61
65
|
|
|
62
66
|
await pool.markMinedAsPending([await tx1.getTxHash()]);
|
|
63
67
|
await expect(pool.getMinedTxHashes()).resolves.toEqual([]);
|
|
@@ -74,7 +78,7 @@ export function describeTxPool(getTxPool: () => TxPool) {
|
|
|
74
78
|
const someTxHashThatThisPeerDidNotSee = await tx2.getTxHash();
|
|
75
79
|
await pool.addTxs([tx1]);
|
|
76
80
|
// this peer knows that tx2 was mined, but it does not have the tx object
|
|
77
|
-
await pool.markAsMined([await tx1.getTxHash(), someTxHashThatThisPeerDidNotSee],
|
|
81
|
+
await pool.markAsMined([await tx1.getTxHash(), someTxHashThatThisPeerDidNotSee], minedBlockHeader);
|
|
78
82
|
expect(await pool.getMinedTxHashes()).toEqual(
|
|
79
83
|
expect.arrayContaining([
|
|
80
84
|
[await tx1.getTxHash(), 1],
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
|
|
12
12
|
import { DatabasePublicStateSource, MerkleTreeId } from '@aztec/stdlib/trees';
|
|
13
13
|
import type { Tx, TxValidationResult } from '@aztec/stdlib/tx';
|
|
14
|
+
import type { UInt64 } from '@aztec/stdlib/types';
|
|
14
15
|
|
|
15
16
|
import { ArchiveCache } from './archive_cache.js';
|
|
16
17
|
import { BlockHeaderTxValidator } from './block_header_validator.js';
|
|
@@ -29,6 +30,7 @@ export interface MessageValidator {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export function createTxMessageValidators(
|
|
33
|
+
timestamp: UInt64,
|
|
32
34
|
blockNumber: number,
|
|
33
35
|
worldStateSynchronizer: WorldStateSynchronizer,
|
|
34
36
|
gasFees: GasFees,
|
|
@@ -51,6 +53,7 @@ export function createTxMessageValidators(
|
|
|
51
53
|
validator: new MetadataTxValidator({
|
|
52
54
|
l1ChainId: new Fr(l1ChainId),
|
|
53
55
|
rollupVersion: new Fr(rollupVersion),
|
|
56
|
+
timestamp,
|
|
54
57
|
blockNumber,
|
|
55
58
|
protocolContractTreeRoot,
|
|
56
59
|
vkTreeRoot: getVKTreeRoot(),
|
|
@@ -76,7 +79,7 @@ export function createTxMessageValidators(
|
|
|
76
79
|
severity: PeerErrorSeverity.HighToleranceError,
|
|
77
80
|
},
|
|
78
81
|
phasesValidator: {
|
|
79
|
-
validator: new PhasesTxValidator(contractDataSource, allowedInSetup,
|
|
82
|
+
validator: new PhasesTxValidator(contractDataSource, allowedInSetup, timestamp),
|
|
80
83
|
severity: PeerErrorSeverity.MidToleranceError,
|
|
81
84
|
},
|
|
82
85
|
blockHeaderValidator: {
|
|
@@ -6,11 +6,12 @@ import {
|
|
|
6
6
|
TX_ERROR_INCORRECT_PROTOCOL_CONTRACT_TREE_ROOT,
|
|
7
7
|
TX_ERROR_INCORRECT_ROLLUP_VERSION,
|
|
8
8
|
TX_ERROR_INCORRECT_VK_TREE_ROOT,
|
|
9
|
-
|
|
9
|
+
TX_ERROR_INVALID_INCLUDE_BY_TIMESTAMP,
|
|
10
10
|
Tx,
|
|
11
11
|
type TxValidationResult,
|
|
12
12
|
type TxValidator,
|
|
13
13
|
} from '@aztec/stdlib/tx';
|
|
14
|
+
import type { UInt64 } from '@aztec/stdlib/types';
|
|
14
15
|
|
|
15
16
|
export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
16
17
|
#log = createLogger('p2p:tx_validator:tx_metadata');
|
|
@@ -19,6 +20,10 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
|
19
20
|
private values: {
|
|
20
21
|
l1ChainId: Fr;
|
|
21
22
|
rollupVersion: Fr;
|
|
23
|
+
// Timestamp at which we will validate that the tx is not expired. This is typically the timestamp of the block
|
|
24
|
+
// being built.
|
|
25
|
+
timestamp: UInt64;
|
|
26
|
+
// Block number in which the tx is considered to be included.
|
|
22
27
|
blockNumber: number;
|
|
23
28
|
vkTreeRoot: Fr;
|
|
24
29
|
protocolContractTreeRoot: Fr;
|
|
@@ -33,8 +38,8 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
|
33
38
|
if (!(await this.#hasCorrectRollupVersion(tx))) {
|
|
34
39
|
errors.push(TX_ERROR_INCORRECT_ROLLUP_VERSION);
|
|
35
40
|
}
|
|
36
|
-
if (!(await this.#
|
|
37
|
-
errors.push(
|
|
41
|
+
if (!(await this.#isValidForTimestamp(tx))) {
|
|
42
|
+
errors.push(TX_ERROR_INVALID_INCLUDE_BY_TIMESTAMP);
|
|
38
43
|
}
|
|
39
44
|
if (!(await this.#hasCorrectVkTreeRoot(tx))) {
|
|
40
45
|
errors.push(TX_ERROR_INCORRECT_VK_TREE_ROOT);
|
|
@@ -84,14 +89,20 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
|
84
89
|
}
|
|
85
90
|
}
|
|
86
91
|
|
|
87
|
-
async #
|
|
88
|
-
const
|
|
92
|
+
async #isValidForTimestamp(tx: T): Promise<boolean> {
|
|
93
|
+
const includeByTimestamp = tx.data.rollupValidationRequests.includeByTimestamp;
|
|
94
|
+
// If building block 1, we skip the expiration check. For details on why see the `validate_include_by_timestamp`
|
|
95
|
+
// function in `noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/validation_requests.nr`.
|
|
96
|
+
const buildingBlock1 = this.values.blockNumber === 1;
|
|
89
97
|
|
|
90
|
-
if (
|
|
98
|
+
if (!buildingBlock1 && includeByTimestamp.isSome && includeByTimestamp.value < this.values.timestamp) {
|
|
99
|
+
if (tx.data.constants.historicalHeader.globalVariables.blockNumber === 0) {
|
|
100
|
+
this.#log.warn(
|
|
101
|
+
`A tx built against a genesis block failed to be included in block 1 which is the only block in which txs built against a genesis block are allowed to be included.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
91
104
|
this.#log.verbose(
|
|
92
|
-
`Rejecting tx ${await Tx.getHash(tx)} for low
|
|
93
|
-
maxBlockNumber.value
|
|
94
|
-
}, current block number: ${this.values.blockNumber}.`,
|
|
105
|
+
`Rejecting tx ${await Tx.getHash(tx)} for low expiration timestamp. Tx expiration timestamp: ${includeByTimestamp.value}, timestamp: ${this.values.timestamp}.`,
|
|
95
106
|
);
|
|
96
107
|
return false;
|
|
97
108
|
} else {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type TxValidationResult,
|
|
12
12
|
type TxValidator,
|
|
13
13
|
} from '@aztec/stdlib/tx';
|
|
14
|
+
import type { UInt64 } from '@aztec/stdlib/types';
|
|
14
15
|
|
|
15
16
|
export class PhasesTxValidator implements TxValidator<Tx> {
|
|
16
17
|
#log = createLogger('sequencer:tx_validator:tx_phases');
|
|
@@ -19,7 +20,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
|
|
|
19
20
|
constructor(
|
|
20
21
|
contracts: ContractDataSource,
|
|
21
22
|
private setupAllowList: AllowedElement[],
|
|
22
|
-
private
|
|
23
|
+
private timestamp: UInt64,
|
|
23
24
|
) {
|
|
24
25
|
this.contractsDB = new PublicContractsDB(contracts);
|
|
25
26
|
}
|
|
@@ -86,7 +87,7 @@ export class PhasesTxValidator implements TxValidator<Tx> {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
const contractClass = await this.contractsDB.getContractInstance(contractAddress, this.
|
|
90
|
+
const contractClass = await this.contractsDB.getContractInstance(contractAddress, this.timestamp);
|
|
90
91
|
|
|
91
92
|
if (!contractClass) {
|
|
92
93
|
throw new Error(`Contract not found: ${contractAddress}`);
|
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import type { Timer } from '@aztec/foundation/timer';
|
|
2
|
-
import
|
|
2
|
+
import { TopicType } from '@aztec/stdlib/p2p';
|
|
3
3
|
import {
|
|
4
4
|
Attributes,
|
|
5
|
+
type BatchObservableResult,
|
|
5
6
|
type Histogram,
|
|
6
7
|
Metrics,
|
|
8
|
+
type ObservableGauge,
|
|
7
9
|
type TelemetryClient,
|
|
8
10
|
type UpDownCounter,
|
|
9
11
|
ValueType,
|
|
10
12
|
} from '@aztec/telemetry-client';
|
|
11
13
|
|
|
14
|
+
import { type RecordableHistogram, createHistogram } from 'node:perf_hooks';
|
|
15
|
+
|
|
12
16
|
export class P2PInstrumentation {
|
|
13
17
|
private messageValidationDuration: Histogram;
|
|
14
18
|
private messagePrevalidationCount: UpDownCounter;
|
|
19
|
+
private messageLatency: Histogram;
|
|
20
|
+
|
|
21
|
+
private aggLatencyHisto = new Map<TopicType, RecordableHistogram>();
|
|
22
|
+
private aggValidationHisto = new Map<TopicType, RecordableHistogram>();
|
|
23
|
+
|
|
24
|
+
private aggLatencyMetrics: Record<'min' | 'max' | 'p50' | 'p90' | 'avg', ObservableGauge>;
|
|
25
|
+
private aggValidationMetrics: Record<'min' | 'max' | 'p50' | 'p90' | 'avg', ObservableGauge>;
|
|
15
26
|
|
|
16
27
|
constructor(client: TelemetryClient, name: string) {
|
|
17
28
|
const meter = client.getMeter(name);
|
|
@@ -26,14 +37,122 @@ export class P2PInstrumentation {
|
|
|
26
37
|
description: 'How many message pass/fail prevalidation',
|
|
27
38
|
valueType: ValueType.INT,
|
|
28
39
|
});
|
|
40
|
+
|
|
41
|
+
this.messageLatency = meter.createHistogram(Metrics.P2P_GOSSIP_MESSAGE_LATENCY, {
|
|
42
|
+
unit: 'ms',
|
|
43
|
+
description: 'P2P message latency',
|
|
44
|
+
valueType: ValueType.INT,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.aggLatencyMetrics = {
|
|
48
|
+
avg: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_LATENCY_AVG, {
|
|
49
|
+
valueType: ValueType.DOUBLE,
|
|
50
|
+
description: 'AVG msg latency',
|
|
51
|
+
unit: 'ms',
|
|
52
|
+
}),
|
|
53
|
+
max: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_LATENCY_MAX, {
|
|
54
|
+
valueType: ValueType.DOUBLE,
|
|
55
|
+
description: 'MAX msg latency',
|
|
56
|
+
unit: 'ms',
|
|
57
|
+
}),
|
|
58
|
+
min: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_LATENCY_MIN, {
|
|
59
|
+
valueType: ValueType.DOUBLE,
|
|
60
|
+
description: 'MIN msg latency',
|
|
61
|
+
unit: 'ms',
|
|
62
|
+
}),
|
|
63
|
+
p50: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_LATENCY_P50, {
|
|
64
|
+
valueType: ValueType.DOUBLE,
|
|
65
|
+
description: 'P50 msg latency',
|
|
66
|
+
unit: 'ms',
|
|
67
|
+
}),
|
|
68
|
+
p90: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_LATENCY_P90, {
|
|
69
|
+
valueType: ValueType.DOUBLE,
|
|
70
|
+
description: 'P90 msg latency',
|
|
71
|
+
unit: 'ms',
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.aggValidationMetrics = {
|
|
76
|
+
avg: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_VALIDATION_DURATION_AVG, {
|
|
77
|
+
valueType: ValueType.DOUBLE,
|
|
78
|
+
description: 'AVG msg validation',
|
|
79
|
+
unit: 'ms',
|
|
80
|
+
}),
|
|
81
|
+
max: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_VALIDATION_DURATION_MAX, {
|
|
82
|
+
valueType: ValueType.DOUBLE,
|
|
83
|
+
description: 'MAX msg validation',
|
|
84
|
+
unit: 'ms',
|
|
85
|
+
}),
|
|
86
|
+
min: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_VALIDATION_DURATION_MIN, {
|
|
87
|
+
valueType: ValueType.DOUBLE,
|
|
88
|
+
description: 'MIN msg validation',
|
|
89
|
+
unit: 'ms',
|
|
90
|
+
}),
|
|
91
|
+
p50: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_VALIDATION_DURATION_P50, {
|
|
92
|
+
valueType: ValueType.DOUBLE,
|
|
93
|
+
description: 'P50 msg validation',
|
|
94
|
+
unit: 'ms',
|
|
95
|
+
}),
|
|
96
|
+
p90: meter.createObservableGauge(Metrics.P2P_GOSSIP_AGG_MESSAGE_VALIDATION_DURATION_P90, {
|
|
97
|
+
valueType: ValueType.DOUBLE,
|
|
98
|
+
description: 'P90 msg validation',
|
|
99
|
+
unit: 'ms',
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
meter.addBatchObservableCallback(this.aggregate, [
|
|
104
|
+
...Object.values(this.aggValidationMetrics),
|
|
105
|
+
...Object.values(this.aggLatencyMetrics),
|
|
106
|
+
]);
|
|
29
107
|
}
|
|
30
108
|
|
|
31
109
|
public recordMessageValidation(topicName: TopicType, timerOrMs: Timer | number) {
|
|
32
|
-
const ms = typeof timerOrMs === 'number' ? timerOrMs : timerOrMs.ms();
|
|
33
|
-
this.messageValidationDuration.record(
|
|
110
|
+
const ms = Math.ceil(typeof timerOrMs === 'number' ? timerOrMs : timerOrMs.ms());
|
|
111
|
+
this.messageValidationDuration.record(ms, { [Attributes.TOPIC_NAME]: topicName });
|
|
112
|
+
|
|
113
|
+
let validationHistogram = this.aggValidationHisto.get(topicName);
|
|
114
|
+
if (!validationHistogram) {
|
|
115
|
+
validationHistogram = createHistogram({ min: 1, max: 5 * 60 * 1000 }); // 5 mins
|
|
116
|
+
this.aggValidationHisto.set(topicName, validationHistogram);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
validationHistogram.record(Math.max(ms, 1));
|
|
34
120
|
}
|
|
35
121
|
|
|
36
122
|
public incMessagePrevalidationStatus(passed: boolean, topicName: TopicType | undefined) {
|
|
37
123
|
this.messagePrevalidationCount.add(1, { [Attributes.TOPIC_NAME]: topicName, [Attributes.OK]: passed });
|
|
38
124
|
}
|
|
125
|
+
|
|
126
|
+
public recordMessageLatency(topicName: TopicType, timerOrMs: Timer | number) {
|
|
127
|
+
const ms = Math.ceil(typeof timerOrMs === 'number' ? timerOrMs : timerOrMs.ms());
|
|
128
|
+
this.messageLatency.record(ms, { [Attributes.TOPIC_NAME]: topicName });
|
|
129
|
+
|
|
130
|
+
let latencyHistogram = this.aggLatencyHisto.get(topicName);
|
|
131
|
+
if (!latencyHistogram) {
|
|
132
|
+
latencyHistogram = createHistogram({ min: 1, max: 24 * 60 * 60 * 1000 }); // 24hrs
|
|
133
|
+
this.aggLatencyHisto.set(topicName, latencyHistogram);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
latencyHistogram.record(Math.max(ms, 1));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private aggregate = (res: BatchObservableResult) => {
|
|
140
|
+
for (const [metrics, histograms] of [
|
|
141
|
+
[this.aggLatencyMetrics, this.aggLatencyHisto],
|
|
142
|
+
[this.aggValidationMetrics, this.aggValidationHisto],
|
|
143
|
+
] as const) {
|
|
144
|
+
for (const topicName of Object.values(TopicType)) {
|
|
145
|
+
const histogram = histograms.get(topicName);
|
|
146
|
+
if (!histogram || histogram.count === 0) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
res.observe(metrics.avg, histogram.mean, { [Attributes.TOPIC_NAME]: topicName });
|
|
151
|
+
res.observe(metrics.max, histogram.max, { [Attributes.TOPIC_NAME]: topicName });
|
|
152
|
+
res.observe(metrics.min, histogram.min, { [Attributes.TOPIC_NAME]: topicName });
|
|
153
|
+
res.observe(metrics.p50, histogram.percentile(50), { [Attributes.TOPIC_NAME]: topicName });
|
|
154
|
+
res.observe(metrics.p90, histogram.percentile(90), { [Attributes.TOPIC_NAME]: topicName });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
39
158
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from '@aztec/stdlib/p2p';
|
|
24
24
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
25
25
|
import { Tx, type TxHash, type TxValidationResult } from '@aztec/stdlib/tx';
|
|
26
|
+
import type { UInt64 } from '@aztec/stdlib/types';
|
|
26
27
|
import { compressComponentVersions } from '@aztec/stdlib/versioning';
|
|
27
28
|
import { Attributes, OtelMetricsAdapter, type TelemetryClient, WithTracer, trackSpan } from '@aztec/telemetry-client';
|
|
28
29
|
|
|
@@ -125,7 +126,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
125
126
|
private peerManager: PeerManagerInterface,
|
|
126
127
|
protected mempools: MemPools<T>,
|
|
127
128
|
private archiver: L2BlockSource & ContractDataSource,
|
|
128
|
-
epochCache: EpochCacheInterface,
|
|
129
|
+
private epochCache: EpochCacheInterface,
|
|
129
130
|
private proofVerifier: ClientProtocolCircuitVerifier,
|
|
130
131
|
private worldStateSynchronizer: WorldStateSynchronizer,
|
|
131
132
|
telemetry: TelemetryClient,
|
|
@@ -536,7 +537,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
536
537
|
return result.recipients.length;
|
|
537
538
|
}
|
|
538
539
|
|
|
539
|
-
protected preValidateReceivedMessage(
|
|
540
|
+
protected preValidateReceivedMessage(
|
|
541
|
+
msg: Message,
|
|
542
|
+
msgId: string,
|
|
543
|
+
source: PeerId,
|
|
544
|
+
): { result: boolean; topicType?: TopicType } {
|
|
540
545
|
let topicType: TopicType | undefined;
|
|
541
546
|
|
|
542
547
|
switch (msg.topic) {
|
|
@@ -559,12 +564,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
559
564
|
if (!validator || !validator.addMessage(msgId)) {
|
|
560
565
|
this.instrumentation.incMessagePrevalidationStatus(false, topicType);
|
|
561
566
|
this.node.services.pubsub.reportMessageValidationResult(msgId, source.toString(), TopicValidatorResult.Ignore);
|
|
562
|
-
return false;
|
|
567
|
+
return { result: false, topicType };
|
|
563
568
|
}
|
|
564
569
|
|
|
565
570
|
this.instrumentation.incMessagePrevalidationStatus(true, topicType);
|
|
566
571
|
|
|
567
|
-
return true;
|
|
572
|
+
return { result: true, topicType };
|
|
568
573
|
}
|
|
569
574
|
|
|
570
575
|
/**
|
|
@@ -582,8 +587,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
582
587
|
messageLatency,
|
|
583
588
|
});
|
|
584
589
|
|
|
585
|
-
|
|
590
|
+
const preValidationResult = this.preValidateReceivedMessage(msg, msgId, source);
|
|
591
|
+
|
|
592
|
+
if (!preValidationResult.result) {
|
|
586
593
|
return;
|
|
594
|
+
} else if (preValidationResult.topicType !== undefined) {
|
|
595
|
+
// guard against clock skew & DST
|
|
596
|
+
if (messageLatency > 0) {
|
|
597
|
+
this.instrumentation.recordMessageLatency(preValidationResult.topicType, messageLatency);
|
|
598
|
+
}
|
|
587
599
|
}
|
|
588
600
|
|
|
589
601
|
if (msg.topic === this.topicStrings[TopicType.tx]) {
|
|
@@ -827,8 +839,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
827
839
|
[Attributes.TX_HASH]: (await tx.getTxHash()).toString(),
|
|
828
840
|
}))
|
|
829
841
|
private async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
|
|
830
|
-
const
|
|
831
|
-
|
|
842
|
+
const currentBlockNumber = await this.archiver.getBlockNumber();
|
|
843
|
+
|
|
844
|
+
// We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
|
|
845
|
+
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
846
|
+
const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
|
|
832
847
|
|
|
833
848
|
for (const validator of messageValidators) {
|
|
834
849
|
const outcome = await this.runValidations(tx, validator);
|
|
@@ -841,7 +856,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
841
856
|
|
|
842
857
|
// Double spend validator has a special case handler
|
|
843
858
|
if (name === 'doubleSpendValidator') {
|
|
844
|
-
|
|
859
|
+
const txBlockNumber = currentBlockNumber + 1; // tx is expected to be in the next block
|
|
860
|
+
severity = await this.handleDoubleSpendFailure(tx, txBlockNumber);
|
|
845
861
|
}
|
|
846
862
|
|
|
847
863
|
this.peerManager.penalizePeer(peerId, severity);
|
|
@@ -862,8 +878,11 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
862
878
|
}
|
|
863
879
|
|
|
864
880
|
public async validate(txs: Tx[]): Promise<void> {
|
|
865
|
-
const
|
|
866
|
-
|
|
881
|
+
const currentBlockNumber = await this.archiver.getBlockNumber();
|
|
882
|
+
|
|
883
|
+
// We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
|
|
884
|
+
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
885
|
+
const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
|
|
867
886
|
|
|
868
887
|
await Promise.all(
|
|
869
888
|
txs.map(async tx => {
|
|
@@ -878,20 +897,27 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
878
897
|
}
|
|
879
898
|
|
|
880
899
|
/**
|
|
881
|
-
* Create message validators for the given block number.
|
|
900
|
+
* Create message validators for the given block number and timestamp.
|
|
882
901
|
*
|
|
883
902
|
* Each validator is a pair of a validator and a severity.
|
|
884
903
|
* If a validator fails, the peer is penalized with the severity of the validator.
|
|
885
904
|
*
|
|
886
|
-
* @param
|
|
905
|
+
* @param currentBlockNumber - The current synced block number.
|
|
906
|
+
* @param nextSlotTimestamp - The timestamp of the next slot (used to validate txs are not expired).
|
|
887
907
|
* @returns The message validators.
|
|
888
908
|
*/
|
|
889
|
-
private async createMessageValidators(
|
|
890
|
-
|
|
909
|
+
private async createMessageValidators(
|
|
910
|
+
currentBlockNumber: number,
|
|
911
|
+
nextSlotTimestamp: UInt64,
|
|
912
|
+
): Promise<Record<string, MessageValidator>[]> {
|
|
913
|
+
const gasFees = await this.getGasFees(currentBlockNumber);
|
|
891
914
|
const allowedInSetup = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
892
915
|
|
|
916
|
+
const blockNumberInWhichTheTxIsConsideredToBeIncluded = currentBlockNumber + 1;
|
|
917
|
+
|
|
893
918
|
return createTxMessageValidators(
|
|
894
|
-
|
|
919
|
+
nextSlotTimestamp,
|
|
920
|
+
blockNumberInWhichTheTxIsConsideredToBeIncluded,
|
|
895
921
|
this.worldStateSynchronizer,
|
|
896
922
|
gasFees,
|
|
897
923
|
this.config.l1ChainId,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MockL2BlockSource } from '@aztec/archiver/test';
|
|
2
2
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
3
|
+
import { SecretValue } from '@aztec/foundation/config';
|
|
3
4
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
4
5
|
import { sleep } from '@aztec/foundation/sleep';
|
|
5
6
|
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
@@ -78,7 +79,7 @@ export async function makeTestP2PClient(
|
|
|
78
79
|
const config: P2PConfig & DataStoreConfig = {
|
|
79
80
|
...p2pBaseConfig,
|
|
80
81
|
p2pEnabled: true,
|
|
81
|
-
peerIdPrivateKey,
|
|
82
|
+
peerIdPrivateKey: new SecretValue(peerIdPrivateKey),
|
|
82
83
|
p2pIp: `127.0.0.1`,
|
|
83
84
|
listenAddress: `127.0.0.1`,
|
|
84
85
|
p2pPort: port,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
2
|
import { timesParallel } from '@aztec/foundation/collection';
|
|
3
|
+
import { SecretValue } from '@aztec/foundation/config';
|
|
3
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
5
|
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
5
6
|
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
|
|
@@ -125,7 +126,7 @@ export async function createTestLibP2PService<T extends P2PClientType>(
|
|
|
125
126
|
peerCheckIntervalMS: 1000,
|
|
126
127
|
maxPeerCount: 5,
|
|
127
128
|
p2pEnabled: true,
|
|
128
|
-
peerIdPrivateKey: Buffer.from(peerId.privateKey!).toString('hex'),
|
|
129
|
+
peerIdPrivateKey: new SecretValue(Buffer.from(peerId.privateKey!).toString('hex')),
|
|
129
130
|
bootstrapNodeEnrVersionCheck: false,
|
|
130
131
|
...chainConfig,
|
|
131
132
|
} as P2PConfig & DataStoreConfig;
|
|
@@ -278,7 +279,7 @@ export function createBootstrapNodeConfig(privateKey: string, port: number, chai
|
|
|
278
279
|
l1ChainId: chainConfig.l1ChainId,
|
|
279
280
|
p2pIp: '127.0.0.1',
|
|
280
281
|
p2pPort: port,
|
|
281
|
-
peerIdPrivateKey: privateKey,
|
|
282
|
+
peerIdPrivateKey: new SecretValue(privateKey),
|
|
282
283
|
dataDirectory: undefined,
|
|
283
284
|
dataStoreMapSizeKB: 0,
|
|
284
285
|
bootstrapNodes: [],
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SecretValue } from '@aztec/foundation/config';
|
|
1
2
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
3
|
import type { Logger } from '@aztec/foundation/log';
|
|
3
4
|
import { sleep } from '@aztec/foundation/sleep';
|
|
@@ -55,7 +56,7 @@ class WorkerClientManager {
|
|
|
55
56
|
return {
|
|
56
57
|
...getP2PDefaultConfig(),
|
|
57
58
|
p2pEnabled: true,
|
|
58
|
-
peerIdPrivateKey: this.peerIdPrivateKeys[clientIndex],
|
|
59
|
+
peerIdPrivateKey: new SecretValue(this.peerIdPrivateKeys[clientIndex]),
|
|
59
60
|
listenAddress: '127.0.0.1',
|
|
60
61
|
p2pIp: '127.0.0.1',
|
|
61
62
|
p2pPort: port,
|