@lodestar/beacon-node 1.41.0-dev.ef310100c0 → 1.41.0-dev.f2caa915ab

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 (118) hide show
  1. package/lib/api/impl/debug/index.d.ts.map +1 -1
  2. package/lib/api/impl/debug/index.js +1 -0
  3. package/lib/api/impl/debug/index.js.map +1 -1
  4. package/lib/api/impl/node/utils.d.ts +1 -1
  5. package/lib/api/impl/node/utils.d.ts.map +1 -1
  6. package/lib/api/impl/node/utils.js.map +1 -1
  7. package/lib/chain/blocks/blockInput/blockInput.d.ts +20 -2
  8. package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
  9. package/lib/chain/blocks/blockInput/blockInput.js +47 -0
  10. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  11. package/lib/chain/blocks/blockInput/types.d.ts +2 -1
  12. package/lib/chain/blocks/blockInput/types.d.ts.map +1 -1
  13. package/lib/chain/blocks/blockInput/types.js +1 -0
  14. package/lib/chain/blocks/blockInput/types.js.map +1 -1
  15. package/lib/chain/blocks/verifyBlocksDataAvailability.d.ts.map +1 -1
  16. package/lib/chain/blocks/verifyBlocksDataAvailability.js +3 -0
  17. package/lib/chain/blocks/verifyBlocksDataAvailability.js.map +1 -1
  18. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +4 -0
  19. package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
  20. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -1
  21. package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
  22. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  23. package/lib/chain/seenCache/seenGossipBlockInput.js +15 -7
  24. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  25. package/lib/db/repositories/blockArchive.d.ts.map +1 -1
  26. package/lib/db/repositories/blockArchive.js +1 -2
  27. package/lib/db/repositories/blockArchive.js.map +1 -1
  28. package/lib/network/core/networkCore.d.ts +3 -3
  29. package/lib/network/core/networkCore.d.ts.map +1 -1
  30. package/lib/network/core/networkCore.js.map +1 -1
  31. package/lib/network/core/networkCoreWorkerHandler.d.ts +3 -3
  32. package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
  33. package/lib/network/core/types.d.ts +2 -2
  34. package/lib/network/core/types.d.ts.map +1 -1
  35. package/lib/network/events.d.ts +2 -1
  36. package/lib/network/events.d.ts.map +1 -1
  37. package/lib/network/events.js.map +1 -1
  38. package/lib/network/gossip/encoding.d.ts +3 -3
  39. package/lib/network/gossip/encoding.d.ts.map +1 -1
  40. package/lib/network/gossip/encoding.js.map +1 -1
  41. package/lib/network/gossip/gossipsub.d.ts +13 -4
  42. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  43. package/lib/network/gossip/gossipsub.js +47 -20
  44. package/lib/network/gossip/gossipsub.js.map +1 -1
  45. package/lib/network/gossip/interface.d.ts +3 -3
  46. package/lib/network/gossip/interface.d.ts.map +1 -1
  47. package/lib/network/gossip/scoringParameters.d.ts +1 -1
  48. package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
  49. package/lib/network/gossip/scoringParameters.js +1 -1
  50. package/lib/network/gossip/scoringParameters.js.map +1 -1
  51. package/lib/network/interface.d.ts +3 -3
  52. package/lib/network/interface.d.ts.map +1 -1
  53. package/lib/network/libp2p/index.d.ts +1 -1
  54. package/lib/network/libp2p/index.d.ts.map +1 -1
  55. package/lib/network/libp2p/index.js +7 -2
  56. package/lib/network/libp2p/index.js.map +1 -1
  57. package/lib/network/network.d.ts +2 -2
  58. package/lib/network/network.d.ts.map +1 -1
  59. package/lib/network/network.js.map +1 -1
  60. package/lib/network/options.d.ts.map +1 -1
  61. package/lib/network/options.js +3 -0
  62. package/lib/network/options.js.map +1 -1
  63. package/lib/network/peers/datastore.d.ts +7 -5
  64. package/lib/network/peers/datastore.d.ts.map +1 -1
  65. package/lib/network/peers/datastore.js +10 -10
  66. package/lib/network/peers/datastore.js.map +1 -1
  67. package/lib/network/peers/peerManager.d.ts +1 -0
  68. package/lib/network/peers/peerManager.d.ts.map +1 -1
  69. package/lib/network/peers/peerManager.js +51 -29
  70. package/lib/network/peers/peerManager.js.map +1 -1
  71. package/lib/network/peers/utils/prioritizePeers.d.ts +3 -3
  72. package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
  73. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  74. package/lib/network/processor/gossipHandlers.js +3 -1
  75. package/lib/network/processor/gossipHandlers.js.map +1 -1
  76. package/lib/network/processor/gossipValidatorFn.js +1 -1
  77. package/lib/network/processor/types.d.ts +1 -1
  78. package/lib/network/processor/types.d.ts.map +1 -1
  79. package/lib/network/reqresp/score.d.ts.map +1 -1
  80. package/lib/network/reqresp/score.js +0 -1
  81. package/lib/network/reqresp/score.js.map +1 -1
  82. package/lib/network/util.js +2 -2
  83. package/lib/network/util.js.map +1 -1
  84. package/lib/util/clock.d.ts +6 -0
  85. package/lib/util/clock.d.ts.map +1 -1
  86. package/lib/util/clock.js +9 -3
  87. package/lib/util/clock.js.map +1 -1
  88. package/package.json +38 -41
  89. package/src/api/impl/debug/index.ts +1 -0
  90. package/src/api/impl/node/utils.ts +3 -3
  91. package/src/chain/blocks/blockInput/blockInput.ts +68 -3
  92. package/src/chain/blocks/blockInput/types.ts +1 -0
  93. package/src/chain/blocks/verifyBlocksDataAvailability.ts +3 -0
  94. package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +9 -2
  95. package/src/chain/seenCache/seenGossipBlockInput.ts +16 -7
  96. package/src/db/repositories/blockArchive.ts +1 -2
  97. package/src/network/core/networkCore.ts +3 -3
  98. package/src/network/core/networkCoreWorkerHandler.ts +3 -3
  99. package/src/network/core/types.ts +2 -2
  100. package/src/network/events.ts +2 -1
  101. package/src/network/gossip/encoding.ts +3 -3
  102. package/src/network/gossip/gossipsub.ts +86 -25
  103. package/src/network/gossip/interface.ts +3 -3
  104. package/src/network/gossip/scoringParameters.ts +4 -4
  105. package/src/network/interface.ts +3 -3
  106. package/src/network/libp2p/index.ts +8 -3
  107. package/src/network/network.ts +3 -3
  108. package/src/network/options.ts +3 -0
  109. package/src/network/peers/datastore.ts +13 -10
  110. package/src/network/peers/peerManager.ts +56 -30
  111. package/src/network/peers/utils/prioritizePeers.ts +3 -3
  112. package/src/network/processor/gossipHandlers.ts +7 -3
  113. package/src/network/processor/gossipValidatorFn.ts +1 -1
  114. package/src/network/processor/types.ts +1 -1
  115. package/src/network/reqresp/score.ts +0 -1
  116. package/src/network/util.ts +2 -2
  117. package/src/util/clock.ts +9 -4
  118. package/src/util/workerEvents.ts +1 -1
