@lodestar/beacon-node 1.47.0-dev.d9c16ea16f → 1.47.0-dev.da8dfeabda

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 (258) 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 -7
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/pool/index.js +12 -1
  6. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  8. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  9. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  10. package/lib/api/impl/events/index.d.ts +1 -1
  11. package/lib/api/impl/events/index.d.ts.map +1 -1
  12. package/lib/api/impl/events/index.js +10 -3
  13. package/lib/api/impl/events/index.js.map +1 -1
  14. package/lib/api/impl/validator/index.d.ts.map +1 -1
  15. package/lib/api/impl/validator/index.js +12 -22
  16. package/lib/api/impl/validator/index.js.map +1 -1
  17. package/lib/api/impl/validator/utils.d.ts +5 -0
  18. package/lib/api/impl/validator/utils.d.ts.map +1 -1
  19. package/lib/api/impl/validator/utils.js +18 -12
  20. package/lib/api/impl/validator/utils.js.map +1 -1
  21. package/lib/chain/archiveStore/historicalState/metrics.d.ts.map +1 -1
  22. package/lib/chain/archiveStore/historicalState/metrics.js +21 -0
  23. package/lib/chain/archiveStore/historicalState/metrics.js.map +1 -1
  24. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  25. package/lib/chain/blocks/importBlock.js +2 -7
  26. package/lib/chain/blocks/importBlock.js.map +1 -1
  27. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  28. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  29. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  30. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  31. package/lib/chain/blocks/index.d.ts.map +1 -1
  32. package/lib/chain/blocks/index.js +15 -7
  33. package/lib/chain/blocks/index.js.map +1 -1
  34. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  35. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  36. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  37. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  38. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  39. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  40. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  41. package/lib/chain/bls/index.d.ts +1 -1
  42. package/lib/chain/bls/index.d.ts.map +1 -1
  43. package/lib/chain/bls/interface.d.ts +5 -5
  44. package/lib/chain/bls/interface.d.ts.map +1 -1
  45. package/lib/chain/bls/multithread/index.d.ts +5 -11
  46. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  47. package/lib/chain/bls/multithread/index.js +116 -101
  48. package/lib/chain/bls/multithread/index.js.map +1 -1
  49. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  50. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  51. package/lib/chain/bls/multithread/jobItem.js +10 -65
  52. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  53. package/lib/chain/bls/multithread/types.d.ts +29 -8
  54. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  55. package/lib/chain/bls/multithread/types.js +12 -0
  56. package/lib/chain/bls/multithread/types.js.map +1 -1
  57. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  58. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  59. package/lib/chain/bls/multithread/utils.js +10 -0
  60. package/lib/chain/bls/multithread/utils.js.map +1 -1
  61. package/lib/chain/bls/multithread/worker.js +49 -23
  62. package/lib/chain/bls/multithread/worker.js.map +1 -1
  63. package/lib/chain/bls/singleThread.d.ts +4 -10
  64. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  65. package/lib/chain/bls/singleThread.js +8 -45
  66. package/lib/chain/bls/singleThread.js.map +1 -1
  67. package/lib/chain/bls/utils.d.ts +4 -4
  68. package/lib/chain/bls/utils.d.ts.map +1 -1
  69. package/lib/chain/bls/utils.js +18 -19
  70. package/lib/chain/bls/utils.js.map +1 -1
  71. package/lib/chain/builderCircuitBreaker.d.ts +6 -5
  72. package/lib/chain/builderCircuitBreaker.d.ts.map +1 -1
  73. package/lib/chain/builderCircuitBreaker.js +28 -15
  74. package/lib/chain/builderCircuitBreaker.js.map +1 -1
  75. package/lib/chain/chain.d.ts +3 -2
  76. package/lib/chain/chain.d.ts.map +1 -1
  77. package/lib/chain/chain.js +8 -5
  78. package/lib/chain/chain.js.map +1 -1
  79. package/lib/chain/errors/blockError.d.ts +2 -0
  80. package/lib/chain/errors/blockError.d.ts.map +1 -1
  81. package/lib/chain/errors/blockError.js +6 -1
  82. package/lib/chain/errors/blockError.js.map +1 -1
  83. package/lib/chain/errors/proposerPreferences.d.ts +6 -0
  84. package/lib/chain/errors/proposerPreferences.d.ts.map +1 -1
  85. package/lib/chain/errors/proposerPreferences.js +1 -0
  86. package/lib/chain/errors/proposerPreferences.js.map +1 -1
  87. package/lib/chain/interface.d.ts +2 -1
  88. package/lib/chain/interface.d.ts.map +1 -1
  89. package/lib/chain/interface.js.map +1 -1
  90. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  91. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  92. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  93. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  94. package/lib/chain/opPools/attestationPool.js +1 -1
  95. package/lib/chain/opPools/attestationPool.js.map +1 -1
  96. package/lib/chain/opPools/payloadAttestationPool.js +3 -3
  97. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  98. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  99. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  100. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  101. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  102. package/lib/chain/opPools/utils.d.ts +1 -1
  103. package/lib/chain/opPools/utils.d.ts.map +1 -1
  104. package/lib/chain/opPools/utils.js +1 -1
  105. package/lib/chain/opPools/utils.js.map +1 -1
  106. package/lib/chain/options.d.ts +1 -1
  107. package/lib/chain/options.d.ts.map +1 -1
  108. package/lib/chain/prepareNextSlot.js +1 -1
  109. package/lib/chain/prepareNextSlot.js.map +1 -1
  110. package/lib/chain/produceBlock/produceBlockBody.d.ts +2 -0
  111. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  112. package/lib/chain/produceBlock/produceBlockBody.js +6 -1
  113. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  114. package/lib/chain/seenCache/seenAttesters.d.ts +9 -5
  115. package/lib/chain/seenCache/seenAttesters.d.ts.map +1 -1
  116. package/lib/chain/seenCache/seenAttesters.js +25 -4
  117. package/lib/chain/seenCache/seenAttesters.js.map +1 -1
  118. package/lib/chain/seenCache/seenBlockProposers.d.ts +5 -1
  119. package/lib/chain/seenCache/seenBlockProposers.d.ts.map +1 -1
  120. package/lib/chain/seenCache/seenBlockProposers.js +10 -3
  121. package/lib/chain/seenCache/seenBlockProposers.js.map +1 -1
  122. package/lib/chain/validation/attestation.d.ts.map +1 -1
  123. package/lib/chain/validation/attestation.js +1 -4
  124. package/lib/chain/validation/attestation.js.map +1 -1
  125. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  126. package/lib/chain/validation/attesterSlashing.js +1 -1
  127. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  128. package/lib/chain/validation/block.d.ts.map +1 -1
  129. package/lib/chain/validation/block.js +21 -7
  130. package/lib/chain/validation/block.js.map +1 -1
  131. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  132. package/lib/chain/validation/executionPayloadBid.js +1 -2
  133. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  134. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  135. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  136. package/lib/chain/validation/payloadAttestationMessage.d.ts.map +1 -1
  137. package/lib/chain/validation/payloadAttestationMessage.js +5 -14
  138. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  139. package/lib/chain/validation/proposerPreferences.d.ts +1 -1
  140. package/lib/chain/validation/proposerPreferences.d.ts.map +1 -1
  141. package/lib/chain/validation/proposerPreferences.js +13 -4
  142. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  143. package/lib/chain/validation/proposerSlashing.js +1 -1
  144. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  145. package/lib/execution/builder/http.d.ts +10 -7
  146. package/lib/execution/builder/http.d.ts.map +1 -1
  147. package/lib/execution/builder/http.js +11 -8
  148. package/lib/execution/builder/http.js.map +1 -1
  149. package/lib/metrics/metrics/beacon.d.ts +3 -3
  150. package/lib/metrics/metrics/beacon.d.ts.map +1 -1
  151. package/lib/metrics/metrics/beacon.js +9 -9
  152. package/lib/metrics/metrics/beacon.js.map +1 -1
  153. package/lib/metrics/metrics/lodestar.d.ts +48 -18
  154. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  155. package/lib/metrics/metrics/lodestar.js +96 -50
  156. package/lib/metrics/metrics/lodestar.js.map +1 -1
  157. package/lib/network/gossip/topic.d.ts +13 -0
  158. package/lib/network/gossip/topic.d.ts.map +1 -1
  159. package/lib/network/gossip/topic.js +37 -0
  160. package/lib/network/gossip/topic.js.map +1 -1
  161. package/lib/network/libp2p/index.d.ts.map +1 -1
  162. package/lib/network/libp2p/index.js +10 -2
  163. package/lib/network/libp2p/index.js.map +1 -1
  164. package/lib/network/network.d.ts.map +1 -1
  165. package/lib/network/network.js +22 -43
  166. package/lib/network/network.js.map +1 -1
  167. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  168. package/lib/network/processor/gossipHandlers.js +32 -5
  169. package/lib/network/processor/gossipHandlers.js.map +1 -1
  170. package/lib/network/reqresp/score.d.ts.map +1 -1
  171. package/lib/network/reqresp/score.js +5 -11
  172. package/lib/network/reqresp/score.js.map +1 -1
  173. package/lib/network/util.d.ts +0 -1
  174. package/lib/network/util.d.ts.map +1 -1
  175. package/lib/network/util.js +0 -4
  176. package/lib/network/util.js.map +1 -1
  177. package/lib/node/nodejs.d.ts +2 -1
  178. package/lib/node/nodejs.d.ts.map +1 -1
  179. package/lib/node/nodejs.js.map +1 -1
  180. package/lib/sync/range/batch.d.ts +23 -5
  181. package/lib/sync/range/batch.d.ts.map +1 -1
  182. package/lib/sync/range/batch.js +61 -22
  183. package/lib/sync/range/batch.js.map +1 -1
  184. package/lib/sync/range/chain.d.ts +2 -1
  185. package/lib/sync/range/chain.d.ts.map +1 -1
  186. package/lib/sync/range/chain.js +23 -10
  187. package/lib/sync/range/chain.js.map +1 -1
  188. package/lib/sync/types.d.ts.map +1 -1
  189. package/lib/sync/types.js.map +1 -1
  190. package/lib/sync/unknownBlock.d.ts +9 -0
  191. package/lib/sync/unknownBlock.d.ts.map +1 -1
  192. package/lib/sync/unknownBlock.js +110 -14
  193. package/lib/sync/unknownBlock.js.map +1 -1
  194. package/package.json +18 -18
  195. package/src/api/impl/beacon/blocks/index.ts +5 -7
  196. package/src/api/impl/beacon/pool/index.ts +12 -1
  197. package/src/api/impl/beacon/state/utils.ts +2 -1
  198. package/src/api/impl/events/index.ts +10 -4
  199. package/src/api/impl/validator/index.ts +22 -27
  200. package/src/api/impl/validator/utils.ts +30 -15
  201. package/src/chain/archiveStore/historicalState/metrics.ts +22 -0
  202. package/src/chain/blocks/importBlock.ts +1 -6
  203. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  204. package/src/chain/blocks/index.ts +19 -7
  205. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  206. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  207. package/src/chain/bls/index.ts +1 -1
  208. package/src/chain/bls/interface.ts +7 -3
  209. package/src/chain/bls/multithread/index.ts +148 -120
  210. package/src/chain/bls/multithread/jobItem.ts +12 -82
  211. package/src/chain/bls/multithread/types.ts +33 -8
  212. package/src/chain/bls/multithread/utils.ts +11 -0
  213. package/src/chain/bls/multithread/worker.ts +85 -27
  214. package/src/chain/bls/singleThread.ts +11 -55
  215. package/src/chain/bls/utils.ts +23 -24
  216. package/src/chain/builderCircuitBreaker.ts +31 -17
  217. package/src/chain/chain.ts +9 -5
  218. package/src/chain/errors/blockError.ts +9 -3
  219. package/src/chain/errors/proposerPreferences.ts +7 -0
  220. package/src/chain/interface.ts +2 -1
  221. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  222. package/src/chain/opPools/attestationPool.ts +1 -1
  223. package/src/chain/opPools/payloadAttestationPool.ts +3 -3
  224. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  225. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  226. package/src/chain/opPools/utils.ts +1 -1
  227. package/src/chain/options.ts +1 -1
  228. package/src/chain/prepareNextSlot.ts +1 -1
  229. package/src/chain/produceBlock/produceBlockBody.ts +13 -0
  230. package/src/chain/seenCache/seenAttesters.ts +31 -5
  231. package/src/chain/seenCache/seenBlockProposers.ts +13 -3
  232. package/src/chain/validation/attestation.ts +1 -7
  233. package/src/chain/validation/attesterSlashing.ts +1 -8
  234. package/src/chain/validation/block.ts +21 -7
  235. package/src/chain/validation/executionPayloadBid.ts +1 -2
  236. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  237. package/src/chain/validation/payloadAttestationMessage.ts +6 -17
  238. package/src/chain/validation/proposerPreferences.ts +15 -5
  239. package/src/chain/validation/proposerSlashing.ts +1 -1
  240. package/src/execution/builder/http.ts +11 -11
  241. package/src/metrics/metrics/beacon.ts +9 -9
  242. package/src/metrics/metrics/lodestar.ts +101 -51
  243. package/src/network/gossip/topic.ts +38 -0
  244. package/src/network/libp2p/index.ts +11 -2
  245. package/src/network/network.ts +27 -55
  246. package/src/network/processor/gossipHandlers.ts +35 -3
  247. package/src/network/reqresp/score.ts +5 -11
  248. package/src/network/util.ts +0 -5
  249. package/src/node/nodejs.ts +2 -1
  250. package/src/sync/range/batch.ts +83 -28
  251. package/src/sync/range/chain.ts +26 -11
  252. package/src/sync/types.ts +3 -1
  253. package/src/sync/unknownBlock.ts +126 -17
  254. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  255. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  256. package/lib/chain/bls/maybeBatch.js +0 -35
  257. package/lib/chain/bls/maybeBatch.js.map +0 -1
  258. package/src/chain/bls/maybeBatch.ts +0 -45
