Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions packages/libp2p/src/connection-manager/dial-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,12 @@ export class DialQueue {

// if we have no multiaddrs, only a peer id, set a flag so we will look the
// peer up in the peer routing to obtain multiaddrs
let forcePeerLookup = options.multiaddrs.size === 0
const peerIdOnlyDial = options.multiaddrs.size === 0
let forcePeerLookup = peerIdOnlyDial

let dialed = 0
let dialIteration = 0
let retriedNoValidAddresses = false
const errors: Error[] = []

this.log('starting dial to %p', peerId)
Expand All @@ -256,10 +258,23 @@ export class DialQueue {

// load addresses from address book, resolve and dnsaddrs, filter
// undialables, add peer IDs, etc
const calculatedAddrs = await this.calculateMultiaddrs(peerId, addrs, {
...options,
signal
})
let calculatedAddrs: Address[]

try {
calculatedAddrs = await this.calculateMultiaddrs(peerId, addrs, {
...options,
signal
})
} catch (err: any) {
if (err.name === NoValidAddressesError.name && peerId != null && peerIdOnlyDial && !retriedNoValidAddresses) {
this.log('no valid addresses for %p, retrying once to pick up newly discovered peer store addresses', peerId)
retriedNoValidAddresses = true
forcePeerLookup = true
continue
}

throw err
}

for (const addr of calculatedAddrs) {
// skip any addresses we have previously failed to dial
Expand Down
68 changes: 66 additions & 2 deletions packages/libp2p/test/connection-manager/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { generateKeyPair } from '@libp2p/crypto/keys'
import { InvalidParametersError, KEEP_ALIVE, start, stop } from '@libp2p/interface'
import { InvalidParametersError, KEEP_ALIVE, NotFoundError, start, stop } from '@libp2p/interface'
import { peerIdFromPrivateKey } from '@libp2p/peer-id'
import { multiaddr } from '@multiformats/multiaddr'
import { expect } from 'aegir/chai'
Expand All @@ -11,7 +11,7 @@ import { createLibp2p } from '../../src/index.js'
import { getComponent } from '../fixtures/get-component.js'
import { createDefaultConnectionManagerComponents } from './utils.js'
import type { StubbedDefaultConnectionManagerComponents } from './utils.js'
import type { Libp2p, Connection, MultiaddrConnection } from '@libp2p/interface'
import type { Libp2p, Connection, MultiaddrConnection, Transport } from '@libp2p/interface'

const defaultOptions = {
maxConnections: 10,
Expand Down Expand Up @@ -419,6 +419,70 @@ describe('Connection Manager', () => {
expect(conn).to.equal(newConnection)
})

it('should retry peer-id openConnection when peer store gains an address during dial', async () => {
connectionManager = new DefaultConnectionManager(components, defaultOptions)
await connectionManager.start()

const remotePeer = peerIdFromPrivateKey(await generateKeyPair('Ed25519'))
const discoveredAddr = multiaddr('/ip4/123.123.123.123/tcp/4001')
const discoveredAddrWithPeer = discoveredAddr.encapsulate(`/p2p/${remotePeer}`)

let resolvePeerRouting: (() => void) | undefined
const peerRoutingBlocked = new Promise<void>((resolve) => {
resolvePeerRouting = resolve
})

const peerStoreReadStarted = pWaitFor(async () => components.peerStore.get.calledOnce)
let hasAddress = false

components.peerStore.get.callsFake(async () => {
if (!hasAddress) {
throw new NotFoundError('Not found')
}

return {
id: remotePeer,
addresses: [{
multiaddr: discoveredAddr,
isCertified: false
}],
protocols: [],
metadata: new Map(),
tags: new Map()
}
})

components.peerRouting.findPeer.callsFake(async () => {
await peerRoutingBlocked

return {
id: remotePeer,
multiaddrs: []
}
})

components.transportManager.dialTransportForMultiaddr.returns(stubInterface<Transport>())
const connection = stubInterface<Connection>({
remotePeer,
remoteAddr: discoveredAddrWithPeer,
status: 'open'
})
components.transportManager.dial.callsFake(async (ma) => {
expect(ma.equals(discoveredAddrWithPeer)).to.equal(true)
return connection
})

const dialPromise = connectionManager.openConnection(remotePeer)
await peerStoreReadStarted

hasAddress = true
resolvePeerRouting?.()

await expect(dialPromise).to.eventually.equal(connection)
expect(components.peerStore.get.callCount).to.equal(2)
expect(components.transportManager.dial.calledOnce).to.equal(true)
})

it('should throw when setMaxConnections is less than 1', async () => {
connectionManager = new DefaultConnectionManager(components, {
...defaultOptions,
Expand Down
Loading