From abbe63666f7b1cea814b31b2013e2a45155ba515 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 16 Feb 2015 01:42:32 -0800 Subject: [PATCH] Add FilterLoad, FilterAdd, and FilterClear Messages + BloomFilter --- lib/bloomfilter.js | 47 +++++++++++++++++++++++++ lib/index.js | 3 +- lib/messages.js | 76 +++++++++++++++++++++++++++++++++++++++++ lib/pool.js | 3 +- package.json | 1 + test/bloomfilter.js | 17 +++++++++ test/data/messages.json | 12 +++++++ test/messages.js | 12 ++++++- 8 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 lib/bloomfilter.js create mode 100644 test/bloomfilter.js diff --git a/lib/bloomfilter.js b/lib/bloomfilter.js new file mode 100644 index 0000000..a994774 --- /dev/null +++ b/lib/bloomfilter.js @@ -0,0 +1,47 @@ +'use strict'; + +var bitcore = require('bitcore'); +var BloomFilter = require('bloom-filter'); +var BufferReader = bitcore.encoding.BufferReader; +var BufferWriter = bitcore.encoding.BufferWriter; +var $ = bitcore.util.preconditions; + + +BloomFilter.fromBuffer = function fromBuffer(payload) { + 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()); + } + + return new BloomFilter({ + vData: vData, + nHashFuncs: nHashFuncs, + nTweak: nTweak, + nFlags: nFlags + }); +} + +BloomFilter.prototype.toBuffer = function toBuffer() { + var bw = new BufferWriter(); + bw.writeVarintNum(this.vData.length); + for(var i = 0; i < this.vData.length; i++) { + bw.writeUInt8(this.vData[i]); + } + bw.writeUInt32LE(this.nHashFuncs); + bw.writeUInt32LE(this.nTweak); + bw.writeUInt8(this.nFlags); + return bw.concat(); +}; + +module.exports = BloomFilter; diff --git a/lib/index.js b/lib/index.js index 37dd492..b65689a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,5 +4,6 @@ module.exports = { Messages: require('./messages'), Peer: require('./peer'), - Pool: require('./pool') + Pool: require('./pool'), + BloomFilter: require('./bloomfilter') }; diff --git a/lib/messages.js b/lib/messages.js index 05d6d1e..bb1ebd9 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -7,6 +7,7 @@ var Buffers = require('buffers'); var Put = require('bufferput'); var util = require('util'); +var BloomFilter = require('./bloomfilter'); var bitcore = require('bitcore'); var _ = bitcore.deps._; @@ -14,6 +15,7 @@ var _ = bitcore.deps._; var BlockHeaderModel = bitcore.BlockHeader; var BlockModel = bitcore.Block; var BufferReader = bitcore.encoding.BufferReader; +var BufferWriter = bitcore.encoding.BufferWriter; var BufferUtil = bitcore.util.buffer; var $ = bitcore.util.preconditions; var Hash = bitcore.crypto.Hash; @@ -826,6 +828,80 @@ function GetHeaders(starts, stop) { 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 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.filterload = 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 * diff --git a/lib/pool.js b/lib/pool.js index 56fdcd7..34b5891 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -86,7 +86,8 @@ Pool.MaxConnectedPeers = 8; Pool.RetrySeconds = 30; Pool.PeerEvents = ['version', 'inv', 'getdata', 'ping', 'pong', 'addr', 'getaddr', 'verack', 'reject', 'alert', 'headers', 'block', - 'tx', 'getblocks', 'getheaders', 'error' + 'tx', 'getblocks', 'getheaders', 'error', 'filterload', 'filteradd', + 'filterclear' ]; diff --git a/package.json b/package.json index 0e65ac3..a344506 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ }, "dependencies": { "bitcore": "^0.10.2", + "bloom-filter": "^0.1.1", "bufferput": "^0.1.2", "buffers": "^0.1.1", "socks5-client": "^0.3.6" diff --git a/test/bloomfilter.js b/test/bloomfilter.js new file mode 100644 index 0000000..9a7194e --- /dev/null +++ b/test/bloomfilter.js @@ -0,0 +1,17 @@ +'use strict'; + +var chai = require('chai'); + +var Data = require('./data/messages'); +var P2P = require('../'); +var BloomFilter = P2P.BloomFilter; + + +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); + }); + +}); diff --git a/test/data/messages.json b/test/data/messages.json index 21728f0..b6724f2 100644 --- a/test/data/messages.json +++ b/test/data/messages.json @@ -62,5 +62,17 @@ "PONG": { "message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c", "payload": "6b86480ae969867c" + }, + "FILTERLOAD": { + "message": "f9beb4d966696c7465726c6f61640000210000002ef97a71170000000000000000000000000000000000000000000000060000000000000000", + "payload": "170000000000000000000000000000000000000000000000060000000000000000" + }, + "FILTERADD": { + "message": "f9beb4d966696c7465726c6f61640000150000009727ea0a1499108ad8ed9bb6274d3980bab5a85c048f0950c8", + "payload": "1499108ad8ed9bb6274d3980bab5a85c048f0950c8" + }, + "FILTERCLEAR": { + "message": "f9beb4d966696c7465726c6365617200000000005df6e0e2", + "payload": "" } } diff --git a/test/messages.js b/test/messages.js index 204bfaf..b87f0ef 100644 --- a/test/messages.js +++ b/test/messages.js @@ -8,6 +8,7 @@ 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; @@ -26,6 +27,9 @@ describe('Messages', function() { Alert: 'alert', Reject: 'reject', Block: 'block', + FilterLoad: 'filterload', + FilterAdd: 'filteradd', + FilterClear: 'filterclear', GetBlocks: 'getblocks', GetHeaders: 'getheaders', GetData: 'getdata', @@ -34,7 +38,7 @@ describe('Messages', function() { Transaction: 'tx', NotFound: 'notfound' }; - // TODO: add data for these + // TODO: add data for these var noPayload = ['Reject', 'GetBlocks', 'GetHeaders']; var names = Object.keys(commands); describe('named', function() { @@ -116,4 +120,10 @@ describe('Messages', function() { }); + 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); + }); + });