From 1a1d63321a8748e6c923350edaf60870c5d8f22e Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Sun, 14 Dec 2014 19:01:41 -0500 Subject: [PATCH 1/4] Transport: Added a peer pool to maintain a set of connected peers --- index.js | 1 + lib/transport/pool.js | 295 +++++++++++++++++++++++++++++++++++++++++ test/transport/peer.js | 34 +++-- test/transport/pool.js | 50 +++++++ 4 files changed, 366 insertions(+), 14 deletions(-) create mode 100644 lib/transport/pool.js create mode 100644 test/transport/pool.js diff --git a/index.js b/index.js index 2ad6ab2..13e446a 100644 --- a/index.js +++ b/index.js @@ -29,6 +29,7 @@ bitcore.util.preconditions = require('./lib/util/preconditions'); bitcore.transport = {}; bitcore.transport.Peer = require('./lib/transport/peer'); bitcore.transport.Messages = require('./lib/transport/messages'); +bitcore.transport.Pool = require('./lib/transport/pool'); // errors thrown by the library bitcore.errors = require('./lib/errors'); diff --git a/lib/transport/pool.js b/lib/transport/pool.js new file mode 100644 index 0000000..fb41c7f --- /dev/null +++ b/lib/transport/pool.js @@ -0,0 +1,295 @@ +'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, options) { + + var self = this; + + if (!options) { + options = {}; + } + + this.dns = options.dns || dns; + 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; + 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; diff --git a/test/transport/peer.js b/test/transport/peer.js index 9733a22..066ee7a 100644 --- a/test/transport/peer.js +++ b/test/transport/peer.js @@ -49,21 +49,27 @@ describe('Peer', function() { peer.port.should.equal(8111); }); - // only for node TODO: (yemel) - it.skip('should be able to set a proxy', function() { - var peer, peer2, socket; + if (typeof(window) === 'undefined'){ - peer = new Peer('localhost'); - expect(peer.proxy).to.be.undefined(); - socket = peer._getSocket(); - socket.should.be.instanceof(Net.Socket); + // Node.js Tests - peer2 = peer.setProxy('127.0.0.1', 9050); - peer2.proxy.host.should.equal('127.0.0.1'); - peer2.proxy.port.should.equal(9050); - socket = peer2._getSocket(); - socket.should.be.instanceof(Socks5Client); + it('should be able to set a proxy', function() { + var peer, peer2, socket; + + peer = new Peer('localhost'); + expect(peer.proxy).to.be.undefined(); + socket = peer._getSocket(); + socket.should.be.instanceof(Net.Socket); + + peer2 = peer.setProxy('127.0.0.1', 9050); + peer2.proxy.host.should.equal('127.0.0.1'); + peer2.proxy.port.should.equal(9050); + socket = peer2._getSocket(); + socket.should.be.instanceof(Socks5Client); + + peer.should.equal(peer2); + }); + + } - peer.should.equal(peer2); - }); }); diff --git a/test/transport/pool.js b/test/transport/pool.js new file mode 100644 index 0000000..8c30616 --- /dev/null +++ b/test/transport/pool.js @@ -0,0 +1,50 @@ +'use strict'; + +if (typeof(window) === 'undefined'){ + + // Node.js Tests + + var chai = require('chai'); + + /* jshint unused: false */ + var should = chai.should(); + var expect = chai.expect; + + var bitcore = require('../..'); + var Peer = bitcore.transport.Peer; + var Pool = bitcore.transport.Pool; + var Networks = bitcore.Networks; + + describe('Pool', function() { + + it('should be able to create instance', function() { + var pool = new Pool(); + pool.network.should.equal(Networks.livenet); + }); + + it('should be able to create instance setting the network', function() { + var pool = new Peer(Networks.testnet); + pool.network.should.equal(Networks.livenet); + }); + + it('should discover peers via dns', function() { + // mock dns + var dns = function() {}; + dns.resolve = function(seed, callback){ + callback(null, ['10.10.10.1', '10.10.10.2', '10.10.10.3']); + }; + var pool = new Pool(Networks.livenet, {dns: dns}); + pool.connect(); + pool.addrs.length.should.equal(3); + }); + + it('should not discover peers via dns', function() { + var pool = new Pool(); + pool.addAddr({ip: '10.10.10.1'}); + pool.connect(); + pool.addrs.length.should.equal(1); + }); + + }); + +} From e04cb76854431b6921e5721a15dd3119546614f7 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Mon, 15 Dec 2014 14:10:45 -0500 Subject: [PATCH 2/4] Transport/Pool: Use sinon for tests. --- lib/transport/pool.js | 9 ++------- package.json | 1 + test/transport/pool.js | 13 ++++++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/transport/pool.js b/lib/transport/pool.js index fb41c7f..6711fe7 100644 --- a/lib/transport/pool.js +++ b/lib/transport/pool.js @@ -28,15 +28,10 @@ function now(){ * @returns {Pool} * @constructor */ -function Pool(network, options) { +function Pool(network) { var self = this; - if (!options) { - options = {}; - } - - this.dns = options.dns || dns; this.network = Networks.get(network) || Networks.defaultNetwork; this.connectedPeers = []; this.addrs = []; @@ -246,7 +241,7 @@ Pool.prototype.addAddr = function addAddr(addr) { */ Pool.prototype.addAddrsFromSeed = function addAddrsFromSeed(seed, done) { var self = this; - this.dns.resolve(seed, function(err, ips) { + dns.resolve(seed, function(err, ips) { if (err) { self.emit('error', err); return done(); diff --git a/package.json b/package.json index bfe4649..3dc1ac2 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,7 @@ "gulp-shell": "^0.2.10", "mocha": "~2.0.1", "run-sequence": "^1.0.2", + "sinon": "^1.12.2", "karma": "^0.12.28", "karma-firefox-launcher": "^0.1.3", "karma-mocha": "^0.1.9" diff --git a/test/transport/pool.js b/test/transport/pool.js index 8c30616..e128f33 100644 --- a/test/transport/pool.js +++ b/test/transport/pool.js @@ -10,6 +10,9 @@ if (typeof(window) === 'undefined'){ var should = chai.should(); var expect = chai.expect; + var dns = require('dns'); + var sinon = require('sinon'); + var bitcore = require('../..'); var Peer = bitcore.transport.Peer; var Pool = bitcore.transport.Pool; @@ -28,14 +31,14 @@ if (typeof(window) === 'undefined'){ }); it('should discover peers via dns', function() { - // mock dns - var dns = function() {}; - dns.resolve = function(seed, callback){ + var stub = sinon.stub(dns, 'resolve', function(seed, callback){ callback(null, ['10.10.10.1', '10.10.10.2', '10.10.10.3']); - }; - var pool = new Pool(Networks.livenet, {dns: dns}); + }); + var pool = new Pool(Networks.livenet); pool.connect(); pool.addrs.length.should.equal(3); + stub.restore(); + }); it('should not discover peers via dns', function() { From b403cc37709f6e2acde0d5f24686267648429054 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 16 Dec 2014 00:16:06 -0500 Subject: [PATCH 3/4] Transport/Pool: Added tests to check that new addrs are added --- lib/transport/messages.js | 28 ++++++-- lib/transport/peer.js | 2 +- lib/transport/pool.js | 141 +++++++++++++++++++------------------- test/transport/pool.js | 47 ++++++++++++- 4 files changed, 143 insertions(+), 75 deletions(-) diff --git a/lib/transport/messages.js b/lib/transport/messages.js index e196c1f..179940a 100644 --- a/lib/transport/messages.js +++ b/lib/transport/messages.js @@ -292,11 +292,31 @@ Addresses.prototype.fromBuffer = function(payload) { this.addresses = []; for (var i = 0; i < addrCount; i++) { // TODO: Time actually depends on the version of the other peer (>=31402) + + var time = parser.readUInt32LE(); + var services = parser.readUInt64LEBN(); + + // parse the ipv6 to a string + var ipv6 = []; + for (var a = 0; a < 6; a++) { + ipv6.push(parser.read(2).toString('hex')); + } + ipv6 = ipv6.join(':'); + + // parse the ipv4 to a string + var ipv4 = []; + for (var b = 0; b < 4; b++) { + ipv4.push(parser.read(1)[0]); + } + ipv4 = ipv4.join('.'); + + var port = parser.readUInt16BE(); + this.addresses.push({ - time: parser.readUInt32LE(), - services: parser.readUInt64LEBN(), - ip: parser.read(16), - port: parser.readUInt16BE() + time: time, + services: services, + ip: { v6: ipv6, v4: ipv4 }, + port: port }); } diff --git a/lib/transport/peer.js b/lib/transport/peer.js index 724cd2e..aea73bd 100644 --- a/lib/transport/peer.js +++ b/lib/transport/peer.js @@ -65,7 +65,7 @@ function Peer(host, port, network) { }); this.on('ping', function(message) { - self.sendPong(message.nonce); + self._sendPong(message.nonce); }); } diff --git a/lib/transport/pool.js b/lib/transport/pool.js index 6711fe7..2ad4785 100644 --- a/lib/transport/pool.js +++ b/lib/transport/pool.js @@ -3,10 +3,11 @@ var dns = require('dns'); var EventEmitter = require('events').EventEmitter; var Networks = require('../networks'); +var sha256 = require('../crypto/hash').sha256; var Peer = require('./peer'); var util = require('util'); -function now(){ +function now() { return Math.floor(new Date().getTime() / 1000); } @@ -33,32 +34,42 @@ function Pool(network) { var self = this; this.network = Networks.get(network) || Networks.defaultNetwork; - this.connectedPeers = []; + 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.on('peeraddr', function peerAddrEvent(peer, message) { + var addrs = message.addresses; + var length = addrs.length; + for (var i = 0; i < length; i++) { + var addr = addrs[i]; + // 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.addAddr(addr); }); - this.on('peerdisconnect', function peerDisconnectEvent(peer, addr){ - self.deprioritizeAddr(addr); - self.removeConnectedPeer(addr); + this.on('seed', function seedEvent(ips) { + ips.forEach(function(ip) { + self.addAddr({ + ip: { + v4: ip + } + }); + }); 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); - }); - }); + this.on('peerdisconnect', function peerDisconnectEvent(peer, addr) { + self.deprioritizeAddr(addr); + self.removeConnectedPeer(addr); + if (self.keepalive) { + self.fillConnections(); + } }); return this; @@ -70,8 +81,9 @@ 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']; + 'getaddr', 'verack', 'reject', 'alert', 'headers', 'block', + 'tx', 'getblocks', 'getheaders' +]; /** @@ -83,9 +95,7 @@ Pool.prototype.connect = function connect() { this.keepalive = true; var self = this; if (self.addrs.length === 0) { - self.addAddrsFromSeeds(function(){ - self.fillConnections(); - }); + self.addAddrsFromSeeds(); } else { self.fillConnections(); } @@ -98,8 +108,7 @@ Pool.prototype.connect = function connect() { */ Pool.prototype.disconnect = function disconnect() { this.keepalive = false; - var length = this.connectedPeers.length; - for (var i = 0; i < length; i++) { + for (var i in this.connectedPeers) { this.connectedPeers[i].disconnect(); } return this; @@ -119,7 +128,7 @@ Pool.prototype.isAvailable = function isAvailable() { * @returns {Boolean} If there are peers connected. */ Pool.prototype.isConnected = function isConnected() { - if (this.connectedPeers.length > 0){ + if (this.numberConnected() > 0) { return true; } return false; @@ -128,8 +137,8 @@ Pool.prototype.isConnected = function isConnected() { /** * @returns {Number} The number of peers currently connected. */ -Pool.prototype.numberConnected = function numberConnected(){ - return this.connectedPeers.length; +Pool.prototype.numberConnected = function numberConnected() { + return Object.keys(this.connectedPeers).length; }; /** @@ -138,7 +147,7 @@ Pool.prototype.numberConnected = function numberConnected(){ Pool.prototype.fillConnections = function fillConnections() { var length = this.addrs.length; for (var i = 0; i < length; i++) { - if (this.connectedPeers.length >= Pool.MaxConnectedPeers ) { + if (this.numberConnected() >= Pool.MaxConnectedPeers) { break; } var addr = this.addrs[i]; @@ -154,12 +163,10 @@ Pool.prototype.fillConnections = function fillConnections() { * @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); - } + if (this.connectedPeers[addr.hash].status !== Peer.STATUS.DISCONNECTED) { + this.connectedPeers[addr.hash].disconnect(); + } else { + delete this.connectedPeers[addr.hash]; } return this; }; @@ -172,28 +179,28 @@ 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(){ + var port = addr.port || self.network.port; + var ip = addr.ip.v4 || addr.ip.v6; + var peer = new Peer(ip, port, self.network); + peer.on('disconnect', function peerDisconnect() { self.emit('peerdisconnect', peer, addr); }); - peer.on('ready', function peerReady(){ + peer.on('ready', function peerReady() { self.emit('peerready', peer, addr); }); + Pool.PeerEvents.forEach(function addPeerEvents(event) { + peer.on(event, function peerEvent(message) { + self.emit('peer' + event, peer, message); + }); + }); peer.connect(); - self.connectedPeers.push(peer); + self.connectedPeers[addr.hash] = 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){ + if (!this.connectedPeers[addr.hash]) { addConnectedPeer(addr); } + return this; }; @@ -204,7 +211,7 @@ Pool.prototype.connectPeer = function connectPeer(addr) { */ Pool.prototype.deprioritizeAddr = function deprioritizeAddr(addr) { for (var i = 0; i < this.addrs.length; i++) { - if (this.addrs[i].ip === addr.ip) { + if (this.addrs[i].hash === addr.hash) { var middle = this.addrs[i]; middle.retryTime = now() + Pool.RetrySeconds; var beginning = this.addrs.splice(0, i); @@ -221,14 +228,18 @@ Pool.prototype.deprioritizeAddr = function deprioritizeAddr(addr) { * @param {Object} */ Pool.prototype.addAddr = function addAddr(addr) { + + // make a unique key + addr.hash = sha256(new Buffer(addr.ip.v6 + addr.ip.v4 + addr.port)).toString('hex'); + var length = this.addrs.length; var exists = false; for (var i = 0; i < length; i++) { - if (this.addrs[i].ip === addr.ip) { + if (this.addrs[i].hash === addr.hash) { exists = true; } } - if (!exists){ + if (!exists) { this.addrs.unshift(addr); } return this; @@ -239,21 +250,19 @@ Pool.prototype.addAddr = function addAddr(addr) { * @param {String} seed - A domain name to resolve known peers * @param {Function} done */ -Pool.prototype.addAddrsFromSeed = function addAddrsFromSeed(seed, done) { +Pool.prototype.addAddrsFromSeed = function addAddrsFromSeed(seed) { var self = this; dns.resolve(seed, function(err, ips) { if (err) { - self.emit('error', err); - return done(); + self.emit('seederror', err); + return; } if (!ips || !ips.length) { - self.emit('error', new Error('No IPs found from seed lookup.')); - return done(); + self.emit('seederror', new Error('No IPs found from seed lookup.')); + return; } - ips.forEach(function(ip){ - self.addAddr({ip: ip}); - }); - return done(); + // announce to pool + self.emit('seed', ips); }); return this; }; @@ -262,17 +271,11 @@ Pool.prototype.addAddrsFromSeed = function addAddrsFromSeed(seed, done) { * Will add addrs to the list of addrs from network DNS seeds * @param {Function} done */ -Pool.prototype.addAddrsFromSeeds = function addAddrsFromSeeds(done) { +Pool.prototype.addAddrsFromSeeds = function addAddrsFromSeeds() { 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(); - } - }); + seeds.forEach(function(seed) { + self.addAddrsFromSeed(seed); }); return this; }; @@ -280,11 +283,11 @@ Pool.prototype.addAddrsFromSeeds = function addAddrsFromSeeds(done) { /** * @returns {String} A string formatted for the console */ -Pool.prototype.inspect = function inspect(){ +Pool.prototype.inspect = function inspect() { return ''; }; -module.exports = Pool; +module.exports = Pool; \ No newline at end of file diff --git a/test/transport/pool.js b/test/transport/pool.js index e128f33..73e480d 100644 --- a/test/transport/pool.js +++ b/test/transport/pool.js @@ -15,6 +15,8 @@ if (typeof(window) === 'undefined'){ var bitcore = require('../..'); var Peer = bitcore.transport.Peer; + var MessagesData = require('../data/messages'); + var Messages = bitcore.transport.Messages; var Pool = bitcore.transport.Pool; var Networks = bitcore.Networks; @@ -36,18 +38,61 @@ if (typeof(window) === 'undefined'){ }); var pool = new Pool(Networks.livenet); pool.connect(); + pool.disconnect(); pool.addrs.length.should.equal(3); stub.restore(); - }); it('should not discover peers via dns', function() { var pool = new Pool(); pool.addAddr({ip: '10.10.10.1'}); pool.connect(); + pool.disconnect(); pool.addrs.length.should.equal(1); }); + it('should add new addrs as they are announced over the network', function(done) { + + // only emit an event, no need to connect + var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function(){ + this._readMessage(); + this.emit('ready'); + }); + + // mock a addr peer event + var peerMessageStub = sinon.stub(Peer.prototype, '_readMessage', function(){ + var payload = new Buffer(MessagesData.ADDR.payload, 'hex'); + var message = new Messages.Addresses().fromBuffer(payload); + this.emit(message.command, message); + }); + + var pool = new Pool(); + + pool.addAddr({ip: {v4: 'localhost'}}); + + // listen for the event + pool.on('peeraddr', function(peer, message) { + pool.addrs.length.should.equal(502); + + // restore stubs + peerConnectStub.restore(); + peerMessageStub.restore(); + + for (var i = 0; i < pool.addrs.length; i++) { + should.exist(pool.addrs[i].hash); + should.exist(pool.addrs[i].ip); + should.exist(pool.addrs[i].ip.v4); + } + + // done + done(); + }); + + pool.connect(); + + }); + + }); } From 709cda26187d2b1fe24bfb2b3d2d60ee9afdc19f Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 16 Dec 2014 03:30:57 -0500 Subject: [PATCH 4/4] Transport/Pool: Mark internal functions --- lib/transport/pool.js | 102 +++++++++++++++++------------------------ test/transport/pool.js | 18 ++++---- 2 files changed, 50 insertions(+), 70 deletions(-) diff --git a/lib/transport/pool.js b/lib/transport/pool.js index 2ad4785..d99033a 100644 --- a/lib/transport/pool.js +++ b/lib/transport/pool.js @@ -34,9 +34,9 @@ function Pool(network) { var self = this; this.network = Networks.get(network) || Networks.defaultNetwork; - this.connectedPeers = {}; - this.addrs = []; this.keepalive = false; + this._connectedPeers = {}; + this._addrs = []; this.on('peeraddr', function peerAddrEvent(peer, message) { var addrs = message.addresses; @@ -47,28 +47,28 @@ function Pool(network) { if (addr.time <= 100000000 || addr.time > (now() + 10 * 60)) { addr.time = now() - 5 * 24 * 60 * 60; } - this.addAddr(addr); + this._addAddr(addr); } }); this.on('seed', function seedEvent(ips) { ips.forEach(function(ip) { - self.addAddr({ + self._addAddr({ ip: { v4: ip } }); }); if (self.keepalive) { - self.fillConnections(); + self._fillConnections(); } }); this.on('peerdisconnect', function peerDisconnectEvent(peer, addr) { - self.deprioritizeAddr(addr); - self.removeConnectedPeer(addr); + self._deprioritizeAddr(addr); + self._removeConnectedPeer(addr); if (self.keepalive) { - self.fillConnections(); + self._fillConnections(); } }); @@ -94,10 +94,10 @@ Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'ping', 'addr', Pool.prototype.connect = function connect() { this.keepalive = true; var self = this; - if (self.addrs.length === 0) { - self.addAddrsFromSeeds(); + if (self._addrs.length === 0) { + self._addAddrsFromSeeds(); } else { - self.fillConnections(); + self._fillConnections(); } return this; }; @@ -108,51 +108,31 @@ Pool.prototype.connect = function connect() { */ Pool.prototype.disconnect = function disconnect() { this.keepalive = false; - for (var i in this.connectedPeers) { - this.connectedPeers[i].disconnect(); + for (var i in this._connectedPeers) { + 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.numberConnected() > 0) { - return true; - } - return false; -}; - /** * @returns {Number} The number of peers currently connected. */ Pool.prototype.numberConnected = function numberConnected() { - return Object.keys(this.connectedPeers).length; + return Object.keys(this._connectedPeers).length; }; /** * Will fill the conneted peers to the maximum amount. */ -Pool.prototype.fillConnections = function fillConnections() { - var length = this.addrs.length; +Pool.prototype._fillConnections = function _fillConnections() { + var length = this._addrs.length; for (var i = 0; i < length; i++) { if (this.numberConnected() >= Pool.MaxConnectedPeers) { break; } - var addr = this.addrs[i]; + var addr = this._addrs[i]; if (!addr.retryTime || now() > addr.retryTime) { - this.connectPeer(addr); + this._connectPeer(addr); } } return this; @@ -162,11 +142,11 @@ Pool.prototype.fillConnections = function fillConnections() { * 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) { - if (this.connectedPeers[addr.hash].status !== Peer.STATUS.DISCONNECTED) { - this.connectedPeers[addr.hash].disconnect(); +Pool.prototype._removeConnectedPeer = function _removeConnectedPeer(addr) { + if (this._connectedPeers[addr.hash].status !== Peer.STATUS.DISCONNECTED) { + this._connectedPeers[addr.hash].disconnect(); } else { - delete this.connectedPeers[addr.hash]; + delete this._connectedPeers[addr.hash]; } return this; }; @@ -175,7 +155,7 @@ Pool.prototype.removeConnectedPeer = function removeConnectedPeer(addr) { * 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) { +Pool.prototype._connectPeer = function _connectPeer(addr) { var self = this; function addConnectedPeer(addr) { @@ -194,10 +174,10 @@ Pool.prototype.connectPeer = function connectPeer(addr) { }); }); peer.connect(); - self.connectedPeers[addr.hash] = peer; + self._connectedPeers[addr.hash] = peer; } - if (!this.connectedPeers[addr.hash]) { + if (!this._connectedPeers[addr.hash]) { addConnectedPeer(addr); } @@ -209,15 +189,15 @@ Pool.prototype.connectPeer = function connectPeer(addr) { * 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].hash === addr.hash) { - var middle = this.addrs[i]; +Pool.prototype._deprioritizeAddr = function _deprioritizeAddr(addr) { + for (var i = 0; i < this._addrs.length; i++) { + if (this._addrs[i].hash === addr.hash) { + 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 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]); + this._addrs = combined.concat([middle]); } } return this; @@ -227,20 +207,20 @@ Pool.prototype.deprioritizeAddr = function deprioritizeAddr(addr) { * Will add an addr to the beginning of the addrs array * @param {Object} */ -Pool.prototype.addAddr = function addAddr(addr) { +Pool.prototype._addAddr = function _addAddr(addr) { // make a unique key addr.hash = sha256(new Buffer(addr.ip.v6 + addr.ip.v4 + addr.port)).toString('hex'); - var length = this.addrs.length; + var length = this._addrs.length; var exists = false; for (var i = 0; i < length; i++) { - if (this.addrs[i].hash === addr.hash) { + if (this._addrs[i].hash === addr.hash) { exists = true; } } if (!exists) { - this.addrs.unshift(addr); + this._addrs.unshift(addr); } return this; }; @@ -250,7 +230,7 @@ Pool.prototype.addAddr = function addAddr(addr) { * @param {String} seed - A domain name to resolve known peers * @param {Function} done */ -Pool.prototype.addAddrsFromSeed = function addAddrsFromSeed(seed) { +Pool.prototype._addAddrsFromSeed = function _addAddrsFromSeed(seed) { var self = this; dns.resolve(seed, function(err, ips) { if (err) { @@ -271,11 +251,11 @@ Pool.prototype.addAddrsFromSeed = function addAddrsFromSeed(seed) { * Will add addrs to the list of addrs from network DNS seeds * @param {Function} done */ -Pool.prototype.addAddrsFromSeeds = function addAddrsFromSeeds() { +Pool.prototype._addAddrsFromSeeds = function _addAddrsFromSeeds() { var self = this; var seeds = this.network.dnsSeeds; seeds.forEach(function(seed) { - self.addAddrsFromSeed(seed); + self._addAddrsFromSeed(seed); }); return this; }; @@ -287,7 +267,7 @@ Pool.prototype.inspect = function inspect() { return ''; + this._addrs.length + '>'; }; -module.exports = Pool; \ No newline at end of file +module.exports = Pool; diff --git a/test/transport/pool.js b/test/transport/pool.js index 73e480d..985723b 100644 --- a/test/transport/pool.js +++ b/test/transport/pool.js @@ -39,16 +39,16 @@ if (typeof(window) === 'undefined'){ var pool = new Pool(Networks.livenet); pool.connect(); pool.disconnect(); - pool.addrs.length.should.equal(3); + pool._addrs.length.should.equal(3); stub.restore(); }); it('should not discover peers via dns', function() { var pool = new Pool(); - pool.addAddr({ip: '10.10.10.1'}); + pool._addAddr({ip: {v4: '10.10.10.1'}}); pool.connect(); pool.disconnect(); - pool.addrs.length.should.equal(1); + pool._addrs.length.should.equal(1); }); it('should add new addrs as they are announced over the network', function(done) { @@ -68,20 +68,20 @@ if (typeof(window) === 'undefined'){ var pool = new Pool(); - pool.addAddr({ip: {v4: 'localhost'}}); + pool._addAddr({ip: {v4: 'localhost'}}); // listen for the event pool.on('peeraddr', function(peer, message) { - pool.addrs.length.should.equal(502); + pool._addrs.length.should.equal(502); // restore stubs peerConnectStub.restore(); peerMessageStub.restore(); - for (var i = 0; i < pool.addrs.length; i++) { - should.exist(pool.addrs[i].hash); - should.exist(pool.addrs[i].ip); - should.exist(pool.addrs[i].ip.v4); + for (var i = 0; i < pool._addrs.length; i++) { + should.exist(pool._addrs[i].hash); + should.exist(pool._addrs[i].ip); + should.exist(pool._addrs[i].ip.v4); } // done