@lodestar/beacon-node 1.48.0-dev.f22c5ce63e → 1.48.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) 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 -11
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/state/utils.js +14 -0
  6. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  7. package/lib/api/impl/config/index.d.ts.map +1 -1
  8. package/lib/api/impl/config/index.js +4 -1
  9. package/lib/api/impl/config/index.js.map +1 -1
  10. package/lib/api/impl/validator/index.d.ts.map +1 -1
  11. package/lib/api/impl/validator/index.js +39 -39
  12. package/lib/api/impl/validator/index.js.map +1 -1
  13. package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
  14. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  15. package/lib/chain/archiveStore/archiveStore.js +5 -1
  16. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  17. package/lib/chain/archiveStore/historicalState/historicalStateRegen.d.ts.map +1 -1
  18. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -0
  19. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  20. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  21. package/lib/chain/archiveStore/historicalState/metrics.js +12 -1
  22. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  23. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -0
  24. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  25. package/lib/chain/archiveStore/historicalState/types.js.map +1 -1
  26. package/lib/chain/archiveStore/historicalState/worker.js +4 -1
  27. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  28. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts +9 -1
  29. package/lib/chain/archiveStore/utils/archiveBlocks.d.ts.map +1 -1
  30. package/lib/chain/archiveStore/utils/archiveBlocks.js +68 -38
  31. package/lib/chain/archiveStore/utils/archiveBlocks.js.map +1 -1
  32. package/lib/chain/blocks/importBlock.d.ts +5 -1
  33. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  34. package/lib/chain/blocks/importBlock.js +12 -18
  35. package/lib/chain/blocks/importBlock.js.map +1 -1
  36. package/lib/chain/blocks/index.d.ts +8 -4
  37. package/lib/chain/blocks/index.d.ts.map +1 -1
  38. package/lib/chain/blocks/index.js +83 -17
  39. package/lib/chain/blocks/index.js.map +1 -1
  40. package/lib/chain/blocks/types.d.ts +8 -0
  41. package/lib/chain/blocks/types.d.ts.map +1 -1
  42. package/lib/chain/blocks/types.js.map +1 -1
  43. package/lib/chain/blocks/utils/chainSegment.d.ts +1 -1
  44. package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
  45. package/lib/chain/blocks/utils/chainSegment.js +26 -11
  46. package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
  47. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  48. package/lib/chain/blocks/writeBlockInputToDb.js +9 -16
  49. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  50. package/lib/chain/chain.d.ts +11 -5
  51. package/lib/chain/chain.d.ts.map +1 -1
  52. package/lib/chain/chain.js +129 -75
  53. package/lib/chain/chain.js.map +1 -1
  54. package/lib/chain/emitter.d.ts +1 -1
  55. package/lib/chain/emitter.js +1 -1
  56. package/lib/chain/errors/blockError.d.ts +19 -7
  57. package/lib/chain/errors/blockError.d.ts.map +1 -1
  58. package/lib/chain/errors/blockError.js +2 -2
  59. package/lib/chain/errors/blockError.js.map +1 -1
  60. package/lib/chain/errors/dataColumnSidecarError.d.ts +15 -2
  61. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  62. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  63. package/lib/chain/errors/executionPayloadBid.d.ts +4 -0
  64. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  65. package/lib/chain/errors/executionPayloadBid.js +1 -0
  66. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  67. package/lib/chain/errors/executionPayloadEnvelope.d.ts +10 -11
  68. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  69. package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
  70. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  71. package/lib/chain/errors/gossipValidation.d.ts +2 -1
  72. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  73. package/lib/chain/errors/gossipValidation.js +1 -0
  74. package/lib/chain/errors/gossipValidation.js.map +1 -1
  75. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  76. package/lib/chain/forkChoice/index.js +2 -0
  77. package/lib/chain/forkChoice/index.js.map +1 -1
  78. package/lib/chain/interface.d.ts +4 -4
  79. package/lib/chain/interface.d.ts.map +1 -1
  80. package/lib/chain/lightClient/index.d.ts +3 -3
  81. package/lib/chain/lightClient/index.d.ts.map +1 -1
  82. package/lib/chain/lightClient/index.js +21 -23
  83. package/lib/chain/lightClient/index.js.map +1 -1
  84. package/lib/chain/lightClient/proofs.d.ts +2 -1
  85. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  86. package/lib/chain/lightClient/proofs.js +5 -1
  87. package/lib/chain/lightClient/proofs.js.map +1 -1
  88. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  89. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  90. package/lib/chain/prepareNextSlot.js +13 -13
  91. package/lib/chain/prepareNextSlot.js.map +1 -1
  92. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  93. package/lib/chain/produceBlock/produceBlockBody.js +46 -64
  94. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  95. package/lib/chain/validation/block.d.ts.map +1 -1
  96. package/lib/chain/validation/block.js +35 -54
  97. package/lib/chain/validation/block.js.map +1 -1
  98. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  99. package/lib/chain/validation/dataColumnSidecar.js +38 -13
  100. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  101. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  102. package/lib/chain/validation/executionPayloadBid.js +56 -40
  103. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  104. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  105. package/lib/chain/validation/executionPayloadEnvelope.js +10 -29
  106. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  107. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  108. package/lib/chain/validation/payloadAttestationMessage.js +5 -6
  109. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  110. package/lib/chain/validatorMonitor.d.ts +7 -1
  111. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  112. package/lib/chain/validatorMonitor.js +22 -5
  113. package/lib/chain/validatorMonitor.js.map +1 -1
  114. package/lib/db/beacon.d.ts +14 -5
  115. package/lib/db/beacon.d.ts.map +1 -1
  116. package/lib/db/beacon.js +17 -3
  117. package/lib/db/beacon.js.map +1 -1
  118. package/lib/db/dataColumnStore.d.ts +39 -0
  119. package/lib/db/dataColumnStore.d.ts.map +1 -0
  120. package/lib/db/dataColumnStore.js +81 -0
  121. package/lib/db/dataColumnStore.js.map +1 -0
  122. package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
  123. package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
  124. package/lib/db/flatFileStore/atomicWrite.js +61 -0
  125. package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
  126. package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
  127. package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
  128. package/lib/db/flatFileStore/dcolFormat.js +286 -0
  129. package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
  130. package/lib/db/flatFileStore/errors.d.ts +82 -0
  131. package/lib/db/flatFileStore/errors.d.ts.map +1 -0
  132. package/lib/db/flatFileStore/errors.js +32 -0
  133. package/lib/db/flatFileStore/errors.js.map +1 -0
  134. package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
  135. package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
  136. package/lib/db/flatFileStore/flatFileStore.js +251 -0
  137. package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
  138. package/lib/db/flatFileStore/index.d.ts +4 -0
  139. package/lib/db/flatFileStore/index.d.ts.map +1 -0
  140. package/lib/db/flatFileStore/index.js +2 -0
  141. package/lib/db/flatFileStore/index.js.map +1 -0
  142. package/lib/db/flatFileStore/interface.d.ts +25 -0
  143. package/lib/db/flatFileStore/interface.d.ts.map +1 -0
  144. package/lib/db/flatFileStore/interface.js +2 -0
  145. package/lib/db/flatFileStore/interface.js.map +1 -0
  146. package/lib/db/flatFileStore/metrics.d.ts +24 -0
  147. package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
  148. package/lib/db/flatFileStore/metrics.js +25 -0
  149. package/lib/db/flatFileStore/metrics.js.map +1 -0
  150. package/lib/db/flatFileStore/path.d.ts +9 -0
  151. package/lib/db/flatFileStore/path.d.ts.map +1 -0
  152. package/lib/db/flatFileStore/path.js +21 -0
  153. package/lib/db/flatFileStore/path.js.map +1 -0
  154. package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
  155. package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
  156. package/lib/db/flatFileStore/slotIndex.js +46 -0
  157. package/lib/db/flatFileStore/slotIndex.js.map +1 -0
  158. package/lib/db/flatFileStore/snappy.d.ts +3 -0
  159. package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
  160. package/lib/db/flatFileStore/snappy.js +12 -0
  161. package/lib/db/flatFileStore/snappy.js.map +1 -0
  162. package/lib/db/index.d.ts +1 -0
  163. package/lib/db/index.d.ts.map +1 -1
  164. package/lib/db/interface.d.ts +8 -1
  165. package/lib/db/interface.d.ts.map +1 -1
  166. package/lib/db/repositories/blockArchive.d.ts +8 -1
  167. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  168. package/lib/db/repositories/blockArchive.js +65 -28
  169. package/lib/db/repositories/blockArchive.js.map +1 -1
  170. package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
  171. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  172. package/lib/db/repositories/blockArchiveIndex.js +3 -7
  173. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  174. package/lib/execution/builder/validateBid.d.ts.map +1 -1
  175. package/lib/execution/builder/validateBid.js +8 -5
  176. package/lib/execution/builder/validateBid.js.map +1 -1
  177. package/lib/execution/engine/mock.d.ts.map +1 -1
  178. package/lib/execution/engine/mock.js +4 -1
  179. package/lib/execution/engine/mock.js.map +1 -1
  180. package/lib/metrics/metrics/lodestar.d.ts +22 -3
  181. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  182. package/lib/metrics/metrics/lodestar.js +60 -6
  183. package/lib/metrics/metrics/lodestar.js.map +1 -1
  184. package/lib/monitoring/clientStats.js +1 -1
  185. package/lib/monitoring/clientStats.js.map +1 -1
  186. package/lib/network/gossip/encoding.d.ts.map +1 -1
  187. package/lib/network/gossip/encoding.js +0 -7
  188. package/lib/network/gossip/encoding.js.map +1 -1
  189. package/lib/network/gossip/topic.d.ts +2 -1
  190. package/lib/network/gossip/topic.d.ts.map +1 -1
  191. package/lib/network/gossip/topic.js +4 -20
  192. package/lib/network/gossip/topic.js.map +1 -1
  193. package/lib/network/network.js +2 -2
  194. package/lib/network/network.js.map +1 -1
  195. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  196. package/lib/network/processor/gossipHandlers.js +388 -285
  197. package/lib/network/processor/gossipHandlers.js.map +1 -1
  198. package/lib/network/processor/index.d.ts +49 -3
  199. package/lib/network/processor/index.d.ts.map +1 -1
  200. package/lib/network/processor/index.js +254 -114
  201. package/lib/network/processor/index.js.map +1 -1
  202. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  203. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
  204. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  205. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  206. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
  207. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  208. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  209. package/lib/network/reqresp/protocols.js +6 -22
  210. package/lib/network/reqresp/protocols.js.map +1 -1
  211. package/lib/network/reqresp/rateLimit.js +2 -1
  212. package/lib/network/reqresp/rateLimit.js.map +1 -1
  213. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
  214. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  215. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
  216. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  217. package/lib/node/nodejs.d.ts +2 -1
  218. package/lib/node/nodejs.d.ts.map +1 -1
  219. package/lib/node/nodejs.js +4 -2
  220. package/lib/node/nodejs.js.map +1 -1
  221. package/lib/sync/range/range.d.ts.map +1 -1
  222. package/lib/sync/range/range.js +24 -1
  223. package/lib/sync/range/range.js.map +1 -1
  224. package/lib/util/sszBytes.d.ts +6 -0
  225. package/lib/util/sszBytes.d.ts.map +1 -1
  226. package/lib/util/sszBytes.js +1 -1
  227. package/lib/util/sszBytes.js.map +1 -1
  228. package/package.json +18 -19
  229. package/src/api/impl/beacon/blocks/index.ts +8 -14
  230. package/src/api/impl/beacon/state/utils.ts +15 -0
  231. package/src/api/impl/config/index.ts +4 -1
  232. package/src/api/impl/validator/index.ts +44 -43
  233. package/src/chain/archiveStore/archiveStore.ts +12 -1
  234. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
  235. package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
  236. package/src/chain/archiveStore/historicalState/types.ts +2 -0
  237. package/src/chain/archiveStore/historicalState/worker.ts +4 -1
  238. package/src/chain/archiveStore/utils/archiveBlocks.ts +73 -42
  239. package/src/chain/blocks/importBlock.ts +25 -20
  240. package/src/chain/blocks/index.ts +120 -23
  241. package/src/chain/blocks/types.ts +9 -0
  242. package/src/chain/blocks/utils/chainSegment.ts +26 -12
  243. package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
  244. package/src/chain/chain.ts +153 -84
  245. package/src/chain/emitter.ts +1 -1
  246. package/src/chain/errors/blockError.ts +22 -10
  247. package/src/chain/errors/dataColumnSidecarError.ts +24 -8
  248. package/src/chain/errors/executionPayloadBid.ts +2 -0
  249. package/src/chain/errors/executionPayloadEnvelope.ts +14 -11
  250. package/src/chain/errors/gossipValidation.ts +1 -0
  251. package/src/chain/forkChoice/index.ts +2 -0
  252. package/src/chain/interface.ts +4 -4
  253. package/src/chain/lightClient/index.ts +32 -36
  254. package/src/chain/lightClient/proofs.ts +10 -0
  255. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  256. package/src/chain/prepareNextSlot.ts +16 -16
  257. package/src/chain/produceBlock/produceBlockBody.ts +50 -71
  258. package/src/chain/validation/block.ts +35 -70
  259. package/src/chain/validation/dataColumnSidecar.ts +47 -14
  260. package/src/chain/validation/executionPayloadBid.ts +62 -44
  261. package/src/chain/validation/executionPayloadEnvelope.ts +10 -37
  262. package/src/chain/validation/payloadAttestationMessage.ts +8 -9
  263. package/src/chain/validatorMonitor.ts +24 -7
  264. package/src/db/beacon.ts +33 -6
  265. package/src/db/dataColumnStore.ts +113 -0
  266. package/src/db/flatFileStore/atomicWrite.ts +65 -0
  267. package/src/db/flatFileStore/dcolFormat.ts +389 -0
  268. package/src/db/flatFileStore/errors.ts +53 -0
  269. package/src/db/flatFileStore/flatFileStore.ts +310 -0
  270. package/src/db/flatFileStore/index.ts +3 -0
  271. package/src/db/flatFileStore/interface.ts +21 -0
  272. package/src/db/flatFileStore/metrics.ts +48 -0
  273. package/src/db/flatFileStore/path.ts +32 -0
  274. package/src/db/flatFileStore/slotIndex.ts +56 -0
  275. package/src/db/flatFileStore/snappy.ts +14 -0
  276. package/src/db/index.ts +1 -0
  277. package/src/db/interface.ts +9 -1
  278. package/src/db/repositories/blockArchive.ts +89 -30
  279. package/src/db/repositories/blockArchiveIndex.ts +4 -9
  280. package/src/execution/builder/validateBid.ts +10 -6
  281. package/src/execution/engine/mock.ts +4 -1
  282. package/src/metrics/metrics/lodestar.ts +63 -6
  283. package/src/monitoring/clientStats.ts +1 -1
  284. package/src/network/gossip/encoding.ts +0 -7
  285. package/src/network/gossip/topic.ts +3 -25
  286. package/src/network/network.ts +2 -2
  287. package/src/network/processor/gossipHandlers.ts +500 -355
  288. package/src/network/processor/index.ts +287 -165
  289. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
  290. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
  291. package/src/network/reqresp/protocols.ts +7 -24
  292. package/src/network/reqresp/rateLimit.ts +2 -1
  293. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
  294. package/src/node/nodejs.ts +6 -1
  295. package/src/sync/range/range.ts +23 -1
  296. package/src/util/sszBytes.ts +1 -1
