@lodestar/beacon-node 1.50.0-dev.f0b11f836e → 1.50.0-dev.f2fb26d453

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 (70) hide show
  1. package/lib/api/impl/beacon/blocks/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js +1 -6
  3. package/lib/api/impl/beacon/blocks/index.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/chain.d.ts +4 -5
  7. package/lib/chain/chain.d.ts.map +1 -1
  8. package/lib/chain/chain.js +13 -11
  9. package/lib/chain/chain.js.map +1 -1
  10. package/lib/chain/errors/envelopeReconstructionError.d.ts +0 -5
  11. package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
  12. package/lib/chain/errors/envelopeReconstructionError.js +0 -2
  13. package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
  14. package/lib/chain/errors/gossipValidation.d.ts +1 -3
  15. package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
  16. package/lib/chain/errors/gossipValidation.js +0 -2
  17. package/lib/chain/errors/gossipValidation.js.map +1 -1
  18. package/lib/chain/interface.d.ts +2 -2
  19. package/lib/chain/interface.d.ts.map +1 -1
  20. package/lib/chain/prepareNextSlot.d.ts.map +1 -1
  21. package/lib/chain/prepareNextSlot.js +2 -5
  22. package/lib/chain/prepareNextSlot.js.map +1 -1
  23. package/lib/chain/produceBlock/produceBlockBody.d.ts +1 -3
  24. package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
  25. package/lib/chain/produceBlock/produceBlockBody.js +2 -6
  26. package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
  27. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +0 -6
  28. package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
  29. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +0 -10
  30. package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
  31. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  32. package/lib/network/processor/gossipHandlers.js +0 -3
  33. package/lib/network/processor/gossipHandlers.js.map +1 -1
  34. package/lib/network/processor/gossipValidatorFn.d.ts +3 -7
  35. package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
  36. package/lib/network/processor/gossipValidatorFn.js +3 -7
  37. package/lib/network/processor/gossipValidatorFn.js.map +1 -1
  38. package/lib/network/processor/index.d.ts +0 -9
  39. package/lib/network/processor/index.d.ts.map +1 -1
  40. package/lib/network/processor/index.js +10 -77
  41. package/lib/network/processor/index.js.map +1 -1
  42. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +1 -1
  43. package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
  44. package/lib/sync/range/range.d.ts.map +1 -1
  45. package/lib/sync/range/range.js +18 -50
  46. package/lib/sync/range/range.js.map +1 -1
  47. package/lib/sync/unknownBlock.d.ts.map +1 -1
  48. package/lib/sync/unknownBlock.js +4 -5
  49. package/lib/sync/unknownBlock.js.map +1 -1
  50. package/lib/util/execution.d.ts +11 -9
  51. package/lib/util/execution.d.ts.map +1 -1
  52. package/lib/util/execution.js +1 -5
  53. package/lib/util/execution.js.map +1 -1
  54. package/package.json +15 -15
  55. package/src/api/impl/beacon/blocks/index.ts +4 -7
  56. package/src/chain/blocks/payloadEnvelopeInput/types.ts +1 -2
  57. package/src/chain/chain.ts +16 -14
  58. package/src/chain/errors/envelopeReconstructionError.ts +0 -3
  59. package/src/chain/errors/gossipValidation.ts +0 -2
  60. package/src/chain/interface.ts +2 -2
  61. package/src/chain/prepareNextSlot.ts +3 -6
  62. package/src/chain/produceBlock/produceBlockBody.ts +1 -8
  63. package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +0 -11
  64. package/src/network/processor/gossipHandlers.ts +0 -3
  65. package/src/network/processor/gossipValidatorFn.ts +3 -13
  66. package/src/network/processor/index.ts +10 -89
  67. package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +1 -1
  68. package/src/sync/range/range.ts +18 -50
  69. package/src/sync/unknownBlock.ts +4 -5
  70. package/src/util/execution.ts +8 -16
