@lodestar/beacon-node 1.50.0-dev.70314c9caa → 1.50.0-dev.7df32d9c25

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 (49) hide show
  1. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  2. package/lib/chain/blocks/importBlock.js +0 -4
  3. package/lib/chain/blocks/importBlock.js.map +1 -1
  4. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +1 -2
  5. package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
  6. package/lib/chain/errors/gossipValidation.d.ts +1 -3
  7. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  8. package/lib/chain/errors/gossipValidation.js +0 -2
  9. package/lib/chain/errors/gossipValidation.js.map +1 -1
  10. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  11. package/lib/chain/prepareNextSlot.js +2 -5
  12. package/lib/chain/prepareNextSlot.js.map +1 -1
  13. package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -3
  14. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  15. package/lib/chain/produceBlock/produceBlockBody.js +2 -6
  16. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  17. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +0 -6
  18. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  19. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +0 -10
  20. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  21. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  22. package/lib/network/processor/gossipHandlers.js +0 -3
  23. package/lib/network/processor/gossipHandlers.js.map +1 -1
  24. package/lib/network/processor/gossipValidatorFn.d.ts +3 -7
  25. package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
  26. package/lib/network/processor/gossipValidatorFn.js +3 -7
  27. package/lib/network/processor/gossipValidatorFn.js.map +1 -1
  28. package/lib/network/processor/index.d.ts +0 -9
  29. package/lib/network/processor/index.d.ts.map +1 -1
  30. package/lib/network/processor/index.js +10 -77
  31. package/lib/network/processor/index.js.map +1 -1
  32. package/lib/sync/range/range.d.ts.map +1 -1
  33. package/lib/sync/range/range.js +18 -50
  34. package/lib/sync/range/range.js.map +1 -1
  35. package/lib/sync/unknownBlock.d.ts.map +1 -1
  36. package/lib/sync/unknownBlock.js +4 -5
  37. package/lib/sync/unknownBlock.js.map +1 -1
  38. package/package.json +15 -15
  39. package/src/chain/blocks/importBlock.ts +0 -4
  40. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -2
  41. package/src/chain/errors/gossipValidation.ts +0 -2
  42. package/src/chain/prepareNextSlot.ts +3 -6
  43. package/src/chain/produceBlock/produceBlockBody.ts +1 -8
  44. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +0 -11
  45. package/src/network/processor/gossipHandlers.ts +0 -3
  46. package/src/network/processor/gossipValidatorFn.ts +3 -13
  47. package/src/network/processor/index.ts +10 -89
  48. package/src/sync/range/range.ts +18 -50
  49. package/src/sync/unknownBlock.ts +4 -5
@@ -22,7 +22,6 @@ import {
22
22
  getParentRootFromSignedBeaconBlockSerialized,
23
23
  getPayloadPresentFromPayloadAttestationMessageSerialized,
24
24
  } from "../../util/sszBytes.js";
25
- import {INetworkCore} from "../core/index.js";
26
25
  import {NetworkEvent, NetworkEventBus} from "../events.js";
27
26
  import {
28
27
  GossipHandlers,
@@ -35,12 +34,7 @@ import {MAX_PEERS_PER_ROOT} from "./constants.js";
35
34
  import {createExtractBlockSlotRootFns} from "./extractSlotRootFns.js";
36
35
  import {GossipHandlerOpts, ValidatorFnsModules, getGossipHandlers} from "./gossipHandlers.js";
37
36
  import {createGossipQueues} from "./gossipQueues/index.js";
38
- import {
39
- ValidatorFnModules,
40
- getGossipValidatorBatchFn,
41
- getGossipValidatorFn,
42
- rejectPeerAction,
43
- } from "./gossipValidatorFn.js";
37
+ import {ValidatorFnModules, getGossipValidatorBatchFn, getGossipValidatorFn} from "./gossipValidatorFn.js";
44
38
  import {PendingGossipsubMessage} from "./types.js";
45
39
 
46
40
  export * from "./types.js";
@@ -108,12 +102,6 @@ type SearchedRootEntry = {
108
102
  */
109
103
  const DEFAULT_EARLIEST_PERMISSIBLE_SLOT_DISTANCE = 32;
110
104
 
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
-
117
105
  type WorkOpts = {
118
106
  bypassQueue?: boolean;
119
107
  };
@@ -244,7 +232,6 @@ type SearchUnknownRootTarget =
244
232
  */
245
233
  export class NetworkProcessor {
246
234
  private readonly chain: IBeaconChain;
247
- private readonly core: INetworkCore;
248
235
  private readonly events: NetworkEventBus;
249
236
  private readonly logger: Logger;
250
237
  private readonly metrics: Metrics | null;
@@ -269,31 +256,23 @@ export class NetworkProcessor {
269
256
  () => new MapDef<RootHex, SearchedRootEntry>(() => ({}))
270
257
  );
271
258
  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>();
274
259
 
275
260
  constructor(
276
261
  modules: NetworkProcessorModules,
277
262
  private readonly opts: NetworkProcessorOpts
278
263
  ) {
279
- const {chain, core, events, logger, metrics} = modules;
264
+ const {chain, events, logger, metrics} = modules;
280
265
  this.chain = chain;
281
- this.core = core;
282
266
  this.events = events;
283
267
  this.metrics = metrics;
284
268
  this.logger = logger;
285
269
  this.events = events;
286
270
  this.gossipQueues = createGossipQueues();
287
271
  this.gossipTopicConcurrency = mapValues(this.gossipQueues, () => 0);
288
- this.gossipValidatorFn = getGossipValidatorFn(
289
- modules.gossipHandlers ?? getGossipHandlers(modules, opts),
290
- modules,
291
- this.onFatalPeer
292
- );
272
+ this.gossipValidatorFn = getGossipValidatorFn(modules.gossipHandlers ?? getGossipHandlers(modules, opts), modules);
293
273
  this.gossipValidatorBatchFn = getGossipValidatorBatchFn(
294
274
  modules.gossipHandlers ?? getGossipHandlers(modules, opts),
295
- modules,
296
- this.onFatalPeer
275
+ modules
297
276
  );
298
277
 
299
278
  events.on(NetworkEvent.pendingGossipsubMessage, this.onPendingGossipsubMessage);
@@ -427,47 +406,19 @@ export class NetworkProcessor {
427
406
  }
428
407
  }
429
408
 
430
- private onFatalPeer = (peer: PeerIdStr): void => {
431
- this.fatalPeers.set(peer, this.chain.clock.currentSlot);
432
- };
433
-
434
409
  private onPendingGossipsubMessage = (message: PendingGossipsubMessage): void => {
435
410
  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
-
442
411
  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
- }
448
412
 
449
413
  // 1st extract round: make sure slot is in range and if block root is not available
450
414
  // proactively search for it + queue the message
451
- const slotRoot = extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork);
415
+ const slotRoot = extractBlockSlotRootFn
416
+ ? extractBlockSlotRootFn(message.msg.data, message.topic.boundary.fork)
417
+ : null;
452
418
  if (slotRoot === null) {
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
- });
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);
471
422
  return;
