@libp2p/peer-store 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +4 -0
  2. package/README.md +44 -0
  3. package/dist/src/address-book.d.ts +31 -0
  4. package/dist/src/address-book.d.ts.map +1 -0
  5. package/dist/src/address-book.js +265 -0
  6. package/dist/src/address-book.js.map +1 -0
  7. package/dist/src/errors.d.ts +5 -0
  8. package/dist/src/errors.d.ts.map +1 -0
  9. package/dist/src/errors.js +5 -0
  10. package/dist/src/errors.js.map +1 -0
  11. package/dist/src/index.d.ts +43 -0
  12. package/dist/src/index.d.ts.map +1 -0
  13. package/dist/src/index.js +88 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/src/key-book.d.ts +21 -0
  16. package/dist/src/key-book.d.ts.map +1 -0
  17. package/dist/src/key-book.js +98 -0
  18. package/dist/src/key-book.js.map +1 -0
  19. package/dist/src/metadata-book.d.ts +28 -0
  20. package/dist/src/metadata-book.d.ts.map +1 -0
  21. package/dist/src/metadata-book.js +168 -0
  22. package/dist/src/metadata-book.js.map +1 -0
  23. package/dist/src/pb/peer.d.ts +222 -0
  24. package/dist/src/pb/peer.js +641 -0
  25. package/dist/src/proto-book.d.ts +18 -0
  26. package/dist/src/proto-book.d.ts.map +1 -0
  27. package/dist/src/proto-book.js +170 -0
  28. package/dist/src/proto-book.js.map +1 -0
  29. package/dist/src/store.d.ts +37 -0
  30. package/dist/src/store.d.ts.map +1 -0
  31. package/dist/src/store.js +169 -0
  32. package/dist/src/store.js.map +1 -0
  33. package/package.json +162 -0
  34. package/src/README.md +145 -0
  35. package/src/address-book.ts +330 -0
  36. package/src/errors.ts +5 -0
  37. package/src/index.ts +123 -0
  38. package/src/key-book.ts +117 -0
  39. package/src/metadata-book.ts +200 -0
  40. package/src/pb/peer.d.ts +222 -0
  41. package/src/pb/peer.js +641 -0
  42. package/src/pb/peer.proto +31 -0
  43. package/src/proto-book.ts +204 -0
  44. package/src/store.ts +224 -0
