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

feat: add support for ipns name resolve /ipns/<fqdn> #2002

Merged
merged 15 commits into from
Jun 26, 2019
Merged
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ os:
- osx
- windows

script: npx nyc -s npm run test:node --timeout=10000 -- --bail
script: npx nyc -s npx aegir test -t node --timeout 10000 --bail
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov

jobs:
Expand Down Expand Up @@ -47,12 +47,12 @@ jobs:
- stage: test
name: electron-main
script:
- xvfb-run npx aegir test -t electron-main -- --bail
- xvfb-run npx aegir test -t electron-main -- --bail --timeout 10000

- stage: test
name: electron-renderer
script:
- xvfb-run npx aegir test -t electron-renderer -- --bail
- xvfb-run npx aegir test -t electron-renderer -- --bail --timeout 10000

notifications:
email: false
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"cid-tool": "~0.3.0",
"cids": "~0.7.1",
"class-is": "^1.1.0",
"clear-module": "^3.2.0",
hugomrdias marked this conversation as resolved.
Show resolved Hide resolved
"datastore-core": "~0.6.0",
"datastore-pubsub": "~0.1.1",
"debug": "^4.1.0",
Expand All @@ -87,12 +88,13 @@
"get-folder-size": "^2.0.0",
"glob": "^7.1.3",
"hapi-pino": "^6.0.0",
"hashlru": "^2.3.0",
"human-to-milliseconds": "^1.0.0",
"interface-datastore": "~0.6.0",
"ipfs-bitswap": "~0.24.1",
"ipfs-block": "~0.8.1",
"ipfs-block-service": "~0.15.1",
"ipfs-http-client": "^32.0.0",
"ipfs-http-client": "ipfs/js-ipfs-http-client#feat/name-resolve-dns",
"ipfs-http-response": "~0.3.0",
"ipfs-mfs": "~0.11.4",
"ipfs-multipart": "~0.1.0",
Expand All @@ -110,6 +112,7 @@
"ipld-raw": "^4.0.0",
"ipld-zcash": "~0.3.0",
"ipns": "~0.5.2",
"is-domain-name": "^1.0.1",
"is-ipfs": "~0.6.1",
"is-pull-stream": "~0.0.0",
"is-stream": "^2.0.0",
Expand Down Expand Up @@ -183,8 +186,8 @@
"execa": "^1.0.0",
"form-data": "^2.3.3",
"hat": "0.0.3",
"interface-ipfs-core": "~0.104.0",
"ipfsd-ctl": "~0.42.0",
"interface-ipfs-core": "ipfs/interface-js-ipfs-core#feat/name-resolve-dns",
"ipfsd-ctl": "ipfs/js-ipfsd-ctl#feat/name-resolve-dns",
"libp2p-websocket-star": "~0.10.2",
"ncp": "^2.0.0",
"qs": "^6.5.2",
Expand Down
22 changes: 10 additions & 12 deletions src/cli/commands/name/publish.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const print = require('../../utils').print
const { print } = require('../../utils')

module.exports = {
command: 'publish <ipfsPath>',
Expand All @@ -11,36 +11,34 @@ module.exports = {
resolve: {
alias: 'r',
describe: 'Resolve given path before publishing. Default: true.',
default: true
default: true,
type: 'boolean'
},
lifetime: {
alias: 't',
describe: 'Time duration that the record will be valid for. Default: 24h.',
default: '24h'
default: '24h',
type: 'string'
},
key: {
alias: 'k',
describe: 'Name of the key to be used, as listed by "ipfs key list -l". Default: self.',
default: 'self'
default: 'self',
type: 'string'
},
ttl: {
describe: 'Time duration this record should be cached for (caution: experimental).',
default: ''
default: '',
type: 'string'
}
},

handler (argv) {
argv.resolve((async () => {
// yargs-promise adds resolve/reject properties to argv
// resolve should use the alias as resolve will always be overwritten to a function
let resolve = true

if (argv.r === false || argv.r === 'false') {
resolve = false
}

const opts = {
resolve,
resolve: argv.r,
lifetime: argv.lifetime,
key: argv.key,
ttl: argv.ttl
Expand Down
8 changes: 2 additions & 6 deletions src/cli/commands/name/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {
type: 'boolean',
alias: 'r',
describe: 'Resolve until the result is not an IPNS name. Default: false.',
default: false
default: true
}
},

Expand All @@ -32,11 +32,7 @@ module.exports = {
const ipfs = await argv.getIpfs()
const result = await ipfs.name.resolve(argv.name, opts)

if (result && result.path) {
print(result.path)
} else {
print(result)
}
print(result)
})())
}
}
66 changes: 50 additions & 16 deletions src/core/components/name.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const parallel = require('async/parallel')
const human = require('human-to-milliseconds')
const crypto = require('libp2p-crypto')
const errcode = require('err-code')
const mergeOptions = require('merge-options')
const mh = require('multihashes')
const isDomain = require('is-domain-name')

const log = debug('ipfs:name')
log.error = debug('ipfs:name:error')
Expand Down Expand Up @@ -35,6 +38,28 @@ const keyLookup = (ipfsNode, kname, callback) => {
})
}

