@lodestar/beacon-node 1.48.0-dev.f22c5ce63e → 1.48.0-dev.f63ac74039

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 (303) 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 +14 -9
  57. package/lib/chain/errors/blockError.d.ts.map +1 -1
  58. package/lib/chain/errors/blockError.js +0 -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.d.ts.map +1 -1
  91. package/lib/chain/prepareNextSlot.js +16 -14
  92. package/lib/chain/prepareNextSlot.js.map +1 -1
  93. package/lib/chain/produceBlock/produceBlockBody.d.ts +4 -2
  94. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  95. package/lib/chain/produceBlock/produceBlockBody.js +57 -72
  96. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  97. package/lib/chain/validation/block.d.ts.map +1 -1
  98. package/lib/chain/validation/block.js +27 -55
  99. package/lib/chain/validation/block.js.map +1 -1
  100. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  101. package/lib/chain/validation/dataColumnSidecar.js +38 -13
  102. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  103. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  104. package/lib/chain/validation/executionPayloadBid.js +56 -40
  105. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  106. package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
  107. package/lib/chain/validation/executionPayloadEnvelope.js +10 -29
  108. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  109. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  110. package/lib/chain/validation/payloadAttestationMessage.js +5 -6
  111. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  112. package/lib/chain/validatorMonitor.d.ts +7 -1
  113. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  114. package/lib/chain/validatorMonitor.js +22 -5
  115. package/lib/chain/validatorMonitor.js.map +1 -1
  116. package/lib/db/beacon.d.ts +14 -5
  117. package/lib/db/beacon.d.ts.map +1 -1
  118. package/lib/db/beacon.js +17 -3
  119. package/lib/db/beacon.js.map +1 -1
  120. package/lib/db/dataColumnStore.d.ts +39 -0
  121. package/lib/db/dataColumnStore.d.ts.map +1 -0
  122. package/lib/db/dataColumnStore.js +81 -0
  123. package/lib/db/dataColumnStore.js.map +1 -0
  124. package/lib/db/flatFileStore/atomicWrite.d.ts +8 -0
  125. package/lib/db/flatFileStore/atomicWrite.d.ts.map +1 -0
  126. package/lib/db/flatFileStore/atomicWrite.js +61 -0
  127. package/lib/db/flatFileStore/atomicWrite.js.map +1 -0
  128. package/lib/db/flatFileStore/dcolFormat.d.ts +90 -0
  129. package/lib/db/flatFileStore/dcolFormat.d.ts.map +1 -0
  130. package/lib/db/flatFileStore/dcolFormat.js +286 -0
  131. package/lib/db/flatFileStore/dcolFormat.js.map +1 -0
  132. package/lib/db/flatFileStore/errors.d.ts +82 -0
  133. package/lib/db/flatFileStore/errors.d.ts.map +1 -0
  134. package/lib/db/flatFileStore/errors.js +32 -0
  135. package/lib/db/flatFileStore/errors.js.map +1 -0
  136. package/lib/db/flatFileStore/flatFileStore.d.ts +41 -0
  137. package/lib/db/flatFileStore/flatFileStore.d.ts.map +1 -0
  138. package/lib/db/flatFileStore/flatFileStore.js +251 -0
  139. package/lib/db/flatFileStore/flatFileStore.js.map +1 -0
  140. package/lib/db/flatFileStore/index.d.ts +4 -0
  141. package/lib/db/flatFileStore/index.d.ts.map +1 -0
  142. package/lib/db/flatFileStore/index.js +2 -0
  143. package/lib/db/flatFileStore/index.js.map +1 -0
  144. package/lib/db/flatFileStore/interface.d.ts +25 -0
  145. package/lib/db/flatFileStore/interface.d.ts.map +1 -0
  146. package/lib/db/flatFileStore/interface.js +2 -0
  147. package/lib/db/flatFileStore/interface.js.map +1 -0
  148. package/lib/db/flatFileStore/metrics.d.ts +24 -0
  149. package/lib/db/flatFileStore/metrics.d.ts.map +1 -0
  150. package/lib/db/flatFileStore/metrics.js +25 -0
  151. package/lib/db/flatFileStore/metrics.js.map +1 -0
  152. package/lib/db/flatFileStore/path.d.ts +9 -0
  153. package/lib/db/flatFileStore/path.d.ts.map +1 -0
  154. package/lib/db/flatFileStore/path.js +21 -0
  155. package/lib/db/flatFileStore/path.js.map +1 -0
  156. package/lib/db/flatFileStore/slotIndex.d.ts +14 -0
  157. package/lib/db/flatFileStore/slotIndex.d.ts.map +1 -0
  158. package/lib/db/flatFileStore/slotIndex.js +46 -0
  159. package/lib/db/flatFileStore/slotIndex.js.map +1 -0
  160. package/lib/db/flatFileStore/snappy.d.ts +3 -0
  161. package/lib/db/flatFileStore/snappy.d.ts.map +1 -0
  162. package/lib/db/flatFileStore/snappy.js +12 -0
  163. package/lib/db/flatFileStore/snappy.js.map +1 -0
  164. package/lib/db/index.d.ts +1 -0
  165. package/lib/db/index.d.ts.map +1 -1
  166. package/lib/db/interface.d.ts +8 -1
  167. package/lib/db/interface.d.ts.map +1 -1
  168. package/lib/db/repositories/blockArchive.d.ts +8 -1
  169. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  170. package/lib/db/repositories/blockArchive.js +65 -28
  171. package/lib/db/repositories/blockArchive.js.map +1 -1
  172. package/lib/db/repositories/blockArchiveIndex.d.ts +1 -2
  173. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  174. package/lib/db/repositories/blockArchiveIndex.js +3 -7
  175. package/lib/db/repositories/blockArchiveIndex.js.map +1 -1
  176. package/lib/execution/builder/validateBid.d.ts.map +1 -1
  177. package/lib/execution/builder/validateBid.js +8 -5
  178. package/lib/execution/builder/validateBid.js.map +1 -1
  179. package/lib/execution/engine/mock.d.ts.map +1 -1
  180. package/lib/execution/engine/mock.js +4 -1
  181. package/lib/execution/engine/mock.js.map +1 -1
  182. package/lib/metrics/metrics/lodestar.d.ts +22 -3
  183. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  184. package/lib/metrics/metrics/lodestar.js +60 -6
  185. package/lib/metrics/metrics/lodestar.js.map +1 -1
  186. package/lib/monitoring/clientStats.js +1 -1
  187. package/lib/monitoring/clientStats.js.map +1 -1
  188. package/lib/network/gossip/encoding.d.ts.map +1 -1
  189. package/lib/network/gossip/encoding.js +0 -7
  190. package/lib/network/gossip/encoding.js.map +1 -1
  191. package/lib/network/gossip/topic.d.ts +2 -1
  192. package/lib/network/gossip/topic.d.ts.map +1 -1
  193. package/lib/network/gossip/topic.js +4 -20
  194. package/lib/network/gossip/topic.js.map +1 -1
  195. package/lib/network/libp2p/index.d.ts +9 -1
  196. package/lib/network/libp2p/index.d.ts.map +1 -1
  197. package/lib/network/libp2p/index.js +23 -0
  198. package/lib/network/libp2p/index.js.map +1 -1
  199. package/lib/network/network.js +2 -2
  200. package/lib/network/network.js.map +1 -1
  201. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  202. package/lib/network/processor/gossipHandlers.js +388 -285
  203. package/lib/network/processor/gossipHandlers.js.map +1 -1
  204. package/lib/network/processor/index.d.ts +49 -3
  205. package/lib/network/processor/index.d.ts.map +1 -1
  206. package/lib/network/processor/index.js +254 -114
  207. package/lib/network/processor/index.js.map +1 -1
  208. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  209. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +112 -91
  210. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  211. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  212. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +2 -0
  213. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  214. package/lib/network/reqresp/protocols.d.ts.map +1 -1
  215. package/lib/network/reqresp/protocols.js +6 -22
  216. package/lib/network/reqresp/protocols.js.map +1 -1
  217. package/lib/network/reqresp/rateLimit.js +2 -1
  218. package/lib/network/reqresp/rateLimit.js.map +1 -1
  219. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts +2 -1
  220. package/lib/network/reqresp/utils/dataColumnResponseValidation.d.ts.map +1 -1
  221. package/lib/network/reqresp/utils/dataColumnResponseValidation.js +14 -9
  222. package/lib/network/reqresp/utils/dataColumnResponseValidation.js.map +1 -1
  223. package/lib/node/nodejs.d.ts +2 -1
  224. package/lib/node/nodejs.d.ts.map +1 -1
  225. package/lib/node/nodejs.js +4 -2
  226. package/lib/node/nodejs.js.map +1 -1
  227. package/lib/sync/range/range.d.ts.map +1 -1
  228. package/lib/sync/range/range.js +24 -1
  229. package/lib/sync/range/range.js.map +1 -1
  230. package/lib/util/sszBytes.d.ts +6 -0
  231. package/lib/util/sszBytes.d.ts.map +1 -1
  232. package/lib/util/sszBytes.js +1 -1
  233. package/lib/util/sszBytes.js.map +1 -1
  234. package/package.json +19 -20
  235. package/src/api/impl/beacon/blocks/index.ts +8 -14
  236. package/src/api/impl/beacon/state/utils.ts +15 -0
  237. package/src/api/impl/config/index.ts +4 -1
  238. package/src/api/impl/validator/index.ts +44 -43
  239. package/src/chain/archiveStore/archiveStore.ts +12 -1
  240. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -0
  241. package/src/chain/archiveStore/historicalState/metrics.ts +14 -1
  242. package/src/chain/archiveStore/historicalState/types.ts +2 -0
  243. package/src/chain/archiveStore/historicalState/worker.ts +4 -1
  244. package/src/chain/archiveStore/utils/archiveBlocks.ts +73 -42
  245. package/src/chain/blocks/importBlock.ts +25 -20
  246. package/src/chain/blocks/index.ts +120 -23
  247. package/src/chain/blocks/types.ts +9 -0
  248. package/src/chain/blocks/utils/chainSegment.ts +26 -12
  249. package/src/chain/blocks/writeBlockInputToDb.ts +10 -17
  250. package/src/chain/chain.ts +153 -84
  251. package/src/chain/emitter.ts +1 -1
  252. package/src/chain/errors/blockError.ts +19 -10
  253. package/src/chain/errors/dataColumnSidecarError.ts +24 -8
  254. package/src/chain/errors/executionPayloadBid.ts +2 -0
  255. package/src/chain/errors/executionPayloadEnvelope.ts +14 -11
  256. package/src/chain/errors/gossipValidation.ts +1 -0
  257. package/src/chain/forkChoice/index.ts +2 -0
  258. package/src/chain/interface.ts +4 -4
  259. package/src/chain/lightClient/index.ts +32 -36
  260. package/src/chain/lightClient/proofs.ts +10 -0
  261. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  262. package/src/chain/prepareNextSlot.ts +21 -17
  263. package/src/chain/produceBlock/produceBlockBody.ts +62 -79
  264. package/src/chain/validation/block.ts +27 -72
  265. package/src/chain/validation/dataColumnSidecar.ts +47 -14
  266. package/src/chain/validation/executionPayloadBid.ts +62 -44
  267. package/src/chain/validation/executionPayloadEnvelope.ts +10 -37
  268. package/src/chain/validation/payloadAttestationMessage.ts +8 -9
  269. package/src/chain/validatorMonitor.ts +24 -7
  270. package/src/db/beacon.ts +33 -6
  271. package/src/db/dataColumnStore.ts +113 -0
  272. package/src/db/flatFileStore/atomicWrite.ts +65 -0
  273. package/src/db/flatFileStore/dcolFormat.ts +389 -0
  274. package/src/db/flatFileStore/errors.ts +53 -0
  275. package/src/db/flatFileStore/flatFileStore.ts +310 -0
  276. package/src/db/flatFileStore/index.ts +3 -0
  277. package/src/db/flatFileStore/interface.ts +21 -0
  278. package/src/db/flatFileStore/metrics.ts +48 -0
  279. package/src/db/flatFileStore/path.ts +32 -0
  280. package/src/db/flatFileStore/slotIndex.ts +56 -0
  281. package/src/db/flatFileStore/snappy.ts +14 -0
  282. package/src/db/index.ts +1 -0
  283. package/src/db/interface.ts +9 -1
  284. package/src/db/repositories/blockArchive.ts +89 -30
  285. package/src/db/repositories/blockArchiveIndex.ts +4 -9
  286. package/src/execution/builder/validateBid.ts +10 -6
  287. package/src/execution/engine/mock.ts +4 -1
  288. package/src/metrics/metrics/lodestar.ts +63 -6
  289. package/src/monitoring/clientStats.ts +1 -1
  290. package/src/network/gossip/encoding.ts +0 -7
  291. package/src/network/gossip/topic.ts +3 -25
  292. package/src/network/libp2p/index.ts +28 -1
  293. package/src/network/network.ts +2 -2
  294. package/src/network/processor/gossipHandlers.ts +500 -355
  295. package/src/network/processor/index.ts +287 -165
  296. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +139 -105
  297. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +2 -0
  298. package/src/network/reqresp/protocols.ts +7 -24
  299. package/src/network/reqresp/rateLimit.ts +2 -1
  300. package/src/network/reqresp/utils/dataColumnResponseValidation.ts +15 -7
  301. package/src/node/nodejs.ts +6 -1
  302. package/src/sync/range/range.ts +23 -1
  303. package/src/util/sszBytes.ts +1 -1