@@ -414,3 +414,41 @@ export const gossipTopicIgnoreDuplicatePublishError: Record<GossipType, boolean>
414
414
  [GossipType.execution_payload_bid]: true,
415
415
  [GossipType.proposer_preferences]: true,
416
416
  };
417
+
418
+ /**
419
+ * Whether a publish that reached zero subscribed peers is an acceptable outcome for a topic.
420
+ *
421
+ * Publishing to a topic with no subscribed peers throws `PublishError.NoPeersSubscribedToTopic`, which
422
+ * propagates to the caller and, for messages submitted through the beacon API, is surfaced to the
423
+ * validator client. That is the right default: a message that reached nobody is a failed broadcast, and a
424
+ * VC configured with several beacon nodes can fall back to another one.
425
+ *
426
+ * Only topics that have a concrete reason to tolerate it opt out here, ie. are set to `true`, and the
427
+ * reason is named on the entry. Everything else is `false` and keeps raising. Note this is applied per
428
+ * publish, a topic set to `false` still honors the global `--network.allowPublishToZeroPeers` flag.
429
+ */
430
+ export const gossipTopicAllowPublishToZeroPeers: Record<GossipType, boolean> = {
431
+ [GossipType.beacon_block]: false,
432
+ [GossipType.blob_sidecar]: false,
433
+ // data_column_sidecar: we ensure having all topic peers via prioritizePeers(). Even with 0 peers on the
434
+ // topic the overall publish can still succeed, because a supernode rebuilds and publishes the missing
435
+ // columns for us, so track sent peers as 0 instead of raising
436
+ [GossipType.data_column_sidecar]: true,
437
+ [GossipType.beacon_aggregate_and_proof]: false,
438
+ [GossipType.beacon_attestation]: false,
439
+ [GossipType.voluntary_exit]: false,
440
+ [GossipType.proposer_slashing]: false,
441
+ [GossipType.attester_slashing]: false,
442
+ [GossipType.sync_committee_contribution_and_proof]: false,
443
+ [GossipType.sync_committee]: false,
444
+ // light_client_finality_update and light_client_optimistic_update: non-mandatory route on most of the
445
+ // network as of Oct 2022, there may be no peer subscribed to the topic at all. Reason carried over from
446
+ // the handlers in network.ts, which used to swallow the error instead
447
+ [GossipType.light_client_finality_update]: true,
448
+ [GossipType.light_client_optimistic_update]: true,
449
+ [GossipType.bls_to_execution_change]: false,
450
+ [GossipType.execution_payload]: false,
451
+ [GossipType.payload_attestation_message]: false,
452
+ [GossipType.execution_payload_bid]: false,
453
+ [GossipType.proposer_preferences]: false,
454
+ };
@@ -1,3 +1,4 @@
1
+ import {getCiphers} from "node:crypto";
1
2
  import {bootstrap} from "@libp2p/bootstrap";