const appendRemainder = (cb, remainder) => {
return (err, result) => {
if (err) {
return cb(err)
}
if (remainder.length) {
return cb(null, result + '/' + remainder.join('/'))
}
return cb(null, result)
}
}

/**
* @typedef { import("../index") } IPFS
*/

/**
* IPNS - Inter-Planetary Naming System
*
* @param {IPFS} self
* @returns {Object}
*/
module.exports = function name (self) {
return {
/**
Expand Down Expand Up @@ -125,22 +150,15 @@ module.exports = function name (self) {
options = {}
}

options = options || {}
const nocache = options.nocache && options.nocache.toString() === 'true'
const recursive = options.recursive && options.recursive.toString() === 'true'
options = mergeOptions({
nocache: false,
recursive: true
}, options)

const offline = self._options.offline

if (!self.isOnline() && !offline) {
const errMsg = utils.OFFLINE_ERROR

log.error(errMsg)
return callback(errcode(errMsg, 'OFFLINE_ERROR'))
}

// TODO: params related logic should be in the core implementation

if (offline && nocache) {
if (offline && options.nocache) {
const error = 'cannot specify both offline and nocache'

log.error(error)
Expand All @@ -156,12 +174,28 @@ module.exports = function name (self) {
name = `/ipns/${name}`
}

const resolveOptions = {
nocache,
recursive
const [ namespace, hash, ...remainder ] = name.slice(1).split('/')
try {
mh.fromB58String(hash)
} catch (err) {
// lets check if we have a domain ex. /ipns/ipfs.io and resolve with dns
if (isDomain(hash)) {
return self.dns(hash, options, appendRemainder(callback, remainder))
}

log.error(err)
return callback(errcode(new Error('Invalid IPNS name.'), 'ERR_IPNS_INVALID_NAME'))
}

self._ipns.resolve(name, resolveOptions, callback)
// multihash is valid lets resolve with IPNS
// IPNS resolve needs a online daemon
if (!self.isOnline() && !offline) {
const errMsg = utils.OFFLINE_ERROR

log.error(errMsg)
return callback(errcode(errMsg, 'OFFLINE_ERROR'))
}
self._ipns.resolve(`/${namespace}/${hash}`, options, appendRemainder(callback, remainder))
}),
pubsub: namePubsub(self)
}
Expand Down
12 changes: 11 additions & 1 deletion src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@ const defaultRepo = require('./runtime/repo-nodejs')
const preload = require('./preload')
const mfsPreload = require('./mfs-preload')
const ipldOptions = require('./runtime/ipld-nodejs')

/**
* @typedef { import("./ipns/index") } IPNS
*/

/**
*
*
* @class IPFS
* @extends {EventEmitter}
*/
class IPFS extends EventEmitter {
constructor (options) {
super()
Expand Down Expand Up @@ -76,6 +85,7 @@ class IPFS extends EventEmitter {
this._ipld = new Ipld(ipldOptions(this._blockService, this._options.ipld, this.log))
this._preload = preload(this)
this._mfsPreload = mfsPreload(this)
/** @type {IPNS} */
this._ipns = undefined
// eslint-disable-next-line no-console
this._print = this._options.silent ? this.log : console.log
Expand Down
34 changes: 19 additions & 15 deletions src/core/ipns/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

const { createFromPrivKey } = require('peer-id')
const series = require('async/series')
const Receptacle = require('receptacle')

const errcode = require('err-code')
const debug = require('debug')
Expand All @@ -13,20 +12,28 @@ const IpnsPublisher = require('./publisher')
const IpnsRepublisher = require('./republisher')
const IpnsResolver = require('./resolver')
const path = require('./path')

const { normalizePath } = require('../utils')
const TLRU = require('../../utils/tlru')
const defaultRecordTtl = 60 * 1000

class IPNS {
constructor (routing, datastore, peerInfo, keychain, options) {
this.publisher = new IpnsPublisher(routing, datastore)
this.republisher = new IpnsRepublisher(this.publisher, datastore, peerInfo, keychain, options)
this.resolver = new IpnsResolver(routing)
this.cache = new Receptacle({ max: 1000 }) // Create an LRU cache with max 1000 items
this.cache = new TLRU(1000)
this.routing = routing
}

// Publish
publish (privKey, value, lifetime, callback) {
publish (privKey, value, lifetime = IpnsPublisher.defaultRecordLifetime, callback) {
try {
value = normalizePath(value)
} catch (err) {
log.error(err)
return callback(err)
}

series([
(cb) => createFromPrivKey(privKey.bytes, cb),
(cb) => this.publisher.publishWithEOL(privKey, value, lifetime, cb)
Expand All @@ -38,12 +45,12 @@ class IPNS {

log(`IPNS value ${value} was published correctly`)

// Add to cache
// // Add to cache
const id = results[0].toB58String()
const ttEol = parseFloat(lifetime)
const ttl = (ttEol < defaultRecordTtl) ? ttEol : defaultRecordTtl

this.cache.set(id, value, { ttl: ttl })
this.cache.set(id, value, ttl)

log(`IPNS value ${value} was cached correctly`)

Expand Down Expand Up @@ -77,9 +84,7 @@ class IPNS {
const result = this.cache.get(id)

if (result) {
return callback(null, {
path: result
})
return callback(null, result)
}
}

Expand All @@ -91,18 +96,17 @@ class IPNS {

log(`IPNS record from ${name} was resolved correctly`)

callback(null, {
path: result
})
callback(null, result)
})
}

// Initialize keyspace
// sets the ipns record for the given key to point to an empty directory
initializeKeyspace (privKey, value, callback) {
this.publisher.publish(privKey, value, callback)
this.publish(privKey, value, IpnsPublisher.defaultRecordLifetime, callback)
}
}

exports = module.exports = IPNS
exports.path = path
IPNS.path = path

module.exports = IPNS
5 changes: 3 additions & 2 deletions src/core/ipns/publisher.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ log.error = debug('ipfs:ipns:publisher:error')

const ipns = require('ipns')

const defaultRecordTtl = 60 * 60 * 1000
const defaultRecordLifetime = 60 * 60 * 1000

// IpnsPublisher is capable of publishing and resolving names to the IPFS routing system.
class IpnsPublisher {
Expand Down Expand Up @@ -46,7 +46,7 @@ class IpnsPublisher {

// Accepts a keypair, as well as a value (ipfsPath), and publishes it out to the routing system
publish (privKey, value, callback) {
this.publishWithEOL(privKey, value, defaultRecordTtl, callback)
this.publishWithEOL(privKey, value, defaultRecordLifetime, callback)
}

_putRecordToRouting (record, peerId, callback) {
Expand Down Expand Up @@ -269,4 +269,5 @@ class IpnsPublisher {
}
}

IpnsPublisher.defaultRecordLifetime = defaultRecordLifetime
exports = module.exports = IpnsPublisher
4 changes: 2 additions & 2 deletions src/http/api/resources/name.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ exports.resolve = {
query: Joi.object().keys({
arg: Joi.string(),
nocache: Joi.boolean().default(false),
recursive: Joi.boolean().default(false)
recursive: Joi.boolean().default(true)
}).unknown()
},
async handler (request, h) {
Expand All @@ -17,7 +17,7 @@ exports.resolve = {
const res = await ipfs.name.resolve(arg, request.query)

return h.response({
Path: res.path
Path: res
})
}
}
Expand Down
Loading