diff --git a/.gitignore b/.gitignore
index 9a3cb9f..7ad9e67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
node_modules
+build
dist
.docs
.coverage
node_modules
package-lock.json
yarn.lock
+.vscode
diff --git a/README.md b/README.md
index b55bf4f..6d7dffe 100644
--- a/README.md
+++ b/README.md
@@ -11,26 +11,6 @@
- [Install](#install)
- [Browser `
```
-## Description
-
-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.
-
-### Submitting records to the PeerStore
-
-Several libp2p subsystems will perform operations that might gather relevant information about peers.
-
-#### Identify
-
-- The Identify protocol automatically runs on every connection when multiplexing is enabled. The protocol will put the multiaddrs and protocols provided by the peer to the PeerStore.
-- 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.
-- While it is currently not supported in js-libp2p, future iterations may also support the [IdentifyDelta protocol](https://github.com/libp2p/specs/pull/176).
-- Taking into account that the Identify protocol records are directly from the peer, they should be considered the source of truth and weighted accordingly.
-
-#### Peer Discovery
-
-- Libp2p discovery protocols aim to discover new peers in the network. In a typical discovery protocol, addresses of the peer are discovered along with its peer id. Once this happens, a libp2p discovery protocol should emit a `peer` event with the information of the discovered peer and this information will be added to the PeerStore by libp2p.
-
-#### Dialer
-
-- Libp2p API supports dialing a peer given a `multiaddr`, and no prior knowledge of the peer. If the node is able to establish a connection with the peer, it and its multiaddr is added to the PeerStore.
-- When a connection is being upgraded, more precisely after its encryption, or even in a discovery protocol, a libp2p node can get to know other parties public keys. In this scenario, libp2p will add the peer's public key to its `KeyBook`.
-
-#### DHT
-
-- On some DHT operations, such as finding providers for a given CID, nodes may exchange peer data as part of the query. This passive peer discovery should result in the DHT emitting the `peer` event in the same way [Peer Discovery](#peerdiscovery) does.
-
-### Retrieving records from the PeerStore
-
-When data in the PeerStore is updated the PeerStore will emit events based on the changes, to allow applications and other subsystems to take action on those changes. Any subsystem interested in these notifications should subscribe the [`PeerStore events`][peer-store-events].
-
-#### Peer
-
-- Each time a new peer is discovered, the PeerStore should emit a [`peer` event][peer-store-events], so that interested parties can leverage this peer and establish a connection with it.
-
-#### Protocols
-
-- When the known protocols of a peer change, the PeerStore emits a [`change:protocols` event][peer-store-events].
-
-#### Multiaddrs
-
-- When the known listening `multiaddrs` of a peer change, the PeerStore emits a [`change:multiaddrs` event][peer-store-events].
-
-### 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.
-
-### Components
-
-#### 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.
-
-`Map`
-
-A `peerId.toString()` identifier mapping to a `Address` object, which should have the following structure:
-
-```js
-{
- multiaddr:
-}
-```
-
-#### Key Book
-
-The `keyBook` tracks the public keys of the peers by keeping their [`PeerId`][peer-id].
-
-`Map>`
-
-A `peerId.toString()` identifier mapping to a `Set` of protocol identifier strings.
-
-#### Metadata Book
-
-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 (`Uint8Array`).
-
-`Map>`
-
-A `peerId.toString()` identifier mapping to the peer metadata Map.
-
-### API
-
-For the complete API documentation, you should check the [API.md](https://libp2p.github.io/js-libp2p-peer-store).
-
-Access to its underlying books:
-
-- `peerStore.addressBook.*`
-- `peerStore.keyBook.*`
-- `peerStore.metadataBook.*`
-- `peerStore.protoBook.*`
-
-### Events
-
-- `peer` - emitted when a new peer is added.
-- `change:multiaddrs` - 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
-
-The data stored in the PeerStore can be persisted if configured appropriately. 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.
-
-The libp2p node will need to receive a [datastore](https://github.com/ipfs/interface-datastore), in order to persist this data across restarts. 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.
-
-The PeerStore should not continuously update the datastore whenever data is changed. Instead, it should only store new data after reaching a certain threshold of "dirty" peers, as well as when the node is stopped, in order to batch writes to the datastore.
-
-The peer id will be appended to the datastore key for each data namespace. The namespaces were defined as follows:
-
-**AddressBook**
-
-All the known peer addresses are stored with a key pattern as follows:
-
-`/peers/addrs/`
-
-**ProtoBook**
-
-All the known peer protocols are stored with a key pattern as follows:
-
-`/peers/protos/`
-
-**KeyBook**
-
-All public keys are stored under the following pattern:
-
-` /peers/keys/`
-
-**MetadataBook**
-
-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.
-
## API Docs
-
diff --git a/package.json b/package.json
index 31c2337..4bb0cc2 100644
--- a/package.json
+++ b/package.json
@@ -131,7 +131,7 @@
"clean": "aegir clean",
"lint": "aegir lint",
"dep-check": "aegir dep-check -i protons",
- "generate": "protons src/pb/peer.proto src/pb/tags.proto",
+ "generate": "protons src/pb/*.proto",
"build": "aegir build",
"test": "aegir test",
"test:chrome": "aegir test -t browser --cov",
@@ -144,17 +144,16 @@
"docs": "aegir docs"
},
"dependencies": {
+ "@libp2p/crypto": "^1.0.15",
+ "@libp2p/interface-libp2p": "^2.0.0",
"@libp2p/interface-peer-id": "^2.0.0",
- "@libp2p/interface-peer-info": "^1.0.3",
- "@libp2p/interface-peer-store": "^1.2.2",
- "@libp2p/interface-record": "^2.0.1",
+ "@libp2p/interface-peer-store": "^2.0.1",
"@libp2p/interfaces": "^3.2.0",
- "@libp2p/logger": "^2.0.0",
+ "@libp2p/logger": "^2.0.7",
"@libp2p/peer-id": "^2.0.0",
- "@libp2p/peer-record": "^5.0.0",
"@multiformats/multiaddr": "^12.0.0",
"interface-datastore": "^8.0.0",
- "mortice": "^3.0.0",
+ "mortice": "^3.0.1",
"multiformats": "^11.0.0",
"protons-runtime": "^5.0.0",
"uint8arraylist": "^2.1.1",
@@ -162,12 +161,11 @@
},
"devDependencies": {
"@libp2p/peer-id-factory": "^2.0.0",
- "@libp2p/utils": "^3.0.2",
"aegir": "^38.1.6",
"datastore-core": "^9.0.1",
"delay": "^5.0.0",
"p-defer": "^4.0.0",
- "p-wait-for": "^5.0.0",
+ "p-event": "^5.0.1",
"protons": "^7.0.2",
"sinon": "^15.0.1"
}
diff --git a/src/address-book.ts b/src/address-book.ts
deleted file mode 100644
index 0552d8c..0000000
--- a/src/address-book.ts
+++ /dev/null
@@ -1,367 +0,0 @@
-import { logger } from '@libp2p/logger'
-import { CodeError } from '@libp2p/interfaces/errors'
-import { isMultiaddr } from '@multiformats/multiaddr'
-import { codes } from './errors.js'
-import { PeerRecord, RecordEnvelope } from '@libp2p/peer-record'
-import { peerIdFromPeerId } from '@libp2p/peer-id'
-import { CustomEvent } from '@libp2p/interfaces/events'
-import type { Address, AddressFilter, Peer, PeerMultiaddrsChangeData, PeerStore } from '@libp2p/interface-peer-store'
-import type { Store } from './store.js'
-import type { Envelope } from '@libp2p/interface-record'
-import type { PeerId } from '@libp2p/interface-peer-id'
-import type { PeerInfo } from '@libp2p/interface-peer-info'
-import type { Multiaddr } from '@multiformats/multiaddr'
-
-const log = logger('libp2p:peer-store:address-book')
-const EVENT_NAME = 'change:multiaddrs'
-
-async function allowAll (): Promise {
- return true
-}
-
-export class PeerStoreAddressBook {
- private readonly dispatchEvent: PeerStore['dispatchEvent']
- private readonly store: Store
- private readonly addressFilter: AddressFilter
-
- constructor (dispatchEvent: PeerStore['dispatchEvent'], store: Store, addressFilter?: AddressFilter) {
- this.dispatchEvent = dispatchEvent
- this.store = store
- this.addressFilter = addressFilter ?? allowAll
- }
-
- /**
- * ConsumePeerRecord adds addresses from a signed peer record contained in a record envelope.
- * This will return a boolean that indicates if the record was successfully processed and added
- * into the AddressBook.
- */
- async consumePeerRecord (envelope: Envelope): Promise {
- log.trace('consumePeerRecord await write lock')
- const release = await this.store.lock.writeLock()
- log.trace('consumePeerRecord got write lock')
-
- let peerId
- let peer: Peer | undefined
- let updatedPeer
-
- try {
- let peerRecord
- try {
- peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
- } catch (err: any) {
- log.error('invalid peer record received')
- return false
- }
-
- peerId = peerRecord.peerId
- const multiaddrs = peerRecord.multiaddrs
-
- // Verify peerId
- if (!peerId.equals(envelope.peerId)) {
- log('signing key does not match PeerId in the PeerRecord')
- return false
- }
-
- // ensure the record has multiaddrs
- if (multiaddrs == null || multiaddrs.length === 0) {
- return false
- }
-
- if (await this.store.has(peerId)) {
- peer = await this.store.load(peerId)
-
- if (peer.peerRecordEnvelope != null) {
- const storedEnvelope = await RecordEnvelope.createFromProtobuf(peer.peerRecordEnvelope)
- const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload)
-
- // ensure seq is greater than, or equal to, the last received
- if (storedRecord.seqNumber >= peerRecord.seqNumber) {
- log('sequence number was lower or equal to existing sequence number - stored: %d received: %d', storedRecord.seqNumber, peerRecord.seqNumber)
- return false
- }
- }
- }
-
- const addresses = await filterMultiaddrs(peerId, multiaddrs, this.addressFilter, true)
-
- // Replace unsigned addresses by the new ones from the record
- // TODO: Once we have ttls for the addresses, we should merge these in
- updatedPeer = await this.store.patchOrCreate(peerId, {
- addresses,
- peerRecordEnvelope: envelope.marshal().subarray()
- })
-
- log('stored provided peer record for %p', peerRecord.peerId)
- } finally {
- log.trace('consumePeerRecord release write lock')
- release()
- }
-
- this.dispatchEvent(new CustomEvent(EVENT_NAME, {
- detail: {
- peerId,
- multiaddrs: updatedPeer.addresses.map(({ multiaddr }) => multiaddr),
- oldMultiaddrs: peer == null ? [] : peer.addresses.map(({ multiaddr }) => multiaddr)
- }
- }))
-
- return true
- }
-
- async getRawEnvelope (peerId: PeerId): Promise {
- log.trace('getRawEnvelope await read lock')
- const release = await this.store.lock.readLock()
- log.trace('getRawEnvelope got read lock')
-
- try {
- const peer = await this.store.load(peerId)
-
- return peer.peerRecordEnvelope
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- } finally {
- log.trace('getRawEnvelope release read lock')
- release()
- }
- }
-
- /**
- * Get an Envelope containing a PeerRecord for the given peer.
- * Returns undefined if no record exists.
- */
- async getPeerRecord (peerId: PeerId): Promise {
- const raw = await this.getRawEnvelope(peerId)
-
- if (raw == null) {
- return undefined
- }
-
- return await RecordEnvelope.createFromProtobuf(raw)
- }
-
- async get (peerId: PeerId): Promise {
- peerId = peerIdFromPeerId(peerId)
-
- log.trace('get wait for read lock')
- const release = await this.store.lock.readLock()
- log.trace('get got read lock')
-
- try {
- const peer = await this.store.load(peerId)
-
- return peer.addresses
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- } finally {
- log.trace('get release read lock')
- release()
- }
-
- return []
- }
-
- async set (peerId: PeerId, multiaddrs: Multiaddr[]): Promise {
- peerId = peerIdFromPeerId(peerId)
-
- if (!Array.isArray(multiaddrs)) {
- log.error('multiaddrs must be an array of Multiaddrs')
- throw new CodeError('multiaddrs must be an array of Multiaddrs', codes.ERR_INVALID_PARAMETERS)
- }
-
- log.trace('set await write lock')
- const release = await this.store.lock.writeLock()
- log.trace('set got write lock')
-
- let hasPeer = false
- let peer: Peer | undefined
- let updatedPeer
-
- try {
- const addresses = await filterMultiaddrs(peerId, multiaddrs, this.addressFilter)
-
- // No valid addresses found
- if (addresses.length === 0) {
- return
- }
-
- try {
- peer = await this.store.load(peerId)
- hasPeer = true
-
- if (new Set([
- ...addresses.map(({ multiaddr }) => multiaddr.toString()),
- ...peer.addresses.map(({ multiaddr }) => multiaddr.toString())
- ]).size === peer.addresses.length && addresses.length === peer.addresses.length) {
- // not changing anything, no need to update
- return
- }
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- }
-
- updatedPeer = await this.store.patchOrCreate(peerId, { addresses })
-
- log('set multiaddrs for %p', peerId)
- } finally {
- log.trace('set multiaddrs for %p', peerId)
- log('set release write lock')
- release()
- }
-
- this.dispatchEvent(new CustomEvent(EVENT_NAME, {
- detail: {
- peerId,
- multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr),
- oldMultiaddrs: peer == null ? [] : peer.addresses.map(({ multiaddr }) => multiaddr)
- }
- }))
-
- // Notify the existence of a new peer
- if (!hasPeer) {
- this.dispatchEvent(new CustomEvent('peer', {
- detail: {
- id: peerId,
- multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr),
- protocols: updatedPeer.protocols
- }
- }))
- }
- }
-
- async add (peerId: PeerId, multiaddrs: Multiaddr[]): Promise {
- peerId = peerIdFromPeerId(peerId)
-
- if (!Array.isArray(multiaddrs)) {
- log.error('multiaddrs must be an array of Multiaddrs')
- throw new CodeError('multiaddrs must be an array of Multiaddrs', codes.ERR_INVALID_PARAMETERS)
- }
-
- log.trace('add await write lock')
- const release = await this.store.lock.writeLock()
- log.trace('add got write lock')
-
- let hasPeer = false
- let peer: Peer | undefined
- let updatedPeer
-
- try {
- const addresses = await filterMultiaddrs(peerId, multiaddrs, this.addressFilter)
-
- // No valid addresses found
- if (addresses.length === 0) {
- return
- }
-
- try {
- peer = await this.store.load(peerId)
- hasPeer = true
-
- if (new Set([
- ...addresses.map(({ multiaddr }) => multiaddr.toString()),
- ...peer.addresses.map(({ multiaddr }) => multiaddr.toString())
- ]).size === peer.addresses.length) {
- return
- }
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- }
-
- updatedPeer = await this.store.mergeOrCreate(peerId, { addresses })
-
- log('added multiaddrs for %p', peerId)
- } finally {
- log.trace('set release write lock')
- release()
- }
-
- this.dispatchEvent(new CustomEvent(EVENT_NAME, {
- detail: {
- peerId,
- multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr),
- oldMultiaddrs: peer == null ? [] : peer.addresses.map(({ multiaddr }) => multiaddr)
- }
- }))
-
- // Notify the existence of a new peer
- if (!hasPeer) {
- this.dispatchEvent(new CustomEvent('peer', {
- detail: {
- id: peerId,
- multiaddrs: updatedPeer.addresses.map(addr => addr.multiaddr),
- protocols: updatedPeer.protocols
- }
- }))
- }
- }
-
- async delete (peerId: PeerId): Promise {
- peerId = peerIdFromPeerId(peerId)
-
- log.trace('delete await write lock')
- const release = await this.store.lock.writeLock()
- log.trace('delete got write lock')
-
- let peer: Peer | undefined
-
- try {
- try {
- peer = await this.store.load(peerId)
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- }
-
- await this.store.patchOrCreate(peerId, {
- addresses: []
- })
- } finally {
- log.trace('delete release write lock')
- release()
- }
-
- if (peer != null) {
- this.dispatchEvent(new CustomEvent(EVENT_NAME, {
- detail: {
- peerId,
- multiaddrs: [],
- oldMultiaddrs: peer == null ? [] : peer.addresses.map(({ multiaddr }) => multiaddr)
- }
- }))
- }
- }
-}
-
-async function filterMultiaddrs (peerId: PeerId, multiaddrs: Multiaddr[], addressFilter: AddressFilter, isCertified: boolean = false): Promise {
- const output: Address[] = []
-
- await Promise.all(
- multiaddrs.map(async multiaddr => {
- if (!isMultiaddr(multiaddr)) {
- log.error('multiaddr must be an instance of Multiaddr')
- throw new CodeError('multiaddr must be an instance of Multiaddr', codes.ERR_INVALID_PARAMETERS)
- }
-
- const include = await addressFilter(peerId, multiaddr)
-
- if (!include) {
- return
- }
-
- output.push({
- multiaddr,
- isCertified
- })
- })
- )
-
- return output
-}
diff --git a/src/errors.ts b/src/errors.ts
index 60efb24..48c52e7 100644
--- a/src/errors.ts
+++ b/src/errors.ts
@@ -1,5 +1,4 @@
export const codes = {
- ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS',
- ERR_NOT_FOUND: 'ERR_NOT_FOUND'
+ ERR_INVALID_PARAMETERS: 'ERR_INVALID_PARAMETERS'
}
diff --git a/src/index.ts b/src/index.ts
index 4970254..29355d3 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,79 +1,79 @@
-import { logger } from '@libp2p/logger'
-import { EventEmitter } from '@libp2p/interfaces/events'
-import { PeerStoreAddressBook } from './address-book.js'
-import { PeerStoreKeyBook } from './key-book.js'
-import { PeerStoreMetadataBook } from './metadata-book.js'
-import { PeerStoreProtoBook } from './proto-book.js'
-import { PersistentStore, Store } from './store.js'
-import type { PeerStore, AddressBook, KeyBook, MetadataBook, ProtoBook, PeerStoreEvents, PeerStoreInit, Peer, TagOptions } from '@libp2p/interface-peer-store'
+import type { EventEmitter } from '@libp2p/interfaces/events'
+import { PersistentStore, PeerUpdate } from './store.js'
+import type { PeerStore, Peer, PeerData } from '@libp2p/interface-peer-store'
import type { PeerId } from '@libp2p/interface-peer-id'
-import { CodeError } from '@libp2p/interfaces/errors'
-import { Tag, Tags } from './pb/tags.js'
import type { Datastore } from 'interface-datastore'
+import type { Multiaddr } from '@multiformats/multiaddr'
+import type { Libp2pEvents } from '@libp2p/interface-libp2p'
+import { logger } from '@libp2p/logger'
const log = logger('libp2p:peer-store')
export interface PersistentPeerStoreComponents {
peerId: PeerId
datastore: Datastore
+ events: EventEmitter
+}
+
+/**
+ * Return true to allow storing the passed multiaddr for the passed peer
+ */
+export interface AddressFilter {
+ (peerId: PeerId, multiaddr: Multiaddr): Promise
+}
+
+export interface PersistentPeerStoreInit {
+ addressFilter?: AddressFilter
}
/**
* An implementation of PeerStore that stores data in a Datastore
*/
-export class PersistentPeerStore extends EventEmitter implements PeerStore {
- public addressBook: AddressBook
- public keyBook: KeyBook
- public metadataBook: MetadataBook
- public protoBook: ProtoBook
-
- private readonly components: PersistentPeerStoreComponents
- private readonly store: Store
-
- constructor (components: PersistentPeerStoreComponents, init: PeerStoreInit = {}) {
- super()
-
- this.components = components
- this.store = new PersistentStore(components)
- this.addressBook = new PeerStoreAddressBook(this.dispatchEvent.bind(this), this.store, init.addressFilter)
- this.keyBook = new PeerStoreKeyBook(this.dispatchEvent.bind(this), this.store)
- this.metadataBook = new PeerStoreMetadataBook(this.dispatchEvent.bind(this), this.store)
- this.protoBook = new PeerStoreProtoBook(this.dispatchEvent.bind(this), this.store)
+export class PersistentPeerStore implements PeerStore {
+ private readonly store: PersistentStore
+ private readonly events: EventEmitter
+ private readonly peerId: PeerId
+
+ constructor (components: PersistentPeerStoreComponents, init: PersistentPeerStoreInit = {}) {
+ this.events = components.events
+ this.peerId = components.peerId
+ this.store = new PersistentStore(components, init)
}
async forEach (fn: (peer: Peer) => void): Promise {
- log.trace('getPeers await read lock')
+ log.trace('forEach await read lock')
const release = await this.store.lock.readLock()
- log.trace('getPeers got read lock')
+ log.trace('forEach got read lock')
try {
for await (const peer of this.store.all()) {
- if (peer.id.equals(this.components.peerId)) {
- // Skip self peer if present
- continue
- }
-
fn(peer)
}
} finally {
- log.trace('getPeers release read lock')
+ log.trace('forEach release read lock')
release()
}
}
async all (): Promise {
- const output: Peer[] = []
+ log.trace('all await read lock')
+ const release = await this.store.lock.readLock()
+ log.trace('all got read lock')
- await this.forEach(peer => {
- output.push(peer)
- })
+ try {
+ const output: Peer[] = []
+
+ for await (const peer of this.store.all()) {
+ output.push(peer)
+ }
- return output
+ return output
+ } finally {
+ log.trace('all release read lock')
+ release()
+ }
}
- /**
- * Delete the information of the given peer in every book
- */
async delete (peerId: PeerId): Promise {
log.trace('delete await write lock')
const release = await this.store.lock.writeLock()
@@ -87,9 +87,19 @@ export class PersistentPeerStore extends EventEmitter implement
}
}
- /**
- * Get the stored information of a given peer
- */
+ async has (peerId: PeerId): Promise {
+ log.trace('has await read lock')
+ const release = await this.store.lock.readLock()
+ log.trace('has got read lock')
+
+ try {
+ return await this.store.has(peerId)
+ } finally {
+ log.trace('has release read lock')
+ release()
+ }
+ }
+
async get (peerId: PeerId): Promise {
log.trace('get await read lock')
const release = await this.store.lock.readLock()
@@ -103,82 +113,66 @@ export class PersistentPeerStore extends EventEmitter implement
}
}
- /**
- * Returns true if we have a record of the peer
- */
- async has (peerId: PeerId): Promise {
- log.trace('has await read lock')
- const release = await this.store.lock.readLock()
- log.trace('has got read lock')
+ async save (id: PeerId, data: PeerData): Promise {
+ log.trace('save await write lock')
+ const release = await this.store.lock.writeLock()
+ log.trace('save got write lock')
try {
- return await this.store.has(peerId)
+ const result = await this.store.save(id, data)
+
+ this.#emitIfUpdated(id, result)
+
+ return result.peer
} finally {
- log.trace('has release read lock')
+ log.trace('save release write lock')
release()
}
}
- async tagPeer (peerId: PeerId, tag: string, options: TagOptions = {}): Promise {
- const providedValue = options.value ?? 0
- const value = Math.round(providedValue)
- const ttl = options.ttl ?? undefined
+ async patch (id: PeerId, data: PeerData): Promise {
+ log.trace('patch await write lock')
+ const release = await this.store.lock.writeLock()
+ log.trace('patch got write lock')
- if (value !== providedValue || value < 0 || value > 100) {
- throw new CodeError('Tag value must be between 0-100', 'ERR_TAG_VALUE_OUT_OF_BOUNDS')
- }
+ try {
+ const result = await this.store.patch(id, data)
- const buf = await this.metadataBook.getValue(peerId, 'tags')
- let tags: Tag[] = []
+ this.#emitIfUpdated(id, result)
- if (buf != null) {
- tags = Tags.decode(buf).tags
+ return result.peer
+ } finally {
+ log.trace('patch release write lock')
+ release()
}
-
- // do not allow duplicate tags
- tags = tags.filter(t => t.name !== tag)
-
- tags.push({
- name: tag,
- value,
- expiry: ttl == null ? undefined : BigInt(Date.now() + ttl)
- })
-
- await this.metadataBook.setValue(peerId, 'tags', Tags.encode({ tags }).subarray())
}
- async unTagPeer (peerId: PeerId, tag: string): Promise {
- const buf = await this.metadataBook.getValue(peerId, 'tags')
- let tags: Tag[] = []
+ async merge (id: PeerId, data: PeerData): Promise {
+ log.trace('merge await write lock')
+ const release = await this.store.lock.writeLock()
+ log.trace('merge got write lock')
- if (buf != null) {
- tags = Tags.decode(buf).tags
- }
+ try {
+ const result = await this.store.merge(id, data)
- tags = tags.filter(t => t.name !== tag)
+ this.#emitIfUpdated(id, result)
- await this.metadataBook.setValue(peerId, 'tags', Tags.encode({ tags }).subarray())
+ return result.peer
+ } finally {
+ log.trace('merge release write lock')
+ release()
+ }
}
- async getTags (peerId: PeerId): Promise> {
- const buf = await this.metadataBook.getValue(peerId, 'tags')
- let tags: Tag[] = []
-
- if (buf != null) {
- tags = Tags.decode(buf).tags
+ #emitIfUpdated (id: PeerId, result: PeerUpdate): void {
+ if (!result.updated) {
+ return
}
- const now = BigInt(Date.now())
- const unexpiredTags = tags.filter(tag => tag.expiry == null || tag.expiry > now)
-
- if (unexpiredTags.length !== tags.length) {
- // remove any expired tags
- await this.metadataBook.setValue(peerId, 'tags', Tags.encode({ tags: unexpiredTags }).subarray())
+ if (this.peerId.equals(id)) {
+ this.events.safeDispatchEvent('self:peer:update', { detail: result })
+ } else {
+ this.events.safeDispatchEvent('peer:update', { detail: result })
}
-
- return unexpiredTags.map(t => ({
- name: t.name,
- value: t.value ?? 0
- }))
}
}
diff --git a/src/key-book.ts b/src/key-book.ts
deleted file mode 100644
index 078964b..0000000
--- a/src/key-book.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import { logger } from '@libp2p/logger'
-import { CodeError } from '@libp2p/interfaces/errors'
-import { codes } from './errors.js'
-import { peerIdFromPeerId } from '@libp2p/peer-id'
-import { equals as uint8arrayEquals } from 'uint8arrays/equals'
-import { CustomEvent } from '@libp2p/interfaces/events'
-import type { Store } from './store.js'
-import type { PeerStore, KeyBook, PeerPublicKeyChangeData, Peer } from '@libp2p/interface-peer-store'
-import type { PeerId } from '@libp2p/interface-peer-id'
-
-const log = logger('libp2p:peer-store:key-book')
-
-const EVENT_NAME = 'change:pubkey'
-
-export class PeerStoreKeyBook implements KeyBook {
- private readonly dispatchEvent: PeerStore['dispatchEvent']
- private readonly store: Store
-
- /**
- * The KeyBook is responsible for keeping the known public keys of a peer
- */
- constructor (dispatchEvent: PeerStore['dispatchEvent'], store: Store) {
- this.dispatchEvent = dispatchEvent
- this.store = store
- }
-
- /**
- * Set the Peer public key
- */
- async set (peerId: PeerId, publicKey: Uint8Array): Promise {
- peerId = peerIdFromPeerId(peerId)
-
- if (!(publicKey instanceof Uint8Array)) {
- log.error('publicKey must be an instance of Uint8Array to store data')
- throw new CodeError('publicKey must be an instance of PublicKey', codes.ERR_INVALID_PARAMETERS)
- }
-
- log.trace('set await write lock')
- const release = await this.store.lock.writeLock()
- log.trace('set got write lock')
-
- let updatedKey = false
- let peer: Peer | undefined
-
- try {
- try {
- peer = await this.store.load(peerId)
-
- if ((peer.pubKey != null) && uint8arrayEquals(peer.pubKey, publicKey)) {
- return
- }
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- }
-
- await this.store.patchOrCreate(peerId, {
- pubKey: publicKey
- })
- updatedKey = true
- } finally {
- log.trace('set release write lock')
- release()
- }
-
- if (updatedKey) {
- this.dispatchEvent(new CustomEvent(EVENT_NAME, {
- detail: {
- peerId,
- publicKey,
- oldPublicKey: peer == null ? undefined : peer.pubKey
- }
- }))
- }
- }
-
- /**
- * Get Public key of the given PeerId, if stored
- */
- async get (peerId: PeerId): Promise {
- peerId = peerIdFromPeerId(peerId)
-
- log.trace('get await write lock')
- const release = await this.store.lock.readLock()
- log.trace('get got write lock')
-
- try {
- const peer = await this.store.load(peerId)
-
- return peer.pubKey
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- } finally {
- log('get release write lock')
- release()
- }
- }
-
- async delete (peerId: PeerId): Promise {
- peerId = peerIdFromPeerId(peerId)
-
- log.trace('delete await write lock')
- const release = await this.store.lock.writeLock()
- log.trace('delete got write lock')
-
- let peer: Peer | undefined
-
- try {
- try {
- peer = await this.store.load(peerId)
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- }
-
- await this.store.patchOrCreate(peerId, {
- pubKey: undefined
- })
- } catch (err: any) {
- if (err.code !== codes.ERR_NOT_FOUND) {
- throw err
- }
- } finally {
- log.trace('delete release write lock')
- release()
- }
-
- this.dispatchEvent(new CustomEvent(EVENT_NAME, {
- detail: {
- peerId,
- publicKey: undefined,
- oldPublicKey: peer == null ? undefined : peer.pubKey
- }
- }))
- }
-}
diff --git a/src/metadata-book.ts b/src/metadata-book.ts
deleted file mode 100644
index 698a009..0000000
--- a/src/metadata-book.ts
+++ /dev/null
@@ -1,244 +0,0 @@
-import { logger } from '@libp2p/logger'
-import { CodeError } from '@libp2p/interfaces/errors'
-import { codes } from './errors.js'
-import { peerIdFromPeerId } from '@libp2p/peer-id'
-import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
-import { CustomEvent } from '@libp2p/interfaces/events'
-import type { Store } from './store.js'
-import type { PeerStore, MetadataBook, PeerMetadataChangeData, Peer } from '@libp2p/interface-peer-store'
-import type { PeerId } from '@libp2p/interface-peer-id'
-
-const log = logger('libp2p:peer-store:metadata-book')
-
-const EVENT_NAME = 'change:metadata'
-
-export class PeerStoreMetadataBook implements MetadataBook {
- private readonly dispatchEvent: PeerStore['dispatchEvent']
- private readonly store: Store
-
- /**
- * The MetadataBook is responsible for keeping metadata
- * about known peers
- */
- constructor (dispatchEvent: PeerStore['dispatchEvent'], store: Store) {
- this.dispatchEvent = dispatchEvent
- this.store = store
- }
-
- /**
- * Get the known data of a provided peer
- */
- async get (peerId: PeerId): Promise