Merge pull request #952 from eordano/modularize/p2p

Drop networking code in favor of bitcore-p2p
This commit is contained in:
Manuel Aráoz 2015-01-13 20:19:56 -03:00
commit a24dc19179
10 changed files with 1 additions and 1756 deletions

View File

@ -1,105 +0,0 @@
title: Peer
description: The Peer class provides a simple interface for connecting to a node in the bitcoin network.
---
# Peer
## Description
Represents a node from the p2p bitcoin network. The Peer class supports connecting directly to other nodes or through a socks5 proxy like Tor.
## Creating a peer
The code to create a new peer looks like this:
```javascript
var bitcore = require('bitcore');
var Peer = bitcore.transport.Peer;
// default port
var livenetPeer = new Peer('5.9.85.34');
var testnetPeer = new Peer('5.9.85.34', bitcore.testnet);
// custom port
var livenetPeer = new Peer('5.9.85.34', 8334);
var testnetPeer = new Peer('5.9.85.34', 18334, bitcore.testnet);
// use sock5 proxy (Tor)
var peer = new Peer('5.9.85.34').setProxy('localhost', 9050);
```
## States
A peer instance is always in one of the following states:
* `disconnected`: No connection with the remote node.
* `connecting`: While establishing the connection.
* `connected`: Exchanging version packages.
* `ready`: Connection ready for sending and receiving messages.
You can subscribe to the change of those states as follows:
```javascript
var bitcore = require('bitcore');
var Peer = bitcore.transport.Peer;
var peer = new Peer('5.9.85.34');
peer.on('ready', function() {
// peer info
console.log(peer.version, peer.subversion, peer.bestHeight);
});
peer.on('disconnect', function() {
console.log('connection closed');
});
peer.connect();
```
## Handle messages
Once connected, a peer instance can send and receive messages. Every time a message arrives it's emitted as a new event. Let's see an example of this:
```javascript
var bitcore = require('bitcore');
var peer = new bitcore.transport.Peer('5.9.85.34');
// handle events
peer.on('inv', function(message) {
// message.inventory[]
});
peer.on('tx', function(message) {
// message.transaction
});
peer.on('addr', function(message) {
// message.addresses[]
});
peer.connect();
```
## Sending messages
In order to send messages the Peer class offers the `sendMessage(message)` method, which receives an instance of a message. All supported messages can be found on the `bitcore.transport.Messages` module. For more information about messages refer to the [protocol specification](https://en.bitcoin.it/wiki/Protocol_specification).
An example for requesting other connected nodes to a peers looks like this:
```javascript
var bitcore = require('bitcore');
var peer = new bitcore.transport.Peer('5.9.85.34');
peer.on('ready', function() {
var message = new bitcore.transport.Messages.GetAddresses();
peer.sendMessage(message);
});
peer.on('addr', function(message) {
message.addresses.forEach(function(address) {
// do something
});
});
peer.connect();
```

View File

@ -1,31 +0,0 @@
title: Pool
description: A simple interface to create and maintain a set of connections to bitcoin nodes.
---
# Pool
A pool maintains a connection of [Peers](peer.md). A pool will discover peers via DNS seeds, as well as when peer addresses are announced through the network.
The quickest way to get connected is to run the following:
```javascript
var bitcore = require('bitcore');
var Pool = bitcore.transport.Pool;
var Networks = bitcore.Networks;
var pool = new Pool(Networks.livenet);
// connect to the network
pool.connect();
// attach peer events
pool.on('peerinv', function(peer, message) {
// a new peer message has arrived
});
// will disconnect all peers
pool.disconnect()
```
For more information about Peer events please read the [Peer](peer.md) documentation. Peer events are relayed to the pool, a peer event `inv` in the pool would be `peerinv`. When a peer is disconnected the pool will try to connect to the list of known addresses to maintain connection.

View File

@ -2,8 +2,5 @@
* @namespace Transport
*/
module.exports = {
explorers: require('./explorers'),
Messages: require('./messages'),
Peer: require('./peer'),
Pool: require('./pool')
explorers: require('./explorers')
};

View File

@ -1,613 +0,0 @@
'use strict';
/**
* @namespace Transport.Message
*/
/* jshint curly: false */
var Buffers = require('buffers');
var Put = require('bufferput');
var util = require('util');
var BlockHeaderModel = require('../blockheader');
var BlockModel = require('../block');
var BufferReader = require('../encoding/bufferreader');
var BufferUtil = require('../util/buffer');
var Hash = require('../crypto/hash');
var Random = require('../crypto/random');
var TransactionModel = require('../transaction');
var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8);
var PROTOCOL_VERSION = 70000;
/**
* Static helper for consuming a data buffer until the next message.
*
* @name Transport.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 Transport.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 Transport.Message
*/
function Message() {}
/**
* @value
* @name Transport.Message.COMMANDS
*/
Message.COMMANDS = {};
/**
* Look up a message type by command name and instantiate the correct Message
* @name Transport.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();
};
module.exports.Message = Message;
/**
* Version Message
*
* @name Transport.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 Transport.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 Transport.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 Transport.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 Transport.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 Transport.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 Transport.Message.GetAddresses
*/
function GetAddresses() {
this.command = 'getaddr';
}
util.inherits(GetAddresses, Message);
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;
/**
* Verack Message
*
* @name Transport.Message.VerAck
*/
function VerAck() {
this.command = 'verack';
}
util.inherits(VerAck, Message);
module.exports.VerAck = Message.COMMANDS.verack = VerAck;
/**
* Reject Message
*
* @name Transport.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 Transport.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 Transport.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 Transport.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 Transport.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 Transport.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 Transport.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;
/**
* GetMempool Message
*
* @name Transport.Message.GetMempool
*/
function GetMempool() {
this.command = 'mempool';
}
util.inherits(GetMempool, Message);
module.exports.GetMempool = Message.COMMANDS.mempool = GetMempool;
// 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;
};

View File

@ -1,194 +0,0 @@
'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 Networks = require('../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;

View File

@ -1,275 +0,0 @@
'use strict';
var dns = require('dns');
var EventEmitter = require('events').EventEmitter;
var Networks = require('../networks');
var sha256 = require('../crypto/hash').sha256;
var Peer = require('./peer');
var util = require('util');
function now() {
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;

View File

@ -76,8 +76,6 @@
"bn.js": "=0.16.1",
"browser-request": "^0.3.3",
"bs58": "=2.0.0",
"bufferput": "^0.1.2",
"buffers": "^0.1.1",
"elliptic": "=0.16.0",
"hash.js": "=0.3.2",
"inherits": "=2.0.1",

View File

@ -1,353 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var bitcore = require('../..');
var Data = require('../data/messages');
var Messages = bitcore.transport.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);
});
});
});

View File

@ -1,75 +0,0 @@
'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('../..');
var Peer = bitcore.transport.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);
});
}
});

View File

@ -1,104 +0,0 @@
'use strict';
if (typeof(window) === 'undefined'){
// Node.js Tests
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('../..');
var Peer = bitcore.transport.Peer;
var MessagesData = require('../data/messages');
var Messages = bitcore.transport.Messages;
var Pool = bitcore.transport.Pool;
var Networks = bitcore.Networks;
describe('Pool', function() {
it('should be able to create instance', function() {
var pool = new Pool();
should.exist(pool.network);
expect(pool.network).to.satisfy(function(network){
if (network === Networks.testnet || network === Networks.livenet) {
return true;
}
return false;
});
});
it('should 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();
});
});
}