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

feat: ipns over dht #1725

Merged
merged 9 commits into from
Dec 6, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@
"joi": "^13.4.0",
"joi-browser": "^13.4.0",
"joi-multiaddr": "^3.0.0",
"libp2p": "~0.24.0",
"libp2p": "~0.24.1",
"libp2p-bootstrap": "~0.9.3",
"libp2p-crypto": "~0.14.1",
"libp2p-kad-dht": "~0.11.1",
"libp2p-kad-dht": "~0.12.1",
"libp2p-keychain": "~0.3.3",
"libp2p-mdns": "~0.12.0",
"libp2p-mplex": "~0.8.4",
Expand Down
11 changes: 11 additions & 0 deletions src/core/components/libp2p.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const promisify = require('promisify-es6')
const get = require('lodash/get')
const defaultsDeep = require('@nodeutils/defaults-deep')
const ipnsUtils = require('../ipns/routing/utils')

module.exports = function libp2p (self) {
return {
Expand All @@ -16,6 +17,7 @@ module.exports = function libp2p (self) {

const defaultBundle = (opts) => {
const libp2pDefaults = {
datastore: opts.datastore,
peerInfo: opts.peerInfo,
peerBook: opts.peerBook,
config: {
Expand Down Expand Up @@ -43,6 +45,14 @@ module.exports = function libp2p (self) {
get(opts.config, 'relay.hop.active', false))
}
},
dht: {
validators: {
ipns: ipnsUtils.validator
},
selectors: {
ipns: ipnsUtils.selector
}
},
EXPERIMENTAL: {
dht: get(opts.options, 'EXPERIMENTAL.dht', false),
pubsub: get(opts.options, 'EXPERIMENTAL.pubsub', false)
Expand Down Expand Up @@ -72,6 +82,7 @@ module.exports = function libp2p (self) {
self._libp2pNode = libp2pBundle({
options: self._options,
config: config,
datastore: self._repo.datastore,
peerInfo: self._peerInfo,
peerBook: self._peerInfoBook
})
Expand Down
12 changes: 8 additions & 4 deletions src/core/components/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ module.exports = (self) => {
ipnsStores.push(pubsubDs)
}

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

// Create ipns routing with a set of datastores
const routing = new TieredDatastore(ipnsStores)
Expand Down
71 changes: 42 additions & 29 deletions src/core/ipns/publisher.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ class IpnsPublisher {
log.error(errMsg)
return callback(errcode(new Error(errMsg), 'ERR_INVALID_PEER_ID'))
}

const publicKey = peerId._pubKey

ipns.embedPublicKey(publicKey, record, (err, embedPublicKeyRecord) => {
Expand Down Expand Up @@ -162,47 +161,56 @@ class IpnsPublisher {
const checkRouting = !(options.checkRouting === false)
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved

this._repo.datastore.get(ipns.getLocalKey(peerId.id), (err, dsVal) => {
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
let result

if (err) {
if (err.code !== 'ERR_NOT_FOUND') {
const errMsg = `unexpected error getting the ipns record ${peerId.id} from datastore`

log.error(errMsg)
return callback(errcode(new Error(errMsg), 'ERR_UNEXPECTED_DATASTORE_RESPONSE'))
} else {
if (!checkRouting) {
return callback(null, null)
} else {
// TODO ROUTING - get from DHT
return callback(new Error('not implemented yet'))
}
}
}

if (Buffer.isBuffer(dsVal)) {
result = dsVal
} else {
const errMsg = `found ipns record that we couldn't convert to a value`
if (!checkRouting) {
return callback(null, null)
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
}

log.error(errMsg)
return callback(errcode(new Error(errMsg), 'ERR_INVALID_IPNS_RECORD'))
}
// Try to get from routing
let keys
try {
keys = ipns.getIdKeys(peerId.toBytes())
} catch (err) {
log.error(err)
return callback(err)
}

// unmarshal data
try {
result = ipns.unmarshal(dsVal)
} catch (err) {
const errMsg = `found ipns record that we couldn't convert to a value`
this._routing.get(keys.routingKey.toBuffer(), (err, res) => {
if (err) {
return callback(err)
}

log.error(errMsg)
return callback(null, null)
// unmarshal data
this._unmarshalData(res, callback)
})
} else {
// unmarshal data
this._unmarshalData(dsVal, callback)
}

callback(null, result)
})
}

_unmarshalData (data, callback) {
let result
try {
result = ipns.unmarshal(data)
} catch (err) {
const errMsg = `found ipns record that we couldn't convert to a value`

log.error(errMsg)
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
return callback(errcode(new Error(errMsg), 'ERR_INVALID_RECORD_DATA'))
}

callback(null, result)
}

_updateOrCreateRecord (privKey, value, validity, peerId, callback) {
if (!(PeerId.isPeerId(peerId))) {
const errMsg = `peerId received is not valid`
Expand All @@ -212,12 +220,17 @@ class IpnsPublisher {
}

const getPublishedOptions = {
checkRouting: false // TODO ROUTING - change to true
checkRouting: true
}

this._getPublished(peerId, getPublishedOptions, (err, record) => {
if (err) {
return callback(err)
if (err.code !== 'ERR_NOT_FOUND') {
const errMsg = `unexpected error when determining the last published IPNS record for ${peerId.id}`

log.error(errMsg)
return callback(errcode(new Error(errMsg), 'ERR_DETERMINING_PUBLISHED_RECORD'))
}
}

// Determinate the record sequence number
Expand Down
34 changes: 26 additions & 8 deletions src/core/ipns/resolver.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use strict'

const ipns = require('ipns')
const crypto = require('libp2p-crypto')
const PeerId = require('peer-id')
const errcode = require('err-code')
const auto = require('async/auto')

const debug = require('debug')
const log = debug('jsipfs:ipns:resolver')
Expand Down Expand Up @@ -96,14 +98,15 @@ class IpnsResolver {
return callback(err)
}

const { routingKey } = ipns.getIdKeys(peerId.toBytes())
const { routingKey, routingPubKey } = ipns.getIdKeys(peerId.toBytes())

// TODO DHT - get public key from routing?
// https://github.com/ipfs/go-ipfs/blob/master/namesys/routing.go#L70
// https://github.com/libp2p/go-libp2p-routing/blob/master/routing.go#L99

this._routing.get(routingKey.toBuffer(), (err, res) => {
if (err) {
auto({
// Name should be the hash of a public key retrievable from ipfs.
// We retrieve public key to add it to the PeerId, as the IPNS record may not have it.
pubKey: (cb) => this._routing.get(routingPubKey.toBuffer(), cb),
record: (cb) => this._routing.get(routingKey.toBuffer(), cb)
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
}, (err, res) => {
if (err && !res.record) {
if (err.code !== 'ERR_NOT_FOUND') {
const errMsg = `unexpected error getting the ipns record ${peerId.id}`

Expand All @@ -116,9 +119,24 @@ class IpnsResolver {
return callback(errcode(new Error(errMsg), 'ERR_NO_RECORD_FOUND'))
}

// If public key was found in the routing, add it to the peer id
// otherwise, wait to check if it is embedded in the record.
if (res.pubKey) {
vasco-santos marked this conversation as resolved.
Show resolved Hide resolved
try {
// Insert it into the peer id public key, to be validated by IPNS validator
peerId.pubKey = crypto.keys.unmarshalPublicKey(res.pubKey)
} catch (err) {
const errMsg = `found public key record that we couldn't convert to a value`

log.error(errMsg)
return callback(errcode(new Error(errMsg), 'ERR_INVALID_PUB_KEY_RECEIVED'))
}
}

// IPNS entry
let ipnsEntry
try {
ipnsEntry = ipns.unmarshal(res)
ipnsEntry = ipns.unmarshal(res.record)
} catch (err) {
const errMsg = `found ipns record that we couldn't convert to a value`

Expand Down
13 changes: 10 additions & 3 deletions src/core/ipns/routing/utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
'use strict'

const multibase = require('multibase')
const ipns = require('ipns')

module.exports.encodeBase32 = (buf) => {
const m = multibase.encode('base32', buf).slice(1) // slice off multibase codec
module.exports = {
encodeBase32: (buf) => {
const m = multibase.encode('base32', buf).slice(1) // slice off multibase codec

return m.toString().toUpperCase() // should be uppercase for interop with go
return m.toString().toUpperCase() // should be uppercase for interop with go
},
validator: {
func: (key, record, cb) => ipns.validator.validate(record, key, cb)
},
selector: (k, records) => ipns.validator.select(records[0], records[1])
}
Loading