Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extensible Messages #48

Merged
merged 48 commits into from
Mar 17, 2015
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
9a813ba
Extensibility
Mar 10, 2015
0b20724
Changed options for Pool
Mar 11, 2015
e8f0725
Moved commands, message and messages to directory
Mar 12, 2015
11bee8b
Improved API:
Mar 12, 2015
32a28b1
Removed bufferput dependency
Mar 12, 2015
42288e8
removed "buildFromObject" alias
Mar 12, 2015
7e29af4
added missing precondition and moved buffer.skip to new file
Mar 12, 2015
d63c117
added test for inventory fromBufferReader
Mar 12, 2015
1dfe709
added test for pool.sendMessage
Mar 12, 2015
608e41d
added test coverage for peer
Mar 12, 2015
6ca2f69
Moved block and version messages and shared utils into seperate files.
Mar 12, 2015
3c2afed
moved ping and verack to seperate files
Mar 12, 2015
feda6e9
pass options directly into command messages
Mar 12, 2015
e460bf9
moved the rest of the commands into seperate files
Mar 12, 2015
2eb7712
organized commands to build from an array
Mar 12, 2015
091893b
whitespace cleanup
Mar 12, 2015
faf2bb1
updated bitcoind integration test
Mar 12, 2015
7f61fc6
listen for incoming connections
Mar 12, 2015
6461748
make listening optional and fixed peer test
Mar 12, 2015
641443f
expose message construtors by name
Mar 13, 2015
4ecb34e
updated bitconid integration test to use exposed message constructors
Mar 13, 2015
a8b8c59
added default options for all command messages, and added tests
Mar 13, 2015
8c9babc
added tests for command edge cases
Mar 13, 2015
3b53593
added tests for pool.listen and improved arguments for tx and block m…
Mar 13, 2015
97f39db
added tests for pool _addConnectedPeer
Mar 13, 2015
6007dc6
updated to use mapped constructers and removed build method
Mar 14, 2015
e31f28e
cleanup builder options
Mar 14, 2015
cfbee2d
update documentation for messages
Mar 14, 2015
a57f864
updated documentation for peer and pool
Mar 14, 2015
a15f11c
add test for default magic for command messages
Mar 14, 2015
6db209b
added test for message utils
Mar 14, 2015
1545abb
added test for version message handling
Mar 14, 2015
e62ddd9
added test for handling addr times and v6 addresses
Mar 14, 2015
ede5f0b
add test for seed event, and remove test stubs
Mar 14, 2015
f17bbf5
updated jsdocs for peer and pool
Mar 16, 2015
42c829e
added jsdocs for inventory
Mar 16, 2015
50d7d37
added jsdocs for bloomfilter and messages
Mar 16, 2015
4c4d53a
add jsdocs for inv, getdata and notfound
Mar 16, 2015
42985a7
added jsdocs for message commands
Mar 16, 2015
f1aa4d3
updated docs for message commands
Mar 16, 2015
39d1ae9
fix jsdocs for message commands
Mar 16, 2015
8f2d008
updated readme and fixed image scaling issue
Mar 16, 2015
664ceb2
improved peer jsdoc, fixed docs for messages, and switched to use svg…
Mar 16, 2015
16aa989
fixed spelling
Mar 16, 2015
f6e9c43
upgrade bloom-filter to 0.2.0
Mar 16, 2015
c0e3bdb
removed fromObject method that is nolonger needed
Mar 16, 2015
7cfe6d1
added unit tests for buffers.skip
Mar 17, 2015
34c3846
moved inventory helper functions to builder
Mar 17, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/messages.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,47 @@
---
title: Messages
description: A superclass for the messages of the bitcoin network
---
# Messages

The bitcoin protocol specifies a set of [messages](https://en.bitcoin.it/wiki/Protocol_specification) that can be sent from peer to peer. `bitcore-p2p` provides support for some of these messages.

To create a messages, you can use any of the message constructors, here is a simple example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: message

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in: 664ceb2


```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
Expand Down
17 changes: 9 additions & 8 deletions docs/peer.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
title: Peer
description: The Peer class provides a simple interface for connecting to a node in the bitcoin network.
---
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
17 changes: 15 additions & 2 deletions docs/pool.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
title: Pool
description: A simple interface to create and maintain a set of connections to bitcoin nodes.
---
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -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.
59 changes: 32 additions & 27 deletions integration/bitcoind.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -92,7 +94,7 @@ describe('Integration with ' + network.name + ' bitcoind', function() {
});
cb();
});
var message = new Messages.GetAddresses();
var message = messages.GetAddr();
peer.sendMessage(message);
});
});
Expand All @@ -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);
});
Expand All @@ -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);
});
});
Expand All @@ -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);
});
});
41 changes: 18 additions & 23 deletions lib/bloomfilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is dropping the checks the way to go here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there are checks already in BloomFilter (e.g. https://github.com/bitpay/bloom-filter/blob/master/lib/filter.js#L69). Though these may need to be moved to apply in the main constructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was going to suggest to use .create but seems like it's a slightly different use case. Spawned bitpay/bloom-filter#1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merged!

};

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);
Expand Down
23 changes: 23 additions & 0 deletions lib/buffers.js
Original file line number Diff line number Diff line change
@@ -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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a pretty useful function. Should we submit it to the global buffers module https://github.com/substack/node-buffers? I'd be happy to give it a go if that's the right thing to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, we may not need this dependency either though, since we may only be using push(). Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I wanted to take out this dependency as well at some point, don't remember what changed my mind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spawned an issue: #52

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you unit-test the skip function please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added test in: braydonf@7cfe6d1

6 changes: 4 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -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')
};
Loading