Merge pull request #39 from throughnothing/filter-messages
Add FilterLoad, FilterAdd, and FilterClear Messages
This commit is contained in:
commit
88d3a5a351
47
lib/bloomfilter.js
Normal file
47
lib/bloomfilter.js
Normal 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;
|
||||
@ -4,5 +4,6 @@
|
||||
module.exports = {
|
||||
Messages: require('./messages'),
|
||||
Peer: require('./peer'),
|
||||
Pool: require('./pool')
|
||||
Pool: require('./pool'),
|
||||
BloomFilter: require('./bloomfilter')
|
||||
};
|
||||
|
||||
@ -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
|
||||
*
|
||||
|
||||
@ -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'
|
||||
];
|
||||
|
||||
|
||||
|
||||
@ -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
77
test/bloomfilter.js
Normal 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')));
|
||||
});
|
||||
|
||||
});
|
||||
@ -65,5 +65,17 @@
|
||||
"PONG": {
|
||||
"message": "f9beb4d9706f6e67000000000000000008000000c6466f1e6b86480ae969867c",
|
||||
"payload": "6b86480ae969867c"
|
||||
},
|
||||
"FILTERLOAD": {
|
||||
"message": "f9beb4d966696c7465726c6f61640000210000002ef97a71170000000000000000000000000000000000000000000000060000000000000000",
|
||||
"payload": "170000000000000000000000000000000000000000000000060000000000000000"
|
||||
},
|
||||
"FILTERADD": {
|
||||
"message": "f9beb4d966696c7465726c6f61640000150000009727ea0a1499108ad8ed9bb6274d3980bab5a85c048f0950c8",
|
||||
"payload": "1499108ad8ed9bb6274d3980bab5a85c048f0950c8"
|
||||
},
|
||||
"FILTERCLEAR": {
|
||||
"message": "f9beb4d966696c7465726c6365617200000000005df6e0e2",
|
||||
"payload": ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user