Skip to content

Commit 6b780fe

Browse files
authored
feat: support peer queries (libp2p#88)
Support filtering and ordering peers in the datastore.
1 parent c9ca1dc commit 6b780fe

File tree

5 files changed

+87
-32
lines changed

5 files changed

+87
-32
lines changed

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -144,17 +144,18 @@
144144
"docs": "aegir docs"
145145
},
146146
"dependencies": {
147-
"@libp2p/crypto": "^1.0.15",
148147
"@libp2p/interface-libp2p": "^3.1.0",
149148
"@libp2p/interface-peer-id": "^2.0.0",
150-
"@libp2p/interface-peer-store": "^2.0.1",
149+
"@libp2p/interface-peer-store": "^2.0.4",
151150
"@libp2p/interfaces": "^3.2.0",
152151
"@libp2p/logger": "^2.0.7",
152+
"@libp2p/peer-collections": "^3.0.1",
153153
"@libp2p/peer-id": "^2.0.0",
154154
"@libp2p/peer-id-factory": "^2.0.0",
155155
"@libp2p/peer-record": "^5.0.3",
156156
"@multiformats/multiaddr": "^12.0.0",
157157
"interface-datastore": "^8.0.0",
158+
"it-all": "^3.0.2",
158159
"mortice": "^3.0.1",
159160
"multiformats": "^11.0.0",
160161
"protons-runtime": "^5.0.0",

src/index.ts

+6-11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { logger } from '@libp2p/logger'
22
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
3+
import all from 'it-all'
34
import { PersistentStore, type PeerUpdate } from './store.js'
45
import type { Libp2pEvents } from '@libp2p/interface-libp2p'
56
import type { PeerId } from '@libp2p/interface-peer-id'
6-
import type { PeerStore, Peer, PeerData } from '@libp2p/interface-peer-store'
7+
import type { PeerStore, Peer, PeerData, PeerQuery } from '@libp2p/interface-peer-store'
78
import type { EventEmitter } from '@libp2p/interfaces/events'
89
import type { Multiaddr } from '@multiformats/multiaddr'
910
import type { Datastore } from 'interface-datastore'
@@ -41,13 +42,13 @@ export class PersistentPeerStore implements PeerStore {
4142
this.store = new PersistentStore(components, init)
4243
}
4344

44-
async forEach (fn: (peer: Peer) => void): Promise<void> {
45+
async forEach (fn: (peer: Peer,) => void, query?: PeerQuery): Promise<void> {
4546
log.trace('forEach await read lock')
4647
const release = await this.store.lock.readLock()
4748
log.trace('forEach got read lock')
4849

4950
try {
50-
for await (const peer of this.store.all()) {
51+
for await (const peer of this.store.all(query)) {
5152
fn(peer)
5253
}
5354
} finally {
@@ -56,19 +57,13 @@ export class PersistentPeerStore implements PeerStore {
5657
}
5758
}
5859

59-
async all (): Promise<Peer[]> {
60+
async all (query?: PeerQuery): Promise<Peer[]> {
6061
log.trace('all await read lock')
6162
const release = await this.store.lock.readLock()
6263
log.trace('all got read lock')
6364

6465
try {
65-
const output: Peer[] = []
66-
67-
for await (const peer of this.store.all()) {
68-
output.push(peer)
69-
}
70-
71-
return output
66+
return await all(this.store.all(query))
7267
} finally {
7368
log.trace('all release read lock')
7469
release()

src/store.ts

+48-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CodeError } from '@libp2p/interfaces/errors'
2+
import { PeerMap } from '@libp2p/peer-collections'
23
import { peerIdFromBytes } from '@libp2p/peer-id'
34
import mortice, { type Mortice } from 'mortice'
45
import { base32 } from 'multiformats/bases/base32'
@@ -11,8 +12,8 @@ import { toPeerPB } from './utils/to-peer-pb.js'
1112
import type { AddressFilter, PersistentPeerStoreComponents, PersistentPeerStoreInit } from './index.js'
1213
import type { PeerUpdate as PeerUpdateExternal } from '@libp2p/interface-libp2p'
1314
import type { PeerId } from '@libp2p/interface-peer-id'
14-
import type { Peer, PeerData } from '@libp2p/interface-peer-store'
15-
import type { Datastore } from 'interface-datastore'
15+
import type { Peer, PeerData, PeerQuery } from '@libp2p/interface-peer-store'
16+
import type { Datastore, Key, Query } from 'interface-datastore'
1617

1718
/**
1819
* Event detail emitted when peer data changes
@@ -21,6 +22,41 @@ export interface PeerUpdate extends PeerUpdateExternal {
2122
updated: boolean
2223
}
2324

25+
function decodePeer (key: Key, value: Uint8Array, cache: PeerMap<Peer>): Peer {
26+
// /peers/${peer-id-as-libp2p-key-cid-string-in-base-32}
27+
const base32Str = key.toString().split('/')[2]
28+
const buf = base32.decode(base32Str)
29+
const peerId = peerIdFromBytes(buf)
30+
31+
const cached = cache.get(peerId)
32+
33+
if (cached != null) {
34+
return cached
35+
}
36+
37+
const peer = bytesToPeer(peerId, value)
38+
39+
cache.set(peerId, peer)
40+
41+
return peer
42+
}
43+
44+
function mapQuery (query: PeerQuery, cache: PeerMap<Peer>): Query {
45+
if (query == null) {
46+
return {}
47+
}
48+
49+
return {
50+
prefix: NAMESPACE_COMMON,
51+
filters: (query.filters ?? []).map(fn => ({ key, value }) => {
52+
return fn(decodePeer(key, value, cache))
53+
}),
54+
orders: (query.orders ?? []).map(fn => (a, b) => {
55+
return fn(decodePeer(a.key, a.value, cache), decodePeer(b.key, b.value, cache))
56+
})
57+
}
58+
}
59+
2460
export class PersistentStore {
2561
private readonly peerId: PeerId
2662
private readonly datastore: Datastore
@@ -96,28 +132,25 @@ export class PersistentStore {
96132
return this.#saveIfDifferent(peerId, peerPb, existingBuf, existingPeer)
97133
}
98134

99-
async * all (): AsyncGenerator<Peer, void, unknown> {
100-
for await (const { key, value } of this.datastore.query({
101-
prefix: NAMESPACE_COMMON
102-
})) {
103-
// /peers/${peer-id-as-libp2p-key-cid-string-in-base-32}
104-
const base32Str = key.toString().split('/')[2]
105-
const buf = base32.decode(base32Str)
106-
const peerId = peerIdFromBytes(buf)
135+
async * all (query?: PeerQuery): AsyncGenerator<Peer, void, unknown> {
136+
const peerCache = new PeerMap<Peer>()
137+
138+
for await (const { key, value } of this.datastore.query(mapQuery(query ?? {}, peerCache))) {
139+
const peer = decodePeer(key, value, peerCache)
107140

108-
if (peerId.equals(this.peerId)) {
141+
if (peer.id.equals(this.peerId)) {
109142
// Skip self peer if present
110143
continue
111144
}
112145

113-
yield bytesToPeer(peerId, value)
146+
yield peer
114147
}
115148
}
116149

117150
async #findExistingPeer (peerId: PeerId): Promise<{ existingBuf?: Uint8Array, existingPeer?: Peer }> {
118151
try {
119152
const existingBuf = await this.datastore.get(peerIdToDatastoreKey(peerId))
120-
const existingPeer = await bytesToPeer(peerId, existingBuf)
153+
const existingPeer = bytesToPeer(peerId, existingBuf)
121154

122155
return {
123156
existingBuf,
@@ -137,7 +170,7 @@ export class PersistentStore {
137170

138171
if (existingBuf != null && uint8ArrayEquals(buf, existingBuf)) {
139172
return {
140-
peer: await bytesToPeer(peerId, buf),
173+
peer: bytesToPeer(peerId, buf),
141174
previous: existingPeer,
142175
updated: false
143176
}
@@ -146,7 +179,7 @@ export class PersistentStore {
146179
await this.datastore.put(peerIdToDatastoreKey(peerId), buf)
147180

148181
return {
149-
peer: await bytesToPeer(peerId, buf),
182+
peer: bytesToPeer(peerId, buf),
150183
previous: existingPeer,
151184
updated: true
152185
}

src/utils/bytes-to-peer.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import { unmarshalPublicKey } from '@libp2p/crypto/keys'
2-
import { createFromPubKey } from '@libp2p/peer-id-factory'
1+
import { peerIdFromPeerId } from '@libp2p/peer-id'
32
import { multiaddr } from '@multiformats/multiaddr'
43
import { Peer as PeerPB } from '../pb/peer.js'
54
import type { PeerId } from '@libp2p/interface-peer-id'
65
import type { Peer, Tag } from '@libp2p/interface-peer-store'
76

8-
export async function bytesToPeer (peerId: PeerId, buf: Uint8Array): Promise<Peer> {
7+
export function bytesToPeer (peerId: PeerId, buf: Uint8Array): Peer {
98
const peer = PeerPB.decode(buf)
109

1110
if (peer.publicKey != null && peerId.publicKey == null) {
12-
peerId = await createFromPubKey(unmarshalPublicKey(peer.publicKey))
11+
peerId = peerIdFromPeerId({
12+
...peerId,
13+
publicKey: peerId.publicKey
14+
})
1315
}
1416

1517
const tags = new Map<string, Tag>()

test/index.spec.ts

+24
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,29 @@ describe('PersistentPeerStore', () => {
259259
await expect(peerStore.consumePeerRecord(signedPeerRecord.marshal(), otherPeerId)).to.eventually.equal(false)
260260
await expect(peerStore.has(peerId)).to.eventually.be.false()
261261
})
262+
263+
it('allows queries', async () => {
264+
await peerStore.save(otherPeerId, {
265+
multiaddrs: [
266+
addr1
267+
]
268+
})
269+
270+
const allPeers = await peerStore.all({
271+
filters: [
272+
() => true
273+
]
274+
})
275+
276+
expect(allPeers).to.not.be.empty()
277+
278+
const noPeers = await peerStore.all({
279+
filters: [
280+
() => false
281+
]
282+
})
283+
284+
expect(noPeers).to.be.empty()
285+
})
262286
})
263287
})

0 commit comments

Comments
 (0)