472
423
  }
473
424
 
@@ -921,10 +872,6 @@ export class NetworkProcessor {
921
872
  for (const slot of this.bufferedRootsBySlot.keys()) {
922
873
  if (slot <= minSlot) this.bufferedRootsBySlot.delete(slot);
923
874
  }
924
-
925
- for (const [peer, slot] of this.fatalPeers) {
926
- if (clockSlot - slot > FATAL_PEER_EXPIRY_SLOTS) this.fatalPeers.delete(peer);
927
- }
928
875
  };
929
876
 
930
877
  private executeWork(): void {
@@ -980,32 +927,6 @@ export class NetworkProcessor {
980
927
  private async processPendingGossipsubMessage(
981
928
  messageOrArray: PendingGossipsubMessage | PendingGossipsubMessage[]
982
929
  ): 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
-
1009
930
  const nowSec = Date.now() / 1000;
1010
931
  if (Array.isArray(messageOrArray)) {
1011
932
  for (const msg of messageOrArray) {
@@ -5,7 +5,6 @@ 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";
9
8
  import {AttestationImportOpt, ImportBlockOpts} from "../../chain/blocks/index.js";
10
9
  import {assertLinearChainSegment} from "../../chain/blocks/utils/chainSegment.js";
11
10
  import {BlockError} from "../../chain/errors/index.js";
@@ -208,50 +207,22 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
208
207
  blsVerifyOnMainThread: false,
209
208
  };
210
209
 
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
- });
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);
244
217
  }
245
218
  }
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();
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();
255
226
  let client = "unknown";
256
227
  if (peerIdStr !== undefined) {
257
228
  try {
@@ -260,18 +231,15 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) {
260
231
  // peer disconnected since serving the envelope, keep "unknown"
261
232
  }
262
233
  }
263
- this.logger.debug("Removing invalid payload envelope in range sync batch", {
264
- slot: payloadInput.slot,
265
- root: payloadInput.blockRootHex,
234
+ this.logger.debug("Orphaned payload envelope in range sync batch", {
235
+ slot,
236
+ root: payloadEnvelopeInput.blockRootHex,
266
237
  peer: peerIdStr ?? "unknown",
267
238
  source,
268
239
  client,
269
- code: e.type.code,
240
+ skipped,
270
241
  });
271
- // The batch is downloaded again, it would otherwise reuse the cached envelope and fail the same way
272
- this.chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
273
242
  }
274
- throw e;
275
243
  }
276
244
  };
277
245
 
@@ -1426,23 +1426,20 @@ export class BlockInputSync {
1426
1426
  pendingPayload.status = PendingPayloadInputStatus.downloaded;
1427
1427
  break;
1428
1428
 
1429
- // The invalid envelope is evicted from the seen cache in the cases below, otherwise the next
1430
- // attempt would reuse it instead of fetching another envelope from peers
1431
1429
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1432
1430
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1433
- this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
1434
1431
  this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.elInvalid);
1435
1432
  break;
1436
1433
 
1437
1434
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1438
1435
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1439
- this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
1440
1436
  this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidEnvelope);
1441
1437
  break;
1442
1438
 
1443
1439
  case PayloadErrorCode.INVALID_SIGNATURE:
1440
+ // TODO GLOAS: Decide how invalid payload inputs should eventually leave memory without
1441
+ // reintroducing envelope replacement / recreation flows.
1444
1442
  this.logger.debug("Error processing payload from unknown sync", logCtx, res.err);
1445
- this.chain.seenPayloadEnvelopeInputCache.removeInvalid(pendingPayload.payloadInput);
1446
1443
  this.removePendingPayloadAndDescendants(rootHex, DroppedItemReason.invalidSignature);
1447
1444
  break;
1448
1445
 
@@ -1882,6 +1879,8 @@ export class BlockInputSync {
1882
1879
 
1883
1880
  // Once a parent payload is invalid, every descendant waiting on that payload lineage becomes unrecoverable too.
1884
1881
  private removePendingPayloadAndDescendants(rootHex: RootHex, headReason: DroppedItemReason): void {
1882
+ // Keep PayloadEnvelopeInput resident in the seen cache. importBlock() owns that object and
1883
+ // later validation/finalization logic decides when it can leave memory.
1885
1884
  if (this.pendingPayloads.delete(rootHex)) {
1886
1885
  this.metrics?.blockInputSync.removedPayloads.inc({reason: headReason}, 1);
1887
1886
  }