Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.

Commit e31696c

Browse files
authored
fix: prefix records with key, remove disjoint queries (#239)
1. Prefix dht records with `/dht/record` and dht providers with `/dht/provider` in the datastore to prevent accidentally overwriting the keys in other parts of the codebase 2. Queries are no longer disjoint as it's not in the spec (though we keep a map of peers seen per query to prevent loops) 3. When looking up providers, do not query the datastore for the bare CID thinking we'll find ourselves as a provider as that's not how the provider record keys are stored 4. Add typedefs for query event types so they are in the generated `index.d.ts`
1 parent 7cf52fa commit e31696c

25 files changed

+195
-239
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
- [Peer Routing](#peer-routing)
3030
- [Content Routing](#content-routing)
3131
- [Peer Discovery](#peer-discovery)
32-
- [Implementation Summary](#implementation-summary)
32+
- [Spec](#spec)
3333
- [Contribute](#contribute)
3434
- [License](#license)
3535
## Install
@@ -84,9 +84,9 @@ Note that you may want to supply your own peer discovery function and datastore
8484

8585
[![](https://raw.githubusercontent.com/libp2p/js-libp2p-interfaces/master/src/peer-discovery/img/badge.png)](https://github.com/libp2p/js-libp2p-interfaces/tree/master/packages/interfaces/src/peer-discovery)
8686

87-
### Implementation Summary
87+
## Spec
8888

89-
A [summary](docs/IMPL_SUMMARY.MD) of the algorithms and API for this implementation of Kademlia.
89+
js-libp2p-kad-dht follows the [libp2p/kad-dht spec](https://github.com/libp2p/specs/tree/master/kad-dht) and implements the algorithms described in the [IPFS DHT documentation](https://docs.ipfs.io/concepts/dht/).
9090

9191
## Contribute
9292

docs/IMPL_SUMMARY.MD

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/constants.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ exports.MAX_RECORD_AGE = 36 * hour
1515

1616
exports.PROTOCOL_DHT = '/kad/1.0.0'
1717

18-
exports.PROVIDERS_KEY_PREFIX = '/providers/'
18+
exports.RECORD_KEY_PREFIX = '/dht/record/'
19+
20+
exports.PROVIDER_KEY_PREFIX = '/dht/provider/'
1921

2022
exports.PROVIDERS_LRU_CACHE_SIZE = 256
2123

src/content-fetching/index.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ContentFetching {
2727
/**
2828
* @param {object} params
2929
* @param {import('peer-id')} params.peerId
30-
* @param {import('interface-datastore').Datastore} params.datastore
30+
* @param {import('interface-datastore').Datastore} params.records
3131
* @param {import('libp2p-interfaces/src/types').DhtValidators} params.validators
3232
* @param {import('libp2p-interfaces/src/types').DhtSelectors} params.selectors
3333
* @param {import('../peer-routing').PeerRouting} params.peerRouting
@@ -36,10 +36,10 @@ class ContentFetching {
3636
* @param {import('../network').Network} params.network
3737
* @param {boolean} params.lan
3838
*/
39-
constructor ({ peerId, datastore, validators, selectors, peerRouting, queryManager, routingTable, network, lan }) {
39+
constructor ({ peerId, records, validators, selectors, peerRouting, queryManager, routingTable, network, lan }) {
4040
this._log = utils.logger(`libp2p:kad-dht:${lan ? 'lan' : 'wan'}:content-fetching`)
4141
this._peerId = peerId
42-
this._datastore = datastore
42+
this._records = records
4343
this._validators = validators
4444
this._selectors = selectors
4545
this._peerRouting = peerRouting
@@ -53,7 +53,7 @@ class ContentFetching {
5353
* @param {Uint8Array} rec
5454
*/
5555
async putLocal (key, rec) { // eslint-disable-line require-await
56-
return this._datastore.put(utils.bufferToKey(key), rec)
56+
return this._records.put(utils.bufferToKey(key), rec)
5757
}
5858

5959
/**
@@ -68,7 +68,7 @@ class ContentFetching {
6868
const dsKey = utils.bufferToKey(key)
6969

7070
this._log(`fetching record for key ${dsKey}`)
71-
const raw = await this._datastore.get(dsKey)
71+
const raw = await this._records.get(dsKey)
7272
this._log(`found ${dsKey} in local datastore`)
7373

7474
const rec = Record.deserialize(raw)
@@ -103,7 +103,7 @@ class ContentFetching {
103103
try {
104104
const dsKey = utils.bufferToKey(key)
105105
this._log(`Storing corrected record for key ${dsKey}`)
106-
await this._datastore.put(dsKey, fixupRec)
106+
await this._records.put(dsKey, fixupRec)
107107
} catch (/** @type {any} */ err) {
108108
this._log.error('Failed error correcting self', err)
109109
}
@@ -149,7 +149,7 @@ class ContentFetching {
149149
// store the record locally
150150
const dsKey = utils.bufferToKey(key)
151151
this._log(`storing record for key ${dsKey}`)
152-
await this._datastore.put(dsKey, record)
152+
await this._records.put(dsKey, record)
153153

154154
// put record to the closest peers
155155
yield * pipe(

src/dual-kad-dht.js

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
const { EventEmitter } = require('events')
44
const PeerId = require('peer-id')
5-
const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
65
const utils = require('./utils')
76
const errCode = require('err-code')
87
const merge = require('it-merge')
8+
const { queryErrorEvent } = require('./query/events')
99

1010
const log = utils.logger('libp2p:kad-dht')
1111

@@ -57,7 +57,6 @@ class DualKadDHT extends EventEmitter {
5757
this._wan = wan
5858
this._lan = lan
5959
this._libp2p = libp2p
60-
this._datastore = libp2p.datastore || this._wan._datastore
6160

6261
// handle peers being discovered during processing of DHT messages
6362
this._wan.on('peer', (peerData) => {
@@ -159,6 +158,7 @@ class DualKadDHT extends EventEmitter {
159158
*/
160159
async * get (key, options = {}) { // eslint-disable-line require-await
161160
let queriedPeers = false
161+
let foundValue = false
162162

163163
for await (const event of merge(
164164
this._lan.get(key, options),
@@ -172,6 +172,10 @@ class DualKadDHT extends EventEmitter {
172172

173173
if (event.name === 'VALUE') {
174174
queriedPeers = true
175+
176+
if (event.value != null) {
177+
foundValue = true
178+
}
175179
}
176180

177181
if (event.name === 'SENDING_QUERY') {
@@ -182,24 +186,12 @@ class DualKadDHT extends EventEmitter {
182186
if (!queriedPeers) {
183187
throw errCode(new Error('No peers found in routing table!'), 'ERR_NO_PEERS_IN_ROUTING_TABLE')
184188
}
185-
}
186189

187-
/**
188-
* Remove the given key from the local datastore
189-
*
190-
* @param {Uint8Array} key
191-
*/
192-
async removeLocal (key) {
193-
log(`removeLocal: ${uint8ArrayToString(key, 'base32')}`)
194-
const dsKey = utils.bufferToKey(key)
195-
196-
try {
197-
await this._datastore.delete(dsKey)
198-
} catch (/** @type {any} */ err) {
199-
if (err.code === 'ERR_NOT_FOUND') {
200-
return undefined
201-
}
202-
throw err
190+
if (!foundValue) {
191+
yield queryErrorEvent({
192+
from: this._libp2p.peerId,
193+
error: errCode(new Error('Not found'), 'ERR_NOT_FOUND')
194+
})
203195
}
204196
}
205197

src/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ const { DualKadDHT } = require('./dual-kad-dht')
66
/**
77
* @typedef {import('./types').DHT} DHT
88
* @typedef {import('./kad-dht').KadDHTOps} KadDHTOps
9+
* @typedef {import('./types').QueryEvent} QueryEvent
10+
* @typedef {import('./types').SendingQueryEvent} SendingQueryEvent
11+
* @typedef {import('./types').PeerResponseEvent} PeerResponseEvent
12+
* @typedef {import('./types').FinalPeerEvent} FinalPeerEvent
13+
* @typedef {import('./types').QueryErrorEvent} QueryErrorEvent
14+
* @typedef {import('./types').ProviderEvent} ProviderEvent
15+
* @typedef {import('./types').ValueEvent} ValueEvent
16+
* @typedef {import('./types').AddingPeerEvent} AddingPeerEvent
17+
* @typedef {import('./types').DialingPeerEvent} DialingPeerEvent
918
*/
1019

1120
module.exports = {

src/kad-dht.js

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ const { RoutingTableRefresh } = require('./routing-table/refresh')
99
const utils = require('./utils')
1010
const {
1111
K,
12-
QUERY_SELF_INTERVAL
12+
QUERY_SELF_INTERVAL,
13+
RECORD_KEY_PREFIX
1314
} = require('./constants')
1415
const { Network } = require('./network')
1516
const { ContentFetching } = require('./content-fetching')
@@ -24,6 +25,8 @@ const {
2425
removePrivateAddresses,
2526
removePublicAddresses
2627
} = require('./utils')
28+
const { KeyTransformDatastore } = require('datastore-core')
29+
const { Key } = require('interface-datastore/key')
2730

2831
/**
2932
* @typedef {import('libp2p')} Libp2p
@@ -58,6 +61,40 @@ const {
5861
* @property {PeerData[]} bootstrapPeers
5962
*/
6063

64+
class PrefixTransform {
65+
/**
66+
*
67+
* @param {string} prefix - : ;
68+
*/
69+
constructor (prefix) {
70+
this._prefix = prefix
71+
72+
if (this._prefix.startsWith('/')) {
73+
this._prefix = this._prefix.substring(1)
74+
}
75+
}
76+
77+
/**
78+
* @param {Key} key
79+
*/
80+
convert (key) {
81+
return new Key(`/${this._prefix}${key}`)
82+
}
83+
84+
/**
85+
* @param {Key} key
86+
*/
87+
invert (key) {
88+
const namespaces = key.namespaces()
89+
90+
if (namespaces[0] === this._prefix) {
91+
namespaces.shift()
92+
}
93+
94+
return Key.withNamespaces(namespaces)
95+
}
96+
}
97+
6198
/**
6299
* A DHT implementation modelled after Kademlia with S/Kademlia modifications.
63100
* Original implementation in go: https://github.com/libp2p/go-libp2p-kad-dht.
@@ -127,19 +164,17 @@ class KadDHT extends EventEmitter {
127164
lan
128165
})
129166

130-
/**
131-
* Reference to the datastore, uses an in-memory store if none given.
132-
*
133-
* @type {Datastore}
134-
*/
135-
this._datastore = libp2p.datastore || new MemoryDatastore()
167+
const datastore = libp2p.datastore || new MemoryDatastore()
168+
const records = new KeyTransformDatastore(datastore, new PrefixTransform(RECORD_KEY_PREFIX))
136169

137170
/**
138171
* Provider management
139172
*
140173
* @type {Providers}
141174
*/
142-
this._providers = new Providers(this._datastore)
175+
this._providers = new Providers({
176+
providers: datastore
177+
})
143178

144179
/**
145180
* @type {boolean}
@@ -185,7 +220,7 @@ class KadDHT extends EventEmitter {
185220
})
186221
this._contentFetching = new ContentFetching({
187222
peerId: libp2p.peerId,
188-
datastore: this._datastore,
223+
records,
189224
validators: this._validators,
190225
selectors: this._selectors,
191226
peerRouting: this._peerRouting,
@@ -216,7 +251,7 @@ class KadDHT extends EventEmitter {
216251
peerStore: libp2p.peerStore,
217252
addressable: libp2p,
218253
peerRouting: this._peerRouting,
219-
datastore: this._datastore,
254+
records,
220255
validators: this._validators,
221256
lan
222257
})

0 commit comments

Comments
 (0)