@aztec/p2p 0.0.1-commit.7cf39cb55 → 0.0.1-commit.808bf7f90

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 (187) hide show
  1. package/dest/client/factory.d.ts +3 -2
  2. package/dest/client/factory.d.ts.map +1 -1
  3. package/dest/client/factory.js +5 -3
  4. package/dest/client/interface.d.ts +7 -9
  5. package/dest/client/interface.d.ts.map +1 -1
  6. package/dest/client/p2p_client.d.ts +4 -7
  7. package/dest/client/p2p_client.d.ts.map +1 -1
  8. package/dest/client/p2p_client.js +38 -14
  9. package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +5 -5
  10. package/dest/config.d.ts +4 -6
  11. package/dest/config.d.ts.map +1 -1
  12. package/dest/config.js +0 -5
  13. package/dest/errors/tx-pool.error.d.ts +8 -0
  14. package/dest/errors/tx-pool.error.d.ts.map +1 -0
  15. package/dest/errors/tx-pool.error.js +9 -0
  16. package/dest/mem_pools/attestation_pool/mocks.d.ts +2 -2
  17. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
  18. package/dest/mem_pools/attestation_pool/mocks.js +2 -2
  19. package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +3 -3
  20. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +3 -1
  21. package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -1
  22. package/dest/mem_pools/tx_pool_v2/deleted_pool.js +9 -0
  23. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts +3 -3
  24. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.d.ts.map +1 -1
  25. package/dest/mem_pools/tx_pool_v2/eviction/eviction_manager.js +18 -9
  26. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -3
  27. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts +3 -3
  28. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.d.ts.map +1 -1
  29. package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.js +10 -4
  30. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts +2 -2
  31. package/dest/mem_pools/tx_pool_v2/eviction/index.d.ts.map +1 -1
  32. package/dest/mem_pools/tx_pool_v2/eviction/index.js +1 -1
  33. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts +48 -5
  34. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.d.ts.map +1 -1
  35. package/dest/mem_pools/tx_pool_v2/eviction/interfaces.js +8 -0
  36. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +4 -4
  37. package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +5 -5
  38. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +2 -2
  39. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
  40. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +12 -6
  41. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts +4 -4
  42. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.d.ts.map +1 -1
  43. package/dest/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.js +14 -4
  44. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts +3 -3
  45. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.d.ts.map +1 -1
  46. package/dest/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.js +2 -2
  47. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts +15 -0
  48. package/dest/mem_pools/tx_pool_v2/instrumentation.d.ts.map +1 -0
  49. package/dest/mem_pools/tx_pool_v2/instrumentation.js +43 -0
  50. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +12 -2
  51. package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
  52. package/dest/mem_pools/tx_pool_v2/interfaces.js +3 -1
  53. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +7 -5
  54. package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
  55. package/dest/mem_pools/tx_pool_v2/tx_metadata.js +29 -5
  56. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +5 -2
  57. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
  58. package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +12 -2
  59. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +5 -2
  60. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
  61. package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -5
  62. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +10 -4
  63. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
  64. package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +240 -124
  65. package/dest/msg_validators/tx_validator/gas_validator.d.ts +1 -1
  66. package/dest/msg_validators/tx_validator/gas_validator.d.ts.map +1 -1
  67. package/dest/msg_validators/tx_validator/gas_validator.js +3 -3
  68. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +2 -2
  69. package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
  70. package/dest/msg_validators/tx_validator/timestamp_validator.js +6 -6
  71. package/dest/services/dummy_service.d.ts +3 -2
  72. package/dest/services/dummy_service.d.ts.map +1 -1
  73. package/dest/services/dummy_service.js +3 -0
  74. package/dest/services/encoding.d.ts +1 -1
  75. package/dest/services/encoding.d.ts.map +1 -1
  76. package/dest/services/encoding.js +2 -1
  77. package/dest/services/gossipsub/topic_score_params.d.ts +18 -6
  78. package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -1
  79. package/dest/services/gossipsub/topic_score_params.js +32 -10
  80. package/dest/services/libp2p/libp2p_service.d.ts +2 -1
  81. package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
  82. package/dest/services/libp2p/libp2p_service.js +7 -3
  83. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts +4 -3
  84. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.d.ts.map +1 -1
  85. package/dest/services/reqresp/batch-tx-requester/batch_tx_requester.js +5 -9
  86. package/dest/services/reqresp/batch-tx-requester/interface.d.ts +2 -6
  87. package/dest/services/reqresp/batch-tx-requester/interface.d.ts.map +1 -1
  88. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts +10 -13
  89. package/dest/services/reqresp/batch-tx-requester/missing_txs.d.ts.map +1 -1
  90. package/dest/services/reqresp/batch-tx-requester/missing_txs.js +25 -46
  91. package/dest/services/service.d.ts +4 -2
  92. package/dest/services/service.d.ts.map +1 -1
  93. package/dest/services/tx_collection/fast_tx_collection.d.ts +1 -1
  94. package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
  95. package/dest/services/tx_collection/fast_tx_collection.js +39 -33
  96. package/dest/services/tx_collection/file_store_tx_collection.d.ts +1 -1
  97. package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -1
  98. package/dest/services/tx_collection/file_store_tx_collection.js +4 -2
  99. package/dest/services/tx_collection/file_store_tx_source.d.ts +15 -6
  100. package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -1
  101. package/dest/services/tx_collection/file_store_tx_source.js +47 -16
  102. package/dest/services/tx_collection/instrumentation.d.ts +1 -1
  103. package/dest/services/tx_collection/instrumentation.d.ts.map +1 -1
  104. package/dest/services/tx_collection/instrumentation.js +2 -1
  105. package/dest/services/tx_collection/missing_txs_tracker.d.ts +32 -0
  106. package/dest/services/tx_collection/missing_txs_tracker.d.ts.map +1 -0
  107. package/dest/services/tx_collection/missing_txs_tracker.js +27 -0
  108. package/dest/services/tx_collection/proposal_tx_collector.d.ts +7 -6
  109. package/dest/services/tx_collection/proposal_tx_collector.d.ts.map +1 -1
  110. package/dest/services/tx_collection/proposal_tx_collector.js +5 -4
  111. package/dest/services/tx_collection/slow_tx_collection.d.ts +2 -2
  112. package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
  113. package/dest/services/tx_collection/slow_tx_collection.js +10 -8
  114. package/dest/services/tx_collection/tx_collection.d.ts +5 -4
  115. package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
  116. package/dest/services/tx_collection/tx_collection_sink.d.ts +6 -5
  117. package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
  118. package/dest/services/tx_collection/tx_collection_sink.js +13 -22
  119. package/dest/services/tx_collection/tx_source.d.ts +8 -3
  120. package/dest/services/tx_collection/tx_source.d.ts.map +1 -1
  121. package/dest/services/tx_collection/tx_source.js +19 -2
  122. package/dest/services/tx_file_store/tx_file_store.js +1 -1
  123. package/dest/test-helpers/mock-pubsub.d.ts +3 -2
  124. package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
  125. package/dest/test-helpers/mock-pubsub.js +6 -0
  126. package/dest/test-helpers/testbench-utils.d.ts +5 -2
  127. package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
  128. package/dest/test-helpers/testbench-utils.js +1 -1
  129. package/dest/testbench/p2p_client_testbench_worker.d.ts +2 -2
  130. package/dest/testbench/p2p_client_testbench_worker.d.ts.map +1 -1
  131. package/dest/testbench/p2p_client_testbench_worker.js +8 -8
  132. package/dest/util.d.ts +2 -2
  133. package/dest/util.d.ts.map +1 -1
  134. package/package.json +14 -14
  135. package/src/client/factory.ts +6 -2
  136. package/src/client/interface.ts +14 -9
  137. package/src/client/p2p_client.ts +44 -17
  138. package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +18 -8
  139. package/src/config.ts +2 -10
  140. package/src/errors/tx-pool.error.ts +12 -0
  141. package/src/mem_pools/attestation_pool/mocks.ts +2 -1
  142. package/src/mem_pools/tx_pool/README.md +1 -1
  143. package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +3 -3
  144. package/src/mem_pools/tx_pool_v2/README.md +1 -1
  145. package/src/mem_pools/tx_pool_v2/deleted_pool.ts +11 -0
  146. package/src/mem_pools/tx_pool_v2/eviction/eviction_manager.ts +21 -8
  147. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +3 -3
  148. package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_pre_add_rule.ts +15 -4
  149. package/src/mem_pools/tx_pool_v2/eviction/index.ts +4 -0
  150. package/src/mem_pools/tx_pool_v2/eviction/interfaces.ts +49 -4
  151. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +4 -4
  152. package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +5 -5
  153. package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +12 -9
  154. package/src/mem_pools/tx_pool_v2/eviction/low_priority_pre_add_rule.ts +24 -6
  155. package/src/mem_pools/tx_pool_v2/eviction/nullifier_conflict_rule.ts +3 -3
  156. package/src/mem_pools/tx_pool_v2/instrumentation.ts +69 -0
  157. package/src/mem_pools/tx_pool_v2/interfaces.ts +11 -2
  158. package/src/mem_pools/tx_pool_v2/tx_metadata.ts +40 -9
  159. package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +14 -3
  160. package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +11 -6
  161. package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +262 -117
  162. package/src/msg_validators/tx_validator/gas_validator.ts +11 -3
  163. package/src/msg_validators/tx_validator/timestamp_validator.ts +7 -7
  164. package/src/services/dummy_service.ts +5 -1
  165. package/src/services/encoding.ts +2 -1
  166. package/src/services/gossipsub/README.md +29 -14
  167. package/src/services/gossipsub/topic_score_params.ts +49 -13
  168. package/src/services/libp2p/libp2p_service.ts +7 -2
  169. package/src/services/reqresp/batch-tx-requester/batch_tx_requester.ts +6 -6
  170. package/src/services/reqresp/batch-tx-requester/interface.ts +1 -5
  171. package/src/services/reqresp/batch-tx-requester/missing_txs.ts +23 -71
  172. package/src/services/service.ts +10 -1
  173. package/src/services/tx_collection/fast_tx_collection.ts +51 -30
  174. package/src/services/tx_collection/file_store_tx_collection.ts +7 -3
  175. package/src/services/tx_collection/file_store_tx_source.ts +61 -17
  176. package/src/services/tx_collection/instrumentation.ts +7 -1
  177. package/src/services/tx_collection/missing_txs_tracker.ts +52 -0
  178. package/src/services/tx_collection/proposal_tx_collector.ts +8 -7
  179. package/src/services/tx_collection/slow_tx_collection.ts +8 -9
  180. package/src/services/tx_collection/tx_collection.ts +4 -3
  181. package/src/services/tx_collection/tx_collection_sink.ts +15 -29
  182. package/src/services/tx_collection/tx_source.ts +22 -3
  183. package/src/services/tx_file_store/tx_file_store.ts +1 -1
  184. package/src/test-helpers/mock-pubsub.ts +10 -0
  185. package/src/test-helpers/testbench-utils.ts +2 -2
  186. package/src/testbench/p2p_client_testbench_worker.ts +20 -13
  187. package/src/util.ts +7 -1
