Initial commit for bitcore-p2p

This commit is contained in:
Esteban Ordano 2014-12-24 13:30:55 -03:00
commit 40225584fd
21 changed files with 1950 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
*.sw[a-z]
coverage
node_modules
bitcore-scaffolding.js
bitcore-scaffolding.min.js
lib/errors/index.js
npm-debug.log
bower_components
report
.DS_Store

36
.jsdoc.conf Normal file
View File

@ -0,0 +1,36 @@
{
"tags": {
"allowUnknownTags": true
},
"source": {
"include": ["docs/README.md"],
"exclude": [],
"includePattern": "lib/.+\\.js(doc)?$",
"excludePattern": "(^|\\/|\\\\)_"
},
"plugins": ["plugins/markdown"],
"templates": {
"cleverLinks": false,
"monospaceLinks": false
},
"opts": {
"template": "node_modules/ink-docstrap/template",
"encoding": "utf8",
"destination": "./apiref/",
"recurse": true,
"query": "value",
"private": true,
"lenient": true
},
"templates": {
"systemName": "bitcore",
"copyright": "© 2013-2015, BitPay Inc.",
"navType": "vertical",
"theme": "journal",
"linenums": true,
"collapseSymbols": false,
"inverseNav": false,
"outputSourceFiles": true
}
}

44
.jshintrc Normal file
View File

@ -0,0 +1,44 @@
{
"bitwise": false, // Prohibit bitwise operators (&, |, ^, etc.).
"browser": true, // Standard browser globals e.g. `window`, `document`.
"camelcase": false, // Permit only camelcase for `var` and `object indexes`.
"curly": true, // Require {} for every new block or scope.
"devel": false, // Allow development statements e.g. `console.log();`.
"eqeqeq": true, // Require triple equals i.e. `===`.
"esnext": true, // Allow ES.next specific features such as `const` and `let`.
"freeze": true, // Forbid overwriting prototypes of native objects such as Array, Date and so on.
"immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
"indent": 2, // Specify indentation spacing
"latedef": true, // Prohibit variable use before definition.
"newcap": false, // Require capitalization of all constructor functions e.g. `new F()`.
"noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`.
"node": true, // Enable globals available when code is running inside of the NodeJS runtime environment.
"noempty": true, // Prohibit use of empty blocks.
"nonew": true, // Prohibits the use of constructor functions for side-effects
"quotmark": "single", // Define quotes to string values.
"regexp": true, // Prohibit `.` and `[^...]` in regular expressions.
"smarttabs": false, // Supress warnings about mixed tabs and spaces
"strict": true, // Require `use strict` pragma in every file.
"trailing": true, // Prohibit trailing whitespaces.
"undef": true, // Require all non-global variables be declared before they are used.
"unused": true, // Warn unused variables.
"maxparams": 4, // Maximum number of parameters for a function
"maxstatements": 15, // Maximum number of statements in a function
"maxcomplexity": 6, // Cyclomatic complexity (http://en.wikipedia.org/wiki/Cyclomatic_complexity)
"maxdepth": 4, // Maximum depth of nested control structures
"maxlen": 120, // Maximum number of cols in a line
"predef": [ // Extra globals.
"after",
"afterEach",
"before",
"beforeEach",
"define",
"describe",
"exports",
"it",
"module",
"require"
]
}

13
.travis.yml Normal file
View File

@ -0,0 +1,13 @@
language: node_js
node_js:
- '0.10'
before_install:
- npm install -g bower
- npm install -g grunt-cli
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
install:
- bower install
- npm install
after_script:
- gulp coveralls

