Merge pull request #31 from maraoz/preconditions

various bugfixes and tests
This commit is contained in:
Yemel Jardi 2015-02-10 12:12:22 -03:00
commit 650f4618c4
6 changed files with 198 additions and 74 deletions

View File

@ -27,8 +27,8 @@ var blockHash = {
'testnet': '0000000058cc069d964711cd25083c0a709f4df2b34c8ff9302ce71fe5b45786'
};
var stopBlock = {
'livenet': '000000000000000006181d9d183e2191a5e704d6ed3513f29b0970198fb34d2e',
'testnet': '000000003d594c41db49d5a8b850344943438620acf79ce8aa88177f5b35e337'
'livenet': '00000000000000000b539ef570128acb953af3dbcfc19dd8e6066949672311a1',
'testnet': '00000000d0bc4271bcefaa7eb25000e345910ba16b91eb375cd944b68624de9f'
};
var txHash = {
'livenet': '22231e8219a0617a0ded618b5dc713fdf9b0db8ebd5bb3322d3011a703119d3b',
@ -87,7 +87,6 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
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);
@ -98,15 +97,13 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
peer.sendMessage(message);
});
});
it('can request inv detailed info', function(cb) {
it('requests 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();
});
@ -116,7 +113,7 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
});
});
});
it('can send tx inv and receive getdata for that tx', function(cb) {
it('sends tx inv and receives getdata for that tx', function(cb) {
connect(function(peer) {
var type = Messages.Inventory.TYPE.TX;
var inv = [{
@ -133,7 +130,7 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
peer.sendMessage(message);
});
});
it('can request block data', function(cb) {
it('requests block data', function(cb) {
connect(function(peer) {
peer.once('block', function(message) {
(message.block instanceof Block).should.equal(true);
@ -143,39 +140,37 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
peer.sendMessage(message);
});
});
it('can handle request tx data not found', function(cb) {
var fakeHash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a';
it('handles request tx data not found', function(cb) {
connect(function(peer) {
var hash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a';
var expected = Messages.NotFound.forTransaction(hash);
var expected = Messages.NotFound.forTransaction(fakeHash);
peer.once('notfound', function(message) {
(message instanceof Messages.NotFound).should.equal(true);
message.should.deep.equal(expected);
cb();
});
var message = Messages.GetData.forTransaction(hash);
var message = Messages.GetData.forTransaction(fakeHash);
peer.sendMessage(message);
});
});
var from = [blockHash[network.name]];
var stop = stopBlock[network.name];
it('can get headers', function(cb) {
it('gets headers', function(cb) {
connect(function(peer) {
peer.once('headers', function(message) {
(message instanceof Messages.Headers).should.equal(true);
message.headers.length.should.equal(2);
message.headers.length.should.equal(3);
cb();
});
var message = new Messages.GetHeaders(from, stop);
peer.sendMessage(message);
});
});
it.skip('can get blocks', function(cb) {
it('gets blocks', function(cb) {
connect(function(peer) {
peer.on('inv', function(message) {
peer.once('inv', function(message) {
(message instanceof Messages.Inventory).should.equal(true);
console.log('inv' + message.inventory.length);
if (message.inventory.length === 2) {
console.log(message);
message.inventory[0].type.should.equal(Messages.Inventory.TYPE.BLOCK);
cb();
}
@ -184,4 +179,26 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
peer.sendMessage(message);
});
});
var testInvGetData = function(expected, message, cb) {
connect(function(peer) {
peer.once('getdata', function(message) {
(message instanceof Messages.GetData).should.equal(true);
message.should.deep.equal(expected);
cb();
});
peer.sendMessage(message);
});
};
it('sends block inv and receives getdata', function(cb) {
var randomHash = Random.getRandomBuffer(32); // needs to be random for repeatability
var expected = Messages.GetData.forBlock(randomHash);
var message = Messages.Inventory.forBlock(randomHash);
testInvGetData(expected, message, cb);
});
it('sends tx inv and receives getdata', function(cb) {
var randomHash = Random.getRandomBuffer(32); // needs to be random for repeatability
var expected = Messages.GetData.forTransaction(randomHash);
var message = Messages.Inventory.forTransaction(randomHash);
testInvGetData(expected, message, cb);
});
});

View File

@ -72,6 +72,8 @@ var PAYLOAD_START = 16;
* @returns{Message|undefined} A message or undefined if there is nothing to read.
*/
var parseMessage = function(network, dataBuffer) {
$.checkArgument(network);
$.checkArgument(dataBuffer);
/* jshint maxstatements: 18 */
if (dataBuffer.length < 20) {
return;
@ -112,13 +114,9 @@ module.exports.parseMessage = parseMessage;
* @name P2P.Message#buildMessage
*/
Message.buildMessage = function(command, payload) {
try {
var CommandClass = Message.COMMANDS[command];
return new CommandClass().fromBuffer(payload);
} catch (err) {
console.log('Error while parsing message', err);
throw err;
}
var CommandClass = Message.COMMANDS[command];
$.checkState(CommandClass, 'Unsupported message command: ' + command);
return new CommandClass().fromBuffer(payload);
};
/**
@ -169,6 +167,13 @@ Message.prototype.serialize = function(network) {
return message.buffer();
};
/**
* check if parser has no more extra data
*/
Message.prototype._checkFinished = function(parser) {
$.checkState(parser.finished(), 'data still available after parsing ' + this.constructor.name);
};
module.exports.Message = Message;
/**
@ -209,15 +214,29 @@ Version.prototype.fromBuffer = function(payload) {
*/
this.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000);
/**
* @type {Buffer}
* @type {object}
* @desc IPv4/6 address of the interface used to connect to this peer
*/
this.addr_me = parser.read(26);
var me_services = parser.readUInt64LEBN();
var me_ip = Addresses.parseIP(parser);
var me_port = parser.readUInt16BE();
this.addr_me = {
services: me_services,
ip: me_ip,
port: me_port
};
/**
* @type {Buffer}
* @type {object}
* @desc IPv4/6 address of the peer
*/
this.addr_you = parser.read(26);
var your_services = parser.readUInt64LEBN();
var your_ip = Addresses.parseIP(parser);
var your_port = parser.readUInt16BE();
this.addr_you = {
services: your_services,
ip: your_ip,
port: your_port
};
/**
* @type {Buffer}
* @desc A random number
@ -227,23 +246,30 @@ Version.prototype.fromBuffer = function(payload) {
* @desc The node's user agent / subversion
* @type {string}
*/
this.subversion = parser.readVarintBuf().toString();
this.subversion = parser.readVarLengthBuffer().toString();
/**
* @desc The height of the last block accepted in the blockchain by this peer
* @type {number}
*/
this.start_height = parser.readUInt32LE();
/**
* @desc Whether the remote peer should announce relayed transactions or not, see BIP 0037
* @type {boolean}
*/
this.relay = !!parser.readUInt8();
this._checkFinished(parser);
return this;
};
Version.prototype.getPayload = function() {
var put = new Put();
put.word32le(this.version); // version
put.word32le(this.version);
put.word64le(1); // services
put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp
put.pad(26); // addr_me
put.pad(26); // addr_you
Addresses.writeAddr(this.addr_me, put);
Addresses.writeAddr(this.addr_you, put);
put.put(this.nonce);
put.varint(this.subversion.length);
put.put(new Buffer(this.subversion, 'ascii'));
@ -315,6 +341,7 @@ Inventory.prototype.fromBuffer = function(payload) {
this.inventory.push(Inventory.forItem(type, hash));
}
this._checkFinished(parser);
return this;
};
@ -396,7 +423,10 @@ function Ping(nonce) {
util.inherits(Ping, Message);
Ping.prototype.fromBuffer = function(payload) {
this.nonce = new BufferReader(payload).read(8);
var parser = new BufferReader(payload);
this.nonce = parser.read(8);
this._checkFinished(parser);
return this;
};
@ -422,9 +452,6 @@ function Pong(nonce) {
}
util.inherits(Pong, Ping);
Pong.prototype.fromBuffer = function() {
return new Pong();
};
module.exports.Pong = Message.COMMANDS.pong = Pong;
/**
@ -443,26 +470,58 @@ function Addresses(addresses) {
}
util.inherits(Addresses, Message);
Addresses.writeAddr = function(addr, put) {
if (_.isUndefined(addr)) {
put.pad(26);
return;
}
put.word64le(addr.services);
Addresses.writeIP(addr.ip, put);
put.word16be(addr.port);
};
Addresses.writeIP = function(ip, put) {
$.checkArgument(ip.v6, 'Need ipv6 to write IP');
var words = ip.v6.split(':').map(function(s) {
return new Buffer(s, 'hex');
});
for (var i = 0; i < words.length; i++) {
var word = words[i];
put.put(word);
}
};
// http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_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'));
var ipv4 = [];
for (var a = 0; a < 8; a++) {
var word = parser.read(2);
ipv6.push(word.toString('hex'));
if (a >= 6) {
ipv4.push(word[0]);
ipv4.push(word[1]);
}
}
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.parseAddr = function(parser) {
var services = parser.readUInt64LEBN();
var ip = Addresses.parseIP(parser);
var port = parser.readUInt16BE();
return {
services: services,
ip: ip,
port: port
};
};
Addresses.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
@ -471,22 +530,15 @@ 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 = new Date(parser.readUInt32LE() * 1000);
var services = parser.readUInt64LEBN();
var ip = Addresses.parseIP(parser);
var addr = Addresses.parseAddr(parser);
addr.time = time;
var port = parser.readUInt16BE();
this.addresses.push({
time: time,
services: services,
ip: ip,
port: port
});
this.addresses.push(addr);
}
this._checkFinished(parser);
return this;
};
@ -495,10 +547,10 @@ Addresses.prototype.getPayload = function() {
put.varint(this.addresses.length);
for (var i = 0; i < this.addresses.length; i++) {
put.word32le(this.addresses[i].time);
put.word64le(this.addresses[i].services);
put.put(this.addresses[i].ip);
put.word16be(this.addresses[i].port);
var addr = this.addresses[i];
put.word32le(addr.time);
Addresses.writeAddr(addr, put);
break;
}
return put.buffer();
@ -559,8 +611,9 @@ util.inherits(Alert, Message);
Alert.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
this.payload = parser.readVarintBuf(); // TODO: Use current format
this.signature = parser.readVarintBuf();
this.payload = parser.readVarLengthBuffer();
this.signature = parser.readVarLengthBuffer();
this._checkFinished(parser);
return this;
};
@ -603,8 +656,11 @@ Headers.prototype.fromBuffer = function(payload) {
for (var i = 0; i < count; i++) {
var header = BlockHeaderModel.fromBufferReader(parser);
this.headers.push(header);
}
var txn_count = parser.readUInt8();
$.checkState(txn_count === 0, 'txn_count should always be 0');
}
this._checkFinished(parser);
return this;
};
@ -617,6 +673,7 @@ Headers.prototype.getPayload = function() {
.headers[i]
.toBuffer();
put.put(buffer);
put.varint(0);
}
return put.buffer();
@ -730,6 +787,7 @@ GetBlocks.prototype.fromBuffer = function(payload) {
this.starts.push(parser.read(32));
}
this.stop = parser.read(32);
this._checkFinished(parser);
return this;
};

View File

@ -52,7 +52,7 @@
"url": "https://github.com/bitpay/bitcore-p2p.git"
},
"dependencies": {
"bitcore": "^0.9.0",
"bitcore": "^0.9.5",
"bufferput": "^0.1.2",
"buffers": "^0.1.1",
"socks5-client": "^0.3.6"

View File

@ -5,7 +5,7 @@
},
"ALERT": {
"message": "",
"payload": ""
"payload": "73010000003766404f00000000b305434f00000000f2030000f1030000001027000048ee00000064000000004653656520626974636f696e2e6f72672f666562323020696620796f7520686176652074726f75626c6520636f6e6e656374696e67206166746572203230204665627275617279004730450221008389df45f0703f39ec8c1cc42c13810ffcae14995bb648340219e353b63b53eb022009ec65e1c1aaeec1fd334c6b684bde2b3f573060d5b70c3a46723326e4e8a4f1"
},
"REJECT": {
"message": "",
@ -20,8 +20,8 @@
"payload": ""
},
"GETDATA": {
"message": "",
"payload": ""
"message": "f9beb4d967657464617461000000000025000000253fc50c01020000003c2a5e4dcb5a44daf5ebff14a89d26a99b8aa1c1d757694b03d7e6ca7eeda6ca",
"payload": "01020000003c2a5e4dcb5a44daf5ebff14a89d26a99b8aa1c1d757694b03d7e6ca7eeda6ca"
},
"GETADDR": {
"message": "",
@ -32,8 +32,8 @@
"payload": ""
},
"HEADERS": {
"message": "f9beb4d9686561646572730000000000a1000000ffd6770b0202000000b91ddbbfc801b7fe6f470ce9528f98f01b496b53f23c411300000000000000004901c9d18d0a468b20cc62ddf75aee58cf410440ea390300bf7a5f6848be350508d4cb54c0a31a18b9f661ec0002000000a02d6472e3e6fc9a1cebeaad14a90208a715e2bd234ea00600000000000000006f596f650fbbd5478489c66d651c9e3ea56f394d1f1481f90975cf0c8dda45fd3ad4cb54c0a31a1872e262",
"payload": "0202000000b91ddbbfc801b7fe6f470ce9528f98f01b496b53f23c411300000000000000004901c9d18d0a468b20cc62ddf75aee58cf410440ea390300bf7a5f6848be350508d4cb54c0a31a18b9f661ec0002000000a02d6472e3e6fc9a1cebeaad14a90208a715e2bd234ea00600000000000000006f596f650fbbd5478489c66d651c9e3ea56f394d1f1481f90975cf0c8dda45fd3ad4cb54c0a31a1872e262"
"message": "f9beb4d9686561646572730000000000f400000043385d010302000000b91ddbbfc801b7fe6f470ce9528f98f01b496b53f23c411300000000000000004901c9d18d0a468b20cc62ddf75aee58cf410440ea390300bf7a5f6848be350508d4cb54c0a31a18b9f661ec0002000000a02d6472e3e6fc9a1cebeaad14a90208a715e2bd234ea00600000000000000006f596f650fbbd5478489c66d651c9e3ea56f394d1f1481f90975cf0c8dda45fd3ad4cb54c0a31a1872e262a200020000002e4db38f1970099bf21335edd604e7a591213e189d1d1806000000000000000031a30091f5bdbca8958d2c4ccc0bfa9df93e2a3ea4d00e03222a663179db90a756d6cb54c0a31a187947855000",
"payload": "0302000000b91ddbbfc801b7fe6f470ce9528f98f01b496b53f23c411300000000000000004901c9d18d0a468b20cc62ddf75aee58cf410440ea390300bf7a5f6848be350508d4cb54c0a31a18b9f661ec0002000000a02d6472e3e6fc9a1cebeaad14a90208a715e2bd234ea00600000000000000006f596f650fbbd5478489c66d651c9e3ea56f394d1f1481f90975cf0c8dda45fd3ad4cb54c0a31a1872e262a200020000002e4db38f1970099bf21335edd604e7a591213e189d1d1806000000000000000031a30091f5bdbca8958d2c4ccc0bfa9df93e2a3ea4d00e03222a663179db90a756d6cb54c0a31a187947855000"
},
"TX": {
"message": "",

View File

@ -4,11 +4,15 @@ var chai = require('chai');
var should = chai.should();
var Buffers = require('buffers');
var bitcore = require('bitcore');
var Data = require('./data/messages');
var P2P = require('../');
var Messages = P2P.Messages;
var Networks = bitcore.Networks;
var BufferUtils = bitcore.util.buffer;
var network = Networks.livenet;
describe('Messages', function() {
@ -31,7 +35,7 @@ describe('Messages', function() {
NotFound: 'notfound'
};
// TODO: add data for these
var noPayload = ['Alert', 'Reject', 'GetBlocks', 'GetHeaders', 'GetData'];
var noPayload = ['Reject', 'GetBlocks', 'GetHeaders'];
var names = Object.keys(commands);
describe('named', function() {
names.forEach(function(name) {
@ -67,4 +71,49 @@ describe('Messages', function() {
});
});
var buildMessage = function(hex) {
var m = Buffers();
m.push(new Buffer(hex, 'hex'));
return m;
};
it('fails with invalid command', function() {
var invalidCommand = 'f9beb4d96d616c6963696f757300000025000000bd5e830c' +
'0102000000ec3995c1bf7269ff728818a65e53af00cbbee6b6eca8ac9ce7bc79d87' +
'7041ed8';
var fails = function() {
Messages.parseMessage(network, buildMessage(invalidCommand));
};
fails.should.throw('Unsupported message command: malicious');
});
it('ignores malformed messages', function() {
var malformed1 = 'd8c4c3d976657273696f6e000000000065000000fc970f1772110' +
'1000100000000000000ba6288540000000001000000000000000000000000000000' +
'0000ffffba8886dceab0010000000000000000000000000000000000ffff0509552' +
'2208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001';
var malformed2 = 'f9beb4d967657464617461000000000089000000d88134740102' +
'0000006308e4a380c949dbad182747b0f7b6a89e874328ca41f37287f74a81b8f84' +
'86d';
var malformed3 = 'f9beb4d967657464617461000000000025000000616263640102' +
'00000069ebcbc34a4f9890da9aea0f773beba883a9afb1ab9ad7647dd4a1cd346c3' +
'728';
[malformed1, malformed2, malformed3].forEach(function(malformed) {
var ret = Messages.parseMessage(network, buildMessage(malformed));
should.not.exist(ret);
});
});
it('Inventory#from family methods work', function() {
var hash = 'eb951630aba498b9a0d10f72b5ea9e39d5ff04b03dc2231e662f52057f948aa1';
[Messages.Inventory, Messages.GetData, Messages.NotFound].forEach(function(clazz) {
var b = clazz.forBlock(hash);
(b instanceof clazz).should.equal(true);
var t = clazz.forTransaction(hash);
(t instanceof clazz).should.equal(true);
clazz.forBlock(BufferUtils.reverse(new Buffer(hash, 'hex'))).should.deep.equal(b);
clazz.forTransaction(BufferUtils.reverse(new Buffer(hash, 'hex'))).should.deep.equal(t);
});
});
});

View File

@ -12,15 +12,15 @@ var sinon = require('sinon');
var fs = require('fs');
var bitcore = require('bitcore');
var P2P = require('../');
var Peer = P2P.Peer;
var p2p = require('../');
var Peer = p2p.Peer;
var Networks = bitcore.Networks;
describe('Peer', function() {
describe('Integration test', function() {
it('parses this stream of data from a connection', function(callback) {
var peer = new P2P.Peer('');
var peer = new p2p.Peer('');
var stub = sinon.stub();
var dataCallback;
var connectCallback;