Compare commits
No commits in common. "master" and "yemel-readme" have entirely different histories.
master
...
yemel-read
@ -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
|
||||
|
||||
16
README.md
16
README.md
@ -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
|
||||
=======
|
||||
|
||||
[](https://www.npmjs.org/package/bitcore-p2p)
|
||||
[](https://travis-ci.org/bitpay/bitcore-p2p)
|
||||
[](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
42
bower.json
Normal 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"
|
||||
]
|
||||
}
|
||||
@ -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();
|
||||
```
|
||||
|
||||
```
|
||||
@ -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');
|
||||
```
|
||||
|
||||
35
docs/peer.md
35
docs/peer.md
@ -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();
|
||||
|
||||
40
docs/pool.md
40
docs/pool.md
@ -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.
|
||||
|
||||
2
index.js
2
index.js
@ -1,4 +1,4 @@
|
||||
var bitcore = require('flocore-lib');
|
||||
var bitcore = require('bitcore');
|
||||
bitcore.P2P = require('./lib');
|
||||
|
||||
module.exports = bitcore.P2P;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
/**
|
||||
* @namespace P2P
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
Inventory: require('./inventory'),
|
||||
BloomFilter: require('./bloomfilter'),
|
||||
Messages: require('./messages'),
|
||||
Peer: require('./peer'),
|
||||
Pool: require('./pool')
|
||||
|
||||
121
lib/inventory.js
121
lib/inventory.js
@ -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
713
lib/messages.js
Normal 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;
|
||||
};
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
138
lib/peer.js
138
lib/peer.js
@ -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() {
|
||||
|
||||
184
lib/pool.js
184
lib/pool.js
@ -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
2446
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@ -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"
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
@ -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
18
test/index.html
Normal 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>
|
||||
@ -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
355
test/messages.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@ -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]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
176
test/peer.js
176
test/peer.js
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
638
test/pool.js
638
test/pool.js
@ -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();
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user