@lodestar/beacon-node 1.50.0-dev.9c3f7bebe9 → 1.50.0-dev.9c7f3c203f

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 (226) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +6 -1
  3. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  4. package/lib/api/impl/beacon/index.d.ts.map +1 -1
  5. package/lib/api/impl/beacon/index.js +13 -0
  6. package/lib/api/impl/beacon/index.js.map +1 -1
  7. package/lib/api/impl/beacon/pool/index.js +4 -4
  8. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  9. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  10. package/lib/api/impl/beacon/state/index.js +23 -3
  11. package/lib/api/impl/beacon/state/index.js.map +1 -1
  12. package/lib/api/impl/debug/index.d.ts.map +1 -1
  13. package/lib/api/impl/debug/index.js +26 -18
  14. package/lib/api/impl/debug/index.js.map +1 -1
  15. package/lib/api/impl/validator/index.d.ts +1 -0
  16. package/lib/api/impl/validator/index.d.ts.map +1 -1
  17. package/lib/api/impl/validator/index.js +23 -1
  18. package/lib/api/impl/validator/index.js.map +1 -1
  19. package/lib/chain/archiveStore/archiveStore.js +1 -1
  20. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  21. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
  22. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  23. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
  24. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  25. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
  26. package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
  27. package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
  28. package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
  29. package/lib/chain/archiveStore/historicalState/worker.js +1 -1
  30. package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
  31. package/lib/chain/archiveStore/interface.d.ts +1 -1
  32. package/lib/chain/archiveStore/interface.d.ts.map +1 -1
  33. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  34. package/lib/chain/blocks/importBlock.js +4 -0
  35. package/lib/chain/blocks/importBlock.js.map +1 -1
  36. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
  37. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  38. package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
  39. package/lib/chain/blocks/verifyBlock.js +1 -1
  40. package/lib/chain/blocks/verifyBlock.js.map +1 -1
  41. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
  42. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  43. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
  44. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  45. package/lib/chain/chain.d.ts +7 -4
  46. package/lib/chain/chain.d.ts.map +1 -1
  47. package/lib/chain/chain.js +50 -17
  48. package/lib/chain/chain.js.map +1 -1
  49. package/lib/chain/emitter.d.ts +7 -1
  50. package/lib/chain/emitter.d.ts.map +1 -1
  51. package/lib/chain/emitter.js +5 -0
  52. package/lib/chain/emitter.js.map +1 -1
  53. package/lib/chain/errors/envelopeReconstructionError.d.ts +5 -0
  54. package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
  55. package/lib/chain/errors/envelopeReconstructionError.js +2 -0
  56. package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
  57. package/lib/chain/errors/gossipValidation.d.ts +3 -1
  58. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  59. package/lib/chain/errors/gossipValidation.js +2 -0
  60. package/lib/chain/errors/gossipValidation.js.map +1 -1
  61. package/lib/chain/forkChoice/index.d.ts.map +1 -1
  62. package/lib/chain/forkChoice/index.js +2 -0
  63. package/lib/chain/forkChoice/index.js.map +1 -1
  64. package/lib/chain/initState.d.ts +2 -2
  65. package/lib/chain/initState.d.ts.map +1 -1
  66. package/lib/chain/initState.js +3 -4
  67. package/lib/chain/initState.js.map +1 -1
  68. package/lib/chain/interface.d.ts +3 -2
  69. package/lib/chain/interface.d.ts.map +1 -1
  70. package/lib/chain/opPools/opPool.d.ts +5 -4
  71. package/lib/chain/opPools/opPool.d.ts.map +1 -1
  72. package/lib/chain/opPools/opPool.js +62 -18
  73. package/lib/chain/opPools/opPool.js.map +1 -1
  74. package/lib/chain/opPools/proposerPreferencesPool.d.ts +4 -4
  75. package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
  76. package/lib/chain/opPools/proposerPreferencesPool.js +14 -11
  77. package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
  78. package/lib/chain/opPools/utils.d.ts +4 -1
  79. package/lib/chain/opPools/utils.d.ts.map +1 -1
  80. package/lib/chain/opPools/utils.js +11 -1
  81. package/lib/chain/opPools/utils.js.map +1 -1
  82. package/lib/chain/options.d.ts +2 -1
  83. package/lib/chain/options.d.ts.map +1 -1
  84. package/lib/chain/options.js +1 -1
  85. package/lib/chain/options.js.map +1 -1
  86. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  87. package/lib/chain/prepareNextSlot.js +5 -2
  88. package/lib/chain/prepareNextSlot.js.map +1 -1
  89. package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
  90. package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
  91. package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
  92. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  93. package/lib/chain/produceBlock/produceBlockBody.js +6 -2
  94. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  95. package/lib/chain/regen/regen.d.ts +2 -0
  96. package/lib/chain/regen/regen.d.ts.map +1 -1
  97. package/lib/chain/regen/regen.js +10 -4
  98. package/lib/chain/regen/regen.js.map +1 -1
  99. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
  100. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  101. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
  102. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  103. package/lib/chain/validation/attesterSlashing.d.ts +4 -4
  104. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  105. package/lib/chain/validation/attesterSlashing.js +3 -0
  106. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  107. package/lib/chain/validation/proposerSlashing.d.ts +3 -3
  108. package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
  109. package/lib/chain/validation/proposerSlashing.js +3 -0
  110. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  111. package/lib/chain/validatorMonitor.d.ts +5 -1
  112. package/lib/chain/validatorMonitor.d.ts.map +1 -1
  113. package/lib/chain/validatorMonitor.js +64 -54
  114. package/lib/chain/validatorMonitor.js.map +1 -1
  115. package/lib/db/repositories/proposerSlashing.d.ts +1 -0
  116. package/lib/db/repositories/proposerSlashing.d.ts.map +1 -1
  117. package/lib/db/repositories/proposerSlashing.js +5 -1
  118. package/lib/db/repositories/proposerSlashing.js.map +1 -1
  119. package/lib/execution/engine/disabled.d.ts +0 -2
  120. package/lib/execution/engine/disabled.d.ts.map +1 -1
  121. package/lib/execution/engine/disabled.js +0 -6
  122. package/lib/execution/engine/disabled.js.map +1 -1
  123. package/lib/execution/engine/http.d.ts +1 -3
  124. package/lib/execution/engine/http.d.ts.map +1 -1
  125. package/lib/execution/engine/http.js +2 -17
  126. package/lib/execution/engine/http.js.map +1 -1
  127. package/lib/execution/engine/interface.d.ts +2 -4
  128. package/lib/execution/engine/interface.d.ts.map +1 -1
  129. package/lib/execution/engine/mock.d.ts +0 -2
  130. package/lib/execution/engine/mock.d.ts.map +1 -1
  131. package/lib/execution/engine/mock.js +0 -8
  132. package/lib/execution/engine/mock.js.map +1 -1
  133. package/lib/execution/engine/types.d.ts +0 -8
  134. package/lib/execution/engine/types.d.ts.map +1 -1
  135. package/lib/execution/engine/types.js.map +1 -1
  136. package/lib/metrics/metrics.d.ts +7 -3
  137. package/lib/metrics/metrics.d.ts.map +1 -1
  138. package/lib/metrics/metrics.js +4 -4
  139. package/lib/metrics/metrics.js.map +1 -1
  140. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  141. package/lib/network/processor/gossipHandlers.js +7 -4
  142. package/lib/network/processor/gossipHandlers.js.map +1 -1
  143. package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
  144. package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
  145. package/lib/network/processor/gossipValidatorFn.js +7 -3
  146. package/lib/network/processor/gossipValidatorFn.js.map +1 -1
  147. package/lib/network/processor/index.d.ts +9 -0
  148. package/lib/network/processor/index.d.ts.map +1 -1
  149. package/lib/network/processor/index.js +77 -10
  150. package/lib/network/processor/index.js.map +1 -1
  151. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
  152. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  153. package/lib/node/nodejs.d.ts.map +1 -1
  154. package/lib/node/nodejs.js +25 -3
  155. package/lib/node/nodejs.js.map +1 -1
  156. package/lib/sync/range/batch.d.ts +6 -0
  157. package/lib/sync/range/batch.d.ts.map +1 -1
  158. package/lib/sync/range/batch.js +10 -0
  159. package/lib/sync/range/batch.js.map +1 -1
  160. package/lib/sync/range/chain.d.ts.map +1 -1
  161. package/lib/sync/range/chain.js +5 -1
  162. package/lib/sync/range/chain.js.map +1 -1
  163. package/lib/sync/range/range.d.ts.map +1 -1
  164. package/lib/sync/range/range.js +50 -18
  165. package/lib/sync/range/range.js.map +1 -1
  166. package/lib/sync/unknownBlock.d.ts +9 -5
  167. package/lib/sync/unknownBlock.d.ts.map +1 -1
  168. package/lib/sync/unknownBlock.js +88 -46
  169. package/lib/sync/unknownBlock.js.map +1 -1
  170. package/lib/util/execution.d.ts +9 -11
  171. package/lib/util/execution.d.ts.map +1 -1
  172. package/lib/util/execution.js +5 -1
  173. package/lib/util/execution.js.map +1 -1
  174. package/package.json +17 -17
  175. package/src/api/impl/beacon/blocks/index.ts +7 -4
  176. package/src/api/impl/beacon/index.ts +15 -0
  177. package/src/api/impl/beacon/pool/index.ts +4 -4
  178. package/src/api/impl/beacon/state/index.ts +31 -3
  179. package/src/api/impl/debug/index.ts +28 -19
  180. package/src/api/impl/validator/index.ts +24 -2
  181. package/src/chain/archiveStore/archiveStore.ts +1 -1
  182. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
  183. package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
  184. package/src/chain/archiveStore/historicalState/types.ts +2 -2
  185. package/src/chain/archiveStore/historicalState/worker.ts +1 -2
  186. package/src/chain/archiveStore/interface.ts +1 -1
  187. package/src/chain/blocks/importBlock.ts +4 -0
  188. package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
  189. package/src/chain/blocks/verifyBlock.ts +1 -0
  190. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
  191. package/src/chain/chain.ts +67 -20
  192. package/src/chain/emitter.ts +7 -1
  193. package/src/chain/errors/envelopeReconstructionError.ts +3 -0
  194. package/src/chain/errors/gossipValidation.ts +2 -0
  195. package/src/chain/forkChoice/index.ts +2 -0
  196. package/src/chain/initState.ts +5 -6
  197. package/src/chain/interface.ts +3 -2
  198. package/src/chain/opPools/opPool.ts +91 -18
  199. package/src/chain/opPools/proposerPreferencesPool.ts +14 -10
  200. package/src/chain/opPools/utils.ts +31 -3
  201. package/src/chain/options.ts +3 -3
  202. package/src/chain/prepareNextSlot.ts +6 -3
  203. package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
  204. package/src/chain/produceBlock/produceBlockBody.ts +8 -1
  205. package/src/chain/regen/regen.ts +12 -4
  206. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
  207. package/src/chain/validation/attesterSlashing.ts +7 -4
  208. package/src/chain/validation/proposerSlashing.ts +7 -4
  209. package/src/chain/validatorMonitor.ts +75 -57
  210. package/src/db/repositories/proposerSlashing.ts +6 -1
  211. package/src/execution/engine/disabled.ts +0 -8
  212. package/src/execution/engine/http.ts +1 -30
  213. package/src/execution/engine/interface.ts +2 -6
  214. package/src/execution/engine/mock.ts +1 -19
  215. package/src/execution/engine/types.ts +0 -10
  216. package/src/metrics/metrics.ts +21 -6
  217. package/src/network/processor/gossipHandlers.ts +7 -4
  218. package/src/network/processor/gossipValidatorFn.ts +13 -3
  219. package/src/network/processor/index.ts +89 -10
  220. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
  221. package/src/node/nodejs.ts +31 -3
  222. package/src/sync/range/batch.ts +11 -0
  223. package/src/sync/range/chain.ts +6 -1
  224. package/src/sync/range/range.ts +50 -18
  225. package/src/sync/unknownBlock.ts +99 -50
  226. package/src/util/execution.ts +16 -8
