@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.
- package/lib/api/impl/debug/index.d.ts.map +1 -1
- package/lib/api/impl/debug/index.js +1 -0
- package/lib/api/impl/debug/index.js.map +1 -1
- 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/chain/blocks/blockInput/blockInput.d.ts +20 -2
- package/lib/chain/blocks/blockInput/blockInput.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/blockInput.js +47 -0
- package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
- package/lib/chain/blocks/blockInput/types.d.ts +2 -1
- package/lib/chain/blocks/blockInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/blockInput/types.js +1 -0
- package/lib/chain/blocks/blockInput/types.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksDataAvailability.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksDataAvailability.js +3 -0
- package/lib/chain/blocks/verifyBlocksDataAvailability.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts +4 -0
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js +5 -1
- package/lib/chain/blocks/verifyBlocksExecutionPayloads.js.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenGossipBlockInput.js +15 -7
- package/lib/chain/seenCache/seenGossipBlockInput.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/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 +1 -0
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +51 -29
- 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/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +3 -1
- package/lib/network/processor/gossipHandlers.js.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/lib/util/clock.d.ts +6 -0
- package/lib/util/clock.d.ts.map +1 -1
- package/lib/util/clock.js +9 -3
- package/lib/util/clock.js.map +1 -1
- package/package.json +38 -41
- package/src/api/impl/debug/index.ts +1 -0
- package/src/api/impl/node/utils.ts +3 -3
- package/src/chain/blocks/blockInput/blockInput.ts +68 -3
- package/src/chain/blocks/blockInput/types.ts +1 -0
- package/src/chain/blocks/verifyBlocksDataAvailability.ts +3 -0
- package/src/chain/blocks/verifyBlocksExecutionPayloads.ts +9 -2
- package/src/chain/seenCache/seenGossipBlockInput.ts +16 -7
- package/src/db/repositories/blockArchive.ts +1 -2
- 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 +56 -30
- package/src/network/peers/utils/prioritizePeers.ts +3 -3
- package/src/network/processor/gossipHandlers.ts +7 -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/clock.ts +9 -4
- 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
|
|
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
|
-
|
|
117
|
-
globalSignaturePolicy:
|
|
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
|
-
|
|
201
|
-
const
|
|
202
|
-
const
|
|
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:
|
|
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
|
|
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/
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 "@
|
|
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";
|
package/src/network/interface.ts
CHANGED
|
@@ -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({
|
|
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
|
package/src/network/network.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
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";
|
package/src/network/options.ts
CHANGED
|
@@ -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):
|
|
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> {
|
|
@@ -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
|
-
|
|
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:
|
|
761
|
-
|
|
762
|
-
|
|
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 {
|
|
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;
|
|
@@ -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
|
-
|
|
421
|
-
|
|
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/
|
|
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
|
}
|