@lodestar/beacon-node 1.48.0-dev.e9c9b8835d → 1.48.0-dev.fd70e5fc15

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 (200) 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/chain/archiveStore/archiveStore.d.ts +0 -1
  5. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  6. package/lib/chain/archiveStore/archiveStore.js +0 -1
  7. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  8. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  9. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +0 -1
  10. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  11. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  12. package/lib/chain/archiveStore/historicalState/metrics.js +1 -12
  13. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  14. package/lib/chain/archiveStore/historicalState/types.d.ts +0 -2
  15. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  16. package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/worker.js +1 -4
  18. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  19. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  20. package/lib/chain/archiveStore/utils/archiveBlocks.js +34 -31
  21. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  22. package/lib/chain/blocks/index.d.ts.map +1 -1
  23. package/lib/chain/blocks/index.js +2 -3
  24. package/lib/chain/blocks/index.js.map +1 -1
  25. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  26. package/lib/chain/blocks/writeBlockInputToDb.js +16 -9
  27. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  28. package/lib/chain/chain.d.ts +3 -4
  29. package/lib/chain/chain.d.ts.map +1 -1
  30. package/lib/chain/chain.js +35 -60
  31. package/lib/chain/chain.js.map +1 -1
  32. package/lib/chain/errors/blockError.d.ts +2 -16
  33. package/lib/chain/errors/blockError.d.ts.map +1 -1
  34. package/lib/chain/errors/blockError.js.map +1 -1
  35. package/lib/chain/errors/dataColumnSidecarError.d.ts +2 -15
  36. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  37. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  38. package/lib/chain/errors/executionPayloadEnvelope.d.ts +0 -10
  39. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  40. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  41. package/lib/chain/interface.d.ts +2 -2
  42. package/lib/chain/interface.d.ts.map +1 -1
  43. package/lib/chain/prepareNextSlot.js +13 -13
  44. package/lib/chain/prepareNextSlot.js.map +1 -1
  45. package/lib/chain/validation/block.d.ts.map +1 -1
  46. package/lib/chain/validation/block.js +4 -26
  47. package/lib/chain/validation/block.js.map +1 -1
  48. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  49. package/lib/chain/validation/dataColumnSidecar.js +13 -38
  50. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  51. package/lib/chain/validation/executionPayloadEnvelope.js +0 -10
  52. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  53. package/lib/db/beacon.d.ts +5 -14
  54. package/lib/db/beacon.d.ts.map +1 -1
  55. package/lib/db/beacon.js +3 -17
  56. package/lib/db/beacon.js.map +1 -1
  57. package/lib/db/index.d.ts +0 -1
  58. package/lib/db/index.d.ts.map +1 -1
  59. package/lib/db/interface.d.ts +1 -8
  60. package/lib/db/interface.d.ts.map +1 -1
  61. package/lib/db/repositories/blockArchive.d.ts +1 -8
  62. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  63. package/lib/db/repositories/blockArchive.js +28 -65
  64. package/lib/db/repositories/blockArchive.js.map +1 -1
  65. package/lib/db/repositories/blockArchiveIndex.d.ts +2 -1
  66. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  67. package/lib/db/repositories/blockArchiveIndex.js +7 -3
  68. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  69. package/lib/metrics/metrics/lodestar.d.ts +0 -18
  70. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  71. package/lib/metrics/metrics/lodestar.js +1 -55
  72. package/lib/metrics/metrics/lodestar.js.map +1 -1
  73. package/lib/monitoring/clientStats.js +1 -1
  74. package/lib/monitoring/clientStats.js.map +1 -1
  75. package/lib/network/gossip/encoding.d.ts.map +1 -1
  76. package/lib/network/gossip/encoding.js +6 -0
  77. package/lib/network/gossip/encoding.js.map +1 -1
  78. package/lib/network/gossip/topic.d.ts +1 -2
  79. package/lib/network/gossip/topic.d.ts.map +1 -1
  80. package/lib/network/gossip/topic.js +20 -4
  81. package/lib/network/gossip/topic.js.map +1 -1
  82. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  83. package/lib/network/processor/gossipHandlers.js +7 -6
  84. package/lib/network/processor/gossipHandlers.js.map +1 -1
  85. package/lib/network/processor/index.d.ts +3 -49
  86. package/lib/network/processor/index.d.ts.map +1 -1
  87. package/lib/network/processor/index.js +98 -232
  88. package/lib/network/processor/index.js.map +1 -1
  89. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  90. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +91 -112
  91. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  92. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  93. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +0 -2
  94. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  95. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  96. package/lib/network/reqresp/protocols.js +22 -6
  97. package/lib/network/reqresp/protocols.js.map +1 -1
  98. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +1 -2
  99. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  100. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +9 -14
  101. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  102. package/lib/node/nodejs.d.ts +1 -2
  103. package/lib/node/nodejs.d.ts.map +1 -1
  104. package/lib/node/nodejs.js +2 -4
  105. package/lib/node/nodejs.js.map +1 -1
  106. package/lib/util/sszBytes.d.ts +0 -6
  107. package/lib/util/sszBytes.d.ts.map +1 -1
  108. package/lib/util/sszBytes.js +1 -1
  109. package/lib/util/sszBytes.js.map +1 -1
  110. package/package.json +15 -15
  111. package/src/api/impl/beacon/blocks/index.ts +4 -8
  112. package/src/chain/archiveStore/archiveStore.ts +1 -6
  113. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +0 -1
  114. package/src/chain/archiveStore/historicalState/metrics.ts +1 -14
  115. package/src/chain/archiveStore/historicalState/types.ts +0 -2
  116. package/src/chain/archiveStore/historicalState/worker.ts +1 -4
  117. package/src/chain/archiveStore/utils/archiveBlocks.ts +39 -34
  118. package/src/chain/blocks/index.ts +2 -13
  119. package/src/chain/blocks/writeBlockInputToDb.ts +17 -10
  120. package/src/chain/chain.ts +34 -75
  121. package/src/chain/errors/blockError.ts +8 -20
  122. package/src/chain/errors/dataColumnSidecarError.ts +8 -24
  123. package/src/chain/errors/executionPayloadEnvelope.ts +2 -14
  124. package/src/chain/interface.ts +2 -2
  125. package/src/chain/prepareNextSlot.ts +16 -16
  126. package/src/chain/validation/block.ts +4 -26
  127. package/src/chain/validation/dataColumnSidecar.ts +14 -47
  128. package/src/chain/validation/executionPayloadEnvelope.ts +0 -10
  129. package/src/db/beacon.ts +6 -33
  130. package/src/db/index.ts +0 -1
  131. package/src/db/interface.ts +1 -9
  132. package/src/db/repositories/blockArchive.ts +30 -89
  133. package/src/db/repositories/blockArchiveIndex.ts +9 -4
  134. package/src/metrics/metrics/lodestar.ts +1 -57
  135. package/src/monitoring/clientStats.ts +1 -1
  136. package/src/network/gossip/encoding.ts +6 -0
  137. package/src/network/gossip/topic.ts +25 -3
  138. package/src/network/processor/gossipHandlers.ts +11 -11
  139. package/src/network/processor/index.ts +148 -263
  140. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +105 -139
  141. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +0 -2
  142. package/src/network/reqresp/protocols.ts +24 -7
  143. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +7 -15
  144. package/src/node/nodejs.ts +1 -6
  145. package/src/util/sszBytes.ts +1 -1
  146. package/lib/db/dataColumnStore.d.ts +0 -39
  147. package/lib/db/dataColumnStore.d.ts.map +0 -1
  148. package/lib/db/dataColumnStore.js +0 -81
  149. package/lib/db/dataColumnStore.js.map +0 -1
  150. package/lib/db/flatFileStore/atomicWrite.d.ts +0 -8
  151. package/lib/db/flatFileStore/atomicWrite.d.ts.map +0 -1
  152. package/lib/db/flatFileStore/atomicWrite.js +0 -61
  153. package/lib/db/flatFileStore/atomicWrite.js.map +0 -1
  154. package/lib/db/flatFileStore/dcolFormat.d.ts +0 -90
  155. package/lib/db/flatFileStore/dcolFormat.d.ts.map +0 -1
  156. package/lib/db/flatFileStore/dcolFormat.js +0 -286
  157. package/lib/db/flatFileStore/dcolFormat.js.map +0 -1
  158. package/lib/db/flatFileStore/errors.d.ts +0 -82
  159. package/lib/db/flatFileStore/errors.d.ts.map +0 -1
  160. package/lib/db/flatFileStore/errors.js +0 -32
  161. package/lib/db/flatFileStore/errors.js.map +0 -1
  162. package/lib/db/flatFileStore/flatFileStore.d.ts +0 -41
  163. package/lib/db/flatFileStore/flatFileStore.d.ts.map +0 -1
  164. package/lib/db/flatFileStore/flatFileStore.js +0 -251
  165. package/lib/db/flatFileStore/flatFileStore.js.map +0 -1
  166. package/lib/db/flatFileStore/index.d.ts +0 -4
  167. package/lib/db/flatFileStore/index.d.ts.map +0 -1
  168. package/lib/db/flatFileStore/index.js +0 -2
  169. package/lib/db/flatFileStore/index.js.map +0 -1
  170. package/lib/db/flatFileStore/interface.d.ts +0 -25
  171. package/lib/db/flatFileStore/interface.d.ts.map +0 -1
  172. package/lib/db/flatFileStore/interface.js +0 -2
  173. package/lib/db/flatFileStore/interface.js.map +0 -1
  174. package/lib/db/flatFileStore/metrics.d.ts +0 -24
  175. package/lib/db/flatFileStore/metrics.d.ts.map +0 -1
  176. package/lib/db/flatFileStore/metrics.js +0 -25
  177. package/lib/db/flatFileStore/metrics.js.map +0 -1
  178. package/lib/db/flatFileStore/path.d.ts +0 -9
  179. package/lib/db/flatFileStore/path.d.ts.map +0 -1
  180. package/lib/db/flatFileStore/path.js +0 -21
  181. package/lib/db/flatFileStore/path.js.map +0 -1
  182. package/lib/db/flatFileStore/slotIndex.d.ts +0 -14
  183. package/lib/db/flatFileStore/slotIndex.d.ts.map +0 -1
  184. package/lib/db/flatFileStore/slotIndex.js +0 -46
  185. package/lib/db/flatFileStore/slotIndex.js.map +0 -1
  186. package/lib/db/flatFileStore/snappy.d.ts +0 -3
  187. package/lib/db/flatFileStore/snappy.d.ts.map +0 -1
  188. package/lib/db/flatFileStore/snappy.js +0 -12
  189. package/lib/db/flatFileStore/snappy.js.map +0 -1
  190. package/src/db/dataColumnStore.ts +0 -113
  191. package/src/db/flatFileStore/atomicWrite.ts +0 -65
  192. package/src/db/flatFileStore/dcolFormat.ts +0 -389
  193. package/src/db/flatFileStore/errors.ts +0 -53
  194. package/src/db/flatFileStore/flatFileStore.ts +0 -310
  195. package/src/db/flatFileStore/index.ts +0 -3
  196. package/src/db/flatFileStore/interface.ts +0 -21
  197. package/src/db/flatFileStore/metrics.ts +0 -48
  198. package/src/db/flatFileStore/path.ts +0 -32
  199. package/src/db/flatFileStore/slotIndex.ts +0 -56
  200. package/src/db/flatFileStore/snappy.ts +0 -14
