@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.
Files changed (35) hide show
  1. package/dest/client/index.js +2 -39
  2. package/dest/client/p2p_client.d.ts +33 -9
  3. package/dest/client/p2p_client.d.ts.map +1 -1
  4. package/dest/client/p2p_client.js +121 -30
  5. package/dest/config.d.ts +5 -1
  6. package/dest/config.d.ts.map +1 -1
  7. package/dest/config.js +7 -2
  8. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +3 -2
  9. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
  10. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +30 -5
  11. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +3 -2
  12. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
  13. package/dest/mem_pools/tx_pool/memory_tx_pool.js +26 -5
  14. package/dest/mem_pools/tx_pool/tx_pool.d.ts +8 -2
  15. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
  16. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -1
  17. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +30 -3
  18. package/dest/mocks/index.d.ts +18 -2
  19. package/dest/mocks/index.d.ts.map +1 -1
  20. package/dest/mocks/index.js +79 -5
  21. package/dest/service/peer_scoring.js +5 -5
  22. package/dest/util.d.ts +3 -0
  23. package/dest/util.d.ts.map +1 -1
  24. package/dest/util.js +38 -1
  25. package/package.json +12 -7
  26. package/src/client/index.ts +1 -48
  27. package/src/client/p2p_client.ts +156 -40
  28. package/src/config.ts +11 -1
  29. package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +35 -6
  30. package/src/mem_pools/tx_pool/memory_tx_pool.ts +32 -6
  31. package/src/mem_pools/tx_pool/tx_pool.ts +9 -2
  32. package/src/mem_pools/tx_pool/tx_pool_test_suite.ts +34 -2
  33. package/src/mocks/index.ts +123 -5
  34. package/src/service/peer_scoring.ts +4 -4
  35. package/src/util.ts +53 -0
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@aztec/p2p",
3
- "version": "0.60.0",
3
+ "version": "0.62.0",
4
4
  "type": "module",
5
- "exports": "./dest/index.js",
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.60.0",
63
- "@aztec/circuits.js": "0.60.0",
64
- "@aztec/foundation": "0.60.0",
65
- "@aztec/kv-store": "0.60.0",
66
- "@aztec/telemetry-client": "0.60.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",
@@ -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 { getPublicIp, resolveAddressIfNecessary, splitAddressPort } from '../util.js';
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
- }
@@ -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 { getP2PConfigEnvVars } from '../config.js';
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
- * Broadcasts an EpochProofQuote to other peers.
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
- broadcastEpochProofQuote(quote: EpochProofQuote): void;
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
- getTxByHash(txHash: TxHash): Tx | undefined;
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: checkInterval, l2QueueSize: p2pL2QueueSize } = getP2PConfigEnvVars();
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.epochProofQuotePool.addQuote(quote);
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
- // start looking for further blocks
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.latestBlockDownloader.stop();
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
- getTxByHash(txHash: TxHash): Tx | undefined {
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 getP2PConfigEnvVars(): P2PConfig {
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: AztecSet<string>;
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.openSet('minedTxs');
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.add(key);
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(x => TxHash.fromString(x));
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: Set<bigint>;
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 Set();
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.add(key);
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(x => TxHash.fromBigInt(x));
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);