@@ -1,6 +1,6 @@
1
1
  import type { EthAddress } from '@aztec/foundation/eth-address';
2
2
  import type { PeerInfo } from '@aztec/stdlib/interfaces/server';
3
- import type { Gossipable, PeerErrorSeverity } from '@aztec/stdlib/p2p';
3
+ import type { Gossipable, PeerErrorSeverity, TopicType } from '@aztec/stdlib/p2p';
4
4
  import { Tx, TxHash } from '@aztec/stdlib/tx';
5
5
 
6
6
  import type { PeerId } from '@libp2p/interface';
@@ -44,6 +44,10 @@ export class DummyP2PService implements P2PService {
44
44
  return [];
45
45
  }
46
46
 
47
+ getGossipMeshPeerCount(_topicType: TopicType): number {
48
+ return 0;
49
+ }
50
+
47
51
  /**
48
52
  * Starts the dummy implementation.
49
53
  * @returns A resolved promise.
@@ -58,7 +58,8 @@ const DefaultMaxSizesKb: Record<TopicType, number> = {
58
58
  // Proposals may carry some tx objects, so we allow a larger size capped at 10mb
59
59
  // Note this may not be enough for carrying all tx objects in a block
60
60
  [TopicType.block_proposal]: 1024 * 10,
61
- // TODO(palla/mbps): Check size for checkpoint proposal
61
+ // Checkpoint proposals carry almost the same data as a block proposal (see the lastBlockProposal)
62
+ // Only diff is an additional header, which is pretty small compared to the 10mb limit
62
63
  [TopicType.checkpoint_proposal]: 1024 * 10,
63
64
  };
64
65
 
@@ -40,7 +40,7 @@ We configure all parameters (P1-P4) with values calculated dynamically from netw
40
40
  | P3b: meshFailurePenalty | -34 per topic | Sticky penalty after pruning |
41
41
  | P4: invalidMessageDeliveries | -20 per message | Attack detection |
42
42
 
43
- **Important:** P1 and P2 are only enabled on topics with P3 enabled (block_proposal, checkpoint_proposal, checkpoint_attestation). The tx topic has all scoring disabled except P4, to prevent free positive score accumulation that would offset penalties from other topics.
43
+ **Important:** P1 and P2 are only enabled on topics with P3 enabled. By default, P3 is enabled for checkpoint_proposal and checkpoint_attestation (2 topics). Block proposal scoring is controlled by `expectedBlockProposalsPerSlot` (current default: `0`, including when env var is unset, so disabled) - see [Block Proposals](#block-proposals-block_proposal) for details. The tx topic has all scoring disabled except P4, to prevent free positive score accumulation that would offset penalties from other topics.
44
44
 
45
45
  ## Exponential Decay
46
46
 
@@ -217,7 +217,21 @@ Transactions are submitted unpredictably by users, so we cannot set meaningful d
217
217
 
218
218
  ### Block Proposals (block_proposal)
219
219
 
220
- In Multi-Block-Per-Slot (MBPS) mode, N-1 block proposals are gossiped per slot (the last block is bundled with the checkpoint). In single-block mode, this is 0.
220
+ Block proposal scoring is controlled by the `expectedBlockProposalsPerSlot` config (`SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT` env var):
221
+
222
+ | Config Value | Behavior |
223
+ |-------------|----------|
224
+ | `0` (current default) | Block proposal P3 scoring is **disabled** |
225
+ | Positive number | Uses the provided value as expected proposals per slot |
226
+ | `undefined` | Falls back to `blocksPerSlot - 1` (MBPS mode: N-1, single block: 0) |
227
+
228
+ **Current behavior note:** In the current implementation, if `SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT` is not set, config mapping applies `0` by default (scoring disabled). The `undefined` fallback above is currently reachable only if the value is explicitly provided as `undefined` in code.
229
+
230
+ **Future intent:** Once throughput is stable, we may change env parsing/defaults so an unset env var resolves to `undefined` again (re-enabling automatic fallback to `blocksPerSlot - 1`).
231
+
232
+ **Why disabled by default?** In MBPS mode, gossipsub expects N-1 block proposals per slot. When transaction throughput is low (as expected at launch), fewer blocks are actually built, causing peers to be incorrectly penalized for under-delivering block proposals. The default of 0 disables this scoring. Set to a positive value when throughput increases and block production is consistent.
233
+
234
+ In MBPS mode (when enabled), N-1 block proposals are gossiped per slot (the last block is bundled with the checkpoint). In single-block mode, this is 0.
221
235
 
222
236
  ### Checkpoint Proposals (checkpoint_proposal)
223
237
 
@@ -241,6 +255,7 @@ The scoring parameters depend on:
241
255
  | `targetCommitteeSize` | L1RollupConstants | 48 |
242
256
  | `heartbeatInterval` | P2PConfig.gossipsubInterval | 700ms |
243
257
  | `blockDurationMs` | P2PConfig.blockDurationMs | undefined (single block) |
258
+ | `expectedBlockProposalsPerSlot` | P2PConfig.expectedBlockProposalsPerSlot | 0 (disabled; current unset-env behavior) |
244
259
 
245
260
  ## Invalid Message Handling (P4)
246
261
 
@@ -320,9 +335,9 @@ Conversely, if topic scores are low, a peer slightly above the disconnect thresh
320
335
 
321
336
  Topic scores provide **burst response** to attacks, while app score provides **stable baseline**:
322
337
 
323
- - P1 (time in mesh): Max +8 per topic (+24 across 3 topics)
324
- - P2 (first deliveries): Max +25 per topic (+75 across 3 topics, but decays fast)
325
- - P3 (under-delivery): Max -34 per topic (-102 across 3 topics in MBPS; -68 in single-block mode)
338
+ - P1 (time in mesh): Max +8 per topic (+16 default, +24 with block proposal scoring enabled)
339
+ - P2 (first deliveries): Max +25 per topic (+50 default, +75 with block proposal scoring, but decays fast)
340
+ - P3 (under-delivery): Max -34 per topic (-68 default with 2 topics, -102 with block proposal scoring enabled)
326
341
  - P4 (invalid messages): -20 per invalid message, can spike to -2000+ during attacks
327
342
 
328
343
  Example attack scenario:
@@ -373,21 +388,21 @@ When a peer is pruned from the mesh:
373
388
  3. **P3b captures the penalty**: The P3 deficit at prune time becomes P3b, which decays slowly
374
389
 
375
390
  After pruning, the peer's score consists mainly of P3b:
376
- - **Total P3b across 3 topics: -102** (max)
391
+ - **Total P3b: -68** (default, 2 topics) or **-102** (with block proposal scoring enabled, 3 topics)
377
392
  - **Recovery time**: P3b decays to ~1% over one decay window (2-5 slots = 2-6 minutes)
378
393
  - **Grafting eligibility**: Peer can be grafted when score ≥ 0, but asymptotic decay means recovery is slow
379
394
 
380
395
  ### Why Non-Contributors Aren't Disconnected
381
396
 
382
- With P3b capped at -102 total after pruning (MBPS mode). In single-block mode, the cap is -68:
397
+ With P3b capped at -68 (default, 2 topics) or -102 (with block proposal scoring, 3 topics) after pruning:
383
398
 
384
399
  | Threshold | Value | P3b Score | Triggered? |
385
400
  |-----------|-------|-----------|------------|
386
- | gossipThreshold | -500 | -102 (MBPS) / -68 (single) | No |
387
- | publishThreshold | -1000 | -102 (MBPS) / -68 (single) | No |
388
- | graylistThreshold | -2000 | -102 (MBPS) / -68 (single) | No |
401
+ | gossipThreshold | -500 | -68 (default) / -102 (block scoring on) | No |
402
+ | publishThreshold | -1000 | -68 (default) / -102 (block scoring on) | No |
403
+ | graylistThreshold | -2000 | -68 (default) / -102 (block scoring on) | No |
389
404
 
390
- **A score of -102 (MBPS) or -68 (single-block) is well above -500**, so non-contributing peers:
405
+ **A score of -68 or -102 is well above -500**, so non-contributing peers:
391
406
  - Are pruned from mesh (good - stops them slowing propagation)
392
407
  - Still receive gossip (can recover by reconnecting/restarting)
393
408
  - Are NOT disconnected unless they also have application-level penalties
@@ -547,7 +562,7 @@ What happens when a peer experiences a network outage and stops delivering messa
547
562
  While the peer is disconnected:
548
563
 
549
564
  1. **P3 penalty accumulates**: The message delivery counter decays toward 0, causing increasing P3 penalty
550
- 2. **Max P3 penalty reached**: Once counter drops below threshold, penalty hits -34 per topic (-102 total in MBPS; -68 single-block)
565
+ 2. **Max P3 penalty reached**: Once counter drops below threshold, penalty hits -34 per topic (-68 default, -102 with block proposal scoring)
551
566
  3. **Mesh pruning**: Topic score goes negative → peer is pruned from mesh
552
567
  4. **P3b captures penalty**: The P3 deficit at prune time becomes P3b (sticky penalty)
553
568
 
@@ -569,13 +584,13 @@ Note: If the peer just joined the mesh, P3 penalties only start after
569
584
  During a network outage, the peer:
570
585
  - **Does NOT send invalid messages** → No P4 penalty
571
586
  - **Does NOT violate protocols** → No application-level penalty
572
- - **Only accumulates topic-level penalties** → Max -102 (P3b, MBPS) or -68 (single-block)
587
+ - **Only accumulates topic-level penalties** → Max -68 (default) or -102 (with block proposal scoring)
573
588
 
574
589
  This is the crucial difference from malicious behavior:
575
590
 
576
591
  | Scenario | App Score | Topic Score | Total | Threshold Hit |
577
592
  |----------|-----------|-------------|-------|---------------|
578
- | Network outage | 0 | -102 (MBPS) / -68 (single) | -102 / -68 | None |
593
+ | Network outage | 0 | -68 (default) / -102 (block scoring on) | -68 / -102 | None |
579
594
  | Validation failure | -50 | -20 | -520 | gossipThreshold |
580
595
  | Malicious peer | -100 | -2000+ | -2100+ | graylistThreshold |
581
596
 
@@ -15,6 +15,8 @@ export type TopicScoringNetworkParams = {
15
15
  targetCommitteeSize: number;
16
16
  /** Duration per block in milliseconds when building multiple blocks per slot. If undefined, single block mode. */