@@ -1,6 +1,6 @@
1
1
  import {TopicValidatorResult} from "@libp2p/gossipsub";
2
2
  import {routes} from "@lodestar/api";
3
- import {ForkSeq, NUMBER_OF_COLUMNS} from "@lodestar/params";
3
+ import {ForkSeq} 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,36 +59,6 @@ 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
-
92
62
  /**
93
63
  * This is respective to gossipsub seenTTL (which is 550 * 0.7 = 385s), also it's respective
94
64
  * to beacon_attestation ATTESTATION_PROPAGATION_SLOT_RANGE (32 slots).
@@ -150,14 +120,6 @@ export enum ReprocessRejectReason {
150
120
  * There are too many gossip messages that have unknown block root.
151
121
  */
152
122
  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",
161
123
  /**
162
124
  * The awaiting gossip message is pruned per clock slot.
163
125
  */
@@ -223,21 +185,19 @@ export class NetworkProcessor {
223
185
  private readonly gossipTopicConcurrency: {[K in GossipType]: number};
224
186
  private readonly extractBlockSlotRootFns = createExtractBlockSlotRootFns();
225
187
  // we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
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>>>;
188
+ // to be stored in this Map and reprocessed once the block comes
189
+ private readonly awaitingMessagesByBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
229
190
  private awaitingBlockMessageCount = 0;
230
191
  // we may not receive the payload for messages that require the FULL payload variant to be processed,
231
192
  // in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
232
- private readonly awaitingMessagesByPayloadBlockRoot: MapDef<
233
- RootHex,
234
- MapDef<GossipType, Set<PendingGossipsubMessage>>
235
- >;
193
+ private readonly awaitingMessagesByPayloadBlockRoot: MapDef<RootHex, Set<PendingGossipsubMessage>>;
236
194
  private awaitingPayloadMessageCount = 0;
237
- private searchedRootsBySlot = new MapDef<Slot, MapDef<RootHex, SearchedRootEntry>>(
238
- () => new MapDef<RootHex, SearchedRootEntry>(() => ({}))
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())
239
200
  );