3
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,3 @@
# Contributing
Please see [CONTRIBUTING.md](https://github.com/bitpay/bitcore/blob/master/CONTRIBUTING.md) on the main bitcore repo.

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2015 BitPay, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

16
README.md Normal file
View File

@ -0,0 +1,16 @@
bitcore-p2p
=======
bitcore-p2p adds support for connecting to the bitcoin p2p network for node.
See [the main bitcore repo](https://github.com/bitpay/bitcore) for more information.
## Contributing
See [CONTRIBUTING.md](https://github.com/bitpay/bitcore) on the main bitcore repo for information about how to contribute.
## License
Code released under [the MIT license](https://github.com/bitpay/bitcore/blob/master/LICENSE).
Copyright 2013-2015 BitPay, Inc. Bitcore is a trademark maintained by BitPay, Inc.

92
gulpfile.js Normal file
View File

@ -0,0 +1,92 @@
/**
* @file gulpfile.js
*
* Defines tasks that can be run on gulp.
*
* Summary: <ul>
* <li> `test` - runs all the tests on node
* <li> `test:nofail` - internally used for watching (due to bug on gulp-mocha)
* <li> `watch:test` - watch for file changes and run tests
* <li> `lint` - run `jshint`
* <li> `coverage` - run `istanbul` with mocha to generate a report of test coverage
* <li> `coveralls` - updates coveralls info
* <li> `release` - automates release process (only for bitcore maintainers)
* </ul>
*/
'use strict';
var gulp = require('gulp');
var coveralls = require('gulp-coveralls');
var jshint = require('gulp-jshint');
var mocha = require('gulp-mocha');
var shell = require('gulp-shell');
var files = ['lib/**/*.js'];
var tests = ['test/**/*.js'];
var alljs = files.concat(tests);
function ignoreError() {
/* jshint ignore:start */ // using `this` in this context is weird
this.emit('end');
/* jshint ignore:end */
}
var testMocha = function() {
return gulp.src(tests).pipe(new mocha({
reporter: 'spec'
}));
};
/**
* Testing
*/
gulp.task('test', testMocha);
gulp.task('test:nofail', function() {
return testMocha().on('error', ignoreError);
});
/**
* Code quality and documentation
*/
gulp.task('lint', function() {
return gulp.src(alljs)
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
gulp.task('plato', shell.task(['plato -d report -r -l .jshintrc -t bitcore lib']));
gulp.task('coverage', shell.task(['node_modules/.bin/./istanbul cover node_modules/.bin/_mocha -- --recursive']));
gulp.task('coveralls', ['coverage'], function() {
gulp.src('coverage/lcov.info').pipe(coveralls());
});
/**
* Watch tasks
*/
gulp.task('watch:test', function() {
// TODO: Only run tests that are linked to file changes by doing
// something smart like reading through the require statements
return gulp.watch(alljs, ['test']);
});
gulp.task('watch:coverage', function() {
// TODO: Only run tests that are linked to file changes by doing
// something smart like reading through the require statements
return gulp.watch(alljs, ['coverage']);
});
gulp.task('watch:lint', function() {
// TODO: Only lint files that are linked to file changes by doing
// something smart like reading through the require statements
return gulp.watch(alljs, ['lint']);
});
/* Default task */
gulp.task('default', ['lint', 'coverage'], function() { });

4
index.js Normal file
View File

@ -0,0 +1,4 @@
var bitcore = require('bitcore-base');
bitcore.P2P = require('./lib');
module.exports = bitcore.P2P;

13
karma.conf.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
// karma.conf.js
module.exports = function(config) {
config.set({
frameworks: ['mocha'],
browsers: ['Chrome', 'Firefox'],
singleRun: true,
files: [
'browser/tests.js'
]
});
};

8
lib/index.js Normal file
View File

@ -0,0 +1,8 @@
/**
* @namespace P2P
*/
module.exports = {
Messages: require('./messages'),
Peer: require('./peer'),
Pool: require('./pool')
};

602
lib/messages.js Normal file
View File

@ -0,0 +1,602 @@
'use strict';
/**
* @namespace P2P.Message
*/
/* jshint curly: false */
var Buffers = require('buffers');
var Put = require('bufferput');
var util = require('util');
var bitcore = require('bitcore-base');
var BlockHeaderModel = bitcore.BlockHeader;
var BlockModel = bitcore.Block;
var BufferReader = bitcore.encoding.BufferReader;
var BufferUtil = bitcore.util.buffer;
var Hash = bitcore.crypto.Hash;
var Random = bitcore.crypto.Random;
var TransactionModel = bitcore.Transaction;
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 founds the next message.
* @name P2P.Message#discardUntilNextMessage
*/
function discardUntilNextMessage(network, dataBuffer) {
var magicNumber = network.networkMagic;
var i = 0;
for (;;) {
// check if it's the beginning of a new message
var packageNumber = dataBuffer.slice(0, 4);
if (BufferUtil.equals(packageNumber, magicNumber)) {
dataBuffer.skip(i);
return true;
}
// did we reach the end of the buffer?
if (i > (dataBuffer.length - 4)) {
dataBuffer.skip(i);
return false;
}
i++; // continue scanning
}
}
/**
* Abstract Message that knows how to parse and serialize itself.
* Concret subclases should implement {fromBuffer} and {getPayload} methods.
* @name P2P.Message
*/
function Message() {}
/**
* @value
* @name P2P.Message.COMMANDS
*/
Message.COMMANDS = {};
/**
* Look up a message type by command name and instantiate the correct Message
* @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);
}
};
/**
* Parse instance state from buffer.
*
* @param{Buffer} payload - the buffer to read from
* @returns{Message} The same message instance
*/
Message.prototype.fromBuffer = function(payload) {
/* jshint unused: false */
return this;
};
/**
* Serialize the payload into a buffer.
*
* @returns{Buffer} the serialized payload
*/
Message.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
/**
* Serialize the message into a buffer.
*
* @returns{Buffer} the serialized message
*/
Message.prototype.serialize = function(network) {
var magic = network.networkMagic;
var commandBuf = new Buffer(this.command, 'ascii');
if (commandBuf.length > 12) throw 'Command name too long';
var payload = this.getPayload();
var checksum = Hash.sha256sha256(payload).slice(0, 4);
// -- HEADER --
var message = new Put();
message.put(magic);
message.put(commandBuf);
message.pad(12 - commandBuf.length); // zero-padded
message.word32le(payload.length);
message.put(checksum);
// -- BODY --
message.put(payload);
return message.buffer();
}
/**
* Version Message
*
* @name P2P.Message.Version
* @param{string} subversion - version of the client
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Version(subversion, nonce) {
this.command = 'version';
this.version = PROTOCOL_VERSION;
this.subversion = subversion || '/BitcoinX:0.1/';
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Version, Message);
Version.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
this.version = parser.readUInt32LE();
this.services = parser.readUInt64LEBN();
this.timestamp = parser.readUInt64LEBN();
this.addr_me = parser.read(26);
this.addr_you = parser.read(26);
this.nonce = parser.read(8);
this.subversion = parser.readVarintBuf().toString();
this.start_height = parser.readUInt32LE();
return this;
};
Version.prototype.getPayload = function() {
var put = new Put();
put.word32le(this.version); // version
put.word64le(1); // services
put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp
put.pad(26); // addr_me
put.pad(26); // addr_you
put.put(this.nonce);
put.varint(this.subversion.length);
put.put(new Buffer(this.subversion, 'ascii'));
put.word32le(0);
return put.buffer();
};
module.exports.Version = Message.COMMANDS.version = Version;
/**
* Inv Message
*
* @name P2P.Message.Inventory
* @param{Array} inventory - reported elements
*/
function Inventory(inventory) {
this.command = 'inv';
this.inventory = inventory || [];
}
util.inherits(Inventory, Message);
Inventory.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
this.inventory.push({
type: parser.readUInt32LE(),
hash: parser.read(32)
});
}
return this;
};
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();
};
module.exports.Inventory = Message.COMMANDS.inv = Inventory;
/**
* Getdata Message
*
* @name P2P.Message.GetData
* @param{Array} inventory - requested elements
*/
function GetData(inventory) {
this.command = 'getdata';
this.inventory = inventory || [];
}
util.inherits(GetData, Inventory);
module.exports.GetData = GetData;
/**
* Ping Message
*
* @name P2P.Message.Ping
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Ping(nonce) {
this.command = 'ping';
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Ping, Message);
Ping.prototype.fromBuffer = function(payload) {
this.nonce = new BufferReader(payload).read(8);
return this;
};
Ping.prototype.getPayload = function() {
return this.nonce;
};
module.exports.Ping = Message.COMMANDS.ping = Ping;
/**
* Pong Message
*
* @name P2P.Message.Pong
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Pong(nonce) {
this.command = 'pong';
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Pong, Ping);
module.exports.Pong = Message.COMMANDS.pong = Pong;
/**
* Addr Message
*
* @name P2P.Message.Addressess
* @param{Array} addresses - array of know addresses
*/
function Addresses(addresses) {
this.command = 'addr';
this.addresses = addresses || [];
}
util.inherits(Addresses, Message);
Addresses.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var addrCount = Math.min(parser.readVarintNum(), 1000);
this.addresses = [];
for (var i = 0; i < addrCount; i++) {
// TODO: Time actually depends on the version of the other peer (>=31402)
var time = parser.readUInt32LE();
var services = parser.readUInt64LEBN();
// parse the ipv6 to a string
var ipv6 = [];
for (var a = 0; a < 6; a++) {
ipv6.push(parser.read(2).toString('hex'));
}
ipv6 = ipv6.join(':');
// parse the ipv4 to a string
var ipv4 = [];
for (var b = 0; b < 4; b++) {
ipv4.push(parser.read(1)[0]);
}
ipv4 = ipv4.join('.');
var port = parser.readUInt16BE();
this.addresses.push({
time: time,
services: services,
ip: { v6: ipv6, v4: ipv4 },
port: port
});
}
return this;
};
Addresses.prototype.getPayload = function() {
var put = new Put();
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);
}
return put.buffer();
};
module.exports.Addresses = Message.COMMANDS.addr = Addresses;
/**
* GetAddr Message
*
* @name P2P.Message.GetAddresses
*/
function GetAddresses() {
this.command = 'getaddr';
}
util.inherits(GetAddresses, Message);
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;
/**
* Verack Message
*
* @name P2P.Message.VerAck
*/
function VerAck() {
this.command = 'verack';
}
util.inherits(VerAck, Message);
module.exports.VerAck = Message.COMMANDS.verack = VerAck;
/**
* Reject Message
*
* @name P2P.Message.Reject
*/
function Reject() {
this.command = 'reject';
}
util.inherits(Reject, Message);
// TODO: Parse REJECT message
module.exports.Reject = Message.COMMANDS.reject = Reject;
/**
* Alert Message
*
* @name P2P.Message.Alert
*/
function Alert(payload, signature) {
this.command = 'alert';
this.payload = payload || new Buffer(32);
this.signature = signature || new Buffer(32);
}
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();
return this;
};
Alert.prototype.getPayload = function() {
var put = new Put();
put.varint(this.payload.length);
put.put(this.payload);
put.varint(this.signature.length);
put.put(this.signature);
return put.buffer();
};
module.exports.Alert = Message.COMMANDS.alert = Alert;
/**
* Headers Message
*
* @name P2P.Message.Headers
* @param{Array} blockheaders - array of block headers
*/
function Headers(blockheaders) {
this.command = 'headers';
this.headers = blockheaders || [];
}
util.inherits(Headers, Message);
Headers.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
this.headers = [];
for (var i = 0; i < count; i++) {
var header = BlockHeaderModel._fromBufferReader(parser);
this.headers.push(header);
}
return this;
};
Headers.prototype.getPayload = function() {
var put = new Put();
put.varint(this.headers.length);
for (var i = 0; i < this.headers.length; i++) {
var buffer = this.headers[i].toBuffer();
put.put(buffer);
}
return put.buffer();
};
module.exports.Headers = Message.COMMANDS.headers = Headers;
/**
* Block Message
*
* @name P2P.Message.Block
* @param{Block} block
*/
function Block(block) {
this.command = 'block';
this.block = block;
}
util.inherits(Block, Message);
Block.prototype.fromBuffer = function(payload) {
this.block = BlockModel(payload);
return this;
};
Block.prototype.getPayload = function() {
return this.block.toBuffer();
};
module.exports.Block = Message.COMMANDS.block = Block;
/**
* Tx Message
*
* @name P2P.Message.Transaction
* @param{Transaction} transaction
*/
function Transaction(transaction) {
this.command = 'tx';
this.transaction = transaction;
}
util.inherits(Transaction, Message);
Transaction.prototype.fromBuffer = function(payload) {
this.transaction = TransactionModel(payload);
return this;
};
Transaction.prototype.getPayload = function() {
return this.transaction.toBuffer();
};
module.exports.Transaction = Message.COMMANDS.tx = Transaction;
/**
* Getblocks Message
*
* @name P2P.Message.GetBlocks
* @param{Array} starts - array of buffers with the starting block hashes
* @param{Buffer} [stop] - hash of the last block
*/
function GetBlocks(starts, stop) {
this.command = 'getblocks';
this.version = PROTOCOL_VERSION;
this.starts = starts || [];
this.stop = stop || BufferUtil.NULL_HASH;
}
util.inherits(GetBlocks, Message);
GetBlocks.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
this.version = parser.readUInt32LE();
var startCount = Math.min(parser.readVarintNum(), 500);
this.starts = [];
for (var i = 0; i < startCount; i++) {
this.starts.push(parser.read(32));
}
this.stop = parser.read(32);
return this;
};
GetBlocks.prototype.getPayload = function() {
var put = new Put();
put.word32le(this.version);
put.varint(this.starts.length);
for (var i = 0; i < this.starts.length; i++) {
if (this.starts[i].length !== 32) {
throw new Error('Invalid hash length');
}
put.put(this.starts[i]);
}
if (this.stop.length !== 32) {
throw new Error('Invalid hash length');
}
put.put(this.stop);
return put.buffer();
};
module.exports.GetBlocks = Message.COMMANDS.getblocks = GetBlocks;
/**
* Getheaders Message
*
* @name P2P.Message.GetHeaders
* @param{Array} starts - array of buffers with the starting block hashes
* @param{Buffer} [stop] - hash of the last block
*/
function GetHeaders(starts, stop) {
this.command = 'getheaders';
this.version = PROTOCOL_VERSION;
this.starts = starts || [];
this.stop = stop || BufferUtil.NULL_HASH;
}
util.inherits(GetHeaders, GetBlocks);
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;
// TODO: Remove this PATCH (yemel)
Buffers.prototype.skip = function (i) {
if (i === 0) return;
if (i === this.length) {
this.buffers = [];
this.length = 0;
return;
}
var pos = this.pos(i);
this.buffers = this.buffers.slice(pos.buf);
this.buffers[0] = new Buffer(this.buffers[0].slice(pos.offset));
this.length -= i;
};

