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

Commit 46222e1

Browse files
jacobheunalanshaw
authored andcommitted
feat: add libp2p factory config option with example (#1470)
Resolves #1463 and libp2p/js-libp2p#222 This adds the ability to pass a libp2p generator function to the ipfs configuration. This makes it easier for users to add custom modules to libp2p that require some startup time properties, like peerInfo (libp2p/js-libp2p#222). I have also included an example of how to do this. I think this makes complex libp2p configuration much cleaner and easier to do. Tests have been added for the old configuration options and the new generator option. I isolated them to avoid spinning up a full ipfs node.
1 parent 0b65a1d commit 46222e1

File tree

9 files changed

+444
-40
lines changed

9 files changed

+444
-40
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,11 @@ Modify the default IPFS node config. This object will be *merged* with the defau
320320
| Type | Default |
321321
|------|---------|
322322
| object | [`libp2p-nodejs.js`](https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/libp2p-nodejs.js) in Node.js, [`libp2p-browser.js`](https://github.com/ipfs/js-ipfs/blob/master/src/core/runtime/libp2p-browser.js) in browsers |
323+
| function | [`libp2p bundle`](examples/custom-libp2p) |
323324

324-
Add custom modules to the libp2p stack of your node.
325+
The libp2p option allows you to build your libp2p node by configuration, or via a bundle function. If you are looking to just modify the below options, using the object format is the quickest way to get the default features of libp2p. If you need to create a more customized libp2p node, such as with custom transports or peer/content routers that need some of the ipfs data on startup, a custom bundle is a great way to achieve this.
326+
327+
You can see the bundle in action in the [custom libp2p example](examples/custom-libp2p).
325328

326329
- `modules` (object):
327330
- `transport` (Array<[libp2p.Transport](https://github.com/libp2p/interface-transport)>): An array of Libp2p transport classes/instances to use _instead_ of the defaults. See [libp2p/interface-transport](https://github.com/libp2p/interface-transport) for details.

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Let us know if you find any issue or if you want to contribute and add a new tut
2323
- [js-ipfs in electron](./run-in-electron)
2424
- [Using streams to add a directory of files to ipfs](./browser-add-readable-stream)
2525
- [Customizing the ipfs repository](./custom-ipfs-repo)
26+
- [Customizing your libp2p bundle](./custom-libp2p)
2627
- [Streaming video from ipfs to the browser using `ReadableStream`s](./browser-readablestream)
2728
- [The Mutable File System in the browser](./browser-mfs)
2829

examples/custom-libp2p/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Customizing your libp2p bundle
2+
3+
This example shows you how to make full use of the ipfs configuration to create a libp2p bundle function. As IPFS applications become more complex, their needs for a custom libp2p bundle also grow. Instead of fighting with configuration options, you can use your own libp2p bundle function to get exactly what you need.
4+
5+
## Run this example
6+
7+
Running this example should result in metrics being logged out to the console every few seconds.
8+
9+
```
10+
> npm install
11+
> npm start
12+
```
13+
14+
## Play with the configuration!
15+
16+
With the metrics for peers and bandwidth stats being logged out, try playing around with the nodes configuration to see what kind of metrics you can get. How many peers are you getting? What does your bandwidth look like?
17+
18+
This is also a good opportunity to explore the various stats that ipfs offers! Not seeing a statistic you think would be useful? We'd love to have you [contribute](https://github.com/ipfs/js-ipfs/blob/master/CONTRIBUTING.md)!

examples/custom-libp2p/index.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
'use strict'
2+
3+
const Libp2p = require('libp2p')
4+
const IPFS = require('ipfs')
5+
const TCP = require('libp2p-tcp')
6+
const MulticastDNS = require('libp2p-mdns')
7+
const WebSocketStar = require('libp2p-websocket-star')
8+
const Bootstrap = require('libp2p-railing')
9+
const SPDY = require('libp2p-spdy')
10+
const KadDHT = require('libp2p-kad-dht')
11+
const MPLEX = require('libp2p-mplex')
12+
const SECIO = require('libp2p-secio')
13+
const assert = require('assert')
14+
15+
/**
16+
* Options for the libp2p bundle
17+
* @typedef {Object} libp2pBundle~options
18+
* @property {PeerInfo} peerInfo - The PeerInfo of the IPFS node
19+
* @property {PeerBook} peerBook - The PeerBook of the IPFS node
20+
* @property {Object} config - The config of the IPFS node
21+
* @property {Object} options - The options given to the IPFS node
22+
*/
23+
24+
/**
25+
* This is the bundle we will use to create our fully customized libp2p bundle.
26+
*
27+
* @param {libp2pBundle~options} opts The options to use when generating the libp2p node
28+
* @returns {Libp2p} Our new libp2p node
29+
*/
30+
const libp2pBundle = (opts) => {
31+
// Set convenience variables to clearly showcase some of the useful things that are available
32+
const peerInfo = opts.peerInfo
33+
const peerBook = opts.peerBook
34+
const bootstrapList = opts.config.Bootstrap
35+
36+
// Create our WebSocketStar transport and give it our PeerId, straight from the ipfs node
37+
const wsstar = new WebSocketStar({
38+
id: peerInfo.id
39+
})
40+
41+
// Build and return our libp2p node
42+
return new Libp2p({
43+
peerInfo,
44+
peerBook,
45+
// Lets limit the connection managers peers and have it check peer health less frequently
46+
connectionManager: {
47+
maxPeers: 25,
48+
pollInterval: 5000
49+
},
50+
modules: {
51+
transport: [
52+
TCP,
53+
wsstar
54+
],
55+
streamMuxer: [
56+
MPLEX,
57+
SPDY
58+
],
59+
connEncryption: [
60+
SECIO
61+
],
62+
peerDiscovery: [
63+
MulticastDNS,
64+
Bootstrap,
65+
wsstar.discovery
66+
],
67+
dht: KadDHT
68+
},
69+
config: {
70+
peerDiscovery: {
71+
mdns: {
72+
interval: 10000,
73+
enabled: true
74+
},
75+
bootstrap: {
76+
interval: 10000,
77+
enabled: true,
78+
list: bootstrapList
79+
}
80+
},
81+
// Turn on relay with hop active so we can connect to more peers
82+
relay: {
83+
enabled: true,
84+
hop: {
85+
enabled: true,
86+
active: true
87+
}
88+
},
89+
dht: {
90+
kBucketSize: 20
91+
},
92+
EXPERIMENTAL: {
93+
dht: true,
94+
pubsub: true
95+
}
96+
}
97+
})
98+
}
99+
100+
// Now that we have our custom libp2p bundle, let's start up the ipfs node!
101+
const node = new IPFS({
102+
libp2p: libp2pBundle
103+
})
104+
105+
// Listen for the node to start, so we can log out some metrics
106+
node.once('start', (err) => {
107+
assert.ifError(err, 'Should startup without issue')
108+
109+
// Lets log out the number of peers we have every 2 seconds
110+
setInterval(() => {
111+
node.swarm.peers((err, peers) => {
112+
if (err) {
113+
console.log('An error occurred trying to check our peers:', err)
114+
process.exit(1)
115+
}
116+
console.log(`The node now has ${peers.length} peers.`)
117+
})
118+
}, 2000)
119+
120+
// Log out the bandwidth stats every 4 seconds so we can see how our configuration is doing
121+
setInterval(() => {
122+
node.stats.bw((err, stats) => {
123+
if (err) {
124+
console.log('An error occurred trying to check our stats:', err)
125+
}
126+
console.log(`\nBandwidth Stats: ${JSON.stringify(stats, null, 2)}\n`)
127+
})
128+
}, 4000)
129+
})

examples/custom-libp2p/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "custom-libp2p",
3+
"version": "0.1.0",
4+
"description": "Customizing your libp2p node",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"start": "node index.js"
9+
},
10+
"license": "MIT",
11+
"dependencies": {
12+
"ipfs": "file:../../",
13+
"libp2p": "~0.22.0",
14+
"libp2p-kad-dht": "~0.10.1",
15+
"libp2p-mdns": "~0.12.0",
16+
"libp2p-mplex": "~0.8.0",
17+
"libp2p-railing": "~0.9.2",
18+
"libp2p-secio": "~0.10.0",
19+
"libp2p-spdy": "~0.12.1",
20+
"libp2p-tcp": "~0.12.0",
21+
"libp2p-websocket-star": "~0.8.1"
22+
}
23+
}

src/core/components/libp2p.js

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,64 @@ module.exports = function libp2p (self) {
1616
return callback(err)
1717
}
1818

19-
const libp2pDefaults = {
20-
peerInfo: self._peerInfo,
21-
peerBook: self._peerInfoBook,
22-
config: {
23-
peerDiscovery: {
24-
mdns: {
25-
enabled: get(self._options, 'config.Discovery.MDNS.Enabled',
26-
get(config, 'Discovery.MDNS.Enabled', true))
19+
const defaultBundle = (opts) => {
20+
const libp2pDefaults = {
21+
peerInfo: opts.peerInfo,
22+
peerBook: opts.peerBook,
23+
config: {
24+
peerDiscovery: {
25+
mdns: {
26+
enabled: get(opts.options, 'config.Discovery.MDNS.Enabled',
27+
get(opts.config, 'Discovery.MDNS.Enabled', true))
28+
},
29+
webRTCStar: {
30+
enabled: get(opts.options, 'config.Discovery.webRTCStar.Enabled',
31+
get(opts.config, 'Discovery.webRTCStar.Enabled', true))
32+
},
33+
bootstrap: {
34+
list: get(opts.options, 'config.Bootstrap',
35+
get(opts.config, 'Bootstrap', []))
36+
}
2737
},
28-
webRTCStar: {
29-
enabled: get(self._options, 'config.Discovery.webRTCStar.Enabled',
30-
get(config, 'Discovery.webRTCStar.Enabled', true))
38+
relay: {
39+
enabled: get(opts.options, 'relay.enabled',
40+
get(opts.config, 'relay.enabled', false)),
41+
hop: {
42+
enabled: get(opts.options, 'relay.hop.enabled',
43+
get(opts.config, 'relay.hop.enabled', false)),
44+
active: get(opts.options, 'relay.hop.active',
45+
get(opts.config, 'relay.hop.active', false))
46+
}
3147
},
32-
bootstrap: {
33-
list: get(self._options, 'config.Bootstrap',
34-
get(config, 'Bootstrap', []))
35-
}
36-
},
37-
relay: {
38-
enabled: get(self._options, 'relay.enabled',
39-
get(config, 'relay.enabled', false)),
40-
hop: {
41-
enabled: get(self._options, 'relay.hop.enabled',
42-
get(config, 'relay.hop.enabled', false)),
43-
active: get(self._options, 'relay.hop.active',
44-
get(config, 'relay.hop.active', false))
48+
EXPERIMENTAL: {
49+
dht: get(opts.options, 'EXPERIMENTAL.dht', false),
50+
pubsub: get(opts.options, 'EXPERIMENTAL.pubsub', false)
4551
}
4652
},
47-
EXPERIMENTAL: {
48-
dht: get(self._options, 'EXPERIMENTAL.dht', false),
49-
pubsub: get(self._options, 'EXPERIMENTAL.pubsub', false)
50-
}
51-
},
52-
connectionManager: get(self._options, 'connectionManager',
53-
get(config, 'connectionManager', {}))
53+
connectionManager: get(opts.options, 'connectionManager',
54+
get(opts.config, 'connectionManager', {}))
55+
}
56+
57+
const libp2pOptions = defaultsDeep(
58+
get(self._options, 'libp2p', {}),
59+
libp2pDefaults
60+
)
61+
62+
return new Node(libp2pOptions)
5463
}
5564

56-
const libp2pOptions = defaultsDeep(
57-
get(self._options, 'libp2p', {}),
58-
libp2pDefaults
59-
)
65+
// Always create libp2p via a bundle function
66+
let libp2pBundle = get(self._options, 'libp2p', null)
67+
if (typeof libp2pBundle !== 'function') {
68+
libp2pBundle = defaultBundle
69+
}
6070

61-
self._libp2pNode = new Node(libp2pOptions)
71+
self._libp2pNode = libp2pBundle({
72+
options: self._options,
73+
config: config,
74+
peerInfo: self._peerInfo,
75+
peerBook: self._peerInfoBook
76+
})
6277

6378
self._libp2pNode.on('peer:discovery', (peerInfo) => {
6479
const dial = () => {

src/core/config.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ const schema = Joi.object().keys({
4949
}).allow(null),
5050
Bootstrap: Joi.array().items(Joi.multiaddr().IPFS().options({ convert: false }))
5151
}).allow(null),
52-
libp2p: Joi.object().keys({
53-
modules: Joi.object().allow(null) // TODO: schemas for libp2p modules?
54-
}).allow(null)
52+
libp2p: Joi.alternatives().try(
53+
Joi.func(),
54+
Joi.object().keys({
55+
modules: Joi.object().allow(null) // TODO: schemas for libp2p modules?
56+
})
57+
).allow(null)
5558
}).options({ allowUnknown: true })
5659

5760
module.exports.validate = (config) => Joi.attempt(config, schema)

test/core/config.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ describe('config', () => {
226226
{ libp2p: { modules: null } },
227227
{ libp2p: { modules: undefined } },
228228
{ libp2p: { unknown: 'value' } },
229+
{ libp2p: () => {} },
229230
{ libp2p: null },
230231
{ libp2p: undefined }
231232
]

0 commit comments

Comments
 (0)