240
- private bufferedRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
241
201
 
242
202
  constructor(
243
203
  modules: NetworkProcessorModules,
@@ -262,12 +222,8 @@ export class NetworkProcessor {
262
222
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadEnvelopeProcessed);
263
223
  this.chain.clock.on(ClockEvent.slot, this.onClockSlot);
264
224
 
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
- );
225
+ this.awaitingMessagesByBlockRoot = new MapDef<RootHex, Set<PendingGossipsubMessage>>(() => new Set());
226
+ this.awaitingMessagesByPayloadBlockRoot = new MapDef<RootHex, Set<PendingGossipsubMessage>>(() => new Set());
271
227
 
272
228
  // TODO: Implement queues and priorization for ReqResp incoming requests
273
229
  // Listens to NetworkEvent.reqRespIncomingRequest event
@@ -327,12 +283,9 @@ export class NetworkProcessor {
327
283
  if (this.chain.seenBlock(root)) {
328
284
  return;
329
285
  }
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;
286
+ const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
287
+ const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
288
+ const forwardedPeers = peersForRoot.getOrDefault(root);
336
289
 
337
290
  // brand-new search always emits (peer may be undefined)
338
291
  let shouldEmit = !alreadySearching;
@@ -356,12 +309,10 @@ export class NetworkProcessor {
356
309
  if (this.chain.seenPayloadEnvelope(root)) {
357
310
  return;
358
311
  }
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;
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);
365
316
 
366
317
  let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
367
318
  if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
@@ -423,15 +374,14 @@ export class NetworkProcessor {
423
374
  }
424
375
 
425
376
  message.msgSlot = slot;
426
- const peerId = message.propagationSource.toString();
427
377
 
428
378
  // this determines whether this message needs to wait for a Block or Envelope
429
- // a message should only wait for what it voted for, hence we don't want to put it on both queues.
379
+ // a message should only wait for what it voted for, hence we don't want to put it on both queues
430
380
  let preprocessResult: PreprocessResult = {action: PreprocessAction.PushToQueue};
431
381
  // 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
432
382
  if (root && !this.chain.forkChoice.hasBlockHexUnsafe(root)) {
433
383
  // starting from GLOAS, unknown root from data_column_sidecar also falls into this case
434
- this.searchUnknownRoot({slot, root}, true, false, peerId);
384
+ this.searchUnknownBlock({slot, root}, BlockInputSource.network_processor, message.propagationSource.toString());
435
385
  // for beacon_attestation and beacon_aggregate_and_proof messages, this is only temporary.
436
386
  // if "index = 1" we need to await for the Envelope instead
437
387
  preprocessResult = {action: PreprocessAction.AwaitBlock, root};
@@ -441,32 +391,40 @@ export class NetworkProcessor {
441
391
  // we separate the search action from the await action
442
392
 
443
393
  // beacon_block: proactively search for parent block/envelope across all forks, but never queue.
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).
394
+ // BlockInputSync handles cascading recovery if the gossip handler throws.
446
395
  if (topicType === GossipType.beacon_block) {
447
396
  const parentRoot = getParentRootFromSignedBeaconBlockSerialized(message.msg.data);
448
397
  if (parentRoot) {
449
- let searchParentBlock = false;
450
- let searchParentEnvelope = false;
451
398
  if (ForkSeq[fork] >= ForkSeq.gloas) {
452
399
  // GLOAS: also check parent envelope, same logic as execution_payload_bid
453
400
  const parentBlockHash = getParentBlockHashFromGloasSignedBeaconBlockSerialized(message.msg.data);
454
401
  if (parentBlockHash && !this.chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHash)) {
455
402
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
456
403
  if (protoBlock === null) {
457
- searchParentBlock = true;
404
+ this.searchUnknownBlock(
405
+ {slot, root: parentRoot},
406
+ BlockInputSource.network_processor,
407
+ message.propagationSource.toString()
408
+ );
458
409
  } else if (
459
410
  protoBlock.executionPayloadBlockHash &&
460
411
  protoBlock.executionPayloadBlockHash !== parentBlockHash
461
412
  ) {
462
413
  // only search for the envelope by block root if we're sure there is one. Otherwise UnknownBlockSync will penalize the peer.
463
- searchParentEnvelope = true;
414
+ this.searchUnknownEnvelope(
415
+ {slot, root: parentRoot},
416
+ BlockInputSource.network_processor,
417
+ message.propagationSource.toString()
418
+ );
464
419
  }
465
420
  }
466
421
  } else if (!this.chain.forkChoice.hasBlockHexUnsafe(parentRoot)) {
467
- searchParentBlock = true;
422
+ this.searchUnknownBlock(
423
+ {slot, root: parentRoot},
424
+ BlockInputSource.network_processor,
425
+ message.propagationSource.toString()
426
+ );
468
427
  }
469
- this.searchUnknownRoot({slot, root: parentRoot}, searchParentBlock, searchParentEnvelope, peerId);
470
428
  }
471
429
  preprocessResult = {action: PreprocessAction.PushToQueue};
472
430
  }
@@ -485,7 +443,11 @@ export class NetworkProcessor {
485
443
  : getDataIndexFromSignedAggregateAndProofSerialized(message.msg.data);
486
444
  if (attIndex === 1 && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
487
445
  // attestation votes that the payload is available but it is not yet known
488
- this.searchUnknownRoot({slot, root}, false, true, peerId);
446
+ this.searchUnknownEnvelope(
447
+ {slot, root},
448
+ BlockInputSource.network_processor,
449
+ message.propagationSource.toString()
450
+ );
489
451
  preprocessResult = {action: PreprocessAction.AwaitEnvelope, root};
490
452
  }
491
453
  break;
@@ -495,7 +457,11 @@ export class NetworkProcessor {
495
457
  const payloadPresent = getPayloadPresentFromPayloadAttestationMessageSerialized(message.msg.data);
496
458
  if (payloadPresent && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
497
459
  // payload attestation votes that the payload is available but it is not yet known
498
- this.searchUnknownRoot({slot, root}, false, true, peerId);
460
+ this.searchUnknownEnvelope(
461
+ {slot, root},
462
+ BlockInputSource.network_processor,
463
+ message.propagationSource.toString()
464
+ );
499
465
  // do not await the envelope, payload attestation processing only requires that the block is known
500
466
  // also do not reset preprocessResult, we may already await for the block
501
467
  }
@@ -504,7 +470,11 @@ export class NetworkProcessor {
504
470
  case GossipType.data_column_sidecar: {
505
471
  if (root == null) break;
506
472
  if (!this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
507
- this.searchUnknownRoot({slot, root}, false, true, peerId);
473
+ this.searchUnknownEnvelope(
474
+ {slot, root},
475
+ BlockInputSource.network_processor,
476
+ message.propagationSource.toString()
477
+ );
508
478
  // do not await the envelope, we can do gossip validation
509
479
  // also do not reset preprocessResult, we may already await for the block
510
480
  }
@@ -515,7 +485,11 @@ export class NetworkProcessor {
515
485
  // Extract beacon_block_root directly
516
486
  const blockRoot = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(message.msg.data);
517
487
  if (blockRoot && !this.chain.forkChoice.hasBlockHexUnsafe(blockRoot)) {
518
- this.searchUnknownRoot({slot, root: blockRoot}, true, false, peerId);
488
+ this.searchUnknownBlock(
489
+ {slot, root: blockRoot},
490
+ BlockInputSource.network_processor,
491
+ message.propagationSource.toString()
492
+ );
519
493
  // We always want to await the block
520
494
  // This allows us to properly forward the payload envelope
521
495
  preprocessResult = {action: PreprocessAction.AwaitBlock, root: blockRoot};
@@ -534,13 +508,21 @@ export class NetworkProcessor {
534
508
  ) {
535
509
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentBlockRoot);
536
510
  if (protoBlock === null) {
537
- this.searchUnknownRoot({slot, root: parentBlockRoot}, true, false, peerId);
511
+ this.searchUnknownBlock(
512
+ {slot, root: parentBlockRoot},
513
+ BlockInputSource.network_processor,
514
+ message.propagationSource.toString()
515
+ );
538
516
  preprocessResult = {action: PreprocessAction.AwaitBlock, root: parentBlockRoot};
539
517
  } else if (
540
518
  protoBlock.executionPayloadBlockHash &&
541
519
  protoBlock.executionPayloadBlockHash !== parentBlockHash
542
520
  ) {
543
- this.searchUnknownRoot({slot, root: parentBlockRoot}, false, true, peerId);
521
+ this.searchUnknownEnvelope(
522
+ {slot, root: parentBlockRoot},
523
+ BlockInputSource.network_processor,
524
+ message.propagationSource.toString()
525
+ );
544
526
  preprocessResult = {action: PreprocessAction.AwaitEnvelope, root: parentBlockRoot};
545
527
  }
546
528
  }
@@ -553,106 +535,42 @@ export class NetworkProcessor {
553
535
  case PreprocessAction.PushToQueue:
554
536
  this.pushPendingGossipsubMessageToQueue(message);
555
537
  break;
556
- case PreprocessAction.AwaitBlock:
557
- this.maybeAwaitBlock(preprocessResult.root, topicType, slot, message);
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++;
558
552
  break;
559
- case PreprocessAction.AwaitEnvelope:
560
- this.maybeAwaitPayload(preprocessResult.root, topicType, slot, message);
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++;
561
569
  break;
570
+ }
562
571
  }
563
572
  };
564
573
 
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
-
656
574
  private pushPendingGossipsubMessageToQueue(message: PendingGossipsubMessage): void {
657
575
  const topicType = message.topic.type;
658
576
  message.queueAddedMs = Date.now();
@@ -667,82 +585,64 @@ export class NetworkProcessor {
667
585
  }
668
586
 
669
587
  private onBlockProcessed = async ({block: rootHex}: {block: string; executionOptimistic: boolean}): Promise<void> => {
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) {
588
+ const waitingGossipsubMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
589
+ if (!waitingGossipsubMessages || waitingGossipsubMessages.size === 0) {
679
590
  return;
680
591
  }
681
592
 
682
593
  // Atomically remove from map and update counter before async iteration to
683
594
  // prevent double-decrement race with onClockSlot during yield points below
684
595
  if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
685
- this.awaitingBlockMessageCount -= total;
596
+ this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
686
597
  }
687
598
 
688
599
  const nowSec = Date.now() / 1000;
689
600
  let count = 0;
690
601
  // TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
691
602
  // cache, it may not be necessary at this time
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
- }
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);
707
616
  }
708
617
  }
709
618
  };
