diff --git a/lib/transport/messages.js b/lib/transport/messages.js index e196c1f5c..179940a6b 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 724cd2e88..aea73bdfb 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 6711fe7d7..2ad478592 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 e128f3305..73e480dc5 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(); + + }); + + }); }