@libp2p/peer-store
Version:
Stores information about peers libp2p knows on the network
167 lines • 6.17 kB
JavaScript
import { NotFoundError } from '@libp2p/interface';
import { peerIdFromCID } from '@libp2p/peer-id';
import mortice, {} from 'mortice';
import { base32 } from 'multiformats/bases/base32';
import { CID } from 'multiformats/cid';
import { MAX_ADDRESS_AGE, MAX_PEER_AGE } from './constants.js';
import { Peer as PeerPB } from './pb/peer.js';
import { bytesToPeer, pbToPeer } from './utils/bytes-to-peer.js';
import { peerEquals } from './utils/peer-equals.js';
import { NAMESPACE_COMMON, peerIdToDatastoreKey } from './utils/peer-id-to-datastore-key.js';
import { toPeerPB } from './utils/to-peer-pb.js';
function keyToPeerId(key) {
// /peers/${peer-id-as-libp2p-key-cid-string-in-base-32}
const base32Str = key.toString().split('/')[2];
const buf = CID.parse(base32Str, base32);
return peerIdFromCID(buf);
}
function decodePeer(key, value, maxAddressAge) {
const peerId = keyToPeerId(key);
return bytesToPeer(peerId, value, maxAddressAge);
}
function mapQuery(query, maxAddressAge) {
return {
prefix: NAMESPACE_COMMON,
filters: (query.filters ?? []).map(fn => ({ key, value }) => {
return fn(decodePeer(key, value, maxAddressAge));
}),
orders: (query.orders ?? []).map(fn => (a, b) => {
return fn(decodePeer(a.key, a.value, maxAddressAge), decodePeer(b.key, b.value, maxAddressAge));
})
};
}
export class PersistentStore {
peerId;
datastore;
lock;
addressFilter;
log;
maxAddressAge;
maxPeerAge;
constructor(components, init = {}) {
this.log = components.logger.forComponent('libp2p:peer-store');
this.peerId = components.peerId;
this.datastore = components.datastore;
this.addressFilter = init.addressFilter;
this.lock = mortice({
name: 'peer-store',
singleProcess: true
});
this.maxAddressAge = init.maxAddressAge ?? MAX_ADDRESS_AGE;
this.maxPeerAge = init.maxPeerAge ?? MAX_PEER_AGE;
}
async has(peerId) {
try {
await this.load(peerId);
return true;
}
catch (err) {
if (err.name !== 'NotFoundError') {
throw err;
}
}
return false;
}
async delete(peerId) {
if (this.peerId.equals(peerId)) {
return;
}
await this.datastore.delete(peerIdToDatastoreKey(peerId));
}
async load(peerId) {
const key = peerIdToDatastoreKey(peerId);
const buf = await this.datastore.get(key);
const peer = PeerPB.decode(buf);
if (this.#peerIsExpired(peerId, peer)) {
await this.datastore.delete(key);
throw new NotFoundError();
}
return pbToPeer(peerId, peer, this.peerId.equals(peerId) ? Infinity : this.maxAddressAge);
}
async save(peerId, data) {
const existingPeer = await this.#findExistingPeer(peerId);
const peerPb = await toPeerPB(peerId, data, 'patch', {
addressFilter: this.addressFilter
});
return this.#saveIfDifferent(peerId, peerPb, existingPeer);
}
async patch(peerId, data) {
const existingPeer = await this.#findExistingPeer(peerId);
const peerPb = await toPeerPB(peerId, data, 'patch', {
addressFilter: this.addressFilter,
existingPeer
});
return this.#saveIfDifferent(peerId, peerPb, existingPeer);
}
async merge(peerId, data) {
const existingPeer = await this.#findExistingPeer(peerId);
const peerPb = await toPeerPB(peerId, data, 'merge', {
addressFilter: this.addressFilter,
existingPeer
});
return this.#saveIfDifferent(peerId, peerPb, existingPeer);
}
async *all(query) {
for await (const { key, value } of this.datastore.query(mapQuery(query ?? {}, this.maxAddressAge))) {
const peerId = keyToPeerId(key);
// skip self peer if present
if (peerId.equals(this.peerId)) {
continue;
}
const peer = PeerPB.decode(value);
// remove expired peer
if (this.#peerIsExpired(peerId, peer)) {
await this.datastore.delete(key);
continue;
}
yield pbToPeer(peerId, peer, this.peerId.equals(peerId) ? Infinity : this.maxAddressAge);
}
}
async #findExistingPeer(peerId) {
try {
const key = peerIdToDatastoreKey(peerId);
const buf = await this.datastore.get(key);
const peerPB = PeerPB.decode(buf);
// remove expired peer
if (this.#peerIsExpired(peerId, peerPB)) {
await this.datastore.delete(key);
throw new NotFoundError();
}
return {
peerPB,
peer: bytesToPeer(peerId, buf, this.maxAddressAge)
};
}
catch (err) {
if (err.name !== 'NotFoundError') {
this.log.error('invalid peer data found in peer store - %e', err);
}
}
}
async #saveIfDifferent(peerId, peer, existingPeer) {
// record last update
peer.updated = Date.now();
const buf = PeerPB.encode(peer);
await this.datastore.put(peerIdToDatastoreKey(peerId), buf);
return {
peer: bytesToPeer(peerId, buf, this.maxAddressAge),
previous: existingPeer?.peer,
updated: existingPeer == null || !peerEquals(peer, existingPeer.peerPB)
};
}
#peerIsExpired(peerId, peer) {
if (peer.updated == null) {
return true;
}
if (this.peerId.equals(peerId)) {
return false;
}
const expired = peer.updated < (Date.now() - this.maxPeerAge);
const minAddressObserved = Date.now() - this.maxAddressAge;
const addrs = peer.addresses.filter(addr => {
return addr.observed != null && addr.observed > minAddressObserved;
});
return expired && addrs.length === 0;
}
}
//# sourceMappingURL=store.js.map