Merge pull request #48 from braydonf/feature/extensibility
Extensible Messages
This commit is contained in:
commit
487b5f0bb7
11
README.md
11
README.md
@ -1,12 +1,12 @@
|
||||
<img src="http://bitcore.io/css/images/module-p2p.png" alt="bitcore payment protocol" height="35">
|
||||
P2P Networking capabilities for bitcore
|
||||
<img src="http://bitcore.io/css/images/bitcore-p2p.svg" alt="bitcore payment protocol" height="35" width="102">
|
||||
Bitcore P2P
|
||||
=======
|
||||
|
||||
[](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 support for connecting to the bitcoin p2p network in [Node.js](http://nodejs.org/).
|
||||
`bitcore-p2p` adds [Bitcoin protocol](https://en.bitcoin.it/wiki/Protocol_documentation) support for Bitcore.
|
||||
|
||||
See [the main bitcore repo](https://github.com/bitpay/bitcore) for more information.
|
||||
|
||||
@ -15,12 +15,12 @@ See [the main bitcore repo](https://github.com/bitpay/bitcore) for more informat
|
||||
```sh
|
||||
npm 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:
|
||||
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.
|
||||
|
||||
```javascript
|
||||
var Peer = require('bitcore-p2p').Peer;
|
||||
|
||||
var peer = new Peer('0.0.0.0');
|
||||
var peer = new Peer({host: '127.0.0.1'});
|
||||
|
||||
peer.on('ready', function() {
|
||||
// peer info
|
||||
@ -55,4 +55,3 @@ See [CONTRIBUTING.md](https://github.com/bitpay/bitcore) on the main bitcore rep
|
||||
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.
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
---
|
||||
title: Messages
|
||||
description: A superclass for the messages of the bitcoin network
|
||||
---
|
||||
@ -5,6 +6,42 @@ description: A superclass for the messages of the bitcoin network
|
||||
|
||||
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 custom network magic:
|
||||
|
||||
```javascript
|
||||
var messages = new Messages({magicNumber: 0x0b120907});
|
||||
```
|
||||
|
||||
## List of Messages
|
||||
|
||||
### Version
|
||||
|
||||
17
docs/peer.md
17
docs/peer.md
@ -1,3 +1,4 @@
|
||||
---
|
||||
title: Peer
|
||||
description: The Peer class provides a simple interface for connecting to a node in the bitcoin network.
|
||||
---
|
||||
@ -15,15 +16,15 @@ The code to create a new peer looks like this:
|
||||
var Peer = require('bitcore-p2p').Peer;
|
||||
|
||||
// default port
|
||||
var livenetPeer = new Peer('5.9.85.34');
|
||||
var testnetPeer = new Peer('5.9.85.34', bitcore.testnet);
|
||||
var livenetPeer = new Peer({host: '5.9.85.34'});
|
||||
var testnetPeer = new Peer({host: '5.9.85.34', network: Networks.testnet});
|
||||
|
||||
// custom port
|
||||
var livenetPeer = new Peer('5.9.85.34', 8334);
|
||||
var testnetPeer = new Peer('5.9.85.34', 18334, bitcore.testnet);
|
||||
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});
|
||||
|
||||
// use sock5 proxy (Tor)
|
||||
var peer = new Peer('5.9.85.34').setProxy('localhost', 9050);
|
||||
var peer = new Peer({host: '5.9.85.34'}).setProxy('localhost', 9050);
|
||||
```
|
||||
|
||||
## States
|
||||
@ -40,7 +41,7 @@ You can subscribe to the change of those states as follows:
|
||||
```javascript
|
||||
var Peer = require('bitcore-p2p').Peer;
|
||||
|
||||
var peer = new Peer('5.9.85.34');
|
||||
var peer = new Peer({host: '5.9.85.34'});
|
||||
|
||||
peer.on('ready', function() {
|
||||
// peer info
|
||||
@ -60,7 +61,7 @@ Once connected, a peer instance can send and receive messages. Every time a mess
|
||||
|
||||
```javascript
|
||||
var Peer = require('bitcore-p2p').Peer;
|
||||
var peer = new Peer('5.9.85.34');
|
||||
var peer = new Peer({host: '5.9.85.34'});
|
||||
|
||||
// handle events
|
||||
peer.on('inv', function(message) {
|
||||
@ -88,7 +89,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('5.9.85.34');
|
||||
var peer = new Peer({host: '5.9.85.34'});
|
||||
|
||||
peer.on('ready', function() {
|
||||
var message = new Messages.GetAddresses();
|
||||
|
||||
17
docs/pool.md
17
docs/pool.md
@ -1,3 +1,4 @@
|
||||
---
|
||||
title: Pool
|
||||
description: A simple interface to create and maintain a set of connections to bitcoin nodes.
|
||||
---
|
||||
@ -12,7 +13,7 @@ The quickest way to get connected is to run the following:
|
||||
var Pool = require('bitcore-p2p').Pool;
|
||||
var Networks = require('bitcore').Networks;
|
||||
|
||||
var pool = new Pool(Networks.livenet);
|
||||
var pool = new Pool({network: Networks.livenet});
|
||||
|
||||
// connect to the network
|
||||
pool.connect();
|
||||
@ -35,7 +36,8 @@ By default, peers will be added via DNS discovery and as peers are announced in
|
||||
|
||||
```javascript
|
||||
|
||||
var pool = new Pool(Networks.livenet, {
|
||||
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
|
||||
@ -50,3 +52,14 @@ var pool = new Pool(Networks.livenet, {
|
||||
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.
|
||||
@ -16,6 +16,8 @@ var Peer = p2p.Peer;
|
||||
var Pool = p2p.Pool;
|
||||
var Networks = bitcore.Networks;
|
||||
var Messages = p2p.Messages;
|
||||
var Inventory = p2p.Inventory;
|
||||
var messages = new Messages();
|
||||
var Block = bitcore.Block;
|
||||
var Transaction = bitcore.Transaction;
|
||||
|
||||
@ -45,7 +47,7 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
|
||||
m.services.toString().should.equal('1');
|
||||
Math.abs(new Date() - m.timestamp).should.be.below(10000); // less than 10 seconds of time difference
|
||||
m.nonce.length.should.equal(8);
|
||||
m.start_height.should.be.above(300000);
|
||||
m.startHeight.should.be.above(300000);
|
||||
cb();
|
||||
});
|
||||
peer.once('verack', function(m) {
|
||||
@ -92,7 +94,7 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
|
||||
});
|
||||
cb();
|
||||
});
|
||||
var message = new Messages.GetAddresses();
|
||||
var message = messages.GetAddr();
|
||||
peer.sendMessage(message);
|
||||
});
|
||||
});
|
||||
@ -106,25 +108,24 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
|
||||
should.exist(message.transaction);
|
||||
cb();
|
||||
});
|
||||
peer.once('inv', function(m) {
|
||||
var message = new Messages.GetData(m.inventory);
|
||||
peer.sendMessage(message);
|
||||
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 = Messages.Inventory.TYPE.TX;
|
||||
var type = Inventory.TYPE.TX;
|
||||
var inv = [{
|
||||
type: type,
|
||||
typeName: Messages.Inventory.TYPE_NAME[type],
|
||||
hash: Random.getRandomBuffer(32) // needs to be random for repeatability
|
||||
hash: new Buffer(Random.getRandomBuffer(32)) // needs to be random for repeatability
|
||||
}];
|
||||
peer.once('getdata', function(message) {
|
||||
message.inventory.should.deep.equal(inv);
|
||||
cb();
|
||||
});
|
||||
var message = new Messages.Inventory(inv);
|
||||
var message = messages.Inventory(inv);
|
||||
message.inventory[0].hash.length.should.equal(32);
|
||||
peer.sendMessage(message);
|
||||
});
|
||||
@ -135,20 +136,22 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
|
||||
(message.block instanceof Block).should.equal(true);
|
||||
cb();
|
||||
});
|
||||
var message = Messages.GetData.forBlock(blockHash[network.name]);
|
||||
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);
|
||||
var expected = messages.NotFound.forTransaction(fakeHash);
|
||||
peer.once('notfound', function(message) {
|
||||
(message instanceof Messages.NotFound).should.equal(true);
|
||||
message.should.deep.equal(expected);
|
||||
(message instanceof messages.NotFound).should.equal(true);
|
||||
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);
|
||||
var message = messages.GetData.forTransaction(fakeHash);
|
||||
peer.sendMessage(message);
|
||||
});
|
||||
});
|
||||
@ -157,47 +160,49 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
|
||||
it('gets headers', function(cb) {
|
||||
connect(function(peer) {
|
||||
peer.once('headers', function(message) {
|
||||
(message instanceof Messages.Headers).should.equal(true);
|
||||
(message instanceof messages.Headers).should.equal(true);
|
||||
message.headers.length.should.equal(3);
|
||||
cb();
|
||||
});
|
||||
var message = new Messages.GetHeaders(from, stop);
|
||||
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 instanceof Messages.Inventory).should.equal(true);
|
||||
message.command.should.equal('inv');
|
||||
if (message.inventory.length === 2) {
|
||||
message.inventory[0].type.should.equal(Messages.Inventory.TYPE.BLOCK);
|
||||
message.inventory[0].type.should.equal(Inventory.TYPE.BLOCK);
|
||||
cb();
|
||||
}
|
||||
});
|
||||
var message = new Messages.GetBlocks(from, stop);
|
||||
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 instanceof Messages.GetData).should.equal(true);
|
||||
message.should.deep.equal(expected);
|
||||
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 = Random.getRandomBuffer(32); // needs to be random for repeatability
|
||||
var expected = Messages.GetData.forBlock(randomHash);
|
||||
var message = Messages.Inventory.forBlock(randomHash);
|
||||
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 = Random.getRandomBuffer(32); // needs to be random for repeatability
|
||||
var expected = Messages.GetData.forTransaction(randomHash);
|
||||
var message = Messages.Inventory.forTransaction(randomHash);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,34 +4,29 @@ var bitcore = require('bitcore');
|
||||
var BloomFilter = require('bloom-filter');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
|
||||
/**
|
||||
* 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 data = parser.readVarLengthBuffer();
|
||||
$.checkState(data.length <= BloomFilter.MAX_BLOOM_FILTER_SIZE,
|
||||
'Filter data must be <= MAX_BLOOM_FILTER_SIZE bytes');
|
||||
var nHashFuncs = parser.readUInt32LE();
|
||||
$.checkState(nHashFuncs <= BloomFilter.MAX_HASH_FUNCS,
|
||||
'Filter nHashFuncs must be <= MAX_HASH_FUNCS');
|
||||
var nTweak = parser.readUInt32LE();
|
||||
var nFlags = parser.readUInt8();
|
||||
|
||||
var vData = [];
|
||||
var dataParser = new BufferReader(data);
|
||||
for(var i = 0; i < data.length; i++) {
|
||||
vData.push(dataParser.readUInt8());
|
||||
var length = parser.readUInt8();
|
||||
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);
|
||||
};
|
||||
|
||||
return new BloomFilter({
|
||||
vData: vData,
|
||||
nHashFuncs: nHashFuncs,
|
||||
nTweak: nTweak,
|
||||
nFlags: nFlags
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
BloomFilter.prototype.toBuffer = function toBuffer() {
|
||||
var bw = new BufferWriter();
|
||||
bw.writeVarintNum(this.vData.length);
|
||||
|
||||
23
lib/buffers.js
Normal file
23
lib/buffers.js
Normal file
@ -0,0 +1,23 @@
|
||||
'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;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
/**
|
||||
* @namespace P2P
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
Inventory: require('./inventory'),
|
||||
BloomFilter: require('./bloomfilter'),
|
||||
Messages: require('./messages'),
|
||||
Peer: require('./peer'),
|
||||
Pool: require('./pool'),
|
||||
BloomFilter: require('./bloomfilter')
|
||||
Pool: require('./pool')
|
||||
};
|
||||
|
||||
121
lib/inventory.js
Normal file
121
lib/inventory.js
Normal file
@ -0,0 +1,121 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
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} - An instance of BufferWriter
|
||||
*/
|
||||
Inventory.prototype.toBufferWriter = function(bw) {
|
||||
bw.writeUInt32LE(this.type);
|
||||
bw.write(this.hash);
|
||||
return bw;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Buffer} - Seralized 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} - 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;
|
||||
979
lib/messages.js
979
lib/messages.js
@ -1,979 +0,0 @@
|
||||
'use strict';
|
||||
/**
|
||||
* @namespace P2P.Message
|
||||
*/
|
||||
/* jshint curly: false */
|
||||
|
||||
var Buffers = require('buffers');
|
||||
var Put = require('bufferput');
|
||||
var util = require('util');
|
||||
var BloomFilter = require('./bloomfilter');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var BlockHeaderModel = bitcore.BlockHeader;
|
||||
var BlockModel = bitcore.Block;
|
||||
var MerkleBlockModel = bitcore.MerkleBlock;
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var Hash = bitcore.crypto.Hash;
|
||||
var Random = bitcore.crypto.Random;
|
||||
var TransactionModel = bitcore.Transaction;
|
||||
|
||||
var CONNECTION_NONCE = Random.getPseudoRandomBuffer(8);
|
||||
var PROTOCOL_VERSION = 70000;
|
||||
|
||||
/**
|
||||
* @desc Internal function that discards data until another message is found.
|
||||
* @name P2P.Message#discardUntilNextMessage
|
||||
*/
|
||||
var discardUntilNextMessage = function(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 this knows how to parse and serialize itself.
|
||||
* Concrete subclasses should implement {fromBuffer} and {getPayload} methods.
|
||||
* @name P2P.Message
|
||||
*/
|
||||
function Message() {}
|
||||
|
||||
/**
|
||||
* @value
|
||||
* @name P2P.Message.COMMANDS
|
||||
*/
|
||||
Message.COMMANDS = {};
|
||||
|
||||
var PAYLOAD_START = 16;
|
||||
/**
|
||||
* Static helper for consuming a data buffer until the next message.
|
||||
*
|
||||
* @name P2P.Message#parseMessage
|
||||
* @param{Network} network - the network object
|
||||
* @param{Buffer} dataBuffer - the buffer to read from
|
||||
* @returns{Message|undefined} A message or undefined if there is nothing to read.
|
||||
*/
|
||||
var parseMessage = function(network, dataBuffer) {
|
||||
$.checkArgument(network);
|
||||
$.checkArgument(dataBuffer);
|
||||
/* jshint maxstatements: 18 */
|
||||
if (dataBuffer.length < 20) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search the next magic number
|
||||
if (!discardUntilNextMessage(network, dataBuffer)) return;
|
||||
|
||||
var payloadLen = (dataBuffer.get(PAYLOAD_START)) +
|
||||
(dataBuffer.get(PAYLOAD_START + 1) << 8) +
|
||||
(dataBuffer.get(PAYLOAD_START + 2) << 16) +
|
||||
(dataBuffer.get(PAYLOAD_START + 3) << 24);
|
||||
|
||||
var messageLength = 24 + payloadLen;
|
||||
if (dataBuffer.length < messageLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
var command = dataBuffer.slice(4, 16).toString('ascii').replace(/\0+$/, '');
|
||||
var payload = dataBuffer.slice(24, messageLength);
|
||||
var checksum = dataBuffer.slice(20, 24);
|
||||
|
||||
var checksumConfirm = Hash.sha256sha256(payload).slice(0, 4);
|
||||
if (!BufferUtil.equals(checksumConfirm, checksum)) {
|
||||
dataBuffer.skip(messageLength);
|
||||
return;
|
||||
}
|
||||
|
||||
dataBuffer.skip(messageLength);
|
||||
return Message.buildMessage(command, payload);
|
||||
};
|
||||
|
||||
module.exports.parseMessage = parseMessage;
|
||||
|
||||
|
||||
/**
|
||||
* Look up a message type by command name and instantiate the correct Message
|
||||
* @name P2P.Message#buildMessage
|
||||
*/
|
||||
Message.buildMessage = function(command, payload) {
|
||||
var CommandClass = Message.COMMANDS[command];
|
||||
$.checkState(CommandClass, 'Unsupported message command: ' + command);
|
||||
return new CommandClass().fromBuffer(payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$.checkArgument(network, 'Must specify network for serialization');
|
||||
var commandBuf = new Buffer(this.command, 'ascii');
|
||||
$.checkState(commandBuf.length <= 12, 'Command name too long');
|
||||
var magic = network.networkMagic;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
* check if parser has no more extra data
|
||||
*/
|
||||
Message.prototype._checkFinished = function(parser) {
|
||||
$.checkState(parser.finished(), 'data still available after parsing ' + this.constructor.name);
|
||||
};
|
||||
|
||||
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, relay) {
|
||||
var packageInfo = require('../package.json');
|
||||
this.command = 'version';
|
||||
this.version = PROTOCOL_VERSION;
|
||||
this.subversion = subversion || '/bitcore:' + packageInfo.version + '/';
|
||||
this.nonce = nonce || CONNECTION_NONCE;
|
||||
this.relay = relay === false ? false : true;
|
||||
}
|
||||
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 = new Date(parser.readUInt64LEBN().toNumber() * 1000);
|
||||
/**
|
||||
* @type {object}
|
||||
* @desc IPv4/6 address of the interface used to connect to this peer
|
||||
*/
|
||||
var me_services = parser.readUInt64LEBN();
|
||||
var me_ip = Addresses.parseIP(parser);
|
||||
var me_port = parser.readUInt16BE();
|
||||
this.addr_me = {
|
||||
services: me_services,
|
||||
ip: me_ip,
|
||||
port: me_port
|
||||
};
|
||||
/**
|
||||
* @type {object}
|
||||
* @desc IPv4/6 address of the peer
|
||||
*/
|
||||
var your_services = parser.readUInt64LEBN();
|
||||
var your_ip = Addresses.parseIP(parser);
|
||||
var your_port = parser.readUInt16BE();
|
||||
this.addr_you = {
|
||||
services: your_services,
|
||||
ip: your_ip,
|
||||
port: your_port
|
||||
};
|
||||
/**
|
||||
* @type {Buffer}
|
||||
* @desc A random number
|
||||
*/
|
||||
this.nonce = parser.read(8);
|
||||
/**
|
||||
* @desc The node's user agent / subversion
|
||||
* @type {string}
|
||||
*/
|
||||
this.subversion = parser.readVarLengthBuffer().toString();
|
||||
/**
|
||||
* @desc The height of the last block accepted in the blockchain by this peer
|
||||
* @type {number}
|
||||
*/
|
||||
this.start_height = parser.readUInt32LE();
|
||||
|
||||
/**
|
||||
* @desc Whether the remote peer should announce relayed transactions or not, see BIP 0037
|
||||
* @type {boolean}
|
||||
*/
|
||||
// This field is optional, so should not always be read
|
||||
if(parser.finished()) {
|
||||
this.relay = true;
|
||||
} else {
|
||||
this.relay = !!parser.readUInt8();
|
||||
}
|
||||
|
||||
this._checkFinished(parser);
|
||||
return this;
|
||||
};
|
||||
|
||||
Version.prototype.getPayload = function() {
|
||||
var put = new Put();
|
||||
put.word32le(this.version);
|
||||
put.word64le(1); // services
|
||||
put.word64le(Math.round(new Date().getTime() / 1000)); // timestamp
|
||||
Addresses.writeAddr(this.addr_me, put);
|
||||
Addresses.writeAddr(this.addr_you, put);
|
||||
put.put(this.nonce);
|
||||
put.varint(this.subversion.length);
|
||||
put.put(new Buffer(this.subversion, 'ascii'));
|
||||
put.word32le(this.start_height);
|
||||
put.word8(this.relay);
|
||||
|
||||
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) {
|
||||
$.checkArgument(_.isUndefined(inventory) ||
|
||||
_.isArray(inventory), 'Inventory for ' +
|
||||
this.constructor.name + ' must be an array of objects');
|
||||
$.checkArgument(_.isUndefined(inventory) ||
|
||||
inventory.length === 0 ||
|
||||
(inventory[0] && !_.isUndefined(inventory[0].type) && !_.isUndefined(inventory[0].hash)),
|
||||
'Inventory for ' + this.constructor.name + ' must be an array of objects');
|
||||
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);
|
||||
|
||||
// https://en.bitcoin.it/wiki/Protocol_specification#Inventory_Vectors
|
||||
Inventory.TYPE = {};
|
||||
Inventory.TYPE.ERROR = 0;
|
||||
Inventory.TYPE.TX = 1;
|
||||
Inventory.TYPE.BLOCK = 2;
|
||||
Inventory.TYPE.FILTERED_BLOCK = 3;
|
||||
Inventory.TYPE_NAME = [
|
||||
'ERROR',
|
||||
'TX',
|
||||
'BLOCK',
|
||||
'FILTERED_BLOCK'
|
||||
];
|
||||
|
||||
Inventory.forItem = function(type, hash) {
|
||||
$.checkArgument(hash);
|
||||
if (_.isString(hash)) {
|
||||
hash = new Buffer(hash, 'hex');
|
||||
hash = BufferUtil.reverse(hash);
|
||||
}
|
||||
return {
|
||||
type: type,
|
||||
typeName: Inventory.TYPE_NAME[type],
|
||||
hash: hash
|
||||
};
|
||||
};
|
||||
|
||||
Inventory.prototype.fromBuffer = function(payload) {
|
||||
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(Inventory.forItem(type, hash));
|
||||
}
|
||||
|
||||
this._checkFinished(parser);
|
||||
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();
|
||||
};
|
||||
|
||||
var creatorForItem = function(clazz, type) {
|
||||
return function(hash) {
|
||||
return new clazz([Inventory.forItem(type, hash)]);
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.Inventory = Message.COMMANDS.inv = Inventory;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* (from bitcoin's protocol spec)
|
||||
*
|
||||
* @name P2P.Message.NotFound
|
||||
* @param{Array} inventory - not found elements
|
||||
*/
|
||||
function NotFound(inventory) {
|
||||
Inventory.call(this, inventory);
|
||||
this.command = 'notfound';
|
||||
}
|
||||
|
||||
util.inherits(NotFound, Inventory);
|
||||
module.exports.NotFound = Message.COMMANDS.notfound = NotFound;
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* (from bitcoin's protocol spec)
|
||||
*
|
||||
* @name P2P.Message.GetData
|
||||
* @param{Array} inventory - requested elements
|
||||
*/
|
||||
function GetData(inventory) {
|
||||
Inventory.call(this, inventory);
|
||||
this.command = 'getdata';
|
||||
this.inventory = inventory || [];
|
||||
}
|
||||
|
||||
|
||||
util.inherits(GetData, Inventory);
|
||||
module.exports.GetData = Message.COMMANDS.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) {
|
||||
var parser = new BufferReader(payload);
|
||||
this.nonce = parser.read(8);
|
||||
|
||||
this._checkFinished(parser);
|
||||
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.writeAddr = function(addr, put) {
|
||||
if (_.isUndefined(addr)) {
|
||||
put.pad(26);
|
||||
return;
|
||||
}
|
||||
put.word64le(addr.services);
|
||||
Addresses.writeIP(addr.ip, put);
|
||||
put.word16be(addr.port);
|
||||
};
|
||||
|
||||
Addresses.writeIP = function(ip, put) {
|
||||
$.checkArgument(ip.v6, 'Need ipv6 to write IP');
|
||||
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];
|
||||
put.put(word);
|
||||
}
|
||||
};
|
||||
|
||||
// http://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
|
||||
Addresses.parseIP = function(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
|
||||
};
|
||||
};
|
||||
|
||||
Addresses.parseAddr = function(parser) {
|
||||
var services = parser.readUInt64LEBN();
|
||||
var ip = Addresses.parseIP(parser);
|
||||
var port = parser.readUInt16BE();
|
||||
return {
|
||||
services: services,
|
||||
ip: ip,
|
||||
port: port
|
||||
};
|
||||
};
|
||||
|
||||
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 = new Date(parser.readUInt32LE() * 1000);
|
||||
|
||||
var addr = Addresses.parseAddr(parser);
|
||||
addr.time = time;
|
||||
|
||||
this.addresses.push(addr);
|
||||
}
|
||||
|
||||
this._checkFinished(parser);
|
||||
return this;
|
||||
};
|
||||
|
||||
Addresses.prototype.getPayload = function() {
|
||||
var put = new Put();
|
||||
put.varint(this.addresses.length);
|
||||
|
||||
for (var i = 0; i < this.addresses.length; i++) {
|
||||
var addr = this.addresses[i];
|
||||
put.word32le(addr.time);
|
||||
Addresses.writeAddr(addr, put);
|
||||
break;
|
||||
}
|
||||
|
||||
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.readVarLengthBuffer();
|
||||
this.signature = parser.readVarLengthBuffer();
|
||||
this._checkFinished(parser);
|
||||
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) {
|
||||
$.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 = BlockHeaderModel.fromBufferReader(parser);
|
||||
this.headers.push(header);
|
||||
var txn_count = parser.readUInt8();
|
||||
$.checkState(txn_count === 0, 'txn_count should always be 0');
|
||||
|
||||
}
|
||||
this._checkFinished(parser);
|
||||
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);
|
||||
put.varint(0);
|
||||
}
|
||||
|
||||
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) {
|
||||
$.checkArgument(_.isUndefined(block) || block instanceof BlockModel);
|
||||
this.command = 'block';
|
||||
|
||||
/**
|
||||
* @type {Block}
|
||||
* @desc The block received
|
||||
*/
|
||||
this.block = block;
|
||||
}
|
||||
util.inherits(Block, Message);
|
||||
|
||||
Block.prototype.fromBuffer = function(payload) {
|
||||
$.checkArgument(BufferUtil.isBuffer(payload));
|
||||
var block = BlockModel(payload);
|
||||
return new Block(block);
|
||||
};
|
||||
|
||||
Block.prototype.getPayload = function() {
|
||||
return this.block ? this.block.toBuffer() : new Buffer(0);
|
||||
};
|
||||
|
||||
module.exports.Block = Message.COMMANDS.block = Block;
|
||||
|
||||
/**
|
||||
* Contains information about a MerkleBlock
|
||||
*
|
||||
* @name P2P.Message.MerkleBlock
|
||||
* @param {MerkleBlock} block
|
||||
*/
|
||||
function MerkleBlock(block) {
|
||||
$.checkArgument(_.isUndefined(block) || block instanceof MerkleBlockModel);
|
||||
this.command = 'merkleblock';
|
||||
|
||||
/**
|
||||
* @type {Block}
|
||||
* @desc The block received
|
||||
*/
|
||||
this.merkleBlock = block;
|
||||
}
|
||||
util.inherits(MerkleBlock, Message);
|
||||
|
||||
MerkleBlock.prototype.fromBuffer = function(payload) {
|
||||
$.checkArgument(BufferUtil.isBuffer(payload));
|
||||
var block = MerkleBlockModel(payload);
|
||||
return new MerkleBlock(block);
|
||||
};
|
||||
|
||||
MerkleBlock.prototype.getPayload = function() {
|
||||
return this.merkleBlock ? this.merkleBlock.toBuffer() : new Buffer(0);
|
||||
};
|
||||
|
||||
module.exports.MerkleBlock = Message.COMMANDS.merkleblock = MerkleBlock;
|
||||
|
||||
|
||||
/**
|
||||
* Contains information about a transaction
|
||||
*
|
||||
* @name P2P.Message.Transaction
|
||||
* @param{Transaction} transaction
|
||||
*/
|
||||
function Transaction(transaction) {
|
||||
$.checkArgument(_.isUndefined(transaction) || transaction instanceof TransactionModel);
|
||||
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 ? this.transaction.toBuffer() : new Buffer(0);
|
||||
};
|
||||
|
||||
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 or strings with the starting block hashes
|
||||
* @param{Buffer} [stop] - hash of the last block
|
||||
*/
|
||||
function GetBlocks(starts, stop) {
|
||||
$.checkArgument(_.isUndefined(starts) || _.isArray(starts));
|
||||
this.command = 'getblocks';
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.version = PROTOCOL_VERSION;
|
||||
|
||||
starts = starts ? starts.map(function(hash) {
|
||||
return _.isString(hash) ? BufferUtil.reverse(new Buffer(hash, 'hex')) : hash;
|
||||
}) : undefined;
|
||||
/**
|
||||
* @type {Array.Buffer}
|
||||
*/
|
||||
this.starts = starts || [];
|
||||
|
||||
for (var i = 0; i < this.starts.length; i++) {
|
||||
if (this.starts[i].length !== 32) {
|
||||
throw new Error('Invalid hash ' + i + ' length: ' + this.starts[i].length);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @type {Array.Buffer}
|
||||
* @desc Hashes to limit the amount of blocks to be sent
|
||||
*/
|
||||
this.stop = (_.isString(stop) ? BufferUtil.reverse(new Buffer(stop, 'hex')) : stop) || BufferUtil.NULL_HASH;
|
||||
}
|
||||
util.inherits(GetBlocks, Message);
|
||||
|
||||
GetBlocks.prototype.fromBuffer = 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);
|
||||
this._checkFinished(parser);
|
||||
|
||||
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++) {
|
||||
put.put(this.starts[i]);
|
||||
}
|
||||
|
||||
if (this.stop.length !== 32) {
|
||||
throw new Error('Invalid hash length: ' + this.stop.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) {
|
||||
GetBlocks.call(this, starts, stop);
|
||||
this.command = 'getheaders';
|
||||
}
|
||||
|
||||
util.inherits(GetHeaders, GetBlocks);
|
||||
module.exports.GetHeaders = Message.COMMANDS.getheaders = GetHeaders;
|
||||
|
||||
/**
|
||||
* Request peer to apply a bloom filter to 'inv' messages sent back
|
||||
*
|
||||
* @name P2P.Message.filterload
|
||||
* @param{BloomFilter} filter - a BloomFilter object
|
||||
*/
|
||||
function FilterLoad(filter) {
|
||||
this.command = 'filterload';
|
||||
$.checkArgument(_.isUndefined(filter) || filter instanceof BloomFilter,
|
||||
'BloomFilter object or undefined required for FilterLoad');
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
util.inherits(FilterLoad, Message);
|
||||
|
||||
FilterLoad.prototype.fromBuffer = function(payload) {
|
||||
this.filter = BloomFilter.fromBuffer(payload);
|
||||
return this;
|
||||
};
|
||||
|
||||
FilterLoad.prototype.getPayload = function() {
|
||||
if(this.filter) {
|
||||
return this.filter.toBuffer()
|
||||
} else {
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.FilterLoad = Message.COMMANDS.filterload = FilterLoad;
|
||||
|
||||
/**
|
||||
* Request peer to add data to a bloom filter already set by 'filterload'
|
||||
*
|
||||
* @name P2P.Message.filteradd
|
||||
* @param{Buffer} data - Array of bytes representing bloom filter data
|
||||
*/
|
||||
function FilterAdd(data) {
|
||||
this.command = 'filteradd';
|
||||
this.data = data || new Buffer(0,'hex');
|
||||
return this;
|
||||
}
|
||||
util.inherits(FilterAdd, Message);
|
||||
|
||||
FilterAdd.prototype.fromBuffer = function(payload) {
|
||||
$.checkArgument(payload);
|
||||
var parser = new BufferReader(payload);
|
||||
this.data = parser.readVarLengthBuffer();
|
||||
$.checkState(this.data.length <= BloomFilter.MAX_BLOOM_FILTER_SIZE,
|
||||
'FilterAdd data must be <= 520 bytes');
|
||||
this._checkFinished(parser);
|
||||
return this;
|
||||
};
|
||||
|
||||
FilterAdd.prototype.getPayload = function() {
|
||||
var bw = new BufferWriter();
|
||||
bw.writeVarintNum(this.data.length);
|
||||
bw.write(this.data);
|
||||
return bw.concat();
|
||||
};
|
||||
|
||||
module.exports.FilterAdd = Message.COMMANDS.filteradd = FilterAdd;
|
||||
|
||||
/**
|
||||
* Request peer to apply a bloom filter to 'inv' messages sent back
|
||||
*
|
||||
* @name P2P.Message.filterclear
|
||||
*/
|
||||
function FilterClear() {
|
||||
this.command = 'filterclear';
|
||||
}
|
||||
util.inherits(FilterClear, Message);
|
||||
|
||||
module.exports.FilterClear = Message.COMMANDS.filterclear = FilterClear;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
[Inventory, GetData, NotFound].forEach(function(clazz) {
|
||||
clazz.forBlock = creatorForItem(clazz, Inventory.TYPE.BLOCK);
|
||||
clazz.forFilteredBlock = clazz.forMerkleBlock =
|
||||
creatorForItem(clazz, Inventory.TYPE.FILTERED_BLOCK);
|
||||
clazz.forTransaction = creatorForItem(clazz, Inventory.TYPE.TX);
|
||||
});
|
||||
93
lib/messages/builder.js
Normal file
93
lib/messages/builder.js
Normal file
@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var Inventory = require('../inventory');
|
||||
|
||||
function builder(options) {
|
||||
/* jshint maxstatements: 20 */
|
||||
/* jshint maxcomplexity: 10 */
|
||||
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (!options.magicNumber) {
|
||||
options.magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
}
|
||||
|
||||
options.Block = options.Block || bitcore.Block;
|
||||
options.BlockHeader = options.BlockHeader || bitcore.BlockHeader;
|
||||
options.Transaction = options.Transaction || bitcore.Transaction;
|
||||
options.MerkleBlock = options.MerkleBlock || bitcore.MerkleBlock;
|
||||
options.protocolVersion = options.protocolVersion || 70000;
|
||||
|
||||
var exported = {
|
||||
constructors: {
|
||||
Block: options.Block,
|
||||
BlockHeader: options.BlockHeader,
|
||||
Transaction: options.Transaction,
|
||||
MerkleBlock: options.MerkleBlock
|
||||
},
|
||||
defaults: {
|
||||
protocolVersion: options.protocolVersion,
|
||||
magicNumber: options.magicNumber
|
||||
},
|
||||
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: 'Address',
|
||||
alert: 'Alert',
|
||||
reject: 'Reject',
|
||||
merkleblock: 'MerkleBlock',
|
||||
filterload: 'FilterLoad',
|
||||
filteradd: 'FilterAdd',
|
||||
filterclear: 'FilterClear',
|
||||
getblocks: 'GetBlocks',
|
||||
getheaders: 'GetHeaders',
|
||||
mempool: 'MemPool',
|
||||
getaddr: 'GetAddr'
|
||||
},
|
||||
commands: {}
|
||||
};
|
||||
|
||||
for (var key in exported.commandsMap) {
|
||||
exported.commands[key] = require('./commands/' + key)(options);
|
||||
}
|
||||
|
||||
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;
|
||||
68
lib/messages/commands/addr.js
Normal file
68
lib/messages/commands/addr.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* @param {Object} - options
|
||||
* @param {Array} - options.addresses - An array of addrs
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function AddrMessage(options) {
|
||||
if (!(this instanceof AddrMessage)) {
|
||||
return new AddrMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'addr';
|
||||
this.magicNumber = magicNumber;
|
||||
this.addresses = options.addresses;
|
||||
}
|
||||
inherits(AddrMessage, Message);
|
||||
|
||||
AddrMessage.fromBuffer = function(payload) {
|
||||
var parser = new BufferReader(payload);
|
||||
|
||||
var addrCount = parser.readVarintNum();
|
||||
|
||||
var obj = {};
|
||||
obj.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;
|
||||
obj.addresses.push(addr);
|
||||
}
|
||||
|
||||
utils.checkFinished(parser);
|
||||
return new AddrMessage(obj);
|
||||
};
|
||||
|
||||
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 = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return AddrMessage;
|
||||
};
|
||||
58
lib/messages/commands/alert.js
Normal file
58
lib/messages/commands/alert.js
Normal file
@ -0,0 +1,58 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* @param {Object} - options
|
||||
* @param {Buffer} - options.payload
|
||||
* @param {Buffer} - options.signature
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function AlertMessage(options) {
|
||||
if (!(this instanceof AlertMessage)) {
|
||||
return new AlertMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'alert';
|
||||
|
||||
this.payload = options.payload || new Buffer(32);
|
||||
this.signature = options.signature || new Buffer(32);
|
||||
}
|
||||
inherits(AlertMessage, Message);
|
||||
|
||||
AlertMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
var parser = new BufferReader(payload);
|
||||
obj.payload = parser.readVarLengthBuffer();
|
||||
obj.signature = parser.readVarLengthBuffer();
|
||||
utils.checkFinished(parser);
|
||||
return new AlertMessage(obj);
|
||||
};
|
||||
|
||||
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 = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return AlertMessage;
|
||||
};
|
||||
51
lib/messages/commands/block.js
Normal file
51
lib/messages/commands/block.js
Normal file
@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
|
||||
var Block = bitcore.Block;
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* @param {Object|Block} - options - If is an instance of Block will use as options.block
|
||||
* @param {Block} - options.block - An instance of a Block
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function BlockMessage(options) {
|
||||
if (!(this instanceof BlockMessage)) {
|
||||
return new BlockMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'block';
|
||||
this.magicNumber = magicNumber;
|
||||
|
||||
var block;
|
||||
if (options instanceof Block) {
|
||||
block = options;
|
||||
} else {
|
||||
block = options.block;
|
||||
}
|
||||
|
||||
this.block = block;
|
||||
}
|
||||
inherits(BlockMessage, Message);
|
||||
|
||||
BlockMessage.fromBuffer = function(payload) {
|
||||
var block = Block.fromBuffer(payload);
|
||||
return new BlockMessage({block: block});
|
||||
};
|
||||
|
||||
BlockMessage.prototype.getPayload = function() {
|
||||
return this.block.toBuffer();
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
Block = options.Block || Block;
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return BlockMessage;
|
||||
};
|
||||
54
lib/messages/commands/filteradd.js
Normal file
54
lib/messages/commands/filteradd.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* Request peer to add data to a bloom filter already set by 'filterload'
|
||||
* @param {Object} options
|
||||
* @param {Buffer} options.data - Array of bytes representing bloom filter data
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function FilteraddMessage(options) {
|
||||
if (!(this instanceof FilteraddMessage)) {
|
||||
return new FilteraddMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'filteradd';
|
||||
this.data = options.data || BufferUtil.EMPTY_BUFFER;
|
||||
}
|
||||
inherits(FilteraddMessage, Message);
|
||||
|
||||
FilteraddMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
$.checkArgument(payload);
|
||||
var parser = new BufferReader(payload);
|
||||
obj.data = parser.readVarLengthBuffer();
|
||||
utils.checkFinished(parser);
|
||||
return new FilteraddMessage(obj);
|
||||
};
|
||||
|
||||
FilteraddMessage.prototype.getPayload = function() {
|
||||
var bw = new BufferWriter();
|
||||
bw.writeVarintNum(this.data.length);
|
||||
bw.write(this.data);
|
||||
return bw.concat();
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return FilteraddMessage;
|
||||
};
|
||||
39
lib/messages/commands/filterclear.js
Normal file
39
lib/messages/commands/filterclear.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* Request peer to clear data for a bloom filter
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function FilterclearMessage(options) {
|
||||
if (!(this instanceof FilterclearMessage)) {
|
||||
return new FilterclearMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'filterclear';
|
||||
}
|
||||
inherits(FilterclearMessage, Message);
|
||||
|
||||
FilterclearMessage.fromBuffer = function(payload) {
|
||||
return new FilterclearMessage({});
|
||||
};
|
||||
|
||||
FilterclearMessage.prototype.getPayload = function() {
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return FilterclearMessage;
|
||||
};
|
||||
52
lib/messages/commands/filterload.js
Normal file
52
lib/messages/commands/filterload.js
Normal file
@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
var BloomFilter = require('../../bloomfilter');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* Request peer to send inv messages based on a bloom filter
|
||||
* @param {BloomFilter} options.filter - An instance of BloomFilter
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function FilterloadMessage(options) {
|
||||
if (!(this instanceof FilterloadMessage)) {
|
||||
return new FilterloadMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'filterload';
|
||||
$.checkArgument(_.isUndefined(options.filter) || options.filter instanceof BloomFilter,
|
||||
'BloomFilter object or undefined required for FilterLoad');
|
||||
this.filter = options.filter;
|
||||
}
|
||||
inherits(FilterloadMessage, Message);
|
||||
|
||||
FilterloadMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
obj.filter = BloomFilter.fromBuffer(payload);
|
||||
return new FilterloadMessage(obj);
|
||||
};
|
||||
|
||||
FilterloadMessage.prototype.getPayload = function() {
|
||||
if(this.filter) {
|
||||
return this.filter.toBuffer();
|
||||
} else {
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return FilterloadMessage;
|
||||
};
|
||||
39
lib/messages/commands/getaddr.js
Normal file
39
lib/messages/commands/getaddr.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* Request information about active peers
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function GetaddrMessage(options) {
|
||||
if (!(this instanceof GetaddrMessage)) {
|
||||
return new GetaddrMessage(options);
|
||||
}
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'getaddr';
|
||||
}
|
||||
inherits(GetaddrMessage, Message);
|
||||
|
||||
GetaddrMessage.fromBuffer = function() {
|
||||
return new GetaddrMessage({});
|
||||
};
|
||||
|
||||
GetaddrMessage.prototype.getPayload = function() {
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return GetaddrMessage;
|
||||
};
|
||||
75
lib/messages/commands/getblocks.js
Normal file
75
lib/messages/commands/getblocks.js
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
var protocolVersion = 70000;
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* 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} options
|
||||
* @param {Array} options.starts - Array of buffers or strings with the starting block hashes
|
||||
* @param {Buffer} options.stop - Hash of the last block
|
||||
*/
|
||||
function GetblocksMessage(options) {
|
||||
if (!(this instanceof GetblocksMessage)) {
|
||||
return new GetblocksMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'getblocks';
|
||||
this.version = protocolVersion;
|
||||
this.magicNumber = magicNumber;
|
||||
|
||||
options = utils.sanitizeStartStop(options);
|
||||
this.starts = options.starts;
|
||||
this.stop = options.stop;
|
||||
}
|
||||
inherits(GetblocksMessage, Message);
|
||||
|
||||
GetblocksMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
var parser = new BufferReader(payload);
|
||||
$.checkArgument(!parser.finished(), 'No data received in payload');
|
||||
|
||||
obj.version = parser.readUInt32LE();
|
||||
var startCount = parser.readVarintNum();
|
||||
|
||||
obj.starts = [];
|
||||
for (var i = 0; i < startCount; i++) {
|
||||
obj.starts.push(parser.read(32));
|
||||
}
|
||||
obj.stop = parser.read(32);
|
||||
utils.checkFinished(parser);
|
||||
return new GetblocksMessage(obj);
|
||||
};
|
||||
|
||||
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 = function(options) {
|
||||
protocolVersion = options.protocolVersion || protocolVersion;
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return GetblocksMessage;
|
||||
};
|
||||
69
lib/messages/commands/getdata.js
Normal file
69
lib/messages/commands/getdata.js
Normal file
@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var Inventory = require('../../inventory');
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* @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(options) {
|
||||
if (!(this instanceof GetdataMessage)) {
|
||||
return new GetdataMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'getdata';
|
||||
this.magicNumber = magicNumber;
|
||||
|
||||
var inventory;
|
||||
if (_.isArray(options)) {
|
||||
inventory = options;
|
||||
} else {
|
||||
inventory = options.inventory;
|
||||
}
|
||||
|
||||
utils.checkInventory(inventory);
|
||||
this.inventory = inventory;
|
||||
}
|
||||
inherits(GetdataMessage, Message);
|
||||
|
||||
GetdataMessage.fromBuffer = function(payload) {
|
||||
var obj = {
|
||||
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);
|
||||
obj.inventory.push({type: type, hash: hash});
|
||||
}
|
||||
|
||||
utils.checkFinished(parser);
|
||||
return new GetdataMessage(obj);
|
||||
};
|
||||
|
||||
GetdataMessage.prototype.getPayload = function() {
|
||||
var bw = new BufferWriter();
|
||||
utils.writeInventory(this.inventory, bw);
|
||||
return bw.concat();
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return GetdataMessage;
|
||||
};
|
||||
75
lib/messages/commands/getheaders.js
Normal file
75
lib/messages/commands/getheaders.js
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
var protocolVersion = 70000;
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function GetheadersMessage(options) {
|
||||
if (!(this instanceof GetheadersMessage)) {
|
||||
return new GetheadersMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'getheaders';
|
||||
this.version = protocolVersion;
|
||||
this.magicNumber = magicNumber;
|
||||
|
||||
options = utils.sanitizeStartStop(options);
|
||||
this.starts = options.starts;
|
||||
this.stop = options.stop;
|
||||
}
|
||||
inherits(GetheadersMessage, Message);
|
||||
|
||||
GetheadersMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
var parser = new BufferReader(payload);
|
||||
$.checkArgument(!parser.finished(), 'No data received in payload');
|
||||
|
||||
obj.version = parser.readUInt32LE();
|
||||
var startCount = Math.min(parser.readVarintNum(), 500);
|
||||
|
||||
obj.starts = [];
|
||||
for (var i = 0; i < startCount; i++) {
|
||||
obj.starts.push(parser.read(32));
|
||||
}
|
||||
obj.stop = parser.read(32);
|
||||
utils.checkFinished(parser);
|
||||
return new GetheadersMessage(obj);
|
||||
};
|
||||
|
||||
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 = function(options) {
|
||||
protocolVersion = options.protocolVersion || protocolVersion;
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return GetheadersMessage;
|
||||
};
|
||||
67
lib/messages/commands/headers.js
Normal file
67
lib/messages/commands/headers.js
Normal file
@ -0,0 +1,67 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var $ = bitcore.util.preconditions;
|
||||
|
||||
var BlockHeader = bitcore.BlockHeader;
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* Sent in response to a `getheaders` message. It contains information about
|
||||
* block headers.
|
||||
* @param {Object} options
|
||||
* @param {Array} options.headers - array of block headers
|
||||
*/
|
||||
function HeadersMessage(options) {
|
||||
if (!(this instanceof HeadersMessage)) {
|
||||
return new HeadersMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'headers';
|
||||
this.headers = options.headers;
|
||||
}
|
||||
inherits(HeadersMessage, Message);
|
||||
|
||||
HeadersMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
|
||||
$.checkArgument(payload && payload.length > 0, 'No data found to create Headers message');
|
||||
var parser = new BufferReader(payload);
|
||||
var count = parser.readVarintNum();
|
||||
|
||||
obj.headers = [];
|
||||
for (var i = 0; i < count; i++) {
|
||||
var header = BlockHeader.fromBufferReader(parser);
|
||||
obj.headers.push(header);
|
||||
var txn_count = parser.readUInt8();
|
||||
$.checkState(txn_count === 0, 'txn_count should always be 0');
|
||||
}
|
||||
utils.checkFinished(parser);
|
||||
|
||||
return new HeadersMessage(obj);
|
||||
};
|
||||
|
||||
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 = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return HeadersMessage;
|
||||
};
|
||||
68
lib/messages/commands/inv.js
Normal file
68
lib/messages/commands/inv.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var Inventory = require('../../inventory');
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* @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 InvMessage(options) {
|
||||
if (!(this instanceof InvMessage)) {
|
||||
return new InvMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'inv';
|
||||
this.magicNumber = magicNumber;
|
||||
|
||||
var inventory;
|
||||
if (_.isArray(options)) {
|
||||
inventory = options;
|
||||
} else {
|
||||
inventory = options.inventory;
|
||||
}
|
||||
utils.checkInventory(inventory);
|
||||
this.inventory = inventory;
|
||||
}
|
||||
inherits(InvMessage, Message);
|
||||
|
||||
InvMessage.prototype.getPayload = function() {
|
||||
var bw = new BufferWriter();
|
||||
utils.writeInventory(this.inventory, bw);
|
||||
return bw.concat();
|
||||
};
|
||||
|
||||
InvMessage.fromBuffer = function(payload) {
|
||||
var obj = {
|
||||
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);
|
||||
obj.inventory.push({type: type, hash: hash});
|
||||
}
|
||||
|
||||
utils.checkFinished(parser);
|
||||
return new InvMessage(obj);
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return InvMessage;
|
||||
};
|
||||
41
lib/messages/commands/mempool.js
Normal file
41
lib/messages/commands/mempool.js
Normal file
@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function MempoolMessage(options) {
|
||||
if (!(this instanceof MempoolMessage)) {
|
||||
return new MempoolMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'mempool';
|
||||
}
|
||||
inherits(MempoolMessage, Message);
|
||||
|
||||
MempoolMessage.fromBuffer = function(payload) {
|
||||
return new MempoolMessage({});
|
||||
};
|
||||
|
||||
MempoolMessage.prototype.getPayload = function() {
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return MempoolMessage;
|
||||
};
|
||||
52
lib/messages/commands/merkleblock.js
Normal file
52
lib/messages/commands/merkleblock.js
Normal file
@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var MerkleBlock = bitcore.MerkleBlock;
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* Contains information about a MerkleBlock
|
||||
* @see https://en.bitcoin.it/wiki/Protocol_documentation
|
||||
* @param {Object} options
|
||||
* @param {MerkleBlock} options.merkleBlock
|
||||
*/
|
||||
function MerkleblockMessage(options) {
|
||||
if (!(this instanceof MerkleblockMessage)) {
|
||||
return new MerkleblockMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'merkleblock';
|
||||
$.checkArgument(
|
||||
_.isUndefined(options.merkleBlock) ||
|
||||
options.merkleBlock instanceof MerkleBlock
|
||||
);
|
||||
this.merkleBlock = options.merkleBlock;
|
||||
}
|
||||
inherits(MerkleblockMessage, Message);
|
||||
|
||||
MerkleblockMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
$.checkArgument(BufferUtil.isBuffer(payload));
|
||||
obj.merkleBlock = MerkleBlock.fromBuffer(payload);
|
||||
return new MerkleblockMessage(obj);
|
||||
};
|
||||
|
||||
MerkleblockMessage.prototype.getPayload = function() {
|
||||
return this.merkleBlock ? this.merkleBlock.toBuffer() : BufferUtil.EMPTY_BUFFER;
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
MerkleBlock = options.MerkleBlock || MerkleBlock;
|
||||
return MerkleblockMessage;
|
||||
};
|
||||
68
lib/messages/commands/notfound.js
Normal file
68
lib/messages/commands/notfound.js
Normal file
@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var Inventory = require('../../inventory');
|
||||
var _ = bitcore.deps._;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* @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 NotfoundMessage(options) {
|
||||
if (!(this instanceof NotfoundMessage)) {
|
||||
return new NotfoundMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'notfound';
|
||||
this.magicNumber = magicNumber;
|
||||
|
||||
var inventory;
|
||||
if (_.isArray(options)) {
|
||||
inventory = options;
|
||||
} else {
|
||||
inventory = options.inventory;
|
||||
}
|
||||
utils.checkInventory(inventory);
|
||||
this.inventory = inventory;
|
||||
}
|
||||
inherits(NotfoundMessage, Message);
|
||||
|
||||
NotfoundMessage.fromBuffer = function(payload) {
|
||||
var obj = {
|
||||
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);
|
||||
obj.inventory.push({type: type, hash: hash});
|
||||
}
|
||||
|
||||
utils.checkFinished(parser);
|
||||
return new NotfoundMessage(obj);
|
||||
};
|
||||
|
||||
NotfoundMessage.prototype.getPayload = function() {
|
||||
var bw = new BufferWriter();
|
||||
utils.writeInventory(this.inventory, bw);
|
||||
return bw.concat();
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return NotfoundMessage;
|
||||
};
|
||||
48
lib/messages/commands/ping.js
Normal file
48
lib/messages/commands/ping.js
Normal file
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* A message to confirm that a connection is still valid.
|
||||
* @param {Object} options
|
||||
* @param {Buffer} options.nonce
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function PingMessage(options) {
|
||||
if (!(this instanceof PingMessage)) {
|
||||
return new PingMessage(options);
|
||||
}
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'ping';
|
||||
this.magicNumber = magicNumber;
|
||||
this.nonce = options.nonce || utils.getNonce();
|
||||
}
|
||||
inherits(PingMessage, Message);
|
||||
|
||||
PingMessage.prototype.getPayload = function() {
|
||||
return this.nonce;
|
||||
};
|
||||
|
||||
PingMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
var parser = new BufferReader(payload);
|
||||
obj.nonce = parser.read(8);
|
||||
|
||||
utils.checkFinished(parser);
|
||||
return new PingMessage(obj);
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return PingMessage;
|
||||
};
|
||||
48
lib/messages/commands/pong.js
Normal file
48
lib/messages/commands/pong.js
Normal file
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var utils = require('../utils');
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* A message in response to a ping message.
|
||||
* @param {Object} options
|
||||
* @param {Buffer} options.nonce
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function PongMessage(options) {
|
||||
if (!(this instanceof PongMessage)) {
|
||||
return new PongMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'pong';
|
||||
this.magicNumber = magicNumber;
|
||||
this.nonce = options.nonce;
|
||||
}
|
||||
inherits(PongMessage, Message);
|
||||
|
||||
PongMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
var parser = new BufferReader(payload);
|
||||
obj.nonce = parser.read(8);
|
||||
|
||||
utils.checkFinished(parser);
|
||||
return new PongMessage(obj);
|
||||
};
|
||||
|
||||
PongMessage.prototype.getPayload = function() {
|
||||
return this.nonce;
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return PongMessage;
|
||||
};
|
||||
36
lib/messages/commands/reject.js
Normal file
36
lib/messages/commands/reject.js
Normal file
@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
// todo: add payload: https://en.bitcoin.it/wiki/Protocol_documentation#reject
|
||||
function RejectMessage(options) {
|
||||
if (!(this instanceof RejectMessage)) {
|
||||
return new RejectMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'reject';
|
||||
}
|
||||
inherits(RejectMessage, Message);
|
||||
|
||||
RejectMessage.fromBuffer = function(payload) {
|
||||
var obj = {};
|
||||
return new RejectMessage(obj);
|
||||
};
|
||||
|
||||
RejectMessage.prototype.getPayload = function() {
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return RejectMessage;
|
||||
};
|
||||
56
lib/messages/commands/tx.js
Normal file
56
lib/messages/commands/tx.js
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
|
||||
var Transaction = bitcore.Transaction;
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* @param {Object|Transaction} - options - If is an instance of Transaction will use as options.transaction
|
||||
* @param {Transaction} - options.transaction - An instance of a Transaction
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function TransactionMessage(options) {
|
||||
if (!(this instanceof TransactionMessage)) {
|
||||
return new TransactionMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.command = 'tx';
|
||||
this.magicNumber = magicNumber;
|
||||
|
||||
var transaction;
|
||||
if(options instanceof Transaction) {
|
||||
transaction = options;
|
||||
} else {
|
||||
transaction = options.transaction;
|
||||
}
|
||||
|
||||
this.transaction = transaction;
|
||||
}
|
||||
inherits(TransactionMessage, Message);
|
||||
|
||||
TransactionMessage.fromBuffer = function(payload) {
|
||||
var transaction;
|
||||
if (Transaction.prototype.fromBuffer) {
|
||||
transaction = new Transaction().fromBuffer(payload);
|
||||
} else {
|
||||
transaction = Transaction.fromBuffer(payload);
|
||||
}
|
||||
return new TransactionMessage({transaction: transaction});
|
||||
};
|
||||
|
||||
TransactionMessage.prototype.getPayload = function() {
|
||||
return this.transaction.toBuffer();
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
Transaction = options.Transaction || Transaction;
|
||||
return TransactionMessage;
|
||||
};
|
||||
39
lib/messages/commands/verack.js
Normal file
39
lib/messages/commands/verack.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
|
||||
/**
|
||||
* A message in response to a version message.
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function VerackMessage(options) {
|
||||
if (!(this instanceof VerackMessage)) {
|
||||
return new VerackMessage(options);
|
||||
}
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
Message.call(this, options);
|
||||
this.magicNumber = magicNumber;
|
||||
this.command = 'verack';
|
||||
}
|
||||
inherits(VerackMessage, Message);
|
||||
|
||||
VerackMessage.fromBuffer = function(payload) {
|
||||
return new VerackMessage({});
|
||||
};
|
||||
|
||||
VerackMessage.prototype.getPayload = function() {
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
};
|
||||
|
||||
module.exports = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
return VerackMessage;
|
||||
};
|
||||
104
lib/messages/commands/version.js
Normal file
104
lib/messages/commands/version.js
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
var Message = require('../message');
|
||||
var inherits = require('util').inherits;
|
||||
var bitcore = require('bitcore');
|
||||
var BufferWriter = bitcore.encoding.BufferWriter;
|
||||
var BufferReader = bitcore.encoding.BufferReader;
|
||||
var _ = bitcore.deps._;
|
||||
var BN = bitcore.crypto.BN;
|
||||
|
||||
var utils = require('../utils');
|
||||
var magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
var protocolVersion = 70000;
|
||||
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} [obj] - properties for the version
|
||||
* @param{Buffer} [obj.nonce] - a random 8 byte buffer
|
||||
* @param{String} [obj.subversion] - version of the client
|
||||
* @param{BN} [obj.services]
|
||||
* @param{Date} [obj.timestamp]
|
||||
* @param{Number} [obj.startHeight]
|
||||
* @extends Message
|
||||
* @constructor
|
||||
*/
|
||||
function VersionMessage(obj) {
|
||||
if (!(this instanceof VersionMessage)) {
|
||||
return new VersionMessage(obj);
|
||||
}
|
||||
/* jshint maxcomplexity: 10 */
|
||||
Message.call(this, obj);
|
||||
this.command = 'version';
|
||||
_.assign(this, obj);
|
||||
this.magicNumber = magicNumber;
|
||||
this.nonce = this.nonce || utils.getNonce();
|
||||
this.services = this.services || new BN(1, 10);
|
||||
this.timestamp = this.timestamp || new Date();
|
||||
this.version = this.version || protocolVersion;
|
||||
this.subversion = this.subversion || '/bitcore:' + packageInfo.version + '/';
|
||||
this.startHeight = this.startHeight || 0;
|
||||
}
|
||||
inherits(VersionMessage, Message);
|
||||
|
||||
VersionMessage.fromBuffer = function(payload) {
|
||||
var parser = new BufferReader(payload);
|
||||
var obj = {};
|
||||
obj.version = parser.readUInt32LE();
|
||||
obj.services = parser.readUInt64LEBN();
|
||||
obj.timestamp = new Date(parser.readUInt64LEBN().toNumber() * 1000);
|
||||
|
||||
obj.addrMe = {
|
||||
services: parser.readUInt64LEBN(),
|
||||
ip: utils.parseIP(parser),
|
||||
port: parser.readUInt16BE()
|
||||
};
|
||||
obj.addrYou = {
|
||||
services: parser.readUInt64LEBN(),
|
||||
ip: utils.parseIP(parser),
|
||||
port: parser.readUInt16BE()
|
||||
};
|
||||
obj.nonce = parser.read(8);
|
||||
obj.subversion = parser.readVarLengthBuffer().toString();
|
||||
obj.startHeight = parser.readUInt32LE();
|
||||
|
||||
if(parser.finished()) {
|
||||
obj.relay = true;
|
||||
} else {
|
||||
obj.relay = !!parser.readUInt8();
|
||||
}
|
||||
utils.checkFinished(parser);
|
||||
|
||||
return new VersionMessage(obj);
|
||||
};
|
||||
|
||||
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 = function(options) {
|
||||
magicNumber = options.magicNumber || magicNumber;
|
||||
protocolVersion = options.protocolVersion || protocolVersion;
|
||||
return VersionMessage;
|
||||
};
|
||||
104
lib/messages/index.js
Normal file
104
lib/messages/index.js
Normal file
@ -0,0 +1,104 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
var Hash = bitcore.crypto.Hash;
|
||||
|
||||
/**
|
||||
* A factory to build Bitcoin protocol messages.
|
||||
* @param {Object} - [options]
|
||||
* @param {Number} - [options.magicNumber]
|
||||
* @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 = {};
|
||||
}
|
||||
var defaultMagicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
this.magicNumber = options.magicNumber || defaultMagicNumber;
|
||||
}
|
||||
|
||||
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) {
|
||||
var i = 0;
|
||||
for (;;) {
|
||||
// check if it's the beginning of a new message
|
||||
var packageNumber = dataBuffer.slice(0, 4).readUInt32LE(0);
|
||||
if (packageNumber === this.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
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
module.exports = Messages;
|
||||
45
lib/messages/message.js
Normal file
45
lib/messages/message.js
Normal file
@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
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 {Number} - options.magicNumber
|
||||
* @constructor
|
||||
*/
|
||||
function Message(options) {
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
this.command = options.command;
|
||||
this.magicNumber = options.magicNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Buffer} - Serialized message
|
||||
* @constructor
|
||||
*/
|
||||
Message.prototype.toBuffer = Message.prototype.serialize = function() {
|
||||
|
||||
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.writeUInt32LE(this.magicNumber);
|
||||
bw.write(commandBuf);
|
||||
bw.writeUInt32LE(payload.length);
|
||||
bw.write(checksum);
|
||||
bw.write(payload);
|
||||
|
||||
return bw.concat();
|
||||
};
|
||||
|
||||
module.exports = Message;
|
||||
117
lib/messages/utils.js
Normal file
117
lib/messages/utils.js
Normal file
@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var BufferUtil = bitcore.util.buffer;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var _ = bitcore.deps._;
|
||||
var utils;
|
||||
|
||||
module.exports = utils = {
|
||||
checkInventory: function(inventory) {
|
||||
$.checkArgument(
|
||||
_.isUndefined(inventory) ||
|
||||
inventory.length === 0 ||
|
||||
(!_.isUndefined(inventory[0].type) && !_.isUndefined(inventory[0].hash)),
|
||||
'Inventory must 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;
|
||||
}
|
||||
};
|
||||
114
lib/peer.js
114
lib/peer.js
@ -1,60 +1,82 @@
|
||||
'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 util = require('util');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var $ = bitcore.util.preconditions;
|
||||
var Networks = bitcore.Networks;
|
||||
var Messages = require('./messages');
|
||||
|
||||
var MAX_RECEIVE_BUFFER = 10000000;
|
||||
var $ = bitcore.util.preconditions;
|
||||
var util = require('util');
|
||||
|
||||
/**
|
||||
* A Peer instance represents a remote bitcoin node and allows to communicate
|
||||
* with it using the standard messages of the bitcoin p2p protocol.
|
||||
* 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.
|
||||
*
|
||||
* @example
|
||||
* ```javascript
|
||||
*
|
||||
* var peer = new Peer('127.0.0.1').setProxy('127.0.0.1', 9050);
|
||||
* var peer = new Peer({host: '127.0.0.1'}).setProxy('127.0.0.1', 9050);
|
||||
* peer.on('tx', function(tx) {
|
||||
* console.log('New transaction: ', tx.id);
|
||||
* });
|
||||
* peer.connect();
|
||||
* ```
|
||||
*
|
||||
* @param {String} host - IP address of the remote host
|
||||
* @param {Number} [port] - Port number of the remote host
|
||||
* @param {Network} [network] - The context for this communication
|
||||
* @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=Networks.defaultNetwork] - 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
|
||||
|
||||
* @returns {Peer} A new instance of Peer.
|
||||
* @constructor
|
||||
*/
|
||||
function Peer(host, port, network, relay) {
|
||||
function Peer(options) {
|
||||
/* jshint maxstatements: 26 */
|
||||
/* jshint maxcomplexity: 8 */
|
||||
|
||||
if (!(this instanceof Peer)) {
|
||||
return new Peer(host, port, network);
|
||||
return new Peer(options);
|
||||
}
|
||||
|
||||
// overloading stuff
|
||||
if (port instanceof Object && !network) {
|
||||
network = port;
|
||||
port = undefined;
|
||||
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;
|
||||
}
|
||||
|
||||
this.host = host || 'localhost';
|
||||
this.status = Peer.STATUS.DISCONNECTED;
|
||||
this.network = network || Networks.defaultNetwork;
|
||||
this.port = port || this.network.port;
|
||||
this.relay = relay === false ? false : true;
|
||||
this.network = Networks.get(options.network) || Networks.defaultNetwork;
|
||||
|
||||
if (!this.port) {
|
||||
this.port = this.network.port;
|
||||
}
|
||||
|
||||
this.messages = options.messages || new Messages({
|
||||
magicNumber: this.network.networkMagic.readUInt32LE(0),
|
||||
Block: bitcore.Block,
|
||||
Transaction: bitcore.Transaction
|
||||
});
|
||||
|
||||
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;
|
||||
@ -66,16 +88,26 @@ function Peer(host, port, network, relay) {
|
||||
this.on('version', function(message) {
|
||||
self.version = message.version;
|
||||
self.subversion = message.subversion;
|
||||
self.bestHeight = message.start_height;
|
||||
self.bestHeight = message.startHeight;
|
||||
|
||||
var verackResponse = self.messages.VerAck();
|
||||
self.sendMessage(verackResponse);
|
||||
|
||||
if(!self.versionSent) {
|
||||
self._sendVersion();
|
||||
}
|
||||
});
|
||||
|
||||
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',
|
||||
@ -85,7 +117,6 @@ 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.
|
||||
@ -102,8 +133,7 @@ Peer.prototype.setProxy = function(host, port) {
|
||||
|
||||
/**
|
||||
* Init the connection with the remote peer.
|
||||
*
|
||||
* @returns {Socket} The same peer instance.
|
||||
* @returns {Peer} The same peer instance.
|
||||
*/
|
||||
Peer.prototype.connect = function() {
|
||||
this.socket = this._getSocket();
|
||||
@ -116,31 +146,36 @@ 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('end', self.disconnect.bind(this));
|
||||
|
||||
this.socket.on('data', function(data) {
|
||||
self.dataBuffer.push(data);
|
||||
|
||||
if (self.dataBuffer.length > MAX_RECEIVE_BUFFER) {
|
||||
if (self.dataBuffer.length > Peer.MAX_RECEIVE_BUFFER) {
|
||||
// TODO: handle this case better
|
||||
return self.disconnect();
|
||||
}
|
||||
self._readMessage();
|
||||
});
|
||||
|
||||
this.socket.connect(this.port, this.host);
|
||||
return this;
|
||||
};
|
||||
|
||||
Peer.prototype._onError = function(e) {
|
||||
this.emit('error', e);
|
||||
this.disconnect();
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnects the remote connection.
|
||||
*
|
||||
* @returns {Socket} The same peer instance.
|
||||
* @returns {Peer} The same peer instance.
|
||||
*/
|
||||
Peer.prototype.disconnect = function() {
|
||||
this.status = Peer.STATUS.DISCONNECTED;
|
||||
@ -151,18 +186,19 @@ 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.serialize(this.network));
|
||||
this.socket.write(message.toBuffer());
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal function that sends VERSION message to the remote peer.
|
||||
*/
|
||||
Peer.prototype._sendVersion = function() {
|
||||
var message = new Messages.Version(null, null, this.relay);
|
||||
// todo: include sending local ip address
|
||||
var message = this.messages.Version({relay: this.relay});
|
||||
this.versionSent = true;
|
||||
this.sendMessage(message);
|
||||
};
|
||||
|
||||
@ -170,7 +206,7 @@ Peer.prototype._sendVersion = function() {
|
||||
* Send a PONG message to the remote peer.
|
||||
*/
|
||||
Peer.prototype._sendPong = function(nonce) {
|
||||
var message = new Messages.Pong(nonce);
|
||||
var message = this.messages.Pong({nonce: nonce});
|
||||
this.sendMessage(message);
|
||||
};
|
||||
|
||||
@ -178,8 +214,7 @@ Peer.prototype._sendPong = function(nonce) {
|
||||
* Internal function that tries to read a message from the data buffer
|
||||
*/
|
||||
Peer.prototype._readMessage = function() {
|
||||
var message = Messages.parseMessage(this.network, this.dataBuffer);
|
||||
|
||||
var message = this.messages.parseBuffer(this.dataBuffer);
|
||||
if (message) {
|
||||
this.emit(message.command, message);
|
||||
this._readMessage();
|
||||
@ -188,7 +223,6 @@ Peer.prototype._readMessage = function() {
|
||||
|
||||
/**
|
||||
* Internal function that creates a socket using a proxy if neccesary.
|
||||
*
|
||||
* @returns {Socket} A Socket instance not yet connected.
|
||||
*/
|
||||
Peer.prototype._getSocket = function() {
|
||||
|
||||
129
lib/pool.js
129
lib/pool.js
@ -2,12 +2,12 @@
|
||||
|
||||
var dns = require('dns');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
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,15 +22,15 @@ function now() {
|
||||
* @example
|
||||
* ```javascript
|
||||
*
|
||||
* var pool = new Pool(Networks.livenet);
|
||||
* var pool = new Pool({network: Networks.livenet});
|
||||
* pool.on('peerinv', function(peer, message) {
|
||||
* // do something with the inventory announcement
|
||||
* });
|
||||
* pool.connect();
|
||||
* ```
|
||||
*
|
||||
* @param {Network|String} network - The network to connect
|
||||
* @param {Object} [options] - Options object
|
||||
* @param {Object} [options]
|
||||
* @param {Network} [options.network=Networks.defaultNetwork] - The network configuration
|
||||
* @param {Boolean} [options.listenAddr=true] - Prevent new peers being added from addr messages
|
||||
* @param {Boolean} [options.dnsSeed=true] - Prevent seeding with DNS discovered known peers
|
||||
* @param {Boolean} [options.relay=true] - Prevent inventory announcements until a filter is loaded
|
||||
@ -38,12 +38,13 @@ function now() {
|
||||
* @returns {Pool}
|
||||
* @constructor
|
||||
*/
|
||||
function Pool(network, options) {
|
||||
function Pool(options) {
|
||||
/* jshint maxcomplexity: 10 */
|
||||
/* jshint maxstatements: 20 */
|
||||
|
||||
var self = this;
|
||||
|
||||
options = options || {};
|
||||
this.network = Networks.get(network) || Networks.defaultNetwork;
|
||||
this.keepalive = false;
|
||||
|
||||
this._connectedPeers = {};
|
||||
@ -51,8 +52,10 @@ function Pool(network, options) {
|
||||
|
||||
this.listenAddr = options.listenAddr !== false;
|
||||
this.dnsSeed = options.dnsSeed !== false;
|
||||
this.relay = options.relay !== 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++) {
|
||||
@ -66,9 +69,11 @@ function Pool(network, options) {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
@ -110,7 +115,6 @@ Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'pong', 'addr',
|
||||
'filterclear'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -127,7 +131,6 @@ Pool.prototype.connect = function connect() {
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Will disconnect all peers that are connected.
|
||||
*/
|
||||
@ -183,35 +186,71 @@ Pool.prototype._removeConnectedPeer = function _removeConnectedPeer(addr) {
|
||||
Pool.prototype._connectPeer = function _connectPeer(addr) {
|
||||
var self = this;
|
||||
|
||||
function addConnectedPeer(addr) {
|
||||
if (!this._connectedPeers[addr.hash]) {
|
||||
var port = addr.port || self.network.port;
|
||||
var ip = addr.ip.v4 || addr.ip.v6;
|
||||
var peer = new Peer(ip, port, self.network, self.relay);
|
||||
peer.on('disconnect', function peerDisconnect() {
|
||||
self.emit('peerdisconnect', peer, addr);
|
||||
var peer = new Peer({
|
||||
host: ip,
|
||||
port: port,
|
||||
messages: self.messages,
|
||||
relay: self.relay
|
||||
});
|
||||
|
||||
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
|
||||
* intializes 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]) {
|
||||
addConnectedPeer(addr);
|
||||
var peer = new Peer({
|
||||
socket: socket,
|
||||
messages: self.messages
|
||||
});
|
||||
|
||||
self._addPeerEventHandlers(peer, addr);
|
||||
self._connectedPeers[addr.hash] = peer;
|
||||
self.emit('peerconnect', peer, 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
|
||||
@ -250,7 +289,7 @@ Pool.prototype._addAddr = function _addAddr(addr) {
|
||||
if (!exists) {
|
||||
this._addrs.unshift(addr);
|
||||
}
|
||||
return this;
|
||||
return addr;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -298,4 +337,40 @@ 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 = self._addAddr(addr);
|
||||
self._addConnectedPeer(socket, addr);
|
||||
});
|
||||
this.server.listen(this.network.port);
|
||||
};
|
||||
|
||||
module.exports = Pool;
|
||||
|
||||
@ -53,8 +53,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bitcore": "^0.11.0",
|
||||
"bloom-filter": "^0.1.1",
|
||||
"bufferput": "^0.1.2",
|
||||
"bloom-filter": "^0.2.0",
|
||||
"buffers": "^0.1.1",
|
||||
"socks5-client": "^0.3.6"
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var chai = require('chai');
|
||||
var should = chai.should();
|
||||
|
||||
var assert = require('assert');
|
||||
var bitcore = require('bitcore');
|
||||
@ -8,6 +9,10 @@ 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 = [];
|
||||
@ -21,15 +26,15 @@ function ParseHex(str) {
|
||||
|
||||
describe('BloomFilter', function() {
|
||||
|
||||
it('BloomFilter#fromBuffer and toBuffer methods work', function() {
|
||||
var testPayload = Data.FILTERLOAD.payload;
|
||||
var filter = new BloomFilter.fromBuffer(new Buffer(testPayload, 'hex'));
|
||||
filter.toBuffer().toString('hex').should.equal(testPayload);
|
||||
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('correctly serialize filter with public keys added', function() {
|
||||
it('serialize filter with public keys added', function() {
|
||||
|
||||
var privateKey = bitcore.PrivateKey.fromWIF('5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C');
|
||||
var publicKey = privateKey.toPublicKey();
|
||||
@ -44,7 +49,7 @@ describe('BloomFilter', function() {
|
||||
|
||||
});
|
||||
|
||||
it('correctly serialize to a buffer', function() {
|
||||
it('serialize to a buffer', function() {
|
||||
|
||||
var filter = BloomFilter.create(3, 0.01, 0, BloomFilter.BLOOM_UPDATE_ALL);
|
||||
|
||||
@ -63,7 +68,7 @@ describe('BloomFilter', function() {
|
||||
actual.should.deep.equal(expected);
|
||||
});
|
||||
|
||||
it('correctly deserialize a buffer', function() {
|
||||
it('deserialize a buffer', function() {
|
||||
|
||||
var buffer = new Buffer('03614e9b050000000000000001', 'hex');
|
||||
var filter = BloomFilter.fromBuffer(buffer);
|
||||
|
||||
63
test/buffers.js
Normal file
63
test/buffers.js
Normal file
@ -0,0 +1,63 @@
|
||||
'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
107
test/inventory.js
Normal file
107
test/inventory.js
Normal file
@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
/*jshint immed: false */
|
||||
|
||||
var should = require('chai').should();
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
160
test/messages.js
160
test/messages.js
@ -1,160 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var chai = require('chai');
|
||||
|
||||
var should = chai.should();
|
||||
|
||||
var Buffers = require('buffers');
|
||||
var bitcore = require('bitcore');
|
||||
var Data = require('./data/messages');
|
||||
var P2P = require('../');
|
||||
var BloomFilter = P2P.BloomFilter;
|
||||
var Messages = P2P.Messages;
|
||||
var Networks = bitcore.Networks;
|
||||
var BufferUtils = bitcore.util.buffer;
|
||||
|
||||
var network = Networks.livenet;
|
||||
|
||||
describe('Messages', function() {
|
||||
|
||||
var commands = {
|
||||
Version: 'version',
|
||||
VerAck: 'verack',
|
||||
Inventory: 'inv',
|
||||
Addresses: 'addr',
|
||||
Ping: 'ping',
|
||||
Pong: 'pong',
|
||||
Alert: 'alert',
|
||||
Reject: 'reject',
|
||||
Block: 'block',
|
||||
MerkleBlock: 'merkleblock',
|
||||
FilterLoad: 'filterload',
|
||||
FilterAdd: 'filteradd',
|
||||
FilterClear: 'filterclear',
|
||||
GetBlocks: 'getblocks',
|
||||
GetHeaders: 'getheaders',
|
||||
GetData: 'getdata',
|
||||
GetAddresses: 'getaddr',
|
||||
Headers: 'headers',
|
||||
Transaction: 'tx',
|
||||
NotFound: 'notfound'
|
||||
};
|
||||
// TODO: add data for these
|
||||
var noPayload = ['Reject', 'GetBlocks', 'GetHeaders'];
|
||||
var names = Object.keys(commands);
|
||||
describe('named', function() {
|
||||
names.forEach(function(name) {
|
||||
var command = commands[name];
|
||||
var data = Data[command.toUpperCase()];
|
||||
|
||||
it('should have data for ' + name, function() {
|
||||
should.exist(data);
|
||||
});
|
||||
|
||||
it('command for name ' + name, function() {
|
||||
Messages.Message.COMMANDS[command].should.equal(Messages[name]);
|
||||
});
|
||||
|
||||
describe(name, function() {
|
||||
var message = new Messages[name]();
|
||||
it('should be able to create instance', function() {
|
||||
message.command.should.equal(command);
|
||||
});
|
||||
|
||||
it('should be able to serialize the payload', function() {
|
||||
var payload = message.getPayload();
|
||||
should.exist(payload);
|
||||
});
|
||||
|
||||
it('should be able to serialize the message', function() {
|
||||
var buffer = message.serialize(Networks.livenet);
|
||||
should.exist(buffer);
|
||||
});
|
||||
|
||||
if (noPayload.indexOf(name) === -1) {
|
||||
it('should be able to parse payload', function() {
|
||||
var payload = new Buffer(data.payload, 'hex');
|
||||
var m = new Messages[name]().fromBuffer(payload);
|
||||
should.exist(m);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var buildMessage = function(hex) {
|
||||
var m = Buffers();
|
||||
m.push(new Buffer(hex, 'hex'));
|
||||
return m;
|
||||
};
|
||||
it('fails with invalid command', function() {
|
||||
var invalidCommand = 'f9beb4d96d616c6963696f757300000025000000bd5e830c' +
|
||||
'0102000000ec3995c1bf7269ff728818a65e53af00cbbee6b6eca8ac9ce7bc79d87' +
|
||||
'7041ed8';
|
||||
var fails = function() {
|
||||
Messages.parseMessage(network, buildMessage(invalidCommand));
|
||||
};
|
||||
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.parseMessage(network, buildMessage(malformed));
|
||||
should.not.exist(ret);
|
||||
});
|
||||
});
|
||||
|
||||
it('Inventory#from family methods work', function() {
|
||||
var hash = 'eb951630aba498b9a0d10f72b5ea9e39d5ff04b03dc2231e662f52057f948aa1';
|
||||
[Messages.Inventory, Messages.GetData, Messages.NotFound].forEach(function(clazz) {
|
||||
var b = clazz.forBlock(hash);
|
||||
var mb = clazz.forMerkleBlock(hash);
|
||||
(b instanceof clazz).should.equal(true);
|
||||
var t = clazz.forTransaction(hash);
|
||||
(t instanceof clazz).should.equal(true);
|
||||
clazz.forBlock(BufferUtils.reverse(new Buffer(hash, 'hex'))).should.deep.equal(b);
|
||||
clazz.forMerkleBlock(BufferUtils.reverse(new Buffer(hash, 'hex'))).should.deep.equal(mb);
|
||||
clazz.forFilteredBlock(BufferUtils.reverse(new Buffer(hash, 'hex'))).should.deep.equal(mb);
|
||||
clazz.forTransaction(BufferUtils.reverse(new Buffer(hash, 'hex'))).should.deep.equal(t);
|
||||
});
|
||||
});
|
||||
|
||||
it('Version#fromBuffer works w/o fRelay arg', function() {
|
||||
var messageHex = Data.VERSION_NO_FRELAY.payload;
|
||||
var message = new Messages.Version()
|
||||
.fromBuffer(new Buffer(messageHex, 'hex'));
|
||||
});
|
||||
|
||||
it('Version#relay setting works', function() {
|
||||
[true,false].forEach(function(relay) {
|
||||
var message = new Messages.Version(null,null,relay);
|
||||
message.relay.should.equal(relay);
|
||||
var messageBuf = message.getPayload();
|
||||
var newMessage = new Messages.Version().fromBuffer(messageBuf)
|
||||
newMessage.relay.should.equal(relay);
|
||||
});
|
||||
});
|
||||
|
||||
it('FilterLoad#fromBuffer method works', function() {
|
||||
var testPayload = Data.FILTERLOAD.payload;
|
||||
var msg = new Messages.FilterLoad().fromBuffer(new Buffer(testPayload, 'hex'));
|
||||
msg.getPayload().toString('hex').should.equal(testPayload);
|
||||
});
|
||||
|
||||
it('MerkleBlock#fromBuffer method works', function() {
|
||||
var testPayload = Data.MERKLEBLOCK.payload;
|
||||
var msg = new Messages.MerkleBlock().fromBuffer(new Buffer(testPayload, 'hex'));
|
||||
msg.getPayload().toString('hex').should.equal(testPayload);
|
||||
});
|
||||
|
||||
});
|
||||
114
test/messages/builder.js
Normal file
114
test/messages/builder.js
Normal file
@ -0,0 +1,114 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var P2P = require('../../');
|
||||
var builder = P2P.Messages.builder;
|
||||
var commandData = require('../data/messages.json');
|
||||
var Data = require('../data/messages');//todo merge with commandData
|
||||
var bitcore = require('bitcore');
|
||||
|
||||
function getPayloadBuffer(messageBuffer) {
|
||||
return new Buffer(messageBuffer.slice(48), 'hex');
|
||||
}
|
||||
|
||||
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({
|
||||
magicNumber: 0xd9b4bef9,
|
||||
Block: bitcore.Block,
|
||||
Transaction: bitcore.Transaction
|
||||
});
|
||||
should.exist(b);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Commands', function() {
|
||||
|
||||
var b = builder();
|
||||
|
||||
describe('#fromBuffer/#toBuffer round trip for all commands', function() {
|
||||
Object.keys(b.commands).forEach(function(command) {
|
||||
|
||||
it(command, function(done) {
|
||||
var payloadBuffer = getPayloadBuffer(commandData[command].message);
|
||||
should.exist(b.commands[command]);
|
||||
var message = b.commands[command].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('version', function() {
|
||||
it('#fromBuffer works w/o fRelay arg', function() {
|
||||
var payloadBuffer = getPayloadBuffer(Data.version.messagenofrelay);
|
||||
var message = b.commands.version.fromBuffer(payloadBuffer);
|
||||
message.relay.should.equal(true);
|
||||
});
|
||||
|
||||
it('#relay setting works', function() {
|
||||
[true,false].forEach(function(relay) {
|
||||
var message = new b.commands.version({relay: relay});
|
||||
message.relay.should.equal(relay);
|
||||
var messageBuf = message.getPayload();
|
||||
var newMessage = b.commands.version.fromBuffer(messageBuf);
|
||||
newMessage.relay.should.equal(relay);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Inventory helpers for: ' + b.inventoryCommands.join(', '), function() {
|
||||
|
||||
var constructors = b.inventoryCommands;
|
||||
var fakeHash = 'e2dfb8afe1575bfacae1a0b4afc49af7ddda69285857267bae0e22be15f74a3a';
|
||||
|
||||
describe('#forTransaction', function() {
|
||||
constructors.forEach(function(name) {
|
||||
it(name, function() {
|
||||
should.exist(b.commands[name].forTransaction);
|
||||
var message = b.commands[name].forTransaction(fakeHash);
|
||||
should.exist(message);
|
||||
message.should.be.instanceof(b.commands[name]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#forBlock', function() {
|
||||
constructors.forEach(function(name) {
|
||||
it(name, function() {
|
||||
var message = b.commands[name].forBlock(fakeHash);
|
||||
should.exist(message);
|
||||
message.should.be.instanceof(b.commands[name]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#forFilteredBlock', function() {
|
||||
constructors.forEach(function(name) {
|
||||
it(name, function() {
|
||||
var message = b.commands[name].forFilteredBlock(fakeHash);
|
||||
should.exist(message);
|
||||
message.should.be.instanceof(b.commands[name]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
160
test/messages/commands/index.js
Normal file
160
test/messages/commands/index.js
Normal file
@ -0,0 +1,160 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var P2P = require('../../../');
|
||||
var Messages = P2P.Messages;
|
||||
var sinon = require('sinon');
|
||||
var bitcore = require('bitcore');
|
||||
|
||||
describe('Command Messages', function() {
|
||||
|
||||
var messages = new Messages();
|
||||
var commandsMap = {
|
||||
version: 'Version',
|
||||
verack: 'VerAck',
|
||||
ping: 'Ping',
|
||||
pong: 'Pong',
|
||||
block: 'Block',
|
||||
tx: 'Transaction',
|
||||
getdata: 'GetData',
|
||||
headers: 'Headers',
|
||||
notfound: 'NotFound',
|
||||
inv: 'Inventory',
|
||||
addr: 'Address',
|
||||
alert: 'Alert',
|
||||
reject: 'Reject',
|
||||
merkleblock: 'MerkleBlock',
|
||||
filterload: 'FilterLoad',
|
||||
filteradd: 'FilterAdd',
|
||||
filterclear: 'FilterClear',
|
||||
getblocks: 'GetBlocks',
|
||||
getheaders: 'GetHeaders',
|
||||
mempool: 'MemPool',
|
||||
getaddr: 'GetAddr'
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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('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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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('MerkleBlock', function() {
|
||||
|
||||
it('should return null buffer for payload', function() {
|
||||
var message = messages.MerkleBlock();
|
||||
var payload = message.getPayload();
|
||||
payload.length.should.equal(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('Default Magic Number', function() {
|
||||
|
||||
Object.keys(commandsMap).forEach(function(command) {
|
||||
it(command, function() {
|
||||
var messageConstructor = require('../../../lib/messages/commands/' + command)({});
|
||||
var message = new messageConstructor();
|
||||
var defaultMagic = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
message.magicNumber.should.equal(defaultMagic);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
81
test/messages/index.js
Normal file
81
test/messages/index.js
Normal file
@ -0,0 +1,81 @@
|
||||
'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');
|
||||
|
||||
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 magicNumber = bitcore.Networks.defaultNetwork.networkMagic.readUInt32LE(0);
|
||||
var messages = new Messages({
|
||||
magicNumber: magicNumber,
|
||||
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.magicNumber.should.equal(magicNumber);
|
||||
});
|
||||
});
|
||||
|
||||
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]);
|
||||
messages[name].super_.should.equal(Messages.Message);
|
||||
var message = messages[name]();
|
||||
should.exist(message);
|
||||
message.should.be.instanceof(messages[name]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parseBuffer', function() {
|
||||
it('fails with invalid command', function() {
|
||||
var invalidCommand = 'f9beb4d96d616c6963696f757300000025000000bd5e830c' +
|
||||
'0102000000ec3995c1bf7269ff728818a65e53af00cbbee6b6eca8ac9ce7bc79d87' +
|
||||
'7041ed8';
|
||||
var fails = function() {
|
||||
messages.parseBuffer(buildMessage(invalidCommand));
|
||||
};
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
29
test/messages/message.js
Normal file
29
test/messages/message.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
var should = require('chai').should();
|
||||
var P2P = require('../../');
|
||||
var Message = P2P.Messages.Message;
|
||||
|
||||
describe('Message', function() {
|
||||
|
||||
describe('@constructor', function() {
|
||||
it('construct with magic number and command', function() {
|
||||
var message = new Message({magicNumber: 0xd9b4bef9, command: 'command'});
|
||||
message.command.should.equal('command');
|
||||
message.magicNumber.should.equal(0xd9b4bef9);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#toBuffer', function() {
|
||||
it('serialize to a buffer', function() {
|
||||
var message = new Message({magicNumber: 0xd9b4bef9, command: 'command'});
|
||||
message.getPayload = function() {
|
||||
return new Buffer(0);
|
||||
};
|
||||
var buffer = message.toBuffer();
|
||||
var expectedBuffer = new Buffer('f9beb4d9636f6d6d616e640000000000000000005df6e0e2', 'hex');
|
||||
buffer.should.deep.equal(expectedBuffer);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
33
test/messages/util.js
Normal file
33
test/messages/util.js
Normal file
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
/* jshint unused: false */
|
||||
|
||||
var should = require('chai').should();
|
||||
var utils = require('../../lib/messages/utils');
|
||||
var bitcore = require('bitcore');
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
119
test/peer.js
119
test/peer.js
@ -12,8 +12,11 @@ var fs = require('fs');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var _ = bitcore.deps._;
|
||||
var p2p = require('../');
|
||||
var Peer = p2p.Peer;
|
||||
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() {
|
||||
@ -52,7 +55,7 @@ describe('Peer', function() {
|
||||
return stub;
|
||||
};
|
||||
peer.on('connect', function() {
|
||||
dataCallback(fs.readFileSync('./test/connection.log'));
|
||||
dataCallback(fs.readFileSync('./test/data/connection.log'));
|
||||
});
|
||||
var check = function(message) {
|
||||
received[message.command]++;
|
||||
@ -68,43 +71,42 @@ describe('Peer', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should be able to create instance', function() {
|
||||
it('create instance', function() {
|
||||
var peer = new Peer('localhost');
|
||||
peer.host.should.equal('localhost');
|
||||
peer.network.should.equal(Networks.livenet);
|
||||
peer.port.should.equal(Networks.livenet.port);
|
||||
});
|
||||
|
||||
it('should be able to create instance setting a port', function() {
|
||||
var peer = new Peer('localhost', 8111);
|
||||
it('create instance setting a port', function() {
|
||||
var peer = new Peer({host: 'localhost', port: 8111});
|
||||
peer.host.should.equal('localhost');
|
||||
peer.network.should.equal(Networks.livenet);
|
||||
peer.port.should.equal(8111);
|
||||
});
|
||||
|
||||
it('should be able to create instance setting a network', function() {
|
||||
var peer = new Peer('localhost', Networks.testnet);
|
||||
it('create instance setting a network', function() {
|
||||
var peer = new Peer({host: 'localhost', network: Networks.testnet});
|
||||
peer.host.should.equal('localhost');
|
||||
peer.network.should.equal(Networks.testnet);
|
||||
peer.port.should.equal(Networks.testnet.port);
|
||||
});
|
||||
|
||||
it('should be able to create instance setting port and network', function() {
|
||||
var peer = new Peer('localhost', 8111, Networks.testnet);
|
||||
it('create instance setting port and network', function() {
|
||||
var peer = new Peer({host: 'localhost', port: 8111, network: Networks.testnet});
|
||||
peer.host.should.equal('localhost');
|
||||
peer.network.should.equal(Networks.testnet);
|
||||
peer.port.should.equal(8111);
|
||||
});
|
||||
|
||||
it('should support creating instance without new', function() {
|
||||
var peer = Peer('localhost', 8111, Networks.testnet);
|
||||
it('create instance without new', function() {
|
||||
var peer = Peer({host: 'localhost', port: 8111, network: Networks.testnet});
|
||||
peer.host.should.equal('localhost');
|
||||
peer.network.should.equal(Networks.testnet);
|
||||
peer.port.should.equal(8111);
|
||||
});
|
||||
|
||||
it('should be able to set a proxy', function() {
|
||||
it('set a proxy', function() {
|
||||
var peer, peer2, socket;
|
||||
|
||||
peer = new Peer('localhost');
|
||||
@ -121,18 +123,95 @@ describe('Peer', function() {
|
||||
peer.should.equal(peer2);
|
||||
});
|
||||
|
||||
it('Peer.relay setting set properly', function() {
|
||||
var peer = new Peer('localhost');
|
||||
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);
|
||||
});
|
||||
|
||||
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.connect();
|
||||
peer.socket.emit('error', 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('localhost', null, null, false);
|
||||
var peer2 = new Peer({host: 'localhost', relay: false});
|
||||
peer2.relay.should.equal(false);
|
||||
var peer3 = new Peer('localhost', null, null, true);
|
||||
var peer3 = new Peer({host: 'localhost', relay: true});
|
||||
peer3.relay.should.equal(true);
|
||||
});
|
||||
|
||||
it('Peer.relay setting respected', function() {
|
||||
it('relay setting respected', function() {
|
||||
[true,false].forEach(function(relay) {
|
||||
var peer = new Peer('localhost', null, null, relay);
|
||||
var peer = new Peer({host: 'localhost', relay: relay});
|
||||
var peerSendMessageStub = sinon.stub(Peer.prototype, 'sendMessage', function(message) {
|
||||
message.relay.should.equal(relay);
|
||||
});
|
||||
|
||||
278
test/pool.js
278
test/pool.js
@ -11,15 +11,21 @@ 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 dns = require('dns');
|
||||
var sinon = require('sinon');
|
||||
var net = require('net');
|
||||
|
||||
function getPayloadBuffer(messageBuffer) {
|
||||
return new Buffer(messageBuffer.slice(48), 'hex');
|
||||
}
|
||||
|
||||
describe('Pool', function() {
|
||||
|
||||
it('should be able to create instance', function() {
|
||||
it('create instance', function() {
|
||||
var pool = new Pool();
|
||||
should.exist(pool.network);
|
||||
expect(pool.network).to.satisfy(function(network) {
|
||||
@ -27,27 +33,33 @@ describe('Pool', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to create instance setting the network', function() {
|
||||
var pool = new Pool(Networks.testnet);
|
||||
it('create instance setting the network', function() {
|
||||
var pool = new Pool({network: Networks.testnet});
|
||||
pool.network.should.equal(Networks.testnet);
|
||||
});
|
||||
|
||||
it('should discover peers via dns', function() {
|
||||
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(Networks.livenet);
|
||||
var pool = new Pool({network: Networks.livenet});
|
||||
pool.connect();
|
||||
pool.disconnect();
|
||||
pool._addrs.length.should.equal(3);
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('can optionally connect without dns seeds', function() {
|
||||
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: [
|
||||
@ -63,15 +75,17 @@ describe('Pool', function() {
|
||||
}
|
||||
]
|
||||
};
|
||||
var pool = new Pool(Networks.livenet, options);
|
||||
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: [
|
||||
{
|
||||
@ -81,11 +95,11 @@ describe('Pool', function() {
|
||||
}
|
||||
]
|
||||
};
|
||||
var pool = new Pool(Networks.livenet, options);
|
||||
var pool = new Pool(options);
|
||||
pool._addrs.length.should.equal(1);
|
||||
});
|
||||
|
||||
it('should add new addrs as they are announced over the network', function(done) {
|
||||
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() {
|
||||
@ -95,12 +109,13 @@ describe('Pool', function() {
|
||||
|
||||
// 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);
|
||||
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: [
|
||||
{
|
||||
@ -111,7 +126,7 @@ describe('Pool', function() {
|
||||
]
|
||||
};
|
||||
|
||||
var pool = new Pool(Networks.testnet, options);
|
||||
var pool = new Pool(options);
|
||||
|
||||
// listen for the event
|
||||
pool.on('peeraddr', function(peer, message) {
|
||||
@ -145,12 +160,13 @@ describe('Pool', function() {
|
||||
|
||||
// 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);
|
||||
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: [
|
||||
@ -162,7 +178,7 @@ describe('Pool', function() {
|
||||
]
|
||||
};
|
||||
|
||||
var pool = new Pool(Networks.testnet, options);
|
||||
var pool = new Pool(options);
|
||||
|
||||
// listen for the event
|
||||
pool.on('peeraddr', function(peer, message) {
|
||||
@ -186,7 +202,7 @@ describe('Pool', function() {
|
||||
|
||||
});
|
||||
|
||||
it('should propagate connect, ready, and disconnect peer events', function(done) {
|
||||
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');
|
||||
@ -196,7 +212,7 @@ describe('Pool', function() {
|
||||
});
|
||||
var poolRemoveStub = sinon.stub(Pool.prototype, '_removeConnectedPeer', function() {});
|
||||
|
||||
var pool = new Pool(null, {
|
||||
var pool = new Pool({
|
||||
dnsSeed: false,
|
||||
addrs: [
|
||||
{
|
||||
@ -231,13 +247,13 @@ describe('Pool', function() {
|
||||
pool.connect();
|
||||
});
|
||||
|
||||
it('should propagate Pool.relay property to peers', function(done) {
|
||||
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(null,{ relay: relay, dnsSeed: false });
|
||||
var pool = new Pool({relay: relay, dnsSeed: false});
|
||||
pool._addAddr({ ip: { v4: 'localhost' } });
|
||||
pool.on('peerconnect', function(peer, addr) {
|
||||
peer.relay.should.equal(relay);
|
||||
@ -251,16 +267,16 @@ describe('Pool', function() {
|
||||
peerConnectStub.restore();
|
||||
});
|
||||
|
||||
it('should output the console correctly', function() {
|
||||
it('output the console correctly', function() {
|
||||
var pool = new Pool();
|
||||
pool.inspect().should.equal('<Pool network: livenet, connected: 0, available: 0>');
|
||||
});
|
||||
|
||||
it('should emit seederrors with error', function(done) {
|
||||
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(Networks.livenet, {maxSize: 1});
|
||||
var pool = new Pool({network: Networks.livenet, maxSize: 1});
|
||||
pool.once('seederror', function(error) {
|
||||
should.exist(error);
|
||||
pool.disconnect();
|
||||
@ -270,11 +286,11 @@ describe('Pool', function() {
|
||||
pool.connect();
|
||||
});
|
||||
|
||||
it('should emit seederrors with notfound', function(done) {
|
||||
it('emit seederrors with notfound', function(done) {
|
||||
var dnsStub = sinon.stub(dns, 'resolve', function(seed, callback) {
|
||||
callback(null, []);
|
||||
});
|
||||
var pool = new Pool(Networks.livenet, {maxSize: 1});
|
||||
var pool = new Pool({network: Networks.livenet, maxSize: 1});
|
||||
pool.once('seederror', function(error) {
|
||||
should.exist(error);
|
||||
pool.disconnect();
|
||||
@ -284,4 +300,218 @@ describe('Pool', function() {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
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('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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user