Skip to content

Commit c242610

Browse files
committed
feat: custom dialer addr sorter
1 parent bd214d5 commit c242610

File tree

9 files changed

+164
-54
lines changed

9 files changed

+164
-54
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 { sortPublicAddressesFirst } = require('libp2p/src/dialer/utils')
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: sortPublicAddressesFirst
526529
}
527530
```
528531

src/circuit/auto-relay.js

Lines changed: 11 additions & 41 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,28 +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-
}
131+
// Get peer known addresses and sort them per public addresses first
132+
// Sorting addresses to public first
133+
const remoteAddrs = this._addressSorter(
134+
this._peerStore.addressBook.get(connection.remotePeer) || []
135+
)
150136

151-
remoteMultiaddr = remoteAddrs[0].multiaddr
137+
if (!remoteAddrs || !remoteAddrs.length) {
138+
return
152139
}
153140

141+
const remoteMultiaddr = remoteAddrs[0].multiaddr
142+
143+
let listenAddr
154144
if (!remoteMultiaddr.protoNames().includes('p2p')) {
155145
listenAddr = `${remoteMultiaddr.toString()}/p2p/${connection.remotePeer.toB58String()}/p2p-circuit`
156146
} else {
@@ -269,24 +259,4 @@ class AutoRelay {
269259
}
270260
}
271261

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-
292262
module.exports = AutoRelay

src/config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { dnsaddrResolver } = require('multiaddr/src/resolvers')
66
const Constants = require('./constants')
77
const RelayConstants = require('./circuit/constants')
88

9+
const { sortPublicAddressesFirst } = require('./dialer/utils')
910
const { FaultTolerance } = require('./transport-manager')
1011

1112
const DefaultConfig = {
@@ -26,7 +27,8 @@ const DefaultConfig = {
2627
dialTimeout: Constants.DIAL_TIMEOUT,
2728
resolvers: {
2829
dnsaddr: dnsaddrResolver
29-
}
30+
},
31+
addressSorter: sortPublicAddressesFirst
3032
},
3133
metrics: {
3234
enabled: false

src/dialer/index.js

Lines changed: 14 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 { sortPublicAddressesFirst } = require('./utils')
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 = sortPublicAddressesFirst] - 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 = sortPublicAddressesFirst,
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,16 @@ class Dialer {
120124
this.peerStore.addressBook.add(id, multiaddrs)
121125
}
122126

123-
let knownAddrs = this.peerStore.addressBook.getMultiaddrsForPeer(id) || []
127+
let knownAddrs = this.addressSorter(
128+
this.peerStore.addressBook.get(id) || []
129+
).map((address) => {
130+
const multiaddr = address.multiaddr
131+
132+
const idString = multiaddr.getPeerId()
133+
if (idString && idString === id.toB58String()) return multiaddr
134+
135+
return multiaddr.encapsulate(`/p2p/${id.toB58String()}`)
136+
})
124137

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

src/dialer/utils.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
'use strict'
2+
3+
const isPrivate = require('libp2p-utils/src/multiaddr/is-private')
4+
5+
// TODO: Move to libp2p-utils?
6+
7+
/**
8+
* Compare function for array.sort().
9+
* This sort aims to move the private adresses to the end of the array.
10+
* In case of equality, a cerified address will come first.
11+
*
12+
* @param {Address} a
13+
* @param {Address} b
14+
* @returns {number}
15+
*/
16+
function addressesPublicFirstCompareFunction (a, b) {
17+
const isAPrivate = isPrivate(a.multiaddr)
18+
const isBPrivate = isPrivate(b.multiaddr)
19+
20+
if (isAPrivate && !isBPrivate) {
21+
return 1
22+
} else if (!isAPrivate && isBPrivate) {
23+
return -1
24+
}
25+
// Check certified?
26+
if (a.isCertified && !b.isCertified) {
27+
return -1
28+
} else if (!a.isCertified && b.isCertified) {
29+
return 1
30+
}
31+
32+
return 0
33+
}
34+
35+
/**
36+
* Sort given addresses by putting public addresses first.
37+
*
38+
* @param {Array<Address>} addresses
39+
* @returns {Array<Address>}
40+
*/
41+
function sortPublicAddressesFirst (addresses) {
42+
return [...addresses].sort(addressesPublicFirstCompareFunction)
43+
}
44+
45+
module.exports.sortPublicAddressesFirst = sortPublicAddressesFirst

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) => {

test/dialing/direct.node.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe('Dialing (direct, TCP)', () => {
132132
peerStore: {
133133
addressBook: {
134134
add: () => {},
135-
getMultiaddrsForPeer: () => [unsupportedAddr]
135+
get: () => [{ multiaddr: unsupportedAddr }]
136136
}
137137
}
138138
})
@@ -175,7 +175,7 @@ describe('Dialing (direct, TCP)', () => {
175175
peerStore: {
176176
addressBook: {
177177
add: () => {},
178-
getMultiaddrsForPeer: () => addrs
178+
get: () => addrs.map(a => ({ multiaddr: multiaddr(a) }))
179179
}
180180
}
181181
})

test/dialing/direct.spec.js

Lines changed: 30 additions & 7 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 dialerUtils = require('../../src/dialer/utils')
1920
const PeerStore = require('../../src/peer-store')
2021
const TransportManager = require('../../src/transport-manager')
2122
const Libp2p = require('../../src')
@@ -85,7 +86,7 @@ describe('Dialing (direct, WebSockets)', () => {
8586
peerStore: {
8687
addressBook: {
8788
add: () => {},
88-
getMultiaddrsForPeer: () => [remoteAddr]
89+
get: () => [{ multiaddr: remoteAddr.decapsulate('/p2p') }]
8990
}
9091
}
9192
})
@@ -101,7 +102,7 @@ describe('Dialing (direct, WebSockets)', () => {
101102
peerStore: {
102103
addressBook: {
103104
add: () => {},
104-
getMultiaddrsForPeer: () => [remoteAddr]
105+
get: () => [{ multiaddr: remoteAddr.decapsulate('/p2p') }]
105106
}
106107
}
107108
})
@@ -125,7 +126,7 @@ describe('Dialing (direct, WebSockets)', () => {
125126
peerStore: {
126127
addressBook: {
127128
add: () => {},
128-
getMultiaddrsForPeer: () => [remoteAddr]
129+
get: () => [{ multiaddr: remoteAddr.decapsulate('/p2p') }]
129130
}
130131
}
131132
})
@@ -141,7 +142,7 @@ describe('Dialing (direct, WebSockets)', () => {
141142
peerStore: {
142143
addressBook: {
143144
set: () => {},
144-
getMultiaddrsForPeer: () => [unsupportedAddr]
145+
get: () => [{ multiaddr: unsupportedAddr.decapsulate('/p2p') }]
145146
}
146147
}
147148
})
@@ -158,7 +159,7 @@ describe('Dialing (direct, WebSockets)', () => {
158159
peerStore: {
159160
addressBook: {
160161
add: () => {},
161-
getMultiaddrsForPeer: () => [remoteAddr]
162+
get: () => [{ multiaddr: remoteAddr.decapsulate('/p2p') }]
162163
}
163164
}
164165
})
@@ -176,14 +177,36 @@ describe('Dialing (direct, WebSockets)', () => {
176177
.and.to.have.property('code', ErrorCodes.ERR_TIMEOUT)
177178
})
178179

180+
it('should sort addresses on dial', async () => {
181+
sinon.spy(dialerUtils, 'sortPublicAddressesFirst')
182+
const dialer = new Dialer({
183+
transportManager: localTM,
184+
addressSorter: dialerUtils.sortPublicAddressesFirst,
185+
concurrency: 2,
186+
peerStore: {
187+
addressBook: {
188+
set: () => { },
189+
get: () => [{ multiaddr: remoteAddr }, { multiaddr: remoteAddr }, { multiaddr: remoteAddr }]
190+
}
191+
}
192+
})
193+
194+
sinon.stub(localTM, 'dial').callsFake(createMockConnection)
195+
196+
// Perform 3 multiaddr dials
197+
await dialer.connectToPeer(peerId)
198+
199+
expect(dialerUtils.sortPublicAddressesFirst.callCount).to.eql(1)
200+
})
201+
179202
it('should dial to the max concurrency', async () => {
180203
const dialer = new Dialer({
181204
transportManager: localTM,
182205
concurrency: 2,
183206
peerStore: {
184207
addressBook: {
185208
set: () => {},
186-
getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
209+
get: () => [{ multiaddr: remoteAddr }, { multiaddr: remoteAddr }, { multiaddr: remoteAddr }]
187210
}
188211
}
189212
})
@@ -221,7 +244,7 @@ describe('Dialing (direct, WebSockets)', () => {
221244
peerStore: {
222245
addressBook: {
223246
set: () => {},
224-
getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr]
247+
get: () => [{ multiaddr: remoteAddr }, { multiaddr: remoteAddr }, { multiaddr: remoteAddr }]
225248
}
226249
}
227250
})

0 commit comments

Comments
 (0)