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

Commit 1a943f8

Browse files
vasco-santosAlan Shaw
authored and
Alan Shaw
committed
feat: ipns over dht (#1725)
1 parent 83b8e9b commit 1a943f8

File tree

59 files changed

+606
-488
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+606
-488
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@
127127
"joi": "^13.4.0",
128128
"joi-browser": "^13.4.0",
129129
"joi-multiaddr": "^3.0.0",
130-
"libp2p": "~0.24.0",
130+
"libp2p": "~0.24.1",
131131
"libp2p-bootstrap": "~0.9.3",
132132
"libp2p-crypto": "~0.14.1",
133-
"libp2p-kad-dht": "~0.11.1",
133+
"libp2p-kad-dht": "~0.12.1",
134134
"libp2p-keychain": "~0.3.3",
135135
"libp2p-mdns": "~0.12.0",
136136
"libp2p-mplex": "~0.8.4",

src/core/components/init.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ module.exports = function init (self) {
117117
(_, cb) => {
118118
const offlineDatastore = new OfflineDatastore(self._repo)
119119

120-
self._ipns = new IPNS(offlineDatastore, self._repo, self._peerInfo, self._keychain, self._options)
120+
self._ipns = new IPNS(offlineDatastore, self._repo.datastore, self._peerInfo, self._keychain, self._options)
121121
cb(null, true)
122122
},
123123
// add empty unixfs dir object (go-ipfs assumes this exists)

src/core/components/libp2p.js

Lines changed: 11 additions & 0 deletions
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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,18 @@ module.exports = (self) => {
5454
ipnsStores.push(pubsubDs)
5555
}
5656

57-
// NOTE: IPNS routing is being replaced by the local repo datastore while the IPNS over DHT is not ready
58-
// When DHT is added, if local option enabled, should receive offlineDatastore as well
59-
const offlineDatastore = new OfflineDatastore(self._repo)
60-
ipnsStores.push(offlineDatastore)
57+
// DHT should be added as routing if we are not running with local flag
58+
// TODO: Need to change this logic once DHT is enabled by default, for now fallback to Offline datastore
59+
if (get(self._options, 'EXPERIMENTAL.dht', false) && !self._options.local) {
60+
ipnsStores.push(self._libp2pNode.dht)
61+
} else {
62+
const offlineDatastore = new OfflineDatastore(self._repo)
63+
ipnsStores.push(offlineDatastore)
64+
}
6165

6266
// Create ipns routing with a set of datastores
6367
const routing = new TieredDatastore(ipnsStores)
64-
self._ipns = new IPNS(routing, self._repo, self._peerInfo, self._keychain, self._options)
68+
self._ipns = new IPNS(routing, self._repo.datastore, self._peerInfo, self._keychain, self._options)
6569

6670
self._bitswap = new Bitswap(
6771
self._libp2pNode,

src/core/ipns/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ const path = require('./path')
1717
const defaultRecordTtl = 60 * 1000
1818

1919
class IPNS {
20-
constructor (routing, repo, peerInfo, keychain, options) {
21-
this.publisher = new IpnsPublisher(routing, repo)
22-
this.republisher = new IpnsRepublisher(this.publisher, repo, peerInfo, keychain, options)
20+
constructor (routing, datastore, peerInfo, keychain, options) {
21+
this.publisher = new IpnsPublisher(routing, datastore)
22+
this.republisher = new IpnsRepublisher(this.publisher, datastore, peerInfo, keychain, options)
2323
this.resolver = new IpnsResolver(routing)
2424
this.cache = new Receptacle({ max: 1000 }) // Create an LRU cache with max 1000 items
2525
this.routing = routing

src/core/ipns/publisher.js

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ const defaultRecordTtl = 60 * 60 * 1000
1515

1616
// IpnsPublisher is capable of publishing and resolving names to the IPFS routing system.
1717
class IpnsPublisher {
18-
constructor (routing, repo) {
18+
constructor (routing, datastore) {
1919
this._routing = routing
20-
this._repo = repo
20+
this._datastore = datastore
2121
}
2222

2323
// publish record with a eol
@@ -56,7 +56,6 @@ class IpnsPublisher {
5656
log.error(errMsg)
5757
return callback(errcode(new Error(errMsg), 'ERR_INVALID_PEER_ID'))
5858
}
59-
6059
const publicKey = peerId._pubKey
6160

6261
ipns.embedPublicKey(publicKey, record, (err, embedPublicKeyRecord) => {
@@ -74,9 +73,10 @@ class IpnsPublisher {
7473

7574
series([
7675
(cb) => this._publishEntry(keys.routingKey, embedPublicKeyRecord || record, peerId, cb),
77-
// Publish the public key if a public key cannot be extracted from the ID
78-
// We will be able to deprecate this part in the future, since the public keys will be only in the peerId
79-
(cb) => embedPublicKeyRecord ? this._publishPublicKey(keys.routingPubKey, publicKey, peerId, cb) : cb()
76+
// Publish the public key to support old go-ipfs nodes that are looking for it in the routing
77+
// We will be able to deprecate this part in the future, since the public keys will be only
78+
// in IPNS record and the peerId.
79+
(cb) => this._publishPublicKey(keys.routingPubKey, publicKey, peerId, cb)
8080
], (err) => {
8181
if (err) {
8282
log.error(err)
@@ -159,50 +159,57 @@ class IpnsPublisher {
159159
}
160160

161161
options = options || {}
162-
const checkRouting = !(options.checkRouting === false)
163-
164-
this._repo.datastore.get(ipns.getLocalKey(peerId.id), (err, dsVal) => {
165-
let result
162+
const checkRouting = options.checkRouting !== false
166163

164+
this._datastore.get(ipns.getLocalKey(peerId.id), (err, dsVal) => {
167165
if (err) {
168166
if (err.code !== 'ERR_NOT_FOUND') {
169167
const errMsg = `unexpected error getting the ipns record ${peerId.id} from datastore`
170168

171169
log.error(errMsg)
172170
return callback(errcode(new Error(errMsg), 'ERR_UNEXPECTED_DATASTORE_RESPONSE'))
173-
} else {
174-
if (!checkRouting) {
175-
return callback(null, null)
176-
} else {
177-
// TODO ROUTING - get from DHT
178-
return callback(new Error('not implemented yet'))
179-
}
180171
}
181-
}
182172

183-
if (Buffer.isBuffer(dsVal)) {
184-
result = dsVal
185-
} else {
186-
const errMsg = `found ipns record that we couldn't convert to a value`
173+
if (!checkRouting) {
174+
return callback((errcode(err)))
175+
}
187176

188-
log.error(errMsg)
189-
return callback(errcode(new Error(errMsg), 'ERR_INVALID_IPNS_RECORD'))
190-
}
177+
// Try to get from routing
178+
let keys
179+
try {
180+
keys = ipns.getIdKeys(peerId.toBytes())
181+
} catch (err) {
182+
log.error(err)
183+
return callback(err)
184+
}
191185

192-
// unmarshal data
193-
try {
194-
result = ipns.unmarshal(dsVal)
195-
} catch (err) {
196-
const errMsg = `found ipns record that we couldn't convert to a value`
186+
this._routing.get(keys.routingKey.toBuffer(), (err, res) => {
187+
if (err) {
188+
return callback(err)
189+
}
197190

198-
log.error(errMsg)
199-
return callback(null, null)
191+
// unmarshal data
192+
this._unmarshalData(res, callback)
193+
})
194+
} else {
195+
// unmarshal data
196+
this._unmarshalData(dsVal, callback)
200197
}
201-
202-
callback(null, result)
203198
})
204199
}
205200

201+
_unmarshalData (data, callback) {
202+
let result
203+
try {
204+
result = ipns.unmarshal(data)
205+
} catch (err) {
206+
log.error(err)
207+
return callback(errcode(err, 'ERR_INVALID_RECORD_DATA'))
208+
}
209+
210+
callback(null, result)
211+
}
212+
206213
_updateOrCreateRecord (privKey, value, validity, peerId, callback) {
207214
if (!(PeerId.isPeerId(peerId))) {
208215
const errMsg = `peerId received is not valid`
@@ -212,12 +219,17 @@ class IpnsPublisher {
212219
}
213220

214221
const getPublishedOptions = {
215-
checkRouting: false // TODO ROUTING - change to true
222+
checkRouting: true
216223
}
217224

218225
this._getPublished(peerId, getPublishedOptions, (err, record) => {
219226
if (err) {
220-
return callback(err)
227+
if (err.code !== 'ERR_NOT_FOUND') {
228+
const errMsg = `unexpected error when determining the last published IPNS record for ${peerId.id}`
229+
230+
log.error(errMsg)
231+
return callback(errcode(new Error(errMsg), 'ERR_DETERMINING_PUBLISHED_RECORD'))
232+
}
221233
}
222234

223235
// Determinate the record sequence number
@@ -241,7 +253,7 @@ class IpnsPublisher {
241253
const data = ipns.marshal(entryData)
242254

243255
// Store the new record
244-
this._repo.datastore.put(ipns.getLocalKey(peerId.id), data, (err, res) => {
256+
this._datastore.put(ipns.getLocalKey(peerId.id), data, (err, res) => {
245257
if (err) {
246258
const errMsg = `ipns record for ${value} could not be stored in the datastore`
247259

src/core/ipns/republisher.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ const defaultBroadcastInterval = 4 * hour
1818
const defaultRecordLifetime = 24 * hour
1919

2020
class IpnsRepublisher {
21-
constructor (publisher, repo, peerInfo, keychain, options) {
21+
constructor (publisher, datastore, peerInfo, keychain, options) {
2222
this._publisher = publisher
23-
this._repo = repo
23+
this._datastore = datastore
2424
this._peerInfo = peerInfo
2525
this._keychain = keychain
2626
this._options = options
@@ -160,7 +160,7 @@ class IpnsRepublisher {
160160
return callback(errcode(new Error(errMsg), 'ERR_INVALID_PEER_ID'))
161161
}
162162

163-
this._repo.datastore.get(ipns.getLocalKey(peerId.id), (err, dsVal) => {
163+
this._datastore.get(ipns.getLocalKey(peerId.id), (err, dsVal) => {
164164
// error handling
165165
// no need to republish
166166
if (err && err.notFound) {

src/core/ipns/resolver.js

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict'
22

33
const ipns = require('ipns')
4+
const crypto = require('libp2p-crypto')
45
const PeerId = require('peer-id')
56
const errcode = require('err-code')
67

@@ -96,13 +97,9 @@ class IpnsResolver {
9697
return callback(err)
9798
}
9899

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

101-
// TODO DHT - get public key from routing?
102-
// https://github.com/ipfs/go-ipfs/blob/master/namesys/routing.go#L70
103-
// https://github.com/libp2p/go-libp2p-routing/blob/master/routing.go#L99
104-
105-
this._routing.get(routingKey.toBuffer(), (err, res) => {
102+
this._routing.get(routingKey.toBuffer(), (err, record) => {
106103
if (err) {
107104
if (err.code !== 'ERR_NOT_FOUND') {
108105
const errMsg = `unexpected error getting the ipns record ${peerId.id}`
@@ -116,29 +113,66 @@ class IpnsResolver {
116113
return callback(errcode(new Error(errMsg), 'ERR_NO_RECORD_FOUND'))
117114
}
118115

116+
// IPNS entry
119117
let ipnsEntry
120118
try {
121-
ipnsEntry = ipns.unmarshal(res)
119+
ipnsEntry = ipns.unmarshal(record)
122120
} catch (err) {
123121
const errMsg = `found ipns record that we couldn't convert to a value`
124122

125123
log.error(errMsg)
126124
return callback(errcode(new Error(errMsg), 'ERR_INVALID_RECORD_RECEIVED'))
127125
}
128126

129-
ipns.extractPublicKey(peerId, ipnsEntry, (err, pubKey) => {
127+
// if the record has a public key validate it
128+
if (ipnsEntry.pubKey) {
129+
return this._validateRecord(peerId, ipnsEntry, callback)
130+
}
131+
132+
// Otherwise, try to get the public key from routing
133+
this._routing.get(routingKey.toBuffer(), (err, pubKey) => {
130134
if (err) {
131-
return callback(err)
132-
}
135+
if (err.code !== 'ERR_NOT_FOUND') {
136+
const errMsg = `unexpected error getting the public key for the ipns record ${peerId.id}`
133137

134-
// IPNS entry validation
135-
ipns.validate(pubKey, ipnsEntry, (err) => {
136-
if (err) {
137-
return callback(err)
138+
log.error(errMsg)
139+
return callback(errcode(new Error(errMsg), 'ERR_UNEXPECTED_ERROR_GETTING_PUB_KEY'))
138140
}
141+
const errMsg = `public key requested was not found for ${name} (${routingPubKey}) in the network`
142+
143+
log.error(errMsg)
144+
return callback(errcode(new Error(errMsg), 'ERR_NO_RECORD_FOUND'))
145+
}
146+
147+
try {
148+
// Insert it into the peer id, in order to be validated by IPNS validator
149+
peerId.pubKey = crypto.keys.unmarshalPublicKey(pubKey)
150+
} catch (err) {
151+
const errMsg = `found public key record that we couldn't convert to a value`
152+
153+
log.error(errMsg)
154+
return callback(errcode(new Error(errMsg), 'ERR_INVALID_PUB_KEY_RECEIVED'))
155+
}
156+
157+
this._validateRecord(peerId, ipnsEntry, callback)
158+
})
159+
})
160+
}
161+
162+
// validate a resolved record
163+
_validateRecord (peerId, ipnsEntry, callback) {
164+
ipns.extractPublicKey(peerId, ipnsEntry, (err, pubKey) => {
165+
if (err) {
166+
return callback(err)
167+
}
168+
169+
// IPNS entry validation
170+
ipns.validate(pubKey, ipnsEntry, (err) => {
171+
if (err) {
172+
return callback(err)
173+
}
139174

140-
callback(null, ipnsEntry.value.toString())
141-
})
175+
callback(null, ipnsEntry.value.toString())
142176
})
143177
})
144178
}

src/core/ipns/routing/utils.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
'use strict'
22

33
const multibase = require('multibase')
4+
const ipns = require('ipns')
45

5-
module.exports.encodeBase32 = (buf) => {
6-
const m = multibase.encode('base32', buf).slice(1) // slice off multibase codec
6+
module.exports = {
7+
encodeBase32: (buf) => {
8+
const m = multibase.encode('base32', buf).slice(1) // slice off multibase codec
79

8-
return m.toString().toUpperCase() // should be uppercase for interop with go
10+
return m.toString().toUpperCase() // should be uppercase for interop with go
11+
},
12+
validator: {
13+
func: (key, record, cb) => ipns.validator.validate(record, key, cb)
14+
},
15+
selector: (k, records) => ipns.validator.select(records[0], records[1])
916
}

0 commit comments

Comments
 (0)