Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: resolve multiaddrs before dial #782

Merged
merged 4 commits into from
Nov 4, 2020
Merged
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
8 changes: 7 additions & 1 deletion doc/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d
| maxParallelDials | `number` | How many multiaddrs we can dial in parallel. |
| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. |
| dialTimeout | `number` | Second dial timeout per peer in ms. |
| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs |

The below configuration example shows how the dialer should be configured, with the current defaults:

Expand All @@ -474,6 +475,8 @@ const TCP = require('libp2p-tcp')
const MPLEX = require('libp2p-mplex')
const { NOISE } = require('libp2p-noise')

const { dnsaddrResolver } = require('multiaddr/src/resolvers')

const node = await Libp2p.create({
modules: {
transport: [TCP],
Expand All @@ -483,7 +486,10 @@ const node = await Libp2p.create({
dialer: {
maxParallelDials: 100,
maxDialsPerPeer: 4,
dialTimeout: 30e3
dialTimeout: 30e3,
resolvers: {
dnsaddr: dnsaddrResolver
}
}
```

Expand Down
4 changes: 2 additions & 2 deletions doc/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ const Bootstrap = require('libp2p-bootstrap')

// Known peers addresses
const bootstrapMultiaddrs = [
'/dns4/ams-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',
'/dns4/lon-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN'
]

const node = await Libp2p.create({
Expand Down
11 changes: 5 additions & 6 deletions examples/libp2p-in-the-browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ document.addEventListener('DOMContentLoaded', async () => {
[Bootstrap.tag]: {
enabled: true,
list: [
'/dns4/ams-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd',
'/dns4/lon-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3',
'/dns4/sfo-3.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM',
'/dns4/sgp-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu',
'/dns4/nyc-1.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm',
'/dns4/nyc-2.bootstrap.libp2p.io/tcp/443/wss/p2p/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64'
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmZa1sAxajnQjVM8WjWXoMbmPd7NsWhfKsPkErzpm9wGkp',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt'
]
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"mafmt": "^8.0.0",
"merge-options": "^2.0.0",
"moving-average": "^1.0.0",
"multiaddr": "^8.0.0",
"multiaddr": "^8.1.0",
"multicodec": "^2.0.0",
"multistream-select": "^1.0.0",
"mutable-proxy": "^1.0.0",
Expand Down
7 changes: 6 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict'

const mergeOptions = require('merge-options')
const { dnsaddrResolver } = require('multiaddr/src/resolvers')

const Constants = require('./constants')

const { FaultTolerance } = require('./transport-manager')
Expand All @@ -20,7 +22,10 @@ const DefaultConfig = {
dialer: {
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
dialTimeout: Constants.DIAL_TIMEOUT
dialTimeout: Constants.DIAL_TIMEOUT,
resolvers: {
dnsaddr: dnsaddrResolver
}
},
metrics: {
enabled: false
Expand Down
72 changes: 65 additions & 7 deletions src/dialer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ class Dialer {
* @param {number} [options.concurrency = MAX_PARALLEL_DIALS] - Number of max concurrent dials.
* @param {number} [options.perPeerLimit = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer.
* @param {number} [options.timeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take.
* @param {object} [options.resolvers = {}] - multiaddr resolvers to use when dialing
*/
constructor ({
transportManager,
peerStore,
concurrency = MAX_PARALLEL_DIALS,
timeout = DIAL_TIMEOUT,
perPeerLimit = MAX_PER_PEER_DIALS
perPeerLimit = MAX_PER_PEER_DIALS,
resolvers = {}
}) {
this.transportManager = transportManager
this.peerStore = peerStore
Expand All @@ -42,6 +44,10 @@ class Dialer {
this.perPeerLimit = perPeerLimit
this.tokens = [...new Array(concurrency)].map((_, index) => index)
this._pendingDials = new Map()

for (const [key, value] of Object.entries(resolvers)) {
multiaddr.resolvers.set(key, value)
}
}

/**
Expand Down Expand Up @@ -69,7 +75,7 @@ class Dialer {
* @returns {Promise<Connection>}
*/
async connectToPeer (peer, options = {}) {
const dialTarget = this._createDialTarget(peer)
const dialTarget = await this._createDialTarget(peer)

if (!dialTarget.addrs.length) {
throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES)
Expand Down Expand Up @@ -105,22 +111,28 @@ class Dialer {
*
* @private
* @param {PeerId|Multiaddr|string} peer - A PeerId or Multiaddr
* @returns {DialTarget}
* @returns {Promise<DialTarget>}
*/
_createDialTarget (peer) {
async _createDialTarget (peer) {
const { id, multiaddrs } = getPeer(peer)

if (multiaddrs) {
this.peerStore.addressBook.add(id, multiaddrs)
}

let addrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []
let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []

// If received a multiaddr to dial, it should be the first to use
// But, if we know other multiaddrs for the peer, we should try them too.
if (multiaddr.isMultiaddr(peer)) {
addrs = addrs.filter((addr) => !peer.equals(addr))
addrs.unshift(peer)
knownAddrs = knownAddrs.filter((addr) => !peer.equals(addr))
knownAddrs.unshift(peer)
}

const addrs = []
for (const a of knownAddrs) {
const resolvedAddrs = await this._resolve(a)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolve can throw, we shouldn't let a single address failure stop the whole dial, we need to catch it

resolvedAddrs.forEach(ra => addrs.push(ra))
}

return {
Expand Down Expand Up @@ -190,6 +202,52 @@ class Dialer {
log('token %d released', token)
this.tokens.push(token)
}

/**
* Resolve multiaddr recursively.
*
* @param {Multiaddr} ma
* @returns {Promise<Array<Multiaddr>>}
*/
async _resolve (ma) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we support recursion in multiaddr, as well as dns4+dns6 resolution, we will not need this function, only the function below

// TODO: recursive logic should live in multiaddr once dns4/dns6 support is in place
// Now only supporting resolve for dnsaddr
const resolvableProto = ma.protoNames().includes('dnsaddr')
jacobheun marked this conversation as resolved.
Show resolved Hide resolved

// Multiaddr is not resolvable? End recursion!
if (!resolvableProto) {
return [ma]
}

const resolvedMultiaddrs = await this._resolveRecord(ma)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can throw which will stop the whole dial, we should let individual addresses fail.

const recursiveMultiaddrs = await Promise.all(resolvedMultiaddrs.map((nm) => {
return this._resolve(nm)
}))

return recursiveMultiaddrs.flat().reduce((array, newM) => {
if (!array.find(m => m.equals(newM))) {
array.push(newM)
}
return array
}, []) // Unique addresses
}

/**
* Resolve a given multiaddr. If this fails, an empty array will be returned
*
* @param {Multiaddr} ma
* @returns {Promise<Array<Multiaddr>>}
*/
async _resolveRecord (ma) {
try {
ma = multiaddr(ma.toString()) // Use current multiaddr module
const multiaddrs = await ma.resolve()
return multiaddrs
} catch (_) {
log.error(`multiaddr ${ma} could not be resolved`)
return []
}
}
}

module.exports = Dialer
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ class Libp2p extends EventEmitter {
peerStore: this.peerStore,
concurrency: this._options.dialer.maxParallelDials,
perPeerLimit: this._options.dialer.maxDialsPerPeer,
timeout: this._options.dialer.dialTimeout
timeout: this._options.dialer.dialTimeout,
resolvers: this._options.dialer.resolvers
})

this._modules.transport.forEach((Transport) => {
Expand Down
6 changes: 3 additions & 3 deletions test/dialing/direct.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ describe('Dialing (direct, TCP)', () => {

it('should dial to the max concurrency', async () => {
const addrs = [
'/ip4/0.0.0.0/tcp/8000',
'/ip4/0.0.0.0/tcp/8001',
'/ip4/0.0.0.0/tcp/8002'
multiaddr('/ip4/0.0.0.0/tcp/8000'),
multiaddr('/ip4/0.0.0.0/tcp/8001'),
multiaddr('/ip4/0.0.0.0/tcp/8002')
]
const dialer = new Dialer({
transportManager: localTM,
Expand Down
5 changes: 0 additions & 5 deletions test/dialing/direct.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,18 +263,13 @@ describe('Dialing (direct, WebSockets)', () => {

describe('libp2p.dialer', () => {
let libp2p
let remoteLibp2p

afterEach(async () => {
sinon.restore()
libp2p && await libp2p.stop()
libp2p = null
})

after(async () => {
remoteLibp2p && await remoteLibp2p.stop()
})

it('should create a dialer', () => {
libp2p = new Libp2p({
peerId,
Expand Down
Loading