diff --git a/README.md b/README.md index db6ae8b4..8ad1c9d5 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ The libp2p-kad-dht module offers 3 APIs: Peer Routing, Content Routing and Peer [![](https://github.com/libp2p/interface-peer-discovery/raw/master/img/badge.png)](https://github.com/libp2p/interface-peer-discovery) +`libp2p-kad-dht` provides a discovery service called `Random Walk` (random walks on the DHT to discover more nodes). It is accessible through `dht.randomWalk` and exposes the [Peer Discovery interface](https://github.com/libp2p/interface-peer-discovery). + ## Contribute Feel free to join in. All welcome. Open an [issue](https://github.com/libp2p/js-libp2p-ipfs/issues)! diff --git a/src/index.js b/src/index.js index 6164edd7..11df48be 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ const errors = require('./errors') const privateApi = require('./private') const Providers = require('./providers') const Message = require('./message') +const RandomWalk = require('./random-walk') const assert = require('assert') /** @@ -88,6 +89,13 @@ class KadDHT { // Inject private apis so we don't clutter up this file const pa = privateApi(this) Object.keys(pa).forEach((name) => { this[name] = pa[name] }) + + /** + * Provider management + * + * @type {RandomWalk} + */ + this.randomWalk = new RandomWalk(this) } /** @@ -119,7 +127,7 @@ class KadDHT { */ stop (callback) { this._running = false - this.bootstrapStop() + this.randomWalk.stop() this.providers.stop() this.network.stop(callback) } @@ -514,48 +522,6 @@ class KadDHT { callback(null, this.peerBook.get(p)) }) } - - /** - * Start the bootstrap process. This means running a number of queries every interval requesting random data. - * This is done to keep the dht healthy over time. - * - * @param {number} [queries=1] - how many queries to run per period - * @param {number} [period=300000] - how often to run the the bootstrap process, in milliseconds (5min) - * @param {number} [maxTimeout=10000] - how long to wait for the the bootstrap query to run, in milliseconds (10s) - * @returns {void} - */ - bootstrapStart (queries, period, maxTimeout) { - if (queries == null) { - queries = 1 - } - if (period == null) { - period = 5 * c.minute - } - if (maxTimeout == null) { - maxTimeout = 10 * c.second - } - - // Don't run twice - if (this._bootstrapRunning) { - return - } - - this._bootstrapRunning = setInterval( - () => this._bootstrap(queries, maxTimeout), - period - ) - } - - /** - * Stop the bootstrap process. - * - * @returns {void} - */ - bootstrapStop () { - if (this._bootstrapRunning) { - clearInterval(this._bootstrapRunning) - } - } } module.exports = KadDHT diff --git a/src/private.js b/src/private.js index 0c99a971..5e49daa0 100644 --- a/src/private.js +++ b/src/private.js @@ -6,10 +6,7 @@ const waterfall = require('async/waterfall') const series = require('async/series') const each = require('async/each') const timeout = require('async/timeout') -const times = require('async/times') -const crypto = require('libp2p-crypto') const PeerInfo = require('peer-info') -const multihashing = require('multihashing-async') const utils = require('./utils') const errors = require('./errors') @@ -569,70 +566,5 @@ module.exports = (dht) => ({ _findProvidersSingle (peer, key, callback) { const msg = new Message(Message.TYPES.GET_PROVIDERS, key.buffer, 0) dht.network.sendRequest(peer, msg, callback) - }, - /** - * Do the bootstrap work. - * - * @param {number} queries - * @param {number} maxTimeout - * @returns {void} - * - * @private - */ - _bootstrap (queries, maxTimeout) { - dht._log('bootstrap:start') - times(queries, (i, cb) => { - waterfall([ - (cb) => this._generateBootstrapId(cb), - (id, cb) => timeout((cb) => { - this._bootstrapQuery(id, cb) - }, maxTimeout)(cb) - ], (err) => { - if (err) { - dht._log.error('bootstrap:error', err) - } - dht._log('bootstrap:done') - }) - }) - }, - /** - * The query run during a bootstrap request. - * - * @param {PeerId} id - * @param {function(Error)} callback - * @returns {void} - * - * @private - */ - _bootstrapQuery (id, callback) { - dht._log('bootstrap:query:%s', id.toB58String()) - this.findPeer(id, (err, peer) => { - if (err instanceof errors.NotFoundError) { - // expected case, we asked for random stuff after all - return callback() - } - if (err) { - return callback(err) - } - dht._log('bootstrap:query:found', err, peer) - // wait what, there was something found? - callback(new Error(`Bootstrap peer: ACTUALLY FOUND PEER: ${peer}, ${id.toB58String()}`)) - }) - }, - /** - * Generate a random peer id for bootstrapping purposes. - * - * @param {function(Error, PeerId)} callback - * @returns {void} - * - * @private - */ - _generateBootstrapId (callback) { - multihashing(crypto.randomBytes(16), 'sha2-256', (err, digest) => { - if (err) { - return callback(err) - } - callback(null, new PeerId(digest)) - }) } }) diff --git a/src/random-walk.js b/src/random-walk.js new file mode 100644 index 00000000..177302da --- /dev/null +++ b/src/random-walk.js @@ -0,0 +1,125 @@ +'use strict' + +const times = require('async/times') +const crypto = require('libp2p-crypto') +const waterfall = require('async/waterfall') +const timeout = require('async/timeout') +const multihashing = require('multihashing-async') +const PeerId = require('peer-id') +const assert = require('assert') +const errors = require('./errors') +const c = require('./constants') + +class RandomWalk { + constructor (kadDHT) { + assert(kadDHT, 'Random Walk needs an instance of the Kademlia DHT') + this._running = false + this._kadDHT = kadDHT + } + + /** + * Start the Random Walk process. This means running a number of queries + * every interval requesting random data. This is done to keep the dht + * healthy over time. + * + * @param {number} [queries=1] - how many queries to run per period + * @param {number} [period=300000] - how often to run the the random-walk process, in milliseconds (5min) + * @param {number} [maxTimeout=10000] - how long to wait for the the random-walk query to run, in milliseconds (10s) + * @returns {void} + */ + start (queries, period, maxTimeout) { + if (queries == null) { queries = 1 } + if (period == null) { period = 5 * c.minute } + if (maxTimeout == null) { maxTimeout = 10 * c.second } + // Don't run twice + if (this._running) { return } + + this._running = setInterval( + () => this._walk(queries, maxTimeout), + period + ) + } + + /** + * Stop the random-walk process. + * + * @returns {void} + */ + stop () { + if (this._running) { + clearInterval(this._running) + } + } + + /** + * Do the random walk work. + * + * @param {number} queries + * @param {number} maxTimeout + * @returns {void} + * + * @private + */ + _walk (queries, maxTimeout) { + this._kadDHT._log('random-walk:start') + + times(queries, (i, cb) => { + waterfall([ + (cb) => this._randomPeerId(cb), + (id, cb) => timeout((cb) => { + this._query(id, cb) + }, maxTimeout)(cb) + ], (err) => { + if (err) { return this._kadDHT._log.error('random-walk:error', err) } + + this._kadDHT._log('random-walk:done') + }) + }) + } + + /** + * The query run during a random walk request. + * + * @param {PeerId} id + * @param {function(Error)} callback + * @returns {void} + * + * @private + */ + _query (id, callback) { + this._kadDHT._log('random-walk:query:%s', id.toB58String()) + + this._kadDHT.findPeer(id, (err, peer) => { + if (err instanceof errors.NotFoundError) { + // expected case, we asked for random stuff after all + return callback() + } + if (err) { + return callback(err) + } + this._kadDHT._log('random-walk:query:found', err, peer) + + // wait what, there was something found? Lucky day! + callback(new Error(`random-walk: ACTUALLY FOUND PEER: ${peer}, ${id.toB58String()}`)) + }) + } + + /** + * Generate a random peer id for random-walk purposes. + * + * @param {function(Error, PeerId)} callback + * @returns {void} + * + * @private + */ + _randomPeerId (callback) { + multihashing(crypto.randomBytes(16), 'sha2-256', (err, digest) => { + if (err) { + return callback(err) + } + callback(null, new PeerId(digest)) + }) + } +} + +module.exports = RandomWalk diff --git a/test/kad-dht.spec.js b/test/kad-dht.spec.js index 93b363ea..bc02caa9 100644 --- a/test/kad-dht.spec.js +++ b/test/kad-dht.spec.js @@ -69,7 +69,7 @@ function connect (a, b, callback) { function bootstrap (dhts) { dhts.forEach((dht) => { - dht._bootstrap(3, 10000) + dht.randomWalk._walk(3, 10000) }) }