This repository has been archived by the owner on Feb 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A working version of **IPNS working locally** is here! π π πͺ Steps: - [x] Create a new repo (**js-ipns**) like it was made with go in the last week ([go-ipns](https://github.com/ipfs/go-ipns)) and port the related code from this PR to there - [x] Resolve IPNS names in publish, in order to verify if they exist (as it is being done for regular files) before being published - [x] Handle remaining parameters in publish and resolve (except ttl). - [x] Test interface core spec [interface-ipfs-core#327](ipfs-inactive/interface-js-ipfs-core#327) - [x] Test interoperability with go. [interop#26](ipfs/interop#26) - [x] Integrate logging - [x] Write unit tests - [x] Add support for the lifetime with nanoseconds precision - [x] Add Cache - [x] Add initializeKeyspace - [x] Republish Some notes, regarding the previous steps: - There is an optional parameter not implemented in this PR, which is `ttl`, since it is still experimental, we can add it in a separate PR. Finally, thanks @Stebalien for your help and time answering all my questions regarding the IPNS implementation in GO. Moreover, since there are no specs, and not that much documentation, I have been writing a document with all the IPNS workflow. It is a WIP by now, but it is currently available [here](ipfs/specs#184). Related PRs: - [x] [js-ipns#4](ipfs/js-ipns#4) - [x] [js-ipfs-repo#173](ipfs/js-ipfs-repo#173) - [x] [js-ipfs#1496](#1496) - [x] [interface-ipfs-core#327](ipfs-inactive/interface-js-ipfs-core#327) - [x] enable `interface-ipfs-core` tests for IPNS in `js-ipfs`
- Loading branch information
1 parent
61b91f8
commit 1110e96
Showing
31 changed files
with
1,653 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
'use strict' | ||
|
||
/* | ||
IPNS is a PKI namespace, where names are the hashes of public keys, and | ||
the private key enables publishing new (signed) values. In both publish | ||
and resolve, the default name used is the node's own PeerID, | ||
which is the hash of its public key. | ||
*/ | ||
module.exports = { | ||
command: 'name <command>', | ||
|
||
description: 'Publish and resolve IPNS names.', | ||
|
||
builder (yargs) { | ||
return yargs.commandDir('name') | ||
}, | ||
|
||
handler (argv) { | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
'use strict' | ||
|
||
const print = require('../../utils').print | ||
|
||
module.exports = { | ||
command: 'publish <ipfsPath>', | ||
|
||
describe: 'Publish IPNS names.', | ||
|
||
builder: { | ||
resolve: { | ||
describe: 'Resolve given path before publishing. Default: true.', | ||
default: true | ||
}, | ||
lifetime: { | ||
alias: 't', | ||
describe: 'Time duration that the record will be valid for. Default: 24h.', | ||
default: '24h' | ||
}, | ||
key: { | ||
alias: 'k', | ||
describe: 'Name of the key to be used or a valid PeerID, as listed by "ipfs key list -l". Default: self.', | ||
default: 'self' | ||
}, | ||
ttl: { | ||
describe: 'Time duration this record should be cached for (caution: experimental).', | ||
default: '' | ||
} | ||
}, | ||
|
||
handler (argv) { | ||
const opts = { | ||
resolve: argv.resolve, | ||
lifetime: argv.lifetime, | ||
key: argv.key, | ||
ttl: argv.ttl | ||
} | ||
|
||
argv.ipfs.name.publish(argv.ipfsPath, opts, (err, result) => { | ||
if (err) { | ||
throw err | ||
} | ||
|
||
print(`Published to ${result.name}: ${result.value}`) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
'use strict' | ||
|
||
const print = require('../../utils').print | ||
|
||
module.exports = { | ||
command: 'resolve [<name>]', | ||
|
||
describe: 'Resolve IPNS names.', | ||
|
||
builder: { | ||
nocache: { | ||
alias: 'n', | ||
describe: 'Do not use cached entries. Default: false.', | ||
default: false | ||
}, | ||
recursive: { | ||
alias: 'r', | ||
recursive: 'Resolve until the result is not an IPNS name. Default: false.', | ||
default: false | ||
} | ||
}, | ||
|
||
handler (argv) { | ||
const opts = { | ||
nocache: argv.nocache, | ||
recursive: argv.recursive | ||
} | ||
|
||
argv.ipfs.name.resolve(argv.name, opts, (err, result) => { | ||
if (err) { | ||
throw err | ||
} | ||
|
||
if (result && result.path) { | ||
print(result.path) | ||
} else { | ||
print(result) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
'use strict' | ||
|
||
const debug = require('debug') | ||
const promisify = require('promisify-es6') | ||
const waterfall = require('async/waterfall') | ||
const parallel = require('async/parallel') | ||
const human = require('human-to-milliseconds') | ||
const crypto = require('libp2p-crypto') | ||
const errcode = require('err-code') | ||
|
||
const log = debug('jsipfs:name') | ||
log.error = debug('jsipfs:name:error') | ||
|
||
const utils = require('../utils') | ||
const path = require('../ipns/path') | ||
|
||
const keyLookup = (ipfsNode, kname, callback) => { | ||
if (kname === 'self') { | ||
return callback(null, ipfsNode._peerInfo.id.privKey) | ||
} | ||
|
||
const pass = ipfsNode._options.pass | ||
|
||
waterfall([ | ||
(cb) => ipfsNode._keychain.exportKey(kname, pass, cb), | ||
(pem, cb) => crypto.keys.import(pem, pass, cb) | ||
], (err, privateKey) => { | ||
if (err) { | ||
log.error(err) | ||
return callback(errcode(err, 'ERR_CANNOT_GET_KEY')) | ||
} | ||
|
||
return callback(null, privateKey) | ||
}) | ||
} | ||
|
||
module.exports = function name (self) { | ||
return { | ||
/** | ||
* IPNS is a PKI namespace, where names are the hashes of public keys, and | ||
* the private key enables publishing new (signed) values. In both publish | ||
* and resolve, the default name used is the node's own PeerID, | ||
* which is the hash of its public key. | ||
* | ||
* @param {String} value ipfs path of the object to be published. | ||
* @param {Object} options ipfs publish options. | ||
* @param {boolean} options.resolve resolve given path before publishing. | ||
* @param {String} options.lifetime time duration that the record will be valid for. | ||
This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are | ||
"ns", "ms", "s", "m", "h". Default is 24h. | ||
* @param {String} options.ttl time duration this record should be cached for (NOT IMPLEMENTED YET). | ||
* This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are | ||
"ns", "ms", "s", "m", "h" (caution: experimental). | ||
* @param {String} options.key name of the key to be used or a valid PeerID, as listed by 'ipfs key list -l'. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
*/ | ||
publish: promisify((value, options, callback) => { | ||
if (typeof options === 'function') { | ||
callback = options | ||
options = {} | ||
} | ||
|
||
options = options || {} | ||
const resolve = !(options.resolve === false) | ||
const lifetime = options.lifetime || '24h' | ||
const key = options.key || 'self' | ||
|
||
if (!self.isOnline()) { | ||
const errMsg = utils.OFFLINE_ERROR | ||
|
||
log.error(errMsg) | ||
return callback(errcode(errMsg, 'OFFLINE_ERROR')) | ||
} | ||
|
||
// TODO: params related logic should be in the core implementation | ||
|
||
// Normalize path value | ||
try { | ||
value = utils.normalizePath(value) | ||
} catch (err) { | ||
log.error(err) | ||
return callback(err) | ||
} | ||
|
||
parallel([ | ||
(cb) => human(lifetime, cb), | ||
// (cb) => ttl ? human(ttl, cb) : cb(), | ||
(cb) => keyLookup(self, key, cb), | ||
// verify if the path exists, if not, an error will stop the execution | ||
(cb) => resolve.toString() === 'true' ? path.resolvePath(self, value, cb) : cb() | ||
], (err, results) => { | ||
if (err) { | ||
log.error(err) | ||
return callback(err) | ||
} | ||
|
||
// Calculate lifetime with nanoseconds precision | ||
const pubLifetime = results[0].toFixed(6) | ||
const privateKey = results[1] | ||
|
||
// TODO IMPROVEMENT - Handle ttl for cache | ||
// const ttl = results[1] | ||
// const privateKey = results[2] | ||
|
||
// Start publishing process | ||
self._ipns.publish(privateKey, value, pubLifetime, callback) | ||
}) | ||
}), | ||
|
||
/** | ||
* Given a key, query the DHT for its best value. | ||
* | ||
* @param {String} name ipns name to resolve. Defaults to your node's peerID. | ||
* @param {Object} options ipfs resolve options. | ||
* @param {boolean} options.nocache do not use cached entries. | ||
* @param {boolean} options.recursive resolve until the result is not an IPNS name. | ||
* @param {function(Error)} [callback] | ||
* @returns {Promise|void} | ||
*/ | ||
resolve: promisify((name, options, callback) => { | ||
if (typeof options === 'function') { | ||
callback = options | ||
options = {} | ||
} | ||
|
||
options = options || {} | ||
const nocache = options.nocache && options.nocache.toString() === 'true' | ||
const recursive = options.recursive && options.recursive.toString() === 'true' | ||
|
||
const local = true // TODO ROUTING - use self._options.local | ||
|
||
if (!self.isOnline() && !local) { | ||
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 (local && nocache) { | ||
const error = 'cannot specify both local and nocache' | ||
|
||
log.error(error) | ||
return callback(errcode(new Error(error), 'ERR_NOCACHE_AND_LOCAL')) | ||
} | ||
|
||
// Set node id as name for being resolved, if it is not received | ||
if (!name) { | ||
name = self._peerInfo.id.toB58String() | ||
} | ||
|
||
if (!name.startsWith('/ipns/')) { | ||
name = `/ipns/${name}` | ||
} | ||
|
||
const resolveOptions = { | ||
nocache, | ||
recursive, | ||
local | ||
} | ||
|
||
self._ipns.resolve(name, self._peerInfo.id, resolveOptions, callback) | ||
}) | ||
} | ||
} |
Oops, something went wrong.