From 89cee5804f01301d0e8e69be98658e6a6249787c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 27 Jan 2015 13:16:50 -0300 Subject: [PATCH 1/8] polish tests --- test/index.html | 18 ----- test/pool.js | 172 ++++++++++++++++++++++++------------------------ 2 files changed, 87 insertions(+), 103 deletions(-) delete mode 100644 test/index.html diff --git a/test/index.html b/test/index.html deleted file mode 100644 index 3280e5e..0000000 --- a/test/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - Mocha - - - - - -
- - - - - - diff --git a/test/pool.js b/test/pool.js index 1af891b..91ab03c 100644 --- a/test/pool.js +++ b/test/pool.js @@ -1,105 +1,107 @@ 'use strict'; -if (typeof(window) === 'undefined'){ +var chai = require('chai'); - // Node.js Tests +/* jshint unused: false */ +var should = chai.should(); +var expect = chai.expect; - var chai = require('chai'); +var bitcore = require('bitcore'); +var P2P = require('../'); +var Peer = P2P.Peer; +var MessagesData = require('./data/messages'); +var Messages = P2P.Messages; +var Pool = P2P.Pool; +var Networks = bitcore.Networks; - /* jshint unused: false */ - var should = chai.should(); - var expect = chai.expect; +var dns = require('dns'); +var sinon = require('sinon'); - var bitcore = require('bitcore'); - var P2P = require('../'); - var Peer = P2P.Peer; - var MessagesData = require('./data/messages'); - var Messages = P2P.Messages; - var Pool = P2P.Pool; - var Networks = bitcore.Networks; +describe('Pool', function() { - var dns = require('dns'); - var sinon = require('sinon'); + it('should be able to create instance', function() { + var pool = new Pool(); + should.exist(pool.network); + expect(pool.network).to.satisfy(function(network) { + if (network === Networks.testnet || network === Networks.livenet) { + return true; + } + return false; + }); + }); - describe('Pool', function() { + 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 be able to create instance', function() { - var pool = new Pool(); - should.exist(pool.network); - expect(pool.network).to.satisfy(function(network){ - if (network === Networks.testnet || network === Networks.livenet) { - return true; - } - return false; - }); + it('should discover peers via dns', function() { + 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); + 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: { + v4: '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'); }); - it('should be able to create instance setting the network', function() { - var pool = new Peer(Networks.testnet); - pool.network.should.equal(Networks.livenet); + // 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); }); - it('should discover peers via dns', function() { - 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); - pool.connect(); - pool.disconnect(); - pool._addrs.length.should.equal(3); - stub.restore(); + var pool = new Pool(); + + pool._addAddr({ + ip: { + v4: 'localhost' + } }); - it('should not discover peers via dns', function() { - var pool = new Pool(); - pool._addAddr({ip: {v4: '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(); - + // 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 aeec0ddfdab461d2b4d0f25cf8d2d8c95009ded1 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 27 Jan 2015 13:19:08 -0300 Subject: [PATCH 2/8] fix a test --- test/pool.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/pool.js b/test/pool.js index 91ab03c..fb5e13b 100644 --- a/test/pool.js +++ b/test/pool.js @@ -23,16 +23,13 @@ describe('Pool', function() { var pool = new Pool(); should.exist(pool.network); expect(pool.network).to.satisfy(function(network) { - if (network === Networks.testnet || network === Networks.livenet) { - return true; - } - return false; + return network === Networks.testnet || network === 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); + var pool = new Pool(Networks.testnet); + pool.network.should.equal(Networks.testnet); }); it('should discover peers via dns', function() { @@ -103,5 +100,4 @@ describe('Pool', function() { }); - }); From ce7ccc773e036f3288e35e9ac26a7547bd867f51 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 27 Jan 2015 13:20:39 -0300 Subject: [PATCH 3/8] remove browser checks --- test/peer.js | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/test/peer.js b/test/peer.js index d965195..bd11e14 100644 --- a/test/peer.js +++ b/test/peer.js @@ -44,8 +44,7 @@ describe('Peer', function() { connectCallback = arguments[1]; } }; - stub.write = function() { - }; + stub.write = function() {}; stub.connect = function() { connectCallback(); }; @@ -56,7 +55,7 @@ describe('Peer', function() { dataCallback(fs.readFileSync('./test/connection.log')); }); var check = function(message) { - received[message.command]++; + received[message.command]++; if (_.isEqual(received, expected)) { callback(); } @@ -105,27 +104,20 @@ describe('Peer', function() { peer.port.should.equal(8111); }); - if (typeof(window) === 'undefined'){ + it('should be able to set a proxy', function() { + var peer, peer2, socket; - // Node.js Tests + peer = new Peer('localhost'); + expect(peer.proxy).to.be.undefined(); + socket = peer._getSocket(); + socket.should.be.instanceof(Net.Socket); - 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); - }); - - } + 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); + }); }); From eaca92b1c6fcfb27113ba5c66340ef7c2511cde6 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 27 Jan 2015 16:29:34 -0300 Subject: [PATCH 4/8] refactor message#fromBuffer() --- lib/messages.js | 107 ++++++++++++++++++++++++++++------------------- test/messages.js | 35 ++++++++++------ test/pool.js | 2 +- 3 files changed, 87 insertions(+), 57 deletions(-) diff --git a/lib/messages.js b/lib/messages.js index 72f715b..4cf3e3b 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -17,6 +17,7 @@ var BufferUtil = bitcore.util.buffer; var Hash = bitcore.crypto.Hash; var Random = bitcore.crypto.Random; var TransactionModel = bitcore.Transaction; +var errors = bitcore.Errors; var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); var PROTOCOL_VERSION = 70000; @@ -88,7 +89,7 @@ function discardUntilNextMessage(network, dataBuffer) { /** * Abstract Message that knows how to parse and serialize itself. - * Concret subclases should implement {fromBuffer} and {getPayload} methods. + * Concrete subclasses should implement {fromBuffer} and {getPayload} methods. * @name P2P.Message */ function Message() {} @@ -106,7 +107,7 @@ Message.COMMANDS = {}; Message.buildMessage = function(command, payload) { try { var CommandClass = Message.COMMANDS[command]; - return new CommandClass().fromBuffer(payload); + return new CommandClass.fromBuffer(payload); } catch (err) { console.log('Error while parsing message', err); } @@ -118,9 +119,9 @@ Message.buildMessage = function(command, payload) { * @param{Buffer} payload - the buffer to read from * @returns{Message} The same message instance */ -Message.prototype.fromBuffer = function(payload) { +Message.fromBuffer = function(payload) { /* jshint unused: false */ - return this; + throw new errors.NotImplemented(); }; /** @@ -180,51 +181,52 @@ function Version(subversion, nonce) { } util.inherits(Version, Message); -Version.prototype.fromBuffer = function(payload) { +Version.fromBuffer = function(payload) { + var that = new Version(); var parser = new BufferReader(payload); /** * @type {number} * @desc The version of the bitcoin protocol */ - this.version = parser.readUInt32LE(); + that.version = parser.readUInt32LE(); /** * @type {BN} * @desc A mapbit with service bits: what features are supported by the peer */ - this.services = parser.readUInt64LEBN(); + that.services = parser.readUInt64LEBN(); /** * @type {BN} * @desc The time this message was sent */ - this.timestamp = parser.readUInt64LEBN(); + that.timestamp = parser.readUInt64LEBN(); /** * @type {Buffer} * @desc IPv4/6 address of the interface used to connect to this peer */ - this.addr_me = parser.read(26); + that.addr_me = parser.read(26); /** * @type {Buffer} * @desc IPv4/6 address of the peer */ - this.addr_you = parser.read(26); + that.addr_you = parser.read(26); /** * @type {Buffer} * @desc A random number */ - this.nonce = parser.read(8); + that.nonce = parser.read(8); /** * @desc A random number * @type {string} */ - this.subversion = parser.readVarintBuf().toString(); + that.subversion = parser.readVarintBuf().toString(); /** * @desc The height of the last block accepted in the blockchain by this peer * @type {number} */ - this.start_height = parser.readUInt32LE(); + that.start_height = parser.readUInt32LE(); - return this; + return that; }; Version.prototype.getPayload = function() { @@ -263,17 +265,18 @@ function Inventory(inventory) { } util.inherits(Inventory, Message); -Inventory.prototype.fromBuffer = function(payload) { +Inventory.fromBuffer = function(payload) { + var that = new Inventory(); var parser = new BufferReader(payload); var count = parser.readVarintNum(); for (var i = 0; i < count; i++) { - this.inventory.push({ + that.inventory.push({ type: parser.readUInt32LE(), hash: parser.read(32) }); } - return this; + return that; }; Inventory.prototype.getPayload = function() { @@ -327,9 +330,10 @@ function Ping(nonce) { } util.inherits(Ping, Message); -Ping.prototype.fromBuffer = function(payload) { - this.nonce = new BufferReader(payload).read(8); - return this; +Ping.fromBuffer = function(payload) { + var that = new Ping(); + that.nonce = new BufferReader(payload).read(8); + return that; }; Ping.prototype.getPayload = function() { @@ -354,6 +358,9 @@ function Pong(nonce) { } util.inherits(Pong, Ping); +Pong.fromBuffer = function() { + return new Pong(); +}; module.exports.Pong = Message.COMMANDS.pong = Pong; /** @@ -372,11 +379,12 @@ function Addresses(addresses) { } util.inherits(Addresses, Message); -Addresses.prototype.fromBuffer = function(payload) { +Addresses.fromBuffer = function(payload) { + var that = new Addresses(); var parser = new BufferReader(payload); var addrCount = Math.min(parser.readVarintNum(), 1000); - this.addresses = []; + that.addresses = []; for (var i = 0; i < addrCount; i++) { // TODO: Time actually depends on the version of the other peer (>=31402) @@ -399,7 +407,7 @@ Addresses.prototype.fromBuffer = function(payload) { var port = parser.readUInt16BE(); - this.addresses.push({ + that.addresses.push({ time: time, services: services, ip: { v6: ipv6, v4: ipv4 }, @@ -407,7 +415,7 @@ Addresses.prototype.fromBuffer = function(payload) { }); } - return this; + return that; }; Addresses.prototype.getPayload = function() { @@ -436,6 +444,9 @@ function GetAddresses() { } util.inherits(GetAddresses, Message); +GetAddresses.fromBuffer = function() { + return new GetAddresses(); +}; module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses; /** @@ -448,6 +459,9 @@ function VerAck() { } util.inherits(VerAck, Message); +VerAck.fromBuffer = function() { + return new VerAck(); +}; module.exports.VerAck = Message.COMMANDS.verack = VerAck; /** @@ -477,11 +491,12 @@ function Alert(payload, signature) { } util.inherits(Alert, Message); -Alert.prototype.fromBuffer = function(payload) { +Alert.fromBuffer = function(payload) { + var that = new Alert(); var parser = new BufferReader(payload); - this.payload = parser.readVarintBuf(); // TODO: Use current format - this.signature = parser.readVarintBuf(); - return this; + that.payload = parser.readVarintBuf(); // TODO: Use current format + that.signature = parser.readVarintBuf(); + return that; }; Alert.prototype.getPayload = function() { @@ -514,17 +529,18 @@ function Headers(blockheaders) { } util.inherits(Headers, Message); -Headers.prototype.fromBuffer = function(payload) { +Headers.fromBuffer = function(payload) { + var that = new Headers(); var parser = new BufferReader(payload); var count = parser.readVarintNum(); - this.headers = []; + that.headers = []; for (var i = 0; i < count; i++) { var header = BlockHeaderModel._fromBufferReader(parser); - this.headers.push(header); + that.headers.push(header); } - return this; + return that; }; Headers.prototype.getPayload = function() { @@ -558,9 +574,10 @@ function Block(block) { } util.inherits(Block, Message); -Block.prototype.fromBuffer = function(payload) { - this.block = BlockModel(payload); - return this; +Block.fromBuffer = function(payload) { + var that = new Block(); + that.block = BlockModel(payload); + return that; }; Block.prototype.getPayload = function() { @@ -584,9 +601,10 @@ function Transaction(transaction) { } util.inherits(Transaction, Message); -Transaction.prototype.fromBuffer = function(payload) { - this.transaction = TransactionModel(payload); - return this; +Transaction.fromBuffer = function(payload) { + var that = new Transaction(); + that.transaction = TransactionModel(payload); + return that; }; Transaction.prototype.getPayload = function() { @@ -622,18 +640,19 @@ function GetBlocks(starts, stop) { } util.inherits(GetBlocks, Message); -GetBlocks.prototype.fromBuffer = function(payload) { +GetBlocks.fromBuffer = function(payload) { + var that = new GetBlocks(); var parser = new BufferReader(payload); - this.version = parser.readUInt32LE(); + that.version = parser.readUInt32LE(); var startCount = Math.min(parser.readVarintNum(), 500); - this.starts = []; + that.starts = []; for (var i = 0; i < startCount; i++) { - this.starts.push(parser.read(32)); + that.starts.push(parser.read(32)); } - this.stop = parser.read(32); + that.stop = parser.read(32); - return this; + return that; }; GetBlocks.prototype.getPayload = function() { diff --git a/test/messages.js b/test/messages.js index 0935cf6..3b5de1e 100644 --- a/test/messages.js +++ b/test/messages.js @@ -36,31 +36,32 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.VERSION.payload, 'hex'); - new Messages.Version().fromBuffer(payload); + var m = Messages.Version.fromBuffer(payload); + should.exist(m); }); }); - describe('VerAck', function() { + var name = 'VerAck'; + describe(name, function() { + var message = new Messages[name](); it('should be able to create instance', function() { - var message = new Messages.VerAck(); - message.command.should.equal('verack'); + message.command.should.equal(name.toLowerCase()); }); it('should be able to serialize the payload', function() { - var message = new Messages.VerAck(); var payload = message.getPayload(); should.exist(payload); }); it('should be able to serialize the message', function() { - var message = new Messages.VerAck(); var buffer = message.serialize(Networks.livenet); should.exist(buffer); }); it('should be able to parse payload', function() { - var payload = new Buffer(Data.VERACK.payload, 'hex'); - new Messages.VerAck().fromBuffer(payload); + var payload = new Buffer(Data[name.toUpperCase()].payload, 'hex'); + var m = Messages[name].fromBuffer(payload); + should.exist(m); }); }); @@ -84,7 +85,8 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.INV.payload, 'hex'); - new Messages.Inventory().fromBuffer(payload); + var m = Messages.Inventory.fromBuffer(payload); + should.exist(m); }); }); @@ -108,10 +110,17 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.ADDR.payload, 'hex'); - new Messages.Addresses().fromBuffer(payload); + var m = Messages.Addresses.fromBuffer(payload); + should.exist(m); }); }); + /* + Data.forEach(function(x) { + console.log(x); + }); + */ + describe('Ping', function() { it('should be able to create instance', function() { var message = new Messages.Ping(); @@ -132,7 +141,8 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.PING.payload, 'hex'); - new Messages.Ping().fromBuffer(payload); + var m = Messages.Ping.fromBuffer(payload); + should.exist(m); }); }); @@ -156,7 +166,8 @@ describe('Messages', function() { it('should be able to parse payload', function() { var payload = new Buffer(Data.PING.payload, 'hex'); - new Messages.Pong().fromBuffer(payload); + var m = Messages.Pong.fromBuffer(payload); + should.exist(m); }); }); diff --git a/test/pool.js b/test/pool.js index fb5e13b..eac97f0 100644 --- a/test/pool.js +++ b/test/pool.js @@ -66,7 +66,7 @@ describe('Pool', function() { // 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); + var message = Messages.Addresses.fromBuffer(payload); this.emit(message.command, message); }); From cdd34bf8dff03af9644bb5c54bf56e228a1a5577 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 28 Jan 2015 17:50:14 -0300 Subject: [PATCH 5/8] add tests for more messages --- lib/messages.js | 34 ++-- test/data/messages.json | 40 +++++ test/messages.js | 363 ++++++---------------------------------- 3 files changed, 110 insertions(+), 327 deletions(-) diff --git a/lib/messages.js b/lib/messages.js index 4cf3e3b..beb53b1 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -7,6 +7,7 @@ var Buffers = require('buffers'); var Put = require('bufferput'); var util = require('util'); +var _ = require('lodash'); var bitcore = require('bitcore'); @@ -14,6 +15,7 @@ var BlockHeaderModel = bitcore.BlockHeader; var BlockModel = bitcore.Block; var BufferReader = bitcore.encoding.BufferReader; var BufferUtil = bitcore.util.buffer; +var $ = bitcore.util.preconditions; var Hash = bitcore.crypto.Hash; var Random = bitcore.crypto.Random; var TransactionModel = bitcore.Transaction; @@ -281,13 +283,13 @@ Inventory.fromBuffer = function(payload) { Inventory.prototype.getPayload = function() { var put = new Put(); - + put.varint(this.inventory.length); this.inventory.forEach(function(value) { put.word32le(value.type); put.put(value.hash); }); - + return put.buffer(); }; @@ -323,7 +325,7 @@ module.exports.GetData = GetData; function Ping(nonce) { this.command = 'ping'; /** - * @desc A random number that should be returned by the peer in a pong message + * @desc A random number that should be returned by the peer in a pong message * @type {number} */ this.nonce = nonce || CONNECTION_NONCE; @@ -351,7 +353,7 @@ module.exports.Ping = Message.COMMANDS.ping = Ping; function Pong(nonce) { this.command = 'pong'; /** - * @desc A random number that must match the one sent in the corresponding `ping` message + * @desc A random number that must match the one sent in the corresponding `ping` message * @type {number} */ this.nonce = nonce || CONNECTION_NONCE; @@ -410,7 +412,10 @@ Addresses.fromBuffer = function(payload) { that.addresses.push({ time: time, services: services, - ip: { v6: ipv6, v4: ipv4 }, + ip: { + v6: ipv6, + v4: ipv4 + }, port: port }); } @@ -530,6 +535,7 @@ function Headers(blockheaders) { util.inherits(Headers, Message); Headers.fromBuffer = function(payload) { + $.checkArgument(payload && payload.length > 0, 'No data found to create Headers message'); var that = new Headers(); var parser = new BufferReader(payload); var count = parser.readVarintNum(); @@ -564,6 +570,7 @@ module.exports.Headers = Message.COMMANDS.headers = Headers; * @param {Block} block */ function Block(block) { + $.checkArgument(_.isUndefined(block) || block instanceof BlockModel); this.command = 'block'; /** @@ -575,13 +582,13 @@ function Block(block) { util.inherits(Block, Message); Block.fromBuffer = function(payload) { - var that = new Block(); - that.block = BlockModel(payload); - return that; + $.checkArgument(BufferUtil.isBuffer(payload)); + var block = BlockModel(payload); + return new Block(block); }; Block.prototype.getPayload = function() { - return this.block.toBuffer(); + return this.block ? this.block.toBuffer() : new Buffer(0); }; module.exports.Block = Message.COMMANDS.block = Block; @@ -593,6 +600,7 @@ module.exports.Block = Message.COMMANDS.block = Block; * @param{Transaction} transaction */ function Transaction(transaction) { + $.checkArgument(_.isUndefined(transaction) || transaction instanceof TransactionModel); this.command = 'tx'; /** * @type {Transaction} @@ -608,7 +616,7 @@ Transaction.fromBuffer = function(payload) { }; Transaction.prototype.getPayload = function() { - return this.transaction.toBuffer(); + return this.transaction ? this.transaction.toBuffer() : new Buffer(0); }; module.exports.Transaction = Message.COMMANDS.tx = Transaction; @@ -643,6 +651,7 @@ util.inherits(GetBlocks, Message); GetBlocks.fromBuffer = function(payload) { var that = new GetBlocks(); var parser = new BufferReader(payload); + $.checkArgument(!parser.finished(), 'No data received in payload'); that.version = parser.readUInt32LE(); var startCount = Math.min(parser.readVarintNum(), 500); @@ -701,6 +710,9 @@ function GetHeaders(starts, stop) { } util.inherits(GetHeaders, GetBlocks); +GetHeaders.fromBuffer = function() { + return new GetHeaders(); +}; module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders; /** @@ -716,7 +728,7 @@ util.inherits(GetMempool, Message); module.exports.GetMempool = Message.COMMANDS.mempool = GetMempool; // TODO: Remove this PATCH (yemel) -Buffers.prototype.skip = function (i) { +Buffers.prototype.skip = function(i) { if (i === 0) return; if (i === this.length) { diff --git a/test/data/messages.json b/test/data/messages.json index cc1fa33..8e89c5f 100644 --- a/test/data/messages.json +++ b/test/data/messages.json @@ -3,6 +3,42 @@ "message": "f9beb4d976657273696f6e000000000065000000fc970f17721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001", "payload": "721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001" }, + "ALERT": { + "message": "", + "payload": "" + }, + "REJECT": { + "message": "", + "payload": "" + }, + "GETBLOCKS": { + "message": "", + "payload": "" + }, + "GETDATA": { + "message": "", + "payload": "" + }, + "GETADDR": { + "message": "", + "payload": "" + }, + "GETHEADERS": { + "message": "", + "payload": "" + }, + "HEADERS": { + "message": "", + "payload": "" + }, + "TX": { + "message": "", + "payload": "01000000015884e5db9de218238671572340b207ee85b628074e7e467096c267266baf77a4000000006a473044022013fa3089327b50263029265572ae1b022a91d10ac80eb4f32f291c914533670b02200d8a5ed5f62634a7e1a0dc9188a3cc460a986267ae4d58faf50c79105431327501210223078d2942df62c45621d209fab84ea9a7a23346201b7727b9b45a29c4e76f5effffffff0150690f00000000001976a9147821c0a3768aa9d1a37e16cf76002aef5373f1a888ac00000000" + }, + "BLOCK": { + "message": "", + "payload": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000" + }, "VERACK": { "message": "f9beb4d976657261636b000000000000000000005df6e0e2", "payload": "" @@ -18,5 +54,9 @@ "PING": { "message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c", "payload": "6b86480ae969867c" + }, + "PONG": { + "message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c", + "payload": "6b86480ae969867c" } } diff --git a/test/messages.js b/test/messages.js index 3b5de1e..df57655 100644 --- a/test/messages.js +++ b/test/messages.js @@ -41,326 +41,57 @@ describe('Messages', function() { }); }); - var name = 'VerAck'; - describe(name, function() { - var message = new Messages[name](); - it('should be able to create instance', function() { - message.command.should.equal(name.toLowerCase()); - }); + var commands = { + VerAck: 'verack', + Inventory: 'inv', + Addresses: 'addr', + Ping: 'ping', + Pong: 'pong', + Alert: 'alert', + Reject: 'reject', + Block: 'block', + GetBlocks: 'getblocks', + GetHeaders: 'getheaders', + GetData: 'getdata', + GetAddresses: 'getaddr', + Headers: 'headers', + Transaction: 'tx' + }; + // TODO: add data for these + var noPayload = ['Alert', 'Reject', 'GetBlocks', 'GetHeaders', 'GetData', 'Headers']; + var names = Object.keys(commands); + describe.only('named', function() { + names.forEach(function(name) { + var command = commands[name]; + var data = Data[command.toUpperCase()]; + it('should have data for ' + name, function() { + should.exist(data); + }); + describe(name, function() { + var message = new Messages[name](); + it('should be able to create instance', function() { + message.command.should.equal(command); + }); - it('should be able to serialize the payload', function() { - var payload = message.getPayload(); - should.exist(payload); - }); + it('should be able to serialize the payload', function() { + var payload = message.getPayload(); + should.exist(payload); + }); - it('should be able to serialize the message', function() { - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); + it('should be able to serialize the message', function() { + var buffer = message.serialize(Networks.livenet); + should.exist(buffer); + }); - it('should be able to parse payload', function() { - var payload = new Buffer(Data[name.toUpperCase()].payload, 'hex'); - var m = Messages[name].fromBuffer(payload); - should.exist(m); + if (noPayload.indexOf(name) === -1) { + it('should be able to parse payload', function() { + var payload = new Buffer(data.payload, 'hex'); + var m = Messages[name].fromBuffer(payload); + should.exist(m); + }); + } + }); }); }); - describe('Inventory', function() { - it('should be able to create instance', function() { - var message = new Messages.Inventory(); - message.command.should.equal('inv'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Inventory(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Inventory(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - - it('should be able to parse payload', function() { - var payload = new Buffer(Data.INV.payload, 'hex'); - var m = Messages.Inventory.fromBuffer(payload); - should.exist(m); - }); - }); - - describe('Addresses', function() { - it('should be able to create instance', function() { - var message = new Messages.Addresses(); - message.command.should.equal('addr'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Addresses(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Addresses(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - - it('should be able to parse payload', function() { - var payload = new Buffer(Data.ADDR.payload, 'hex'); - var m = Messages.Addresses.fromBuffer(payload); - should.exist(m); - }); - }); - - /* - Data.forEach(function(x) { - console.log(x); - }); - */ - - describe('Ping', function() { - it('should be able to create instance', function() { - var message = new Messages.Ping(); - message.command.should.equal('ping'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Ping(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Ping(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - - it('should be able to parse payload', function() { - var payload = new Buffer(Data.PING.payload, 'hex'); - var m = Messages.Ping.fromBuffer(payload); - should.exist(m); - }); - }); - - describe('Pong', function() { - it('should be able to create instance', function() { - var message = new Messages.Pong(); - message.command.should.equal('pong'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Pong(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Pong(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - - it('should be able to parse payload', function() { - var payload = new Buffer(Data.PING.payload, 'hex'); - var m = Messages.Pong.fromBuffer(payload); - should.exist(m); - }); - }); - - describe('Alert', function() { - it('should be able to create instance', function() { - var message = new Messages.Alert(); - message.command.should.equal('alert'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Alert(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Alert(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('Reject', function() { - it('should be able to create instance', function() { - var message = new Messages.Reject(); - message.command.should.equal('reject'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Reject(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Reject(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('Block', function() { - var blockHex = '0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000'; - var block = new bitcore.Block(new Buffer(blockHex, 'hex')); - - it('should be able to create instance', function() { - var message = new Messages.Block(block); - message.command.should.equal('block'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Block(block); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Block(block); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('GetBlocks', function() { - it('should be able to create instance', function() { - var message = new Messages.GetBlocks(); - message.command.should.equal('getblocks'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.GetBlocks(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.GetBlocks(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('GetHeaders', function() { - it('should be able to create instance', function() { - var message = new Messages.GetHeaders(); - message.command.should.equal('getheaders'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.GetHeaders(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.GetHeaders(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('GetData', function() { - it('should be able to create instance', function() { - var message = new Messages.GetData(); - message.command.should.equal('getdata'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.GetData(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.GetData(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('GetData', function() { - it('should be able to create instance', function() { - var message = new Messages.GetData(); - message.command.should.equal('getdata'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.GetData(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.GetData(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('GetAddresses', function() { - it('should be able to create instance', function() { - var message = new Messages.GetAddresses(); - message.command.should.equal('getaddr'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.GetAddresses(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.GetAddresses(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('Headers', function() { - it('should be able to create instance', function() { - var message = new Messages.Headers(); - message.command.should.equal('headers'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Headers(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Headers(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); - - describe('Transaction', function() { - it('should be able to create instance', function() { - var message = new Messages.Transaction(new bitcore.Transaction()); - message.command.should.equal('tx'); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Transaction(new bitcore.Transaction()); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Transaction(new bitcore.Transaction()); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - }); }); From 77e399cf1a25b92bac1f545e2fc80aed8ab15375 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 28 Jan 2015 17:50:33 -0300 Subject: [PATCH 6/8] remove only --- test/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/messages.js b/test/messages.js index df57655..5975f98 100644 --- a/test/messages.js +++ b/test/messages.js @@ -60,7 +60,7 @@ describe('Messages', function() { // TODO: add data for these var noPayload = ['Alert', 'Reject', 'GetBlocks', 'GetHeaders', 'GetData', 'Headers']; var names = Object.keys(commands); - describe.only('named', function() { + describe('named', function() { names.forEach(function(name) { var command = commands[name]; var data = Data[command.toUpperCase()]; From 98990dbc82b3b7db4b698094f5c6e0621b7266ce Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 30 Jan 2015 16:37:14 -0300 Subject: [PATCH 7/8] new bitcoind integration tests --- lib/messages.js | 265 +++++++++++++++++++---------------- lib/peer.js | 28 ++-- test/integration/bitcoind.js | 141 +++++++++++++++++++ test/messages.js | 32 +---- test/mocha.opts | 1 - test/pool.js | 2 +- 6 files changed, 307 insertions(+), 162 deletions(-) create mode 100644 test/integration/bitcoind.js delete mode 100644 test/mocha.opts diff --git a/lib/messages.js b/lib/messages.js index beb53b1..456b3aa 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -24,50 +24,11 @@ var errors = bitcore.Errors; var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8); var PROTOCOL_VERSION = 70000; -/** - * Static helper for consuming a data buffer until the next message. - * - * @name P2P.Message#parseMessage - * @param{Network} network - the network object - * @param{Buffer} dataBuffer - the buffer to read from - * @returns{Message|undefined} A message or undefined if there is nothing to read. - */ -var parseMessage = function(network, dataBuffer) { - if (dataBuffer.length < 20) return; - - // Search the next magic number - if (!discardUntilNextMessage(network, dataBuffer)) return; - - var PAYLOAD_START = 16; - var payloadLen = (dataBuffer.get(PAYLOAD_START)) + - (dataBuffer.get(PAYLOAD_START + 1) << 8) + - (dataBuffer.get(PAYLOAD_START + 2) << 16) + - (dataBuffer.get(PAYLOAD_START + 3) << 24); - - var messageLength = 24 + payloadLen; - if (dataBuffer.length < messageLength) return; - - var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, ''); - var payload = dataBuffer.slice(24, messageLength); - var checksum = dataBuffer.slice(20, 24); - - var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); - if (!BufferUtil.equals(checksumConfirm, checksum)) { - dataBuffer.skip(messageLength); - return; - } - - dataBuffer.skip(messageLength); - return Message.buildMessage(command, payload); -}; - -module.exports.parseMessage = parseMessage; - /** * @desc Internal function that discards data until another message is found. * @name P2P.Message#discardUntilNextMessage */ -function discardUntilNextMessage(network, dataBuffer) { +var discardUntilNextMessage = function(network, dataBuffer) { var magicNumber = network.networkMagic; var i = 0; @@ -87,10 +48,10 @@ function discardUntilNextMessage(network, dataBuffer) { i++; // continue scanning } -} +}; /** - * Abstract Message that knows how to parse and serialize itself. + * Abstract Message this knows how to parse and serialize itself. * Concrete subclasses should implement {fromBuffer} and {getPayload} methods. * @name P2P.Message */ @@ -102,6 +63,51 @@ function Message() {} */ Message.COMMANDS = {}; +var PAYLOAD_START = 16; +/** + * Static helper for consuming a data buffer until the next message. + * + * @name P2P.Message#parseMessage + * @param{Network} network - the network object + * @param{Buffer} dataBuffer - the buffer to read from + * @returns{Message|undefined} A message or undefined if there is nothing to read. + */ +var parseMessage = function(network, dataBuffer) { + /* jshint maxstatements: 18 */ + if (dataBuffer.length < 20) { + return; + } + + // Search the next magic number + if (!discardUntilNextMessage(network, dataBuffer)) return; + + var payloadLen = (dataBuffer.get(PAYLOAD_START)) + + (dataBuffer.get(PAYLOAD_START + 1) << 8) + + (dataBuffer.get(PAYLOAD_START + 2) << 16) + + (dataBuffer.get(PAYLOAD_START + 3) << 24); + + var messageLength = 24 + payloadLen; + if (dataBuffer.length < messageLength) { + return; + } + + var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, ''); + var payload = dataBuffer.slice(24, messageLength); + var checksum = dataBuffer.slice(20, 24); + + var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4); + if (!BufferUtil.equals(checksumConfirm, checksum)) { + dataBuffer.skip(messageLength); + return; + } + + dataBuffer.skip(messageLength); + return Message.buildMessage(command, payload); +}; + +module.exports.parseMessage = parseMessage; + + /** * Look up a message type by command name and instantiate the correct Message * @name P2P.Message#buildMessage @@ -109,9 +115,10 @@ Message.COMMANDS = {}; Message.buildMessage = function(command, payload) { try { var CommandClass = Message.COMMANDS[command]; - return new CommandClass.fromBuffer(payload); + return new CommandClass().fromBuffer(payload); } catch (err) { console.log('Error while parsing message', err); + throw err; } }; @@ -121,7 +128,7 @@ Message.buildMessage = function(command, payload) { * @param{Buffer} payload - the buffer to read from * @returns{Message} The same message instance */ -Message.fromBuffer = function(payload) { +Message.prototype.fromBuffer = function(payload) { /* jshint unused: false */ throw new errors.NotImplemented(); }; @@ -141,9 +148,10 @@ Message.prototype.getPayload = function() { * @returns{Buffer} the serialized message */ Message.prototype.serialize = function(network) { - var magic = network.networkMagic; + $.checkArgument(network); var commandBuf = new Buffer(this.command, 'ascii'); - if (commandBuf.length > 12) throw 'Command name too long'; + $.checkState(commandBuf.length <= 12, 'Command name too long'); + var magic = network.networkMagic; var payload = this.getPayload(); var checksum = Hash.sha256sha256(payload).slice(0, 4); @@ -183,52 +191,51 @@ function Version(subversion, nonce) { } util.inherits(Version, Message); -Version.fromBuffer = function(payload) { - var that = new Version(); +Version.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); /** * @type {number} * @desc The version of the bitcoin protocol */ - that.version = parser.readUInt32LE(); + this.version = parser.readUInt32LE(); /** * @type {BN} * @desc A mapbit with service bits: what features are supported by the peer */ - that.services = parser.readUInt64LEBN(); + this.services = parser.readUInt64LEBN(); /** * @type {BN} * @desc The time this message was sent */ - that.timestamp = parser.readUInt64LEBN(); + this.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000); /** * @type {Buffer} * @desc IPv4/6 address of the interface used to connect to this peer */ - that.addr_me = parser.read(26); + this.addr_me = parser.read(26); /** * @type {Buffer} * @desc IPv4/6 address of the peer */ - that.addr_you = parser.read(26); + this.addr_you = parser.read(26); /** * @type {Buffer} * @desc A random number */ - that.nonce = parser.read(8); + this.nonce = parser.read(8); /** - * @desc A random number + * @desc The node's user agent / subversion * @type {string} */ - that.subversion = parser.readVarintBuf().toString(); + this.subversion = parser.readVarintBuf().toString(); /** * @desc The height of the last block accepted in the blockchain by this peer * @type {number} */ - that.start_height = parser.readUInt32LE(); + this.start_height = parser.readUInt32LE(); - return that; + return this; }; Version.prototype.getPayload = function() { @@ -241,7 +248,7 @@ Version.prototype.getPayload = function() { put.put(this.nonce); put.varint(this.subversion.length); put.put(new Buffer(this.subversion, 'ascii')); - put.word32le(0); + put.word32le(this.start_height); return put.buffer(); }; @@ -260,25 +267,39 @@ function Inventory(inventory) { this.command = 'inv'; /** * @name P2P.Message.Inventory.inventory - * @desc An array of objects with `{type: int, hash: buffer}` signature + * @desc An array of objects with `{type: int, hash: Buffer}` signature * @type {Array.Buffer} */ this.inventory = inventory || []; } util.inherits(Inventory, Message); -Inventory.fromBuffer = function(payload) { - var that = new Inventory(); +// https://en.bitcoin.it/wiki/Protocol_specification#Inventory_Vectors +Inventory.TYPE = {}; +Inventory.TYPE.ERROR = 0; +Inventory.TYPE.TX = 1; +Inventory.TYPE.BLOCK = 2; +Inventory.TYPE.FILTERED_BLOCK = 3; +Inventory.TYPE_NAME = [ + 'ERROR', + 'TX', + 'BLOCK', + 'FILTERED_BLOCK' +]; + +Inventory.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); var count = parser.readVarintNum(); for (var i = 0; i < count; i++) { - that.inventory.push({ - type: parser.readUInt32LE(), + var type = parser.readUInt32LE(); + this.inventory.push({ + type: type, + typeName: Inventory.TYPE_NAME[type], hash: parser.read(32) }); } - return that; + return this; }; Inventory.prototype.getPayload = function() { @@ -309,12 +330,18 @@ module.exports.Inventory = Message.COMMANDS.inv = Inventory; * @param{Array} inventory - requested elements */ function GetData(inventory) { + $.checkArgument(_.isUndefined(inventory) || + _.isArray(inventory), 'Inventory for GetData must be an array of objects'); + $.checkArgument(_.isUndefined(inventory) || + inventory.length === 0 || + (inventory[0] && !_.isUndefined(inventory[0].type) && !_.isUndefined(inventory[0].hash)), + 'Inventory for GetData must be an array of objects'); this.command = 'getdata'; this.inventory = inventory || []; } util.inherits(GetData, Inventory); -module.exports.GetData = GetData; +module.exports.GetData = Message.COMMANDS.getdata = GetData; /** * Sent to another peer mainly to check the connection is still alive. @@ -332,10 +359,9 @@ function Ping(nonce) { } util.inherits(Ping, Message); -Ping.fromBuffer = function(payload) { - var that = new Ping(); - that.nonce = new BufferReader(payload).read(8); - return that; +Ping.prototype.fromBuffer = function(payload) { + this.nonce = new BufferReader(payload).read(8); + return this; }; Ping.prototype.getPayload = function() { @@ -360,7 +386,7 @@ function Pong(nonce) { } util.inherits(Pong, Ping); -Pong.fromBuffer = function() { +Pong.prototype.fromBuffer = function() { return new Pong(); }; module.exports.Pong = Message.COMMANDS.pong = Pong; @@ -381,46 +407,51 @@ function Addresses(addresses) { } util.inherits(Addresses, Message); -Addresses.fromBuffer = function(payload) { - var that = new Addresses(); +Addresses.parseIP = function(parser) { + // 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('.'); + return { + v6: ipv6, + v4: ipv4 + }; +}; + + +Addresses.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); var addrCount = Math.min(parser.readVarintNum(), 1000); - that.addresses = []; + 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 time = new Date(parser.readUInt32LE() * 1000); 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 ip = Addresses.parseIP(parser); var port = parser.readUInt16BE(); - that.addresses.push({ + this.addresses.push({ time: time, services: services, - ip: { - v6: ipv6, - v4: ipv4 - }, + ip: ip, port: port }); } - return that; + return this; }; Addresses.prototype.getPayload = function() { @@ -449,7 +480,7 @@ function GetAddresses() { } util.inherits(GetAddresses, Message); -GetAddresses.fromBuffer = function() { +GetAddresses.prototype.fromBuffer = function() { return new GetAddresses(); }; module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses; @@ -464,7 +495,7 @@ function VerAck() { } util.inherits(VerAck, Message); -VerAck.fromBuffer = function() { +VerAck.prototype.fromBuffer = function() { return new VerAck(); }; module.exports.VerAck = Message.COMMANDS.verack = VerAck; @@ -496,12 +527,11 @@ function Alert(payload, signature) { } util.inherits(Alert, Message); -Alert.fromBuffer = function(payload) { - var that = new Alert(); +Alert.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); - that.payload = parser.readVarintBuf(); // TODO: Use current format - that.signature = parser.readVarintBuf(); - return that; + this.payload = parser.readVarintBuf(); // TODO: Use current format + this.signature = parser.readVarintBuf(); + return this; }; Alert.prototype.getPayload = function() { @@ -534,19 +564,18 @@ function Headers(blockheaders) { } util.inherits(Headers, Message); -Headers.fromBuffer = function(payload) { +Headers.prototype.fromBuffer = function(payload) { $.checkArgument(payload && payload.length > 0, 'No data found to create Headers message'); - var that = new Headers(); var parser = new BufferReader(payload); var count = parser.readVarintNum(); - that.headers = []; + this.headers = []; for (var i = 0; i < count; i++) { var header = BlockHeaderModel._fromBufferReader(parser); - that.headers.push(header); + this.headers.push(header); } - return that; + return this; }; Headers.prototype.getPayload = function() { @@ -581,7 +610,7 @@ function Block(block) { } util.inherits(Block, Message); -Block.fromBuffer = function(payload) { +Block.prototype.fromBuffer = function(payload) { $.checkArgument(BufferUtil.isBuffer(payload)); var block = BlockModel(payload); return new Block(block); @@ -609,10 +638,9 @@ function Transaction(transaction) { } util.inherits(Transaction, Message); -Transaction.fromBuffer = function(payload) { - var that = new Transaction(); - that.transaction = TransactionModel(payload); - return that; +Transaction.prototype.fromBuffer = function(payload) { + this.transaction = TransactionModel(payload); + return this; }; Transaction.prototype.getPayload = function() { @@ -648,20 +676,19 @@ function GetBlocks(starts, stop) { } util.inherits(GetBlocks, Message); -GetBlocks.fromBuffer = function(payload) { - var that = new GetBlocks(); +GetBlocks.prototype.fromBuffer = function(payload) { var parser = new BufferReader(payload); $.checkArgument(!parser.finished(), 'No data received in payload'); - that.version = parser.readUInt32LE(); + this.version = parser.readUInt32LE(); var startCount = Math.min(parser.readVarintNum(), 500); - that.starts = []; + this.starts = []; for (var i = 0; i < startCount; i++) { - that.starts.push(parser.read(32)); + this.starts.push(parser.read(32)); } - that.stop = parser.read(32); + this.stop = parser.read(32); - return that; + return this; }; GetBlocks.prototype.getPayload = function() { @@ -710,7 +737,7 @@ function GetHeaders(starts, stop) { } util.inherits(GetHeaders, GetBlocks); -GetHeaders.fromBuffer = function() { +GetHeaders.prototype.fromBuffer = function() { return new GetHeaders(); }; module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders; diff --git a/lib/peer.js b/lib/peer.js index e52f441..4be5dd2 100644 --- a/lib/peer.js +++ b/lib/peer.js @@ -7,6 +7,7 @@ var Socks5Client = require('socks5-client'); var util = require('util'); var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; var Networks = bitcore.Networks; var Messages = require('./messages'); @@ -18,7 +19,7 @@ var MAX_RECEIVE_BUFFER = 10000000; * * @example * ```javascript - * + * * var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050); * peer.on('tx', function(tx) { * console.log('New transaction: ', tx.id); @@ -42,10 +43,10 @@ function Peer(host, port, network) { network = port; port = undefined; } - - this.host = host; + + this.host = host || 'localhost'; this.status = Peer.STATUS.DISCONNECTED; - this.network = network || Networks.livenet; + this.network = network || Networks.defaultNetwork; this.port = port || this.network.port; this.dataBuffer = new Buffers(); @@ -64,7 +65,7 @@ function Peer(host, port, network) { this.on('version', function(message) { self.version = message.version; self.subversion = message.subversion; - self.bestHeight = message.start_height + self.bestHeight = message.start_height; }); this.on('ping', function(message) { @@ -89,9 +90,7 @@ Peer.STATUS = { * @returns {Peer} The same Peer instance. */ Peer.prototype.setProxy = function(host, port) { - if (this.status != Peer.STATUS.DISCONNECTED) { - throw Error('Invalid State'); - } + $.checkState(this.status === Peer.STATUS.DISCONNECTED); this.proxy = { host: host, @@ -116,13 +115,16 @@ Peer.prototype.connect = function() { self._sendVersion(); }); - this.socket.on('error', self.disconnect.bind(this)); + this.socket.on('error', self._onError.bind(this)); this.socket.on('end', self.disconnect.bind(this)); this.socket.on('data', function(data) { self.dataBuffer.push(data); - - if (self.dataBuffer.length > MAX_RECEIVE_BUFFER) return self.disconnect(); + + if (self.dataBuffer.length > MAX_RECEIVE_BUFFER) { + // TODO: handle this case better + return self.disconnect(); + } self._readMessage(); }); @@ -130,6 +132,10 @@ Peer.prototype.connect = function() { return this; }; +Peer.prototype._onError = function(e) { + this.emit('error', e); +}; + /** * Disconnects the remote connection. * diff --git a/test/integration/bitcoind.js b/test/integration/bitcoind.js new file mode 100644 index 0000000..3356c8b --- /dev/null +++ b/test/integration/bitcoind.js @@ -0,0 +1,141 @@ +'use strict'; + +var _ = require('lodash'); +var chai = require('chai'); + +/* jshint unused: false */ +var should = chai.should(); +var sinon = require('sinon'); +var _ = require('lodash'); + +var bitcore = require('bitcore'); +var Random = bitcore.crypto.Random; +var BN = bitcore.crypto.BN; +var BufferUtil = bitcore.util.buffer; +var p2p = require('../../'); +var Peer = p2p.Peer; +var Pool = p2p.Pool; +var Networks = bitcore.Networks; +var Messages = p2p.Messages; +var Block = bitcore.Block; + +// config +var network = Networks.livenet; +var blockHash = { + 'livenet': '000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9', + 'testnet': '0000000058cc069d964711cd25083c0a709f4df2b34c8ff9302ce71fe5b45786' +}; + +// These tests require a running bitcoind instance +describe('Integration with ' + network.name + ' bitcoind', function() { + + this.timeout(5000); + it('handshakes', function(cb) { + var peer = new Peer('localhost', network); + peer.once('version', function(m) { + m.version.should.be.above(70000); + m.services.toString().should.equal('1'); + Math.abs(new Date() - m.timestamp).should.be.below(10000); // less than 10 seconds of time difference + m.nonce.length.should.equal(8); + m.start_height.should.be.above(300000); + cb(); + }); + peer.once('verack', function(m) { + should.exist(m); + m.command.should.equal('verack'); + }); + peer.connect(); + }); + var connect = function(cb) { + var peer = new Peer('localhost', network); + peer.once('ready', function() { + cb(peer); + }); + peer.once('error', function(err) { + should.not.exist(err); + }); + peer.connect(); + }; + it('connects', function(cb) { + connect(function(peer) { + peer.version.should.be.above(70000); + _.isString(peer.subversion).should.equal(true); + _.isNumber(peer.bestHeight).should.equal(true); + cb(); + }); + }); + it('handles inv', function(cb) { + // assumes there will be at least one transaction/block + // in the next few seconds + connect(function(peer) { + peer.once('inv', function(message) { + message.inventory[0].hash.length.should.equal(32); + cb(); + }); + }); + }); + it('handles addr', function(cb) { + connect(function(peer) { + peer.once('addr', function(message) { + message.addresses.forEach(function(address) { + // console.log(address.ip.v4 + ':' + address.port); + (address.time instanceof Date).should.equal(true); + should.exist(address.ip); + (address.services instanceof BN).should.equal(true); + }); + cb(); + }); + var message = new Messages.GetAddresses(); + peer.sendMessage(message); + }); + }); + it('can request inv detailed info', function(cb) { + connect(function(peer) { + peer.once('block', function(message) { + //console.log(message.block.toJSON()); + should.exist(message.block); + cb(); + }); + peer.once('tx', function(message) { + //console.log(message.transaction.toJSON()); + should.exist(message.transaction); + cb(); + }); + peer.once('inv', function(m) { + var message = new Messages.GetData(m.inventory); + peer.sendMessage(message); + }); + }); + }); + it('can send tx inv and receive getdata for that tx', function(cb) { + connect(function(peer) { + var type = Messages.Inventory.TYPE.TX; + var inv = [{ + type: type, + typeName: Messages.Inventory.TYPE_NAME[type], + hash: Random.getRandomBuffer(32) // needs to be random for repeatability + }]; + peer.once('getdata', function(message) { + message.inventory.should.deep.equal(inv); + cb(); + }); + var message = new Messages.Inventory(inv); + message.inventory[0].hash.length.should.equal(32); + peer.sendMessage(message); + }); + }); + it('can request block data', function(cb) { + connect(function(peer) { + peer.on('block', function(message) { + (message.block instanceof Block).should.equal(true); + cb(); + }); + // TODO: replace this for a new Messages.GetData.forTransaction(hash) + var message = new Messages.GetData([{ + type: Messages.Inventory.TYPE.BLOCK, + hash: BufferUtil.reverse(new Buffer(blockHash[network.name], 'hex')) + }]); + peer.sendMessage(message); + }); + }); +}); diff --git a/test/messages.js b/test/messages.js index 5975f98..bf1be20 100644 --- a/test/messages.js +++ b/test/messages.js @@ -12,36 +12,8 @@ var Networks = bitcore.Networks; describe('Messages', function() { - describe('Version', function() { - it('should be able to create instance', function() { - var message = new Messages.Version(); - message.command.should.equal('version'); - message.version.should.equal(70000); - var version = require('../package.json').version; - message.subversion.should.equal('/bitcore:' + version + '/'); - should.exist(message.nonce); - }); - - it('should be able to serialize the payload', function() { - var message = new Messages.Version(); - var payload = message.getPayload(); - should.exist(payload); - }); - - it('should be able to serialize the message', function() { - var message = new Messages.Version(); - var buffer = message.serialize(Networks.livenet); - should.exist(buffer); - }); - - it('should be able to parse payload', function() { - var payload = new Buffer(Data.VERSION.payload, 'hex'); - var m = Messages.Version.fromBuffer(payload); - should.exist(m); - }); - }); - var commands = { + Version: 'version', VerAck: 'verack', Inventory: 'inv', Addresses: 'addr', @@ -86,7 +58,7 @@ describe('Messages', function() { if (noPayload.indexOf(name) === -1) { it('should be able to parse payload', function() { var payload = new Buffer(data.payload, 'hex'); - var m = Messages[name].fromBuffer(payload); + var m = new Messages[name]().fromBuffer(payload); should.exist(m); }); } diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 4a52320..0000000 --- a/test/mocha.opts +++ /dev/null @@ -1 +0,0 @@ ---recursive diff --git a/test/pool.js b/test/pool.js index eac97f0..fb5e13b 100644 --- a/test/pool.js +++ b/test/pool.js @@ -66,7 +66,7 @@ describe('Pool', function() { // mock a addr peer event var peerMessageStub = sinon.stub(Peer.prototype, '_readMessage', function() { var payload = new Buffer(MessagesData.ADDR.payload, 'hex'); - var message = Messages.Addresses.fromBuffer(payload); + var message = new Messages.Addresses().fromBuffer(payload); this.emit(message.command, message); }); From 1937abac118b8be3d3ff211aeb4cb0c1723debf9 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Fri, 30 Jan 2015 17:30:39 -0300 Subject: [PATCH 8/8] move bitcoind tests to separate folder --- {test/integration => integration}/bitcoind.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {test/integration => integration}/bitcoind.js (99%) diff --git a/test/integration/bitcoind.js b/integration/bitcoind.js similarity index 99% rename from test/integration/bitcoind.js rename to integration/bitcoind.js index 3356c8b..c3aadee 100644 --- a/test/integration/bitcoind.js +++ b/integration/bitcoind.js @@ -12,7 +12,7 @@ var bitcore = require('bitcore'); var Random = bitcore.crypto.Random; var BN = bitcore.crypto.BN; var BufferUtil = bitcore.util.buffer; -var p2p = require('../../'); +var p2p = require('../'); var Peer = p2p.Peer; var Pool = p2p.Pool; var Networks = bitcore.Networks;