@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.
- package/LICENSE +4 -0
- package/README.md +44 -0
- package/dist/src/address-book.d.ts +31 -0
- package/dist/src/address-book.d.ts.map +1 -0
- package/dist/src/address-book.js +265 -0
- package/dist/src/address-book.js.map +1 -0
- package/dist/src/errors.d.ts +5 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +5 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +43 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +88 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/key-book.d.ts +21 -0
- package/dist/src/key-book.d.ts.map +1 -0
- package/dist/src/key-book.js +98 -0
- package/dist/src/key-book.js.map +1 -0
- package/dist/src/metadata-book.d.ts +28 -0
- package/dist/src/metadata-book.d.ts.map +1 -0
- package/dist/src/metadata-book.js +168 -0
- package/dist/src/metadata-book.js.map +1 -0
- package/dist/src/pb/peer.d.ts +222 -0
- package/dist/src/pb/peer.js +641 -0
- package/dist/src/proto-book.d.ts +18 -0
- package/dist/src/proto-book.d.ts.map +1 -0
- package/dist/src/proto-book.js +170 -0
- package/dist/src/proto-book.js.map +1 -0
- package/dist/src/store.d.ts +37 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +169 -0
- package/dist/src/store.js.map +1 -0
- package/package.json +162 -0
- package/src/README.md +145 -0
- package/src/address-book.ts +330 -0
- package/src/errors.ts +5 -0
- package/src/index.ts +123 -0
- package/src/key-book.ts +117 -0
- package/src/metadata-book.ts +200 -0
- package/src/pb/peer.d.ts +222 -0
- package/src/pb/peer.js +641 -0
- package/src/pb/peer.proto +31 -0
- package/src/proto-book.ts +204 -0
- 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
|
+
}
|