2019-11-06 15:11:13 +01:00
|
|
|
'use strict'
|
|
|
|
|
|
|
|
const assert = require('assert')
|
|
|
|
const debug = require('debug')
|
|
|
|
const log = debug('libp2p:peer-store')
|
|
|
|
log.error = debug('libp2p:peer-store:error')
|
|
|
|
|
|
|
|
const { EventEmitter } = require('events')
|
|
|
|
|
2019-12-02 18:13:14 +01:00
|
|
|
const PeerId = require('peer-id')
|
2019-11-06 15:11:13 +01:00
|
|
|
const PeerInfo = require('peer-info')
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Responsible for managing known peers, as well as their addresses and metadata
|
|
|
|
* @fires PeerStore#peer Emitted when a peer is connected to this node
|
|
|
|
* @fires PeerStore#change:protocols
|
|
|
|
* @fires PeerStore#change:multiaddrs
|
|
|
|
*/
|
|
|
|
class PeerStore extends EventEmitter {
|
|
|
|
constructor () {
|
|
|
|
super()
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Map of peers
|
|
|
|
*
|
|
|
|
* @type {Map<string, PeerInfo>}
|
|
|
|
*/
|
|
|
|
this.peers = new Map()
|
|
|
|
|
|
|
|
// TODO: Track ourselves. We should split `peerInfo` up into its pieces so we get better
|
|
|
|
// control and observability. This will be the initial step for removing PeerInfo
|
|
|
|
// https://github.com/libp2p/go-libp2p-core/blob/master/peerstore/peerstore.go
|
|
|
|
// this.addressBook = new Map()
|
|
|
|
// this.protoBook = new Map()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stores the peerInfo of a new peer.
|
|
|
|
* If already exist, its info is updated.
|
|
|
|
* @param {PeerInfo} peerInfo
|
2019-11-26 16:40:04 +01:00
|
|
|
* @return {PeerInfo}
|
2019-11-06 15:11:13 +01:00
|
|
|
*/
|
|
|
|
put (peerInfo) {
|
|
|
|
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
|
|
|
|
|
2019-11-26 16:40:04 +01:00
|
|
|
let peer
|
2019-11-06 15:11:13 +01:00
|
|
|
// Already know the peer?
|
2019-12-03 20:14:15 +01:00
|
|
|
if (this.has(peerInfo.id)) {
|
2019-11-26 16:40:04 +01:00
|
|
|
peer = this.update(peerInfo)
|
2019-11-06 15:11:13 +01:00
|
|
|
} else {
|
2019-11-26 16:40:04 +01:00
|
|
|
peer = this.add(peerInfo)
|
2019-11-06 15:11:13 +01:00
|
|
|
|
|
|
|
// Emit the new peer found
|
|
|
|
this.emit('peer', peerInfo)
|
|
|
|
}
|
2019-11-26 16:40:04 +01:00
|
|
|
return peer
|
2019-11-06 15:11:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a new peer to the store.
|
|
|
|
* @param {PeerInfo} peerInfo
|
2019-11-26 16:40:04 +01:00
|
|
|
* @return {PeerInfo}
|
2019-11-06 15:11:13 +01:00
|
|
|
*/
|
|
|
|
add (peerInfo) {
|
|
|
|
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
|
|
|
|
|
|
|
|
// Create new instance and add values to it
|
|
|
|
const newPeerInfo = new PeerInfo(peerInfo.id)
|
|
|
|
|
|
|
|
peerInfo.multiaddrs.forEach((ma) => newPeerInfo.multiaddrs.add(ma))
|
|
|
|
peerInfo.protocols.forEach((p) => newPeerInfo.protocols.add(p))
|
|
|
|
|
|
|
|
const connectedMa = peerInfo.isConnected()
|
|
|
|
connectedMa && newPeerInfo.connect(connectedMa)
|
|
|
|
|
|
|
|
const peerProxy = new Proxy(newPeerInfo, {
|
|
|
|
set: (obj, prop, value) => {
|
|
|
|
if (prop === 'multiaddrs') {
|
|
|
|
this.emit('change:multiaddrs', {
|
|
|
|
peerInfo: obj,
|
|
|
|
multiaddrs: value.toArray()
|
|
|
|
})
|
|
|
|
} else if (prop === 'protocols') {
|
|
|
|
this.emit('change:protocols', {
|
|
|
|
peerInfo: obj,
|
|
|
|
protocols: Array.from(value)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return Reflect.set(...arguments)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.peers.set(peerInfo.id.toB58String(), peerProxy)
|
2019-11-26 16:40:04 +01:00
|
|
|
return peerProxy
|
2019-11-06 15:11:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates an already known peer.
|
|
|
|
* @param {PeerInfo} peerInfo
|
2019-11-26 16:40:04 +01:00
|
|
|
* @return {PeerInfo}
|
2019-11-06 15:11:13 +01:00
|
|
|
*/
|
|
|
|
update (peerInfo) {
|
|
|
|
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
|
|
|
|
const id = peerInfo.id.toB58String()
|
|
|
|
const recorded = this.peers.get(id)
|
|
|
|
|
|
|
|
// pass active connection state
|
|
|
|
const ma = peerInfo.isConnected()
|
|
|
|
if (ma) {
|
|
|
|
recorded.connect(ma)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify new multiaddrs
|
|
|
|
// TODO: better track added and removed multiaddrs
|
|
|
|
const multiaddrsIntersection = [
|
|
|
|
...recorded.multiaddrs.toArray()
|
|
|
|
].filter((m) => peerInfo.multiaddrs.has(m))
|
|
|
|
|
|
|
|
if (multiaddrsIntersection.length !== peerInfo.multiaddrs.size ||
|
|
|
|
multiaddrsIntersection.length !== recorded.multiaddrs.size) {
|
|
|
|
for (const ma of peerInfo.multiaddrs.toArray()) {
|
|
|
|
recorded.multiaddrs.add(ma)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.emit('change:multiaddrs', {
|
2019-12-03 20:14:15 +01:00
|
|
|
peerInfo: recorded,
|
2019-11-06 15:11:13 +01:00
|
|
|
multiaddrs: recorded.multiaddrs.toArray()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update protocols
|
|
|
|
// TODO: better track added and removed protocols
|
|
|
|
const protocolsIntersection = new Set(
|
|
|
|
[...recorded.protocols].filter((p) => peerInfo.protocols.has(p))
|
|
|
|
)
|
|
|
|
|
|
|
|
if (protocolsIntersection.size !== peerInfo.protocols.size ||
|
|
|
|
protocolsIntersection.size !== recorded.protocols.size) {
|
|
|
|
for (const protocol of peerInfo.protocols) {
|
|
|
|
recorded.protocols.add(protocol)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.emit('change:protocols', {
|
2019-12-03 20:14:15 +01:00
|
|
|
peerInfo: recorded,
|
2019-11-06 15:11:13 +01:00
|
|
|
protocols: Array.from(recorded.protocols)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the public key if missing
|
|
|
|
if (!recorded.id.pubKey && peerInfo.id.pubKey) {
|
|
|
|
recorded.id.pubKey = peerInfo.id.pubKey
|
|
|
|
}
|
2019-11-26 16:40:04 +01:00
|
|
|
|
|
|
|
return recorded
|
2019-11-06 15:11:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the info to the given id.
|
2019-12-02 18:13:14 +01:00
|
|
|
* @param {PeerId|string} peerId b58str id
|
2019-11-06 15:11:13 +01:00
|
|
|
* @returns {PeerInfo}
|
|
|
|
*/
|
|
|
|
get (peerId) {
|
2019-12-02 18:13:14 +01:00
|
|
|
// TODO: deprecate this and just accept `PeerId` instances
|
|
|
|
if (PeerId.isPeerId(peerId)) {
|
|
|
|
peerId = peerId.toB58String()
|
|
|
|
}
|
|
|
|
|
2019-12-03 20:14:15 +01:00
|
|
|
return this.peers.get(peerId)
|
2019-11-06 15:11:13 +01:00
|
|
|
}
|
|
|
|
|
2019-11-26 16:40:04 +01:00
|
|
|
/**
|
|
|
|
* Has the info to the given id.
|
2019-12-02 18:13:14 +01:00
|
|
|
* @param {PeerId|string} peerId b58str id
|
2019-11-26 16:40:04 +01:00
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
has (peerId) {
|
2019-12-02 18:13:14 +01:00
|
|
|
// TODO: deprecate this and just accept `PeerId` instances
|
|
|
|
if (PeerId.isPeerId(peerId)) {
|
|
|
|
peerId = peerId.toB58String()
|
|
|
|
}
|
|
|
|
|
2019-11-26 16:40:04 +01:00
|
|
|
return this.peers.has(peerId)
|
|
|
|
}
|
|
|
|
|
2019-11-06 15:11:13 +01:00
|
|
|
/**
|
|
|
|
* Removes the Peer with the matching `peerId` from the PeerStore
|
2019-12-02 18:13:14 +01:00
|
|
|
* @param {PeerId|string} peerId b58str id
|
2019-11-06 15:11:13 +01:00
|
|
|
* @returns {boolean} true if found and removed
|
|
|
|
*/
|
|
|
|
remove (peerId) {
|
2019-12-02 18:13:14 +01:00
|
|
|
// TODO: deprecate this and just accept `PeerId` instances
|
|
|
|
if (PeerId.isPeerId(peerId)) {
|
|
|
|
peerId = peerId.toB58String()
|
|
|
|
}
|
|
|
|
|
2019-11-06 15:11:13 +01:00
|
|
|
return this.peers.delete(peerId)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Completely replaces the existing peers metadata with the given `peerInfo`
|
|
|
|
* @param {PeerInfo} peerInfo
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
replace (peerInfo) {
|
|
|
|
assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of peer-info')
|
|
|
|
|
|
|
|
this.remove(peerInfo.id.toB58String())
|
|
|
|
this.add(peerInfo)
|
2019-12-03 20:14:15 +01:00
|
|
|
|
|
|
|
// This should be cleaned up in PeerStore v2
|
|
|
|
this.emit('change:multiaddrs', {
|
|
|
|
peerInfo,
|
|
|
|
multiaddrs: peerInfo.multiaddrs.toArray()
|
|
|
|
})
|
|
|
|
this.emit('change:protocols', {
|
|
|
|
peerInfo,
|
|
|
|
protocols: Array.from(peerInfo.protocols)
|
|
|
|
})
|
2019-11-06 15:11:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = PeerStore
|