@@ -4,6 +4,8 @@ const multiaddr = require('multiaddr')
4
4
const errCode = require ( 'err-code' )
5
5
const TimeoutController = require ( 'timeout-abort-controller' )
6
6
const anySignal = require ( 'any-signal' )
7
+ const PeerId = require ( 'peer-id' )
8
+ const PeerInfo = require ( 'peer-info' )
7
9
const debug = require ( 'debug' )
8
10
const log = debug ( 'libp2p:dialer' )
9
11
log . error = debug ( 'libp2p:dialer:error' )
@@ -38,7 +40,7 @@ class Dialer {
38
40
this . timeout = timeout
39
41
this . perPeerLimit = perPeerLimit
40
42
this . tokens = [ ...new Array ( concurrency ) ] . map ( ( _ , index ) => index )
41
- this . _pendingDials = new Set ( )
43
+ this . _pendingDials = new Map ( )
42
44
}
43
45
44
46
/**
@@ -56,72 +58,111 @@ class Dialer {
56
58
}
57
59
58
60
/**
59
- * Connects to the first success of a given list of `Multiaddr`. `addrs` should
60
- * include the id of the peer being dialed, it will be used for encryption verification.
61
+ * Connects to a given `PeerId` or `Multiaddr` by dialing all of its known addresses.
62
+ * The dial to the first address that is successfully able to upgrade a connection
63
+ * will be used.
61
64
*
62
- * @param {Array<Multiaddr> |Multiaddr } addrs
65
+ * @param {PeerInfo |Multiaddr } peer The peer to dial
63
66
* @param {object } [options]
64
67
* @param {AbortSignal } [options.signal] An AbortController signal
65
68
* @returns {Promise<Connection> }
66
69
*/
67
- async connectToMultiaddr ( addrs , options = { } ) {
68
- if ( ! Array . isArray ( addrs ) ) addrs = [ multiaddr ( addrs ) ]
69
-
70
- const dialAction = ( addr , options ) => {
71
- if ( options . signal . aborted ) throw errCode ( new Error ( 'already aborted' ) , 'ERR_ALREADY_ABORTED' )
72
- return this . transportManager . dial ( addr , options )
70
+ async connectToPeer ( peer , options = { } ) {
71
+ const dialTarget = this . _createDialTarget ( peer )
72
+ if ( dialTarget . addrs . length === 0 ) {
73
+ throw errCode ( new Error ( 'The dial request has no addresses' ) , 'ERR_NO_DIAL_MULTIADDRS' )
73
74
}
74
- const dialRequest = new DialRequest ( {
75
- addrs,
76
- dialAction,
77
- dialer : this
78
- } )
79
-
80
- // Combine the timeout signal and options.signal, if provided
81
- const timeoutController = new TimeoutController ( this . timeout )
82
- const signals = [ timeoutController . signal ]
83
- options . signal && signals . push ( options . signal )
84
- const signal = anySignal ( signals )
85
-
86
- const dial = {
87
- dialRequest,
88
- controller : timeoutController
89
- }
90
- this . _pendingDials . add ( dial )
75
+ const pendingDial = this . _pendingDials . get ( dialTarget . id ) || this . _createPendingDial ( dialTarget , options )
91
76
92
77
try {
93
- const dialResult = await dialRequest . run ( { ... options , signal } )
94
- log ( 'dial succeeded to %s' , dialResult . remoteAddr )
95
- return dialResult
78
+ const connection = await pendingDial . promise
79
+ log ( 'dial succeeded to %s' , dialTarget . id )
80
+ return connection
96
81
} catch ( err ) {
97
82
// Error is a timeout
98
- if ( timeoutController . signal . aborted ) {
83
+ if ( pendingDial . controller . signal . aborted ) {
99
84
err . code = codes . ERR_TIMEOUT
100
85
}
101
86
log . error ( err )
102
87
throw err
103
88
} finally {
104
- timeoutController . clear ( )
105
- this . _pendingDials . delete ( dial )
89
+ pendingDial . destroy ( )
106
90
}
107
91
}
108
92
109
93
/**
110
- * Connects to a given `PeerInfo` or `PeerId` by dialing all of its known addresses.
111
- * The dial to the first address that is successfully able to upgrade a connection
112
- * will be used.
113
- *
114
- * @param {PeerId } peerId The remote peer id to dial
94
+ * @typedef DialTarget
95
+ * @property {string } id
96
+ * @property {Multiaddr[] } addrs
97
+ */
98
+
99
+ /**
100
+ * Creates a DialTarget. The DialTarget is used to create and track
101
+ * the DialRequest to a given peer.
102
+ * @private
103
+ * @param {PeerInfo|Multiaddr } peer A PeerId or Multiaddr
104
+ * @returns {DialTarget }
105
+ */
106
+ _createDialTarget ( peer ) {
107
+ const dialable = Dialer . getDialable ( peer )
108
+ if ( multiaddr . isMultiaddr ( dialable ) ) {
109
+ return {
110
+ id : dialable . toString ( ) ,
111
+ addrs : [ dialable ]
112
+ }
113
+ }
114
+ const addrs = this . peerStore . multiaddrsForPeer ( dialable )
115
+ return {
116
+ id : dialable . id . toString ( ) ,
117
+ addrs
118
+ }
119
+ }
120
+
121
+ /**
122
+ * @typedef PendingDial
123
+ * @property {DialRequest } dialRequest
124
+ * @property {TimeoutController } controller
125
+ * @property {Promise } promise
126
+ * @property {function():void } destroy
127
+ */
128
+
129
+ /**
130
+ * Creates a PendingDial that wraps the underlying DialRequest
131
+ * @private
132
+ * @param {DialTarget } dialTarget
115
133
* @param {object } [options]
116
134
* @param {AbortSignal } [options.signal] An AbortController signal
117
- * @returns {Promise<Connection> }
135
+ * @returns {PendingDial }
118
136
*/
119
- connectToPeer ( peerId , options = { } ) {
120
- const addrs = this . peerStore . multiaddrsForPeer ( peerId )
137
+ _createPendingDial ( dialTarget , options ) {
138
+ const dialAction = ( addr , options ) => {
139
+ if ( options . signal . aborted ) throw errCode ( new Error ( 'already aborted' ) , 'ERR_ALREADY_ABORTED' )
140
+ return this . transportManager . dial ( addr , options )
141
+ }
121
142
122
- // TODO: ensure the peer id is on the multiaddr
143
+ const dialRequest = new DialRequest ( {
144
+ addrs : dialTarget . addrs ,
145
+ dialAction,
146
+ dialer : this
147
+ } )
148
+
149
+ // Combine the timeout signal and options.signal, if provided
150
+ const timeoutController = new TimeoutController ( this . timeout )
151
+ const signals = [ timeoutController . signal ]
152
+ options . signal && signals . push ( options . signal )
153
+ const signal = anySignal ( signals )
123
154
124
- return this . connectToMultiaddr ( addrs , options )
155
+ const pendingDial = {
156
+ dialRequest,
157
+ controller : timeoutController ,
158
+ promise : dialRequest . run ( { ...options , signal } ) ,
159
+ destroy : ( ) => {
160
+ timeoutController . clear ( )
161
+ this . _pendingDials . delete ( dialTarget . id )
162
+ }
163
+ }
164
+ this . _pendingDials . set ( dialTarget . id , pendingDial )
165
+ return pendingDial
125
166
}
126
167
127
168
getTokens ( num ) {
@@ -137,6 +178,37 @@ class Dialer {
137
178
log ( 'token %d released' , token )
138
179
this . tokens . push ( token )
139
180
}
181
+
182
+ /**
183
+ * Converts the given `peer` into a `PeerInfo` or `Multiaddr`.
184
+ * @static
185
+ * @param {PeerInfo|PeerId|Multiaddr|string } peer
186
+ * @returns {PeerInfo|Multiaddr }
187
+ */
188
+ static getDialable ( peer ) {
189
+ if ( PeerInfo . isPeerInfo ( peer ) ) return peer
190
+ if ( typeof peer === 'string' ) {
191
+ peer = multiaddr ( peer )
192
+ }
193
+
194
+ let addr
195
+ if ( multiaddr . isMultiaddr ( peer ) ) {
196
+ addr = peer
197
+ try {
198
+ peer = PeerId . createFromCID ( peer . getPeerId ( ) )
199
+ } catch ( err ) {
200
+ // Couldn't get the PeerId, just use the address
201
+ return peer
202
+ }
203
+ }
204
+
205
+ if ( PeerId . isPeerId ( peer ) ) {
206
+ peer = new PeerInfo ( peer )
207
+ }
208
+
209
+ addr && peer . multiaddrs . add ( addr )
210
+ return peer
211
+ }
140
212
}
141
213
142
214
module . exports = Dialer
0 commit comments