@@ -20,6 +20,7 @@ import {
20
20
  VoluntaryExitErrorCode,
21
21
  } from "../../chain/errors/index.js";
22
22
  import {Metrics} from "../../metrics/index.js";
23
+ import {PeerIdStr} from "../../util/peerId.js";
23
24
  import {INetworkCore} from "../core/index.js";
24
25
  import {
25
26
  BatchGossipHandlerFn,
@@ -40,6 +41,8 @@ export type ValidatorFnModules = {
40
41
  core: INetworkCore;
41
42
  };
42
43
 
44
+ export type OnFatalPeerFn = (peer: PeerIdStr) => void;
45
+
43
46
  type RejectPeerActionRule = {default: PeerAction; byCode?: Record<string, PeerAction>};
44
47
 
45
48
  /**
@@ -112,7 +115,7 @@ const gossipRejectPeerAction: Record<GossipType, RejectPeerActionRule> = {
112
115
  [GossipType.light_client_optimistic_update]: {default: PeerAction.HighToleranceError},
113
116
  };
114
117
 
115
- function rejectPeerAction(type: GossipType, code: string): PeerAction {
118
+ export function rejectPeerAction(type: GossipType, code: string): PeerAction {
116
119
  const rule = gossipRejectPeerAction[type];
117
120
  return rule.byCode?.[code] ?? rule.default;
118
121
  }
@@ -123,7 +126,8 @@ function rejectPeerAction(type: GossipType, code: string): PeerAction {
123
126
  */
124
127
  export function getGossipValidatorBatchFn(
125
128
  gossipHandlers: GossipHandlers,
126
- modules: ValidatorFnModules
129
+ modules: ValidatorFnModules,
130
+ onFatalPeer: OnFatalPeerFn
127
131
  ): GossipValidatorBatchFn {
128
132
  const {logger, metrics, core} = modules;
129
133
 
@@ -173,6 +177,7 @@ export function getGossipValidatorBatchFn(
173
177
  metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
174
178
  const peerAction = rejectPeerAction(type, e.type.code);
175
179
  core.reportPeer(propagationSource, peerAction, e.type.code);
180
+ if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
176
181
  logger.debug(
177
182
  `Gossip validation ${type} rejected`,
178
183
  {peer: propagationSource, clientAgent, clientVersion, peerAction},
@@ -208,7 +213,11 @@ export function getGossipValidatorBatchFn(
208
213
  *
209
214
  * @see getGossipHandlers for reasoning on why GossipHandlerFn are used for gossip validation.
210
215
  */
211
- export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: ValidatorFnModules): GossipValidatorFn {
216
+ export function getGossipValidatorFn(
217
+ gossipHandlers: GossipHandlers,
218
+ modules: ValidatorFnModules,
219
+ onFatalPeer: OnFatalPeerFn
220
+ ): GossipValidatorFn {
212
221
  const {logger, metrics, core} = modules;
213
222
 
214
223
  return async function gossipValidatorFn({
@@ -260,6 +269,7 @@ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: Va
260
269
  metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
261
270
  const peerAction = rejectPeerAction(type, e.type.code);
262
271
  core.reportPeer(propagationSource, peerAction, e.type.code);
272
+ if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
263
273
  logger.debug(
264
274
  `Gossip validation ${type} rejected`,
265
275
  {peer: propagationSource, clientAgent, clientVersion, peerAction},
@@ -22,6 +22,7 @@ import {
22
22
  getParentRootFromSignedBeaconBlockSerialized,
23
23
  getPayloadPresentFromPayloadAttestationMessageSerialized,
24
24
  } from "../../util/sszBytes.js";
25
+ import {INetworkCore} from "../core/index.js";
25
26
  import {NetworkEvent, NetworkEventBus} from "../events.js";
26
27
  import {
27
28
  GossipHandlers,
@@ -34,7 +35,12 @@ import {MAX_PEERS_PER_ROOT} from "./constants.js";
34
35
  import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
35
36
  import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
36
37
  import {createGossipQueues} from "./gossipQueues/index.js";
37
- import {ValidatorFnModules, getGossipValidatorBatchFn, getGossipValidatorFn} from "./gossipValidatorFn.js";
38
+ import {
39
+ ValidatorFnModules,
40
+ getGossipValidatorBatchFn,
41
+ getGossipValidatorFn,
42
+ rejectPeerAction,
43
+ } from "./gossipValidatorFn.js";
38
44
  import {PendingGossipsubMessage} from "./types.js";
39
45
 
40
46
  export * from "./types.js";
@@ -102,6 +108,12 @@ type SearchedRootEntry = {
102
108
  */
103
109
  const DEFAULT_EARLIEST_PERMISSIBLE_SLOT_DISTANCE = 32;
104
110
 
111
+ /**
112
+ * The network worker bans a Fatal peer only on its next PeerManager heartbeat (30s). Until then the main thread
113
+ * would keep validating that peer's messages, so drop them here. 3 slots (36s) outlasts one heartbeat.
114
+ */
115
+ export const FATAL_PEER_EXPIRY_SLOTS = 3;
116
+
105
117
  type WorkOpts = {
106
118
  bypassQueue?: boolean;
107
119
  };
@@ -232,6 +244,7 @@ type SearchUnknownRootTarget =
232
244
  */
233
245
  export class NetworkProcessor {
234
246
  private readonly chain: IBeaconChain;
247
+ private readonly core: INetworkCore;
235
248
  private readonly events: NetworkEventBus;
236
249
  private readonly logger: Logger;
237
250
  private readonly metrics: Metrics | null;
@@ -256,23 +269,31 @@ export class NetworkProcessor {
256
269
  () => new MapDef<RootHex, SearchedRootEntry>(() => ({}))
257
270
  );
258
271
  private bufferedRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
272
+ /** peer -> clock slot when it was Fatal'ed */
273
+ private readonly fatalPeers = new Map<PeerIdStr, Slot>();
259
274
 
260
275
  constructor(
261
276
  modules: NetworkProcessorModules,
262
277
  private readonly opts: NetworkProcessorOpts
263
278
  ) {
264
- const {chain, events, logger, metrics} = modules;
279
+ const {chain, core, events, logger, metrics} = modules;
265
280
  this.chain = chain;
281
+ this.core = core;
266
282
  this.events = events;
267
283
  this.metrics = metrics;
268
284
  this.logger = logger;
269
285
  this.events = events;
270
286
  this.gossipQueues = createGossipQueues();
271
287
  this.gossipTopicConcurrency = mapValues(this.gossipQueues, () => 0);
272
- this.gossipValidatorFn = getGossipValidatorFn(modules.gossipHandlers ?? getGossipHandlers(modules, opts), modules);
288
+ this.gossipValidatorFn = getGossipValidatorFn(
289
+ modules.gossipHandlers ?? getGossipHandlers(modules, opts),
290
+ modules,
291
+ this.onFatalPeer
292
+ );
273
293
  this.gossipValidatorBatchFn = getGossipValidatorBatchFn(
274
294
  modules.gossipHandlers ?? getGossipHandlers(modules, opts),
275
- modules
295
+ modules,
296
+ this.onFatalPeer
276
297
  );
277
298
 
278
299
  events.on(NetworkEvent.pendingGossipsubMessage, this.onPendingGossipsubMessage);
@@ -406,19 +427,47 @@ export class NetworkProcessor {
406
427
  }
407
428
  }
408
429
 
430
+ private onFatalPeer = (peer: PeerIdStr): void => {
431
+ this.fatalPeers.set(peer, this.chain.clock.currentSlot);
432
+ };
433
+
409
434
  private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
410
435
  const topicType = message.topic.type;
436
+ if (this.fatalPeers.has(message.propagationSource)) {
437
+ this.metrics?.networkProcessor.gossipValidationError.inc({topic: topicType, error: GossipErrorCode.FATAL_PEER});
438
+ // No need to report the dropped job to gossip. It will be eventually pruned from the mcache
439
+ return;
440
+ }
441
+
411
442
  const extractBlockSlotRootFn = this.extractBlockSlotRootFns[topicType];
443
+ if (extractBlockSlotRootFn === undefined) {
444
+ // some messages don't have slot and root
445
+ this.pushPendingGossipsubMessageToQueue(message);
446
+ return;
447
+ }
412
448
 
413
449
  // 1st extract round: make sure slot is in range and if block root is not available
414
450
  // proactively search for it + queue the message
415
- const slotRoot = extractBlockSlotRootFn
416
- ? extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork)
417
- : null;
451
+ const slotRoot = extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork);
418
452
  if (slotRoot === null) {
419
- // some messages don't have slot and root
420
- // if the msg.data is invalid, message will be rejected when deserializing data in later phase (gossipValidatorFn)
421
- this.pushPendingGossipsubMessageToQueue(message);
453
+ // DOS protection: null means malformed bytes or slot >= 2^32, neither is forwarded by honest peers.
454
+ const {propagationSource, clientAgent, clientVersion} = message;
455
+ const code = GossipErrorCode.INVALID_SLOT;
456
+ const peerAction = rejectPeerAction(topicType, code);
457
+ this.metrics?.networkProcessor.gossipValidationReject.inc({topic: topicType});
458
+ this.core.reportPeer(propagationSource, peerAction, code);
459
+ this.logger.debug(`Gossip validation ${topicType} rejected due to null slot`, {
460
+ peer: propagationSource,
461
+ clientAgent,
462
+ clientVersion,
463
+ peerAction,
464
+ code,
465
+ });
466
+ this.events.emit(NetworkEvent.gossipMessageValidationResult, {
467
+ msgId: message.msgId,
468
+ propagationSource,
469
+ acceptance: TopicValidatorResult.Reject,
470
+ });
422
471
  return;
423
472
  }
424
473
 
@@ -872,6 +921,10 @@ export class NetworkProcessor {
872
921
  for (const slot of this.bufferedRootsBySlot.keys()) {
873
922
  if (slot <= minSlot) this.bufferedRootsBySlot.delete(slot);
874
923
  }
924
+
925
+ for (const [peer, slot] of this.fatalPeers) {
926
+ if (clockSlot - slot > FATAL_PEER_EXPIRY_SLOTS) this.fatalPeers.delete(peer);
927
+ }
875
928
  };
876
929
 
877
930
  private executeWork(): void {
@@ -927,6 +980,32 @@ export class NetworkProcessor {
927
980
  private async processPendingGossipsubMessage(
928
981
  messageOrArray: PendingGossipsubMessage | PendingGossipsubMessage[]
929
982
  ): Promise<void> {
983
+ // drop messages from peers Fatal'ed after these were queued or started awaiting a block/envelope
984
+ // No need to report the dropped jobs to gossip. They will be eventually pruned from the mcache
985
+ if (this.fatalPeers.size > 0) {
986
+ if (Array.isArray(messageOrArray)) {
987
+ const allowed: PendingGossipsubMessage[] = [];
988
+ for (const msg of messageOrArray) {
989
+ if (this.fatalPeers.has(msg.propagationSource)) {
990
+ this.metrics?.networkProcessor.gossipValidationError.inc({
991
+ topic: msg.topic.type,
992
+ error: GossipErrorCode.FATAL_PEER,
993
+ });
994
+ } else {
995
+ allowed.push(msg);
996
+ }
997
+ }
998
+ if (allowed.length === 0) return;
999
+ messageOrArray = allowed;
1000
+ } else if (this.fatalPeers.has(messageOrArray.propagationSource)) {
1001
+ this.metrics?.networkProcessor.gossipValidationError.inc({
1002
+ topic: messageOrArray.topic.type,
1003
+ error: GossipErrorCode.FATAL_PEER,
1004
+ });
1005
+ return;
1006
+ }
1007
+ }
1008
+
930
1009
  const nowSec = Date.now() / 1000;
931
1010
  if (Array.isArray(messageOrArray)) {
932
1011
  for (const msg of messageOrArray) {
@@ -53,7 +53,7 @@ export async function* onExecutionPayloadEnvelopesByRoot(
53
53
  const batch = requests.slice(i, i + MAX_BODIES_PER_REQUEST);
54
54
  let envelopesBytes: (Uint8Array | null)[];
55
55
  try {
56
- // By-root allows omission, so a mismatched envelope is left out rather than failing the response.
56
+ // By-root allows omission, so an envelope that cannot be rebuilt is left out rather than failing the response.
57
57
  envelopesBytes = await chain.getSerializedExecutionPayloadEnvelopes(batch, "omit");
58
58
  } catch (e) {
59
59
  if (e instanceof EnvelopeReconstructionError) {
@@ -1,6 +1,12 @@
1
1
  import {setMaxListeners} from "node:events";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Registry} from "prom-client";
4
+ import {
5
+ init as initNativeMetrics,
6
+ registerLocalValidator as registerNativeLocalValidator,
7
+ scrapeMetrics as scrapeNativeMetrics,
8
+ unregisterLocalValidator as unregisterNativeLocalValidator,
9
+ } from "@chainsafe/lodestar-z/metrics";
4
10
  import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
5
11
  import {hasher} from "@chainsafe/persistent-merkle-tree";
6
12
  import {BeaconApiMethods} from "@lodestar/api/beacon/server";
@@ -171,7 +177,13 @@ export class BeaconNode {
171
177
  // monitoring relies on metrics data
172
178
  opts.monitoring.endpoint
173
179
  ) {
174
- metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries);
180
+ if (opts.chain.nativeStateTransition) {
181
+ initNativeMetrics();
182
+ }
183
+
184
+ metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries, {
185
+ includeStateTransitionMetrics: !opts.chain.nativeStateTransition,
186
+ });
175
187
  initBeaconMetrics(metrics, anchorState);
176
188
  // Since the db is instantiated before this, metrics must be injected manually afterwards
177
189
  db.setMetrics(metrics.db, metrics.flatFileStore);
@@ -185,7 +197,13 @@ export class BeaconNode {
185
197
  config,
186
198
  anchorState.genesisTime,
187
199
  logger.child({module: LoggerModule.vmon}),
188
- opts.validatorMonitor
200
+ opts.validatorMonitor,
201
+ opts.chain.nativeStateTransition
202
+ ? {
203
+ registerLocalValidator: registerNativeLocalValidator,
204
+ unregisterLocalValidator: unregisterNativeLocalValidator,
205
+ }
206
+ : null
189
207
  )
190
208
  : null;
191
209
 
@@ -303,7 +321,17 @@ export class BeaconNode {
303
321
  const metricsServer = opts.metrics.enabled
304
322
  ? await getHttpMetricsServer(opts.metrics, {
305
323
  register: (metrics as Metrics).register,
306
- getOtherMetrics: async () => Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]),
324
+ getOtherMetrics: async () => {
325
+ const otherMetrics = await Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]);
326
+ if (opts.chain.nativeStateTransition) {
327
+ try {
328
+ otherMetrics.push(scrapeNativeMetrics());
329
+ } catch (e) {
330
+ logger.warn("Failed to scrape native state-transition metrics", {}, e as Error);
331
+ }
332
+ }
333
+ return otherMetrics;
334
+ },
307
335
  logger: logger.child({module: LoggerModule.metrics}),
308
336
  })
309
337
  : null;
@@ -773,6 +773,7 @@ export class Batch {
773
773
  // remove any downloaded blocks and re-attempt
774
774
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
775
775
  this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
776
+ this.resetRequests();
776
777
  }
777
778
 
778
779
  private onProcessingError(attempt: FailedAttempt): void {
@@ -784,6 +785,16 @@ export class Batch {
784
785
  // remove any downloaded blocks and re-attempt
785
786
  // TODO(fulu): need to remove the bad blocks from the SeenBlockInputCache
786
787
  this.state = {status: BatchStatus.AwaitingDownload, blocks: [], payloadEnvelopes: null};
788
+ this.resetRequests();
789
+ }
790
+
791
+ /**
792
+ * After a partial download `requests` only cover the data still missing from the downloaded blocks,
793
+ * e.g. a single column. Once those blocks are dropped, request the whole batch again, otherwise
794
+ * every peer fails the retry for lack of blocks to validate the data against.
795
+ */
796
+ private resetRequests(): void {
797
+ this.requests = this.getRequests([]);
787
798
  }
788
799
 
789
800
  /** Helper to construct typed BatchError. Stack traces are correct as the error is thrown above */
@@ -225,6 +225,9 @@ export class SyncChain {
225
225
  Math.floor((localFinalizedEpoch - this.lastEpochWithProcessBlocks) / EPOCHS_PER_BATCH) * EPOCHS_PER_BATCH;
226
226
  this.advanceChain(lastEpochWithProcessBlocksAligned);
227
227
 
228
+ // stopSyncing() cleared the retry timer, but peers can still be in rate limit backoff
229
+ this.scheduleRateLimitBackoffRetry();
230
+
228
231
  // Potentially download new batches and process pending
229
232
  this.triggerBatchDownloader();
230
233
  this.triggerBatchProcessor();
@@ -417,8 +420,10 @@ export class SyncChain {
417
420
  this.rateLimitBackoffTimeout = setTimeout(
418
421
  () => {
419
422
  this.rateLimitBackoffTimeout = undefined;
420
- this.triggerBatchDownloader();
423
+ // Must run before the downloader, it drops expired peers without triggering a download. If it ran
424
+ // after, a backoff expiring in between would leave the peer idle with no retry scheduled.
421
425
  this.scheduleRateLimitBackoffRetry();
426
+ this.triggerBatchDownloader();
422
427
  },
423
428
  Math.max(0, retryAt - now)
424
429
  );
@@ -5,6 +5,7 @@ import {IBeaconStateViewGloas, computeStartSlotAtEpoch, isStatePostGloas} from "
5
5
  import {Epoch, Status, fulu} from "@lodestar/types";
6
6
  import {Logger, prettyPrintIndices, toRootHex} from "@lodestar/utils";
7
7
  import {IBlockInput} from "../../chain/blocks/blockInput/types.js";
8
+ import {PayloadError, PayloadErrorCode} from "../../chain/blocks/importExecutionPayload.js";
8
9
  import {AttestationImportOpt, ImportBlockOpts} from "../../chain/blocks/index.js";
9
10
  import {assertLinearChainSegment} from "../../chain/blocks/utils/chainSegment.js";
10
11
  import {BlockError} from "../../chain/errors/index.js";
@@ -207,22 +208,50 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
207
208
  blsVerifyOnMainThread: false,
208
209
  };
209
210
 
210
- if (this.opts?.disableProcessAsChainSegment) {
211
- // Should only be used for debugging or testing
212
- for (const block of blocks) {
213
- await this.chain.processBlock(block, flags);
214
- const payloadEnvelope = payloadEnvelopes?.get(block.slot);
215
- if (payloadEnvelope?.hasPayloadEnvelope()) {
216
- await this.chain.processExecutionPayload(payloadEnvelope);
211
+ try {
212
+ if (this.opts?.disableProcessAsChainSegment) {
213
+ // Should only be used for debugging or testing
214
+ for (const block of blocks) {
215
+ await this.chain.processBlock(block, flags);
216
+ const payloadEnvelope = payloadEnvelopes?.get(block.slot);
217
+ if (payloadEnvelope?.hasPayloadEnvelope()) {
218
+ await this.chain.processExecutionPayload(payloadEnvelope);
219
+ }
220
+ }
221
+ } else {
222
+ const {orphaned, skipped} = await this.chain.processChainSegment(blocks, payloadEnvelopes, flags);
223
+ // We log orphaned payloads to work with different clients
224
+ // in the future, consider applying penalties in certain conditions
225
+ // make sure the payload source was from range sync in that case
226
+ for (const {slot, payloadEnvelopeInput} of orphaned) {
227
+ const {peerIdStr, source} = payloadEnvelopeInput.getPayloadEnvelopeSource();
228
+ let client = "unknown";
229
+ if (peerIdStr !== undefined) {
230
+ try {
231
+ client = this.getConnectedPeerSyncMeta(peerIdStr).client;
232
+ } catch {
233
+ // peer disconnected since serving the envelope, keep "unknown"
234
+ }
235
+ }
236
+ this.logger.debug("Orphaned payload envelope in range sync batch", {
237
+ slot,
238
+ root: payloadEnvelopeInput.blockRootHex,
239
+ peer: peerIdStr ?? "unknown",
240
+ source,
241
+ client,
242
+ skipped,
243
+ });
217
244
  }
218
245
  }
219
- } else {
220
- const {orphaned, skipped} = await this.chain.processChainSegment(blocks, payloadEnvelopes, flags);
221
- // We log orphaned payloads to work with different clients
222
- // in the future, consider applying penalties in certain conditions
223
- // make sure the payload source was from range sync in that case
224
- for (const {slot, payloadEnvelopeInput} of orphaned) {
225
- const {peerIdStr, source} = payloadEnvelopeInput.getPayloadEnvelopeSource();
246
+ } catch (e) {
247
+ if (
248
+ e instanceof PayloadError &&
249
+ (e.type.code === PayloadErrorCode.INVALID_SIGNATURE ||
250
+ e.type.code === PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR ||
251
+ e.type.code === PayloadErrorCode.EXECUTION_ENGINE_INVALID)
252
+ ) {
253
+ const {payloadInput} = e;
254
+ const {peerIdStr, source} = payloadInput.getPayloadEnvelopeSource();
226
255
  let client = "unknown";
227
256
  if (peerIdStr !== undefined) {
228
257
  try {
@@ -231,15 +260,18 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
231
260
  // peer disconnected since serving the envelope, keep "unknown"
232
261
  }
233
262
  }
234
- this.logger.debug("Orphaned payload envelope in range sync batch", {
235
- slot,
236
- root: payloadEnvelopeInput.blockRootHex,
263
+ this.logger.debug("Removing invalid payload envelope in range sync batch", {
264
+ slot: payloadInput.slot,
265
+ root: payloadInput.blockRootHex,
237
266
  peer: peerIdStr ?? "unknown",
238
267
  source,
239
268
  client,
240
- skipped,
269
+ code: e.type.code,
241
270
  });
271
+ // The batch is downloaded again, it would otherwise reuse the cached envelope and fail the same way
272
+ this.chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
242
273
  }
274
+ throw e;
243
275
  }
244
276
  };
245
277