@lodestar/beacon-node 1.48.0-dev.b71ab45af3 → 1.48.0-dev.bb3ffba9e9

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 (230) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +4 -4
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/validator/index.d.ts.map +1 -1
  5. package/lib/api/impl/validator/index.js +39 -39
  6. package/lib/api/impl/validator/index.js.map +1 -1
  7. package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
  8. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/archiveStore.js +1 -0
  10. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  11. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  12. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -0
  13. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  14. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  15. package/lib/chain/archiveStore/historicalState/metrics.js +12 -1
  16. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -0
  18. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  19. package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
  20. package/lib/chain/archiveStore/historicalState/worker.js +4 -1
  21. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  22. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  23. package/lib/chain/archiveStore/utils/archiveBlocks.js +31 -34
  24. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  25. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  26. package/lib/chain/blocks/importBlock.js +0 -13
  27. package/lib/chain/blocks/importBlock.js.map +1 -1
  28. package/lib/chain/blocks/index.d.ts.map +1 -1
  29. package/lib/chain/blocks/index.js +3 -2
  30. package/lib/chain/blocks/index.js.map +1 -1
  31. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  32. package/lib/chain/blocks/writeBlockInputToDb.js +9 -16
  33. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  34. package/lib/chain/chain.d.ts +9 -3
  35. package/lib/chain/chain.d.ts.map +1 -1
  36. package/lib/chain/chain.js +127 -73
  37. package/lib/chain/chain.js.map +1 -1
  38. package/lib/chain/emitter.d.ts +1 -1
  39. package/lib/chain/emitter.js +1 -1
  40. package/lib/chain/errors/blockError.d.ts +19 -7
  41. package/lib/chain/errors/blockError.d.ts.map +1 -1
  42. package/lib/chain/errors/blockError.js +2 -2
  43. package/lib/chain/errors/blockError.js.map +1 -1
  44. package/lib/chain/errors/dataColumnSidecarError.d.ts +15 -2
  45. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  46. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  47. package/lib/chain/errors/executionPayloadEnvelope.d.ts +10 -11
  48. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  49. package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
  50. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  51. package/lib/chain/interface.d.ts +2 -2
  52. package/lib/chain/interface.d.ts.map +1 -1
  53. package/lib/chain/prepareNextSlot.js +13 -13
  54. package/lib/chain/prepareNextSlot.js.map +1 -1
  55. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  56. package/lib/chain/produceBlock/produceBlockBody.js +46 -64
  57. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  58. package/lib/chain/validation/block.d.ts.map +1 -1
  59. package/lib/chain/validation/block.js +35 -54
  60. package/lib/chain/validation/block.js.map +1 -1
  61. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  62. package/lib/chain/validation/dataColumnSidecar.js +38 -13
  63. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  64. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  65. package/lib/chain/validation/executionPayloadEnvelope.js +10 -29
  66. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  67. package/lib/chain/validatorMonitor.d.ts +7 -1
  68. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  69. package/lib/chain/validatorMonitor.js +22 -5
  70. package/lib/chain/validatorMonitor.js.map +1 -1
  71. package/lib/db/beacon.d.ts +14 -5
  72. package/lib/db/beacon.d.ts.map +1 -1
  73. package/lib/db/beacon.js +17 -3
  74. package/lib/db/beacon.js.map +1 -1
  75. package/lib/db/dataColumnStore.d.ts +39 -0
  76. package/lib/db/dataColumnStore.d.ts.map +1 -0
  77. package/lib/db/dataColumnStore.js +81 -0
  78. package/lib/db/dataColumnStore.js.map +1 -0
  79. package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
  80. package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
  81. package/lib/db/flatFileStore/atomicWrite.js +61 -0
  82. package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
  83. package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
  84. package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
  85. package/lib/db/flatFileStore/dcolFormat.js +286 -0
  86. package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
  87. package/lib/db/flatFileStore/errors.d.ts +82 -0
  88. package/lib/db/flatFileStore/errors.d.ts.map +1 -0
  89. package/lib/db/flatFileStore/errors.js +32 -0
  90. package/lib/db/flatFileStore/errors.js.map +1 -0
  91. package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
  92. package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
  93. package/lib/db/flatFileStore/flatFileStore.js +251 -0
  94. package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
  95. package/lib/db/flatFileStore/index.d.ts +4 -0
  96. package/lib/db/flatFileStore/index.d.ts.map +1 -0
  97. package/lib/db/flatFileStore/index.js +2 -0
  98. package/lib/db/flatFileStore/index.js.map +1 -0
  99. package/lib/db/flatFileStore/interface.d.ts +25 -0
  100. package/lib/db/flatFileStore/interface.d.ts.map +1 -0
  101. package/lib/db/flatFileStore/interface.js +2 -0
  102. package/lib/db/flatFileStore/interface.js.map +1 -0
  103. package/lib/db/flatFileStore/metrics.d.ts +24 -0
  104. package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
  105. package/lib/db/flatFileStore/metrics.js +25 -0
  106. package/lib/db/flatFileStore/metrics.js.map +1 -0
  107. package/lib/db/flatFileStore/path.d.ts +9 -0
  108. package/lib/db/flatFileStore/path.d.ts.map +1 -0
  109. package/lib/db/flatFileStore/path.js +21 -0
  110. package/lib/db/flatFileStore/path.js.map +1 -0
  111. package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
  112. package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
  113. package/lib/db/flatFileStore/slotIndex.js +46 -0
  114. package/lib/db/flatFileStore/slotIndex.js.map +1 -0
  115. package/lib/db/flatFileStore/snappy.d.ts +3 -0
  116. package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
  117. package/lib/db/flatFileStore/snappy.js +12 -0
  118. package/lib/db/flatFileStore/snappy.js.map +1 -0
  119. package/lib/db/index.d.ts +1 -0
  120. package/lib/db/index.d.ts.map +1 -1
  121. package/lib/db/interface.d.ts +8 -1
  122. package/lib/db/interface.d.ts.map +1 -1
  123. package/lib/db/repositories/blockArchive.d.ts +8 -1
  124. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  125. package/lib/db/repositories/blockArchive.js +65 -28
  126. package/lib/db/repositories/blockArchive.js.map +1 -1
  127. package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
  128. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  129. package/lib/db/repositories/blockArchiveIndex.js +3 -7
  130. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  131. package/lib/execution/engine/mock.d.ts.map +1 -1
  132. package/lib/execution/engine/mock.js +4 -1
  133. package/lib/execution/engine/mock.js.map +1 -1
  134. package/lib/metrics/metrics/lodestar.d.ts +18 -0
  135. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  136. package/lib/metrics/metrics/lodestar.js +55 -1
  137. package/lib/metrics/metrics/lodestar.js.map +1 -1
  138. package/lib/monitoring/clientStats.js +1 -1
  139. package/lib/monitoring/clientStats.js.map +1 -1
  140. package/lib/network/gossip/encoding.d.ts.map +1 -1
  141. package/lib/network/gossip/encoding.js +0 -7
  142. package/lib/network/gossip/encoding.js.map +1 -1
  143. package/lib/network/gossip/topic.d.ts +2 -1
  144. package/lib/network/gossip/topic.d.ts.map +1 -1
  145. package/lib/network/gossip/topic.js +4 -20
  146. package/lib/network/gossip/topic.js.map +1 -1
  147. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  148. package/lib/network/processor/gossipHandlers.js +6 -7
  149. package/lib/network/processor/gossipHandlers.js.map +1 -1
  150. package/lib/network/processor/index.d.ts +49 -3
  151. package/lib/network/processor/index.d.ts.map +1 -1
  152. package/lib/network/processor/index.js +232 -98
  153. package/lib/network/processor/index.js.map +1 -1
  154. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  155. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
  156. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  157. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  158. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
  159. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  160. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  161. package/lib/network/reqresp/protocols.js +6 -22
  162. package/lib/network/reqresp/protocols.js.map +1 -1
  163. package/lib/network/reqresp/rateLimit.js +2 -1
  164. package/lib/network/reqresp/rateLimit.js.map +1 -1
  165. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
  166. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  167. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
  168. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  169. package/lib/node/nodejs.d.ts +2 -1
  170. package/lib/node/nodejs.d.ts.map +1 -1
  171. package/lib/node/nodejs.js +4 -2
  172. package/lib/node/nodejs.js.map +1 -1
  173. package/lib/util/sszBytes.d.ts +6 -0
  174. package/lib/util/sszBytes.d.ts.map +1 -1
  175. package/lib/util/sszBytes.js +1 -1
  176. package/lib/util/sszBytes.js.map +1 -1
  177. package/package.json +18 -19
  178. package/src/api/impl/beacon/blocks/index.ts +8 -4
  179. package/src/api/impl/validator/index.ts +44 -43
  180. package/src/chain/archiveStore/archiveStore.ts +6 -1
  181. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
  182. package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
  183. package/src/chain/archiveStore/historicalState/types.ts +2 -0
  184. package/src/chain/archiveStore/historicalState/worker.ts +4 -1
  185. package/src/chain/archiveStore/utils/archiveBlocks.ts +34 -39
  186. package/src/chain/blocks/importBlock.ts +0 -13
  187. package/src/chain/blocks/index.ts +13 -2
  188. package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
  189. package/src/chain/chain.ts +149 -80
  190. package/src/chain/emitter.ts +1 -1
  191. package/src/chain/errors/blockError.ts +22 -10
  192. package/src/chain/errors/dataColumnSidecarError.ts +24 -8
  193. package/src/chain/errors/executionPayloadEnvelope.ts +14 -11
  194. package/src/chain/interface.ts +2 -2
  195. package/src/chain/prepareNextSlot.ts +16 -16
  196. package/src/chain/produceBlock/produceBlockBody.ts +50 -71
  197. package/src/chain/validation/block.ts +35 -70
  198. package/src/chain/validation/dataColumnSidecar.ts +47 -14
  199. package/src/chain/validation/executionPayloadEnvelope.ts +10 -37
  200. package/src/chain/validatorMonitor.ts +24 -7
  201. package/src/db/beacon.ts +33 -6
  202. package/src/db/dataColumnStore.ts +113 -0
  203. package/src/db/flatFileStore/atomicWrite.ts +65 -0
  204. package/src/db/flatFileStore/dcolFormat.ts +389 -0
  205. package/src/db/flatFileStore/errors.ts +53 -0
  206. package/src/db/flatFileStore/flatFileStore.ts +310 -0
  207. package/src/db/flatFileStore/index.ts +3 -0
  208. package/src/db/flatFileStore/interface.ts +21 -0
  209. package/src/db/flatFileStore/metrics.ts +48 -0
  210. package/src/db/flatFileStore/path.ts +32 -0
  211. package/src/db/flatFileStore/slotIndex.ts +56 -0
  212. package/src/db/flatFileStore/snappy.ts +14 -0
  213. package/src/db/index.ts +1 -0
  214. package/src/db/interface.ts +9 -1
  215. package/src/db/repositories/blockArchive.ts +89 -30
  216. package/src/db/repositories/blockArchiveIndex.ts +4 -9
  217. package/src/execution/engine/mock.ts +4 -1
  218. package/src/metrics/metrics/lodestar.ts +57 -1
  219. package/src/monitoring/clientStats.ts +1 -1
  220. package/src/network/gossip/encoding.ts +0 -7
  221. package/src/network/gossip/topic.ts +3 -25
  222. package/src/network/processor/gossipHandlers.ts +11 -11
  223. package/src/network/processor/index.ts +263 -148
  224. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
  225. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
  226. package/src/network/reqresp/protocols.ts +7 -24
  227. package/src/network/reqresp/rateLimit.ts +2 -1
  228. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
  229. package/src/node/nodejs.ts +6 -1
  230. package/src/util/sszBytes.ts +1 -1