@@ -1,13 +1,12 @@
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 { MapDef, mapValues, sleep } from "@lodestar/utils";
6
6
  import { BlockInputSource } from "../../chain/blocks/blockInput/types.js";
7
7
  import { ChainEvent } from "../../chain/emitter.js";
8
8
  import { GossipErrorCode } from "../../chain/errors/gossipValidation.js";
9
9
  import { ClockEvent } from "../../util/clock.js";
10
- import { callInNextEventLoop } from "../../util/eventLoop.js";
11
10
  import { getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized, getDataIndexFromSignedAggregateAndProofSerialized, getDataIndexFromSingleAttestationSerialized, getParentBlockHashFromGloasSignedBeaconBlockSerialized, getParentBlockHashFromSignedExecutionPayloadBidSerialized, getParentBlockRootFromSignedExecutionPayloadBidSerialized, getParentRootFromSignedBeaconBlockSerialized, getPayloadPresentFromPayloadAttestationMessageSerialized, } from "../../util/sszBytes.js";
12
11
  import { NetworkEvent } from "../events.js";
13
12
  import { GossipType, } from "../gossip/interface.js";
@@ -21,6 +20,28 @@ export * from "./types.js";
21
20
  * Keep up to 3 slot of unknown roots, so we don't always emit to UnknownBlock sync.
