'use strict'; var dns = require('dns'); var EventEmitter = require('events').EventEmitter; var Networks = require('../networks'); var Peer = require('./peer'); var util = require('util'); function now(){ return Math.floor(new Date().getTime() / 1000); } /** * A pool is a collection of Peers. A pool will discover peers from DNS seeds, and * collect information about new peers in the network. When a peer disconnects the pool * will connect to others that are available to maintain a max number of * ongoing peer connections. Peer events are relayed to the pool. * * @example * * var pool = new Pool(Networks.livenet); * pool.on('peerinv', function(peer, message) { * // do something with the inventory announcement * }); * pool.connect(); * * @param {Network|String} network - The network to connect * @returns {Pool} * @constructor */ function Pool(network) { var self = this; this.network = Networks.get(network) || Networks.defaultNetwork; this.connectedPeers = []; this.addrs = []; this.keepalive = false; this.on('peeraddr', function peerAddrEvent(peer, addr){ // In case of an invalid time, assume "5 days ago" if (addr.time <= 100000000 || addr.time > (now() + 10 * 60)) { addr.time = now() - 5 * 24 * 60 * 60; } this.addAddr(addr); }); this.on('peerdisconnect', function peerDisconnectEvent(peer, addr){ self.deprioritizeAddr(addr); self.removeConnectedPeer(addr); if (self.keepalive) { self.fillConnections(); } }); this.on('peerready', function peerReadyEvent(peer){ Pool.PeerEvents.forEach(function addPeerEvents(event) { peer.on(event, function peerEvent(message) { self.emit('peer' + event, peer, message); }); }); }); return this; } util.inherits(Pool, EventEmitter); Pool.MaxConnectedPeers = 8; Pool.RetrySeconds = 30; Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'ping', 'addr', 'getaddr', 'verack', 'reject', 'alert', 'headers', 'block', 'tx', 'getblocks', 'getheaders']; /** * Will initiatiate connection to peers, if available peers have been added to * the pool, it will connect to those, otherwise will use DNS seeds to find * peers to connect. When a peer disconnects it will add another. */ Pool.prototype.connect = function connect() { this.keepalive = true; var self = this; if (self.addrs.length === 0) { self.addAddrsFromSeeds(function(){ self.fillConnections(); }); } else { self.fillConnections(); } return this; }; /** * Will disconnect all peers that are connected. */ Pool.prototype.disconnect = function disconnect() { this.keepalive = false; var length = this.connectedPeers.length; for (var i = 0; i < length; i++) { this.connectedPeers[i].disconnect(); } return this; }; /** * @returns {Boolean} If the pool has peers (addrs) available to connect. */ Pool.prototype.isAvailable = function isAvailable() { if (this.addrs.length > 0) { return true; } return false; }; /** * @returns {Boolean} If there are peers connected. */ Pool.prototype.isConnected = function isConnected() { if (this.connectedPeers.length > 0){ return true; } return false; }; /** * @returns {Number} The number of peers currently connected. */ Pool.prototype.numberConnected = function numberConnected(){ return this.connectedPeers.length; }; /** * Will fill the conneted peers to the maximum amount. */ Pool.prototype.fillConnections = function fillConnections() { var length = this.addrs.length; for (var i = 0; i < length; i++) { if (this.connectedPeers.length >= Pool.MaxConnectedPeers ) { break; } var addr = this.addrs[i]; if (!addr.retryTime || now() > addr.retryTime) { this.connectPeer(addr); } } return this; }; /** * Will remove a peer from the list of connected peers. * @param {Object} addr - An addr from the list of addrs */ Pool.prototype.removeConnectedPeer = function removeConnectedPeer(addr) { for (var i = 0; i < this.connectedPeers.length; i++) { if (this.connectedPeers[i].host === addr.ip) { var beginning = this.connectedPeers.splice(0, i); var end = this.connectedPeers.splice(i + 1, this.connectedPeers.length); this.connectedPeers = beginning.concat(end); } } return this; }; /** * Will connect a peer and add to the list of connected peers. * @param {Object} addr - An addr from the list of addrs */ Pool.prototype.connectPeer = function connectPeer(addr) { var self = this; function addConnectedPeer(addr) { var peer = new Peer(addr.ip, self.network.port, self.network); peer.on('disconnect', function peerDisconnect(){ self.emit('peerdisconnect', peer, addr); }); peer.on('ready', function peerReady(){ self.emit('peerready', peer, addr); }); peer.connect(); self.connectedPeers.push(peer); } var exists = false; var length = this.connectedPeers.length; for (var i = 0; i < length; i++) { if ( this.connectedPeers[i].host === addr.ip ) { exists = true; } } if (!exists){ addConnectedPeer(addr); } return this; }; /** * Will deprioritize an addr in the list of addrs by moving it to the end * of the array, and setting a retryTime * @param {Object} addr - An addr from the list of addrs */ Pool.prototype.deprioritizeAddr = function deprioritizeAddr(addr) { for (var i = 0; i < this.addrs.length; i++) { if (this.addrs[i].ip === addr.ip) { var middle = this.addrs[i]; middle.retryTime = now() + Pool.RetrySeconds; var beginning = this.addrs.splice(0, i); var end = this.addrs.splice(i + 1, this.addrs.length); var combined = beginning.concat(end); this.addrs = combined.concat([middle]); } } return this; }; /** * Will add an addr to the beginning of the addrs array * @param {Object} */ Pool.prototype.addAddr = function addAddr(addr) { var length = this.addrs.length; var exists = false; for (var i = 0; i < length; i++) { if (this.addrs[i].ip === addr.ip) { exists = true; } } if (!exists){ this.addrs.unshift(addr); } return this; }; /** * Will add addrs to the list of addrs from a DNS seed * @param {String} seed - A domain name to resolve known peers * @param {Function} done */ Pool.prototype.addAddrsFromSeed = function addAddrsFromSeed(seed, done) { var self = this; dns.resolve(seed, function(err, ips) { if (err) { self.emit('error', err); return done(); } if (!ips || !ips.length) { self.emit('error', new Error('No IPs found from seed lookup.')); return done(); } ips.forEach(function(ip){ self.addAddr({ip: ip}); }); return done(); }); return this; }; /** * Will add addrs to the list of addrs from network DNS seeds * @param {Function} done */ Pool.prototype.addAddrsFromSeeds = function addAddrsFromSeeds(done) { var self = this; var seeds = this.network.dnsSeeds; var completed = []; seeds.forEach(function(seed){ self.addAddrsFromSeed(seed, function(){ completed.push(seed); if (completed.length === seeds.length && typeof(done) === 'function' ){ done(); } }); }); return this; }; /** * @returns {String} A string formatted for the console */ Pool.prototype.inspect = function inspect(){ return ''; }; module.exports = Pool;