710
619
 
711
620
  private onPayloadEnvelopeProcessed = async ({blockRoot: rootHex}: {blockRoot: RootHex}): Promise<void> => {
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) {
621
+ const waitingGossipsubMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
622
+ if (!waitingGossipsubMessages || waitingGossipsubMessages.size === 0) {
721
623
  return;
722
624
  }
723
625
 
724
626
  // Atomically remove from map and update counter before async iteration to
725
627
  // prevent double-decrement race with onClockSlot during yield points below
726
628
  if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
727
- this.awaitingPayloadMessageCount -= total;
629
+ this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
728
630
  }
729
631
 
730
632
  const nowSec = Date.now() / 1000;
731
633
  let count = 0;
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
- }
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);
746
646
  }
747
647
  }
748
648
  };
@@ -751,19 +651,12 @@ export class NetworkProcessor {
751
651
  const nowSec = Date.now() / 1000;
752
652
  const minSlot = clockSlot - MAX_UNKNOWN_ROOTS_SLOT_CACHE_SIZE;
753
653
 
754
- for (const [slot, searchedRoots] of this.searchedRootsBySlot) {
654
+ for (const [slot, roots] of this.unknownBlocksBySlot) {
755
655
  if (slot > minSlot) continue;
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) {
656
+ for (const rootHex of roots.keys()) {
657
+ const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
658
+ if (gossipMessages !== undefined) {
659
+ for (const message of gossipMessages) {
767
660
  const topicType = message.topic.type;
768
661
  this.metrics?.awaitingBlockGossipMessages.reject.inc({
769
662
  topic: topicType,
@@ -775,22 +668,20 @@ export class NetworkProcessor {
775
668
  );
776
669
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
777
670
  }
778
- removed += messages.size;
779
- }
780
- if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
781
- this.awaitingBlockMessageCount -= removed;
671
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
672
+ this.awaitingBlockMessageCount -= gossipMessages.size;
673
+ }
782
674
  }
783
675
  }
676
+ this.unknownBlocksBySlot.delete(slot);
677
+ }
784
678
 
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) {
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) {
794
685
  const topicType = message.topic.type;
795
686
  this.metrics?.awaitingPayloadGossipMessages.reject.inc({
796
687
  topic: topicType,
@@ -802,18 +693,12 @@ export class NetworkProcessor {
802
693
  );
803
694
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
804
695
  }
805
- removed += messages.size;
806
- }
807
- if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
808
- this.awaitingPayloadMessageCount -= removed;
696
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
697
+ this.awaitingPayloadMessageCount -= gossipMessages.size;
698
+ }
809
699
  }
810
700
  }
811
-
812
- this.searchedRootsBySlot.delete(slot);
813
- }
814
-
815
- for (const slot of this.bufferedRootsBySlot.keys()) {
816
- if (slot <= minSlot) this.bufferedRootsBySlot.delete(slot);
701
+ this.unknownEnvelopesBySlot.delete(slot);
817
702
  }
818
703
  };
819
704