@@ -9,8 +9,6 @@ export enum GossipErrorCode {
9
9
  INVALID_SERIALIZED_BYTES_ERROR_CODE = "GOSSIP_ERROR_INVALID_SERIALIZED_BYTES",
10
10
  PAST_SLOT = "GOSSIP_ERROR_PAST_SLOT",
11
11
  FUTURE_SLOT = "GOSSIP_ERROR_FUTURE_SLOT",
12
- FATAL_PEER = "GOSSIP_ERROR_FATAL_PEER",
13
- INVALID_SLOT = "GOSSIP_ERROR_INVALID_SLOT",
14
12
  }
15
13
 
16
14
  export class GossipActionError<T extends {code: string}> extends LodestarError<T> {
@@ -30,7 +30,7 @@ import {Metrics} from "../metrics/metrics.js";
30
30
  import {BufferPool} from "../util/bufferPool.js";
31
31
  import {IClock} from "../util/clock.js";
32
32
  import {CustodyConfig} from "../util/dataColumns.js";
33
- import {ReconstructMissPolicy} from "../util/execution.js";
33
+ import {ReconstructMismatchPolicy} from "../util/execution.js";
34
34
  import {SerializedCache} from "../util/serializedCache.js";
35
35
  import {IArchiveStore} from "./archiveStore/interface.js";
36
36
  import {CheckpointBalancesCache} from "./balancesCache.js";
@@ -239,7 +239,7 @@ export interface IBeaconChain {
239
239
  getSerializedExecutionPayloadEnvelope(blockSlot: Slot, blockRootHex: string): Promise<Uint8Array | null>;
240
240
  getSerializedExecutionPayloadEnvelopes(
241
241
  requests: {blockSlot: Slot; blockRootHex: RootHex}[],
242
- onMiss?: ReconstructMissPolicy
242
+ onMismatch?: ReconstructMismatchPolicy
243
243
  ): Promise<(Uint8Array | null)[]>;
244
244
  getExecutionPayloadEnvelope(
245
245
  blockSlot: Slot,
@@ -244,10 +244,6 @@ export class PrepareNextSlotScheduler {
244
244
  parentBlockHash = preparedState.latestExecutionPayloadHeader.blockHash;
245
245
  }
246
246
 
247
- // The payload_attributes event must carry the same hashes we send to the EL
248
- const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
249
- const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
250
-
251
247
  let payloadAttributes: PayloadAttributes | undefined;
252
248
  // If emitPayloadAttributes is true emit a SSE payloadAttributes event for
253
249
  // every slot. Without the flag, only emit the event if we are proposing in the next slot.
@@ -260,8 +256,6 @@ export class PrepareNextSlotScheduler {
260
256
  prepareSlot,
261
257
  parentBlockRoot: fromHex(updatedHead.blockRoot),
262
258
  parentBlockHash,
263
- safeBlockHash,
264
- finalizedBlockHash,
265
259
  feeRecipient: feeRecipient ?? "0x0000000000000000000000000000000000000000",
266
260
  });
267
261
  this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork});
@@ -273,6 +267,9 @@ export class PrepareNextSlotScheduler {
273
267
  computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
274
268
  this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
275
269
 
270
+ const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
271
+ const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice);
272
+
276
273
  // awaiting here instead of throwing an async call because there is no other task
277
274
  // left for scheduler and this gives nice semantics to catch and log errors in the
278
275
  // try/catch wrapper here.