195
lib/peer.js Normal file
View File

@ -0,0 +1,195 @@
'use strict';
var Buffers = require('buffers');
var EventEmitter = require('events').EventEmitter;
var Net = require('net');
var Socks5Client = require('socks5-client');
var util = require('util');
var bitcore = require('bitcore-base');
var Networks = bitcore.Networks;
var Messages = require('./messages');
var MAX_RECEIVE_BUFFER = 10000000;
/**
* A Peer instance represents a remote bitcoin node and allows to communicate
* with it using the standar messages of the bitcoin p2p protocol.
*
* @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);
* });
* peer.connect();
* ```
*
* @param {String} host - IP address of the remote host
* @param {Number} [port] - Port number of the remote host
* @param {Network} [network] - The context for this communication
* @returns {Peer} A new instance of Peer.
* @constructor
*/
function Peer(host, port, network) {
if (!(this instanceof Peer)) {
return new Peer(host, port, network);
}
// overloading stuff
if (port instanceof Object && !network) {
network = port;
port = undefined;
}
this.host = host;
this.status = Peer.STATUS.DISCONNECTED;
this.network = network || Networks.livenet;
this.port = port || this.network.port;
this.dataBuffer = new Buffers();
this.version = 0;
this.bestHeight = 0;
this.subversion = null;
// set message handlers
var self = this;
this.on('verack', function() {
self.status = Peer.STATUS.READY;
self.emit('ready');
});
this.on('version', function(message) {
self.version = message.version;
self.subversion = message.subversion;
self.bestHeight = message.start_height
});
this.on('ping', function(message) {
self._sendPong(message.nonce);
});
}
util.inherits(Peer, EventEmitter);
Peer.STATUS = {
DISCONNECTED: 'disconnected',
CONNECTING: 'connecting',
CONNECTED: 'connected',
READY: 'ready'
};
/**
* Set a socks5 proxy for the connection. Enables the use of the TOR network.
*
* @param {String} host - IP address of the proxy
* @param {Number} port - Port number of the proxy
* @returns {Peer} The same Peer instance.
*/
Peer.prototype.setProxy = function(host, port) {
if (this.status != Peer.STATUS.DISCONNECTED) {
throw Error('Invalid State');
}
this.proxy = {
host: host,
port: port
};
return this;
};
/**
* Init the connection with the remote peer.
*
* @returns {Socket} The same peer instance.
*/
Peer.prototype.connect = function() {
this.socket = this._getSocket();
this.status = Peer.STATUS.CONNECTING;
var self = this;
this.socket.on('connect', function(ev) {
self.status = Peer.STATUS.CONNECTED;
self.emit('connect');
self._sendVersion();
});
this.socket.on('error', self.disconnect.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();
self._readMessage();
});
this.socket.connect(this.port, this.host);
return this;
};
/**
* Disconnects the remote connection.
*
* @returns {Socket} The same peer instance.
*/
Peer.prototype.disconnect = function() {
this.status = Peer.STATUS.DISCONNECTED;
this.socket.destroy();
this.emit('disconnect');
return this;
};
/**
* Send a Message to the remote peer.
*
* @param {Message} message - A message instance
*/
Peer.prototype.sendMessage = function(message) {
this.socket.write(message.serialize(this.network));
};
/**
* Internal function that sends VERSION message to the remote peer.
*/
Peer.prototype._sendVersion = function() {
var message = new Messages.Version();
this.sendMessage(message);
};
/**
* Send a PONG message to the remote peer.
*/
Peer.prototype._sendPong = function(nonce) {
var message = new Messages.Pong(nonce);
this.sendMessage(message);
};
/**
* Internal function that tries to read a message from the data buffer
*/
Peer.prototype._readMessage = function() {
var message = Messages.parseMessage(this.network, this.dataBuffer);
if (message) {
this.emit(message.command, message);
this._readMessage();
}
};
/**
* Internal function that creates a socket using a proxy if neccesary.
*
* @returns {Socket} A Socket instance not yet connected.
*/
Peer.prototype._getSocket = function() {
if (this.proxy) {
return new Socks5Client(this.proxy.host, this.proxy.port);
}
return new Net.Socket();
};
module.exports = Peer;