22
21
  */
23
22
  const MAX_UNKNOWN_ROOTS_SLOT_CACHE_SIZE = 3;
23
+ /**
24
+ * Max distinct roots we BUFFER messages for, per slot. Kept tight to avoid the OOM
25
+ * risk. We don't support super forky condition where there are more than this many roots per slot via gossip.
26
+ */
27
+ export const MAX_BUFFERED_ROOTS_PER_SLOT = 5;
28
+ /**
29
+ * Max distinct roots we emit an unknown-root search for, per slot. Higher than
30
+ * the buffer budget because:
31
+ * - in the attack scenario, the genuine root may comes after the first MAX_BUFFERED_ROOTS_PER_SLOT roots
32
+ * - if we cannot search, we'll penalize peers in UnknownBlockInput, not in NetworkProcessor
33
+ */
34
+ export const MAX_SEARCHED_ROOTS_PER_SLOT = 32;
35
+ /**
36
+ * Given the same root and topic, we'll ignore messages after the below cap. Each cap is the number of
37
+ * legitimate messages we expect for one block, plus headroom for some malformed messages that may arrives first
38
+ * they will be penalized by the gossip handler
39
+ */
40
+ export const MAX_AWAITING_MESSAGES_PER_ROOT = {
41
+ [GossipType.data_column_sidecar]: 2 * NUMBER_OF_COLUMNS,
42
+ [GossipType.execution_payload]: 2,
43
+ [GossipType.execution_payload_bid]: 8,
44
+ };
24
45
  /**
25
46
  * This is respective to gossipsub seenTTL (which is 550 * 0.7 = 385s), also it's respective
26
47
  * to beacon_attestation ATTESTATION_PROPAGATION_SLOT_RANGE (32 slots).
@@ -73,6 +94,14 @@ export var ReprocessRejectReason;
73
94
  * There are too many gossip messages that have unknown block root.
74
95
  */
