@lodestar/beacon-node 1.47.0 → 1.48.0-dev.042c96183c

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 (328) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +8 -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 +44 -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 +89 -23
  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/verifyBlock.d.ts.map +1 -1
  48. package/lib/chain/blocks/verifyBlock.js +5 -2
  49. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  50. package/lib/chain/blocks/writeBlockInputToDb.d.ts.map +1 -1
  51. package/lib/chain/blocks/writeBlockInputToDb.js +9 -16
  52. package/lib/chain/blocks/writeBlockInputToDb.js.map +1 -1
  53. package/lib/chain/chain.d.ts +12 -6
  54. package/lib/chain/chain.d.ts.map +1 -1
  55. package/lib/chain/chain.js +131 -76
  56. package/lib/chain/chain.js.map +1 -1
  57. package/lib/chain/emitter.d.ts +1 -1
  58. package/lib/chain/emitter.js +1 -1
  59. package/lib/chain/errors/blockError.d.ts +19 -7
  60. package/lib/chain/errors/blockError.d.ts.map +1 -1
  61. package/lib/chain/errors/blockError.js +2 -2
  62. package/lib/chain/errors/blockError.js.map +1 -1
  63. package/lib/chain/errors/dataColumnSidecarError.d.ts +15 -2
  64. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  65. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  66. package/lib/chain/errors/executionPayloadBid.d.ts +9 -0
  67. package/lib/chain/errors/executionPayloadBid.d.ts.map +1 -1
  68. package/lib/chain/errors/executionPayloadBid.js +2 -0
  69. package/lib/chain/errors/executionPayloadBid.js.map +1 -1
  70. package/lib/chain/errors/executionPayloadEnvelope.d.ts +10 -11
  71. package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
  72. package/lib/chain/errors/executionPayloadEnvelope.js +0 -2
  73. package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
  74. package/lib/chain/errors/gossipValidation.d.ts +2 -1
  75. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  76. package/lib/chain/errors/gossipValidation.js +1 -0
  77. package/lib/chain/errors/gossipValidation.js.map +1 -1
  78. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  79. package/lib/chain/forkChoice/index.js +2 -0
  80. package/lib/chain/forkChoice/index.js.map +1 -1
  81. package/lib/chain/interface.d.ts +6 -6
  82. package/lib/chain/interface.d.ts.map +1 -1
  83. package/lib/chain/lightClient/index.d.ts +3 -3
  84. package/lib/chain/lightClient/index.d.ts.map +1 -1
  85. package/lib/chain/lightClient/index.js +21 -23
  86. package/lib/chain/lightClient/index.js.map +1 -1
  87. package/lib/chain/lightClient/proofs.d.ts +2 -1
  88. package/lib/chain/lightClient/proofs.d.ts.map +1 -1
  89. package/lib/chain/lightClient/proofs.js +5 -1
  90. package/lib/chain/lightClient/proofs.js.map +1 -1
  91. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  92. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  93. package/lib/chain/prepareNextSlot.d.ts +5 -0
  94. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  95. package/lib/chain/prepareNextSlot.js +40 -15
  96. package/lib/chain/prepareNextSlot.js.map +1 -1
  97. package/lib/chain/produceBlock/computeNewStateRoot.d.ts.map +1 -1
  98. package/lib/chain/produceBlock/computeNewStateRoot.js +3 -25
  99. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  100. package/lib/chain/produceBlock/produceBlockBody.d.ts +4 -2
  101. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  102. package/lib/chain/produceBlock/produceBlockBody.js +57 -72
  103. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  104. package/lib/chain/regen/interface.d.ts +1 -0
  105. package/lib/chain/regen/interface.d.ts.map +1 -1
  106. package/lib/chain/regen/interface.js +1 -0
  107. package/lib/chain/regen/interface.js.map +1 -1
  108. package/lib/chain/validation/block.d.ts.map +1 -1
  109. package/lib/chain/validation/block.js +74 -63
  110. package/lib/chain/validation/block.js.map +1 -1
  111. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  112. package/lib/chain/validation/dataColumnSidecar.js +38 -13
  113. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  114. package/lib/chain/validation/executionPayloadBid.d.ts +15 -3
  115. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  116. package/lib/chain/validation/executionPayloadBid.js +199 -28
  117. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  118. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  119. package/lib/chain/validation/executionPayloadEnvelope.js +10 -29
  120. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  121. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  122. package/lib/chain/validation/payloadAttestationMessage.js +5 -6
  123. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  124. package/lib/chain/validatorMonitor.d.ts +7 -1
  125. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  126. package/lib/chain/validatorMonitor.js +22 -5
  127. package/lib/chain/validatorMonitor.js.map +1 -1
  128. package/lib/db/beacon.d.ts +14 -5
  129. package/lib/db/beacon.d.ts.map +1 -1
  130. package/lib/db/beacon.js +17 -3
  131. package/lib/db/beacon.js.map +1 -1
  132. package/lib/db/dataColumnStore.d.ts +39 -0
  133. package/lib/db/dataColumnStore.d.ts.map +1 -0
  134. package/lib/db/dataColumnStore.js +81 -0
  135. package/lib/db/dataColumnStore.js.map +1 -0
  136. package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
  137. package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
  138. package/lib/db/flatFileStore/atomicWrite.js +61 -0
  139. package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
  140. package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
  141. package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
  142. package/lib/db/flatFileStore/dcolFormat.js +286 -0
  143. package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
  144. package/lib/db/flatFileStore/errors.d.ts +82 -0
  145. package/lib/db/flatFileStore/errors.d.ts.map +1 -0
  146. package/lib/db/flatFileStore/errors.js +32 -0
  147. package/lib/db/flatFileStore/errors.js.map +1 -0
  148. package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
  149. package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
  150. package/lib/db/flatFileStore/flatFileStore.js +251 -0
  151. package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
  152. package/lib/db/flatFileStore/index.d.ts +4 -0
  153. package/lib/db/flatFileStore/index.d.ts.map +1 -0
  154. package/lib/db/flatFileStore/index.js +2 -0
  155. package/lib/db/flatFileStore/index.js.map +1 -0
  156. package/lib/db/flatFileStore/interface.d.ts +25 -0
  157. package/lib/db/flatFileStore/interface.d.ts.map +1 -0
  158. package/lib/db/flatFileStore/interface.js +2 -0
  159. package/lib/db/flatFileStore/interface.js.map +1 -0
  160. package/lib/db/flatFileStore/metrics.d.ts +24 -0
  161. package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
  162. package/lib/db/flatFileStore/metrics.js +25 -0
  163. package/lib/db/flatFileStore/metrics.js.map +1 -0
  164. package/lib/db/flatFileStore/path.d.ts +9 -0
  165. package/lib/db/flatFileStore/path.d.ts.map +1 -0
  166. package/lib/db/flatFileStore/path.js +21 -0
  167. package/lib/db/flatFileStore/path.js.map +1 -0
  168. package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
  169. package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
  170. package/lib/db/flatFileStore/slotIndex.js +46 -0
  171. package/lib/db/flatFileStore/slotIndex.js.map +1 -0
  172. package/lib/db/flatFileStore/snappy.d.ts +3 -0
  173. package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
  174. package/lib/db/flatFileStore/snappy.js +12 -0
  175. package/lib/db/flatFileStore/snappy.js.map +1 -0
  176. package/lib/db/index.d.ts +1 -0
  177. package/lib/db/index.d.ts.map +1 -1
  178. package/lib/db/interface.d.ts +8 -1
  179. package/lib/db/interface.d.ts.map +1 -1
  180. package/lib/db/repositories/blockArchive.d.ts +8 -1
  181. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  182. package/lib/db/repositories/blockArchive.js +65 -28
  183. package/lib/db/repositories/blockArchive.js.map +1 -1
  184. package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
  185. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  186. package/lib/db/repositories/blockArchiveIndex.js +3 -7
  187. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  188. package/lib/execution/builder/apiClient.d.ts +6 -0
  189. package/lib/execution/builder/apiClient.d.ts.map +1 -1
  190. package/lib/execution/builder/apiClient.js +19 -1
  191. package/lib/execution/builder/apiClient.js.map +1 -1
  192. package/lib/execution/builder/validateBid.d.ts +1 -0
  193. package/lib/execution/builder/validateBid.d.ts.map +1 -1
  194. package/lib/execution/builder/validateBid.js +17 -6
  195. package/lib/execution/builder/validateBid.js.map +1 -1
  196. package/lib/execution/engine/mock.d.ts.map +1 -1
  197. package/lib/execution/engine/mock.js +4 -1
  198. package/lib/execution/engine/mock.js.map +1 -1
  199. package/lib/metrics/metrics/lodestar.d.ts +32 -4
  200. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  201. package/lib/metrics/metrics/lodestar.js +78 -8
  202. package/lib/metrics/metrics/lodestar.js.map +1 -1
  203. package/lib/monitoring/clientStats.js +1 -1
  204. package/lib/monitoring/clientStats.js.map +1 -1
  205. package/lib/network/gossip/encoding.d.ts.map +1 -1
  206. package/lib/network/gossip/encoding.js +0 -7
  207. package/lib/network/gossip/encoding.js.map +1 -1
  208. package/lib/network/gossip/topic.d.ts +2 -1
  209. package/lib/network/gossip/topic.d.ts.map +1 -1
  210. package/lib/network/gossip/topic.js +4 -20
  211. package/lib/network/gossip/topic.js.map +1 -1
  212. package/lib/network/network.d.ts.map +1 -1
  213. package/lib/network/network.js +7 -6
  214. package/lib/network/network.js.map +1 -1
  215. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  216. package/lib/network/processor/gossipHandlers.js +388 -285
  217. package/lib/network/processor/gossipHandlers.js.map +1 -1
  218. package/lib/network/processor/gossipQueues/indexed.d.ts.map +1 -1
  219. package/lib/network/processor/gossipQueues/indexed.js +0 -1
  220. package/lib/network/processor/gossipQueues/indexed.js.map +1 -1
  221. package/lib/network/processor/index.d.ts +49 -3
  222. package/lib/network/processor/index.d.ts.map +1 -1
  223. package/lib/network/processor/index.js +270 -122
  224. package/lib/network/processor/index.js.map +1 -1
  225. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  226. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
  227. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  228. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  229. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
  230. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  231. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  232. package/lib/network/reqresp/protocols.js +6 -22
  233. package/lib/network/reqresp/protocols.js.map +1 -1
  234. package/lib/network/reqresp/rateLimit.js +2 -1
  235. package/lib/network/reqresp/rateLimit.js.map +1 -1
  236. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
  237. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  238. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
  239. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  240. package/lib/node/nodejs.d.ts +2 -1
  241. package/lib/node/nodejs.d.ts.map +1 -1
  242. package/lib/node/nodejs.js +4 -2
  243. package/lib/node/nodejs.js.map +1 -1
  244. package/lib/sync/range/chain.d.ts.map +1 -1
  245. package/lib/sync/range/chain.js +9 -2
  246. package/lib/sync/range/chain.js.map +1 -1
  247. package/lib/sync/range/range.d.ts.map +1 -1
  248. package/lib/sync/range/range.js +25 -2
  249. package/lib/sync/range/range.js.map +1 -1
  250. package/lib/util/sszBytes.d.ts +6 -0
  251. package/lib/util/sszBytes.d.ts.map +1 -1
  252. package/lib/util/sszBytes.js +1 -1
  253. package/lib/util/sszBytes.js.map +1 -1
  254. package/package.json +19 -20
  255. package/src/api/impl/beacon/blocks/index.ts +12 -14
  256. package/src/api/impl/beacon/state/utils.ts +15 -0
  257. package/src/api/impl/config/index.ts +4 -1
  258. package/src/api/impl/validator/index.ts +49 -43
  259. package/src/chain/archiveStore/archiveStore.ts +12 -1
  260. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
  261. package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
  262. package/src/chain/archiveStore/historicalState/types.ts +2 -0
  263. package/src/chain/archiveStore/historicalState/worker.ts +4 -1
  264. package/src/chain/archiveStore/utils/archiveBlocks.ts +73 -42
  265. package/src/chain/blocks/importBlock.ts +25 -20
  266. package/src/chain/blocks/index.ts +126 -29
  267. package/src/chain/blocks/types.ts +9 -0
  268. package/src/chain/blocks/utils/chainSegment.ts +26 -12
  269. package/src/chain/blocks/verifyBlock.ts +5 -2
  270. package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
  271. package/src/chain/chain.ts +155 -85
  272. package/src/chain/emitter.ts +1 -1
  273. package/src/chain/errors/blockError.ts +22 -10
  274. package/src/chain/errors/dataColumnSidecarError.ts +24 -8
  275. package/src/chain/errors/executionPayloadBid.ts +8 -0
  276. package/src/chain/errors/executionPayloadEnvelope.ts +14 -11
  277. package/src/chain/errors/gossipValidation.ts +1 -0
  278. package/src/chain/forkChoice/index.ts +2 -0
  279. package/src/chain/interface.ts +6 -6
  280. package/src/chain/lightClient/index.ts +32 -36
  281. package/src/chain/lightClient/proofs.ts +10 -0
  282. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  283. package/src/chain/prepareNextSlot.ts +47 -18
  284. package/src/chain/produceBlock/computeNewStateRoot.ts +3 -38
  285. package/src/chain/produceBlock/produceBlockBody.ts +62 -79
  286. package/src/chain/regen/interface.ts +1 -0
  287. package/src/chain/validation/block.ts +77 -79
  288. package/src/chain/validation/dataColumnSidecar.ts +47 -14
  289. package/src/chain/validation/executionPayloadBid.ts +230 -32
  290. package/src/chain/validation/executionPayloadEnvelope.ts +10 -37
  291. package/src/chain/validation/payloadAttestationMessage.ts +8 -9
  292. package/src/chain/validatorMonitor.ts +24 -7
  293. package/src/db/beacon.ts +33 -6
  294. package/src/db/dataColumnStore.ts +113 -0
  295. package/src/db/flatFileStore/atomicWrite.ts +65 -0
  296. package/src/db/flatFileStore/dcolFormat.ts +389 -0
  297. package/src/db/flatFileStore/errors.ts +53 -0
  298. package/src/db/flatFileStore/flatFileStore.ts +310 -0
  299. package/src/db/flatFileStore/index.ts +3 -0
  300. package/src/db/flatFileStore/interface.ts +21 -0
  301. package/src/db/flatFileStore/metrics.ts +48 -0
  302. package/src/db/flatFileStore/path.ts +32 -0
  303. package/src/db/flatFileStore/slotIndex.ts +56 -0
  304. package/src/db/flatFileStore/snappy.ts +14 -0
  305. package/src/db/index.ts +1 -0
  306. package/src/db/interface.ts +9 -1
  307. package/src/db/repositories/blockArchive.ts +89 -30
  308. package/src/db/repositories/blockArchiveIndex.ts +4 -9
  309. package/src/execution/builder/apiClient.ts +21 -1
  310. package/src/execution/builder/validateBid.ts +26 -7
  311. package/src/execution/engine/mock.ts +4 -1
  312. package/src/metrics/metrics/lodestar.ts +83 -9
  313. package/src/monitoring/clientStats.ts +1 -1
  314. package/src/network/gossip/encoding.ts +0 -7
  315. package/src/network/gossip/topic.ts +3 -25
  316. package/src/network/network.ts +9 -6
  317. package/src/network/processor/gossipHandlers.ts +500 -355
  318. package/src/network/processor/gossipQueues/indexed.ts +0 -1
  319. package/src/network/processor/index.ts +312 -176
  320. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
  321. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
  322. package/src/network/reqresp/protocols.ts +7 -24
  323. package/src/network/reqresp/rateLimit.ts +2 -1
  324. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
  325. package/src/node/nodejs.ts +6 -1
  326. package/src/sync/range/chain.ts +9 -2
  327. package/src/sync/range/range.ts +24 -2
  328. package/src/util/sszBytes.ts +1 -1