277
lib/pool.js Normal file
View File

@ -0,0 +1,277 @@
'use strict';
var dns = require('dns');
var EventEmitter = require('events').EventEmitter;
var bitcore = require('bitcore-base');
var Networks = bitcore.Networks;
var sha256 = bitcore.crypto.Hash.sha256;
var Peer = require('./peer');
var util = require('util');
function now() {
return Math.floor(new Date().getTime() / 1000);
}
/**
* A pool is a collection of Peers. A pool will discover peers from DNS seeds, and
* collect information about new peers in the network. When a peer disconnects the pool
* will connect to others that are available to maintain a max number of
* ongoing peer connections. Peer events are relayed to the pool.
*
* @example
* ```javascript
*
* var pool = new Pool(Networks.livenet);
* pool.on('peerinv', function(peer, message) {
* // do something with the inventory announcement
* });
* pool.connect();
* ```
*
* @param {Network|String} network - The network to connect
* @returns {Pool}
* @constructor
*/
function Pool(network) {
var self = this;
this.network = Networks.get(network) || Networks.defaultNetwork;
this.keepalive = false;
this._connectedPeers = {};
this._addrs = [];
this.on('peeraddr', function peerAddrEvent(peer, message) {
var addrs = message.addresses;
var length = addrs.length;
for (var i = 0; i < length; i++) {
var addr = addrs[i];
// In case of an invalid time, assume "5 days ago"
if (addr.time <= 100000000 || addr.time > (now() + 10 * 60)) {
addr.time = now() - 5 * 24 * 60 * 60;
}
this._addAddr(addr);
}
});
this.on('seed', function seedEvent(ips) {
ips.forEach(function(ip) {
self._addAddr({
ip: {
v4: ip
}
});
});
if (self.keepalive) {
self._fillConnections();
}
});
this.on('peerdisconnect', function peerDisconnectEvent(peer, addr) {
self._deprioritizeAddr(addr);
self._removeConnectedPeer(addr);
if (self.keepalive) {
self._fillConnections();
}
});
return this;
}
util.inherits(Pool, EventEmitter);
Pool.MaxConnectedPeers = 8;
Pool.RetrySeconds = 30;
Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'ping', 'addr',
'getaddr', 'verack', 'reject', 'alert', 'headers', 'block',
'tx', 'getblocks', 'getheaders'
];
/**
* Will initiatiate connection to peers, if available peers have been added to
* the pool, it will connect to those, otherwise will use DNS seeds to find
* peers to connect. When a peer disconnects it will add another.
*/
Pool.prototype.connect = function connect() {
this.keepalive = true;
var self = this;
if (self._addrs.length === 0) {
self._addAddrsFromSeeds();
} else {
self._fillConnections();
}
return this;
};
/**
* Will disconnect all peers that are connected.
*/
Pool.prototype.disconnect = function disconnect() {
this.keepalive = false;
for (var i in this._connectedPeers) {
this._connectedPeers[i].disconnect();
}
return this;
};
/**
* @returns {Number} The number of peers currently connected.
*/
Pool.prototype.numberConnected = function numberConnected() {
return Object.keys(this._connectedPeers).length;
};
/**
* Will fill the conneted peers to the maximum amount.
*/
Pool.prototype._fillConnections = function _fillConnections() {
var length = this._addrs.length;
for (var i = 0; i < length; i++) {
if (this.numberConnected() >= Pool.MaxConnectedPeers) {
break;
}
var addr = this._addrs[i];
if (!addr.retryTime || now() > addr.retryTime) {
this._connectPeer(addr);
}
}
return this;
};
/**
* Will remove a peer from the list of connected peers.
* @param {Object} addr - An addr from the list of addrs
*/
Pool.prototype._removeConnectedPeer = function _removeConnectedPeer(addr) {
if (this._connectedPeers[addr.hash].status !== Peer.STATUS.DISCONNECTED) {
this._connectedPeers[addr.hash].disconnect();
} else {
delete this._connectedPeers[addr.hash];
}
return this;
};
/**
* Will connect a peer and add to the list of connected peers.
* @param {Object} addr - An addr from the list of addrs
*/
Pool.prototype._connectPeer = function _connectPeer(addr) {
var self = this;
function addConnectedPeer(addr) {
var port = addr.port || self.network.port;
var ip = addr.ip.v4 || addr.ip.v6;
var peer = new Peer(ip, port, self.network);
peer.on('disconnect', function peerDisconnect() {
self.emit('peerdisconnect', peer, addr);
});
peer.on('ready', function peerReady() {
self.emit('peerready', peer, addr);
});
Pool.PeerEvents.forEach(function addPeerEvents(event) {
peer.on(event, function peerEvent(message) {
self.emit('peer' + event, peer, message);
});
});
peer.connect();
self._connectedPeers[addr.hash] = peer;
}
if (!this._connectedPeers[addr.hash]) {
addConnectedPeer(addr);
}
return this;
};
/**
* Will deprioritize an addr in the list of addrs by moving it to the end
* of the array, and setting a retryTime
* @param {Object} addr - An addr from the list of addrs
*/
Pool.prototype._deprioritizeAddr = function _deprioritizeAddr(addr) {
for (var i = 0; i < this._addrs.length; i++) {
if (this._addrs[i].hash === addr.hash) {
var middle = this._addrs[i];
middle.retryTime = now() + Pool.RetrySeconds;
var beginning = this._addrs.splice(0, i);
var end = this._addrs.splice(i + 1, this._addrs.length);
var combined = beginning.concat(end);
this._addrs = combined.concat([middle]);
}
}
return this;
};
/**
* Will add an addr to the beginning of the addrs array
* @param {Object}
*/
Pool.prototype._addAddr = function _addAddr(addr) {
// make a unique key
addr.hash = sha256(new Buffer(addr.ip.v6 + addr.ip.v4 + addr.port)).toString('hex');
var length = this._addrs.length;
var exists = false;
for (var i = 0; i < length; i++) {
if (this._addrs[i].hash === addr.hash) {
exists = true;
}
}
if (!exists) {
this._addrs.unshift(addr);
}
return this;
};
/**
* Will add addrs to the list of addrs from a DNS seed
* @param {String} seed - A domain name to resolve known peers
* @param {Function} done
*/
Pool.prototype._addAddrsFromSeed = function _addAddrsFromSeed(seed) {
var self = this;
dns.resolve(seed, function(err, ips) {
if (err) {
self.emit('seederror', err);
return;
}
if (!ips || !ips.length) {
self.emit('seederror', new Error('No IPs found from seed lookup.'));
return;
}
// announce to pool
self.emit('seed', ips);
});
return this;
};
/**
* Will add addrs to the list of addrs from network DNS seeds
* @param {Function} done
*/
Pool.prototype._addAddrsFromSeeds = function _addAddrsFromSeeds() {
var self = this;
var seeds = this.network.dnsSeeds;
seeds.forEach(function(seed) {
self._addAddrsFromSeed(seed);
});
return this;
};
/**
* @returns {String} A string formatted for the console
*/
Pool.prototype.inspect = function inspect() {
return '<Pool network: ' +
this.network + ', connected: ' +
this.numberConnected() + ', available: ' +
this._addrs.length + '>';
};
module.exports = Pool;

