@aztec/p2p 0.60.0 → 0.62.0
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/index.js +2 -39
- package/dest/client/p2p_client.d.ts +33 -9
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +121 -30
- package/dest/config.d.ts +5 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +7 -2
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +3 -2
- 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 +30 -5
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +3 -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 +26 -5
- package/dest/mem_pools/tx_pool/tx_pool.d.ts +8 -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 +30 -3
- package/dest/mocks/index.d.ts +18 -2
- package/dest/mocks/index.d.ts.map +1 -1
- package/dest/mocks/index.js +79 -5
- package/dest/service/peer_scoring.js +5 -5
- package/dest/util.d.ts +3 -0
- package/dest/util.d.ts.map +1 -1
- package/dest/util.js +38 -1
- package/package.json +12 -7
- package/src/client/index.ts +1 -48
- package/src/client/p2p_client.ts +156 -40
- package/src/config.ts +11 -1
- package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +35 -6
- package/src/mem_pools/tx_pool/memory_tx_pool.ts +32 -6
- package/src/mem_pools/tx_pool/tx_pool.ts +9 -2
- package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +34 -2
- package/src/mocks/index.ts +123 -5
- package/src/service/peer_scoring.ts +4 -4
- package/src/util.ts +53 -0
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/p2p",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.62.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"exports":
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dest/index.js",
|
|
7
|
+
"./mocks": "./dest/mocks/index.js",
|
|
8
|
+
"./bootstrap": "./dest/bootstrap/bootstrap.js"
|
|
9
|
+
},
|
|
6
10
|
"typedocOptions": {
|
|
7
11
|
"entryPoints": [
|
|
8
12
|
"./src/index.ts"
|
|
@@ -59,11 +63,11 @@
|
|
|
59
63
|
"testTimeout": 15000
|
|
60
64
|
},
|
|
61
65
|
"dependencies": {
|
|
62
|
-
"@aztec/circuit-types": "0.
|
|
63
|
-
"@aztec/circuits.js": "0.
|
|
64
|
-
"@aztec/foundation": "0.
|
|
65
|
-
"@aztec/kv-store": "0.
|
|
66
|
-
"@aztec/telemetry-client": "0.
|
|
66
|
+
"@aztec/circuit-types": "0.62.0",
|
|
67
|
+
"@aztec/circuits.js": "0.62.0",
|
|
68
|
+
"@aztec/foundation": "0.62.0",
|
|
69
|
+
"@aztec/kv-store": "0.62.0",
|
|
70
|
+
"@aztec/telemetry-client": "0.62.0",
|
|
67
71
|
"@chainsafe/discv5": "9.0.0",
|
|
68
72
|
"@chainsafe/enr": "3.0.0",
|
|
69
73
|
"@chainsafe/libp2p-gossipsub": "13.0.0",
|
|
@@ -93,6 +97,7 @@
|
|
|
93
97
|
"@jest/globals": "^29.5.0",
|
|
94
98
|
"@types/jest": "^29.5.0",
|
|
95
99
|
"@types/node": "^18.14.6",
|
|
100
|
+
"get-port": "^7.1.0",
|
|
96
101
|
"it-drain": "^3.0.5",
|
|
97
102
|
"it-length": "^3.0.6",
|
|
98
103
|
"jest": "^29.5.0",
|
package/src/client/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { AztecKVTxPool, type TxPool } from '../mem_pools/tx_pool/index.js';
|
|
|
16
16
|
import { DiscV5Service } from '../service/discV5_service.js';
|
|
17
17
|
import { DummyP2PService } from '../service/dummy_service.js';
|
|
18
18
|
import { LibP2PService, createLibP2PPeerId } from '../service/index.js';
|
|
19
|
-
import {
|
|
19
|
+
import { configureP2PClientAddresses } from '../util.js';
|
|
20
20
|
|
|
21
21
|
export * from './p2p_client.js';
|
|
22
22
|
|
|
@@ -67,50 +67,3 @@ export const createP2PClient = async (
|
|
|
67
67
|
}
|
|
68
68
|
return new P2PClient(store, l2BlockSource, mempools, p2pService, config.keepProvenTxsInPoolFor, telemetry);
|
|
69
69
|
};
|
|
70
|
-
|
|
71
|
-
async function configureP2PClientAddresses(_config: P2PConfig & DataStoreConfig): Promise<P2PConfig & DataStoreConfig> {
|
|
72
|
-
const config = { ..._config };
|
|
73
|
-
const {
|
|
74
|
-
tcpAnnounceAddress: configTcpAnnounceAddress,
|
|
75
|
-
udpAnnounceAddress: configUdpAnnounceAddress,
|
|
76
|
-
queryForIp,
|
|
77
|
-
} = config;
|
|
78
|
-
|
|
79
|
-
config.tcpAnnounceAddress = configTcpAnnounceAddress
|
|
80
|
-
? await resolveAddressIfNecessary(configTcpAnnounceAddress)
|
|
81
|
-
: undefined;
|
|
82
|
-
config.udpAnnounceAddress = configUdpAnnounceAddress
|
|
83
|
-
? await resolveAddressIfNecessary(configUdpAnnounceAddress)
|
|
84
|
-
: undefined;
|
|
85
|
-
|
|
86
|
-
// create variable for re-use if needed
|
|
87
|
-
let publicIp;
|
|
88
|
-
|
|
89
|
-
// check if no announce IP was provided
|
|
90
|
-
const splitTcpAnnounceAddress = splitAddressPort(configTcpAnnounceAddress || '', true);
|
|
91
|
-
if (splitTcpAnnounceAddress.length == 2 && splitTcpAnnounceAddress[0] === '') {
|
|
92
|
-
if (queryForIp) {
|
|
93
|
-
publicIp = await getPublicIp();
|
|
94
|
-
const tcpAnnounceAddress = `${publicIp}:${splitTcpAnnounceAddress[1]}`;
|
|
95
|
-
config.tcpAnnounceAddress = tcpAnnounceAddress;
|
|
96
|
-
} else {
|
|
97
|
-
throw new Error(
|
|
98
|
-
`Invalid announceTcpAddress provided: ${configTcpAnnounceAddress}. Expected format: <addr>:<port>`,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const splitUdpAnnounceAddress = splitAddressPort(configUdpAnnounceAddress || '', true);
|
|
104
|
-
if (splitUdpAnnounceAddress.length == 2 && splitUdpAnnounceAddress[0] === '') {
|
|
105
|
-
// If announceUdpAddress is not provided, use announceTcpAddress
|
|
106
|
-
if (!queryForIp && config.tcpAnnounceAddress) {
|
|
107
|
-
config.udpAnnounceAddress = config.tcpAnnounceAddress;
|
|
108
|
-
} else if (queryForIp) {
|
|
109
|
-
const udpPublicIp = publicIp || (await getPublicIp());
|
|
110
|
-
const udpAnnounceAddress = `${udpPublicIp}:${splitUdpAnnounceAddress[1]}`;
|
|
111
|
-
config.udpAnnounceAddress = udpAnnounceAddress;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return config;
|
|
116
|
-
}
|
package/src/client/p2p_client.ts
CHANGED
|
@@ -3,20 +3,22 @@ import {
|
|
|
3
3
|
type BlockProposal,
|
|
4
4
|
type EpochProofQuote,
|
|
5
5
|
type L2Block,
|
|
6
|
-
L2BlockDownloader,
|
|
7
6
|
type L2BlockId,
|
|
8
7
|
type L2BlockSource,
|
|
8
|
+
L2BlockStream,
|
|
9
|
+
type L2BlockStreamEvent,
|
|
10
|
+
type L2Tips,
|
|
9
11
|
type Tx,
|
|
10
12
|
type TxHash,
|
|
11
13
|
} from '@aztec/circuit-types';
|
|
12
14
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js/constants';
|
|
13
15
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
14
|
-
import { type AztecKVStore, type AztecSingleton } from '@aztec/kv-store';
|
|
16
|
+
import { type AztecKVStore, type AztecMap, type AztecSingleton } from '@aztec/kv-store';
|
|
15
17
|
import { Attributes, type TelemetryClient, WithTracer, trackSpan } from '@aztec/telemetry-client';
|
|
16
18
|
|
|
17
19
|
import { type ENR } from '@chainsafe/enr';
|
|
18
20
|
|
|
19
|
-
import {
|
|
21
|
+
import { getP2PConfigFromEnv } from '../config.js';
|
|
20
22
|
import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js';
|
|
21
23
|
import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js';
|
|
22
24
|
import { type MemPools } from '../mem_pools/interface.js';
|
|
@@ -77,11 +79,11 @@ export interface P2P {
|
|
|
77
79
|
getEpochProofQuotes(epoch: bigint): Promise<EpochProofQuote[]>;
|
|
78
80
|
|
|
79
81
|
/**
|
|
80
|
-
*
|
|
82
|
+
* Adds an EpochProofQuote to the pool and broadcasts an EpochProofQuote to other peers.
|
|
81
83
|
*
|
|
82
84
|
* @param quote - the quote to broadcast
|
|
83
85
|
*/
|
|
84
|
-
|
|
86
|
+
addEpochProofQuote(quote: EpochProofQuote): Promise<void>;
|
|
85
87
|
|
|
86
88
|
/**
|
|
87
89
|
* Registers a callback from the validator client that determines how to behave when
|
|
@@ -130,7 +132,14 @@ export interface P2P {
|
|
|
130
132
|
* @param txHash - Hash of tx to return.
|
|
131
133
|
* @returns A single tx or undefined.
|
|
132
134
|
*/
|
|
133
|
-
|
|
135
|
+
getTxByHashFromPool(txHash: TxHash): Tx | undefined;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns a transaction in the transaction pool by its hash, requesting it from the network if it is not found.
|
|
139
|
+
* @param txHash - Hash of tx to return.
|
|
140
|
+
* @returns A single tx or undefined.
|
|
141
|
+
*/
|
|
142
|
+
getTxByHash(txHash: TxHash): Promise<Tx | undefined>;
|
|
134
143
|
|
|
135
144
|
/**
|
|
136
145
|
* Returns whether the given tx hash is flagged as pending or mined.
|
|
@@ -172,12 +181,6 @@ export interface P2P {
|
|
|
172
181
|
* The P2P client implementation.
|
|
173
182
|
*/
|
|
174
183
|
export class P2PClient extends WithTracer implements P2P {
|
|
175
|
-
/** L2 block download to stay in sync with latest blocks. */
|
|
176
|
-
private latestBlockDownloader: L2BlockDownloader;
|
|
177
|
-
|
|
178
|
-
/** L2 block download to stay in sync with proven blocks. */
|
|
179
|
-
private provenBlockDownloader: L2BlockDownloader;
|
|
180
|
-
|
|
181
184
|
/** Property that indicates whether the client is running. */
|
|
182
185
|
private stopping = false;
|
|
183
186
|
|
|
@@ -190,6 +193,7 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
190
193
|
private latestBlockNumberAtStart = -1;
|
|
191
194
|
private provenBlockNumberAtStart = -1;
|
|
192
195
|
|
|
196
|
+
private synchedBlockHashes: AztecMap<number, string>;
|
|
193
197
|
private synchedLatestBlockNumber: AztecSingleton<number>;
|
|
194
198
|
private synchedProvenBlockNumber: AztecSingleton<number>;
|
|
195
199
|
|
|
@@ -197,6 +201,8 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
197
201
|
private attestationPool: AttestationPool;
|
|
198
202
|
private epochProofQuotePool: EpochProofQuotePool;
|
|
199
203
|
|
|
204
|
+
private blockStream;
|
|
205
|
+
|
|
200
206
|
/**
|
|
201
207
|
* In-memory P2P client constructor.
|
|
202
208
|
* @param store - The client's instance of the KV store.
|
|
@@ -217,14 +223,14 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
217
223
|
) {
|
|
218
224
|
super(telemetryClient, 'P2PClient');
|
|
219
225
|
|
|
220
|
-
const { blockCheckIntervalMS
|
|
221
|
-
const l2DownloaderOpts = { maxQueueSize: p2pL2QueueSize, pollIntervalMS: checkInterval };
|
|
222
|
-
// TODO(palla/prover-node): This effectively downloads blocks twice from the archiver, which is an issue
|
|
223
|
-
// if the archiver is remote. We should refactor this so the downloader keeps a single queue and handles
|
|
224
|
-
// latest/proven metadata, as well as block reorgs.
|
|
225
|
-
this.latestBlockDownloader = new L2BlockDownloader(l2BlockSource, l2DownloaderOpts);
|
|
226
|
-
this.provenBlockDownloader = new L2BlockDownloader(l2BlockSource, { ...l2DownloaderOpts, proven: true });
|
|
226
|
+
const { blockCheckIntervalMS, blockRequestBatchSize } = getP2PConfigFromEnv();
|
|
227
227
|
|
|
228
|
+
this.blockStream = new L2BlockStream(l2BlockSource, this, this, {
|
|
229
|
+
batchSize: blockRequestBatchSize,
|
|
230
|
+
pollIntervalMS: blockCheckIntervalMS,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
this.synchedBlockHashes = store.openMap('p2p_pool_block_hashes');
|
|
228
234
|
this.synchedLatestBlockNumber = store.openSingleton('p2p_pool_last_l2_block');
|
|
229
235
|
this.synchedProvenBlockNumber = store.openSingleton('p2p_pool_last_proven_l2_block');
|
|
230
236
|
|
|
@@ -233,19 +239,88 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
233
239
|
this.epochProofQuotePool = mempools.epochProofQuotePool;
|
|
234
240
|
}
|
|
235
241
|
|
|
242
|
+
public getL2BlockHash(number: number): Promise<string | undefined> {
|
|
243
|
+
return Promise.resolve(this.synchedBlockHashes.get(number));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public getL2Tips(): Promise<L2Tips> {
|
|
247
|
+
const latestBlockNumber = this.getSyncedLatestBlockNum();
|
|
248
|
+
let latestBlockHash: string | undefined;
|
|
249
|
+
const provenBlockNumber = this.getSyncedProvenBlockNum();
|
|
250
|
+
let provenBlockHash: string | undefined;
|
|
251
|
+
|
|
252
|
+
if (latestBlockNumber > 0) {
|
|
253
|
+
latestBlockHash = this.synchedBlockHashes.get(latestBlockNumber);
|
|
254
|
+
if (typeof latestBlockHash === 'undefined') {
|
|
255
|
+
this.log.warn(`Block hash for latest block ${latestBlockNumber} not found`);
|
|
256
|
+
throw new Error();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (provenBlockNumber > 0) {
|
|
261
|
+
provenBlockHash = this.synchedBlockHashes.get(provenBlockNumber);
|
|
262
|
+
if (typeof provenBlockHash === 'undefined') {
|
|
263
|
+
this.log.warn(`Block hash for proven block ${provenBlockNumber} not found`);
|
|
264
|
+
throw new Error();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return Promise.resolve({
|
|
269
|
+
latest: { hash: latestBlockHash!, number: latestBlockNumber },
|
|
270
|
+
proven: { hash: provenBlockHash!, number: provenBlockNumber },
|
|
271
|
+
finalized: { hash: provenBlockHash!, number: provenBlockNumber },
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
|
|
276
|
+
this.log.debug(`Handling block stream event ${event.type}`);
|
|
277
|
+
switch (event.type) {
|
|
278
|
+
case 'blocks-added':
|
|
279
|
+
await this.handleLatestL2Blocks(event.blocks);
|
|
280
|
+
break;
|
|
281
|
+
case 'chain-finalized':
|
|
282
|
+
// TODO (alexg): I think we can prune the block hashes map here
|
|
283
|
+
break;
|
|
284
|
+
case 'chain-proven': {
|
|
285
|
+
const from = this.getSyncedProvenBlockNum() + 1;
|
|
286
|
+
const limit = event.blockNumber - from + 1;
|
|
287
|
+
await this.handleProvenL2Blocks(await this.l2BlockSource.getBlocks(from, limit));
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'chain-pruned':
|
|
291
|
+
await this.handlePruneL2Blocks(event.blockNumber);
|
|
292
|
+
break;
|
|
293
|
+
default: {
|
|
294
|
+
const _: never = event;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
236
300
|
#assertIsReady() {
|
|
301
|
+
// this.log.info('Checking if p2p client is ready, current state: ', this.currentState);
|
|
237
302
|
if (!this.isReady()) {
|
|
238
303
|
throw new Error('P2P client not ready');
|
|
239
304
|
}
|
|
240
305
|
}
|
|
241
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Adds an EpochProofQuote to the pool and broadcasts an EpochProofQuote to other peers.
|
|
309
|
+
* @param quote - the quote to broadcast
|
|
310
|
+
*/
|
|
311
|
+
addEpochProofQuote(quote: EpochProofQuote): Promise<void> {
|
|
312
|
+
this.epochProofQuotePool.addQuote(quote);
|
|
313
|
+
this.broadcastEpochProofQuote(quote);
|
|
314
|
+
return Promise.resolve();
|
|
315
|
+
}
|
|
316
|
+
|
|
242
317
|
getEpochProofQuotes(epoch: bigint): Promise<EpochProofQuote[]> {
|
|
243
318
|
return Promise.resolve(this.epochProofQuotePool.getQuotes(epoch));
|
|
244
319
|
}
|
|
245
320
|
|
|
246
321
|
broadcastEpochProofQuote(quote: EpochProofQuote): void {
|
|
247
322
|
this.#assertIsReady();
|
|
248
|
-
this.
|
|
323
|
+
this.log.info('Broadcasting epoch proof quote', quote.toViemArgs());
|
|
249
324
|
return this.p2pService.propagate(quote);
|
|
250
325
|
}
|
|
251
326
|
|
|
@@ -286,21 +361,7 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
286
361
|
// publish any txs in TxPool after its doing initial sync
|
|
287
362
|
this.syncPromise = this.syncPromise.then(() => this.publishStoredTxs());
|
|
288
363
|
|
|
289
|
-
|
|
290
|
-
const processLatest = async () => {
|
|
291
|
-
while (!this.stopping) {
|
|
292
|
-
await this.latestBlockDownloader.getBlocks(1).then(this.handleLatestL2Blocks.bind(this));
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
const processProven = async () => {
|
|
296
|
-
while (!this.stopping) {
|
|
297
|
-
await this.provenBlockDownloader.getBlocks(1).then(this.handleProvenL2Blocks.bind(this));
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
this.runningPromise = Promise.all([processLatest(), processProven()]).then(() => {});
|
|
302
|
-
this.latestBlockDownloader.start(syncedLatestBlock);
|
|
303
|
-
this.provenBlockDownloader.start(syncedLatestBlock);
|
|
364
|
+
this.blockStream.start();
|
|
304
365
|
this.log.verbose(`Started block downloader from block ${syncedLatestBlock}`);
|
|
305
366
|
|
|
306
367
|
return this.syncPromise;
|
|
@@ -315,8 +376,7 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
315
376
|
this.stopping = true;
|
|
316
377
|
await this.p2pService.stop();
|
|
317
378
|
this.log.debug('Stopped p2p service');
|
|
318
|
-
await this.
|
|
319
|
-
await this.provenBlockDownloader.stop();
|
|
379
|
+
await this.blockStream.stop();
|
|
320
380
|
this.log.debug('Stopped block downloader');
|
|
321
381
|
await this.runningPromise;
|
|
322
382
|
this.setCurrentState(P2PClientState.STOPPED);
|
|
@@ -388,7 +448,7 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
388
448
|
} else if (filter === 'mined') {
|
|
389
449
|
return this.txPool
|
|
390
450
|
.getMinedTxHashes()
|
|
391
|
-
.map(txHash => this.txPool.getTxByHash(txHash))
|
|
451
|
+
.map(([txHash]) => this.txPool.getTxByHash(txHash))
|
|
392
452
|
.filter((tx): tx is Tx => !!tx);
|
|
393
453
|
} else if (filter === 'pending') {
|
|
394
454
|
return this.txPool
|
|
@@ -406,10 +466,24 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
406
466
|
* @param txHash - Hash of the transaction to look for in the pool.
|
|
407
467
|
* @returns A single tx or undefined.
|
|
408
468
|
*/
|
|
409
|
-
|
|
469
|
+
getTxByHashFromPool(txHash: TxHash): Tx | undefined {
|
|
410
470
|
return this.txPool.getTxByHash(txHash);
|
|
411
471
|
}
|
|
412
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Returns a transaction in the transaction pool by its hash.
|
|
475
|
+
* If the transaction is not in the pool, it will be requested from the network.
|
|
476
|
+
* @param txHash - Hash of the transaction to look for in the pool.
|
|
477
|
+
* @returns A single tx or undefined.
|
|
478
|
+
*/
|
|
479
|
+
getTxByHash(txHash: TxHash): Promise<Tx | undefined> {
|
|
480
|
+
const tx = this.txPool.getTxByHash(txHash);
|
|
481
|
+
if (tx) {
|
|
482
|
+
return Promise.resolve(tx);
|
|
483
|
+
}
|
|
484
|
+
return this.requestTxByHash(txHash);
|
|
485
|
+
}
|
|
486
|
+
|
|
413
487
|
/**
|
|
414
488
|
* Verifies the 'tx' and, if valid, adds it to local tx pool and forwards it to other peers.
|
|
415
489
|
* @param tx - The tx to verify.
|
|
@@ -493,7 +567,7 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
493
567
|
private async markTxsAsMinedFromBlocks(blocks: L2Block[]): Promise<void> {
|
|
494
568
|
for (const block of blocks) {
|
|
495
569
|
const txHashes = block.body.txEffects.map(txEffect => txEffect.txHash);
|
|
496
|
-
await this.txPool.markAsMined(txHashes);
|
|
570
|
+
await this.txPool.markAsMined(txHashes, block.number);
|
|
497
571
|
}
|
|
498
572
|
}
|
|
499
573
|
|
|
@@ -519,8 +593,10 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
519
593
|
if (!blocks.length) {
|
|
520
594
|
return Promise.resolve();
|
|
521
595
|
}
|
|
596
|
+
|
|
522
597
|
await this.markTxsAsMinedFromBlocks(blocks);
|
|
523
598
|
const lastBlockNum = blocks[blocks.length - 1].number;
|
|
599
|
+
await Promise.all(blocks.map(block => this.synchedBlockHashes.set(block.number, block.hash().toString())));
|
|
524
600
|
await this.synchedLatestBlockNumber.set(lastBlockNum);
|
|
525
601
|
this.log.debug(`Synched to latest block ${lastBlockNum}`);
|
|
526
602
|
await this.startServiceIfSynched();
|
|
@@ -558,6 +634,46 @@ export class P2PClient extends WithTracer implements P2P {
|
|
|
558
634
|
await this.startServiceIfSynched();
|
|
559
635
|
}
|
|
560
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Updates the tx pool after a chain prune.
|
|
639
|
+
* @param latestBlock - The block number the chain was pruned to.
|
|
640
|
+
*/
|
|
641
|
+
private async handlePruneL2Blocks(latestBlock: number): Promise<void> {
|
|
642
|
+
const txsToDelete: TxHash[] = [];
|
|
643
|
+
for (const tx of this.txPool.getAllTxs()) {
|
|
644
|
+
// every tx that's been generated against a block that has now been pruned is no longer valid
|
|
645
|
+
if (tx.data.constants.historicalHeader.globalVariables.blockNumber.toNumber() > latestBlock) {
|
|
646
|
+
txsToDelete.push(tx.getTxHash());
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
this.log.info(
|
|
651
|
+
`Detected chain prune. Removing invalid txs count=${
|
|
652
|
+
txsToDelete.length
|
|
653
|
+
} newLatestBlock=${latestBlock} previousLatestBlock=${this.getSyncedLatestBlockNum()}`,
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
// delete invalid txs (both pending and mined)
|
|
657
|
+
await this.txPool.deleteTxs(txsToDelete);
|
|
658
|
+
|
|
659
|
+
// everything left in the mined set was built against a block on the proven chain so its still valid
|
|
660
|
+
// move back to pending the txs that were reorged out of the chain
|
|
661
|
+
// NOTE: we can't move _all_ txs back to pending because the tx pool could keep hold of mined txs for longer
|
|
662
|
+
// (see this.keepProvenTxsFor)
|
|
663
|
+
const txsToMoveToPending: TxHash[] = [];
|
|
664
|
+
for (const [txHash, blockNumber] of this.txPool.getMinedTxHashes()) {
|
|
665
|
+
if (blockNumber > latestBlock) {
|
|
666
|
+
txsToMoveToPending.push(txHash);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
this.log.info(`Moving ${txsToMoveToPending.length} mined txs back to pending`);
|
|
671
|
+
await this.txPool.markMinedAsPending(txsToMoveToPending);
|
|
672
|
+
|
|
673
|
+
await this.synchedLatestBlockNumber.set(latestBlock);
|
|
674
|
+
// no need to update block hashes, as they will be updated as new blocks are added
|
|
675
|
+
}
|
|
676
|
+
|
|
561
677
|
private async startServiceIfSynched() {
|
|
562
678
|
if (
|
|
563
679
|
this.currentState === P2PClientState.SYNCHING &&
|
package/src/config.ts
CHANGED
|
@@ -23,6 +23,11 @@ export interface P2PConfig extends P2PReqRespConfig {
|
|
|
23
23
|
*/
|
|
24
24
|
blockCheckIntervalMS: number;
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The number of blocks to fetch in a single batch.
|
|
28
|
+
*/
|
|
29
|
+
blockRequestBatchSize: number;
|
|
30
|
+
|
|
26
31
|
/**
|
|
27
32
|
* The frequency in which to check for new peers.
|
|
28
33
|
*/
|
|
@@ -295,6 +300,11 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
|
|
|
295
300
|
description: 'The chain id of the L1 chain.',
|
|
296
301
|
...numberConfigHelper(31337),
|
|
297
302
|
},
|
|
303
|
+
blockRequestBatchSize: {
|
|
304
|
+
env: 'P2P_BLOCK_REQUEST_BATCH_SIZE',
|
|
305
|
+
description: 'The number of blocks to fetch in a single batch.',
|
|
306
|
+
...numberConfigHelper(20),
|
|
307
|
+
},
|
|
298
308
|
...p2pReqRespConfigMappings,
|
|
299
309
|
};
|
|
300
310
|
|
|
@@ -302,7 +312,7 @@ export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
|
|
|
302
312
|
* Gets the config values for p2p client from environment variables.
|
|
303
313
|
* @returns The config values for p2p client.
|
|
304
314
|
*/
|
|
305
|
-
export function
|
|
315
|
+
export function getP2PConfigFromEnv(): P2PConfig {
|
|
306
316
|
return getConfigFromMappings<P2PConfig>(p2pConfigMappings);
|
|
307
317
|
}
|
|
308
318
|
|
|
@@ -19,7 +19,7 @@ export class AztecKVTxPool implements TxPool {
|
|
|
19
19
|
/** Index for pending txs. */
|
|
20
20
|
#pendingTxs: AztecSet<string>;
|
|
21
21
|
/** Index for mined txs. */
|
|
22
|
-
#minedTxs:
|
|
22
|
+
#minedTxs: AztecMap<string, number>;
|
|
23
23
|
|
|
24
24
|
#log: Logger;
|
|
25
25
|
|
|
@@ -32,7 +32,7 @@ export class AztecKVTxPool implements TxPool {
|
|
|
32
32
|
*/
|
|
33
33
|
constructor(store: AztecKVStore, telemetry: TelemetryClient, log = createDebugLogger('aztec:tx_pool')) {
|
|
34
34
|
this.#txs = store.openMap('txs');
|
|
35
|
-
this.#minedTxs = store.
|
|
35
|
+
this.#minedTxs = store.openMap('minedTxs');
|
|
36
36
|
this.#pendingTxs = store.openSet('pendingTxs');
|
|
37
37
|
|
|
38
38
|
this.#store = store;
|
|
@@ -40,12 +40,12 @@ export class AztecKVTxPool implements TxPool {
|
|
|
40
40
|
this.#metrics = new PoolInstrumentation(telemetry, 'AztecKVTxPool');
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
public markAsMined(txHashes: TxHash[]): Promise<void> {
|
|
43
|
+
public markAsMined(txHashes: TxHash[], blockNumber: number): Promise<void> {
|
|
44
44
|
return this.#store.transaction(() => {
|
|
45
45
|
let deleted = 0;
|
|
46
46
|
for (const hash of txHashes) {
|
|
47
47
|
const key = hash.toString();
|
|
48
|
-
void this.#minedTxs.
|
|
48
|
+
void this.#minedTxs.set(key, blockNumber);
|
|
49
49
|
if (this.#pendingTxs.has(key)) {
|
|
50
50
|
deleted++;
|
|
51
51
|
void this.#pendingTxs.delete(key);
|
|
@@ -56,12 +56,41 @@ export class AztecKVTxPool implements TxPool {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
public markMinedAsPending(txHashes: TxHash[]): Promise<void> {
|
|
60
|
+
if (txHashes.length === 0) {
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.#store.transaction(() => {
|
|
65
|
+
let deleted = 0;
|
|
66
|
+
let added = 0;
|
|
67
|
+
for (const hash of txHashes) {
|
|
68
|
+
const key = hash.toString();
|
|
69
|
+
if (this.#minedTxs.has(key)) {
|
|
70
|
+
deleted++;
|
|
71
|
+
void this.#minedTxs.delete(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.#txs.has(key)) {
|
|
75
|
+
added++;
|
|
76
|
+
void this.#pendingTxs.add(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.#metrics.recordRemovedObjects(deleted, 'mined');
|
|
81
|
+
this.#metrics.recordAddedObjects(added, 'pending');
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
59
85
|
public getPendingTxHashes(): TxHash[] {
|
|
60
86
|
return Array.from(this.#pendingTxs.entries()).map(x => TxHash.fromString(x));
|
|
61
87
|
}
|
|
62
88
|
|
|
63
|
-
public getMinedTxHashes(): TxHash[] {
|
|
64
|
-
return Array.from(this.#minedTxs.entries()).map(
|
|
89
|
+
public getMinedTxHashes(): [TxHash, number][] {
|
|
90
|
+
return Array.from(this.#minedTxs.entries()).map(([txHash, blockNumber]) => [
|
|
91
|
+
TxHash.fromString(txHash),
|
|
92
|
+
blockNumber,
|
|
93
|
+
]);
|
|
65
94
|
}
|
|
66
95
|
|
|
67
96
|
public getTxStatus(txHash: TxHash): 'pending' | 'mined' | undefined {
|
|
@@ -14,7 +14,7 @@ export class InMemoryTxPool implements TxPool {
|
|
|
14
14
|
* Our tx pool, stored as a Map in-memory, with K: tx hash and V: the transaction.
|
|
15
15
|
*/
|
|
16
16
|
private txs: Map<bigint, Tx>;
|
|
17
|
-
private minedTxs:
|
|
17
|
+
private minedTxs: Map<bigint, number>;
|
|
18
18
|
private pendingTxs: Set<bigint>;
|
|
19
19
|
|
|
20
20
|
private metrics: PoolInstrumentation<Tx>;
|
|
@@ -25,15 +25,15 @@ export class InMemoryTxPool implements TxPool {
|
|
|
25
25
|
*/
|
|
26
26
|
constructor(telemetry: TelemetryClient, private log = createDebugLogger('aztec:tx_pool')) {
|
|
27
27
|
this.txs = new Map<bigint, Tx>();
|
|
28
|
-
this.minedTxs = new
|
|
28
|
+
this.minedTxs = new Map();
|
|
29
29
|
this.pendingTxs = new Set();
|
|
30
30
|
this.metrics = new PoolInstrumentation(telemetry, 'InMemoryTxPool');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
public markAsMined(txHashes: TxHash[]): Promise<void> {
|
|
33
|
+
public markAsMined(txHashes: TxHash[], blockNumber: number): Promise<void> {
|
|
34
34
|
const keys = txHashes.map(x => x.toBigInt());
|
|
35
35
|
for (const key of keys) {
|
|
36
|
-
this.minedTxs.
|
|
36
|
+
this.minedTxs.set(key, blockNumber);
|
|
37
37
|
this.pendingTxs.delete(key);
|
|
38
38
|
}
|
|
39
39
|
this.metrics.recordRemovedObjects(txHashes.length, 'pending');
|
|
@@ -41,12 +41,38 @@ export class InMemoryTxPool implements TxPool {
|
|
|
41
41
|
return Promise.resolve();
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
public markMinedAsPending(txHashes: TxHash[]): Promise<void> {
|
|
45
|
+
if (txHashes.length === 0) {
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const keys = txHashes.map(x => x.toBigInt());
|
|
50
|
+
let deleted = 0;
|
|
51
|
+
let added = 0;
|
|
52
|
+
for (const key of keys) {
|
|
53
|
+
if (this.minedTxs.delete(key)) {
|
|
54
|
+
deleted++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// only add back to the pending set if we have the tx object
|
|
58
|
+
if (this.txs.has(key)) {
|
|
59
|
+
added++;
|
|
60
|
+
this.pendingTxs.add(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.metrics.recordRemovedObjects(deleted, 'mined');
|
|
65
|
+
this.metrics.recordAddedObjects(added, 'pending');
|
|
66
|
+
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
}
|
|
69
|
+
|
|
44
70
|
public getPendingTxHashes(): TxHash[] {
|
|
45
71
|
return Array.from(this.pendingTxs).map(x => TxHash.fromBigInt(x));
|
|
46
72
|
}
|
|
47
73
|
|
|
48
|
-
public getMinedTxHashes(): TxHash[] {
|
|
49
|
-
return Array.from(this.minedTxs).map(
|
|
74
|
+
public getMinedTxHashes(): [TxHash, number][] {
|
|
75
|
+
return Array.from(this.minedTxs.entries()).map(([txHash, blockNumber]) => [TxHash.fromBigInt(txHash), blockNumber]);
|
|
50
76
|
}
|
|
51
77
|
|
|
52
78
|
public getTxStatus(txHash: TxHash): 'pending' | 'mined' | undefined {
|
|
@@ -21,7 +21,14 @@ export interface TxPool {
|
|
|
21
21
|
* Marks the set of txs as mined, as opposed to pending.
|
|
22
22
|
* @param txHashes - Hashes of the txs to flag as mined.
|
|
23
23
|
*/
|
|
24
|
-
markAsMined(txHashes: TxHash[]): Promise<void>;
|
|
24
|
+
markAsMined(txHashes: TxHash[], blockNumber: number): Promise<void>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Moves mined txs back to the pending set in the case of a reorg.
|
|
28
|
+
* Note: txs not known by this peer will be ignored.
|
|
29
|
+
* @param txHashes - Hashes of the txs to flag as pending.
|
|
30
|
+
*/
|
|
31
|
+
markMinedAsPending(txHashes: TxHash[]): Promise<void>;
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
34
|
* Deletes transactions from the pool. Tx hashes that are not present are ignored.
|
|
@@ -51,7 +58,7 @@ export interface TxPool {
|
|
|
51
58
|
* Gets the hashes of mined transactions currently in the tx pool.
|
|
52
59
|
* @returns An array of mined transaction hashes found in the tx pool.
|
|
53
60
|
*/
|
|
54
|
-
getMinedTxHashes(): TxHash[];
|
|
61
|
+
getMinedTxHashes(): [tx: TxHash, blockNumber: number][];
|
|
55
62
|
|
|
56
63
|
/**
|
|
57
64
|
* Returns whether the given tx hash is flagged as pending or mined.
|
|
@@ -38,14 +38,46 @@ export function describeTxPool(getTxPool: () => TxPool) {
|
|
|
38
38
|
const tx2 = mockTx(2);
|
|
39
39
|
|
|
40
40
|
await pool.addTxs([tx1, tx2]);
|
|
41
|
-
await pool.markAsMined([tx1.getTxHash()]);
|
|
41
|
+
await pool.markAsMined([tx1.getTxHash()], 1);
|
|
42
42
|
|
|
43
43
|
expect(pool.getTxByHash(tx1.getTxHash())).toEqual(tx1);
|
|
44
44
|
expect(pool.getTxStatus(tx1.getTxHash())).toEqual('mined');
|
|
45
|
-
expect(pool.getMinedTxHashes()).toEqual([tx1.getTxHash()]);
|
|
45
|
+
expect(pool.getMinedTxHashes()).toEqual([[tx1.getTxHash(), 1]]);
|
|
46
46
|
expect(pool.getPendingTxHashes()).toEqual([tx2.getTxHash()]);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
it('Marks txs as pending after being mined', async () => {
|
|
50
|
+
const tx1 = mockTx(1);
|
|
51
|
+
const tx2 = mockTx(2);
|
|
52
|
+
|
|
53
|
+
await pool.addTxs([tx1, tx2]);
|
|
54
|
+
await pool.markAsMined([tx1.getTxHash()], 1);
|
|
55
|
+
|
|
56
|
+
await pool.markMinedAsPending([tx1.getTxHash()]);
|
|
57
|
+
expect(pool.getMinedTxHashes()).toEqual([]);
|
|
58
|
+
const pending = pool.getPendingTxHashes();
|
|
59
|
+
expect(pending).toHaveLength(2);
|
|
60
|
+
expect(pending).toEqual(expect.arrayContaining([tx1.getTxHash(), tx2.getTxHash()]));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('Only marks txs as pending if they are known', async () => {
|
|
64
|
+
const tx1 = mockTx(1);
|
|
65
|
+
// simulate a situation where not all peers have all the txs
|
|
66
|
+
const someTxHashThatThisPeerDidNotSee = mockTx(2).getTxHash();
|
|
67
|
+
await pool.addTxs([tx1]);
|
|
68
|
+
// this peer knows that tx2 was mined, but it does not have the tx object
|
|
69
|
+
await pool.markAsMined([tx1.getTxHash(), someTxHashThatThisPeerDidNotSee], 1);
|
|
70
|
+
expect(pool.getMinedTxHashes()).toEqual([
|
|
71
|
+
[tx1.getTxHash(), 1],
|
|
72
|
+
[someTxHashThatThisPeerDidNotSee, 1],
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// reorg: both txs should now become available again
|
|
76
|
+
await pool.markMinedAsPending([tx1.getTxHash(), someTxHashThatThisPeerDidNotSee]);
|
|
77
|
+
expect(pool.getMinedTxHashes()).toEqual([]);
|
|
78
|
+
expect(pool.getPendingTxHashes()).toEqual([tx1.getTxHash()]); // tx2 is not in the pool
|
|
79
|
+
});
|
|
80
|
+
|
|
49
81
|
it('Returns all transactions in the pool', async () => {
|
|
50
82
|
const tx1 = mockTx(1);
|
|
51
83
|
const tx2 = mockTx(2);
|