17
17
  blockDurationMs?: number;
18
+ /** Expected number of block proposals per slot for scoring override. 0 disables scoring, undefined falls back to blocksPerSlot - 1. */
19
+ expectedBlockProposalsPerSlot?: number;
18
20
  };
19
21
 
20
22
  /**
@@ -89,18 +91,41 @@ export function computeThreshold(convergence: number, conservativeFactor: number
89
91
  return convergence * conservativeFactor;
90
92
  }
91
93
 
94
+ /**
95
+ * Determines the effective expected block proposals per slot for scoring.
96
+ * Returns undefined if scoring should be disabled, or a positive number if enabled.
97
+ *
98
+ * @param blocksPerSlot - Number of blocks per slot from timetable
99
+ * @param expectedBlockProposalsPerSlot - Config override. 0 disables scoring, undefined falls back to blocksPerSlot - 1.
100
+ * @returns Positive number of expected block proposals, or undefined if scoring is disabled
101
+ */
102
+ export function getEffectiveBlockProposalsPerSlot(
103
+ blocksPerSlot: number,
104
+ expectedBlockProposalsPerSlot?: number,
105
+ ): number | undefined {
106
+ if (expectedBlockProposalsPerSlot !== undefined) {
107
+ return expectedBlockProposalsPerSlot > 0 ? expectedBlockProposalsPerSlot : undefined;
108
+ }
109
+ // Fallback: In MBPS mode, N-1 block proposals per slot (last one bundled with checkpoint)
110
+ // In single block mode (blocksPerSlot=1), this is 0 → disabled
111
+ const fallback = Math.max(0, blocksPerSlot - 1);
112
+ return fallback > 0 ? fallback : undefined;
113
+ }
114
+
92
115
  /**
93
116
  * Gets the expected messages per slot for a given topic type.
94
117
  *
95
118
  * @param topicType - The topic type
96
119
  * @param targetCommitteeSize - Target committee size
97
120
  * @param blocksPerSlot - Number of blocks per slot
121
+ * @param expectedBlockProposalsPerSlot - Override for block proposals. 0 disables scoring, undefined falls back to blocksPerSlot - 1.
98
122
  * @returns Expected messages per slot, or undefined if unpredictable
99
123
  */
