Skip to content

Commit 1892f50

Browse files
fix: don't dial discovered peers if have already been attempted dial (#1657)
* don't dial peers if they exist in PeerStore already * fix(tests): bugs & add types to dispatch event * fix: more tests * fix: dial validation * update doc & reduce delay * refactor test * use -1 instead of Infinity * fix comment * fix rebase
1 parent 0f7d63e commit 1892f50

File tree

3 files changed

+162
-49
lines changed

3 files changed

+162
-49
lines changed

packages/core/src/lib/connection_manager.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class ConnectionManager
3838
private dialAttemptsForPeer: Map<string, number> = new Map();
3939
private dialErrorsForPeer: Map<string, any> = new Map();
4040

41-
private currentActiveDialCount = 0;
41+
private currentActiveParallelDialCount = 0;
4242
private pendingPeerDialQueue: Array<PeerId> = [];
4343

4444
public static create(
@@ -183,7 +183,7 @@ export class ConnectionManager
183183
}
184184

185185
private async dialPeer(peerId: PeerId): Promise<void> {
186-
this.currentActiveDialCount += 1;
186+
this.currentActiveParallelDialCount += 1;
187187
let dialAttempt = 0;
188188
while (dialAttempt < this.options.maxDialAttemptsForPeer) {
189189
try {
@@ -199,7 +199,10 @@ export class ConnectionManager
199199
conn.tags = Array.from(new Set([...conn.tags, ...tags]));
200200
});
201201

202-
this.dialAttemptsForPeer.delete(peerId.toString());
202+
// instead of deleting the peer from the peer store, we set the dial attempt to -1
203+
// this helps us keep track of peers that have been dialed before
204+
this.dialAttemptsForPeer.set(peerId.toString(), -1);
205+
203206
// Dialing succeeded, break the loop
204207
break;
205208
} catch (error) {
@@ -224,7 +227,7 @@ export class ConnectionManager
224227
}
225228

226229
// Always decrease the active dial count and process the dial queue
227-
this.currentActiveDialCount--;
230+
this.currentActiveParallelDialCount--;
228231
this.processDialQueue();
229232

