@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
@@ -3,7 +3,7 @@ import { PayloadStatus } from "@lodestar/fork-choice";
3
3
  import { ForkSeq, NUMBER_OF_COLUMNS, isForkPostElectra, isForkPostGloas, } from "@lodestar/params";
4
4
  import { computeTimeAtSlot } from "@lodestar/state-transition";
5
5
  import { isGloasDataColumnSidecar, ssz, sszTypesFor, } from "@lodestar/types";
6
- import { LogLevel, prettyBytes, toHex, toRootHex } from "@lodestar/utils";
6
+ import { LogLevel, toHex, toRootHex } from "@lodestar/utils";
7
7
  import { BlockInputSource, isBlockInputColumns, } from "../../chain/blocks/blockInput/index.js";
8
8
  import { PayloadError, PayloadErrorCode } from "../../chain/blocks/importExecutionPayload.js";
9
9
  import { PayloadEnvelopeInputSource } from "../../chain/blocks/payloadEnvelopeInput/index.js";
@@ -22,6 +22,7 @@ import { validateGossipProposerPreferences } from "../../chain/validation/propos
22
22
  import { OpSource } from "../../chain/validatorMonitor.js";
23
23
  import { kzgCommitmentToVersionedHash } from "../../util/blobs.js";
24
24
  import { getBlobKzgCommitments, getDataColumnSidecarSlot } from "../../util/dataColumns.js";
25
+ import { callInNextEventLoop } from "../../util/eventLoop.js";
25
26
  import { GossipType, } from "../gossip/interface.js";
26
27
  import { sszDeserialize } from "../gossip/topic.js";
27
28
  import { PeerAction } from "../peers/index.js";
