Skip to content
This repository was 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
Show file tree
Hide file tree
Changes from all 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
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 @@ -87,12 +87,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": "^32.0.1",
"ipfs-http-response": "~0.3.0",
"ipfs-mfs": "~0.11.4",
"ipfs-multipart": "~0.1.0",
Expand All @@ -110,6 +111,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 @@ -176,15 +178,16 @@
"aegir": "^19.0.3",
"base64url": "^3.0.1",
"chai": "^4.2.0",
"clear-module": "^3.2.0",
"delay": "^4.1.0",
"detect-node": "^2.0.4",
"dir-compare": "^1.4.0",
"dirty-chai": "^2.0.1",
"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": "~0.105.0",
"ipfsd-ctl": "~0.43.0",
"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