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

Commit 8712542

Browse files
vasco-santosAlan Shaw
authored and
Alan Shaw
committed
feat: ipns over pubsub (#1559)
Co-Authored-By: vasco-santos <[email protected]>
1 parent 6f1381f commit 8712542

23 files changed

+818
-31
lines changed

README.md

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

305305
- `pubsub` (boolean): Enable libp2p pub-sub. (Default: `false`)
306+
- `ipnsPubsub` (boolean): Enable pub-sub on IPNS. (Default: `false`)
306307
- `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`)
307308
- `dht` (boolean): Enable KadDHT. **This is currently not interoperable with `go-ipfs`.**
308309

@@ -561,6 +562,9 @@ The core API is grouped into several areas:
561562

562563
- [name](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md)
563564
- [`ipfs.name.publish(value, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepublish)
565+
- [`ipfs.name.pubsub.cancel(arg, [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepubsubcancel)
566+
- [`ipfs.name.pubsub.state([callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepubsubstate)
567+
- [`ipfs.name.pubsub.subs([callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#namepubsubsubs)
564568
- [`ipfs.name.resolve(value, [options], [callback])`](https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/NAME.md#nameresolve)
565569

566570
#### Crypto and Key Management

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"execa": "^1.0.0",
7070
"form-data": "^2.3.3",
7171
"hat": "0.0.3",
72-
"interface-ipfs-core": "~0.88.0",
72+
"interface-ipfs-core": "~0.89.0",
7373
"ipfsd-ctl": "~0.40.1",
7474
"ncp": "^2.0.0",
7575
"qs": "^6.5.2",
@@ -88,7 +88,9 @@
8888
"byteman": "^1.3.5",
8989
"cid-tool": "~0.2.0",
9090
"cids": "~0.5.5",
91+
"class-is": "^1.1.0",
9192
"datastore-core": "~0.6.0",
93+
"datastore-pubsub": "~0.1.1",
9294
"debug": "^4.1.0",
9395
"deep-extend": "~0.6.0",
9496
"err-code": "^1.1.2",
@@ -118,7 +120,7 @@
118120
"ipld-ethereum": "^2.0.1",
119121
"ipld-git": "~0.2.2",
120122
"ipld-zcash": "~0.1.6",
121-
"ipns": "~0.3.0",
123+
"ipns": "~0.4.3",
122124
"is-ipfs": "~0.4.7",
123125
"is-pull-stream": "~0.0.0",
124126
"is-stream": "^1.1.0",

src/cli/commands/daemon.js

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ module.exports = {
2727
})
2828
.option('local', {
2929
desc: 'Run commands locally to the daemon',
30+
default: false
31+
})
32+
.option('enable-namesys-pubsub', {
3033
type: 'boolean',
3134
default: false
3235
})

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.forEach((s) => {
16+
print(s)
17+
})
18+
}
19+
})
20+
}
21+
}

src/core/components/name-pubsub.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
'use strict'
2+
3+
const debug = require('debug')
4+
const errcode = require('err-code')
5+
const promisify = require('promisify-es6')
6+
7+
const IpnsPubsubDatastore = require('../ipns/routing/pubsub-datastore')
8+
9+
const log = debug('jsipfs:name-pubsub')
10+
log.error = debug('jsipfs:name-pubsub:error')
11+
12+
// Is pubsub enabled
13+
const isNamePubsubEnabled = (node) => {
14+
try {
15+
return Boolean(getPubsubRouting(node))
16+
} catch (err) {
17+
return false
18+
}
19+
}
20+
21+
// Get pubsub from IPNS routing
22+
const getPubsubRouting = (node) => {
23+
if (!node._ipns || !node._options.EXPERIMENTAL.ipnsPubsub) {
24+
const errMsg = 'IPNS pubsub subsystem is not enabled'
25+
26+
throw errcode(errMsg, 'ERR_IPNS_PUBSUB_NOT_ENABLED')
27+
}
28+
29+
// Only one store and it is pubsub
30+
if (IpnsPubsubDatastore.isIpnsPubsubDatastore(node._ipns.routing)) {
31+
return node._ipns.routing
32+
}
33+
34+
// Find in tiered
35+
const pubsub = (node._ipns.routing.stores || []).find(s => IpnsPubsubDatastore.isIpnsPubsubDatastore(s))
36+
37+
if (!pubsub) {
38+
const errMsg = 'IPNS pubsub datastore not found'
39+
40+
throw errcode(errMsg, 'ERR_PUBSUB_DATASTORE_NOT_FOUND')
41+
}
42+
43+
return pubsub
44+
}
45+
46+
module.exports = function namePubsub (self) {
47+
return {
48+
/**
49+
* Query the state of IPNS pubsub.
50+
*
51+
* @returns {Promise|void}
52+
*/
53+
state: promisify((callback) => {
54+
callback(null, {
55+
enabled: isNamePubsubEnabled(self)
56+
})
57+
}),
58+
/**
59+
* Cancel a name subscription.
60+
*
61+
* @param {String} name subscription name.
62+
* @param {function(Error)} [callback]
63+
* @returns {Promise|void}
64+
*/
65+
cancel: promisify((name, callback) => {
66+
let pubsub
67+
try {
68+
pubsub = getPubsubRouting(self)
69+
} catch (err) {
70+
return callback(err)
71+
}
72+
73+
pubsub.cancel(name, callback)
74+
}),
75+
/**
76+
* Show current name subscriptions.
77+
*
78+
* @param {function(Error)} [callback]
79+
* @returns {Promise|void}
80+
*/
81+
subs: promisify((callback) => {
82+
let pubsub
83+
try {
84+
pubsub = getPubsubRouting(self)
85+
} catch (err) {
86+
return callback(err)
87+
}
88+
89+
pubsub.getSubscriptions(callback)
90+
})
91+
}
92+
}

