chore: store self protocols in protobook (#760)

This commit is contained in:
Vasco Santos 2020-10-27 13:50:14 +00:00
parent 20d9d98b3a
commit 186f9b758e
6 changed files with 258 additions and 100 deletions

View File

@ -37,6 +37,7 @@
* [`peerStore.protoBook.add`](#peerstoreprotobookadd) * [`peerStore.protoBook.add`](#peerstoreprotobookadd)
* [`peerStore.protoBook.delete`](#peerstoreprotobookdelete) * [`peerStore.protoBook.delete`](#peerstoreprotobookdelete)
* [`peerStore.protoBook.get`](#peerstoreprotobookget) * [`peerStore.protoBook.get`](#peerstoreprotobookget)
* [`peerStore.protoBook.remove`](#peerstoreprotobookremove)
* [`peerStore.protoBook.set`](#peerstoreprotobookset) * [`peerStore.protoBook.set`](#peerstoreprotobookset)
* [`peerStore.delete`](#peerstoredelete) * [`peerStore.delete`](#peerstoredelete)
* [`peerStore.get`](#peerstoreget) * [`peerStore.get`](#peerstoreget)
@ -875,32 +876,6 @@ Consider using `addressBook.add()` if you're not sure this is what you want to d
peerStore.addressBook.add(peerId, multiaddr) peerStore.addressBook.add(peerId, multiaddr)
``` ```
### peerStore.protoBook.add
Add known `protocols` of a given peer.
`peerStore.protoBook.add(peerId, protocols)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | peerId to set |
| protocols | `Array<string>` | protocols to add |
#### Returns
| Type | Description |
|------|-------------|
| `ProtoBook` | Returns the Proto Book component |
#### Example
```js
peerStore.protoBook.add(peerId, protocols)
```
### peerStore.keyBook.delete ### peerStore.keyBook.delete
Delete the provided peer from the book. Delete the provided peer from the book.
@ -1123,6 +1098,31 @@ Set known metadata of a given `peerId`.
peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Berlin')) peerStore.metadataBook.set(peerId, 'location', uint8ArrayFromString('Berlin'))
``` ```
### peerStore.protoBook.add
Add known `protocols` of a given peer.
`peerStore.protoBook.add(peerId, protocols)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | peerId to set |
| protocols | `Array<string>` | protocols to add |
#### Returns
| Type | Description |
|------|-------------|
| `ProtoBook` | Returns the Proto Book component |
#### Example
```js
peerStore.protoBook.add(peerId, protocols)
```
### peerStore.protoBook.delete ### peerStore.protoBook.delete
Delete the provided peer from the book. Delete the provided peer from the book.
@ -1179,6 +1179,31 @@ peerStore.protoBook.get(peerId)
// [ '/proto/1.0.0', '/proto/1.1.0' ] // [ '/proto/1.0.0', '/proto/1.1.0' ]
``` ```
### peerStore.protoBook.remove
Remove given `protocols` of a given peer.
`peerStore.protoBook.remove(peerId, protocols)`
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| peerId | [`PeerId`][peer-id] | peerId to set |
| protocols | `Array<string>` | protocols to remove |
#### Returns
| Type | Description |
|------|-------------|
| `ProtoBook` | Returns the Proto Book component |
#### Example
```js
peerStore.protoBook.remove(peerId, protocols)
```
### peerStore.protoBook.set ### peerStore.protoBook.set
Set known `protocols` of a given peer. Set known `protocols` of a given peer.

View File

@ -51,9 +51,8 @@ class IdentifyService {
* @class * @class
* @param {object} options * @param {object} options
* @param {Libp2p} options.libp2p * @param {Libp2p} options.libp2p
* @param {Map<string, handler>} options.protocols - A reference to the protocols we support
*/ */
constructor ({ libp2p, protocols }) { constructor ({ libp2p }) {
/** /**
* @property {PeerStore} * @property {PeerStore}
*/ */
@ -74,8 +73,6 @@ class IdentifyService {
*/ */
this._libp2p = libp2p this._libp2p = libp2p
this._protocols = protocols
this.handleMessage = this.handleMessage.bind(this) this.handleMessage = this.handleMessage.bind(this)
// Store self host metadata // Store self host metadata
@ -97,6 +94,13 @@ class IdentifyService {
this.pushToPeerStore() this.pushToPeerStore()
} }
}) })
// When self protocols change, trigger identify-push
this.peerStore.on('change:protocols', ({ peerId }) => {
if (peerId.toString() === this.peerId.toString()) {
this.pushToPeerStore()
}
})
} }
/** /**
@ -108,7 +112,7 @@ class IdentifyService {
async push (connections) { async push (connections) {
const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId)
const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes) const listenAddrs = this._libp2p.multiaddrs.map((ma) => ma.bytes)
const protocols = Array.from(this._protocols.keys()) const protocols = this.peerStore.protoBook.get(this.peerId) || []
const pushes = connections.map(async connection => { const pushes = connections.map(async connection => {
try { try {
@ -139,6 +143,11 @@ class IdentifyService {
* @returns {void} * @returns {void}
*/ */
pushToPeerStore () { pushToPeerStore () {
// Do not try to push if libp2p node is not running
if (!this._libp2p.isStarted()) {
return
}
const connections = [] const connections = []
let connection let connection
for (const peer of this.peerStore.peers.values()) { for (const peer of this.peerStore.peers.values()) {
@ -258,6 +267,7 @@ class IdentifyService {
} }
const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId) const signedPeerRecord = await this.peerStore.addressBook.getRawEnvelope(this.peerId)
const protocols = this.peerStore.protoBook.get(this.peerId) || []
const message = Message.encode({ const message = Message.encode({
protocolVersion: this._host.protocolVersion, protocolVersion: this._host.protocolVersion,
@ -266,7 +276,7 @@ class IdentifyService {
listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes), listenAddrs: this._libp2p.multiaddrs.map((ma) => ma.bytes),
signedPeerRecord, signedPeerRecord,
observedAddr: connection.remoteAddr.bytes, observedAddr: connection.remoteAddr.bytes,
protocols: Array.from(this._protocols.keys()) protocols
}) })
try { try {

View File

@ -158,10 +158,7 @@ class Libp2p extends EventEmitter {
}) })
// Add the identify service since we can multiplex // Add the identify service since we can multiplex
this.identifyService = new IdentifyService({ this.identifyService = new IdentifyService({ libp2p: this })
libp2p: this,
protocols: this.upgrader.protocols
})
this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage) this.handle(Object.values(IDENTIFY_PROTOCOLS), this.identifyService.handleMessage)
} }
@ -442,10 +439,8 @@ class Libp2p extends EventEmitter {
this.upgrader.protocols.set(protocol, handler) this.upgrader.protocols.set(protocol, handler)
}) })
// Only push if libp2p is running // Add new protocols to self protocols in the Protobook
if (this.isStarted() && this.identifyService) { this.peerStore.protoBook.add(this.peerId, protocols)
this.identifyService.pushToPeerStore()
}
} }
/** /**
@ -460,10 +455,8 @@ class Libp2p extends EventEmitter {
this.upgrader.protocols.delete(protocol) this.upgrader.protocols.delete(protocol)
}) })
// Only push if libp2p is running // Remove protocols from self protocols in the Protobook
if (this.isStarted() && this.identifyService) { this.peerStore.protoBook.remove(this.peerId, protocols)
this.identifyService.pushToPeerStore()
}
} }
async _onStarting () { async _onStarting () {

View File

@ -112,13 +112,50 @@ class ProtoBook extends Book {
return this return this
} }
protocols = [...newSet]
this._setData(peerId, newSet) this._setData(peerId, newSet)
log(`added provided protocols for ${id}`) log(`added provided protocols for ${id}`)
return this return this
} }
/**
* Removes known protocols of a provided peer.
* If the protocols did not exist before, nothing will be done.
*
* @param {PeerId} peerId
* @param {Array<string>} protocols
* @returns {ProtoBook}
*/
remove (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)
if (recSet) {
const newSet = new Set([
...recSet
].filter((p) => !protocols.includes(p)))
// Any protocol removed?
if (recSet.size === newSet.size) {
return this
}
this._setData(peerId, newSet)
log(`removed provided protocols for ${id}`)
}
return this
}
} }
module.exports = ProtoBook module.exports = ProtoBook