@@ -1,12 +1,12 @@
1
+ import { TopicValidatorResult } from "@libp2p/gossipsub";
1
2
  import { routes } from "@lodestar/api";
2
- import { ForkSeq } from "@lodestar/params";
3
+ import { ForkSeq, NUMBER_OF_COLUMNS } from "@lodestar/params";
3
4
  import { computeStartSlotAtEpoch } from "@lodestar/state-transition";
4
5
  import { MapDef, mapValues, sleep } from "@lodestar/utils";
5
6
  import { BlockInputSource } from "../../chain/blocks/blockInput/types.js";
6
7
  import { ChainEvent } from "../../chain/emitter.js";
7
8
  import { GossipErrorCode } from "../../chain/errors/gossipValidation.js";
8
9
  import { ClockEvent } from "../../util/clock.js";
9
- import { callInNextEventLoop } from "../../util/eventLoop.js";
10
10
  import { getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized, getDataIndexFromSignedAggregateAndProofSerialized, getDataIndexFromSingleAttestationSerialized, getParentBlockHashFromGloasSignedBeaconBlockSerialized, getParentBlockHashFromSignedExecutionPayloadBidSerialized, getParentBlockRootFromSignedExecutionPayloadBidSerialized, getParentRootFromSignedBeaconBlockSerialized, getPayloadPresentFromPayloadAttestationMessageSerialized, } from "../../util/sszBytes.js";