2
3
  import {identify} from "@libp2p/identify";
3
4
  import type {PrivateKey} from "@libp2p/interface";
@@ -9,12 +10,20 @@ import {Libp2pInit, createLibp2p} from "libp2p";
9
10
  import {Registry} from "prom-client";
10
11
  import {ENR} from "@chainsafe/enr";
11
12
  import {noise} from "@chainsafe/libp2p-noise";
12
- import {defaultCrypto} from "@chainsafe/libp2p-noise/crypto";
13
+ import {asCrypto, defaultCrypto} from "@chainsafe/libp2p-noise/crypto";
13
14
  import {quic} from "@chainsafe/libp2p-quic";
14
15
  import {Libp2p, LodestarComponents} from "../interface.js";
15
16
  import {NetworkOptions, defaultNetworkOptions} from "../options.js";
16
17
  import {Eth2PeerDataStore} from "../peers/datastore.js";
17
18
 
19
+ const noiseCrypto = getCiphers().includes("chacha20-poly1305")
20
+ ? defaultCrypto
21
+ : {
22
+ ...defaultCrypto,
23
+ chaCha20Poly1305Encrypt: asCrypto.chaCha20Poly1305Encrypt,
24
+ chaCha20Poly1305Decrypt: asCrypto.chaCha20Poly1305Decrypt,
25
+ };
26
+
18
27
  export type NodeJsLibp2pOpts = {
19
28
  peerStoreDir?: string;
20
29
  disablePeerDiscovery?: boolean;
@@ -122,7 +131,7 @@ export async function createNodeJsLibp2p(
122
131
  listen: localMultiaddrs,
123
132
  announce: [],
124
133
  },
125
- connectionEncrypters: [noise({crypto: defaultCrypto})],
134
+ connectionEncrypters: [noise({crypto: noiseCrypto})],
126
135
  transports,
127
136
  streamMuxers: [mplex({disconnectThreshold})],
128
137
  peerDiscovery,
@@ -49,7 +49,12 @@ import {INetworkCore, NetworkCore, WorkerNetworkCore} from "./core/index.js";
49
49
  import {INetworkEventBus, NetworkEvent, NetworkEventBus, NetworkEventData} from "./events.js";
50
50
  import {getActiveForkBoundaries} from "./forks.js";
51
51
  import {GossipHandlers, GossipTopicMap, GossipType, GossipTypeMap} from "./gossip/index.js";
52
- import {getGossipSSZType, gossipTopicIgnoreDuplicatePublishError, stringifyGossipTopic} from "./gossip/topic.js";
52
+ import {
53
+ getGossipSSZType,
54
+ gossipTopicAllowPublishToZeroPeers,
55
+ gossipTopicIgnoreDuplicatePublishError,
56
+ stringifyGossipTopic,
57
+ } from "./gossip/topic.js";
53
58
  import {INetwork} from "./interface.js";
54
59
  import {NetworkOptions} from "./options.js";
55
60
  import {PeerAction, PeerScoreStats} from "./peers/index.js";
@@ -65,7 +70,7 @@ import {
65
70
  } from "./reqresp/utils/collect.js";
66
71
  import {collectSequentialBlocksInRange} from "./reqresp/utils/collectSequentialBlocksInRange.js";
67
72
  import {CommitteeSubscription} from "./subnets/index.js";
68
- import {isPublishDuplicateError, isPublishToZeroPeersError, prettyPrintPeerIdStr} from "./util.js";
73
+ import {isPublishDuplicateError, prettyPrintPeerIdStr} from "./util.js";
69
74
 
70
75
  type NetworkModules = {
71
76
  opts: NetworkOptions;
@@ -352,9 +357,7 @@ export class Network implements INetwork {
352
357
  const epoch = computeEpochAtSlot(signedBlock.message.slot);
353
358
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
354
359
 
355
- return this.publishGossip<GossipType.beacon_block>({type: GossipType.beacon_block, boundary}, signedBlock, {
356
- ignoreDuplicatePublishError: true,
357
- });
360
+ return this.publishGossip<GossipType.beacon_block>({type: GossipType.beacon_block, boundary}, signedBlock);
358
361
  }
359
362
 
360
363
  async publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise<number> {
@@ -363,9 +366,7 @@ export class Network implements INetwork {
363
366
 
364
367
  const subnet = blobSidecar.index;
365
368
 
366
- return this.publishGossip<GossipType.blob_sidecar>({type: GossipType.blob_sidecar, boundary, subnet}, blobSidecar, {
367
- ignoreDuplicatePublishError: true,
368
- });
369
+ return this.publishGossip<GossipType.blob_sidecar>({type: GossipType.blob_sidecar, boundary, subnet}, blobSidecar);
369
370
  }
370
371
 
371
372
  async publishDataColumnSidecar(
@@ -381,14 +382,7 @@ export class Network implements INetwork {
381
382
  try {
382
383
  const sentPeers = await this.publishGossip<GossipType.data_column_sidecar>(
383
384
  {type: GossipType.data_column_sidecar, boundary, subnet},
384
- dataColumnSidecar,
385
- {
386
- // we ensure having all topic peers via prioritizePeers() function
387
- // in the worse case, if there is 0 peer on the topic, the overall publish operation could be still a success
388
- // because supernode will rebuild and publish missing data column sidecars for us
389
- // hence we want to track sent peers as 0 instead of an error
390
- allowPublishToZeroTopicPeers: true,
391
- }
385
+ dataColumnSidecar
392
386
  );
393
387
  return {sentPeers, alreadyPublished: false};
394
388
  } catch (e) {
@@ -405,8 +399,7 @@ export class Network implements INetwork {
405
399
 
406
400
  return this.publishGossip<GossipType.beacon_aggregate_and_proof>(
407
401
  {type: GossipType.beacon_aggregate_and_proof, boundary},
408
- aggregateAndProof,
409
- {ignoreDuplicatePublishError: true}
402
+ aggregateAndProof
410
403
  );
411
404
  }
412
405
 
@@ -416,8 +409,7 @@ export class Network implements INetwork {
416
409
 
417
410
  return this.publishGossip<GossipType.beacon_attestation>(
418
411
  {type: GossipType.beacon_attestation, boundary, subnet},
419
- attestation,
420
- {ignoreDuplicatePublishError: true}
412
+ attestation
421
413
  );
422
414
  }
423
415
 
@@ -425,9 +417,7 @@ export class Network implements INetwork {
425
417
  const epoch = voluntaryExit.message.epoch;
426
418
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
427
419
 
428
- return this.publishGossip<GossipType.voluntary_exit>({type: GossipType.voluntary_exit, boundary}, voluntaryExit, {
429
- ignoreDuplicatePublishError: true,
430
- });
420
+ return this.publishGossip<GossipType.voluntary_exit>({type: GossipType.voluntary_exit, boundary}, voluntaryExit);
431
421
  }
432
422
 
433
423
  async publishBlsToExecutionChange(blsToExecutionChange: capella.SignedBLSToExecutionChange): Promise<number> {
@@ -438,8 +428,7 @@ export class Network implements INetwork {
438
428
  if (fork >= ForkSeq.capella) {
439
429
  const publishPromise = this.publishGossip<GossipType.bls_to_execution_change>(
440
430
  {type: GossipType.bls_to_execution_change, boundary},
441
- blsToExecutionChange,
442
- {ignoreDuplicatePublishError: true}
431
+ blsToExecutionChange
443
432
  );
444
433
  publishChanges.push(publishPromise);
445
434
  }
@@ -457,8 +446,7 @@ export class Network implements INetwork {
457
446
 
458
447
  return this.publishGossip<GossipType.proposer_slashing>(
459
448
  {type: GossipType.proposer_slashing, boundary},
460
- proposerSlashing,
461
- {ignoreDuplicatePublishError: true}
449
+ proposerSlashing
462
450
  );
463
451
  }
464
452
 
@@ -478,10 +466,7 @@ export class Network implements INetwork {
478
466
 
479
467
  return this.publishGossip<GossipType.sync_committee>(
480
468
  {type: GossipType.sync_committee, boundary, subnet},
481
- signature,
482
- {
483
- ignoreDuplicatePublishError: true,
484
- }
469
+ signature
485
470
  );
486
471
  }
487
472
 
@@ -491,8 +476,7 @@ export class Network implements INetwork {
491
476
 
492
477
  return this.publishGossip<GossipType.sync_committee_contribution_and_proof>(
493
478
  {type: GossipType.sync_committee_contribution_and_proof, boundary},
494
- contributionAndProof,
495
- {ignoreDuplicatePublishError: true}
479
+ contributionAndProof
496
480
  );
497
481
  }
498
482
 
@@ -522,8 +506,7 @@ export class Network implements INetwork {
522
506
 
523
507
  return this.publishGossip<GossipType.execution_payload>(
524
508
  {type: GossipType.execution_payload, boundary},
525
- signedEnvelope,
526
- {ignoreDuplicatePublishError: true}
509
+ signedEnvelope
527
510
  );
528
511
  }
529
512
 
@@ -533,8 +516,7 @@ export class Network implements INetwork {
533
516
 
534
517
  return this.publishGossip<GossipType.execution_payload_bid>(
535
518
  {type: GossipType.execution_payload_bid, boundary},
536
- signedBid,
537
- {ignoreDuplicatePublishError: true}
519
+ signedBid
538
520
  );
539
521
  }
540
522
 
@@ -544,8 +526,7 @@ export class Network implements INetwork {
544
526
 
545
527
  return this.publishGossip<GossipType.payload_attestation_message>(
546
528
  {type: GossipType.payload_attestation_message, boundary},
547
- payloadAttestationMessage,
548
- {ignoreDuplicatePublishError: true}
529
+ payloadAttestationMessage
549
530
  );
550
531
  }
551
532
 
@@ -555,22 +536,21 @@ export class Network implements INetwork {
555
536
 
556
537
  return this.publishGossip<GossipType.proposer_preferences>(
557
538
  {type: GossipType.proposer_preferences, boundary},
558
- signedProposerPreferences,
559
- {ignoreDuplicatePublishError: true}
539
+ signedProposerPreferences
560
540
  );
561
541
  }
562
542
 
563
543
  private async publishGossip<K extends GossipType>(
564
544
  topic: GossipTopicMap[K],
565
- object: GossipTypeMap[K],
566
- opts?: PublishOpts | undefined
545
+ object: GossipTypeMap[K]
567
546
  ): Promise<number> {
568
547
  const topicStr = stringifyGossipTopic(this.config, topic);
569
548
  const sszType = getGossipSSZType(topic);
570
549
  const messageData = (sszType.serialize as (object: GossipTypeMap[GossipType]) => Uint8Array)(object);
571
- opts = {
572
- ...opts,
550
+ const opts: PublishOpts = {
573
551
  ignoreDuplicatePublishError: gossipTopicIgnoreDuplicatePublishError[topic.type],
552
+ // Leave undefined unless the topic opts out, so `--network.allowPublishToZeroPeers` still applies
553
+ allowPublishToZeroTopicPeers: gossipTopicAllowPublishToZeroPeers[topic.type] ? true : undefined,
574
554
  };
575
555
  const sentPeers = await this.core.publishGossip(topicStr, messageData, opts);
576
556
 
@@ -813,11 +793,7 @@ export class Network implements INetwork {
813
793
  await this.waitForSyncMessageCutoff(finalityUpdate.signatureSlot);
814
794
  await this.publishLightClientFinalityUpdate(finalityUpdate);
815
795
  } catch (e) {
816
- // Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
817
- // Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
818
- if (!isPublishToZeroPeersError(e as Error)) {
819
- this.logger.debug("Error on BeaconGossipHandler.onLightclientFinalityUpdate", {}, e as Error);
820
- }
796
+ this.logger.debug("Error on BeaconGossipHandler.onLightclientFinalityUpdate", {}, e as Error);
821
797
  }
822
798
  };
823
799
 
@@ -830,11 +806,7 @@ export class Network implements INetwork {
830
806
  await this.waitForSyncMessageCutoff(optimisticUpdate.signatureSlot);
831
807
  await this.publishLightClientOptimisticUpdate(optimisticUpdate);
832
808
  } catch (e) {
833
- // Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
834
- // Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
835
- if (!isPublishToZeroPeersError(e as Error)) {
836
- this.logger.debug("Error on BeaconGossipHandler.onLightclientOptimisticUpdate", {}, e as Error);
837
- }
809
+ this.logger.debug("Error on BeaconGossipHandler.onLightclientOptimisticUpdate", {}, e as Error);
838
810
  }
839
811
  };
840
812
 
@@ -713,9 +713,32 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
713
713
  const {serializedData} = gossipData;
714
714
 
715
715
  const signedBlock = sszDeserialize(topic, serializedData);
716
- const blockInput = await validateBeaconBlock(signedBlock, topic.boundary.fork, peerIdStr, seenTimestampSec);
717
- chain.serializedCache.set(signedBlock, serializedData);
718
- handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
716
+ try {
717
+ const blockInput = await validateBeaconBlock(signedBlock, topic.boundary.fork, peerIdStr, seenTimestampSec);
718
+ chain.serializedCache.set(signedBlock, serializedData);
719
+ handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
720
+ } catch (e) {
721
+ // Spec: IGNORE the block, ie not to re-publish to peers
722
+ // but we should still import an equivocating (REPEAT_PROPOSAL) block into fork choice because we don't
723
+ // know if this block (or the 1st known block with same slot) will become canonical yet
724
+ if (
725
+ e instanceof BlockGossipError &&
726
+ e.type.code === BlockErrorCode.REPEAT_PROPOSAL &&
727
+ // this is make sure the block's proposer signature was verified, it should be true anyway
728
+ chain.seenBlockProposers.hasBlockRoot(signedBlock.message.slot, e.type.proposerIndex, e.type.root)
729
+ ) {
730
+ // blockInput was optimistically seeded in validateBeaconBlock and retained on IGNORE
731
+ const blockInput = chain.seenBlockInputCache.get(e.type.root);
732
+ if (blockInput) {
733
+ chain.serializedCache.set(signedBlock, serializedData);
734
+ // this is technically not a valid gossip block but gossip validation is a cheap subset of checks
735
+ // this runs the full state transition, so importing an equivocating-but-valid block here is safe.
736
+ handleValidBeaconBlock(blockInput, peerIdStr, seenTimestampSec);
737
+ }
738
+ }
739
+ // rethrow so gossipValidatorFn maps IGNORE -> TopicValidatorResult.Ignore (message not forwarded)
740
+ throw e;
741
+ }
719
742
  },
720
743
 
721
744
  [GossipType.blob_sidecar]: async ({
@@ -1232,11 +1255,15 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1232
1255
  [GossipType.payload_attestation_message]: async ({
1233
1256
  gossipData,
1234
1257
  topic,
1258
+ seenTimestampSec,
1235
1259
  }: GossipHandlerParamGeneric<GossipType.payload_attestation_message>) => {
1236
1260
  const {serializedData} = gossipData;
1237
1261
  const payloadAttestationMessage = sszDeserialize(topic, serializedData);
1238
1262
  const validationResult = await validateGossipPayloadAttestationMessage(chain, payloadAttestationMessage);
1239
1263
 
1264
+ const delaySec = chain.clock.secFromSlot(payloadAttestationMessage.data.slot, seenTimestampSec);
1265
+ metrics?.gossipPayloadAttestationMessage.elapsedTimeTillReceived.observe({source: OpSource.gossip}, delaySec);
1266
+
1240
1267
  try {
1241
1268
  const insertOutcome = chain.payloadAttestationPool.add(
1242
1269
  payloadAttestationMessage,
@@ -1263,11 +1290,16 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1263
1290
  [GossipType.execution_payload_bid]: async ({
1264
1291
  gossipData,
1265
1292
  topic,
1293
+ seenTimestampSec,
1266
1294
  }: GossipHandlerParamGeneric<GossipType.execution_payload_bid>) => {
1267
1295
  const {serializedData} = gossipData;
1268
1296
  const executionPayloadBid = sszDeserialize(topic, serializedData);
1269
1297
  const {proposerIndex} = await validateGossipExecutionPayloadBid(chain, executionPayloadBid);
1270
1298
 
1299
+ // this could be negative, because it's most likely the bid of next slot comes at this clock slot
1300
+ const elapsedSec = chain.clock.secFromSlot(executionPayloadBid.message.slot, seenTimestampSec);
1301
+ metrics?.gossipExecutionPayloadBid.elapsedTimeTillReceived.observe({source: OpSource.gossip}, elapsedSec);
1302
+
1271
1303
  // Handle valid payload bid by storing in a bid pool
1272
1304
  try {
1273
1305
  const insertOutcome = chain.executionPayloadBidPool.add(executionPayloadBid);
@@ -44,17 +44,11 @@ export function onOutgoingReqRespError(e: RequestError, method: ReqRespMethod):
44
44
  // failure, so keep the stronger penalty (Fatal for Ping, as before).
45
45
  return method === ReqRespMethod.Ping ? PeerAction.Fatal : PeerAction.LowToleranceError;
46
46
  }
47
- switch (method) {
48
- // Ping and Status are liveness probes; their dial timeouts are dominated by transient
49
- // network congestion rather than peer misbehavior, so penalize leniently to avoid
50
- // self-inflicted peer starvation (https://github.com/ChainSafe/lodestar/issues/9562),
51
- // while still applying some penalty so genuinely dead peers eventually free the slot.
52
- case ReqRespMethod.Ping:
53
- case ReqRespMethod.Status:
54
- return PeerAction.HighToleranceError;
55
- default:
56
- return PeerAction.LowToleranceError;
57
- }
47
+ // A dial failure means we could not open a stream. That is evidence about the transport, not
48
+ // about the peer's behavior, and it is dominated by transient congestion on our side, so
49
+ // penalize leniently for every method to avoid self-inflicted peer starvation (#9562). A
50
+ // genuinely dead peer still accumulates penalty and is eventually evicted, freeing the slot.
51
+ return PeerAction.HighToleranceError;
58
52
  // TODO: Detect SSZDecodeError and return PeerAction.Fatal
59
53
 
60
54
  case RequestErrorCode.RESP_TIMEOUT:
@@ -23,11 +23,6 @@ export function getConnection(libp2p: Libp2p, peerIdStr: string): Connection | u
23
23
  return getConnectionsMap(libp2p).get(peerIdStr)?.value[0] ?? undefined;
24
24
  }
25
25
 
26
- // https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
27
- export function isPublishToZeroPeersError(e: Error): boolean {
28
- return e.message.includes("PublishError.NoPeersSubscribedToTopic");
29
- }
30
-
31
26
  // https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
32
27
  export function isPublishDuplicateError(e: Error): boolean {
33
28
  return e.message.includes("PublishError.Duplicate");
@@ -1,12 +1,13 @@
1
1
  import {setMaxListeners} from "node:events";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Registry} from "prom-client";
4
+ import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
4
5
  import {hasher} from "@chainsafe/persistent-merkle-tree";
5
6
  import {BeaconApiMethods} from "@lodestar/api/beacon/server";
6
7
  import {BeaconConfig} from "@lodestar/config";
7
8
  import type {LoggerNode} from "@lodestar/logger/node";
8
9
  import {ZERO_HASH_HEX} from "@lodestar/params";
9
- import {IBeaconStateView, PubkeyCache, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
10
+ import {IBeaconStateView, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
10
11
  import {phase0} from "@lodestar/types";
11
12
  import {sleep, toRootHex} from "@lodestar/utils";
12
13
  import {ProcessShutdownCallback} from "@lodestar/validator";
@@ -39,6 +39,7 @@ export enum BatchStatus {
39
39
  AwaitingValidation = "AwaitingValidation",
40
40
  }
41
41
 
42
+ /** Identity of one download attempt*/
42
43
  export type Attempt = {
43
44
  /** The peer that made the attempt */
44
45
  peers: PeerIdStr[];
@@ -46,6 +47,16 @@ export type Attempt = {
46
47
  hash: RootHex;
47
48
  };
48
49
 
50
+ /**
51
+ * A failed attempt that has been classified for blame.
52
+ */
53
+ export type FailedAttempt = Attempt & {
54
+ /**
55
+ * True when the failure is evidence the peers served bad data, so they may be downscored..
56
+ */
57
+ peerAttributable: boolean;
58
+ };
59
+
49
60
  type TrackedRequest = {
50
61
  /** only happen for the 1st batch in checkpoint sync */
51
62
  parentPayload: boolean;
@@ -108,10 +119,12 @@ export type BatchMetadata = {
108
119
  // Retry counters
109
120
  downloadAttempts: number;
110
121
  processingAttempts: number;
122
+ executionErrorAttempts: number;
111
123
 
112
124
  // Cumulative peer attribution for failed attempts (only present when non-empty)
113
125
  failedDownloadPeers?: string;
114
126
  failedProcessingPeers?: string;
127
+ executionErrorPeers?: string;
115
128
  };
116
129
 
117
130
  function formatRangeReq(req: {startSlot: Slot; count: number}): string {
@@ -152,10 +165,10 @@ export class Batch {
152
165
  state: BatchState = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
153
166
  /** Peers that provided good data, with column coverage for by_range requests */
154
167
  private readonly successfulDownloads = new Map<PeerIdStr, TrackedRequest>();
155
- /** The `Attempts` that have been made and failed to send us this batch. */
156
- readonly failedProcessingAttempts: Attempt[] = [];
157
- /** The `Attempts` that have been made and failed because of execution malfunction. */
158
- readonly executionErrorAttempts: Attempt[] = [];
168
+ /** The attempts that have been made and failed to send us this batch. */
169
+ readonly failedProcessingAttempts: FailedAttempt[] = [];
170
+ /** The attempts that have been made and failed because of execution malfunction. */
171
+ readonly executionErrorAttempts: FailedAttempt[] = [];
159
172
  /** The number of download retries this batch has undergone due to a failed request. */
160
173
  private readonly failedDownloadAttempts: PeerIdStr[] = [];
161
174
  private readonly config: ChainForkConfig;
@@ -419,10 +432,18 @@ export class Batch {
419
432
  }
420
433
 
421
434
  /**
422
- * Gives a list of peers from which this batch has had a failed download or processing attempt.
435
+ * Gives a list of peers from which this batch has had a failed download attempt, or a
436
+ * peer-attributable failed processing/execution attempt.
423
437
  */
424
438
  getFailedPeers(): PeerIdStr[] {
425
- return [...this.failedDownloadAttempts, ...this.failedProcessingAttempts.flatMap((a) => a.peers)];
439
+ return [...this.failedDownloadAttempts, ...this.getPeerAttributableAttempts().flatMap((a) => a.peers)];
440
+ }
441
+
442
+ /**
443
+ * Attempts whose failure is peer-attributable, across both the processing and execution error
444
+ */
445
+ getPeerAttributableAttempts(): FailedAttempt[] {
446
+ return [...this.failedProcessingAttempts, ...this.executionErrorAttempts].filter((a) => a.peerAttributable);
426
447
  }
427
448
 
428
449
  /**
@@ -457,6 +478,7 @@ export class Batch {
457
478
  getMetadata(): BatchMetadata {
458
479
  const {blocksRequest, blobsRequest, columnsRequest, envelopesRequest} = this.requests;
459
480
  const failedProcessingPeerList = this.failedProcessingAttempts.flatMap((a) => a.peers);
481
+ const executionErrorPeerList = this.executionErrorAttempts.flatMap((a) => a.peers);
460
482
  return {
461
483
  startEpoch: this.startEpoch,
462
484
  startSlot: this.startSlot,
@@ -468,12 +490,16 @@ export class Batch {
468
490
  ...(envelopesRequest && {envelopesReq: formatRangeReq(envelopesRequest)}),
469
491
  downloadAttempts: this.failedDownloadAttempts.length,
470
492
  processingAttempts: this.failedProcessingAttempts.length,
493
+ executionErrorAttempts: this.executionErrorAttempts.length,
471
494
  ...(this.failedDownloadAttempts.length > 0 && {
472
495
  failedDownloadPeers: this.failedDownloadAttempts.join(","),
473
496
  }),
474
497
  ...(failedProcessingPeerList.length > 0 && {
475
498
  failedProcessingPeers: failedProcessingPeerList.join(","),
476
499
  }),
500
+ ...(executionErrorPeerList.length > 0 && {
501
+ executionErrorPeers: executionErrorPeerList.join(","),
502
+ }),
477
503
  };
478
504
  }
479
505
 
@@ -703,11 +729,7 @@ export class Batch {
703
729
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.Processing));
704
730
  }
705
731
 
706
- if (isExecutionEngineError(err)) {
707
- this.onExecutionEngineError(this.state.attempt);
708
- } else {
709
- this.onProcessingError(this.state.attempt);
710
- }
732
+ this.routeProcessingFailure(err, this.state.attempt);
711
733
  }
712
734
 
713
735
  /**
@@ -718,10 +740,17 @@ export class Batch {
718
740
  throw new BatchError(this.wrongStatusErrorType(BatchStatus.AwaitingValidation));
719
741
  }
720
742
 
721
- if (isExecutionEngineError(err)) {
722
- this.onExecutionEngineError(this.state.attempt);
743
+ this.routeProcessingFailure(err, this.state.attempt);
744
+ }
745
+
746
+ private routeProcessingFailure(err: Error, attempt: Attempt): void {
747
+ const code = err instanceof BlockError || err instanceof PayloadError ? err.type.code : null;
748
+ const failedAttempt: FailedAttempt = {...attempt, peerAttributable: isPeerAttributableFailure(code)};
749
+
750
+ if (isExecutionEngineFailure(code)) {
751
+ this.onExecutionEngineError(failedAttempt);
723
752
  } else {
724
- this.onProcessingError(this.state.attempt);
753
+ this.onProcessingError(failedAttempt);
725
754
  }
726
755
  }
727
756
 
@@ -735,7 +764,7 @@ export class Batch {
735
764
  return this.state.attempt;
736
765
  }
737
766
 
738
- private onExecutionEngineError(attempt: Attempt): void {
767
+ private onExecutionEngineError(attempt: FailedAttempt): void {
739
768
  this.executionErrorAttempts.push(attempt);
740
769
  if (this.executionErrorAttempts.length > MAX_BATCH_PROCESSING_ATTEMPTS) {
741
770
  throw new BatchError(this.errorType({code: BatchErrorCode.MAX_EXECUTION_ENGINE_ERROR_ATTEMPTS}));
@@ -746,7 +775,7 @@ export class Batch {
746
775
  this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
747
776
  }
748
777
 
749
- private onProcessingError(attempt: Attempt): void {
778
+ private onProcessingError(attempt: FailedAttempt): void {
750
779
  this.failedProcessingAttempts.push(attempt);
751
780
  if (this.failedProcessingAttempts.length > MAX_BATCH_PROCESSING_ATTEMPTS) {
752
781
  throw new BatchError(this.errorType({code: BatchErrorCode.MAX_PROCESSING_ATTEMPTS}));
@@ -789,18 +818,44 @@ type BatchErrorMetadata = {
789
818
 
790
819
  export class BatchError extends LodestarError<BatchErrorType & BatchErrorMetadata> {}
791
820
 
792
- function isExecutionEngineError(err: Error): boolean {
793
- if (!(err instanceof BlockError)) {
794
- return false;
795
- }
796
-
797
- if (err.type.code === BlockErrorCode.EXECUTION_ENGINE_ERROR) {
798
- return true;
799
- }
800
-
821
+ /**
822
+ * Whether a processing failure is an execution-engine failure. Pre-gloas, all EL errors stay in
823
+ * BlockErrorCode, post-gloas, they stay in PayloadErrorCode.
824
+ */
825
+ function isExecutionEngineFailure(code: BlockErrorCode | PayloadErrorCode | null): boolean {
801
826
  return (
802
- err.type.code === BlockErrorCode.BEACON_CHAIN_ERROR &&
803
- err.type.error instanceof PayloadError &&
804
- err.type.error.type.code === PayloadErrorCode.EXECUTION_ENGINE_ERROR
827
+ code === BlockErrorCode.EXECUTION_ENGINE_INVALID ||
828
+ code === BlockErrorCode.EXECUTION_ENGINE_ERROR ||
829
+ code === PayloadErrorCode.EXECUTION_ENGINE_INVALID ||
830
+ code === PayloadErrorCode.EXECUTION_ENGINE_ERROR
805
831
  );
806
832
  }
833
+
834
+ /**
835
+ * Based on an error code, return if peer is attributable for FailedAttempt.
836
+ */
837
+ function isPeerAttributableFailure(code: BlockErrorCode | PayloadErrorCode | null): boolean {
838
+ switch (code) {
839
+ // EL returned a definitive INVALID verdict on the payload
840
+ case BlockErrorCode.EXECUTION_ENGINE_INVALID:
841
+ case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
842
+ // a block or envelope signature is invalid
843
+ case BlockErrorCode.INVALID_SIGNATURE:
844
+ case PayloadErrorCode.INVALID_SIGNATURE:
845
+ // the block's state transition produced an invalid state root, or failed per_slot/per_block processing
846
+ case BlockErrorCode.INVALID_STATE_ROOT:
847
+ case BlockErrorCode.PER_BLOCK_PROCESSING_ERROR:
848
+ // the segment this peer served is internally inconsistent (in-segment link breaks, see PR #9684)
849
+ case BlockErrorCode.NON_LINEAR_SLOTS:
850
+ case BlockErrorCode.NON_LINEAR_PARENT_ROOTS:
851
+ case BlockErrorCode.NON_LINEAR_PAYLOAD_ROOTS:
852
+ // the envelope references a mismatched block root, or fails field verification
853
+ case BlockErrorCode.ENVELOPE_BLOCK_ROOT_MISMATCH:
854
+ case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
855
+ // the peer served a block we have explicitly blacklisted
856
+ case BlockErrorCode.BLACKLISTED_BLOCK:
857
+ return true;
858
+ default:
859
+ return false;
860
+ }
861
+ }