@lodestar/beacon-node 1.41.0-dev.95cf2edc4c → 1.41.0-dev.96f78af84b
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.
- package/lib/api/impl/node/utils.d.ts +1 -1
- package/lib/api/impl/node/utils.d.ts.map +1 -1
- package/lib/api/impl/node/utils.js.map +1 -1
- package/lib/constants/network.d.ts +2 -1
- package/lib/constants/network.d.ts.map +1 -1
- package/lib/constants/network.js +1 -0
- package/lib/constants/network.js.map +1 -1
- package/lib/db/repositories/blockArchive.d.ts.map +1 -1
- package/lib/db/repositories/blockArchive.js +1 -2
- package/lib/db/repositories/blockArchive.js.map +1 -1
- package/lib/execution/engine/http.d.ts +1 -0
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +3 -0
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +3 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +5 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/core/networkCore.d.ts +3 -3
- package/lib/network/core/networkCore.d.ts.map +1 -1
- package/lib/network/core/networkCore.js.map +1 -1
- package/lib/network/core/networkCoreWorkerHandler.d.ts +3 -3
- package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
- package/lib/network/core/types.d.ts +2 -2
- package/lib/network/core/types.d.ts.map +1 -1
- package/lib/network/events.d.ts +2 -1
- package/lib/network/events.d.ts.map +1 -1
- package/lib/network/events.js.map +1 -1
- package/lib/network/gossip/encoding.d.ts +3 -3
- package/lib/network/gossip/encoding.d.ts.map +1 -1
- package/lib/network/gossip/encoding.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts +13 -4
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +47 -20
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/gossip/interface.d.ts +3 -3
- package/lib/network/gossip/interface.d.ts.map +1 -1
- package/lib/network/gossip/scoringParameters.d.ts +1 -1
- package/lib/network/gossip/scoringParameters.d.ts.map +1 -1
- package/lib/network/gossip/scoringParameters.js +1 -1
- package/lib/network/gossip/scoringParameters.js.map +1 -1
- package/lib/network/interface.d.ts +3 -3
- package/lib/network/interface.d.ts.map +1 -1
- package/lib/network/libp2p/index.d.ts +1 -1
- package/lib/network/libp2p/index.d.ts.map +1 -1
- package/lib/network/libp2p/index.js +7 -2
- package/lib/network/libp2p/index.js.map +1 -1
- package/lib/network/network.d.ts +2 -2
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js.map +1 -1
- package/lib/network/options.d.ts.map +1 -1
- package/lib/network/options.js +3 -0
- package/lib/network/options.js.map +1 -1
- package/lib/network/peers/datastore.d.ts +7 -5
- package/lib/network/peers/datastore.d.ts.map +1 -1
- package/lib/network/peers/datastore.js +10 -10
- package/lib/network/peers/datastore.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts +11 -0
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +148 -53
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts +3 -3
- package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +1 -1
- package/lib/network/processor/types.d.ts +1 -1
- package/lib/network/processor/types.d.ts.map +1 -1
- package/lib/network/reqresp/score.d.ts.map +1 -1
- package/lib/network/reqresp/score.js +0 -1
- package/lib/network/reqresp/score.js.map +1 -1
- package/lib/network/util.js +2 -2
- package/lib/network/util.js.map +1 -1
- package/package.json +38 -41
- package/src/api/impl/node/utils.ts +3 -3
- package/src/constants/network.ts +1 -0
- package/src/db/repositories/blockArchive.ts +1 -2
- package/src/execution/engine/http.ts +3 -0
- package/src/metrics/metrics/lodestar.ts +5 -0
- package/src/network/core/networkCore.ts +3 -3
- package/src/network/core/networkCoreWorkerHandler.ts +3 -3
- package/src/network/core/types.ts +2 -2
- package/src/network/events.ts +2 -1
- package/src/network/gossip/encoding.ts +3 -3
- package/src/network/gossip/gossipsub.ts +86 -25
- package/src/network/gossip/interface.ts +3 -3
- package/src/network/gossip/scoringParameters.ts +4 -4
- package/src/network/interface.ts +3 -3
- package/src/network/libp2p/index.ts +8 -3
- package/src/network/network.ts +3 -3
- package/src/network/options.ts +3 -0
- package/src/network/peers/datastore.ts +13 -10
- package/src/network/peers/peerManager.ts +175 -54
- package/src/network/peers/utils/prioritizePeers.ts +3 -3
- package/src/network/processor/gossipValidatorFn.ts +1 -1
- package/src/network/processor/types.ts +1 -1
- package/src/network/reqresp/score.ts +0 -1
- package/src/network/util.ts +2 -2
- package/src/util/workerEvents.ts +1 -1
|
@@ -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):
|
|
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):
|
|
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> {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {Connection, PeerId, PrivateKey} from "@libp2p/interface";
|
|
1
|
+
import {Connection, type IdentifyResult, PeerId, PrivateKey} from "@libp2p/interface";
|
|
2
2
|
import {BitArray} from "@chainsafe/ssz";
|
|
3
3
|
import {BeaconConfig} from "@lodestar/config";
|
|
4
4
|
import {LoggerNode} from "@lodestar/logger/node";
|
|
@@ -9,6 +9,7 @@ import {prettyPrintIndices, toHex, withTimeout} from "@lodestar/utils";
|
|
|
9
9
|
import {GOODBYE_KNOWN_CODES, GoodByeReasonCode, Libp2pEvent} from "../../constants/index.js";
|
|
10
10
|
import {IClock} from "../../util/clock.js";
|
|
11
11
|
import {computeColumnsForCustodyGroup, getCustodyGroups} from "../../util/dataColumns.js";
|
|
12
|
+
import {callInNextEventLoop} from "../../util/eventLoop.js";
|
|
12
13
|
import {NetworkCoreMetrics} from "../core/metrics.js";
|
|
13
14
|
import {LodestarDiscv5Opts} from "../discv5/types.js";
|
|
14
15
|
import {INetworkEventBus, NetworkEvent, NetworkEventData} from "../events.js";
|
|
@@ -161,6 +162,8 @@ export class PeerManager {
|
|
|
161
162
|
|
|
162
163
|
// A single map of connected peers with all necessary data to handle PINGs, STATUS, and metrics
|
|
163
164
|
private connectedPeers: Map<PeerIdStr, PeerData>;
|
|
165
|
+
/** Track one in-flight identify call per peer/connection id */
|
|
166
|
+
private readonly identifyInProgress = new Map<PeerIdStr, string>();
|
|
164
167
|
|
|
165
168
|
private opts: PeerManagerOpts;
|
|
166
169
|
private intervals: NodeJS.Timeout[] = [];
|
|
@@ -192,10 +195,18 @@ export class PeerManager {
|
|
|
192
195
|
|
|
193
196
|
this.libp2p.services.components.events.addEventListener(Libp2pEvent.connectionOpen, this.onLibp2pPeerConnect);
|
|
194
197
|
this.libp2p.services.components.events.addEventListener(Libp2pEvent.connectionClose, this.onLibp2pPeerDisconnect);
|
|
198
|
+
this.libp2p.services.components.events.addEventListener(Libp2pEvent.peerIdentify, this.onPeerIdentify);
|
|
195
199
|
this.networkEventBus.on(NetworkEvent.reqRespRequest, this.onRequest);
|
|
196
200
|
|
|
197
201
|
this.lastStatus = this.statusCache.get();
|
|
198
202
|
|
|
203
|
+
// A connection may already be open before listeners are attached.
|
|
204
|
+
// Seed those peers so they are tracked in connectedPeers immediately.
|
|
205
|
+
this.bootstrapAlreadyOpenConnections();
|
|
206
|
+
// Defer status/ping to the next event loop tick so the heartbeat interval and
|
|
207
|
+
// event listeners are fully registered before we begin handshakes.
|
|
208
|
+
callInNextEventLoop(() => this.pingAndStatusTimeouts());
|
|
209
|
+
|
|
199
210
|
// On start-up will connected to existing peers in libp2p.peerStore, same as autoDial behaviour
|
|
200
211
|
this.heartbeat();
|
|
201
212
|
this.intervals = [
|
|
@@ -228,6 +239,7 @@ export class PeerManager {
|
|
|
228
239
|
Libp2pEvent.connectionClose,
|
|
229
240
|
this.onLibp2pPeerDisconnect
|
|
230
241
|
);
|
|
242
|
+
this.libp2p.services.components.events.removeEventListener(Libp2pEvent.peerIdentify, this.onPeerIdentify);
|
|
231
243
|
this.networkEventBus.off(NetworkEvent.reqRespRequest, this.onRequest);
|
|
232
244
|
for (const interval of this.intervals) clearInterval(interval);
|
|
233
245
|
}
|
|
@@ -472,6 +484,32 @@ export class PeerManager {
|
|
|
472
484
|
clientAgent,
|
|
473
485
|
custodyColumns,
|
|
474
486
|
});
|
|
487
|
+
|
|
488
|
+
// Identify peer after status proves the connection is usable.
|
|
489
|
+
// This is the only place we trigger identify — avoids wasted streams on
|
|
490
|
+
// peers that close identify right after connection open or turn out to be
|
|
491
|
+
// irrelevant.
|
|
492
|
+
if (peerData?.agentVersion === null) {
|
|
493
|
+
const peerIdStr = peer.toString();
|
|
494
|
+
const connection = getConnection(this.libp2p, peerIdStr);
|
|
495
|
+
if (!connection || connection.status !== "open") {
|
|
496
|
+
this.logger.debug("Peer has no open connection for identify", {peerId: prettyPrintPeerId(peer)});
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const identifyKey = connection.id;
|
|
501
|
+
if (this.identifyInProgress.get(peerIdStr) === identifyKey) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
this.identifyInProgress.set(peerIdStr, identifyKey);
|
|
506
|
+
void this.identifyPeer(peerIdStr, prettyPrintPeerId(peer), connection, identifyKey).finally(() => {
|
|
507
|
+
// Clear only if this identify attempt is still the active one for this peer
|
|
508
|
+
if (this.identifyInProgress.get(peerIdStr) === identifyKey) {
|
|
509
|
+
this.identifyInProgress.delete(peerIdStr);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
475
513
|
}
|
|
476
514
|
}
|
|
477
515
|
|
|
@@ -692,36 +730,39 @@ export class PeerManager {
|
|
|
692
730
|
}
|
|
693
731
|
}
|
|
694
732
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
733
|
+
private bootstrapAlreadyOpenConnections(): void {
|
|
734
|
+
let bootstrapped = 0;
|
|
735
|
+
|
|
736
|
+
for (const {value: connections} of getConnectionsMap(this.libp2p).values()) {
|
|
737
|
+
for (const connection of connections) {
|
|
738
|
+
// trackLibp2pConnection handles deduplication via overwriteExisting: false
|
|
739
|
+
if (this.trackLibp2pConnection(connection, {overwriteExisting: false, triggerHandshakeNow: false})) {
|
|
740
|
+
bootstrapped++;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (bootstrapped > 0) {
|
|
746
|
+
this.logger.verbose("Bootstrapped already-open libp2p peers", {bootstrapped});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
private trackLibp2pConnection(
|
|
751
|
+
connection: Connection,
|
|
752
|
+
opts: {overwriteExisting: boolean; triggerHandshakeNow: boolean}
|
|
753
|
+
): boolean {
|
|
754
|
+
const {direction, status, remotePeer} = connection;
|
|
704
755
|
const remotePeerStr = remotePeer.toString();
|
|
705
756
|
const remotePeerPrettyStr = prettyPrintPeerId(remotePeer);
|
|
706
|
-
this.logger.verbose("peer connected", {peer: remotePeerPrettyStr, direction, status});
|
|
707
|
-
// NOTE: The peerConnect event is not emitted here here, but after asserting peer relevance
|
|
708
|
-
this.metrics?.peerConnectedEvent.inc({direction, status});
|
|
709
757
|
|
|
710
|
-
if (
|
|
758
|
+
if (status !== "open") {
|
|
711
759
|
this.logger.debug("Peer disconnected before identify protocol initiated", {
|
|
712
760
|
peerId: remotePeerPrettyStr,
|
|
713
|
-
status
|
|
761
|
+
status,
|
|
714
762
|
});
|
|
715
|
-
return;
|
|
763
|
+
return false;
|
|
716
764
|
}
|
|
717
765
|
|
|
718
|
-
// On connection:
|
|
719
|
-
// - Outbound connections: send a STATUS and PING request
|
|
720
|
-
// - Inbound connections: expect to be STATUS'd, schedule STATUS and PING for latter
|
|
721
|
-
// NOTE: libp2p may emit two "peer:connect" events: One for inbound, one for outbound
|
|
722
|
-
// If that happens, it's okay. Only the "outbound" connection triggers immediate action
|
|
723
|
-
const now = Date.now();
|
|
724
|
-
|
|
725
766
|
// Ethereum uses secp256k1 for node IDs, reject peers with other key types
|
|
726
767
|
if (remotePeer.type !== "secp256k1") {
|
|
727
768
|
this.logger.debug("Peer does not have secp256k1 key, disconnecting", {
|
|
@@ -729,52 +770,64 @@ export class PeerManager {
|
|
|
729
770
|
type: remotePeer.type,
|
|
730
771
|
});
|
|
731
772
|
void this.goodbyeAndDisconnect(remotePeer, GoodByeReasonCode.IRRELEVANT_NETWORK);
|
|
732
|
-
return;
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (!opts.overwriteExisting && this.connectedPeers.has(remotePeerStr)) {
|
|
777
|
+
return false;
|
|
733
778
|
}
|
|
734
779
|
|
|
780
|
+
// On connection:
|
|
781
|
+
// - Outbound connections: send a STATUS and PING request
|
|
782
|
+
// - Inbound connections: expect to be STATUS'd, schedule STATUS and PING for later
|
|
783
|
+
// NOTE: libp2p may emit two "peer:connect" events: One for inbound, one for outbound
|
|
784
|
+
// If that happens, it's okay. Only the "outbound" connection triggers immediate action
|
|
785
|
+
const now = Date.now();
|
|
786
|
+
const existingPeerData = this.connectedPeers.get(remotePeerStr);
|
|
735
787
|
const nodeId = computeNodeId(remotePeer);
|
|
736
788
|
const peerData: PeerData = {
|
|
737
|
-
|
|
789
|
+
// Keep existing timestamps if this peer already had another open connection.
|
|
790
|
+
// libp2p may emit multiple connection:open events per peer.
|
|
791
|
+
lastReceivedMsgUnixTsMs: existingPeerData?.lastReceivedMsgUnixTsMs ?? (direction === "outbound" ? 0 : now),
|
|
738
792
|
// If inbound, request after STATUS_INBOUND_GRACE_PERIOD
|
|
739
|
-
lastStatusUnixTsMs:
|
|
740
|
-
|
|
741
|
-
|
|
793
|
+
lastStatusUnixTsMs:
|
|
794
|
+
existingPeerData?.lastStatusUnixTsMs ??
|
|
795
|
+
(direction === "outbound" ? 0 : now - STATUS_INTERVAL_MS + STATUS_INBOUND_GRACE_PERIOD),
|
|
796
|
+
connectedUnixTsMs: existingPeerData?.connectedUnixTsMs ?? now,
|
|
797
|
+
relevantStatus: existingPeerData?.relevantStatus ?? RelevantPeerStatus.Unknown,
|
|
742
798
|
direction,
|
|
743
799
|
nodeId,
|
|
744
800
|
peerId: remotePeer,
|
|
745
|
-
status: null,
|
|
746
|
-
metadata: null,
|
|
747
|
-
agentVersion: null,
|
|
748
|
-
agentClient: null,
|
|
749
|
-
encodingPreference: null,
|
|
801
|
+
status: existingPeerData?.status ?? null,
|
|
802
|
+
metadata: existingPeerData?.metadata ?? null,
|
|
803
|
+
agentVersion: existingPeerData?.agentVersion ?? null,
|
|
804
|
+
agentClient: existingPeerData?.agentClient ?? null,
|
|
805
|
+
encodingPreference: existingPeerData?.encodingPreference ?? null,
|
|
750
806
|
};
|
|
751
807
|
this.connectedPeers.set(remotePeerStr, peerData);
|
|
752
808
|
|
|
753
|
-
if (direction === "outbound") {
|
|
754
|
-
// this.pingAndStatusTimeouts();
|
|
809
|
+
if (direction === "outbound" && opts.triggerHandshakeNow) {
|
|
755
810
|
void this.requestPing(remotePeer);
|
|
756
811
|
void this.requestStatus(remotePeer, this.statusCache.get());
|
|
757
812
|
}
|
|
758
813
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
777
|
-
});
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* The libp2p Upgrader has successfully upgraded a peer connection on a particular multiaddress
|
|
819
|
+
* This event is routed through the connectionManager
|
|
820
|
+
*
|
|
821
|
+
* Registers a peer as connected. The `direction` parameter determines if the peer is being
|
|
822
|
+
* dialed or connecting to us.
|
|
823
|
+
*/
|
|
824
|
+
private onLibp2pPeerConnect = (evt: CustomEvent<Connection>): void => {
|
|
825
|
+
const {direction, status, remotePeer} = evt.detail;
|
|
826
|
+
this.logger.verbose("peer connected", {peer: prettyPrintPeerId(remotePeer), direction, status});
|
|
827
|
+
// NOTE: The peerConnect event is not emitted here here, but after asserting peer relevance
|
|
828
|
+
this.metrics?.peerConnectedEvent.inc({direction, status});
|
|
829
|
+
|
|
830
|
+
this.trackLibp2pConnection(evt.detail, {overwriteExisting: true, triggerHandshakeNow: true});
|
|
778
831
|
};
|
|
779
832
|
|
|
780
833
|
/**
|
|
@@ -784,6 +837,19 @@ export class PeerManager {
|
|
|
784
837
|
const {direction, status, remotePeer} = evt.detail;
|
|
785
838
|
const peerIdStr = remotePeer.toString();
|
|
786
839
|
|
|
840
|
+
const openConnections =
|
|
841
|
+
getConnectionsMap(this.libp2p)
|
|
842
|
+
.get(peerIdStr)
|
|
843
|
+
?.value.filter((connection) => connection.status === "open") ?? [];
|
|
844
|
+
if (openConnections.length > 0) {
|
|
845
|
+
this.logger.debug("Ignoring peer disconnect event while another connection is still open", {
|
|
846
|
+
peerId: prettyPrintPeerIdStr(peerIdStr),
|
|
847
|
+
direction,
|
|
848
|
+
status,
|
|
849
|
+
});
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
787
853
|
let logMessage = "onLibp2pPeerDisconnect";
|
|
788
854
|
const logContext: Record<string, string | number> = {
|
|
789
855
|
peerId: prettyPrintPeerIdStr(peerIdStr),
|
|
@@ -801,6 +867,7 @@ export class PeerManager {
|
|
|
801
867
|
|
|
802
868
|
// remove the ping and status timer for the peer
|
|
803
869
|
this.connectedPeers.delete(peerIdStr);
|
|
870
|
+
this.identifyInProgress.delete(peerIdStr);
|
|
804
871
|
|
|
805
872
|
this.logger.verbose(logMessage, logContext);
|
|
806
873
|
this.networkEventBus.emit(NetworkEvent.peerDisconnected, {peer: peerIdStr});
|
|
@@ -818,6 +885,60 @@ export class PeerManager {
|
|
|
818
885
|
}
|
|
819
886
|
}
|
|
820
887
|
|
|
888
|
+
/**
|
|
889
|
+
* Consume successful identify results from libp2p events.
|
|
890
|
+
* This captures agentVersion from identify-push or successful inbound/outbound identify,
|
|
891
|
+
* even if our explicit identify request failed earlier.
|
|
892
|
+
*/
|
|
893
|
+
private onPeerIdentify = (evt: CustomEvent<IdentifyResult>): void => {
|
|
894
|
+
const {peerId, agentVersion} = evt.detail;
|
|
895
|
+
if (!agentVersion) return;
|
|
896
|
+
|
|
897
|
+
const peerIdStr = peerId.toString();
|
|
898
|
+
const peerData = this.connectedPeers.get(peerIdStr);
|
|
899
|
+
if (!peerData) return;
|
|
900
|
+
|
|
901
|
+
peerData.agentVersion = agentVersion;
|
|
902
|
+
peerData.agentClient = getKnownClientFromAgentVersion(agentVersion);
|
|
903
|
+
this.identifyInProgress.delete(peerIdStr);
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
private async identifyPeer(
|
|
907
|
+
peerIdStr: string,
|
|
908
|
+
peerIdPretty: string,
|
|
909
|
+
connection: Connection,
|
|
910
|
+
identifyKey: string
|
|
911
|
+
): Promise<void> {
|
|
912
|
+
if (this.identifyInProgress.get(peerIdStr) !== identifyKey) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (connection.status !== "open") {
|
|
917
|
+
this.logger.debug("Peer has no open connection for identify", {peerId: peerIdPretty});
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
try {
|
|
922
|
+
const result = await this.libp2p.services.identify.identify(connection);
|
|
923
|
+
|
|
924
|
+
// A newer identify attempt may have superseded this one (e.g. reconnect).
|
|
925
|
+
if (this.identifyInProgress.get(peerIdStr) !== identifyKey) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const agentVersion = result.agentVersion;
|
|
930
|
+
if (agentVersion) {
|
|
931
|
+
const connectedPeerData = this.connectedPeers.get(peerIdStr);
|
|
932
|
+
if (connectedPeerData) {
|
|
933
|
+
connectedPeerData.agentVersion = agentVersion;
|
|
934
|
+
connectedPeerData.agentClient = getKnownClientFromAgentVersion(agentVersion);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} catch (e) {
|
|
938
|
+
this.logger.debug("Error setting agentVersion for the peer", {peerId: peerIdPretty}, e as Error);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
821
942
|
private async goodbyeAndDisconnect(peer: PeerId, goodbye: GoodByeReasonCode): Promise<void> {
|
|
822
943
|
const reason = GOODBYE_KNOWN_CODES[goodbye.toString()] || "";
|
|
823
944
|
const peerIdStr = peer.toString();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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:
|
|
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:
|
|
140
|
+
direction: MessageStreamDirection | null;
|
|
141
141
|
status: Status | null;
|
|
142
142
|
attnets: phase0.AttestationSubnets | null;
|
|
143
143
|
syncnets: altair.SyncSubnets | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {TopicValidatorResult} from "@libp2p/
|
|
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";
|
|
@@ -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:
|
package/src/network/util.ts
CHANGED
|
@@ -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/
|
|
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.
|
|
28
|
+
return e.message.includes("PublishError.NoPeersSubscribedToTopic");
|
|
29
29
|
}
|
package/src/util/workerEvents.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {MessagePort, Worker} from "node:worker_threads";
|
|
2
|
-
import {Message} from "@libp2p/
|
|
2
|
+
import type {Message} from "@libp2p/gossipsub";
|
|
3
3
|
import {Thread} from "@chainsafe/threads";
|
|
4
4
|
import {Logger} from "@lodestar/logger";
|
|
5
5
|
import {sleep} from "@lodestar/utils";
|