@lodestar/beacon-node 1.36.0-dev.797fa46c1b → 1.36.0-dev.801b1f4f52

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 (104) hide show
  1. package/lib/chain/errors/blobSidecarError.d.ts +5 -0
  2. package/lib/chain/errors/blobSidecarError.d.ts.map +1 -1
  3. package/lib/chain/errors/blobSidecarError.js.map +1 -1
  4. package/lib/chain/errors/blockError.d.ts +1 -0
  5. package/lib/chain/errors/blockError.d.ts.map +1 -1
  6. package/lib/chain/errors/dataColumnSidecarError.d.ts +21 -14
  7. package/lib/chain/errors/dataColumnSidecarError.d.ts.map +1 -1
  8. package/lib/chain/errors/dataColumnSidecarError.js +4 -0
  9. package/lib/chain/errors/dataColumnSidecarError.js.map +1 -1
  10. package/lib/chain/options.d.ts.map +1 -1
  11. package/lib/chain/options.js +2 -1
  12. package/lib/chain/options.js.map +1 -1
  13. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts +16 -1
  14. package/lib/chain/stateCache/persistentCheckpointsCache.d.ts.map +1 -1
  15. package/lib/chain/stateCache/persistentCheckpointsCache.js +31 -1
  16. package/lib/chain/stateCache/persistentCheckpointsCache.js.map +1 -1
  17. package/lib/chain/validation/blobSidecar.d.ts +4 -1
  18. package/lib/chain/validation/blobSidecar.d.ts.map +1 -1
  19. package/lib/chain/validation/blobSidecar.js +46 -11
  20. package/lib/chain/validation/blobSidecar.js.map +1 -1
  21. package/lib/chain/validation/block.d.ts.map +1 -1
  22. package/lib/chain/validation/block.js +1 -0
  23. package/lib/chain/validation/block.js.map +1 -1
  24. package/lib/chain/validation/dataColumnSidecar.d.ts +4 -1
  25. package/lib/chain/validation/dataColumnSidecar.d.ts.map +1 -1
  26. package/lib/chain/validation/dataColumnSidecar.js +64 -19
  27. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  28. package/lib/network/core/networkCoreWorker.js +6 -1
  29. package/lib/network/core/networkCoreWorker.js.map +1 -1
  30. package/lib/network/core/networkCoreWorkerHandler.js +1 -1
  31. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  32. package/lib/network/events.d.ts +1 -0
  33. package/lib/network/events.d.ts.map +1 -1
  34. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  35. package/lib/network/gossip/gossipsub.js +6 -1
  36. package/lib/network/gossip/gossipsub.js.map +1 -1
  37. package/lib/network/gossip/interface.d.ts +2 -0
  38. package/lib/network/gossip/interface.d.ts.map +1 -1
  39. package/lib/network/peers/discover.js +2 -2
  40. package/lib/network/peers/discover.js.map +1 -1
  41. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  42. package/lib/network/processor/gossipHandlers.js +15 -1
  43. package/lib/network/processor/gossipHandlers.js.map +1 -1
  44. package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
  45. package/lib/network/processor/gossipValidatorFn.js +8 -6
  46. package/lib/network/processor/gossipValidatorFn.js.map +1 -1
  47. package/lib/network/processor/types.d.ts +2 -0
  48. package/lib/network/processor/types.d.ts.map +1 -1
  49. package/lib/network/reqresp/ReqRespBeaconNode.d.ts.map +1 -1
  50. package/lib/network/reqresp/ReqRespBeaconNode.js +3 -1
  51. package/lib/network/reqresp/ReqRespBeaconNode.js.map +1 -1
  52. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts +2 -1
  53. package/lib/network/reqresp/handlers/beaconBlocksByRange.d.ts.map +1 -1
  54. package/lib/network/reqresp/handlers/beaconBlocksByRange.js +11 -2
  55. package/lib/network/reqresp/handlers/beaconBlocksByRange.js.map +1 -1
  56. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts +2 -1
  57. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.d.ts.map +1 -1
  58. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js +9 -1
  59. package/lib/network/reqresp/handlers/dataColumnSidecarsByRange.js.map +1 -1
  60. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts +2 -1
  61. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.d.ts.map +1 -1
  62. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js +9 -1
  63. package/lib/network/reqresp/handlers/dataColumnSidecarsByRoot.js.map +1 -1
  64. package/lib/network/reqresp/handlers/index.js +6 -6
  65. package/lib/network/reqresp/handlers/index.js.map +1 -1
  66. package/lib/network/reqresp/types.d.ts +1 -0
  67. package/lib/network/reqresp/types.d.ts.map +1 -1
  68. package/lib/sync/unknownBlock.js +1 -1
  69. package/lib/sync/unknownBlock.js.map +1 -1
  70. package/lib/sync/utils/downloadByRange.d.ts +59 -15
  71. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  72. package/lib/sync/utils/downloadByRange.js +204 -83
  73. package/lib/sync/utils/downloadByRange.js.map +1 -1
  74. package/lib/sync/utils/downloadByRoot.d.ts +8 -14
  75. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  76. package/lib/sync/utils/downloadByRoot.js +18 -33
  77. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  78. package/package.json +15 -15
  79. package/src/chain/errors/blobSidecarError.ts +12 -2
  80. package/src/chain/errors/blockError.ts +1 -1
  81. package/src/chain/errors/dataColumnSidecarError.ts +31 -16
  82. package/src/chain/options.ts +2 -0
  83. package/src/chain/stateCache/persistentCheckpointsCache.ts +45 -2
  84. package/src/chain/validation/blobSidecar.ts +54 -10
  85. package/src/chain/validation/block.ts +1 -0
  86. package/src/chain/validation/dataColumnSidecar.ts +76 -19
  87. package/src/network/core/networkCoreWorker.ts +7 -2
  88. package/src/network/core/networkCoreWorkerHandler.ts +1 -1
  89. package/src/network/events.ts +1 -1
  90. package/src/network/gossip/gossipsub.ts +7 -1
  91. package/src/network/gossip/interface.ts +2 -0
  92. package/src/network/peers/discover.ts +2 -2
  93. package/src/network/processor/gossipHandlers.ts +16 -1
  94. package/src/network/processor/gossipValidatorFn.ts +33 -6
  95. package/src/network/processor/types.ts +2 -0
  96. package/src/network/reqresp/ReqRespBeaconNode.ts +3 -1
  97. package/src/network/reqresp/handlers/beaconBlocksByRange.ts +15 -2
  98. package/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts +13 -1
  99. package/src/network/reqresp/handlers/dataColumnSidecarsByRoot.ts +13 -1
  100. package/src/network/reqresp/handlers/index.ts +6 -6
  101. package/src/network/reqresp/types.ts +1 -0
  102. package/src/sync/unknownBlock.ts +1 -1
  103. package/src/sync/utils/downloadByRange.ts +273 -108
  104. package/src/sync/utils/downloadByRoot.ts +22 -56
