walletdb: use bloom filter for rescanning.

This commit is contained in:
Christopher Jeffrey 2016-10-24 18:27:25 -07:00
parent 3bffbed25a
commit 5a4ebccfd4
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 77 additions and 129 deletions

View File

@ -1099,15 +1099,16 @@ ChainDB.prototype.getTXByAddress = co(function* getTXByAddress(addresses) {
/** /**
* Scan the blockchain for transactions containing specified address hashes. * Scan the blockchain for transactions containing specified address hashes.
* @param {Hash} start - Block hash to start at. * @param {Hash} start - Block hash to start at.
* @param {Hash[]} hashes - Address hashes. * @param {Bloom} filter - Bloom filter containing tx and address hashes.
* @param {Function} iter - Iterator. * @param {Function} iter - Iterator.
* @returns {Promise} * @returns {Promise}
*/ */
ChainDB.prototype.scan = co(function* scan(start, filter, iter) { ChainDB.prototype.scan = co(function* scan(start, filter, iter) {
var total = 0; var total = 0;
var i, j, entry, hashes, hash, tx, txs, block; var i, j, entry, hashes;
var input, prevout, found, txid; var hash, tx, txs, block;
var input, prevout, found;
if (start == null) if (start == null)
start = this.network.genesis.hash; start = this.network.genesis.hash;
@ -1117,9 +1118,6 @@ ChainDB.prototype.scan = co(function* scan(start, filter, iter) {
else else
this.logger.info('Scanning from block %s.', utils.revHex(start)); this.logger.info('Scanning from block %s.', utils.revHex(start));
if (Array.isArray(filter))
filter = utils.toMap(filter);
entry = yield this.getEntry(start); entry = yield this.getEntry(start);
if (!entry) if (!entry)
@ -1149,29 +1147,29 @@ ChainDB.prototype.scan = co(function* scan(start, filter, iter) {
tx = block.txs[i]; tx = block.txs[i];
found = false; found = false;
for (j = 0; j < tx.inputs.length; j++) { if (!tx.isCoinbase()) {
input = tx.inputs[j]; for (j = 0; j < tx.inputs.length; j++) {
prevout = input.prevout; input = tx.inputs[j];
if (filter[prevout.hash]) { prevout = input.prevout;
txs.push(tx); if (filter.test(prevout.hash, 'hex')) {
txid = tx.hash('hex'); txs.push(tx);
filter[txid] = true; filter.add(tx.hash());
found = true; found = true;
break; break;
}
} }
if (found)
continue;
} }
if (found) hashes = tx.getOutputHashes();
continue;
hashes = tx.getOutputHashes('hex');
for (j = 0; j < hashes.length; j++) { for (j = 0; j < hashes.length; j++) {
hash = hashes[j]; hash = hashes[j];
if (filter[hash]) { if (filter.test(hash)) {
txs.push(tx); txs.push(tx);
txid = tx.hash('hex'); filter.add(tx.hash());
filter[txid] = true;
break; break;
} }
} }

View File

@ -17,6 +17,7 @@ var HTTPBase = require('./base');
var utils = require('../utils/utils'); var utils = require('../utils/utils');
var co = require('../utils/co'); var co = require('../utils/co');
var Address = require('../primitives/address'); var Address = require('../primitives/address');
var Bloom = require('../utils/bloom');
var TX = require('../primitives/tx'); var TX = require('../primitives/tx');
var KeyRing = require('../primitives/keyring'); var KeyRing = require('../primitives/keyring');
var Outpoint = require('../primitives/outpoint'); var Outpoint = require('../primitives/outpoint');
@ -1226,24 +1227,6 @@ HTTPServer.prototype._initIO = function _initIO() {
callback(); callback();
}); });
socket.on('unwatch hash', function(args, callback) {
var hashes = args[0];
if (!Array.isArray(hashes))
return callback({ error: 'Invalid parameter.' });
if (!socket.api)
return callback({ error: 'Not authorized.' });
try {
socket.removeFilter(hashes);
} catch (e) {
return callback({ error: e.message });
}
callback();
});
socket.on('estimate fee', function(args, callback) { socket.on('estimate fee', function(args, callback) {
var blocks = args[0]; var blocks = args[0];
var rate; var rate;
@ -1433,8 +1416,7 @@ function ClientSocket(server, socket) {
this.host = socket.conn.remoteAddress; this.host = socket.conn.remoteAddress;
this.timeout = null; this.timeout = null;
this.auth = false; this.auth = false;
this.filter = {}; this.filter = null;
this.filterCount = 0;
this.api = false; this.api = false;
this.node = this.server.node; this.node = this.server.node;
@ -1483,23 +1465,8 @@ ClientSocket.prototype._init = function _init() {
ClientSocket.prototype.addFilter = function addFilter(hashes) { ClientSocket.prototype.addFilter = function addFilter(hashes) {
var i, hash; var i, hash;
for (i = 0; i < hashes.length; i++) { if (!this.filter)
hash = Address.getHash(hashes[i], 'hex'); this.filter = Bloom.fromRate(100000, 0.001, -1);
if (!hash)
throw new Error('Bad hash.');
if (!this.filter[hash]) {
this.filter[hash] = true;
this.filterCount++;
if (this.pool.options.spv)
this.pool.watch(hash, 'hex');
}
}
};
ClientSocket.prototype.removeFilter = function removeFilter(hashes) {
var i, hash;
for (i = 0; i < hashes.length; i++) { for (i = 0; i < hashes.length; i++) {
hash = Address.getHash(hashes[i], 'hex'); hash = Address.getHash(hashes[i], 'hex');
@ -1507,10 +1474,10 @@ ClientSocket.prototype.removeFilter = function removeFilter(hashes) {
if (!hash) if (!hash)
throw new Error('Bad hash.'); throw new Error('Bad hash.');
if (this.filter[hash]) { this.filter.add(hash, 'hex');
delete this.filter[hash];
this.filterCount--; if (this.pool.options.spv)
} this.pool.watch(hash, 'hex');
} }
}; };
@ -1578,7 +1545,7 @@ ClientSocket.prototype.testBlock = function testBlock(block) {
var txs = []; var txs = [];
var i, tx; var i, tx;
if (this.filterCount === 0) if (!this.filter)
return; return;
for (i = 0; i < block.txs.length; i++) { for (i = 0; i < block.txs.length; i++) {
@ -1594,18 +1561,33 @@ ClientSocket.prototype.testBlock = function testBlock(block) {
}; };
ClientSocket.prototype.testFilter = function testFilter(tx) { ClientSocket.prototype.testFilter = function testFilter(tx) {
var i, hashes, hash; var i, hashes, hash, input, prevout;
if (this.filterCount === 0) if (!this.filter)
return; return false;
hashes = tx.getHashes('hex'); if (!tx.isCoinbase()) {
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
if (this.filter.test(prevout.hash, 'hex')) {
this.filter.add(tx.hash());
return true;
}
}
}
hashes = tx.getOutputHashes();
for (i = 0; i < hashes.length; i++) { for (i = 0; i < hashes.length; i++) {
hash = hashes[i]; hash = hashes[i];
if (this.filter[hash]) if (this.filter.test(hash)) {
this.filter.add(tx.hash());
return true; return true;
}
} }
return false;
}; };
ClientSocket.prototype.scan = co(function* scan(start) { ClientSocket.prototype.scan = co(function* scan(start) {

View File

@ -185,9 +185,7 @@ function WalletDB(options) {
// lose membership, even if quality // lose membership, even if quality
// degrades. // degrades.
// Memory used: 1.7mb // Memory used: 1.7mb
this.filter = this.options.useFilter !== false this.filter = Bloom.fromRate(1000000, 0.001, -1);
? Bloom.fromRate(1000000, 0.001, -1)
: null;
this.db = ldb({ this.db = ldb({
location: this.options.location, location: this.options.location,
@ -239,7 +237,6 @@ WalletDB.prototype._open = co(function* open() {
'WalletDB loaded (depth=%d, height=%d).', 'WalletDB loaded (depth=%d, height=%d).',
this.depth, this.height); this.depth, this.height);
yield this.loadFilter();
yield this.connect(); yield this.connect();
yield this.resend(); yield this.resend();
}); });
@ -285,18 +282,16 @@ WalletDB.prototype.connect = co(function* connect() {
*/ */
WalletDB.prototype._connect = co(function* connect() { WalletDB.prototype._connect = co(function* connect() {
var hashes, tip, height; var hashes = yield this.getFilterHashes();
var tip, height;
this.logger.info('Adding %d hashes to filter.', hashes.length);
this.addFilter(hashes);
if (!this.client) if (!this.client)
return; return;
// yield this.client.connect();
// yield this.client.watchChain();
hashes = yield this.getFilterHashes();
yield this.client.watchHash(hashes);
if (this.options.noScan) { if (this.options.noScan) {
tip = yield this.client.getTip(); tip = yield this.client.getTip();
@ -342,18 +337,10 @@ WalletDB.prototype.rescan = co(function* rescan(height) {
*/ */
WalletDB.prototype._rescan = co(function* rescan(height) { WalletDB.prototype._rescan = co(function* rescan(height) {
var hashes;
if (!this.client) if (!this.client)
return; return;
assert(utils.isNumber(height), 'Must pass in a height.'); yield this.scan(height);
hashes = yield this.getFilterHashes();
yield this.client.watchHash(hashes);
yield this.scan(height, hashes);
}); });
/** /**
@ -364,7 +351,7 @@ WalletDB.prototype._rescan = co(function* rescan(height) {
* @returns {Promise} * @returns {Promise}
*/ */
WalletDB.prototype.scan = co(function* scan(height, hashes) { WalletDB.prototype.scan = co(function* scan(height) {
var self = this; var self = this;
var blocks; var blocks;
@ -383,9 +370,9 @@ WalletDB.prototype.scan = co(function* scan(height, hashes) {
yield this.rollback(height); yield this.rollback(height);
this.logger.info('Scanning for %d hashes.', hashes.length); this.logger.info('Scanning for blocks.');
yield this.client.scan(this.tip.hash, hashes, function(block, txs) { yield this.client.scan(this.tip.hash, this.filter, function(block, txs) {
return self._addBlock(block, txs); return self._addBlock(block, txs);
}); });
}); });
@ -467,12 +454,12 @@ WalletDB.prototype.wipe = co(function* wipe() {
} }
gte = new Buffer(33); gte = new Buffer(33);
key.fill(0); gte.fill(0);
key[0] = 0x62; gte[0] = 0x62;
lte = new Buffer(33); lte = new Buffer(33);
key.fill(255); lte.fill(255);
key[0] = 0x62; lte[0] = 0x62;
keys = yield this.db.keys({ keys = yield this.db.keys({
gte: gte, gte: gte,
@ -648,26 +635,6 @@ WalletDB.prototype.commit = co(function* commit(wallet) {
wallet.pathCache.commit(); wallet.pathCache.commit();
}); });
/**
* Load the bloom filter into memory.
* @private
* @returns {Promise}
*/
WalletDB.prototype.loadFilter = co(function* loadFilter() {
var i, hashes, hash;
if (!this.filter)
return;
hashes = yield this.getFilterHashes();
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
this.filter.add(hash, 'hex');
}
});
/** /**
* Test the bloom filter against a tx or address hash. * Test the bloom filter against a tx or address hash.
* @private * @private
@ -676,9 +643,6 @@ WalletDB.prototype.loadFilter = co(function* loadFilter() {
*/ */
WalletDB.prototype.testFilter = function testFilter(hash) { WalletDB.prototype.testFilter = function testFilter(hash) {
if (!this.filter)
return true;
return this.filter.test(hash, 'hex'); return this.filter.test(hash, 'hex');
}; };
@ -688,11 +652,19 @@ WalletDB.prototype.testFilter = function testFilter(hash) {
* @param {Hash} hash * @param {Hash} hash
*/ */
WalletDB.prototype.addFilter = function addFilter(hash) { WalletDB.prototype.addFilter = function addFilter(hashes) {
if (!this.filter) var i, hash;
return;
return this.filter.add(hash, 'hex'); if (!Array.isArray(hashes))
hashes = [hashes];
if (this.client)
this.client.watchHash(hashes);
for (i = 0; i < hashes.length; i++) {
hash = hashes[i];
this.filter.add(hash, 'hex');
}
}; };
/** /**
@ -1138,8 +1110,6 @@ WalletDB.prototype.savePath = co(function* savePath(wallet, path) {
this.addFilter(hash); this.addFilter(hash);
yield this.watchHash([hash]);
map = yield this.getPathMap(hash); map = yield this.getPathMap(hash);
if (!map) if (!map)
@ -1660,7 +1630,6 @@ WalletDB.prototype.writeTXMap = function writeTXMap(wallet, hash, map) {
var batch = this.batch(wallet); var batch = this.batch(wallet);
batch.put(layout.e(hash), map.toRaw()); batch.put(layout.e(hash), map.toRaw());
this.addFilter(hash); this.addFilter(hash);
this.watchHash([hash]);
}; };
/** /**

View File

@ -235,9 +235,8 @@ describe('Chain', function() {
it('should rescan for transactions', cob(function* () { it('should rescan for transactions', cob(function* () {
var total = 0; var total = 0;
var hashes = yield walletdb.getHashes();
yield chain.db.scan(null, hashes, function(block, txs) { yield chain.db.scan(null, walletdb.filter, function(block, txs) {
total += txs.length; total += txs.length;
return Promise.resolve(); return Promise.resolve();
}); });