@aztec/p2p 0.0.1-commit.808bf7f90 → 0.0.1-commit.8227e42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dest/client/factory.d.ts +1 -1
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +8 -20
  4. package/dest/client/interface.d.ts +3 -10
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +2 -10
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +2 -57
  9. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +5 -5
  10. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  11. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +9 -1
  12. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  13. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +12 -0
  14. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +2 -2
  15. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  16. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +2 -2
  17. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  18. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +1 -6
  19. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +2 -2
  20. package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
  21. package/dest/msg_validators/tx_validator/aggregate_tx_validator.js +3 -3
  22. package/dest/msg_validators/tx_validator/factory.d.ts +114 -6
  23. package/dest/msg_validators/tx_validator/factory.d.ts.map +1 -1
  24. package/dest/msg_validators/tx_validator/factory.js +219 -58
  25. package/dest/msg_validators/tx_validator/gas_validator.d.ts +58 -3
  26. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  27. package/dest/msg_validators/tx_validator/gas_validator.js +72 -35
  28. package/dest/msg_validators/tx_validator/index.d.ts +2 -1
  29. package/dest/msg_validators/tx_validator/index.d.ts.map +1 -1
  30. package/dest/msg_validators/tx_validator/index.js +1 -0
  31. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts +14 -0
  32. package/dest/msg_validators/tx_validator/nullifier_cache.d.ts.map +1 -0
  33. package/dest/msg_validators/tx_validator/nullifier_cache.js +24 -0
  34. package/dest/services/dummy_service.d.ts +2 -3
  35. package/dest/services/dummy_service.d.ts.map +1 -1
  36. package/dest/services/dummy_service.js +1 -4
  37. package/dest/services/libp2p/libp2p_service.d.ts +10 -7
  38. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  39. package/dest/services/libp2p/libp2p_service.js +57 -70
  40. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +1 -1
  41. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  42. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +14 -37
  43. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts +17 -11
  44. package/dest/services/reqresp/batch-tx-requester/peer_collection.d.ts.map +1 -1
  45. package/dest/services/reqresp/batch-tx-requester/peer_collection.js +49 -15
  46. package/dest/services/reqresp/batch-tx-requester/tx_validator.js +2 -2
  47. package/dest/services/service.d.ts +2 -2
  48. package/dest/services/service.d.ts.map +1 -1
  49. package/dest/services/tx_provider.d.ts +3 -3
  50. package/dest/services/tx_provider.d.ts.map +1 -1
  51. package/dest/services/tx_provider.js +4 -4
  52. package/dest/test-helpers/testbench-utils.d.ts +2 -2
  53. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  54. package/package.json +14 -14
  55. package/src/client/factory.ts +13 -33
  56. package/src/client/interface.ts +2 -10
  57. package/src/client/p2p_client.ts +2 -76
  58. package/src/mem_pools/tx_pool_v2/interfaces.ts +4 -4
  59. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +13 -0
  60. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +1 -1
  61. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +2 -8
  62. package/src/msg_validators/tx_validator/README.md +115 -0
  63. package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +3 -3
  64. package/src/msg_validators/tx_validator/factory.ts +353 -77
  65. package/src/msg_validators/tx_validator/gas_validator.ts +84 -29
  66. package/src/msg_validators/tx_validator/index.ts +1 -0
  67. package/src/msg_validators/tx_validator/nullifier_cache.ts +30 -0
  68. package/src/services/dummy_service.ts +1 -5
  69. package/src/services/libp2p/libp2p_service.ts +70 -79
  70. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +14 -42
  71. package/src/services/reqresp/batch-tx-requester/peer_collection.ts +63 -24
  72. package/src/services/reqresp/batch-tx-requester/tx_validator.ts +2 -2
  73. package/src/services/service.ts +1 -1
  74. package/src/services/tx_provider.ts +2 -2
  75. package/src/test-helpers/testbench-utils.ts +1 -1
@@ -69,9 +69,11 @@ import {
69
69
  } from '../../msg_validators/index.js';