@@ -7,7 +7,8 @@ import {
7
7
  import {
8
8
  computeEpochAtSlot,
9
9
  computeStartSlotAtEpoch,
10
- getBlockHeaderProposerSignatureSet,
10
+ getBlockHeaderProposerSignatureSetByHeaderSlot,
11
+ getBlockHeaderProposerSignatureSetByParentStateSlot,
11
12
  } from "@lodestar/state-transition";
12
13
  import {Root, Slot, SubnetID, fulu, ssz} from "@lodestar/types";
13
14
  import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";
@@ -39,7 +40,7 @@ export async function validateGossipDataColumnSidecar(
39
40
  if (computeSubnetForDataColumnSidecar(chain.config, dataColumnSidecar) !== gossipSubnet) {
40
41
  throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
41
42
  code: DataColumnSidecarErrorCode.INVALID_SUBNET,
42
- columnIdx: dataColumnSidecar.index,
43
+ columnIndex: dataColumnSidecar.index,
43
44
  gossipSubnet: gossipSubnet,
44
45
  });
45
46
  }
@@ -86,6 +87,7 @@ export async function validateGossipDataColumnSidecar(
86
87
  throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
87
88
  code: DataColumnSidecarErrorCode.PARENT_UNKNOWN,
88
89
  parentRoot,
90
+ slot: blockHeader.slot,
89
91
  });
90
92
  }
91
93
 
@@ -108,6 +110,7 @@ export async function validateGossipDataColumnSidecar(
108
110
  throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
109
111
  code: DataColumnSidecarErrorCode.PARENT_UNKNOWN,
110
112
  parentRoot,
113
+ slot: blockHeader.slot,
111
114
  });
112
115
  });
113
116
 
@@ -128,15 +131,23 @@ export async function validateGossipDataColumnSidecar(
128
131
  }
129
132
 
130
133
  // 5) [REJECT] The proposer signature of sidecar.signed_block_header, is valid with respect to the block_header.proposer_index pubkey.
131
- const signatureSet = getBlockHeaderProposerSignatureSet(blockState, dataColumnSidecar.signedBlockHeader);
134
+ const signatureSet = getBlockHeaderProposerSignatureSetByParentStateSlot(
135
+ blockState,
136
+ dataColumnSidecar.signedBlockHeader
137
+ );
132
138
  // Don't batch so verification is not delayed
133
139
  if (
134
140
  !(await chain.bls.verifySignatureSets([signatureSet], {
135
141
  verifyOnMainThread: blockHeader.slot > chain.forkChoice.getHead().slot,
136
142
  }))
137
143
  ) {
144
+ const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(dataColumnSidecar.signedBlockHeader.message);
145
+ const blockRootHex = toRootHex(blockRoot);
138
146
  throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
139
147
  code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
148
+ blockRoot: blockRootHex,
149
+ index: dataColumnSidecar.index,
150
+ slot: blockHeader.slot,
140
151
  });
141
152
  }
142
153
 
