Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 7f51f8b

Browse files
committed
feat: ipns over dht
1 parent 2ae1152 commit 7f51f8b

File tree

10 files changed

+491
-222
lines changed

10 files changed

+491
-222
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -118,17 +118,17 @@
118118
"ipld-ethereum": "^2.0.1",
119119
"ipld-git": "~0.2.2",
120120
"ipld-zcash": "~0.1.6",
121-
"ipns": "~0.3.0",
121+
"ipns": "~0.4.2",
122122
"is-ipfs": "~0.4.7",
123123
"is-pull-stream": "~0.0.0",
124124
"is-stream": "^1.1.0",
125125
"joi": "^13.4.0",
126126
"joi-browser": "^13.4.0",
127127
"joi-multiaddr": "^3.0.0",
128-
"libp2p": "~0.24.0",
128+
"libp2p": "libp2p/js-libp2p#master",
129129
"libp2p-bootstrap": "~0.9.3",
130130
"libp2p-crypto": "~0.14.1",
131-
"libp2p-kad-dht": "~0.11.1",
131+
"libp2p-kad-dht": "libp2p/js-libp2p-kad-dht#feat/allow-configurable-validators-and-selectors",
132132
"libp2p-keychain": "~0.3.3",
133133
"libp2p-mdns": "~0.12.0",
134134
"libp2p-mplex": "~0.8.4",

src/core/components/libp2p.js

+11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const promisify = require('promisify-es6')
44
const get = require('lodash/get')
55
const defaultsDeep = require('@nodeutils/defaults-deep')
6+
const ipnsUtils = require('../ipns/routing/utils')
67

