Compare commits

..

No commits in common. "master" and "yemel-readme" have entirely different histories.

58 changed files with 1468 additions and 6245 deletions

View File

@ -1,7 +1,6 @@
language: node_js
node_js:
- '4'
- '6'
- '0.10'
before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

View File

@ -1,13 +1,11 @@
<img src="http://bitcore.io/css/images/bitcore-p2p.svg" alt="bitcore payment protocol" height="35" width="102">
Bitcore P2P
P2P Networking capabilities for bitcore
=======
[![NPM Package](https://img.shields.io/npm/v/bitcore-p2p.svg?style=flat-square)](https://www.npmjs.org/package/bitcore-p2p)
[![Build Status](https://img.shields.io/travis/bitpay/bitcore-p2p.svg?branch=master&style=flat-square)](https://travis-ci.org/bitpay/bitcore-p2p)
[![Coverage Status](https://img.shields.io/coveralls/bitpay/bitcore-p2p.svg?style=flat-square)](https://coveralls.io/r/bitpay/bitcore-p2p?branch=master)
`bitcore-p2p` adds [Bitcoin protocol](https://en.bitcoin.it/wiki/Protocol_documentation) support for Bitcore.
bitcore-p2p adds support for connecting to the bitcoin p2p network on node.
See [the main bitcore repo](https://github.com/bitpay/bitcore) for more information.
@ -16,12 +14,15 @@ See [the main bitcore repo](https://github.com/bitpay/bitcore) for more informat
```sh
npm install bitcore-p2p
```
In order to connect to the Bitcoin network, you'll need to know the IP address of at least one node of the network, or use [Pool](/docs/pool.md) to discover peers using a DNS seed.
```sh
bower install bitcore-p2p
```
In order to connect to the bitcore network, you'll need to know the IP address of at least one node of the network. You can do that by using the known DNS servers. Then, you can connect to it:
```javascript
var Peer = require('bitcore-p2p').Peer;
var peer = new Peer({host: '127.0.0.1'});
var peer = new Peer('0.0.0.0');
peer.on('ready', function() {
// peer info
@ -49,10 +50,11 @@ Take a look at the [bitcore guide](http://bitcore.io/guide/peer.html) on the usa
## Contributing
See [CONTRIBUTING.md](https://github.com/bitpay/bitcore/blob/master/CONTRIBUTING.md) on the main bitcore repo for information about how to contribute.
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.

42
bower.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "bitcore-p2p",
"main": "bitcore-p2p.min.js",
"version": "0.8.7",
"homepage": "http://bitcore.io",
"authors": [
"BitPay, Inc.",
{
"name": "Yemel Jardi",
"email": "yemel@bitpay.com"
},
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
},
{
"name": "Ryan X. Charles",
"email": "ryan@bitpay.com"
},
],
"description": "Interface to the bitcoin P2P network for bitcore",
"moduleType": [
"globals"
],
"keywords": [
"bitcoin",
"bitcore",
"btc",
"satoshi"
],
"license": "MIT",
"ignore": [
"**/.*",
"CONTRIBUTING.md",
"gulpfile.js",
"lib",
"index.js",
"karma.conf.js",
"npm-shrinkwrap.json",
"test"
]
}

View File

@ -1,20 +1,32 @@
---
title: Peer-to-Peer Networking
description: Peer-to-Peer Networking Capabilities for Bitcore
---
# Peer-to-Peer
The `bitcore-p2p` module provides peer-to-peer networking capabilities for [Bitcore](https://github.com/bitpay/bitcore), and includes [Peer](peer.md) and [Pool](pool.md) classes. A [Message](messages.md) class is also exposed, in addition to [several types of messages](messages.md). Pool will maintain connection to several peers, Peers represents a node in the bitcoin network, and Message represents data sent to and from a Peer. For detailed technical information about the bitcoin protocol, please visit the [Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) on the Bitcoin Wiki.
## Description
The `bitcore-p2p` module provides peer-to-peer networking capabilites for [Bitcore](https://github.com/bitpay/bitcore), and includes [Peer](peer.md) and [Pool](pool.md) classes. A [Message](messages.md) class is also exposed, in addition to [several types of messages](messages.md). Pool will maintain connection to several peers, Peers represents a node in the bitcoin network, and Message represents data sent to and from a Peer. For detailed technical information about the bitcoin protocol, please visit the [Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) on the Bitcoin Wiki.
## Installation
Peer-to-peer is implemented as a separate module.
Peer-to-peer is implemented as a seperate module.
For node projects:
```bash
npm install bitcore-p2p --save
```
For client-side projects:
```bash
bower install bitcore-p2p --save
```
## Quick Start
```javascript
var Peer = require('bitcore-p2p').Peer;
var peer = new Peer({host: '5.9.85.34'});
var peer = new Peer('5.9.85.34');
// handle events
peer.on('inv', function(message) {
@ -22,4 +34,5 @@ peer.on('inv', function(message) {
});
peer.connect();
```
```

View File

@ -1,85 +1,50 @@
title: Messages
description: A superclass for the messages of the bitcoin network
---
# Messages
The bitcoin protocol specifies a set of [messages](https://en.bitcoin.it/wiki/Protocol_specification) that can be sent from peer to peer. `bitcore-p2p` provides support for some of these messages.
To create a message, you can use any of the message constructors, here is a simple example:
```javascript
var messages = new Messages();
var message = messages.Ping();
```
There are also several convenient helpers for inventory based messages:
```javascript
message = messages.GetData.forTransaction(txHash);
message = messages.GetData.forBlock(blockHash);
message = messages.Inventory.forTransaction(txHash);
```
As well as sending "tx" and "block" messages with Bitcore instances:
```javascript
message = messages.Block(block);
message = messages.Transaction(transaction);
```
Note: A list of further messages is available below.
For advanced usage, you can also customize which constructor is used for Block and Transaction messages by passing it as an argument to Messages, for example:
```javascript
var messages = new Messages({Block: MyBlock, Transaction: MyTransaction});
```
And additionally a custom network:
```javascript
var messages = new Messages({network: Networks.testnet});
```
## List of Messages
### Version
The version message (`ver`) is used on connection creation, to advertise the type of node. The remote node will respond with its version, and no communication is possible until both peers have exchanged their versions. By default, bitcore advertises itself as named `bitcore` with the current version of the `bitcore-p2p` package.
### VerAck
Finishes the connection handshake started by the `ver` message.
### Inventory
From the bitcoin protocol spec: "Allows a node to advertise its knowledge of one or more objects. It can be received unsolicited, or in reply to getblocks.".
### GetData
From the bitcoin protocol spec: `getdata` is used in response to `inv`, to retrieve the content of a specific object, and is usually sent after receiving an `inv` packet, after filtering known elements. It can be used to retrieve transactions, but only if they are in the memory pool or relay set - arbitrary access to transactions in the chain is not allowed to avoid having clients start to depend on nodes having full transaction indexes (which modern nodes do not).
GetData inherits from Inventory, as they both have the same structure.
### NotFound
notfound is a response to a getdata, sent if any requested data items could not be relayed, for example, because the requested transaction was not in the memory pool or relay set. Contains inventory information specifying which items were not found.
### Ping
Sent to another peer mainly to check the connection is still alive.
### Pong
Sent in response to a `ping` message.
### Address and GetAddresses
Provides information on known nodes of the network. `GetAddresses` is used to query another peer for known addresses.
### GetHeaders and Headers
`getheaders` allows a peer to query another about blockheaders. `headers` is sent in response to a `getheaders` message, containing information about block headers.
### GetBlocks and Block
Same as `getheaders` and `headers`, but the response comes one block at the time.
### Transaction
Message that contains a transaction.
## Custom Messages
It is possible to extend the default peer to peer messages and add custom ones. First you will need to create a message which resembles the default messages in `lib/messages/commands`.
Then to add the custom message:
```javascript
messages.add('custom', 'Custom', CustomMessage);
var customMessage = messages.Custom('argument');
```

View File

@ -1,37 +1,46 @@
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 Peer = require('bitcore-p2p').Peer;
// default port
var livenetPeer = new Peer({host: '5.9.85.34'});
var testnetPeer = new Peer({host: '5.9.85.34', network: Networks.testnet});
var livenetPeer = new Peer('5.9.85.34');
var testnetPeer = new Peer('5.9.85.34', bitcore.testnet);
// custom port
var livenetPeer = new Peer({host: '5.9.85.34', port: 8334});
var testnetPeer = new Peer({host: '5.9.85.34', port: 18334, network: Networks.testnet});
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({host: '5.9.85.34'}).setProxy('localhost', 9050);
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.
* `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 Peer = require('bitcore-p2p').Peer;
var peer = new Peer({host: '5.9.85.34'});
var peer = new Peer('5.9.85.34');
peer.on('ready', function() {
// peer info
@ -46,11 +55,12 @@ 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 Peer = require('bitcore-p2p').Peer;
var peer = new Peer({host: '5.9.85.34'});
var peer = new Peer('5.9.85.34');
// handle events
peer.on('inv', function(message) {
@ -69,6 +79,7 @@ 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 in the `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:
@ -77,7 +88,7 @@ An example for requesting other connected nodes to a peers looks like this:
var p2p = require('bitcore-p2p')
var Peer = p2p.Peer;
var Messages = p2p.Messages;
var peer = new Peer({host: '5.9.85.34'});
var peer = new Peer('5.9.85.34');
peer.on('ready', function() {
var message = new Messages.GetAddresses();

View File

@ -1,4 +1,8 @@
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:
@ -6,9 +10,9 @@ The quickest way to get connected is to run the following:
```javascript
var Pool = require('bitcore-p2p').Pool;
var Networks = require('bitcore-lib').Networks;
var Networks = require('bitcore').Networks;
var pool = new Pool({network: Networks.livenet});
var pool = new Pool(Networks.livenet);
// connect to the network
pool.connect();
@ -20,37 +24,7 @@ pool.on('peerinv', function(peer, message) {
// 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.
## Trusted Peers
By default, peers will be added via DNS discovery and as peers are announced in the network. Configuration options can be included to connect only to specific trusted peers:
```javascript
var pool = new Pool({
network: Networks.livenet, // the network object
dnsSeed: false, // prevent seeding with DNS discovered known peers upon connecting
listenAddr: false, // prevent new peers being added from addr messages
addrs: [ // initial peers to connect to
{
ip: {
v4: '127.0.0.1'
}
}
]
});
pool.connect();
```
## Listening for Peers
It's also possible to listen to incoming socket connections to add peers to the pool. To enable this capability, you can do the following:
```javascript
var pool = new Pool({network: Networks.livenet});
pool.listen();
```
When there are incoming connections the peer will be added to the pool.

View File

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

View File

@ -1,221 +0,0 @@
'use strict';
var chai = require('chai');
/* jshint unused: false */
var should = chai.should();
var sinon = require('sinon');
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var Random = bitcore.crypto.Random;
var BN = bitcore.crypto.BN;
var BufferUtil = bitcore.util.buffer;
var p2p = require('../');
var Peer = p2p.Peer;
var Pool = p2p.Pool;
var Networks = bitcore.Networks;
var Messages = p2p.Messages;
var Inventory = p2p.Inventory;
var Block = bitcore.Block;
var Transaction = bitcore.Transaction;
// config
var network = process.env.NETWORK === 'testnet' ? Networks.testnet : Networks.livenet;
var messages = new Messages({
network: network
});
var blockHash = {
'livenet': '000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9',
'testnet': '0000000058cc069d964711cd25083c0a709f4df2b34c8ff9302ce71fe5b45786'
};
var stopBlock = {
'livenet': '00000000000000000b539ef570128acb953af3dbcfc19dd8e6066949672311a1',
'testnet': '00000000d0bc4271bcefaa7eb25000e345910ba16b91eb375cd944b68624de9f'
};
var txHash = {
'livenet': '22231e8219a0617a0ded618b5dc713fdf9b0db8ebd5bb3322d3011a703119d3b',
'testnet': '22231e8219a0617a0ded618b5dc713fdf9b0db8ebd5bb3322d3011a703119d3b'
};
// These tests require a running bitcoind instance
describe('Integration with ' + network.name + ' bitcoind', function() {
this.timeout(15000);
var opts = {
host: 'localhost',
network: network.name
};
it('handshakes', function(cb) {
var peer = new Peer(opts);
peer.once('version', function(m) {
m.version.should.be.above(70000);
m.services.toString().should.equal('1');
Math.abs(new Date() - m.timestamp).should.be.below(10000); // less than 10 seconds of time difference
m.nonce.length.should.equal(8);
m.startHeight.should.be.above(300000);
cb();
});
peer.once('verack', function(m) {
should.exist(m);
m.command.should.equal('verack');
});
peer.connect();
});
var connect = function(cb) {
var peer = new Peer(opts);
peer.once('ready', function() {
cb(peer);
});
peer.once('error', function(err) {
should.not.exist(err);
});
peer.connect();
};
it('connects', function(cb) {
connect(function(peer) {
peer.version.should.be.above(70000);
_.isString(peer.subversion).should.equal(true);
_.isNumber(peer.bestHeight).should.equal(true);
cb();
});
});
it('handles inv', function(cb) {
// assumes there will be at least one transaction/block
// in the next few seconds
connect(function(peer) {
peer.once('inv', function(message) {
message.inventory[0].hash.length.should.equal(32);
cb();
});
});
});
it('handles addr', function(cb) {
connect(function(peer) {
peer.once('addr', function(message) {
message.addresses.forEach(function(address) {
(address.time instanceof Date).should.equal(true);
should.exist(address.ip);
(address.services instanceof BN).should.equal(true);
});
cb();
});
var message = messages.GetAddr();
peer.sendMessage(message);
});
});
it('requests inv detailed info', function(cb) {
connect(function(peer) {
peer.once('block', function(message) {
should.exist(message.block);
cb();
});
peer.once('tx', function(message) {
should.exist(message.transaction);
cb();
});
peer.once('inv', function(message) {
var get = messages.GetData(message.inventory);
peer.sendMessage(get);
});
});
});
it('sends tx inv and receives getdata for that tx', function(cb) {
connect(function(peer) {
var type = Inventory.TYPE.TX;
var inv = [{
type: type,
hash: new Buffer(Random.getRandomBuffer(32)) // needs to be random for repeatability
}];
peer.once('getdata', function(message) {
message.inventory[0].should.deep.equal(inv[0]);
cb();
});
var message = messages.Inventory(inv);
message.inventory[0].hash.length.should.equal(32);
peer.sendMessage(message);
});
});
it('requests block data', function(cb) {
connect(function(peer) {
peer.once('block', function(message) {
(message.block instanceof Block).should.equal(true);
cb();
});
var message = messages.GetData.forBlock(blockHash[network.name]);
peer.sendMessage(message);
});
});
var fakeHash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a';
it('handles request tx data not found', function(cb) {
connect(function(peer) {
var expected = messages.NotFound.forTransaction(fakeHash);
peer.once('notfound', function(message) {
message.command.should.equal('notfound');
message.inventory[0].type.should.equal(Inventory.TYPE.TX);
var expectedHash = expected.inventory[0].hash.toString('hex');
message.inventory[0].hash.toString('hex').should.equal(expectedHash);
cb();
});
var message = messages.GetData.forTransaction(fakeHash);
peer.sendMessage(message);
});
});
var from = [blockHash[network.name]];
var stop = stopBlock[network.name];
it('gets headers', function(cb) {
connect(function(peer) {
peer.once('headers', function(message) {
message.command.should.equal('headers');
message.headers.length.should.equal(3);
cb();
});
var message = messages.GetHeaders({
starts: from,
stop: stop
});
peer.sendMessage(message);
});
});
it('gets blocks', function(cb) {
connect(function(peer) {
peer.once('inv', function(message) {
message.command.should.equal('inv');
if (message.inventory.length === 2) {
message.inventory[0].type.should.equal(Inventory.TYPE.BLOCK);
message.inventory[1].type.should.equal(Inventory.TYPE.BLOCK);
cb();
}
});
var message = messages.GetBlocks({
starts: from,
stop: stop
});
peer.sendMessage(message);
});
});
var testInvGetData = function(expected, message, cb) {
connect(function(peer) {
peer.once('getdata', function(message) {
message.command.should.equal('getdata');
message.inventory[0].type.should.equal(expected.inventory[0].type);
var expectedHash = expected.inventory[0].hash.toString('hex');
message.inventory[0].hash.toString('hex').should.equal(expectedHash);
cb();
});
peer.sendMessage(message);
});
};
it('sends block inv and receives getdata', function(cb) {
var randomHash = new Buffer(Random.getRandomBuffer(32)); // slow buffer
var expected = messages.GetData.forBlock(randomHash);
var message = messages.Inventory.forBlock(randomHash);
testInvGetData(expected, message, cb);
});
it('sends tx inv and receives getdata', function(cb) {
var randomHash = new Buffer(Random.getRandomBuffer(32)); // slow buffer
var expected = messages.GetData.forTransaction(randomHash);
var message = messages.Inventory.forTransaction(randomHash);
testInvGetData(expected, message, cb);
});
});

View File

@ -1,42 +0,0 @@
'use strict';
var bitcore = require('flocore-lib');
var BloomFilter = require('bloom-filter');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* A constructor for Bloom Filters
* @see https://github.com/bitpay/bloom-filter
* @param {Buffer} - payload
*/
BloomFilter.fromBuffer = function fromBuffer(payload) {
var obj = {};
var parser = new BufferReader(payload);
var length = parser.readVarintNum();
obj.vData = [];
for(var i = 0; i < length; i++) {
obj.vData.push(parser.readUInt8());
}
obj.nHashFuncs = parser.readUInt32LE();
obj.nTweak = parser.readUInt32LE();
obj.nFlags = parser.readUInt8();
return new BloomFilter(obj);
};
/**
* @returns {Buffer}
*/
BloomFilter.prototype.toBuffer = function toBuffer() {
var bw = new BufferWriter();
bw.writeVarintNum(this.vData.length);
for(var i = 0; i < this.vData.length; i++) {
bw.writeUInt8(this.vData[i]);
}
bw.writeUInt32LE(this.nHashFuncs);
bw.writeUInt32LE(this.nTweak);
bw.writeUInt8(this.nFlags);
return bw.concat();
};
module.exports = BloomFilter;

View File

@ -1,23 +0,0 @@
'use strict';
var Buffers = require('buffers');
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;
};
module.exports = Buffers;

View File

@ -5,4 +5,4 @@ var spec = {
message: 'Internal Error on bitcore-p2p Module {0}'
};
module.exports = require('flocore-lib').errors.extend(spec);
module.exports = require('bitcore').errors.extend(spec);

View File

@ -1,10 +1,7 @@
/**
* @namespace P2P
*/
module.exports = {
Inventory: require('./inventory'),
BloomFilter: require('./bloomfilter'),
Messages: require('./messages'),
Peer: require('./peer'),
Pool: require('./pool')

View File

@ -1,121 +0,0 @@
'use strict';
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var BufferUtil = bitcore.util.buffer;
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* A constructor for inventory related Bitcoin messages such as
* "getdata", "inv" and "notfound".
* @param {Object} obj
* @param {Number} obj.type - Inventory.TYPE
* @param {Buffer} obj.hash - The hash for the inventory
* @constructor
*/
function Inventory(obj) {
this.type = obj.type;
if (!BufferUtil.isBuffer(obj.hash)) {
throw new TypeError('Unexpected hash, expected to be a buffer');
}
this.hash = obj.hash;
}
/**
* A convenience constructor for Inventory.
* @param {Number} type - Inventory.TYPE
* @param {Buffer|String} hash - The hash for the inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forItem = function(type, hash) {
$.checkArgument(hash);
if (_.isString(hash)) {
hash = new Buffer(hash, 'hex');
hash = BufferUtil.reverse(hash);
}
return new Inventory({type: type, hash: hash});
};
/**
* A convenience constructor for Inventory for block inventory types.
* @param {Buffer|String} hash - The hash for the block inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forBlock = function(hash) {
return Inventory.forItem(Inventory.TYPE.BLOCK, hash);
};
/**
* A convenience constructor for Inventory for filtered/merkle block inventory types.
* @param {Buffer|String} hash - The hash for the filtered block inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forFilteredBlock = function(hash) {
return Inventory.forItem(Inventory.TYPE.FILTERED_BLOCK, hash);
};
/**
* A convenience constructor for Inventory for transaction inventory types.
* @param {Buffer|String} hash - The hash for the transaction inventory
* @returns {Inventory} - A new instance of Inventory
*/
Inventory.forTransaction = function(hash) {
return Inventory.forItem(Inventory.TYPE.TX, hash);
};
/**
* @returns {Buffer} - Serialized inventory
*/
Inventory.prototype.toBuffer = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.type);
bw.write(this.hash);
return bw.concat();
};
/**
* @param {BufferWriter} bw - An instance of BufferWriter
*/
Inventory.prototype.toBufferWriter = function(bw) {
bw.writeUInt32LE(this.type);
bw.write(this.hash);
return bw;
};
/**
* @param {Buffer} payload - Serialized buffer of the inventory
*/
Inventory.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
var obj = {};
obj.type = parser.readUInt32LE();
obj.hash = parser.read(32);
return new Inventory(obj);
};
/**
* @param {BufferWriter} br - An instance of BufferWriter
*/
Inventory.fromBufferReader = function(br) {
var obj = {};
obj.type = br.readUInt32LE();
obj.hash = br.read(32);
return new Inventory(obj);
};
// https://en.bitcoin.it/wiki/Protocol_specification#Inventory_Vectors
Inventory.TYPE = {};
Inventory.TYPE.ERROR = 0;
Inventory.TYPE.TX = 1;
Inventory.TYPE.BLOCK = 2;
Inventory.TYPE.FILTERED_BLOCK = 3;
Inventory.TYPE_NAME = [
'ERROR',
'TX',
'BLOCK',
'FILTERED_BLOCK'
];
module.exports = Inventory;

713
lib/messages.js Normal file
View File

@ -0,0 +1,713 @@
'use strict';
/**
* @namespace P2P.Message
*/
/* jshint curly: false */
var Buffers = require('buffers');
var Put = require('bufferput');
var util = require('util');
var bitcore = require('bitcore');
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 another message is found.
* @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();
};
module.exports.Message = Message;
/**
* The version message(`ver`) is used on connection creation, to advertise
* the type of node.The remote node will respond with its version, and no
* communication is possible until both peers have exchanged their versions.
* By default, bitcore advertises itself as named `bitcore:0.8`.
*
* @name P2P.Message.Version
* @param{string} subversion - version of the client
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Version(subversion, nonce) {
var packageInfo = require('../package.json');
this.command = 'version';
this.version = PROTOCOL_VERSION;
this.subversion = subversion || '/bitcore:' + packageInfo.version + '/';
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Version, Message);
Version.prototype.fromBuffer = function(payload) {
var parser = new BufferReader(payload);
/**
* @type {number}
* @desc The version of the bitcoin protocol
*/
this.version = parser.readUInt32LE();
/**
* @type {BN}
* @desc A mapbit with service bits: what features are supported by the peer
*/
this.services = parser.readUInt64LEBN();
/**
* @type {BN}
* @desc The time this message was sent
*/
this.timestamp = parser.readUInt64LEBN();
/**
* @type {Buffer}
* @desc IPv4/6 address of the interface used to connect to this peer
*/
this.addr_me = parser.read(26);
/**
* @type {Buffer}
* @desc IPv4/6 address of the peer
*/
this.addr_you = parser.read(26);
/**
* @type {Buffer}
* @desc A random number
*/
this.nonce = parser.read(8);
/**
* @desc A random number
* @type {string}
*/
this.subversion = parser.readVarintBuf().toString();
/**
* @desc The height of the last block accepted in the blockchain by this peer
* @type {number}
*/
this.start_height = parser.readUInt32LE();
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;
/**
* From the bitcoin protocol spec: "Allows a node to advertise its knowledge of
* one or more objects. It can be received unsolicited, or in reply to
* getblocks.".
*
* @name P2P.Message.Inventory
* @param{Array} inventory - reported elements
*/
function Inventory(inventory) {
this.command = 'inv';
/**
* @name P2P.Message.Inventory.inventory
* @desc An array of objects with `{type: int, hash: buffer}` signature
* @type {Array.Buffer}
*/
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 is used in response to inv, to retrieve the content of a specific
* object, and is usually sent after receiving an inv packet, after filtering
* known elements. It can be used to retrieve transactions, but only if they
* are in the memory pool or relay set - arbitrary access to transactions in the
* chain is not allowed to avoid having clients start to depend on nodes having
* full transaction indexes (which modern nodes do not).
*
* (taken from bitcoin's protocol spec)
*
* @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;
/**
* Sent to another peer mainly to check the connection is still alive.
*
* @name P2P.Message.Ping
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Ping(nonce) {
this.command = 'ping';
/**
* @desc A random number that should be returned by the peer in a pong message
* @type {number}
*/
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;
/**
* Sent in response to a Ping message
*
* @name P2P.Message.Pong
* @param{Buffer} nonce - a random 8 bytes buffer
*/
function Pong(nonce) {
this.command = 'pong';
/**
* @desc A random number that must match the one sent in the corresponding `ping` message
* @type {number}
*/
this.nonce = nonce || CONNECTION_NONCE;
}
util.inherits(Pong, Ping);
module.exports.Pong = Message.COMMANDS.pong = Pong;
/**
* Message used to notify about known addresses.
*
* @name P2P.Message.Addressess
* @param{Array} addresses - array of know addresses
*/
function Addresses(addresses) {
this.command = 'addr';
/**
* @type {Array.Buffer}
* @desc An array of ipv4/6 addresses
*/
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;
/**
* Query another node for known IPV4/6 addresses.
*
* @name P2P.Message.GetAddresses
*/
function GetAddresses() {
this.command = 'getaddr';
}
util.inherits(GetAddresses, Message);
module.exports.GetAddresses = Message.COMMANDS.getaddr = GetAddresses;
/**
* Finishes the connection handshake started by the `ver` message.
*
* @name P2P.Message.VerAck
*/
function VerAck() {
this.command = 'verack';
}
util.inherits(VerAck, Message);
module.exports.VerAck = Message.COMMANDS.verack = VerAck;
/**
* A reject message should be sent when a message is not supported or
* interpreted as invalid.
*
* @name P2P.Message.Reject
*/
function Reject() {
this.command = 'reject';
}
util.inherits(Reject, Message);
// TODO: Parse REJECT message
module.exports.Reject = Message.COMMANDS.reject = Reject;
/**
* Used to send a message signed by a developer of the bitcoin project.
*
* @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;
/**
* Sent in response to a `getheaders` message. It contains information about
* block headers.
*
* @name P2P.Message.Headers
* @param{Array} blockheaders - array of block headers
*/
function Headers(blockheaders) {
this.command = 'headers';
/**
* @type {Array.BlockHeader}
* @desc An array of `BlockHeader`
*/
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;
/**
* Contains information about a Block
*
* @name P2P.Message.Block
* @param {Block} block
*/
function Block(block) {
this.command = 'block';
/**
* @type {Block}
* @desc The block received
*/
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;
/**
* Contains information about a transaction
*
* @name P2P.Message.Transaction
* @param{Transaction} transaction
*/
function Transaction(transaction) {
this.command = 'tx';
/**
* @type {Transaction}
*/
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;
/**
* Query another peer about blocks. It can query for multiple block hashes,
* and the response will contain all the chains of blocks starting from those
* hashes.
*
* @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';
/**
* @type {number}
*/
this.version = PROTOCOL_VERSION;
/**
* @type {Array.Buffer}
*/
this.starts = starts || [];
/**
* @type {Array.Buffer}
* @desc Hashes to limit the amount of blocks to be sent
*/
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;
/**
* Request block headers starting from a hash
*
* @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';
/**
* @type {number}
*/
this.version = PROTOCOL_VERSION;
/**
* @type {Array.Buffer}
*/
this.starts = starts || [];
/**
* @type {Array.Buffer}
*/
this.stop = stop || BufferUtil.NULL_HASH;
}
util.inherits(GetHeaders, GetBlocks);
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;
/**
* Request for transactions on the mempool
*
* @name P2P.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,108 +0,0 @@
'use strict';
var bitcore = require('flocore-lib');
var fcoin = require('fcoin');
var Inventory = require('../inventory');
function builder(options) {
/* jshint maxstatements: 20 */
/* jshint maxcomplexity: 10 */
if (!options) {
options = {};
}
if (!options.network) {
options.network = bitcore.Networks.defaultNetwork;
}
options.Block = options.Block || fcoin.Block;
options.BlockHeader = options.BlockHeader || bitcore.BlockHeader;
options.Transaction = options.Transaction || fcoin.TX;
options.MerkleBlock = options.MerkleBlock || bitcore.MerkleBlock;
options.protocolVersion = options.protocolVersion || 70001;
var exported = {
constructors: {
Block: options.Block,
BlockHeader: options.BlockHeader,
Transaction: options.Transaction,
MerkleBlock: options.MerkleBlock
},
defaults: {
protocolVersion: options.protocolVersion,
network: options.network
},
inventoryCommands: [
'getdata',
'inv',
'notfound'
],
commandsMap: {
version: 'Version',
verack: 'VerAck',
ping: 'Ping',
pong: 'Pong',
block: 'Block',
tx: 'Transaction',
getdata: 'GetData',
headers: 'Headers',
notfound: 'NotFound',
inv: 'Inventory',
addr: 'Addresses',
alert: 'Alert',
reject: 'Reject',
merkleblock: 'MerkleBlock',
filterload: 'FilterLoad',
filteradd: 'FilterAdd',
filterclear: 'FilterClear',
getblocks: 'GetBlocks',
getheaders: 'GetHeaders',
mempool: 'MemPool',
getaddr: 'GetAddr'
},
commands: {}
};
exported.add = function(key, Command) {
exported.commands[key] = function(obj) {
return new Command(obj, options);
};
exported.commands[key]._constructor = Command;
exported.commands[key].fromBuffer = function(buffer) {
var message = exported.commands[key]();
message.setPayload(buffer);
return message;
};
};
Object.keys(exported.commandsMap).forEach(function(key) {
exported.add(key, require('./commands/' + key));
});
exported.inventoryCommands.forEach(function(command) {
// add forTransaction methods
exported.commands[command].forTransaction = function forTransaction(hash) {
return new exported.commands[command]([Inventory.forTransaction(hash)]);
};
// add forBlock methods
exported.commands[command].forBlock = function forBlock(hash) {
return new exported.commands[command]([Inventory.forBlock(hash)]);
};
// add forFilteredBlock methods
exported.commands[command].forFilteredBlock = function forFilteredBlock(hash) {
return new exported.commands[command]([Inventory.forFilteredBlock(hash)]);
};
});
return exported;
}
module.exports = builder;

View File

@ -1,64 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* @param {Array=} arg - An array of addrs
* @param {Object=} options
* @extends Message
* @constructor
*/
function AddrMessage(arg, options) {
Message.call(this, options);
this.command = 'addr';
$.checkArgument(
_.isUndefined(arg) ||
(Array.isArray(arg) &&
!_.isUndefined(arg[0].services) &&
!_.isUndefined(arg[0].ip) &&
!_.isUndefined(arg[0].port)),
'First argument is expected to be an array of addrs'
);
this.addresses = arg;
}
inherits(AddrMessage, Message);
AddrMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
var addrCount = parser.readVarintNum();
this.addresses = [];
for (var i = 0; i < addrCount; i++) {
// todo: time only available on versions >=31402
var time = new Date(parser.readUInt32LE() * 1000);
var addr = utils.parseAddr(parser);
addr.time = time;
this.addresses.push(addr);
}
utils.checkFinished(parser);
};
AddrMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.addresses.length);
for (var i = 0; i < this.addresses.length; i++) {
var addr = this.addresses[i];
bw.writeUInt32LE(addr.time.getTime() / 1000);
utils.writeAddr(addr, bw);
}
return bw.concat();
};
module.exports = AddrMessage;

View File

@ -1,47 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* @param {Object=} arg
* @param {Buffer=} arg.payload
* @param {Buffer=} arg.signature
* @param {Object} options
* @extends Message
* @constructor
*/
function AlertMessage(arg, options) {
Message.call(this, options);
this.command = 'alert';
if (!arg) {
arg = {};
}
this.payload = arg.payload || new Buffer(32);
this.signature = arg.signature || new Buffer(32);
}
inherits(AlertMessage, Message);
AlertMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.payload = parser.readVarLengthBuffer();
this.signature = parser.readVarLengthBuffer();
utils.checkFinished(parser);
};
AlertMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.payload.length);
bw.write(this.payload);
bw.writeVarintNum(this.signature.length);
bw.write(this.signature);
return bw.concat();
};
module.exports = AlertMessage;

View File

@ -1,43 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* @param {Block=} arg - An instance of a Block
* @param {Object} options
* @param {Function} options.Block - A block constructor
* @extends Message
* @constructor
*/
function BlockMessage(arg, options) {
Message.call(this, options);
this.Block = options.Block;
this.command = 'block';
$.checkArgument(
_.isUndefined(arg) || arg instanceof this.Block,
'An instance of Block or undefined is expected'
);
this.block = arg;
}
inherits(BlockMessage, Message);
BlockMessage.prototype.setPayload = function(payload) {
if (typeof this.Block.fromRaw === 'function') {
this.block = this.Block.fromRaw(payload);
} else {
this.block = this.Block.fromBuffer(payload);
}
};
BlockMessage.prototype.getPayload = function() {
if (typeof this.Block.fromRaw === 'function') {
return this.block.toRaw();
}
return this.block.toBuffer();
};
module.exports = BlockMessage;

View File

@ -1,45 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferUtil = bitcore.util.buffer;
var BufferWriter = bitcore.encoding.BufferWriter;
var BufferReader = bitcore.encoding.BufferReader;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* Request peer to add data to a bloom filter already set by 'filterload'
* @param {Buffer=} data - Array of bytes representing bloom filter data
* @param {Object=} options
* @extends Message
* @constructor
*/
function FilteraddMessage(arg, options) {
Message.call(this, options);
this.command = 'filteradd';
$.checkArgument(
_.isUndefined(arg) || BufferUtil.isBuffer(arg),
'First argument is expected to be a Buffer or undefined'
);
this.data = arg || BufferUtil.EMPTY_BUFFER;
}
inherits(FilteraddMessage, Message);
FilteraddMessage.prototype.setPayload = function(payload) {
$.checkArgument(payload);
var parser = new BufferReader(payload);
this.data = parser.readVarLengthBuffer();
utils.checkFinished(parser);
};
FilteraddMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.data.length);
bw.write(this.data);
return bw.concat();
};
module.exports = FilteraddMessage;

View File

@ -1,25 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* Request peer to clear data for a bloom filter
* @extends Message
* @constructor
*/
function FilterclearMessage(arg, options) {
Message.call(this, options);
this.command = 'filterclear';
}
inherits(FilterclearMessage, Message);
FilterclearMessage.prototype.setPayload = function() {};
FilterclearMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = FilterclearMessage;

View File

@ -1,41 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var BloomFilter = require('../../bloomfilter');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* Request peer to send inv messages based on a bloom filter
* @param {BloomFilter=} arg - An instance of BloomFilter
* @param {Object} options
* @extends Message
* @constructor
*/
function FilterloadMessage(arg, options) {
Message.call(this, options);
this.command = 'filterload';
$.checkArgument(
_.isUndefined(arg) || arg instanceof BloomFilter,
'An instance of BloomFilter or undefined is expected'
);
this.filter = arg;
}
inherits(FilterloadMessage, Message);
FilterloadMessage.prototype.setPayload = function(payload) {
this.filter = BloomFilter.fromBuffer(payload);
};
FilterloadMessage.prototype.getPayload = function() {
if(this.filter) {
return this.filter.toBuffer();
} else {
return BufferUtil.EMPTY_BUFFER;
}
};
module.exports = FilterloadMessage;

View File

@ -1,26 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* Request information about active peers
* @extends Message
* @param {Object} options
* @constructor
*/
function GetaddrMessage(arg, options) {
Message.call(this, options);
this.command = 'getaddr';
}
inherits(GetaddrMessage, Message);
GetaddrMessage.prototype.setPayload = function() {};
GetaddrMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = GetaddrMessage;

View File

@ -1,64 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var $ = bitcore.util.preconditions;
/**
* Query another peer about blocks. It can query for multiple block hashes,
* and the response will contain all the chains of blocks starting from those
* hashes.
* @param {Object=} arg
* @param {Array=} arg.starts - Array of buffers or strings with the starting block hashes
* @param {Buffer=} arg.stop - Hash of the last block
* @param {Object} options
* @extends Message
* @constructor
*/
function GetblocksMessage(arg, options) {
Message.call(this, options);
this.command = 'getblocks';
this.version = options.protocolVersion;
if (!arg) {
arg = {};
}
arg = utils.sanitizeStartStop(arg);
this.starts = arg.starts;
this.stop = arg.stop;
}
inherits(GetblocksMessage, Message);
GetblocksMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
$.checkArgument(!parser.finished(), 'No data received in payload');
this.version = parser.readUInt32LE();
var startCount = parser.readVarintNum();
this.starts = [];
for (var i = 0; i < startCount; i++) {
this.starts.push(parser.read(32));
}
this.stop = parser.read(32);
utils.checkFinished(parser);
};
GetblocksMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.version);
bw.writeVarintNum(this.starts.length);
for (var i = 0; i < this.starts.length; i++) {
bw.write(this.starts[i]);
}
if (this.stop.length !== 32) {
throw new Error('Invalid hash length: ' + this.stop.length);
}
bw.write(this.stop);
return bw.concat();
};
module.exports = GetblocksMessage;

View File

@ -1,45 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* @param {Object|Array=} - options - If options is an array will use as "inventory"
* @param {Array=} options.inventory - An array of inventory items
* @extends Message
* @constructor
*/
function GetdataMessage(arg, options) {
Message.call(this, options);
this.command = 'getdata';
utils.checkInventory(arg);
this.inventory = arg;
}
inherits(GetdataMessage, Message);
GetdataMessage.prototype.setPayload = function(payload) {
this.inventory = [];
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
var type = parser.readUInt32LE();
var hash = parser.read(32);
this.inventory.push({type: type, hash: hash});
}
utils.checkFinished(parser);
};
GetdataMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
utils.writeInventory(this.inventory, bw);
return bw.concat();
};
module.exports = GetdataMessage;

View File

@ -1,63 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var $ = bitcore.util.preconditions;
/**
* Query another peer about block headers. It can query for multiple block hashes,
* and the response will contain all the chains of blocks starting from those
* hashes.
* @param {Object=} options
* @param {Array=} options.starts - Array of buffers or strings with the starting block hashes
* @param {Buffer=} options.stop - Hash of the last block
* @extends Message
* @constructor
*/
function GetheadersMessage(arg, options) {
Message.call(this, options);
this.command = 'getheaders';
this.version = options.protocolVersion;
if (!arg) {
arg = {};
}
arg = utils.sanitizeStartStop(arg);
this.starts = arg.starts;
this.stop = arg.stop;
}
inherits(GetheadersMessage, Message);
GetheadersMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
$.checkArgument(!parser.finished(), 'No data received in 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);
utils.checkFinished(parser);
};
GetheadersMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.version);
bw.writeVarintNum(this.starts.length);
for (var i = 0; i < this.starts.length; i++) {
bw.write(this.starts[i]);
}
if (this.stop.length !== 32) {
throw new Error('Invalid hash length: ' + this.stop.length);
}
bw.write(this.stop);
return bw.concat();
};
module.exports = GetheadersMessage;

View File

@ -1,60 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
/**
* Sent in response to a `getheaders` message. It contains information about
* block headers.
* @param {Array} arg - An array of BlockHeader instances
* @param {Object=} options
* @param {Array=} options.headers - array of block headers
* @param {Function} options.BlockHeader - a BlockHeader constructor
* @extends Message
* @constructor
*/
function HeadersMessage(arg, options) {
Message.call(this, options);
this.BlockHeader = options.BlockHeader;
this.command = 'headers';
$.checkArgument(
_.isUndefined(arg) || (Array.isArray(arg) && arg[0] instanceof this.BlockHeader),
'First argument is expected to be an array of BlockHeader instances'
);
this.headers = arg;
}
inherits(HeadersMessage, Message);
HeadersMessage.prototype.setPayload = function(payload) {
$.checkArgument(payload && payload.length > 0, 'No data found to create Headers message');
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
this.headers = [];
for (var i = 0; i < count; i++) {
var header = this.BlockHeader.fromBufferReader(parser);
this.headers.push(header);
var txn_count = parser.readUInt8();
$.checkState(txn_count === 0, 'txn_count should always be 0');
}
utils.checkFinished(parser);
};
HeadersMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.headers.length);
for (var i = 0; i < this.headers.length; i++) {
var buffer = this.headers[i].toBuffer();
bw.write(buffer);
bw.writeUInt8(0);
}
return bw.concat();
};
module.exports = HeadersMessage;

View File

@ -1,46 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* @param {Array=} arg - An array of inventory
* @param {Object} options
* @param {Array=} options.inventory - An array of inventory items
* @extends Message
* @constructor
*/
function InvMessage(arg, options) {
Message.call(this, options);
this.command = 'inv';
utils.checkInventory(arg);
this.inventory = arg;
}
inherits(InvMessage, Message);
InvMessage.prototype.setPayload = function(payload) {
this.inventory = [];
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
var type = parser.readUInt32LE();
var hash = parser.read(32);
this.inventory.push({type: type, hash: hash});
}
utils.checkFinished(parser);
};
InvMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
utils.writeInventory(this.inventory, bw);
return bw.concat();
};
module.exports = InvMessage;

View File

@ -1,28 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* The mempool message sends a request to a node asking for information about
* transactions it has verified but which have not yet confirmed.
* @see https://en.bitcoin.it/wiki/Protocol_documentation#mempool
* @param {Object} options
* @extends Message
* @constructor
*/
function MempoolMessage(arg, options) {
Message.call(this, options);
this.command = 'mempool';
}
inherits(MempoolMessage, Message);
MempoolMessage.prototype.setPayload = function() {};
MempoolMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = MempoolMessage;

View File

@ -1,40 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* Contains information about a MerkleBlock
* @see https://en.bitcoin.it/wiki/Protocol_documentation
* @param {MerkleBlock} arg - An instance of MerkleBlock
* @param {Object=} options
* @param {Function} options.MerkleBlock - a MerkleBlock constructor
* @extends Message
* @constructor
*/
function MerkleblockMessage(arg, options) {
Message.call(this, options);
this.MerkleBlock = options.MerkleBlock; // constructor
this.command = 'merkleblock';
$.checkArgument(
_.isUndefined(arg) || arg instanceof this.MerkleBlock,
'An instance of MerkleBlock or undefined is expected'
);
this.merkleBlock = arg;
}
inherits(MerkleblockMessage, Message);
MerkleblockMessage.prototype.setPayload = function(payload) {
$.checkArgument(BufferUtil.isBuffer(payload));
this.merkleBlock = this.MerkleBlock.fromBuffer(payload);
};
MerkleblockMessage.prototype.getPayload = function() {
return this.merkleBlock ? this.merkleBlock.toBuffer() : BufferUtil.EMPTY_BUFFER;
};
module.exports = MerkleblockMessage;

View File

@ -1,46 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
var _ = bitcore.deps._;
/**
* @param {Array} arg - An array of inventory
* @param {Object} options
* @param {Array=} options.inventory - An array of inventory items
* @extends Message
* @constructor
*/
function NotfoundMessage(arg, options) {
Message.call(this, options);
this.command = 'notfound';
utils.checkInventory(arg);
this.inventory = arg;
}
inherits(NotfoundMessage, Message);
NotfoundMessage.prototype.setPayload = function(payload) {
this.inventory = [];
var parser = new BufferReader(payload);
var count = parser.readVarintNum();
for (var i = 0; i < count; i++) {
var type = parser.readUInt32LE();
var hash = parser.read(32);
this.inventory.push({type: type, hash: hash});
}
utils.checkFinished(parser);
};
NotfoundMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
utils.writeInventory(this.inventory, bw);
return bw.concat();
};
module.exports = NotfoundMessage;

View File

@ -1,41 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var BufferUtil = bitcore.util.buffer;
var BufferReader = bitcore.encoding.BufferReader;
/**
* A message to confirm that a connection is still valid.
* @param {Number} arg - A nonce for the Ping message
* @param {Object=} options
* @extends Message
* @constructor
*/
function PingMessage(arg, options) {
Message.call(this, options);
this.command = 'ping';
$.checkArgument(
_.isUndefined(arg) || (BufferUtil.isBuffer(arg) && arg.length === 8),
'First argument is expected to be an 8 byte buffer'
);
this.nonce = arg || utils.getNonce();
}
inherits(PingMessage, Message);
PingMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.nonce = parser.read(8);
utils.checkFinished(parser);
};
PingMessage.prototype.getPayload = function() {
return this.nonce;
};
module.exports = PingMessage;

View File

@ -1,41 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var BufferUtil = bitcore.util.buffer;
var BufferReader = bitcore.encoding.BufferReader;
/**
* A message in response to a ping message.
* @param {Number} arg - A nonce for the Pong message
* @param {Object=} options
* @extends Message
* @constructor
*/
function PongMessage(arg, options) {
Message.call(this, options);
this.command = 'pong';
$.checkArgument(
_.isUndefined(arg) || (BufferUtil.isBuffer(arg) && arg.length === 8),
'First argument is expected to be an 8 byte buffer'
);
this.nonce = arg || utils.getNonce();
}
inherits(PongMessage, Message);
PongMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.nonce = parser.read(8);
utils.checkFinished(parser);
};
PongMessage.prototype.getPayload = function() {
return this.nonce;
};
module.exports = PongMessage;

View File

@ -1,67 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var utils = require('../utils');
var BufferReader = bitcore.encoding.BufferReader;
var BufferWriter = bitcore.encoding.BufferWriter;
/**
* The reject message is sent when messages are rejected.
*
* @see https://en.bitcoin.it/wiki/Protocol_documentation#reject
* @param {Object=} arg - properties for the reject message
* @param {String=} arg.message - type of message rejected
* @param {Number=} arg.ccode - code relating to rejected message
* @param {String=} arg.reason - text version of reason for rejection
* @param {Buffer=} arg.data - Optional extra data provided by some errors.
* @param {Object} options
* @extends Message
* @constructor
*/
function RejectMessage(arg, options) {
if (!arg) {
arg = {};
}
Message.call(this, options);
this.command = 'reject';
this.message = arg.message;
this.ccode = arg.ccode;
this.reason = arg.reason;
this.data = arg.data;
}
inherits(RejectMessage, Message);
RejectMessage.CCODE = {
REJECT_MALFORMED: 0x01,
REJECT_INVALID: 0x10,
REJECT_OBSOLETE: 0x11,
REJECT_DUPLICATE: 0x12,
REJECT_NONSTANDARD: 0x40,
REJECT_DUST: 0x41,
REJECT_INSUFFICIENTFEE: 0x42,
REJECT_CHECKPOINT: 0x43
};
RejectMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.message = parser.readVarLengthBuffer().toString('utf-8');
this.ccode = parser.readUInt8();
this.reason = parser.readVarLengthBuffer().toString('utf-8');
this.data = parser.readAll();
utils.checkFinished(parser);
};
RejectMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeVarintNum(this.message.length);
bw.write(new Buffer(this.message, 'utf-8'));
bw.writeUInt8(this.ccode);
bw.writeVarintNum(this.reason.length);
bw.write(new Buffer(this.reason, 'utf-8'));
bw.write(this.data);
return bw.toBuffer();
};
module.exports = RejectMessage;

View File

@ -1,47 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
/**
* @param {Transaction=} arg - An instance of Transaction
* @param {Object} options
* @extends Message
* @constructor
*/
function TransactionMessage(arg, options) {
Message.call(this, options);
this.command = 'tx';
this.Transaction = options.Transaction;
$.checkArgument(
_.isUndefined(arg) || arg instanceof this.Transaction,
'An instance of Transaction or undefined is expected'
);
this.transaction = arg;
if (!this.transaction) {
this.transaction = new this.Transaction();
}
}
inherits(TransactionMessage, Message);
TransactionMessage.prototype.setPayload = function(payload) {
if (typeof this.Transaction.fromRaw === 'function') {
this.transaction = this.Transaction.fromRaw(payload);
} else if (this.Transaction.prototype.fromBuffer) {
this.transaction = new this.Transaction().fromBuffer(payload);
} else {
this.transaction = this.Transaction.fromBuffer(payload);
}
};
TransactionMessage.prototype.getPayload = function() {
if (typeof this.Transaction.fromRaw === 'function') {
return this.transaction.toRaw();
}
return this.transaction.toBuffer();
};
module.exports = TransactionMessage;

View File

@ -1,25 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
/**
* A message in response to a version message.
* @extends Message
* @constructor
*/
function VerackMessage(arg, options) {
Message.call(this, options);
this.command = 'verack';
}
inherits(VerackMessage, Message);
VerackMessage.prototype.setPayload = function() {};
VerackMessage.prototype.getPayload = function() {
return BufferUtil.EMPTY_BUFFER;
};
module.exports = VerackMessage;

View File

@ -1,94 +0,0 @@
'use strict';
var Message = require('../message');
var inherits = require('util').inherits;
var bitcore = require('flocore-lib');
var BufferWriter = bitcore.encoding.BufferWriter;
var BufferReader = bitcore.encoding.BufferReader;
var BN = bitcore.crypto.BN;
var utils = require('../utils');
var packageInfo = require('../../../package.json');
/**
* The version message is used on connection creation to advertise
* the type of node. The remote node will respond with its version, and no
* communication is possible until both peers have exchanged their versions.
*
* @see https://en.bitcoin.it/wiki/Protocol_documentation#version
* @param {Object=} arg - properties for the version message
* @param {Buffer=} arg.nonce - a random 8 byte buffer
* @param {String=} arg.subversion - version of the client
* @param {BN=} arg.services
* @param {Date=} arg.timestamp
* @param {Number=} arg.startHeight
* @param {Object} options
* @extends Message
* @constructor
*/
function VersionMessage(arg, options) {
/* jshint maxcomplexity: 10 */
if (!arg) {
arg = {};
}
Message.call(this, options);
this.command = 'version';
this.version = arg.version || options.protocolVersion;
this.nonce = arg.nonce || utils.getNonce();
this.services = arg.services || new BN(1, 10);
this.timestamp = arg.timestamp || new Date();
this.subversion = arg.subversion || '/bitcore:' + packageInfo.version + '/';
this.startHeight = arg.startHeight || 0;
this.relay = arg.relay === false ? false : true;
}
inherits(VersionMessage, Message);
VersionMessage.prototype.setPayload = function(payload) {
var parser = new BufferReader(payload);
this.version = parser.readUInt32LE();
this.services = parser.readUInt64LEBN();
this.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000);
this.addrMe = {
services: parser.readUInt64LEBN(),
ip: utils.parseIP(parser),
port: parser.readUInt16BE()
};
this.addrYou = {
services: parser.readUInt64LEBN(),
ip: utils.parseIP(parser),
port: parser.readUInt16BE()
};
this.nonce = parser.read(8);
this.subversion = parser.readVarLengthBuffer().toString();
this.startHeight = parser.readUInt32LE();
if(parser.finished()) {
this.relay = true;
} else {
this.relay = !!parser.readUInt8();
}
utils.checkFinished(parser);
};
VersionMessage.prototype.getPayload = function() {
var bw = new BufferWriter();
bw.writeUInt32LE(this.version);
bw.writeUInt64LEBN(this.services);
var timestampBuffer = new Buffer(Array(8));
timestampBuffer.writeUInt32LE(Math.round(this.timestamp.getTime() / 1000), 0);
bw.write(timestampBuffer);
utils.writeAddr(this.addrMe, bw);
utils.writeAddr(this.addrYou, bw);
bw.write(this.nonce);
bw.writeVarintNum(this.subversion.length);
bw.write(new Buffer(this.subversion, 'ascii'));
bw.writeUInt32LE(this.startHeight);
bw.writeUInt8(this.relay);
return bw.concat();
};
module.exports = VersionMessage;

View File

@ -1,111 +0,0 @@
'use strict';
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var Hash = bitcore.crypto.Hash;
var $ = bitcore.util.preconditions;
/**
* A factory to build Bitcoin protocol messages.
* @param {Object=} options
* @param {Network=} options.network
* @param {Function=} options.Block - A block constructor
* @param {Function=} options.BlockHeader - A block header constructor
* @param {Function=} options.MerkleBlock - A merkle block constructor
* @param {Function=} options.Transaction - A transaction constructor
* @constructor
*/
function Messages(options) {
this.builder = Messages.builder(options);
// map message constructors by name
for(var key in this.builder.commandsMap) {
var name = this.builder.commandsMap[key];
this[name] = this.builder.commands[key];
}
if (!options) {
options = {};
}
this.network = options.network || bitcore.Networks.defaultNetwork;
}
Messages.MINIMUM_LENGTH = 20;
Messages.PAYLOAD_START = 16;
Messages.Message = require('./message');
Messages.builder = require('./builder');
/**
* @param {Buffers} dataBuffer
*/
Messages.prototype.parseBuffer = function(dataBuffer) {
/* jshint maxstatements: 18 */
if (dataBuffer.length < Messages.MINIMUM_LENGTH) {
return;
}
// Search the next magic number
if (!this._discardUntilNextMessage(dataBuffer)) {
return;
}
var payloadLen = (dataBuffer.get(Messages.PAYLOAD_START)) +
(dataBuffer.get(Messages.PAYLOAD_START + 1) << 8) +
(dataBuffer.get(Messages.PAYLOAD_START + 2) << 16) +
(dataBuffer.get(Messages.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 this._buildFromBuffer(command, payload);
};
Messages.prototype._discardUntilNextMessage = function(dataBuffer) {
$.checkArgument(dataBuffer);
$.checkState(this.network, 'network must be set');
var i = 0;
for (;;) {
// check if it's the beginning of a new message
var packageNumber = dataBuffer.slice(0, 4).toString('hex');
if (packageNumber === this.network.networkMagic.toString('hex')) {
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
}
};
Messages.prototype._buildFromBuffer = function(command, payload) {
if (!this.builder.commands[command]) {
throw new Error('Unsupported message command: ' + command);
}
return this.builder.commands[command].fromBuffer(payload);
};
Messages.prototype.add = function(key, name, Command) {
this.builder.add(key, Command);
this[name] = this.builder.commands[key];
};
module.exports = Messages;

View File

@ -1,43 +0,0 @@
'use strict';
var bitcore = require('flocore-lib');
var $ = bitcore.util.preconditions;
var BufferWriter = bitcore.encoding.BufferWriter;
var Hash = bitcore.crypto.Hash;
/**
* Base message that can be inherited to add an additional
* `getPayload` method to modify the message payload.
* @param {Object=} options
* @param {String=} options.command
* @param {Network=} options.network
* @constructor
*/
function Message(options) {
this.command = options.command;
this.network = options.network;
}
/**
* @returns {Buffer} - Serialized message
* @constructor
*/
Message.prototype.toBuffer = Message.prototype.serialize = function() {
$.checkState(this.network, 'Need to have a defined network to serialize message');
var commandBuf = new Buffer(Array(12));
commandBuf.write(this.command, 'ascii');
var payload = this.getPayload();
var checksum = Hash.sha256sha256(payload).slice(0, 4);
var bw = new BufferWriter();
bw.write(this.network.networkMagic);
bw.write(commandBuf);
bw.writeUInt32LE(payload.length);
bw.write(checksum);
bw.write(payload);
return bw.concat();
};
module.exports = Message;

View File

@ -1,117 +0,0 @@
'use strict';
var bitcore = require('flocore-lib');
var BufferUtil = bitcore.util.buffer;
var $ = bitcore.util.preconditions;
var _ = bitcore.deps._;
var utils;
module.exports = utils = {
checkInventory: function(arg) {
$.checkArgument(
_.isUndefined(arg) ||
(Array.isArray(arg) && arg.length === 0) ||
(Array.isArray(arg) && !_.isUndefined(arg[0].type) && !_.isUndefined(arg[0].hash)),
'Argument is expected to be an array of inventory objects'
);
},
checkFinished: function checkFinished(parser) {
if(!parser.finished()) {
throw new Error('Data still available after parsing');
}
},
getNonce: function getNonce() {
return bitcore.crypto.Random.getRandomBuffer(8);
},
writeIP: function writeIP(ip, bw) {
var words = ip.v6.split(':').map(function(s) {
return new Buffer(s, 'hex');
});
for (var i = 0; i < words.length; i++) {
var word = words[i];
bw.write(word);
}
},
writeAddr: function writeAddr(addr, bw) {
if (_.isUndefined(addr)) {
var pad = new Buffer(Array(26));
bw.write(pad);
return;
}
bw.writeUInt64LEBN(addr.services);
utils.writeIP(addr.ip, bw);
bw.writeUInt16BE(addr.port);
},
writeInventory: function writeInventory(inventory, bw) {
bw.writeVarintNum(inventory.length);
inventory.forEach(function(value) {
bw.writeUInt32LE(value.type);
bw.write(value.hash);
});
},
parseIP: function parseIP(parser) {
var ipv6 = [];
var ipv4 = [];
for (var a = 0; a < 8; a++) {
var word = parser.read(2);
ipv6.push(word.toString('hex'));
if (a >= 6) {
ipv4.push(word[0]);
ipv4.push(word[1]);
}
}
ipv6 = ipv6.join(':');
ipv4 = ipv4.join('.');
return {
v6: ipv6,
v4: ipv4
};
},
parseAddr: function parseAddr(parser) {
var services = parser.readUInt64LEBN();
var ip = utils.parseIP(parser);
var port = parser.readUInt16BE();
return {
services: services,
ip: ip,
port: port
};
},
sanitizeStartStop: function sanitizeStartStop(obj) {
/* jshint maxcomplexity: 10 */
/* jshint maxstatements: 20 */
$.checkArgument(_.isUndefined(obj.starts) || _.isArray(obj.starts));
var starts = obj.starts;
var stop = obj.stop;
if (starts) {
starts = starts.map(function(hash) {
if (_.isString(hash)) {
return BufferUtil.reverse(new Buffer(hash, 'hex'));
} else {
return hash;
}
});
} else {
starts = [];
}
for (var i = 0; i < starts.length; i++) {
if (starts[i].length !== 32) {
throw new Error('Invalid hash ' + i + ' length: ' + starts[i].length);
}
}
stop = obj.stop;
if (_.isString(stop)) {
stop = BufferUtil.reverse(new Buffer(stop, 'hex'));
}
if (!stop) {
stop = BufferUtil.NULL_HASH;
}
obj.starts = starts;
obj.stop = stop;
return obj;
}
};

View File

@ -1,83 +1,58 @@
'use strict';
var Buffers = require('./buffers');
var Buffers = require('buffers');
var EventEmitter = require('events').EventEmitter;
var Net = require('net');
var Socks5Client = require('socks5-client');
var bitcore = require('flocore-lib');
var bcoin = require('fcoin');
var Networks = bitcore.Networks;
var Messages = require('./messages');
var $ = bitcore.util.preconditions;
var util = require('util');
var bitcore = require('bitcore');
var Networks = bitcore.Networks;
var Messages = require('./messages');
var MAX_RECEIVE_BUFFER = 10000000;
/**
* The Peer constructor will create an instance of Peer to send and receive messages
* using the standard Bitcoin protocol. A Peer instance represents one connection
* on the Bitcoin network. To create a new peer connection provide the host and port
* options and then invoke the connect method. Additionally, a newly connected socket
* can be provided instead of host and port.
* 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({host: '127.0.0.1'}).setProxy('127.0.0.1', 9050);
*
* var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050);
* peer.on('tx', function(tx) {
* console.log('New transaction: ', tx.id);
* });
* peer.connect();
* ```
*
* @param {Object} options
* @param {String} options.host - IP address of the remote host
* @param {Number} options.port - Port number of the remote host
* @param {Network} options.network - The network configuration
* @param {Boolean=} options.relay - An option to disable automatic inventory relaying from the remote peer
* @param {Socket=} options.socket - An existing connected socket
* @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(options) {
/* jshint maxstatements: 26 */
/* jshint maxcomplexity: 8 */
function Peer(host, port, network) {
if (!(this instanceof Peer)) {
return new Peer(options);
return new Peer(host, port, network);
}
if (options.socket) {
this.socket = options.socket;
this.host = this.socket.remoteAddress;
this.port = this.socket.remotePort;
this.status = Peer.STATUS.CONNECTED;
this._addSocketEventHandlers();
} else {
this.host = options.host || 'localhost';
this.status = Peer.STATUS.DISCONNECTED;
this.port = options.port;
// overloading stuff
if (port instanceof Object && !network) {
network = port;
port = undefined;
}
this.network = Networks.get(options.network) || Networks.defaultNetwork;
if (!this.port) {
this.port = this.network.port;
}
this.messages = options.messages || new Messages({
network: this.network,
Block: bcoin.block,
Transaction: bcoin.tx
});
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;
this.relay = options.relay === false ? false : true;
this.versionSent = false;
// set message handlers
var self = this;
@ -89,26 +64,16 @@ function Peer(options) {
this.on('version', function(message) {
self.version = message.version;
self.subversion = message.subversion;
self.bestHeight = message.startHeight;
var verackResponse = self.messages.VerAck();
self.sendMessage(verackResponse);
if(!self.versionSent) {
self._sendVersion();
}
self.bestHeight = message.start_height
});
this.on('ping', function(message) {
self._sendPong(message.nonce);
});
return this;
}
util.inherits(Peer, EventEmitter);
Peer.MAX_RECEIVE_BUFFER = 10000000;
Peer.STATUS = {
DISCONNECTED: 'disconnected',
CONNECTING: 'connecting',
@ -118,12 +83,15 @@ Peer.STATUS = {
/**
* 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) {
$.checkState(this.status === Peer.STATUS.DISCONNECTED);
if (this.status != Peer.STATUS.DISCONNECTED) {
throw Error('Invalid State');
}
this.proxy = {
host: host,
@ -134,7 +102,8 @@ Peer.prototype.setProxy = function(host, port) {
/**
* Init the connection with the remote peer.
* @returns {Peer} The same peer instance.
*
* @returns {Socket} The same peer instance.
*/
Peer.prototype.connect = function() {
this.socket = this._getSocket();
@ -147,38 +116,24 @@ Peer.prototype.connect = function() {
self._sendVersion();
});
this._addSocketEventHandlers();
this.socket.connect(this.port, this.host);
return this;
};
Peer.prototype._addSocketEventHandlers = function() {
var self = this;
this.socket.on('error', self._onError.bind(this));
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 > Peer.MAX_RECEIVE_BUFFER) {
// TODO: handle this case better
return self.disconnect();
}
if (self.dataBuffer.length > MAX_RECEIVE_BUFFER) return self.disconnect();
self._readMessage();
});
};
Peer.prototype._onError = function(e) {
this.emit('error', e);
if (this.status !== Peer.STATUS.DISCONNECTED) {
this.disconnect();
}
this.socket.connect(this.port, this.host);
return this;
};
/**
* Disconnects the remote connection.
* @returns {Peer} The same peer instance.
*
* @returns {Socket} The same peer instance.
*/
Peer.prototype.disconnect = function() {
this.status = Peer.STATUS.DISCONNECTED;
@ -189,19 +144,18 @@ Peer.prototype.disconnect = function() {
/**
* Send a Message to the remote peer.
*
* @param {Message} message - A message instance
*/
Peer.prototype.sendMessage = function(message) {
this.socket.write(message.toBuffer());
this.socket.write(message.serialize(this.network));
};
/**
* Internal function that sends VERSION message to the remote peer.
*/
Peer.prototype._sendVersion = function() {
// todo: include sending local ip address
var message = this.messages.Version({relay: this.relay});
this.versionSent = true;
var message = new Messages.Version();
this.sendMessage(message);
};
@ -209,7 +163,7 @@ Peer.prototype._sendVersion = function() {
* Send a PONG message to the remote peer.
*/
Peer.prototype._sendPong = function(nonce) {
var message = this.messages.Pong(nonce);
var message = new Messages.Pong(nonce);
this.sendMessage(message);
};
@ -217,7 +171,8 @@ Peer.prototype._sendPong = function(nonce) {
* Internal function that tries to read a message from the data buffer
*/
Peer.prototype._readMessage = function() {
var message = this.messages.parseBuffer(this.dataBuffer);
var message = Messages.parseMessage(this.network, this.dataBuffer);
if (message) {
this.emit(message.command, message);
this._readMessage();
@ -225,7 +180,8 @@ Peer.prototype._readMessage = function() {
};
/**
* Internal function that creates a socket using a proxy if necessary.
* Internal function that creates a socket using a proxy if neccesary.
*
* @returns {Socket} A Socket instance not yet connected.
*/
Peer.prototype._getSocket = function() {

View File

@ -2,12 +2,12 @@
var dns = require('dns');
var EventEmitter = require('events').EventEmitter;
var bitcore = require('flocore-lib');
var bitcore = require('bitcore');
var Networks = bitcore.Networks;
var sha256 = bitcore.crypto.Hash.sha256;
var Peer = require('./peer');
var Networks = bitcore.Networks;
var util = require('util');
var net = require('net');
function now() {
return Math.floor(new Date().getTime() / 1000);
@ -22,63 +22,38 @@ function now() {
* @example
* ```javascript
*
* var pool = new Pool({network: Networks.livenet});
* var pool = new Pool(Networks.livenet);
* pool.on('peerinv', function(peer, message) {
* // do something with the inventory announcement
* });
* pool.connect();
* ```
*
* @param {Object=} options
* @param {Network=} options.network - The network configuration
* @param {Boolean=} options.listenAddr - Prevent new peers being added from addr messages
* @param {Boolean=} options.dnsSeed - Prevent seeding with DNS discovered known peers
* @param {Boolean=} options.relay - Prevent inventory announcements until a filter is loaded
* @param {Number=} options.maxSize - The max number of peers
* @param {Network|String} network - The network to connect
* @returns {Pool}
* @constructor
*/
function Pool(options) {
/* jshint maxcomplexity: 10 */
/* jshint maxstatements: 20 */
function Pool(network) {
var self = this;
options = options || {};
this.network = Networks.get(network) || Networks.defaultNetwork;
this.keepalive = false;
this._connectedPeers = {};
this._addrs = [];
this.listenAddr = options.listenAddr !== false;
this.dnsSeed = options.dnsSeed !== false;
this.maxSize = options.maxSize || Pool.MaxConnectedPeers;
this.messages = options.messages;
this.network = Networks.get(options.network) || Networks.defaultNetwork;
this.relay = options.relay === false ? false : true;
if (options.addrs) {
for(var i = 0; i < options.addrs.length; i++) {
this._addAddr(options.addrs[i]);
}
}
if (this.listenAddr) {
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];
var future = new Date().getTime() + (10 * 60 * 1000);
if (addr.time.getTime() <= 100000000000 || addr.time.getTime() > future) {
// In case of an invalid time, assume "5 days ago"
var past = new Date(new Date().getTime() - 5 * 24 * 60 * 60 * 1000);
addr.time = past;
}
this._addAddr(addr);
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) {
@ -109,21 +84,21 @@ util.inherits(Pool, EventEmitter);
Pool.MaxConnectedPeers = 8;
Pool.RetrySeconds = 30;
Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'pong', 'addr',
'getaddr', 'verack', 'reject', 'alert', 'headers', 'block', 'merkleblock',
'tx', 'getblocks', 'getheaders', 'error', 'filterload', 'filteradd',
'filterclear'
Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'ping', 'addr',
'getaddr', 'verack', 'reject', 'alert', 'headers', 'block',
'tx', 'getblocks', 'getheaders'
];
/**
* Will initiate connection to peers, if available peers have been added to
* 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 (this.dnsSeed) {
if (self._addrs.length === 0) {
self._addAddrsFromSeeds();
} else {
self._fillConnections();
@ -131,6 +106,7 @@ Pool.prototype.connect = function connect() {
return this;
};
/**
* Will disconnect all peers that are connected.
*/
@ -150,12 +126,12 @@ Pool.prototype.numberConnected = function numberConnected() {
};
/**
* Will fill the connected peers to the maximum amount.
* 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() >= this.maxSize) {
if (this.numberConnected() >= Pool.MaxConnectedPeers) {
break;
}
var addr = this._addrs[i];
@ -186,73 +162,32 @@ Pool.prototype._removeConnectedPeer = function _removeConnectedPeer(addr) {
Pool.prototype._connectPeer = function _connectPeer(addr) {
var self = this;
if (!this._connectedPeers[addr.hash]) {
function addConnectedPeer(addr) {
var port = addr.port || self.network.port;
var ip = addr.ip.v4 || addr.ip.v6;
var peer = new Peer({
host: ip,
port: port,
messages: self.messages,
network: this.network,
relay: self.relay
var peer = new Peer(ip, port, self.network);
peer.on('disconnect', function peerDisconnect() {
self.emit('peerdisconnect', peer, addr);
});
peer.on('connect', function peerConnect() {
self.emit('peerconnect', 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);
});
});
self._addPeerEventHandlers(peer, addr);
peer.connect();
self._connectedPeers[addr.hash] = peer;
}
return this;
};
/**
* Adds a peer with a connected socket to the _connectedPeers object, and
* initializes the associated event handlers.
* @param {Socket} - socket - A new connected socket
* @param {Object} - addr - The associated addr object for the peer
*/
Pool.prototype._addConnectedPeer = function _addConnectedPeer(socket, addr) {
var self = this;
if (!this._connectedPeers[addr.hash]) {
var peer = new Peer({
socket: socket,
network: this.network,
messages: self.messages
});
self._addPeerEventHandlers(peer, addr);
self._connectedPeers[addr.hash] = peer;
self.emit('peerconnect', peer, addr);
addConnectedPeer(addr);
}
return this;
};
/**
* Will add disconnect and ready events for a peer and intialize
* handlers for relay peer message events.
*/
Pool.prototype._addPeerEventHandlers = function(peer, addr) {
var self = this;
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);
});
});
};
/**
* Will deprioritize an addr in the list of addrs by moving it to the end
* of the array, and setting a retryTime
@ -277,8 +212,6 @@ Pool.prototype._deprioritizeAddr = function _deprioritizeAddr(addr) {
* @param {Object}
*/
Pool.prototype._addAddr = function _addAddr(addr) {
// Use default port if not specified
addr.port = addr.port || this.network.port;
// make a unique key
addr.hash = sha256(new Buffer(addr.ip.v6 + addr.ip.v4 + addr.port)).toString('hex');
@ -293,7 +226,7 @@ Pool.prototype._addAddr = function _addAddr(addr) {
if (!exists) {
this._addrs.unshift(addr);
}
return addr;
return this;
};
/**
@ -341,41 +274,4 @@ Pool.prototype.inspect = function inspect() {
this._addrs.length + '>';
};
/**
* Will send a message to all of the peers in the pool.
* @param {Message} message - An instance of the message to send
*/
Pool.prototype.sendMessage = function(message) {
// broadcast to peers
for(var key in this._connectedPeers) {
var peer = this._connectedPeers[key];
peer.sendMessage(message);
}
};
/**
* Will enable a listener for peer connections, when a peer connects
* it will be added to the pool.
*/
Pool.prototype.listen = function() {
var self = this;
// Create server
this.server = net.createServer(function(socket) {
var addr = {
ip: {}
};
if(net.isIPv6(socket.remoteAddress)) {
addr.ip.v6 = socket.remoteAddress;
} else {
addr.ip.v4 = socket.remoteAddress;
}
addr.port = socket.remotePort;
addr = self._addAddr(addr);
self._addConnectedPeer(socket, addr);
});
this.server.listen(this.network.port);
};
module.exports = Pool;

2446
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
{
"name": "flocore-p2p",
"version": "5.0.0-beta.8",
"description": "Interface to the Flo P2P network for flocore",
"name": "bitcore-p2p",
"version": "0.8.12",
"description": "Interface to the bitcoin P2P network for bitcore",
"author": "BitPay <dev@bitpay.com>",
"main": "index.js",
"scripts": {
"lint": "gulp lint",
@ -9,6 +10,39 @@
"coverage": "gulp coverage",
"build": "gulp"
},
"contributors": [
{
"name": "Yemel Jardi",
"email": "yemel@bitpay.com"
},
{
"name": "Braydon Fuller",
"email": "braydon@bitpay.com"
},
{
"name": "Ryan X. Charles",
"email": "ryan@bitpay.com"
},
{
"name": "Eric Martindale",
"email": "eric@bitpay.com"
},
{
"name": "Philip Hutchins",
"email": "philip@bitpay.com"
},
{
"name": "Manuel Araoz",
"email": "maraoz@bitpay.com"
},
{
"name": "Esteban Ordano",
"email": "eordano@gmail.com"
},
{
"name": "Elichai Turkel"
}
],
"keywords": [
"bitcoin",
"bitcore"
@ -18,17 +52,18 @@
"url": "https://github.com/bitpay/bitcore-p2p.git"
},
"dependencies": {
"bloom-filter": "^0.2.0",
"buffers": "bitpay/node-buffers#v0.1.2-bitpay",
"fcoin": "^1.1.0",
"flocore-lib": "^0.15.2",
"bitcore": "^0.8.6",
"bufferput": "^0.1.2",
"buffers": "^0.1.1",
"karma-detect-browsers": "^0.1.3",
"socks5-client": "^0.3.6"
},
"devDependencies": {
"bitcore-build": "git://github.com/bitpay/bitcore-build.git",
"bitcore-build": "^0.4.2",
"brfs": "^1.2.0",
"chai": "~1.10.0",
"gulp": "^3.8.10",
"lodash": "^2.4.1",
"sinon": "^1.12.2"
},
"license": "MIT"

View File

@ -1,88 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var assert = require('assert');
var bitcore = require('bitcore-lib');
var Data = require('./data/messages');
var P2P = require('../');
var BloomFilter = P2P.BloomFilter;
function getPayloadBuffer(messageBuffer) {
return new Buffer(messageBuffer.slice(48), 'hex');
}
// convert a hex string to a bytes buffer
function ParseHex(str) {
var result = [];
while (str.length >= 2) {
result.push(parseInt(str.substring(0, 2), 16));
str = str.substring(2, str.length);
}
var buf = new Buffer(result, 16);
return buf;
}
describe('BloomFilter', function() {
it('#fromBuffer and #toBuffer round trip', function() {
var testPayloadBuffer = getPayloadBuffer(Data.filterload.message);
var filter = new BloomFilter.fromBuffer(testPayloadBuffer);
filter.toBuffer().should.deep.equal(testPayloadBuffer);
});
// test data from: https://github.com/bitcoin/bitcoin/blob/master/src/test/bloom_tests.cpp
it('serialize filter with public keys added', function() {
var privateKey = bitcore.PrivateKey.fromWIF('5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C');
var publicKey = privateKey.toPublicKey();
var filter = BloomFilter.create(2, 0.001, 0, BloomFilter.BLOOM_UPDATE_ALL);
filter.insert(publicKey.toBuffer());
filter.insert(bitcore.crypto.Hash.sha256ripemd160(publicKey.toBuffer()));
var expectedFilter = BloomFilter.fromBuffer(ParseHex('038fc16b080000000000000001'));
filter.toBuffer().should.deep.equal(expectedFilter.toBuffer());
});
it('serialize to a buffer', function() {
var filter = BloomFilter.create(3, 0.01, 0, BloomFilter.BLOOM_UPDATE_ALL);
filter.insert(ParseHex('99108ad8ed9bb6274d3980bab5a85c048f0950c8'));
assert(filter.contains(ParseHex('99108ad8ed9bb6274d3980bab5a85c048f0950c8')));
// one bit different in first byte
assert(!filter.contains(ParseHex('19108ad8ed9bb6274d3980bab5a85c048f0950c8')));
filter.insert(ParseHex('b5a2c786d9ef4658287ced5914b37a1b4aa32eee'));
assert(filter.contains(ParseHex("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")));
filter.insert(ParseHex('b9300670b4c5366e95b2699e8b18bc75e5f729c5'));
assert(filter.contains(ParseHex('b9300670b4c5366e95b2699e8b18bc75e5f729c5')));
var actual = filter.toBuffer();
var expected = new Buffer('03614e9b050000000000000001', 'hex');
actual.should.deep.equal(expected);
});
it('deserialize a buffer', function() {
var buffer = new Buffer('03614e9b050000000000000001', 'hex');
var filter = BloomFilter.fromBuffer(buffer);
assert(filter.contains(ParseHex('99108ad8ed9bb6274d3980bab5a85c048f0950c8')));
assert(!filter.contains(ParseHex('19108ad8ed9bb6274d3980bab5a85c048f0950c8')));
assert(filter.contains(ParseHex("b5a2c786d9ef4658287ced5914b37a1b4aa32eee")));
assert(filter.contains(ParseHex('b9300670b4c5366e95b2699e8b18bc75e5f729c5')));
});
it('#toBuffer and #fromBuffer round trip, with a large filter', function() {
var filter = BloomFilter.create(10000, 0.001);
var buffer = filter.toBuffer();
new BloomFilter.fromBuffer(buffer).should.deep.equal(filter);
});
});

View File

@ -1,63 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var Buffers = require('../lib/buffers');
describe('Buffers', function() {
var buffs = function buffs() {
var b = new Buffers();
b.push(new Buffer('0123', 'hex'));
b.push(new Buffer('4567', 'hex'));
b.push(new Buffer('89ab', 'hex'));
b.push(new Buffer('cdef', 'hex'));
return b;
};
it('set buffers to empty if "i" is greater than the total length', function() {
var b = buffs();
b.length.should.equal(8);
b.skip(100);
b.buffers.should.deep.equal([]);
b.length.should.equal(0);
});
it('set buffers to empty if "i" is equal than the total length', function() {
var b = buffs();
b.length.should.equal(8);
b.skip(8);
b.buffers.should.deep.equal([]);
b.length.should.equal(0);
});
it('do not skip if "i" is zero', function() {
var b = buffs();
b.skip(0);
b.length.should.equal(8);
});
it('remove part of the first buffer', function() {
var b = buffs();
b.skip(1);
b.length.should.equal(7);
b.buffers[0].should.deep.equal(new Buffer('23', 'hex'));
});
it('remove the first three buffers', function() {
var b = buffs();
b.skip(6);
b.length.should.equal(2);
should.not.exist(b.buffers[1]);
should.not.exist(b.buffers[2]);
should.not.exist(b.buffers[3]);
});
it('remove part of the fourth buffer', function() {
var b = buffs();
b.skip(7);
b.length.should.equal(1);
b.buffers[0].should.deep.equal(new Buffer('ef', 'hex'));
});
});

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="../tests.js"></script>
<script>
mocha.run();
</script>
</body>
</html>

View File

@ -1,107 +0,0 @@
'use strict';
/*jshint immed: false */
var should = require('chai').should();
var bitcore = require('bitcore-lib');
var P2P = require('../');
var Inventory = P2P.Inventory;
var BufferUtils = bitcore.util.buffer;
var BufferWriter = bitcore.encoding.BufferWriter;
var BufferReader = bitcore.encoding.BufferReader;
describe('Inventory', function() {
var hash = new Buffer('eb951630aba498b9a0d10f72b5ea9e39d5ff04b03dc2231e662f52057f948aa1', 'hex');
var hashedStr = BufferUtils.reverse(new Buffer(hash, 'hex')).toString('hex');
var inventoryBuffer = new Buffer(
'01000000eb951630aba498b9a0d10f72b5ea9e39d5ff04b03dc2231e662f52057f948aa1',
'hex'
);
describe('@constructor', function() {
it('create inventory', function() {
var inventory = new Inventory({type: Inventory.TYPE.TX, hash: hash});
should.exist(inventory);
});
it('error with string hash', function() {
(function() {
var inventory = new Inventory({type: Inventory.TYPE.TX, hash: hashedStr});
should.not.exist(inventory);
}).should.throw('Unexpected hash');
});
});
describe('#forItem', function() {
it('handle a string hash (reversed)', function() {
var inventory = Inventory.forItem(Inventory.TYPE.TX, hashedStr);
should.exist(inventory);
inventory.hash.should.deep.equal(new Buffer(hash, 'hex'));
});
});
describe('#forBlock', function() {
it('use correct block type', function() {
var inventory = Inventory.forBlock(hash);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.BLOCK);
});
});
describe('#forFilteredBlock', function() {
it('use correct filtered block type', function() {
var inventory = Inventory.forFilteredBlock(hash);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.FILTERED_BLOCK);
});
});
describe('#forTransaction', function() {
it('use correct filtered tx type', function() {
var inventory = Inventory.forTransaction(hash);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.TX);
});
});
describe('#toBuffer', function() {
it('serialize correctly', function() {
var inventory = Inventory.forTransaction(hash);
var buffer = inventory.toBuffer();
buffer.should.deep.equal(inventoryBuffer);
});
});
describe('#toBufferWriter', function() {
it('write to a buffer writer', function() {
var bw = new BufferWriter();
var inventory = Inventory.forTransaction(hash);
inventory.toBufferWriter(bw);
bw.concat().should.deep.equal(inventoryBuffer);
});
});
describe('#fromBuffer', function() {
it('deserialize a buffer', function() {
var inventory = Inventory.fromBuffer(inventoryBuffer);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.TX);
inventory.hash.should.deep.equal(hash);
});
});
describe('#fromBufferWriter', function() {
it('deserialize from a buffer reader', function() {
var bw = new BufferReader(inventoryBuffer);
var inventory = Inventory.fromBufferReader(bw);
should.exist(inventory);
inventory.type.should.equal(Inventory.TYPE.TX);
inventory.hash.should.deep.equal(hash);
});
});
});

355
test/messages.js Normal file
View File

@ -0,0 +1,355 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var bitcore = require('bitcore');
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);
var version = require('../package.json').version;
message.subversion.should.equal('/bitcore:' + version + '/');
should.exist(message.nonce);
});
it('should be able to serialize the payload', function() {
var message = new Messages.Version();
var payload = message.getPayload();
should.exist(payload);
});
it('should be able to serialize the message', function() {
var message = new Messages.Version();
var buffer = message.serialize(Networks.livenet);
should.exist(buffer);
});
it('should be able to parse payload', function() {
var payload = new Buffer(Data.VERSION.payload, 'hex');
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,30 +0,0 @@
'use strict';
var should = require('chai').should();
var P2P = require('../../');
var builder = P2P.Messages.builder;
var bitcore = require('bitcore-lib');
describe('Messages Builder', function() {
describe('@constructor', function() {
it('should return commands based on default', function() {
// instantiate
var b = builder();
should.exist(b);
});
it('should return commands with customizations', function() {
// instantiate
var b = builder({
network: bitcore.Networks.testnet,
Block: bitcore.Block,
Transaction: bitcore.Transaction
});
should.exist(b);
});
});
});

View File

@ -1,322 +0,0 @@
'use strict';
var should = require('chai').should();
var expect = require('chai').expect;
var P2P = require('../../../');
var Messages = P2P.Messages;
var sinon = require('sinon');
var bitcore = require('bitcore-lib');
describe('Command Messages', function() {
var messages = new Messages();
describe('Addr', function() {
it('should error if arg is not an array of addrs', function() {
(function() {
var message = messages.Addresses(['not an addr']);
}).should.throw('First argument is expected to be an array of addrs');
});
it('should instantiate with an array of addrs', function() {
var message = messages.Addresses([{
ip: {
v4: 'localhost'
},
services: 1,
port: 1234
}]);
});
});
describe('Alert', function() {
it('should accept a transaction instance as an argument', function() {
var message = messages.Alert({
payload: new Buffer('abcdef', 'hex'),
signature: new Buffer('123456', 'hex')
});
message.payload.should.deep.equal(new Buffer('abcdef', 'hex'));
message.signature.should.deep.equal(new Buffer('123456', 'hex'));
});
});
describe('Transaction', function() {
it('should accept a transaction instance as an argument', function() {
var tx = new bitcore.Transaction();
var message = messages.Transaction(tx);
message.transaction.should.be.instanceof(bitcore.Transaction);
});
it('should create a transaction instance', function() {
var message = messages.Transaction();
message.transaction.should.be.instanceof(bitcore.Transaction);
});
it('version should remain the same', function() {
var tx = new bitcore.Transaction();
var version = Number(tx.version);
var message = messages.Transaction(tx);
message.transaction.version.should.equal(version);
});
});
describe('Block', function() {
it('should accept a block instance as an argument', function() {
var block = new bitcore.Block({
header: {},
transactions: []
});
var message = messages.Block(block);
message.block.should.be.instanceof(bitcore.Block);
});
});
describe('Pong', function() {
it('should error if nonce is not a buffer', function() {
(function() {
var message = messages.Pong('not a buffer');
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should error if nonce buffer has invalid length', function() {
(function() {
var message = messages.Pong(new Buffer(Array(9)));
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should set a nonce if not included', function() {
var message = messages.Pong();
should.exist(message.nonce);
message.nonce.length.should.equal(8);
});
});
describe('Ping', function() {
it('should error if nonce is not a buffer', function() {
(function() {
var message = messages.Ping('not a buffer');
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should error if nonce buffer has invalid length', function() {
(function() {
var message = messages.Ping(new Buffer(Array(9)));
}).should.throw('First argument is expected to be an 8 byte buffer');
});
it('should set a nonce if not included', function() {
var message = messages.Ping();
should.exist(message.nonce);
message.nonce.length.should.equal(8);
});
});
describe('FilterAdd', function() {
it('should error if arg is not a buffer', function() {
(function() {
var message = messages.FilterAdd('not a buffer');
}).should.throw('First argument is expected to be a Buffer or undefined');
});
});
describe('FilterLoad', function() {
it('should return a null payload', function() {
var message = messages.FilterLoad();
var payload = message.getPayload();
payload.length.should.equal(0);
payload.should.be.instanceof(Buffer);
});
it('should error if filter is not a bloom filter', function() {
(function() {
var message = messages.FilterLoad({filter: 'not a bloom filter'});
}).should.throw('An instance of BloomFilter');
});
});
describe('Inventory', function() {
it('should error if arg is not an array', function() {
(function() {
var message = messages.Inventory({});
}).should.throw('Argument is expected to be an array of inventory objects');
});
it('should not error if arg is an empty array', function() {
var message = messages.Inventory([]);
});
it('should error if arg is not an array of inventory objects', function() {
(function() {
var message = messages.Inventory([Number(0)]);
}).should.throw('Argument is expected to be an array of inventory objects');
});
});
describe('Transaction', function() {
it('should be able to pass a custom Transaction', function(done) {
var Transaction = function(){};
Transaction.prototype.fromBuffer = function() {
done();
};
var messagesCustom = new Messages({Transaction: Transaction});
var message = messagesCustom.Transaction.fromBuffer();
should.exist(message);
});
it('should work with Transaction.fromBuffer', function(done) {
var Transaction = sinon.stub();
Transaction.fromBuffer = function() {
done();
};
var messagesCustom = new Messages({Transaction: Transaction});
var message = messagesCustom.Transaction.fromBuffer();
should.exist(message);
});
});
describe('Block', function() {
it('should be able to pass a custom Block', function(done) {
var Block = sinon.stub();
Block.fromBuffer = function() {
done();
};
var messagesCustom = new Messages({Block: Block});
var message = messagesCustom.Block.fromBuffer();
should.exist(message);
});
});
describe('GetBlocks', function() {
it('should error with invalid stop', function() {
var invalidStop = '000000';
var starts = ['000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9'];
(function() {
var message = messages.GetBlocks({starts: starts, stop: invalidStop});
var buffer = message.toBuffer();
should.not.exist(buffer);
}).should.throw('Invalid hash length');
});
});
describe('GetHeaders', function() {
it('should error with invalid stop', function() {
var invalidStop = '000000';
var starts = ['000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9'];
(function() {
var message = messages.GetHeaders({starts: starts, stop: invalidStop});
var buffer = message.toBuffer();
should.not.exist(buffer);
}).should.throw('Invalid hash length');
});
});
describe('Headers', function() {
it('should error if arg is not an array', function() {
(function() {
var message = messages.Headers({});
}).should.throw('First argument is expected to be an array');
});
it('should error if arg is an empty array', function() {
(function() {
var message = messages.Headers([]);
}).should.throw('First argument is expected to be an array');
});
it('should error if arg is not an array of BlockHeaders', function() {
(function() {
var message = messages.Headers([Number(0)]);
}).should.throw('First argument is expected to be an array');
});
});
describe('MerkleBlock', function() {
it('should return null buffer for payload', function() {
var message = messages.MerkleBlock();
var payload = message.getPayload();
payload.length.should.equal(0);
});
it('should error if merkleBlock is not a MerkleBlock', function() {
(function() {
var message = messages.MerkleBlock({merkleBlock: 'not a merkle block'});
}).should.throw('An instance of MerkleBlock');
});
});
describe('Reject', function() {
it('should set properties from arg in constructor', function() {
var message = messages.Reject({
message: 'tx',
ccode: 0x01,
reason: 'transaction is malformed',
data: new Buffer('12345678901234567890123456789012', 'hex')
});
message.message.should.equal('tx');
message.ccode.should.equal(0x01);
message.reason.should.equal('transaction is malformed');
message.data.toString('hex').should.equal('12345678901234567890123456789012');
});
it('should let arg be optional in constructor', function() {
var message = messages.Reject();
expect(message.message).to.be.undefined;
expect(message.ccode).to.be.undefined;
expect(message.reason).to.be.undefined;
expect(message.data).to.be.undefined;
});
it('should write payload correctly', function() {
var message = messages.Reject({
message: 'tx',
ccode: 0x01,
reason: 'transaction is malformed',
data: new Buffer('12345678901234567890123456789012', 'hex')
});
var payload = message.getPayload();
message = messages.Reject();
message.setPayload(payload);
message.message.should.equal('tx');
message.ccode.should.equal(0x01);
message.reason.should.equal('transaction is malformed');
message.data.toString('hex').should.equal('12345678901234567890123456789012');
});
});
describe('Version', function() {
it('should set the default relay property as true', function() {
var message = messages.Version();
should.exist(message.relay);
message.relay.should.equal(true);
});
it('should set the relay as false', function() {
var message = messages.Version({relay: false});
should.exist(message.relay);
message.relay.should.equal(false);
});
it('should set the relay as true', function() {
var message = messages.Version({relay: true});
should.exist(message.relay);
message.relay.should.equal(true);
});
});
});

View File

@ -1,216 +0,0 @@
'use strict';
var chai = require('chai');
var should = chai.should();
var Buffers = require('buffers');
var P2P = require('../../');
var Messages = P2P.Messages;
var messages = new Messages();
var bitcore = require('bitcore-lib');
var Data = require('../data/messages'); //todo merge with commandData
var commandData = require('../data/messages.json');
function getPayloadBuffer(messageBuffer) {
return new Buffer(messageBuffer.slice(48), 'hex');
}
describe('Messages', function() {
var buildMessage = function(hex) {
var m = Buffers();
m.push(new Buffer(hex, 'hex'));
return m;
};
describe('@constructor', function() {
it('sets properties correctly', function() {
var network = bitcore.Networks.defaultNetwork;
var messages = new Messages({
network: network,
Block: bitcore.Block,
Transaction: bitcore.Transaction
});
should.exist(messages.builder.commands);
should.exist(messages.builder.constructors);
messages.builder.constructors.Block.should.equal(bitcore.Block);
messages.builder.constructors.Transaction.should.equal(bitcore.Transaction);
messages.network.should.deep.equal(network);
});
it('network should be unique for each set of messages', function() {
var messages = new Messages({
network: bitcore.Networks.livenet
});
var messages2 = new Messages({
network: bitcore.Networks.testnet
});
messages.network.should.deep.equal(bitcore.Networks.livenet);
messages2.network.should.deep.equal(bitcore.Networks.testnet);
var message1 = messages.Version();
message1.network.should.deep.equal(bitcore.Networks.livenet);
var message2 = messages2.Version();
message2.network.should.deep.equal(bitcore.Networks.testnet);
});
});
describe('@constructor for all command messages', function() {
var messages = new Messages();
Object.keys(messages.builder.commandsMap).forEach(function(command) {
var name = messages.builder.commandsMap[command];
it('message.' + name, function(done) {
should.exist(messages[name]);
var message = messages[name]();
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
done();
});
});
});
describe('#fromBuffer/#toBuffer round trip for all commands', function() {
var messages = new Messages();
Object.keys(messages.builder.commandsMap).forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function(done) {
var payloadBuffer = getPayloadBuffer(commandData[command].message);
should.exist(messages[name]);
var message = messages[name].fromBuffer(payloadBuffer);
var outputBuffer = message.getPayload();
outputBuffer.toString('hex').should.equal(payloadBuffer.toString('hex'));
outputBuffer.should.deep.equal(payloadBuffer);
var expectedBuffer = new Buffer(commandData[command].message, 'hex');
message.toBuffer().should.deep.equal(expectedBuffer);
done();
});
});
});
describe('Default Network', function() {
var messages = new Messages();
Object.keys(messages.builder.commandsMap).forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
var message = messages[name]();
message.network.should.deep.equal(bitcore.Networks.defaultNetwork);
});
});
});
describe('messages.Version', function() {
var messages = new Messages();
it('#fromBuffer works w/o fRelay arg', function() {
var payloadBuffer = getPayloadBuffer(Data.version.messagenofrelay);
var message = messages.Version.fromBuffer(payloadBuffer);
message.relay.should.equal(true);
});
it('#relay setting works', function() {
[true, false].forEach(function(relay) {
var message = messages.Version({
relay: relay
});
message.relay.should.equal(relay);
var messageBuf = message.getPayload();
var newMessage = messages.Version.fromBuffer(messageBuf);
newMessage.relay.should.equal(relay);
});
});
});
describe('Inventory Helpers', function() {
var messages = new Messages();
var constructors = messages.builder.inventoryCommands;
var fakeHash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a';
describe('#forTransaction', function() {
constructors.forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
should.exist(messages[name].forTransaction);
var message = messages[name].forTransaction(fakeHash);
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
});
});
});
describe('#forBlock', function() {
constructors.forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
var message = messages[name].forBlock(fakeHash);
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
});
});
});
describe('#forFilteredBlock', function() {
constructors.forEach(function(command) {
var name = messages.builder.commandsMap[command];
it(name, function() {
var message = messages[name].forFilteredBlock(fakeHash);
should.exist(message);
message.should.be.instanceof(messages[name]._constructor);
});
});
});
});
describe('#parseBuffer', function() {
it('fails with invalid command', function() {
var invalidCommand = 'f9beb4d96d616c6963696f757300000025000000bd5e830c' +
'0102000000ec3995c1bf7269ff728818a65e53af00cbbee6b6eca8ac9ce7bc79d87' +
'7041ed8';
var fails = function() {
var bufs = buildMessage(invalidCommand);
messages.parseBuffer(bufs);
};
fails.should.throw('Unsupported message command: malicious');
});
it('ignores malformed messages', function() {
var malformed1 = 'd8c4c3d976657273696f6e000000000065000000fc970f1772110' +
'1000100000000000000ba6288540000000001000000000000000000000000000000' +
'0000ffffba8886dceab0010000000000000000000000000000000000ffff0509552' +
'2208de7e1c1ef80a1cea70f2f5361746f7368693a302e392e312fa317050001';
var malformed2 = 'f9beb4d967657464617461000000000089000000d88134740102' +
'0000006308e4a380c949dbad182747b0f7b6a89e874328ca41f37287f74a81b8f84' +
'86d';
var malformed3 = 'f9beb4d967657464617461000000000025000000616263640102' +
'00000069ebcbc34a4f9890da9aea0f773beba883a9afb1ab9ad7647dd4a1cd346c3' +
'728';
[malformed1, malformed2, malformed3].forEach(function(malformed) {
var ret = messages.parseBuffer(buildMessage(malformed));
should.not.exist(ret);
});
});
});
describe('#add', function() {
it('should add a custom message', function() {
var network = bitcore.Networks.defaultNetwork;
var messages = new Messages({
network: network,
Block: bitcore.Block,
Transaction: bitcore.Transaction
});
var CustomMessage = function(arg, options) {
this.arg = arg;
};
messages.add('custom', 'Custom', CustomMessage);
should.exist(messages.Custom);
var message = messages.Custom('hello');
message.arg.should.equal('hello');
});
});
});

View File

@ -1,39 +0,0 @@
'use strict';
var should = require('chai').should();
var P2P = require('../../');
var Message = P2P.Messages.Message;
var Networks = require('bitcore-lib').Networks;
describe('Message', function() {
describe('@constructor', function() {
it('construct with magic number and command', function() {
var message = new Message({
network: {
networkMagic: 0xd9b4bef9
},
command: 'command'
});
should.exist(message);
message.command.should.equal('command');
message.network.networkMagic.should.equal(0xd9b4bef9);
});
});
describe('#toBuffer', function() {
it('serialize to a buffer', function() {
var message = new Message({
command: 'command',
network: Networks.defaultNetwork
});
message.getPayload = function() {
return new Buffer(0);
};
var buffer = message.toBuffer();
var expectedBuffer = new Buffer('f9beb4d9636f6d6d616e640000000000000000005df6e0e2', 'hex');
buffer.should.deep.equal(expectedBuffer);
});
});
});

View File

@ -1,39 +0,0 @@
'use strict';
/* jshint unused: false */
var should = require('chai').should();
var utils = require('../../lib/messages/utils');
var bitcore = require('bitcore-lib');
var BufferReader = bitcore.encoding.BufferReader;
describe('Message Utils', function() {
describe('checkFinished', function() {
it('should throw an error if buffer reader is not finished', function() {
/*jshint immed: false */
var buffer = new Buffer(Array(32));
var br = new BufferReader(buffer);
(function() {
utils.checkFinished(br);
}).should.throw('Data still available after parsing');
});
});
describe('sanitizeStartStop', function() {
it('should throw an error if starts is invalid length', function() {
/*jshint immed: false */
var stop = '000000000000000013413cf2536b491bf0988f52e90c476ffeb701c8bfdb1db9';
(function() {
utils.sanitizeStartStop({starts: ['0000'], stop: stop});
}).should.throw('Invalid hash');
});
it('should keep buffers as buffers', function() {
/*jshint immed: false */
var starts = [new Buffer(Array(32))];
var obj = utils.sanitizeStartStop({starts: starts});
obj.starts[0].should.deep.equal(starts[0]);
});
});
});

View File

@ -1,5 +1,6 @@
'use strict';
var _ = require('lodash');
var chai = require('chai');
var Net = require('net');
var Socks5Client = require('socks5-client');
@ -10,20 +11,16 @@ var expect = chai.expect;
var sinon = require('sinon');
var fs = require('fs');
var bitcore = require('bitcore-lib');
var _ = bitcore.deps._;
var bitcore = require('bitcore');
var P2P = require('../');
var Peer = P2P.Peer;
var EventEmitter = require('events').EventEmitter;
var Messages = P2P.Messages;
var messages = new Messages();
var Networks = bitcore.Networks;
describe('Peer', function() {
describe('Integration test', function() {
it('parses this stream of data from a connection', function(callback) {
var peer = new Peer('');
var peer = new P2P.Peer('');
var stub = sinon.stub();
var dataCallback;
var connectCallback;
@ -47,7 +44,8 @@ describe('Peer', function() {
connectCallback = arguments[1];
}
};
stub.write = function() {};
stub.write = function() {
};
stub.connect = function() {
connectCallback();
};
@ -55,10 +53,10 @@ describe('Peer', function() {
return stub;
};
peer.on('connect', function() {
dataCallback(fs.readFileSync('./test/data/connection.log'));
dataCallback(fs.readFileSync('./test/connection.log'));
});
var check = function(message) {
received[message.command]++;
received[message.command]++;
if (_.isEqual(received, expected)) {
callback();
}
@ -71,173 +69,63 @@ describe('Peer', function() {
});
});
it('create instance', 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('create instance setting a port', function() {
var peer = new Peer({host: 'localhost', port: 8111});
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('create instance setting a network', function() {
var peer = new Peer({host: 'localhost', network: Networks.testnet});
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('create instance setting port and network', function() {
var peer = new Peer({host: 'localhost', port: 8111, network: Networks.testnet});
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('create instance without new', function() {
var peer = Peer({host: 'localhost', port: 8111, network: Networks.testnet});
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);
});
it('set a proxy', function() {
var peer, peer2, socket;
if (typeof(window) === 'undefined'){
peer = new Peer('localhost');
expect(peer.proxy).to.be.undefined();
socket = peer._getSocket();
socket.should.be.instanceof(Net.Socket);
// Node.js Tests
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);
it('should be able to set a proxy', function() {
var peer, peer2, socket;
peer.should.equal(peer2);
});
peer = new Peer('localhost');
expect(peer.proxy).to.be.undefined();
socket = peer._getSocket();
socket.should.be.instanceof(Net.Socket);
it('send pong on ping', function(done) {
var peer = new Peer({host: 'localhost'});
var pingMessage = messages.Ping();
peer.sendMessage = function(message) {
message.command.should.equal('pong');
message.nonce.should.equal(pingMessage.nonce);
done();
};
peer.emit('ping', pingMessage);
});
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);
it('relay error from socket', function(done) {
var peer = new Peer({host: 'localhost'});
var socket = new EventEmitter();
socket.connect = sinon.spy();
socket.destroy = sinon.spy();
peer._getSocket = function() {
return socket;
};
var error = new Error('error');
peer.on('error', function(err) {
err.should.equal(error);
done();
peer.should.equal(peer2);
});
peer.connect();
peer.socket.emit('error', error);
});
it('will not disconnect twice on disconnect and error', function(done) {
var peer = new Peer({host: 'localhost'});
var socket = new EventEmitter();
socket.connect = sinon.stub();
socket.destroy = sinon.stub();
peer._getSocket = function() {
return socket;
};
peer.on('error', sinon.stub());
peer.connect();
var called = 0;
peer.on('disconnect', function() {
called++;
called.should.not.be.above(1);
done();
});
peer.disconnect();
peer.socket.emit('error', new Error('fake error'));
});
it('disconnect with max buffer length', function(done) {
var peer = new Peer({host: 'localhost'});
var socket = new EventEmitter();
socket.connect = sinon.spy();
peer._getSocket = function() {
return socket;
};
peer.disconnect = function() {
done();
};
peer.connect();
var buffer = new Buffer(Array(Peer.MAX_RECEIVE_BUFFER + 1));
peer.socket.emit('data', buffer);
});
it('should send version on version if not already sent', function(done) {
var peer = new Peer({host:'localhost'});
var commands = {};
peer.sendMessage = function(message) {
commands[message.command] = true;
if (commands.verack && commands.version) {
done();
}
};
peer.socket = {};
peer.emit('version', {
version: 'version',
subversion: 'subversion',
startHeight: 'startHeight'
});
});
it('should not send version on version if already sent', function(done) {
var peer = new Peer({host:'localhost'});
peer.versionSent = true;
var commands = {};
peer.sendMessage = function(message) {
message.command.should.not.equal('version');
done();
};
peer.socket = {};
peer.emit('version', {
version: 'version',
subversion: 'subversion',
startHeight: 'startHeight'
});
});
it('relay set properly', function() {
var peer = new Peer({host: 'localhost'});
peer.relay.should.equal(true);
var peer2 = new Peer({host: 'localhost', relay: false});
peer2.relay.should.equal(false);
var peer3 = new Peer({host: 'localhost', relay: true});
peer3.relay.should.equal(true);
});
it('relay setting respected', function() {
[true,false].forEach(function(relay) {
var peer = new Peer({host: 'localhost', relay: relay});
var peerSendMessageStub = sinon.stub(Peer.prototype, 'sendMessage', function(message) {
message.relay.should.equal(relay);
});
peer._sendVersion();
peerSendMessageStub.restore();
});
});
}
});

View File

@ -1,565 +1,105 @@
'use strict';
var chai = require('chai');
if (typeof(window) === 'undefined'){
/* jshint unused: false */
var should = chai.should();
var expect = chai.expect;
// Node.js Tests
var bitcore = require('bitcore-lib');
var P2P = require('../');
var Peer = P2P.Peer;
var MessagesData = require('./data/messages');
var Messages = P2P.Messages;
var messages = new Messages();
var Pool = P2P.Pool;
var Networks = bitcore.Networks;
var chai = require('chai');
var dns = require('dns');
var sinon = require('sinon');
var net = require('net');
/* jshint unused: false */
var should = chai.should();
var expect = chai.expect;
function getPayloadBuffer(messageBuffer) {
return new Buffer(messageBuffer.slice(48), 'hex');
}
var bitcore = require('bitcore');
var P2P = require('../');
var Peer = P2P.Peer;
var MessagesData = require('./data/messages');
var Messages = P2P.Messages;
var Pool = P2P.Pool;
var Networks = bitcore.Networks;
describe('Pool', function() {
var dns = require('dns');
var sinon = require('sinon');
it('create instance', function() {
var pool = new Pool();
should.exist(pool.network);
expect(pool.network).to.satisfy(function(network) {
return network === Networks.testnet || network === Networks.livenet;
});
});
describe('Pool', function() {
it('create instance setting the network', function() {
var pool = new Pool({network: Networks.testnet});
pool.network.should.equal(Networks.testnet);
});
it('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({network: Networks.livenet});
pool.connect();
pool.disconnect();
pool._addrs.length.should.equal(3);
stub.restore();
});
it('optionally connect without dns seeds', function() {
sinon.stub(Peer.prototype, 'connect', function() {
this.socket = {
destroy: sinon.stub()
};
});
var stub = sinon.stub(dns, 'resolve', function(seed, callback) {
throw new Error('DNS should not be called');
});
var options = {
network: Networks.livenet,
dnsSeed: false,
maxSize: 1,
addrs: [
{
ip: {
v4: 'localhost'
}
},
{
ip: {
v4: 'localhost2'
}
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;
}
]
};
var pool = new Pool(options);
pool.connect();
pool.disconnect();
pool._addrs.length.should.equal(2);
stub.restore();
Peer.prototype.connect.restore();
});
it('will add addrs via options argument', function() {
var options = {
network: Networks.livenet,
dnsSeed: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
};
var pool = new Pool(options);
pool._addrs.length.should.equal(1);
});
it('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 payloadBuffer = getPayloadBuffer(MessagesData.addr.message);
var message = messages._buildFromBuffer('addr', payloadBuffer);
this.emit(message.command, message);
});
var options = {
network: Networks.testnet,
dnsSeed: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
};
var pool = new Pool(options);
// 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();
});
it('can optionally not listen to new addrs messages', 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 payloadBuffer = getPayloadBuffer(MessagesData.addr.message);
var message = messages._buildFromBuffer('addr', payloadBuffer);
this.emit(message.command, message);
});
var options = {
network: Networks.testnet,
dnsSeed: false,
listenAddr: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
};
var pool = new Pool(options);
// listen for the event
pool.on('peeraddr', function(peer, message) {
pool._addrs.length.should.equal(1);
// 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();
});
it('propagate connect, ready, and disconnect peer events', function(done) {
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function() {
this.emit('connect', this, {});
this.emit('ready');
});
var peerDisconnectStub = sinon.stub(Peer.prototype, 'disconnect', function() {
this.emit('disconnect', this, {});
});
var poolRemoveStub = sinon.stub(Pool.prototype, '_removeConnectedPeer', function() {});
var pool = new Pool({
dnsSeed: false,
addrs: [
{
ip: {
v4: 'localhost'
}
}
]
});
var poolDisconnectStub;
pool.on('peerconnect', function(peer, addr) {
pool.on('peerready', function(peer, addr) {
// disconnect when the peer is ready
poolDisconnectStub = sinon.stub(Pool.prototype, 'disconnect', function() {
peer.disconnect();
});
pool.disconnect();
});
});
pool.on('peerdisconnect', function(peer, addr) {
// Restore stubs
peerConnectStub.restore();
peerDisconnectStub.restore();
poolDisconnectStub.restore();
poolRemoveStub.restore();
// done
done();
});
pool.connect();
});
it('propagate relay property to peers', function(done) {
var count = 0;
var peerConnectStub = sinon.stub(Peer.prototype, 'connect', function() {
this.emit('connect', this, {});
});
[true, false].forEach(function(relay) {
var pool = new Pool({relay: relay, dnsSeed: false});
pool._addAddr({ ip: { v4: 'localhost' } });
pool.on('peerconnect', function(peer, addr) {
peer.relay.should.equal(relay);
pool.disconnect();
if(++count == 2) {
done();
}
});
pool.connect();
});
peerConnectStub.restore();
});
it('output the console correctly', function() {
var pool = new Pool();
pool.inspect().should.equal('<Pool network: livenet, connected: 0, available: 0>');
});
it('emit seederrors with error', function(done) {
var dnsStub = sinon.stub(dns, 'resolve', function(seed, callback) {
callback(new Error('A DNS error'));
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool.once('seederror', function(error) {
should.exist(error);
pool.disconnect();
dnsStub.restore();
done();
});
pool.connect();
});
it('emit seederrors with notfound', function(done) {
var dnsStub = sinon.stub(dns, 'resolve', function(seed, callback) {
callback(null, []);
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool.once('seederror', function(error) {
should.exist(error);
pool.disconnect();
dnsStub.restore();
done();
});
pool.connect();
});
it('send message to all peers', function(done) {
var message = 'message';
sinon.stub(Peer.prototype, 'connect', function() {
this.socket = {
destroy: sinon.stub()
};
var self = this;
process.nextTick(function() {
self.emit('ready');
});
});
sinon.stub(Peer.prototype, 'sendMessage', function(message) {
message.should.equal(message);
Peer.prototype.connect.restore();
Peer.prototype.sendMessage.restore();
pool.disconnect();
done();
});
var pool = new Pool({
network: Networks.livenet,
maxSize: 1,
dnsSeed: false,
addrs: [
{
ip:{
v4: 'localhost'
}
}
]
});
pool.on('peerready', function() {
pool.sendMessage(message);
});
pool.connect();
});
it('not call _fillConnections if keepalive is false on seed', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._fillConnections = sinon.stub();
pool.keepalive = false;
pool.on('seed', function() {
process.nextTick(function() {
pool._fillConnections.called.should.equal(false);
done();
});
});
pool.emit('seed', []);
});
it('keep original time for handling peeraddr messages', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
var now = new Date();
pool._addAddr = function(addr) {
addr.time.should.equal(now);
done();
};
pool.emit('peeraddr', {}, {
addresses: [
{
time: now
}
]
});
});
it('replace time if time is invalid on peeraddr messages', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
var future = new Date(new Date().getTime() + 10 * 70 * 1000);
var past = new Date(new Date().getTime() - 4 * 24 * 60 * 60 * 1000); // 4 days ago
pool._addAddr = function(addr) {
addr.time.should.not.equal(future);
addr.time.getTime().should.be.below(past.getTime());
done();
};
pool.emit('peeraddr', {}, {
addresses: [
{
time: future
}
]
});
});
describe('#_removeConnectedPeer', function() {
it('disconnect peer if peer status is not disconnected', function(done) {
var pool = new Pool({network: Networks.livenet, maxSize: 1});
/* jshint sub: true */
pool._connectedPeers['hash'] = {
status: Peer.STATUS.CONNECTED,
disconnect: function() {
done();
}
};
pool._removeConnectedPeer({
hash: 'hash'
});
});
});
describe('#_connectPeer', function() {
it('connect ipv6 peer', function() {
var connectStub = sinon.stub(Peer.prototype, 'connect');
var pool = new Pool({network: Networks.livenet, maxSize: 1});
var ipv6 = '2001:0db8:85a3:0042:1000:8a2e:0370:7334';
pool._addPeerEventHandlers = sinon.stub();
pool._connectPeer({
ip: {
v6: ipv6
},
hash: 'hash'
});
/* jshint sub: true */
should.exist(pool._connectedPeers['hash']);
pool._addPeerEventHandlers.calledOnce.should.equal(true);
Peer.prototype.connect.calledOnce.should.equal(true);
connectStub.restore();
});
it('will pass network to peer', function() {
var connectStub = sinon.stub(Peer.prototype, 'connect');
var pool = new Pool({network: Networks.testnet, maxSize: 1});
var ipv6 = '2001:0db8:85a3:0042:1000:8a2e:0370:7334';
pool._addPeerEventHandlers = sinon.stub();
pool._connectPeer({
ip: {
v6: ipv6
},
hash: 'hash'
});
/* jshint sub: true */
pool._connectedPeers['hash'].network.should.equal(pool.network);
connectStub.restore();
});
});
describe('#_addConnectedPeer', function() {
it('should add a peer', function() {
/* jshint sub: true */
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addPeerEventHandlers = sinon.stub();
pool._addConnectedPeer({
on: sinon.stub()
}, {hash: 'hash'});
should.exist(pool._connectedPeers['hash']);
pool._addPeerEventHandlers.calledOnce.should.equal(true);
});
it('should not already added peer', function() {
/* jshint sub: true */
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addPeerEventHandlers = sinon.stub();
pool._connectedPeers['hash'] = {};
pool._addConnectedPeer({
on: sinon.stub()
}, {hash: 'hash'});
should.exist(pool._connectedPeers['hash']);
pool._addPeerEventHandlers.calledOnce.should.equal(false);
});
it('will pass network to peer', function() {
/* jshint sub: true */
var pool = new Pool({network: Networks.testnet, maxSize: 1});
pool._addConnectedPeer({
on: sinon.stub()
}, {hash: 'hash'});
should.exist(pool._connectedPeers['hash']);
pool._connectedPeers['hash'].network.should.equal(pool.network);
});
});
describe('#listen', function() {
it('create a server', function(done) {
var netStub = sinon.stub(net, 'createServer', function() {
return {
listen: function() {
netStub.restore();
done();
}
};
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool.listen();
});
it('should handle an ipv6 connection', function(done) {
var ipv6 = '2001:0db8:85a3:0042:1000:8a2e:0370:7334';
sinon.stub(net, 'createServer', function(callback) {
callback({
remoteAddress: ipv6
});
return {
listen: sinon.stub()
};
});
sinon.stub(net, 'isIPv6', function() {
return true;
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addAddr = function(addr) {
should.exist(addr.ip.v6);
addr.ip.v6.should.equal(ipv6);
net.isIPv6.restore();
net.createServer.restore();
done();
};
pool._addConnectedPeer = sinon.stub();
pool.listen();
});
it('include port for addr on incoming connections', function(done) {
var port = 12345;
sinon.stub(net, 'createServer', function(callback) {
callback({
remoteAddress: '127.0.0.1',
remotePort: port
});
return {
listen: sinon.stub()
};
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addAddr = function(addr) {
should.exist(addr.port);
addr.port.should.equal(port);
net.createServer.restore();
done();
};
pool._addConnectedPeer = sinon.stub();
pool.listen();
});
it('should handle an ipv4 connection', function(done) {
var ipv4 = '127.0.0.1';
sinon.stub(net, 'createServer', function(callback) {
callback({
remoteAddress: ipv4
});
return {
listen: sinon.stub()
};
});
sinon.stub(net, 'isIPv6', function() {
return false;
});
var pool = new Pool({network: Networks.livenet, maxSize: 1});
pool._addAddr = function(addr) {
should.exist(addr.ip.v4);
addr.ip.v4.should.equal(ipv4);
net.isIPv6.restore();
net.createServer.restore();
done();
};
pool._addConnectedPeer = sinon.stub();
pool.listen();
});
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();
});
});
});
}