@@ -1,10 +1,18 @@
1
+ import {
2
+ type GossipSub,
3
+ type GossipSubEvents,
4
+ type PublishResult,
5
+ StrictNoSign,
6
+ type TopicValidatorResult,
7
+ gossipsub,
8
+ } from "@libp2p/gossipsub";
9
+ import type {MetricsRegister, TopicLabel, TopicStrToLabel} from "@libp2p/gossipsub/metrics";
10
+ import type {PeerScoreParams, PeerScoreStatsDump} from "@libp2p/gossipsub/score";
11
+ import type {AddrInfo, PublishOpts, TopicStr} from "@libp2p/gossipsub/types";
12
+ import type {PeerId} from "@libp2p/interface";
1
13
  import {peerIdFromString} from "@libp2p/peer-id";
2
14
  import {multiaddr} from "@multiformats/multiaddr";
3
15
  import {ENR} from "@chainsafe/enr";
4
- import {GossipSub, GossipsubEvents} from "@chainsafe/libp2p-gossipsub";
5
- import {MetricsRegister, TopicLabel, TopicStrToLabel} from "@chainsafe/libp2p-gossipsub/metrics";
6
- import {PeerScoreParams} from "@chainsafe/libp2p-gossipsub/score";
7
- import {AddrInfo, SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
8
16
  import {routes} from "@lodestar/api";
9
17
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
10
18
  import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
@@ -69,6 +77,24 @@ export type Eth2GossipsubOpts = {
69
77
 
70
78
  export type ForkBoundaryLabel = string;
71
79
 
80
+ // Many of the internal properties we need are not available on the public interface,
81
+ // so we create an extended type here to avoid excessive type assertions throughout the codebase.
82
+ // Mind that any updates to the gossipsub package may require updates to this type.
83
+ type GossipSubInternal = GossipSub & {
84
+ mesh: Map<string, Set<string>>;
85
+ peers: Map<string, PeerId>;
86
+ score: {score: (peerIdStr: string) => number};
87
+ direct: Set<string>;
88
+ topics: Map<string, Set<string>>;
89
+ start: () => Promise<void>;
90
+ stop: () => Promise<void>;
91
+ publish: (topic: TopicStr, data: Uint8Array, opts?: PublishOpts) => Promise<PublishResult>;
92
+ getMeshPeers: (topic: TopicStr) => string[];
93
+ dumpPeerScoreStats: () => PeerScoreStatsDump;
94
+ getScore: (peerIdStr: string) => number;
95
+ reportMessageValidationResult: (msgId: string, propagationSource: string, acceptance: TopicValidatorResult) => void;
96
+ };
97
+
72
98
  /**
73
99
  * Wrapper around js-libp2p-gossipsub with the following extensions:
74
100
  * - Eth2 message id
@@ -82,13 +108,14 @@ export type ForkBoundaryLabel = string;
82
108
  *
83
109
  * See https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#the-gossip-domain-gossipsub
84
110
  */
85
- export class Eth2Gossipsub extends GossipSub {
111
+ export class Eth2Gossipsub {
86
112
  readonly scoreParams: Partial<PeerScoreParams>;
87
113
  private readonly config: BeaconConfig;
88
114
  private readonly logger: Logger;
89
115
  private readonly peersData: PeersData;
90
116
  private readonly events: NetworkEventBus;
91
117
  private readonly libp2p: Libp2p;
118
+ private readonly gossipsub: GossipSubInternal;
92
119
 
93
120
  // Internal caches
94
121
  private readonly gossipTopicCache: GossipTopicCache;
@@ -103,9 +130,6 @@ export class Eth2Gossipsub extends GossipSub {
103
130
  let metrics: Eth2GossipsubMetrics | null = null;
104
131
  if (metricsRegister) {
105
132
  metrics = createEth2GossipsubMetrics(metricsRegister);
106
- metrics.gossipMesh.peersByType.addCollect(() =>
107
- this.onScrapeLodestarMetrics(metrics as Eth2GossipsubMetrics, networkConfig)
108
- );
109
133
  }
110
134
 
111
135
  // Parse direct peers from multiaddr strings to AddrInfo objects
@@ -113,8 +137,8 @@ export class Eth2Gossipsub extends GossipSub {
113
137
 
114
138
  // Gossipsub parameters defined here:
115
139
  // https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#the-gossip-domain-gossipsub
116
- super(modules.libp2p.services.components, {
117
- globalSignaturePolicy: SignaturePolicy.StrictNoSign,
140
+ const gossipsubInstance = gossipsub({
141
+ globalSignaturePolicy: StrictNoSign,
118
142
  allowPublishToZeroTopicPeers: allowPublishToZeroPeers,
119
143
  D: gossipsubD ?? GOSSIP_D,
120
144
  Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
@@ -155,7 +179,12 @@ export class Eth2Gossipsub extends GossipSub {
155
179
  // This should be large enough to not send IDONTWANT for "small" messages
156
180
  // See https://github.com/ChainSafe/lodestar/pull/7077#issuecomment-2383679472
157
181
  idontwantMinDataSize: 16829,
158
- });
182
+ })(modules.libp2p.services.components) as GossipSubInternal;
183
+
184
+ if (metrics) {
185
+ metrics.gossipMesh.peersByType.addCollect(() => this.onScrapeLodestarMetrics(metrics, networkConfig));
186
+ }
187
+ this.gossipsub = gossipsubInstance;
159
188
  this.scoreParams = scoreParams;
160
189
  this.config = config;
161
190
  this.logger = logger;
@@ -164,7 +193,7 @@ export class Eth2Gossipsub extends GossipSub {
164
193
  this.libp2p = modules.libp2p;
165
194
  this.gossipTopicCache = gossipTopicCache;
166
195
 
167
- this.addEventListener("gossipsub:message", this.onGossipsubMessage.bind(this));
196
+ this.gossipsub.addEventListener("gossipsub:message", this.onGossipsubMessage.bind(this));
168
197
  this.events.on(NetworkEvent.gossipMessageValidationResult, this.onValidationResult.bind(this));
169
198
 
170
199
  // Having access to this data is CRUCIAL for debugging. While this is a massive log, it must not be deleted.
@@ -174,6 +203,38 @@ export class Eth2Gossipsub extends GossipSub {
174
203
  }
175
204
  }
176
205
 
206
+ async start(): Promise<void> {
207
+ await this.gossipsub.start();
208
+ }
209
+
210
+ async stop(): Promise<void> {
211
+ await this.gossipsub.stop();
212
+ }
213
+
214
+ get mesh(): Map<string, Set<string>> {
215
+ return this.gossipsub.mesh;
216
+ }
217
+
218
+ getTopics(): TopicStr[] {
219
+ return this.gossipsub.getTopics();
220
+ }
221
+
222
+ getMeshPeers(topic: TopicStr): string[] {
223
+ return this.gossipsub.getMeshPeers(topic);
224
+ }
225
+
226
+ publish(topic: TopicStr, data: Uint8Array, opts?: PublishOpts): Promise<PublishResult> {
227
+ return this.gossipsub.publish(topic, data, opts);
228
+ }
229
+
230
+ dumpPeerScoreStats(): PeerScoreStatsDump {
231
+ return this.gossipsub.dumpPeerScoreStats();
232
+ }
233
+
234
+ getScore(peerIdStr: string): number {
235
+ return this.gossipsub.getScore(peerIdStr);
236
+ }
237
+
177
238
  /**
178
239
  * Subscribe to a `GossipTopic`
179
240
  */
@@ -183,7 +244,7 @@ export class Eth2Gossipsub extends GossipSub {
183
244
  this.gossipTopicCache.setTopic(topicStr, topic);
184
245
 
185
246
  this.logger.verbose("Subscribe to gossipsub topic", {topic: topicStr});
186
- this.subscribe(topicStr);
247
+ this.gossipsub.subscribe(topicStr);
187
248
  }
188
249
 
189
250
  /**
@@ -192,15 +253,14 @@ export class Eth2Gossipsub extends GossipSub {
192
253
  unsubscribeTopic(topic: GossipTopic): void {
193
254
  const topicStr = stringifyGossipTopic(this.config, topic);
194
255
  this.logger.verbose("Unsubscribe to gossipsub topic", {topic: topicStr});
195
- this.unsubscribe(topicStr);
256
+ this.gossipsub.unsubscribe(topicStr);
196
257
  }
197
258
 
198
259
  private onScrapeLodestarMetrics(metrics: Eth2GossipsubMetrics, networkConfig: NetworkConfig): void {
199
- const mesh = this.mesh;
200
- // biome-ignore lint/complexity/useLiteralKeys: `topics` is a private attribute
201
- const topics = this["topics"] as Map<string, Set<string>>;
202
- const peers = this.peers;
203
- const score = this.score;
260
+ const mesh = this.gossipsub.mesh;
261
+ const topics = this.gossipsub.topics;
262
+ const peers = this.gossipsub.peers;
263
+ const score = this.gossipsub.score;
204
264
  const meshPeersByClient = new Map<string, number>();
205
265
  const meshPeerIdStrs = new Set<string>();
206
266
 
@@ -305,7 +365,7 @@ export class Eth2Gossipsub extends GossipSub {
305
365
  metrics.gossipPeer.score.set(gossipScores);
306
366
  }
307
367
 
308
- private onGossipsubMessage(event: GossipsubEvents["gossipsub:message"]): void {
368
+ private onGossipsubMessage(event: GossipSubEvents["gossipsub:message"]): void {
309
369
  const {propagationSource, msgId, msg} = event.detail;
310
370
 
311
371
  // Also validates that the topicStr is known
@@ -341,7 +401,7 @@ export class Eth2Gossipsub extends GossipSub {
341
401
  // Without this we'll have huge event loop lag
342
402
  // See https://github.com/ChainSafe/lodestar/issues/5604
343
403
  callInNextEventLoop(() => {
344
- this.reportMessageValidationResult(data.msgId, data.propagationSource, data.acceptance);
404
+ this.gossipsub.reportMessageValidationResult(data.msgId, data.propagationSource, data.acceptance);
345
405
  });
346
406
  }
347
407
 
@@ -379,7 +439,7 @@ export class Eth2Gossipsub extends GossipSub {
379
439
  }
380
440
 
381
441
  // Add to direct peers set only after addresses are stored
382
- this.direct.add(peerIdStr);
442
+ this.gossipsub.direct.add(peerIdStr);
383
443
 
384
444
  this.logger.info("Added direct peer via API", {peerId: peerIdStr});
385
445
  return peerIdStr;
@@ -389,7 +449,7 @@ export class Eth2Gossipsub extends GossipSub {
389
449
  * Remove a peer from direct peers.
390
450
  */
391
451
  removeDirectPeer(peerIdStr: string): boolean {
392
- const removed = this.direct.delete(peerIdStr);
452
+ const removed = this.gossipsub.direct.delete(peerIdStr);
393
453
  if (removed) {
394
454
  this.logger.info("Removed direct peer via API", {peerId: peerIdStr});
395
455
  }
@@ -400,7 +460,7 @@ export class Eth2Gossipsub extends GossipSub {
400
460
  * Get list of current direct peer IDs.
401
461
  */
402
462
  getDirectPeers(): string[] {
403
- return Array.from(this.direct);
463
+ return Array.from(this.gossipsub.direct);
404
464
  }
405
465
  }
406
466
 
@@ -498,7 +558,8 @@ export function parseDirectPeers(directPeerStrs: routes.lodestar.DirectPeer[], l
498
558
  try {
499
559
  const ma = multiaddr(peerStr);
500
560
 
501
- const peerIdStr = ma.getPeerId();
561
+ const peerIdComponent = ma.getComponents().findLast((component) => component.name === "p2p");
562
+ const peerIdStr = peerIdComponent?.value;
502
563
  if (!peerIdStr) {
503
564
  logger.warn("Direct peer multiaddr must contain /p2p/ component with peer ID", {multiaddr: peerStr});
504
565
  continue;
@@ -1,6 +1,6 @@
1
- import {Message, TopicValidatorResult} from "@libp2p/interface";
2
- import {Libp2p} from "libp2p";
3
- import {PeerIdStr} from "@chainsafe/libp2p-gossipsub/types";
1
+ import type {Message, TopicValidatorResult} from "@libp2p/gossipsub";
2
+ import type {PeerIdStr} from "@libp2p/gossipsub/types";
3
+ import type {Libp2p} from "libp2p";
4
4
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
5
5
  import {
6
6
  AttesterSlashing,
@@ -1,9 +1,9 @@
1
1
  import {
2
- PeerScoreParams,
3
- PeerScoreThresholds,
4
- TopicScoreParams,
2
+ type PeerScoreParams,
3
+ type PeerScoreThresholds,
4
+ type TopicScoreParams,
5
5
  defaultTopicScoreParams,
6
- } from "@chainsafe/libp2p-gossipsub/score";
6
+ } from "@libp2p/gossipsub/score";
7
7
  import {BeaconConfig} from "@lodestar/config";
8
8
  import {ATTESTATION_SUBNET_COUNT, PTC_SIZE, SLOTS_PER_EPOCH, TARGET_AGGREGATORS_PER_COMMITTEE} from "@lodestar/params";
9
9
  import {computeCommitteeCount} from "@lodestar/state-transition";
@@ -1,5 +1,5 @@
1
- import {Identify} from "@libp2p/identify";
2
- import {
1
+ import type {Identify} from "@libp2p/identify";
2
+ import type {
3
3
  ComponentLogger,
4
4
  ConnectionGater,
5
5
  ConnectionProtector,
@@ -16,7 +16,7 @@ import {
16
16
  } from "@libp2p/interface";
17
17
  import type {AddressManager, ConnectionManager, Registrar, TransportManager} from "@libp2p/interface-internal";
18
18
  import type {Datastore} from "interface-datastore";
19
- import {Libp2p as ILibp2p} from "libp2p";
19
+ import type {Libp2p as ILibp2p} from "libp2p";
20
20
  import {
21
21
  AttesterSlashing,
22
22
  DataColumnSidecar,
@@ -1,6 +1,6 @@
1
1
  import {bootstrap} from "@libp2p/bootstrap";
2
2
  import {identify} from "@libp2p/identify";
3
- import {PrivateKey} from "@libp2p/interface";
3
+ import type {PrivateKey} from "@libp2p/interface";
4
4
  import {mdns} from "@libp2p/mdns";
5
5
  import {mplex} from "@libp2p/mplex";
6
6
  import {prometheusMetrics} from "@libp2p/prometheus-metrics";
@@ -39,6 +39,7 @@ export async function createNodeJsLibp2p(
39
39
  nodeJsLibp2pOpts: NodeJsLibp2pOpts = {}
40
40
  ): Promise<Libp2p> {
41
41
  const localMultiaddrs = networkOpts.localMultiaddrs || defaultNetworkOptions.localMultiaddrs;
42
+ const disconnectThreshold = networkOpts.disconnectThreshold ?? defaultNetworkOptions.disconnectThreshold;
42
43
  const {peerStoreDir, disablePeerDiscovery} = nodeJsLibp2pOpts;
43
44
 
44
45
  let datastore: undefined | Eth2PeerDataStore = undefined;
@@ -74,6 +75,11 @@ export async function createNodeJsLibp2p(
74
75
 
75
76
  return createLibp2p({
76
77
  privateKey,
78
+ nodeInfo: {
79
+ name: "lodestar",
80
+ version: networkOpts.version ?? "unknown",
81
+ userAgent: networkOpts.private ? "" : networkOpts.version ? `lodestar/${networkOpts.version}` : "lodestar",
82
+ },
77
83
  addresses: {
78
84
  listen: localMultiaddrs,
79
85
  announce: [],
@@ -93,7 +99,7 @@ export async function createNodeJsLibp2p(
93
99
  },
94
100
  }),
95
101
  ],
96
- streamMuxers: [mplex({maxInboundStreams: 256, disconnectThreshold: networkOpts.disconnectThreshold})],
102
+ streamMuxers: [mplex({disconnectThreshold})],
97
103
  peerDiscovery,
98
104
  metrics: nodeJsLibp2pOpts.metrics
99
105
  ? prometheusMetrics({
@@ -124,7 +130,6 @@ export async function createNodeJsLibp2p(
124
130
  datastore,
125
131
  services: {
126
132
  identify: identify({
127
- agentVersion: networkOpts.private ? "" : networkOpts.version ? `lodestar/${networkOpts.version}` : "lodestar",
128
133
  runOnConnectionOpen: false,
129
134
  }),
130
135
  // individual components are specified because the components object is a Proxy
@@ -1,7 +1,7 @@
1
- import {PeerId, PrivateKey} from "@libp2p/interface";
1
+ import type {PeerScoreStatsDump} from "@libp2p/gossipsub/score";
2
+ import type {PublishOpts} from "@libp2p/gossipsub/types";
3
+ import type {PeerId, PrivateKey} from "@libp2p/interface";
2
4
  import {peerIdFromPrivateKey} from "@libp2p/peer-id";
3
- import {PeerScoreStatsDump} from "@chainsafe/libp2p-gossipsub/score";
4
- import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types";
5
5
  import {routes} from "@lodestar/api";
6
6
  import {BeaconConfig} from "@lodestar/config";
7
7
  import {LoggerNode} from "@lodestar/logger/node";
@@ -72,4 +72,7 @@ export const defaultNetworkOptions: NetworkOptions = {
72
72
  // - for fusaka-devnets, we have 25-30 peers per subnet
73
73
  // - for public testnets or mainnet, average number of peers per group is SAMPLES_PER_SLOT * targetPeers / NUMBER_OF_CUSTODY_GROUPS = 6.25 so this should not be an issue
74
74
  targetGroupPeers: 6,
75
+ // Keep this high enough for normal req/resp bursts on stable connections.
76
+ // libp2p-mplex default (5) is too low and can cause frequent connection resets.
77
+ disconnectThreshold: 50,
75
78
  };
@@ -8,6 +8,9 @@ type MemoryItem = {
8
8
  data: Uint8Array;
9
9
  };
10
10
 
11
+ // biome-ignore lint/suspicious/noExplicitAny: used below (copied from upstream)
12
+ type AwaitGenerator<T, TReturn = any, TNext = any> = Generator<T, TReturn, TNext> | AsyncGenerator<T, TReturn, TNext>;
13
+
11
14
  /**
12
15
  * Before libp2p 0.35, peerstore stays in memory and periodically write to db after n dirty items
13
16
  * This has a memory issue because all peer data stays in memory and loaded at startup time
@@ -93,7 +96,7 @@ export class Eth2PeerDataStore extends BaseDatastore {
93
96
  * This throws error if not found
94
97
  * see https://github.com/ipfs/js-datastore-level/blob/38f44058dd6be858e757a1c90b8edb31590ec0bc/src/index.js#L102
95
98
  */
96
- async get(key: Key): Promise<Uint8Array> {
99
+ async get(key: Key, options?: AbortOptions): Promise<Uint8Array> {
97
100
  const keyStr = key.toString();
98
101
  const memoryItem = this._memoryDatastore.get(keyStr);
99
102
  if (memoryItem) {
@@ -102,16 +105,16 @@ export class Eth2PeerDataStore extends BaseDatastore {
102
105
  }
103
106
 
104
107
  // this throws error if not found
105
- const dbValue = await this._dbDatastore.get(key);
108
+ const dbValue = await this._dbDatastore.get(key, options);
106
109
  // don't call this._memoryDatastore.set directly
107
110
  // we want to get through prune() logic with fromDb as true
108
111
  await this._put(key, dbValue, true);
109
112
  return dbValue;
110
113
  }
111
114
 
112
- async has(key: Key): Promise<boolean> {
115
+ async has(key: Key, options?: AbortOptions): Promise<boolean> {
113
116
  try {
114
- await this.get(key);
117
+ await this.get(key, options);
115
118
  } catch (err) {
116
119
  // this is the same to how js-datastore-level handles notFound error
117
120
  // https://github.com/ipfs/js-datastore-level/blob/38f44058dd6be858e757a1c90b8edb31590ec0bc/src/index.js#L121
@@ -121,26 +124,26 @@ export class Eth2PeerDataStore extends BaseDatastore {
121
124
  return true;
122
125
  }
123
126
 
124
- async delete(key: Key): Promise<void> {
127
+ async delete(key: Key, options?: AbortOptions): Promise<void> {
125
128
  this._memoryDatastore.delete(key.toString());
126
- await this._dbDatastore.delete(key);
129
+ await this._dbDatastore.delete(key, options);
127
130
  }
128
131
 
129
- async *_all(q: Query): AsyncIterable<Pair> {
132
+ async *_all(q: Query, options?: AbortOptions): AwaitGenerator<Pair> {
130
133
  for (const [key, value] of this._memoryDatastore.entries()) {
131
134
  yield {
132
135
  key: new Key(key),
133
136
  value: value.data,
134
137
  };
135
138
  }
136
- yield* this._dbDatastore.query(q);
139
+ yield* this._dbDatastore.query(q, options);
137
140
  }
138
141
 
139
- async *_allKeys(q: KeyQuery): AsyncIterable<Key> {
142
+ async *_allKeys(q: KeyQuery, options?: AbortOptions): AwaitGenerator<Key> {
140
143
  for (const key of this._memoryDatastore.keys()) {
141
144
  yield new Key(key);
142
145
  }
143
- yield* this._dbDatastore.queryKeys(q);
146
+ yield* this._dbDatastore.queryKeys(q, options);
144
147
  }
145
148
 
146
149
  private async _addDirtyItem(keyStr: string): Promise<void> {
@@ -162,7 +162,6 @@ export class PeerManager {
162
162
 
163
163
  // A single map of connected peers with all necessary data to handle PINGs, STATUS, and metrics
164
164
  private connectedPeers: Map<PeerIdStr, PeerData>;
165
-
166
165
  private opts: PeerManagerOpts;
167
166
  private intervals: NodeJS.Timeout[] = [];
168
167
 
@@ -480,6 +479,14 @@ export class PeerManager {
480
479
  clientAgent,
481
480
  custodyColumns,
482
481
  });
482
+
483
+ // Identify peer after status proves the connection is usable.
484
+ // This is the only place we trigger identify — avoids wasted streams on
485
+ // peers that close identify right after connection open or turn out to be
486
+ // irrelevant.
487
+ if (peerData?.agentVersion === null) {
488
+ void this.identifyPeer(peer.toString(), prettyPrintPeerId(peer), getConnection(this.libp2p, peer.toString()));
489
+ }
483
490
  }
484
491
  }
485
492
 
@@ -753,21 +760,26 @@ export class PeerManager {
753
760
  // NOTE: libp2p may emit two "peer:connect" events: One for inbound, one for outbound
754
761
  // If that happens, it's okay. Only the "outbound" connection triggers immediate action
755
762
  const now = Date.now();
763
+ const existingPeerData = this.connectedPeers.get(remotePeerStr);
756
764
  const nodeId = computeNodeId(remotePeer);
757
765
  const peerData: PeerData = {
758
- lastReceivedMsgUnixTsMs: direction === "outbound" ? 0 : now,
766
+ // Keep existing timestamps if this peer already had another open connection.
767
+ // libp2p may emit multiple connection:open events per peer.
768
+ lastReceivedMsgUnixTsMs: existingPeerData?.lastReceivedMsgUnixTsMs ?? (direction === "outbound" ? 0 : now),
759
769
  // If inbound, request after STATUS_INBOUND_GRACE_PERIOD
760
- lastStatusUnixTsMs: direction === "outbound" ? 0 : now - STATUS_INTERVAL_MS + STATUS_INBOUND_GRACE_PERIOD,
761
- connectedUnixTsMs: now,
762
- relevantStatus: RelevantPeerStatus.Unknown,
770
+ lastStatusUnixTsMs:
771
+ existingPeerData?.lastStatusUnixTsMs ??
772
+ (direction === "outbound" ? 0 : now - STATUS_INTERVAL_MS + STATUS_INBOUND_GRACE_PERIOD),
773
+ connectedUnixTsMs: existingPeerData?.connectedUnixTsMs ?? now,
774
+ relevantStatus: existingPeerData?.relevantStatus ?? RelevantPeerStatus.Unknown,
763
775
  direction,
764
776
  nodeId,
765
777
  peerId: remotePeer,
766
- status: null,
767
- metadata: null,
768
- agentVersion: null,
769
- agentClient: null,
770
- encodingPreference: null,
778
+ status: existingPeerData?.status ?? null,
779
+ metadata: existingPeerData?.metadata ?? null,
780
+ agentVersion: existingPeerData?.agentVersion ?? null,
781
+ agentClient: existingPeerData?.agentClient ?? null,
782
+ encodingPreference: existingPeerData?.encodingPreference ?? null,
771
783
  };
772
784
  this.connectedPeers.set(remotePeerStr, peerData);
773
785
 
@@ -776,26 +788,6 @@ export class PeerManager {
776
788
  void this.requestStatus(remotePeer, this.statusCache.get());
777
789
  }
778
790
 
779
- this.libp2p.services.identify
780
- .identify(connection)
781
- .then((result) => {
782
- const agentVersion = result.agentVersion;
783
- if (agentVersion) {
784
- peerData.agentVersion = agentVersion;
785
- peerData.agentClient = getKnownClientFromAgentVersion(agentVersion);
786
- }
787
- })
788
- .catch((err) => {
789
- if (connection.status !== "open") {
790
- this.logger.debug("Peer disconnected during identify protocol", {
791
- peerId: remotePeerPrettyStr,
792
- error: (err as Error).message,
793
- });
794
- } else {
795
- this.logger.debug("Error setting agentVersion for the peer", {peerId: remotePeerPrettyStr}, err);
796
- }
797
- });
798
-
799
791
  return true;
800
792
  }
801
793
 
@@ -822,6 +814,19 @@ export class PeerManager {
822
814
  const {direction, status, remotePeer} = evt.detail;
823
815
  const peerIdStr = remotePeer.toString();
824
816
 
817
+ const openConnections =
818
+ getConnectionsMap(this.libp2p)
819
+ .get(peerIdStr)
820
+ ?.value.filter((connection) => connection.status === "open") ?? [];
821
+ if (openConnections.length > 0) {
822
+ this.logger.debug("Ignoring peer disconnect event while another connection is still open", {
823
+ peerId: prettyPrintPeerIdStr(peerIdStr),
824
+ direction,
825
+ status,
826
+ });
827
+ return;
828
+ }
829
+
825
830
  let logMessage = "onLibp2pPeerDisconnect";
826
831
  const logContext: Record<string, string | number> = {
827
832
  peerId: prettyPrintPeerIdStr(peerIdStr),
@@ -856,6 +861,27 @@ export class PeerManager {
856
861
  }
857
862
  }
858
863
 
864
+ private async identifyPeer(peerIdStr: string, peerIdPretty: string, connection?: Connection): Promise<void> {
865
+ if (!connection || connection.status !== "open") {
866
+ this.logger.debug("Peer has no open connection for identify", {peerId: peerIdPretty});
867
+ return;
868
+ }
869
+
870
+ try {
871
+ const result = await this.libp2p.services.identify.identify(connection);
872
+ const agentVersion = result.agentVersion;
873
+ if (agentVersion) {
874
+ const connectedPeerData = this.connectedPeers.get(peerIdStr);
875
+ if (connectedPeerData) {
876
+ connectedPeerData.agentVersion = agentVersion;
877
+ connectedPeerData.agentClient = getKnownClientFromAgentVersion(agentVersion);
878
+ }
879
+ }
880
+ } catch (e) {
881
+ this.logger.debug("Error setting agentVersion for the peer", {peerId: peerIdPretty}, e as Error);
882
+ }
883
+ }
884
+
859
885
  private async goodbyeAndDisconnect(peer: PeerId, goodbye: GoodByeReasonCode): Promise<void> {
860
886
  const reason = GOODBYE_KNOWN_CODES[goodbye.toString()] || "";
861
887
  const peerIdStr = peer.toString();
@@ -1,4 +1,4 @@
1
- import {Direction, PeerId} from "@libp2p/interface";
1
+ import type {MessageStreamDirection, PeerId} from "@libp2p/interface";
2
2
  import {BitArray} from "@chainsafe/ssz";
3
3
  import {ChainConfig} from "@lodestar/config";
4
4
  import {ATTESTATION_SUBNET_COUNT, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
@@ -95,7 +95,7 @@ function computeStatusScore(ours: Status, theirs: Status | null, opts: Prioritiz
95
95
 
96
96
  type PeerInfo = {
97
97
  id: PeerId;
98
- direction: Direction | null;
98
+ direction: MessageStreamDirection | null;
99
99
  statusScore: StatusScore;
100
100
  attnets: phase0.AttestationSubnets;
101
101
  syncnets: altair.SyncSubnets;
@@ -137,7 +137,7 @@ export enum ExcessPeerDisconnectReason {
137
137
  export function prioritizePeers(
138
138
  connectedPeersInfo: {
139
139
  id: PeerId;
140
- direction: Direction | null;
140
+ direction: MessageStreamDirection | null;
141
141
  status: Status | null;
142
142
  attnets: phase0.AttestationSubnets | null;
143
143
  syncnets: altair.SyncSubnets | null;
@@ -2,6 +2,7 @@ import {routes} from "@lodestar/api";
2
2
  import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
3
3
  import {
4
4
  ForkName,
5
+ ForkPostDeneb,
5
6
  ForkPostElectra,
6
7
  ForkPreElectra,
7
8
  ForkSeq,
@@ -70,6 +71,7 @@ import {validateGossipPayloadAttestationMessage} from "../../chain/validation/pa
70
71
  import {OpSource} from "../../chain/validatorMonitor.js";
71
72
  import {Metrics} from "../../metrics/index.js";
72
73
  import {kzgCommitmentToVersionedHash} from "../../util/blobs.js";
74
+ import {getBlobKzgCommitments} from "../../util/dataColumns.ts";
73
75
  import {INetworkCore} from "../core/index.js";
74
76
  import {NetworkEventBus} from "../events.js";
75
77
  import {
@@ -417,9 +419,11 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
417
419
  chain.getBlobsTracker.triggerGetBlobs(blockInput);
418
420
  } else {
419
421
  metrics?.blockInputFetchStats.totalDataAvailableBlockInputs.inc();
420
- metrics?.blockInputFetchStats.totalDataAvailableBlockInputBlobs.inc(
421
- (signedBlock.message as deneb.BeaconBlock).body.blobKzgCommitments.length
422
- );
422
+ const blobCount = getBlobKzgCommitments(
423
+ blockInput.forkName,
424
+ signedBlock as SignedBeaconBlock<ForkPostDeneb>
425
+ ).length;
426
+ metrics?.blockInputFetchStats.totalDataAvailableBlockInputBlobs.inc(blobCount);
423
427
  }
424
428
 
425
429
  chain
@@ -1,4 +1,4 @@
1
- import {TopicValidatorResult} from "@libp2p/interface";
1
+ import {TopicValidatorResult} from "@libp2p/gossipsub";
2
2
  import {ChainForkConfig} from "@lodestar/config";
3
3
  import {Logger} from "@lodestar/utils";
4
4
  import {AttestationError, GossipAction, GossipActionError} from "../../chain/errors/index.js";
@@ -1,4 +1,4 @@
1
- import {Message} from "@libp2p/interface";
1
+ import type {Message} from "@libp2p/gossipsub";
2
2
  import {ForkName} from "@lodestar/params";
3
3
  import {Slot, SlotOptionalRoot} from "@lodestar/types";
4
4
  import {PeerIdStr} from "../../util/peerId.js";
@@ -38,7 +38,6 @@ export function onOutgoingReqRespError(e: RequestError, method: ReqRespMethod):
38
38
  : PeerAction.LowToleranceError;
39
39
  // TODO: Detect SSZDecodeError and return PeerAction.Fatal
40
40
 
41
- case RequestErrorCode.TTFB_TIMEOUT:
42
41
  case RequestErrorCode.RESP_TIMEOUT:
43
42
  switch (method) {
44
43
  case ReqRespMethod.Ping:
@@ -23,7 +23,7 @@ export function getConnection(libp2p: Libp2p, peerIdStr: string): Connection | u
23
23
  return getConnectionsMap(libp2p).get(peerIdStr)?.value[0] ?? undefined;
24
24
  }
25
25
 
26
- // https://github.com/ChainSafe/js-libp2p-gossipsub/blob/3475242ed254f7647798ab7f36b21909f6cb61da/src/index.ts#L2009
26
+ // https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
27
27
  export function isPublishToZeroPeersError(e: Error): boolean {
28
- return e.message.includes("PublishError.InsufficientPeers");
28
+ return e.message.includes("PublishError.NoPeersSubscribedToTopic");
29
29
  }