70
70
  import { MessageSeenValidator } from '../../msg_validators/msg_seen_validator/msg_seen_validator.js';
71
71
  import {
72
- type MessageValidator,
73
- createTxMessageValidators,
74
- createTxReqRespValidator,
72
+ type TransactionValidator,
73
+ createFirstStageTxValidationsForGossipedTransactions,
74
+ createSecondStageTxValidationsForGossipedTransactions,
75
+ createTxValidatorForBlockProposalReceivedTxs,
76
+ createTxValidatorForReqResponseReceivedTxs,
75
77
  } from '../../msg_validators/tx_validator/factory.js';
76
78
  import { GossipSubEvent } from '../../types/index.js';
77
79
  import { type PubSubLibp2p, convertToMultiaddr } from '../../util.js';
@@ -87,6 +89,9 @@ import { PeerScoring } from '../peer-manager/peer_scoring.js';
87
89
  import type { BatchTxRequesterLibP2PService } from '../reqresp/batch-tx-requester/interface.js';
88
90
  import type { P2PReqRespConfig } from '../reqresp/config.js';
89
91
  import {
92
+ AuthRequest,
93
+ BlockTxsRequest,
94
+ BlockTxsResponse,
90
95
  DEFAULT_SUB_PROTOCOL_VALIDATORS,
91
96
  type ReqRespInterface,
92
97
  type ReqRespResponse,
@@ -94,14 +99,9 @@ import {
94
99
  type ReqRespSubProtocolHandler,
95
100
  type ReqRespSubProtocolHandlers,
96
101
  type ReqRespSubProtocolValidators,
102
+ StatusMessage,
97
103
  type SubProtocolMap,
98
104
  ValidationError,
99
- } from '../reqresp/index.js';
100
- import {
101
- AuthRequest,
102
- BlockTxsRequest,
103
- BlockTxsResponse,
104
- StatusMessage,
105
105
  pingHandler,
106
106
  reqGoodbyeHandler,
107
107
  reqRespBlockHandler,
@@ -906,15 +906,45 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
906
906
  protected async handleGossipedTx(payloadData: Buffer, msgId: string, source: PeerId) {
907
907
  const validationFunc: () => Promise<ReceivedMessageValidationResult<Tx>> = async () => {
908
908
  const tx = Tx.fromBuffer(payloadData);
909
- const isValid = await this.validatePropagatedTx(tx, source);
910
- if (!isValid) {
911
- this.logger.trace(`Rejecting invalid propagated tx`, {
912
- [Attributes.P2P_ID]: source.toString(),
913
- });
909
+
910
+ const currentBlockNumber = await this.archiver.getBlockNumber();
911
+ const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
912
+
913
+ // Stage 1: fast validators (metadata, data, timestamps, double-spend, gas, phases, block header)
914
+ const firstStageValidators = await this.createFirstStageMessageValidators(currentBlockNumber, nextSlotTimestamp);
915
+ const firstStageOutcome = await this.runValidations(tx, firstStageValidators);
916
+ if (!firstStageOutcome.allPassed) {
917
+ const { name } = firstStageOutcome.failure;
918
+ let { severity } = firstStageOutcome.failure;
919
+
920
+ // Double spend validator has a special case handler. We perform more detailed examination
921
+ // as to how recently the nullifier was entered into the tree and if the transaction should
922
+ // have 'known' the nullifier existed. This determines the severity of the penalty applied to the peer.
923
+ if (name === 'doubleSpendValidator') {
924
+ const txBlockNumber = BlockNumber(currentBlockNumber + 1);
925
+ severity = await this.handleDoubleSpendFailure(tx, txBlockNumber);
926
+ }
927
+
928
+ this.peerManager.penalizePeer(source, severity);
929
+ return { result: TopicValidatorResult.Reject };
930
+ }
931
+
932
+ // Pool pre-check: see if the pool would accept this tx before doing expensive proof verification
933
+ const canAdd = await this.mempools.txPool.canAddPendingTx(tx);
934
+ if (canAdd === 'ignored') {
935
+ return { result: TopicValidatorResult.Ignore, obj: tx };
936
+ }
937
+
938
+ // Stage 2: expensive proof verification
939
+ const secondStageValidators = this.createSecondStageMessageValidators();
940
+ const secondStageOutcome = await this.runValidations(tx, secondStageValidators);
941
+ if (!secondStageOutcome.allPassed) {
942
+ const { severity } = secondStageOutcome.failure;
943
+ this.peerManager.penalizePeer(source, severity);
914
944
  return { result: TopicValidatorResult.Reject };
915
945
  }
916
946
 
917
- // Propagate only on pool acceptance
947
+ // Pool add: persist the tx
918
948
  const txHash = tx.getTxHash();
919
949
  const addResult = await this.mempools.txPool.addPendingTxs([tx], { source: 'gossip' });
920
950
 
@@ -922,7 +952,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
922
952
  const wasIgnored = addResult.ignored.some(h => h.equals(txHash));
923
953
 
924
954
  this.logger.trace(`Validate propagated tx`, {
925
- isValid,
926
955
  wasAccepted,
927
956
  wasIgnored,
928
957
  [Attributes.P2P_ID]: source.toString(),
@@ -1537,43 +1566,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1537
1566
  }
1538
1567
 
1539
1568
  protected createRequestedTxValidator(): TxValidator {
1540
- return createTxReqRespValidator(this.proofVerifier, {
1569
+ return createTxValidatorForReqResponseReceivedTxs(this.proofVerifier, {
1541
1570
  l1ChainId: this.config.l1ChainId,
1542
1571
  rollupVersion: this.config.rollupVersion,
1543
1572
  });
1544
1573
  }
1545
1574
 
1546
- @trackSpan('Libp2pService.validatePropagatedTx', tx => ({
1547
- [Attributes.TX_HASH]: tx.getTxHash().toString(),
1548
- }))
1549
- protected async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
1550
- const currentBlockNumber = await this.archiver.getBlockNumber();
1551
-
1552
- // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1553
- const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1554
- const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
1555
-
1556
- for (const validator of messageValidators) {
1557
- const outcome = await this.runValidations(tx, validator);
1558
-
1559
- if (outcome.allPassed) {
1560
- continue;
1561
- }
1562
- const { name } = outcome.failure;
1563
- let { severity } = outcome.failure;
1564
-
1565
- // Double spend validator has a special case handler
1566
- if (name === 'doubleSpendValidator') {
1567
- const txBlockNumber = BlockNumber(currentBlockNumber + 1); // tx is expected to be in the next block
1568
- severity = await this.handleDoubleSpendFailure(tx, txBlockNumber);
1569
- }
1570
-
1571
- this.peerManager.penalizePeer(peerId, severity);
1572
- return false;
1573
- }
1574
- return true;
1575
- }
1576
-
1577
1575
  private async getGasFees(blockNumber: BlockNumber): Promise<GasFees> {
1578
1576
  if (blockNumber === this.feesCache?.blockNumber) {
1579
1577
  return this.feesCache.gasFees;
@@ -1601,60 +1599,53 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1601
1599
  };
1602
1600
  }
1603
1601
 
1604
- public async validate(txs: Tx[]): Promise<void> {
1605
- const currentBlockNumber = await this.archiver.getBlockNumber();
1606
-
1607
- // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1608
- const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1609
- const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
1602
+ public async validateTxsReceivedInBlockProposal(txs: Tx[]): Promise<void> {
1603
+ const validator = createTxValidatorForBlockProposalReceivedTxs(
1604
+ this.proofVerifier,
1605
+ { l1ChainId: this.config.l1ChainId, rollupVersion: this.config.rollupVersion },
1606
+ this.logger.getBindings(),
1607
+ );
1610
1608
 
1611
- await Promise.all(
1609
+ const results = await Promise.all(
1612
1610
  txs.map(async tx => {
1613
- for (const validator of messageValidators) {
1614
- const outcome = await this.runValidations(tx, validator);
1615
- if (!outcome.allPassed) {
1616
- throw new Error('Invalid tx detected', { cause: { outcome } });
1617
- }
1618
- }
1611
+ const result = await validator.validateTx(tx);
1612
+ return result.result !== 'invalid';
1619
1613
  }),
1620
1614
  );
1615
+ if (results.some(value => value === false)) {
1616
+ throw new Error('Invalid tx detected');
1617
+ }
1621
1618
  }
1622
1619
 
1623
- /**
1624
- * Create message validators for the given block number and timestamp.
1625
- *
1626
- * Each validator is a pair of a validator and a severity.
1627
- * If a validator fails, the peer is penalized with the severity of the validator.
1628
- *
1629
- * @param currentBlockNumber - The current synced block number.
1630
- * @param nextSlotTimestamp - The timestamp of the next slot (used to validate txs are not expired).
1631
- * @returns The message validators.
1632
- */
1633
- private async createMessageValidators(
1620
+ /** Creates the first stage (fast) validators for gossiped transactions. */
1621
+ protected async createFirstStageMessageValidators(
1634
1622
  currentBlockNumber: BlockNumber,
1635
1623
  nextSlotTimestamp: UInt64,
1636
- ): Promise<Record<string, MessageValidator>[]> {
1624
+ ): Promise<Record<string, TransactionValidator>> {
1637
1625
  const gasFees = await this.getGasFees(currentBlockNumber);
1638
1626
  const allowedInSetup = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
1627
+ const blockNumber = BlockNumber(currentBlockNumber + 1);
1639
1628
 
1640
- const blockNumberInWhichTheTxIsConsideredToBeIncluded = BlockNumber(currentBlockNumber + 1);
1641
-
1642
- return createTxMessageValidators(
1629
+ return createFirstStageTxValidationsForGossipedTransactions(
1643
1630
  nextSlotTimestamp,
1644
- blockNumberInWhichTheTxIsConsideredToBeIncluded,
1631
+ blockNumber,
1645
1632
  this.worldStateSynchronizer,
1646
1633
  gasFees,
1647
1634
  this.config.l1ChainId,
1648
1635
  this.config.rollupVersion,
1649
1636
  protocolContractsHash,
1650
1637
  this.archiver,
1651
- this.proofVerifier,
1652
1638
  !this.config.disableTransactions,
1653
1639
  allowedInSetup,
1654
1640
  this.logger.getBindings(),
1655
1641
  );
1656
1642
  }
1657
1643
 
1644
+ /** Creates the second stage (expensive proof verification) validators for gossiped transactions. */
1645
+ protected createSecondStageMessageValidators(): Record<string, TransactionValidator> {
1646
+ return createSecondStageTxValidationsForGossipedTransactions(this.proofVerifier, this.logger.getBindings());
1647
+ }
1648
+
1658
1649
  /**
1659
1650
  * Run validations on a tx.
1660
1651
  * @param tx - The tx to validate.
@@ -1663,7 +1654,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1663
1654
  */
1664
1655
  private async runValidations(
1665
1656
  tx: Tx,
1666
- messageValidators: Record<string, MessageValidator>,
1657
+ messageValidators: Record<string, TransactionValidator>,
1667
1658
  ): Promise<ValidationOutcome> {
1668
1659
  const validationPromises = Object.entries(messageValidators).map(async ([name, { validator, severity }]) => {
1669
1660
  const { result } = await validator.validateTx(tx);
@@ -8,7 +8,6 @@ import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
8
8
  import { Tx, TxArray, TxHash } from '@aztec/stdlib/tx';
9
9
 
10
10
  import type { PeerId } from '@libp2p/interface';
11
- import { peerIdFromString } from '@libp2p/peer-id';
12
11
 
13
12
  import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_tracker.js';
14
13
  import { ReqRespSubProtocol } from '.././interface.js';
@@ -90,10 +89,9 @@ export class BatchTxRequester {
90
89
  if (this.opts.peerCollection) {
91
90
  this.peers = this.opts.peerCollection;
92
91
  } else {
93
- const initialPeers = this.p2pService.connectionSampler.getPeerListSortedByConnectionCountAsc();
94
92
  const badPeerThreshold = this.opts.badPeerThreshold ?? DEFAULT_BATCH_TX_REQUESTER_BAD_PEER_THRESHOLD;
95
93
  this.peers = new PeerCollection(
96
- initialPeers,
94
+ this.p2pService.connectionSampler,
97
95
  this.pinnedPeer,
98
96
  this.dateProvider,
99
97
  badPeerThreshold,
@@ -227,7 +225,6 @@ export class BatchTxRequester {
227
225
  * Starts dumb worker loops
228
226
  * */
229
227
  private async dumbRequester() {
230
- const nextPeerIndex = this.makeRoundRobinIndexer();
231
228
  const nextBatchIndex = this.makeRoundRobinIndexer();
232
229
 
233
230
  // Chunk missing tx hashes into batches of txBatchSize, wrapping around to ensure no peer gets less than txBatchSize
@@ -263,15 +260,9 @@ export class BatchTxRequester {
263
260
  return { blockRequest, txs };
264
261
  };
265
262
 
266
- const nextPeer = () => {
267
- const peers = this.peers.getDumbPeersToQuery();
268
- const idx = nextPeerIndex(() => peers.length);
269
- return idx === undefined ? undefined : peerIdFromString(peers[idx]);
270
- };
271
-
272
- const workerCount = Math.min(this.dumbParallelWorkerCount, this.peers.getAllPeers().size);
263
+ const workerCount = this.dumbParallelWorkerCount;
273
264
  const workers = Array.from({ length: workerCount }, (_, index) =>
274
- this.dumbWorkerLoop(nextPeer, makeRequest, index + 1),
265
+ this.dumbWorkerLoop(this.peers.nextDumbPeerToQuery.bind(this.peers), makeRequest, index + 1),
275
266
  );
276
267
 
277
268
  await Promise.allSettled(workers);
@@ -332,14 +323,6 @@ export class BatchTxRequester {
332
323
  * Starts smart worker loops
333
324
  * */
334
325
  private async smartRequester() {
335
- const nextPeerIndex = this.makeRoundRobinIndexer();
336
-
337
- const nextPeer = () => {
338
- const peers = this.peers.getSmartPeersToQuery();
339
- const idx = nextPeerIndex(() => peers.length);
340
- return idx === undefined ? undefined : peerIdFromString(peers[idx]);
341
- };
342
-
343
326
  const makeRequest = (pid: PeerId) => {
344
327
  const txs = this.txsMetadata.getTxsToRequestFromThePeer(pid);
345
328
  const blockRequest = BlockTxsRequest.fromTxsSourceAndMissingTxs(this.blockTxsSource, txs);
@@ -350,9 +333,8 @@ export class BatchTxRequester {
350
333
  return { blockRequest, txs };
351
334
  };
352
335
 
353
- const workers = Array.from(
354
- { length: Math.min(this.smartParallelWorkerCount, this.peers.getAllPeers().size) },
355
- (_, index) => this.smartWorkerLoop(nextPeer, makeRequest, index + 1),
336
+ const workers = Array.from({ length: this.smartParallelWorkerCount }, (_, index) =>
337
+ this.smartWorkerLoop(this.peers.nextSmartPeerToQuery.bind(this.peers), makeRequest, index + 1),
356
338
  );
357
339
 
358
340
  await Promise.allSettled(workers);
@@ -387,26 +369,18 @@ export class BatchTxRequester {
387
369
  if (weRanOutOfPeersToQuery) {
388
370
  this.logger.debug(`Worker loop smart: No more peers to query`);
389
371
 
390
- // If there are no more dumb peers to query then none of our peers can become smart,
391
- // thus we can simply exit this worker
392
- const noMoreDumbPeersToQuery = this.peers.getDumbPeersToQuery().length === 0;
393
- if (noMoreDumbPeersToQuery) {
394
- // These might be either smart peers that will get unblocked after _some time_
395
- const nextSmartPeerDelay = this.peers.getNextSmartPeerAvailabilityDelayMs();
396
- const thereAreSomeRateLimitedSmartPeers = nextSmartPeerDelay !== undefined;
397
- if (thereAreSomeRateLimitedSmartPeers) {
398
- await this.sleepClampedToDeadline(nextSmartPeerDelay);
399
- continue;
400
- }
401
-
402
- this.logger.debug(`Worker loop smart: No more smart peers to query killing ${workerIndex}`);
403
- break;
372
+ // If we have rate limited peers wait for them.
373
+ const nextSmartPeerDelay = this.peers.getNextSmartPeerAvailabilityDelayMs();
374
+ const thereAreSomeRateLimitedSmartPeers = nextSmartPeerDelay !== undefined;
375
+ if (thereAreSomeRateLimitedSmartPeers) {
376
+ await this.sleepClampedToDeadline(nextSmartPeerDelay);
377
+ continue;
404
378
  }
405
379
 
406
- // Otherwise there are still some dumb peers that could become smart.
407
380
  // We end up here when all known smart peers became temporarily unavailable via combination of
408
381
  // (bad, in-flight, or rate-limited) or in some weird scenario all current smart peers turn bad which is permanent
409
- // but dumb peers still exist that could become smart.
382
+ // but there are dumb peers that could be promoted
383
+ // or new peer can join as dumb and be promoted later
410
384
  //
411
385
  // When a dumb peer responds with valid txIndices, it gets
412
386
  // promoted to smart and releases the semaphore, waking this worker.
@@ -599,9 +573,7 @@ export class BatchTxRequester {
599
573
  this.markTxsPeerHas(peerId, response);
600
574
 
601
575
  // Unblock smart workers
602
- if (this.peers.getSmartPeersToQuery().length <= this.smartParallelWorkerCount) {
603
- this.smartRequesterSemaphore.release();
604
- }
576
+ this.smartRequesterSemaphore.release();
605
577
  }
606
578
 
607
579
  private isBlockResponseValid(response: BlockTxsResponse): boolean {
@@ -2,18 +2,22 @@ import type { DateProvider } from '@aztec/foundation/timer';
2
2
  import type { PeerErrorSeverity } from '@aztec/stdlib/p2p';
3
3
 
4
4
  import type { PeerId } from '@libp2p/interface';
5
+ import { peerIdFromString } from '@libp2p/peer-id';
5
6
 
7
+ import type { ConnectionSampler } from '../connection-sampler/connection_sampler.js';
6
8
  import { DEFAULT_BATCH_TX_REQUESTER_BAD_PEER_THRESHOLD } from './config.js';
7
9
  import type { IPeerPenalizer } from './interface.js';
8
10
 
9
11
  export const RATE_LIMIT_EXCEEDED_PEER_CACHE_TTL = 1000; // 1s
10
12
 
11
13
  export interface IPeerCollection {
12
- getAllPeers(): Set<string>;
13
- getSmartPeers(): Set<string>;
14
14
  markPeerSmart(peerId: PeerId): void;
15
- getSmartPeersToQuery(): Array<string>;
16
- getDumbPeersToQuery(): Array<string>;
15
+
16
+ /** Sample next peer in round-robin fashion. No smart peers if returns undefined */
17
+ nextSmartPeerToQuery(): PeerId | undefined;
18
+ /** Sample next peer in round-robin fashion. No dumb peers if returns undefined */
19
+ nextDumbPeerToQuery(): PeerId | undefined;
20
+
17
21
  thereAreSomeDumbRatelimitExceededPeers(): boolean;
18
22
  penalisePeer(peerId: PeerId, severity: PeerErrorSeverity): void;
19
23
  unMarkPeerAsBad(peerId: PeerId): void;
@@ -28,8 +32,6 @@ export interface IPeerCollection {
28
32
  }
29
33
 
30
34
  export class PeerCollection implements IPeerCollection {
31
- private readonly peers;
32
-
33
35
  private readonly smartPeers = new Set<string>();
34
36
  private readonly inFlightPeers = new Set<string>();
35
37
  private readonly rateLimitExceededPeers = new Map<string, number>();
@@ -37,46 +39,60 @@ export class PeerCollection implements IPeerCollection {
37
39
  private readonly badPeers = new Set<string>();
38
40
 
39
41
  constructor(
40
- initialPeers: PeerId[],
42
+ private readonly connectionSampler: Pick<ConnectionSampler, 'getPeerListSortedByConnectionCountAsc'>,
41
43
  private readonly pinnedPeerId: PeerId | undefined,
42
44
  private readonly dateProvider: DateProvider,
43
45
  private readonly badPeerThreshold: number = DEFAULT_BATCH_TX_REQUESTER_BAD_PEER_THRESHOLD,
44
46
  private readonly peerPenalizer?: IPeerPenalizer,
45
47
  ) {
46
- this.peers = new Set(initialPeers.map(peer => peer.toString()));
47
-
48
- // Pinned peer is treaded specially, always mark it as in-flight
48
+ // Pinned peer is treated specially, always mark it as in-flight
49
49
  // and never return it as part of smart/dumb peers
50
50
  if (this.pinnedPeerId) {
51
51
  const peerIdStr = this.pinnedPeerId.toString();
52
52
  this.inFlightPeers.add(peerIdStr);
53
- this.peers.delete(peerIdStr);
54
53
  }
55
54
  }
56
55
 
57
- public getAllPeers(): Set<string> {
58
- return this.peers;
56
+ public markPeerSmart(peerId: PeerId): void {
57
+ this.smartPeers.add(peerId.toString());
59
58
  }
60
59
 
61
- public getSmartPeers(): Set<string> {
62
- return this.smartPeers;
60
+ // We keep track of all peers that are queried for peer sampling algorithm
61
+ private queriedSmartPeers: Set<string> = new Set<string>();
62
+ private queriedDumbPeers: Set<string> = new Set<string>();
63
+
64
+ private static nextPeer(allPeers: Set<string>, queried: Set<string>): PeerId | undefined {
65
+ if (allPeers.size === 0) {
66
+ return undefined;
67
+ }
68
+ const availablePeers = allPeers.difference(queried);
69
+ let [first] = availablePeers;
70
+ if (first === undefined) {
71
+ // We queried all peers. Start over
72
+ [first] = allPeers;
73
+ queried.clear();
74
+ }
75
+ queried.add(first);
76
+ return peerIdFromString(first);
63
77
  }
64
78
 
65
- public markPeerSmart(peerId: PeerId): void {
66
- this.smartPeers.add(peerId.toString());
79
+ public nextSmartPeerToQuery(): PeerId | undefined {
80
+ return PeerCollection.nextPeer(this.availableSmartPeers, this.queriedSmartPeers);
81
+ }
82
+
83
+ public nextDumbPeerToQuery(): PeerId | undefined {
84
+ return PeerCollection.nextPeer(this.availableDumbPeers, this.queriedDumbPeers);
67
85
  }
68
86
 
69
- public getSmartPeersToQuery(): Array<string> {
70
- return Array.from(
87
+ private get availableSmartPeers(): Set<string> {
88
+ return this.peers.intersection(
71
89
  this.smartPeers.difference(this.getBadPeers().union(this.inFlightPeers).union(this.getRateLimitExceededPeers())),
72
90
  );
73
91
  }
74
92
 
75
- public getDumbPeersToQuery(): Array<string> {
76
- return Array.from(
77
- this.peers.difference(
78
- this.smartPeers.union(this.getBadPeers()).union(this.inFlightPeers).union(this.getRateLimitExceededPeers()),
79
- ),
93
+ private get availableDumbPeers(): Set<string> {
94
+ return this.peers.difference(
95
+ this.smartPeers.union(this.getBadPeers()).union(this.inFlightPeers).union(this.getRateLimitExceededPeers()),
80
96
  );
81
97
  }
82
98
 
@@ -202,4 +218,27 @@ export class PeerCollection implements IPeerCollection {
202
218
 
203
219
  return minExpiry! - now;
204
220
  }
221
+
222
+ private orderedPeers: Set<string> = new Set();
223
+
224
+ private get peers(): Set<string> {
225
+ const pinnedStr = this.pinnedPeerId?.toString();
226
+ const currentlyConnected = new Set(
227
+ this.connectionSampler
228
+ .getPeerListSortedByConnectionCountAsc()
229
+ .map(p => p.toString())
230
+ .filter(p => p !== pinnedStr),
231
+ );
232
+
233
+ // Remove disconnected peers, preserving order of the rest.
234
+ this.orderedPeers = this.orderedPeers.intersection(currentlyConnected);
235
+
236
+ // Append newly connected peers at the end (lowest priority).
237
+ for (const peer of currentlyConnected) {
238
+ if (!this.orderedPeers.has(peer)) {
239
+ this.orderedPeers.add(peer);
240
+ }
241
+ }
242
+ return this.orderedPeers;
243
+ }
205
244
  }
@@ -1,7 +1,7 @@
1
1
  import type { ClientProtocolCircuitVerifier } from '@aztec/stdlib/interfaces/server';
2
2
  import { Tx, type TxValidationResult, type TxValidator } from '@aztec/stdlib/tx';
3
3
 
4
- import { createTxReqRespValidator } from '../../../msg_validators/tx_validator/factory.js';
4
+ import { createTxValidatorForReqResponseReceivedTxs } from '../../../msg_validators/index.js';
5
5
 
6
6
  export interface BatchRequestTxValidatorConfig {
7
7
  l1ChainId: number;
@@ -29,7 +29,7 @@ export class BatchRequestTxValidator implements IBatchRequestTxValidator {
29
29
  }
30
30
 
31
31
  static createRequestedTxValidator(config: BatchRequestTxValidatorConfig): TxValidator {
32
- return createTxReqRespValidator(config.proofVerifier, {
32
+ return createTxValidatorForReqResponseReceivedTxs(config.proofVerifier, {
33
33
  l1ChainId: config.l1ChainId,
34
34
  rollupVersion: config.rollupVersion,
35
35
  });
@@ -139,7 +139,7 @@ export interface P2PService {
139
139
  /** Returns the number of peers in the GossipSub mesh for a given topic type. */
140
140
  getGossipMeshPeerCount(topicType: TopicType): number;
141
141
 
142
- validate(txs: Tx[]): Promise<void>;
142
+ validateTxsReceivedInBlockProposal(txs: Tx[]): Promise<void>;
143
143
 
144
144
  addReqRespSubProtocol(
145
145
  subProtocol: ReqRespSubProtocol,
@@ -25,7 +25,7 @@ export class TxProvider implements ITxProvider {
25
25
  constructor(
26
26
  private txCollection: TxCollection,
27
27
  private txPool: TxPoolV2,
28
- private txValidator: Pick<P2PClient, 'validate'>,
28
+ private blockProposalTransactionValidator: Pick<P2PClient, 'validateTxsReceivedInBlockProposal'>,
29
29
  private log: Logger = createLogger('p2p:tx-collector'),
30
30
  client: TelemetryClient = getTelemetryClient(),
31
31
  ) {
@@ -227,7 +227,7 @@ export class TxProvider implements ITxProvider {
227
227
  if (txs.length === 0) {
228
228
  return;
229
229
  }
230
- await this.txValidator.validate(txs);
230
+ await this.blockProposalTransactionValidator.validateTxsReceivedInBlockProposal(txs);
231
231
  await this.txPool.addProtectedTxs(txs, blockHeader);
232
232
  }
233
233
  }
@@ -76,7 +76,7 @@ export class InMemoryTxPool extends EventEmitter implements TxPoolV2 {
76
76
  return Promise.resolve({ accepted, ignored: [], rejected: [] });
77
77
  }
78
78
 
79
- canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored' | 'rejected'> {
79
+ canAddPendingTx(tx: Tx): Promise<'accepted' | 'ignored'> {
80
80
  const key = tx.getTxHash().toString();
81
81
  if (this.txsByHash.has(key)) {
82
82
  return Promise.resolve('ignored');