54
package.json Normal file
View File

@ -0,0 +1,54 @@
{
"name": "bitcore-p2p",
"version": "0.8.5",
"description": "Interface to the bitcoin P2P network for bitcore",
"author": "BitPay <dev@bitpay.com>",
"main": "index.js",
"scripts": {
"lint": "gulp lint",
"test": "gulp test",
"coverage": "gulp coverage",
"build": "gulp",
"postinstall": "node ./lib/errors/build.js"
},
"contributors": [
{
"name": "Esteban Ordano",
"email": "eordano@gmail.com"
}
],
"keywords": [
"bitcoin",
"bitcore"
],
"repository": {
"type": "git",
"url": "https://github.com/bitpay/bitcore-p2p.git"
},
"dependencies": {
"bitcore-base": "~0.8.5"
},
"devDependencies": {
"browserify": "~6.3.3",
"chai": "~1.10.0",
"gulp": "^3.8.10",
"gulp-bump": "^0.1.11",
"gulp-coveralls": "^0.1.3",
"gulp-git": "^0.5.5",
"gulp-jshint": "^1.9.0",
"gulp-mocha": "^2.0.0",
"gulp-rename": "^1.2.0",
"gulp-shell": "^0.2.10",
"gulp-uglify": "^1.0.2",
"gulp-util": "=3.0.1",
"istanbul": "^0.3.5",
"karma": "^0.12.28",
"karma-firefox-launcher": "^0.1.3",
"karma-mocha": "^0.1.9",
"mocha": "~2.0.1",
"plato": "^1.3.0",
"run-sequence": "^1.0.2",
"sinon": "^1.12.2"
},
"license": "MIT"
}