@@ -0,0 +1,204 @@
1
+ import { logger } from '@libp2p/logger'
2
+ import errcode from 'err-code'
3
+ import { codes } from './errors.js'
4
+ import { PeerId } from '@libp2p/peer-id'
5
+ import type { Store } from './store.js'
6
+ import type { PeerStore, ProtoBook } from '@libp2p/interfaces/src/peer-store'
7
+ import { base58btc } from 'multiformats/bases/base58'
8
+
9
+ const log = logger('libp2p:peer-store:proto-book')
10
+
11
+ const EVENT_NAME = 'change:protocols'
12
+
13
+ export class PeerStoreProtoBook implements ProtoBook {
14
+ private emit: PeerStore["emit"]
15
+ private store: Store
16
+
17
+ /**
18
+ * The ProtoBook is responsible for keeping the known supported
19
+ * protocols of a peer
20
+ */
21
+ constructor (emit: PeerStore["emit"], store: Store) {
22
+ this.emit = emit
23
+ this.store = store
24
+ }
25
+
26
+ async get (peerId: PeerId) {
27
+ log('get wait for read lock')
28
+ const release = await this.store.lock.readLock()
29
+ log('get got read lock')
30
+
31
+ try {
32
+ const peer = await this.store.load(peerId)
33
+
34
+ return peer.protocols
35
+ } catch (err: any) {
36
+ if (err.code !== codes.ERR_NOT_FOUND) {
37
+ throw err
38
+ }
39
+ } finally {
40
+ log('get release read lock')
41
+ release()
42
+ }
43
+
44
+ return []
45
+ }
46
+
47
+ async set (peerId: PeerId, protocols: string[]) {
48
+ peerId = PeerId.fromPeerId(peerId)
49
+
50
+ if (!Array.isArray(protocols)) {
51
+ log.error('protocols must be provided to store data')
52
+ throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS)
53
+ }
54
+
55
+ log('set await write lock')
56
+ const release = await this.store.lock.writeLock()
57
+ log('set got write lock')
58
+
59
+ let updatedPeer
60
+
61
+ try {
62
+ try {
63
+ const peer = await this.store.load(peerId)
64
+
65
+ if (new Set([
66
+ ...protocols
67
+ ]).size === peer.protocols.length) {
68
+ return
69
+ }
70
+ } catch (err: any) {
71
+ if (err.code !== codes.ERR_NOT_FOUND) {
72
+ throw err
73
+ }
74
+ }
75
+
76
+ updatedPeer = await this.store.patchOrCreate(peerId, {
77
+ protocols
78
+ })
79
+
80
+ log(`stored provided protocols for ${peerId.toString(base58btc)}`)
81
+ } finally {
82
+ log('set release write lock')
83
+ release()
84
+ }
85
+
86
+ this.emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols })
87
+ }
88
+
89
+ async add (peerId: PeerId, protocols: string[]) {
90
+ peerId = PeerId.fromPeerId(peerId)
91
+
92
+ if (!Array.isArray(protocols)) {
93
+ log.error('protocols must be provided to store data')
94
+ throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS)
95
+ }
96
+
97
+ log('add await write lock')
98
+ const release = await this.store.lock.writeLock()
99
+ log('add got write lock')
100
+
101
+ let updatedPeer
102
+
103
+ try {
104
+ try {
105
+ const peer = await this.store.load(peerId)
106
+
107
+ if (new Set([
108
+ ...peer.protocols,
109
+ ...protocols
110
+ ]).size === peer.protocols.length) {
111
+ return
112
+ }
113
+ } catch (err: any) {
114
+ if (err.code !== codes.ERR_NOT_FOUND) {
115
+ throw err
116
+ }
117
+ }
118
+
119
+ updatedPeer = await this.store.mergeOrCreate(peerId, {
120
+ protocols
121
+ })
122
+
123
+ log(`added provided protocols for ${peerId.toString(base58btc)}`)
124
+ } finally {
125
+ log('add release write lock')
126
+ release()
127
+ }
128
+
129
+ this.emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols })
130
+ }
131
+
132
+ async remove (peerId: PeerId, protocols: string[]) {
133
+ peerId = PeerId.fromPeerId(peerId)
134
+
135
+ if (!Array.isArray(protocols)) {
136
+ log.error('protocols must be provided to store data')
137
+ throw errcode(new Error('protocols must be provided'), codes.ERR_INVALID_PARAMETERS)
138
+ }
139
+
140
+ log('remove await write lock')
141
+ const release = await this.store.lock.writeLock()
142
+ log('remove got write lock')
143
+
144
+ let updatedPeer
145
+
146
+ try {
147
+ try {
148
+ const peer = await this.store.load(peerId)
149
+ const protocolSet = new Set(peer.protocols)
150
+
151
+ for (const protocol of protocols) {
152
+ protocolSet.delete(protocol)
153
+ }
154
+
155
+ if (peer.protocols.length === protocolSet.size) {
156
+ return
157
+ }
158
+
159
+ protocols = Array.from(protocolSet)
160
+ } catch (err: any) {
161
+ if (err.code !== codes.ERR_NOT_FOUND) {
162
+ throw err
163
+ }
164
+ }
165
+
166
+ updatedPeer = await this.store.patchOrCreate(peerId, {
167
+ protocols
168
+ })
169
+ } finally {
170
+ log('remove release write lock')
171
+ release()
172
+ }
173
+
174
+ this.emit(EVENT_NAME, { peerId, protocols: updatedPeer.protocols })
175
+ }
176
+
177
+ async delete (peerId: PeerId) {
178
+ peerId = PeerId.fromPeerId(peerId)
179
+
180
+ log('delete await write lock')
181
+ const release = await this.store.lock.writeLock()
182
+ log('delete got write lock')
183
+ let has
184
+
185
+ try {
186
+ has = await this.store.has(peerId)
187
+
188
+ await this.store.patchOrCreate(peerId, {
189
+ protocols: []
190
+ })
191
+ } catch (err: any) {
192
+ if (err.code !== codes.ERR_NOT_FOUND) {
193
+ throw err
194
+ }
195
+ } finally {
196
+ log('delete release write lock')
197
+ release()
198
+ }
199
+
200
+ if (has) {
201
+ this.emit(EVENT_NAME, { peerId, protocols: [] })
202
+ }
203
+ }
204
+ }
package/src/store.ts ADDED
@@ -0,0 +1,224 @@
1
+ import { logger } from '@libp2p/logger'
2
+ import { PeerId } from '@libp2p/peer-id'
3
+ import errcode from 'err-code'
4
+ import { codes } from './errors.js'
5
+ import { Key } from 'interface-datastore/key'
6
+ import { base32 } from 'multiformats/bases/base32'
7
+ import { Multiaddr } from '@multiformats/multiaddr'
8
+ import { Peer as PeerPB } from './pb/peer.js'
9
+ import mortice from 'mortice'
10
+ import { equals as uint8arrayEquals } from 'uint8arrays/equals'
11
+ import type { Peer } from '@libp2p/interfaces/peer-store'
12
+ import type { Datastore } from 'interface-datastore'
13
+
14
+ const log = logger('libp2p:peer-store:store')
15
+
16
+ const NAMESPACE_COMMON = '/peers/'
17
+
18
+ export interface Store {
19
+ has: (peerId: PeerId) => Promise<boolean>
20
+ save: (peer: Peer) => Promise<Peer>
21
+ load: (peerId: PeerId) => Promise<Peer>
22
+ delete (peerId: PeerId): Promise<void>
23
+ merge: (peerId: PeerId, data: Partial<Peer>) => Promise<Peer>
24
+ mergeOrCreate: (peerId: PeerId, data: Partial<Peer>) => Promise<Peer>
25
+ patch: (peerId: PeerId, data: Partial<Peer>) => Promise<Peer>
26
+ patchOrCreate: (peerId: PeerId, data: Partial<Peer>) => Promise<Peer>
27
+ all: () => AsyncIterable<Peer>
28
+
29
+ lock: {
30
+ readLock: () => Promise<() => void>
31
+ writeLock: () => Promise<() => void>
32
+ }
33
+ }
34
+
35
+
36
+ export class PersistentStore {
37
+ private datastore: Datastore
38
+ public lock: any
39
+
40
+ constructor (datastore: Datastore) {
41
+ this.datastore = datastore
42
+ this.lock = mortice({
43
+ name: 'peer-store',
44
+ singleProcess: true
45
+ })
46
+ }
47
+
48
+ _peerIdToDatastoreKey (peerId: PeerId) {
49
+ if (peerId.type == null) {
50
+ log.error('peerId must be an instance of peer-id to store data')
51
+ throw errcode(new Error('peerId must be an instance of peer-id'), codes.ERR_INVALID_PARAMETERS)
52
+ }
53
+
54
+ const b32key = peerId.toString(base32)
55
+ return new Key(`${NAMESPACE_COMMON}b${b32key}`)
56
+ }
57
+
58
+ async has (peerId: PeerId) {
59
+ return this.datastore.has(this._peerIdToDatastoreKey(peerId))
60
+ }
61
+
62
+ async delete (peerId: PeerId) {
63
+ await this.datastore.delete(this._peerIdToDatastoreKey(peerId))
64
+ }
65
+
66
+ async load (peerId: PeerId) {
67
+ const buf = await this.datastore.get(this._peerIdToDatastoreKey(peerId))
68
+ const peer = PeerPB.decode(buf)
69
+ const metadata = new Map()
70
+
71
+ for (const meta of peer.metadata) {
72
+ metadata.set(meta.key, meta.value)
73
+ }
74
+
75
+ return {
76
+ ...peer,
77
+ id: peerId,
78
+ addresses: peer.addresses.map(({ multiaddr, isCertified }) => ({
79
+ multiaddr: new Multiaddr(multiaddr),
80
+ isCertified: isCertified || false
81
+ })),
82
+ metadata,
83
+ peerRecordEnvelope: peer.peerRecordEnvelope || undefined
84
+ } as Peer
85
+ }
86
+
87
+ async save (peer: Peer) {
88
+ if (peer.pubKey != null && peer.id.publicKey != null && !uint8arrayEquals(peer.pubKey, peer.id.publicKey)) {
89
+ log.error('peer publicKey bytes do not match peer id publicKey bytes')
90
+ throw errcode(new Error('publicKey bytes do not match peer id publicKey bytes'), codes.ERR_INVALID_PARAMETERS)
91
+ }
92
+
93
+ // dedupe addresses
94
+ const addressSet = new Set()
95
+
96
+ const buf = PeerPB.encode({
97
+ addresses: peer.addresses
98
+ .filter(address => {
99
+ if (addressSet.has(address.multiaddr.toString())) {
100
+ return false
101
+ }
102
+
103
+ addressSet.add(address.multiaddr.toString())
104
+ return true
105
+ })
106
+ .sort((a, b) => {
107
+ return a.multiaddr.toString().localeCompare(b.multiaddr.toString())
108
+ })
109
+ .map(({ multiaddr, isCertified }) => ({
110
+ multiaddr: multiaddr.bytes,
111
+ isCertified
112
+ })),
113
+ protocols: peer.protocols.sort(),
114
+ pubKey: peer.pubKey,
115
+ metadata: [...peer.metadata.keys()].sort().map(key => ({ key, value: peer.metadata.get(key) })),
116
+ peerRecordEnvelope: peer.peerRecordEnvelope
117
+ }).finish()
118
+
119
+ await this.datastore.put(this._peerIdToDatastoreKey(peer.id), buf)
120
+
121
+ return this.load(peer.id)
122
+ }
123
+
124
+ async patch (peerId: PeerId, data: Partial<Peer>) {
125
+ const peer = await this.load(peerId)
126
+
127
+ return await this._patch(peerId, data, peer)
128
+ }
129
+
130
+ async patchOrCreate (peerId: PeerId, data: Partial<Peer>) {
131
+ let peer: Peer
132
+
133
+ try {
134
+ peer = await this.load(peerId)
135
+ } catch (err: any) {
136
+ if (err.code !== codes.ERR_NOT_FOUND) {
137
+ throw err
138
+ }
139
+
140
+ peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() }
141
+ }
142
+
143
+ return await this._patch(peerId, data, peer)
144
+ }
145
+
146
+ async _patch (peerId: PeerId, data: Partial<Peer>, peer: Peer) {
147
+ return await this.save({
148
+ ...peer,
149
+ ...data,
150
+ id: peerId
151
+ })
152
+ }
153
+
154
+ async merge (peerId: PeerId, data: Partial<Peer>) {
155
+ const peer = await this.load(peerId)
156
+
157
+ return this._merge(peerId, data, peer)
158
+ }
159
+
160
+ async mergeOrCreate (peerId: PeerId, data: Partial<Peer>) {
161
+ /** @type {Peer} */
162
+ let peer
163
+
164
+ try {
165
+ peer = await this.load(peerId)
166
+ } catch (err: any) {
167
+ if (err.code !== codes.ERR_NOT_FOUND) {
168
+ throw err
169
+ }
170
+
171
+ peer = { id: peerId, addresses: [], protocols: [], metadata: new Map() }
172
+ }
173
+
174
+ return await this._merge(peerId, data, peer)
175
+ }
176
+
177
+ async _merge (peerId: PeerId, data: Partial<Peer>, peer: Peer) {
178
+ // if the peer has certified addresses, use those in
179
+ // favour of the supplied versions
180
+ /** @type {Map<string, boolean>} */
181
+ const addresses = new Map()
182
+
183
+ ;(data.addresses || []).forEach(addr => {
184
+ addresses.set(addr.multiaddr.toString(), addr.isCertified)
185
+ })
186
+
187
+ peer.addresses.forEach(({ multiaddr, isCertified }) => {
188
+ const addrStr = multiaddr.toString()
189
+ addresses.set(addrStr, Boolean(addresses.get(addrStr) || isCertified))
190
+ })
191
+
192
+ return await this.save({
193
+ id: peerId,
194
+ addresses: Array.from(addresses.entries()).map(([addrStr, isCertified]) => {
195
+ return {
196
+ multiaddr: new Multiaddr(addrStr),
197
+ isCertified
198
+ }
199
+ }),
200
+ protocols: Array.from(new Set([
201
+ ...(peer.protocols || []),
202
+ ...(data.protocols || [])
203
+ ])),
204
+ metadata: new Map([
205
+ ...(peer.metadata ? peer.metadata.entries() : []),
206
+ ...(data.metadata ? data.metadata.entries() : [])
207
+ ]),
208
+ pubKey: data.pubKey || (peer != null ? peer.pubKey : undefined),
209
+ peerRecordEnvelope: data.peerRecordEnvelope || (peer != null ? peer.peerRecordEnvelope : undefined)
210
+ })
211
+ }
212
+
213
+ async * all () {
214
+ for await (const key of this.datastore.queryKeys({
215
+ prefix: NAMESPACE_COMMON
216
+ })) {
217
+ // /peers/${peer-id-as-libp2p-key-cid-string-in-base-32}
218
+ const base32Str = key.toString().split('/')[2]
219
+ const buf = base32.decode(base32Str)
220
+
221
+ yield this.load(PeerId.fromBytes(buf))
222
+ }
223
+ }
224
+ }