@@ -156,7 +167,7 @@ export async function validateGossipDataColumnSidecar(
156
167
  throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
157
168
  code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID,
158
169
  slot: dataColumnSidecar.signedBlockHeader.message.slot,
159
- columnIdx: dataColumnSidecar.index,
170
+ columnIndex: dataColumnSidecar.index,
160
171
  });
161
172
  }
162
173
 
@@ -173,7 +184,7 @@ export async function validateGossipDataColumnSidecar(
173
184
  throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
174
185
  code: DataColumnSidecarErrorCode.INVALID_KZG_PROOF,
175
186
  slot: blockHeader.slot,
176
- columnIdx: dataColumnSidecar.index,
187
+ columnIndex: dataColumnSidecar.index,
177
188
  });
178
189
  } finally {
179
190
  kzgProofTimer?.();
@@ -193,7 +204,7 @@ function verifyDataColumnSidecar(config: ChainForkConfig, dataColumnSidecar: ful
193
204
  throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
194
205
  code: DataColumnSidecarErrorCode.INVALID_INDEX,
195
206
  slot: dataColumnSidecar.signedBlockHeader.message.slot,
196
- columnIdx: dataColumnSidecar.index,
207
+ columnIndex: dataColumnSidecar.index,
197
208
  });
198
209
  }
199
210
 
@@ -201,7 +212,7 @@ function verifyDataColumnSidecar(config: ChainForkConfig, dataColumnSidecar: ful
201
212
  throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
202
213
  code: DataColumnSidecarErrorCode.NO_COMMITMENTS,
203
214
  slot: dataColumnSidecar.signedBlockHeader.message.slot,
204
- columnIdx: dataColumnSidecar.index,
215
+ columnIndex: dataColumnSidecar.index,
205
216
  });
206
217
  }
207
218
 