22
test/data/messages.json Normal file

File diff suppressed because one or more lines are too long

18
test/index.html Normal file
View File

@ -0,0 +1,18 @@
<!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="../browser/tests.js"></script>
<script>
mocha.run();
</script>
</body>
</html>

354
test/messages.js Normal file
View File

@ -0,0 +1,354 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var bitcore = require('bitcore-base');
var Data = require('./data/messages');
var P2P = require('../');
var Messages = P2P.Messages;
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);
message.subversion.should.equal('/BitcoinX:0.1/');
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');
new Messages.Version().fromBuffer(payload);
});
});
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 = 'f9beb4d91d0100000100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000';
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
test/mocha.opts Normal file
View File

@ -0,0 +1 @@
--recursive

76
test/peer.js Normal file
View File

@ -0,0 +1,76 @@
'use strict';
var chai = require('chai');
var Net = require('net');
var Socks5Client = require('socks5-client');
/* jshint unused: false */
var should = chai.should();
var expect = chai.expect;
var bitcore = require('bitcore-base');
var P2P = require('../');
var Peer = P2P.Peer;
var Networks = bitcore.Networks;
describe('Peer', function() {
it('should be able to create instance', function() {
var peer = new Peer('localhost');
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.livenet);
peer.port.should.equal(Networks.livenet.port);
});
it('should be able to create instance setting a port', function() {
var peer = new Peer('localhost', 8111);
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.livenet);
peer.port.should.equal(8111);
});
it('should be able to create instance setting a network', function() {
var peer = new Peer('localhost', Networks.testnet);
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.testnet);
peer.port.should.equal(Networks.testnet.port);
});
it('should be able to create instance setting port and network', function() {
var peer = new Peer('localhost', 8111, Networks.testnet);
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.testnet);
peer.port.should.equal(8111);
});
it('should support creating instance without new', function() {
var peer = Peer('localhost', 8111, Networks.testnet);
peer.host.should.equal('localhost');
peer.network.should.equal(Networks.testnet);
peer.port.should.equal(8111);
});
if (typeof(window) === 'undefined'){
// Node.js Tests
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);
});
}
});

91
test/pool.js Normal file
View File

@ -0,0 +1,91 @@
'use strict';
var chai = require('chai');
/* jshint unused: false */
var should = chai.should();
var expect = chai.expect;
var dns = require('dns');
var sinon = require('sinon');
var bitcore = require('bitcore-base');
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() {
it('should be able to create instance', function() {
var pool = new Pool();
pool.network.should.equal(Networks.livenet);
});
it('should be able to create instance setting the network', function() {
var pool = new Peer(Networks.testnet);
pool.network.should.equal(Networks.livenet);
});
it('should discover peers via dns', function() {
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');
});
// 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();
});
});