From a1617e6793e603fee4ea3bcba26bac017018e7f4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 23 Mar 2020 16:32:43 +0100 Subject: [PATCH 1/8] feat: address and proto books --- src/index.js | 2 +- src/peer-store/README.md | 155 ++++++++++++++++- src/peer-store/address-book.js | 269 +++++++++++++++++++++++++++++ src/peer-store/index.js | 29 ++-- src/peer-store/proto-book.js | 233 +++++++++++++++++++++++++ src/ping/index.js | 4 +- src/registrar.js | 2 + test/peer-store/proto-book.spec.js | 69 ++++++++ 8 files changed, 748 insertions(+), 15 deletions(-) create mode 100644 src/peer-store/address-book.js create mode 100644 src/peer-store/proto-book.js create mode 100644 test/peer-store/proto-book.spec.js diff --git a/src/index.js b/src/index.js index 86dad11208..a7ee76b649 100644 --- a/src/index.js +++ b/src/index.js @@ -328,7 +328,7 @@ class Libp2p extends EventEmitter { async ping (peer) { const peerInfo = await getPeerInfo(peer, this.peerStore) - return ping(this, peerInfo) + return ping(this, peerInfo.id) } /** diff --git a/src/peer-store/README.md b/src/peer-store/README.md index d9d79fe56a..056c4b7010 100644 --- a/src/peer-store/README.md +++ b/src/peer-store/README.md @@ -1,3 +1,156 @@ # Peerstore -WIP \ No newline at end of file +Libp2p's Peerstore is responsible for keeping an updated register with the relevant information of the known peers. It should gather environment changes and be able to take decisions and notice interested parties of relevant changes. The Peerstore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. These book components have similar characteristics with the `Javascript Map` implementation. + +The PeerStore needs to manage the high level operations on its inner books, have a job runner to trigger other books runners for data trimming or computations. Moreover, the peerStore should be responsible for noticing interested parties of relevant events, through its Event Emitter. + +## Peers Environment + +#### Sense + +Several libp2p subsystems will perform operations, which will gather relevant information about peers. Some operations might not have this as an end goal, but can also gather important data. + +In a libp2p node life, it will discover peers the existance of peers through its discovery protocols. In a typical discovery protocol, an address of the peer is discovered combined with its peer id. Once this happens, the `PeerStore` should collect this information for future (or immediate) usage by other subsystems. When the information is stored, the `PeerStore` should inform interested parties of the peer discovered (`peer` event). + +Taking into account a different scenario, a peer might perform/receive a dial request to/from a unkwown peer. In such a scenario, the `PeerStore` must store the peer's multiaddr once a connection is established. + +(NOTE: this should be removed later) +(currently we silently put it to the peerStore, without emitting events, as this logic exists in the `onConnected` callback from the upgrader. This way, we are never emitting the `peer` event when inbound connections happen, or a unkwown peer is dialed. Should we differentiate this?) + +After a connection is established with a peer, the Identify Service will act on this connection. A stream is created and peers exchange their information (listenMuldiaddrs and running protocols). Once this information is obtained, the PeerStore can collect the new data. In this specific case, we have a guarantee that this data is complete and updated, so the data stored in the PeerStore should be replaced (older and outdated data should disappear). However, if the recorded `multiaddrs` or `protocols` have changed, interested parties must be informed via `change:multiaddrs` or `change:protocols` events. + +In the background, the Identify Service is also waiting for new protocols to be started by the peer. If a new protocol is started, the `identify-push` message is sent to all the connected peers, so that their PeerStore can be updated with the new protocol and relevant parties are noticed. As the `identify-push` also sends complete and updated information, the data in the PeerStore is replaced. + +On different context, it is also possible to gather relevant information for the peers. For instance, in `dht` operations, nodes can exchanges data of peers they know as part of the `dht` operation. In this case, we can get information from a peer that we already know. As a consequence, the `PeerStore` should act accordingly and not replace the data it owns, but just try to merge it the discovered data is new. For example, discovered a new address of a peer. + +#### Act + +When the `PeerStore` data is updated, this information might be important for different parties. + +`js-libp2p` keeps a topology of peers for each protocol a node is running. This way, once a protocol is supported for a peer, the topology of that protocol should be informed that a new peer may be used and the subsystem can decide if it should open a new stream it that peer or not. + +Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to a peer. The same is true for pinging a peer. + +## PeerStore implementation + +(Note: except for highlighting the APIs functionallity, they should be better formally described on `API.md` file) + +#### API: + +Access to its underlying books: + +- `peerStore.protoBook.*` +- `peerStore.addressBook.*` + +High level operations: + +- `peerStore.set(peerId, data, options)` or `events` + +High level set which should be able to identify the type of data received and forward to the appropriate book sets. More than a bridge, this aims to allow the combination of multiple data storage as follows: + +`data = { multiaddrs: [...], protocols: [...] }` + +---- (should be removed / re-written, but important for design decisions) + +One aspect that we need to consider is wether we should add information to every book, even if we don't have any relevant information for it. For instance, if we just discover a peer via a typical discovery service, we will have the `peerId` and an array of `multiaddr`. When we do `peerStore.set()`, should we also do `protoBook.set()` with an empty list of protocols? I don't see any advantage on adding to the remaining ones. + +**IMPORTANT:** This is one of the biggest design decisions to make (set vs events). The programmatic API is the easiest solution but it can provide users an API that they sould not need. If we go on an event-based approach, the `peerStore` should receive all the relevant subsystems (discovery, identifyService, ...) and sense the environment (via events) to gather the information that would need to be sent via the API. Thile the latest seems the best solution, it is the more complex one to implement, as we would ideally have an interface that those subsystems would need to implement and each time we have a new subsystem that needs to add data to the peerStore, we might need to update the `peer-store` codebase (or have a good set of abstractions). + +It is also important pointing out that users would be able to use `peerStore.protoBook.*`, so eventually we should move those into `peerStore._ protoBook.*` if we do not intend them to use it. + +--- + +- `peerStore.get(peerId, options)` + +Get the information of a provided peer. The amount of information that we want can be customized with the following options, which are true by default: + +```js +{ + address: true, + proto: true, + key: true, + metadata: true +} +``` + +- `peerStore.delete(peerId, [data])` + +Deletes the provided peer from every book. If data is provided, just remove the data from the books. The data should be provided as follows: + +```js +{ + address: [], + proto: [], + key: [], + metadata: [] +} +``` + +- `peerStore.peers(options)` + +Get an array of all the peers, as well as their information. The information intended can be customized with the following options, which are true by default: + +```js +{ + address: true, + proto: true, + key: true, + metadata: true +} +``` + +## Address Book + +The `addressBook` keeps the known multiaddrs of a peer. The multiaddrs of each peer are not a constant and the Address book must have this into consideration. + +`Map` + +A `peerId.toString()` identifier mapping to a `multiaddrInfo` object, which should have the following structure: + +```js +{ + multiaddr: , + validity: , + confidence: +} +``` + +**Note:** except for multiaddr namings, the other properties are placeholders for now and might not be as described in the future milestones. + +- `addressBook.set()` +- `addressBook.get()` +- `getMultiaddrsForPeer()` +- `addressBook.has()` +- `addressBook.delete()` +- `addressBook.peers()` + +It is important pointing out that the API methods which return arrays of data (`set`, `get`, `getMultiaddrsForPeer`) shuld return the `multiaddr` property of the `multiaddrInfo` and not the entire `multiaddrInfo` as the remaining data should be used internally. Should we consider having two datastructure instead? + +Further API methods will probably be added in the context of multiaddr `ttl` and multiaddr confidence. + +**Not Yet Implemented**: Multiaddr Confidence + +## Key Book + +The `keyBook` tracks the keys of the peers. + +**Not Yet Implemented** + +## Protocol Book + +The `protoBook` holds the identifiers of the protocols supported by each peer. The protocols supported by each peer are dynamic and will change over time. + +`Map>` + +A `peerId.toString()` identifier mapping to a `Set` of protocol identifier strings. + +- `protoBook.set()` +- `protoBook.get()` +- `protoBook.has()` +- `protoBook.delete()` +- `protoBook.supports()` +- `protoBook.peers()` + +## Metadata Book + +**Not Yet Implemented** diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js new file mode 100644 index 0000000000..7f84cc165e --- /dev/null +++ b/src/peer-store/address-book.js @@ -0,0 +1,269 @@ +'use strict' + +const errcode = require('err-code') +const debug = require('debug') +const log = debug('libp2p:peer-store:address-book') +log.error = debug('libp2p:peer-store:address-book:error') + +const multiaddr = require('multiaddr') +const PeerId = require('peer-id') + +const { + ERR_INVALID_PARAMETERS +} = require('../errors') + +/** + * The AddressBook is responsible for keeping the known multiaddrs + * of a peer. + */ +class AddressBook { + /** + * MultiaddrInfo object + * @typedef {Object} multiaddrInfo + * @property {Multiaddr} multiaddr peer multiaddr. + * @property {number} validity NOT USED YET + * @property {number} confidence NOT USED YET + */ + + /** + * @constructor + * @param {EventEmitter} peerStore + */ + constructor (peerStore) { + /** + * PeerStore Event emitter, used by the AddressBook to emit: + * "peer" - emitted when a peer is discovered by the node. + * "change:multiaddrs" - emitted when the known multiaddrs of a peer change. + */ + this._ps = peerStore + + /** + * Map known peers to their known multiaddrs. + * @type {Map} + */ + this.addressBook = new Map() + } + + /** + * Set known addresses of a provided peer. + * @param {PeerId} peerId + * @param {Array|Multiaddr} addresses + * @param {Object} [options] + * @param {boolean} [options.replace = true] wether addresses received replace stored ones or a unique union is performed. + * @returns {Array} + */ + set (peerId, addresses, { replace = true } = {}) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + if (!addresses) { + throw errcode(new Error('addresses must be provided'), ERR_INVALID_PARAMETERS) + } + + if (!Array.isArray(addresses)) { + addresses = [addresses] + } + + // create multiaddrInfo for each address + const multiaddrInfos = [] + addresses.forEach((addr) => { + if (!multiaddr.isMultiaddr(addr)) { + throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) + } + + multiaddrInfos.push({ + multiaddr: addr + }) + }) + + if (replace) { + return this._replace(peerId, multiaddrInfos) + } + + return this._add(peerId, multiaddrInfos) + } + + /** + * Replace known addresses to a provided peer. + * If the peer is not known, it is set with the given addresses. + * @param {PeerId} peerId + * @param {Array} multiaddrInfos + * @returns {Array} + */ + _replace (peerId, multiaddrInfos) { + const id = peerId.toString() + const rec = this.addressBook.get(id) + + // Already know the peer + if (rec && rec.length === multiaddrInfos.length) { + const intersection = rec.filter((mi) => multiaddrInfos.some((newMi) => mi.multiaddr === newMi.multiaddr)) + + // New addresses equal the old ones? + // If yes, no changes needed! + if (intersection.length === rec.length) { + return [...multiaddrInfos] + } + } + + this.addressBook.set(id, multiaddrInfos) + + this._ps.emit('peer', peerId) + this._ps.emit('change:multiaddrs', { + peerId, + multiaddrs: multiaddrInfos.map((mi) => mi.multiaddr) + }) + + return [...multiaddrInfos] + } + + /** + * Add new known addresses to a provided peer. + * If the peer is not known, it is set with the given addresses. + * @param {PeerId} peerId + * @param {Array} multiaddrInfos + * @returns {Array} + */ + _add (peerId, multiaddrInfos) { + const id = peerId.toString() + const rec = this.addressBook.get(id) || [] + + // Add recorded uniquely to the new array + rec.forEach((mi) => { + if (!multiaddrInfos.find(r => r.multiaddr === mi.multiaddr)) { + multiaddrInfos.push(mi) + } + }) + + // If the recorded length is equal to the new after the uniquely union + // The content is the same, no need to update. + if (rec.length === multiaddrInfos) { + return [...multiaddrInfos] + } + + this.addressBook.set(id, multiaddrInfos) + this._ps.emit('change:multiaddrs', { + peerId, + multiaddrs: multiaddrInfos.map((mi) => mi.multiaddr) + }) + + // Notify the existance of a new peer + // TODO: do we need this? + if (!rec) { + this._ps.emit('peer', peerId) + } + + return [...multiaddrInfos] + } + + /** + * Get known addresses of a provided peer. + * @param {PeerId} peerId + * @returns {Array} + */ + get (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + const record = this.addressBook.get(peerId.toString()) + + if (!record) { + return undefined + } + + return record.map((multiaddrInfo) => multiaddrInfo.multiaddr) + } + + /** + * Get the known multiaddrs for a given peer. All returned multiaddrs + * will include the encapsulated `PeerId` of the peer. + * @param {PeerId} peerId + * @returns {Array} + */ + getMultiaddrsForPeer (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + const record = this.addressBook.get(peerId.toString()) + + if (!record) { + return undefined + } + + return record.map((multiaddrInfo) => { + const addr = multiaddrInfo.multiaddr + + if (addr.getPeerId()) return addr + return addr.encapsulate(`/p2p/${peerId.toB58String()}`) + }) + } + + /** + * Has known addresses of a provided peer. + * @param {PeerId} peerId + * @returns {boolean} + */ + has (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + return this.addressBook.has(peerId.toString()) + } + + /** + * Deletes the provided peer from the book. + * If addresses are provided, just remove the provided addresses and keep the peer. + * @param {PeerId} peerId + * @param {Array|multiaddr} [addresses] + * @returns {boolean} + */ + delete (peerId, addresses) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + if (addresses) { + return this._remove(peerId, addresses) + } + + this._ps('change:multiaddrs', { + peerId, + multiaddrs: [] + }) + + return this.addressBook.delete(peerId.toString()) + } + + /** + * Removes the given multiaddrs from the provided peer. + * @param {PeerId} peerId + * @param {Array|multiaddr} addresses + * @returns {boolean} + */ + _remove (peerId, addresses) { + if (!Array.isArray(addresses)) { + addresses = [addresses] + } + + const record = this.addressBook.get(peerId.toString()) + + if (!record) { + return false + } + + record.filter((mi) => addresses.includes(mi.multiaddr)) + // TODO: should we keep it if empty? + + this._ps('change:multiaddrs', { + peerId, + multiaddrs: record.map((multiaddrInfo) => multiaddrInfo.multiaddr) + }) + + return true + } +} + +module.exports = AddressBook diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 494d613d13..6e61b73923 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -30,11 +30,17 @@ class PeerStore extends EventEmitter { */ 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() + /** + * Map known peers to their known multiaddrs. + * @type {Map} + */ + this.addressBook = new Map() + + /** + * Map known peers to their known supported protocols. + * @type {Map} + */ + this.protoBook = new Map() } /** @@ -82,8 +88,8 @@ class PeerStore extends EventEmitter { 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 connectedMa = peerInfo.isConnected() + // connectedMa && newPeerInfo.connect(connectedMa) const peerProxy = new Proxy(newPeerInfo, { set: (obj, prop, value) => { @@ -120,10 +126,10 @@ class PeerStore extends EventEmitter { const recorded = this.peers.get(id) // pass active connection state - const ma = peerInfo.isConnected() - if (ma) { - recorded.connect(ma) - } + // const ma = peerInfo.isConnected() + // if (ma) { + // recorded.connect(ma) + // } // Verify new multiaddrs // TODO: better track added and removed multiaddrs @@ -229,6 +235,7 @@ class PeerStore extends EventEmitter { peerInfo, multiaddrs: peerInfo.multiaddrs.toArray() }) + this.emit('change:protocols', { peerInfo, protocols: Array.from(peerInfo.protocols) diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js new file mode 100644 index 0000000000..3eeccb6151 --- /dev/null +++ b/src/peer-store/proto-book.js @@ -0,0 +1,233 @@ +'use strict' + +const errcode = require('err-code') +const debug = require('debug') +const log = debug('libp2p:peer-store:proto-book') +log.error = debug('libp2p:peer-store:proto-book:error') + +const PeerId = require('peer-id') + +const { + ERR_INVALID_PARAMETERS +} = require('../errors') + +/** + * The ProtoBook is responsible for keeping the known suppoerted + * protocols of a peer. + * @fires ProtoBook#change:protocols + */ +class ProtoBook { + /** + * @constructor + * @param {EventEmitter} peerStore + */ + constructor (peerStore) { + /** + * PeerStore Event emitter, used by the ProtoBook to emit: + * "change:protocols" - emitted when the known protocols of a peer change. + */ + this._ps = peerStore + + /** + * Map known peers to their known protocols. + * @type {Map} + */ + this.protoBook = new Map() + } + + /** + * Set known protocols of a provided peer. + * If the peer was not known before, it will be added. + * @param {PeerId} peerId + * @param {Array|string} protocols + * @param {Object} [options] + * @param {boolean} [options.replace = true] wether protocols received replace stored ones or a unique union is performed. + * @returns {Array} + */ + set (peerId, protocols, { replace = true } = {}) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + if (!protocols) { + throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) + } + + if (!Array.isArray(protocols)) { + protocols = [protocols] + } + + if (replace) { + return this._replace(PeerId, protocols) + } + + return this._add(PeerId, protocols) + } + + /** + * Replace known protocols to a provided peer. + * If the peer is not known, it is set with the given protocols. + * @param {PeerId} peerId + * @param {Array} protocols + * @returns {Array} + */ + _replace (peerId, protocols) { + const id = peerId.toString() + const recSet = this.protoBook.get(id) + const newSet = new Set(protocols) + + const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)) + + // Already know the peer and the recorded protocols are the same? + // If yes, no changes needed! + if (recSet && isSetEqual(recSet, newSet)) { + return protocols + } + + this.protoBook.set(id, newSet) + this._ps.emit('change:protocols', { + peerId, + protocols + }) + + return protocols + } + + /** + * Add new known protocols to a provided peer. + * If the peer is not known, it is set with the given protocols. + * @param {PeerId} peerId + * @param {Array|string} protocols + * @returns {Array} + */ + _add (peerId, protocols) { + const id = peerId.toString() + const recSet = this.protoBook.get(id) || new Set() + const newSet = new Set([...recSet, ...protocols]) + + // Any new protocol added? + if (recSet.size === newSet.size) { + return protocols + } + + protocols = [...newSet] + + this.protoBook.set(id, newSet) + this._ps.emit('change:protocols', { + peerId, + protocols + }) + + return protocols + } + + /** + * Get known supported protocols of a provided peer. + * @param {PeerId} peerId + * @returns {Array} + */ + get (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + const recSet = this.protoBook.get(peerId.toString()) + + return recSet ? [...recSet] : undefined + } + + /** + * Verify if the provided peer supports the given protocols. + * @param {PeerId} peerId + * @param {Array|string} protocols + * @returns {boolean} + */ + supports (peerId, protocols) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + if (!Array.isArray(protocols)) { + protocols = [protocols] + } + + const recSet = this.protoBook.get(peerId.toString()) + + if (!recSet) { + return false + } + + return [...recSet].filter((p) => protocols.includes(p)).length === protocols.length + } + + /** + * Has known protocols of a provided peer. + * @param {PeerId} peerId + * @returns {boolean} + */ + has (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + return this.protoBook.has(peerId.toString()) + } + + /** + * Deletes the provided peer from the book. + * If protocols are provided, just remove the provided protocols and keep the peer. + * @param {PeerId} peerId + * @param {Array|string} [protocols] + * @returns {boolean} + */ + delete (peerId, protocols) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + if (protocols) { + return this._remove(peerId, protocols) + } + + if (!this.protoBook.delete(peerId.toString())) { + return false + } + + this._ps.emit('change:protocols', { + peerId, + protocols: [] + }) + + return true + } + + /** + * Removes the given protocols from the provided peer. + * @param {PeerId} peerId + * @param {Array|string} protocols + * @returns {boolean} + */ + _remove (peerId, protocols) { + if (!Array.isArray(protocols)) { + protocols = [protocols] + } + + const recSet = this.protoBook.get(peerId.toString()) + + if (!recSet) { + return false + } + + protocols.forEach((p) => recSet.delete(p)) + // TODO: should we keep it if empty? + + this._ps.emit('change:protocols', { + peerId, + protocols: [...recSet] + }) + + return true + } +} + +module.exports = ProtoBook diff --git a/src/ping/index.js b/src/ping/index.js index d679a5c102..8590323056 100644 --- a/src/ping/index.js +++ b/src/ping/index.js @@ -15,11 +15,11 @@ const { PROTOCOL, PING_LENGTH } = require('./constants') /** * Ping a given peer and wait for its response, getting the operation latency. * @param {Libp2p} node - * @param {PeerInfo} peer + * @param {PeerId} peer * @returns {Promise} */ async function ping (node, peer) { - log('dialing %s to %s', PROTOCOL, peer.id.toB58String()) + log('dialing %s to %s', PROTOCOL, peer.toB58String()) const { stream } = await node.dialProtocol(peer, PROTOCOL) diff --git a/src/registrar.js b/src/registrar.js index cc24548516..43fa235d03 100644 --- a/src/registrar.js +++ b/src/registrar.js @@ -22,6 +22,8 @@ class Registrar { * @constructor */ constructor ({ peerStore }) { + // Used on topology to listen for protocol changes + // TODO: should we only provide the protobook? this.peerStore = peerStore /** diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js new file mode 100644 index 0000000000..acd9193b7d --- /dev/null +++ b/test/peer-store/proto-book.spec.js @@ -0,0 +1,69 @@ +'use strict' +/* eslint-env mocha */ + +const chai = require('chai') +chai.use(require('dirty-chai')) +const { expect } = chai +const sinon = require('sinon') + +const { EventEmitter } = require('events') + +const PeerId = require('peer-id') + +const ProtoBook = require('../../src/peer-store/proto-book') + +const { + ERR_INVALID_PARAMETERS +} = require('../../src/errors') + +describe('protoBook', () => { + describe('protoBook.set', () => { + let peerId + let ee, pb + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + pb = new ProtoBook(ee) + }) + + it('should thrown invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + pb.set('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('should thrown invalid parameters error if no protocols provided', () => { + expect(() => { + pb.set(peerId) + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('should replace the stored content by default', () => { + sinon.spy(pb, '_replace') + sinon.spy(pb, '_add') + + const supportedProtocols = ['protocol1', 'protocol2'] + const protocols = pb.set(peerId, supportedProtocols) + + expect(pb._replace.callCount).to.equal(1) + expect(pb._add.callCount).to.equal(0) + expect(protocols).to.eql(supportedProtocols) + }) + + it('should replace the stored content by default', () => { + sinon.spy(pb, '_replace') + sinon.spy(pb, '_add') + + const supportedProtocols = ['protocol1', 'protocol2'] + const protocols = pb.set(peerId, supportedProtocols) + + expect(pb._replace.callCount).to.equal(1) + expect(pb._add.callCount).to.equal(0) + expect(protocols).to.eql(supportedProtocols) + }) + }) +}) From 8dab477aca505cd4e9e1852b89465a87f24fdebd Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 25 Mar 2020 15:58:16 +0100 Subject: [PATCH 2/8] chore: apply suggestions from code review Co-Authored-By: Jacob Heun --- src/peer-store/README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/peer-store/README.md b/src/peer-store/README.md index 056c4b7010..23492165ac 100644 --- a/src/peer-store/README.md +++ b/src/peer-store/README.md @@ -2,7 +2,9 @@ Libp2p's Peerstore is responsible for keeping an updated register with the relevant information of the known peers. It should gather environment changes and be able to take decisions and notice interested parties of relevant changes. The Peerstore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. These book components have similar characteristics with the `Javascript Map` implementation. -The PeerStore needs to manage the high level operations on its inner books, have a job runner to trigger other books runners for data trimming or computations. Moreover, the peerStore should be responsible for noticing interested parties of relevant events, through its Event Emitter. +The PeerStore needs to manage the high level operations on its inner books. Moreover, the peerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter. + +(Future considerations: Peerstore should manage a job runner to trigger books runners for data trimming or computations) ## Peers Environment @@ -10,18 +12,20 @@ The PeerStore needs to manage the high level operations on its inner books, have Several libp2p subsystems will perform operations, which will gather relevant information about peers. Some operations might not have this as an end goal, but can also gather important data. -In a libp2p node life, it will discover peers the existance of peers through its discovery protocols. In a typical discovery protocol, an address of the peer is discovered combined with its peer id. Once this happens, the `PeerStore` should collect this information for future (or immediate) usage by other subsystems. When the information is stored, the `PeerStore` should inform interested parties of the peer discovered (`peer` event). +In a libp2p node's life, it will discover peers through its discovery protocols. In a typical discovery protocol, addresses of the peer are discovered along with its peer id. Once this happens, the `PeerStore` should collect this information for future (or immediate) usage by other subsystems. When the information is stored, the `PeerStore` should inform interested parties of the peer discovered (`peer` event). Taking into account a different scenario, a peer might perform/receive a dial request to/from a unkwown peer. In such a scenario, the `PeerStore` must store the peer's multiaddr once a connection is established. (NOTE: this should be removed later) (currently we silently put it to the peerStore, without emitting events, as this logic exists in the `onConnected` callback from the upgrader. This way, we are never emitting the `peer` event when inbound connections happen, or a unkwown peer is dialed. Should we differentiate this?) -After a connection is established with a peer, the Identify Service will act on this connection. A stream is created and peers exchange their information (listenMuldiaddrs and running protocols). Once this information is obtained, the PeerStore can collect the new data. In this specific case, we have a guarantee that this data is complete and updated, so the data stored in the PeerStore should be replaced (older and outdated data should disappear). However, if the recorded `multiaddrs` or `protocols` have changed, interested parties must be informed via `change:multiaddrs` or `change:protocols` events. +After a connection is established with a peer, the Identify protocol will run automatically. A stream is created and peers exchange their information (Multiaddrs, running protocols and their public key). Once this information is obtained, it should be added to the PeerStore. In this specific case, as we are speaking to the source of truth, we should ensure the PeerStore is prioritizing these records. If the recorded `multiaddrs` or `protocols` have changed, interested parties must be informed via the `change:multiaddrs` or `change:protocols` events respectively. + +In the background, the Identify Service is also waiting for protocol change notifications of peers via the IdentifyPush protocol. Peers may leverage the `identify-push` message to communicate protocol changes to all connected peers, so that their PeerStore can be updated with the updated protocols. As the `identify-push` also sends complete and updated information, the data in the PeerStore can be replaced. -In the background, the Identify Service is also waiting for new protocols to be started by the peer. If a new protocol is started, the `identify-push` message is sent to all the connected peers, so that their PeerStore can be updated with the new protocol and relevant parties are noticed. As the `identify-push` also sends complete and updated information, the data in the PeerStore is replaced. +While it is currently not supported in js-libp2p, future iterations may also support the [IdentifyDelta protocol](https://github.com/libp2p/specs/pull/176). -On different context, it is also possible to gather relevant information for the peers. For instance, in `dht` operations, nodes can exchanges data of peers they know as part of the `dht` operation. In this case, we can get information from a peer that we already know. As a consequence, the `PeerStore` should act accordingly and not replace the data it owns, but just try to merge it the discovered data is new. For example, discovered a new address of a peer. +It is also possible to gather relevant information for peers from other protocols / subsystems. For instance, in `DHT` operations, nodes can exchange peer data as part of the `DHT` operation. In this case, we can learn additional information about a peer we already know. In this scenario the `PeerStore` should not replace the existing data it has, just add it. #### Act @@ -101,7 +105,7 @@ Get an array of all the peers, as well as their information. The information int ## Address Book -The `addressBook` keeps the known multiaddrs of a peer. The multiaddrs of each peer are not a constant and the Address book must have this into consideration. +The `addressBook` keeps the known multiaddrs of a peer. The multiaddrs of each peer may change over time and the Address Book must account for this. `Map` @@ -124,7 +128,7 @@ A `peerId.toString()` identifier mapping to a `multiaddrInfo` object, which shou - `addressBook.delete()` - `addressBook.peers()` -It is important pointing out that the API methods which return arrays of data (`set`, `get`, `getMultiaddrsForPeer`) shuld return the `multiaddr` property of the `multiaddrInfo` and not the entire `multiaddrInfo` as the remaining data should be used internally. Should we consider having two datastructure instead? +It is important pointing out that the API methods which return arrays of data (`set`, `get`, `getMultiaddrsForPeer`) should return the `multiaddr` property of the `multiaddrInfo` and not the entire `multiaddrInfo` as the remaining data should be used internally. Should we consider having two datastructure instead? Further API methods will probably be added in the context of multiaddr `ttl` and multiaddr confidence. From 72dea969c391fc938aa6cc6614fa37560b18da36 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 25 Mar 2020 19:10:17 +0100 Subject: [PATCH 3/8] chore: minor fixes and initial tests added --- src/peer-store/README.md | 79 ++---- src/peer-store/address-book.js | 109 ++------ src/peer-store/book.js | 75 ++++++ src/peer-store/proto-book.js | 109 ++------ test/peer-store/address-book.spec.js | 357 +++++++++++++++++++++++++++ test/peer-store/proto-book.spec.js | 302 +++++++++++++++++++++- 6 files changed, 773 insertions(+), 258 deletions(-) create mode 100644 src/peer-store/book.js create mode 100644 test/peer-store/address-book.spec.js diff --git a/src/peer-store/README.md b/src/peer-store/README.md index 23492165ac..3a3e9d4ed9 100644 --- a/src/peer-store/README.md +++ b/src/peer-store/README.md @@ -6,9 +6,7 @@ The PeerStore needs to manage the high level operations on its inner books. More (Future considerations: Peerstore should manage a job runner to trigger books runners for data trimming or computations) -## Peers Environment - -#### Sense +## Data gathering Several libp2p subsystems will perform operations, which will gather relevant information about peers. Some operations might not have this as an end goal, but can also gather important data. @@ -27,13 +25,13 @@ While it is currently not supported in js-libp2p, future iterations may also sup It is also possible to gather relevant information for peers from other protocols / subsystems. For instance, in `DHT` operations, nodes can exchange peer data as part of the `DHT` operation. In this case, we can learn additional information about a peer we already know. In this scenario the `PeerStore` should not replace the existing data it has, just add it. -#### Act +## Data Consumption When the `PeerStore` data is updated, this information might be important for different parties. -`js-libp2p` keeps a topology of peers for each protocol a node is running. This way, once a protocol is supported for a peer, the topology of that protocol should be informed that a new peer may be used and the subsystem can decide if it should open a new stream it that peer or not. +Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to a peer. The same is true for pinging a peer. While the `AddressBook` is going to keep its data updated, it will also emit `change:multiaddrs` events so that subsystems/users interested in knowing these changes can be notifyied instead of pooling the `AddressBook`. -Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to a peer. The same is true for pinging a peer. +Everytime a peer starts/stops supporting a protocol, libp2p subsystems or users might need to act accordingly. `js-libp2p` registrar orchestrates known peers, established connections and protocol topologies. This way, once a protocol is supported for a peer, the topology of that protocol should be informed that a new peer may be used and the subsystem can decide if it should open a new stream with that peer or not. For these situations, the `ProtoBook` will emit `change:protocols` events whenever supported protocols of a peer change. ## PeerStore implementation @@ -48,60 +46,17 @@ Access to its underlying books: High level operations: -- `peerStore.set(peerId, data, options)` or `events` - -High level set which should be able to identify the type of data received and forward to the appropriate book sets. More than a bridge, this aims to allow the combination of multiple data storage as follows: - -`data = { multiaddrs: [...], protocols: [...] }` - ----- (should be removed / re-written, but important for design decisions) - -One aspect that we need to consider is wether we should add information to every book, even if we don't have any relevant information for it. For instance, if we just discover a peer via a typical discovery service, we will have the `peerId` and an array of `multiaddr`. When we do `peerStore.set()`, should we also do `protoBook.set()` with an empty list of protocols? I don't see any advantage on adding to the remaining ones. - -**IMPORTANT:** This is one of the biggest design decisions to make (set vs events). The programmatic API is the easiest solution but it can provide users an API that they sould not need. If we go on an event-based approach, the `peerStore` should receive all the relevant subsystems (discovery, identifyService, ...) and sense the environment (via events) to gather the information that would need to be sent via the API. Thile the latest seems the best solution, it is the more complex one to implement, as we would ideally have an interface that those subsystems would need to implement and each time we have a new subsystem that needs to add data to the peerStore, we might need to update the `peer-store` codebase (or have a good set of abstractions). - -It is also important pointing out that users would be able to use `peerStore.protoBook.*`, so eventually we should move those into `peerStore._ protoBook.*` if we do not intend them to use it. - ---- - -- `peerStore.get(peerId, options)` - -Get the information of a provided peer. The amount of information that we want can be customized with the following options, which are true by default: - -```js -{ - address: true, - proto: true, - key: true, - metadata: true -} -``` - -- `peerStore.delete(peerId, [data])` +- `peerStore.delete(peerId)` -Deletes the provided peer from every book. If data is provided, just remove the data from the books. The data should be provided as follows: +Deletes the provided peer from every book. -```js -{ - address: [], - proto: [], - key: [], - metadata: [] -} -``` +- `peerStore.find(peerId)` -- `peerStore.peers(options)` +TODO (Move to API.doc and reference) -Get an array of all the peers, as well as their information. The information intended can be customized with the following options, which are true by default: +- `peerStore.peers()` -```js -{ - address: true, - proto: true, - key: true, - metadata: true -} -``` +Get an array of all the peers, as well as their information. ## Address Book @@ -119,18 +74,17 @@ A `peerId.toString()` identifier mapping to a `multiaddrInfo` object, which shou } ``` -**Note:** except for multiaddr namings, the other properties are placeholders for now and might not be as described in the future milestones. +**Note:** except for multiaddr naming, the other properties are placeholders for now and might not be as described in the future milestones. +- `addressBook.data` - `addressBook.set()` - `addressBook.get()` -- `getMultiaddrsForPeer()` -- `addressBook.has()` +- `addressBook.getMultiaddrsForPeer()` - `addressBook.delete()` -- `addressBook.peers()` -It is important pointing out that the API methods which return arrays of data (`set`, `get`, `getMultiaddrsForPeer`) should return the `multiaddr` property of the `multiaddrInfo` and not the entire `multiaddrInfo` as the remaining data should be used internally. Should we consider having two datastructure instead? +It is important pointing out that the API methods which return arrays of data (`set`, `get`, `getMultiaddrsForPeer`) should return the `multiaddr` property of the `multiaddrInfo` and not the entire `multiaddrInfo` as the remaining data should be used internally. -Further API methods will probably be added in the context of multiaddr `ttl` and multiaddr confidence. +(Future considerations: Further API methods will probably be added in the context of multiaddr `ttl` and multiaddr confidence.) **Not Yet Implemented**: Multiaddr Confidence @@ -148,12 +102,11 @@ The `protoBook` holds the identifiers of the protocols supported by each peer. T A `peerId.toString()` identifier mapping to a `Set` of protocol identifier strings. +- `protoBook.data` - `protoBook.set()` - `protoBook.get()` -- `protoBook.has()` - `protoBook.delete()` - `protoBook.supports()` -- `protoBook.peers()` ## Metadata Book diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 7f84cc165e..1ec7aaaf98 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -8,6 +8,8 @@ log.error = debug('libp2p:peer-store:address-book:error') const multiaddr = require('multiaddr') const PeerId = require('peer-id') +const Book = require('./book') + const { ERR_INVALID_PARAMETERS } = require('../errors') @@ -16,7 +18,7 @@ const { * The AddressBook is responsible for keeping the known multiaddrs * of a peer. */ -class AddressBook { +class AddressBook extends Book { /** * MultiaddrInfo object * @typedef {Object} multiaddrInfo @@ -30,6 +32,7 @@ class AddressBook { * @param {EventEmitter} peerStore */ constructor (peerStore) { + super(peerStore, 'change:multiaddrs', 'multiaddrs') /** * PeerStore Event emitter, used by the AddressBook to emit: * "peer" - emitted when a peer is discovered by the node. @@ -41,7 +44,7 @@ class AddressBook { * Map known peers to their known multiaddrs. * @type {Map} */ - this.addressBook = new Map() + this.data = new Map() } /** @@ -50,7 +53,7 @@ class AddressBook { * @param {Array|Multiaddr} addresses * @param {Object} [options] * @param {boolean} [options.replace = true] wether addresses received replace stored ones or a unique union is performed. - * @returns {Array} + * @returns {Array} */ set (peerId, addresses, { replace = true } = {}) { if (!PeerId.isPeerId(peerId)) { @@ -89,11 +92,11 @@ class AddressBook { * If the peer is not known, it is set with the given addresses. * @param {PeerId} peerId * @param {Array} multiaddrInfos - * @returns {Array} + * @returns {Array} */ _replace (peerId, multiaddrInfos) { const id = peerId.toString() - const rec = this.addressBook.get(id) + const rec = this.data.get(id) // Already know the peer if (rec && rec.length === multiaddrInfos.length) { @@ -106,7 +109,7 @@ class AddressBook { } } - this.addressBook.set(id, multiaddrInfos) + this.data.set(id, multiaddrInfos) this._ps.emit('peer', peerId) this._ps.emit('change:multiaddrs', { @@ -122,11 +125,11 @@ class AddressBook { * If the peer is not known, it is set with the given addresses. * @param {PeerId} peerId * @param {Array} multiaddrInfos - * @returns {Array} + * @returns {Array} */ _add (peerId, multiaddrInfos) { const id = peerId.toString() - const rec = this.addressBook.get(id) || [] + const rec = this.data.get(id) || [] // Add recorded uniquely to the new array rec.forEach((mi) => { @@ -137,11 +140,11 @@ class AddressBook { // If the recorded length is equal to the new after the uniquely union // The content is the same, no need to update. - if (rec.length === multiaddrInfos) { + if (rec.length === multiaddrInfos.length) { return [...multiaddrInfos] } - this.addressBook.set(id, multiaddrInfos) + this.data.set(id, multiaddrInfos) this._ps.emit('change:multiaddrs', { peerId, multiaddrs: multiaddrInfos.map((mi) => mi.multiaddr) @@ -156,25 +159,6 @@ class AddressBook { return [...multiaddrInfos] } - /** - * Get known addresses of a provided peer. - * @param {PeerId} peerId - * @returns {Array} - */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - const record = this.addressBook.get(peerId.toString()) - - if (!record) { - return undefined - } - - return record.map((multiaddrInfo) => multiaddrInfo.multiaddr) - } - /** * Get the known multiaddrs for a given peer. All returned multiaddrs * will include the encapsulated `PeerId` of the peer. @@ -186,7 +170,7 @@ class AddressBook { throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } - const record = this.addressBook.get(peerId.toString()) + const record = this.data.get(peerId.toString()) if (!record) { return undefined @@ -199,71 +183,6 @@ class AddressBook { return addr.encapsulate(`/p2p/${peerId.toB58String()}`) }) } - - /** - * Has known addresses of a provided peer. - * @param {PeerId} peerId - * @returns {boolean} - */ - has (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - return this.addressBook.has(peerId.toString()) - } - - /** - * Deletes the provided peer from the book. - * If addresses are provided, just remove the provided addresses and keep the peer. - * @param {PeerId} peerId - * @param {Array|multiaddr} [addresses] - * @returns {boolean} - */ - delete (peerId, addresses) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - if (addresses) { - return this._remove(peerId, addresses) - } - - this._ps('change:multiaddrs', { - peerId, - multiaddrs: [] - }) - - return this.addressBook.delete(peerId.toString()) - } - - /** - * Removes the given multiaddrs from the provided peer. - * @param {PeerId} peerId - * @param {Array|multiaddr} addresses - * @returns {boolean} - */ - _remove (peerId, addresses) { - if (!Array.isArray(addresses)) { - addresses = [addresses] - } - - const record = this.addressBook.get(peerId.toString()) - - if (!record) { - return false - } - - record.filter((mi) => addresses.includes(mi.multiaddr)) - // TODO: should we keep it if empty? - - this._ps('change:multiaddrs', { - peerId, - multiaddrs: record.map((multiaddrInfo) => multiaddrInfo.multiaddr) - }) - - return true - } } module.exports = AddressBook diff --git a/src/peer-store/book.js b/src/peer-store/book.js new file mode 100644 index 0000000000..af4725ed1c --- /dev/null +++ b/src/peer-store/book.js @@ -0,0 +1,75 @@ +'use strict' + +const errcode = require('err-code') +const PeerId = require('peer-id') + +const { + ERR_INVALID_PARAMETERS +} = require('../errors') + +/** + * The Book is the skeleton for the PeerStore books. + */ +class Book { + constructor (eventEmitter, eventName, eventProperty) { + this.eventEmitter = eventEmitter + this.eventName = eventName + this.eventProperty = eventProperty + + /** + * Map known peers to their data. + * @type {Map} + */ + this.data = new Map() + } + + /** + * Set known data of a provided peer. + * @param {PeerId} peerId + * @param {Array|Data} data + * @param {Object} [options] + * @param {boolean} [options.replace = true] wether data received replace stored the one or a unique union is performed. + */ + set (peerId, data, options) { + throw errcode(new Error('set must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED') + } + + /** + * Get the known data of a provided peer. + * @param {PeerId} peerId + * @returns {Array} + */ + get (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + const rec = this.data.get(peerId.toString()) + + return rec ? [...rec] : undefined + } + + /** + * Deletes the provided peer from the book. + * @param {PeerId} peerId + * @returns {boolean} + */ + delete (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + if (!this.data.delete(peerId.toString())) { + return false + } + + this.eventEmitter.emit(this.eventName, { + peerId, + [this.eventProperty]: [] + }) + + return true + } +} + +module.exports = Book diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index 3eeccb6151..7c859ce858 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -7,6 +7,8 @@ log.error = debug('libp2p:peer-store:proto-book:error') const PeerId = require('peer-id') +const Book = require('./book') + const { ERR_INVALID_PARAMETERS } = require('../errors') @@ -16,12 +18,14 @@ const { * protocols of a peer. * @fires ProtoBook#change:protocols */ -class ProtoBook { +class ProtoBook extends Book { /** * @constructor * @param {EventEmitter} peerStore */ constructor (peerStore) { + super(peerStore, 'change:protocols', 'protocols') + /** * PeerStore Event emitter, used by the ProtoBook to emit: * "change:protocols" - emitted when the known protocols of a peer change. @@ -32,12 +36,13 @@ class ProtoBook { * Map known peers to their known protocols. * @type {Map} */ - this.protoBook = new Map() + this.data = new Map() } /** * Set known protocols of a provided peer. * If the peer was not known before, it will be added. + * @override * @param {PeerId} peerId * @param {Array|string} protocols * @param {Object} [options] @@ -58,10 +63,10 @@ class ProtoBook { } if (replace) { - return this._replace(PeerId, protocols) + return this._replace(peerId, protocols) } - return this._add(PeerId, protocols) + return this._add(peerId, protocols) } /** @@ -73,7 +78,7 @@ class ProtoBook { */ _replace (peerId, protocols) { const id = peerId.toString() - const recSet = this.protoBook.get(id) + const recSet = this.data.get(id) const newSet = new Set(protocols) const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)) @@ -84,7 +89,7 @@ class ProtoBook { return protocols } - this.protoBook.set(id, newSet) + this.data.set(id, newSet) this._ps.emit('change:protocols', { peerId, protocols @@ -102,7 +107,7 @@ class ProtoBook { */ _add (peerId, protocols) { const id = peerId.toString() - const recSet = this.protoBook.get(id) || new Set() + const recSet = this.data.get(id) || new Set() const newSet = new Set([...recSet, ...protocols]) // Any new protocol added? @@ -112,7 +117,7 @@ class ProtoBook { protocols = [...newSet] - this.protoBook.set(id, newSet) + this.data.set(id, newSet) this._ps.emit('change:protocols', { peerId, protocols @@ -121,21 +126,6 @@ class ProtoBook { return protocols } - /** - * Get known supported protocols of a provided peer. - * @param {PeerId} peerId - * @returns {Array} - */ - get (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - const recSet = this.protoBook.get(peerId.toString()) - - return recSet ? [...recSet] : undefined - } - /** * Verify if the provided peer supports the given protocols. * @param {PeerId} peerId @@ -147,86 +137,21 @@ class ProtoBook { throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } - if (!Array.isArray(protocols)) { - protocols = [protocols] - } - - const recSet = this.protoBook.get(peerId.toString()) - - if (!recSet) { - return false - } - - return [...recSet].filter((p) => protocols.includes(p)).length === protocols.length - } - - /** - * Has known protocols of a provided peer. - * @param {PeerId} peerId - * @returns {boolean} - */ - has (peerId) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - return this.protoBook.has(peerId.toString()) - } - - /** - * Deletes the provided peer from the book. - * If protocols are provided, just remove the provided protocols and keep the peer. - * @param {PeerId} peerId - * @param {Array|string} [protocols] - * @returns {boolean} - */ - delete (peerId, protocols) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - if (protocols) { - return this._remove(peerId, protocols) - } - - if (!this.protoBook.delete(peerId.toString())) { - return false + if (!protocols) { + throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) } - this._ps.emit('change:protocols', { - peerId, - protocols: [] - }) - - return true - } - - /** - * Removes the given protocols from the provided peer. - * @param {PeerId} peerId - * @param {Array|string} protocols - * @returns {boolean} - */ - _remove (peerId, protocols) { if (!Array.isArray(protocols)) { protocols = [protocols] } - const recSet = this.protoBook.get(peerId.toString()) + const recSet = this.data.get(peerId.toString()) if (!recSet) { return false } - protocols.forEach((p) => recSet.delete(p)) - // TODO: should we keep it if empty? - - this._ps.emit('change:protocols', { - peerId, - protocols: [...recSet] - }) - - return true + return [...recSet].filter((p) => protocols.includes(p)).length === protocols.length } } diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js new file mode 100644 index 0000000000..8b96638e79 --- /dev/null +++ b/test/peer-store/address-book.spec.js @@ -0,0 +1,357 @@ +'use strict' +/* eslint-env mocha */ + +const chai = require('chai') +chai.use(require('dirty-chai')) +const { expect } = chai +const sinon = require('sinon') + +const { EventEmitter } = require('events') +const pDefer = require('p-defer') + +const multiaddr = require('multiaddr') +const PeerId = require('peer-id') + +const AddressBook = require('../../src/peer-store/address-book') + +const { + ERR_INVALID_PARAMETERS +} = require('../../src/errors') + +const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000') +const addr2 = multiaddr('/ip4/127.0.0.1/tcp/8001') +const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002') + +const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item) + +describe('addressBook', () => { + describe('addressBook.set', () => { + let peerId + let ee, ab + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + ab = new AddressBook(ee) + }) + + afterEach(() => { + ee.removeAllListeners() + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + ab.set('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('throwns invalid parameters error if no addresses provided', () => { + expect(() => { + ab.set(peerId) + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('throwns invalid parameters error if invalid multiaddrs are provided', () => { + expect(() => { + ab.set(peerId, 'invalid multiaddr') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('replaces the stored content by default and emit change event', () => { + const defer = pDefer() + sinon.spy(ab, '_replace') + sinon.spy(ab, '_add') + + const supportedMultiaddrs = [addr1, addr2] + + ee.once('change:multiaddrs', ({ peerId, multiaddrs }) => { + expect(peerId).to.exist() + expect(multiaddrs).to.eql(supportedMultiaddrs) + defer.resolve() + }) + + const multiaddrInfos = ab.set(peerId, supportedMultiaddrs) + const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + + expect(ab._replace.callCount).to.equal(1) + expect(ab._add.callCount).to.equal(0) + expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) + + return defer.promise + }) + + it('adds the new content if replace is disabled and emit change event', () => { + const defer = pDefer() + sinon.spy(ab, '_replace') + sinon.spy(ab, '_add') + + const supportedMultiaddrsA = [addr1, addr2] + const supportedMultiaddrsB = [addr3] + const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB) + + let changeTrigger = 2 + ee.on('change:multiaddrs', ({ multiaddrs }) => { + changeTrigger-- + if (changeTrigger === 0 && arraysAreEqual(multiaddrs, finalMultiaddrs)) { + defer.resolve() + } + }) + + // Replace + let multiaddrInfos = ab.set(peerId, supportedMultiaddrsA) + let multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + expect(ab._replace.callCount).to.equal(1) + expect(ab._add.callCount).to.equal(0) + expect(multiaddrs).to.have.deep.members(supportedMultiaddrsA) + + // Add + multiaddrInfos = ab.set(peerId, supportedMultiaddrsB, { replace: false }) + multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + expect(ab._replace.callCount).to.equal(1) + expect(ab._add.callCount).to.equal(1) + expect(multiaddrs).to.have.deep.members(finalMultiaddrs) + + return defer.promise + }) + + it('emits on set (replace) if not storing the exact same content', async () => { + const defer = pDefer() + + const supportedMultiaddrsA = [addr1, addr2] + const supportedMultiaddrsB = [addr2] + + let changeCounter = 0 + ee.on('change:multiaddrs', () => { + changeCounter++ + if (changeCounter > 1) { + defer.resolve() + } + }) + + // set 1 + ab.set(peerId, supportedMultiaddrsA) + + // set 2 (same content) + const multiaddrInfos = ab.set(peerId, supportedMultiaddrsB) + const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + expect(multiaddrs).to.have.deep.members(supportedMultiaddrsB) + + await defer.promise + }) + + it('does not emit on set (replace) if it is storing the exact same content', async () => { + const defer = pDefer() + + const supportedMultiaddrs = [addr1, addr2] + + let changeCounter = 0 + ee.on('change:multiaddrs', () => { + changeCounter++ + if (changeCounter > 1) { + defer.reject() + } + }) + + // set 1 + ab.set(peerId, supportedMultiaddrs) + + // set 2 (same content) + ab.set(peerId, supportedMultiaddrs) + + // Wait 50ms for incorrect second event + setTimeout(() => { + defer.resolve() + }, 50) + + await defer.promise + }) + + it('emits on set (add) if the content to add not exists', async () => { + const defer = pDefer() + + const supportedMultiaddrsA = [addr1] + const supportedMultiaddrsB = [addr2] + const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB) + + let changeCounter = 0 + ee.on('change:multiaddrs', () => { + changeCounter++ + if (changeCounter > 1) { + defer.resolve() + } + }) + + // set 1 + ab.set(peerId, supportedMultiaddrsA) + + // set 2 (content already existing) + const multiaddrInfos = ab.set(peerId, supportedMultiaddrsB, { replace: false }) + const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + expect(multiaddrs).to.have.deep.members(finalMultiaddrs) + + await defer.promise + }) + + it('does not emit on set (merge) if the content to add already exists', async () => { + const defer = pDefer() + + const supportedMultiaddrsA = [addr1, addr2] + const supportedMultiaddrsB = [addr2] + + let changeCounter = 0 + ee.on('change:multiaddrs', () => { + changeCounter++ + if (changeCounter > 1) { + defer.reject() + } + }) + + // set 1 + ab.set(peerId, supportedMultiaddrsA) + + // set 2 (content already existing) + ab.set(peerId, supportedMultiaddrsB, { replace: false }) + + // Wait 50ms for incorrect second event + setTimeout(() => { + defer.resolve() + }, 50) + + await defer.promise + }) + }) + + describe('addressBook.get', () => { + let peerId + let ee, ab + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + ab = new AddressBook(ee) + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + ab.get('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('returns undefined if no multiaddrs are known for the provided peer', () => { + const multiaddrInfos = ab.get(peerId) + + expect(multiaddrInfos).to.not.exist() + }) + + it('returns the multiaddrs stored', () => { + const supportedMultiaddrs = [addr1, addr2] + + ab.set(peerId, supportedMultiaddrs) + + const multiaddrInfos = ab.get(peerId) + const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) + }) + }) + + describe('addressBook.getMultiaddrsForPeer', () => { + let peerId + let ee, ab + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + ab = new AddressBook(ee) + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + ab.getMultiaddrsForPeer('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('returns undefined if no multiaddrs are known for the provided peer', () => { + const multiaddrInfos = ab.getMultiaddrsForPeer(peerId) + + expect(multiaddrInfos).to.not.exist() + }) + + it('returns the multiaddrs stored', () => { + const supportedMultiaddrs = [addr1, addr2] + + ab.set(peerId, supportedMultiaddrs) + + const multiaddrs = ab.getMultiaddrsForPeer(peerId) + multiaddrs.forEach((m) => { + expect(m.getPeerId()).to.equal(peerId.toB58String()) + }) + }) + }) + + describe('addressBook.delete', () => { + let peerId + let ee, ab + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + ab = new AddressBook(ee) + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + ab.delete('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('returns false if no records exist for the peer and no event is emitted', () => { + const defer = pDefer() + + ee.on('change:multiaddrs', () => { + defer.reject() + }) + + const deleted = ab.delete(peerId) + + expect(deleted).to.equal(false) + + // Wait 50ms for incorrect invalid event + setTimeout(() => { + defer.resolve() + }, 50) + + return defer.promise + }) + + it('returns true if the record exists and an event is emitted', () => { + const defer = pDefer() + + const supportedMultiaddrs = [addr1, addr2] + ab.set(peerId, supportedMultiaddrs) + + // Listen after set + ee.on('change:multiaddrs', ({ multiaddrs }) => { + expect(multiaddrs.length).to.eql(0) + defer.resolve() + }) + + const deleted = ab.delete(peerId) + + expect(deleted).to.equal(true) + + return defer.promise + }) + }) +}) diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js index acd9193b7d..cb8d6bbc69 100644 --- a/test/peer-store/proto-book.spec.js +++ b/test/peer-store/proto-book.spec.js @@ -7,6 +7,7 @@ const { expect } = chai const sinon = require('sinon') const { EventEmitter } = require('events') +const pDefer = require('p-defer') const PeerId = require('peer-id') @@ -16,6 +17,8 @@ const { ERR_INVALID_PARAMETERS } = require('../../src/errors') +const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item) + describe('protoBook', () => { describe('protoBook.set', () => { let peerId @@ -30,40 +33,323 @@ describe('protoBook', () => { pb = new ProtoBook(ee) }) - it('should thrown invalid parameters error if invalid PeerId is provided', () => { + afterEach(() => { + ee.removeAllListeners() + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { expect(() => { pb.set('invalid peerId') }).to.throw(ERR_INVALID_PARAMETERS) }) - it('should thrown invalid parameters error if no protocols provided', () => { + it('throwns invalid parameters error if no protocols provided', () => { expect(() => { pb.set(peerId) }).to.throw(ERR_INVALID_PARAMETERS) }) - it('should replace the stored content by default', () => { + it('replaces the stored content by default and emit change event', () => { + const defer = pDefer() sinon.spy(pb, '_replace') sinon.spy(pb, '_add') const supportedProtocols = ['protocol1', 'protocol2'] + + ee.once('change:protocols', ({ peerId, protocols }) => { + expect(peerId).to.exist() + expect(protocols).to.have.deep.members(supportedProtocols) + defer.resolve() + }) + const protocols = pb.set(peerId, supportedProtocols) expect(pb._replace.callCount).to.equal(1) expect(pb._add.callCount).to.equal(0) - expect(protocols).to.eql(supportedProtocols) + expect(protocols).to.have.deep.members(supportedProtocols) + + return defer.promise }) - it('should replace the stored content by default', () => { + it('adds the new content if replace is disabled and emit change event', () => { + const defer = pDefer() sinon.spy(pb, '_replace') sinon.spy(pb, '_add') - const supportedProtocols = ['protocol1', 'protocol2'] - const protocols = pb.set(peerId, supportedProtocols) + const supportedProtocolsA = ['protocol1', 'protocol2'] + const supportedProtocolsB = ['protocol3'] + const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB) + + let changeTrigger = 2 + ee.on('change:protocols', ({ protocols }) => { + changeTrigger-- + if (changeTrigger === 0 && arraysAreEqual(protocols, finalProtocols)) { + defer.resolve() + } + }) + // Replace + let protocols = pb.set(peerId, supportedProtocolsA) expect(pb._replace.callCount).to.equal(1) expect(pb._add.callCount).to.equal(0) - expect(protocols).to.eql(supportedProtocols) + expect(protocols).to.have.deep.members(supportedProtocolsA) + + // Add + protocols = pb.set(peerId, supportedProtocolsB, { replace: false }) + expect(pb._replace.callCount).to.equal(1) + expect(pb._add.callCount).to.equal(1) + expect(protocols).to.have.deep.members(finalProtocols) + + return defer.promise + }) + + it('emits on set (replace) if not storing the exact same content', () => { + const defer = pDefer() + + const supportedProtocolsA = ['protocol1', 'protocol2'] + const supportedProtocolsB = ['protocol2'] + + let changeCounter = 0 + ee.on('change:protocols', () => { + changeCounter++ + if (changeCounter > 1) { + defer.resolve() + } + }) + + // set 1 + pb.set(peerId, supportedProtocolsA) + + // set 2 (same content) + const protocols = pb.set(peerId, supportedProtocolsB) + expect(protocols).to.have.deep.members(supportedProtocolsB) + + return defer.promise + }) + + it('does not emit on set (replace) if it is storing the exact same content', () => { + const defer = pDefer() + + const supportedProtocols = ['protocol1', 'protocol2'] + + let changeCounter = 0 + ee.on('change:protocols', () => { + changeCounter++ + if (changeCounter > 1) { + defer.reject() + } + }) + + // set 1 + pb.set(peerId, supportedProtocols) + + // set 2 (same content) + pb.set(peerId, supportedProtocols) + + // Wait 50ms for incorrect second event + setTimeout(() => { + defer.resolve() + }, 50) + + return defer.promise + }) + + it('emits on set (add) if the content to add not exists', () => { + const defer = pDefer() + + const supportedProtocolsA = ['protocol1'] + const supportedProtocolsB = ['protocol2'] + const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB) + + let changeCounter = 0 + ee.on('change:protocols', () => { + changeCounter++ + if (changeCounter > 1) { + defer.resolve() + } + }) + + // set 1 + pb.set(peerId, supportedProtocolsA) + + // set 2 (content already existing) + const protocols = pb.set(peerId, supportedProtocolsB, { replace: false }) + expect(protocols).to.have.deep.members(finalProtocols) + + return defer.promise + }) + + it('does not emit on set (merge) if the content to add already exists', () => { + const defer = pDefer() + + const supportedProtocolsA = ['protocol1', 'protocol2'] + const supportedProtocolsB = ['protocol2'] + + let changeCounter = 0 + ee.on('change:protocols', () => { + changeCounter++ + if (changeCounter > 1) { + defer.reject() + } + }) + + // set 1 + pb.set(peerId, supportedProtocolsA) + + // set 2 (content already existing) + pb.set(peerId, supportedProtocolsB, { replace: false }) + + // Wait 50ms for incorrect second event + setTimeout(() => { + defer.resolve() + }, 50) + + return defer.promise + }) + }) + + describe('protoBook.get', () => { + let peerId + let ee, pb + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + pb = new ProtoBook(ee) + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + pb.get('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('returns undefined if no protocols are known for the provided peer', () => { + const protocols = pb.get(peerId) + + expect(protocols).to.not.exist() + }) + + it('returns the protocols stored', () => { + const supportedProtocols = ['protocol1', 'protocol2'] + + pb.set(peerId, supportedProtocols) + + const protocols = pb.get(peerId) + expect(protocols).to.have.deep.members(supportedProtocols) + }) + }) + + describe('protoBook.supports', () => { + let peerId + let ee, pb + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + pb = new ProtoBook(ee) + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + pb.supports('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('throwns invalid parameters error if no protocols provided', () => { + expect(() => { + pb.supports(peerId) + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('returns false if no records exist for the peer', () => { + const supportedProtocols = ['protocol1'] + const supports = pb.supports(peerId, supportedProtocols[0]) + + expect(supports).to.equal(false) + }) + + it('returns true if the protocol is supported', () => { + const supportedProtocols = ['protocol1', 'protocol2'] + + pb.set(peerId, supportedProtocols) + + const supports = pb.supports(peerId, supportedProtocols[1]) + expect(supports).to.equal(true) + }) + + it('returns false if part of the protocols is supported', () => { + const supportedProtocols = ['protocol1', 'protocol2'] + const otherProtocols = ['protocol3', 'protocol4'] + + pb.set(peerId, supportedProtocols) + + const supports = pb.supports(peerId, [supportedProtocols[0], ...otherProtocols]) + expect(supports).to.equal(false) + }) + }) + + describe('protoBook.delete', () => { + let peerId + let ee, pb + + before(async () => { + peerId = await PeerId.create() + }) + + beforeEach(() => { + ee = new EventEmitter() + pb = new ProtoBook(ee) + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + pb.delete('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('returns false if no records exist for the peer and no event is emitted', () => { + const defer = pDefer() + + ee.on('change:protocols', () => { + defer.reject() + }) + + const deleted = pb.delete(peerId) + + expect(deleted).to.equal(false) + + // Wait 50ms for incorrect invalid event + setTimeout(() => { + defer.resolve() + }, 50) + + return defer.promise + }) + + it('returns true if the record exists and an event is emitted', () => { + const defer = pDefer() + + const supportedProtocols = ['protocol1', 'protocol2'] + pb.set(peerId, supportedProtocols) + + // Listen after set + ee.on('change:protocols', ({ protocols }) => { + expect(protocols.length).to.eql(0) + defer.resolve() + }) + + const deleted = pb.delete(peerId) + + expect(deleted).to.equal(true) + + return defer.promise }) }) }) From d3e4ad2d697a843a8ddc87c02c297b00d6b13d30 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 30 Mar 2020 18:54:35 +0200 Subject: [PATCH 4/8] chore: integrate new peer-store with code using adapters for other modules --- doc/API.md | 350 +++++++++++++++++++++ src/dialer/index.js | 36 ++- src/get-peer-info.js | 2 +- src/identify/index.js | 58 +--- src/index.js | 13 +- src/peer-store/README.md | 63 ++-- src/peer-store/address-book.js | 58 +++- src/peer-store/book.js | 5 + src/peer-store/index.js | 323 +++++++++---------- src/peer-store/proto-book.js | 29 +- src/registrar.js | 26 +- src/upgrader.js | 4 +- test/content-routing/dht/operation.node.js | 7 +- test/dialing/direct.node.js | 15 +- test/dialing/direct.spec.js | 46 ++- test/dialing/relay.node.js | 7 +- test/identify/index.spec.js | 56 ++-- test/peer-discovery/index.spec.js | 8 +- test/peer-store/address-book.spec.js | 29 +- test/peer-store/peer-store.spec.js | 225 +++++-------- test/peer-store/proto-book.spec.js | 29 +- test/registrar/registrar.spec.js | 18 +- 22 files changed, 865 insertions(+), 542 deletions(-) diff --git a/doc/API.md b/doc/API.md index 3f12f22dfb..6051362324 100644 --- a/doc/API.md +++ b/doc/API.md @@ -17,6 +17,17 @@ * [`contentRouting.put`](#contentroutingput) * [`contentRouting.get`](#contentroutingget) * [`contentRouting.getMany`](#contentroutinggetmany) + * [`peerStore.addressBook.delete`](#peerstoreaddressbookdelete) + * [`peerStore.addressBook.get`](#peerstoreaddressbookget) + * [`peerStore.addressBook.getMultiaddrsForPeer`](#peerstoreaddressbookgetmultiaddrsforpeer) + * [`peerStore.addressBook.set`](#peerstoreaddressbookset) + * [`peerStore.protoBook.delete`](#peerstoreprotobookdelete) + * [`peerStore.protoBook.get`](#peerstoreprotobookget) + * [`peerStore.protoBook.set`](#peerstoreprotobookset) + * [`peerStore.protoBook.supports`](#peerstoreprotobooksupports) + * [`peerStore.delete`](#peerstoredelete) + * [`peerStore.find`](#peerstorefind) + * [`peerStore.peers`](#peerstorepeers) * [`pubsub.getSubscribers`](#pubsubgetsubscribers) * [`pubsub.getTopics`](#pubsubgettopics) * [`pubsub.publish`](#pubsubpublish) @@ -507,6 +518,345 @@ const key = '/key' const { from, val } = await libp2p.contentRouting.get(key) ``` +### peerStore.addressBook.delete + +Delete the provided peer from the book. + +`peerStore.addressBook.delete(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to remove | + +#### Returns + +| Type | Description | +|------|-------------| +| `boolean` | true if found and removed | + +#### Example + +```js +peerStore.addressBook.delete(peerId) +// false +peerStore.addressBook.set(peerId, discoveredMultiaddr) +peerStore.addressBook.delete(peerId) +// true +``` + +### peerStore.addressBook.get + +Get the known `multiaddrInfos` of a provided peer. + +`peerStore.addressBook.get(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to get | + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | Array of peer's multiaddr with their relevant information | + +#### Example + +```js +peerStore.addressBook.get(peerId) +// undefined +peerStore.addressBook.set(peerId, discoveredMultiaddr) +peerStore.addressBook.get(peerId) +// [ +// { +// multiaddr: /ip4/140.10.2.1/tcp/8000, +// ... +// }, +// { +// multiaddr: /ip4/140.10.2.1/ws/8001 +// ... +// }, +// ] +``` + +## peerStore.addressBook.getMultiaddrsForPeer + +Get the known `multiaddr` of a provided peer. All returned multiaddrs will include the encapsulated `PeerId` of the peer. + +`peerStore.addressBook.getMultiaddrsForPeer(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to get | + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | Array of peer's multiaddr | + +#### Example + +```js +peerStore.addressBook.getMultiaddrsForPeer(peerId) +// undefined +peerStore.addressBook.set(peerId, discoveredMultiaddr) +peerStore.addressBook.getMultiaddrsForPeer(peerId) +// [ +// /ip4/140.10.2.1/tcp/8000/p2p/QmW8rAgaaA6sRydK1k6vonShQME47aDxaFidbtMevWs73t +// /ip4/140.10.2.1/ws/8001/p2p/QmW8rAgaaA6sRydK1k6vonShQME47aDxaFidbtMevWs73t +// ] +``` + +### peerStore.addressBook.set + +Set known `multiaddrs` of a given peer. + +`peerStore.addressBook.set(peerId, multiaddrs, options)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to set | +| multiaddrs | `multiaddr|Array` | multiaddrs to store | +| [options] | `object` | options to set | +| [options.replace] | `Object` | replace stored data (if exists) or unique union (default: true) | + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | Array of peer's multiaddr with their relevant information | + +#### Example + +```js +peerStore.addressBook.set(peerId, discoveredMultiaddr) +// [ +// { +// multiaddr: /ip4/140.10.2.1/tcp/8000, +// ... +// }, +// { +// multiaddr: /ip4/140.10.2.1/ws/8001 +// ... +// }, +// ] +``` + +### peerStore.protoBook.delete + +Delete the provided peer from the book. + +`peerStore.protoBook.delete(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to remove | + +#### Returns + +| Type | Description | +|------|-------------| +| `boolean` | true if found and removed | + +#### Example + +```js +peerStore.protoBook.delete(peerId) +// false +peerStore.protoBook.set(peerId, supportedProtocols) +peerStore.protoBook.delete(peerId) +// true +``` + +### peerStore.protoBook.get + +Get the known `protocols` of a provided peer. + +`peerStore.protoBook.get(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to get | + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | Array of peer's supported protocols | + +#### Example + +```js +peerStore.protoBook.get(peerId) +// undefined +peerStore.protoBook.set(peerId, supportedProtocols) +peerStore.protoBook.get(peerId) +// [ '/proto/1.0.0', '/proto/1.1.0' ] +``` + +### peerStore.protoBook.set + +Set known `protocols` of a given peer. + +`peerStore.protoBook.set(peerId, protocols, options)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to set | +| protocols | `string|Array` | protocols to store | +| [options] | `object` | options to set | +| [options.replace] | `Object` | replace stored data (if exists) or unique union (default: true) | + +#### Returns + +| Type | Description | +|------|-------------| +| `Array` | Array of peer's supported protocols | + +#### Example + +```js +peerStore.protoBook.set(peerId, supportedProtocols) +// [ '/proto/1.0.0', '/proto/1.1.0' ] +``` + +### peerStore.protoBook.supports + +Verify if the provided peer supports the given `protocols`. + +`peerStore.protoBook.supports(peerId, protocols)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to get | +| protocols | `string|Array` | protocols to verify | + +#### Returns + +| Type | Description | +|------|-------------| +| `boolean` | true if found and removed | + +#### Example + +```js +const supportedProtocols = [ '/proto/1.0.0', '/proto/1.1.0' ] +peerStore.protoBook.supports(peerId, supportedProtocols) +// false +peerStore.protoBook.supports(peerId, supportedProtocols[0]) +// false +peerStore.protoBook.set(peerId, supportedProtocols) +peerStore.protoBook.supports(peerId, supportedProtocols) +// true +peerStore.protoBook.supports(peerId, supportedProtocols[0]) +// true +``` + +### peerStore.delete + +Delete the provided peer from every book. + +`peerStore.delete(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to remove | + +#### Returns + +| Type | Description | +|------|-------------| +| `boolean` | true if found and removed | + +#### Example + +```js +peerStore.delete(peerId) +// false +peerStore.addressBook.set(peerId, discoveredMultiaddrs) +peerStore.protoBook.set(peerId, supportedProtocols) +peerStore.delete(peerId) +// true +peerStore.delete(peerId2) +// false +peerStore.addressBook.set(peerId2, discoveredMultiaddrs) +peerStore.delete(peerId2) +// true +``` + +### peerStore.find + +Find the stored information of a given peer. + +`peerStore.find(peerId)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | `peerid` | peerId to find | + +#### Returns + +| Type | Description | +|------|-------------| +| `peerInfo` | Peer information of the provided peer | + +#### Example + +```js +peerStore.find(peerId) +// false +peerStore.addressBook.set(peerId, discoveredMultiaddrs) +peerStore.protoBook.set(peerId, supportedProtocols) +peerStore.find(peerId) +// { +// multiaddrInfos: [...], +// protocols: [...] +// } +``` + +### peerStore.peers + +Get all the stored information of every peer. + +`peerStore.peers()` + +#### Returns + +| Type | Description | +|------|-------------| +| `Map` | Peer information of every peer | + +TODO: change when `peer-info` is deprecated (breaking change) + +#### Example + +```js +for (peer of peerStore.peers().values()) { + // peerInfo instance +} +``` + ### pubsub.getSubscribers Gets a list of the peer-ids that are subscribed to one topic. diff --git a/src/dialer/index.js b/src/dialer/index.js index 7638ffe572..9d257cb5f3 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -5,7 +5,6 @@ const errCode = require('err-code') const TimeoutController = require('timeout-abort-controller') const anySignal = require('any-signal') const PeerId = require('peer-id') -const PeerInfo = require('peer-info') const debug = require('debug') const log = debug('libp2p:dialer') log.error = debug('libp2p:dialer:error') @@ -62,13 +61,13 @@ class Dialer { * The dial to the first address that is successfully able to upgrade a connection * will be used. * - * @param {PeerInfo|Multiaddr} peer The peer to dial + * @param {PeerId|Multiaddr} peerId The peer to dial * @param {object} [options] * @param {AbortSignal} [options.signal] An AbortController signal * @returns {Promise} */ - async connectToPeer (peer, options = {}) { - const dialTarget = this._createDialTarget(peer) + async connectToPeer (peerId, options = {}) { + const dialTarget = this._createDialTarget(peerId) if (dialTarget.addrs.length === 0) { throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES) } @@ -100,7 +99,7 @@ class Dialer { * Creates a DialTarget. The DialTarget is used to create and track * the DialRequest to a given peer. * @private - * @param {PeerInfo|Multiaddr} peer A PeerId or Multiaddr + * @param {PeerId|Multiaddr} peer A PeerId or Multiaddr * @returns {DialTarget} */ _createDialTarget (peer) { @@ -111,7 +110,10 @@ class Dialer { addrs: [dialable] } } - const addrs = this.peerStore.multiaddrsForPeer(dialable) + + dialable.multiaddrs && this.peerStore.addressBook.set(dialable.id, Array.from(dialable.multiaddrs), { replace: false }) + const addrs = this.peerStore.addressBook.getMultiaddrsForPeer(dialable.id) + return { id: dialable.id.toB58String(), addrs @@ -179,21 +181,27 @@ class Dialer { this.tokens.push(token) } + /** + * PeerInfo object + * @typedef {Object} peerInfo + * @property {Multiaddr} multiaddr peer multiaddr. + * @property {PeerId} id peer id. + */ + /** * Converts the given `peer` into a `PeerInfo` or `Multiaddr`. * @static - * @param {PeerInfo|PeerId|Multiaddr|string} peer - * @returns {PeerInfo|Multiaddr} + * @param {PeerId|Multiaddr|string} peer + * @returns {peerInfo|Multiaddr} */ static getDialable (peer) { - if (PeerInfo.isPeerInfo(peer)) return peer if (typeof peer === 'string') { peer = multiaddr(peer) } - let addr + let addrs if (multiaddr.isMultiaddr(peer)) { - addr = peer + addrs = new Set([peer]) // TODO: after peer-info removal, a Set should not be needed try { peer = PeerId.createFromCID(peer.getPeerId()) } catch (err) { @@ -202,10 +210,12 @@ class Dialer { } if (PeerId.isPeerId(peer)) { - peer = new PeerInfo(peer) + peer = { + id: peer, + multiaddrs: addrs + } } - addr && peer.multiaddrs.add(addr) return peer } } diff --git a/src/get-peer-info.js b/src/get-peer-info.js index 01a6bc49f2..83135eebe4 100644 --- a/src/get-peer-info.js +++ b/src/get-peer-info.js @@ -38,7 +38,7 @@ function getPeerInfo (peer, peerStore) { addr && peer.multiaddrs.add(addr) - return peerStore ? peerStore.put(peer) : peer + return peerStore ? peerStore.put(peer, { replace: false }) : peer } /** diff --git a/src/identify/index.js b/src/identify/index.js index d8643611b9..75a685948c 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -6,7 +6,6 @@ const lp = require('it-length-prefixed') const pipe = require('it-pipe') const { collect, take, consume } = require('streaming-iterables') -const PeerInfo = require('peer-info') const PeerId = require('peer-id') const multiaddr = require('multiaddr') const { toBuffer } = require('it-buffer') @@ -27,39 +26,6 @@ const errCode = require('err-code') const { codes } = require('../errors') class IdentifyService { - /** - * Replaces the multiaddrs on the given `peerInfo`, - * with the provided `multiaddrs` - * @param {PeerInfo} peerInfo - * @param {Array|Array} multiaddrs - */ - static updatePeerAddresses (peerInfo, multiaddrs) { - if (multiaddrs && multiaddrs.length > 0) { - peerInfo.multiaddrs.clear() - multiaddrs.forEach(ma => { - try { - peerInfo.multiaddrs.add(ma) - } catch (err) { - log.error('could not add multiaddr', err) - } - }) - } - } - - /** - * Replaces the protocols on the given `peerInfo`, - * with the provided `protocols` - * @static - * @param {PeerInfo} peerInfo - * @param {Array} protocols - */ - static updatePeerProtocols (peerInfo, protocols) { - if (protocols && protocols.length > 0) { - peerInfo.protocols.clear() - protocols.forEach(proto => peerInfo.protocols.add(proto)) - } - } - /** * Takes the `addr` and converts it to a Multiaddr if possible * @param {Buffer|String} addr @@ -181,7 +147,7 @@ class IdentifyService { } = message const id = await PeerId.createFromPubKey(publicKey) - const peerInfo = new PeerInfo(id) + if (connection.remotePeer.toB58String() !== id.toB58String()) { throw errCode(new Error('identified peer does not match the expected peer'), codes.ERR_INVALID_PEER) } @@ -189,11 +155,10 @@ class IdentifyService { // Get the observedAddr if there is one observedAddr = IdentifyService.getCleanMultiaddr(observedAddr) - // Copy the listenAddrs and protocols - IdentifyService.updatePeerAddresses(peerInfo, listenAddrs) - IdentifyService.updatePeerProtocols(peerInfo, protocols) + // Update peers data in PeerStore + this.registrar.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr))) + this.registrar.peerStore.protoBook.set(id, protocols) - this.registrar.peerStore.replace(peerInfo) // TODO: Track our observed address so that we can score it log('received observed address of %s', observedAddr) } @@ -241,6 +206,9 @@ class IdentifyService { protocols: Array.from(this._protocols.keys()) }) + // TODO: should we add to peerStore.addressBook.set() here? + // We can have an inbound connection from an unkwown peer + pipe( [message], lp.encode(), @@ -273,20 +241,16 @@ class IdentifyService { return log.error('received invalid message', err) } - // Update the listen addresses - const peerInfo = new PeerInfo(connection.remotePeer) - + // Update peers data in PeerStore + const id = connection.remotePeer try { - IdentifyService.updatePeerAddresses(peerInfo, message.listenAddrs) + this.registrar.peerStore.addressBook.set(id, message.listenAddrs.map((addr) => multiaddr(addr))) } catch (err) { return log.error('received invalid listen addrs', err) } // Update the protocols - IdentifyService.updatePeerProtocols(peerInfo, message.protocols) - - // Update the peer in the PeerStore - this.registrar.peerStore.replace(peerInfo) + this.registrar.peerStore.protoBook.set(id, message.protocols) } } diff --git a/src/index.js b/src/index.js index a7ee76b649..db597cae8b 100644 --- a/src/index.js +++ b/src/index.js @@ -59,7 +59,7 @@ class Libp2p extends EventEmitter { localPeer: this.peerInfo.id, metrics: this.metrics, onConnection: (connection) => { - const peerInfo = this.peerStore.put(new PeerInfo(connection.remotePeer), { silent: true }) + const peerInfo = new PeerInfo(connection.remotePeer) this.registrar.onConnect(peerInfo, connection) this.connectionManager.onConnect(connection) this.emit('peer:connect', peerInfo) @@ -289,7 +289,11 @@ class Libp2p extends EventEmitter { const dialable = Dialer.getDialable(peer) let connection if (PeerInfo.isPeerInfo(dialable)) { - this.peerStore.put(dialable, { silent: true }) + // TODO Inconsistency from: getDialable adds a set, while regular peerInfo uses a Multiaddr set + // This should be handled on `peer-info` removal + const multiaddrs = dialable.multiaddrs.toArray ? dialable.multiaddrs.toArray() : Array.from(dialable.multiaddrs) + this.peerStore.addressBook.set(dialable.id, multiaddrs, { replace: false }) + connection = this.registrar.getConnection(dialable) } @@ -430,7 +434,10 @@ class Libp2p extends EventEmitter { log.error(new Error(codes.ERR_DISCOVERED_SELF)) return } - this.peerStore.put(peerInfo) + + // TODO: once we deprecate peer-info, we should only set if we have data + this.peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray(), { replace: false }) + this.peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols), { replace: false }) } /** diff --git a/src/peer-store/README.md b/src/peer-store/README.md index 3a3e9d4ed9..835c8c7ea5 100644 --- a/src/peer-store/README.md +++ b/src/peer-store/README.md @@ -1,8 +1,8 @@ # Peerstore -Libp2p's Peerstore is responsible for keeping an updated register with the relevant information of the known peers. It should gather environment changes and be able to take decisions and notice interested parties of relevant changes. The Peerstore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. These book components have similar characteristics with the `Javascript Map` implementation. +Libp2p's Peerstore is responsible for keeping an updated register with the relevant information of the known peers. It should gather environment changes, be able to take decisions and notice interested parties of relevant changes. The Peerstore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. These book components have similar characteristics with the `Javascript Map` implementation. -The PeerStore needs to manage the high level operations on its inner books. Moreover, the peerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter. +The PeerStore manages the high level operations on its inner books. Moreover, the peerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter. (Future considerations: Peerstore should manage a job runner to trigger books runners for data trimming or computations) @@ -14,13 +14,12 @@ In a libp2p node's life, it will discover peers through its discovery protocols. Taking into account a different scenario, a peer might perform/receive a dial request to/from a unkwown peer. In such a scenario, the `PeerStore` must store the peer's multiaddr once a connection is established. -(NOTE: this should be removed later) -(currently we silently put it to the peerStore, without emitting events, as this logic exists in the `onConnected` callback from the upgrader. This way, we are never emitting the `peer` event when inbound connections happen, or a unkwown peer is dialed. Should we differentiate this?) - After a connection is established with a peer, the Identify protocol will run automatically. A stream is created and peers exchange their information (Multiaddrs, running protocols and their public key). Once this information is obtained, it should be added to the PeerStore. In this specific case, as we are speaking to the source of truth, we should ensure the PeerStore is prioritizing these records. If the recorded `multiaddrs` or `protocols` have changed, interested parties must be informed via the `change:multiaddrs` or `change:protocols` events respectively. In the background, the Identify Service is also waiting for protocol change notifications of peers via the IdentifyPush protocol. Peers may leverage the `identify-push` message to communicate protocol changes to all connected peers, so that their PeerStore can be updated with the updated protocols. As the `identify-push` also sends complete and updated information, the data in the PeerStore can be replaced. +(To consider: Should we not replace until we get to multiaddr confidence? we might loose true information as we will talk with older nodes on the network.) + While it is currently not supported in js-libp2p, future iterations may also support the [IdentifyDelta protocol](https://github.com/libp2p/specs/pull/176). It is also possible to gather relevant information for peers from other protocols / subsystems. For instance, in `DHT` operations, nodes can exchange peer data as part of the `DHT` operation. In this case, we can learn additional information about a peer we already know. In this scenario the `PeerStore` should not replace the existing data it has, just add it. @@ -29,15 +28,17 @@ It is also possible to gather relevant information for peers from other protocol When the `PeerStore` data is updated, this information might be important for different parties. -Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to a peer. The same is true for pinging a peer. While the `AddressBook` is going to keep its data updated, it will also emit `change:multiaddrs` events so that subsystems/users interested in knowing these changes can be notifyied instead of pooling the `AddressBook`. +Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to it. The same is true for pinging a peer. While the `AddressBook` is going to keep its data updated, it will also emit `change:multiaddrs` events so that subsystems/users interested in knowing these changes can be notifyied instead of pooling the `AddressBook`. Everytime a peer starts/stops supporting a protocol, libp2p subsystems or users might need to act accordingly. `js-libp2p` registrar orchestrates known peers, established connections and protocol topologies. This way, once a protocol is supported for a peer, the topology of that protocol should be informed that a new peer may be used and the subsystem can decide if it should open a new stream with that peer or not. For these situations, the `ProtoBook` will emit `change:protocols` events whenever supported protocols of a peer change. ## PeerStore implementation -(Note: except for highlighting the APIs functionallity, they should be better formally described on `API.md` file) +The Peerstore wraps four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. Moreover, it provides a high level API for those components, as well as data events. -#### API: +### API + +For the complete API documentation, you should check the [API.md](../../doc/API.md). Access to its underlying books: @@ -46,19 +47,27 @@ Access to its underlying books: High level operations: -- `peerStore.delete(peerId)` +- [`peerStore.delete(peerId)`](../../doc/API.md#peerstoredelete) Deletes the provided peer from every book. -- `peerStore.find(peerId)` +- [`peerStore.find(peerId)`](../../doc/API.md#peerstorefind) + +Finds the stored information of a given peer. -TODO (Move to API.doc and reference) +- [`peerStore.peers()`](../../doc/API.md#peerstorepeers) -- `peerStore.peers()` +Gets an array of all the peers, as well as their information. -Get an array of all the peers, as well as their information. +### Events -## Address Book +- `peer` - emitted when a new peer is added. +- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs. +- `change:protocols` - emitted when a known peer supports a different set of protocols. + +### Components API + +#### Address Book The `addressBook` keeps the known multiaddrs of a peer. The multiaddrs of each peer may change over time and the Address Book must account for this. @@ -77,24 +86,22 @@ A `peerId.toString()` identifier mapping to a `multiaddrInfo` object, which shou **Note:** except for multiaddr naming, the other properties are placeholders for now and might not be as described in the future milestones. - `addressBook.data` -- `addressBook.set()` -- `addressBook.get()` -- `addressBook.getMultiaddrsForPeer()` -- `addressBook.delete()` - -It is important pointing out that the API methods which return arrays of data (`set`, `get`, `getMultiaddrsForPeer`) should return the `multiaddr` property of the `multiaddrInfo` and not the entire `multiaddrInfo` as the remaining data should be used internally. +- [`addressBook.delete()`](../../doc/API.md#peerstoreaddressbookdelete) +- [`addressBook.get()`](../../doc/API.md#peerstoreaddressbookget) +- [`addressBook.getMultiaddrsForPeer()`](../../doc/API.md#peerstoreaddressbookgetmultiaddrsforpeer) +- [`addressBook.set()`](../../doc/API.md#peerstoreaddressbookset) -(Future considerations: Further API methods will probably be added in the context of multiaddr `ttl` and multiaddr confidence.) +(Future considerations: Further API methods will probably be added in the context of multiaddr validity and multiaddr confidence.) **Not Yet Implemented**: Multiaddr Confidence -## Key Book +#### Key Book The `keyBook` tracks the keys of the peers. **Not Yet Implemented** -## Protocol Book +#### Protocol Book The `protoBook` holds the identifiers of the protocols supported by each peer. The protocols supported by each peer are dynamic and will change over time. @@ -103,11 +110,11 @@ The `protoBook` holds the identifiers of the protocols supported by each peer. T A `peerId.toString()` identifier mapping to a `Set` of protocol identifier strings. - `protoBook.data` -- `protoBook.set()` -- `protoBook.get()` -- `protoBook.delete()` -- `protoBook.supports()` +- [`protoBook.delete()`](../../doc/API.md#peerstoreprotobookdelete) +- [`protoBook.get()`](../../doc/API.md#peerstoreprotobookget) +- [`protoBook.set()`](../../doc/API.md#peerstoreprotobookset) +- [`protoBook.supports()`](../../doc/API.md#peerstoreprotobooksupports) -## Metadata Book +#### Metadata Book **Not Yet Implemented** diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 1ec7aaaf98..c3c3ed25fa 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -7,6 +7,7 @@ log.error = debug('libp2p:peer-store:address-book:error') const multiaddr = require('multiaddr') const PeerId = require('peer-id') +const PeerInfo = require('peer-info') const Book = require('./book') @@ -52,15 +53,17 @@ class AddressBook extends Book { * @param {PeerId} peerId * @param {Array|Multiaddr} addresses * @param {Object} [options] - * @param {boolean} [options.replace = true] wether addresses received replace stored ones or a unique union is performed. + * @param {boolean} [options.replace = true] whether addresses received replace stored ones or a unique union is performed. * @returns {Array} */ set (peerId, addresses, { replace = true } = {}) { if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } if (!addresses) { + log.error('addresses must be provided to store data') throw errcode(new Error('addresses must be provided'), ERR_INVALID_PARAMETERS) } @@ -72,6 +75,7 @@ class AddressBook extends Book { const multiaddrInfos = [] addresses.forEach((addr) => { if (!multiaddr.isMultiaddr(addr)) { + log.error(`multiaddr ${addr} must be an instance of multiaddr`) throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) } @@ -88,7 +92,7 @@ class AddressBook extends Book { } /** - * Replace known addresses to a provided peer. + * Replace known addresses of a provided peer. * If the peer is not known, it is set with the given addresses. * @param {PeerId} peerId * @param {Array} multiaddrInfos @@ -98,22 +102,39 @@ class AddressBook extends Book { const id = peerId.toString() const rec = this.data.get(id) - // Already know the peer + // Not replace multiaddrs + if (!multiaddrInfos.length) { + return rec ? [...rec] : [] + } + + // Already knows the peer if (rec && rec.length === multiaddrInfos.length) { const intersection = rec.filter((mi) => multiaddrInfos.some((newMi) => mi.multiaddr === newMi.multiaddr)) - // New addresses equal the old ones? + // Are new addresses equal to the old ones? // If yes, no changes needed! if (intersection.length === rec.length) { + log(`the addresses provided to store are equal to the already stored for ${id}`) return [...multiaddrInfos] } } this.data.set(id, multiaddrInfos) + log(`stored provided multiaddrs for ${id}`) + + // TODO: Remove peerInfo and its usage on peer-info deprecate + const peerInfo = new PeerInfo(peerId) + multiaddrInfos.forEach((mi) => peerInfo.multiaddrs.add(mi.multiaddr)) + + // Notify the existance of a new peer + if (!rec) { + // this._ps.emit('peer', peerId) + this._ps.emit('peer', peerInfo) + } - this._ps.emit('peer', peerId) this._ps.emit('change:multiaddrs', { peerId, + peerInfo, multiaddrs: multiaddrInfos.map((mi) => mi.multiaddr) }) @@ -129,31 +150,40 @@ class AddressBook extends Book { */ _add (peerId, multiaddrInfos) { const id = peerId.toString() - const rec = this.data.get(id) || [] + const rec = this.data.get(id) - // Add recorded uniquely to the new array - rec.forEach((mi) => { + // Add recorded uniquely to the new array (Union) + rec && rec.forEach((mi) => { if (!multiaddrInfos.find(r => r.multiaddr === mi.multiaddr)) { multiaddrInfos.push(mi) } }) - // If the recorded length is equal to the new after the uniquely union + // If the recorded length is equal to the new after the unique union // The content is the same, no need to update. - if (rec.length === multiaddrInfos.length) { + if (rec && rec.length === multiaddrInfos.length) { + log(`the addresses provided to store are already stored for ${id}`) return [...multiaddrInfos] } this.data.set(id, multiaddrInfos) + + log(`added provided multiaddrs for ${id}`) + + // TODO: Remove peerInfo and its usage on peer-info deprecate + const peerInfo = new PeerInfo(peerId) + multiaddrInfos.forEach((mi) => peerInfo.multiaddrs.add(mi.multiaddr)) + this._ps.emit('change:multiaddrs', { peerId, + peerInfo, multiaddrs: multiaddrInfos.map((mi) => mi.multiaddr) }) // Notify the existance of a new peer - // TODO: do we need this? if (!rec) { - this._ps.emit('peer', peerId) + // this._ps.emit('peer', peerId) + this._ps.emit('peer', peerInfo) } return [...multiaddrInfos] @@ -179,7 +209,9 @@ class AddressBook extends Book { return record.map((multiaddrInfo) => { const addr = multiaddrInfo.multiaddr - if (addr.getPeerId()) return addr + const idString = addr.getPeerId() + if (idString && idString === peerId.toB58String()) return addr + return addr.encapsulate(`/p2p/${peerId.toB58String()}`) }) } diff --git a/src/peer-store/book.js b/src/peer-store/book.js index af4725ed1c..02168d2386 100644 --- a/src/peer-store/book.js +++ b/src/peer-store/book.js @@ -2,6 +2,7 @@ const errcode = require('err-code') const PeerId = require('peer-id') +const PeerInfo = require('peer-info') const { ERR_INVALID_PARAMETERS @@ -63,8 +64,12 @@ class Book { return false } + // TODO: Remove peerInfo and its usage on peer-info deprecate + const peerInfo = new PeerInfo(peerId) + this.eventEmitter.emit(this.eventName, { peerId, + peerInfo, [this.eventProperty]: [] }) diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 6e61b73923..597b69e178 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -9,251 +9,212 @@ const { EventEmitter } = require('events') const PeerId = require('peer-id') const PeerInfo = require('peer-info') + +const AddressBook = require('./address-book') +const ProtoBook = require('./proto-book') + const { ERR_INVALID_PARAMETERS } = require('../errors') /** - * 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 + * Responsible for managing known peers, as well as their addresses, protocols and metadata. + * @fires PeerStore#peer Emitted when a new peer is added. + * @fires PeerStore#change:protocols Emitted when a known peer supports a different set of protocols. + * @fires PeerStore#change:multiaddrs Emitted when a known peer has a different set of multiaddrs. */ class PeerStore extends EventEmitter { + /** + * PeerInfo object + * @typedef {Object} peerInfo + * @property {Array} multiaddrsInfos peer's information of the multiaddrs. + * @property {Array} protocols peer's supported protocols. + */ + constructor () { super() /** - * Map of peers - * - * @type {Map} + * AddressBook containing a map of peerIdStr to multiaddrsInfo */ - this.peers = new Map() + this.addressBook = new AddressBook(this) /** - * Map known peers to their known multiaddrs. - * @type {Map} + * ProtoBook containing a map of peerIdStr to supported protocols. */ - this.addressBook = new Map() - - /** - * Map known peers to their known supported protocols. - * @type {Map} - */ - this.protoBook = new Map() + this.protoBook = new ProtoBook(this) } + // TODO: Temporary adapter for modules using PeerStore + // This should be removed under a breaking change /** - * Stores the peerInfo of a new peer. - * If already exist, its info is updated. If `silent` is set to - * true, no 'peer' event will be emitted. This can be useful if you - * are already in the process of dialing the peer. The peer is technically - * known, but may not have been added to the PeerStore yet. + * Stores the peerInfo of a new peer on each book. * @param {PeerInfo} peerInfo * @param {object} [options] - * @param {boolean} [options.silent] (Default=false) - * @return {PeerInfo} - */ - put (peerInfo, options = { silent: false }) { - if (!PeerInfo.isPeerInfo(peerInfo)) { - throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) - } - - let peer - // Already know the peer? - if (this.has(peerInfo.id)) { - peer = this.update(peerInfo) - } else { - peer = this.add(peerInfo) - - // Emit the peer if silent = false - !options.silent && this.emit('peer', peerInfo) - } - return peer - } - - /** - * Add a new peer to the store. - * @param {PeerInfo} peerInfo + * @param {boolean} [options.replace = true] * @return {PeerInfo} */ - add (peerInfo) { - if (!PeerInfo.isPeerInfo(peerInfo)) { - throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) - } + put (peerInfo, options) { + const multiaddrs = peerInfo.multiaddrs.toArray() + const protocols = Array.from(peerInfo.protocols || new Set()) - // 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) - return peerProxy - } + this.addressBook.set(peerInfo.id, multiaddrs, options) + this.protoBook.set(peerInfo.id, protocols, options) - /** - * Updates an already known peer. - * @param {PeerInfo} peerInfo - * @return {PeerInfo} - */ - update (peerInfo) { - if (!PeerInfo.isPeerInfo(peerInfo)) { - throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) - } - - 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', { - peerInfo: recorded, - 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) - } + const peer = this.find(peerInfo.id) + const pInfo = new PeerInfo(peerInfo.id) - this.emit('change:protocols', { - peerInfo: recorded, - protocols: Array.from(recorded.protocols) - }) + if (!peer) { + return pInfo } - // Add the public key if missing - if (!recorded.id.pubKey && peerInfo.id.pubKey) { - recorded.id.pubKey = peerInfo.id.pubKey - } + peer.protocols.forEach((p) => pInfo.protocols.add(p)) + peer.multiaddrInfos.forEach((mi) => pInfo.multiaddrs.add(mi.multiaddr)) - return recorded + return pInfo } + // TODO: Temporary adapter for modules using PeerStore + // This should be removed under a breaking change /** - * Get the info to the given id. - * @param {PeerId|string} peerId b58str id + * Get the info of the given id. + * @param {peerId} peerId * @returns {PeerInfo} */ get (peerId) { - // TODO: deprecate this and just accept `PeerId` instances - if (PeerId.isPeerId(peerId)) { - peerId = peerId.toB58String() - } + const peer = this.find(peerId) + + const pInfo = new PeerInfo(peerId) + peer.protocols.forEach((p) => pInfo.protocols.add(p)) + peer.multiaddrInfos.forEach((mi) => pInfo.multiaddrs.add(mi.multiaddr)) - return this.peers.get(peerId) + return pInfo } + // TODO: Temporary adapter for modules using PeerStore + // This should be removed under a breaking change /** * Has the info to the given id. - * @param {PeerId|string} peerId b58str id + * @param {PeerId} peerId * @returns {boolean} */ has (peerId) { - // TODO: deprecate this and just accept `PeerId` instances - if (PeerId.isPeerId(peerId)) { - peerId = peerId.toB58String() - } - - return this.peers.has(peerId) + return Boolean(this.find(peerId)) } + // TODO: Temporary adapter for modules using PeerStore + // This should be removed under a breaking change /** - * Removes the Peer with the matching `peerId` from the PeerStore - * @param {PeerId|string} peerId b58str id + * Removes the peer provided. + * @param {PeerId} peerId * @returns {boolean} true if found and removed */ remove (peerId) { - // TODO: deprecate this and just accept `PeerId` instances - if (PeerId.isPeerId(peerId)) { - peerId = peerId.toB58String() - } - - return this.peers.delete(peerId) + return this.delete(peerId) } + // TODO: Temporary adapter for modules using PeerStore + // This should be removed under a breaking change /** * Completely replaces the existing peers metadata with the given `peerInfo` * @param {PeerInfo} peerInfo * @returns {void} */ replace (peerInfo) { - if (!PeerInfo.isPeerInfo(peerInfo)) { - throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) + this.put(peerInfo) + } + + // TODO: Temporary adapter for modules using PeerStore + // This should be removed under a breaking change + /** + * Returns the known multiaddrs for a given `PeerInfo`. All returned multiaddrs + * will include the encapsulated `PeerId` of the peer. + * @param {PeerInfo} peerInfo + * @returns {Array} + */ + multiaddrsForPeer (peerInfo) { + return this.addressBook.getMultiaddrsForPeer(peerInfo.id) + } + + /** + * Get all the stored information of every peer. + * @returns {Map} + */ + get peers () { + const peerInfos = new Map() + + // AddressBook + for (const [idStr, multiaddrInfos] of this.addressBook.data.entries()) { + // TODO: Remove peerInfo and its usage on peer-info deprecate + const peerInfo = new PeerInfo(PeerId.createFromCID(idStr)) + + multiaddrInfos.forEach((mi) => peerInfo.multiaddrs.add((mi.multiaddr))) + + const protocols = this.protoBook.data.get(idStr) || [] + protocols.forEach((p) => peerInfo.protocols.add(p)) + + peerInfos.set(idStr, peerInfo) + // TODO + // peerInfos.set(idStr, { + // id: PeerId.createFromCID(idStr), + // multiaddrInfos, + // protocols: this.protoBook.data.get(idStr) || [] + // }) } - this.remove(peerInfo.id.toB58String()) - this.add(peerInfo) + // ProtoBook + for (const [idStr, protocols] of this.protoBook.data.entries()) { + // TODO: Remove peerInfo and its usage on peer-info deprecate + const peerInfo = peerInfos.get(idStr) + + if (!peerInfo) { + const peerInfo = new PeerInfo(PeerId.createFromCID(idStr)) + + protocols.forEach((p) => peerInfo.protocols.add(p)) + peerInfos.set(idStr, peerInfo) + // peerInfos.set(idStr, { + // id: PeerId.createFromCID(idStr), + // multiaddrInfos: [], + // protocols: protocols + // }) + } + } - // This should be cleaned up in PeerStore v2 - this.emit('change:multiaddrs', { - peerInfo, - multiaddrs: peerInfo.multiaddrs.toArray() - }) + return peerInfos + } - this.emit('change:protocols', { - peerInfo, - protocols: Array.from(peerInfo.protocols) - }) + /** + * Delete the information of the given peer in every book. + * @param {PeerId} peerId + * @returns {boolean} true if found and removed + */ + delete (peerId) { + const addressesDeleted = this.addressBook.delete(peerId) + const protocolsDeleted = this.protoBook.delete(peerId) + return addressesDeleted || protocolsDeleted } /** - * Returns the known multiaddrs for a given `PeerInfo`. All returned multiaddrs - * will include the encapsulated `PeerId` of the peer. - * @param {PeerInfo} peer - * @returns {Array} + * Find the stored information of a given peer. + * @param {PeerId} peerId + * @returns {peerInfo} */ - multiaddrsForPeer (peer) { - return this.put(peer, true).multiaddrs.toArray().map(addr => { - const idString = addr.getPeerId() - if (idString && idString === peer.id.toB58String()) return addr - return addr.encapsulate(`/p2p/${peer.id.toB58String()}`) - }) + find (peerId) { + if (!PeerId.isPeerId(peerId)) { + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + const multiaddrInfos = this.addressBook.get(peerId) + const protocols = this.protoBook.get(peerId) + + if (!multiaddrInfos && !protocols) { + return undefined + } + + return { + multiaddrInfos: multiaddrInfos || [], + protocols: protocols || [] + } } } diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index 7c859ce858..be7762e4b0 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -6,6 +6,7 @@ const log = debug('libp2p:peer-store:proto-book') log.error = debug('libp2p:peer-store:proto-book:error') const PeerId = require('peer-id') +const PeerInfo = require('peer-info') const Book = require('./book') @@ -14,7 +15,7 @@ const { } = require('../errors') /** - * The ProtoBook is responsible for keeping the known suppoerted + * The ProtoBook is responsible for keeping the known supported * protocols of a peer. * @fires ProtoBook#change:protocols */ @@ -46,15 +47,17 @@ class ProtoBook extends Book { * @param {PeerId} peerId * @param {Array|string} protocols * @param {Object} [options] - * @param {boolean} [options.replace = true] wether protocols received replace stored ones or a unique union is performed. + * @param {boolean} [options.replace = true] whether protocols received replace stored ones or a unique union is performed. * @returns {Array} */ set (peerId, protocols, { replace = true } = {}) { if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } if (!protocols) { + log.error('protocols must be provided to store data') throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) } @@ -70,7 +73,7 @@ class ProtoBook extends Book { } /** - * Replace known protocols to a provided peer. + * Replace known protocols of a provided peer. * If the peer is not known, it is set with the given protocols. * @param {PeerId} peerId * @param {Array} protocols @@ -83,15 +86,23 @@ class ProtoBook extends Book { const isSetEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)) - // Already know the peer and the recorded protocols are the same? + // Already knows the peer and the recorded protocols are the same? // If yes, no changes needed! if (recSet && isSetEqual(recSet, newSet)) { + log(`the protocols provided to store are equal to the already stored for ${id}`) return protocols } this.data.set(id, newSet) + log(`stored provided protocols for ${id}`) + + // TODO: Remove peerInfo and its usage on peer-info deprecate + const peerInfo = new PeerInfo(peerId) + protocols.forEach((p) => peerInfo.protocols.add(p)) + this._ps.emit('change:protocols', { peerId, + peerInfo, protocols }) @@ -108,18 +119,26 @@ class ProtoBook extends Book { _add (peerId, protocols) { const id = peerId.toString() const recSet = this.data.get(id) || new Set() - const newSet = new Set([...recSet, ...protocols]) + const newSet = new Set([...recSet, ...protocols]) // Set Union // Any new protocol added? if (recSet.size === newSet.size) { + log(`the protocols provided to store are already stored for ${id}`) return protocols } protocols = [...newSet] this.data.set(id, newSet) + log(`added provided protocols for ${id}`) + + // TODO: Remove peerInfo and its usage on peer-info deprecate + const peerInfo = new PeerInfo(peerId) + protocols.forEach((p) => peerInfo.protocols.add(p)) + this._ps.emit('change:protocols', { peerId, + peerInfo, protocols }) diff --git a/src/registrar.js b/src/registrar.js index 43fa235d03..2aa6bcf85e 100644 --- a/src/registrar.js +++ b/src/registrar.js @@ -10,7 +10,6 @@ const { } = require('./errors') const Topology = require('libp2p-interfaces/src/topology') const { Connection } = require('libp2p-interfaces/src/connection') -const PeerInfo = require('peer-info') /** * Responsible for notifying registered protocols of events in the network. @@ -23,7 +22,6 @@ class Registrar { */ constructor ({ peerStore }) { // Used on topology to listen for protocol changes - // TODO: should we only provide the protobook? this.peerStore = peerStore /** @@ -76,9 +74,11 @@ class Registrar { * @returns {void} */ onConnect (peerInfo, conn) { - if (!PeerInfo.isPeerInfo(peerInfo)) { - throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) - } + // TODO: This is not a `peer-info` instance anymore, but an object with the data. + // This can be modified to `peer-id` though, once `peer-info` is deprecated. + // if (!PeerInfo.isPeerInfo(peerInfo)) { + // throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) + // } if (!Connection.isConnection(conn)) { throw errcode(new Error('conn must be an instance of interface-connection'), ERR_INVALID_PARAMETERS) @@ -103,9 +103,11 @@ class Registrar { * @returns {void} */ onDisconnect (peerInfo, connection, error) { - if (!PeerInfo.isPeerInfo(peerInfo)) { - throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) - } + // TODO: This is not a `peer-info` instance anymore, but an object with the data. + // This can be modified to `peer-id` though, once `peer-info` is deprecated. + // if (!PeerInfo.isPeerInfo(peerInfo)) { + // throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) + // } const id = peerInfo.id.toB58String() let storedConn = this.connections.get(id) @@ -128,9 +130,11 @@ class Registrar { * @returns {Connection} */ getConnection (peerInfo) { - if (!PeerInfo.isPeerInfo(peerInfo)) { - throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) - } + // TODO: This is not a `peer-info` instance anymore, but an object with the data. + // This can be modified to `peer-id` though, once `peer-info` is deprecated. + // if (!PeerInfo.isPeerInfo(peerInfo)) { + // throw errcode(new Error('peerInfo must be an instance of peer-info'), ERR_INVALID_PARAMETERS) + // } const connections = this.connections.get(peerInfo.id.toB58String()) // Return the first, open connection diff --git a/src/upgrader.js b/src/upgrader.js index 25cb6d3f11..ad2391971f 100644 --- a/src/upgrader.js +++ b/src/upgrader.js @@ -317,7 +317,7 @@ class Upgrader { * Attempts to encrypt the incoming `connection` with the provided `cryptos`. * @private * @async - * @param {PeerId} localPeer The initiators PeerInfo + * @param {PeerId} localPeer The initiators PeerId * @param {*} connection * @param {Map} cryptos * @returns {CryptoResult} An encrypted connection, remote peer `PeerId` and the protocol of the `Crypto` used @@ -346,7 +346,7 @@ class Upgrader { * The first `Crypto` module to succeed will be used * @private * @async - * @param {PeerId} localPeer The initiators PeerInfo + * @param {PeerId} localPeer The initiators PeerId * @param {*} connection * @param {PeerId} remotePeerId * @param {Map} cryptos diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js index 8520b28548..0f91ebb23b 100644 --- a/test/content-routing/dht/operation.node.js +++ b/test/content-routing/dht/operation.node.js @@ -43,7 +43,8 @@ describe('DHT subsystem operates correctly', () => { remoteLibp2p.start() ]) - remAddr = libp2p.peerStore.multiaddrsForPeer(remotePeerInfo)[0] + libp2p.peerStore.addressBook.set(remotePeerInfo.id, remoteListenAddr) + remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerInfo.id)[0] }) afterEach(() => Promise.all([ @@ -67,7 +68,6 @@ describe('DHT subsystem operates correctly', () => { const value = Buffer.from('world') await libp2p.dialProtocol(remAddr, subsystemMulticodecs) - await Promise.all([ pWaitFor(() => libp2p._dht.routingTable.size === 1), pWaitFor(() => remoteLibp2p._dht.routingTable.size === 1) @@ -98,7 +98,8 @@ describe('DHT subsystem operates correctly', () => { await libp2p.start() await remoteLibp2p.start() - remAddr = libp2p.peerStore.multiaddrsForPeer(remotePeerInfo)[0] + libp2p.peerStore.addressBook.set(remotePeerInfo.id, remoteListenAddr) + remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerInfo.id)[0] }) afterEach(() => Promise.all([ diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index c846e8ad68..c87d8814f9 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -99,7 +99,10 @@ describe('Dialing (direct, TCP)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore: { - multiaddrsForPeer: () => [remoteAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [remoteAddr] + } } }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -131,7 +134,10 @@ describe('Dialing (direct, TCP)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore: { - multiaddrsForPeer: () => [unsupportedAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [unsupportedAddr] + } } }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -172,7 +178,10 @@ describe('Dialing (direct, TCP)', () => { transportManager: localTM, concurrency: 2, peerStore: { - multiaddrsForPeer: () => addrs + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => addrs + } } }) diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index f6c716cbd1..6651b3de61 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -87,7 +87,10 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore: { - multiaddrsForPeer: () => [remoteAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [remoteAddr] + } } }) @@ -100,7 +103,10 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore: { - multiaddrsForPeer: () => [remoteAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [remoteAddr] + } } }) @@ -121,7 +127,10 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore: { - multiaddrsForPeer: () => [remoteAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [remoteAddr] + } } }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -135,7 +144,10 @@ describe('Dialing (direct, WebSockets)', () => { const dialer = new Dialer({ transportManager: localTM, peerStore: { - multiaddrsForPeer: () => [unsupportedAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [unsupportedAddr] + } } }) const peerId = await PeerId.createFromJSON(Peers[0]) @@ -150,7 +162,10 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, timeout: 50, peerStore: { - multiaddrsForPeer: () => [remoteAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [remoteAddr] + } } }) sinon.stub(localTM, 'dial').callsFake(async (addr, options) => { @@ -172,7 +187,10 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, concurrency: 2, peerStore: { - multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] + } } }) @@ -208,7 +226,10 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, concurrency: 2, peerStore: { - multiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] + addressBook: { + set: () => {}, + getMultiaddrsForPeer: () => [remoteAddr, remoteAddr, remoteAddr] + } } }) @@ -316,7 +337,7 @@ describe('Dialing (direct, WebSockets)', () => { }) sinon.spy(libp2p.dialer, 'connectToPeer') - sinon.spy(libp2p.peerStore, 'put') + sinon.spy(libp2p.peerStore.addressBook, 'set') const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() @@ -325,7 +346,7 @@ describe('Dialing (direct, WebSockets)', () => { expect(protocol).to.equal('/echo/1.0.0') await connection.close() expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) - expect(libp2p.peerStore.put.callCount).to.be.at.least(1) + expect(libp2p.peerStore.addressBook.set.callCount).to.be.at.least(1) }) it('should run identify automatically after connecting', async () => { @@ -339,19 +360,22 @@ describe('Dialing (direct, WebSockets)', () => { }) sinon.spy(libp2p.identifyService, 'identify') - sinon.spy(libp2p.peerStore, 'replace') sinon.spy(libp2p.upgrader, 'onConnection') const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() + sinon.spy(libp2p.peerStore.addressBook, 'set') + sinon.spy(libp2p.peerStore.protoBook, 'set') + // Wait for onConnection to be called await pWaitFor(() => libp2p.upgrader.onConnection.callCount === 1) expect(libp2p.identifyService.identify.callCount).to.equal(1) await libp2p.identifyService.identify.firstCall.returnValue - expect(libp2p.peerStore.replace.callCount).to.equal(1) + expect(libp2p.peerStore.addressBook.set.callCount).to.equal(1) + expect(libp2p.peerStore.protoBook.set.callCount).to.equal(1) }) it('should be able to use hangup to close connections', async () => { diff --git a/test/dialing/relay.node.js b/test/dialing/relay.node.js index 8dde1c32e5..250e9bf092 100644 --- a/test/dialing/relay.node.js +++ b/test/dialing/relay.node.js @@ -11,6 +11,8 @@ const multiaddr = require('multiaddr') const { collect } = require('streaming-iterables') const pipe = require('it-pipe') const AggregateError = require('aggregate-error') +const PeerId = require('peer-id') + const { createPeerInfo } = require('../utils/creators/peer') const baseOptions = require('../utils/base-options') const Libp2p = require('../../src') @@ -51,8 +53,9 @@ describe('Dialing (via relay, TCP)', () => { return Promise.all([srcLibp2p, relayLibp2p, dstLibp2p].map(async libp2p => { await libp2p.stop() // Clear the peer stores - for (const peerId of libp2p.peerStore.peers.keys()) { - libp2p.peerStore.remove(peerId) + for (const peerIdStr of libp2p.peerStore.peers.keys()) { + const peerId = PeerId.createFromCID(peerIdStr) + libp2p.peerStore.delete(peerId) } })) }) diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index ca32e023f2..e585233f73 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -48,7 +48,12 @@ describe('Identify', () => { protocols, registrar: { peerStore: { - replace: () => {} + addressBook: { + set: () => { } + }, + protoBook: { + set: () => { } + } } } }) @@ -64,7 +69,8 @@ describe('Identify', () => { const [local, remote] = duplexPair() sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY }) - sinon.spy(localIdentify.registrar.peerStore, 'replace') + sinon.spy(localIdentify.registrar.peerStore.addressBook, 'set') + sinon.spy(localIdentify.registrar.peerStore.protoBook, 'set') // Run identify await Promise.all([ @@ -76,9 +82,10 @@ describe('Identify', () => { }) ]) - expect(localIdentify.registrar.peerStore.replace.callCount).to.equal(1) + expect(localIdentify.registrar.peerStore.addressBook.set.callCount).to.equal(1) + expect(localIdentify.registrar.peerStore.protoBook.set.callCount).to.equal(1) // Validate the remote peer gets updated in the peer store - const call = localIdentify.registrar.peerStore.replace.firstCall + const call = localIdentify.registrar.peerStore.addressBook.set.firstCall expect(call.args[0].id.bytes).to.equal(remotePeer.id.bytes) }) @@ -88,7 +95,12 @@ describe('Identify', () => { protocols, registrar: { peerStore: { - replace: () => {} + addressBook: { + set: () => { } + }, + protoBook: { + set: () => { } + } } } }) @@ -134,7 +146,12 @@ describe('Identify', () => { peerInfo: remotePeer, registrar: { peerStore: { - replace: () => {} + addressBook: { + set: () => {} + }, + protoBook: { + set: () => { } + } } } }) @@ -152,9 +169,8 @@ describe('Identify', () => { const [local, remote] = duplexPair() sinon.stub(localConnectionMock, 'newStream').returns({ stream: local, protocol: multicodecs.IDENTIFY_PUSH }) - sinon.spy(IdentifyService, 'updatePeerAddresses') - sinon.spy(IdentifyService, 'updatePeerProtocols') - sinon.spy(remoteIdentify.registrar.peerStore, 'replace') + sinon.spy(remoteIdentify.registrar.peerStore.addressBook, 'set') + sinon.spy(remoteIdentify.registrar.peerStore.protoBook, 'set') // Run identify await Promise.all([ @@ -166,14 +182,14 @@ describe('Identify', () => { }) ]) - expect(IdentifyService.updatePeerAddresses.callCount).to.equal(1) - expect(IdentifyService.updatePeerProtocols.callCount).to.equal(1) - - expect(remoteIdentify.registrar.peerStore.replace.callCount).to.equal(1) - const [peerInfo] = remoteIdentify.registrar.peerStore.replace.firstCall.args - expect(peerInfo.id.bytes).to.eql(localPeer.id.bytes) - expect(peerInfo.multiaddrs.toArray()).to.eql([listeningAddr]) - expect(peerInfo.protocols).to.eql(localProtocols) + expect(remoteIdentify.registrar.peerStore.addressBook.set.callCount).to.equal(1) + expect(remoteIdentify.registrar.peerStore.protoBook.set.callCount).to.equal(1) + const [peerId, multiaddrs] = remoteIdentify.registrar.peerStore.addressBook.set.firstCall.args + expect(peerId.bytes).to.eql(localPeer.id.bytes) + expect(multiaddrs).to.eql([listeningAddr]) + const [peerId2, protocols] = remoteIdentify.registrar.peerStore.protoBook.set.firstCall.args + expect(peerId2.bytes).to.eql(localPeer.id.bytes) + expect(protocols).to.eql(Array.from(localProtocols)) }) }) @@ -204,13 +220,14 @@ describe('Identify', () => { }) sinon.spy(libp2p.identifyService, 'identify') - const peerStoreSpy = sinon.spy(libp2p.peerStore, 'replace') + const peerStoreSpy = sinon.spy(libp2p.peerStore.addressBook, 'set') const connection = await libp2p.dialer.connectToPeer(remoteAddr) expect(connection).to.exist() // Wait for peer store to be updated - await pWaitFor(() => peerStoreSpy.callCount === 1) + // Dialer._createDialTarget (add), Identify (replace) + await pWaitFor(() => peerStoreSpy.callCount === 2) expect(libp2p.identifyService.identify.callCount).to.equal(1) // The connection should have no open streams @@ -226,7 +243,6 @@ describe('Identify', () => { sinon.spy(libp2p.identifyService, 'identify') sinon.spy(libp2p.identifyService, 'push') - sinon.spy(libp2p.peerStore, 'update') const connection = await libp2p.dialer.connectToPeer(remoteAddr) expect(connection).to.exist() diff --git a/test/peer-discovery/index.spec.js b/test/peer-discovery/index.spec.js index 31fc38cdb1..f230ee190e 100644 --- a/test/peer-discovery/index.spec.js +++ b/test/peer-discovery/index.spec.js @@ -36,7 +36,9 @@ describe('peer discovery', () => { ...baseOptions, peerInfo }) - libp2p.peerStore.add(remotePeerInfo) + libp2p.peerStore.addressBook.set(remotePeerInfo.id, remotePeerInfo.multiaddrs.toArray()) + libp2p.peerStore.protoBook.set(remotePeerInfo.id, Array.from(remotePeerInfo.protocols)) + const deferred = defer() sinon.stub(libp2p.dialer, 'connectToPeer').callsFake((remotePeerInfo) => { expect(remotePeerInfo).to.equal(remotePeerInfo) @@ -47,7 +49,9 @@ describe('peer discovery', () => { libp2p.start() await deferred.promise - expect(spy.getCall(0).args).to.eql([remotePeerInfo]) + + expect(spy.calledOnce).to.eql(true) + expect(spy.getCall(0).args[0].id.toString()).to.eql(remotePeerInfo.id.toString()) }) it('should ignore self on discovery', async () => { diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 8b96638e79..140699eb05 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -8,12 +8,11 @@ const sinon = require('sinon') const { EventEmitter } = require('events') const pDefer = require('p-defer') - const multiaddr = require('multiaddr') -const PeerId = require('peer-id') const AddressBook = require('../../src/peer-store/address-book') +const peerUtils = require('../utils/creators/peer') const { ERR_INVALID_PARAMETERS } = require('../../src/errors') @@ -25,14 +24,15 @@ const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002') const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item) describe('addressBook', () => { + let peerId + + before(async () => { + [peerId] = await peerUtils.createPeerId() + }) + describe('addressBook.set', () => { - let peerId let ee, ab - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() ab = new AddressBook(ee) @@ -225,13 +225,8 @@ describe('addressBook', () => { }) describe('addressBook.get', () => { - let peerId let ee, ab - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() ab = new AddressBook(ee) @@ -261,13 +256,8 @@ describe('addressBook', () => { }) describe('addressBook.getMultiaddrsForPeer', () => { - let peerId let ee, ab - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() ab = new AddressBook(ee) @@ -298,13 +288,8 @@ describe('addressBook', () => { }) describe('addressBook.delete', () => { - let peerId let ee, ab - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() ab = new AddressBook(ee) diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index 91628432cc..c4be9598d5 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -4,185 +4,114 @@ const chai = require('chai') chai.use(require('dirty-chai')) const { expect } = chai -const sinon = require('sinon') - -const pDefer = require('p-defer') const PeerStore = require('../../src/peer-store') const multiaddr = require('multiaddr') -const peerUtils = require('../utils/creators/peer') - -const addr = multiaddr('/ip4/127.0.0.1/tcp/8000') - -describe('peer-store', () => { - let peerStore - beforeEach(() => { - peerStore = new PeerStore() - }) - - it('should add a new peer and emit it when it does not exist', async () => { - const defer = pDefer() +const peerUtils = require('../utils/creators/peer') - sinon.spy(peerStore, 'put') - sinon.spy(peerStore, 'add') - sinon.spy(peerStore, 'update') +const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000') +const addr2 = multiaddr('/ip4/127.0.0.1/tcp/8001') +const addr3 = multiaddr('/ip4/127.0.0.1/tcp/8002') +const addr4 = multiaddr('/ip4/127.0.0.1/tcp/8003') - const [peerInfo] = await peerUtils.createPeerInfo() +const proto1 = '/protocol1' +const proto2 = '/protocol2' +const proto3 = '/protocol3' - peerStore.on('peer', (peer) => { - expect(peer).to.exist() - defer.resolve() +describe('peer-store', () => { + let peerIds + before(async () => { + peerIds = await peerUtils.createPeerId({ + number: 3 }) - peerStore.put(peerInfo) - - // Wait for peerStore to emit the peer - await defer.promise - - expect(peerStore.put.callCount).to.equal(1) - expect(peerStore.add.callCount).to.equal(1) - expect(peerStore.update.callCount).to.equal(0) }) - it('should update peer when it is already in the store', async () => { - const [peerInfo] = await peerUtils.createPeerInfo() - - // Put the peer in the store - peerStore.put(peerInfo) - - sinon.spy(peerStore, 'add') - sinon.spy(peerStore, 'update') + describe('empty books', () => { + let peerStore - // When updating, peer event must not be emitted - peerStore.on('peer', () => { - throw new Error('should not emit twice') - }) - // If no multiaddrs change, the event should not be emitted - peerStore.on('change:multiaddrs', () => { - throw new Error('should not emit change:multiaddrs') - }) - // If no protocols change, the event should not be emitted - peerStore.on('change:protocols', () => { - throw new Error('should not emit change:protocols') + beforeEach(() => { + peerStore = new PeerStore() }) - peerStore.put(peerInfo) - - expect(peerStore.add.callCount).to.equal(0) - expect(peerStore.update.callCount).to.equal(1) - }) - - it('should emit the "change:multiaddrs" event when a peer has new multiaddrs', async () => { - const defer = pDefer() - const [createdPeerInfo] = await peerUtils.createPeerInfo() - - // Put the peer in the store - peerStore.put(createdPeerInfo) + it('has an empty map of peers', () => { + const peers = peerStore.peers + expect(peers.size).to.equal(0) + }) - // When updating, "change:multiaddrs" event must not be emitted - peerStore.on('change:multiaddrs', ({ peerInfo, multiaddrs }) => { - expect(peerInfo).to.exist() - expect(peerInfo.id).to.eql(createdPeerInfo.id) - expect(peerInfo.protocols).to.eql(createdPeerInfo.protocols) - expect(multiaddrs).to.exist() - expect(multiaddrs).to.eql(createdPeerInfo.multiaddrs.toArray()) - defer.resolve() + it('returns false on trying to delete a non existant peerId', () => { + const deleted = peerStore.delete(peerIds[0]) + expect(deleted).to.equal(false) }) - // If no protocols change, the event should not be emitted - peerStore.on('change:protocols', () => { - throw new Error('should not emit change:protocols') + + it('returns undefined on trying to find a non existant peerId', () => { + const peerInfo = peerStore.find(peerIds[0]) + expect(peerInfo).to.not.exist() }) + }) - createdPeerInfo.multiaddrs.add(addr) - peerStore.put(createdPeerInfo) + describe('previously populated books', () => { + let peerStore - // Wait for peerStore to emit the event - await defer.promise - }) + beforeEach(() => { + peerStore = new PeerStore() - it('should emit the "change:protocols" event when a peer has new protocols', async () => { - const defer = pDefer() - const [createdPeerInfo] = await peerUtils.createPeerInfo() + // Add peer0 with { addr1, addr2 } and { proto1 } + peerStore.addressBook.set(peerIds[0], [addr1, addr2]) + peerStore.protoBook.set(peerIds[0], proto1) - // Put the peer in the store - peerStore.put(createdPeerInfo) + // Add peer1 with { addr3 } and { proto2, proto3 } + peerStore.addressBook.set(peerIds[1], [addr3]) + peerStore.protoBook.set(peerIds[1], [proto2, proto3]) - // If no multiaddrs change, the event should not be emitted - peerStore.on('change:multiaddrs', () => { - throw new Error('should not emit change:multiaddrs') - }) - // When updating, "change:protocols" event must be emitted - peerStore.on('change:protocols', ({ peerInfo, protocols }) => { - expect(peerInfo).to.exist() - expect(peerInfo.id).to.eql(createdPeerInfo.id) - expect(peerInfo.multiaddrs).to.eql(createdPeerInfo.multiaddrs) - expect(protocols).to.exist() - expect(protocols).to.eql(Array.from(createdPeerInfo.protocols)) - defer.resolve() + // Add peer2 { addr4 } + peerStore.addressBook.set(peerIds[2], [addr4]) }) - createdPeerInfo.protocols.add('/new-protocol/1.0.0') - peerStore.put(createdPeerInfo) + it('has peers', () => { + const peers = peerStore.peers - // Wait for peerStore to emit the event - await defer.promise - }) + expect(peers.size).to.equal(3) + expect(Array.from(peers.keys())).to.have.members([ + peerIds[0].toString(), + peerIds[1].toString(), + peerIds[2].toString() + ]) + }) - it('should be able to retrieve a peer from store through its b58str id', async () => { - const [peerInfo] = await peerUtils.createPeerInfo() - const id = peerInfo.id + it('returns true on deleting a stored peer', () => { + const deleted = peerStore.delete(peerIds[0]) + expect(deleted).to.equal(true) - let retrievedPeer = peerStore.get(id) - expect(retrievedPeer).to.not.exist() + const peers = peerStore.peers + expect(peers.size).to.equal(2) + expect(Array.from(peers.keys())).to.not.have.members([peerIds[0].toString()]) + }) - // Put the peer in the store - peerStore.put(peerInfo) + it('returns true on deleting a stored peer which is only on one book', () => { + const deleted = peerStore.delete(peerIds[2]) + expect(deleted).to.equal(true) - retrievedPeer = peerStore.get(id) - expect(retrievedPeer).to.exist() - expect(retrievedPeer.id).to.equal(peerInfo.id) - expect(retrievedPeer.multiaddrs).to.eql(peerInfo.multiaddrs) - expect(retrievedPeer.protocols).to.eql(peerInfo.protocols) - }) - - it('should be able to remove a peer from store through its b58str id', async () => { - const [peerInfo] = await peerUtils.createPeerInfo() - const id = peerInfo.id + const peers = peerStore.peers + expect(peers.size).to.equal(2) + }) - let removed = peerStore.remove(id) - expect(removed).to.eql(false) + it('finds the stored information of a peer in all its books', () => { + const peerInfo = peerStore.find(peerIds[0]) + expect(peerInfo).to.exist() + expect(peerInfo.protocols).to.have.members([proto1]) - // Put the peer in the store - peerStore.put(peerInfo) - expect(peerStore.peers.size).to.equal(1) + const peerMultiaddrs = peerInfo.multiaddrInfos.map((mi) => mi.multiaddr) + expect(peerMultiaddrs).to.have.members([addr1, addr2]) + }) - removed = peerStore.remove(id) - expect(removed).to.eql(true) - expect(peerStore.peers.size).to.equal(0) - }) + it('finds the stored information of a peer that is not present in all its books', () => { + const peerInfo = peerStore.find(peerIds[2]) + expect(peerInfo).to.exist() + expect(peerInfo.protocols.length).to.eql(0) - it('should be able to get the multiaddrs for a peer', async () => { - const [peerInfo, relayInfo] = await peerUtils.createPeerInfo({ number: 2 }) - const id = peerInfo.id - const ma1 = multiaddr('/ip4/127.0.0.1/tcp/4001') - const ma2 = multiaddr('/ip4/127.0.0.1/tcp/4002/ws') - const ma3 = multiaddr(`/ip4/127.0.0.1/tcp/4003/ws/p2p/${relayInfo.id.toB58String()}/p2p-circuit`) - - peerInfo.multiaddrs.add(ma1) - peerInfo.multiaddrs.add(ma2) - peerInfo.multiaddrs.add(ma3) - - const multiaddrs = peerStore.multiaddrsForPeer(peerInfo) - const expectedAddrs = [ - ma1.encapsulate(`/p2p/${id.toB58String()}`), - ma2.encapsulate(`/p2p/${id.toB58String()}`), - ma3.encapsulate(`/p2p/${id.toB58String()}`) - ] - - expect(multiaddrs).to.eql(expectedAddrs) + const peerMultiaddrs = peerInfo.multiaddrInfos.map((mi) => mi.multiaddr) + expect(peerMultiaddrs).to.have.members([addr4]) + }) }) }) - -describe('peer-store on discovery', () => { - // TODO: implement with discovery -}) diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js index cb8d6bbc69..5c9fe5b983 100644 --- a/test/peer-store/proto-book.spec.js +++ b/test/peer-store/proto-book.spec.js @@ -9,10 +9,9 @@ const sinon = require('sinon') const { EventEmitter } = require('events') const pDefer = require('p-defer') -const PeerId = require('peer-id') - const ProtoBook = require('../../src/peer-store/proto-book') +const peerUtils = require('../utils/creators/peer') const { ERR_INVALID_PARAMETERS } = require('../../src/errors') @@ -20,14 +19,15 @@ const { const arraysAreEqual = (a, b) => a.length === b.length && a.sort().every((item, index) => b[index] === item) describe('protoBook', () => { + let peerId + + before(async () => { + [peerId] = await peerUtils.createPeerId() + }) + describe('protoBook.set', () => { - let peerId let ee, pb - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() pb = new ProtoBook(ee) @@ -209,13 +209,8 @@ describe('protoBook', () => { }) describe('protoBook.get', () => { - let peerId let ee, pb - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() pb = new ProtoBook(ee) @@ -244,13 +239,8 @@ describe('protoBook', () => { }) describe('protoBook.supports', () => { - let peerId let ee, pb - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() pb = new ProtoBook(ee) @@ -296,13 +286,8 @@ describe('protoBook', () => { }) describe('protoBook.delete', () => { - let peerId let ee, pb - before(async () => { - peerId = await PeerId.create() - }) - beforeEach(() => { ee = new EventEmitter() pb = new ProtoBook(ee) diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index 056c4b49ab..a8c17c935e 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -89,7 +89,9 @@ describe('registrar', () => { remotePeerInfo.protocols.add(multicodec) // Add connected peer to peerStore and registrar - peerStore.put(remotePeerInfo) + peerStore.addressBook.set(remotePeerInfo.id, remotePeerInfo.multiaddrs.toArray()) + peerStore.protoBook.set(remotePeerInfo.id, Array.from(remotePeerInfo.protocols)) + registrar.onConnect(remotePeerInfo, conn) expect(registrar.connections.size).to.eql(1) @@ -156,18 +158,23 @@ describe('registrar', () => { const peerInfo = await PeerInfo.create(conn.remotePeer) // Add connected peer to peerStore and registrar - peerStore.put(peerInfo) + peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray()) + peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols)) + registrar.onConnect(peerInfo, conn) // Add protocol to peer and update it peerInfo.protocols.add(multicodec) - peerStore.put(peerInfo) + peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray(), { replace: false }) + peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols), { replace: false }) await onConnectDefer.promise // Remove protocol to peer and update it peerInfo.protocols.delete(multicodec) - peerStore.replace(peerInfo) + + peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray()) + peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols)) await onDisconnectDefer.promise }) @@ -197,7 +204,8 @@ describe('registrar', () => { const id = peerInfo.id.toB58String() // Add connection to registrar - peerStore.put(peerInfo) + peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray()) + peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols)) registrar.onConnect(peerInfo, conn1) registrar.onConnect(peerInfo, conn2) From eb27b8db032e6b1002df4dde8c23e7111e7fbbf3 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Wed, 1 Apr 2020 11:12:34 +0200 Subject: [PATCH 5/8] chore: do not use peerstore.put on get-peer-info --- src/get-peer-info.js | 7 ++++++- src/peer-store/address-book.js | 4 ++-- test/dialing/direct.node.js | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/get-peer-info.js b/src/get-peer-info.js index 83135eebe4..122a8ddc6b 100644 --- a/src/get-peer-info.js +++ b/src/get-peer-info.js @@ -38,7 +38,12 @@ function getPeerInfo (peer, peerStore) { addr && peer.multiaddrs.add(addr) - return peerStore ? peerStore.put(peer, { replace: false }) : peer + if (peerStore) { + peerStore.addressBook.set(peer.id, peer.multiaddrs.toArray(), { replace: false }) + peerStore.protoBook.set(peer.id, Array.from(peer.protocols), { replace: false }) + } + + return peer } /** diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index c3c3ed25fa..c9a54bb493 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -109,7 +109,7 @@ class AddressBook extends Book { // Already knows the peer if (rec && rec.length === multiaddrInfos.length) { - const intersection = rec.filter((mi) => multiaddrInfos.some((newMi) => mi.multiaddr === newMi.multiaddr)) + const intersection = rec.filter((mi) => multiaddrInfos.some((newMi) => mi.multiaddr.equals(newMi.multiaddr))) // Are new addresses equal to the old ones? // If yes, no changes needed! @@ -154,7 +154,7 @@ class AddressBook extends Book { // Add recorded uniquely to the new array (Union) rec && rec.forEach((mi) => { - if (!multiaddrInfos.find(r => r.multiaddr === mi.multiaddr)) { + if (!multiaddrInfos.find(r => r.multiaddr.equals(mi.multiaddr))) { multiaddrInfos.push(mi) } }) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index c87d8814f9..dccc583f2d 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -123,7 +123,7 @@ describe('Dialing (direct, TCP)', () => { const peerId = await PeerId.createFromJSON(Peers[0]) const peerInfo = new PeerInfo(peerId) peerInfo.multiaddrs.add(remoteAddr) - peerStore.put(peerInfo) + peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray()) const connection = await dialer.connectToPeer(peerInfo) expect(connection).to.exist() From f938535478065f2868432077c11a5b4489a69bfc Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 2 Apr 2020 10:47:53 +0200 Subject: [PATCH 6/8] chore: apply suggestions from code review Co-Authored-By: Jacob Heun --- doc/API.md | 239 +++++++++++---------- src/dialer/index.js | 2 +- src/get-peer-info.js | 4 +- src/identify/index.js | 3 - src/index.js | 6 +- src/peer-store/README.md | 32 +-- src/peer-store/address-book.js | 114 +++++----- src/peer-store/book.js | 23 +- src/peer-store/proto-book.js | 94 +++----- test/content-routing/dht/operation.node.js | 4 +- test/dialing/direct.node.js | 6 +- test/dialing/direct.spec.js | 12 +- test/identify/index.spec.js | 5 +- test/peer-store/address-book.spec.js | 121 ++++++----- test/peer-store/peer-store.spec.js | 8 +- test/peer-store/proto-book.spec.js | 158 ++++++-------- test/registrar/registrar.spec.js | 4 +- 17 files changed, 397 insertions(+), 438 deletions(-) diff --git a/doc/API.md b/doc/API.md index 6051362324..4a95b2d866 100644 --- a/doc/API.md +++ b/doc/API.md @@ -17,16 +17,17 @@ * [`contentRouting.put`](#contentroutingput) * [`contentRouting.get`](#contentroutingget) * [`contentRouting.getMany`](#contentroutinggetmany) + * [`peerStore.addressBook.add`](#peerstoreaddressbookadd) * [`peerStore.addressBook.delete`](#peerstoreaddressbookdelete) * [`peerStore.addressBook.get`](#peerstoreaddressbookget) * [`peerStore.addressBook.getMultiaddrsForPeer`](#peerstoreaddressbookgetmultiaddrsforpeer) * [`peerStore.addressBook.set`](#peerstoreaddressbookset) + * [`peerStore.protoBook.add`](#peerstoreprotobookadd) * [`peerStore.protoBook.delete`](#peerstoreprotobookdelete) * [`peerStore.protoBook.get`](#peerstoreprotobookget) * [`peerStore.protoBook.set`](#peerstoreprotobookset) - * [`peerStore.protoBook.supports`](#peerstoreprotobooksupports) * [`peerStore.delete`](#peerstoredelete) - * [`peerStore.find`](#peerstorefind) + * [`peerStore.get`](#peerstoreget) * [`peerStore.peers`](#peerstorepeers) * [`pubsub.getSubscribers`](#pubsubgetsubscribers) * [`pubsub.getTopics`](#pubsubgettopics) @@ -55,13 +56,13 @@ Creates an instance of Libp2p. | Name | Type | Description | |------|------|-------------| -| options | `Object` | libp2p options | -| options.modules | `Array` | libp2p modules to use | -| [options.config] | `Object` | libp2p modules configuration and core configuration | -| [options.connectionManager] | `Object` | libp2p Connection Manager configuration | -| [options.datastore] | `Object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) | -| [options.dialer] | `Object` | libp2p Dialer configuration -| [options.metrics] | `Object` | libp2p Metrics configuration +| options | `object` | libp2p options | +| options.modules | `Array` | libp2p modules to use | +| [options.config] | `object` | libp2p modules configuration and core configuration | +| [options.connectionManager] | `object` | libp2p Connection Manager configuration | +| [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) | +| [options.dialer] | `object` | libp2p Dialer configuration +| [options.metrics] | `object` | libp2p Metrics configuration | [options.peerInfo] | [`PeerInfo`][peer-info] | peerInfo instance (it will be created if not provided) | For Libp2p configurations and modules details read the [Configuration Document](./CONFIGURATION.md). @@ -192,7 +193,7 @@ for (const [peerId, connections] of libp2p.connections) { | Name | Type | Description | |------|------|-------------| | peer | [`PeerInfo`][peer-info]\|[`PeerId`][peer-id]\|[`Multiaddr`][multiaddr]\|`string` | The peer to dial. If a [`Multiaddr`][multiaddr] or its string is provided, it **must** include the peer id | -| [options] | `Object` | dial options | +| [options] | `object` | dial options | | [options.signal] | [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | An `AbortSignal` instance obtained from an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) that can be used to abort the connection before it completes | #### Returns @@ -227,8 +228,8 @@ Dials to another peer in the network and selects a protocol to communicate with | Name | Type | Description | |------|------|-------------| | peer | [`PeerInfo`][peer-info]\|[`PeerId`][peer-id]\|[`Multiaddr`][multiaddr]\|`string` | The peer to dial. If a [`Multiaddr`][multiaddr] or its string is provided, it **must** include the peer id | -| protocols | `String|Array` | A list of protocols (or single protocol) to negotiate with. Protocols are attempted in order until a match is made. (e.g '/ipfs/bitswap/1.1.0') | -| [options] | `Object` | dial options | +| protocols | `string|Array` | A list of protocols (or single protocol) to negotiate with. Protocols are attempted in order until a match is made. (e.g '/ipfs/bitswap/1.1.0') | +| [options] | `object` | dial options | | [options.signal] | [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) | An `AbortSignal` instance obtained from an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) that can be used to abort the connection before it completes | #### Returns @@ -286,7 +287,7 @@ In the event of a new handler for the same protocol being added, the first one i | Name | Type | Description | |------|------|-------------| -| protocols | `Array|String` | protocols to register | +| protocols | `Array|string` | protocols to register | | handler | `function({ connection:*, stream:*, protocol:string })` | handler to call | @@ -311,7 +312,7 @@ Unregisters all handlers with the given protocols | Name | Type | Description | |------|------|-------------| -| protocols | `Array|String` | protocols to unregister | +| protocols | `Array|string` | protocols to unregister | #### Example @@ -356,7 +357,7 @@ Iterates over all peer routers in series to find the given peer. If the DHT is e | Name | Type | Description | |------|------|-------------| | peerId | [`PeerId`][peer-id] | ID of the peer to find | -| options | `Object` | operation options | +| options | `object` | operation options | | options.timeout | `number` | maximum time the query should run | #### Returns @@ -384,7 +385,7 @@ Once a content router succeeds, the iteration will stop. If the DHT is enabled, | Name | Type | Description | |------|------|-------------| | cid | [`CID`][cid] | cid to find | -| options | `Object` | operation options | +| options | `object` | operation options | | options.timeout | `number` | maximum time the query should run | | options.maxNumProviders | `number` | maximum number of providers to find | @@ -438,9 +439,9 @@ Writes a value to a key in the DHT. | Name | Type | Description | |------|------|-------------| -| key | `String` | key to add to the dht | +| key | `string` | key to add to the dht | | value | `Buffer` | value to add to the dht | -| [options] | `Object` | put options | +| [options] | `object` | put options | | [options.minPeers] | `number` | minimum number of peers required to successfully put (default: closestPeers.length) | #### Returns @@ -469,8 +470,8 @@ Queries the DHT for a value stored for a given key. | Name | Type | Description | |------|------|-------------| -| key | `String` | key to get from the dht | -| [options] | `Object` | get options | +| key | `string` | key to get from the dht | +| [options] | `object` | get options | | [options.timeout] | `number` | maximum time the query should run | #### Returns @@ -498,9 +499,9 @@ Queries the DHT for the n values stored for the given key (without sorting). | Name | Type | Description | |------|------|-------------| -| key | `String` | key to get from the dht | +| key | `string` | key to get from the dht | | nvals | `number` | number of values aimed | -| [options] | `Object` | get options | +| [options] | `object` | get options | | [options.timeout] | `number` | maximum time the query should run | #### Returns @@ -518,6 +519,31 @@ const key = '/key' const { from, val } = await libp2p.contentRouting.get(key) ``` +### peerStore.addressBook.Add + +Adds known `multiaddrs` of a given peer. If the peer is not known, it will be set with the provided multiaddrs. + +`peerStore.addressBook.add(peerId, multiaddrs)` + +#### Parameters + +| Name | Type | Description | +|------|------|-------------| +| peerId | [`PeerId`][peer-id] | peerId to set | +| multiaddrs | |`Array` | [`Multiaddrs`][multiaddr] to add | + +#### Returns + +| Type | Description | +|------|-------------| +| `Map` | Map of known peers' string identifier with their relevant information [`MultiaddrInfo`](multiaddr-info) | + +#### Example + +```js +peerStore.addressBook.add(peerId, multiaddr) +``` + ### peerStore.addressBook.delete Delete the provided peer from the book. @@ -528,7 +554,7 @@ Delete the provided peer from the book. | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to remove | +| peerId | [`PeerId`][peer-id] | peerId to remove | #### Returns @@ -541,14 +567,14 @@ Delete the provided peer from the book. ```js peerStore.addressBook.delete(peerId) // false -peerStore.addressBook.set(peerId, discoveredMultiaddr) +peerStore.addressBook.set(peerId, multiaddr) peerStore.addressBook.delete(peerId) // true ``` ### peerStore.addressBook.get -Get the known `multiaddrInfos` of a provided peer. +Get the known [`MultiaddrInfos`](multiaddr-info) of a provided peer. `peerStore.addressBook.get(peerId)` @@ -556,20 +582,20 @@ Get the known `multiaddrInfos` of a provided peer. | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to get | +| peerId | [`PeerId`][peer-id] | peerId to get | #### Returns | Type | Description | |------|-------------| -| `Array` | Array of peer's multiaddr with their relevant information | +| `Array` | Array of peer's multiaddr with their relevant information [`MultiaddrInfo`](multiaddr-info) | #### Example ```js peerStore.addressBook.get(peerId) // undefined -peerStore.addressBook.set(peerId, discoveredMultiaddr) +peerStore.addressBook.set(peerId, multiaddr) peerStore.addressBook.get(peerId) // [ // { @@ -585,7 +611,7 @@ peerStore.addressBook.get(peerId) ## peerStore.addressBook.getMultiaddrsForPeer -Get the known `multiaddr` of a provided peer. All returned multiaddrs will include the encapsulated `PeerId` of the peer. +Get the known `Multiaddr` of a provided peer. All returned multiaddrs will include the encapsulated `PeerId` of the peer. `peerStore.addressBook.getMultiaddrsForPeer(peerId)` @@ -593,20 +619,20 @@ Get the known `multiaddr` of a provided peer. All returned multiaddrs will inclu | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to get | +| peerId | [`PeerId`][peer-id] | peerId to get | #### Returns | Type | Description | |------|-------------| -| `Array` | Array of peer's multiaddr | +| `Array` | Array of peer's multiaddr | #### Example ```js peerStore.addressBook.getMultiaddrsForPeer(peerId) // undefined -peerStore.addressBook.set(peerId, discoveredMultiaddr) +peerStore.addressBook.set(peerId, multiaddr) peerStore.addressBook.getMultiaddrsForPeer(peerId) // [ // /ip4/140.10.2.1/tcp/8000/p2p/QmW8rAgaaA6sRydK1k6vonShQME47aDxaFidbtMevWs73t @@ -618,109 +644,91 @@ peerStore.addressBook.getMultiaddrsForPeer(peerId) Set known `multiaddrs` of a given peer. -`peerStore.addressBook.set(peerId, multiaddrs, options)` +`peerStore.addressBook.set(peerId, multiaddrs)` #### Parameters | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to set | -| multiaddrs | `multiaddr|Array` | multiaddrs to store | -| [options] | `object` | options to set | -| [options.replace] | `Object` | replace stored data (if exists) or unique union (default: true) | +| peerId | [`PeerId`][peer-id] | peerId to set | +| multiaddrs | |`Array` | [`Multiaddrs`][multiaddr] to store | #### Returns | Type | Description | |------|-------------| -| `Array` | Array of peer's multiaddr with their relevant information | +| `Map` | Map of known peers' string identifier with their relevant information [`MultiaddrInfo`](multiaddr-info) | #### Example ```js -peerStore.addressBook.set(peerId, discoveredMultiaddr) -// [ -// { -// multiaddr: /ip4/140.10.2.1/tcp/8000, -// ... -// }, -// { -// multiaddr: /ip4/140.10.2.1/ws/8001 -// ... -// }, -// ] +peerStore.addressBook.add(peerId, multiaddr) ``` -### peerStore.protoBook.delete +### peerStore.protoBook.add -Delete the provided peer from the book. +Add known `protocols` of a given peer. -`peerStore.protoBook.delete(peerId)` +`peerStore.protoBook.add(peerId, protocols)` #### Parameters | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to remove | +| peerId | [`PeerId`][peer-id] | peerId to set | +| protocols | `Array` | protocols to add | #### Returns | Type | Description | |------|-------------| -| `boolean` | true if found and removed | +| `Map` | Map of known peers' string identifier with their supported protocols | #### Example ```js -peerStore.protoBook.delete(peerId) -// false -peerStore.protoBook.set(peerId, supportedProtocols) -peerStore.protoBook.delete(peerId) -// true +peerStore.protoBook.add(peerId, protocols) ``` -### peerStore.protoBook.get +### peerStore.protoBook.delete -Get the known `protocols` of a provided peer. +Delete the provided peer from the book. -`peerStore.protoBook.get(peerId)` +`peerStore.protoBook.delete(peerId)` #### Parameters | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to get | +| peerId | [`PeerId`][peer-id] | peerId to remove | #### Returns | Type | Description | |------|-------------| -| `Array` | Array of peer's supported protocols | +| `boolean` | true if found and removed | #### Example ```js -peerStore.protoBook.get(peerId) -// undefined -peerStore.protoBook.set(peerId, supportedProtocols) -peerStore.protoBook.get(peerId) -// [ '/proto/1.0.0', '/proto/1.1.0' ] +peerStore.protoBook.delete(peerId) +// false +peerStore.protoBook.set(peerId, protocols) +peerStore.protoBook.delete(peerId) +// true ``` -### peerStore.protoBook.set +### peerStore.protoBook.get -Set known `protocols` of a given peer. +Get the known `protocols` of a provided peer. -`peerStore.protoBook.set(peerId, protocols, options)` +`peerStore.protoBook.get(peerId)` #### Parameters | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to set | -| protocols | `string|Array` | protocols to store | -| [options] | `object` | options to set | -| [options.replace] | `Object` | replace stored data (if exists) or unique union (default: true) | +| peerId | [`PeerId`][peer-id] | peerId to get | #### Returns @@ -731,42 +739,36 @@ Set known `protocols` of a given peer. #### Example ```js -peerStore.protoBook.set(peerId, supportedProtocols) +peerStore.protoBook.get(peerId) +// undefined +peerStore.protoBook.set(peerId, [ '/proto/1.0.0', '/proto/1.1.0' ]) +peerStore.protoBook.get(peerId) // [ '/proto/1.0.0', '/proto/1.1.0' ] ``` -### peerStore.protoBook.supports +### peerStore.protoBook.set -Verify if the provided peer supports the given `protocols`. +Set known `protocols` of a given peer. -`peerStore.protoBook.supports(peerId, protocols)` +`peerStore.protoBook.set(peerId, protocols)` #### Parameters | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to get | -| protocols | `string|Array` | protocols to verify | +| peerId | [`PeerId`][peer-id] | peerId to set | +| protocols | `Array` | protocols to store | #### Returns | Type | Description | |------|-------------| -| `boolean` | true if found and removed | +| `Map` | Map of known peers' string identifier with their supported protocols | #### Example ```js -const supportedProtocols = [ '/proto/1.0.0', '/proto/1.1.0' ] -peerStore.protoBook.supports(peerId, supportedProtocols) -// false -peerStore.protoBook.supports(peerId, supportedProtocols[0]) -// false -peerStore.protoBook.set(peerId, supportedProtocols) -peerStore.protoBook.supports(peerId, supportedProtocols) -// true -peerStore.protoBook.supports(peerId, supportedProtocols[0]) -// true +peerStore.protoBook.set(peerId, protocols) ``` ### peerStore.delete @@ -779,7 +781,7 @@ Delete the provided peer from every book. | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to remove | +| peerId | [`PeerId`][peer-id] | peerId to remove | #### Returns @@ -792,45 +794,47 @@ Delete the provided peer from every book. ```js peerStore.delete(peerId) // false -peerStore.addressBook.set(peerId, discoveredMultiaddrs) -peerStore.protoBook.set(peerId, supportedProtocols) +peerStore.addressBook.set(peerId, multiaddrs) +peerStore.protoBook.set(peerId, protocols) peerStore.delete(peerId) // true peerStore.delete(peerId2) // false -peerStore.addressBook.set(peerId2, discoveredMultiaddrs) +peerStore.addressBook.set(peerId2, multiaddrs) peerStore.delete(peerId2) // true ``` -### peerStore.find +### peerStore.get -Find the stored information of a given peer. +Get the stored information of a given peer. -`peerStore.find(peerId)` +`peerStore.get(peerId)` #### Parameters | Name | Type | Description | |------|------|-------------| -| peerId | `peerid` | peerId to find | +| peerId | [`PeerId`][peer-id] | peerId to get | #### Returns | Type | Description | |------|-------------| -| `peerInfo` | Peer information of the provided peer | +| [`PeerInfo`][peer-info] | Peer information of the provided peer | + +TODO: change when `peer-info` is deprecated to new pointer #### Example ```js -peerStore.find(peerId) +peerStore.get(peerId) // false -peerStore.addressBook.set(peerId, discoveredMultiaddrs) -peerStore.protoBook.set(peerId, supportedProtocols) -peerStore.find(peerId) +peerStore.addressBook.set(peerId, multiaddrs) +peerStore.protoBook.set(peerId, protocols) +peerStore.get(peerId) // { -// multiaddrInfos: [...], +// MultiaddrInfos: [...], // protocols: [...] // } ``` @@ -839,7 +843,7 @@ peerStore.find(peerId) Get all the stored information of every peer. -`peerStore.peers()` +`peerStore.peers` #### Returns @@ -847,12 +851,12 @@ Get all the stored information of every peer. |------|-------------| | `Map` | Peer information of every peer | -TODO: change when `peer-info` is deprecated (breaking change) +TODO: change when `peer-info` is deprecated to new pointer (breaking change) #### Example ```js -for (peer of peerStore.peers().values()) { +for (let [peerIdString, peerInfo] of peerStore.peers.entries()) { // peerInfo instance } ``` @@ -873,7 +877,7 @@ Gets a list of the peer-ids that are subscribed to one topic. | Type | Description | |------|-------------| -| `Array` | peer-id subscribed to the topic | +| `Array` | peer-id subscribed to the topic | #### Example @@ -891,7 +895,7 @@ Gets a list of topics the node is subscribed to. | Type | Description | |------|-------------| -| `Array` | topics the node is subscribed to | +| `Array` | topics the node is subscribed to | #### Example @@ -938,7 +942,7 @@ Subscribes the given handler to a pubsub topic. | Name | Type | Description | |------|------|-------------| | topic | `string` | topic to subscribe | -| handler | `function({ from: String, data: Buffer, seqno: Buffer, topicIDs: Array, signature: Buffer, key: Buffer })` | handler for new data on topic | +| handler | `function({ from: string, data: Buffer, seqno: Buffer, topicIDs: Array, signature: Buffer, key: Buffer })` | handler for new data on topic | #### Returns @@ -968,7 +972,7 @@ Unsubscribes the given handler from a pubsub topic. If no handler is provided, a | Name | Type | Description | |------|------|-------------| | topic | `string` | topic to unsubscribe | -| handler | `function()` | handler subscribed | +| handler | `function()` | handler subscribed | #### Returns @@ -1137,9 +1141,9 @@ This event will be triggered anytime we are disconnected from another peer, rega - `dataReceived`: The stringified value of total incoming data for this stat. - `dataSent`: The stringified value of total outgoing data for this stat. - `movingAverages`: The properties are dependent on the configuration of the moving averages interval. Defaults are listed here. - - `['60000']`: The calculated moving average at a 1 minute interval. - - `['300000']`: The calculated moving average at a 5 minute interval. - - `['900000']`: The calculated moving average at a 15 minute interval. + - `['60000']`: The calculated moving average at a 1 minute interval. + - `['300000']`: The calculated moving average at a 5 minute interval. + - `['900000']`: The calculated moving average at a 15 minute interval. - `snapshot`: A getter that returns a clone of the raw stats. - `dataReceived`: A [`BigNumber`](https://github.com/MikeMcl/bignumber.js/) of the amount of incoming data - `dataSent`: A [`BigNumber`](https://github.com/MikeMcl/bignumber.js/) of the amount of outgoing data @@ -1148,6 +1152,7 @@ This event will be triggered anytime we are disconnected from another peer, rega - `['300000']`: The [MovingAverage](https://www.npmjs.com/package/moving-averages) at a 5 minute interval. - `['900000']`: The [MovingAverage](https://www.npmjs.com/package/moving-averages) at a 15 minute interval. +[multiaddr-info]: https://github.com/libp2p/js-libp2p/tree/master/src/peer-store/address-book.js [cid]: https://github.com/multiformats/js-cid [connection]: https://github.com/libp2p/js-interfaces/tree/master/src/connection [multiaddr]: https://github.com/multiformats/js-multiaddr diff --git a/src/dialer/index.js b/src/dialer/index.js index 9d257cb5f3..de9a394394 100644 --- a/src/dialer/index.js +++ b/src/dialer/index.js @@ -111,7 +111,7 @@ class Dialer { } } - dialable.multiaddrs && this.peerStore.addressBook.set(dialable.id, Array.from(dialable.multiaddrs), { replace: false }) + dialable.multiaddrs && this.peerStore.addressBook.add(dialable.id, Array.from(dialable.multiaddrs)) const addrs = this.peerStore.addressBook.getMultiaddrsForPeer(dialable.id) return { diff --git a/src/get-peer-info.js b/src/get-peer-info.js index 122a8ddc6b..5b0748ea8e 100644 --- a/src/get-peer-info.js +++ b/src/get-peer-info.js @@ -39,8 +39,8 @@ function getPeerInfo (peer, peerStore) { addr && peer.multiaddrs.add(addr) if (peerStore) { - peerStore.addressBook.set(peer.id, peer.multiaddrs.toArray(), { replace: false }) - peerStore.protoBook.set(peer.id, Array.from(peer.protocols), { replace: false }) + peerStore.addressBook.add(peer.id, peer.multiaddrs.toArray()) + peerStore.protoBook.add(peer.id, Array.from(peer.protocols)) } return peer diff --git a/src/identify/index.js b/src/identify/index.js index 75a685948c..3fe06f4f5c 100644 --- a/src/identify/index.js +++ b/src/identify/index.js @@ -206,9 +206,6 @@ class IdentifyService { protocols: Array.from(this._protocols.keys()) }) - // TODO: should we add to peerStore.addressBook.set() here? - // We can have an inbound connection from an unkwown peer - pipe( [message], lp.encode(), diff --git a/src/index.js b/src/index.js index db597cae8b..a2028dccb4 100644 --- a/src/index.js +++ b/src/index.js @@ -292,7 +292,7 @@ class Libp2p extends EventEmitter { // TODO Inconsistency from: getDialable adds a set, while regular peerInfo uses a Multiaddr set // This should be handled on `peer-info` removal const multiaddrs = dialable.multiaddrs.toArray ? dialable.multiaddrs.toArray() : Array.from(dialable.multiaddrs) - this.peerStore.addressBook.set(dialable.id, multiaddrs, { replace: false }) + this.peerStore.addressBook.add(dialable.id, multiaddrs) connection = this.registrar.getConnection(dialable) } @@ -436,8 +436,8 @@ class Libp2p extends EventEmitter { } // TODO: once we deprecate peer-info, we should only set if we have data - this.peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray(), { replace: false }) - this.peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols), { replace: false }) + this.peerStore.addressBook.add(peerInfo.id, peerInfo.multiaddrs.toArray()) + this.peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols)) } /** diff --git a/src/peer-store/README.md b/src/peer-store/README.md index 835c8c7ea5..a17b26c79a 100644 --- a/src/peer-store/README.md +++ b/src/peer-store/README.md @@ -1,18 +1,16 @@ -# Peerstore +# PeerStore -Libp2p's Peerstore is responsible for keeping an updated register with the relevant information of the known peers. It should gather environment changes, be able to take decisions and notice interested parties of relevant changes. The Peerstore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. These book components have similar characteristics with the `Javascript Map` implementation. +Libp2p's PeerStore is responsible for keeping an updated register with the relevant information of the known peers. It should be the single source of truth for all peer data, where a subsystem can learn about peers' data and where someone can listen for updates. The PeerStore comprises four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. -The PeerStore manages the high level operations on its inner books. Moreover, the peerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter. - -(Future considerations: Peerstore should manage a job runner to trigger books runners for data trimming or computations) +The PeerStore manages the high level operations on its inner books. Moreover, the PeerStore should be responsible for notifying interested parties of relevant events, through its Event Emitter. ## Data gathering Several libp2p subsystems will perform operations, which will gather relevant information about peers. Some operations might not have this as an end goal, but can also gather important data. -In a libp2p node's life, it will discover peers through its discovery protocols. In a typical discovery protocol, addresses of the peer are discovered along with its peer id. Once this happens, the `PeerStore` should collect this information for future (or immediate) usage by other subsystems. When the information is stored, the `PeerStore` should inform interested parties of the peer discovered (`peer` event). +In a libp2p node's life, it will discover peers through its discovery protocols. In a typical discovery protocol, addresses of the peer are discovered along with its peer id. Once this happens, the PeerStore should collect this information for future (or immediate) usage by other subsystems. When the information is stored, the PeerStore should inform interested parties of the peer discovered (`peer` event). -Taking into account a different scenario, a peer might perform/receive a dial request to/from a unkwown peer. In such a scenario, the `PeerStore` must store the peer's multiaddr once a connection is established. +Taking into account a different scenario, a peer might perform/receive a dial request to/from a unkwown peer. In such a scenario, the PeerStore must store the peer's multiaddr once a connection is established. After a connection is established with a peer, the Identify protocol will run automatically. A stream is created and peers exchange their information (Multiaddrs, running protocols and their public key). Once this information is obtained, it should be added to the PeerStore. In this specific case, as we are speaking to the source of truth, we should ensure the PeerStore is prioritizing these records. If the recorded `multiaddrs` or `protocols` have changed, interested parties must be informed via the `change:multiaddrs` or `change:protocols` events respectively. @@ -22,11 +20,11 @@ In the background, the Identify Service is also waiting for protocol change noti While it is currently not supported in js-libp2p, future iterations may also support the [IdentifyDelta protocol](https://github.com/libp2p/specs/pull/176). -It is also possible to gather relevant information for peers from other protocols / subsystems. For instance, in `DHT` operations, nodes can exchange peer data as part of the `DHT` operation. In this case, we can learn additional information about a peer we already know. In this scenario the `PeerStore` should not replace the existing data it has, just add it. +It is also possible to gather relevant information for peers from other protocols / subsystems. For instance, in `DHT` operations, nodes can exchange peer data as part of the `DHT` operation. In this case, we can learn additional information about a peer we already know. In this scenario the PeerStore should not replace the existing data it has, just add it. ## Data Consumption -When the `PeerStore` data is updated, this information might be important for different parties. +When the PeerStore data is updated, this information might be important for different parties. Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to it. The same is true for pinging a peer. While the `AddressBook` is going to keep its data updated, it will also emit `change:multiaddrs` events so that subsystems/users interested in knowing these changes can be notifyied instead of pooling the `AddressBook`. @@ -34,7 +32,7 @@ Everytime a peer starts/stops supporting a protocol, libp2p subsystems or users ## PeerStore implementation -The Peerstore wraps four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. Moreover, it provides a high level API for those components, as well as data events. +The PeerStore wraps four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. Moreover, it provides a high level API for those components, as well as data events. ### API @@ -51,9 +49,9 @@ High level operations: Deletes the provided peer from every book. -- [`peerStore.find(peerId)`](../../doc/API.md#peerstorefind) +- [`peerStore.get(peerId)`](../../doc/API.md#peerstoreget) -Finds the stored information of a given peer. +Gets the stored information of a given peer. - [`peerStore.peers()`](../../doc/API.md#peerstorepeers) @@ -78,14 +76,13 @@ A `peerId.toString()` identifier mapping to a `multiaddrInfo` object, which shou ```js { multiaddr: , - validity: , - confidence: } ``` **Note:** except for multiaddr naming, the other properties are placeholders for now and might not be as described in the future milestones. - `addressBook.data` +- [`addressBook.add()`](../../doc/API.md#peerstoreaddressbookadd) - [`addressBook.delete()`](../../doc/API.md#peerstoreaddressbookdelete) - [`addressBook.get()`](../../doc/API.md#peerstoreaddressbookget) - [`addressBook.getMultiaddrsForPeer()`](../../doc/API.md#peerstoreaddressbookgetmultiaddrsforpeer) @@ -110,11 +107,16 @@ The `protoBook` holds the identifiers of the protocols supported by each peer. T A `peerId.toString()` identifier mapping to a `Set` of protocol identifier strings. - `protoBook.data` +- [`protoBook.add()`](../../doc/API.md#peerstoreprotobookadd) - [`protoBook.delete()`](../../doc/API.md#peerstoreprotobookdelete) - [`protoBook.get()`](../../doc/API.md#peerstoreprotobookget) - [`protoBook.set()`](../../doc/API.md#peerstoreprotobookset) -- [`protoBook.supports()`](../../doc/API.md#peerstoreprotobooksupports) #### Metadata Book **Not Yet Implemented** + +## Future Considerations + +- If multiaddr TTLs are added, the PeerStore may schedule jobs to delete all addresses that exceed the TTL to prevent AddressBook bloating +- Further API methods will probably need to be added in the context of multiaddr validity and confidence. diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index c9a54bb493..6a08277f41 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -22,10 +22,8 @@ const { class AddressBook extends Book { /** * MultiaddrInfo object - * @typedef {Object} multiaddrInfo + * @typedef {Object} MultiaddrInfo * @property {Multiaddr} multiaddr peer multiaddr. - * @property {number} validity NOT USED YET - * @property {number} confidence NOT USED YET */ /** @@ -33,78 +31,40 @@ class AddressBook extends Book { * @param {EventEmitter} peerStore */ constructor (peerStore) { - super(peerStore, 'change:multiaddrs', 'multiaddrs') /** * PeerStore Event emitter, used by the AddressBook to emit: * "peer" - emitted when a peer is discovered by the node. * "change:multiaddrs" - emitted when the known multiaddrs of a peer change. */ - this._ps = peerStore + super(peerStore, 'change:multiaddrs', 'multiaddrs') /** * Map known peers to their known multiaddrs. - * @type {Map} + * @type {Map>} */ this.data = new Map() } /** * Set known addresses of a provided peer. + * @override * @param {PeerId} peerId - * @param {Array|Multiaddr} addresses - * @param {Object} [options] - * @param {boolean} [options.replace = true] whether addresses received replace stored ones or a unique union is performed. - * @returns {Array} + * @param {Array} addresses + * @returns {Map>} */ - set (peerId, addresses, { replace = true } = {}) { + set (peerId, addresses) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } - if (!addresses) { - log.error('addresses must be provided to store data') - throw errcode(new Error('addresses must be provided'), ERR_INVALID_PARAMETERS) - } - - if (!Array.isArray(addresses)) { - addresses = [addresses] - } - - // create multiaddrInfo for each address - const multiaddrInfos = [] - addresses.forEach((addr) => { - if (!multiaddr.isMultiaddr(addr)) { - log.error(`multiaddr ${addr} must be an instance of multiaddr`) - throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) - } - - multiaddrInfos.push({ - multiaddr: addr - }) - }) - - if (replace) { - return this._replace(peerId, multiaddrInfos) - } - - return this._add(peerId, multiaddrInfos) - } - - /** - * Replace known addresses of a provided peer. - * If the peer is not known, it is set with the given addresses. - * @param {PeerId} peerId - * @param {Array} multiaddrInfos - * @returns {Array} - */ - _replace (peerId, multiaddrInfos) { - const id = peerId.toString() + const multiaddrInfos = this._toMultiaddrInfos(addresses) + const id = peerId.toB58String() const rec = this.data.get(id) // Not replace multiaddrs if (!multiaddrInfos.length) { - return rec ? [...rec] : [] + return this.data } // Already knows the peer @@ -115,7 +75,7 @@ class AddressBook extends Book { // If yes, no changes needed! if (intersection.length === rec.length) { log(`the addresses provided to store are equal to the already stored for ${id}`) - return [...multiaddrInfos] + return this.data } } @@ -138,18 +98,25 @@ class AddressBook extends Book { multiaddrs: multiaddrInfos.map((mi) => mi.multiaddr) }) - return [...multiaddrInfos] + return this.data } /** - * Add new known addresses to a provided peer. + * Add known addresses of a provided peer. * If the peer is not known, it is set with the given addresses. + * @override * @param {PeerId} peerId - * @param {Array} multiaddrInfos - * @returns {Array} + * @param {Array} addresses + * @returns {Map>} */ - _add (peerId, multiaddrInfos) { - const id = peerId.toString() + add (peerId, addresses) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + const multiaddrInfos = this._toMultiaddrInfos(addresses) + const id = peerId.toB58String() const rec = this.data.get(id) // Add recorded uniquely to the new array (Union) @@ -163,7 +130,7 @@ class AddressBook extends Book { // The content is the same, no need to update. if (rec && rec.length === multiaddrInfos.length) { log(`the addresses provided to store are already stored for ${id}`) - return [...multiaddrInfos] + return this.data } this.data.set(id, multiaddrInfos) @@ -186,7 +153,34 @@ class AddressBook extends Book { this._ps.emit('peer', peerInfo) } - return [...multiaddrInfos] + return this.data + } + + /** + * Transforms received multiaddrs into MultiaddrInfo. + * @param {Array} addresses + * @returns {Array} + */ + _toMultiaddrInfos (addresses) { + if (!addresses) { + log.error('addresses must be provided to store data') + throw errcode(new Error('addresses must be provided'), ERR_INVALID_PARAMETERS) + } + + // create MultiaddrInfo for each address + const multiaddrInfos = [] + addresses.forEach((addr) => { + if (!multiaddr.isMultiaddr(addr)) { + log.error(`multiaddr ${addr} must be an instance of multiaddr`) + throw errcode(new Error(`multiaddr ${addr} must be an instance of multiaddr`), ERR_INVALID_PARAMETERS) + } + + multiaddrInfos.push({ + multiaddr: addr + }) + }) + + return multiaddrInfos } /** @@ -200,7 +194,7 @@ class AddressBook extends Book { throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } - const record = this.data.get(peerId.toString()) + const record = this.data.get(peerId.toB58String()) if (!record) { return undefined diff --git a/src/peer-store/book.js b/src/peer-store/book.js index 02168d2386..32456b7c13 100644 --- a/src/peer-store/book.js +++ b/src/peer-store/book.js @@ -12,8 +12,8 @@ const { * The Book is the skeleton for the PeerStore books. */ class Book { - constructor (eventEmitter, eventName, eventProperty) { - this.eventEmitter = eventEmitter + constructor (peerStore, eventName, eventProperty) { + this._ps = peerStore this.eventName = eventName this.eventProperty = eventProperty @@ -28,10 +28,17 @@ class Book { * Set known data of a provided peer. * @param {PeerId} peerId * @param {Array|Data} data - * @param {Object} [options] - * @param {boolean} [options.replace = true] wether data received replace stored the one or a unique union is performed. */ - set (peerId, data, options) { + set (peerId, data) { + throw errcode(new Error('set must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED') + } + + /** + * Add known data of a provided peer. + * @param {PeerId} peerId + * @param {Array|Data} data + */ + add (peerId, data) { throw errcode(new Error('set must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED') } @@ -45,7 +52,7 @@ class Book { throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } - const rec = this.data.get(peerId.toString()) + const rec = this.data.get(peerId.toB58String()) return rec ? [...rec] : undefined } @@ -60,14 +67,14 @@ class Book { throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) } - if (!this.data.delete(peerId.toString())) { + if (!this.data.delete(peerId.toB58String())) { return false } // TODO: Remove peerInfo and its usage on peer-info deprecate const peerInfo = new PeerInfo(peerId) - this.eventEmitter.emit(this.eventName, { + this._ps.emit(this.eventName, { peerId, peerInfo, [this.eventProperty]: [] diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index be7762e4b0..8a35303119 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -25,17 +25,15 @@ class ProtoBook extends Book { * @param {EventEmitter} peerStore */ constructor (peerStore) { - super(peerStore, 'change:protocols', 'protocols') - /** * PeerStore Event emitter, used by the ProtoBook to emit: * "change:protocols" - emitted when the known protocols of a peer change. */ - this._ps = peerStore + super(peerStore, 'change:protocols', 'protocols') /** * Map known peers to their known protocols. - * @type {Map} + * @type {Map>} */ this.data = new Map() } @@ -45,12 +43,10 @@ class ProtoBook extends Book { * If the peer was not known before, it will be added. * @override * @param {PeerId} peerId - * @param {Array|string} protocols - * @param {Object} [options] - * @param {boolean} [options.replace = true] whether protocols received replace stored ones or a unique union is performed. - * @returns {Array} + * @param {Array} protocols + * @returns {Map>} */ - set (peerId, protocols, { replace = true } = {}) { + set (peerId, protocols) { if (!PeerId.isPeerId(peerId)) { log.error('peerId must be an instance of peer-id to store data') throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) @@ -61,26 +57,7 @@ class ProtoBook extends Book { throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) } - if (!Array.isArray(protocols)) { - protocols = [protocols] - } - - if (replace) { - return this._replace(peerId, protocols) - } - - return this._add(peerId, protocols) - } - - /** - * Replace known protocols of a provided peer. - * If the peer is not known, it is set with the given protocols. - * @param {PeerId} peerId - * @param {Array} protocols - * @returns {Array} - */ - _replace (peerId, protocols) { - const id = peerId.toString() + const id = peerId.toB58String() const recSet = this.data.get(id) const newSet = new Set(protocols) @@ -90,7 +67,7 @@ class ProtoBook extends Book { // If yes, no changes needed! if (recSet && isSetEqual(recSet, newSet)) { log(`the protocols provided to store are equal to the already stored for ${id}`) - return protocols + return this.data } this.data.set(id, newSet) @@ -106,25 +83,36 @@ class ProtoBook extends Book { protocols }) - return protocols + return this.data } /** - * Add new known protocols to a provided peer. - * If the peer is not known, it is set with the given protocols. + * Adds known protocols of a provided peer. + * If the peer was not known before, it will be added. + * @override * @param {PeerId} peerId - * @param {Array|string} protocols - * @returns {Array} + * @param {Array} protocols + * @returns {Map>} */ - _add (peerId, protocols) { - const id = peerId.toString() + add (peerId, protocols) { + if (!PeerId.isPeerId(peerId)) { + log.error('peerId must be an instance of peer-id to store data') + throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) + } + + if (!protocols) { + log.error('protocols must be provided to store data') + throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) + } + + const id = peerId.toB58String() const recSet = this.data.get(id) || new Set() const newSet = new Set([...recSet, ...protocols]) // Set Union // Any new protocol added? if (recSet.size === newSet.size) { log(`the protocols provided to store are already stored for ${id}`) - return protocols + return this.data } protocols = [...newSet] @@ -142,35 +130,7 @@ class ProtoBook extends Book { protocols }) - return protocols - } - - /** - * Verify if the provided peer supports the given protocols. - * @param {PeerId} peerId - * @param {Array|string} protocols - * @returns {boolean} - */ - supports (peerId, protocols) { - if (!PeerId.isPeerId(peerId)) { - throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS) - } - - if (!protocols) { - throw errcode(new Error('protocols must be provided'), ERR_INVALID_PARAMETERS) - } - - if (!Array.isArray(protocols)) { - protocols = [protocols] - } - - const recSet = this.data.get(peerId.toString()) - - if (!recSet) { - return false - } - - return [...recSet].filter((p) => protocols.includes(p)).length === protocols.length + return this.data } } diff --git a/test/content-routing/dht/operation.node.js b/test/content-routing/dht/operation.node.js index 0f91ebb23b..538d8b326f 100644 --- a/test/content-routing/dht/operation.node.js +++ b/test/content-routing/dht/operation.node.js @@ -43,7 +43,7 @@ describe('DHT subsystem operates correctly', () => { remoteLibp2p.start() ]) - libp2p.peerStore.addressBook.set(remotePeerInfo.id, remoteListenAddr) + libp2p.peerStore.addressBook.set(remotePeerInfo.id, [remoteListenAddr]) remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerInfo.id)[0] }) @@ -98,7 +98,7 @@ describe('DHT subsystem operates correctly', () => { await libp2p.start() await remoteLibp2p.start() - libp2p.peerStore.addressBook.set(remotePeerInfo.id, remoteListenAddr) + libp2p.peerStore.addressBook.set(remotePeerInfo.id, [remoteListenAddr]) remAddr = libp2p.peerStore.addressBook.getMultiaddrsForPeer(remotePeerInfo.id)[0] }) diff --git a/test/dialing/direct.node.js b/test/dialing/direct.node.js index dccc583f2d..734687d0a2 100644 --- a/test/dialing/direct.node.js +++ b/test/dialing/direct.node.js @@ -100,7 +100,7 @@ describe('Dialing (direct, TCP)', () => { transportManager: localTM, peerStore: { addressBook: { - set: () => {}, + add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } } @@ -135,7 +135,7 @@ describe('Dialing (direct, TCP)', () => { transportManager: localTM, peerStore: { addressBook: { - set: () => {}, + add: () => {}, getMultiaddrsForPeer: () => [unsupportedAddr] } } @@ -179,7 +179,7 @@ describe('Dialing (direct, TCP)', () => { concurrency: 2, peerStore: { addressBook: { - set: () => {}, + add: () => {}, getMultiaddrsForPeer: () => addrs } } diff --git a/test/dialing/direct.spec.js b/test/dialing/direct.spec.js index 6651b3de61..3d4041120b 100644 --- a/test/dialing/direct.spec.js +++ b/test/dialing/direct.spec.js @@ -88,7 +88,7 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, peerStore: { addressBook: { - set: () => {}, + add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } } @@ -104,7 +104,7 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, peerStore: { addressBook: { - set: () => {}, + add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } } @@ -128,7 +128,7 @@ describe('Dialing (direct, WebSockets)', () => { transportManager: localTM, peerStore: { addressBook: { - set: () => {}, + add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } } @@ -163,7 +163,7 @@ describe('Dialing (direct, WebSockets)', () => { timeout: 50, peerStore: { addressBook: { - set: () => {}, + add: () => {}, getMultiaddrsForPeer: () => [remoteAddr] } } @@ -337,7 +337,7 @@ describe('Dialing (direct, WebSockets)', () => { }) sinon.spy(libp2p.dialer, 'connectToPeer') - sinon.spy(libp2p.peerStore.addressBook, 'set') + sinon.spy(libp2p.peerStore.addressBook, 'add') const connection = await libp2p.dial(remoteAddr) expect(connection).to.exist() @@ -346,7 +346,7 @@ describe('Dialing (direct, WebSockets)', () => { expect(protocol).to.equal('/echo/1.0.0') await connection.close() expect(libp2p.dialer.connectToPeer.callCount).to.equal(1) - expect(libp2p.peerStore.addressBook.set.callCount).to.be.at.least(1) + expect(libp2p.peerStore.addressBook.add.callCount).to.be.at.least(1) }) it('should run identify automatically after connecting', async () => { diff --git a/test/identify/index.spec.js b/test/identify/index.spec.js index e585233f73..7af2f67c84 100644 --- a/test/identify/index.spec.js +++ b/test/identify/index.spec.js @@ -220,14 +220,15 @@ describe('Identify', () => { }) sinon.spy(libp2p.identifyService, 'identify') - const peerStoreSpy = sinon.spy(libp2p.peerStore.addressBook, 'set') + const peerStoreSpySet = sinon.spy(libp2p.peerStore.addressBook, 'set') + const peerStoreSpyAdd = sinon.spy(libp2p.peerStore.addressBook, 'add') const connection = await libp2p.dialer.connectToPeer(remoteAddr) expect(connection).to.exist() // Wait for peer store to be updated // Dialer._createDialTarget (add), Identify (replace) - await pWaitFor(() => peerStoreSpy.callCount === 2) + await pWaitFor(() => peerStoreSpySet.callCount === 1 && peerStoreSpyAdd.callCount === 1) expect(libp2p.identifyService.identify.callCount).to.equal(1) // The connection should have no open streams diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 140699eb05..1ec11eb742 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -4,7 +4,6 @@ const chai = require('chai') chai.use(require('dirty-chai')) const { expect } = chai -const sinon = require('sinon') const { EventEmitter } = require('events') const pDefer = require('p-defer') @@ -62,9 +61,6 @@ describe('addressBook', () => { it('replaces the stored content by default and emit change event', () => { const defer = pDefer() - sinon.spy(ab, '_replace') - sinon.spy(ab, '_add') - const supportedMultiaddrs = [addr1, addr2] ee.once('change:multiaddrs', ({ peerId, multiaddrs }) => { @@ -73,51 +69,15 @@ describe('addressBook', () => { defer.resolve() }) - const multiaddrInfos = ab.set(peerId, supportedMultiaddrs) + ab.set(peerId, supportedMultiaddrs) + const multiaddrInfos = ab.get(peerId) const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) - - expect(ab._replace.callCount).to.equal(1) - expect(ab._add.callCount).to.equal(0) expect(multiaddrs).to.have.deep.members(supportedMultiaddrs) return defer.promise }) - it('adds the new content if replace is disabled and emit change event', () => { - const defer = pDefer() - sinon.spy(ab, '_replace') - sinon.spy(ab, '_add') - - const supportedMultiaddrsA = [addr1, addr2] - const supportedMultiaddrsB = [addr3] - const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB) - - let changeTrigger = 2 - ee.on('change:multiaddrs', ({ multiaddrs }) => { - changeTrigger-- - if (changeTrigger === 0 && arraysAreEqual(multiaddrs, finalMultiaddrs)) { - defer.resolve() - } - }) - - // Replace - let multiaddrInfos = ab.set(peerId, supportedMultiaddrsA) - let multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) - expect(ab._replace.callCount).to.equal(1) - expect(ab._add.callCount).to.equal(0) - expect(multiaddrs).to.have.deep.members(supportedMultiaddrsA) - - // Add - multiaddrInfos = ab.set(peerId, supportedMultiaddrsB, { replace: false }) - multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) - expect(ab._replace.callCount).to.equal(1) - expect(ab._add.callCount).to.equal(1) - expect(multiaddrs).to.have.deep.members(finalMultiaddrs) - - return defer.promise - }) - - it('emits on set (replace) if not storing the exact same content', async () => { + it('emits on set if not storing the exact same content', async () => { const defer = pDefer() const supportedMultiaddrsA = [addr1, addr2] @@ -135,14 +95,15 @@ describe('addressBook', () => { ab.set(peerId, supportedMultiaddrsA) // set 2 (same content) - const multiaddrInfos = ab.set(peerId, supportedMultiaddrsB) + ab.set(peerId, supportedMultiaddrsB) + const multiaddrInfos = ab.get(peerId) const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(supportedMultiaddrsB) await defer.promise }) - it('does not emit on set (replace) if it is storing the exact same content', async () => { + it('does not emit on set if it is storing the exact same content', async () => { const defer = pDefer() const supportedMultiaddrs = [addr1, addr2] @@ -168,8 +129,69 @@ describe('addressBook', () => { await defer.promise }) + }) + + describe('addressBook.add', () => { + let ee, ab + + beforeEach(() => { + ee = new EventEmitter() + ab = new AddressBook(ee) + }) + + afterEach(() => { + ee.removeAllListeners() + }) - it('emits on set (add) if the content to add not exists', async () => { + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + ab.add('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('throwns invalid parameters error if no addresses provided', () => { + expect(() => { + ab.add(peerId) + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('throwns invalid parameters error if invalid multiaddrs are provided', () => { + expect(() => { + ab.add(peerId, 'invalid multiaddr') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('adds the new content and emits change event', () => { + const defer = pDefer() + + const supportedMultiaddrsA = [addr1, addr2] + const supportedMultiaddrsB = [addr3] + const finalMultiaddrs = supportedMultiaddrsA.concat(supportedMultiaddrsB) + + let changeTrigger = 2 + ee.on('change:multiaddrs', ({ multiaddrs }) => { + changeTrigger-- + if (changeTrigger === 0 && arraysAreEqual(multiaddrs, finalMultiaddrs)) { + defer.resolve() + } + }) + + // Replace + ab.set(peerId, supportedMultiaddrsA) + let multiaddrInfos = ab.get(peerId) + let multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + expect(multiaddrs).to.have.deep.members(supportedMultiaddrsA) + + // Add + ab.add(peerId, supportedMultiaddrsB) + multiaddrInfos = ab.get(peerId) + multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) + expect(multiaddrs).to.have.deep.members(finalMultiaddrs) + + return defer.promise + }) + + it('emits on add if the content to add not exists', async () => { const defer = pDefer() const supportedMultiaddrsA = [addr1] @@ -188,14 +210,15 @@ describe('addressBook', () => { ab.set(peerId, supportedMultiaddrsA) // set 2 (content already existing) - const multiaddrInfos = ab.set(peerId, supportedMultiaddrsB, { replace: false }) + ab.add(peerId, supportedMultiaddrsB) + const multiaddrInfos = ab.get(peerId) const multiaddrs = multiaddrInfos.map((mi) => mi.multiaddr) expect(multiaddrs).to.have.deep.members(finalMultiaddrs) await defer.promise }) - it('does not emit on set (merge) if the content to add already exists', async () => { + it('does not emit on add if the content to add already exists', async () => { const defer = pDefer() const supportedMultiaddrsA = [addr1, addr2] @@ -213,7 +236,7 @@ describe('addressBook', () => { ab.set(peerId, supportedMultiaddrsA) // set 2 (content already existing) - ab.set(peerId, supportedMultiaddrsB, { replace: false }) + ab.add(peerId, supportedMultiaddrsB) // Wait 50ms for incorrect second event setTimeout(() => { diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index c4be9598d5..a306e17696 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -58,7 +58,7 @@ describe('peer-store', () => { // Add peer0 with { addr1, addr2 } and { proto1 } peerStore.addressBook.set(peerIds[0], [addr1, addr2]) - peerStore.protoBook.set(peerIds[0], proto1) + peerStore.protoBook.set(peerIds[0], [proto1]) // Add peer1 with { addr3 } and { proto2, proto3 } peerStore.addressBook.set(peerIds[1], [addr3]) @@ -73,9 +73,9 @@ describe('peer-store', () => { expect(peers.size).to.equal(3) expect(Array.from(peers.keys())).to.have.members([ - peerIds[0].toString(), - peerIds[1].toString(), - peerIds[2].toString() + peerIds[0].toB58String(), + peerIds[1].toB58String(), + peerIds[2].toB58String() ]) }) diff --git a/test/peer-store/proto-book.spec.js b/test/peer-store/proto-book.spec.js index 5c9fe5b983..7985dfe1dc 100644 --- a/test/peer-store/proto-book.spec.js +++ b/test/peer-store/proto-book.spec.js @@ -4,7 +4,6 @@ const chai = require('chai') chai.use(require('dirty-chai')) const { expect } = chai -const sinon = require('sinon') const { EventEmitter } = require('events') const pDefer = require('p-defer') @@ -51,9 +50,6 @@ describe('protoBook', () => { it('replaces the stored content by default and emit change event', () => { const defer = pDefer() - sinon.spy(pb, '_replace') - sinon.spy(pb, '_add') - const supportedProtocols = ['protocol1', 'protocol2'] ee.once('change:protocols', ({ peerId, protocols }) => { @@ -62,48 +58,14 @@ describe('protoBook', () => { defer.resolve() }) - const protocols = pb.set(peerId, supportedProtocols) - - expect(pb._replace.callCount).to.equal(1) - expect(pb._add.callCount).to.equal(0) + pb.set(peerId, supportedProtocols) + const protocols = pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocols) return defer.promise }) - it('adds the new content if replace is disabled and emit change event', () => { - const defer = pDefer() - sinon.spy(pb, '_replace') - sinon.spy(pb, '_add') - - const supportedProtocolsA = ['protocol1', 'protocol2'] - const supportedProtocolsB = ['protocol3'] - const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB) - - let changeTrigger = 2 - ee.on('change:protocols', ({ protocols }) => { - changeTrigger-- - if (changeTrigger === 0 && arraysAreEqual(protocols, finalProtocols)) { - defer.resolve() - } - }) - - // Replace - let protocols = pb.set(peerId, supportedProtocolsA) - expect(pb._replace.callCount).to.equal(1) - expect(pb._add.callCount).to.equal(0) - expect(protocols).to.have.deep.members(supportedProtocolsA) - - // Add - protocols = pb.set(peerId, supportedProtocolsB, { replace: false }) - expect(pb._replace.callCount).to.equal(1) - expect(pb._add.callCount).to.equal(1) - expect(protocols).to.have.deep.members(finalProtocols) - - return defer.promise - }) - - it('emits on set (replace) if not storing the exact same content', () => { + it('emits on set if not storing the exact same content', () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -121,13 +83,14 @@ describe('protoBook', () => { pb.set(peerId, supportedProtocolsA) // set 2 (same content) - const protocols = pb.set(peerId, supportedProtocolsB) + pb.set(peerId, supportedProtocolsB) + const protocols = pb.get(peerId) expect(protocols).to.have.deep.members(supportedProtocolsB) return defer.promise }) - it('does not emit on set (replace) if it is storing the exact same content', () => { + it('does not emit on set if it is storing the exact same content', () => { const defer = pDefer() const supportedProtocols = ['protocol1', 'protocol2'] @@ -153,8 +116,61 @@ describe('protoBook', () => { return defer.promise }) + }) + + describe('protoBook.add', () => { + let ee, pb + + beforeEach(() => { + ee = new EventEmitter() + pb = new ProtoBook(ee) + }) + + afterEach(() => { + ee.removeAllListeners() + }) + + it('throwns invalid parameters error if invalid PeerId is provided', () => { + expect(() => { + pb.add('invalid peerId') + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('throwns invalid parameters error if no protocols provided', () => { + expect(() => { + pb.add(peerId) + }).to.throw(ERR_INVALID_PARAMETERS) + }) + + it('adds the new content and emits change event', () => { + const defer = pDefer() + + const supportedProtocolsA = ['protocol1', 'protocol2'] + const supportedProtocolsB = ['protocol3'] + const finalProtocols = supportedProtocolsA.concat(supportedProtocolsB) + + let changeTrigger = 2 + ee.on('change:protocols', ({ protocols }) => { + changeTrigger-- + if (changeTrigger === 0 && arraysAreEqual(protocols, finalProtocols)) { + defer.resolve() + } + }) + + // Replace + pb.set(peerId, supportedProtocolsA) + let protocols = pb.get(peerId) + expect(protocols).to.have.deep.members(supportedProtocolsA) + + // Add + pb.add(peerId, supportedProtocolsB) + protocols = pb.get(peerId) + expect(protocols).to.have.deep.members(finalProtocols) + + return defer.promise + }) - it('emits on set (add) if the content to add not exists', () => { + it('emits on add if the content to add not exists', () => { const defer = pDefer() const supportedProtocolsA = ['protocol1'] @@ -173,13 +189,14 @@ describe('protoBook', () => { pb.set(peerId, supportedProtocolsA) // set 2 (content already existing) - const protocols = pb.set(peerId, supportedProtocolsB, { replace: false }) + pb.add(peerId, supportedProtocolsB) + const protocols = pb.get(peerId) expect(protocols).to.have.deep.members(finalProtocols) return defer.promise }) - it('does not emit on set (merge) if the content to add already exists', () => { + it('does not emit on add if the content to add already exists', () => { const defer = pDefer() const supportedProtocolsA = ['protocol1', 'protocol2'] @@ -197,7 +214,7 @@ describe('protoBook', () => { pb.set(peerId, supportedProtocolsA) // set 2 (content already existing) - pb.set(peerId, supportedProtocolsB, { replace: false }) + pb.add(peerId, supportedProtocolsB) // Wait 50ms for incorrect second event setTimeout(() => { @@ -238,53 +255,6 @@ describe('protoBook', () => { }) }) - describe('protoBook.supports', () => { - let ee, pb - - beforeEach(() => { - ee = new EventEmitter() - pb = new ProtoBook(ee) - }) - - it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { - pb.supports('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) - }) - - it('throwns invalid parameters error if no protocols provided', () => { - expect(() => { - pb.supports(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) - }) - - it('returns false if no records exist for the peer', () => { - const supportedProtocols = ['protocol1'] - const supports = pb.supports(peerId, supportedProtocols[0]) - - expect(supports).to.equal(false) - }) - - it('returns true if the protocol is supported', () => { - const supportedProtocols = ['protocol1', 'protocol2'] - - pb.set(peerId, supportedProtocols) - - const supports = pb.supports(peerId, supportedProtocols[1]) - expect(supports).to.equal(true) - }) - - it('returns false if part of the protocols is supported', () => { - const supportedProtocols = ['protocol1', 'protocol2'] - const otherProtocols = ['protocol3', 'protocol4'] - - pb.set(peerId, supportedProtocols) - - const supports = pb.supports(peerId, [supportedProtocols[0], ...otherProtocols]) - expect(supports).to.equal(false) - }) - }) - describe('protoBook.delete', () => { let ee, pb diff --git a/test/registrar/registrar.spec.js b/test/registrar/registrar.spec.js index a8c17c935e..3e2744901e 100644 --- a/test/registrar/registrar.spec.js +++ b/test/registrar/registrar.spec.js @@ -165,8 +165,8 @@ describe('registrar', () => { // Add protocol to peer and update it peerInfo.protocols.add(multicodec) - peerStore.addressBook.set(peerInfo.id, peerInfo.multiaddrs.toArray(), { replace: false }) - peerStore.protoBook.set(peerInfo.id, Array.from(peerInfo.protocols), { replace: false }) + peerStore.addressBook.add(peerInfo.id, peerInfo.multiaddrs.toArray()) + peerStore.protoBook.add(peerInfo.id, Array.from(peerInfo.protocols)) await onConnectDefer.promise From 166206575e7340f8a8e712eb8c526b7324b8fae9 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Mon, 6 Apr 2020 10:37:20 +0200 Subject: [PATCH 7/8] chore: add new peer store tests --- test/peer-store/peer-store.spec.js | 47 +++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index a306e17696..411977f86d 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -23,7 +23,7 @@ describe('peer-store', () => { let peerIds before(async () => { peerIds = await peerUtils.createPeerId({ - number: 3 + number: 4 }) }) @@ -64,18 +64,23 @@ describe('peer-store', () => { peerStore.addressBook.set(peerIds[1], [addr3]) peerStore.protoBook.set(peerIds[1], [proto2, proto3]) - // Add peer2 { addr4 } + // Add peer2 with { addr4 } peerStore.addressBook.set(peerIds[2], [addr4]) + + // Add peer3 with { addr4 } and { proto2 } + peerStore.addressBook.set(peerIds[3], [addr4]) + peerStore.protoBook.set(peerIds[3], [proto2]) }) it('has peers', () => { const peers = peerStore.peers - expect(peers.size).to.equal(3) + expect(peers.size).to.equal(4) expect(Array.from(peers.keys())).to.have.members([ peerIds[0].toB58String(), peerIds[1].toB58String(), - peerIds[2].toB58String() + peerIds[2].toB58String(), + peerIds[3].toB58String() ]) }) @@ -84,8 +89,8 @@ describe('peer-store', () => { expect(deleted).to.equal(true) const peers = peerStore.peers - expect(peers.size).to.equal(2) - expect(Array.from(peers.keys())).to.not.have.members([peerIds[0].toString()]) + expect(peers.size).to.equal(3) + expect(Array.from(peers.keys())).to.not.have.members([peerIds[0].toB58String()]) }) it('returns true on deleting a stored peer which is only on one book', () => { @@ -93,7 +98,7 @@ describe('peer-store', () => { expect(deleted).to.equal(true) const peers = peerStore.peers - expect(peers.size).to.equal(2) + expect(peers.size).to.equal(3) }) it('finds the stored information of a peer in all its books', () => { @@ -113,5 +118,33 @@ describe('peer-store', () => { const peerMultiaddrs = peerInfo.multiaddrInfos.map((mi) => mi.multiaddr) expect(peerMultiaddrs).to.have.members([addr4]) }) + + it('can find all the peers supporting a protocol', () => { + const peerSupporting2 = [] + + for (const [, peerInfo] of peerStore.peers.entries()) { + if (peerInfo.protocols.has(proto2)) { + peerSupporting2.push(peerInfo) + } + } + + expect(peerSupporting2.length).to.eql(2) + expect(peerSupporting2[0].id.toB58String()).to.eql(peerIds[1].toB58String()) + expect(peerSupporting2[1].id.toB58String()).to.eql(peerIds[3].toB58String()) + }) + + it('can find all the peers listening on a given address', () => { + const peerListenint4 = [] + + for (const [, peerInfo] of peerStore.peers.entries()) { + if (peerInfo.multiaddrs.has(addr4)) { + peerListenint4.push(peerInfo) + } + } + + expect(peerListenint4.length).to.eql(2) + expect(peerListenint4[0].id.toB58String()).to.eql(peerIds[2].toB58String()) + expect(peerListenint4[1].id.toB58String()).to.eql(peerIds[3].toB58String()) + }) }) }) From 7dcc78ea7a897c3b13f7ab5d5ac3e66d12fe43f4 Mon Sep 17 00:00:00 2001 From: Vasco Santos Date: Thu, 9 Apr 2020 12:54:48 +0200 Subject: [PATCH 8/8] chore: apply suggestions from code review Co-Authored-By: Jacob Heun --- doc/API.md | 14 +++---- src/peer-store/README.md | 69 +++++++++------------------------- src/peer-store/address-book.js | 14 +++---- src/peer-store/proto-book.js | 12 +++--- 4 files changed, 38 insertions(+), 71 deletions(-) diff --git a/doc/API.md b/doc/API.md index 4a95b2d866..bfaa9f778b 100644 --- a/doc/API.md +++ b/doc/API.md @@ -519,7 +519,7 @@ const key = '/key' const { from, val } = await libp2p.contentRouting.get(key) ``` -### peerStore.addressBook.Add +### peerStore.addressBook.add Adds known `multiaddrs` of a given peer. If the peer is not known, it will be set with the provided multiaddrs. @@ -536,7 +536,7 @@ Adds known `multiaddrs` of a given peer. If the peer is not known, it will be se | Type | Description | |------|-------------| -| `Map` | Map of known peers' string identifier with their relevant information [`MultiaddrInfo`](multiaddr-info) | +| `AddressBook` | Returns the Address Book component | #### Example @@ -574,7 +574,7 @@ peerStore.addressBook.delete(peerId) ### peerStore.addressBook.get -Get the known [`MultiaddrInfos`](multiaddr-info) of a provided peer. +Get the known [`MultiaddrInfos`][multiaddr-info] of a provided peer. `peerStore.addressBook.get(peerId)` @@ -588,7 +588,7 @@ Get the known [`MultiaddrInfos`](multiaddr-info) of a provided peer. | Type | Description | |------|-------------| -| `Array` | Array of peer's multiaddr with their relevant information [`MultiaddrInfo`](multiaddr-info) | +| `Array` | Array of peer's multiaddr with their relevant information [`MultiaddrInfo`][multiaddr-info] | #### Example @@ -657,7 +657,7 @@ Set known `multiaddrs` of a given peer. | Type | Description | |------|-------------| -| `Map` | Map of known peers' string identifier with their relevant information [`MultiaddrInfo`](multiaddr-info) | +| `AddressBook` | Returns the Address Book component | #### Example @@ -682,7 +682,7 @@ Add known `protocols` of a given peer. | Type | Description | |------|-------------| -| `Map` | Map of known peers' string identifier with their supported protocols | +| `ProtoBook` | Returns the Proto Book component | #### Example @@ -763,7 +763,7 @@ Set known `protocols` of a given peer. | Type | Description | |------|-------------| -| `Map` | Map of known peers' string identifier with their supported protocols | +| `ProtoBook` | Returns the Proto Book component | #### Example diff --git a/src/peer-store/README.md b/src/peer-store/README.md index a17b26c79a..bb309a0ddf 100644 --- a/src/peer-store/README.md +++ b/src/peer-store/README.md @@ -26,7 +26,7 @@ It is also possible to gather relevant information for peers from other protocol When the PeerStore data is updated, this information might be important for different parties. -Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to it. The same is true for pinging a peer. While the `AddressBook` is going to keep its data updated, it will also emit `change:multiaddrs` events so that subsystems/users interested in knowing these changes can be notifyied instead of pooling the `AddressBook`. +Every time a peer needs to dial another peer, it is essential that it knows the multiaddrs used by the peer, in order to perform a successful dial to it. The same is true for pinging a peer. While the `AddressBook` is going to keep its data updated, it will also emit `change:multiaddrs` events so that subsystems/users interested in knowing these changes can be notified instead of polling the `AddressBook`. Everytime a peer starts/stops supporting a protocol, libp2p subsystems or users might need to act accordingly. `js-libp2p` registrar orchestrates known peers, established connections and protocol topologies. This way, once a protocol is supported for a peer, the topology of that protocol should be informed that a new peer may be used and the subsystem can decide if it should open a new stream with that peer or not. For these situations, the `ProtoBook` will emit `change:protocols` events whenever supported protocols of a peer change. @@ -34,36 +34,7 @@ Everytime a peer starts/stops supporting a protocol, libp2p subsystems or users The PeerStore wraps four main components: `addressBook`, `keyBook`, `protocolBook` and `metadataBook`. Moreover, it provides a high level API for those components, as well as data events. -### API - -For the complete API documentation, you should check the [API.md](../../doc/API.md). - -Access to its underlying books: - -- `peerStore.protoBook.*` -- `peerStore.addressBook.*` - -High level operations: - -- [`peerStore.delete(peerId)`](../../doc/API.md#peerstoredelete) - -Deletes the provided peer from every book. - -- [`peerStore.get(peerId)`](../../doc/API.md#peerstoreget) - -Gets the stored information of a given peer. - -- [`peerStore.peers()`](../../doc/API.md#peerstorepeers) - -Gets an array of all the peers, as well as their information. - -### Events - -- `peer` - emitted when a new peer is added. -- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs. -- `change:protocols` - emitted when a known peer supports a different set of protocols. - -### Components API +### Components #### Address Book @@ -75,23 +46,10 @@ A `peerId.toString()` identifier mapping to a `multiaddrInfo` object, which shou ```js { - multiaddr: , + multiaddr: } ``` -**Note:** except for multiaddr naming, the other properties are placeholders for now and might not be as described in the future milestones. - -- `addressBook.data` -- [`addressBook.add()`](../../doc/API.md#peerstoreaddressbookadd) -- [`addressBook.delete()`](../../doc/API.md#peerstoreaddressbookdelete) -- [`addressBook.get()`](../../doc/API.md#peerstoreaddressbookget) -- [`addressBook.getMultiaddrsForPeer()`](../../doc/API.md#peerstoreaddressbookgetmultiaddrsforpeer) -- [`addressBook.set()`](../../doc/API.md#peerstoreaddressbookset) - -(Future considerations: Further API methods will probably be added in the context of multiaddr validity and multiaddr confidence.) - -**Not Yet Implemented**: Multiaddr Confidence - #### Key Book The `keyBook` tracks the keys of the peers. @@ -106,16 +64,25 @@ The `protoBook` holds the identifiers of the protocols supported by each peer. T A `peerId.toString()` identifier mapping to a `Set` of protocol identifier strings. -- `protoBook.data` -- [`protoBook.add()`](../../doc/API.md#peerstoreprotobookadd) -- [`protoBook.delete()`](../../doc/API.md#peerstoreprotobookdelete) -- [`protoBook.get()`](../../doc/API.md#peerstoreprotobookget) -- [`protoBook.set()`](../../doc/API.md#peerstoreprotobookset) - #### Metadata Book **Not Yet Implemented** +### API + +For the complete API documentation, you should check the [API.md](../../doc/API.md). + +Access to its underlying books: + +- `peerStore.protoBook.*` +- `peerStore.addressBook.*` + +### Events + +- `peer` - emitted when a new peer is added. +- `change:multiaadrs` - emitted when a known peer has a different set of multiaddrs. +- `change:protocols` - emitted when a known peer supports a different set of protocols. + ## Future Considerations - If multiaddr TTLs are added, the PeerStore may schedule jobs to delete all addresses that exceed the TTL to prevent AddressBook bloating diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 6a08277f41..445d731e1e 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -50,7 +50,7 @@ class AddressBook extends Book { * @override * @param {PeerId} peerId * @param {Array} addresses - * @returns {Map>} + * @returns {AddressBook} */ set (peerId, addresses) { if (!PeerId.isPeerId(peerId)) { @@ -64,7 +64,7 @@ class AddressBook extends Book { // Not replace multiaddrs if (!multiaddrInfos.length) { - return this.data + return this } // Already knows the peer @@ -75,7 +75,7 @@ class AddressBook extends Book { // If yes, no changes needed! if (intersection.length === rec.length) { log(`the addresses provided to store are equal to the already stored for ${id}`) - return this.data + return this } } @@ -98,7 +98,7 @@ class AddressBook extends Book { multiaddrs: multiaddrInfos.map((mi) => mi.multiaddr) }) - return this.data + return this } /** @@ -107,7 +107,7 @@ class AddressBook extends Book { * @override * @param {PeerId} peerId * @param {Array} addresses - * @returns {Map>} + * @returns {AddressBook} */ add (peerId, addresses) { if (!PeerId.isPeerId(peerId)) { @@ -130,7 +130,7 @@ class AddressBook extends Book { // The content is the same, no need to update. if (rec && rec.length === multiaddrInfos.length) { log(`the addresses provided to store are already stored for ${id}`) - return this.data + return this } this.data.set(id, multiaddrInfos) @@ -153,7 +153,7 @@ class AddressBook extends Book { this._ps.emit('peer', peerInfo) } - return this.data + return this } /** diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index 8a35303119..a5c5867ee8 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -44,7 +44,7 @@ class ProtoBook extends Book { * @override * @param {PeerId} peerId * @param {Array} protocols - * @returns {Map>} + * @returns {ProtoBook} */ set (peerId, protocols) { if (!PeerId.isPeerId(peerId)) { @@ -67,7 +67,7 @@ class ProtoBook extends Book { // If yes, no changes needed! if (recSet && isSetEqual(recSet, newSet)) { log(`the protocols provided to store are equal to the already stored for ${id}`) - return this.data + return this } this.data.set(id, newSet) @@ -83,7 +83,7 @@ class ProtoBook extends Book { protocols }) - return this.data + return this } /** @@ -92,7 +92,7 @@ class ProtoBook extends Book { * @override * @param {PeerId} peerId * @param {Array} protocols - * @returns {Map>} + * @returns {ProtoBook} */ add (peerId, protocols) { if (!PeerId.isPeerId(peerId)) { @@ -112,7 +112,7 @@ class ProtoBook extends Book { // Any new protocol added? if (recSet.size === newSet.size) { log(`the protocols provided to store are already stored for ${id}`) - return this.data + return this } protocols = [...newSet] @@ -130,7 +130,7 @@ class ProtoBook extends Book { protocols }) - return this.data + return this } }