100
124
  export function getExpectedMessagesPerSlot(
101
125
  topicType: TopicType,
102
126
  targetCommitteeSize: number,
103
127
  blocksPerSlot: number,
128
+ expectedBlockProposalsPerSlot?: number,
104
129
  ): number | undefined {
105
130
  switch (topicType) {
106
131
  case TopicType.tx:
@@ -108,9 +133,7 @@ export function getExpectedMessagesPerSlot(
108
133
  return undefined;
109
134
 
110
135
  case TopicType.block_proposal:
111
- // In MBPS mode, N-1 block proposals per slot (last one bundled with checkpoint)
112
- // In single block mode (blocksPerSlot=1), this is 0
113
- return Math.max(0, blocksPerSlot - 1);
136
+ return getEffectiveBlockProposalsPerSlot(blocksPerSlot, expectedBlockProposalsPerSlot);
114
137
 
115
138
  case TopicType.checkpoint_proposal:
116
139
  // Exactly 1 checkpoint proposal per slot
@@ -190,10 +213,12 @@ const P2_DECAY_WINDOW_SLOTS = 2;
190
213
  // |P3| > 8 + 25 = 33
191
214
  //
192
215
  // We set P3 max = -34 per topic (slightly more than P1+P2) to ensure pruning.
193
- // With 3 topics having P3 enabled, total P3b after pruning = -102.
216
+ // The number of P3-enabled topics depends on config: by default 2 (checkpoint_proposal +
217
+ // checkpoint_attestation), or 3 if block proposal scoring is enabled via
218
+ // expectedBlockProposalsPerSlot. Total P3b after pruning = -68 (2 topics) or -102 (3 topics).
194
219
  //
195
- // With appSpecificWeight=10, ~20 HighTolerance errors (-40 app score) plus max P3b (-102)
196
- // would cross gossipThreshold (-500). This keeps non-contributors from being disconnected
220
+ // With appSpecificWeight=10, ~20 HighTolerance errors (-40 app score) plus max P3b (-68 or -102)
221
+ // would not cross gossipThreshold (-500). This keeps non-contributors from being disconnected
197
222
  // unless they also accrue app-level penalties.
198
223
  //
199
224
  // The weight formula ensures max penalty equals MAX_P3_PENALTY_PER_TOPIC:
@@ -203,12 +228,6 @@ const P2_DECAY_WINDOW_SLOTS = 2;
203
228
  /** Maximum P3 penalty per topic (must exceed P1 + P2 to cause pruning) */
204
229
  export const MAX_P3_PENALTY_PER_TOPIC = -(MAX_P1_SCORE + MAX_P2_SCORE + 1); // -34
205
230
 
206
- /** Number of topics with P3 enabled in MBPS mode (block_proposal + checkpoint_proposal + checkpoint_attestation) */
207
- export const NUM_P3_ENABLED_TOPICS = 3;
208
-
209
- /** Total maximum P3b penalty across all topics after pruning in MBPS mode */
210
- export const TOTAL_MAX_P3B_PENALTY = MAX_P3_PENALTY_PER_TOPIC * NUM_P3_ENABLED_TOPICS; // -102
211
-
212
231
  /**
213
232
  * Factory class for creating gossipsub topic scoring parameters.
214
233
  * Computes shared values once and reuses them across all topics.
@@ -384,6 +403,18 @@ export class TopicScoreParamsFactory {
384
403
  });
385
404
  }
386
405
 
406
+ /** Number of topics with P3 enabled, computed from config. Always 2 (checkpoint_proposal + checkpoint_attestation) plus optionally block_proposal. */
407
+ get numP3EnabledTopics(): number {
408
+ const blockProposalP3Enabled =
409
+ getEffectiveBlockProposalsPerSlot(this.blocksPerSlot, this.params.expectedBlockProposalsPerSlot) !== undefined;
410
+ return blockProposalP3Enabled ? 3 : 2;
411
+ }
412
+
413
+ /** Total maximum P3b penalty across all topics after pruning, computed from config */
414
+ get totalMaxP3bPenalty(): number {
415
+ return MAX_P3_PENALTY_PER_TOPIC * this.numP3EnabledTopics;
416
+ }
417
+
387
418
  /**
388
419
  * Creates topic score parameters for a specific topic type.
389
420
  *
@@ -391,7 +422,12 @@ export class TopicScoreParamsFactory {
391
422
  * @returns TopicScoreParams for the topic
392
423
  */
393
424
  createForTopic(topicType: TopicType): ReturnType<typeof createTopicScoreParams> {
394
- const expectedPerSlot = getExpectedMessagesPerSlot(topicType, this.params.targetCommitteeSize, this.blocksPerSlot);
425
+ const expectedPerSlot = getExpectedMessagesPerSlot(
426
+ topicType,
427
+ this.params.targetCommitteeSize,
428
+ this.blocksPerSlot,
429
+ this.params.expectedBlockProposalsPerSlot,
430
+ );
395
431
 
396
432
  // For unpredictable topics (tx) or topics with 0 expected messages, disable P3/P3b
397
433
  if (expectedPerSlot === undefined || expectedPerSlot === 0) {
@@ -341,6 +341,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
341
341
  heartbeatIntervalMs: config.gossipsubInterval,
342
342
  targetCommitteeSize: l1Constants.targetCommitteeSize,
343
343
  blockDurationMs: config.blockDurationMs,
344
+ expectedBlockProposalsPerSlot: config.expectedBlockProposalsPerSlot,
344
345
  });
345
346
 
346
347
  const node = await createLibp2p({
@@ -614,6 +615,10 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
614
615
  return this.peerManager.getPeers(includePending);
615
616
  }
616
617
 
618
+ public getGossipMeshPeerCount(topicType: TopicType): number {
619
+ return this.node.services.pubsub.getMeshPeers(this.topicStrings[topicType]).length;
620
+ }
621
+
617
622
  private handleGossipSubEvent(e: CustomEvent<GossipsubMessage>) {
618
623
  this.logger.trace(`Received PUBSUB message.`);
619
624
 
@@ -1544,7 +1549,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1544
1549
  protected async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
1545
1550
  const currentBlockNumber = await this.archiver.getBlockNumber();
1546
1551
 
1547
- // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1552
+ // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1548
1553
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1549
1554
  const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
1550
1555
 
@@ -1599,7 +1604,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
1599
1604
  public async validate(txs: Tx[]): Promise<void> {
1600
1605
  const currentBlockNumber = await this.archiver.getBlockNumber();
1601
1606
 
1602
- // We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
1607
+ // We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
1603
1608
  const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
1604
1609
  const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
1605
1610
 
@@ -10,6 +10,7 @@ import { Tx, TxArray, TxHash } from '@aztec/stdlib/tx';
10
10
  import type { PeerId } from '@libp2p/interface';
11
11
  import { peerIdFromString } from '@libp2p/peer-id';
12
12
 
13
+ import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_tracker.js';
13
14
  import { ReqRespSubProtocol } from '.././interface.js';
14
15
  import { BlockTxsRequest, BlockTxsResponse, type BlockTxsSource } from '.././protocols/index.js';
15
16
  import { ReqRespStatus } from '.././status.js';
@@ -20,7 +21,7 @@ import {
20
21
  DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE,
21
22
  } from './config.js';
22
23
  import type { BatchTxRequesterLibP2PService, BatchTxRequesterOptions, ITxMetadataCollection } from './interface.js';
23
- import { MissingTxMetadata, MissingTxMetadataCollection } from './missing_txs.js';
24
+ import { MissingTxMetadataCollection } from './missing_txs.js';
24
25
  import { type IPeerCollection, PeerCollection } from './peer_collection.js';
25
26
  import { BatchRequestTxValidator, type IBatchRequestTxValidator } from './tx_validator.js';
26
27
 
@@ -60,7 +61,7 @@ export class BatchTxRequester {
60
61
  private readonly txBatchSize: number;
61
62
 
62
63
  constructor(
63
- missingTxs: TxHash[],
64
+ missingTxsTracker: IMissingTxsTracker,
64
65
  blockTxsSource: BlockTxsSource,
65
66
  pinnedPeer: PeerId | undefined,
66
67
  timeoutMs: number,
@@ -99,8 +100,7 @@ export class BatchTxRequester {
99
100
  this.p2pService.peerScoring,
100
101
  );
101
102
  }
102
- const entries: Array<[string, MissingTxMetadata]> = missingTxs.map(h => [h.toString(), new MissingTxMetadata(h)]);
103
- this.txsMetadata = new MissingTxMetadataCollection(entries, this.txBatchSize);
103
+ this.txsMetadata = new MissingTxMetadataCollection(missingTxsTracker, this.txBatchSize);
104
104
  this.smartRequesterSemaphore = this.opts.semaphore ?? new Semaphore(0);
105
105
  }
106
106
 
@@ -661,7 +661,7 @@ export class BatchTxRequester {
661
661
  /*
662
662
  * @returns true if all missing txs have been fetched */
663
663
  private fetchedAllTxs() {
664
- return Array.from(this.txsMetadata.values()).every(tx => tx.fetched);
664
+ return this.txsMetadata.getMissingTxHashes().size == 0;
665
665
  }
666
666
 
667
667
  /*
@@ -679,7 +679,7 @@ export class BatchTxRequester {
679
679
  this.unlockSmartRequesterSemaphores();
680
680
  }
681
681
 
682
- return aborted || this.txsMetadata.size === 0 || this.fetchedAllTxs() || this.dateProvider.now() > this.deadline;
682
+ return aborted || this.fetchedAllTxs() || this.dateProvider.now() > this.deadline;
683
683
  }
684
684
 
685
685
  /*
@@ -6,7 +6,6 @@ import type { PeerId } from '@libp2p/interface';
6
6
 
7
7
  import type { ConnectionSampler } from '../connection-sampler/connection_sampler.js';
8
8
  import type { ReqRespInterface } from '../interface.js';
9
- import type { MissingTxMetadata } from './missing_txs.js';
10
9
  import type { IPeerCollection } from './peer_collection.js';
11
10
  import type { BatchRequestTxValidatorConfig, IBatchRequestTxValidator } from './tx_validator.js';
12
11
 
@@ -15,18 +14,15 @@ export interface IPeerPenalizer {
15
14
  }
16
15
 
17
16
  export interface ITxMetadataCollection {
18
- size: number;
19
- values(): IterableIterator<MissingTxMetadata>;
20
17
  getMissingTxHashes(): Set<string>;
18
+ markFetched(peerId: PeerId, tx: Tx): boolean;
21
19
  getTxsToRequestFromThePeer(peer: PeerId): TxHash[];
22
20
  markRequested(txHash: TxHash): void;
23
21
  markInFlightBySmartPeer(txHash: TxHash): void;
24
22
  markNotInFlightBySmartPeer(txHash: TxHash): void;
25
23
  alreadyFetched(txHash: TxHash): boolean;
26
24
  // Returns true if tx was marked as fetched, false if it was already marked as fetched
27
- markFetched(peerId: PeerId, tx: Tx): boolean;
28
25
  markPeerHas(peerId: PeerId, txHashes: TxHash[]): void;
29
- getFetchedTxs(): Tx[];
30
26
  }
31
27
 
32
28
  /**
@@ -2,13 +2,13 @@ import { type Tx, TxHash } from '@aztec/stdlib/tx';
2
2
 
3
3
  import type { PeerId } from '@libp2p/interface';
4
4
 
5
+ import type { IMissingTxsTracker } from '../../tx_collection/missing_txs_tracker.js';
5
6
  import { DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE } from './config.js';
6
7
  import type { ITxMetadataCollection } from './interface.js';
7
8
 
8
- export class MissingTxMetadata {
9
+ class MissingTxMetadata {
9
10
  constructor(
10
- public readonly txHash: TxHash,
11
- public fetched = false,
11
+ public readonly txHash: string,
12
12
  public requestedCount = 0,
13
13
  public inFlightCount = 0,
14
14
  public tx: Tx | undefined = undefined,
@@ -30,24 +30,6 @@ export class MissingTxMetadata {
30
30
  public isInFlight(): boolean {
31
31
  return this.inFlightCount > 0;
32
32
  }
33
-
34
- //Returns true if this is the first time we mark it as fetched
35
- public markAsFetched(peerId: PeerId, tx: Tx): boolean {
36
- if (this.fetched) {
37
- return false;
38
- }
39
-
40
- this.fetched = true;
41
- this.tx = tx;
42
-
43
- this.peers.add(peerId.toString());
44
-
45
- return true;
46
- }
47
-
48
- public toString() {
49
- return this.txHash.toString();
50
- }
51
33
  }
52
34
 
53
35
  /*
@@ -55,21 +37,18 @@ export class MissingTxMetadata {
55
37
  * This could be better optimized but given expected count of missing txs (N < 100)
56
38
  * At the moment there is no need for it. And benefit is that we have everything in single store
57
39
  * */
58
- export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata> implements ITxMetadataCollection {
40
+ export class MissingTxMetadataCollection implements ITxMetadataCollection {
41
+ private txMetadata = new Map<string, MissingTxMetadata>();
42
+
59
43
  constructor(
60
- entries?: readonly (readonly [string, MissingTxMetadata])[] | null,
44
+ private missingTxsTracker: IMissingTxsTracker,
61
45
  private readonly txBatchSize: number = DEFAULT_BATCH_TX_REQUESTER_TX_BATCH_SIZE,
62
46
  ) {
63
- super(entries);
64
- }
65
- public getSortedByRequestedCountAsc(txs: string[]): MissingTxMetadata[] {
66
- return Array.from(this.values().filter(txMeta => txs.includes(txMeta.txHash.toString()))).sort(
67
- (a, b) => a.requestedCount - b.requestedCount,
68
- );
47
+ missingTxsTracker.missingTxHashes.forEach(hash => this.txMetadata.set(hash, new MissingTxMetadata(hash)));
69
48
  }
70
49
 
71
50
  public getPrioritizingNotInFlightAndLowerRequestCount(txs: string[]): MissingTxMetadata[] {
72
- const filtered = Array.from(this.values()).filter(txMeta => txs.includes(txMeta.txHash.toString()));
51
+ const filtered = Array.from(this.txMetadata.values()).filter(txMeta => txs.includes(txMeta.txHash.toString()));
73
52
 
74
53
  const [notInFlight, inFlight] = filtered.reduce<[MissingTxMetadata[], MissingTxMetadata[]]>(
75
54
  (buckets, tx) => {
@@ -85,43 +64,15 @@ export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata>
85
64
  return [...notInFlight, ...inFlight];
86
65
  }
87
66
 
88
- public getFetchedTxHashes(): Set<string> {
89
- return new Set(
90
- this.values()
91
- .filter(t => t.fetched)
92
- .map(t => t.txHash.toString()),
93
- );
94
- }
95
-
96
67
  public getMissingTxHashes(): Set<string> {
97
- return new Set(
98
- this.values()
99
- .filter(t => !t.fetched)
100
- .map(t => t.txHash.toString()),
101
- );
102
- }
103
-
104
- public getInFlightTxHashes(): Set<string> {
105
- return new Set(
106
- this.values()
107
- .filter(t => t.isInFlight())
108
- .map(t => t.txHash.toString()),
109
- );
110
- }
111
-
112
- public getFetchedTxs(): Tx[] {
113
- return Array.from(
114
- this.values()
115
- .map(t => t.tx)
116
- .filter(t => !!t),
117
- );
68
+ return this.missingTxsTracker.missingTxHashes;
118
69
  }
119
70
 
120
71
  public getTxsPeerHas(peer: PeerId): Set<string> {
121
72
  const peerIdStr = peer.toString();
122
73
  const txsPeerHas = new Set<string>();
123
74
 
124
- this.values().forEach(txMeta => {
75
+ this.txMetadata.values().forEach(txMeta => {
125
76
  if (txMeta.peers.has(peerIdStr)) {
126
77
  txsPeerHas.add(txMeta.txHash.toString());
127
78
  }
@@ -132,13 +83,13 @@ export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata>
132
83
 
133
84
  public getTxsToRequestFromThePeer(peer: PeerId): TxHash[] {
134
85
  const txsPeerHas = this.getTxsPeerHas(peer);
135
- const fetchedTxs = this.getFetchedTxHashes();
86
+ const missingTxHashes = this.getMissingTxHashes();
136
87
 
137
- const txsToRequest = txsPeerHas.difference(fetchedTxs);
88
+ const txsToRequest = txsPeerHas.intersection(missingTxHashes);
138
89
 
139
90
  if (txsToRequest.size >= this.txBatchSize) {
140
91
  return this.getPrioritizingNotInFlightAndLowerRequestCount(Array.from(txsToRequest))
141
- .map(t => t.txHash)
92
+ .map(t => TxHash.fromString(t.txHash))
142
93
  .slice(0, this.txBatchSize);
143
94
  }
144
95
 
@@ -150,13 +101,13 @@ export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata>
150
101
  Array.from(this.getMissingTxHashes().difference(txsToRequest)),
151
102
  )
152
103
  .slice(0, countToFill)
153
- .map(t => t.txHash);
104
+ .map(t => TxHash.fromString(t.txHash));
154
105
 
155
106
  return [...Array.from(txsToRequest).map(t => TxHash.fromString(t)), ...txsToFill];
156
107
  }
157
108
 
158
109
  public markRequested(txHash: TxHash) {
159
- this.get(txHash.toString())?.markAsRequested();
110
+ this.txMetadata.get(txHash.toString())?.markAsRequested();
160
111
  }
161
112
 
162
113
  /*
@@ -165,7 +116,7 @@ export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata>
165
116
  * "dumb" peer might return it, or might not - we don't know
166
117
  * */
167
118
  public markInFlightBySmartPeer(txHash: TxHash) {
168
- this.get(txHash.toString())?.markInFlight();
119
+ this.txMetadata.get(txHash.toString())?.markInFlight();
169
120
  }
170
121
 
171
122
  /*
@@ -173,16 +124,16 @@ export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata>
173
124
  * Because the smart peer should return this tx, whereas
174
125
  * "dumb" peer might return it, or might not - we don't know*/
175
126
  public markNotInFlightBySmartPeer(txHash: TxHash) {
176
- this.get(txHash.toString())?.markNotInFlight();
127
+ this.txMetadata.get(txHash.toString())?.markNotInFlight();
177
128
  }
178
129
 
179
130
  public alreadyFetched(txHash: TxHash): boolean {
180
- return this.get(txHash.toString())?.fetched ?? false;
131
+ return !this.missingTxsTracker.isMissing(txHash.toString());
181
132
  }
182
133
 
183
134
  public markFetched(peerId: PeerId, tx: Tx): boolean {
184
135
  const txHashStr = tx.txHash.toString();
185
- const txMeta = this.get(txHashStr);
136
+ const txMeta = this.txMetadata.get(txHashStr);
186
137
  if (!txMeta) {
187
138
  //TODO: what to do about peer which sent txs we didn't request?
188
139
  // 1. don't request from it in the scope of this batch request
@@ -192,7 +143,8 @@ export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata>
192
143
  return false;
193
144
  }
194
145
 
195
- return txMeta.markAsFetched(peerId, tx);
146
+ txMeta.peers.add(peerId.toString());
147
+ return this.missingTxsTracker.markFetched(tx);
196
148
  }
197
149
 
198
150
  public markPeerHas(peerId: PeerId, txHash: TxHash[]) {
@@ -200,7 +152,7 @@ export class MissingTxMetadataCollection extends Map<string, MissingTxMetadata>
200
152
  txHash
201
153
  .map(t => t.toString())
202
154
  .forEach(txh => {
203
- const txMeta = this.get(txh);
155
+ const txMeta = this.txMetadata.get(txh);
204
156
  if (txMeta) {
205
157
  txMeta.peers.add(peerIdStr);
206
158
  }
@@ -1,7 +1,13 @@
1
1
  import type { SlotNumber } from '@aztec/foundation/branded-types';
2
2
  import type { EthAddress } from '@aztec/foundation/eth-address';
3
3
  import type { PeerInfo } from '@aztec/stdlib/interfaces/server';
4
- import type { BlockProposal, CheckpointAttestation, CheckpointProposalCore, Gossipable } from '@aztec/stdlib/p2p';
4
+ import type {
5
+ BlockProposal,
6
+ CheckpointAttestation,
7
+ CheckpointProposalCore,
8
+ Gossipable,
9
+ TopicType,
10
+ } from '@aztec/stdlib/p2p';
5
11
  import type { Tx } from '@aztec/stdlib/tx';
6
12
 
7
13
  import type { PeerId } from '@libp2p/interface';
@@ -130,6 +136,9 @@ export interface P2PService {
130
136
 
131
137
  getPeers(includePending?: boolean): PeerInfo[];
132
138
 
139
+ /** Returns the number of peers in the GossipSub mesh for a given topic type. */
140
+ getGossipMeshPeerCount(topicType: TopicType): number;
141
+
133
142
  validate(txs: Tx[]): Promise<void>;
134
143
 
135
144
  addReqRespSubProtocol(