mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-31 00:12:00 +00:00
feat: metadata book (#638)
* feat: metadata book * chore: address review * chore: address review
This commit is contained in:
@@ -75,7 +75,11 @@ A `peerId.toB58String()` identifier mapping to a `Set` of protocol identifier st
|
||||
|
||||
#### Metadata Book
|
||||
|
||||
**Not Yet Implemented**
|
||||
The `metadataBook` keeps track of the known metadata of a peer. Its metadata is stored in a key value fashion, where a key identifier (`string`) represents a metadata value (`Buffer`).
|
||||
|
||||
`Map<string, Map<string, Buffer>>`
|
||||
|
||||
A `peerId.toB58String()` identifier mapping to the peer metadata Map.
|
||||
|
||||
### API
|
||||
|
||||
@@ -85,6 +89,7 @@ Access to its underlying books:
|
||||
|
||||
- `peerStore.addressBook.*`
|
||||
- `peerStore.keyBook.*`
|
||||
- `peerStore.metadataBook.*`
|
||||
- `peerStore.protoBook.*`
|
||||
|
||||
### Events
|
||||
@@ -92,6 +97,8 @@ Access to its underlying books:
|
||||
- `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.
|
||||
- `change:pubkey` - emitted when a peer's public key is known.
|
||||
- `change:metadata` - emitted when known metadata of a peer changes.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
@@ -123,8 +130,6 @@ All public keys are stored under the following pattern:
|
||||
|
||||
**MetadataBook**
|
||||
|
||||
_NOT_YET_IMPLEMENTED_
|
||||
|
||||
Metadata is stored under the following key pattern:
|
||||
|
||||
`/peers/metadata/<b32 peer id no padding>/<key>`
|
||||
|
@@ -97,7 +97,6 @@ class AddressBook extends Book {
|
||||
/**
|
||||
* 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<Multiaddr>} multiaddrs
|
||||
* @returns {AddressBook}
|
||||
|
@@ -10,6 +10,7 @@ const PeerId = require('peer-id')
|
||||
|
||||
const AddressBook = require('./address-book')
|
||||
const KeyBook = require('./key-book')
|
||||
const MetadataBook = require('./metadata-book')
|
||||
const ProtoBook = require('./proto-book')
|
||||
|
||||
const {
|
||||
@@ -21,6 +22,8 @@ const {
|
||||
* @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.
|
||||
* @fires PeerStore#change:pubkey Emitted emitted when a peer's public key is known.
|
||||
* @fires PeerStore#change:metadata Emitted when the known metadata of a peer change.
|
||||
*/
|
||||
class PeerStore extends EventEmitter {
|
||||
/**
|
||||
@@ -47,6 +50,11 @@ class PeerStore extends EventEmitter {
|
||||
*/
|
||||
this.keyBook = new KeyBook(this)
|
||||
|
||||
/**
|
||||
* MetadataBook containing a map of peerIdStr to their metadata Map.
|
||||
*/
|
||||
this.metadataBook = new MetadataBook(this)
|
||||
|
||||
/**
|
||||
* ProtoBook containing a map of peerIdStr to supported protocols.
|
||||
*/
|
||||
@@ -68,31 +76,17 @@ class PeerStore extends EventEmitter {
|
||||
* @returns {Map<string, Peer>}
|
||||
*/
|
||||
get peers () {
|
||||
const storedPeers = new Set([
|
||||
...this.addressBook.data.keys(),
|
||||
...this.keyBook.data.keys(),
|
||||
...this.protoBook.data.keys(),
|
||||
...this.metadataBook.data.keys()
|
||||
])
|
||||
|
||||
const peersData = new Map()
|
||||
|
||||
// AddressBook
|
||||
for (const [idStr, addresses] of this.addressBook.data.entries()) {
|
||||
const id = this.keyBook.data.get(idStr) || PeerId.createFromCID(idStr)
|
||||
peersData.set(idStr, {
|
||||
id,
|
||||
addresses,
|
||||
protocols: this.protoBook.get(id) || []
|
||||
})
|
||||
}
|
||||
|
||||
// ProtoBook
|
||||
for (const [idStr, protocols] of this.protoBook.data.entries()) {
|
||||
const pData = peersData.get(idStr)
|
||||
const id = this.keyBook.data.get(idStr) || PeerId.createFromCID(idStr)
|
||||
|
||||
if (!pData) {
|
||||
peersData.set(idStr, {
|
||||
id,
|
||||
addresses: [],
|
||||
protocols: Array.from(protocols)
|
||||
})
|
||||
}
|
||||
}
|
||||
storedPeers.forEach((idStr) => {
|
||||
peersData.set(idStr, this.get(PeerId.createFromCID(idStr)))
|
||||
})
|
||||
|
||||
return peersData
|
||||
}
|
||||
@@ -106,8 +100,9 @@ class PeerStore extends EventEmitter {
|
||||
const addressesDeleted = this.addressBook.delete(peerId)
|
||||
const keyDeleted = this.keyBook.delete(peerId)
|
||||
const protocolsDeleted = this.protoBook.delete(peerId)
|
||||
const metadataDeleted = this.metadataBook.delete(peerId)
|
||||
|
||||
return addressesDeleted || keyDeleted || protocolsDeleted
|
||||
return addressesDeleted || keyDeleted || protocolsDeleted || metadataDeleted
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,16 +117,18 @@ class PeerStore extends EventEmitter {
|
||||
|
||||
const id = this.keyBook.data.get(peerId.toB58String())
|
||||
const addresses = this.addressBook.get(peerId)
|
||||
const metadata = this.metadataBook.get(peerId)
|
||||
const protocols = this.protoBook.get(peerId)
|
||||
|
||||
if (!addresses && !protocols) {
|
||||
if (!id && !addresses && !metadata && !protocols) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return {
|
||||
id: id || peerId,
|
||||
addresses: addresses || [],
|
||||
protocols: protocols || []
|
||||
protocols: protocols || [],
|
||||
metadata: metadata
|
||||
}
|
||||
}
|
||||
}
|
||||
|
161
src/peer-store/metadata-book.js
Normal file
161
src/peer-store/metadata-book.js
Normal file
@@ -0,0 +1,161 @@
|
||||
'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 { Buffer } = require('buffer')
|
||||
|
||||
const PeerId = require('peer-id')
|
||||
|
||||
const Book = require('./book')
|
||||
|
||||
const {
|
||||
codes: { ERR_INVALID_PARAMETERS }
|
||||
} = require('../errors')
|
||||
|
||||
/**
|
||||
* The MetadataBook is responsible for keeping the known supported
|
||||
* protocols of a peer.
|
||||
* @fires MetadataBook#change:metadata
|
||||
*/
|
||||
class MetadataBook extends Book {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {PeerStore} peerStore
|
||||
*/
|
||||
constructor (peerStore) {
|
||||
/**
|
||||
* PeerStore Event emitter, used by the MetadataBook to emit:
|
||||
* "change:metadata" - emitted when the known metadata of a peer change.
|
||||
*/
|
||||
super({
|
||||
peerStore,
|
||||
eventName: 'change:metadata',
|
||||
eventProperty: 'metadata'
|
||||
})
|
||||
|
||||
/**
|
||||
* Map known peers to their known protocols.
|
||||
* @type {Map<string, Map<string, Buffer>>}
|
||||
*/
|
||||
this.data = new Map()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set metadata key and value of a provided peer.
|
||||
* @override
|
||||
* @param {PeerId} peerId
|
||||
* @param {string} key metadata key
|
||||
* @param {Buffer} value metadata value
|
||||
* @returns {ProtoBook}
|
||||
*/
|
||||
set (peerId, key, value) {
|
||||
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 (typeof key !== 'string' || !Buffer.isBuffer(value)) {
|
||||
log.error('valid key and value must be provided to store data')
|
||||
throw errcode(new Error('valid key and value must be provided'), ERR_INVALID_PARAMETERS)
|
||||
}
|
||||
|
||||
this._setValue(peerId, key, value)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data into the datastructure
|
||||
* @override
|
||||
*/
|
||||
_setValue (peerId, key, value, { emit = true } = {}) {
|
||||
const id = peerId.toB58String()
|
||||
const rec = this.data.get(id) || new Map()
|
||||
const recMap = rec.get(key)
|
||||
|
||||
// Already exists and is equal
|
||||
if (recMap && value.equals(recMap)) {
|
||||
log(`the metadata provided to store is equal to the already stored for ${id} on ${key}`)
|
||||
return
|
||||
}
|
||||
|
||||
rec.set(key, value)
|
||||
this.data.set(id, rec)
|
||||
|
||||
emit && this._emit(peerId, key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the known data of a provided peer.
|
||||
* @param {PeerId} peerId
|
||||
* @returns {Map<string, Buffer>}
|
||||
*/
|
||||
get (peerId) {
|
||||
if (!PeerId.isPeerId(peerId)) {
|
||||
throw errcode(new Error('peerId must be an instance of peer-id'), ERR_INVALID_PARAMETERS)
|
||||
}
|
||||
|
||||
return this.data.get(peerId.toB58String())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific metadata value, if it exists
|
||||
* @param {PeerId} peerId
|
||||
* @param {string} key
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
getValue (peerId, key) {
|
||||
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.toB58String())
|
||||
return rec && rec.get(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.toB58String())) {
|
||||
return false
|
||||
}
|
||||
|
||||
this._emit(peerId)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the provided peer metadata key from the book.
|
||||
* @param {PeerId} peerId
|
||||
* @param {string} key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
deleteValue (peerId, key) {
|
||||
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.toB58String())
|
||||
|
||||
if (!rec || !rec.delete(key)) {
|
||||
return false
|
||||
}
|
||||
|
||||
this._emit(peerId, key)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MetadataBook
|
@@ -8,5 +8,8 @@ module.exports.NAMESPACE_ADDRESS = '/peers/addrs/'
|
||||
// /peers/keys/<b32 peer id no padding>
|
||||
module.exports.NAMESPACE_KEYS = '/peers/keys/'
|
||||
|
||||
// /peers/metadata/<b32 peer id no padding>/<key>
|
||||
module.exports.NAMESPACE_METADATA = '/peers/metadata/'
|
||||
|
||||
// /peers/addrs/<b32 peer id no padding>
|
||||
module.exports.NAMESPACE_PROTOCOL = '/peers/protos/'
|
||||
|
@@ -14,6 +14,7 @@ const {
|
||||
NAMESPACE_ADDRESS,
|
||||
NAMESPACE_COMMON,
|
||||
NAMESPACE_KEYS,
|
||||
NAMESPACE_METADATA,
|
||||
NAMESPACE_PROTOCOL
|
||||
} = require('./consts')
|
||||
|
||||
@@ -43,6 +44,12 @@ class PersistentPeerStore extends PeerStore {
|
||||
*/
|
||||
this._dirtyPeers = new Set()
|
||||
|
||||
/**
|
||||
* Peers metadata changed mapping peer identifers to metadata changed.
|
||||
* @type {Map<string, Set<string>>}
|
||||
*/
|
||||
this._dirtyMetadata = new Map()
|
||||
|
||||
this.threshold = threshold
|
||||
this._addDirtyPeer = this._addDirtyPeer.bind(this)
|
||||
}
|
||||
@@ -58,6 +65,7 @@ class PersistentPeerStore extends PeerStore {
|
||||
this.on('change:protocols', this._addDirtyPeer)
|
||||
this.on('change:multiaddrs', this._addDirtyPeer)
|
||||
this.on('change:pubkey', this._addDirtyPeer)
|
||||
this.on('change:metadata', this._addDirtyPeerMetadata)
|
||||
|
||||
// Load data
|
||||
for await (const entry of this._datastore.query({ prefix: NAMESPACE_COMMON })) {
|
||||
@@ -92,6 +100,30 @@ class PersistentPeerStore extends PeerStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add modified metadata peer to the set.
|
||||
* @private
|
||||
* @param {Object} params
|
||||
* @param {PeerId} params.peerId
|
||||
* @param {string} params.metadata
|
||||
*/
|
||||
_addDirtyPeerMetadata ({ peerId, metadata }) {
|
||||
const peerIdstr = peerId.toB58String()
|
||||
|
||||
log('add dirty metadata peer', peerIdstr)
|
||||
this._dirtyPeers.add(peerIdstr)
|
||||
|
||||
// Add dirty metadata key
|
||||
const mData = this._dirtyMetadata.get(peerIdstr) || new Set()
|
||||
mData.add(metadata)
|
||||
this._dirtyMetadata.set(peerIdstr, mData)
|
||||
|
||||
if (this._dirtyPeers.size >= this.threshold) {
|
||||
// Commit current data
|
||||
this._commitData()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the peers current data to a datastore batch and commit it.
|
||||
* @private
|
||||
@@ -120,6 +152,9 @@ class PersistentPeerStore extends PeerStore {
|
||||
// Key Book
|
||||
this._batchKeyBook(peerId, batch)
|
||||
|
||||
// Metadata Book
|
||||
this._batchMetadataBook(peerId, batch)
|
||||
|
||||
// Proto Book
|
||||
this._batchProtoBook(peerId, batch)
|
||||
}
|
||||
@@ -184,6 +219,32 @@ class PersistentPeerStore extends PeerStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add metadata book data of the peer to the batch.
|
||||
* @private
|
||||
* @param {PeerId} peerId
|
||||
* @param {Object} batch
|
||||
*/
|
||||
_batchMetadataBook (peerId, batch) {
|
||||
const b32key = peerId.toString()
|
||||
const dirtyMetada = this._dirtyMetadata.get(peerId.toB58String()) || []
|
||||
|
||||
try {
|
||||
dirtyMetada.forEach((dirtyKey) => {
|
||||
const key = new Key(`${NAMESPACE_METADATA}${b32key}/${dirtyKey}`)
|
||||
const dirtyValue = this.metadataBook.getValue(peerId, dirtyKey)
|
||||
|
||||
if (dirtyValue) {
|
||||
batch.put(key, dirtyValue)
|
||||
} else {
|
||||
batch.delete(key)
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add proto book data of the peer to the batch.
|
||||
* @private
|
||||
@@ -244,6 +305,13 @@ class PersistentPeerStore extends PeerStore {
|
||||
decoded,
|
||||
{ emit: false })
|
||||
break
|
||||
case 'metadata':
|
||||
this.metadataBook._setValue(
|
||||
peerId,
|
||||
keyParts[4],
|
||||
value,
|
||||
{ emit: false })
|
||||
break
|
||||
case 'protos':
|
||||
decoded = Protocols.decode(value)
|
||||
|
||||
|
@@ -83,7 +83,6 @@ class ProtoBook extends Book {
|
||||
/**
|
||||
* 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 {ProtoBook}
|
||||
|
Reference in New Issue
Block a user