src/core/components/name.js

+3-1
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

@@ -161,6 +162,7 @@ module.exports = function name (self) {
161162
}
162163

163164
self._ipns.resolve(name, resolveOptions, callback)
164-
})
165+
}),
166+
pubsub: namePubsub(self)
165167
}
166168
}

src/core/components/start.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
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')
78
const { TieredDatastore } = require('datastore-core')
89

910
const IPNS = require('../ipns')
11+
const PubsubDatastore = require('../ipns/routing/pubsub-datastore')
1012
const OfflineDatastore = require('../ipns/routing/offline-datastore')
1113

1214
module.exports = (self) => {
@@ -41,7 +43,16 @@ module.exports = (self) => {
4143
// Setup online routing for IPNS with a tiered routing composed by a DHT and a Pubsub router (if properly enabled)
4244
const ipnsStores = []
4345

44-
// TODO Add IPNS pubsub if enabled
46+
// Add IPNS pubsub if enabled
47+
let pubsubDs
48+
if (get(self._options, 'EXPERIMENTAL.ipnsPubsub', false)) {
49+
const pubsub = self._libp2pNode.pubsub
50+
const localDatastore = self._repo.datastore
51+
const peerId = self._peerInfo.id
52+
53+
pubsubDs = new PubsubDatastore(pubsub, localDatastore, peerId)
54+
ipnsStores.push(pubsubDs)
55+
}
4556

4657
// NOTE: IPNS routing is being replaced by the local repo datastore while the IPNS over DHT is not ready
4758
// When DHT is added, if local option enabled, should receive offlineDatastore as well

src/core/config.js

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const schema = Joi.object().keys({
2929
}).allow(null),
3030
EXPERIMENTAL: Joi.object().keys({
3131
pubsub: Joi.boolean(),
32+
ipnsPubsub: Joi.boolean(),
3233
sharding: Joi.boolean(),
3334
dht: Joi.boolean()
3435
}).allow(null),

src/core/index.js

+8
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ class IPFS extends EventEmitter {
165165
if (this._options.EXPERIMENTAL.pubsub) {
166166
this.log('EXPERIMENTAL pubsub is enabled')
167167
}
168+
if (this._options.EXPERIMENTAL.ipnsPubsub) {
169+
if (!this._options.EXPERIMENTAL.pubsub) {
170+
this.log('EXPERIMENTAL pubsub is enabled to use IPNS pubsub')
171+
this._options.EXPERIMENTAL.pubsub = true
172+
}
173+
174+
this.log('EXPERIMENTAL IPNS pubsub is enabled')
175+
}
168176
if (this._options.EXPERIMENTAL.sharding) {
169177
this.log('EXPERIMENTAL sharding is enabled')
170178
}

src/core/ipns/publisher.js

+4-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
22

33
const PeerId = require('peer-id')
4-
const Record = require('libp2p-record').Record
54
const { Key } = require('interface-datastore')
65
const series = require('async/series')
76
const errcode = require('err-code')
@@ -97,19 +96,17 @@ class IpnsPublisher {
9796
return callback(errcode(new Error(errMsg), 'ERR_INVALID_DATASTORE_KEY'))
9897
}
9998

100-
let rec
99+
let entryData
101100
try {
102101
// Marshal record
103-
const entryData = ipns.marshal(entry)
104-
// Marshal to libp2p record
105-
rec = new Record(key.toBuffer(), entryData)
102+
entryData = ipns.marshal(entry)
106103
} catch (err) {
107104
log.error(err)
108105
return callback(err)
109106
}
110107

111108
// Add record to routing (buffer key)
112-
this._routing.put(key.toBuffer(), rec.serialize(), (err, res) => {
109+
this._routing.put(key.toBuffer(), entryData, (err, res) => {
113110
if (err) {
114111
const errMsg = `ipns record for ${key.toString()} could not be stored in the routing`
115112

@@ -137,17 +134,8 @@ class IpnsPublisher {
137134
return callback(errcode(new Error(errMsg), 'ERR_UNDEFINED_PARAMETER'))
138135
}
139136

140-
let rec
141-
try {
142-
// Marshal to libp2p record
143-
rec = new Record(key.toBuffer(), publicKey.bytes)
144-
} catch (err) {
145-
log.error(err)
146-
return callback(err)
147-
}
148-
149137
// Add public key to routing (buffer key)
150-
this._routing.put(key.toBuffer(), rec.serialize(), (err, res) => {
138+
this._routing.put(key.toBuffer(), publicKey.bytes, (err, res) => {
151139
if (err) {
152140
const errMsg = `public key for ${key.toString()} could not be stored in the routing`
153141

src/core/ipns/resolver.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict'
22

33
const ipns = require('ipns')
4-
const Record = require('libp2p-record').Record
54
const PeerId = require('peer-id')
65
const errcode = require('err-code')
76

@@ -119,8 +118,7 @@ class IpnsResolver {
119118

120119
let ipnsEntry
121120
try {
122-
const record = Record.deserialize(res)
123-
ipnsEntry = ipns.unmarshal(record.value)
121+
ipnsEntry = ipns.unmarshal(res)
124122
} catch (err) {
125123
const errMsg = `found ipns record that we couldn't convert to a value`
126124

0 commit comments

Comments
 (0)