@@ -845,8 +845,6 @@ export function getPayloadAttributesForSSE(
845
845
  prepareSlot,
846
846
  parentBlockRoot,
847
847
  parentBlockHash,
848
- safeBlockHash,
849
- finalizedBlockHash,
850
848
  feeRecipient,
851
849
  }: {
852
850
  /**
@@ -857,8 +855,6 @@ export function getPayloadAttributesForSSE(
857
855
  prepareSlot: Slot;
858
856
  parentBlockRoot: Root;
859
857
  parentBlockHash: Bytes32;
860
- safeBlockHash: RootHex;
861
- finalizedBlockHash: RootHex;
862
858
  feeRecipient: string;
863
859
  }
864
860
  ): SSEPayloadAttributes {
@@ -878,10 +874,7 @@ export function getPayloadAttributesForSSE(
878
874
  payloadAttributes,
879
875
  } as SSEPayloadAttributes;
880
876
 
881
- if (isForkPostGloas(fork)) {
882
- (ssePayloadAttributes as gloas.SSEPayloadAttributes).safeBlockHash = fromHex(safeBlockHash);
883
- (ssePayloadAttributes as gloas.SSEPayloadAttributes).finalizedBlockHash = fromHex(finalizedBlockHash);
884
- } else {
877
+ if (!isForkPostGloas(fork)) {
885
878
  // Removed in Gloas, builders can get the block number from the EL via the block hash if required
886
879
  (ssePayloadAttributes as bellatrix.SSEPayloadAttributes).parentBlockNumber = prepareState.payloadBlockNumber;
887
880
  }
@@ -264,17 +264,6 @@ export class SeenPayloadEnvelopeInput {
264
264
  }
265
265
  }
266
266
 
267
- /**
268
- * Removes a PayloadEnvelopeInput whose envelope failed import with a definitive invalid verdict. The envelope
269
- * cannot be replaced in place, sync recreates the entry via `getOrReload()` or `add()` to fetch another one.
270
- * No-op if the cached entry is a different object, it may already be the recreated one.
271
- */
272
- removeInvalid(payloadInput: PayloadEnvelopeInput): void {
273
- if (this.payloadInputs.get(payloadInput.blockRootHex) === payloadInput) {
274
- this.evictPayloadInput(payloadInput, "invalid");
275
- }
276
- }
277
-
278
267
  pruneBelowParent(parentBlock: ProtoBlock): void {
279
268
  for (const block of this.forkChoice.getAllAncestorBlocks(parentBlock.blockRoot, parentBlock.payloadStatus)) {
280
269
  // Only evict once the payload is FULL (revealed/imported) — on an EMPTY/PENDING branch we may
@@ -1341,9 +1341,6 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
1341
1341
  case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
1342
1342
  case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
1343
1343
  core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
1344
- // The builder may have signed another envelope that is valid, keeping this one would make
1345
- // by-root and range sync reuse it and never import the payload
1346
- chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
1347
1344
  // Misbehaving peer, but could highlight an issue in another client
1348
1345
  logLevel = LogLevel.warn;
1349
1346
  break;
@@ -20,7 +20,6 @@ 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";
24
23
  import {INetworkCore} from "../core/index.js";
25
24
  import {
26
25
  BatchGossipHandlerFn,
@@ -41,8 +40,6 @@ export type ValidatorFnModules = {
41
40
  core: INetworkCore;
42
41
  };
43
42
 
44
- export type OnFatalPeerFn = (peer: PeerIdStr) => void;
45
-
46
43
  type RejectPeerActionRule = {default: PeerAction; byCode?: Record<string, PeerAction>};
47
44
 
48
45
  /**
@@ -115,7 +112,7 @@ const gossipRejectPeerAction: Record<GossipType, RejectPeerActionRule> = {
115
112
  [GossipType.light_client_optimistic_update]: {default: PeerAction.HighToleranceError},
116
113
  };
117
114
 
118
- export function rejectPeerAction(type: GossipType, code: string): PeerAction {
115
+ function rejectPeerAction(type: GossipType, code: string): PeerAction {
119
116
  const rule = gossipRejectPeerAction[type];
120
117
  return rule.byCode?.[code] ?? rule.default;
121
118
  }
@@ -126,8 +123,7 @@ export function rejectPeerAction(type: GossipType, code: string): PeerAction {
126
123
  */
127
124
  export function getGossipValidatorBatchFn(
128
125
  gossipHandlers: GossipHandlers,
129
- modules: ValidatorFnModules,
130
- onFatalPeer: OnFatalPeerFn
126
+ modules: ValidatorFnModules
131
127
  ): GossipValidatorBatchFn {
132
128
  const {logger, metrics, core} = modules;
133
129
 
@@ -177,7 +173,6 @@ export function getGossipValidatorBatchFn(
177
173
  metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
178
174
  const peerAction = rejectPeerAction(type, e.type.code);
179
175
  core.reportPeer(propagationSource, peerAction, e.type.code);
180
- if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
181
176
  logger.debug(
182
177
  `Gossip validation ${type} rejected`,
183
178
  {peer: propagationSource, clientAgent, clientVersion, peerAction},
@@ -213,11 +208,7 @@ export function getGossipValidatorBatchFn(
213
208
  *
214
209
  * @see getGossipHandlers for reasoning on why GossipHandlerFn are used for gossip validation.
215
210
  */
216
- export function getGossipValidatorFn(
217
- gossipHandlers: GossipHandlers,
218
- modules: ValidatorFnModules,
219
- onFatalPeer: OnFatalPeerFn
220
- ): GossipValidatorFn {
211
+ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: ValidatorFnModules): GossipValidatorFn {
221
212
  const {logger, metrics, core} = modules;
222
213
 
223
214
  return async function gossipValidatorFn({
@@ -269,7 +260,6 @@ export function getGossipValidatorFn(
269
260
  metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
270
261
  const peerAction = rejectPeerAction(type, e.type.code);
271
262
  core.reportPeer(propagationSource, peerAction, e.type.code);
272
- if (peerAction === PeerAction.Fatal) onFatalPeer(propagationSource);
273
263
  logger.debug(
274
264
  `Gossip validation ${type} rejected`,
275
265
  {peer: propagationSource, clientAgent, clientVersion, peerAction},
@@ -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) {
@@ -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 an envelope that cannot be rebuilt is left out rather than failing the response.
56
+ // By-root allows omission, so a mismatched envelope 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) {
@@ -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
  }
@@ -266,15 +266,14 @@ type SlotEnvelopeBytes = {slot: Slot; envelopeBytes: Uint8Array};
266
266
 
267
267
  type RangeEntry = ArchivedEnvelope & {slot: Slot};
268
268
 
269
- /** What an envelope that cannot be rebuilt (EL-unavailable body or body root mismatch) means on a given serving path */
270
- export type ReconstructMissPolicy = "throw" | "omit";
269
+ /** What a body root mismatch means on a given serving path */
270
+ export type ReconstructMismatchPolicy = "throw" | "omit";
271
271
 
272
- /**
273
- * An archived envelope that could not be rebuilt. `"unavailable"`: the EL does not have the block or its
274
- * block access list (BODY_UNAVAILABLE). `"mismatch"`: an EL body does not hash to its stored root, a local
275
- * inconsistency (BODY_ROOT_MISMATCH). `error` carries the matching code for paths that surface it.
276
- */
277
- export type RebuildMiss = {slot: Slot; reason: "unavailable" | "mismatch"; error: EnvelopeReconstructionError};
272
+ export type RebuildMiss =
273
+ /** EL does not have the block, or has pruned its block access list */
274
+ | {slot: Slot; reason: "unavailable"}
275
+ /** An EL body does not hash to its stored root (local inconsistency) */
276
+ | {slot: Slot; reason: "mismatch"; error: EnvelopeReconstructionError};
278
277
 
279
278
  /**
280
279
  * Stream finalized envelopes over [startSlot, endSlot) as serialized bytes, rebuilding header
@@ -406,14 +405,7 @@ async function reconstructEnvelopesBatch(
406
405
  // A zero-length block access list cannot be valid, RLP encodes an empty list as 0xc0
407
406
  if (body == null || body.withdrawals == null || body.blockAccessList == null || body.blockAccessList.length === 0) {
408
407
  metrics?.payloadEnvelopeReconstruction.envelopes.inc({result: "unavailable"});
409
- return {
410
- slot,
411
- reason: "unavailable",
412
- error: new EnvelopeReconstructionError(
413
- {code: EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE, slot},
414
- `execution client cannot serve the payload body or block access list for archived envelope slot=${slot}`
415
- ),
416
- };
408
+ return {slot, reason: "unavailable"};
417
409
  }
418
410
  try {
419
411
  const envelope = signedHeaderEnvelopeToFull(headerEnvelope, {