230233
// If max dial attempts reached and dialing failed, delete the peer
@@ -276,7 +279,7 @@ export class ConnectionManager
276279
private processDialQueue(): void {
277280
if (
278281
this.pendingPeerDialQueue.length > 0 &&
279-
this.currentActiveDialCount < this.options.maxParallelDials
282+
this.currentActiveParallelDialCount < this.options.maxParallelDials
280283
) {
281284
const peerId = this.pendingPeerDialQueue.shift();
282285
if (!peerId) return;
@@ -322,7 +325,7 @@ export class ConnectionManager
322325
private async attemptDial(peerId: PeerId): Promise<void> {
323326
if (!(await this.shouldDialPeer(peerId))) return;
324327

325-
if (this.currentActiveDialCount >= this.options.maxParallelDials) {
328+
if (this.currentActiveParallelDialCount >= this.options.maxParallelDials) {
326329
this.pendingPeerDialQueue.push(peerId);
327330
return;
328331
}
@@ -404,6 +407,7 @@ export class ConnectionManager
404407
* 1. If the peer is already connected, don't dial
405408
* 2. If the peer is not part of any of the configured pubsub topics, don't dial
406409
* 3. If the peer is not dialable based on bootstrap status, don't dial
410+
* 4. If the peer is already has an active dial attempt, or has been dialed before, don't dial it
407411
* @returns true if the peer should be dialed, false otherwise
408412
*/
409413
private async shouldDialPeer(peerId: PeerId): Promise<boolean> {
@@ -437,6 +441,14 @@ export class ConnectionManager
437441
return false;
438442
}
439443

444+
// If the peer is already already has an active dial attempt, or has been dialed before, don't dial it
445+
if (this.dialAttemptsForPeer.has(peerId.toString())) {
446+
log.warn(
447+
`Peer ${peerId.toString()} has already been attempted dial before, or already has a dial attempt in progress, skipping dial`
448+
);
449+
return false;
450+
}
451+
440452
return true;
441453
}
442454

packages/tests/tests/connection_manager.spec.ts

+78-40
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { PeerId } from "@libp2p/interface/peer-id";
2+
import type { PeerInfo } from "@libp2p/interface/peer-info";
13
import { CustomEvent } from "@libp2p/interfaces/events";
24
import { createSecp256k1PeerId } from "@libp2p/peer-id-factory";
35
import { EPeersByDiscoveryEvents, LightNode, Tags } from "@waku/interfaces";
@@ -49,7 +51,13 @@ describe("ConnectionManager", function () {
4951
});
5052

5153
waku.libp2p.dispatchEvent(
52-
new CustomEvent("peer", { detail: await createSecp256k1PeerId() })
54+
new CustomEvent<PeerInfo>("peer:discovery", {
55+
detail: {
56+
id: peerIdBootstrap,
57+
multiaddrs: [],
58+
protocols: []
59+
}
60+
})
5361
);
5462

5563
expect(await peerDiscoveryBootstrap).to.eq(true);
@@ -77,7 +85,13 @@ describe("ConnectionManager", function () {
7785
});
7886

7987
waku.libp2p.dispatchEvent(
80-
new CustomEvent("peer", { detail: peerIdPx })
88+
new CustomEvent<PeerInfo>("peer:discovery", {
89+
detail: {
90+
id: peerIdPx,
91+
multiaddrs: [],
92+
protocols: []
93+
}
94+
})
8195
);
8296

8397
expect(await peerDiscoveryPeerExchange).to.eq(true);
@@ -109,7 +123,7 @@ describe("ConnectionManager", function () {
109123
});
110124

111125
waku.libp2p.dispatchEvent(
112-
new CustomEvent("peer:connect", { detail: peerIdBootstrap })
126+
new CustomEvent<PeerId>("peer:connect", { detail: peerIdBootstrap })
113127
);
114128

115129
expect(await peerConnectedBootstrap).to.eq(true);
@@ -136,7 +150,7 @@ describe("ConnectionManager", function () {
136150
});
137151

138152
waku.libp2p.dispatchEvent(
139-
new CustomEvent("peer:connect", { detail: peerIdPx })
153+
new CustomEvent<PeerId>("peer:connect", { detail: peerIdPx })
140154
);
141155

142156
expect(await peerConnectedPeerExchange).to.eq(true);
@@ -182,27 +196,34 @@ describe("ConnectionManager", function () {
182196
attemptDialSpy.restore();
183197
});
184198

185-
it("should be called on all `peer:discovery` events", async function () {
199+
it("should be called at least once on all `peer:discovery` events", async function () {
186200
this.timeout(TEST_TIMEOUT);
187201

188202
const totalPeerIds = 5;
189203
for (let i = 1; i <= totalPeerIds; i++) {
190204
waku.libp2p.dispatchEvent(
191-
new CustomEvent("peer:discovery", { detail: `peer-id-${i}` })
205+
new CustomEvent<PeerInfo>("peer:discovery", {
206+
detail: {
207+
id: await createSecp256k1PeerId(),
208+
multiaddrs: [],
209+
protocols: []
210+
}
211+
})
192212
);
193213
}
194214

195-
// add delay to allow async function calls within attemptDial to finish
196215
await delay(100);
197216

198-
expect(attemptDialSpy.callCount).to.equal(
217+
expect(attemptDialSpy.callCount).to.be.greaterThanOrEqual(
199218
totalPeerIds,
200-
"attemptDial should be called once for each peer:discovery event"
219+
"attemptDial should be called at least once for each peer:discovery event"
201220
);
202221
});
203222
});
204223

205224
describe("dialPeer method", function () {
225+
let peerStoreHasStub: SinonStub;
226+
let dialAttemptsForPeerHasStub: SinonStub;
206227
beforeEach(function () {
207228
getConnectionsStub = sinon.stub(
208229
(waku.connectionManager as any).libp2p,
@@ -213,29 +234,44 @@ describe("ConnectionManager", function () {
213234
"getTagNamesForPeer"
214235
);
215236
dialPeerStub = sinon.stub(waku.connectionManager as any, "dialPeer");
237+
peerStoreHasStub = sinon.stub(waku.libp2p.peerStore, "has");
238+
dialAttemptsForPeerHasStub = sinon.stub(
239+
(waku.connectionManager as any).dialAttemptsForPeer,
240+
"has"
241+
);
242+
243+
// simulate that the peer is not connected
244+
getConnectionsStub.returns([]);
245+
246+
// simulate that the peer is a bootstrap peer
247+
getTagNamesForPeerStub.resolves([Tags.BOOTSTRAP]);
248+
249+
// simulate that the peer is not in the peerStore
250+
peerStoreHasStub.returns(false);
251+
252+
// simulate that the peer has not been dialed before
253+
dialAttemptsForPeerHasStub.returns(false);
216254
});
217255

218256
afterEach(function () {
219257
dialPeerStub.restore();
220258
getTagNamesForPeerStub.restore();
221259
getConnectionsStub.restore();
260+
peerStoreHasStub.restore();
261+
dialAttemptsForPeerHasStub.restore();
222262
});
223263

224264
describe("For bootstrap peers", function () {
225265
it("should be called for bootstrap peers", async function () {
226266
this.timeout(TEST_TIMEOUT);
227267

228-
// simulate that the peer is not connected
229-
getConnectionsStub.returns([]);
230-
231-
// simulate that the peer is a bootstrap peer
232-
getTagNamesForPeerStub.resolves([Tags.BOOTSTRAP]);
233-
234268
const bootstrapPeer = await createSecp256k1PeerId();
235269

236270
// emit a peer:discovery event
237271
waku.libp2p.dispatchEvent(
238-
new CustomEvent("peer:discovery", { detail: bootstrapPeer })
272+
new CustomEvent<PeerInfo>("peer:discovery", {
273+
detail: { id: bootstrapPeer, multiaddrs: [], protocols: [] }
274+
})
239275
);
240276

241277
// wait for the async function calls within attemptDial to finish
@@ -251,15 +287,15 @@ describe("ConnectionManager", function () {
251287
it("should not be called more than DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED times for bootstrap peers", async function () {
252288
this.timeout(TEST_TIMEOUT);
253289

254-
// simulate that the peer is not connected
255-
getConnectionsStub.returns([]);
256-
257-
// simulate that the peer is a bootstrap peer
258-
getTagNamesForPeerStub.resolves([Tags.BOOTSTRAP]);
259-
260290
// emit first peer:discovery event
261291
waku.libp2p.dispatchEvent(
262-
new CustomEvent("peer:discovery", { detail: "bootstrap-peer" })
292+
new CustomEvent<PeerInfo>("peer:discovery", {
293+
detail: {
294+
id: await createSecp256k1PeerId(),
295+
multiaddrs: [],
296+
protocols: []
297+
}
298+
})
263299
);
264300
await delay(500);
265301

@@ -271,8 +307,12 @@ describe("ConnectionManager", function () {
271307
for (let i = 1; i <= totalBootstrapPeers; i++) {
272308
await delay(500);
273309
waku.libp2p.dispatchEvent(
274-
new CustomEvent("peer:discovery", {
275-
detail: await createSecp256k1PeerId()
310+
new CustomEvent<PeerInfo>("peer:discovery", {
311+
detail: {
312+
id: await createSecp256k1PeerId(),
313+
multiaddrs: [],
314+
protocols: []
315+
}
276316
})
277317
);
278318
}
@@ -289,17 +329,17 @@ describe("ConnectionManager", function () {
289329
it("should be called for peers with PEER_EXCHANGE tags", async function () {
290330
this.timeout(TEST_TIMEOUT);
291331

292-
// simulate that the peer is not connected
293-
getConnectionsStub.returns([]);
294-
295-
// simulate that the peer has a PEER_EXCHANGE tag
296-
getTagNamesForPeerStub.resolves([Tags.PEER_EXCHANGE]);
297-
298332
const pxPeer = await createSecp256k1PeerId();
299333

300334
// emit a peer:discovery event
301335
waku.libp2p.dispatchEvent(
302-
new CustomEvent("peer:discovery", { detail: pxPeer })
336+
new CustomEvent<PeerInfo>("peer:discovery", {
337+
detail: {
338+
id: pxPeer,
339+
multiaddrs: [],
340+
protocols: []
341+
}
342+
})
303343
);
304344

305345
// wait for the async function calls within attemptDial to finish
@@ -315,18 +355,16 @@ describe("ConnectionManager", function () {
315355
it("should be called for every peer with PEER_EXCHANGE tags", async function () {
316356
this.timeout(TEST_TIMEOUT);
317357

318-
// simulate that the peer is not connected
319-
getConnectionsStub.returns([]);
320-
321-
// simulate that the peer has a PEER_EXCHANGE tag
322-
getTagNamesForPeerStub.resolves([Tags.PEER_EXCHANGE]);
323-
324358
// emit multiple peer:discovery events
325359
const totalPxPeers = 5;
326360
for (let i = 0; i < totalPxPeers; i++) {
327361
waku.libp2p.dispatchEvent(
328-
new CustomEvent("peer:discovery", {
329-
detail: await createSecp256k1PeerId()
362+
new CustomEvent<PeerInfo>("peer:discovery", {
363+
detail: {
364+
id: await createSecp256k1PeerId(),
365+
multiaddrs: [],
366+
protocols: []
367+
}
330368
})
331369
);
332370
await delay(500);

0 commit comments

Comments
 (0)