diff --git a/examples/PeerDiscovery.js b/examples/PeerDiscovery.js new file mode 100644 index 0000000..b070c29 --- /dev/null +++ b/examples/PeerDiscovery.js @@ -0,0 +1,4 @@ +var PeerManager = require('../lib/PeerManager'); +var peerman = new PeerManager(); + +peerman.discover({ limit: 12 }).start(); diff --git a/lib/Peer.js b/lib/Peer.js index 05be302..a73d66e 100644 --- a/lib/Peer.js +++ b/lib/Peer.js @@ -34,8 +34,8 @@ function Peer(host, port, services) { Peer.IPV6_IPV4_PADDING = new Buffer([0,0,0,0,0,0,0,0,0,0,255,255]); Peer.prototype.createConnection = function () { - var c = Net.createConnection(this.port, this.host); - return c; + this.connection = Net.createConnection(this.port, this.host); + return this.connection; }; Peer.prototype.getHostAsBuffer = function () { diff --git a/lib/PeerManager.js b/lib/PeerManager.js index fff56e0..c9f6924 100644 --- a/lib/PeerManager.js +++ b/lib/PeerManager.js @@ -1,10 +1,8 @@ - - var imports = require('soop').imports(); +var extend = imports.extend || require('extend'); var log = imports.log || require('../util/log'); var bitcoreDefaults = imports.config || require('../config'); var Connection = imports.Connection || require ('./Connection'); - var Peer = imports.Peer || require('./Peer'); GetAdjustedTime = imports.GetAdjustedTime || function () { @@ -13,11 +11,13 @@ GetAdjustedTime = imports.GetAdjustedTime || function () { }; function PeerManager(config) { - this.config = config || bitcoreDefaults; + // extend defaults with config + this.config = extend(true, config || {}, bitcoreDefaults); this.active = false; this.timer = null; this.peers = []; + this.pool = []; this.connections = []; this.isConnected = false; this.peerDiscovery = false; @@ -26,6 +26,12 @@ function PeerManager(config) { this.interval = 5000; this.minConnections = 8; this.minKnownPeers = 10; + + // keep track of tried seeds and results + this.seeds = { + resolved: [], + failed: [] + }; } PeerManager.parent = imports.parent || require('events').EventEmitter; @@ -61,6 +67,13 @@ PeerManager.prototype.addPeer = function(peer, port) { } }; +PeerManager.prototype.removePeer = function(peer) { + var index = this.peers.indexOf(peer); + var exists = !!~index; + if (exists) this.peers.splice(index, 1); + return exists; +}; + PeerManager.prototype.checkStatus = function checkStatus() { // Make sure we are connected to all forcePeers if(this.peers.length) { @@ -77,6 +90,13 @@ PeerManager.prototype.checkStatus = function checkStatus() { } }); + // for debug purposes, print how many of our peers are actually connected + var connected = 0 + this.peers.forEach(function(p) { + if (p.connection && !p.connection._connecting) connected++ + }); + log.info(connected + ' of ' + this.peers.length + ' peers connected'); + Object.keys(peerIndex).forEach(function(i) { this.connectTo(peerIndex[i]); }.bind(this)); @@ -84,7 +104,7 @@ PeerManager.prototype.checkStatus = function checkStatus() { }; PeerManager.prototype.connectTo = function(peer) { - log.info('connecting to '+peer); + log.info('connecting to ' + peer); try { return this.addConnection(peer.createConnection(), peer); } catch (e) { @@ -174,10 +194,16 @@ PeerManager.prototype.handleError = function(e) { }; PeerManager.prototype.handleDisconnect = function(e) { - log.info('disconnected from peer '+e.peer); + log.info('disconnected from peer ' + e.peer); var i = this.connections.indexOf(e.conn); if(i != -1) this.connections.splice(i, 1); + this.removePeer(e.peer); + if (this.pool.length) { + log.info('replacing peer using the pool of ' + this.pool.length + ' seeds'); + this.addPeer(this.pool.pop()); + } + if(!this.connections.length) { this.emit('netDisconnected'); this.isConnected = false; @@ -212,4 +238,72 @@ PeerManager.prototype.getActiveConnections = function () { return this.connections.slice(0); }; +PeerManager.prototype.discover = function(options, callback) { + var self = this; + var async = imports.async || require('async'); + var dns = imports.dns || require('dns'); + var networks = imports.networks || require('../networks'); + var seeds = networks[self.config.network].dnsSeeds; + + self.limit = options.limit || 12; + + var dnsExecutor = seeds.map(function(seed) { + return function(done) { + // have we already resolved this seed? + if (~self.seeds.resolved.indexOf(seed)) { + // if so, just pass back cached peer list + return done(null, self.seeds.results[seed]); + } + + // has this seed failed to resolve? + if (~self.seeds.failed.indexOf(seed)) { + // if so, pass back empty results + return done(null, []); + } + + log.info('resolving dns seed '+ seed); + + dns.resolve(seed, function(err, peers) { + if (err) { + log.err('failed to resolve dns seed '+ seed, err); + self.seeds.failed.push(seed); + return done(null, []); + } + + log.info('found '+ peers.length + ' peers from ' + seed); + self.seeds.resolved.push(seed); + + // transform that list into a list of Peer instances + peers = peers.map(function(ip) { + return new Peer(ip, networks.defaultClientPort); + }); + + peers.forEach(function(p) { + if (self.peers.length < self.limit) self.addPeer(p); + else self.pool.push(p); + }); + + self.emit('peers', peers); + + return done(null, peers); + }); + + }; + }); + + // try resolving all seeds + async.parallel(dnsExecutor, function(err, results) { + var peers = []; + + // consolidate all resolved peers into one list + results.forEach(function(peerlist) { + peers = peers.concat(peerlist); + }); + + if (typeof callback === 'function') callback(null, peers); + }); + + return self; +}; + module.exports = require('soop')(PeerManager); diff --git a/networks.js b/networks.js index 27f60c4..787f719 100644 --- a/networks.js +++ b/networks.js @@ -19,7 +19,16 @@ exports.livenet = { prev_hash: buffertools.fill(new Buffer(32), 0), timestamp: 1231006505, bits: 486604799, - } + }, + dnsSeeds: [ + 'seed.bitcoin.sipa.be', + 'dnsseed.bluematt.me', + 'dnsseed.bitcoin.dashjr.org', + 'seed.bitcoinstats.com', + 'seed.bitnodes.io', + 'bitseed.xf2.org' + ], + defaultClientPort: 8333 }; exports.testnet = { @@ -39,5 +48,10 @@ exports.testnet = { prev_hash: buffertools.fill(new Buffer(32), 0), timestamp: 1296688602, bits: 486604799, - } + }, + dnsSeeds: [ + 'testnet-seed.bitcoin.petertodd.org', + 'testnet-seed.bluematt.me' + ], + defaultClientPort: 18333 }; diff --git a/package.json b/package.json index 930691b..e7f53fc 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "socks5-client": "~0.3.6", "brfs": "=1.0.0", "chai": "=1.9.1", - "uglifyify": "=1.2.3" + "uglifyify": "=1.2.3", + "extend": "~1.2.1" }, "devDependencies": { "grunt-contrib-watch": "~0.5.3",