75
96
  ReprocessRejectReason["reached_limit"] = "reached_limit";
97
+ /**
98
+ * Too many distinct unknown roots are already tracked for the message's slot (MAX_UNKNOWN_ROOTS_PER_SLOT).
99
+ */
100
+ ReprocessRejectReason["reached_root_limit"] = "reached_root_limit";
101
+ /**
102
+ * Too many gossip messages are already buffered for this (root, topic) (MAX_AWAITING_MESSAGES_PER_ROOT).
103
+ */
104
+ ReprocessRejectReason["reached_topic_limit"] = "reached_topic_limit";
76
105
  /**
77
106
  * The awaiting gossip message is pruned per clock slot.
78
107
  */
@@ -133,15 +162,16 @@ export class NetworkProcessor {
133
162
  gossipTopicConcurrency;
134
163
  extractBlockSlotRootFns = createExtractBlockSlotRootFns();
135
164
  // we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
136
- // to be stored in this Map and reprocessed once the block comes
165
+ // to be stored in this Map and reprocessed once the block comes. Keyed by topic per root so we can cap
166
+ // the number of buffered messages per (root, topic) - see MAX_AWAITING_MESSAGES_PER_ROOT.
137
167
  awaitingMessagesByBlockRoot;
138
168
  awaitingBlockMessageCount = 0;
139
169
  // we may not receive the payload for messages that require the FULL payload variant to be processed,
140
170
  // in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
141
171
  awaitingMessagesByPayloadBlockRoot;
142
172
  awaitingPayloadMessageCount = 0;
143
- unknownBlocksBySlot = new MapDef(() => new MapDef(() => new Set()));
144
- unknownEnvelopesBySlot = new MapDef(() => new MapDef(() => new Set()));
173
+ searchedRootsBySlot = new MapDef(() => new MapDef(() => ({})));
174
+ bufferedRootsBySlot = new MapDef(() => new Set());
145
175
  constructor(modules, opts) {
146
176
  this.opts = opts;
147
177
  const { chain, events, logger, metrics } = modules;
@@ -158,8 +188,8 @@ export class NetworkProcessor {
158
188
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockProcessed);
159
189
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadEnvelopeProcessed);
160
190
  this.chain.clock.on(ClockEvent.slot, this.onClockSlot);
161
- this.awaitingMessagesByBlockRoot = new MapDef(() => new Set());
162
- this.awaitingMessagesByPayloadBlockRoot = new MapDef(() => new Set());
191
+ this.awaitingMessagesByBlockRoot = new MapDef(() => new MapDef(() => new Set()));
192
+ this.awaitingMessagesByPayloadBlockRoot = new MapDef(() => new MapDef(() => new Set()));
163
193
  // TODO: Implement queues and priorization for ReqResp incoming requests
164
194
  // Listens to NetworkEvent.reqRespIncomingRequest event
165
195
  if (metrics) {
@@ -211,9 +241,12 @@ export class NetworkProcessor {
211
241
  if (this.chain.seenBlock(root)) {
212
242
  return;
213
243
  }
214
- const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
215
- const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
216
- const forwardedPeers = peersForRoot.getOrDefault(root);
244
+ const entry = this.searchedRootsBySlot.getOrDefault(slot).getOrDefault(root);
245
+ const alreadySearching = entry.blockPeerIds !== undefined;
246
+ if (entry.blockPeerIds === undefined) {
247
+ entry.blockPeerIds = new Set();
248
+ }
249
+ const forwardedPeers = entry.blockPeerIds;
217
250
  // brand-new search always emits (peer may be undefined)
218
251
  let shouldEmit = !alreadySearching;
219
252
  if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
@@ -235,10 +268,12 @@ export class NetworkProcessor {
235
268
  if (this.chain.seenPayloadEnvelope(root)) {
236
269
  return;
237
270
  }
238
- const peersForRoot = this.unknownEnvelopesBySlot.getOrDefault(slot);
239
- // capture "already searching" BEFORE getOrDefault(root) creates the entry
240
- const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByPayloadBlockRoot.has(root);
241
- const forwardedPeers = peersForRoot.getOrDefault(root);
271
+ const entry = this.searchedRootsBySlot.getOrDefault(slot).getOrDefault(root);
272
+ const alreadySearching = entry.envelopePeerIds !== undefined;
273
+ if (entry.envelopePeerIds === undefined) {
274
+ entry.envelopePeerIds = new Set();
275
+ }
276
+ const forwardedPeers = entry.envelopePeerIds;
242
277
  let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
243
278
  if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
244
279
  // First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
@@ -281,14 +316,26 @@ export class NetworkProcessor {
281
316
  });
282
317
  return;
283
318
  }
319
+ // DOS protection: drop far-future messages before allocating any unknown-root tracking entry.
320
+ // Bound each topic to what its gossip validator accepts; only execution_payload_bid accepts the next slot.
321
+ const latestPermissableSlot = this.chain.clock.currentSlotWithGossipDisparity + (topicType === GossipType.execution_payload_bid ? 1 : 0);
322
+ if (slot > latestPermissableSlot) {
323
+ // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
324
+ this.metrics?.networkProcessor.gossipValidationError.inc({
325
+ topic: topicType,
326
+ error: GossipErrorCode.FUTURE_SLOT,
327
+ });
328
+ return;
329
+ }
284
330
  message.msgSlot = slot;
331
+ const peerId = message.propagationSource.toString();
285
332
  // this determines whether this message needs to wait for a Block or Envelope
286
- // a message should only wait for what it voted for, hence we don't want to put it on both queues
333
+ // a message should only wait for what it voted for, hence we don't want to put it on both queues.
287
334
  let preprocessResult = { action: PreprocessAction.PushToQueue };
288
335
  // 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
289
336
  if (root && !this.chain.forkChoice.hasBlockHexUnsafe(root)) {
290
337
  // starting from GLOAS, unknown root from data_column_sidecar also falls into this case
291
- this.searchUnknownBlock({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
338
+ this.searchUnknownRoot({ slot, root }, true, false, peerId);
292
339
  // for beacon_attestation and beacon_aggregate_and_proof messages, this is only temporary.
293
340
  // if "index = 1" we need to await for the Envelope instead
294
341
  preprocessResult = { action: PreprocessAction.AwaitBlock, root };
@@ -296,28 +343,32 @@ export class NetworkProcessor {
296
343
  // 2nd extract round for some specific topics
297
344
  // we separate the search action from the await action
298
345
  // beacon_block: proactively search for parent block/envelope across all forks, but never queue.
299
- // BlockInputSync handles cascading recovery if the gossip handler throws.
346
+ // BlockInputSync handles cascading recovery if the gossip handler throws. The parent root differs from
347
+ // the message root and this message never awaits, so its search flushes inline here (gated by the budget).
300
348
  if (topicType === GossipType.beacon_block) {
301
349
  const parentRoot = getParentRootFromSignedBeaconBlockSerialized(message.msg.data);
302
350
  if (parentRoot) {
351
+ let searchParentBlock = false;
352
+ let searchParentEnvelope = false;
303
353
  if (ForkSeq[fork] >= ForkSeq.gloas) {
304
354
  // GLOAS: also check parent envelope, same logic as execution_payload_bid
305
355
  const parentBlockHash = getParentBlockHashFromGloasSignedBeaconBlockSerialized(message.msg.data);
306
356
  if (parentBlockHash && !this.chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHash)) {
307
357
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
308
358
  if (protoBlock === null) {
309
- this.searchUnknownBlock({ slot, root: parentRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
359
+ searchParentBlock = true;
310
360
  }
311
361
  else if (protoBlock.executionPayloadBlockHash &&
312
362
  protoBlock.executionPayloadBlockHash !== parentBlockHash) {
313
363
  // only search for the envelope by block root if we're sure there is one. Otherwise UnknownBlockSync will penalize the peer.
314
- this.searchUnknownEnvelope({ slot, root: parentRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
364
+ searchParentEnvelope = true;
315
365
  }
316
366
  }
317
367
  }
318
368
  else if (!this.chain.forkChoice.hasBlockHexUnsafe(parentRoot)) {
319
- this.searchUnknownBlock({ slot, root: parentRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
369
+ searchParentBlock = true;
320
370
  }
371
+ this.searchUnknownRoot({ slot, root: parentRoot }, searchParentBlock, searchParentEnvelope, peerId);
321
372
  }
322
373
  preprocessResult = { action: PreprocessAction.PushToQueue };
323
374
  }
@@ -335,7 +386,7 @@ export class NetworkProcessor {
335
386
  : getDataIndexFromSignedAggregateAndProofSerialized(message.msg.data);
336
387
  if (attIndex === 1 && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
337
388
  // attestation votes that the payload is available but it is not yet known
338
- this.searchUnknownEnvelope({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
389
+ this.searchUnknownRoot({ slot, root }, false, true, peerId);
339
390
  preprocessResult = { action: PreprocessAction.AwaitEnvelope, root };
340
391
  }
341
392
  break;
@@ -346,7 +397,7 @@ export class NetworkProcessor {
346
397
  const payloadPresent = getPayloadPresentFromPayloadAttestationMessageSerialized(message.msg.data);
347
398
  if (payloadPresent && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
348
399
  // payload attestation votes that the payload is available but it is not yet known
349
- this.searchUnknownEnvelope({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
400
+ this.searchUnknownRoot({ slot, root }, false, true, peerId);
350
401
  // do not await the envelope, payload attestation processing only requires that the block is known
351
402
  // also do not reset preprocessResult, we may already await for the block
352
403
  }
@@ -356,7 +407,7 @@ export class NetworkProcessor {
356
407
  if (root == null)
357
408
  break;
358
409
  if (!this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
359
- this.searchUnknownEnvelope({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
410
+ this.searchUnknownRoot({ slot, root }, false, true, peerId);
360
411
  // do not await the envelope, we can do gossip validation
361
412
  // also do not reset preprocessResult, we may already await for the block
362
413
  }
@@ -367,7 +418,7 @@ export class NetworkProcessor {
367
418
  // Extract beacon_block_root directly
368
419
  const blockRoot = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(message.msg.data);
369
420
  if (blockRoot && !this.chain.forkChoice.hasBlockHexUnsafe(blockRoot)) {
370
- this.searchUnknownBlock({ slot, root: blockRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
421
+ this.searchUnknownRoot({ slot, root: blockRoot }, true, false, peerId);
371
422
  // We always want to await the block
372
423
  // This allows us to properly forward the payload envelope
373
424
  preprocessResult = { action: PreprocessAction.AwaitBlock, root: blockRoot };
@@ -384,12 +435,12 @@ export class NetworkProcessor {
384
435
  !this.chain.forkChoice.getBlockHexAndBlockHash(parentBlockRoot, parentBlockHash)) {
385
436
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentBlockRoot);
386
437
  if (protoBlock === null) {
387
- this.searchUnknownBlock({ slot, root: parentBlockRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
438
+ this.searchUnknownRoot({ slot, root: parentBlockRoot }, true, false, peerId);
388
439
  preprocessResult = { action: PreprocessAction.AwaitBlock, root: parentBlockRoot };
389
440
  }
390
441
  else if (protoBlock.executionPayloadBlockHash &&
391
442
  protoBlock.executionPayloadBlockHash !== parentBlockHash) {
392
- this.searchUnknownEnvelope({ slot, root: parentBlockRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
443
+ this.searchUnknownRoot({ slot, root: parentBlockRoot }, false, true, peerId);
393
444
  preprocessResult = { action: PreprocessAction.AwaitEnvelope, root: parentBlockRoot };
394
445
  }
395
446
  }
@@ -401,37 +452,96 @@ export class NetworkProcessor {
401
452
  case PreprocessAction.PushToQueue:
402
453
  this.pushPendingGossipsubMessageToQueue(message);
403
454
  break;
404
- case PreprocessAction.AwaitBlock: {
405
- if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
406
- // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
407
- this.metrics?.awaitingBlockGossipMessages.reject.inc({
408
- reason: ReprocessRejectReason.reached_limit,
409
- topic: topicType,
410
- });
411
- return;
412
- }
413
- this.metrics?.awaitingBlockGossipMessages.queue.inc({ topic: topicType });
414
- const awaitingGossipsubMessages = this.awaitingMessagesByBlockRoot.getOrDefault(preprocessResult.root);
415
- awaitingGossipsubMessages.add(message);
416
- this.awaitingBlockMessageCount++;
455
+ case PreprocessAction.AwaitBlock:
456
+ this.maybeAwaitBlock(preprocessResult.root, topicType, slot, message);
417
457
  break;
418
- }
419
- case PreprocessAction.AwaitEnvelope: {
420
- if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
421
- this.metrics?.awaitingPayloadGossipMessages.reject.inc({
422
- reason: ReprocessRejectReason.reached_limit,
423
- topic: topicType,
424
- });
425
- return;
426
- }
427
- this.metrics?.awaitingPayloadGossipMessages.queue.inc({ topic: topicType });
428
- const awaitingPayloadGossipsubMessages = this.awaitingMessagesByPayloadBlockRoot.getOrDefault(preprocessResult.root);
429
- awaitingPayloadGossipsubMessages.add(message);
430
- this.awaitingPayloadMessageCount++;
458
+ case PreprocessAction.AwaitEnvelope:
459
+ this.maybeAwaitPayload(preprocessResult.root, topicType, slot, message);
431
460
  break;
432
- }
433
461
  }
434
462
  };
463
+ /**
464
+ * Buffer a gossip message that must wait for an unknown block before it can be validated.
465
+ *
466
+ * For DOS protection, the message is ignored (reject metric incremented) when any cap is hit:
467
+ * - MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS: global awaiting-block message count
468
+ * - MAX_BUFFERED_ROOTS_PER_SLOT: distinct roots we buffer messages for, per slot
469
+ * - MAX_AWAITING_MESSAGES_PER_ROOT[topic]: messages buffered per (root, topic)
470
+ */
471
+ maybeAwaitBlock(root, topicType, slot, message) {
472
+ const metric = this.metrics?.awaitingBlockGossipMessages;
473
+ // global message-count cap - cheapest check first
474
+ if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
475
+ metric?.reject.inc({ reason: ReprocessRejectReason.reached_limit, topic: topicType });
476
+ return;
477
+ }
478
+ // per-(root, topic) message cap: e.g. <= NUMBER_OF_COLUMNS columns per root
479
+ const perRootCap = MAX_AWAITING_MESSAGES_PER_ROOT[topicType];
480
+ if (perRootCap !== undefined &&
481
+ (this.awaitingMessagesByBlockRoot.get(root)?.get(topicType)?.size ?? 0) >= perRootCap) {
482
+ metric?.reject.inc({ reason: ReprocessRejectReason.reached_topic_limit, topic: topicType });
483
+ return;
484
+ }
485
+ if (!this.tryReserveBufferedRoot(slot, root)) {
486
+ metric?.reject.inc({ reason: ReprocessRejectReason.reached_root_limit, topic: topicType });
487
+ return;
488
+ }
489
+ metric?.queue.inc({ topic: topicType });
490
+ this.awaitingMessagesByBlockRoot.getOrDefault(root).getOrDefault(topicType).add(message);
491
+ this.awaitingBlockMessageCount++;
492
+ }
493
+ /** Payload counterpart of maybeAwaitBlock */
494
+ maybeAwaitPayload(root, topicType, slot, message) {
495
+ const metric = this.metrics?.awaitingPayloadGossipMessages;
496
+ if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
497
+ metric?.reject.inc({ reason: ReprocessRejectReason.reached_limit, topic: topicType });
498
+ return;
499
+ }
500
+ const perRootCap = MAX_AWAITING_MESSAGES_PER_ROOT[topicType];
501
+ if (perRootCap !== undefined &&
502
+ (this.awaitingMessagesByPayloadBlockRoot.get(root)?.get(topicType)?.size ?? 0) >= perRootCap) {
503
+ metric?.reject.inc({ reason: ReprocessRejectReason.reached_topic_limit, topic: topicType });
504
+ return;
505
+ }
506
+ if (!this.tryReserveBufferedRoot(slot, root)) {
507
+ metric?.reject.inc({ reason: ReprocessRejectReason.reached_root_limit, topic: topicType });
508
+ return;
509
+ }
510
+ metric?.queue.inc({ topic: topicType });
511
+ this.awaitingMessagesByPayloadBlockRoot.getOrDefault(root).getOrDefault(topicType).add(message);
512
+ this.awaitingPayloadMessageCount++;
513
+ }
514
+ /**
515
+ * Cap distinct roots we emit an unknown-root search for, an already-tracked root (dedup) never counts as "too many".
516
+ */
517
+ tooManySearchedRoots(slot, root) {
518
+ const roots = this.searchedRootsBySlot.get(slot);
519
+ if (roots === undefined)
520
+ return false;
521
+ return !roots.has(root) && roots.size >= MAX_SEARCHED_ROOTS_PER_SLOT;
522
+ }
523
+ /**
524
+ * Cap distinct roots we buffer per slot.
525
+ */
526
+ tryReserveBufferedRoot(slot, root) {
527
+ const roots = this.bufferedRootsBySlot.get(slot);
528
+ if (roots?.has(root))
529
+ return true;
530
+ if ((roots?.size ?? 0) >= MAX_BUFFERED_ROOTS_PER_SLOT)
531
+ return false;
532
+ this.bufferedRootsBySlot.getOrDefault(slot).add(root);
533
+ return true;
534
+ }
535
+ searchUnknownRoot(slotRoot, searchBlock, searchEnvelope, peerId) {
536
+ if (!searchBlock && !searchEnvelope)
537
+ return;
538
+ if (this.tooManySearchedRoots(slotRoot.slot, slotRoot.root))
539
+ return;
540
+ if (searchBlock)
541
+ this.searchUnknownBlock(slotRoot, BlockInputSource.network_processor, peerId);
542
+ if (searchEnvelope)
543
+ this.searchUnknownEnvelope(slotRoot, BlockInputSource.network_processor, peerId);
544
+ }
435
545
  pushPendingGossipsubMessageToQueue(message) {
436
546
  const topicType = message.topic.type;
437
547
  message.queueAddedMs = Date.now();
@@ -444,66 +554,92 @@ export class NetworkProcessor {
444
554
  this.executeWork();
445
555
  }
446
556
  onBlockProcessed = async ({ block: rootHex }) => {
447
- const waitingGossipsubMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
448
- if (!waitingGossipsubMessages || waitingGossipsubMessages.size === 0) {
557
+ const messagesByTopic = this.awaitingMessagesByBlockRoot.get(rootHex);
558
+ if (messagesByTopic === undefined) {
559
+ return;
560
+ }
561
+ let total = 0;
562
+ for (const messages of messagesByTopic.values()) {
563
+ total += messages.size;
564
+ }
565
+ if (total === 0) {
449
566
  return;
450
567
  }
451
568
  // Atomically remove from map and update counter before async iteration to
452
569
  // prevent double-decrement race with onClockSlot during yield points below
453
570
  if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
454
- this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
571
+ this.awaitingBlockMessageCount -= total;
455
572
  }
456
573
  const nowSec = Date.now() / 1000;
457
574
  let count = 0;
458
575
  // TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
459
576
  // cache, it may not be necessary at this time
460
- for (const message of waitingGossipsubMessages) {
461
- const topicType = message.topic.type;
462
- this.metrics?.awaitingBlockGossipMessages.waitSecBeforeResolve.set({ topic: topicType }, nowSec - message.seenTimestampSec);
463
- this.metrics?.awaitingBlockGossipMessages.resolve.inc({ topic: topicType });
464
- this.pushPendingGossipsubMessageToQueue(message);
465
- count++;
466
- // 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
467
- if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
468
- count = 0;
469
- await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
577
+ for (const messages of messagesByTopic.values()) {
578
+ for (const message of messages) {
579
+ const topicType = message.topic.type;
580
+ this.metrics?.awaitingBlockGossipMessages.waitSecBeforeResolve.set({ topic: topicType }, nowSec - message.seenTimestampSec);
581
+ this.metrics?.awaitingBlockGossipMessages.resolve.inc({ topic: topicType });
582
+ this.pushPendingGossipsubMessageToQueue(message);
583
+ count++;
584
+ // 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
585
+ if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
586
+ count = 0;
587
+ await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
588
+ }
470
589
  }
471
590
  }
472
591
  };
473
592
  onPayloadEnvelopeProcessed = async ({ blockRoot: rootHex }) => {
474
- const waitingGossipsubMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
475
- if (!waitingGossipsubMessages || waitingGossipsubMessages.size === 0) {
593
+ const messagesByTopic = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
594
+ if (messagesByTopic === undefined) {
595
+ return;
596
+ }
597
+ let total = 0;
598
+ for (const messages of messagesByTopic.values()) {
599
+ total += messages.size;
600
+ }
601
+ if (total === 0) {
476
602
  return;
477
603
  }
478
604
  // Atomically remove from map and update counter before async iteration to
479
605
  // prevent double-decrement race with onClockSlot during yield points below
480
606
  if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
481
- this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
607
+ this.awaitingPayloadMessageCount -= total;
482
608
  }
483
609
  const nowSec = Date.now() / 1000;
484
610
  let count = 0;
485
- for (const message of waitingGossipsubMessages) {
486
- const topicType = message.topic.type;
487
- this.metrics?.awaitingPayloadGossipMessages.waitSecBeforeResolve.set({ topic: topicType }, nowSec - message.seenTimestampSec);
488
- this.metrics?.awaitingPayloadGossipMessages.resolve.inc({ topic: topicType });
489
- this.pushPendingGossipsubMessageToQueue(message);
490
- count++;
491
- if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
492
- count = 0;
493
- await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
611
+ for (const messages of messagesByTopic.values()) {
612
+ for (const message of messages) {
613
+ const topicType = message.topic.type;
614
+ this.metrics?.awaitingPayloadGossipMessages.waitSecBeforeResolve.set({ topic: topicType }, nowSec - message.seenTimestampSec);
615
+ this.metrics?.awaitingPayloadGossipMessages.resolve.inc({ topic: topicType });
616
+ this.pushPendingGossipsubMessageToQueue(message);
617
+ count++;
618
+ if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
619
+ count = 0;
620
+ await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
621
+ }
494
622
  }
495
623
  }
496
624
  };
497
625
  onClockSlot = (clockSlot) => {
498
626
  const nowSec = Date.now() / 1000;
499
627
  const minSlot = clockSlot - MAX_UNKNOWN_ROOTS_SLOT_CACHE_SIZE;
500
- for (const [slot, roots] of this.unknownBlocksBySlot) {
628
+ for (const [slot, searchedRoots] of this.searchedRootsBySlot) {
501
629
  if (slot > minSlot)
502
630
  continue;
503
- for (const rootHex of roots.keys()) {
504
- const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
505
- if (gossipMessages !== undefined) {
506
- for (const message of gossipMessages) {
631
+ // expire messages awaiting an unknown block for these roots. Only roots this slot did a BLOCK search
632
+ // for (blockPeerIds set) have block waits keyed here; skipping the rest avoids deleting a payload wait
633
+ // that a newer slot created for the same root (block/envelope searches share searchedRootsBySlot).
634
+ for (const [rootHex, entry] of searchedRoots) {
635
+ if (entry.blockPeerIds === undefined)
636
+ continue;
637
+ const messagesByTopic = this.awaitingMessagesByBlockRoot.get(rootHex);
638
+ if (messagesByTopic === undefined)
639
+ continue;
640
+ let removed = 0;
641
+ for (const messages of messagesByTopic.values()) {
642
+ for (const message of messages) {
507
643
  const topicType = message.topic.type;
508
644
  this.metrics?.awaitingBlockGossipMessages.reject.inc({
509
645
  topic: topicType,
@@ -512,20 +648,23 @@ export class NetworkProcessor {
512
648
  this.metrics?.awaitingBlockGossipMessages.waitSecBeforeReject.set({ topic: topicType, reason: ReprocessRejectReason.expired }, nowSec - message.seenTimestampSec);
513
649
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
514
650
  }
515
- if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
516
- this.awaitingBlockMessageCount -= gossipMessages.size;
517
- }
651
+ removed += messages.size;
652
+ }
653
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
654
+ this.awaitingBlockMessageCount -= removed;
518
655
  }
519
656
  }
520
- this.unknownBlocksBySlot.delete(slot);
521
- }
522
- for (const [slot, roots] of this.unknownEnvelopesBySlot) {
523
- if (slot > minSlot)
524
- continue;
525
- for (const rootHex of roots.keys()) {
526
- const gossipMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
527
- if (gossipMessages !== undefined) {
528
- for (const message of gossipMessages) {
657
+ // expire messages awaiting an unknown payload envelope for these roots. Symmetric to the block pass:
658
+ // only roots this slot did an ENVELOPE search for (envelopePeerIds set) have payload waits keyed here.
659
+ for (const [rootHex, entry] of searchedRoots) {
660
+ if (entry.envelopePeerIds === undefined)
661
+ continue;
662
+ const messagesByTopic = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
663
+ if (messagesByTopic === undefined)
664
+ continue;
665
+ let removed = 0;
666
+ for (const messages of messagesByTopic.values()) {
667
+ for (const message of messages) {
529
668
  const topicType = message.topic.type;
530
669
  this.metrics?.awaitingPayloadGossipMessages.reject.inc({
531
670
  topic: topicType,
@@ -534,12 +673,17 @@ export class NetworkProcessor {
534
673
  this.metrics?.awaitingPayloadGossipMessages.waitSecBeforeReject.set({ topic: topicType, reason: ReprocessRejectReason.expired }, nowSec - message.seenTimestampSec);
535
674
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
536
675
  }
537
- if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
538
- this.awaitingPayloadMessageCount -= gossipMessages.size;
539
- }
676
+ removed += messages.size;
677
+ }
678
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
679
+ this.awaitingPayloadMessageCount -= removed;
540
680
  }
541
681
  }
542
- this.unknownEnvelopesBySlot.delete(slot);
682
+ this.searchedRootsBySlot.delete(slot);
683
+ }
684
+ for (const slot of this.bufferedRootsBySlot.keys()) {
685
+ if (slot <= minSlot)
686
+ this.bufferedRootsBySlot.delete(slot);
543
687
  }
544
688
  };
545
689
  executeWork() {
@@ -614,27 +758,23 @@ export class NetworkProcessor {
614
758
  else {
615
759
  this.trackJobTime(messageOrArray, 1, acceptanceArr[0]);
616
760
  }
617
- // Use setTimeout to yield to the macro queue
618
- // This is mostly due to too many attestation messages, and a gossipsub RPC may
619
- // contain multiple of them. This helps avoid the I/O lag issue.
761
+ // Report the validation verdict synchronously so it reaches the network worker before any deferred
762
+ // handler (e.g. block import) runs on the next event loop. At network thread side, gossipsub onValidationResult()
763
+ // has its own callInNextEventLoop
620
764
  if (Array.isArray(messageOrArray)) {
621
765
  for (const [i, msg] of messageOrArray.entries()) {
622
- callInNextEventLoop(() => {
623
- this.events.emit(NetworkEvent.gossipMessageValidationResult, {
624
- msgId: msg.msgId,
625
- propagationSource: msg.propagationSource,
626
- acceptance: acceptanceArr[i],
627
- });
766
+ this.events.emit(NetworkEvent.gossipMessageValidationResult, {
767
+ msgId: msg.msgId,
768
+ propagationSource: msg.propagationSource,
769
+ acceptance: acceptanceArr[i],
628
770
  });
629
771
  }
630
772
  }
631
773
  else {
632
- callInNextEventLoop(() => {
633
- this.events.emit(NetworkEvent.gossipMessageValidationResult, {
634
- msgId: messageOrArray.msgId,
635
- propagationSource: messageOrArray.propagationSource,
636
- acceptance: acceptanceArr[0],
637
- });
774
+ this.events.emit(NetworkEvent.gossipMessageValidationResult, {
775
+ msgId: messageOrArray.msgId,
776
+ propagationSource: messageOrArray.propagationSource,
777
+ acceptance: acceptanceArr[0],
638
778
  });
639
779
  }
640
780
  }