Skip to content

Commit d01c37e

Browse files
authored
fix: when passed a multiaddr, only dial that multiaddr (#1498)
* fix: when passed a multiaddr, only dial that multiaddr When a user passes a multiaddr to `libp2p.dial`, only dial that multiaddr instead of extracting the peer id from the multiaddr, using it to look up every known address and dialing them all. When passing a peer id to `libp2p.dial`, all addresses will be dialed as before. Fixes #1497 * chore: review comments * chore: linting
1 parent bae32ba commit d01c37e

File tree

4 files changed

+182
-73
lines changed

4 files changed

+182
-73
lines changed

src/connection-manager/dialer/index.ts

+95-65
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import { logger } from '@libp2p/logger'
2-
import all from 'it-all'
3-
import filter from 'it-filter'
4-
import { pipe } from 'it-pipe'
52
import errCode from 'err-code'
6-
import type { Multiaddr, Resolver } from '@multiformats/multiaddr'
7-
import { multiaddr, resolvers } from '@multiformats/multiaddr'
3+
import { isMultiaddr, Multiaddr, Resolver, multiaddr, resolvers } from '@multiformats/multiaddr'
84
import { TimeoutController } from 'timeout-abort-controller'
9-
import { AbortError } from '@libp2p/interfaces/errors'
105
import { anySignal } from 'any-signal'
116
import { setMaxListeners } from 'events'
127
import { DialAction, DialRequest } from './dial-request.js'
@@ -22,10 +17,8 @@ import {
2217
import type { Connection, ConnectionGater } from '@libp2p/interface-connection'
2318
import type { AbortOptions } from '@libp2p/interfaces'
2419
import type { Startable } from '@libp2p/interfaces/startable'
25-
import type { PeerId } from '@libp2p/interface-peer-id'
20+
import { isPeerId, PeerId } from '@libp2p/interface-peer-id'
2621
import { getPeer } from '../../get-peer.js'
27-
import sort from 'it-sort'
28-
import map from 'it-map'
2922
import type { AddressSorter, PeerStore } from '@libp2p/interface-peer-store'
3023
import type { Metrics } from '@libp2p/interface-metrics'
3124
import type { Dialer } from '@libp2p/interface-connection-manager'
@@ -98,7 +91,7 @@ export class DefaultDialer implements Startable, Dialer {
9891
private readonly maxDialsPerPeer: number
9992
public tokens: number[]
10093
public pendingDials: Map<string, PendingDial>
101-
public pendingDialTargets: Map<string, PendingDialTarget>
94+
public pendingDialTargets: Map<string, AbortController>
10295
private started: boolean
10396

10497
constructor (components: DefaultDialerComponents, init: DialerInit = {}) {
@@ -147,7 +140,7 @@ export class DefaultDialer implements Startable, Dialer {
147140
this.pendingDials.clear()
148141

149142
for (const pendingTarget of this.pendingDialTargets.values()) {
150-
pendingTarget.reject(new AbortError('Dialer was destroyed'))
143+
pendingTarget.abort()
151144
}
152145
this.pendingDialTargets.clear()
153146
}
@@ -157,8 +150,8 @@ export class DefaultDialer implements Startable, Dialer {
157150
* The dial to the first address that is successfully able to upgrade a connection
158151
* will be used.
159152
*/
160-
async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise<Connection> {
161-
const { id, multiaddrs } = getPeer(peer)
153+
async dial (peerIdOrMultiaddr: PeerId | Multiaddr, options: AbortOptions = {}): Promise<Connection> {
154+
const { id, multiaddrs } = getPeer(peerIdOrMultiaddr)
162155

163156
if (this.components.peerId.equals(id)) {
164157
throw errCode(new Error('Tried to dial self'), codes.ERR_DIALED_SELF)
@@ -177,13 +170,35 @@ export class DefaultDialer implements Startable, Dialer {
177170

178171
log('creating dial target for %p', id)
179172

180-
const dialTarget = await this._createCancellableDialTarget(id, options)
173+
// resolving multiaddrs can involve dns lookups so allow them to be aborted
174+
const controller = new AbortController()
175+
const controllerId = randomId()
176+
this.pendingDialTargets.set(controllerId, controller)
177+
let signal = controller.signal
178+
179+
// merge with the passed signal, if any
180+
if (options.signal != null) {
181+
signal = anySignal([signal, options.signal])
182+
}
183+
184+
let dialTarget: DialTarget
185+
186+
try {
187+
dialTarget = await this._createDialTarget(peerIdOrMultiaddr, {
188+
...options,
189+
signal
190+
})
191+
} finally {
192+
// done resolving the multiaddrs so remove the abort controller
193+
this.pendingDialTargets.delete(controllerId)
194+
}
181195

182196
if (dialTarget.addrs.length === 0) {
183197
throw errCode(new Error('The dial request has no valid addresses'), codes.ERR_NO_VALID_ADDRESSES)
184198
}
185199

186-
const pendingDial = this.pendingDials.get(dialTarget.id) ?? this._createPendingDial(dialTarget, options)
200+
// try to join an in-flight dial for this peer if one is available
201+
const pendingDial = this.pendingDials.get(dialTarget.id.toString()) ?? this._createPendingDial(dialTarget, options)
187202

188203
try {
189204
const connection = await pendingDial.promise
@@ -202,76 +217,77 @@ export class DefaultDialer implements Startable, Dialer {
202217
}
203218
}
204219

205-
/**
206-
* Connects to a given `peer` by dialing all of its known addresses.
207-
* The dial to the first address that is successfully able to upgrade a connection
208-
* will be used.
209-
*/
210-
async _createCancellableDialTarget (peer: PeerId, options: AbortOptions): Promise<DialTarget> {
211-
// Make dial target promise cancellable
212-
const id = `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}`
213-
const cancellablePromise = new Promise<DialTarget>((resolve, reject) => {
214-
this.pendingDialTargets.set(id, { resolve, reject })
215-
})
216-
217-
try {
218-
const dialTarget = await Promise.race([
219-
this._createDialTarget(peer, options),
220-
cancellablePromise
221-
])
222-
223-
return dialTarget
224-
} finally {
225-
this.pendingDialTargets.delete(id)
226-
}
227-
}
228-
229220
/**
230221
* Creates a DialTarget. The DialTarget is used to create and track
231222
* the DialRequest to a given peer.
232-
* If a multiaddr is received it should be the first address attempted.
223+
*
224+
* If a multiaddr is received it should be the only address attempted.
225+
*
233226
* Multiaddrs not supported by the available transports will be filtered out.
234227
*/
235-
async _createDialTarget (peer: PeerId, options: AbortOptions): Promise<DialTarget> {
228+
async _createDialTarget (peerIdOrMultiaddr: PeerId | Multiaddr, options: AbortOptions): Promise<DialTarget> {
236229
const _resolve = this._resolve.bind(this)
237230

238-
let addrs = await pipe(
239-
await this.components.peerStore.addressBook.get(peer),
240-
(source) => filter(source, async (address) => {
241-
return !(await this.components.connectionGater.denyDialMultiaddr(peer, address.multiaddr))
242-
}),
243-
// Sort addresses so, for example, we try certified public address first
244-
(source) => sort(source, this.addressSorter),
245-
async function * resolve (source) {
246-
for await (const a of source) {
247-
yield * await _resolve(a.multiaddr, options)
248-
}
249-
},
250-
// Multiaddrs not supported by the available transports will be filtered out.
251-
(source) => filter(source, (ma) => Boolean(this.components.transportManager.transportForMultiaddr(ma))),
252-
(source) => map(source, (ma) => {
253-
if (peer.toString() === ma.getPeerId()) {
254-
return ma
255-
}
231+
let addrs = isMultiaddr(peerIdOrMultiaddr) ? [peerIdOrMultiaddr] : await this._loadAddresses(peerIdOrMultiaddr)
256232

257-
return ma.encapsulate(`/p2p/${peer.toString()}`)
258-
}),
259-
async (source) => await all(source)
260-
)
233+
addrs = (await Promise.all(
234+
addrs.map(async (ma) => await _resolve(ma, options))
235+
))
236+
.flat()
237+
// Multiaddrs not supported by the available transports will be filtered out.
238+
.filter(ma => Boolean(this.components.transportManager.transportForMultiaddr(ma)))
261239

240+
// deduplicate addresses
262241
addrs = [...new Set(addrs)]
263242

264243
if (addrs.length > this.maxAddrsToDial) {
265-
await this.components.peerStore.delete(peer)
266244
throw errCode(new Error('dial with more addresses than allowed'), codes.ERR_TOO_MANY_ADDRESSES)
267245
}
268246

247+
const peerId = isPeerId(peerIdOrMultiaddr) ? peerIdOrMultiaddr : undefined
248+
249+
if (peerId != null) {
250+
const peerIdMultiaddr = `/p2p/${peerId.toString()}`
251+
addrs = addrs.map(addr => {
252+
const addressPeerId = addr.getPeerId()
253+
254+
if (addressPeerId == null || !peerId.equals(addressPeerId)) {
255+
return addr.encapsulate(peerIdMultiaddr)
256+
}
257+
258+
return addr
259+
})
260+
}
261+
269262
return {
270-
id: peer.toString(),
263+
id: peerId == null ? randomId() : peerId.toString(),
271264
addrs
272265
}
273266
}
274267

268+
/**
269+
* Loads a list of addresses from the peer store for the passed peer id
270+
*/
271+
async _loadAddresses (peer: PeerId): Promise<Multiaddr[]> {
272+
const addresses = await this.components.peerStore.addressBook.get(peer)
273+
274+
return (await Promise.all(
275+
addresses.map(async address => {
276+
const deny = await this.components.connectionGater.denyDialMultiaddr(peer, address.multiaddr)
277+
278+
if (deny) {
279+
return false
280+
}
281+
282+
return address
283+
})
284+
))
285+
.filter(isTruthy)
286+
// Sort addresses so, for example, we try certified public address first
287+
.sort(this.addressSorter)
288+
.map(address => address.multiaddr)
289+
}
290+
275291
/**
276292
* Creates a PendingDial that wraps the underlying DialRequest
277293
*/
@@ -383,3 +399,17 @@ export class DefaultDialer implements Startable, Dialer {
383399
}
384400
}
385401
}
402+
403+
/**
404+
* Type safe version of `list.filter(Boolean)`
405+
*/
406+
function isTruthy <T> (e: T | false | null | undefined): e is T {
407+
return Boolean(e)
408+
}
409+
410+
/**
411+
* Returns a random string
412+
*/
413+
function randomId (): string {
414+
return `${(parseInt(String(Math.random() * 1e9), 10)).toString()}${Date.now()}`
415+
}

src/connection-manager/index.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import type { Connection, MultiaddrConnection } from '@libp2p/interface-connecti
1212
import type { ConnectionManager, Dialer } from '@libp2p/interface-connection-manager'
1313
import * as STATUS from '@libp2p/interface-connection/status'
1414
import type { AddressSorter, PeerStore } from '@libp2p/interface-peer-store'
15-
import { multiaddr, Multiaddr, Resolver } from '@multiformats/multiaddr'
15+
import { isMultiaddr, multiaddr, Multiaddr, Resolver } from '@multiformats/multiaddr'
1616
import { PeerMap } from '@libp2p/peer-collections'
1717
import { TimeoutController } from 'timeout-abort-controller'
1818
import { KEEP_ALIVE } from '@libp2p/interface-peer-store/tags'
1919
import { RateLimiterMemory } from 'rate-limiter-flexible'
2020
import type { Metrics } from '@libp2p/interface-metrics'
2121
import type { Upgrader } from '@libp2p/interface-transport'
22+
import { getPeer } from '../get-peer.js'
2223

2324
const log = logger('libp2p:connection-manager')
2425

@@ -494,7 +495,18 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
494495
return conns
495496
}
496497

497-
async openConnection (peerId: PeerId, options: AbortOptions = {}): Promise<Connection> {
498+
async openConnection (peerIdOrMultiaddr: PeerId | Multiaddr, options: AbortOptions = {}): Promise<Connection> {
499+
let peerId: PeerId
500+
501+
if (isPeerId(peerIdOrMultiaddr)) {
502+
peerId = peerIdOrMultiaddr
503+
} else if (isMultiaddr(peerIdOrMultiaddr)) {
504+
const info = getPeer(peerIdOrMultiaddr)
505+
peerId = info.id
506+
} else {
507+
throw errCode(new TypeError('Can only open connections to PeerIds or Multiaddrs'), codes.ERR_INVALID_PARAMETERS)
508+
}
509+
498510
log('dial to %p', peerId)
499511
const existingConnections = this.getConnections(peerId)
500512

@@ -517,7 +529,7 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
517529
}
518530

519531
try {
520-
const connection = await this.components.dialer.dial(peerId, options)
532+
const connection = await this.components.dialer.dial(peerIdOrMultiaddr, options)
521533
let peerConnections = this.connections.get(peerId.toString())
522534

523535
if (peerConnections == null) {

src/keychain/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export interface KeyChainComponents {
118118
*/
119119
export class KeyChain implements Startable {
120120
private readonly components: KeyChainComponents
121-
private init: KeyChainInit
121+
private readonly init: KeyChainInit
122122
private started: boolean
123123

124124
/**

0 commit comments

Comments
 (0)