Skip to content

Commit 91040ee

Browse files
vasco-santosjacobheun
authored andcommitted
feat: peer store (#470)
* feat: peer-store v0 * chore: apply suggestions from code review Co-Authored-By: Jacob Heun <jacobheun@gmail.com>
1 parent c4b4431 commit 91040ee

File tree

8 files changed

+519
-16
lines changed

8 files changed

+519
-16
lines changed

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"once": "^1.4.0",
6464
"p-queue": "^6.1.1",
6565
"p-settle": "^3.1.0",
66-
"peer-book": "^0.9.1",
6766
"peer-id": "^0.13.3",
6867
"peer-info": "^0.17.0",
6968
"promisify-es6": "^1.0.3",

src/index.js

+19-15
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const promisify = require('promisify-es6')
1111
const each = require('async/each')
1212
const nextTick = require('async/nextTick')
1313

14-
const PeerBook = require('peer-book')
1514
const PeerInfo = require('peer-info')
1615
const multiaddr = require('multiaddr')
1716
const Switch = require('./switch')
@@ -29,6 +28,7 @@ const { codes } = require('./errors')
2928
const Dialer = require('./dialer')
3029
const TransportManager = require('./transport-manager')
3130
const Upgrader = require('./upgrader')
31+
const PeerStore = require('./peer-store')
3232

3333
const notStarted = (action, state) => {
3434
return errCode(
@@ -54,21 +54,23 @@ class Libp2p extends EventEmitter {
5454

5555
this.datastore = this._options.datastore
5656
this.peerInfo = this._options.peerInfo
57-
this.peerBook = this._options.peerBook || new PeerBook()
57+
this.peerStore = new PeerStore()
5858

5959
this._modules = this._options.modules
6060
this._config = this._options.config
6161
this._transport = [] // Transport instances/references
6262
this._discovery = [] // Discovery service instances/references
6363

6464
// create the switch, and listen for errors
65-
this._switch = new Switch(this.peerInfo, this.peerBook, this._options.switch)
65+
this._switch = new Switch(this.peerInfo, this.peerStore, this._options.switch)
6666

6767
// Setup the Upgrader
6868
this.upgrader = new Upgrader({
6969
localPeer: this.peerInfo.id,
7070
onConnection: (connection) => {
7171
const peerInfo = getPeerInfo(connection.remotePeer)
72+
73+
this.peerStore.put(peerInfo)
7274
this.emit('peer:connect', peerInfo)
7375
},
7476
onConnectionEnd: (connection) => {
@@ -179,10 +181,10 @@ class Libp2p extends EventEmitter {
179181

180182
// Once we start, emit and dial any peers we may have already discovered
181183
this.state.on('STARTED', () => {
182-
this.peerBook.getAllArray().forEach((peerInfo) => {
184+
for (const peerInfo of this.peerStore.peers) {
183185
this.emit('peer:discovery', peerInfo)
184186
this._maybeConnect(peerInfo)
185-
})
187+
}
186188
})
187189

188190
this._peerDiscovered = this._peerDiscovered.bind(this)
@@ -245,7 +247,7 @@ class Libp2p extends EventEmitter {
245247

246248
/**
247249
* Dials to the provided peer. If successful, the `PeerInfo` of the
248-
* peer will be added to the nodes `PeerBook`
250+
* peer will be added to the nodes `peerStore`
249251
*
250252
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial
251253
* @param {object} options
@@ -258,7 +260,7 @@ class Libp2p extends EventEmitter {
258260

259261
/**
260262
* Dials to the provided peer and handshakes with the given protocol.
261-
* If successful, the `PeerInfo` of the peer will be added to the nodes `PeerBook`,
263+
* If successful, the `PeerInfo` of the peer will be added to the nodes `peerStore`,
262264
* and the `Connection` will be sent in the callback
263265
*
264266
* @async
@@ -277,11 +279,19 @@ class Libp2p extends EventEmitter {
277279
connection = await this.dialer.connectToPeer(peer, options)
278280
}
279281

282+
const peerInfo = getPeerInfo(connection.remotePeer)
283+
280284
// If a protocol was provided, create a new stream
281285
if (protocols) {
282-
return connection.newStream(protocols)
286+
const stream = await connection.newStream(protocols)
287+
288+
peerInfo.protocols.add(stream.protocol)
289+
this.peerStore.put(peerInfo)
290+
291+
return stream
283292
}
284293

294+
this.peerStore.put(peerInfo)
285295
return connection
286296
}
287297

@@ -369,12 +379,6 @@ class Libp2p extends EventEmitter {
369379
* the `peer:discovery` event. If auto dial is enabled for libp2p
370380
* and the current connection count is under the low watermark, the
371381
* peer will be dialed.
372-
*
373-
* TODO: If `peerBook.put` becomes centralized, https://github.com/libp2p/js-libp2p/issues/345,
374-
* it would be ideal if only new peers were emitted. Currently, with
375-
* other modules adding peers to the `PeerBook` we have no way of knowing
376-
* if a peer is new or not, so it has to be emitted.
377-
*
378382
* @private
379383
* @param {PeerInfo} peerInfo
380384
*/
@@ -383,7 +387,7 @@ class Libp2p extends EventEmitter {
383387
log.error(new Error(codes.ERR_DISCOVERED_SELF))
384388
return
385389
}
386-
peerInfo = this.peerBook.put(peerInfo)
390+
peerInfo = this.peerStore.put(peerInfo)
387391

388392
if (!this.isStarted()) return
389393

src/peer-store/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Peerstore
2+
3+
WIP

src/peer-store/index.js

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
'use strict'
2+
3+
const assert = require('assert')
4+
const debug = require('debug')
5+
const log = debug('libp2p:peer-store')
6+
log.error = debug('libp2p:peer-store:error')
7+
8+
const { EventEmitter } = require('events')
9+
10+
const PeerInfo = require('peer-info')
11+
12+
/**
13+
* Responsible for managing known peers, as well as their addresses and metadata
14+
* @fires PeerStore#peer Emitted when a peer is connected to this node
15+
* @fires PeerStore#change:protocols
16+
* @fires PeerStore#change:multiaddrs
17+
*/
18+
class PeerStore extends EventEmitter {
19+
constructor () {
20+
super()
21+
22+
/**
23+
* Map of peers
24+
*
25+
* @type {Map<string, PeerInfo>}
26+
*/
27+
this.peers = new Map()
28+
29+
// TODO: Track ourselves. We should split `peerInfo` up into its pieces so we get better
30+
// control and observability. This will be the initial step for removing PeerInfo
31+
// https://github.com/libp2p/go-libp2p-core/blob/master/peerstore/peerstore.go
32+
// this.addressBook = new Map()
33+
// this.protoBook = new Map()
34+
}
35+
36+
/**
37+
* Stores the peerInfo of a new peer.
38+
* If already exist, its info is updated.
39+
* @param {PeerInfo} peerInfo
40+
*/
41+
put (peerInfo) {
42+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
43+
44+
// Already know the peer?
45+
if (this.peers.has(peerInfo.id.toB58String())) {
46+
this.update(peerInfo)
47+
} else {
48+
this.add(peerInfo)
49+
50+
// Emit the new peer found
51+
this.emit('peer', peerInfo)
52+
}
53+
}
54+
55+
/**
56+
* Add a new peer to the store.
57+
* @param {PeerInfo} peerInfo
58+
*/
59+
add (peerInfo) {
60+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
61+
62+
// Create new instance and add values to it
63+
const newPeerInfo = new PeerInfo(peerInfo.id)
64+
65+
peerInfo.multiaddrs.forEach((ma) => newPeerInfo.multiaddrs.add(ma))
66+
peerInfo.protocols.forEach((p) => newPeerInfo.protocols.add(p))
67+
68+
const connectedMa = peerInfo.isConnected()
69+
connectedMa && newPeerInfo.connect(connectedMa)
70+
71+
const peerProxy = new Proxy(newPeerInfo, {
72+
set: (obj, prop, value) => {
73+
if (prop === 'multiaddrs') {
74+
this.emit('change:multiaddrs', {
75+
peerInfo: obj,
76+
multiaddrs: value.toArray()
77+
})
78+
} else if (prop === 'protocols') {
79+
this.emit('change:protocols', {
80+
peerInfo: obj,
81+
protocols: Array.from(value)
82+
})
83+
}
84+
return Reflect.set(...arguments)
85+
}
86+
})
87+
88+
this.peers.set(peerInfo.id.toB58String(), peerProxy)
89+
}
90+
91+
/**
92+
* Updates an already known peer.
93+
* @param {PeerInfo} peerInfo
94+
*/
95+
update (peerInfo) {
96+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
97+
const id = peerInfo.id.toB58String()
98+
const recorded = this.peers.get(id)
99+
100+
// pass active connection state
101+
const ma = peerInfo.isConnected()
102+
if (ma) {
103+
recorded.connect(ma)
104+
}
105+
106+
// Verify new multiaddrs
107+
// TODO: better track added and removed multiaddrs
108+
const multiaddrsIntersection = [
109+
...recorded.multiaddrs.toArray()
110+
].filter((m) => peerInfo.multiaddrs.has(m))
111+
112+
if (multiaddrsIntersection.length !== peerInfo.multiaddrs.size ||
113+
multiaddrsIntersection.length !== recorded.multiaddrs.size) {
114+
// recorded.multiaddrs = peerInfo.multiaddrs
115+
recorded.multiaddrs.clear()
116+
117+
for (const ma of peerInfo.multiaddrs.toArray()) {
118+
recorded.multiaddrs.add(ma)
119+
}
120+
121+
this.emit('change:multiaddrs', {
122+
peerInfo: peerInfo,
123+
multiaddrs: recorded.multiaddrs.toArray()
124+
})
125+
}
126+
127+
// Update protocols
128+
// TODO: better track added and removed protocols
129+
const protocolsIntersection = new Set(
130+
[...recorded.protocols].filter((p) => peerInfo.protocols.has(p))
131+
)
132+
133+
if (protocolsIntersection.size !== peerInfo.protocols.size ||
134+
protocolsIntersection.size !== recorded.protocols.size) {
135+
recorded.protocols.clear()
136+
137+
for (const protocol of peerInfo.protocols) {
138+
recorded.protocols.add(protocol)
139+
}
140+
141+
this.emit('change:protocols', {
142+
peerInfo: peerInfo,
143+
protocols: Array.from(recorded.protocols)
144+
})
145+
}
146+
147+
// Add the public key if missing
148+
if (!recorded.id.pubKey && peerInfo.id.pubKey) {
149+
recorded.id.pubKey = peerInfo.id.pubKey
150+
}
151+
}
152+
153+
/**
154+
* Get the info to the given id.
155+
* @param {string} peerId b58str id
156+
* @returns {PeerInfo}
157+
*/
158+
get (peerId) {
159+
const peerInfo = this.peers.get(peerId)
160+
161+
if (peerInfo) {
162+
return peerInfo
163+
}
164+
165+
return undefined
166+
}
167+
168+
/**
169+
* Removes the Peer with the matching `peerId` from the PeerStore
170+
* @param {string} peerId b58str id
171+
* @returns {boolean} true if found and removed
172+
*/
173+
remove (peerId) {
174+
return this.peers.delete(peerId)
175+
}
176+
177+
/**
178+
* Completely replaces the existing peers metadata with the given `peerInfo`
179+
* @param {PeerInfo} peerInfo
180+
* @returns {void}
181+
*/
182+
replace (peerInfo) {
183+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
184+
185+
this.remove(peerInfo.id.toB58String())
186+
this.add(peerInfo)
187+
}
188+
}
189+
190+
module.exports = PeerStore

0 commit comments

Comments
 (0)