@lodestar/beacon-node 1.40.0-dev.787d0f5eee → 1.40.0-dev.7922561845

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.
@@ -1,7 +1,10 @@
1
+ import {peerIdFromString} from "@libp2p/peer-id";
2
+ import {multiaddr} from "@multiformats/multiaddr";
3
+ import {ENR} from "@chainsafe/enr";
1
4
  import {GossipSub, GossipsubEvents} from "@chainsafe/libp2p-gossipsub";
2
5
  import {MetricsRegister, TopicLabel, TopicStrToLabel} from "@chainsafe/libp2p-gossipsub/metrics";
3
6
  import {PeerScoreParams} from "@chainsafe/libp2p-gossipsub/score";
4
- import {SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
7
+ import {AddrInfo, SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
5
8
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
6
9
  import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
7
10
  import {SubnetID} from "@lodestar/types";
@@ -55,6 +58,12 @@ export type Eth2GossipsubOpts = {
55
58
  disableFloodPublish?: boolean;
56
59
  skipParamsLog?: boolean;
57
60
  disableLightClientServer?: boolean;
61
+ /**
62
+ * Direct peers for GossipSub - these peers maintain permanent mesh connections without GRAFT/PRUNE.
63
+ * Supports multiaddr strings with peer ID (e.g., "/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...")
64
+ * or ENR strings (e.g., "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...")
65
+ */
66
+ directPeers?: string[];
58
67
  };
59
68
 
60
69
  export type ForkBoundaryLabel = string;
@@ -97,6 +106,9 @@ export class Eth2Gossipsub extends GossipSub {
97
106
  );
98
107
  }
99
108
 
109
+ // Parse direct peers from multiaddr strings to AddrInfo objects
110
+ const directPeers = parseDirectPeers(opts.directPeers ?? [], logger);
111
+
100
112
  // Gossipsub parameters defined here:
101
113
  // https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#the-gossip-domain-gossipsub
102
114
  super(modules.libp2p.services.components, {
@@ -106,6 +118,7 @@ export class Eth2Gossipsub extends GossipSub {
106
118
  Dlo: gossipsubDLow ?? GOSSIP_D_LOW,
107
119
  Dhi: gossipsubDHigh ?? GOSSIP_D_HIGH,
108
120
  Dlazy: 6,
121
+ directPeers,
109
122
  heartbeatInterval: GOSSIPSUB_HEARTBEAT_INTERVAL,
110
123
  fanoutTTL: 60 * 1000,
111
124
  mcacheLength: 6,
@@ -381,3 +394,75 @@ function getForkBoundaryLabel(boundary: ForkBoundary): ForkBoundaryLabel {
381
394
 
382
395
  return label;
383
396
  }
397
+
398
+ /**
399
+ * Parse direct peer strings into AddrInfo objects for GossipSub.
400
+ * Direct peers maintain permanent mesh connections without GRAFT/PRUNE negotiation.
401
+ *
402
+ * Supported formats:
403
+ * - Multiaddr with peer ID: `/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7...`
404
+ * - ENR: `enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOo...`
405
+ *
406
+ * For multiaddrs, the string must contain a /p2p/ component with the peer ID.
407
+ * For ENRs, the TCP multiaddr and peer ID are extracted from the encoded record.
408
+ */
409
+ export function parseDirectPeers(directPeerStrs: string[], logger: Logger): AddrInfo[] {
410
+ const directPeers: AddrInfo[] = [];
411
+
412
+ for (const peerStr of directPeerStrs) {
413
+ // Check if this is an ENR (starts with "enr:")
414
+ if (peerStr.startsWith("enr:")) {
415
+ try {
416
+ const enr = ENR.decodeTxt(peerStr);
417
+ const peerId = enr.peerId;
418
+
419
+ // Get TCP multiaddr from ENR
420
+ const multiaddrTCP = enr.getLocationMultiaddr("tcp");
421
+ if (!multiaddrTCP) {
422
+ logger.warn("ENR does not contain TCP multiaddr", {enr: peerStr});
423
+ continue;
424
+ }
425
+
426
+ directPeers.push({
427
+ id: peerId,
428
+ addrs: [multiaddrTCP],
429
+ });
430
+
431
+ logger.info("Added direct peer from ENR", {peerId: peerId.toString(), addr: multiaddrTCP.toString()});
432
+ } catch (e) {
433
+ logger.warn("Failed to parse direct peer ENR", {enr: peerStr}, e as Error);
434
+ }
435
+ } else {
436
+ // Parse as multiaddr
437
+ try {
438
+ const ma = multiaddr(peerStr);
439
+
440
+ const peerIdStr = ma.getPeerId();
441
+ if (!peerIdStr) {
442
+ logger.warn("Direct peer multiaddr must contain /p2p/ component with peer ID", {multiaddr: peerStr});
443
+ continue;
444
+ }
445
+
446
+ try {
447
+ const peerId = peerIdFromString(peerIdStr);
448
+
449
+ // Get the address without the /p2p/ component
450
+ const addr = ma.decapsulate("/p2p/" + peerIdStr);
451
+
452
+ directPeers.push({
453
+ id: peerId,
454
+ addrs: [addr],
455
+ });
456
+
457
+ logger.info("Added direct peer", {peerId: peerIdStr, addr: addr.toString()});
458
+ } catch (e) {
459
+ logger.warn("Invalid peer ID in direct peer multiaddr", {multiaddr: peerStr, peerId: peerIdStr}, e as Error);
460
+ }
461
+ } catch (e) {
462
+ logger.warn("Failed to parse direct peer multiaddr", {multiaddr: peerStr}, e as Error);
463
+ }
464
+ }
465
+ }
466
+
467
+ return directPeers;
468
+ }
@@ -15,6 +15,12 @@ export interface NetworkOptions
15
15
  Omit<Eth2GossipsubOpts, "disableLightClientServer"> {
16
16
  localMultiaddrs: string[];
17
17
  bootMultiaddrs?: string[];
18
+ /**
19
+ * Direct peers for GossipSub - these peers maintain permanent mesh connections without GRAFT/PRUNE.
20
+ * Format: multiaddr strings with peer ID, e.g., "/ip4/192.168.1.1/tcp/9000/p2p/16Uiu2HAmKLhW7..."
21
+ * Both peers must configure each other as direct peers for the feature to work properly.
22
+ */
23
+ directPeers?: string[];
18
24
  subscribeAllSubnets?: boolean;
19
25
  mdns?: boolean;
20
26
  connectToDiscv5Bootnodes?: boolean;
@@ -579,7 +579,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
579
579
  break;
580
580
  }
581
581
 
582
- if (!blockInput.hasAllData()) {
582
+ if (!blockInput.hasComputedAllData()) {
583
583
  // immediately attempt fetch of data columns from execution engine
584
584
  chain.getBlobsTracker.triggerGetBlobs(blockInput);
585
585
  // if we've received at least half of the columns, trigger reconstruction of the rest