@@ -1,6 +1,6 @@
1
1
  import {TopicValidatorResult} from "@libp2p/gossipsub";
2
2
  import {routes} from "@lodestar/api";
3
- import {ForkSeq} from "@lodestar/params";
3
+ import {ForkSeq, NUMBER_OF_COLUMNS} from "@lodestar/params";
4
4
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
5
5
  import {RootHex, Slot, SlotRootHex} from "@lodestar/types";
6
6
  import {Logger, MapDef, mapValues, sleep} from "@lodestar/utils";
@@ -59,6 +59,36 @@ export type NetworkProcessorOpts = GossipHandlerOpts & {
59
59
  */
60
60
  const MAX_UNKNOWN_ROOTS_SLOT_CACHE_SIZE = 3;
61
61
 
62
+ /**
63
+ * Max distinct roots we BUFFER messages for, per slot. Kept tight to avoid the OOM
64
+ * risk. We don't support super forky condition where there are more than this many roots per slot via gossip.
65
+ */
66
+ export const MAX_BUFFERED_ROOTS_PER_SLOT = 5;
67
+
68
+ /**
69
+ * Max distinct roots we emit an unknown-root search for, per slot. Higher than
70
+ * the buffer budget because:
71
+ * - in the attack scenario, the genuine root may comes after the first MAX_BUFFERED_ROOTS_PER_SLOT roots
72
+ * - if we cannot search, we'll penalize peers in UnknownBlockInput, not in NetworkProcessor
73
+ */
74
+ export const MAX_SEARCHED_ROOTS_PER_SLOT = 32;
75
+
76
+ /**
77
+ * Given the same root and topic, we'll ignore messages after the below cap. Each cap is the number of
78
+ * legitimate messages we expect for one block, plus headroom for some malformed messages that may arrives first
79
+ * they will be penalized by the gossip handler
80
+ */
81
+ export const MAX_AWAITING_MESSAGES_PER_ROOT: Partial<Record<GossipType, number>> = {
82
+ [GossipType.data_column_sidecar]: 2 * NUMBER_OF_COLUMNS,
83
+ [GossipType.execution_payload]: 2,
84
+ [GossipType.execution_payload_bid]: 8,
85
+ };
86
+
87
+ /**
88
+ * Track the forwarded peers we already emit to UnknownBlockInput sync.
89
+ */
90
+ type SearchedRootEntry = {blockPeerIds?: Set<PeerIdStr>; envelopePeerIds?: Set<PeerIdStr>};
91
+
62
92
  /**
63
93
  * This is respective to gossipsub seenTTL (which is 550 * 0.7 = 385s), also it's respective
64
94
  * to beacon_attestation ATTESTATION_PROPAGATION_SLOT_RANGE (32 slots).
@@ -120,6 +150,14 @@ export enum ReprocessRejectReason {
120
150
  * There are too many gossip messages that have unknown block root.
121
151
  */
122
152
  reached_limit = "reached_limit",
153
+ /**
154
+ * Too many distinct unknown roots are already tracked for the message's slot (MAX_UNKNOWN_ROOTS_PER_SLOT).
155
+ */
156
+ reached_root_limit = "reached_root_limit",
157
+ /**
158
+ * Too many gossip messages are already buffered for this (root, topic) (MAX_AWAITING_MESSAGES_PER_ROOT).
159
+ */
160
+ reached_topic_limit = "reached_topic_limit",
123
161
  /**
124
162
  * The awaiting gossip message is pruned per clock slot.
125
163
  */
@@ -185,19 +223,21 @@ export class NetworkProcessor {
185
223
  private readonly gossipTopicConcurrency: {[K in GossipType]: number};
186
224
  private readonly extractBlockSlotRootFns = createExtractBlockSlotRootFns();
187
225
  // we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
188
- // to be stored in this Map and reprocessed once the block comes
189
- private readonly awaitingMessagesByBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
226
+ // to be stored in this Map and reprocessed once the block comes. Keyed by topic per root so we can cap
227
+ // the number of buffered messages per (root, topic) - see MAX_AWAITING_MESSAGES_PER_ROOT.
228
+ private readonly awaitingMessagesByBlockRoot: MapDef<RootHex, MapDef<GossipType, Set<PendingGossipsubMessage>>>;
190
229
  private awaitingBlockMessageCount = 0;
191
230
  // we may not receive the payload for messages that require the FULL payload variant to be processed,
192
231
  // in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
193
- private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
232
+ private readonly awaitingMessagesByPayloadBlockRoot: MapDef<
233
+ RootHex,
234
+ MapDef<GossipType, Set<PendingGossipsubMessage>>
235
+ >;
194
236
  private awaitingPayloadMessageCount = 0;
195
- private unknownBlocksBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
196
- () => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
197
- );
198
- private unknownEnvelopesBySlot = new MapDef<Slot, MapDef<RootHex, Set<PeerIdStr>>>(
199
- () => new MapDef<RootHex, Set<PeerIdStr>>(() => new Set())
237
+ private searchedRootsBySlot = new MapDef<Slot, MapDef<RootHex, SearchedRootEntry>>(
238
+ () => new MapDef<RootHex, SearchedRootEntry>(() => ({}))
200
239
  );
240
+ private bufferedRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
201
241
 
202
242
  constructor(
203
243
  modules: NetworkProcessorModules,
@@ -222,8 +262,12 @@ export class NetworkProcessor {
222
262
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadEnvelopeProcessed);
223
263
  this.chain.clock.on(ClockEvent.slot, this.onClockSlot);
224
264
 
225
- this.awaitingMessagesByBlockRoot = new MapDef<RootHex, Set<PendingGossipsubMessage>>(() => new Set());
226
- this.awaitingMessagesByPayloadBlockRoot = new MapDef<RootHex, Set<PendingGossipsubMessage>>(() => new Set());
265
+ this.awaitingMessagesByBlockRoot = new MapDef<RootHex, MapDef<GossipType, Set<PendingGossipsubMessage>>>(
266
+ () => new MapDef<GossipType, Set<PendingGossipsubMessage>>(() => new Set())
267
+ );
268
+ this.awaitingMessagesByPayloadBlockRoot = new MapDef<RootHex, MapDef<GossipType, Set<PendingGossipsubMessage>>>(
269
+ () => new MapDef<GossipType, Set<PendingGossipsubMessage>>(() => new Set())
270
+ );
227
271
 
228
272
  // TODO: Implement queues and priorization for ReqResp incoming requests
229
273
  // Listens to NetworkEvent.reqRespIncomingRequest event
@@ -283,9 +327,12 @@ export class NetworkProcessor {
283
327
  if (this.chain.seenBlock(root)) {
284
328
  return;
285
329
  }
286
- const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
287
- const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
288
- const forwardedPeers = peersForRoot.getOrDefault(root);
330
+ const entry = this.searchedRootsBySlot.getOrDefault(slot).getOrDefault(root);
331
+ const alreadySearching = entry.blockPeerIds !== undefined;
332
+ if (entry.blockPeerIds === undefined) {
333
+ entry.blockPeerIds = new Set();
334
+ }
335
+ const forwardedPeers = entry.blockPeerIds;
289
336
 
290
337
  // brand-new search always emits (peer may be undefined)
291
338
  let shouldEmit = !alreadySearching;
@@ -309,10 +356,12 @@ export class NetworkProcessor {
309
356
  if (this.chain.seenPayloadEnvelope(root)) {
310
357
  return;
311
358
  }
312
- const peersForRoot = this.unknownEnvelopesBySlot.getOrDefault(slot);
313
- // capture "already searching" BEFORE getOrDefault(root) creates the entry
314
- const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByPayloadBlockRoot.has(root);
315
- const forwardedPeers = peersForRoot.getOrDefault(root);
359
+ const entry = this.searchedRootsBySlot.getOrDefault(slot).getOrDefault(root);
360
+ const alreadySearching = entry.envelopePeerIds !== undefined;
361
+ if (entry.envelopePeerIds === undefined) {
362
+ entry.envelopePeerIds = new Set();
363
+ }
364
+ const forwardedPeers = entry.envelopePeerIds;
316
365
 
317
366
  let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
318
367
  if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
@@ -374,14 +423,15 @@ export class NetworkProcessor {
374
423
  }
375
424
 
376
425
  message.msgSlot = slot;
426
+ const peerId = message.propagationSource.toString();
377
427
 
378
428
  // this determines whether this message needs to wait for a Block or Envelope
379
- // a message should only wait for what it voted for, hence we don't want to put it on both queues
429
+ // a message should only wait for what it voted for, hence we don't want to put it on both queues.
380
430
  let preprocessResult: PreprocessResult = {action: PreprocessAction.PushToQueue};
381
431
  // no need to check if root is a descendant of the current finalized block, it will be checked once we validate the message if needed
382
432
  if (root && !this.chain.forkChoice.hasBlockHexUnsafe(root)) {
383
433
  // starting from GLOAS, unknown root from data_column_sidecar also falls into this case
384
- this.searchUnknownBlock({slot, root}, BlockInputSource.network_processor, message.propagationSource.toString());
434
+ this.searchUnknownRoot({slot, root}, true, false, peerId);
385
435
  // for beacon_attestation and beacon_aggregate_and_proof messages, this is only temporary.
386
436
  // if "index = 1" we need to await for the Envelope instead
387
437
  preprocessResult = {action: PreprocessAction.AwaitBlock, root};
@@ -391,40 +441,32 @@ export class NetworkProcessor {
391
441
  // we separate the search action from the await action
392
442
 
393
443
  // beacon_block: proactively search for parent block/envelope across all forks, but never queue.
394
- // BlockInputSync handles cascading recovery if the gossip handler throws.
444
+ // BlockInputSync handles cascading recovery if the gossip handler throws. The parent root differs from
445
+ // the message root and this message never awaits, so its search flushes inline here (gated by the budget).
395
446
  if (topicType === GossipType.beacon_block) {
396
447
  const parentRoot = getParentRootFromSignedBeaconBlockSerialized(message.msg.data);
397
448
  if (parentRoot) {
449
+ let searchParentBlock = false;
450
+ let searchParentEnvelope = false;
398
451
  if (ForkSeq[fork] >= ForkSeq.gloas) {
399
452
  // GLOAS: also check parent envelope, same logic as execution_payload_bid
400
453
  const parentBlockHash = getParentBlockHashFromGloasSignedBeaconBlockSerialized(message.msg.data);
401
454
  if (parentBlockHash && !this.chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHash)) {
402
455
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
403
456
  if (protoBlock === null) {
404
- this.searchUnknownBlock(
405
- {slot, root: parentRoot},
406
- BlockInputSource.network_processor,
407
- message.propagationSource.toString()
408
- );
457
+ searchParentBlock = true;
409
458
  } else if (
410
459
  protoBlock.executionPayloadBlockHash &&
411
460
  protoBlock.executionPayloadBlockHash !== parentBlockHash
412
461
  ) {
413
462
  // only search for the envelope by block root if we're sure there is one. Otherwise UnknownBlockSync will penalize the peer.
414
- this.searchUnknownEnvelope(
415
- {slot, root: parentRoot},
416
- BlockInputSource.network_processor,
417
- message.propagationSource.toString()
418
- );
463
+ searchParentEnvelope = true;
419
464
  }
420
465
  }
421
466
  } else if (!this.chain.forkChoice.hasBlockHexUnsafe(parentRoot)) {
422
- this.searchUnknownBlock(
423
- {slot, root: parentRoot},
424
- BlockInputSource.network_processor,
425
- message.propagationSource.toString()
426
- );
467
+ searchParentBlock = true;
427
468
  }
469
+ this.searchUnknownRoot({slot, root: parentRoot}, searchParentBlock, searchParentEnvelope, peerId);
428
470
  }
429
471
  preprocessResult = {action: PreprocessAction.PushToQueue};
430
472
  }
@@ -443,11 +485,7 @@ export class NetworkProcessor {
443
485
  : getDataIndexFromSignedAggregateAndProofSerialized(message.msg.data);
444
486
  if (attIndex === 1 && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
445
487
  // attestation votes that the payload is available but it is not yet known
446
- this.searchUnknownEnvelope(
447
- {slot, root},
448
- BlockInputSource.network_processor,
449
- message.propagationSource.toString()
450
- );
488
+ this.searchUnknownRoot({slot, root}, false, true, peerId);
451
489
  preprocessResult = {action: PreprocessAction.AwaitEnvelope, root};
452
490
  }
453
491
  break;
@@ -457,11 +495,7 @@ export class NetworkProcessor {
457
495
  const payloadPresent = getPayloadPresentFromPayloadAttestationMessageSerialized(message.msg.data);
458
496
  if (payloadPresent && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
459
497
  // payload attestation votes that the payload is available but it is not yet known
460
- this.searchUnknownEnvelope(
461
- {slot, root},
462
- BlockInputSource.network_processor,
463
- message.propagationSource.toString()
464
- );
498
+ this.searchUnknownRoot({slot, root}, false, true, peerId);
465
499
  // do not await the envelope, payload attestation processing only requires that the block is known
466
500
  // also do not reset preprocessResult, we may already await for the block
467
501
  }
@@ -470,11 +504,7 @@ export class NetworkProcessor {
470
504
  case GossipType.data_column_sidecar: {
471
505
  if (root == null) break;
472
506
  if (!this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
473
- this.searchUnknownEnvelope(
474
- {slot, root},
475
- BlockInputSource.network_processor,
476
- message.propagationSource.toString()
477
- );
507
+ this.searchUnknownRoot({slot, root}, false, true, peerId);
478
508
  // do not await the envelope, we can do gossip validation
479
509
  // also do not reset preprocessResult, we may already await for the block
480
510
  }
@@ -485,11 +515,7 @@ export class NetworkProcessor {
485
515
  // Extract beacon_block_root directly
486
516
  const blockRoot = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(message.msg.data);
487
517
  if (blockRoot && !this.chain.forkChoice.hasBlockHexUnsafe(blockRoot)) {
488
- this.searchUnknownBlock(
489
- {slot, root: blockRoot},
490
- BlockInputSource.network_processor,
491
- message.propagationSource.toString()
492
- );
518
+ this.searchUnknownRoot({slot, root: blockRoot}, true, false, peerId);
493
519
  // We always want to await the block
494
520
  // This allows us to properly forward the payload envelope
495
521
  preprocessResult = {action: PreprocessAction.AwaitBlock, root: blockRoot};
@@ -508,21 +534,13 @@ export class NetworkProcessor {
508
534
  ) {
509
535
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentBlockRoot);
510
536
  if (protoBlock === null) {
511
- this.searchUnknownBlock(
512
- {slot, root: parentBlockRoot},
513
- BlockInputSource.network_processor,
514
- message.propagationSource.toString()
515
- );
537
+ this.searchUnknownRoot({slot, root: parentBlockRoot}, true, false, peerId);
516
538
  preprocessResult = {action: PreprocessAction.AwaitBlock, root: parentBlockRoot};
517
539
  } else if (
518
540
  protoBlock.executionPayloadBlockHash &&
519
541
  protoBlock.executionPayloadBlockHash !== parentBlockHash
520
542
  ) {
521
- this.searchUnknownEnvelope(
522
- {slot, root: parentBlockRoot},
523
- BlockInputSource.network_processor,
524
- message.propagationSource.toString()
525
- );
543
+ this.searchUnknownRoot({slot, root: parentBlockRoot}, false, true, peerId);
526
544
  preprocessResult = {action: PreprocessAction.AwaitEnvelope, root: parentBlockRoot};
527
545
  }
528
546
  }
@@ -535,42 +553,106 @@ export class NetworkProcessor {
535
553
  case PreprocessAction.PushToQueue:
536
554
  this.pushPendingGossipsubMessageToQueue(message);
537
555
  break;
538
- case PreprocessAction.AwaitBlock: {
539
- if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
540
- // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
541
- this.metrics?.awaitingBlockGossipMessages.reject.inc({
542
- reason: ReprocessRejectReason.reached_limit,
543
- topic: topicType,
544
- });
545
- return;
546
- }
547
-
548
- this.metrics?.awaitingBlockGossipMessages.queue.inc({topic: topicType});
549
- const awaitingGossipsubMessages = this.awaitingMessagesByBlockRoot.getOrDefault(preprocessResult.root);
550
- awaitingGossipsubMessages.add(message);
551
- this.awaitingBlockMessageCount++;
556
+ case PreprocessAction.AwaitBlock:
557
+ this.maybeAwaitBlock(preprocessResult.root, topicType, slot, message);
552
558
  break;
553
- }
554
- case PreprocessAction.AwaitEnvelope: {
555
- if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
556
- this.metrics?.awaitingPayloadGossipMessages.reject.inc({
557
- reason: ReprocessRejectReason.reached_limit,
558
- topic: topicType,
559
- });
560
- return;
561
- }
562
-
563
- this.metrics?.awaitingPayloadGossipMessages.queue.inc({topic: topicType});
564
- const awaitingPayloadGossipsubMessages = this.awaitingMessagesByPayloadBlockRoot.getOrDefault(
565
- preprocessResult.root
566
- );
567
- awaitingPayloadGossipsubMessages.add(message);
568
- this.awaitingPayloadMessageCount++;
559
+ case PreprocessAction.AwaitEnvelope:
560
+ this.maybeAwaitPayload(preprocessResult.root, topicType, slot, message);
569
561
  break;
570
- }
571
562
  }
572
563
  };
573
564
 
565
+ /**
566
+ * Buffer a gossip message that must wait for an unknown block before it can be validated.
567
+ *
568
+ * For DOS protection, the message is ignored (reject metric incremented) when any cap is hit:
569
+ * - MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS: global awaiting-block message count
570
+ * - MAX_BUFFERED_ROOTS_PER_SLOT: distinct roots we buffer messages for, per slot
571
+ * - MAX_AWAITING_MESSAGES_PER_ROOT[topic]: messages buffered per (root, topic)
572
+ */
573
+ private maybeAwaitBlock(root: RootHex, topicType: GossipType, slot: Slot, message: PendingGossipsubMessage): void {
574
+ const metric = this.metrics?.awaitingBlockGossipMessages;
575
+ // global message-count cap - cheapest check first
576
+ if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
577
+ metric?.reject.inc({reason: ReprocessRejectReason.reached_limit, topic: topicType});
578
+ return;
579
+ }
580
+ // per-(root, topic) message cap: e.g. <= NUMBER_OF_COLUMNS columns per root
581
+ const perRootCap = MAX_AWAITING_MESSAGES_PER_ROOT[topicType];
582
+ if (
583
+ perRootCap !== undefined &&
584
+ (this.awaitingMessagesByBlockRoot.get(root)?.get(topicType)?.size ?? 0) >= perRootCap
585
+ ) {
586
+ metric?.reject.inc({reason: ReprocessRejectReason.reached_topic_limit, topic: topicType});
587
+ return;
588
+ }
589
+ if (!this.tryReserveBufferedRoot(slot, root)) {
590
+ metric?.reject.inc({reason: ReprocessRejectReason.reached_root_limit, topic: topicType});
591
+ return;
592
+ }
593
+
594
+ metric?.queue.inc({topic: topicType});
595
+ this.awaitingMessagesByBlockRoot.getOrDefault(root).getOrDefault(topicType).add(message);
596
+ this.awaitingBlockMessageCount++;
597
+ }
598
+
599
+ /** Payload counterpart of maybeAwaitBlock */
600
+ private maybeAwaitPayload(root: RootHex, topicType: GossipType, slot: Slot, message: PendingGossipsubMessage): void {
601
+ const metric = this.metrics?.awaitingPayloadGossipMessages;
602
+ if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
603
+ metric?.reject.inc({reason: ReprocessRejectReason.reached_limit, topic: topicType});
604
+ return;
605
+ }
606
+ const perRootCap = MAX_AWAITING_MESSAGES_PER_ROOT[topicType];
607
+ if (
608
+ perRootCap !== undefined &&
609
+ (this.awaitingMessagesByPayloadBlockRoot.get(root)?.get(topicType)?.size ?? 0) >= perRootCap
610
+ ) {
611
+ metric?.reject.inc({reason: ReprocessRejectReason.reached_topic_limit, topic: topicType});
612
+ return;
613
+ }
614
+ if (!this.tryReserveBufferedRoot(slot, root)) {
615
+ metric?.reject.inc({reason: ReprocessRejectReason.reached_root_limit, topic: topicType});
616
+ return;
617
+ }
618
+
619
+ metric?.queue.inc({topic: topicType});
620
+ this.awaitingMessagesByPayloadBlockRoot.getOrDefault(root).getOrDefault(topicType).add(message);
621
+ this.awaitingPayloadMessageCount++;
622
+ }
623
+
624
+ /**
625
+ * Cap distinct roots we emit an unknown-root search for, an already-tracked root (dedup) never counts as "too many".
626
+ */
627
+ private tooManySearchedRoots(slot: Slot, root: RootHex): boolean {
628
+ const roots = this.searchedRootsBySlot.get(slot);
629
+ if (roots === undefined) return false;
630
+ return !roots.has(root) && roots.size >= MAX_SEARCHED_ROOTS_PER_SLOT;
631
+ }
632
+
633
+ /**
634
+ * Cap distinct roots we buffer per slot.
635
+ */
636
+ tryReserveBufferedRoot(slot: Slot, root: RootHex): boolean {
637
+ const roots = this.bufferedRootsBySlot.get(slot);
638
+ if (roots?.has(root)) return true;
639
+ if ((roots?.size ?? 0) >= MAX_BUFFERED_ROOTS_PER_SLOT) return false;
640
+ this.bufferedRootsBySlot.getOrDefault(slot).add(root);
641
+ return true;
642
+ }
643
+
644
+ private searchUnknownRoot(
645
+ slotRoot: SlotRootHex,
646
+ searchBlock: boolean,
647
+ searchEnvelope: boolean,
648
+ peerId: PeerIdStr
649
+ ): void {
650
+ if (!searchBlock && !searchEnvelope) return;
651
+ if (this.tooManySearchedRoots(slotRoot.slot, slotRoot.root)) return;
652
+ if (searchBlock) this.searchUnknownBlock(slotRoot, BlockInputSource.network_processor, peerId);
653
+ if (searchEnvelope) this.searchUnknownEnvelope(slotRoot, BlockInputSource.network_processor, peerId);
654
+ }
655
+
574
656
  private pushPendingGossipsubMessageToQueue(message: PendingGossipsubMessage): void {
575
657
  const topicType = message.topic.type;
576
658
  message.queueAddedMs = Date.now();
@@ -585,64 +667,82 @@ export class NetworkProcessor {
585
667
  }
586
668
 
587
669
  private onBlockProcessed = async ({block: rootHex}: {block: string; executionOptimistic: boolean}): Promise<void> => {
588
- const waitingGossipsubMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
589
- if (!waitingGossipsubMessages || waitingGossipsubMessages.size === 0) {
670
+ const messagesByTopic = this.awaitingMessagesByBlockRoot.get(rootHex);
671
+ if (messagesByTopic === undefined) {
672
+ return;
673
+ }
674
+ let total = 0;
675
+ for (const messages of messagesByTopic.values()) {
676
+ total += messages.size;
677
+ }
678
+ if (total === 0) {
590
679
  return;
591
680
  }
592
681
 
593
682
  // Atomically remove from map and update counter before async iteration to
594
683
  // prevent double-decrement race with onClockSlot during yield points below
595
684
  if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
596
- this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
685
+ this.awaitingBlockMessageCount -= total;
597
686
  }
598
687
 
599
688
  const nowSec = Date.now() / 1000;
600
689
  let count = 0;
601
690
  // TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
602
691
  // cache, it may not be necessary at this time
603
- for (const message of waitingGossipsubMessages) {
604
- const topicType = message.topic.type;
605
- this.metrics?.awaitingBlockGossipMessages.waitSecBeforeResolve.set(
606
- {topic: topicType},
607
- nowSec - message.seenTimestampSec
608
- );
609
- this.metrics?.awaitingBlockGossipMessages.resolve.inc({topic: topicType});
610
- this.pushPendingGossipsubMessageToQueue(message);
611
- count++;
612
- // don't want to block the event loop, worse case it'd wait for 16_084 / 1024 * 50ms = 800ms which is not a big deal
613
- if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
614
- count = 0;
615
- await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
692
+ for (const messages of messagesByTopic.values()) {
693
+ for (const message of messages) {
694
+ const topicType = message.topic.type;
695
+ this.metrics?.awaitingBlockGossipMessages.waitSecBeforeResolve.set(
696
+ {topic: topicType},
697
+ nowSec - message.seenTimestampSec
698
+ );
699
+ this.metrics?.awaitingBlockGossipMessages.resolve.inc({topic: topicType});
700
+ this.pushPendingGossipsubMessageToQueue(message);
701
+ count++;
702
+ // don't want to block the event loop, worse case it'd wait for 16_084 / 1024 * 50ms = 800ms which is not a big deal
703
+ if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
704
+ count = 0;
705
+ await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
706
+ }
616
707
  }
617
708
  }
618
709
  };
619
710
 
620
711
  private onPayloadEnvelopeProcessed = async ({blockRoot: rootHex}: {blockRoot: RootHex}): Promise<void> => {
621
- const waitingGossipsubMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
622
- if (!waitingGossipsubMessages || waitingGossipsubMessages.size === 0) {
712
+ const messagesByTopic = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
713
+ if (messagesByTopic === undefined) {
714
+ return;
715
+ }
716
+ let total = 0;
717
+ for (const messages of messagesByTopic.values()) {
718
+ total += messages.size;
719
+ }
720
+ if (total === 0) {
623
721
  return;
624
722
  }
625
723
 
626
724
  // Atomically remove from map and update counter before async iteration to
627
725
  // prevent double-decrement race with onClockSlot during yield points below
628
726
  if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
629
- this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
727
+ this.awaitingPayloadMessageCount -= total;
630
728
  }
631
729
 
632
730
  const nowSec = Date.now() / 1000;
633
731
  let count = 0;
634
- for (const message of waitingGossipsubMessages) {
635
- const topicType = message.topic.type;
636
- this.metrics?.awaitingPayloadGossipMessages.waitSecBeforeResolve.set(
637
- {topic: topicType},
638
- nowSec - message.seenTimestampSec
639
- );
640
- this.metrics?.awaitingPayloadGossipMessages.resolve.inc({topic: topicType});
641
- this.pushPendingGossipsubMessageToQueue(message);
642
- count++;
643
- if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
644
- count = 0;
645
- await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
732
+ for (const messages of messagesByTopic.values()) {
733
+ for (const message of messages) {
734
+ const topicType = message.topic.type;
735
+ this.metrics?.awaitingPayloadGossipMessages.waitSecBeforeResolve.set(
736
+ {topic: topicType},
737
+ nowSec - message.seenTimestampSec
738
+ );
739
+ this.metrics?.awaitingPayloadGossipMessages.resolve.inc({topic: topicType});
740
+ this.pushPendingGossipsubMessageToQueue(message);
741
+ count++;
742
+ if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
743
+ count = 0;
744
+ await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
745
+ }
646
746
  }
647
747
  }
648
748
  };
@@ -651,12 +751,19 @@ export class NetworkProcessor {
651
751
  const nowSec = Date.now() / 1000;
652
752
  const minSlot = clockSlot - MAX_UNKNOWN_ROOTS_SLOT_CACHE_SIZE;
653
753
 
654
- for (const [slot, roots] of this.unknownBlocksBySlot) {
754
+ for (const [slot, searchedRoots] of this.searchedRootsBySlot) {
655
755
  if (slot > minSlot) continue;
656
- for (const rootHex of roots.keys()) {
657
- const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
658
- if (gossipMessages !== undefined) {
659
- for (const message of gossipMessages) {
756
+
757
+ // expire messages awaiting an unknown block for these roots. Only roots this slot did a BLOCK search
758
+ // for (blockPeerIds set) have block waits keyed here; skipping the rest avoids deleting a payload wait
759
+ // that a newer slot created for the same root (block/envelope searches share searchedRootsBySlot).
760
+ for (const [rootHex, entry] of searchedRoots) {
761
+ if (entry.blockPeerIds === undefined) continue;
762
+ const messagesByTopic = this.awaitingMessagesByBlockRoot.get(rootHex);
763
+ if (messagesByTopic === undefined) continue;
764
+ let removed = 0;
765
+ for (const messages of messagesByTopic.values()) {
766
+ for (const message of messages) {
660
767
  const topicType = message.topic.type;
661
768
  this.metrics?.awaitingBlockGossipMessages.reject.inc({
662
769
  topic: topicType,
@@ -668,20 +775,22 @@ export class NetworkProcessor {
668
775
  );
669
776
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
670
777
  }
671
- if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
672
- this.awaitingBlockMessageCount -= gossipMessages.size;
673
- }
778
+ removed += messages.size;
779
+ }
780
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
781
+ this.awaitingBlockMessageCount -= removed;
674
782
  }
675
783
  }
676
- this.unknownBlocksBySlot.delete(slot);
677
- }
678
784
 
679
- for (const [slot, roots] of this.unknownEnvelopesBySlot) {
680
- if (slot > minSlot) continue;
681
- for (const rootHex of roots.keys()) {
682
- const gossipMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
683
- if (gossipMessages !== undefined) {
684
- for (const message of gossipMessages) {
785
+ // expire messages awaiting an unknown payload envelope for these roots. Symmetric to the block pass:
786
+ // only roots this slot did an ENVELOPE search for (envelopePeerIds set) have payload waits keyed here.
787
+ for (const [rootHex, entry] of searchedRoots) {
788
+ if (entry.envelopePeerIds === undefined) continue;
789
+ const messagesByTopic = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
790
+ if (messagesByTopic === undefined) continue;
791
+ let removed = 0;
792
+ for (const messages of messagesByTopic.values()) {
793
+ for (const message of messages) {
685
794
  const topicType = message.topic.type;
686
795
  this.metrics?.awaitingPayloadGossipMessages.reject.inc({
687
796
  topic: topicType,
@@ -693,12 +802,18 @@ export class NetworkProcessor {
693
802
  );
694
803
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
695
804
  }
696
- if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
697
- this.awaitingPayloadMessageCount -= gossipMessages.size;
698
- }
805
+ removed += messages.size;
806
+ }
807
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
808
+ this.awaitingPayloadMessageCount -= removed;
699
809
  }
700
810
  }
701
- this.unknownEnvelopesBySlot.delete(slot);
811
+
812
+ this.searchedRootsBySlot.delete(slot);
813
+ }
814
+
815
+ for (const slot of this.bufferedRootsBySlot.keys()) {
816
+ if (slot <= minSlot) this.bufferedRootsBySlot.delete(slot);
702
817
  }
703
818
  };
704
819