Merge pull request #39 from throughnothing/filter-messages

Add FilterLoad, FilterAdd, and FilterClear Messages
This commit is contained in:
Manuel Aráoz 2015-02-19 16:48:25 -03:00
commit 88d3a5a351
8 changed files with 228 additions and 3 deletions

47
lib/bloomfilter.js Normal file
View File

@ -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;

View File

@ -4,5 +4,6 @@
module.exports = {
Messages: require('./messages'),
Peer: require('./peer'),
Pool: require('./pool')
Pool: require('./pool'),
BloomFilter: require('./bloomfilter')
};

View File

@ -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;
@ -833,6 +835,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) || 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
*

View File

@ -106,7 +106,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'
];

View File

@ -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"

77
test/bloomfilter.js Normal file
View File

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

View File

@ -65,5 +65,17 @@
"PONG": {
"message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c",
"payload": "6b86480ae969867c"
},
"FILTERLOAD": {
"message": "f9beb4d966696c7465726c6f61640000210000002ef97a71170000000000000000000000000000000000000000000000060000000000000000",
"payload": "170000000000000000000000000000000000000000000000060000000000000000"
},
"FILTERADD": {
"message": "f9beb4d966696c7465726c6f61640000150000009727ea0a1499108ad8ed9bb6274d3980bab5a85c048f0950c8",
"payload": "1499108ad8ed9bb6274d3980bab5a85c048f0950c8"
},
"FILTERCLEAR": {
"message": "f9beb4d966696c7465726c6365617200000000005df6e0e2",
"payload": ""
}
}

View File

@ -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() {
@ -131,4 +135,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);
});
});