11
11
  import { NetworkEvent } from "../events.js";
12
12
  import { GossipType, } from "../gossip/interface.js";
@@ -20,6 +20,28 @@ export * from "./types.js";
20
20
  * Keep up to 3 slot of unknown roots, so we don't always emit to UnknownBlock sync.
21
21
  */
22
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
+ };
23
45
  /**
24
46
  * This is respective to gossipsub seenTTL (which is 550 * 0.7 = 385s), also it's respective
25
47
  * to beacon_attestation ATTESTATION_PROPAGATION_SLOT_RANGE (32 slots).
@@ -72,6 +94,14 @@ export var ReprocessRejectReason;
72
94
  * There are too many gossip messages that have unknown block root.
73
95
  */
74
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";
75
105
  /**
76
106
  * The awaiting gossip message is pruned per clock slot.
77
107
  */
@@ -132,15 +162,16 @@ export class NetworkProcessor {
132
162
  gossipTopicConcurrency;
133
163
  extractBlockSlotRootFns = createExtractBlockSlotRootFns();
134
164
  // we may not receive the block for messages like Attestation and SignedAggregateAndProof messages, in that case PendingGossipsubMessage needs
135
- // 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.
136
167
  awaitingMessagesByBlockRoot;
137
168
  awaitingBlockMessageCount = 0;
138
169
  // we may not receive the payload for messages that require the FULL payload variant to be processed,
139
170
  // in that case PendingGossipsubMessage needs to be stored in this Map and reprocessed once the payload comes
140
171
  awaitingMessagesByPayloadBlockRoot;
141
172
  awaitingPayloadMessageCount = 0;
142
- unknownBlocksBySlot = new MapDef(() => new MapDef(() => new Set()));
143
- unknownEnvelopesBySlot = new MapDef(() => new MapDef(() => new Set()));
173
+ searchedRootsBySlot = new MapDef(() => new MapDef(() => ({})));
174
+ bufferedRootsBySlot = new MapDef(() => new Set());
144
175
  constructor(modules, opts) {
145
176
  this.opts = opts;
146
177
  const { chain, events, logger, metrics } = modules;
@@ -157,8 +188,8 @@ export class NetworkProcessor {
157
188
  this.chain.emitter.on(routes.events.EventType.block, this.onBlockProcessed);
158
189
  this.chain.emitter.on(routes.events.EventType.executionPayload, this.onPayloadEnvelopeProcessed);
159
190
  this.chain.clock.on(ClockEvent.slot, this.onClockSlot);
160
- this.awaitingMessagesByBlockRoot = new MapDef(() => new Set());
161
- this.awaitingMessagesByPayloadBlockRoot = new MapDef(() => new Set());
191
+ this.awaitingMessagesByBlockRoot = new MapDef(() => new MapDef(() => new Set()));
192
+ this.awaitingMessagesByPayloadBlockRoot = new MapDef(() => new MapDef(() => new Set()));
162
193
  // TODO: Implement queues and priorization for ReqResp incoming requests
163
194
  // Listens to NetworkEvent.reqRespIncomingRequest event
164
195
  if (metrics) {
@@ -210,9 +241,12 @@ export class NetworkProcessor {
210
241
  if (this.chain.seenBlock(root)) {
211
242
  return;
212
243
  }
213
- const peersForRoot = this.unknownBlocksBySlot.getOrDefault(slot);
214
- const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByBlockRoot.has(root);
215
- 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;
216
250
  // brand-new search always emits (peer may be undefined)
217
251
  let shouldEmit = !alreadySearching;
218
252
  if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
@@ -234,10 +268,12 @@ export class NetworkProcessor {
234
268
  if (this.chain.seenPayloadEnvelope(root)) {
235
269
  return;
236
270
  }
237
- const peersForRoot = this.unknownEnvelopesBySlot.getOrDefault(slot);
238
- // capture "already searching" BEFORE getOrDefault(root) creates the entry
239
- const alreadySearching = peersForRoot.has(root) || this.awaitingMessagesByPayloadBlockRoot.has(root);
240
- 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;
241
277
  let shouldEmit = !alreadySearching; // brand-new search always emits (peer may be undefined)
242
278
  if (peer !== undefined && !forwardedPeers.has(peer) && forwardedPeers.size < MAX_PEERS_PER_ROOT) {
243
279
  // First time we forward THIS peer for THIS root: emit so BlockInputSync accumulates it.
@@ -280,14 +316,26 @@ export class NetworkProcessor {
280
316
  });
281
317
  return;
282
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
+ }
283
330
  message.msgSlot = slot;
331
+ const peerId = message.propagationSource.toString();
284
332
  // this determines whether this message needs to wait for a Block or Envelope
285
- // 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.
286
334
  let preprocessResult = { action: PreprocessAction.PushToQueue };
287
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
288
336
  if (root && !this.chain.forkChoice.hasBlockHexUnsafe(root)) {
289
337
  // starting from GLOAS, unknown root from data_column_sidecar also falls into this case
290
- this.searchUnknownBlock({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
338
+ this.searchUnknownRoot({ slot, root }, true, false, peerId);
291
339
  // for beacon_attestation and beacon_aggregate_and_proof messages, this is only temporary.
292
340
  // if "index = 1" we need to await for the Envelope instead
293
341
  preprocessResult = { action: PreprocessAction.AwaitBlock, root };
@@ -295,28 +343,32 @@ export class NetworkProcessor {
295
343
  // 2nd extract round for some specific topics
296
344
  // we separate the search action from the await action
297
345
  // beacon_block: proactively search for parent block/envelope across all forks, but never queue.
298
- // 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).
299
348
  if (topicType === GossipType.beacon_block) {
300
349
  const parentRoot = getParentRootFromSignedBeaconBlockSerialized(message.msg.data);
301
350
  if (parentRoot) {
351
+ let searchParentBlock = false;
352
+ let searchParentEnvelope = false;
302
353
  if (ForkSeq[fork] >= ForkSeq.gloas) {
303
354
  // GLOAS: also check parent envelope, same logic as execution_payload_bid
304
355
  const parentBlockHash = getParentBlockHashFromGloasSignedBeaconBlockSerialized(message.msg.data);
305
356
  if (parentBlockHash && !this.chain.forkChoice.getBlockHexAndBlockHash(parentRoot, parentBlockHash)) {
306
357
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentRoot);
307
358
  if (protoBlock === null) {
308
- this.searchUnknownBlock({ slot, root: parentRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
359
+ searchParentBlock = true;
309
360
  }
310
361
  else if (protoBlock.executionPayloadBlockHash &&
311
362
  protoBlock.executionPayloadBlockHash !== parentBlockHash) {
312
363
  // only search for the envelope by block root if we're sure there is one. Otherwise UnknownBlockSync will penalize the peer.
313
- this.searchUnknownEnvelope({ slot, root: parentRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
364
+ searchParentEnvelope = true;
314
365
  }
315
366
  }
316
367
  }
317
368
  else if (!this.chain.forkChoice.hasBlockHexUnsafe(parentRoot)) {
318
- this.searchUnknownBlock({ slot, root: parentRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
369
+ searchParentBlock = true;
319
370
  }
371
+ this.searchUnknownRoot({ slot, root: parentRoot }, searchParentBlock, searchParentEnvelope, peerId);
320
372
  }
321
373
  preprocessResult = { action: PreprocessAction.PushToQueue };
322
374
  }
@@ -334,7 +386,7 @@ export class NetworkProcessor {
334
386
  : getDataIndexFromSignedAggregateAndProofSerialized(message.msg.data);
335
387
  if (attIndex === 1 && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
336
388
  // attestation votes that the payload is available but it is not yet known
337
- this.searchUnknownEnvelope({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
389
+ this.searchUnknownRoot({ slot, root }, false, true, peerId);
338
390
  preprocessResult = { action: PreprocessAction.AwaitEnvelope, root };
339
391
  }
340
392
  break;
@@ -345,7 +397,7 @@ export class NetworkProcessor {
345
397
  const payloadPresent = getPayloadPresentFromPayloadAttestationMessageSerialized(message.msg.data);
346
398
  if (payloadPresent && !this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
347
399
  // payload attestation votes that the payload is available but it is not yet known
348
- this.searchUnknownEnvelope({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
400
+ this.searchUnknownRoot({ slot, root }, false, true, peerId);
349
401
  // do not await the envelope, payload attestation processing only requires that the block is known
350
402
  // also do not reset preprocessResult, we may already await for the block
351
403
  }
@@ -355,7 +407,7 @@ export class NetworkProcessor {
355
407
  if (root == null)
356
408
  break;
357
409
  if (!this.chain.forkChoice.hasPayloadHexUnsafe(root)) {
358
- this.searchUnknownEnvelope({ slot, root }, BlockInputSource.network_processor, message.propagationSource.toString());
410
+ this.searchUnknownRoot({ slot, root }, false, true, peerId);
359
411
  // do not await the envelope, we can do gossip validation
360
412
  // also do not reset preprocessResult, we may already await for the block
361
413
  }
@@ -366,7 +418,7 @@ export class NetworkProcessor {
366
418
  // Extract beacon_block_root directly
367
419
  const blockRoot = getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(message.msg.data);
368
420
  if (blockRoot && !this.chain.forkChoice.hasBlockHexUnsafe(blockRoot)) {
369
- this.searchUnknownBlock({ slot, root: blockRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
421
+ this.searchUnknownRoot({ slot, root: blockRoot }, true, false, peerId);
370
422
  // We always want to await the block
371
423
  // This allows us to properly forward the payload envelope
372
424
  preprocessResult = { action: PreprocessAction.AwaitBlock, root: blockRoot };
@@ -383,12 +435,12 @@ export class NetworkProcessor {
383
435
  !this.chain.forkChoice.getBlockHexAndBlockHash(parentBlockRoot, parentBlockHash)) {
384
436
  const protoBlock = this.chain.forkChoice.getBlockHexDefaultStatus(parentBlockRoot);
385
437
  if (protoBlock === null) {
386
- this.searchUnknownBlock({ slot, root: parentBlockRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
438
+ this.searchUnknownRoot({ slot, root: parentBlockRoot }, true, false, peerId);
387
439
  preprocessResult = { action: PreprocessAction.AwaitBlock, root: parentBlockRoot };
388
440
  }
389
441
  else if (protoBlock.executionPayloadBlockHash &&
390
442
  protoBlock.executionPayloadBlockHash !== parentBlockHash) {
391
- this.searchUnknownEnvelope({ slot, root: parentBlockRoot }, BlockInputSource.network_processor, message.propagationSource.toString());
443
+ this.searchUnknownRoot({ slot, root: parentBlockRoot }, false, true, peerId);
392
444
  preprocessResult = { action: PreprocessAction.AwaitEnvelope, root: parentBlockRoot };
393
445
  }
394
446
  }
@@ -400,39 +452,99 @@ export class NetworkProcessor {
400
452
  case PreprocessAction.PushToQueue:
401
453
  this.pushPendingGossipsubMessageToQueue(message);
402
454
  break;
403
- case PreprocessAction.AwaitBlock: {
404
- if (this.awaitingBlockMessageCount > MAX_QUEUED_UNKNOWN_BLOCK_GOSSIP_OBJECTS) {
405
- // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
406
- this.metrics?.awaitingBlockGossipMessages.reject.inc({
407
- reason: ReprocessRejectReason.reached_limit,
408
- topic: topicType,
409
- });
410
- return;
411
- }
412
- this.metrics?.awaitingBlockGossipMessages.queue.inc({ topic: topicType });
413
- const awaitingGossipsubMessages = this.awaitingMessagesByBlockRoot.getOrDefault(preprocessResult.root);
414
- awaitingGossipsubMessages.add(message);
415
- this.awaitingBlockMessageCount++;
455
+ case PreprocessAction.AwaitBlock:
456
+ this.maybeAwaitBlock(preprocessResult.root, topicType, slot, message);
416
457
  break;
417
- }
418
- case PreprocessAction.AwaitEnvelope: {
419
- if (this.awaitingPayloadMessageCount > MAX_QUEUED_UNKNOWN_PAYLOAD_GOSSIP_OBJECTS) {
420
- this.metrics?.awaitingPayloadGossipMessages.reject.inc({
421
- reason: ReprocessRejectReason.reached_limit,
422
- topic: topicType,
423
- });
424
- return;
425
- }
426
- this.metrics?.awaitingPayloadGossipMessages.queue.inc({ topic: topicType });
427
- const awaitingPayloadGossipsubMessages = this.awaitingMessagesByPayloadBlockRoot.getOrDefault(preprocessResult.root);
428
- awaitingPayloadGossipsubMessages.add(message);
429
- this.awaitingPayloadMessageCount++;
458
+ case PreprocessAction.AwaitEnvelope:
459
+ this.maybeAwaitPayload(preprocessResult.root, topicType, slot, message);
430
460
  break;
431
- }
432
461
  }
433
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
+ }
434
545
  pushPendingGossipsubMessageToQueue(message) {
435
546
  const topicType = message.topic.type;
547
+ message.queueAddedMs = Date.now();
436
548
  const droppedCount = this.gossipQueues[topicType].add(message);
437
549
  if (droppedCount) {
438
550
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
@@ -442,66 +554,92 @@ export class NetworkProcessor {
442
554
  this.executeWork();
443
555
  }
444
556
  onBlockProcessed = async ({ block: rootHex }) => {
445
- const waitingGossipsubMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
446
- 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) {
447
566
  return;
448
567
  }
449
568
  // Atomically remove from map and update counter before async iteration to
450
569
  // prevent double-decrement race with onClockSlot during yield points below
451
570
  if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
452
- this.awaitingBlockMessageCount -= waitingGossipsubMessages.size;
571
+ this.awaitingBlockMessageCount -= total;
453
572
  }
454
573
  const nowSec = Date.now() / 1000;
455
574
  let count = 0;
456
575
  // TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
457
576
  // cache, it may not be necessary at this time
458
- for (const message of waitingGossipsubMessages) {
459
- const topicType = message.topic.type;
460
- this.metrics?.awaitingBlockGossipMessages.waitSecBeforeResolve.set({ topic: topicType }, nowSec - message.seenTimestampSec);
461
- this.metrics?.awaitingBlockGossipMessages.resolve.inc({ topic: topicType });
462
- this.pushPendingGossipsubMessageToQueue(message);
463
- count++;
464
- // 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
465
- if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
466
- count = 0;
467
- 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
+ }
468
589
  }
469
590
  }
470
591
  };
471
592
  onPayloadEnvelopeProcessed = async ({ blockRoot: rootHex }) => {
472
- const waitingGossipsubMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
473
- 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) {
474
602
  return;
475
603
  }
476
604
  // Atomically remove from map and update counter before async iteration to
477
605
  // prevent double-decrement race with onClockSlot during yield points below
478
606
  if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
479
- this.awaitingPayloadMessageCount -= waitingGossipsubMessages.size;
607
+ this.awaitingPayloadMessageCount -= total;
480
608
  }
481
609
  const nowSec = Date.now() / 1000;
482
610
  let count = 0;
483
- for (const message of waitingGossipsubMessages) {
484
- const topicType = message.topic.type;
485
- this.metrics?.awaitingPayloadGossipMessages.waitSecBeforeResolve.set({ topic: topicType }, nowSec - message.seenTimestampSec);
486
- this.metrics?.awaitingPayloadGossipMessages.resolve.inc({ topic: topicType });
487
- this.pushPendingGossipsubMessageToQueue(message);
488
- count++;
489
- if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
490
- count = 0;
491
- 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
+ }
492
622
  }
493
623
  }
494
624
  };
495
625
  onClockSlot = (clockSlot) => {
496
626
  const nowSec = Date.now() / 1000;
497
627
  const minSlot = clockSlot - MAX_UNKNOWN_ROOTS_SLOT_CACHE_SIZE;
498
- for (const [slot, roots] of this.unknownBlocksBySlot) {
628
+ for (const [slot, searchedRoots] of this.searchedRootsBySlot) {
499
629
  if (slot > minSlot)
500
630
  continue;
501
- for (const rootHex of roots.keys()) {
502
- const gossipMessages = this.awaitingMessagesByBlockRoot.get(rootHex);
503
- if (gossipMessages !== undefined) {
504
- 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) {
505
643
  const topicType = message.topic.type;
506
644
  this.metrics?.awaitingBlockGossipMessages.reject.inc({
507
645
  topic: topicType,
@@ -510,20 +648,23 @@ export class NetworkProcessor {
510
648
  this.metrics?.awaitingBlockGossipMessages.waitSecBeforeReject.set({ topic: topicType, reason: ReprocessRejectReason.expired }, nowSec - message.seenTimestampSec);
511
649
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
512
650
  }
513
- if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
514
- this.awaitingBlockMessageCount -= gossipMessages.size;
515
- }
651
+ removed += messages.size;
652
+ }
653
+ if (this.awaitingMessagesByBlockRoot.delete(rootHex)) {
654
+ this.awaitingBlockMessageCount -= removed;
516
655
  }
517
656
  }
518
- this.unknownBlocksBySlot.delete(slot);
519
- }
520
- for (const [slot, roots] of this.unknownEnvelopesBySlot) {
521
- if (slot > minSlot)
522
- continue;
523
- for (const rootHex of roots.keys()) {
524
- const gossipMessages = this.awaitingMessagesByPayloadBlockRoot.get(rootHex);
525
- if (gossipMessages !== undefined) {
526
- 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) {
527
668
  const topicType = message.topic.type;
528
669
  this.metrics?.awaitingPayloadGossipMessages.reject.inc({
529
670
  topic: topicType,
@@ -532,12 +673,17 @@ export class NetworkProcessor {
532
673
  this.metrics?.awaitingPayloadGossipMessages.waitSecBeforeReject.set({ topic: topicType, reason: ReprocessRejectReason.expired }, nowSec - message.seenTimestampSec);
533
674
  // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
534
675
  }
535
- if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
536
- this.awaitingPayloadMessageCount -= gossipMessages.size;
537
- }
676
+ removed += messages.size;
677
+ }
678
+ if (this.awaitingMessagesByPayloadBlockRoot.delete(rootHex)) {
679
+ this.awaitingPayloadMessageCount -= removed;
538
680
  }
539
681
  }
540
- 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);
541
687
  }
542
688
  };
543
689
  executeWork() {
@@ -586,13 +732,15 @@ export class NetworkProcessor {
586
732
  for (const msg of messageOrArray) {
587
733
  msg.startProcessUnixSec = nowSec;
588
734
  if (msg.queueAddedMs !== undefined) {
589
- this.metrics?.gossipValidationQueue.queueTime.observe(nowSec - msg.queueAddedMs / 1000);
735
+ this.metrics?.gossipValidationQueue.queueTime.observe({ topic: msg.topic.type }, nowSec - msg.queueAddedMs / 1000);
590
736
  }
591
737
  }
592
738
  }
593
739
  else {
594
- // indexed queue is not used here
595
740
  messageOrArray.startProcessUnixSec = nowSec;
741
+ if (messageOrArray.queueAddedMs !== undefined) {
742
+ this.metrics?.gossipValidationQueue.queueTime.observe({ topic: messageOrArray.topic.type }, nowSec - messageOrArray.queueAddedMs / 1000);
743
+ }
596
744
  }
597
745
  const acceptanceArr = Array.isArray(messageOrArray)
598
746
  ? // for beacon_attestation topic, process attestations with same attestation data
@@ -603,42 +751,42 @@ export class NetworkProcessor {
603
751
  await this.gossipValidatorFn({ ...messageOrArray, msgSlot: messageOrArray.msgSlot ?? null }),
604
752
  ];
605
753
  if (Array.isArray(messageOrArray)) {
606
- for (const msg of messageOrArray) {
607
- this.trackJobTime(msg, messageOrArray.length);
754
+ for (const [i, msg] of messageOrArray.entries()) {
755
+ this.trackJobTime(msg, messageOrArray.length, acceptanceArr[i]);
608
756
  }
609
757
  }
610
758
  else {
611
- this.trackJobTime(messageOrArray, 1);
759
+ this.trackJobTime(messageOrArray, 1, acceptanceArr[0]);
612
760
  }
613
- // Use setTimeout to yield to the macro queue
614
- // This is mostly due to too many attestation messages, and a gossipsub RPC may
615
- // 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
616
764
  if (Array.isArray(messageOrArray)) {
617
765
  for (const [i, msg] of messageOrArray.entries()) {
618
- callInNextEventLoop(() => {
619
- this.events.emit(NetworkEvent.gossipMessageValidationResult, {
620
- msgId: msg.msgId,
621
- propagationSource: msg.propagationSource,
622
- acceptance: acceptanceArr[i],
623
- });
766
+ this.events.emit(NetworkEvent.gossipMessageValidationResult, {
767
+ msgId: msg.msgId,
768
+ propagationSource: msg.propagationSource,
769
+ acceptance: acceptanceArr[i],
624
770
  });
625
771
  }
626
772
  }
627
773
  else {
628
- callInNextEventLoop(() => {
629
- this.events.emit(NetworkEvent.gossipMessageValidationResult, {
630
- msgId: messageOrArray.msgId,
631
- propagationSource: messageOrArray.propagationSource,
632
- acceptance: acceptanceArr[0],
633
- });
774
+ this.events.emit(NetworkEvent.gossipMessageValidationResult, {
775
+ msgId: messageOrArray.msgId,
776
+ propagationSource: messageOrArray.propagationSource,
777
+ acceptance: acceptanceArr[0],
634
778
  });
635
779
  }
636
780
  }
637
- trackJobTime(message, numJob) {
781
+ trackJobTime(message, numJob, acceptance) {
638
782
  if (message.startProcessUnixSec !== null) {
783
+ // record job wait time for all messages
639
784
  this.metrics?.gossipValidationQueue.jobWaitTime.observe({ topic: message.topic.type }, message.startProcessUnixSec - message.seenTimestampSec);
640
- // if it takes 64ms to process 64 jobs, the average job time is 1ms
641
- this.metrics?.gossipValidationQueue.jobTime.observe({ topic: message.topic.type }, (Date.now() / 1000 - message.startProcessUnixSec) / numJob);
785
+ // but only record job time for ACCEPT messages
786
+ if (acceptance === TopicValidatorResult.Accept) {
787
+ // if it takes 64ms to process 64 jobs, the average job time is 1ms
788
+ this.metrics?.gossipValidationQueue.jobTime.observe({ topic: message.topic.type }, (Date.now() / 1000 - message.startProcessUnixSec) / numJob);
789
+ }
642
790
  }
643
791
  }
644
792
  /**