@@ -54,7 +55,6 @@ function getSequentialHandlers(modules, options) {
54
55
  const slot = signedBlock.message.slot;
55
56
  const forkTypes = config.getForkTypes(slot);
56
57
  const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
57
- const blockShortHex = prettyBytes(blockRootHex);
58
58
  const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
59
59
  const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
60
60
  // always set block to seen cache for all forks so that we don't need to download it
@@ -62,6 +62,7 @@ function getSequentialHandlers(modules, options) {
62
62
  // tracked in https://github.com/ChainSafe/lodestar/issues/7957
63
63
  const logCtx = {
64
64
  slot,
65
+ root: blockRootHex,
65
66
  currentSlot: chain.clock.currentSlot,
66
67
  peerId: peerIdStr,
67
68
  delaySec,
@@ -113,7 +114,7 @@ function getSequentialHandlers(modules, options) {
113
114
  }
114
115
  catch (e) {
115
116
  if (e instanceof BlockGossipError) {
116
- logger.debug("Gossip block has error", { slot, root: blockShortHex, code: e.type.code });
117
+ logger.debug("Gossip block has error", { slot, root: blockRootHex, code: e.type.code });
117
118
  if ((e.type.code === BlockErrorCode.PARENT_BLOCK_UNKNOWN ||
118
119
  e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN) &&
119
120
  blockInput) {
@@ -131,7 +132,7 @@ function getSequentialHandlers(modules, options) {
131
132
  if (e.action === GossipAction.IGNORE) {
132
133
  throw e;
133
134
  }
134
- chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`);
135
+ chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`, blockRootHex);
135
136
  }
136
137
  // REJECT or unexpected (non-BlockGossipError) error: drop the optimistically-added entries from
137
138
  // both caches, keeping them consistent.
@@ -155,7 +156,6 @@ function getSequentialHandlers(modules, options) {
155
156
  const slot = blobBlockHeader.slot;
156
157
  const fork = config.getForkName(slot);
157
158
  const blockRootHex = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobBlockHeader));
158
- const blockShortHex = prettyBytes(blockRootHex);
159
159
  const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
160
160
  const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
161
161
  try {
@@ -204,7 +204,7 @@ function getSequentialHandlers(modules, options) {
204
204
  if (e instanceof BlobSidecarGossipError) {
205
205
  // Don't trigger this yet if full block and blobs haven't arrived yet
206
206
  if (e.type.code === BlobSidecarErrorCode.PARENT_UNKNOWN) {
207
- logger.debug("Gossip blob has error", { slot, root: blockShortHex, code: e.type.code });
207
+ logger.debug("Gossip blob has error", { slot, root: blockRootHex, code: e.type.code });
208
208
  // no need to trigger `unknownBlockParent` event here, as we already did it in `validateBeaconBlock()`
209
209
  //
210
210
  // TODO(fulu): is this note above correct? Could have random blob that we see that could trigger
@@ -509,7 +509,7 @@ function getSequentialHandlers(modules, options) {
509
509
  logLevel = LogLevel.error;
510
510
  }
511
511
  metrics?.gossipBlock.processBlockErrors.inc({ error: e instanceof BlockError ? e.type.code : "NOT_BLOCK_ERROR" });
512
- logger[logLevel]("Error processing block", { slot, peer: peerIdStr, blockRoot: prettyBytes(blockInput.blockRootHex) }, e);
512
+ logger[logLevel]("Error processing block", { slot, root: blockInput.blockRootHex, peer: peerIdStr }, e);
513
513
  // TODO(fulu): Revisit when we prune block inputs
514
514
  chain.seenBlockInputCache.prune(blockInput.blockRootHex);
515
515
  });
@@ -520,8 +520,16 @@ function getSequentialHandlers(modules, options) {
520
520
  const signedBlock = sszDeserialize(topic, serializedData);
521
521
  try {
522
522
  const blockInput = await validateBeaconBlock(signedBlock, topic.boundary.fork, peerIdStr, seenTimestampSec);
523
- chain.serializedCache.set(signedBlock, serializedData);
524
- handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
523
+ // Handler - deferred to next event loop so the validation result propagates first
524
+ callInNextEventLoop(() => {
525
+ try {
526
+ chain.serializedCache.set(signedBlock, serializedData);
527
+ handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
528
+ }
529
+ catch (e) {
530
+ logger.debug("Error handling gossip block", { slot: signedBlock.message.slot, root: blockInput.blockRootHex }, e);
531
+ }
532
+ });
525
533
  }
526
534
  catch (e) {
527
535
  // Spec: IGNORE the block, ie not to re-publish to peers
@@ -534,6 +542,8 @@ function getSequentialHandlers(modules, options) {
534
542
  // blockInput was optimistically seeded in validateBeaconBlock and retained on IGNORE
535
543
  const blockInput = chain.seenBlockInputCache.get(e.type.root);
536
544
  if (blockInput) {
545
+ // We're returning IGNORE (thrown below), so this block is not forwarded to peers. Unlike the
546
+ // happy path there is no rush to forward, so we don't need to wrap in callInNextEventLoop.
537
547
  chain.serializedCache.set(signedBlock, serializedData);
538
548
  // this is technically not a valid gossip block but gossip validation is a cheap subset of checks
539
549
  // this runs the full state transition, so importing an equivocating-but-valid block here is safe.
@@ -553,26 +563,34 @@ function getSequentialHandlers(modules, options) {
553
563
  throw new GossipActionError(GossipAction.REJECT, { code: "PRE_DENEB_BLOCK" });
554
564
  }
555
565
  const blockInput = await validateBeaconBlob(blobSidecar, topic.subnet, peerIdStr, seenTimestampSec);
556
- chain.serializedCache.set(blobSidecar, serializedData);
557
- if (!blockInput.hasBlockAndAllData()) {
558
- const cutoffTimeMs = getCutoffTimeMs(chain, blobSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
559
- chain.logger.debug("Received gossip blob, waiting for full data availability", {
560
- msToWait: cutoffTimeMs,
561
- blobIndex: index,
562
- ...blockInput.getLogMeta(),
563
- });
564
- blockInput.waitForAllData(cutoffTimeMs).catch((_e) => {
565
- chain.logger.debug("Waited for data after receiving gossip blob. Cut-off reached so attempting to fetch remainder of BlockInput", {
566
- blobIndex: index,
567
- ...blockInput.getLogMeta(),
568
- });
569
- chain.emitter.emit(ChainEvent.incompleteBlockInput, {
570
- blockInput,
571
- peer: peerIdStr,
572
- source: BlockInputSource.gossip,
573
- });
574
- });
575
- }
566
+ // Handler - deferred to next event loop so the validation result propagates first
567
+ callInNextEventLoop(() => {
568
+ try {
569
+ chain.serializedCache.set(blobSidecar, serializedData);
570
+ if (!blockInput.hasBlockAndAllData()) {
571
+ const cutoffTimeMs = getCutoffTimeMs(chain, blobSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
572
+ chain.logger.debug("Received gossip blob, waiting for full data availability", {
573
+ msToWait: cutoffTimeMs,
574
+ blobIndex: index,
575
+ ...blockInput.getLogMeta(),
576
+ });
577
+ blockInput.waitForAllData(cutoffTimeMs).catch((_e) => {
578
+ chain.logger.debug("Waited for data after receiving gossip blob. Cut-off reached so attempting to fetch remainder of BlockInput", {
579
+ blobIndex: index,
580
+ ...blockInput.getLogMeta(),
581
+ });
582
+ chain.emitter.emit(ChainEvent.incompleteBlockInput, {
583
+ blockInput,
584
+ peer: peerIdStr,
585
+ source: BlockInputSource.gossip,
586
+ });
587
+ });
588
+ }
589
+ }
590
+ catch (e) {
591
+ logger.debug("Error handling gossip blob", { slot: blobSlot, root: blockInput.blockRootHex, index }, e);
592
+ }
593
+ });
576
594
  },
577
595
  [GossipType.data_column_sidecar]: async ({ gossipData, topic, peerIdStr, seenTimestampSec, }) => {
578
596
  const { fork } = topic.boundary;
@@ -592,50 +610,58 @@ function getSequentialHandlers(modules, options) {
592
610
  }
593
611
  // After gloas, data columns are tracked in PayloadEnvelopeInput
594
612
  const payloadInput = await validatePayloadDataColumn(dataColumnSidecar, topic.subnet, peerIdStr, seenTimestampSec);
595
- chain.serializedCache.set(dataColumnSidecar, serializedData);
596
- const payloadInputMeta = payloadInput.getLogMeta();
597
- const { receivedColumns } = payloadInputMeta;
598
- // it's not helpful to track every single column received
599
- // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
600
- switch (receivedColumns) {
601
- case 1:
602
- case config.SAMPLES_PER_SLOT:
603
- case 2 * config.SAMPLES_PER_SLOT:
604
- case NUMBER_OF_COLUMNS / 4:
605
- case NUMBER_OF_COLUMNS / 2:
606
- case NUMBER_OF_COLUMNS:
607
- metrics?.dataColumns.elapsedTimeTillReceived.observe({ receivedOrder: receivedColumns }, delaySec);
608
- break;
609
- }
610
- if (!payloadInput.hasComputedAllData()) {
611
- // if we've received at least half of the columns, trigger reconstruction of the rest
612
- if (receivedColumns >= NUMBER_OF_COLUMNS / 2) {
613
- chain.columnReconstructionTracker.triggerColumnReconstruction(payloadInput);
613
+ // Handler - deferred to next event loop so the validation result propagates first
614
+ callInNextEventLoop(() => {
615
+ try {
616
+ chain.serializedCache.set(dataColumnSidecar, serializedData);
617
+ const payloadInputMeta = payloadInput.getLogMeta();
618
+ const { receivedColumns } = payloadInputMeta;
619
+ // it's not helpful to track every single column received
620
+ // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
621
+ switch (receivedColumns) {
622
+ case 1:
623
+ case config.SAMPLES_PER_SLOT:
624
+ case 2 * config.SAMPLES_PER_SLOT:
625
+ case NUMBER_OF_COLUMNS / 4:
626
+ case NUMBER_OF_COLUMNS / 2:
627
+ case NUMBER_OF_COLUMNS:
628
+ metrics?.dataColumns.elapsedTimeTillReceived.observe({ receivedOrder: receivedColumns }, delaySec);
629
+ break;
630
+ }
631
+ if (!payloadInput.hasComputedAllData()) {
632
+ // if we've received at least half of the columns, trigger reconstruction of the rest
633
+ if (receivedColumns >= NUMBER_OF_COLUMNS / 2) {
634
+ chain.columnReconstructionTracker.triggerColumnReconstruction(payloadInput);
635
+ }
636
+ chain.logger.debug("Received gossip data column, payload envelope input not yet complete", {
637
+ dataColumnIndex: index,
638
+ ...payloadInputMeta,
639
+ });
640
+ }
641
+ // NOTE: we do NOT call chain.processExecutionPayload here. That is triggered only by
642
+ // envelope arrival (gossip or API). An in-flight importExecutionPayload is awaiting
643
+ // payloadInput.waitForAllData(); addColumn above will resolve it once hasAllData flips.
644
+ if (!payloadInput.isComplete()) {
645
+ const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
646
+ // do not await here to not delay gossip validation
647
+ payloadInput.waitForEnvelopeAndAllData(cutoffTimeMs).catch((_e) => {
648
+ chain.logger.debug("Waited for envelope and data after receiving gossip column. Cut-off reached so emitting incompletePayloadEnvelope", {
649
+ dataColumnIndex: index,
650
+ ...payloadInputMeta,
651
+ });
652
+ // TODO GLOAS: UnknownBlockSync to handle this event
653
+ chain.emitter.emit(ChainEvent.incompletePayloadEnvelope, {
654
+ payloadInput,
655
+ peer: peerIdStr,
656
+ source: BlockInputSource.gossip,
657
+ });
658
+ });
659
+ }
614
660
  }
615
- chain.logger.debug("Received gossip data column, payload envelope input not yet complete", {
616
- dataColumnIndex: index,
617
- ...payloadInputMeta,
618
- });
619
- }
620
- // NOTE: we do NOT call chain.processExecutionPayload here. That is triggered only by
621
- // envelope arrival (gossip or API). An in-flight importExecutionPayload is awaiting
622
- // payloadInput.waitForAllData(); addColumn above will resolve it once hasAllData flips.
623
- if (!payloadInput.isComplete()) {
624
- const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
625
- // do not await here to not delay gossip validation
626
- payloadInput.waitForEnvelopeAndAllData(cutoffTimeMs).catch((_e) => {
627
- chain.logger.debug("Waited for envelope and data after receiving gossip column. Cut-off reached so emitting incompletePayloadEnvelope", {
628
- dataColumnIndex: index,
629
- ...payloadInputMeta,
630
- });
631
- // TODO GLOAS: UnknownBlockSync to handle this event
632
- chain.emitter.emit(ChainEvent.incompletePayloadEnvelope, {
633
- payloadInput,
634
- peer: peerIdStr,
635
- source: BlockInputSource.gossip,
636
- });
637
- });
638
- }
661
+ catch (e) {
662
+ logger.debug("Error handling gossip data column", { slot: dataColumnSlot, root: payloadInput.blockRootHex, index }, e);
663
+ }
664
+ });
639
665
  }
640
666
  else {
641
667
  if (config.getForkSeq(dataColumnSlot) < ForkSeq.fulu) {
@@ -651,49 +677,57 @@ function getSequentialHandlers(modules, options) {
651
677
  }
652
678
  // Before gloas, data columns are tracked in BlockInput
653
679
  const blockInput = await validateBeaconDataColumn(dataColumnSidecar, serializedData, topic.subnet, peerIdStr, seenTimestampSec);
654
- chain.serializedCache.set(dataColumnSidecar, serializedData);
655
- const blockInputMeta = blockInput.getLogMeta();
656
- const { receivedColumns } = blockInputMeta;
657
- // it's not helpful to track every single column received
658
- // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
659
- switch (receivedColumns) {
660
- case 1:
661
- case config.SAMPLES_PER_SLOT:
662
- case 2 * config.SAMPLES_PER_SLOT:
663
- case NUMBER_OF_COLUMNS / 4:
664
- case NUMBER_OF_COLUMNS / 2:
665
- case NUMBER_OF_COLUMNS:
666
- metrics?.dataColumns.elapsedTimeTillReceived.observe({ receivedOrder: receivedColumns }, delaySec);
667
- break;
668
- }
669
- if (!blockInput.hasComputedAllData()) {
670
- // immediately attempt fetch of data columns from execution engine
671
- chain.getBlobsTracker.triggerGetBlobs(blockInput);
672
- // if we've received at least half of the columns, trigger reconstruction of the rest
673
- if (blockInput.columnCount >= NUMBER_OF_COLUMNS / 2) {
674
- chain.columnReconstructionTracker.triggerColumnReconstruction(blockInput);
680
+ // Handler - deferred to next event loop so the validation result propagates first
681
+ callInNextEventLoop(() => {
682
+ try {
683
+ chain.serializedCache.set(dataColumnSidecar, serializedData);
684
+ const blockInputMeta = blockInput.getLogMeta();
685
+ const { receivedColumns } = blockInputMeta;
686
+ // it's not helpful to track every single column received
687
+ // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
688
+ switch (receivedColumns) {
689
+ case 1:
690
+ case config.SAMPLES_PER_SLOT:
691
+ case 2 * config.SAMPLES_PER_SLOT:
692
+ case NUMBER_OF_COLUMNS / 4:
693
+ case NUMBER_OF_COLUMNS / 2:
694
+ case NUMBER_OF_COLUMNS:
695
+ metrics?.dataColumns.elapsedTimeTillReceived.observe({ receivedOrder: receivedColumns }, delaySec);
696
+ break;
697
+ }
698
+ if (!blockInput.hasComputedAllData()) {
699
+ // immediately attempt fetch of data columns from execution engine
700
+ chain.getBlobsTracker.triggerGetBlobs(blockInput);
701
+ // if we've received at least half of the columns, trigger reconstruction of the rest
702
+ if (blockInput.columnCount >= NUMBER_OF_COLUMNS / 2) {
703
+ chain.columnReconstructionTracker.triggerColumnReconstruction(blockInput);
704
+ }
705
+ }
706
+ if (!blockInput.hasBlockAndAllData()) {
707
+ const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
708
+ chain.logger.debug("Received gossip data column, waiting for full data availability", {
709
+ msToWait: cutoffTimeMs,
710
+ dataColumnIndex: index,
711
+ ...blockInputMeta,
712
+ });
713
+ // do not await here to not delay gossip validation
714
+ blockInput.waitForBlockAndAllData(cutoffTimeMs).catch((_e) => {
715
+ chain.logger.debug("Waited for data after receiving gossip column. Cut-off reached so attempting to fetch remainder of BlockInput", {
716
+ dataColumnIndex: index,
717
+ ...blockInputMeta,
718
+ });
719
+ chain.emitter.emit(ChainEvent.incompleteBlockInput, {
720
+ blockInput,
721
+ peer: peerIdStr,
722
+ source: BlockInputSource.gossip,
723
+ });
724
+ });
725
+ }
675
726
  }
676
- }
677
- if (!blockInput.hasBlockAndAllData()) {
678
- const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
679
- chain.logger.debug("Received gossip data column, waiting for full data availability", {
680
- msToWait: cutoffTimeMs,
681
- dataColumnIndex: index,
682
- ...blockInputMeta,
683
- });
684
- // do not await here to not delay gossip validation
685
- blockInput.waitForBlockAndAllData(cutoffTimeMs).catch((_e) => {
686
- chain.logger.debug("Waited for data after receiving gossip column. Cut-off reached so attempting to fetch remainder of BlockInput", {
687
- dataColumnIndex: index,
688
- ...blockInputMeta,
689
- });
690
- chain.emitter.emit(ChainEvent.incompleteBlockInput, {
691
- blockInput,
692
- peer: peerIdStr,
693
- source: BlockInputSource.gossip,
694
- });
695
- });
696
- }
727
+ catch (e) {
728
+ logger.debug("Error handling gossip data column", { slot: dataColumnSlot, root: blockInput.blockRootHex, index }, e);
729
+ }
730
+ });
697
731
  }
698
732
  },
699
733
  [GossipType.beacon_aggregate_and_proof]: async ({ gossipData, topic, seenTimestampSec, }) => {
@@ -710,62 +744,76 @@ function getSequentialHandlers(modules, options) {
710
744
  }
711
745
  throw e;
712
746
  }
713
- // Handler
747
+ // Handler - deferred to next event loop so the validation result propagates first
714
748
  const { indexedAttestation, committeeValidatorIndices, attDataRootHex } = validationResult;
715
- chain.validatorMonitor?.registerGossipAggregatedAttestation(seenTimestampSec, signedAggregateAndProof, indexedAttestation);
716
- const aggregatedAttestation = signedAggregateAndProof.message.aggregate;
717
- const insertOutcome = chain.aggregatedAttestationPool.add(aggregatedAttestation, attDataRootHex, indexedAttestation.attestingIndices.length, committeeValidatorIndices);
718
- metrics?.opPool.aggregatedAttestationPool.gossipInsertOutcome.inc({ insertOutcome });
719
- if (!options.dontSendGossipAttestationsToForkchoice) {
749
+ callInNextEventLoop(() => {
720
750
  try {
721
- chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
751
+ chain.validatorMonitor?.registerGossipAggregatedAttestation(seenTimestampSec, signedAggregateAndProof, indexedAttestation);
752
+ const aggregatedAttestation = signedAggregateAndProof.message.aggregate;
753
+ const insertOutcome = chain.aggregatedAttestationPool.add(aggregatedAttestation, attDataRootHex, indexedAttestation.attestingIndices.length, committeeValidatorIndices);
754
+ metrics?.opPool.aggregatedAttestationPool.gossipInsertOutcome.inc({ insertOutcome });
755
+ if (!options.dontSendGossipAttestationsToForkchoice) {
756
+ chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
757
+ }
758
+ chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate);
722
759
  }
723
760
  catch (e) {
724
- logger.debug("Error adding gossip aggregated attestation to forkchoice", { slot: aggregatedAttestation.data.slot }, e);
761
+ logger.debug("Error handling gossip aggregate and proof", {
762
+ slot: signedAggregateAndProof.message.aggregate.data.slot,
763
+ root: toRootHex(signedAggregateAndProof.message.aggregate.data.beaconBlockRoot),
764
+ }, e);
725
765
  }
726
- }
727
- chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate);
766
+ });
728
767
  },
729
768
  [GossipType.attester_slashing]: async ({ gossipData, topic, }) => {
730
769
  const { serializedData } = gossipData;
731
770
  const { fork } = topic.boundary;
732
771
  const attesterSlashing = sszDeserialize(topic, serializedData);
733
772
  await validateGossipAttesterSlashing(chain, attesterSlashing);
734
- // Handler
735
- try {
736
- chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
737
- chain.forkChoice.onAttesterSlashing(attesterSlashing);
738
- }
739
- catch (e) {
740
- logger.error("Error adding attesterSlashing to pool", {}, e);
741
- }
742
- chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
773
+ // Handler - deferred to next event loop so the validation result propagates first
774
+ callInNextEventLoop(() => {
775
+ try {
776
+ chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
777
+ chain.forkChoice.onAttesterSlashing(attesterSlashing);
778
+ chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
779
+ }
780
+ catch (e) {
781
+ logger.debug("Error handling gossip attester slashing", { slot: attesterSlashing.attestation1.data.slot }, e);
782
+ }
783
+ });
743
784
  },
744
785
  [GossipType.proposer_slashing]: async ({ gossipData, topic, }) => {
745
786
  const { serializedData } = gossipData;
746
787
  const proposerSlashing = sszDeserialize(topic, serializedData);
747
788
  await validateGossipProposerSlashing(chain, proposerSlashing);
748
- // Handler
749
- try {
750
- chain.opPool.insertProposerSlashing(proposerSlashing);
751
- }
752
- catch (e) {
753
- logger.error("Error adding attesterSlashing to pool", {}, e);
754
- }
755
- chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
789
+ // Handler - deferred to next event loop so the validation result propagates first
790
+ callInNextEventLoop(() => {
791
+ try {
792
+ chain.opPool.insertProposerSlashing(proposerSlashing);
793
+ chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
794
+ }
795
+ catch (e) {
796
+ logger.debug("Error handling gossip proposer slashing", {
797
+ slot: proposerSlashing.signedHeader1.message.slot,
798
+ proposerIndex: proposerSlashing.signedHeader1.message.proposerIndex,
799
+ }, e);
800
+ }
801
+ });
756
802
  },
757
803
  [GossipType.voluntary_exit]: async ({ gossipData, topic }) => {
758
804
  const { serializedData } = gossipData;
759
805
  const voluntaryExit = sszDeserialize(topic, serializedData);
760
806
  await validateGossipVoluntaryExit(chain, voluntaryExit);
761
- // Handler
762
- try {
763
- chain.opPool.insertVoluntaryExit(voluntaryExit);
764
- }
765
- catch (e) {
766
- logger.error("Error adding voluntaryExit to pool", {}, e);
767
- }
768
- chain.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit);
807
+ // Handler - deferred to next event loop so the validation result propagates first
808
+ callInNextEventLoop(() => {
809
+ try {
810
+ chain.opPool.insertVoluntaryExit(voluntaryExit);
811
+ chain.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit);
812
+ }
813
+ catch (e) {
814
+ logger.debug("Error handling gossip voluntary exit", { epoch: voluntaryExit.message.epoch, validatorIndex: voluntaryExit.message.validatorIndex }, e);
815
+ }
816
+ });
769
817
  },
770
818
  [GossipType.sync_committee_contribution_and_proof]: async ({ gossipData, topic, }) => {
771
819
  const { serializedData } = gossipData;
@@ -776,16 +824,21 @@ function getSequentialHandlers(modules, options) {
776
824
  }
777
825
  throw e;
778
826
  });
779
- // Handler
780
- chain.validatorMonitor?.registerGossipSyncContributionAndProof(contributionAndProof.message, syncCommitteeParticipantIndices);
781
- try {
782
- const insertOutcome = chain.syncContributionAndProofPool.add(contributionAndProof.message, syncCommitteeParticipantIndices.length);
783
- metrics?.opPool.syncContributionAndProofPool.gossipInsertOutcome.inc({ insertOutcome });
784
- }
785
- catch (e) {
786
- logger.error("Error adding to contributionAndProof pool", {}, e);
787
- }
788
- chain.emitter.emit(routes.events.EventType.contributionAndProof, contributionAndProof);
827
+ // Handler - deferred to next event loop so the validation result propagates first
828
+ callInNextEventLoop(() => {
829
+ try {
830
+ chain.validatorMonitor?.registerGossipSyncContributionAndProof(contributionAndProof.message, syncCommitteeParticipantIndices);
831
+ const insertOutcome = chain.syncContributionAndProofPool.add(contributionAndProof.message, syncCommitteeParticipantIndices.length);
832
+ metrics?.opPool.syncContributionAndProofPool.gossipInsertOutcome.inc({ insertOutcome });
833
+ chain.emitter.emit(routes.events.EventType.contributionAndProof, contributionAndProof);
834
+ }
835
+ catch (e) {
836
+ logger.debug("Error handling gossip contribution and proof", {
837
+ slot: contributionAndProof.message.contribution.slot,
838
+ subcommitteeIndex: contributionAndProof.message.contribution.subcommitteeIndex,
839
+ }, e);
840
+ }
841
+ });
789
842
  },
790
843
  [GossipType.sync_committee]: async ({ gossipData, topic }) => {
791
844
  const { serializedData } = gossipData;
@@ -801,16 +854,19 @@ function getSequentialHandlers(modules, options) {
801
854
  }
802
855
  throw e;
803
856
  }
804
- // Handler — add for ALL positions this validator holds in the subcommittee
805
- try {
806
- for (const indexInSubcommittee of indicesInSubcommittee) {
807
- const insertOutcome = chain.syncCommitteeMessagePool.add(subnet, syncCommittee, indexInSubcommittee);
808
- metrics?.opPool.syncCommitteeMessagePoolInsertOutcome.inc({ insertOutcome });
857
+ // Handler - deferred to next event loop so the validation result propagates first
858
+ // add for ALL positions this validator holds in the subcommittee
859
+ callInNextEventLoop(() => {
860
+ try {
861
+ for (const indexInSubcommittee of indicesInSubcommittee) {
862
+ const insertOutcome = chain.syncCommitteeMessagePool.add(subnet, syncCommittee, indexInSubcommittee);
863
+ metrics?.opPool.syncCommitteeMessagePoolInsertOutcome.inc({ insertOutcome });
864
+ }
809
865
  }
810
- }
811
- catch (e) {
812
- logger.debug("Error adding to syncCommittee pool", { subnet }, e);
813
- }
866
+ catch (e) {
867
+ logger.debug("Error handling gossip sync committee", { slot: syncCommittee.slot, subnet, validatorIndex: syncCommittee.validatorIndex }, e);
868
+ }
869
+ });
814
870
  },
815
871
  [GossipType.light_client_finality_update]: async ({ gossipData, topic, }) => {
816
872
  const { serializedData } = gossipData;
@@ -827,14 +883,16 @@ function getSequentialHandlers(modules, options) {
827
883
  const { serializedData } = gossipData;
828
884
  const blsToExecutionChange = sszDeserialize(topic, serializedData);
829
885
  await validateGossipBlsToExecutionChange(chain, blsToExecutionChange);
830
- // Handler
831
- try {
832
- chain.opPool.insertBlsToExecutionChange(blsToExecutionChange);
833
- }
834
- catch (e) {
835
- logger.error("Error adding blsToExecutionChange to pool", {}, e);
836
- }
837
- chain.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange);
886
+ // Handler - deferred to next event loop so the validation result propagates first
887
+ callInNextEventLoop(() => {
888
+ try {
889
+ chain.opPool.insertBlsToExecutionChange(blsToExecutionChange);
890
+ chain.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange);
891
+ }
892
+ catch (e) {
893
+ logger.debug("Error handling gossip bls to execution change", { validatorIndex: blsToExecutionChange.message.validatorIndex }, e);
894
+ }
895
+ });
838
896
  },
839
897
  [GossipType.execution_payload]: async ({ gossipData, topic, peerIdStr, seenTimestampSec, }) => {
840
898
  const { serializedData } = gossipData;
@@ -878,99 +936,137 @@ function getSequentialHandlers(modules, options) {
878
936
  blockRoot: blockRootHex,
879
937
  });
880
938
  }
881
- chain.serializedCache.set(signedEnvelope, serializedData);
882
- payloadInput.addPayloadEnvelope({
883
- envelope: signedEnvelope,
884
- source: PayloadEnvelopeInputSource.gossip,
885
- seenTimestampSec,
886
- peerIdStr,
887
- });
888
- chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
889
- slot,
890
- builderIndex: envelope.builderIndex,
891
- blockHash: toRootHex(envelope.payload.blockHash),
892
- blockRoot: blockRootHex,
893
- });
894
- chain.processExecutionPayload(payloadInput, { validSignature: true }).catch((e) => {
895
- // Adjust verbosity based on error type
896
- let logLevel;
897
- if (e instanceof PayloadError) {
898
- switch (e.type.code) {
899
- // BLOCK_NOT_IN_FORK_CHOICE should not happen, validateGossipExecutionPayloadEnvelope above
900
- // already verified the block is in fork choice
901
- case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
902
- case PayloadErrorCode.MISS_BLOCK_STATE:
903
- case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
904
- // Errors might indicate an issue with our node or the connected EL client
939
+ // Handler - deferred to next event loop so the validation result propagates first
940
+ callInNextEventLoop(() => {
941
+ try {
942
+ chain.serializedCache.set(signedEnvelope, serializedData);
943
+ payloadInput.addPayloadEnvelope({
944
+ envelope: signedEnvelope,
945
+ source: PayloadEnvelopeInputSource.gossip,
946
+ seenTimestampSec,
947
+ peerIdStr,
948
+ });
949
+ chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
950
+ slot,
951
+ builderIndex: envelope.builderIndex,
952
+ blockHash: toRootHex(envelope.payload.blockHash),
953
+ blockRoot: blockRootHex,
954
+ });
955
+ chain.processExecutionPayload(payloadInput, { validSignature: true }).catch((e) => {
956
+ // Adjust verbosity based on error type
957
+ let logLevel;
958
+ if (e instanceof PayloadError) {
959
+ switch (e.type.code) {
960
+ // BLOCK_NOT_IN_FORK_CHOICE should not happen, validateGossipExecutionPayloadEnvelope above
961
+ // already verified the block is in fork choice
962
+ case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
963
+ case PayloadErrorCode.MISS_BLOCK_STATE:
964
+ case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
965
+ // Errors might indicate an issue with our node or the connected EL client
966
+ logLevel = LogLevel.error;
967
+ break;
968
+ // INVALID_SIGNATURE should not happen, signature is verified during gossip validation
969
+ case PayloadErrorCode.INVALID_SIGNATURE:
970
+ case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
971
+ case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
972
+ core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
973
+ // Misbehaving peer, but could highlight an issue in another client
974
+ logLevel = LogLevel.warn;
975
+ break;
976
+ }
977
+ }
978
+ else {
979
+ // Any unexpected error
905
980
  logLevel = LogLevel.error;
906
- break;
907
- // INVALID_SIGNATURE should not happen, signature is verified during gossip validation
908
- case PayloadErrorCode.INVALID_SIGNATURE:
909
- case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
910
- case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
911
- core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
912
- // Misbehaving peer, but could highlight an issue in another client
913
- logLevel = LogLevel.warn;
914
- break;
915
- }
981
+ }
982
+ metrics?.gossipExecutionPayloadEnvelope.processPayloadErrors.inc({
983
+ error: e instanceof PayloadError ? e.type.code : "NOT_PAYLOAD_ERROR",
984
+ });
985
+ chain.logger[logLevel]("Error processing execution payload from gossip", { slot, root: blockRootHex, peer: peerIdStr }, e);
986
+ });
916
987
  }
917
- else {
918
- // Any unexpected error
919
- logLevel = LogLevel.error;
988
+ catch (e) {
989
+ logger.debug("Error handling gossip execution payload", { slot, root: blockRootHex }, e);
920
990
  }
921
- metrics?.gossipExecutionPayloadEnvelope.processPayloadErrors.inc({
922
- error: e instanceof PayloadError ? e.type.code : "NOT_PAYLOAD_ERROR",
923
- });
924
- chain.logger[logLevel]("Error processing execution payload from gossip", { slot, peer: peerIdStr, root: blockRootHex }, e);
925
991
  });
926
992
  },
927
993
  [GossipType.payload_attestation_message]: async ({ gossipData, topic, seenTimestampSec, }) => {
928
994
  const { serializedData } = gossipData;
929
995
  const payloadAttestationMessage = sszDeserialize(topic, serializedData);
930
996
  const validationResult = await validateGossipPayloadAttestationMessage(chain, payloadAttestationMessage);
931
- const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
932
- metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({ source: OpSource.gossip }, delaySec);
933
- try {
934
- const insertOutcome = chain.payloadAttestationPool.add(payloadAttestationMessage, validationResult.attDataRootHex, validationResult.validatorCommitteeIndices);
935
- metrics?.opPool.payloadAttestationPool.gossipInsertOutcome.inc({ insertOutcome });
936
- }
937
- catch (e) {
938
- logger.error("Error adding to payloadAttestation pool", {}, e);
939
- }
940
- chain.forkChoice.notifyPtcMessages(toRootHex(payloadAttestationMessage.data.beaconBlockRoot), payloadAttestationMessage.data.slot, validationResult.validatorCommitteeIndices, payloadAttestationMessage.data.payloadPresent, payloadAttestationMessage.data.blobDataAvailable);
941
- chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
942
- version: config.getForkName(payloadAttestationMessage.data.slot),
943
- data: payloadAttestationMessage,
997
+ // Handler - deferred to next event loop so the validation result propagates first
998
+ callInNextEventLoop(() => {
999
+ try {
1000
+ const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
1001
+ metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({ source: OpSource.gossip }, delaySec);
1002
+ const nextSlotProposer = chain.getHeadState().getBeaconProposer(payloadAttestationMessage.data.slot + 1);
1003
+ const isNextSlotProposer = chain.beaconProposerCache.get(nextSlotProposer) !== undefined;
1004
+ if (isNextSlotProposer) {
1005
+ try {
1006
+ const insertOutcome = chain.payloadAttestationPool.add(payloadAttestationMessage, validationResult.attDataRootHex, validationResult.validatorCommitteeIndices);
1007
+ metrics?.opPool.payloadAttestationPool.gossipInsertOutcome.inc({ insertOutcome });
1008
+ }
1009
+ catch (e) {
1010
+ logger.debug("Error adding to payloadAttestation pool", {}, e);
1011
+ }
1012
+ }
1013
+ chain.forkChoice.notifyPtcMessages(toRootHex(payloadAttestationMessage.data.beaconBlockRoot), payloadAttestationMessage.data.slot, validationResult.validatorCommitteeIndices, payloadAttestationMessage.data.payloadPresent, payloadAttestationMessage.data.blobDataAvailable);
1014
+ chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
1015
+ version: config.getForkName(payloadAttestationMessage.data.slot),
1016
+ data: payloadAttestationMessage,
1017
+ });
1018
+ }
1019
+ catch (e) {
1020
+ logger.debug("Error handling gossip payload attestation message", {
1021
+ slot: payloadAttestationMessage.data.slot,
1022
+ root: toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
1023
+ }, e);
1024
+ }
944
1025
  });
945
1026
  },
946
1027
  [GossipType.execution_payload_bid]: async ({ gossipData, topic, seenTimestampSec, }) => {
947
1028
  const { serializedData } = gossipData;
948
1029
  const executionPayloadBid = sszDeserialize(topic, serializedData);
949
1030
  const { proposerIndex } = await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
950
- // this could be negative, because it's most likely the bid of next slot comes at this clock slot
951
- const elapsedSec = chain.clock.secFromSlot(executionPayloadBid.message.slot, seenTimestampSec);
952
- metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({ source: OpSource.gossip }, elapsedSec);
953
- // Handle valid payload bid by storing in a bid pool
954
- try {
955
- const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
956
- metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({ insertOutcome });
957
- }
958
- catch (e) {
959
- logger.error("Error adding to executionPayloadBid pool", {}, e);
960
- }
961
- chain.validatorMonitor?.registerExecutionPayloadBid(OpSource.gossip, proposerIndex, executionPayloadBid.message);
962
- chain.emitter.emit(routes.events.EventType.executionPayloadBid, {
963
- version: config.getForkName(executionPayloadBid.message.slot),
964
- data: executionPayloadBid,
1031
+ // Handler - deferred to next event loop so the validation result propagates first
1032
+ callInNextEventLoop(() => {
1033
+ try {
1034
+ // this could be negative, because it's most likely the bid of next slot comes at this clock slot
1035
+ const elapsedSec = chain.clock.secFromSlot(executionPayloadBid.message.slot, seenTimestampSec);
1036
+ metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({ source: OpSource.gossip }, elapsedSec);
1037
+ // Handle valid payload bid by storing in a bid pool
1038
+ const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
1039
+ metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({ insertOutcome });
1040
+ chain.validatorMonitor?.registerExecutionPayloadBid(OpSource.gossip, proposerIndex, executionPayloadBid.message);
1041
+ chain.emitter.emit(routes.events.EventType.executionPayloadBid, {
1042
+ version: config.getForkName(executionPayloadBid.message.slot),
1043
+ data: executionPayloadBid,
1044
+ });
1045
+ }
1046
+ catch (e) {
1047
+ logger.debug("Error handling gossip execution payload bid", {
1048
+ slot: executionPayloadBid.message.slot,
1049
+ root: toRootHex(executionPayloadBid.message.parentBlockRoot),
1050
+ proposerIndex,
1051
+ }, e);
1052
+ }
965
1053
  });
966
1054
  },
967
1055
  [GossipType.proposer_preferences]: async ({ gossipData, topic, }) => {
968
1056
  const { serializedData } = gossipData;
969
1057
  const signedProposerPreferences = sszDeserialize(topic, serializedData);
970
1058
  await validateGossipProposerPreferences(chain, signedProposerPreferences);
971
- chain.emitter.emit(routes.events.EventType.proposerPreferences, {
972
- version: config.getForkName(signedProposerPreferences.message.proposalSlot),
973
- data: signedProposerPreferences,
1059
+ // Handler - deferred to next event loop so the validation result propagates first
1060
+ callInNextEventLoop(() => {
1061
+ try {
1062
+ chain.emitter.emit(routes.events.EventType.proposerPreferences, {
1063
+ version: config.getForkName(signedProposerPreferences.message.proposalSlot),
1064
+ data: signedProposerPreferences,
1065
+ });
1066
+ }
1067
+ catch (e) {
1068
+ logger.debug("Error handling gossip proposer preferences", { proposalSlot: signedProposerPreferences.message.proposalSlot }, e);
1069
+ }
974
1070
  });
975
1071
  },
976
1072
  };
@@ -997,43 +1093,9 @@ function getBatchHandlers(modules, options) {
997
1093
  subnet: param.topic.subnet,
998
1094
  }));
999
1095
  const { results: validationResults, batchableBls } = await validateGossipAttestationsSameAttData(fork, chain, validationParams);
1000
- for (const [i, validationResult] of validationResults.entries()) {
1001
- if (validationResult.err) {
1002
- results.push(validationResult.err);
1003
- continue;
1004
- }
1005
- // null means no error
1006
- results.push(null);
1007
- // Handler
1008
- const { indexedAttestation, attDataRootHex, attestation, committeeIndex, validatorCommitteeIndex, committeeSize, } = validationResult.result;
1009
- chain.validatorMonitor?.registerGossipUnaggregatedAttestation(gossipHandlerParams[i].seenTimestampSec, indexedAttestation);
1010
- const { subnet } = validationResult.result;
1011
- try {
1012
- // Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages
1013
- // but don't add to attestation pool, to save CPU and RAM
1014
- if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) {
1015
- const insertOutcome = chain.attestationPool.add(committeeIndex, attestation, attDataRootHex, validatorCommitteeIndex, committeeSize);
1016
- metrics?.opPool.attestationPool.gossipInsertOutcome.inc({ insertOutcome });
1017
- }
1018
- }
1019
- catch (e) {
1020
- logger.error("Error adding unaggregated attestation to pool", { subnet }, e);
1021
- }
1022
- if (!options.dontSendGossipAttestationsToForkchoice) {
1023
- try {
1024
- chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
1025
- }
1026
- catch (e) {
1027
- logger.debug("Error adding gossip unaggregated attestation to forkchoice", { subnet }, e);
1028
- }
1029
- }
1030
- if (isForkPostElectra(fork)) {
1031
- chain.emitter.emit(routes.events.EventType.singleAttestation, attestation);
1032
- }
1033
- else {
1034
- chain.emitter.emit(routes.events.EventType.attestation, attestation);
1035
- chain.emitter.emit(routes.events.EventType.singleAttestation, toElectraSingleAttestation(attestation, indexedAttestation.attestingIndices[0]));
1036
- }
1096
+ // verdicts computed synchronously and returned immediately
1097
+ for (const validationResult of validationResults) {
1098
+ results.push(validationResult.err ? validationResult.err : null);
1037
1099
  }
1038
1100
  if (batchableBls) {
1039
1101
  metrics?.gossipAttestation.attestationBatchHistogram.observe(attestationCount);
@@ -1041,6 +1103,47 @@ function getBatchHandlers(modules, options) {
1041
1103
  else {
1042
1104
  metrics?.gossipAttestation.attestationNonBatchCount.inc(attestationCount);
1043
1105
  }
1106
+ // Handler - deferred to next event loop so the validation result propagates first.
1107
+ callInNextEventLoop(() => {
1108
+ for (const [i, validationResult] of validationResults.entries()) {
1109
+ if (validationResult.err)
1110
+ continue;
1111
+ const { indexedAttestation, attDataRootHex, attestation, committeeIndex, validatorCommitteeIndex, committeeSize, subnet, } = validationResult.result;
1112
+ try {
1113
+ chain.validatorMonitor?.registerGossipUnaggregatedAttestation(gossipHandlerParams[i].seenTimestampSec, indexedAttestation);
1114
+ try {
1115
+ // Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages
1116
+ // but don't add to attestation pool, to save CPU and RAM
1117
+ if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) {
1118
+ const insertOutcome = chain.attestationPool.add(committeeIndex, attestation, attDataRootHex, validatorCommitteeIndex, committeeSize);
1119
+ metrics?.opPool.attestationPool.gossipInsertOutcome.inc({ insertOutcome });
1120
+ }
1121
+ }
1122
+ catch (e) {
1123
+ logger.debug("Error adding gossip unaggregated attestation to pool", { subnet }, e);
1124
+ }
1125
+ // Separate boundary: a pool insertion error above must not skip the fork-choice vote
1126
+ if (!options.dontSendGossipAttestationsToForkchoice) {
1127
+ try {
1128
+ chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
1129
+ }
1130
+ catch (e) {
1131
+ logger.debug("Error adding gossip unaggregated attestation to forkchoice", { subnet }, e);
1132
+ }
1133
+ }
1134
+ if (isForkPostElectra(fork)) {
1135
+ chain.emitter.emit(routes.events.EventType.singleAttestation, attestation);
1136
+ }
1137
+ else {
1138
+ chain.emitter.emit(routes.events.EventType.attestation, attestation);
1139
+ chain.emitter.emit(routes.events.EventType.singleAttestation, toElectraSingleAttestation(attestation, indexedAttestation.attestingIndices[0]));
1140
+ }
1141
+ }
1142
+ catch (e) {
1143
+ logger.debug("Error handling gossip attestation", { slot: indexedAttestation.data.slot, subnet }, e);
1144
+ }
1145
+ }
1146
+ });
1044
1147
  return results;
1045
1148
  },
1046
1149
  };