View File

@ -29,18 +29,21 @@ const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')] const listenMaddrs = [multiaddr('/ip4/127.0.0.1/tcp/15002/ws')]
describe('Identify', () => { describe('Identify', () => {
let localPeer let localPeer, localPeerStore
let remotePeer let remotePeer, remotePeerStore
const protocols = new Map([ const protocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH]
[multicodecs.IDENTIFY, () => {}],
[multicodecs.IDENTIFY_PUSH, () => {}]
])
before(async () => { before(async () => {
[localPeer, remotePeer] = (await Promise.all([ [localPeer, remotePeer] = (await Promise.all([
PeerId.createFromJSON(Peers[0]), PeerId.createFromJSON(Peers[0]),
PeerId.createFromJSON(Peers[1]) PeerId.createFromJSON(Peers[1])
])) ]))
localPeerStore = new PeerStore({ peerId: localPeer })
localPeerStore.protoBook.set(localPeer, protocols)
remotePeerStore = new PeerStore({ peerId: remotePeer })
remotePeerStore.protoBook.set(remotePeer, protocols)
}) })
afterEach(() => { afterEach(() => {
@ -52,22 +55,19 @@ describe('Identify', () => {
libp2p: { libp2p: {
peerId: localPeer, peerId: localPeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }), peerStore: localPeerStore,
multiaddrs: listenMaddrs, multiaddrs: listenMaddrs,
_options: { host: {} } isStarted: () => true
}, }
protocols
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { libp2p: {
peerId: remotePeer, peerId: remotePeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: remotePeer }), peerStore: remotePeerStore,
multiaddrs: listenMaddrs, multiaddrs: listenMaddrs,
_options: { host: {} } isStarted: () => true
}, }
protocols
}) })
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@ -110,22 +110,20 @@ describe('Identify', () => {
libp2p: { libp2p: {
peerId: localPeer, peerId: localPeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }), peerStore: localPeerStore,
multiaddrs: listenMaddrs, multiaddrs: listenMaddrs,
_options: { host: {} } isStarted: () => true
}, }
protocols
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { libp2p: {
peerId: remotePeer, peerId: remotePeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: remotePeer }), peerStore: remotePeerStore,
multiaddrs: listenMaddrs, multiaddrs: listenMaddrs,
_options: { host: {} } isStarted: () => true
}, }
protocols
}) })
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@ -171,21 +169,17 @@ describe('Identify', () => {
libp2p: { libp2p: {
peerId: localPeer, peerId: localPeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }), peerStore: localPeerStore,
multiaddrs: [], multiaddrs: []
_options: { host: {} } }
},
protocols
}) })
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { libp2p: {
peerId: remotePeer, peerId: remotePeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: remotePeer }), peerStore: remotePeerStore,
multiaddrs: [], multiaddrs: []
_options: { host: {} } }
},
protocols
}) })
const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234') const observedAddr = multiaddr('/ip4/127.0.0.1/tcp/1234')
@ -242,35 +236,38 @@ describe('Identify', () => {
describe('push', () => { describe('push', () => {
it('should be able to push identify updates to another peer', async () => { it('should be able to push identify updates to another peer', async () => {
const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0']
const connectionManager = new EventEmitter() const connectionManager = new EventEmitter()
connectionManager.getConnection = () => { } connectionManager.getConnection = () => { }
const localPeerStore = new PeerStore({ peerId: localPeer })
localPeerStore.protoBook.set(localPeer, storedProtocols)
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: { libp2p: {
peerId: localPeer, peerId: localPeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }), peerStore: localPeerStore,
multiaddrs: listenMaddrs, multiaddrs: listenMaddrs,
_options: { host: {} } isStarted: () => true
}, }
protocols: new Map([
[multicodecs.IDENTIFY],
[multicodecs.IDENTIFY_PUSH],
['/echo/1.0.0']
])
}) })
const remotePeerStore = new PeerStore({ peerId: remotePeer })
remotePeerStore.protoBook.set(remotePeer, storedProtocols)
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { libp2p: {
peerId: remotePeer, peerId: remotePeer,
connectionManager, connectionManager,
peerStore: new PeerStore({ peerId: remotePeer }), peerStore: remotePeerStore,
multiaddrs: [], multiaddrs: [],
_options: { host: {} } isStarted: () => true
} }
}) })
// Setup peer protocols and multiaddrs // Setup peer protocols and multiaddrs
const localProtocols = new Set([multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0']) const localProtocols = new Set(storedProtocols)
const localConnectionMock = { newStream: () => { } } const localConnectionMock = { newStream: () => { } }
const remoteConnectionMock = { remotePeer: localPeer } const remoteConnectionMock = { remotePeer: localPeer }
@ -309,35 +306,39 @@ describe('Identify', () => {
// LEGACY // LEGACY
it('should be able to push identify updates to another peer with no certified peer records support', async () => { it('should be able to push identify updates to another peer with no certified peer records support', async () => {
const storedProtocols = [multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0']
const connectionManager = new EventEmitter() const connectionManager = new EventEmitter()
connectionManager.getConnection = () => { } connectionManager.getConnection = () => { }
const localPeerStore = new PeerStore({ peerId: localPeer })
localPeerStore.protoBook.set(localPeer, storedProtocols)
const localIdentify = new IdentifyService({ const localIdentify = new IdentifyService({
libp2p: { libp2p: {
peerId: localPeer, peerId: localPeer,
connectionManager: new EventEmitter(), connectionManager: new EventEmitter(),
peerStore: new PeerStore({ peerId: localPeer }), peerStore: localPeerStore,
multiaddrs: listenMaddrs, multiaddrs: listenMaddrs,
_options: { host: {} } isStarted: () => true
}, }
protocols: new Map([
[multicodecs.IDENTIFY],
[multicodecs.IDENTIFY_PUSH],
['/echo/1.0.0']
])
}) })
const remotePeerStore = new PeerStore({ peerId: remotePeer })
remotePeerStore.protoBook.set(remotePeer, storedProtocols)
const remoteIdentify = new IdentifyService({ const remoteIdentify = new IdentifyService({
libp2p: { libp2p: {
peerId: remotePeer, peerId: remotePeer,
connectionManager, connectionManager,
peerStore: new PeerStore({ peerId: remotePeer }), peerStore: new PeerStore({ peerId: remotePeer }),
multiaddrs: [], multiaddrs: [],
_options: { host: {} } _options: { host: {} },
isStarted: () => true
} }
}) })
// Setup peer protocols and multiaddrs // Setup peer protocols and multiaddrs
const localProtocols = new Set([multicodecs.IDENTIFY, multicodecs.IDENTIFY_PUSH, '/echo/1.0.0']) const localProtocols = new Set(storedProtocols)
const localConnectionMock = { newStream: () => {} } const localConnectionMock = { newStream: () => {} }
const remoteConnectionMock = { remotePeer: localPeer } const remoteConnectionMock = { remotePeer: localPeer }

View File

@ -5,7 +5,9 @@ const chai = require('chai')
chai.use(require('dirty-chai')) chai.use(require('dirty-chai'))
const { expect } = chai const { expect } = chai
const sinon = require('sinon')
const pDefer = require('p-defer') const pDefer = require('p-defer')
const pWaitFor = require('p-wait-for')
const PeerStore = require('../../src/peer-store') const PeerStore = require('../../src/peer-store')
@ -224,6 +226,96 @@ describe('protoBook', () => {
}) })
}) })
describe('protoBook.remove', () => {
let peerStore, pb
beforeEach(() => {
peerStore = new PeerStore({ peerId })
pb = peerStore.protoBook
})
afterEach(() => {
peerStore.removeAllListeners()
})
it('throws invalid parameters error if invalid PeerId is provided', () => {
expect(() => {
pb.remove('invalid peerId')
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('throws invalid parameters error if no protocols provided', () => {
expect(() => {
pb.remove(peerId)
}).to.throw(ERR_INVALID_PARAMETERS)
})
it('removes the given protocol and emits change event', async () => {
const spy = sinon.spy()
const supportedProtocols = ['protocol1', 'protocol2']
const removedProtocols = ['protocol1']
const finalProtocols = supportedProtocols.filter(p => !removedProtocols.includes(p))
peerStore.on('change:protocols', spy)
// Replace
pb.set(peerId, supportedProtocols)
let protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(supportedProtocols)
// Remove
pb.remove(peerId, removedProtocols)
protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(finalProtocols)
await pWaitFor(() => spy.callCount === 2)
const [firstCallArgs] = spy.firstCall.args
const [secondCallArgs] = spy.secondCall.args
expect(arraysAreEqual(firstCallArgs.protocols, supportedProtocols))
expect(arraysAreEqual(secondCallArgs.protocols, finalProtocols))
})
it('emits on remove if the content changes', () => {
const spy = sinon.spy()
const supportedProtocols = ['protocol1', 'protocol2']
const removedProtocols = ['protocol2']
const finalProtocols = supportedProtocols.filter(p => !removedProtocols.includes(p))
peerStore.on('change:protocols', spy)
// set
pb.set(peerId, supportedProtocols)
// remove (content already existing)
pb.remove(peerId, removedProtocols)
const protocols = pb.get(peerId)
expect(protocols).to.have.deep.members(finalProtocols)
return pWaitFor(() => spy.callCount === 2)
})
it('does not emit on remove if the content does not change', () => {
const spy = sinon.spy()
const supportedProtocols = ['protocol1', 'protocol2']
const removedProtocols = ['protocol3']
peerStore.on('change:protocols', spy)
// set
pb.set(peerId, supportedProtocols)
// remove
pb.remove(peerId, removedProtocols)
// Only one event
expect(spy.callCount).to.eql(1)
})
})
describe('protoBook.get', () => { describe('protoBook.get', () => {
let peerStore, pb let peerStore, pb