@@ -212,7 +223,7 @@ function verifyDataColumnSidecar(config: ChainForkConfig, dataColumnSidecar: ful
212
223
  throw new DataColumnSidecarGossipError(GossipAction.REJECT, {
213
224
  code: DataColumnSidecarErrorCode.TOO_MANY_KZG_COMMITMENTS,
214
225
  slot: dataColumnSidecar.signedBlockHeader.message.slot,
215
- columnIdx: dataColumnSidecar.index,
226
+ columnIndex: dataColumnSidecar.index,
216
227
  count: dataColumnSidecar.kzgCommitments.length,
217
228
  limit: maxBlobsPerBlock,
218
229
  });
@@ -271,8 +282,12 @@ export function verifyDataColumnSidecarInclusionProof(dataColumnSidecar: fulu.Da
271
282
  * Validate a subset of data column sidecars in a block
272
283
  *
273
284
  * Requires the block to be known to the node
285
+ *
286
+ * NOTE: chain is optional to skip signature verification. Helpful for testing purposes and so that can control whether
287
+ * signature gets checked depending on the reqresp method that is being checked
274
288
  */
275
289
  export async function validateBlockDataColumnSidecars(
290
+ chain: IBeaconChain | null,
276
291
  blockSlot: Slot,
277
292
  blockRoot: Root,
278
293
  blockBlobCount: number,
@@ -293,16 +308,16 @@ export async function validateBlockDataColumnSidecars(
293
308
  "Block has no blob commitments but data column sidecars were provided"
294
309
  );
295
310
  }
296
-
297
311
  // Hash the first sidecar block header and compare the rest via (cheaper) equality
298
- const firstSidecarBlockHeader = dataColumnSidecars[0].signedBlockHeader.message;
312
+ const firstSidecarSignedBlockHeader = dataColumnSidecars[0].signedBlockHeader;
313
+ const firstSidecarBlockHeader = firstSidecarSignedBlockHeader.message;
299
314
  const firstBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(firstSidecarBlockHeader);
300
315
  if (Buffer.compare(blockRoot, firstBlockRoot) !== 0) {
301
316
  throw new DataColumnSidecarValidationError(
302
317
  {
303
318
  code: DataColumnSidecarErrorCode.INCORRECT_BLOCK,
304
319
  slot: blockSlot,
305
- columnIdx: 0,
320
+ columnIndex: 0,
306
321
  expected: toRootHex(blockRoot),
307
322
  actual: toRootHex(firstBlockRoot),
308
323
  },
@@ -310,6 +325,26 @@ export async function validateBlockDataColumnSidecars(
310
325
  );
311
326
  }
312
327
 
328
+ if (chain !== null) {
329
+ const headState = await chain.getHeadState();
330
+ const signatureSet = getBlockHeaderProposerSignatureSetByHeaderSlot(headState, firstSidecarSignedBlockHeader);
331
+
332
+ if (
333
+ !(await chain.bls.verifySignatureSets([signatureSet], {
334
+ batchable: true,
335
+ priority: true,
336
+ verifyOnMainThread: false,
337
+ }))
338
+ ) {
339
+ throw new DataColumnSidecarValidationError({
340
+ code: DataColumnSidecarErrorCode.PROPOSAL_SIGNATURE_INVALID,
341
+ blockRoot: toRootHex(blockRoot),
342
+ slot: blockSlot,
343
+ index: dataColumnSidecars[0].index,
344
+ });
345
+ }
346
+ }
347
+
313
348
  const commitments: Uint8Array[] = [];
314
349
  const cellIndices: number[] = [];
315
350
  const cells: Uint8Array[] = [];
@@ -317,33 +352,55 @@ export async function validateBlockDataColumnSidecars(
317
352
  for (let i = 0; i < dataColumnSidecars.length; i++) {
318
353
  const columnSidecar = dataColumnSidecars[i];
319
354
 
355
+ if (
356
+ i !== 0 &&
357
+ !ssz.phase0.SignedBeaconBlockHeader.equals(firstSidecarSignedBlockHeader, columnSidecar.signedBlockHeader)
358
+ ) {
359
+ throw new DataColumnSidecarValidationError({
360
+ code: DataColumnSidecarErrorCode.INCORRECT_HEADER_ROOT,
361
+ slot: blockSlot,
362
+ expected: toRootHex(blockRoot),
363
+ actual: toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(columnSidecar.signedBlockHeader.message)),
364
+ });
365
+ }
366
+
320
367
  if (columnSidecar.index >= NUMBER_OF_COLUMNS) {
321
368
  throw new DataColumnSidecarValidationError(
322
369
  {
323
370
  code: DataColumnSidecarErrorCode.INVALID_INDEX,
324
371
  slot: blockSlot,
325
- columnIdx: columnSidecar.index,
372
+ columnIndex: columnSidecar.index,
326
373
  },
327
374
  "DataColumnSidecar has invalid index"
328
375
  );
329
376
  }
330
377
 
331
- if (columnSidecar.kzgCommitments.length !== blockBlobCount) {
378
+ if (columnSidecar.column.length !== blockBlobCount) {
332
379
  throw new DataColumnSidecarValidationError({
333
- code: DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT,
380
+ code: DataColumnSidecarErrorCode.INCORRECT_CELL_COUNT,
334
381
  slot: blockSlot,
335
- columnIdx: columnSidecar.index,
382
+ columnIndex: columnSidecar.index,
336
383
  expected: blockBlobCount,
384
+ actual: columnSidecar.column.length,
385
+ });
386
+ }
387
+
388
+ if (columnSidecar.column.length !== columnSidecar.kzgCommitments.length) {
389
+ throw new DataColumnSidecarValidationError({
390
+ code: DataColumnSidecarErrorCode.INCORRECT_KZG_COMMITMENTS_COUNT,
391
+ slot: blockSlot,
392
+ columnIndex: columnSidecar.index,
393
+ expected: columnSidecar.column.length,
337
394
  actual: columnSidecar.kzgCommitments.length,
338
395
  });
339
396
  }
340
397
 
341
- if (columnSidecar.kzgProofs.length !== columnSidecar.kzgCommitments.length) {
398
+ if (columnSidecar.column.length !== columnSidecar.kzgProofs.length) {
342
399
  throw new DataColumnSidecarValidationError({
343
400
  code: DataColumnSidecarErrorCode.INCORRECT_KZG_PROOF_COUNT,
344
401
  slot: blockSlot,
345
- columnIdx: columnSidecar.index,
346
- expected: columnSidecar.kzgCommitments.length,
402
+ columnIndex: columnSidecar.index,
403
+ expected: columnSidecar.column.length,
347
404
  actual: columnSidecar.kzgProofs.length,
348
405
  });
349
406
  }
@@ -353,7 +410,7 @@ export async function validateBlockDataColumnSidecars(
353
410
  {
354
411
  code: DataColumnSidecarErrorCode.INCLUSION_PROOF_INVALID,
355
412
  slot: blockSlot,
356
- columnIdx: columnSidecar.index,
413
+ columnIndex: columnSidecar.index,
357
414
  },
358
415
  "DataColumnSidecar has invalid inclusion proof"
359
416
  );
@@ -98,8 +98,13 @@ const core = await NetworkCore.init({
98
98
  metricsRegistry: metricsRegister,
99
99
  events,
100
100
  clock,
101
- getReqRespHandler: (method) => (req, peerId) =>
102
- reqRespBridgeRespCaller.getAsyncIterable({method, req, peerId: peerIdToString(peerId)}),
101
+ getReqRespHandler: (method) => (req, peerId, peerClient) =>
102
+ reqRespBridgeRespCaller.getAsyncIterable({
103
+ method,
104
+ req,
105
+ peerId: peerIdToString(peerId),
106
+ peerClient,
107
+ }),
103
108
  activeValidatorCount: workerData.activeValidatorCount,
104
109
  initialStatus: workerData.initialStatus,
105
110
  initialCustodyGroupCount: workerData.initialCustodyGroupCount,
@@ -73,7 +73,7 @@ export class WorkerNetworkCore implements INetworkCore {
73
73
  // Handles ReqResp response from worker and calls async generator in main thread
74
74
  this.reqRespBridgeRespHandler = new AsyncIterableBridgeHandler(
75
75
  getReqRespBridgeRespEvents(this.reqRespBridgeEventBus),
76
- (data) => modules.getReqRespHandler(data.method)(data.req, peerIdFromString(data.peerId))
76
+ (data) => modules.getReqRespHandler(data.method)(data.req, peerIdFromString(data.peerId), data.peerClient)
77
77
  );
78
78
 
79
79
  wireEventsOnMainThread<NetworkEventData>(
@@ -29,7 +29,7 @@ export type NetworkEventData = {
29
29
  clientAgent: string;
30
30
  };
31
31
  [NetworkEvent.peerDisconnected]: {peer: PeerIdStr};
32
- [NetworkEvent.reqRespRequest]: {request: RequestTypedContainer; peer: PeerId};
32
+ [NetworkEvent.reqRespRequest]: {request: RequestTypedContainer; peer: PeerId; peerClient: string};
33
33
  [NetworkEvent.pendingGossipsubMessage]: PendingGossipsubMessage;
34
34
  [NetworkEvent.gossipMessageValidationResult]: {
35
35
  msgId: string;
@@ -296,6 +296,10 @@ export class Eth2Gossipsub extends GossipSub {
296
296
  // Get seenTimestamp before adding the message to the queue or add async delays
297
297
  const seenTimestampSec = Date.now() / 1000;
298
298
 
299
+ const peerIdStr = propagationSource.toString();
300
+ const clientAgent = this.peersData.getPeerKind(peerIdStr) ?? "Unknown";
301
+ const clientVersion = this.peersData.getAgentVersion(peerIdStr);
302
+
299
303
  // Use setTimeout to yield to the macro queue
300
304
  // Without this we'll have huge event loop lag
301
305
  // See https://github.com/ChainSafe/lodestar/issues/5604
@@ -305,7 +309,9 @@ export class Eth2Gossipsub extends GossipSub {
305
309
  msg,
306
310
  msgId,
307
311
  // Hot path, use cached .toString() version
308
- propagationSource: propagationSource.toString(),
312
+ propagationSource: peerIdStr,
313
+ clientVersion,
314
+ clientAgent,
309
315
  seenTimestampSec,
310
316
  startProcessUnixSec: null,
311
317
  });
@@ -150,6 +150,8 @@ export type GossipMessageInfo = {
150
150
  topic: GossipTopic;
151
151
  msg: Message;
152
152
  propagationSource: PeerIdStr;
153
+ clientAgent: string;
154
+ clientVersion: string;
153
155
  seenTimestampSec: number;
154
156
  msgSlot: Slot | null;
155
157
  indexed?: string;
@@ -391,8 +391,8 @@ export class PeerDiscovery {
391
391
  // tcp multiaddr is known to be be present, checked inside the worker
392
392
  const multiaddrTCP = enr.getLocationMultiaddr(ENRKey.tcp);
393
393
  if (!multiaddrTCP) {
394
- this.logger.error("Discv5 worker sent enr without tcp multiaddr", {enr: enr.encodeTxt()});
395
- this.metrics?.discovery.discoveredStatus.inc({status: DiscoveredPeerStatus.error});
394
+ this.logger.warn("Discv5 worker sent enr without tcp multiaddr", {enr: enr.encodeTxt()});
395
+ this.metrics?.discovery.discoveredStatus.inc({status: DiscoveredPeerStatus.no_multiaddrs});
396
396
  return;
397
397
  }
398
398
  // Are this fields mandatory?
@@ -296,6 +296,21 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
296
296
  const slot = dataColumnBlockHeader.slot;
297
297
  const blockRootHex = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(dataColumnBlockHeader));
298
298
 
299
+ // check to see if block has already been processed and BlockInput has been deleted (column received via reqresp or other means)
300
+ if (chain.forkChoice.hasBlockHex(blockRootHex)) {
301
+ metrics?.peerDas.dataColumnSidecarProcessingSkip.inc();
302
+ logger.debug("Already processed block for column sidecar, skipping processing", {
303
+ slot,
304
+ blockRoot: blockRootHex,
305
+ index: dataColumnSidecar.index,
306
+ });
307
+ throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
308
+ code: DataColumnSidecarErrorCode.ALREADY_KNOWN,
309
+ columnIndex: dataColumnSidecar.index,
310
+ slot,
311
+ });
312
+ }
313
+
299
314
  // first check if we should even process this column (we may have already processed it via getBlobsV2)
300
315
  {
301
316
  const blockInput = chain.seenBlockInputCache.get(blockRootHex);
@@ -307,7 +322,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
307
322
  });
308
323
  throw new DataColumnSidecarGossipError(GossipAction.IGNORE, {
309
324
  code: DataColumnSidecarErrorCode.ALREADY_KNOWN,
310
- columnIdx: dataColumnSidecar.index,
325
+ columnIndex: dataColumnSidecar.index,
311
326
  slot,
312
327
  });
313
328
  }
@@ -11,6 +11,7 @@ import {
11
11
  GossipValidatorBatchFn,
12
12
  GossipValidatorFn,
13
13
  } from "../gossip/interface.js";
14
+ import {prettyPrintPeerIdStr} from "../util.ts";
14
15
 
15
16
  export type ValidatorFnModules = {
16
17
  config: ChainForkConfig;
@@ -45,13 +46,19 @@ export function getGossipValidatorBatchFn(
45
46
  }))
46
47
  );
47
48
 
48
- return results.map((e) => {
49
+ return results.map((e, i) => {
49
50
  if (e == null) {
50
51
  return TopicValidatorResult.Accept;
51
52
  }
52
53
 
54
+ const {clientAgent, clientVersion, propagationSource} = messageInfos[i];
55
+
53
56
  if (!(e instanceof AttestationError)) {
54
- logger.debug(`Gossip batch validation ${type} threw a non-AttestationError`, {}, e as Error);
57
+ logger.debug(
58
+ `Gossip batch validation ${type} threw a non-AttestationError`,
59
+ {peerId: prettyPrintPeerIdStr(propagationSource), clientAgent, clientVersion},
60
+ e as Error
61
+ );
55
62
  metrics?.networkProcessor.gossipValidationIgnore.inc({topic: type});
56
63
  return TopicValidatorResult.Ignore;
57
64
  }
@@ -66,7 +73,11 @@ export function getGossipValidatorBatchFn(
66
73
  metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
67
74
  // only beacon_attestation topic is validated in batch
68
75
  metrics?.networkProcessor.gossipAttestationRejectByReason.inc({reason: e.type.code});
69
- logger.debug(`Gossip validation ${type} rejected`, {}, e);
76
+ logger.debug(
77
+ `Gossip validation ${type} rejected`,
78
+ {peerId: prettyPrintPeerIdStr(propagationSource), clientAgent, clientVersion},
79
+ e
80
+ );
70
81
  return TopicValidatorResult.Reject;
71
82
  }
72
83
  });
@@ -99,7 +110,15 @@ export function getGossipValidatorBatchFn(
99
110
  export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: ValidatorFnModules): GossipValidatorFn {
100
111
  const {logger, metrics} = modules;
101
112
 
102
- return async function gossipValidatorFn({topic, msg, propagationSource, seenTimestampSec, msgSlot}) {
113
+ return async function gossipValidatorFn({
114
+ topic,
115
+ msg,
116
+ propagationSource,
117
+ clientAgent,
118
+ clientVersion,
119
+ seenTimestampSec,
120
+ msgSlot,
121
+ }) {
103
122
  const type = topic.type;
104
123
 
105
124
  try {
@@ -116,7 +135,11 @@ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: Va
116
135
  } catch (e) {
117
136
  if (!(e instanceof GossipActionError)) {
118
137
  // not deserve to log error here, it looks too dangerous to users
119
- logger.debug(`Gossip validation ${type} threw a non-GossipActionError`, {}, e as Error);
138
+ logger.debug(
139
+ `Gossip validation ${type} threw a non-GossipActionError`,
140
+ {peerId: prettyPrintPeerIdStr(propagationSource), clientAgent, clientVersion},
141
+ e as Error
142
+ );
120
143
  return TopicValidatorResult.Ignore;
121
144
  }
122
145
 
@@ -134,7 +157,11 @@ export function getGossipValidatorFn(gossipHandlers: GossipHandlers, modules: Va
134
157
 
135
158
  case GossipAction.REJECT:
136
159
  metrics?.networkProcessor.gossipValidationReject.inc({topic: type});
137
- logger.debug(`Gossip validation ${type} rejected`, {}, e);
160
+ logger.debug(
161
+ `Gossip validation ${type} rejected`,
162
+ {peerId: prettyPrintPeerIdStr(propagationSource), clientAgent, clientVersion},
163
+ e
164
+ );
138
165
  return TopicValidatorResult.Reject;
139
166
  }
140
167
  }
@@ -15,6 +15,8 @@ export type PendingGossipsubMessage = {
15
15
  msgSlot?: Slot;
16
16
  msgId: string;
17
17
  propagationSource: PeerIdStr;
18
+ clientAgent: string;
19
+ clientVersion: string;
18
20
  seenTimestampSec: number;
19
21
  startProcessUnixSec: number | null;
20
22
  // specific properties for IndexedGossipQueueMinSize, for beacon_attestation topic only
@@ -19,6 +19,7 @@ import {callInNextEventLoop} from "../../util/eventLoop.js";
19
19
  import {NetworkCoreMetrics} from "../core/metrics.js";
20
20
  import {INetworkEventBus, NetworkEvent} from "../events.js";
21
21
  import {MetadataController} from "../metadata.js";
22
+ import {ClientKind} from "../peers/client.ts";
22
23
  import {PeersData} from "../peers/peersData.js";
23
24
  import {IPeerRpcScoreStore, PeerAction} from "../peers/score/index.js";
24
25
  import {StatusCache} from "../statusCache.js";
@@ -300,10 +301,11 @@ export class ReqRespBeaconNode extends ReqResp {
300
301
  }
301
302
 
302
303
  protected onIncomingRequestBody(request: RequestTypedContainer, peer: PeerId): void {
304
+ const peerClient = this.peersData.getPeerKind(peer.toString()) ?? ClientKind.Unknown;
303
305
  // Allow onRequest to return and close the stream
304
306
  // For Goodbye there may be a race condition where the listener of `receivedGoodbye`
305
307
  // disconnects in the same synchronous call, preventing the stream from ending cleanly
306
- callInNextEventLoop(() => this.networkEventBus.emit(NetworkEvent.reqRespRequest, {request, peer}));
308
+ callInNextEventLoop(() => this.networkEventBus.emit(NetworkEvent.reqRespRequest, {request, peer, peerClient}));
307
309
  }
308
310
 
309
311
  protected onIncomingRequest(peerId: PeerId, protocol: ProtocolDescriptor): void {
@@ -1,18 +1,22 @@
1
+ import {PeerId} from "@libp2p/interface";
1
2
  import {BeaconConfig} from "@lodestar/config";
2
- import {GENESIS_SLOT, isForkPostDeneb} from "@lodestar/params";
3
+ import {GENESIS_SLOT, isForkPostDeneb, isForkPostFulu} from "@lodestar/params";
3
4
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
4
5
  import {computeEpochAtSlot} from "@lodestar/state-transition";
5
6
  import {deneb, phase0} from "@lodestar/types";
6
7
  import {fromHex} from "@lodestar/utils";
7
8
  import {IBeaconChain} from "../../../chain/index.js";
8
9
  import {IBeaconDb} from "../../../db/index.js";
10
+ import {prettyPrintPeerId} from "../../util.ts";
9
11
 
10
12
  // TODO: Unit test
11
13
 
12
14
  export async function* onBeaconBlocksByRange(
13
15
  request: phase0.BeaconBlocksByRangeRequest,
14
16
  chain: IBeaconChain,
15
- db: IBeaconDb
17
+ db: IBeaconDb,
18
+ peerId: PeerId,
19
+ peerClient: string
16
20
  ): AsyncIterable<ResponseOutgoing> {
17
21
  const {startSlot, count} = validateBeaconBlocksByRangeRequest(chain.config, request);
18
22
  const endSlot = startSlot + count;
@@ -23,6 +27,15 @@ export async function* onBeaconBlocksByRange(
23
27
  // chain.forkChoice.getFinalizeBlock().slot
24
28
  const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();
25
29
 
30
+ const forkName = chain.config.getForkName(startSlot);
31
+ if (isForkPostFulu(forkName) && startSlot < chain.earliestAvailableSlot) {
32
+ chain.logger.verbose("Peer did not respect earliestAvailableSlot for BeaconBlocksByRange", {
33
+ peer: prettyPrintPeerId(peerId),
34
+ client: peerClient,
35
+ });
36
+ return;
37
+ }
38
+
26
39
  // Finalized range of blocks
27
40
  if (startSlot <= finalizedSlot) {
28
41
  // Chain of blobs won't change
@@ -1,3 +1,4 @@
1
+ import {PeerId} from "@libp2p/interface";
1
2
  import {ChainConfig} from "@lodestar/config";
2
3
  import {GENESIS_SLOT} from "@lodestar/params";
3
4
  import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
@@ -6,6 +7,7 @@ import {ColumnIndex, fulu} from "@lodestar/types";
6
7
  import {fromHex} from "@lodestar/utils";
7
8
  import {IBeaconChain} from "../../../chain/index.js";
8
9
  import {IBeaconDb} from "../../../db/index.js";
10
+ import {prettyPrintPeerId} from "../../util.ts";
9
11
  import {
10
12
  handleColumnSidecarUnavailability,
11
13
  validateRequestedDataColumns,
@@ -14,7 +16,9 @@ import {
14
16
  export async function* onDataColumnSidecarsByRange(
15
17
  request: fulu.DataColumnSidecarsByRangeRequest,
16
18
  chain: IBeaconChain,
17
- db: IBeaconDb
19
+ db: IBeaconDb,
20
+ peerId: PeerId,
21
+ peerClient: string
18
22
  ): AsyncIterable<ResponseOutgoing> {
19
23
  // Non-finalized range of columns
20
24
  const {startSlot, count, columns: requestedColumns} = validateDataColumnSidecarsByRangeRequest(chain.config, request);
@@ -25,6 +29,14 @@ export async function* onDataColumnSidecarsByRange(
25
29
  return;
26
30
  }
27
31
 
32
+ if (startSlot < chain.earliestAvailableSlot) {
33
+ chain.logger.verbose("Peer did not respect earliestAvailableSlot for DataColumnSidecarsByRange", {
34
+ peer: prettyPrintPeerId(peerId),
35
+ client: peerClient,
36
+ });
37
+ return;
38
+ }
39
+
28
40
  const finalized = db.dataColumnSidecarArchive;
29
41
  const unfinalized = db.dataColumnSidecar;
30
42
  const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
@@ -1,3 +1,4 @@
1
+ import {PeerId} from "@libp2p/interface";
1
2
  import {ResponseOutgoing} from "@lodestar/reqresp";
2
3
  import {computeEpochAtSlot} from "@lodestar/state-transition";
3
4
  import {ColumnIndex} from "@lodestar/types";
@@ -5,6 +6,7 @@ import {toRootHex} from "@lodestar/utils";
5
6
  import {IBeaconChain} from "../../../chain/index.js";
6
7
  import {IBeaconDb} from "../../../db/index.js";
7
8
  import {DataColumnSidecarsByRootRequest} from "../../../util/types.js";
9
+ import {prettyPrintPeerId} from "../../util.ts";
8
10
  import {
9
11
  handleColumnSidecarUnavailability,
10
12
  validateRequestedDataColumns,
@@ -13,7 +15,9 @@ import {
13
15
  export async function* onDataColumnSidecarsByRoot(
14
16
  requestBody: DataColumnSidecarsByRootRequest,
15
17
  chain: IBeaconChain,
16
- db: IBeaconDb
18
+ db: IBeaconDb,
19
+ peerId: PeerId,
20
+ peerClient: string
17
21
  ): AsyncIterable<ResponseOutgoing> {
18
22
  // SPEC: minimum_request_epoch = max(current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, FULU_FORK_EPOCH)
19
23
  const currentEpoch = chain.clock.currentEpoch;
@@ -39,6 +43,14 @@ export async function* onDataColumnSidecarsByRoot(
39
43
  continue;
40
44
  }
41
45
 
46
+ if (slot < chain.earliestAvailableSlot) {
47
+ chain.logger.verbose("Peer did not respect earliestAvailableSlot for DataColumnSidecarsByRoot", {
48
+ peer: prettyPrintPeerId(peerId),
49
+ client: peerClient,
50
+ });
51
+ continue;
52
+ }
53
+
42
54
  const requestedEpoch = computeEpochAtSlot(slot);
43
55
 
44
56
  // SPEC: Clients MUST support requesting sidecars since minimum_request_epoch.
@@ -35,9 +35,9 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh
35
35
  [ReqRespMethod.Goodbye]: notImplemented(ReqRespMethod.Goodbye),
36
36
  [ReqRespMethod.Ping]: notImplemented(ReqRespMethod.Ping),
37
37
  [ReqRespMethod.Metadata]: notImplemented(ReqRespMethod.Metadata),
38
- [ReqRespMethod.BeaconBlocksByRange]: (req) => {
38
+ [ReqRespMethod.BeaconBlocksByRange]: (req, peerId, peerClient) => {
39
39
  const body = ssz.phase0.BeaconBlocksByRangeRequest.deserialize(req.data);
40
- return onBeaconBlocksByRange(body, chain, db);
40
+ return onBeaconBlocksByRange(body, chain, db, peerId, peerClient);
41
41
  },
42
42
  [ReqRespMethod.BeaconBlocksByRoot]: (req) => {
43
43
  const fork = chain.config.getForkName(chain.clock.currentSlot);
@@ -53,13 +53,13 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh
53
53
  const body = ssz.deneb.BlobSidecarsByRangeRequest.deserialize(req.data);
54
54
  return onBlobSidecarsByRange(body, chain, db);
55
55
  },
56
- [ReqRespMethod.DataColumnSidecarsByRange]: (req) => {
56
+ [ReqRespMethod.DataColumnSidecarsByRange]: (req, peerId, peerClient) => {
57
57
  const body = ssz.fulu.DataColumnSidecarsByRangeRequest.deserialize(req.data);
58
- return onDataColumnSidecarsByRange(body, chain, db);
58
+ return onDataColumnSidecarsByRange(body, chain, db, peerId, peerClient);
59
59
  },
60
- [ReqRespMethod.DataColumnSidecarsByRoot]: (req) => {
60
+ [ReqRespMethod.DataColumnSidecarsByRoot]: (req, peerId, peerClient) => {
61
61
  const body = DataColumnSidecarsByRootRequestType(chain.config).deserialize(req.data);
62
- return onDataColumnSidecarsByRoot(body, chain, db);
62
+ return onDataColumnSidecarsByRoot(body, chain, db, peerId, peerClient);
63
63
  },
64
64
 
65
65
  [ReqRespMethod.LightClientBootstrap]: (req) => {
@@ -169,6 +169,7 @@ export type IncomingRequestArgs = {
169
169
  method: ReqRespMethod;
170
170
  req: ReqRespRequest;
171
171
  peerId: string;
172
+ peerClient: string;
172
173
  };
173
174
 
174
175
  export type GetReqRespHandlerFn = (method: ReqRespMethod) => ProtocolHandler;
@@ -532,7 +532,7 @@ export class BlockInputSync {
532
532
  const downloadResult = await downloadByRoot({
533
533
  config: this.config,
534
534
  network: this.network,
535
- seenCache: this.chain.seenBlockInputCache,
535
+ chain: this.chain,
536
536
  emitter: this.chain.emitter,
537
537
  peerMeta,
538
538
  cacheItem,