78
module.exports = function libp2p (self) {
89
return {
@@ -16,6 +17,7 @@ module.exports = function libp2p (self) {
1617

1718
const defaultBundle = (opts) => {
1819
const libp2pDefaults = {
20+
datastore: opts.datastore,
1921
peerInfo: opts.peerInfo,
2022
peerBook: opts.peerBook,
2123
config: {
@@ -43,6 +45,14 @@ module.exports = function libp2p (self) {
4345
get(opts.config, 'relay.hop.active', false))
4446
}
4547
},
48+
dht: {
49+
validators: {
50+
ipns: ipnsUtils.validator
51+
},
52+
selectors: {
53+
ipns: ipnsUtils.selector
54+
}
55+
},
4656
EXPERIMENTAL: {
4757
dht: get(opts.options, 'EXPERIMENTAL.dht', false),
4858
pubsub: get(opts.options, 'EXPERIMENTAL.pubsub', false)
@@ -72,6 +82,7 @@ module.exports = function libp2p (self) {
7282
self._libp2pNode = libp2pBundle({
7383
options: self._options,
7484
config: config,
85+
datastore: self._repo.datastore,
7586
peerInfo: self._peerInfo,
7687
peerBook: self._peerInfoBook
7788
})

src/core/components/start.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
const series = require('async/series')
44
const Bitswap = require('ipfs-bitswap')
5+
const get = require('lodash/get')
56
const setImmediate = require('async/setImmediate')
67
const promisify = require('promisify-es6')
78
const { TieredDatastore } = require('datastore-core')
89

910
const IPNS = require('../ipns')
1011
const OfflineDatastore = require('../ipns/routing/offline-datastore')
12+
const DhtDatastore = require('../ipns/routing/dht-datastore')
1113

1214
module.exports = (self) => {
1315
return promisify((callback) => {
@@ -43,10 +45,15 @@ module.exports = (self) => {
4345

4446
// TODO Add IPNS pubsub if enabled
4547

46-
// NOTE: IPNS routing is being replaced by the local repo datastore while the IPNS over DHT is not ready
47-
// When DHT is added, if local option enabled, should receive offlineDatastore as well
48-
const offlineDatastore = new OfflineDatastore(self._repo)
49-
ipnsStores.push(offlineDatastore)
48+
// DHT should be added as routing if we are not running with local flag
49+
// TODO: Need to change this logic once DHT is enabled by default, for now fallback to Offline datastore
50+
if (get(self._options, 'EXPERIMENTAL.dht', false) && !self._options.local) {
51+
const dhtDatastore = new DhtDatastore(self._libp2pNode.dht)
52+
ipnsStores.push(dhtDatastore)
53+
} else {
54+
const offlineDatastore = new OfflineDatastore(self._repo)
55+
ipnsStores.push(offlineDatastore)
56+
}
5057

5158
// Create ipns routing with a set of datastores
5259
const routing = new TieredDatastore(ipnsStores)

src/core/ipns/publisher.js

+40-43
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
22

33
const PeerId = require('peer-id')
4-
const Record = require('libp2p-record').Record
54
const { Key } = require('interface-datastore')
65
const series = require('async/series')
76
const errcode = require('err-code')
@@ -57,7 +56,6 @@ class IpnsPublisher {
5756
log.error(errMsg)
5857
return callback(errcode(new Error(errMsg), 'ERR_INVALID_PEER_ID'))
5958
}
60-
6159
const publicKey = peerId._pubKey
6260

6361
ipns.embedPublicKey(publicKey, record, (err, embedPublicKeyRecord) => {
@@ -97,19 +95,17 @@ class IpnsPublisher {
9795
return callback(errcode(new Error(errMsg), 'ERR_INVALID_DATASTORE_KEY'))
9896
}
9997

100-
let rec
98+
let entryData
10199
try {
102100
// Marshal record
103-
const entryData = ipns.marshal(entry)
104-
// Marshal to libp2p record
105-
rec = new Record(key.toBuffer(), entryData)
101+
entryData = ipns.marshal(entry)
106102
} catch (err) {
107103
log.error(err)
108104
return callback(err)
109105
}
110106

111107
// Add record to routing (buffer key)
112-
this._routing.put(key.toBuffer(), rec.serialize(), (err, res) => {
108+
this._routing.put(key.toBuffer(), entryData, (err, res) => {
113109
if (err) {
114110
const errMsg = `ipns record for ${key.toString()} could not be stored in the routing`
115111

@@ -137,17 +133,8 @@ class IpnsPublisher {
137133
return callback(errcode(new Error(errMsg), 'ERR_UNDEFINED_PARAMETER'))
138134
}
139135

140-
let rec
141-
try {
142-
// Marshal to libp2p record
143-
rec = new Record(key.toBuffer(), publicKey.bytes)
144-
} catch (err) {
145-
log.error(err)
146-
return callback(err)
147-
}
148-
149136
// Add public key to routing (buffer key)
150-
this._routing.put(key.toBuffer(), rec.serialize(), (err, res) => {
137+
this._routing.put(key.toBuffer(), publicKey.bytes, (err, res) => {
151138
if (err) {
152139
const errMsg = `public key for ${key.toString()} could not be stored in the routing`
153140

@@ -174,45 +161,55 @@ class IpnsPublisher {
174161
const checkRouting = !(options.checkRouting === false)
175162

176163
this._repo.datastore.get(ipns.getLocalKey(peerId.id), (err, dsVal) => {
177-
let result
178-
179164
if (err) {
180165
if (err.code !== 'ERR_NOT_FOUND') {
181166
const errMsg = `unexpected error getting the ipns record ${peerId.id} from datastore`
182167

183168
log.error(errMsg)
184169
return callback(errcode(new Error(errMsg), 'ERR_UNEXPECTED_DATASTORE_RESPONSE'))
185-
} else {
186-
if (!checkRouting) {
170+
}
171+
172+
if (!checkRouting) {
173+
return callback(null, null)
174+
}
175+
176+
// Try to get from routing
177+
let keys
178+
try {
179+
keys = ipns.getIdKeys(peerId.toBytes())
180+
} catch (err) {
181+
log.error(err)
182+
return callback(err)
183+
}
184+
185+
this._routing.get(keys.routingKey, (err, res) => {
186+
if (err) {
187+
log(`error when determining the last published IPNS record for ${peerId.id}`)
187188
return callback(null, null)
188-
} else {
189-
// TODO ROUTING - get from DHT
190-
return callback(new Error('not implemented yet'))
191189
}
192-
}
193-
}
194190

195-
if (Buffer.isBuffer(dsVal)) {
196-
result = dsVal
191+
// unmarshal data
192+
this._unmarshalData(res, callback)
193+
})
197194
} else {
198-
const errMsg = `found ipns record that we couldn't convert to a value`
199-
200-
log.error(errMsg)
201-
return callback(errcode(new Error(errMsg), 'ERR_INVALID_IPNS_RECORD'))
195+
// unmarshal data
196+
this._unmarshalData(dsVal, callback)
202197
}
198+
})
199+
}
203200

204-
// unmarshal data
205-
try {
206-
result = ipns.unmarshal(dsVal)
207-
} catch (err) {
208-
const errMsg = `found ipns record that we couldn't convert to a value`
201+
_unmarshalData (data, callback) {
202+
let result
203+
try {
204+
result = ipns.unmarshal(data)
205+
} catch (err) {
206+
const errMsg = `found ipns record that we couldn't convert to a value`
209207

210-
log.error(errMsg)
211-
return callback(null, null)
212-
}
208+
log.error(errMsg)
209+
return callback(null, null)
210+
}
213211

214-
callback(null, result)
215-
})
212+
callback(null, result)
216213
}
217214

218215
_updateOrCreateRecord (privKey, value, validity, peerId, callback) {
@@ -224,7 +221,7 @@ class IpnsPublisher {
224221
}
225222

226223
const getPublishedOptions = {
227-
checkRouting: false // TODO ROUTING - change to true
224+
checkRouting: true
228225
}
229226

230227
this._getPublished(peerId, getPublishedOptions, (err, record) => {

src/core/ipns/resolver.js

+22-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use strict'
22

33
const ipns = require('ipns')
4-
const Record = require('libp2p-record').Record
4+
const crypto = require('libp2p-crypto')
55
const PeerId = require('peer-id')
66
const errcode = require('err-code')
7+
const parallel = require('async/parallel')
78

89
const debug = require('debug')
910
const log = debug('jsipfs:ipns:resolver')
@@ -97,13 +98,14 @@ class IpnsResolver {
9798
return callback(err)
9899
}
99100

100-
const { routingKey } = ipns.getIdKeys(peerId.toBytes())
101+
const { routingKey, routingPubKey } = ipns.getIdKeys(peerId.toBytes())
101102

102-
// TODO DHT - get public key from routing?
103-
// https://github.com/ipfs/go-ipfs/blob/master/namesys/routing.go#L70
104-
// https://github.com/libp2p/go-libp2p-routing/blob/master/routing.go#L99
105-
106-
this._routing.get(routingKey.toBuffer(), (err, res) => {
103+
parallel([
104+
// Name should be the hash of a public key retrievable from ipfs.
105+
// We retrieve public key to add it to the PeerId, as the IPNS record may not have it.
106+
(cb) => this._routing.get(routingPubKey.toBuffer(), cb),
107+
(cb) => this._routing.get(routingKey.toBuffer(), cb)
108+
], (err, res) => {
107109
if (err) {
108110
if (err.code !== 'ERR_NOT_FOUND') {
109111
const errMsg = `unexpected error getting the ipns record ${peerId.id}`
@@ -117,10 +119,21 @@ class IpnsResolver {
117119
return callback(errcode(new Error(errMsg), 'ERR_NO_RECORD_FOUND'))
118120
}
119121

122+
// Public key
123+
try {
124+
// Insert it into the peer id public key, to be validated by IPNS validator
125+
peerId.pubKey = crypto.keys.unmarshalPublicKey(res[0])
126+
} catch (err) {
127+
const errMsg = `found public key record that we couldn't convert to a value`
128+
129+
log.error(errMsg)
130+
return callback(errcode(new Error(errMsg), 'ERR_INVALID_PUB_KEY_RECEIVED'))
131+
}
132+
133+
// IPNS entry
120134
let ipnsEntry
121135
try {
122-
const record = Record.deserialize(res)
123-
ipnsEntry = ipns.unmarshal(record.value)
136+
ipnsEntry = ipns.unmarshal(res[1])
124137
} catch (err) {
125138
const errMsg = `found ipns record that we couldn't convert to a value`
126139

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict'
2+
3+
// const { Key } = require('interface-datastore')
4+
// const { encodeBase32 } = require('./utils')
5+
6+
// Dht datastore sets the proper encoding for storing records
7+
class DhtDatastore {
8+
constructor (dht) {
9+
this._dht = dht
10+
}
11+
12+
/**
13+
* Put a value to the dht indexed by the received key properly encoded.
14+
* @param {Buffer} key identifier of the value.
15+
* @param {Buffer} value value to be stored.
16+
* @param {function(Error)} callback
17+
* @returns {void}
18+
*/
19+
put (key, value, callback) {
20+
// encode key properly - base32(/ipns/{cid}) TODO: remove this
21+
// const routingKey = new Key('/' + encodeBase32(key), false)
22+
23+
this._dht.put(key, value, callback)
24+
// this._dht.put(routingKey.toBuffer(), value, callback)
25+
}
26+
27+
/**
28+
* Get a value from the local datastore indexed by the received key properly encoded.
29+
* @param {Buffer} key identifier of the value to be obtained.
30+
* @param {function(Error, Buffer)} callback
31+
* @returns {void}
32+
*/
33+
get (key, callback) {
34+
// encode key properly - base32(/ipns/{cid}) TODO: remove this
35+
// const routingKey = new Key('/' + encodeBase32(key), false)
36+
37+
this._dht.get(key, callback)
38+
// this._dht.get(routingKey.toBuffer(), callback)
39+
}
40+
}
41+
42+
exports = module.exports = DhtDatastore

src/core/ipns/routing/offline-datastore.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const { Key } = require('interface-datastore')
4+
const Record = require('libp2p-record').Record
45
const { encodeBase32 } = require('./utils')
56

67
const errcode = require('err-code')
@@ -48,7 +49,10 @@ class OfflineDatastore {
4849
return callback(errcode(new Error(errMsg), 'ERR_GENERATING_ROUTING_KEY'))
4950
}
5051

51-
this._repo.datastore.put(routingKey, value, callback)
52+
// Marshal to libp2p record as the DHT does
53+
let record = new Record(key, value)
54+
55+
this._repo.datastore.put(routingKey, record.serialize(), callback)
5256
}
5357

5458
/**
@@ -76,7 +80,22 @@ class OfflineDatastore {
7680
return callback(errcode(new Error(errMsg), 'ERR_GENERATING_ROUTING_KEY'))
7781
}
7882

79-
this._repo.datastore.get(routingKey, callback)
83+
this._repo.datastore.get(routingKey, (err, res) => {
84+
if (err) {
85+
return callback(err)
86+
}
87+
88+
// Unmarshal libp2p record as the DHT does
89+
let record
90+
try {
91+
record = Record.deserialize(res)
92+
} catch (err) {
93+
log.error(err)
94+
return callback(err)
95+
}
96+
97+
callback(null, record.value)
98+
})
8099
}
81100

82101
// encode key properly - base32(/ipns/{cid})

0 commit comments

Comments
 (0)