Skip to content

Commit 91b15b6

Browse files
feat: custom dialer addr sorter (#792)
* feat: custom dialer addr sorter * chore: use libp2p utils sorter via addressBook getMultiaddrsForPeer * chore: use new libp2p utils * chore: apply suggestions from code review Co-authored-by: Jacob Heun <[email protected]> Co-authored-by: Jacob Heun <[email protected]>
1 parent 9205fce commit 91b15b6

File tree

9 files changed

+76
-57
lines changed

9 files changed

+76
-57
lines changed

doc/CONFIGURATION.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,7 @@ Dialing in libp2p can be configured to limit the rate of dialing, and how long d
499499
| maxDialsPerPeer | `number` | How many multiaddrs we can dial per peer, in parallel. |
500500
| dialTimeout | `number` | Second dial timeout per peer in ms. |
501501
| resolvers | `object` | Dial [Resolvers](https://github.com/multiformats/js-multiaddr/blob/master/src/resolvers/index.js) for resolving multiaddrs |
502+
| addressSorter | `(Array<Address>) => Array<Address>` | Sort the known addresses of a peer before trying to dial. |
502503

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

@@ -509,6 +510,7 @@ const MPLEX = require('libp2p-mplex')
509510
const { NOISE } = require('libp2p-noise')
510511

511512
const { dnsaddrResolver } = require('multiaddr/src/resolvers')
513+
const { publicAddressesFirst } = require('libp2p-utils/src/address-sort')
512514

513515
const node = await Libp2p.create({
514516
modules: {
@@ -522,7 +524,8 @@ const node = await Libp2p.create({
522524
dialTimeout: 30e3,
523525
resolvers: {
524526
dnsaddr: dnsaddrResolver
525-
}
527+
},
528+
addressSorter: publicAddressesFirst
526529
}
527530
```
528531

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"it-protocol-buffers": "^0.2.0",
6262
"libp2p-crypto": "^0.18.0",
6363
"libp2p-interfaces": "^0.7.2",
64-
"libp2p-utils": "^0.2.1",
64+
"libp2p-utils": "^0.2.2",
6565
"mafmt": "^8.0.0",
6666
"merge-options": "^2.0.0",
6767
"moving-average": "^1.0.0",

src/circuit/auto-relay.js

Lines changed: 9 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ const debug = require('debug')
44
const log = debug('libp2p:auto-relay')
55
log.error = debug('libp2p:auto-relay:error')
66

7-
const isPrivate = require('libp2p-utils/src/multiaddr/is-private')
8-
97
const uint8ArrayFromString = require('uint8arrays/from-string')
108
const uint8ArrayToString = require('uint8arrays/to-string')
119
const multiaddr = require('multiaddr')
@@ -36,6 +34,7 @@ class AutoRelay {
3634
this._peerStore = libp2p.peerStore
3735
this._connectionManager = libp2p.connectionManager
3836
this._transportManager = libp2p.transportManager
37+
this._addressSorter = libp2p.dialer.addressSorter
3938

4039
this.maxListeners = maxListeners
4140

@@ -129,37 +128,19 @@ class AutoRelay {
129128
return
130129
}
131130

132-
// Create relay listen addr
133-
let listenAddr, remoteMultiaddr, remoteAddrs
134-
135-
try {
136-
// Get peer known addresses and sort them per public addresses first
137-
remoteAddrs = this._peerStore.addressBook.get(connection.remotePeer)
138-
// TODO: This sort should be customizable in the config (dialer addr sort)
139-
remoteAddrs.sort(multiaddrsCompareFunction)
140-
141-
remoteMultiaddr = remoteAddrs.find(a => a.isCertified).multiaddr // Get first announced address certified
142-
// TODO: HOP Relays should avoid advertising private addresses!
143-
} catch (_) {
144-
log.error(`${id} does not have announced certified multiaddrs`)
145-
146-
// Attempt first if existing
147-
if (!remoteAddrs || !remoteAddrs.length) {
148-
return
149-
}
150-
151-
remoteMultiaddr = remoteAddrs[0].multiaddr
152-
}
131+
// Get peer known addresses and sort them per public addresses first
132+
const remoteAddrs = this._peerStore.addressBook.getMultiaddrsForPeer(
133+
connection.remotePeer, this._addressSorter
134+
)
153135

154-
if (!remoteMultiaddr.protoNames().includes('p2p')) {
155-
listenAddr = `${remoteMultiaddr.toString()}/p2p/${connection.remotePeer.toB58String()}/p2p-circuit`
156-
} else {
157-
listenAddr = `${remoteMultiaddr.toString()}/p2p-circuit`
136+
if (!remoteAddrs || !remoteAddrs.length) {
137+
return
158138
}
159139

160-
// Attempt to listen on relay
140+
const listenAddr = `${remoteAddrs[0].toString()}/p2p-circuit`
161141
this._listenRelays.add(id)
162142

143+
// Attempt to listen on relay
163144
try {
164145
await this._transportManager.listen([multiaddr(listenAddr)])
165146
// Announce multiaddrs will update on listen success by TransportManager event being triggered
@@ -269,24 +250,4 @@ class AutoRelay {
269250
}
270251
}
271252

272-
/**
273-
* Compare function for array.sort().
274-
* This sort aims to move the private adresses to the end of the array.
275-
*
276-
* @param {Address} a
277-
* @param {Address} b
278-
* @returns {number}
279-
*/
280-
function multiaddrsCompareFunction (a, b) {
281-
const isAPrivate = isPrivate(a.multiaddr)
282-
const isBPrivate = isPrivate(b.multiaddr)
283-
284-
if (isAPrivate && !isBPrivate) {
285-
return 1
286-
} else if (!isAPrivate && isBPrivate) {
287-
return -1
288-
}
289-
return 0
290-
}
291-
292253
module.exports = AutoRelay

src/config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const Constants = require('./constants')
77
const { AGENT_VERSION } = require('./identify/consts')
88
const RelayConstants = require('./circuit/constants')
99

10+
const { publicAddressesFirst } = require('libp2p-utils/src/address-sort')
1011
const { FaultTolerance } = require('./transport-manager')
1112

1213
const DefaultConfig = {
@@ -27,7 +28,8 @@ const DefaultConfig = {
2728
dialTimeout: Constants.DIAL_TIMEOUT,
2829
resolvers: {
2930
dnsaddr: dnsaddrResolver
30-
}
31+
},
32+
addressSorter: publicAddressesFirst
3133
},
3234
host: {
3335
agentVersion: AGENT_VERSION

src/dialer/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const log = debug('libp2p:dialer')
99
log.error = debug('libp2p:dialer:error')
1010

1111
const { DialRequest } = require('./dial-request')
12+
const { publicAddressesFirst } = require('libp2p-utils/src/address-sort')
1213
const getPeer = require('../get-peer')
1314

1415
const { codes } = require('../errors')
@@ -24,6 +25,7 @@ class Dialer {
2425
* @param {object} options
2526
* @param {TransportManager} options.transportManager
2627
* @param {Peerstore} options.peerStore
28+
* @param {(addresses: Array<Address) => Array<Address>} [options.addressSorter = publicAddressesFirst] - Sort the known addresses of a peer before trying to dial.
2729
* @param {number} [options.concurrency = MAX_PARALLEL_DIALS] - Number of max concurrent dials.
2830
* @param {number} [options.perPeerLimit = MAX_PER_PEER_DIALS] - Number of max concurrent dials per peer.
2931
* @param {number} [options.timeout = DIAL_TIMEOUT] - How long a dial attempt is allowed to take.
@@ -32,13 +34,15 @@ class Dialer {
3234
constructor ({
3335
transportManager,
3436
peerStore,
37+
addressSorter = publicAddressesFirst,
3538
concurrency = MAX_PARALLEL_DIALS,
3639
timeout = DIAL_TIMEOUT,
3740
perPeerLimit = MAX_PER_PEER_DIALS,
3841
resolvers = {}
3942
}) {
4043
this.transportManager = transportManager
4144
this.peerStore = peerStore
45+
this.addressSorter = addressSorter
4246
this.concurrency = concurrency
4347
this.timeout = timeout
4448
this.perPeerLimit = perPeerLimit
@@ -120,7 +124,7 @@ class Dialer {
120124
this.peerStore.addressBook.add(id, multiaddrs)
121125
}
122126

123-
let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []
127+
let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id, this.addressSorter) || []
124128

125129
// If received a multiaddr to dial, it should be the first to use
126130
// But, if we know other multiaddrs for the peer, we should try them too.

src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ class Libp2p extends EventEmitter {
136136
concurrency: this._options.dialer.maxParallelDials,
137137
perPeerLimit: this._options.dialer.maxDialsPerPeer,
138138
timeout: this._options.dialer.dialTimeout,
139-
resolvers: this._options.dialer.resolvers
139+
resolvers: this._options.dialer.resolvers,
140+
addressSorter: this._options.dialer.addressSorter
140141
})
141142

142143
this._modules.transport.forEach((Transport) => {

src/peer-store/address-book.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,20 +319,22 @@ class AddressBook extends Book {
319319
* Returns `undefined` if there are no known multiaddrs for the given peer.
320320
*
321321
* @param {PeerId} peerId
322+
* @param {(addresses: Array<Address) => Array<Address>} [addressSorter]
322323
* @returns {Array<Multiaddr>|undefined}
323324
*/
324-
getMultiaddrsForPeer (peerId) {
325+
getMultiaddrsForPeer (peerId, addressSorter = (ms) => ms) {
325326
if (!PeerId.isPeerId(peerId)) {
326327
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
327328
}
328329

329330
const entry = this.data.get(peerId.toB58String())
330-
331331
if (!entry || !entry.addresses) {
332332
return undefined
333333
}
334334

335-
return entry.addresses.map((address) => {
335+
return addressSorter(
336+
entry.addresses || []
337+
).map((address) => {
336338
const multiaddr = address.multiaddr
337339

338340
const idString = multiaddr.getPeerId()

test/dialing/direct.spec.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const { AbortError } = require('libp2p-interfaces/src/transport/errors')
1616
const { codes: ErrorCodes } = require('../../src/errors')
1717
const Constants = require('../../src/constants')
1818
const Dialer = require('../../src/dialer')
19+
const addressSort = require('libp2p-utils/src/address-sort')
1920
const PeerStore = require('../../src/peer-store')
2021
const TransportManager = require('../../src/transport-manager')
2122
const Libp2p = require('../../src')
@@ -44,6 +45,7 @@ describe('Dialing (direct, WebSockets)', () => {
4445
})
4546

4647
afterEach(() => {
48+
peerStore.delete(peerId)
4749
sinon.restore()
4850
})
4951

@@ -176,6 +178,37 @@ describe('Dialing (direct, WebSockets)', () => {
176178
.and.to.have.property('code', ErrorCodes.ERR_TIMEOUT)
177179
})
178180

181+
it('should sort addresses on dial', async () => {
182+
const peerMultiaddrs = [
183+
multiaddr('/ip4/127.0.0.1/tcp/15001/ws'),
184+
multiaddr('/ip4/20.0.0.1/tcp/15001/ws'),
185+
multiaddr('/ip4/30.0.0.1/tcp/15001/ws')
186+
]
187+
188+
sinon.spy(addressSort, 'publicAddressesFirst')
189+
sinon.stub(localTM, 'dial').callsFake(createMockConnection)
190+
191+
const dialer = new Dialer({
192+
transportManager: localTM,
193+
addressSorter: addressSort.publicAddressesFirst,
194+
concurrency: 3,
195+
peerStore
196+
})
197+
198+
// Inject data in the AddressBook
199+
peerStore.addressBook.add(peerId, peerMultiaddrs)
200+
201+
// Perform 3 multiaddr dials
202+
await dialer.connectToPeer(peerId)
203+
204+
expect(addressSort.publicAddressesFirst.callCount).to.eql(1)
205+
206+
const sortedAddresses = addressSort.publicAddressesFirst(peerMultiaddrs.map((m) => ({ multiaddr: m })))
207+
expect(localTM.dial.getCall(0).args[0].equals(sortedAddresses[0].multiaddr))
208+
expect(localTM.dial.getCall(1).args[0].equals(sortedAddresses[1].multiaddr))
209+
expect(localTM.dial.getCall(2).args[0].equals(sortedAddresses[2].multiaddr))
210+
})
211+
179212
it('should dial to the max concurrency', async () => {
180213
const dialer = new Dialer({
181214
transportManager: localTM,

test/peer-store/address-book.spec.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { expect } = require('aegir/utils/chai')
66
const { Buffer } = require('buffer')
77
const multiaddr = require('multiaddr')
88
const arrayEquals = require('libp2p-utils/src/array-equals')
9+
const addressSort = require('libp2p-utils/src/address-sort')
910
const PeerId = require('peer-id')
1011
const pDefer = require('p-defer')
1112

@@ -19,7 +20,7 @@ const {
1920
} = require('../../src/errors')
2021

2122
const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000')
22-
const addr2 = multiaddr('/ip4/127.0.0.1/tcp/8001')
23+
const addr2 = multiaddr('/ip4/20.0.0.1/tcp/8001')
2324
const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002')
2425

2526
describe('addressBook', () => {
@@ -340,6 +341,18 @@ describe('addressBook', () => {
340341
expect(m.getPeerId()).to.equal(peerId.toB58String())
341342
})
342343
})
344+
345+
it('can sort multiaddrs providing a sorter', () => {
346+
const supportedMultiaddrs = [addr1, addr2]
347+
ab.set(peerId, supportedMultiaddrs)
348+
349+
const multiaddrs = ab.getMultiaddrsForPeer(peerId, addressSort.publicAddressesFirst)
350+
const sortedAddresses = addressSort.publicAddressesFirst(supportedMultiaddrs.map((m) => ({ multiaddr: m })))
351+
352+
multiaddrs.forEach((m, index) => {
353+
expect(m.equals(sortedAddresses[index].multiaddr))
354+
})
355+
})
343356
})
344357

345358
describe('addressBook.delete', () => {

0 commit comments

Comments
 (0)