Skip to content

Commit dd400cd

Browse files
authored
fix: allow dialing a peer when we only have transient connections (#2187)
If we dial a peer that we already have a connection to, we return that connection instead of dialing. This creates a problem when we are trying to make a direct connection to a peer that we already have a relay connection to since the relay connection is returned. The fix here is to allow additional dials to peers when we only have relay connections. If a direct connection exists, we will still return it instead of dialing.
1 parent 16a8707 commit dd400cd

File tree

4 files changed

+90
-27
lines changed

4 files changed

+90
-27
lines changed

packages/libp2p/src/circuit-relay/transport/listener.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import { CodeError } from '@libp2p/interface/errors'
22
import { TypedEventEmitter } from '@libp2p/interface/events'
33
import { logger } from '@libp2p/logger'
44
import { PeerMap } from '@libp2p/peer-collections'
5-
import { peerIdFromString } from '@libp2p/peer-id'
65
import { multiaddr } from '@multiformats/multiaddr'
76
import type { ReservationStore } from './reservation-store.js'
8-
import type { Connection } from '@libp2p/interface/connection'
97
import type { PeerId } from '@libp2p/interface/peer-id'
108
import type { Listener, ListenerEvents } from '@libp2p/interface/transport'
119
import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
@@ -41,25 +39,9 @@ class CircuitRelayTransportListener extends TypedEventEmitter<ListenerEvents> im
4139
async listen (addr: Multiaddr): Promise<void> {
4240
log('listen on %a', addr)
4341

44-
const relayPeerStr = addr.getPeerId()
45-
let relayConn: Connection | undefined
46-
47-
// check if we already have a connection to the relay
48-
if (relayPeerStr != null) {
49-
const relayPeer = peerIdFromString(relayPeerStr)
50-
const connections = this.connectionManager.getConnectionsMap().get(relayPeer) ?? []
51-
52-
if (connections.length > 0) {
53-
relayConn = connections[0]
54-
}
55-
}
56-
57-
// open a new connection as we don't already have one
58-
if (relayConn == null) {
59-
const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '')
60-
const ma = multiaddr(addrString)
61-
relayConn = await this.connectionManager.openConnection(ma)
62-
}
42+
// remove the circuit part to get the peer id of the relay
43+
const relayAddr = addr.decapsulate('/p2p-circuit')
44+
const relayConn = await this.connectionManager.openConnection(relayAddr)
6345

6446
if (!this.relayStore.hasReservation(relayConn.remotePeer)) {
6547
// addRelay calls transportManager.listen which calls this listen method

packages/libp2p/src/connection-manager/dial-queue.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AbortError, CodeError } from '@libp2p/interface/errors'
22
import { setMaxListeners } from '@libp2p/interface/events'
33
import { logger } from '@libp2p/logger'
4+
import { PeerMap } from '@libp2p/peer-collections'
45
import { defaultAddressSort } from '@libp2p/utils/address-sort'
56
import { type Multiaddr, type Resolver, resolvers } from '@multiformats/multiaddr'
67
import { dnsaddrResolver } from '@multiformats/multiaddr/resolvers'
@@ -35,6 +36,7 @@ export interface PendingDialTarget {
3536

3637
export interface DialOptions extends AbortOptions {
3738
priority?: number
39+
force?: boolean
3840
}
3941

4042
interface PendingDialInternal extends PendingDial {
@@ -48,6 +50,7 @@ interface DialerInit {
4850
maxParallelDialsPerPeer?: number
4951
dialTimeout?: number
5052
resolvers?: Record<string, Resolver>
53+
connections?: PeerMap<Connection[]>
5154
}
5255

5356
const defaultOptions = {
@@ -83,12 +86,14 @@ export class DialQueue {
8386
private readonly inProgressDialCount?: Metric
8487
private readonly pendingDialCount?: Metric
8588
private readonly shutDownController: AbortController
89+
private readonly connections: PeerMap<Connection[]>
8690

8791
constructor (components: DialQueueComponents, init: DialerInit = {}) {
8892
this.addressSorter = init.addressSorter ?? defaultOptions.addressSorter
8993
this.maxPeerAddrsToDial = init.maxPeerAddrsToDial ?? defaultOptions.maxPeerAddrsToDial
9094
this.maxParallelDialsPerPeer = init.maxParallelDialsPerPeer ?? defaultOptions.maxParallelDialsPerPeer
9195
this.dialTimeout = init.dialTimeout ?? defaultOptions.dialTimeout
96+
this.connections = init.connections ?? new PeerMap()
9297

9398
this.peerId = components.peerId
9499
this.peerStore = components.peerStore
@@ -187,6 +192,23 @@ export class DialQueue {
187192
throw err
188193
}
189194

195+
// make sure we don't have an existing connection to any of the addresses we
196+
// are about to dial
197+
let existingConnection = Array.from(this.connections.values()).flat().find(conn => {
198+
if (options.force === true) {
199+
return false
200+
}
201+
202+
return addrsToDial.find(addr => {
203+
return addr.multiaddr.equals(conn.remoteAddr)
204+
})
205+
})
206+
207+
if (existingConnection != null) {
208+
log('already connected to %a', existingConnection.remoteAddr)
209+
return existingConnection
210+
}
211+
190212
// ready to dial, all async work finished - make sure we don't have any
191213
// pending dials in progress for this peer or set of multiaddrs
192214
const existingDial = this.pendingDials.find(dial => {
@@ -257,7 +279,28 @@ export class DialQueue {
257279
// let other dials join this one
258280
this.pendingDials.push(pendingDial)
259281

260-
return pendingDial.promise
282+
const connection = await pendingDial.promise
283+
284+
// we may have been dialing a multiaddr without a peer id attached but by
285+
// this point we have upgraded the connection so the remote peer information
286+
// should be available - check again that we don't already have a connection
287+
// to the remote multiaddr
288+
existingConnection = Array.from(this.connections.values()).flat().find(conn => {
289+
if (options.force === true) {
290+
return false
291+
}
292+
293+
return conn.id !== connection.id && conn.remoteAddr.equals(connection.remoteAddr)
294+
})
295+
296+
if (existingConnection != null) {
297+
log('already connected to %a', existingConnection.remoteAddr)
298+
await connection.close()
299+
return existingConnection
300+
}
301+
302+
log('connection opened to %a', connection.remoteAddr)
303+
return connection
261304
}
262305

263306
private createDialAbortControllers (userSignal?: AbortSignal): ClearableSignal {

packages/libp2p/src/connection-manager/index.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ export class DefaultConnectionManager implements ConnectionManager, Startable {
261261
dialTimeout: init.dialTimeout ?? DIAL_TIMEOUT,
262262
resolvers: init.resolvers ?? {
263263
dnsaddr: dnsaddrResolver
264-
}
264+
},
265+
connections: this.connections
265266
})
266267
}
267268

@@ -505,12 +506,13 @@ export class DefaultConnectionManager implements ConnectionManager, Startable {
505506

506507
if (peerId != null && options.force !== true) {
507508
log('dial %p', peerId)
508-
const existingConnections = this.getConnections(peerId)
509+
const existingConnection = this.getConnections(peerId)
510+
.find(conn => !conn.transient)
509511

510-
if (existingConnections.length > 0) {
511-
log('had an existing connection to %p', peerId)
512+
if (existingConnection != null) {
513+
log('had an existing non-transient connection to %p', peerId)
512514

513-
return existingConnections[0]
515+
return existingConnection
514516
}
515517
}
516518

packages/libp2p/test/connection-manager/index.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,4 +536,40 @@ describe('Connection Manager', () => {
536536
await expect(connectionManager.acceptIncomingConnection(maConn2))
537537
.to.eventually.be.true()
538538
})
539+
540+
it('should allow dialing peers when an existing transient connection exists', async () => {
541+
connectionManager = new DefaultConnectionManager({
542+
peerId: await createEd25519PeerId(),
543+
peerStore: stubInterface<PeerStore>(),
544+
transportManager: stubInterface<TransportManager>(),
545+
connectionGater: stubInterface<ConnectionGater>(),
546+
events: new TypedEventEmitter()
547+
}, {
548+
...defaultOptions,
549+
maxIncomingPendingConnections: 1
550+
})
551+
await connectionManager.start()
552+
553+
const targetPeer = await createEd25519PeerId()
554+
const addr = multiaddr(`/ip4/123.123.123.123/tcp/123/p2p/${targetPeer}`)
555+
556+
const existingConnection = stubInterface<Connection>({
557+
transient: true
558+
})
559+
const newConnection = stubInterface<Connection>()
560+
561+
sinon.stub(connectionManager.dialQueue, 'dial')
562+
.withArgs(addr)
563+
.resolves(newConnection)
564+
565+
// we have an existing transient connection
566+
const map = connectionManager.getConnectionsMap()
567+
map.set(targetPeer, [
568+
existingConnection
569+
])
570+
571+
const conn = await connectionManager.openConnection(addr)
572+
573+
expect(conn).to.equal(newConnection)
574+
})
539575
})

0 commit comments

Comments
 (0)