Merge pull request #22 from maraoz/improve/p2p
add bitcoind integration tests and general refactor
This commit is contained in:
commit
3fa45d31df
141
integration/bitcoind.js
Normal file
141
integration/bitcoind.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
210
lib/messages.js
210
lib/messages.js
@ -7,6 +7,7 @@
|
|||||||
var Buffers = require('buffers');
|
var Buffers = require('buffers');
|
||||||
var Put = require('bufferput');
|
var Put = require('bufferput');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
|
|
||||||
@ -14,57 +15,20 @@ var BlockHeaderModel = bitcore.BlockHeader;
|
|||||||
var BlockModel = bitcore.Block;
|
var BlockModel = bitcore.Block;
|
||||||
var BufferReader = bitcore.encoding.BufferReader;
|
var BufferReader = bitcore.encoding.BufferReader;
|
||||||
var BufferUtil = bitcore.util.buffer;
|
var BufferUtil = bitcore.util.buffer;
|
||||||
|
var $ = bitcore.util.preconditions;
|
||||||
var Hash = bitcore.crypto.Hash;
|
var Hash = bitcore.crypto.Hash;
|
||||||
var Random = bitcore.crypto.Random;
|
var Random = bitcore.crypto.Random;
|
||||||
var TransactionModel = bitcore.Transaction;
|
var TransactionModel = bitcore.Transaction;
|
||||||
|
var errors = bitcore.Errors;
|
||||||
|
|
||||||
var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8);
|
var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8);
|
||||||
var PROTOCOL_VERSION = 70000;
|
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.
|
* @desc Internal function that discards data until another message is found.
|
||||||
* @name P2P.Message#discardUntilNextMessage
|
* @name P2P.Message#discardUntilNextMessage
|
||||||
*/
|
*/
|
||||||
function discardUntilNextMessage(network, dataBuffer) {
|
var discardUntilNextMessage = function(network, dataBuffer) {
|
||||||
var magicNumber = network.networkMagic;
|
var magicNumber = network.networkMagic;
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
@ -84,11 +48,11 @@ function discardUntilNextMessage(network, dataBuffer) {
|
|||||||
|
|
||||||
i++; // continue scanning
|
i++; // continue scanning
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Message that knows how to parse and serialize itself.
|
* Abstract Message this 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
|
* @name P2P.Message
|
||||||
*/
|
*/
|
||||||
function Message() {}
|
function Message() {}
|
||||||
@ -99,6 +63,51 @@ function Message() {}
|
|||||||
*/
|
*/
|
||||||
Message.COMMANDS = {};
|
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
|
* Look up a message type by command name and instantiate the correct Message
|
||||||
* @name P2P.Message#buildMessage
|
* @name P2P.Message#buildMessage
|
||||||
@ -109,6 +118,7 @@ Message.buildMessage = function(command, payload) {
|
|||||||
return new CommandClass().fromBuffer(payload);
|
return new CommandClass().fromBuffer(payload);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error while parsing message', err);
|
console.log('Error while parsing message', err);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -120,7 +130,7 @@ Message.buildMessage = function(command, payload) {
|
|||||||
*/
|
*/
|
||||||
Message.prototype.fromBuffer = function(payload) {
|
Message.prototype.fromBuffer = function(payload) {
|
||||||
/* jshint unused: false */
|
/* jshint unused: false */
|
||||||
return this;
|
throw new errors.NotImplemented();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -138,9 +148,10 @@ Message.prototype.getPayload = function() {
|
|||||||
* @returns{Buffer} the serialized message
|
* @returns{Buffer} the serialized message
|
||||||
*/
|
*/
|
||||||
Message.prototype.serialize = function(network) {
|
Message.prototype.serialize = function(network) {
|
||||||
var magic = network.networkMagic;
|
$.checkArgument(network);
|
||||||
var commandBuf = new Buffer(this.command, 'ascii');
|
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 payload = this.getPayload();
|
||||||
var checksum = Hash.sha256sha256(payload).slice(0, 4);
|
var checksum = Hash.sha256sha256(payload).slice(0, 4);
|
||||||
@ -197,7 +208,7 @@ Version.prototype.fromBuffer = function(payload) {
|
|||||||
* @type {BN}
|
* @type {BN}
|
||||||
* @desc The time this message was sent
|
* @desc The time this message was sent
|
||||||
*/
|
*/
|
||||||
this.timestamp = parser.readUInt64LEBN();
|
this.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000);
|
||||||
/**
|
/**
|
||||||
* @type {Buffer}
|
* @type {Buffer}
|
||||||
* @desc IPv4/6 address of the interface used to connect to this peer
|
* @desc IPv4/6 address of the interface used to connect to this peer
|
||||||
@ -214,7 +225,7 @@ Version.prototype.fromBuffer = function(payload) {
|
|||||||
*/
|
*/
|
||||||
this.nonce = parser.read(8);
|
this.nonce = parser.read(8);
|
||||||
/**
|
/**
|
||||||
* @desc A random number
|
* @desc The node's user agent / subversion
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
this.subversion = parser.readVarintBuf().toString();
|
this.subversion = parser.readVarintBuf().toString();
|
||||||
@ -237,7 +248,7 @@ Version.prototype.getPayload = function() {
|
|||||||
put.put(this.nonce);
|
put.put(this.nonce);
|
||||||
put.varint(this.subversion.length);
|
put.varint(this.subversion.length);
|
||||||
put.put(new Buffer(this.subversion, 'ascii'));
|
put.put(new Buffer(this.subversion, 'ascii'));
|
||||||
put.word32le(0);
|
put.word32le(this.start_height);
|
||||||
|
|
||||||
return put.buffer();
|
return put.buffer();
|
||||||
};
|
};
|
||||||
@ -256,19 +267,34 @@ function Inventory(inventory) {
|
|||||||
this.command = 'inv';
|
this.command = 'inv';
|
||||||
/**
|
/**
|
||||||
* @name P2P.Message.Inventory.inventory
|
* @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}
|
* @type {Array.Buffer}
|
||||||
*/
|
*/
|
||||||
this.inventory = inventory || [];
|
this.inventory = inventory || [];
|
||||||
}
|
}
|
||||||
util.inherits(Inventory, Message);
|
util.inherits(Inventory, Message);
|
||||||
|
|
||||||
|
// 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) {
|
Inventory.prototype.fromBuffer = function(payload) {
|
||||||
var parser = new BufferReader(payload);
|
var parser = new BufferReader(payload);
|
||||||
var count = parser.readVarintNum();
|
var count = parser.readVarintNum();
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
|
var type = parser.readUInt32LE();
|
||||||
this.inventory.push({
|
this.inventory.push({
|
||||||
type: parser.readUInt32LE(),
|
type: type,
|
||||||
|
typeName: Inventory.TYPE_NAME[type],
|
||||||
hash: parser.read(32)
|
hash: parser.read(32)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -278,13 +304,13 @@ Inventory.prototype.fromBuffer = function(payload) {
|
|||||||
|
|
||||||
Inventory.prototype.getPayload = function() {
|
Inventory.prototype.getPayload = function() {
|
||||||
var put = new Put();
|
var put = new Put();
|
||||||
|
|
||||||
put.varint(this.inventory.length);
|
put.varint(this.inventory.length);
|
||||||
this.inventory.forEach(function(value) {
|
this.inventory.forEach(function(value) {
|
||||||
put.word32le(value.type);
|
put.word32le(value.type);
|
||||||
put.put(value.hash);
|
put.put(value.hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
return put.buffer();
|
return put.buffer();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -304,12 +330,18 @@ module.exports.Inventory = Message.COMMANDS.inv = Inventory;
|
|||||||
* @param{Array} inventory - requested elements
|
* @param{Array} inventory - requested elements
|
||||||
*/
|
*/
|
||||||
function GetData(inventory) {
|
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.command = 'getdata';
|
||||||
this.inventory = inventory || [];
|
this.inventory = inventory || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(GetData, 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.
|
* Sent to another peer mainly to check the connection is still alive.
|
||||||
@ -320,7 +352,7 @@ module.exports.GetData = GetData;
|
|||||||
function Ping(nonce) {
|
function Ping(nonce) {
|
||||||
this.command = 'ping';
|
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}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.nonce = nonce || CONNECTION_NONCE;
|
this.nonce = nonce || CONNECTION_NONCE;
|
||||||
@ -347,13 +379,16 @@ module.exports.Ping = Message.COMMANDS.ping = Ping;
|
|||||||
function Pong(nonce) {
|
function Pong(nonce) {
|
||||||
this.command = 'pong';
|
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}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
this.nonce = nonce || CONNECTION_NONCE;
|
this.nonce = nonce || CONNECTION_NONCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(Pong, Ping);
|
util.inherits(Pong, Ping);
|
||||||
|
Pong.prototype.fromBuffer = function() {
|
||||||
|
return new Pong();
|
||||||
|
};
|
||||||
module.exports.Pong = Message.COMMANDS.pong = Pong;
|
module.exports.Pong = Message.COMMANDS.pong = Pong;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -372,6 +407,27 @@ function Addresses(addresses) {
|
|||||||
}
|
}
|
||||||
util.inherits(Addresses, Message);
|
util.inherits(Addresses, Message);
|
||||||
|
|
||||||
|
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) {
|
Addresses.prototype.fromBuffer = function(payload) {
|
||||||
var parser = new BufferReader(payload);
|
var parser = new BufferReader(payload);
|
||||||
var addrCount = Math.min(parser.readVarintNum(), 1000);
|
var addrCount = Math.min(parser.readVarintNum(), 1000);
|
||||||
@ -380,29 +436,17 @@ Addresses.prototype.fromBuffer = function(payload) {
|
|||||||
for (var i = 0; i < addrCount; i++) {
|
for (var i = 0; i < addrCount; i++) {
|
||||||
// TODO: Time actually depends on the version of the other peer (>=31402)
|
// 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();
|
var services = parser.readUInt64LEBN();
|
||||||
|
|
||||||
// parse the ipv6 to a string
|
var ip = Addresses.parseIP(parser);
|
||||||
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();
|
var port = parser.readUInt16BE();
|
||||||
|
|
||||||
this.addresses.push({
|
this.addresses.push({
|
||||||
time: time,
|
time: time,
|
||||||
services: services,
|
services: services,
|
||||||
ip: { v6: ipv6, v4: ipv4 },
|
ip: ip,
|
||||||
port: port
|
port: port
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -436,6 +480,9 @@ function GetAddresses() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(GetAddresses, Message);
|
util.inherits(GetAddresses, Message);
|
||||||
|
GetAddresses.prototype.fromBuffer = function() {
|
||||||
|
return new GetAddresses();
|
||||||
|
};
|
||||||
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;
|
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -448,6 +495,9 @@ function VerAck() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(VerAck, Message);
|
util.inherits(VerAck, Message);
|
||||||
|
VerAck.prototype.fromBuffer = function() {
|
||||||
|
return new VerAck();
|
||||||
|
};
|
||||||
module.exports.VerAck = Message.COMMANDS.verack = VerAck;
|
module.exports.VerAck = Message.COMMANDS.verack = VerAck;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -515,6 +565,7 @@ function Headers(blockheaders) {
|
|||||||
util.inherits(Headers, Message);
|
util.inherits(Headers, Message);
|
||||||
|
|
||||||
Headers.prototype.fromBuffer = function(payload) {
|
Headers.prototype.fromBuffer = function(payload) {
|
||||||
|
$.checkArgument(payload && payload.length > 0, 'No data found to create Headers message');
|
||||||
var parser = new BufferReader(payload);
|
var parser = new BufferReader(payload);
|
||||||
var count = parser.readVarintNum();
|
var count = parser.readVarintNum();
|
||||||
|
|
||||||
@ -548,6 +599,7 @@ module.exports.Headers = Message.COMMANDS.headers = Headers;
|
|||||||
* @param {Block} block
|
* @param {Block} block
|
||||||
*/
|
*/
|
||||||
function Block(block) {
|
function Block(block) {
|
||||||
|
$.checkArgument(_.isUndefined(block) || block instanceof BlockModel);
|
||||||
this.command = 'block';
|
this.command = 'block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -559,12 +611,13 @@ function Block(block) {
|
|||||||
util.inherits(Block, Message);
|
util.inherits(Block, Message);
|
||||||
|
|
||||||
Block.prototype.fromBuffer = function(payload) {
|
Block.prototype.fromBuffer = function(payload) {
|
||||||
this.block = BlockModel(payload);
|
$.checkArgument(BufferUtil.isBuffer(payload));
|
||||||
return this;
|
var block = BlockModel(payload);
|
||||||
|
return new Block(block);
|
||||||
};
|
};
|
||||||
|
|
||||||
Block.prototype.getPayload = function() {
|
Block.prototype.getPayload = function() {
|
||||||
return this.block.toBuffer();
|
return this.block ? this.block.toBuffer() : new Buffer(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.Block = Message.COMMANDS.block = Block;
|
module.exports.Block = Message.COMMANDS.block = Block;
|
||||||
@ -576,6 +629,7 @@ module.exports.Block = Message.COMMANDS.block = Block;
|
|||||||
* @param{Transaction} transaction
|
* @param{Transaction} transaction
|
||||||
*/
|
*/
|
||||||
function Transaction(transaction) {
|
function Transaction(transaction) {
|
||||||
|
$.checkArgument(_.isUndefined(transaction) || transaction instanceof TransactionModel);
|
||||||
this.command = 'tx';
|
this.command = 'tx';
|
||||||
/**
|
/**
|
||||||
* @type {Transaction}
|
* @type {Transaction}
|
||||||
@ -590,7 +644,7 @@ Transaction.prototype.fromBuffer = function(payload) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.getPayload = function() {
|
Transaction.prototype.getPayload = function() {
|
||||||
return this.transaction.toBuffer();
|
return this.transaction ? this.transaction.toBuffer() : new Buffer(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.Transaction = Message.COMMANDS.tx = Transaction;
|
module.exports.Transaction = Message.COMMANDS.tx = Transaction;
|
||||||
@ -624,6 +678,7 @@ util.inherits(GetBlocks, Message);
|
|||||||
|
|
||||||
GetBlocks.prototype.fromBuffer = function(payload) {
|
GetBlocks.prototype.fromBuffer = function(payload) {
|
||||||
var parser = new BufferReader(payload);
|
var parser = new BufferReader(payload);
|
||||||
|
$.checkArgument(!parser.finished(), 'No data received in payload');
|
||||||
this.version = parser.readUInt32LE();
|
this.version = parser.readUInt32LE();
|
||||||
|
|
||||||
var startCount = Math.min(parser.readVarintNum(), 500);
|
var startCount = Math.min(parser.readVarintNum(), 500);
|
||||||
@ -682,6 +737,9 @@ function GetHeaders(starts, stop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(GetHeaders, GetBlocks);
|
util.inherits(GetHeaders, GetBlocks);
|
||||||
|
GetHeaders.prototype.fromBuffer = function() {
|
||||||
|
return new GetHeaders();
|
||||||
|
};
|
||||||
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;
|
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -697,7 +755,7 @@ util.inherits(GetMempool, Message);
|
|||||||
module.exports.GetMempool = Message.COMMANDS.mempool = GetMempool;
|
module.exports.GetMempool = Message.COMMANDS.mempool = GetMempool;
|
||||||
|
|
||||||
// TODO: Remove this PATCH (yemel)
|
// TODO: Remove this PATCH (yemel)
|
||||||
Buffers.prototype.skip = function (i) {
|
Buffers.prototype.skip = function(i) {
|
||||||
if (i === 0) return;
|
if (i === 0) return;
|
||||||
|
|
||||||
if (i === this.length) {
|
if (i === this.length) {
|
||||||
|
|||||||
28
lib/peer.js
28
lib/peer.js
@ -7,6 +7,7 @@ var Socks5Client = require('socks5-client');
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
|
var $ = bitcore.util.preconditions;
|
||||||
var Networks = bitcore.Networks;
|
var Networks = bitcore.Networks;
|
||||||
var Messages = require('./messages');
|
var Messages = require('./messages');
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ var MAX_RECEIVE_BUFFER = 10000000;
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```javascript
|
* ```javascript
|
||||||
*
|
*
|
||||||
* var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050);
|
* var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050);
|
||||||
* peer.on('tx', function(tx) {
|
* peer.on('tx', function(tx) {
|
||||||
* console.log('New transaction: ', tx.id);
|
* console.log('New transaction: ', tx.id);
|
||||||
@ -42,10 +43,10 @@ function Peer(host, port, network) {
|
|||||||
network = port;
|
network = port;
|
||||||
port = undefined;
|
port = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.host = host;
|
this.host = host || 'localhost';
|
||||||
this.status = Peer.STATUS.DISCONNECTED;
|
this.status = Peer.STATUS.DISCONNECTED;
|
||||||
this.network = network || Networks.livenet;
|
this.network = network || Networks.defaultNetwork;
|
||||||
this.port = port || this.network.port;
|
this.port = port || this.network.port;
|
||||||
|
|
||||||
this.dataBuffer = new Buffers();
|
this.dataBuffer = new Buffers();
|
||||||
@ -64,7 +65,7 @@ function Peer(host, port, network) {
|
|||||||
this.on('version', function(message) {
|
this.on('version', function(message) {
|
||||||
self.version = message.version;
|
self.version = message.version;
|
||||||
self.subversion = message.subversion;
|
self.subversion = message.subversion;
|
||||||
self.bestHeight = message.start_height
|
self.bestHeight = message.start_height;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('ping', function(message) {
|
this.on('ping', function(message) {
|
||||||
@ -89,9 +90,7 @@ Peer.STATUS = {
|
|||||||
* @returns {Peer} The same Peer instance.
|
* @returns {Peer} The same Peer instance.
|
||||||
*/
|
*/
|
||||||
Peer.prototype.setProxy = function(host, port) {
|
Peer.prototype.setProxy = function(host, port) {
|
||||||
if (this.status != Peer.STATUS.DISCONNECTED) {
|
$.checkState(this.status === Peer.STATUS.DISCONNECTED);
|
||||||
throw Error('Invalid State');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.proxy = {
|
this.proxy = {
|
||||||
host: host,
|
host: host,
|
||||||
@ -116,13 +115,16 @@ Peer.prototype.connect = function() {
|
|||||||
self._sendVersion();
|
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('end', self.disconnect.bind(this));
|
||||||
|
|
||||||
this.socket.on('data', function(data) {
|
this.socket.on('data', function(data) {
|
||||||
self.dataBuffer.push(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();
|
self._readMessage();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,6 +132,10 @@ Peer.prototype.connect = function() {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Peer.prototype._onError = function(e) {
|
||||||
|
this.emit('error', e);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects the remote connection.
|
* Disconnects the remote connection.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -3,6 +3,42 @@
|
|||||||
"message": "f9beb4d976657273696f6e000000000065000000fc970f17721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001",
|
"message": "f9beb4d976657273696f6e000000000065000000fc970f17721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001",
|
||||||
"payload": "721101000100000000000000ba62885400000000010000000000000000000000000000000000ffffba8886dceab0010000000000000000000000000000000000ffff05095522208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001"
|
"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": {
|
"VERACK": {
|
||||||
"message": "f9beb4d976657261636b000000000000000000005df6e0e2",
|
"message": "f9beb4d976657261636b000000000000000000005df6e0e2",
|
||||||
"payload": ""
|
"payload": ""
|
||||||
@ -18,5 +54,9 @@
|
|||||||
"PING": {
|
"PING": {
|
||||||
"message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c",
|
"message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c",
|
||||||
"payload": "6b86480ae969867c"
|
"payload": "6b86480ae969867c"
|
||||||
|
},
|
||||||
|
"PONG": {
|
||||||
|
"message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c",
|
||||||
|
"payload": "6b86480ae969867c"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Mocha</title>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="mocha"></div>
|
|
||||||
<script src="../node_modules/mocha/mocha.js"></script>
|
|
||||||
<script>mocha.setup('bdd')</script>
|
|
||||||
<script src="../tests.js"></script>
|
|
||||||
<script>
|
|
||||||
mocha.run();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
382
test/messages.js
382
test/messages.js
@ -12,344 +12,58 @@ var Networks = bitcore.Networks;
|
|||||||
|
|
||||||
describe('Messages', function() {
|
describe('Messages', function() {
|
||||||
|
|
||||||
describe('Version', function() {
|
var commands = {
|
||||||
it('should be able to create instance', function() {
|
Version: 'version',
|
||||||
var message = new Messages.Version();
|
VerAck: 'verack',
|
||||||
message.command.should.equal('version');
|
Inventory: 'inv',
|
||||||
message.version.should.equal(70000);
|
Addresses: 'addr',
|
||||||
var version = require('../package.json').version;
|
Ping: 'ping',
|
||||||
message.subversion.should.equal('/bitcore:' + version + '/');
|
Pong: 'pong',
|
||||||
should.exist(message.nonce);
|
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('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() {
|
it('should be able to serialize the payload', function() {
|
||||||
var message = new Messages.Version();
|
var payload = message.getPayload();
|
||||||
var payload = message.getPayload();
|
should.exist(payload);
|
||||||
should.exist(payload);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to serialize the message', function() {
|
it('should be able to serialize the message', function() {
|
||||||
var message = new Messages.Version();
|
var buffer = message.serialize(Networks.livenet);
|
||||||
var buffer = message.serialize(Networks.livenet);
|
should.exist(buffer);
|
||||||
should.exist(buffer);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to parse payload', function() {
|
if (noPayload.indexOf(name) === -1) {
|
||||||
var payload = new Buffer(Data.VERSION.payload, 'hex');
|
it('should be able to parse payload', function() {
|
||||||
new Messages.Version().fromBuffer(payload);
|
var payload = new Buffer(data.payload, 'hex');
|
||||||
|
var m = new Messages[name]().fromBuffer(payload);
|
||||||
|
should.exist(m);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('VerAck', function() {
|
|
||||||
it('should be able to create instance', function() {
|
|
||||||
var message = new Messages.VerAck();
|
|
||||||
message.command.should.equal('verack');
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
new Messages.Inventory().fromBuffer(payload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
new Messages.Addresses().fromBuffer(payload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
new Messages.Ping().fromBuffer(payload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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');
|
|
||||||
new Messages.Pong().fromBuffer(payload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
--recursive
|
|
||||||
38
test/peer.js
38
test/peer.js
@ -44,8 +44,7 @@ describe('Peer', function() {
|
|||||||
connectCallback = arguments[1];
|
connectCallback = arguments[1];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
stub.write = function() {
|
stub.write = function() {};
|
||||||
};
|
|
||||||
stub.connect = function() {
|
stub.connect = function() {
|
||||||
connectCallback();
|
connectCallback();
|
||||||
};
|
};
|
||||||
@ -56,7 +55,7 @@ describe('Peer', function() {
|
|||||||
dataCallback(fs.readFileSync('./test/connection.log'));
|
dataCallback(fs.readFileSync('./test/connection.log'));
|
||||||
});
|
});
|
||||||
var check = function(message) {
|
var check = function(message) {
|
||||||
received[message.command]++;
|
received[message.command]++;
|
||||||
if (_.isEqual(received, expected)) {
|
if (_.isEqual(received, expected)) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
@ -105,27 +104,20 @@ describe('Peer', function() {
|
|||||||
peer.port.should.equal(8111);
|
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() {
|
peer2 = peer.setProxy('127.0.0.1', 9050);
|
||||||
var peer, peer2, socket;
|
peer2.proxy.host.should.equal('127.0.0.1');
|
||||||
|
peer2.proxy.port.should.equal(9050);
|
||||||
peer = new Peer('localhost');
|
socket = peer2._getSocket();
|
||||||
expect(peer.proxy).to.be.undefined();
|
socket.should.be.instanceof(Socks5Client);
|
||||||
socket = peer._getSocket();
|
|
||||||
socket.should.be.instanceof(Net.Socket);
|
|
||||||
|
|
||||||
peer2 = peer.setProxy('127.0.0.1', 9050);
|
|
||||||
peer2.proxy.host.should.equal('127.0.0.1');
|
|
||||||
peer2.proxy.port.should.equal(9050);
|
|
||||||
socket = peer2._getSocket();
|
|
||||||
socket.should.be.instanceof(Socks5Client);
|
|
||||||
|
|
||||||
peer.should.equal(peer2);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
peer.should.equal(peer2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
168
test/pool.js
168
test/pool.js
@ -1,105 +1,103 @@
|
|||||||
'use strict';
|
'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 dns = require('dns');
|
||||||
var should = chai.should();
|
var sinon = require('sinon');
|
||||||
var expect = chai.expect;
|
|
||||||
|
|
||||||
var bitcore = require('bitcore');
|
describe('Pool', function() {
|
||||||
var P2P = require('../');
|
|
||||||
var Peer = P2P.Peer;
|
|
||||||
var MessagesData = require('./data/messages');
|
|
||||||
var Messages = P2P.Messages;
|
|
||||||
var Pool = P2P.Pool;
|
|
||||||
var Networks = bitcore.Networks;
|
|
||||||
|
|
||||||
var dns = require('dns');
|
it('should be able to create instance', function() {
|
||||||
var sinon = require('sinon');
|
var pool = new Pool();
|
||||||
|
should.exist(pool.network);
|
||||||
|
expect(pool.network).to.satisfy(function(network) {
|
||||||
|
return network === Networks.testnet || network === Networks.livenet;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Pool', function() {
|
it('should be able to create instance setting the network', function() {
|
||||||
|
var pool = new Pool(Networks.testnet);
|
||||||
|
pool.network.should.equal(Networks.testnet);
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to create instance', function() {
|
it('should discover peers via dns', function() {
|
||||||
var pool = new Pool();
|
var stub = sinon.stub(dns, 'resolve', function(seed, callback) {
|
||||||
should.exist(pool.network);
|
callback(null, ['10.10.10.1', '10.10.10.2', '10.10.10.3']);
|
||||||
expect(pool.network).to.satisfy(function(network){
|
});
|
||||||
if (network === Networks.testnet || network === Networks.livenet) {
|
var pool = new Pool(Networks.livenet);
|
||||||
return true;
|
pool.connect();
|
||||||
}
|
pool.disconnect();
|
||||||
return false;
|
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() {
|
// mock a addr peer event
|
||||||
var pool = new Peer(Networks.testnet);
|
var peerMessageStub = sinon.stub(Peer.prototype, '_readMessage', function() {
|
||||||
pool.network.should.equal(Networks.livenet);
|
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 pool = new Pool();
|
||||||
var stub = sinon.stub(dns, 'resolve', function(seed, callback){
|
|
||||||
callback(null, ['10.10.10.1', '10.10.10.2', '10.10.10.3']);
|
pool._addAddr({
|
||||||
});
|
ip: {
|
||||||
var pool = new Pool(Networks.livenet);
|
v4: 'localhost'
|
||||||
pool.connect();
|
}
|
||||||
pool.disconnect();
|
|
||||||
pool._addrs.length.should.equal(3);
|
|
||||||
stub.restore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not discover peers via dns', function() {
|
// listen for the event
|
||||||
var pool = new Pool();
|
pool.on('peeraddr', function(peer, message) {
|
||||||
pool._addAddr({ip: {v4: '10.10.10.1'}});
|
pool._addrs.length.should.equal(502);
|
||||||
pool.connect();
|
|
||||||
pool.disconnect();
|
// restore stubs
|
||||||
pool._addrs.length.should.equal(1);
|
peerConnectStub.restore();
|
||||||
});
|
peerMessageStub.restore();
|
||||||
|
|
||||||
it('should add new addrs as they are announced over the network', function(done) {
|
for (var i = 0; i < pool._addrs.length; i++) {
|
||||||
|
should.exist(pool._addrs[i].hash);
|
||||||
// only emit an event, no need to connect
|
should.exist(pool._addrs[i].ip);
|
||||||
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function(){
|
should.exist(pool._addrs[i].ip.v4);
|
||||||
this._readMessage();
|
}
|
||||||
this.emit('ready');
|
|
||||||
});
|
// done
|
||||||
|
done();
|
||||||
// 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();
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pool.connect();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user