diff --git a/package.json b/package.json index 045d114deb..eb58e594a2 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "debug": "^4.1.1", "err-code": "^2.0.0", "hashlru": "^2.3.0", + "interface-datastore": "^0.8.3", "it-all": "^1.0.1", "it-buffer": "^0.1.1", "it-handshake": "^1.0.1", diff --git a/src/config.js b/src/config.js index cb6aa6f551..9cd183bf1a 100644 --- a/src/config.js +++ b/src/config.js @@ -20,6 +20,9 @@ const DefaultConfig = { metrics: { enabled: false }, + peerStore: { + persistence: true + }, config: { dht: { enabled: false, diff --git a/src/index.js b/src/index.js index 40b4f43a78..93178c05f8 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,7 @@ const debug = require('debug') const log = debug('libp2p') log.error = debug('libp2p:error') +const { MemoryDatastore } = require('interface-datastore') const PeerId = require('peer-id') const peerRouting = require('./peer-routing') @@ -42,9 +43,12 @@ class Libp2p extends EventEmitter { // and add default values where appropriate this._options = validateConfig(_options) - this.datastore = this._options.datastore this.peerId = this._options.peerId - this.peerStore = new PeerStore() + this.datastore = this._options.datastore || new MemoryDatastore() + this.peerStore = new PeerStore({ + datastore: this.datastore, + ...this._options.peerStore + }) // Addresses {listen, announce, noAnnounce} this.addresses = this._options.addresses @@ -405,6 +409,9 @@ class Libp2p extends EventEmitter { // Listen on the provided transports await this.transportManager.listen() + // Start PeerStore + await this.peerStore.start() + if (this._config.pubsub.enabled) { this.pubsub && this.pubsub.start() } diff --git a/src/peer-store/README.md b/src/peer-store/README.md index a1e7193541..3dd121fa5c 100644 --- a/src/peer-store/README.md +++ b/src/peer-store/README.md @@ -83,7 +83,51 @@ Access to its underlying books: - `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. +## Data Persistence + +The data stored in the PeerStore will be persisted by default. Keeping a record of the peers already discovered by the peer, as well as their known data aims to improve the efficiency of peers joining the network after being offline. + +--- +TODO: Discuss if we should make it persisted by default now. Taking into consideration that we will use a MemoryDatastore by default, unless the user configures a datastore to use, it will be worthless. It might make sense to make it disabled by default until we work on improving configuration and provide good defauls for each environment. +--- + +The libp2p node will need to receive a [datastore](https://github.com/ipfs/interface-datastore), in order to store this data in a persistent way. Otherwise, it will be stored on a [memory datastore](https://github.com/ipfs/interface-datastore/blob/master/src/memory.js). + +A [datastore](https://github.com/ipfs/interface-datastore) stores its data in a key-value fashion. As a result, we need coherent keys so that we do not overwrite data. + +Taking into account that a datastore allows queries using a key prefix, we can find all the information if we define a consistent namespace that allow us to find the content without having any information. The namespaces were defined as follows: + +**AddressBook** + +All the knownw peer addresses are stored with a key pattern as follows: + +`/peers/addrs/` + +**ProtoBook** + +All the knownw peer protocols are stored with a key pattern as follows: + +`/peers/protos/` + +**KeyBook** + +_NOT_YET_IMPLEMENTED_ + +All public and private keys are stored under the following pattern: + +` /peers/keys//{pub, priv}` + +**MetadataBook** + +_NOT_YET_IMPLEMENTED_ + +Metadata is stored under the following key pattern: + +`/peers/metadata//` + ## 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. +- When improving libp2p configuration for specific runtimes, we should take into account the PeerStore recommended datastore. +- When improving libp2p configuration, we should think about a possible way of allowing the configuration of Bootstrap to be influenced by the persisted peers, as a way to decrease the load on Bootstrap nodes. diff --git a/src/peer-store/address-book.js b/src/peer-store/address-book.js index 8488fb20b8..85803369fa 100644 --- a/src/peer-store/address-book.js +++ b/src/peer-store/address-book.js @@ -9,14 +9,17 @@ const multiaddr = require('multiaddr') const PeerId = require('peer-id') const Book = require('./book') +const Protobuf = require('./pb/address-book.proto') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../errors') /** * The AddressBook is responsible for keeping the known multiaddrs * of a peer. + * This data will be persisted in the PeerStore datastore as follows: + * /peers/addrs/ */ class AddressBook extends Book { /** @@ -35,7 +38,21 @@ class AddressBook extends Book { * "peer" - emitted when a peer is discovered by the node. * "change:multiaddrs" - emitted when the known multiaddrs of a peer change. */ - super(peerStore, 'change:multiaddrs', 'multiaddrs') + super({ + peerStore, + eventName: 'change:multiaddrs', + eventProperty: 'multiaddrs', + protoBuf: Protobuf, + dsPrefix: '/peers/addrs/', + eventTransformer: (data) => data.map((address) => address.multiaddr), + // TODO: should we already think about persist address data and store it accordingly? + dsSetTransformer: (data) => ({ + addrs: data.map((address) => address.multiaddr.buffer) + }), + dsGetTransformer: (data) => data.addrs.map((a) => ({ + multiaddr: multiaddr(a) + })) + }) /** * Map known peers to their known Addresses. @@ -78,8 +95,7 @@ class AddressBook extends Book { } } - this.data.set(id, addresses) - this._setPeerId(peerId) + this._setData(peerId, addresses) log(`stored provided multiaddrs for ${id}`) // Notify the existance of a new peer @@ -87,11 +103,6 @@ class AddressBook extends Book { this._ps.emit('peer', peerId) } - this._ps.emit('change:multiaddrs', { - peerId, - multiaddrs: addresses.map((mi) => mi.multiaddr) - }) - return this } @@ -127,16 +138,9 @@ class AddressBook extends Book { return this } - this._setPeerId(peerId) - this.data.set(id, addresses) - + this._setData(peerId, addresses) log(`added provided multiaddrs for ${id}`) - this._ps.emit('change:multiaddrs', { - peerId, - multiaddrs: addresses.map((mi) => mi.multiaddr) - }) - // Notify the existance of a new peer if (!rec) { this._ps.emit('peer', peerId) @@ -147,6 +151,7 @@ class AddressBook extends Book { /** * Transforms received multiaddrs into Address. + * @private * @param {Array} multiaddrs * @returns {Array
} */ diff --git a/src/peer-store/book.js b/src/peer-store/book.js index ba2ff13f71..ac8d5ea050 100644 --- a/src/peer-store/book.js +++ b/src/peer-store/book.js @@ -1,20 +1,57 @@ 'use strict' const errcode = require('err-code') +const debug = require('debug') +const log = debug('libp2p:peer-store:book') +log.error = debug('libp2p:peer-store:book:error') + +const { Key } = require('interface-datastore') const PeerId = require('peer-id') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../errors') +const passthrough = data => data + /** * The Book is the skeleton for the PeerStore books. + * It handles the PeerStore persistence and events. */ class Book { - constructor (peerStore, eventName, eventProperty) { + /** + * @constructor + * @param {Object} properties + * @param {PeerStore} properties.peerStore PeerStore instance. + * @param {string} properties.eventName Name of the event to emit by the PeerStore. + * @param {string} properties.eventProperty Name of the property to emit by the PeerStore. + * @param {Object} properties.protoBuf Suffix of the Datastore Key + * @param {String} properties.dsPrefix Prefix of the Datastore Key + * @param {String} [properties.dsSuffix] Suffix of the Datastore Key + * @param {function} [properties.eventTransformer] Transformer function of the provided data for being emitted. + * @param {function} [properties.dsSetTransformer] Transformer function of the provided data for being persisted. + * @param {function} [properties.dsGetTransformer] Transformer function of the persisted data to be loaded. + */ + constructor ({ + peerStore, + eventName, + eventProperty, + protoBuf, + dsPrefix, + dsSuffix = '', + eventTransformer = passthrough, + dsSetTransformer = passthrough, + dsGetTransformer = passthrough + }) { this._ps = peerStore this.eventName = eventName this.eventProperty = eventProperty + this.protoBuf = protoBuf + this.dsPrefix = dsPrefix + this.dsSuffix = dsSuffix + this.eventTransformer = eventTransformer + this.dsSetTransformer = dsSetTransformer + this.dsGetTransformer = dsGetTransformer /** * Map known peers to their data. @@ -23,6 +60,91 @@ class Book { this.data = new Map() } + /** + * Load data from peerStore datastore into the books datastructures. + * This will not persist the replicated data nor emit modify events. + * @private + * @return {Promise} + */ + async _loadData () { + if (!this._ps._datastore || !this._ps._enabledPersistance) { + return + } + + const persistenceQuery = { + prefix: this.dsPrefix + } + + for await (const { key, value } of this._ps._datastore.query(persistenceQuery)) { + try { + // PeerId to add to the book + const b32key = key.toString() + .replace(this.dsPrefix, '') // remove prefix from key + .replace(this.dsSuffix, '') // remove suffix from key + const peerId = PeerId.createFromCID(b32key) + // Data in the format to add to the book + const data = this.dsGetTransformer(this.protoBuf.decode(value)) + // Add the book without persist the replicated data and emit modify + this._setData(peerId, data, { + persist: false, + emit: false + }) + } catch (err) { + log.error(err) + } + } + } + + /** + * Set data into the datastructure, persistence and emit it using the provided transformers. + * @private + * @param {PeerId} peerId peerId of the data to store + * @param {Array<*>} data data to store. + * @param {Object} [options] storing options. + * @param {boolean} [options.persist = true] persist the provided data. + * @param {boolean} [options.emit = true] emit the provided data. + * @return {Promise} + */ + async _setData (peerId, data, { persist = true, emit = true } = {}) { + const b58key = peerId.toB58String() + + // Store data in memory + this.data.set(b58key, data) + this._setPeerId(peerId) + + // Emit event + emit && this._ps.emit(this.eventName, { + peerId, + [this.eventProperty]: this.eventTransformer(data) + }) + + // Add to Persistence datastore + persist && await this._persistData(peerId, data) + } + + /** + * Persist data on the datastore + * @private + * @param {PeerId} peerId peerId of the data to persist + * @param {Array<*>} data data to persist + * @return {Promise} + */ + async _persistData (peerId, data) { + if (!this._ps._datastore || !this._ps._enabledPersistance) { + return + } + + const b32key = peerId.toString() + const k = `${this.dsPrefix}${b32key}${this.dsSuffix}` + try { + const value = this.protoBuf.encode(this.dsSetTransformer(data)) + + await this._ps._datastore.put(new Key(k), value) + } catch (err) { + log.error(err) + } + } + /** * Set known data of a provided peer. * @param {PeerId} peerId @@ -75,9 +197,17 @@ class Book { [this.eventProperty]: [] }) + // Update Persistence datastore + this._persistData(peerId, []) + return true } + /** + * Set PeerId into peerStore datastructure. + * @private + * @param {PeerId} peerId + */ _setPeerId (peerId) { if (!this._ps.peerIds.get(peerId)) { this._ps.peerIds.set(peerId.toB58String(), peerId) diff --git a/src/peer-store/index.js b/src/peer-store/index.js index 6888191af9..3a8a799fb1 100644 --- a/src/peer-store/index.js +++ b/src/peer-store/index.js @@ -30,11 +30,22 @@ class PeerStore extends EventEmitter { * @property {Array} protocols peer's supported protocols. */ - constructor () { + /** + * @constructor + * @param {Object} properties + * @param {Datastore} [properties.datastore] Datastore to persist data. + * @param {boolean} [properties.persistance = true] Persist peerstore data. + */ + constructor ({ datastore, persistance = true } = {}) { super() /** - * AddressBook containing a map of peerIdStr to Address + * Backend datastore used to persist data. + */ + this._datastore = datastore + + /** + * AddressBook containing a map of peerIdStr to Address. */ this.addressBook = new AddressBook(this) @@ -49,6 +60,18 @@ class PeerStore extends EventEmitter { * @type {Map} */ this.peerIds = new Map() + + this._enabledPersistance = persistance + } + + /** + * Start PeerStore and collect data from the datastore to populate it. + */ + async start () { + if (this._enabledPersistance) { + await this.addressBook._loadData() + await this.protoBook._loadData() + } } /** diff --git a/src/peer-store/pb/address-book.proto.js b/src/peer-store/pb/address-book.proto.js new file mode 100644 index 0000000000..13796f7af8 --- /dev/null +++ b/src/peer-store/pb/address-book.proto.js @@ -0,0 +1,12 @@ +'use strict' + +const protons = require('protons') + +/* eslint-disable no-tabs */ +const message = ` +message Addresses { + repeated bytes addrs = 1; +} +` + +module.exports = protons(message).Addresses diff --git a/src/peer-store/pb/proto-book.proto.js b/src/peer-store/pb/proto-book.proto.js new file mode 100644 index 0000000000..74b7e223ed --- /dev/null +++ b/src/peer-store/pb/proto-book.proto.js @@ -0,0 +1,12 @@ +'use strict' + +const protons = require('protons') + +/* eslint-disable no-tabs */ +const message = ` +message Protocols { + repeated string protocols = 1; +} +` + +module.exports = protons(message).Protocols diff --git a/src/peer-store/proto-book.js b/src/peer-store/proto-book.js index ea395dfdf6..32d0cd1e9e 100644 --- a/src/peer-store/proto-book.js +++ b/src/peer-store/proto-book.js @@ -8,15 +8,17 @@ log.error = debug('libp2p:peer-store:proto-book:error') const PeerId = require('peer-id') const Book = require('./book') +const Protobuf = require('./pb/proto-book.proto') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../errors') /** * The ProtoBook is responsible for keeping the known supported * protocols of a peer. - * @fires ProtoBook#change:protocols + * This data will be persisted in the PeerStore datastore as follows: + * /peers/protos/ */ class ProtoBook extends Book { /** @@ -28,7 +30,20 @@ class ProtoBook extends Book { * PeerStore Event emitter, used by the ProtoBook to emit: * "change:protocols" - emitted when the known protocols of a peer change. */ - super(peerStore, 'change:protocols', 'protocols') + super({ + peerStore, + eventName: 'change:protocols', + eventProperty: 'protocols', + protoBuf: Protobuf, + dsPrefix: '/peers/protos/', + eventTransformer: (data) => Array.from(data), + dsSetTransformer: (data) => ({ + protocols: Array.from(data) + }), + dsGetTransformer: (data) => new Set(data.protocols) + // dsSetTransformer: (data) => Array.from(data), + // dsGetTransformer: (data) => new Set(data) + }) /** * Map known peers to their known protocols. @@ -43,7 +58,7 @@ class ProtoBook extends Book { * @override * @param {PeerId} peerId * @param {Array} protocols - * @returns {ProtoBook} + * @returns {Promise} */ set (peerId, protocols) { if (!PeerId.isPeerId(peerId)) { @@ -69,15 +84,9 @@ class ProtoBook extends Book { return this } - this.data.set(id, newSet) - this._setPeerId(peerId) + this._setData(peerId, newSet) log(`stored provided protocols for ${id}`) - this._ps.emit('change:protocols', { - peerId, - protocols - }) - return this } @@ -110,17 +119,9 @@ class ProtoBook extends Book { return this } - protocols = [...newSet] - - this.data.set(id, newSet) - this._setPeerId(peerId) + this._setData(peerId, newSet) log(`added provided protocols for ${id}`) - this._ps.emit('change:protocols', { - peerId, - protocols - }) - return this } } diff --git a/test/peer-store/address-book.spec.js b/test/peer-store/address-book.spec.js index 62e5a487ae..d14923f5b3 100644 --- a/test/peer-store/address-book.spec.js +++ b/test/peer-store/address-book.spec.js @@ -12,7 +12,7 @@ const PeerStore = require('../../src/peer-store') const peerUtils = require('../utils/creators/peer') const { - ERR_INVALID_PARAMETERS + codes: { ERR_INVALID_PARAMETERS } } = require('../../src/errors') const addr1 = multiaddr('/ip4/127.0.0.1/tcp/8000') @@ -41,21 +41,33 @@ describe('addressBook', () => { }) it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { + try { ab.set('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('invalid peerId should throw error') }) it('throwns invalid parameters error if no addresses provided', () => { - expect(() => { + try { ab.set(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('no addresses should throw error') }) it('throwns invalid parameters error if invalid multiaddrs are provided', () => { - expect(() => { - ab.set(peerId, 'invalid multiaddr') - }).to.throw(ERR_INVALID_PARAMETERS) + try { + ab.set(peerId, ['invalid multiaddr']) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('invalid multiaddrs should throw error') }) it('replaces the stored content by default and emit change event', () => { @@ -143,21 +155,33 @@ describe('addressBook', () => { }) it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { + try { ab.add('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('invalid peerId should throw error') }) it('throwns invalid parameters error if no addresses provided', () => { - expect(() => { + try { ab.add(peerId) - }).to.throw(ERR_INVALID_PARAMETERS) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('no addresses provided should throw error') }) it('throwns invalid parameters error if invalid multiaddrs are provided', () => { - expect(() => { - ab.add(peerId, 'invalid multiaddr') - }).to.throw(ERR_INVALID_PARAMETERS) + try { + ab.add(peerId, ['invalid multiaddr']) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('invalid multiaddr should throw error') }) it('adds the new content and emits change event', () => { @@ -255,9 +279,13 @@ describe('addressBook', () => { }) it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { + try { ab.get('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('invalid peerId should throw error') }) it('returns undefined if no multiaddrs are known for the provided peer', () => { @@ -286,9 +314,13 @@ describe('addressBook', () => { }) it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { + try { ab.getMultiaddrsForPeer('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('invalid peerId should throw error') }) it('returns undefined if no multiaddrs are known for the provided peer', () => { @@ -318,9 +350,13 @@ describe('addressBook', () => { }) it('throwns invalid parameters error if invalid PeerId is provided', () => { - expect(() => { + try { ab.delete('invalid peerId') - }).to.throw(ERR_INVALID_PARAMETERS) + } catch (err) { + expect(err.code).to.equal(ERR_INVALID_PARAMETERS) + return + } + throw new Error('invalid peerId should throw error') }) it('returns false if no records exist for the peer and no event is emitted', () => { diff --git a/test/peer-store/peer-store.spec.js b/test/peer-store/peer-store.spec.js index c4f9fbed39..847d7ae13f 100644 --- a/test/peer-store/peer-store.spec.js +++ b/test/peer-store/peer-store.spec.js @@ -4,9 +4,11 @@ const chai = require('chai') chai.use(require('dirty-chai')) const { expect } = chai +const sinon = require('sinon') const PeerStore = require('../../src/peer-store') const multiaddr = require('multiaddr') +const { MemoryDatastore } = require('interface-datastore') const peerUtils = require('../utils/creators/peer') @@ -150,3 +152,157 @@ describe('peer-store', () => { }) }) }) + +describe('libp2p.peerStore', () => { + let libp2p + let memoryDatastore + + beforeEach(async () => { + memoryDatastore = new MemoryDatastore() + ;[libp2p] = await peerUtils.createPeer({ + started: false, + config: { + datastore: memoryDatastore, + peerStore: { + persistance: true + } + } + }) + }) + + it('should try to load content from an empty datastore', async () => { + const spyPeerStore = sinon.spy(libp2p.peerStore, 'start') + const spyDs = sinon.spy(memoryDatastore, 'query') + + await libp2p.start() + expect(spyPeerStore).to.have.property('callCount', 1) + // Should be called for AddressBook and ProtoBook + expect(spyDs).to.have.property('callCount', 2) + // No data to populate + expect(libp2p.peerStore.peers.size).to.eq(0) + }) + + it('should store peerStore content on datastore', async () => { + const [peer] = await peerUtils.createPeerId({ number: 2 }) + const multiaddrs = [multiaddr('/ip4/156.10.1.22/tcp/1000')] + const protocols = ['/ping/1.0.0'] + const spyDs = sinon.spy(memoryDatastore, 'put') + + await libp2p.start() + + // AddressBook + await libp2p.peerStore.addressBook.set(peer, multiaddrs) + + expect(spyDs).to.have.property('callCount', 1) + + // ProtoBook + await libp2p.peerStore.protoBook.set(peer, protocols) + + expect(spyDs).to.have.property('callCount', 2) + + // Should have two peer records stored in the datastore + const queryParams = { + prefix: '/peers/' + } + let count = 0 + for await (const _ of memoryDatastore.query(queryParams)) { // eslint-disable-line + count++ + } + expect(count).to.equal(2) + }) + + it('should load content to the peerStore when restart but not put in datastore again', async () => { + const spyDs = sinon.spy(memoryDatastore, 'put') + const peers = await peerUtils.createPeerId({ number: 2 }) + const multiaddrs = [ + multiaddr('/ip4/156.10.1.22/tcp/1000'), + multiaddr('/ip4/156.10.1.23/tcp/1000') + ] + const protocols = ['/ping/1.0.0'] + + await libp2p.start() + + // AddressBook + await libp2p.peerStore.addressBook.set(peers[0], [multiaddrs[0]]) + await libp2p.peerStore.addressBook.set(peers[1], [multiaddrs[1]]) + + // ProtoBook + await libp2p.peerStore.protoBook.set(peers[0], protocols) + await libp2p.peerStore.protoBook.set(peers[1], protocols) + + expect(spyDs).to.have.property('callCount', 4) + expect(libp2p.peerStore.peers.size).to.equal(2) + + await libp2p.stop() + + // Load on restart + const spyAb = sinon.spy(libp2p.peerStore.addressBook, '_loadData') + const spyPb = sinon.spy(libp2p.peerStore.protoBook, '_loadData') + + await libp2p.start() + + expect(spyAb).to.have.property('callCount', 1) + expect(spyPb).to.have.property('callCount', 1) + expect(spyDs).to.have.property('callCount', 4) + expect(libp2p.peerStore.peers.size).to.equal(2) + }) + + it('should load content to the peerStore when a new node is started with the same datastore', async () => { + const peers = await peerUtils.createPeerId({ number: 2 }) + const multiaddrs = [ + multiaddr('/ip4/156.10.1.22/tcp/1000'), + multiaddr('/ip4/156.10.1.23/tcp/1000') + ] + const protocols = ['/ping/1.0.0'] + + await libp2p.start() + + // AddressBook + await libp2p.peerStore.addressBook.set(peers[0], [multiaddrs[0]]) + await libp2p.peerStore.addressBook.set(peers[1], [multiaddrs[1]]) + + // ProtoBook + await libp2p.peerStore.protoBook.set(peers[0], protocols) + await libp2p.peerStore.protoBook.set(peers[1], protocols) + + expect(libp2p.peerStore.peers.size).to.equal(2) + + await libp2p.stop() + + // Use a new node with the previously populated datastore + const [newNode] = await peerUtils.createPeer({ + started: false, + config: { + datastore: memoryDatastore, + peerStore: { + persistance: true + } + } + }) + + expect(newNode.peerStore.peers.size).to.equal(0) + + const spyAb = sinon.spy(newNode.peerStore.addressBook, '_loadData') + const spyPb = sinon.spy(newNode.peerStore.protoBook, '_loadData') + + await newNode.start() + + expect(spyAb).to.have.property('callCount', 1) + expect(spyPb).to.have.property('callCount', 1) + + expect(newNode.peerStore.peers.size).to.equal(2) + + // Validate data + const peer0 = newNode.peerStore.get(peers[0]) + expect(peer0.id.toB58String()).to.eql(peers[0].toB58String()) + expect(peer0.protocols).to.have.members(protocols) + expect(peer0.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[0].toString()]) + + const peer1 = newNode.peerStore.get(peers[1]) + expect(peer1.id.toB58String()).to.eql(peers[1].toB58String()) + expect(peer1.protocols).to.have.members(protocols) + expect(peer1.addresses.map((a) => a.multiaddr.toString())).to.have.members([multiaddrs[1].toString()]) + + await newNode.stop() + }) +}) diff --git a/test/utils/creators/peer.js b/test/utils/creators/peer.js index db0026f48d..947bd99f2b 100644 --- a/test/utils/creators/peer.js +++ b/test/utils/creators/peer.js @@ -28,6 +28,9 @@ async function createPeer ({ number = 1, fixture = true, started = true, populat const peers = await pTimes(number, (i) => Libp2p.create({ peerId: peerIds[i], addresses, + peerStore: { + persistence: false + }, ...defaultOptions, ...config }))