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

Commit 61a44fb

Browse files
committed
feat: ipns over pubsub
1 parent 9a37ac0 commit 61a44fb

28 files changed

+1064
-58
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ Configure remote preload nodes. The remote will preload content added on this no
304304
Enable and configure experimental features.
305305

306306
- `pubsub` (boolean): Enable libp2p pub-sub. (Default: `false`)
307+
- `ipnsPubsub` (boolean): Enable pub-sub on IPNS. (Default: `false`)
307308
- `sharding` (boolean): Enable directory sharding. Directories that have many child objects will be represented by multiple DAG nodes instead of just one. It can improve lookup performance when a directory has several thousand files or more. (Default: `false`)
308309
- `dht` (boolean): Enable KadDHT. **This is currently not interoperable with `go-ipfs`.**
309310

@@ -554,6 +555,9 @@ The core API is grouped into several areas:
554555
- [name](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md)
555556
- [`ipfs.name.publish(value, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepublish)
556557
- [`ipfs.name.resolve(value, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#nameresolve)
558+
- [`ipfs.name.pubsub.cancel(arg, [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepubsubcancel)
559+
- [`ipfs.name.pubsub.state([callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepubsubstate)
560+
- [`ipfs.name.pubsub.subs([callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepubsubsubs)
557561

558562
#### Crypto and Key Management
559563

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"form-data": "^2.3.2",
7373
"hat": "0.0.3",
7474
"interface-ipfs-core": "~0.78.0",
75-
"ipfsd-ctl": "~0.39.1",
75+
"ipfsd-ctl": "~0.39.2",
7676
"mocha": "^5.2.0",
7777
"ncp": "^2.0.0",
7878
"nexpect": "~0.5.0",
@@ -86,13 +86,16 @@
8686
"dependencies": {
8787
"@nodeutils/defaults-deep": "^1.1.0",
8888
"async": "^2.6.1",
89+
"base32.js": "~0.1.0",
8990
"big.js": "^5.1.2",
9091
"binary-querystring": "~0.1.2",
9192
"bl": "^2.0.1",
9293
"boom": "^7.2.0",
9394
"bs58": "^4.0.1",
9495
"byteman": "^1.3.5",
9596
"cids": "~0.5.3",
97+
"datastore-core": "~0.4.0",
98+
"datastore-pubsub": "~0.0.2",
9699
"debug": "^3.1.0",
97100
"err-code": "^1.1.2",
98101
"file-type": "^8.1.0",

src/cli/commands/daemon.js

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ module.exports = {
2525
type: 'boolean',
2626
default: false
2727
})
28+
.option('enable-namesys-pubsub', {
29+
type: 'boolean',
30+
default: false
31+
})
2832
},
2933

3034
handler (argv) {

src/cli/commands/name/pubsub.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict'
2+
3+
/*
4+
Manage and inspect the state of the IPNS pubsub resolver.
5+
Note: this command is experimental and subject to change as the system is refined.
6+
*/
7+
module.exports = {
8+
command: 'pubsub',
9+
10+
description: 'IPNS pubsub management.',
11+
12+
builder (yargs) {
13+
return yargs.commandDir('pubsub')
14+
},
15+
16+
handler (argv) {
17+
}
18+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
const print = require('../../../utils').print
4+
5+
module.exports = {
6+
command: 'cancel <name>',
7+
8+
describe: 'Cancel a name subscription.',
9+
10+
handler (argv) {
11+
argv.ipfs.name.pubsub.cancel(argv.name, (err, result) => {
12+
if (err) {
13+
throw err
14+
} else {
15+
print(result.canceled ? 'canceled' : 'no subscription')
16+
}
17+
})
18+
}
19+
}

src/cli/commands/name/pubsub/state.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
const print = require('../../../utils').print
4+
5+
module.exports = {
6+
command: 'state',
7+
8+
describe: 'Query the state of IPNS pubsub.',
9+
10+
handler (argv) {
11+
argv.ipfs.name.pubsub.state((err, result) => {
12+
if (err) {
13+
throw err
14+
} else {
15+
print(result.enabled ? 'enabled' : 'disabled')
16+
}
17+
})
18+
}
19+
}

src/cli/commands/name/pubsub/subs.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict'
2+
3+
const print = require('../../../utils').print
4+
5+
module.exports = {
6+
command: 'subs',
7+
8+
describe: 'Show current name subscriptions.',
9+
10+
handler (argv) {
11+
argv.ipfs.name.pubsub.subs((err, result) => {
12+
if (err) {
13+
throw err
14+
} else {
15+
result.strings.forEach((s) => {
16+
print(s)
17+
})
18+
}
19+
})
20+
}
21+
}

src/core/components/init.js

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const promisify = require('promisify-es6')
77
const defaultConfig = require('../runtime/config-nodejs.js')
88
const Keychain = require('libp2p-keychain')
99

10+
const IPNS = require('../ipns')
11+
const OfflineDatastore = require('../ipns/routing/offline-datastore')
12+
1013
const addDefaultAssets = require('./init-assets')
1114

1215
module.exports = function init (self) {
@@ -103,6 +106,13 @@ module.exports = function init (self) {
103106
cb(null, true)
104107
}
105108
},
109+
// Setup offline routing for IPNS. This is primarily used for offline ipns modifications, such as the initializeKeyspace feature.
110+
(_, cb) => {
111+
const offlineDatastore = new OfflineDatastore(self._repo)
112+
113+
self._ipns = new IPNS(offlineDatastore, self)
114+
cb(null, true)
115+
},
106116
// add empty unixfs dir object (go-ipfs assumes this exists)
107117
(_, cb) => {
108118
if (opts.emptyRepo) {

src/core/components/libp2p.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ module.exports = function libp2p (self) {
4545
},
4646
EXPERIMENTAL: {
4747
dht: get(opts.options, 'EXPERIMENTAL.dht', false),
48-
pubsub: get(opts.options, 'EXPERIMENTAL.pubsub', false)
48+
pubsub: get(opts.options, 'EXPERIMENTAL.pubsub', false) || get(opts.options, 'EXPERIMENTAL.ipnsPubsub', false)
4949
}
5050
},
5151
connectionManager: get(opts.options, 'connectionManager',

src/core/components/name-pubsub.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
'use strict'
2+
3+
const debug = require('debug')
4+
const errcode = require('err-code')
5+
const promisify = require('promisify-es6')
6+
7+
const log = debug('jsipfs:name-pubsub')
8+
log.error = debug('jsipfs:name-pubsub:error')
9+
10+
const isNamePubsubEnabled = (node) => (
11+
node._options.EXPERIMENTAL.ipnsPubsub && node._libp2pNode._floodSub
12+
)
13+
14+
module.exports = function namePubsub (self) {
15+
return {
16+
/**
17+
* Query the state of IPNS pubsub.
18+
*
19+
* @returns {Promise|void}
20+
*/
21+
state: promisify((callback) => {
22+
callback(null, {
23+
enabled: Boolean(isNamePubsubEnabled(self))
24+
})
25+
}),
26+
/**
27+
* Cancel a name subscription.
28+
*
29+
* @param {String} name subscription name.
30+
* @param {function(Error)} [callback]
31+
* @returns {Promise|void}
32+
*/
33+
cancel: promisify((name, callback) => {
34+
if (!isNamePubsubEnabled(self)) {
35+
const errMsg = 'IPNS pubsub subsystem is not enabled'
36+
37+
log.error(errMsg)
38+
return callback(errcode(errMsg, 'ERR_IPNS_PS_NOT_ENABLED'))
39+
}
40+
41+
self._ipns.pubsub.cancel(name, callback)
42+
}),
43+
/**
44+
* Show current name subscriptions.
45+
*
46+
* @param {function(Error)} [callback]
47+
* @returns {Promise|void}
48+
*/
49+
subs: promisify((callback) => {
50+
if (!isNamePubsubEnabled(self)) {
51+
const errMsg = 'IPNS pubsub subsystem is not enabled'
52+
53+
log.error(errMsg)
54+
return callback(errcode(errMsg, 'ERR_IPNS_PS_NOT_ENABLED'))
55+
}
56+
57+
self._ipns.pubsub.getSubscriptions(callback)
58+
})
59+
}
60+
}

src/core/components/name.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const errcode = require('err-code')
1111
const log = debug('jsipfs:name')
1212
log.error = debug('jsipfs:name:error')
1313

14+
const namePubsub = require('./name-pubsub')
1415
const utils = require('../utils')
1516
const path = require('../ipns/path')
1617

@@ -128,7 +129,7 @@ module.exports = function name (self) {
128129
const nocache = options.nocache && options.nocache.toString() === 'true'
129130
const recursive = options.recursive && options.recursive.toString() === 'true'
130131

131-
const local = true // TODO ROUTING - use self._options.local
132+
const local = self._options.local
132133

133134
if (!self.isOnline() && !local) {
134135
const errMsg = utils.OFFLINE_ERROR
@@ -157,11 +158,11 @@ module.exports = function name (self) {
157158

158159
const resolveOptions = {
159160
nocache,
160-
recursive,
161-
local
161+
recursive
162162
}
163163

164-
self._ipns.resolve(name, self._peerInfo.id, resolveOptions, callback)
165-
})
164+
self._ipns.resolve(name, resolveOptions, callback)
165+
}),
166+
pubsub: namePubsub(self)
166167
}
167168
}

src/core/components/start.js

+25
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
const series = require('async/series')
44
const Bitswap = require('ipfs-bitswap')
5+
const get = require('lodash/get')
56
const setImmediate = require('async/setImmediate')
67
const promisify = require('promisify-es6')
8+
const { TieredDatastore } = require('datastore-core')
9+
10+
const IPNS = require('../ipns')
11+
const Pubsub = require('../ipns/routing/pubsub')
12+
const OfflineDatastore = require('../ipns/routing/offline-datastore')
713

814
module.exports = (self) => {
915
return promisify((callback) => {
@@ -34,6 +40,25 @@ module.exports = (self) => {
3440
},
3541
(cb) => self.libp2p.start(cb),
3642
(cb) => {
43+
// Setup online routing for IPNS with a tiered routing composed by a DHT and a Pubsub router (if properly enabled)
44+
const ipnsStores = []
45+
46+
// Add IPNS pubsub if enabled
47+
let pubsub
48+
if (get(self._options, 'EXPERIMENTAL.ipnsPubsub', false)) {
49+
pubsub = new Pubsub(self)
50+
51+
ipnsStores.push(pubsub)
52+
}
53+
54+
// NOTE: Until the IPNS over DHT is not ready, it is being replaced by the local repo datastore
55+
// When DHT is added, If local option enabled, should receive offlineDatastore as well
56+
const offlineDatastore = new OfflineDatastore(self._repo)
57+
ipnsStores.push(offlineDatastore)
58+
59+
const routing = new TieredDatastore(ipnsStores)
60+
self._ipns = new IPNS(routing, self, pubsub)
61+
3762
self._bitswap = new Bitswap(
3863
self._libp2pNode,
3964
self._repo.blocks,

src/core/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const schema = Joi.object().keys({
2828
}).allow(null),
2929
EXPERIMENTAL: Joi.object().keys({
3030
pubsub: Joi.boolean(),
31+
namesysPubsub: Joi.boolean(),
3132
sharding: Joi.boolean(),
3233
dht: Joi.boolean()
3334
}).allow(null),

src/core/index.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const EventEmitter = require('events')
2020
const config = require('./config')
2121
const boot = require('./boot')
2222
const components = require('./components')
23-
const IPNS = require('./ipns')
23+
2424
// replaced by repo-browser when running in the browser
2525
const defaultRepo = require('./runtime/repo-nodejs')
2626
const preload = require('./preload')
@@ -89,7 +89,7 @@ class IPFS extends EventEmitter {
8989
this._ipld = new Ipld(this._blockService)
9090
this._preload = preload(this)
9191
this._mfsPreload = mfsPreload(this)
92-
this._ipns = new IPNS(null, this)
92+
this._ipns = undefined
9393

9494
// IPFS Core exposed components
9595
// - for booting up a node
@@ -127,6 +127,9 @@ class IPFS extends EventEmitter {
127127
if (this._options.EXPERIMENTAL.pubsub) {
128128
this.log('EXPERIMENTAL pubsub is enabled')
129129
}
130+
if (this._options.EXPERIMENTAL.ipnsPubsub) {
131+
this.log('EXPERIMENTAL IPNS pubsub is enabled')
132+
}
130133
if (this._options.EXPERIMENTAL.sharding) {
131134
this.log('EXPERIMENTAL sharding is enabled')
132135
}

src/core/ipns/index.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ const path = require('./path')
1616
const defaultRecordTtl = 60 * 1000
1717

1818
class IPNS {
19-
constructor (routing, ipfs) {
19+
constructor (routing, ipfs, pubsub) {
2020
this.publisher = new IpnsPublisher(routing, ipfs._repo)
2121
this.republisher = new IpnsRepublisher(this.publisher, ipfs)
2222
this.resolver = new IpnsResolver(routing, ipfs._repo)
2323
this.cache = new Receptacle({ max: 1000 }) // Create an LRU cache with max 1000 items
24+
this.pubsub = pubsub
2425
}
2526

2627
// Publish
@@ -53,7 +54,7 @@ class IPNS {
5354
}
5455

5556
// Resolve
56-
resolve (name, peerId, options, callback) {
57+
resolve (name, options, callback) {
5758
// If recursive, we should not try to get the cached value
5859
if (!options.nocache && !options.recursive) {
5960
// Try to get the record from cache
@@ -67,7 +68,7 @@ class IPNS {
6768
}
6869
}
6970

70-
this.resolver.resolve(name, peerId, options, (err, result) => {
71+
this.resolver.resolve(name, options, (err, result) => {
7172
if (err) {
7273
log.error(err)
7374
return callback(err)

src/core/ipns/path.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ const resolvePath = (ipfsNode, name, callback) => {
1313
if (isIPFS.ipnsPath(name)) {
1414
log(`resolve ipns path ${name}`)
1515

16-
const local = true // TODO ROUTING - use self._options.local
16+
const local = ipfsNode._options.local
1717

1818
const options = {
1919
local: local
2020
}
2121

22-
return ipfsNode._ipns.resolve(name, ipfsNode._peerInfo.id, options, callback)
22+
return ipfsNode._ipns.resolve(name, options, callback)
2323
}
2424

2525
// ipfs path

0 commit comments

Comments
 (0)