@libp2p/peer-store
Version:
Stores information about peers libp2p knows on the network
251 lines (208 loc) • 7.13 kB
text/typescript
/**
* @packageDocumentation
*
* The peer store is where libp2p stores data about the peers it has encountered on the network.
*/
import { peerIdFromCID } from '@libp2p/peer-id'
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
import all from 'it-all'
import { PersistentStore, type PeerUpdate } from './store.js'
import type { ComponentLogger, Libp2pEvents, Logger, TypedEventTarget, PeerId, PeerStore, Peer, PeerData, PeerQuery } from '@libp2p/interface'
import type { Multiaddr } from '@multiformats/multiaddr'
import type { Datastore } from 'interface-datastore'
export interface PersistentPeerStoreComponents {
peerId: PeerId
datastore: Datastore
events: TypedEventTarget<Libp2pEvents>
logger: ComponentLogger
}
/**
* Return true to allow storing the passed multiaddr for the passed peer
*/
export interface AddressFilter {
(peerId: PeerId, multiaddr: Multiaddr): Promise<boolean> | boolean
}
export interface PersistentPeerStoreInit {
/**
* Used to remove multiaddrs of peers before storing them. The default is to
* store all addresses
*/
addressFilter?: AddressFilter
/**
* The multiaddrs for a given peer will expire after this number of ms after
* which they must be re-fetched using the peer routing.
*
* Defaults to one hour.
*
* @default 3_600_000
*/
maxAddressAge?: number
/**
* Any peer without multiaddrs that has not been updated after this number of
* ms will be evicted from the peer store.
*
* Defaults to six hours.
*
* @default 21_600_000
*/
maxPeerAge?: number
}
/**
* An implementation of PeerStore that stores data in a Datastore
*/
class PersistentPeerStore implements PeerStore {
private readonly store: PersistentStore
private readonly events: TypedEventTarget<Libp2pEvents>
private readonly peerId: PeerId
private readonly log: Logger
constructor (components: PersistentPeerStoreComponents, init: PersistentPeerStoreInit = {}) {
this.log = components.logger.forComponent('libp2p:peer-store')
this.events = components.events
this.peerId = components.peerId
this.store = new PersistentStore(components, init)
}
readonly [Symbol.toStringTag] = '@libp2p/peer-store'
async forEach (fn: (peer: Peer,) => void, query?: PeerQuery): Promise<void> {
this.log.trace('forEach await read lock')
const release = await this.store.lock.readLock()
this.log.trace('forEach got read lock')
try {
for await (const peer of this.store.all(query)) {
fn(peer)
}
} finally {
this.log.trace('forEach release read lock')
release()
}
}
async all (query?: PeerQuery): Promise<Peer[]> {
this.log.trace('all await read lock')
const release = await this.store.lock.readLock()
this.log.trace('all got read lock')
try {
return await all(this.store.all(query))
} finally {
this.log.trace('all release read lock')
release()
}
}
async delete (peerId: PeerId): Promise<void> {
this.log.trace('delete await write lock')
const release = await this.store.lock.writeLock()
this.log.trace('delete got write lock')
try {
await this.store.delete(peerId)
} finally {
this.log.trace('delete release write lock')
release()
}
}
async has (peerId: PeerId): Promise<boolean> {
this.log.trace('has await read lock')
const release = await this.store.lock.readLock()
this.log.trace('has got read lock')
try {
return await this.store.has(peerId)
} finally {
this.log.trace('has release read lock')
release()
}
}
async get (peerId: PeerId): Promise<Peer> {
this.log.trace('get await read lock')
const release = await this.store.lock.readLock()
this.log.trace('get got read lock')
try {
return await this.store.load(peerId)
} finally {
this.log.trace('get release read lock')
release()
}
}
async save (id: PeerId, data: PeerData): Promise<Peer> {
this.log.trace('save await write lock')
const release = await this.store.lock.writeLock()
this.log.trace('save got write lock')
try {
const result = await this.store.save(id, data)
this.#emitIfUpdated(id, result)
return result.peer
} finally {
this.log.trace('save release write lock')
release()
}
}
async patch (id: PeerId, data: PeerData): Promise<Peer> {
this.log.trace('patch await write lock')
const release = await this.store.lock.writeLock()
this.log.trace('patch got write lock')
try {
const result = await this.store.patch(id, data)
this.#emitIfUpdated(id, result)
return result.peer
} finally {
this.log.trace('patch release write lock')
release()
}
}
async merge (id: PeerId, data: PeerData): Promise<Peer> {
this.log.trace('merge await write lock')
const release = await this.store.lock.writeLock()
this.log.trace('merge got write lock')
try {
const result = await this.store.merge(id, data)
this.#emitIfUpdated(id, result)
return result.peer
} finally {
this.log.trace('merge release write lock')
release()
}
}
async consumePeerRecord (buf: Uint8Array, expectedPeer?: PeerId): Promise<boolean> {
const envelope = await RecordEnvelope.openAndCertify(buf, PeerRecord.DOMAIN)
const peerId = peerIdFromCID(envelope.publicKey.toCID())
if (expectedPeer?.equals(peerId) === false) {
this.log('envelope peer id was not the expected peer id - expected: %p received: %p', expectedPeer, peerId)
return false
}
const peerRecord = PeerRecord.createFromProtobuf(envelope.payload)
let peer: Peer | undefined
try {
peer = await this.get(peerId)
} catch (err: any) {
if (err.name !== 'NotFoundError') {
throw err
}
}
// ensure seq is greater than, or equal to, the last received
if (peer?.peerRecordEnvelope != null) {
const storedEnvelope = await RecordEnvelope.createFromProtobuf(peer.peerRecordEnvelope)
const storedRecord = PeerRecord.createFromProtobuf(storedEnvelope.payload)
if (storedRecord.seqNumber >= peerRecord.seqNumber) {
this.log('sequence number was lower or equal to existing sequence number - stored: %d received: %d', storedRecord.seqNumber, peerRecord.seqNumber)
return false
}
}
await this.patch(peerRecord.peerId, {
peerRecordEnvelope: buf,
addresses: peerRecord.multiaddrs.map(multiaddr => ({
isCertified: true,
multiaddr
}))
})
return true
}
#emitIfUpdated (id: PeerId, result: PeerUpdate): void {
if (!result.updated) {
return
}
if (this.peerId.equals(id)) {
this.events.safeDispatchEvent('self:peer:update', { detail: result })
} else {
this.events.safeDispatchEvent('peer:update', { detail: result })
}
}
}
export function persistentPeerStore (components: PersistentPeerStoreComponents, init: PersistentPeerStoreInit = {}): PeerStore {
return new PersistentPeerStore(components, init)
}