Skip to content

Commit 3468fca

Browse files
committed
feat: peer-store v0
1 parent a23d4d2 commit 3468fca

File tree

8 files changed

+440
-15
lines changed

8 files changed

+440
-15
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
"once": "^1.4.0",
6262
"p-queue": "^6.1.1",
6363
"p-settle": "^3.1.0",
64-
"peer-book": "^0.9.1",
6564
"peer-id": "^0.13.3",
6665
"peer-info": "^0.17.0",
6766
"promisify-es6": "^1.0.3",

src/index.js

Lines changed: 16 additions & 14 deletions
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,7 +181,7 @@ 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+
this.peerStore.getAllArray().forEach((peerInfo) => {
183185
this.emit('peer:discovery', peerInfo)
184186
this._maybeConnect(peerInfo)
185187
})
@@ -244,7 +246,7 @@ class Libp2p extends EventEmitter {
244246

245247
/**
246248
* Dials to the provided peer. If successful, the `PeerInfo` of the
247-
* peer will be added to the nodes `PeerBook`
249+
* peer will be added to the nodes `peerStore`
248250
*
249251
* @param {PeerInfo|PeerId|Multiaddr|string} peer The peer to dial
250252
* @param {object} options
@@ -257,7 +259,7 @@ class Libp2p extends EventEmitter {
257259

258260
/**
259261
* Dials to the provided peer and handshakes with the given protocol.
260-
* If successful, the `PeerInfo` of the peer will be added to the nodes `PeerBook`,
262+
* If successful, the `PeerInfo` of the peer will be added to the nodes `peerStore`,
261263
* and the `Connection` will be sent in the callback
262264
*
263265
* @async
@@ -278,7 +280,13 @@ class Libp2p extends EventEmitter {
278280

279281
// If a protocol was provided, create a new stream
280282
if (protocols) {
281-
return connection.newStream(protocols)
283+
const stream = await connection.newStream(protocols)
284+
const peerInfo = getPeerInfo(connection.remotePeer)
285+
286+
peerInfo.protocols.add(stream.protocol)
287+
this.peerStore.put(peerInfo)
288+
289+
return stream
282290
}
283291

284292
return connection
@@ -368,12 +376,6 @@ class Libp2p extends EventEmitter {
368376
* the `peer:discovery` event. If auto dial is enabled for libp2p
369377
* and the current connection count is under the low watermark, the
370378
* peer will be dialed.
371-
*
372-
* TODO: If `peerBook.put` becomes centralized, https://github.com/libp2p/js-libp2p/issues/345,
373-
* it would be ideal if only new peers were emitted. Currently, with
374-
* other modules adding peers to the `PeerBook` we have no way of knowing
375-
* if a peer is new or not, so it has to be emitted.
376-
*
377379
* @private
378380
* @param {PeerInfo} peerInfo
379381
*/
@@ -382,7 +384,7 @@ class Libp2p extends EventEmitter {
382384
log.error(new Error(codes.ERR_DISCOVERED_SELF))
383385
return
384386
}
385-
peerInfo = this.peerBook.put(peerInfo)
387+
peerInfo = this.peerStore.put(peerInfo)
386388

387389
if (!this.isStarted()) return
388390

src/peer-store/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Peerstore
2+
3+
WIP

src/peer-store/index.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
const errCode = require('err-code')
8+
9+
const { EventEmitter } = require('events')
10+
11+
const PeerInfo = require('peer-info')
12+
13+
/**
14+
* Responsible for managing known peers, as well as their addresses and metadata
15+
* @fires PeerStore#peer Emitted when a peer is connected to this node
16+
* @fires PeerStore#change:protocols
17+
* @fires PeerStore#change:multiaddrs
18+
*/
19+
class PeerStore extends EventEmitter {
20+
constructor () {
21+
super()
22+
23+
/**
24+
* Map of peers
25+
*
26+
* @type {Map<string, PeerInfo>}
27+
*/
28+
this.peers = new Map()
29+
30+
// TODO: Track ourselves. We should split `peerInfo` up into its pieces so we get better
31+
// control and observability. This will be the initial step for removing PeerInfo
32+
// https://github.com/libp2p/go-libp2p-core/blob/master/peerstore/peerstore.go
33+
// this.addressBook = new Map()
34+
// this.protoBook = new Map()
35+
}
36+
37+
/**
38+
* Stores the peerInfo of a new peer.
39+
* If already exist, its info is updated.
40+
* @param {PeerInfo} peerInfo
41+
*/
42+
put (peerInfo) {
43+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
44+
45+
// Already know the peer?
46+
if (this.peers.has(peerInfo.id.toB58String())) {
47+
this.update(peerInfo)
48+
} else {
49+
this.add(peerInfo)
50+
51+
// Emit the new peer found
52+
this.emit('peer', peerInfo)
53+
}
54+
}
55+
56+
/**
57+
* Add a new peer to the store.
58+
* @param {PeerInfo} peerInfo
59+
*/
60+
add (peerInfo) {
61+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
62+
63+
this.peers.set(peerInfo.id.toB58String(), peerInfo)
64+
}
65+
66+
/**
67+
* Updates an already known peer.
68+
* If already exist, updates ids info if outdated.
69+
* @param {PeerInfo} peerInfo
70+
*/
71+
update (peerInfo) {
72+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
73+
74+
const recorded = this.peers.get(peerInfo.id.toB58String())
75+
76+
// pass active connection state
77+
const ma = peerInfo.isConnected()
78+
if (ma) {
79+
recorded.connect(ma)
80+
}
81+
82+
// Verify new multiaddrs
83+
// TODO: better track added and removed multiaddrs
84+
if (peerInfo.multiaddrs.size || recorded.multiaddrs.size) {
85+
recorded.multiaddrs = peerInfo.multiaddrs
86+
87+
this.emit('change:multiaddrs', {
88+
peerInfo: recorded,
89+
multiaddrs: Array.from(recorded.multiaddrs)
90+
})
91+
}
92+
93+
// Update protocols
94+
// TODO: better track added and removed protocols
95+
if (peerInfo.protocols.size || recorded.protocols.size) {
96+
recorded.protocols = new Set(peerInfo.protocols)
97+
98+
this.emit('change:protocols', {
99+
peerInfo: recorded,
100+
protocols: Array.from(recorded.protocols)
101+
})
102+
}
103+
104+
// Add the public key if missing
105+
if (!recorded.id.pubKey && peerInfo.id.pubKey) {
106+
recorded.id.pubKey = peerInfo.id.pubKey
107+
}
108+
}
109+
110+
/**
111+
* Get the info to the given id.
112+
* @param {string} peerId b58str id
113+
* @returns {PeerInfo}
114+
*/
115+
get (peerId) {
116+
const peerInfo = this.peers.get(peerId)
117+
118+
if (peerInfo) {
119+
return peerInfo
120+
}
121+
122+
throw errCode(new Error('PeerInfo was not found'), 'ERR_NO_PEER_INFO')
123+
}
124+
125+
/**
126+
* Get an array with all peers known.
127+
* @returns {Array<PeerInfo>}
128+
*/
129+
getAllArray () {
130+
return Array.from(this.peers.values())
131+
}
132+
133+
/**
134+
* Remove the info of the peer with the given id.
135+
* @param {string} peerId b58str id
136+
* @returns {boolean} true if found and removed
137+
*/
138+
remove (peerId) {
139+
return this.peers.delete(peerId)
140+
}
141+
142+
/**
143+
* Replace the info stored of the given peer.
144+
* @param {PeerInfo} peerInfo
145+
* @returns {void}
146+
*/
147+
replace (peerInfo) {
148+
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
149+
150+
this.remove(peerInfo.id.toB58String())
151+
this.add(peerInfo)
152+
}
153+
}
154+
155+
module.exports = PeerStore

test/peer-store/peer-store.node.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict'
2+
/* eslint-env mocha */
3+
4+
const chai = require('chai')
5+
chai.use(require('dirty-chai'))
6+
const { expect } = chai
7+
const sinon = require('sinon')
8+
9+
const mergeOptions = require('merge-options')
10+
11+
const multiaddr = require('multiaddr')
12+
const Libp2p = require('../../src')
13+
14+
const baseOptions = require('../utils/base-options')
15+
const peerUtils = require('../utils/creators/peer')
16+
const listenAddr = multiaddr('/ip4/127.0.0.1/tcp/0')
17+
18+
describe('peer-store on dial', () => {
19+
let peerInfo
20+
let remotePeerInfo
21+
let libp2p
22+
let remoteLibp2p
23+
let remoteAddr
24+
25+
before(async () => {
26+
[peerInfo, remotePeerInfo] = await peerUtils.createPeerInfoFromFixture(2)
27+
remoteLibp2p = new Libp2p(mergeOptions(baseOptions, {
28+
peerInfo: remotePeerInfo
29+
}))
30+
31+
await remoteLibp2p.transportManager.listen([listenAddr])
32+
remoteAddr = remoteLibp2p.transportManager.getAddrs()[0]
33+
})
34+
35+
after(async () => {
36+
sinon.restore()
37+
await remoteLibp2p.stop()
38+
libp2p && await libp2p.stop()
39+
})
40+
41+
it('should put the remote peerInfo after dial and emit event', async () => {
42+
// TODO: needs crypto PR fix
43+
// const remoteId = remotePeerInfo.id.toB58String()
44+
const remoteId = peerInfo.id.toB58String()
45+
46+
libp2p = new Libp2p(mergeOptions(baseOptions, {
47+
peerInfo
48+
}))
49+
50+
sinon.spy(libp2p.peerStore, 'put')
51+
sinon.spy(libp2p.peerStore, 'add')
52+
sinon.spy(libp2p.peerStore, 'update')
53+
54+
const connection = await libp2p.dial(remoteAddr)
55+
await connection.close()
56+
57+
expect(libp2p.peerStore.put.callCount).to.equal(1)
58+
expect(libp2p.peerStore.add.callCount).to.equal(1)
59+
expect(libp2p.peerStore.update.callCount).to.equal(0)
60+
61+
const storedPeer = libp2p.peerStore.get(remoteId)
62+
expect(storedPeer).to.exist()
63+
})
64+
})
65+
66+
describe('peer-store on discovery', () => {
67+
// TODO: implement with discovery
68+
})

0 commit comments

Comments
 (0)