@@ -27,7 +27,7 @@ import {
27
27
  ssz,
28
28
  sszTypesFor,
29
29
  } from "@lodestar/types";
30
- import {LogLevel, Logger, prettyBytes, toHex, toRootHex} from "@lodestar/utils";
30
+ import {LogLevel, Logger, toHex, toRootHex} from "@lodestar/utils";
31
31
  import {
32
32
  BlockInput,
33
33
  BlockInputColumns,
@@ -87,6 +87,7 @@ import {OpSource} from "../../chain/validatorMonitor.js";
87
87
  import {Metrics} from "../../metrics/index.js";
88
88
  import {kzgCommitmentToVersionedHash} from "../../util/blobs.js";
89
89
  import {getBlobKzgCommitments, getDataColumnSidecarSlot} from "../../util/dataColumns.js";
90
+ import {callInNextEventLoop} from "../../util/eventLoop.js";
90
91
  import {INetworkCore} from "../core/index.js";
91
92
  import {NetworkEventBus} from "../events.js";
92
93
  import {
@@ -156,7 +157,6 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
156
157
  const slot = signedBlock.message.slot;
157
158
  const forkTypes = config.getForkTypes(slot);
158
159
  const blockRootHex = toRootHex(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
159
- const blockShortHex = prettyBytes(blockRootHex);
160
160
  const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
161
161
  const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
162
162
 
@@ -166,6 +166,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
166
166
 
167
167
  const logCtx = {
168
168
  slot,
169
+ root: blockRootHex,
169
170
  currentSlot: chain.clock.currentSlot,
170
171
  peerId: peerIdStr,
171
172
  delaySec,
@@ -226,7 +227,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
226
227
  return blockInput;
227
228
  } catch (e) {
228
229
  if (e instanceof BlockGossipError) {
229
- logger.debug("Gossip block has error", {slot, root: blockShortHex, code: e.type.code});
230
+ logger.debug("Gossip block has error", {slot, root: blockRootHex, code: e.type.code});
230
231
  if (
231
232
  (e.type.code === BlockErrorCode.PARENT_BLOCK_UNKNOWN ||
232
233
  e.type.code === BlockErrorCode.PARENT_PAYLOAD_UNKNOWN) &&
@@ -248,7 +249,12 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
248
249
  throw e;
249
250
  }
250
251
 
251
- chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`);
252
+ chain.persistInvalidSszValue(
253
+ forkTypes.SignedBeaconBlock,
254
+ signedBlock,
255
+ `gossip_reject_slot_${slot}`,
256
+ blockRootHex
257
+ );
252
258
  }
253
259
 
254
260
  // REJECT or unexpected (non-BlockGossipError) error: drop the optimistically-added entries from
@@ -278,7 +284,6 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
278
284
  const slot = blobBlockHeader.slot;
279
285
  const fork = config.getForkName(slot);
280
286
  const blockRootHex = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobBlockHeader));
281
- const blockShortHex = prettyBytes(blockRootHex);
282
287
 
283
288
  const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
284
289
  const recvToValLatency = Date.now() / 1000 - seenTimestampSec;
@@ -331,7 +336,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
331
336
  if (e instanceof BlobSidecarGossipError) {
332
337
  // Don't trigger this yet if full block and blobs haven't arrived yet
333
338
  if (e.type.code === BlobSidecarErrorCode.PARENT_UNKNOWN) {
334
- logger.debug("Gossip blob has error", {slot, root: blockShortHex, code: e.type.code});
339
+ logger.debug("Gossip blob has error", {slot, root: blockRootHex, code: e.type.code});
335
340
  // no need to trigger `unknownBlockParent` event here, as we already did it in `validateBeaconBlock()`
336
341
  //
337
342
  // TODO(fulu): is this note above correct? Could have random blob that we see that could trigger
@@ -695,11 +700,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
695
700
  logLevel = LogLevel.error;
696
701
  }
697
702
  metrics?.gossipBlock.processBlockErrors.inc({error: e instanceof BlockError ? e.type.code : "NOT_BLOCK_ERROR"});
698
- logger[logLevel](
699
- "Error processing block",
700
- {slot, peer: peerIdStr, blockRoot: prettyBytes(blockInput.blockRootHex)},
701
- e as Error
702
- );
703
+ logger[logLevel]("Error processing block", {slot, root: blockInput.blockRootHex, peer: peerIdStr}, e as Error);
703
704
  // TODO(fulu): Revisit when we prune block inputs
704
705
  chain.seenBlockInputCache.prune(blockInput.blockRootHex);
705
706
  });
@@ -717,8 +718,19 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
717
718
  const signedBlock = sszDeserialize(topic, serializedData);
718
719
  try {
719
720
  const blockInput = await validateBeaconBlock(signedBlock, topic.boundary.fork, peerIdStr, seenTimestampSec);
720
- chain.serializedCache.set(signedBlock, serializedData);
721
- handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
721
+ // Handler - deferred to next event loop so the validation result propagates first
722
+ callInNextEventLoop(() => {
723
+ try {
724
+ chain.serializedCache.set(signedBlock, serializedData);
725
+ handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
726
+ } catch (e) {
727
+ logger.debug(
728
+ "Error handling gossip block",
729
+ {slot: signedBlock.message.slot, root: blockInput.blockRootHex},
730
+ e as Error
731
+ );
732
+ }
733
+ });
722
734
  } catch (e) {
723
735
  // Spec: IGNORE the block, ie not to re-publish to peers
724
736
  // but we should still import an equivocating (REPEAT_PROPOSAL) block into fork choice because we don't
@@ -732,6 +744,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
732
744
  // blockInput was optimistically seeded in validateBeaconBlock and retained on IGNORE
733
745
  const blockInput = chain.seenBlockInputCache.get(e.type.root);
734
746
  if (blockInput) {
747
+ // We're returning IGNORE (thrown below), so this block is not forwarded to peers. Unlike the
748
+ // happy path there is no rush to forward, so we don't need to wrap in callInNextEventLoop.
735
749
  chain.serializedCache.set(signedBlock, serializedData);
736
750
  // this is technically not a valid gossip block but gossip validation is a cheap subset of checks
737
751
  // this runs the full state transition, so importing an equivocating-but-valid block here is safe.
@@ -758,29 +772,40 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
758
772
  throw new GossipActionError(GossipAction.REJECT, {code: "PRE_DENEB_BLOCK"});
759
773
  }
760
774
  const blockInput = await validateBeaconBlob(blobSidecar, topic.subnet, peerIdStr, seenTimestampSec);
761
- chain.serializedCache.set(blobSidecar, serializedData);
762
- if (!blockInput.hasBlockAndAllData()) {
763
- const cutoffTimeMs = getCutoffTimeMs(chain, blobSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
764
- chain.logger.debug("Received gossip blob, waiting for full data availability", {
765
- msToWait: cutoffTimeMs,
766
- blobIndex: index,
767
- ...blockInput.getLogMeta(),
768
- });
769
- blockInput.waitForAllData(cutoffTimeMs).catch((_e) => {
770
- chain.logger.debug(
771
- "Waited for data after receiving gossip blob. Cut-off reached so attempting to fetch remainder of BlockInput",
772
- {
775
+ // Handler - deferred to next event loop so the validation result propagates first
776
+ callInNextEventLoop(() => {
777
+ try {
778
+ chain.serializedCache.set(blobSidecar, serializedData);
779
+ if (!blockInput.hasBlockAndAllData()) {
780
+ const cutoffTimeMs = getCutoffTimeMs(chain, blobSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
781
+ chain.logger.debug("Received gossip blob, waiting for full data availability", {
782
+ msToWait: cutoffTimeMs,
773
783
  blobIndex: index,
774
784
  ...blockInput.getLogMeta(),
775
- }
785
+ });
786
+ blockInput.waitForAllData(cutoffTimeMs).catch((_e) => {
787
+ chain.logger.debug(
788
+ "Waited for data after receiving gossip blob. Cut-off reached so attempting to fetch remainder of BlockInput",
789
+ {
790
+ blobIndex: index,
791
+ ...blockInput.getLogMeta(),
792
+ }
793
+ );
794
+ chain.emitter.emit(ChainEvent.incompleteBlockInput, {
795
+ blockInput,
796
+ peer: peerIdStr,
797
+ source: BlockInputSource.gossip,
798
+ });
799
+ });
800
+ }
801
+ } catch (e) {
802
+ logger.debug(
803
+ "Error handling gossip blob",
804
+ {slot: blobSlot, root: blockInput.blockRootHex, index},
805
+ e as Error
776
806
  );
777
- chain.emitter.emit(ChainEvent.incompleteBlockInput, {
778
- blockInput,
779
- peer: peerIdStr,
780
- source: BlockInputSource.gossip,
781
- });
782
- });
783
- }
807
+ }
808
+ });
784
809
  },
785
810
 
786
811
  [GossipType.data_column_sidecar]: async ({
@@ -813,58 +838,69 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
813
838
  peerIdStr,
814
839
  seenTimestampSec
815
840
  );
816
- chain.serializedCache.set(dataColumnSidecar, serializedData);
817
-
818
- const payloadInputMeta = payloadInput.getLogMeta();
819
- const {receivedColumns} = payloadInputMeta;
820
- // it's not helpful to track every single column received
821
- // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
822
- switch (receivedColumns) {
823
- case 1:
824
- case config.SAMPLES_PER_SLOT:
825
- case 2 * config.SAMPLES_PER_SLOT:
826
- case NUMBER_OF_COLUMNS / 4:
827
- case NUMBER_OF_COLUMNS / 2:
828
- case NUMBER_OF_COLUMNS:
829
- metrics?.dataColumns.elapsedTimeTillReceived.observe({receivedOrder: receivedColumns}, delaySec);
830
- break;
831
- }
832
-
833
- if (!payloadInput.hasComputedAllData()) {
834
- // if we've received at least half of the columns, trigger reconstruction of the rest
835
- if (receivedColumns >= NUMBER_OF_COLUMNS / 2) {
836
- chain.columnReconstructionTracker.triggerColumnReconstruction(payloadInput);
837
- }
841
+ // Handler - deferred to next event loop so the validation result propagates first
842
+ callInNextEventLoop(() => {
843
+ try {
844
+ chain.serializedCache.set(dataColumnSidecar, serializedData);
845
+
846
+ const payloadInputMeta = payloadInput.getLogMeta();
847
+ const {receivedColumns} = payloadInputMeta;
848
+ // it's not helpful to track every single column received
849
+ // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
850
+ switch (receivedColumns) {
851
+ case 1:
852
+ case config.SAMPLES_PER_SLOT:
853
+ case 2 * config.SAMPLES_PER_SLOT:
854
+ case NUMBER_OF_COLUMNS / 4:
855
+ case NUMBER_OF_COLUMNS / 2:
856
+ case NUMBER_OF_COLUMNS:
857
+ metrics?.dataColumns.elapsedTimeTillReceived.observe({receivedOrder: receivedColumns}, delaySec);
858
+ break;
859
+ }
838
860
 
839
- chain.logger.debug("Received gossip data column, payload envelope input not yet complete", {
840
- dataColumnIndex: index,
841
- ...payloadInputMeta,
842
- });
843
- }
861
+ if (!payloadInput.hasComputedAllData()) {
862
+ // if we've received at least half of the columns, trigger reconstruction of the rest
863
+ if (receivedColumns >= NUMBER_OF_COLUMNS / 2) {
864
+ chain.columnReconstructionTracker.triggerColumnReconstruction(payloadInput);
865
+ }
844
866
 
845
- // NOTE: we do NOT call chain.processExecutionPayload here. That is triggered only by
846
- // envelope arrival (gossip or API). An in-flight importExecutionPayload is awaiting
847
- // payloadInput.waitForAllData(); addColumn above will resolve it once hasAllData flips.
848
-
849
- if (!payloadInput.isComplete()) {
850
- const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
851
- // do not await here to not delay gossip validation
852
- payloadInput.waitForEnvelopeAndAllData(cutoffTimeMs).catch((_e) => {
853
- chain.logger.debug(
854
- "Waited for envelope and data after receiving gossip column. Cut-off reached so emitting incompletePayloadEnvelope",
855
- {
867
+ chain.logger.debug("Received gossip data column, payload envelope input not yet complete", {
856
868
  dataColumnIndex: index,
857
869
  ...payloadInputMeta,
858
- }
870
+ });
871
+ }
872
+
873
+ // NOTE: we do NOT call chain.processExecutionPayload here. That is triggered only by
874
+ // envelope arrival (gossip or API). An in-flight importExecutionPayload is awaiting
875
+ // payloadInput.waitForAllData(); addColumn above will resolve it once hasAllData flips.
876
+
877
+ if (!payloadInput.isComplete()) {
878
+ const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
879
+ // do not await here to not delay gossip validation
880
+ payloadInput.waitForEnvelopeAndAllData(cutoffTimeMs).catch((_e) => {
881
+ chain.logger.debug(
882
+ "Waited for envelope and data after receiving gossip column. Cut-off reached so emitting incompletePayloadEnvelope",
883
+ {
884
+ dataColumnIndex: index,
885
+ ...payloadInputMeta,
886
+ }
887
+ );
888
+ // TODO GLOAS: UnknownBlockSync to handle this event
889
+ chain.emitter.emit(ChainEvent.incompletePayloadEnvelope, {
890
+ payloadInput,
891
+ peer: peerIdStr,
892
+ source: BlockInputSource.gossip,
893
+ });
894
+ });
895
+ }
896
+ } catch (e) {
897
+ logger.debug(
898
+ "Error handling gossip data column",
899
+ {slot: dataColumnSlot, root: payloadInput.blockRootHex, index},
900
+ e as Error
859
901
  );
860
- // TODO GLOAS: UnknownBlockSync to handle this event
861
- chain.emitter.emit(ChainEvent.incompletePayloadEnvelope, {
862
- payloadInput,
863
- peer: peerIdStr,
864
- source: BlockInputSource.gossip,
865
- });
866
- });
867
- }
902
+ }
903
+ });
868
904
  } else {
869
905
  if (config.getForkSeq(dataColumnSlot) < ForkSeq.fulu) {
870
906
  throw new GossipActionError(GossipAction.REJECT, {code: "PRE_FULU_BLOCK"});
@@ -887,54 +923,65 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
887
923
  peerIdStr,
888
924
  seenTimestampSec
889
925
  );
890
- chain.serializedCache.set(dataColumnSidecar, serializedData);
891
- const blockInputMeta = blockInput.getLogMeta();
892
- const {receivedColumns} = blockInputMeta;
893
- // it's not helpful to track every single column received
894
- // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
895
- switch (receivedColumns) {
896
- case 1:
897
- case config.SAMPLES_PER_SLOT:
898
- case 2 * config.SAMPLES_PER_SLOT:
899
- case NUMBER_OF_COLUMNS / 4:
900
- case NUMBER_OF_COLUMNS / 2:
901
- case NUMBER_OF_COLUMNS:
902
- metrics?.dataColumns.elapsedTimeTillReceived.observe({receivedOrder: receivedColumns}, delaySec);
903
- break;
904
- }
926
+ // Handler - deferred to next event loop so the validation result propagates first
927
+ callInNextEventLoop(() => {
928
+ try {
929
+ chain.serializedCache.set(dataColumnSidecar, serializedData);
930
+ const blockInputMeta = blockInput.getLogMeta();
931
+ const {receivedColumns} = blockInputMeta;
932
+ // it's not helpful to track every single column received
933
+ // instead of that, track 1st, 8th, 16th 32th, 64th, and 128th column
934
+ switch (receivedColumns) {
935
+ case 1:
936
+ case config.SAMPLES_PER_SLOT:
937
+ case 2 * config.SAMPLES_PER_SLOT:
938
+ case NUMBER_OF_COLUMNS / 4:
939
+ case NUMBER_OF_COLUMNS / 2:
940
+ case NUMBER_OF_COLUMNS:
941
+ metrics?.dataColumns.elapsedTimeTillReceived.observe({receivedOrder: receivedColumns}, delaySec);
942
+ break;
943
+ }
905
944
 
906
- if (!blockInput.hasComputedAllData()) {
907
- // immediately attempt fetch of data columns from execution engine
908
- chain.getBlobsTracker.triggerGetBlobs(blockInput);
909
- // if we've received at least half of the columns, trigger reconstruction of the rest
910
- if (blockInput.columnCount >= NUMBER_OF_COLUMNS / 2) {
911
- chain.columnReconstructionTracker.triggerColumnReconstruction(blockInput);
912
- }
913
- }
945
+ if (!blockInput.hasComputedAllData()) {
946
+ // immediately attempt fetch of data columns from execution engine
947
+ chain.getBlobsTracker.triggerGetBlobs(blockInput);
948
+ // if we've received at least half of the columns, trigger reconstruction of the rest
949
+ if (blockInput.columnCount >= NUMBER_OF_COLUMNS / 2) {
950
+ chain.columnReconstructionTracker.triggerColumnReconstruction(blockInput);
951
+ }
952
+ }
914
953
 
915
- if (!blockInput.hasBlockAndAllData()) {
916
- const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
917
- chain.logger.debug("Received gossip data column, waiting for full data availability", {
918
- msToWait: cutoffTimeMs,
919
- dataColumnIndex: index,
920
- ...blockInputMeta,
921
- });
922
- // do not await here to not delay gossip validation
923
- blockInput.waitForBlockAndAllData(cutoffTimeMs).catch((_e) => {
924
- chain.logger.debug(
925
- "Waited for data after receiving gossip column. Cut-off reached so attempting to fetch remainder of BlockInput",
926
- {
954
+ if (!blockInput.hasBlockAndAllData()) {
955
+ const cutoffTimeMs = getCutoffTimeMs(chain, dataColumnSlot, BLOCK_AVAILABILITY_CUTOFF_MS);
956
+ chain.logger.debug("Received gossip data column, waiting for full data availability", {
957
+ msToWait: cutoffTimeMs,
927
958
  dataColumnIndex: index,
928
959
  ...blockInputMeta,
929
- }
960
+ });
961
+ // do not await here to not delay gossip validation
962
+ blockInput.waitForBlockAndAllData(cutoffTimeMs).catch((_e) => {
963
+ chain.logger.debug(
964
+ "Waited for data after receiving gossip column. Cut-off reached so attempting to fetch remainder of BlockInput",
965
+ {
966
+ dataColumnIndex: index,
967
+ ...blockInputMeta,
968
+ }
969
+ );
970
+ chain.emitter.emit(ChainEvent.incompleteBlockInput, {
971
+ blockInput,
972
+ peer: peerIdStr,
973
+ source: BlockInputSource.gossip,
974
+ });
975
+ });
976
+ }
977
+ } catch (e) {
978
+ logger.debug(
979
+ "Error handling gossip data column",
980
+ {slot: dataColumnSlot, root: blockInput.blockRootHex, index},
981
+ e as Error
930
982
  );
931
- chain.emitter.emit(ChainEvent.incompleteBlockInput, {
932
- blockInput,
933
- peer: peerIdStr,
934
- source: BlockInputSource.gossip,
935
- });
936
- });
937
- }
983
+ }
984
+ });
938
985
  }
939
986
  },
940
987
 
@@ -961,36 +1008,41 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
961
1008
  throw e;
962
1009
  }
963
1010
 
964
- // Handler
1011
+ // Handler - deferred to next event loop so the validation result propagates first
965
1012
  const {indexedAttestation, committeeValidatorIndices, attDataRootHex} = validationResult;
966
- chain.validatorMonitor?.registerGossipAggregatedAttestation(
967
- seenTimestampSec,
968
- signedAggregateAndProof,
969
- indexedAttestation
970
- );
971
- const aggregatedAttestation = signedAggregateAndProof.message.aggregate;
1013
+ callInNextEventLoop(() => {
1014
+ try {
1015
+ chain.validatorMonitor?.registerGossipAggregatedAttestation(
1016
+ seenTimestampSec,
1017
+ signedAggregateAndProof,
1018
+ indexedAttestation
1019
+ );
1020
+ const aggregatedAttestation = signedAggregateAndProof.message.aggregate;
972
1021
 
973
- const insertOutcome = chain.aggregatedAttestationPool.add(
974
- aggregatedAttestation,
975
- attDataRootHex,
976
- indexedAttestation.attestingIndices.length,
977
- committeeValidatorIndices
978
- );
979
- metrics?.opPool.aggregatedAttestationPool.gossipInsertOutcome.inc({insertOutcome});
1022
+ const insertOutcome = chain.aggregatedAttestationPool.add(
1023
+ aggregatedAttestation,
1024
+ attDataRootHex,
1025
+ indexedAttestation.attestingIndices.length,
1026
+ committeeValidatorIndices
1027
+ );
1028
+ metrics?.opPool.aggregatedAttestationPool.gossipInsertOutcome.inc({insertOutcome});
980
1029
 
981
- if (!options.dontSendGossipAttestationsToForkchoice) {
982
- try {
983
- chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
1030
+ if (!options.dontSendGossipAttestationsToForkchoice) {
1031
+ chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
1032
+ }
1033
+
1034
+ chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate);
984
1035
  } catch (e) {
985
1036
  logger.debug(
986
- "Error adding gossip aggregated attestation to forkchoice",
987
- {slot: aggregatedAttestation.data.slot},
1037
+ "Error handling gossip aggregate and proof",
1038
+ {
1039
+ slot: signedAggregateAndProof.message.aggregate.data.slot,
1040
+ root: toRootHex(signedAggregateAndProof.message.aggregate.data.beaconBlockRoot),
1041
+ },
988
1042
  e as Error
989
1043
  );
990
1044
  }
991
- }
992
-
993
- chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate);
1045
+ });
994
1046
  },
995
1047
 
996
1048
  [GossipType.attester_slashing]: async ({
@@ -1002,16 +1054,20 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1002
1054
  const attesterSlashing = sszDeserialize(topic, serializedData);
1003
1055
  await validateGossipAttesterSlashing(chain, attesterSlashing);
1004
1056
 
1005
- // Handler
1006
-
1007
- try {
1008
- chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
1009
- chain.forkChoice.onAttesterSlashing(attesterSlashing);
1010
- } catch (e) {
1011
- logger.error("Error adding attesterSlashing to pool", {}, e as Error);
1012
- }
1013
-
1014
- chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
1057
+ // Handler - deferred to next event loop so the validation result propagates first
1058
+ callInNextEventLoop(() => {
1059
+ try {
1060
+ chain.opPool.insertAttesterSlashing(fork, attesterSlashing);
1061
+ chain.forkChoice.onAttesterSlashing(attesterSlashing);
1062
+ chain.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing);
1063
+ } catch (e) {
1064
+ logger.debug(
1065
+ "Error handling gossip attester slashing",
1066
+ {slot: attesterSlashing.attestation1.data.slot},
1067
+ e as Error
1068
+ );
1069
+ }
1070
+ });
1015
1071
  },
1016
1072
 
1017
1073
  [GossipType.proposer_slashing]: async ({
@@ -1022,15 +1078,22 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1022
1078
  const proposerSlashing = sszDeserialize(topic, serializedData);
1023
1079
  await validateGossipProposerSlashing(chain, proposerSlashing);
1024
1080
 
1025
- // Handler
1026
-
1027
- try {
1028
- chain.opPool.insertProposerSlashing(proposerSlashing);
1029
- } catch (e) {
1030
- logger.error("Error adding attesterSlashing to pool", {}, e as Error);
1031
- }
1032
-
1033
- chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
1081
+ // Handler - deferred to next event loop so the validation result propagates first
1082
+ callInNextEventLoop(() => {
1083
+ try {
1084
+ chain.opPool.insertProposerSlashing(proposerSlashing);
1085
+ chain.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
1086
+ } catch (e) {
1087
+ logger.debug(
1088
+ "Error handling gossip proposer slashing",
1089
+ {
1090
+ slot: proposerSlashing.signedHeader1.message.slot,
1091
+ proposerIndex: proposerSlashing.signedHeader1.message.proposerIndex,
1092
+ },
1093
+ e as Error
1094
+ );
1095
+ }
1096
+ });
1034
1097
  },
1035
1098
 
1036
1099
  [GossipType.voluntary_exit]: async ({gossipData, topic}: GossipHandlerParamGeneric<GossipType.voluntary_exit>) => {
@@ -1038,15 +1101,19 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1038
1101
  const voluntaryExit = sszDeserialize(topic, serializedData);
1039
1102
  await validateGossipVoluntaryExit(chain, voluntaryExit);
1040
1103
 
1041
- // Handler
1042
-
1043
- try {
1044
- chain.opPool.insertVoluntaryExit(voluntaryExit);
1045
- } catch (e) {
1046
- logger.error("Error adding voluntaryExit to pool", {}, e as Error);
1047
- }
1048
-
1049
- chain.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit);
1104
+ // Handler - deferred to next event loop so the validation result propagates first
1105
+ callInNextEventLoop(() => {
1106
+ try {
1107
+ chain.opPool.insertVoluntaryExit(voluntaryExit);
1108
+ chain.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit);
1109
+ } catch (e) {
1110
+ logger.debug(
1111
+ "Error handling gossip voluntary exit",
1112
+ {epoch: voluntaryExit.message.epoch, validatorIndex: voluntaryExit.message.validatorIndex},
1113
+ e as Error
1114
+ );
1115
+ }
1116
+ });
1050
1117
  },
1051
1118
 
1052
1119
  [GossipType.sync_committee_contribution_and_proof]: async ({
@@ -1065,22 +1132,30 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1065
1132
  throw e;
1066
1133
  });
1067
1134
 
1068
- // Handler
1069
- chain.validatorMonitor?.registerGossipSyncContributionAndProof(
1070
- contributionAndProof.message,
1071
- syncCommitteeParticipantIndices
1072
- );
1073
- try {
1074
- const insertOutcome = chain.syncContributionAndProofPool.add(
1075
- contributionAndProof.message,
1076
- syncCommitteeParticipantIndices.length
1077
- );
1078
- metrics?.opPool.syncContributionAndProofPool.gossipInsertOutcome.inc({insertOutcome});
1079
- } catch (e) {
1080
- logger.error("Error adding to contributionAndProof pool", {}, e as Error);
1081
- }
1082
-
1083
- chain.emitter.emit(routes.events.EventType.contributionAndProof, contributionAndProof);
1135
+ // Handler - deferred to next event loop so the validation result propagates first
1136
+ callInNextEventLoop(() => {
1137
+ try {
1138
+ chain.validatorMonitor?.registerGossipSyncContributionAndProof(
1139
+ contributionAndProof.message,
1140
+ syncCommitteeParticipantIndices
1141
+ );
1142
+ const insertOutcome = chain.syncContributionAndProofPool.add(
1143
+ contributionAndProof.message,
1144
+ syncCommitteeParticipantIndices.length
1145
+ );
1146
+ metrics?.opPool.syncContributionAndProofPool.gossipInsertOutcome.inc({insertOutcome});
1147
+ chain.emitter.emit(routes.events.EventType.contributionAndProof, contributionAndProof);
1148
+ } catch (e) {
1149
+ logger.debug(
1150
+ "Error handling gossip contribution and proof",
1151
+ {
1152
+ slot: contributionAndProof.message.contribution.slot,
1153
+ subcommitteeIndex: contributionAndProof.message.contribution.subcommitteeIndex,
1154
+ },
1155
+ e as Error
1156
+ );
1157
+ }
1158
+ });
1084
1159
  },
1085
1160
 
1086
1161
  [GossipType.sync_committee]: async ({gossipData, topic}: GossipHandlerParamGeneric<GossipType.sync_committee>) => {
@@ -1097,15 +1172,22 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1097
1172
  throw e;
1098
1173
  }
1099
1174
 
1100
- // Handler — add for ALL positions this validator holds in the subcommittee
1101
- try {
1102
- for (const indexInSubcommittee of indicesInSubcommittee) {
1103
- const insertOutcome = chain.syncCommitteeMessagePool.add(subnet, syncCommittee, indexInSubcommittee);
1104
- metrics?.opPool.syncCommitteeMessagePoolInsertOutcome.inc({insertOutcome});
1175
+ // Handler - deferred to next event loop so the validation result propagates first
1176
+ // add for ALL positions this validator holds in the subcommittee
1177
+ callInNextEventLoop(() => {
1178
+ try {
1179
+ for (const indexInSubcommittee of indicesInSubcommittee) {
1180
+ const insertOutcome = chain.syncCommitteeMessagePool.add(subnet, syncCommittee, indexInSubcommittee);
1181
+ metrics?.opPool.syncCommitteeMessagePoolInsertOutcome.inc({insertOutcome});
1182
+ }
1183
+ } catch (e) {
1184
+ logger.debug(
1185
+ "Error handling gossip sync committee",
1186
+ {slot: syncCommittee.slot, subnet, validatorIndex: syncCommittee.validatorIndex},
1187
+ e as Error
1188
+ );
1105
1189
  }
1106
- } catch (e) {
1107
- logger.debug("Error adding to syncCommittee pool", {subnet}, e as Error);
1108
- }
1190
+ });
1109
1191
  },
1110
1192
 
1111
1193
  [GossipType.light_client_finality_update]: async ({
@@ -1135,14 +1217,19 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1135
1217
  const blsToExecutionChange = sszDeserialize(topic, serializedData);
1136
1218
  await validateGossipBlsToExecutionChange(chain, blsToExecutionChange);
1137
1219
 
1138
- // Handler
1139
- try {
1140
- chain.opPool.insertBlsToExecutionChange(blsToExecutionChange);
1141
- } catch (e) {
1142
- logger.error("Error adding blsToExecutionChange to pool", {}, e as Error);
1143
- }
1144
-
1145
- chain.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange);
1220
+ // Handler - deferred to next event loop so the validation result propagates first
1221
+ callInNextEventLoop(() => {
1222
+ try {
1223
+ chain.opPool.insertBlsToExecutionChange(blsToExecutionChange);
1224
+ chain.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange);
1225
+ } catch (e) {
1226
+ logger.debug(
1227
+ "Error handling gossip bls to execution change",
1228
+ {validatorIndex: blsToExecutionChange.message.validatorIndex},
1229
+ e as Error
1230
+ );
1231
+ }
1232
+ });
1146
1233
  },
1147
1234
  [GossipType.execution_payload]: async ({
1148
1235
  gossipData,
@@ -1203,57 +1290,64 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1203
1290
  });
1204
1291
  }
1205
1292
 
1206
- chain.serializedCache.set(signedEnvelope, serializedData);
1207
-
1208
- payloadInput.addPayloadEnvelope({
1209
- envelope: signedEnvelope,
1210
- source: PayloadEnvelopeInputSource.gossip,
1211
- seenTimestampSec,
1212
- peerIdStr,
1213
- });
1293
+ // Handler - deferred to next event loop so the validation result propagates first
1294
+ callInNextEventLoop(() => {
1295
+ try {
1296
+ chain.serializedCache.set(signedEnvelope, serializedData);
1214
1297
 
1215
- chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
1216
- slot,
1217
- builderIndex: envelope.builderIndex,
1218
- blockHash: toRootHex(envelope.payload.blockHash),
1219
- blockRoot: blockRootHex,
1220
- });
1298
+ payloadInput.addPayloadEnvelope({
1299
+ envelope: signedEnvelope,
1300
+ source: PayloadEnvelopeInputSource.gossip,
1301
+ seenTimestampSec,
1302
+ peerIdStr,
1303
+ });
1221
1304
 
1222
- chain.processExecutionPayload(payloadInput, {validSignature: true}).catch((e) => {
1223
- // Adjust verbosity based on error type
1224
- let logLevel: LogLevel;
1305
+ chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
1306
+ slot,
1307
+ builderIndex: envelope.builderIndex,
1308
+ blockHash: toRootHex(envelope.payload.blockHash),
1309
+ blockRoot: blockRootHex,
1310
+ });
1225
1311
 
1226
- if (e instanceof PayloadError) {
1227
- switch (e.type.code) {
1228
- // BLOCK_NOT_IN_FORK_CHOICE should not happen, validateGossipExecutionPayloadEnvelope above
1229
- // already verified the block is in fork choice
1230
- case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
1231
- case PayloadErrorCode.MISS_BLOCK_STATE:
1232
- case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
1233
- // Errors might indicate an issue with our node or the connected EL client
1312
+ chain.processExecutionPayload(payloadInput, {validSignature: true}).catch((e) => {
1313
+ // Adjust verbosity based on error type
1314
+ let logLevel: LogLevel;
1315
+
1316
+ if (e instanceof PayloadError) {
1317
+ switch (e.type.code) {
1318
+ // BLOCK_NOT_IN_FORK_CHOICE should not happen, validateGossipExecutionPayloadEnvelope above
1319
+ // already verified the block is in fork choice
1320
+ case PayloadErrorCode.BLOCK_NOT_IN_FORK_CHOICE:
1321
+ case PayloadErrorCode.MISS_BLOCK_STATE:
1322
+ case PayloadErrorCode.EXECUTION_ENGINE_ERROR:
1323
+ // Errors might indicate an issue with our node or the connected EL client
1324
+ logLevel = LogLevel.error;
1325
+ break;
1326
+ // INVALID_SIGNATURE should not happen, signature is verified during gossip validation
1327
+ case PayloadErrorCode.INVALID_SIGNATURE:
1328
+ case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1329
+ case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1330
+ core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
1331
+ // Misbehaving peer, but could highlight an issue in another client
1332
+ logLevel = LogLevel.warn;
1333
+ break;
1334
+ }
1335
+ } else {
1336
+ // Any unexpected error
1234
1337
  logLevel = LogLevel.error;
1235
- break;
1236
- // INVALID_SIGNATURE should not happen, signature is verified during gossip validation
1237
- case PayloadErrorCode.INVALID_SIGNATURE:
1238
- case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1239
- case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1240
- core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
1241
- // Misbehaving peer, but could highlight an issue in another client
1242
- logLevel = LogLevel.warn;
1243
- break;
1244
- }
1245
- } else {
1246
- // Any unexpected error
1247
- logLevel = LogLevel.error;
1338
+ }
1339
+ metrics?.gossipExecutionPayloadEnvelope.processPayloadErrors.inc({
1340
+ error: e instanceof PayloadError ? e.type.code : "NOT_PAYLOAD_ERROR",
1341
+ });
1342
+ chain.logger[logLevel](
1343
+ "Error processing execution payload from gossip",
1344
+ {slot, root: blockRootHex, peer: peerIdStr},
1345
+ e as Error
1346
+ );
1347
+ });
1348
+ } catch (e) {
1349
+ logger.debug("Error handling gossip execution payload", {slot, root: blockRootHex}, e as Error);
1248
1350
  }
1249
- metrics?.gossipExecutionPayloadEnvelope.processPayloadErrors.inc({
1250
- error: e instanceof PayloadError ? e.type.code : "NOT_PAYLOAD_ERROR",
1251
- });
1252
- chain.logger[logLevel](
1253
- "Error processing execution payload from gossip",
1254
- {slot, peer: peerIdStr, root: blockRootHex},
1255
- e as Error
1256
- );
1257
1351
  });
1258
1352
  },
1259
1353
  [GossipType.payload_attestation_message]: async ({
@@ -1265,30 +1359,49 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1265
1359
  const payloadAttestationMessage = sszDeserialize(topic, serializedData);
1266
1360
  const validationResult = await validateGossipPayloadAttestationMessage(chain, payloadAttestationMessage);
1267
1361
 
1268
- const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
1269
- metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
1362
+ // Handler - deferred to next event loop so the validation result propagates first
1363
+ callInNextEventLoop(() => {
1364
+ try {
1365
+ const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
1366
+ metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
1367
+ const nextSlotProposer = chain.getHeadState().getBeaconProposer(payloadAttestationMessage.data.slot + 1);
1368
+ const isNextSlotProposer = chain.beaconProposerCache.get(nextSlotProposer) !== undefined;
1369
+
1370
+ if (isNextSlotProposer) {
1371
+ try {
1372
+ const insertOutcome = chain.payloadAttestationPool.add(
1373
+ payloadAttestationMessage,
1374
+ validationResult.attDataRootHex,
1375
+ validationResult.validatorCommitteeIndices
1376
+ );
1377
+ metrics?.opPool.payloadAttestationPool.gossipInsertOutcome.inc({insertOutcome});
1378
+ } catch (e) {
1379
+ logger.debug("Error adding to payloadAttestation pool", {}, e as Error);
1380
+ }
1381
+ }
1270
1382
 
1271
- try {
1272
- const insertOutcome = chain.payloadAttestationPool.add(
1273
- payloadAttestationMessage,
1274
- validationResult.attDataRootHex,
1275
- validationResult.validatorCommitteeIndices
1276
- );
1277
- metrics?.opPool.payloadAttestationPool.gossipInsertOutcome.inc({insertOutcome});
1278
- } catch (e) {
1279
- logger.error("Error adding to payloadAttestation pool", {}, e as Error);
1280
- }
1281
- chain.forkChoice.notifyPtcMessages(
1282
- toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
1283
- payloadAttestationMessage.data.slot,
1284
- validationResult.validatorCommitteeIndices,
1285
- payloadAttestationMessage.data.payloadPresent,
1286
- payloadAttestationMessage.data.blobDataAvailable
1287
- );
1383
+ chain.forkChoice.notifyPtcMessages(
1384
+ toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
1385
+ payloadAttestationMessage.data.slot,
1386
+ validationResult.validatorCommitteeIndices,
1387
+ payloadAttestationMessage.data.payloadPresent,
1388
+ payloadAttestationMessage.data.blobDataAvailable
1389
+ );
1288
1390
 
1289
- chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
1290
- version: config.getForkName(payloadAttestationMessage.data.slot),
1291
- data: payloadAttestationMessage,
1391
+ chain.emitter.emit(routes.events.EventType.payloadAttestationMessage, {
1392
+ version: config.getForkName(payloadAttestationMessage.data.slot),
1393
+ data: payloadAttestationMessage,
1394
+ });
1395
+ } catch (e) {
1396
+ logger.debug(
1397
+ "Error handling gossip payload attestation message",
1398
+ {
1399
+ slot: payloadAttestationMessage.data.slot,
1400
+ root: toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
1401
+ },
1402
+ e as Error
1403
+ );
1404
+ }
1292
1405
  });
1293
1406
  },
1294
1407
  [GossipType.execution_payload_bid]: async ({
@@ -1300,23 +1413,38 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1300
1413
  const executionPayloadBid = sszDeserialize(topic, serializedData);
1301
1414
  const {proposerIndex} = await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
1302
1415
 
1303
- // this could be negative, because it's most likely the bid of next slot comes at this clock slot
1304
- const elapsedSec = chain.clock.secFromSlot(executionPayloadBid.message.slot, seenTimestampSec);
1305
- metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({source: OpSource.gossip}, elapsedSec);
1306
-
1307
- // Handle valid payload bid by storing in a bid pool
1308
- try {
1309
- const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
1310
- metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
1311
- } catch (e) {
1312
- logger.error("Error adding to executionPayloadBid pool", {}, e as Error);
1313
- }
1314
-
1315
- chain.validatorMonitor?.registerExecutionPayloadBid(OpSource.gossip, proposerIndex, executionPayloadBid.message);
1416
+ // Handler - deferred to next event loop so the validation result propagates first
1417
+ callInNextEventLoop(() => {
1418
+ try {
1419
+ // this could be negative, because it's most likely the bid of next slot comes at this clock slot
1420
+ const elapsedSec = chain.clock.secFromSlot(executionPayloadBid.message.slot, seenTimestampSec);
1421
+ metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({source: OpSource.gossip}, elapsedSec);
1422
+
1423
+ // Handle valid payload bid by storing in a bid pool
1424
+ const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid, Math.floor(elapsedSec * 1000));
1425
+ metrics?.opPool.executionPayloadBidPool.gossipInsertOutcome.inc({insertOutcome});
1426
+
1427
+ chain.validatorMonitor?.registerExecutionPayloadBid(
1428
+ OpSource.gossip,
1429
+ proposerIndex,
1430
+ executionPayloadBid.message
1431
+ );
1316
1432
 
1317
- chain.emitter.emit(routes.events.EventType.executionPayloadBid, {
1318
- version: config.getForkName(executionPayloadBid.message.slot),
1319
- data: executionPayloadBid,
1433
+ chain.emitter.emit(routes.events.EventType.executionPayloadBid, {
1434
+ version: config.getForkName(executionPayloadBid.message.slot),
1435
+ data: executionPayloadBid,
1436
+ });
1437
+ } catch (e) {
1438
+ logger.debug(
1439
+ "Error handling gossip execution payload bid",
1440
+ {
1441
+ slot: executionPayloadBid.message.slot,
1442
+ root: toRootHex(executionPayloadBid.message.parentBlockRoot),
1443
+ proposerIndex,
1444
+ },
1445
+ e as Error
1446
+ );
1447
+ }
1320
1448
  });
1321
1449
  },
1322
1450
  [GossipType.proposer_preferences]: async ({
@@ -1327,9 +1455,20 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1327
1455
  const signedProposerPreferences = sszDeserialize(topic, serializedData);
1328
1456
  await validateGossipProposerPreferences(chain, signedProposerPreferences);
1329
1457
 
1330
- chain.emitter.emit(routes.events.EventType.proposerPreferences, {
1331
- version: config.getForkName(signedProposerPreferences.message.proposalSlot),
1332
- data: signedProposerPreferences,
1458
+ // Handler - deferred to next event loop so the validation result propagates first
1459
+ callInNextEventLoop(() => {
1460
+ try {
1461
+ chain.emitter.emit(routes.events.EventType.proposerPreferences, {
1462
+ version: config.getForkName(signedProposerPreferences.message.proposalSlot),
1463
+ data: signedProposerPreferences,
1464
+ });
1465
+ } catch (e) {
1466
+ logger.debug(
1467
+ "Error handling gossip proposer preferences",
1468
+ {proposalSlot: signedProposerPreferences.message.proposalSlot},
1469
+ e as Error
1470
+ );
1471
+ }
1333
1472
  });
1334
1473
  },
1335
1474
  };
@@ -1363,69 +1502,9 @@ function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOp
1363
1502
  chain,
1364
1503
  validationParams
1365
1504
  );
1366
- for (const [i, validationResult] of validationResults.entries()) {
1367
- if (validationResult.err) {
1368
- results.push(validationResult.err as AttestationError);
1369
- continue;
1370
- }
1371
- // null means no error
1372
- results.push(null);
1373
-
1374
- // Handler
1375
- const {
1376
- indexedAttestation,
1377
- attDataRootHex,
1378
- attestation,
1379
- committeeIndex,
1380
- validatorCommitteeIndex,
1381
- committeeSize,
1382
- } = validationResult.result;
1383
- chain.validatorMonitor?.registerGossipUnaggregatedAttestation(
1384
- gossipHandlerParams[i].seenTimestampSec,
1385
- indexedAttestation
1386
- );
1387
-
1388
- const {subnet} = validationResult.result;
1389
- try {
1390
- // Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages
1391
- // but don't add to attestation pool, to save CPU and RAM
1392
- if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) {
1393
- const insertOutcome = chain.attestationPool.add(
1394
- committeeIndex,
1395
- attestation,
1396
- attDataRootHex,
1397
- validatorCommitteeIndex,
1398
- committeeSize
1399
- );
1400
- metrics?.opPool.attestationPool.gossipInsertOutcome.inc({insertOutcome});
1401
- }
1402
- } catch (e) {
1403
- logger.error("Error adding unaggregated attestation to pool", {subnet}, e as Error);
1404
- }
1405
-
1406
- if (!options.dontSendGossipAttestationsToForkchoice) {
1407
- try {
1408
- chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
1409
- } catch (e) {
1410
- logger.debug("Error adding gossip unaggregated attestation to forkchoice", {subnet}, e as Error);
1411
- }
1412
- }
1413
-
1414
- if (isForkPostElectra(fork)) {
1415
- chain.emitter.emit(
1416
- routes.events.EventType.singleAttestation,
1417
- attestation as SingleAttestation<ForkPostElectra>
1418
- );
1419
- } else {
1420
- chain.emitter.emit(routes.events.EventType.attestation, attestation as SingleAttestation<ForkPreElectra>);
1421
- chain.emitter.emit(
1422
- routes.events.EventType.singleAttestation,
1423
- toElectraSingleAttestation(
1424
- attestation as SingleAttestation<ForkPreElectra>,
1425
- indexedAttestation.attestingIndices[0]
1426
- )
1427
- );
1428
- }
1505
+ // verdicts computed synchronously and returned immediately
1506
+ for (const validationResult of validationResults) {
1507
+ results.push(validationResult.err ? (validationResult.err as AttestationError) : null);
1429
1508
  }
1430
1509
 
1431
1510
  if (batchableBls) {
@@ -1434,6 +1513,72 @@ function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOp
1434
1513
  metrics?.gossipAttestation.attestationNonBatchCount.inc(attestationCount);
1435
1514
  }
1436
1515
 
1516
+ // Handler - deferred to next event loop so the validation result propagates first.
1517
+ callInNextEventLoop(() => {
1518
+ for (const [i, validationResult] of validationResults.entries()) {
1519
+ if (validationResult.err) continue;
1520
+ const {
1521
+ indexedAttestation,
1522
+ attDataRootHex,
1523
+ attestation,
1524
+ committeeIndex,
1525
+ validatorCommitteeIndex,
1526
+ committeeSize,
1527
+ subnet,
1528
+ } = validationResult.result;
1529
+ try {
1530
+ chain.validatorMonitor?.registerGossipUnaggregatedAttestation(
1531
+ gossipHandlerParams[i].seenTimestampSec,
1532
+ indexedAttestation
1533
+ );
1534
+
1535
+ try {
1536
+ // Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages
1537
+ // but don't add to attestation pool, to save CPU and RAM
1538
+ if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) {
1539
+ const insertOutcome = chain.attestationPool.add(
1540
+ committeeIndex,
1541
+ attestation,
1542
+ attDataRootHex,
1543
+ validatorCommitteeIndex,
1544
+ committeeSize
1545
+ );
1546
+ metrics?.opPool.attestationPool.gossipInsertOutcome.inc({insertOutcome});
1547
+ }
1548
+ } catch (e) {
1549
+ logger.debug("Error adding gossip unaggregated attestation to pool", {subnet}, e as Error);
1550
+ }
1551
+
1552
+ // Separate boundary: a pool insertion error above must not skip the fork-choice vote
1553
+ if (!options.dontSendGossipAttestationsToForkchoice) {
1554
+ try {
1555
+ chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex);
1556
+ } catch (e) {
1557
+ logger.debug("Error adding gossip unaggregated attestation to forkchoice", {subnet}, e as Error);
1558
+ }
1559
+ }
1560
+
1561
+ if (isForkPostElectra(fork)) {
1562
+ chain.emitter.emit(
1563
+ routes.events.EventType.singleAttestation,
1564
+ attestation as SingleAttestation<ForkPostElectra>
1565
+ );
1566
+ } else {
1567
+ chain.emitter.emit(routes.events.EventType.attestation, attestation as SingleAttestation<ForkPreElectra>);
1568
+ chain.emitter.emit(
1569
+ routes.events.EventType.singleAttestation,
1570
+ toElectraSingleAttestation(
1571
+ attestation as SingleAttestation<ForkPreElectra>,
1572
+ indexedAttestation.attestingIndices[0]
1573
+ )
1574
+ );
1575
+ }
1576
+ } catch (e) {
1577
+ logger.debug("Error handling gossip attestation", {slot: indexedAttestation.data.slot, subnet}, e as Error);
1578
+ }
1579
+ }
1580
+ });
1581
+
1437
1582
  return results;
1438
1583
  },
1439
1584
  };