diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index 24865b0c..ee194597 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -37,7 +37,7 @@ function Chain(node, options) { this.loading = false; this.mempool = node.mempool; this.blockdb = node.blockdb; - this.db = new bcoin.chaindb(node, this); + this.db = new bcoin.chaindb(node, this, options); this.busy = false; this.jobs = []; this.pending = []; diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index 7e7bc309..13857f5c 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -35,8 +35,13 @@ function ChainDB(node, chain, options) { this.chain = chain; this.file = options.file; - if (!this.file) - this.file = bcoin.prefix + '/chain-' + network.type + '.db'; + if (!this.file) { + this.file = bcoin.prefix + + '/chain-' + + (options.spv ? 'spv-' : '') + + network.type + + '.db'; + } this.heightLookup = {}; this.queue = {}; diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index a95f84d1..6311bdb0 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -30,6 +30,7 @@ function TXPool(prefix, db, options) { this.options = options; this.busy = false; this.jobs = []; + this.ids = true; if (options.addressFilter) this._hasAddress = options.addressFilter; @@ -85,32 +86,111 @@ TXPool.prototype.add = function add(tx, callback) { }, callback); } - return self._add(tx, callback); + //if (!this.ids) + return this._add(tx, this.__getIDs(tx), callback); + + return self._getIDs(tx.getAddresses(), function(err, ids) { + if (err) + return callback(err); + + return self._add(tx, ids, callback); + }); }; -TXPool.prototype._hasAddress = function _hasAddress(address, callback) { - if (!address) +// Map of address->id. +TXPool.prototype.__getIDs = function _getIDs(tx, callback) { + var map = tx.getAddresses(); + return map.reduce(function(out, addr) { + out[addr] = [addr]; + return out; + }, {}); +}; + +TXPool.prototype.__hasAddress = function __hasAddress(map, address, callback) { + if (!address || !map[address] || !map[address].length) return callback(null, false); return callback(null, true); }; +TXPool.prototype._getIDs = function getIDs(address, callback) { + var p = this.prefix + '/'; + var self = this; + var map = {}; + + if (Array.isArray(address)) { + address = utils.uniqs(address); + return utils.forEachSerial(address, function(address, next) { + self._getIDs(address, function(err, m) { + if (err) + return next(err); + + map[address] = m[address]; + + next(); + }); + }, function(err) { + if (err) + return callback(err); + + return callback(null, map); + }); + } + + var iter = this.db.db.iterator({ + gte: p + 'a/' + address, + lte: p + 'a/' + address + '~', + keys: true, + values: false, + fillCache: false, + keyAsBuffer: false + }); + + callback = utils.ensure(callback); + + map[address] = []; + + (function next() { + iter.next(function(err, key, value) { + if (err) { + return iter.end(function() { + callback(err); + }); + } + + if (key === undefined) { + return iter.end(function(err) { + if (err) + return callback(err); + map[address] = utils.uniqs(map[address]); + return callback(null, map); + }); + } + + map[address].push(key.split('/')[3]); + + next(); + }); + })(); +}; + // This big scary function is what a persistent tx pool // looks like. It's a semi mempool in that it can handle // receiving txs out of order. -TXPool.prototype._add = function add(tx, callback, force) { +TXPool.prototype._add = function add(tx, map, callback, force) { var self = this; var p = this.prefix + '/'; var hash = tx.hash('hex'); var updated = false; var own = false; + var uniq = {}; var batch; - var unlock = this._lock(add, [tx, callback], force); - if (!unlock) - return; + // var unlock = this._lock(add, [tx, map, callback], force); + // if (!unlock) + // return; function done(err, result) { - unlock(); + //unlock(); if (callback) callback(err, result); }; @@ -138,20 +218,16 @@ TXPool.prototype._add = function add(tx, callback, force) { if (input.isCoinbase()) return; - if (type === 'pubkey' || type === 'multisig') - address = null; - - uaddr = address; - - if (uaddr) { - if (!uniq[uaddr]) - uniq[uaddr] = true; - else - uaddr = null; + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; } - if (uaddr) - batch.del(p + 'p/a/' + uaddr + '/' + hash); + if (uaddr) { + map[uaddr].forEach(function(id) { + batch.del(p + 'p/a/' + id + '/' + hash); + }); + } }); utils.forEachSerial(tx.outputs, function(output, next, i) { @@ -159,20 +235,16 @@ TXPool.prototype._add = function add(tx, callback, force) { var address = output.getAddress(); var uaddr; - if (type === 'pubkey' || type === 'multisig') - address = null; - - uaddr = address; - - if (uaddr) { - if (!uniq[uaddr]) - uniq[uaddr] = true; - else - uaddr = null; + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; } - if (uaddr) - batch.del(p + 'p/a/' + uaddr + '/' + hash); + if (uaddr) { + map[uaddr].forEach(function(id) { + batch.del(p + 'p/a/' + id + '/' + hash); + }); + } self.getCoin(hash, i, function(err, coin) { if (err) @@ -210,8 +282,10 @@ TXPool.prototype._add = function add(tx, callback, force) { // Consume unspent money or add orphans utils.forEachSerial(tx.inputs, function(input, next, i) { var key = input.prevout.hash + '/' + input.prevout.index; + if (input.isCoinbase()) + return next(); self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) { - var type, address; + var type, address, uaddr; if (err) return next(err); @@ -233,17 +307,26 @@ TXPool.prototype._add = function add(tx, callback, force) { type = input.getType(); address = input.getAddress(); - if (type === 'pubkey' || type === 'multisig') - address = null; - - if (input.isCoinbase()) - return next(); + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; + } if (address) { - batch.del( - p + 'u/a/' + address - + '/' + input.prevout.hash - + '/' + input.prevout.index); + map[address].forEach(function(id) { + batch.del( + p + 'u/a/' + id + + '/' + input.prevout.hash + + '/' + input.prevout.index); + }); + } + + if (uaddr) { + map[uaddr].forEach(function(id) { + batch.put(p + 't/a/' + id + '/' + hash, new Buffer([])); + if (tx.ts === 0) + batch.put(p + 'p/a/' + id + '/' + hash, new Buffer([])); + }); } batch.del( @@ -255,7 +338,7 @@ TXPool.prototype._add = function add(tx, callback, force) { } // Only add orphans if this input is ours. - self._hasAddress(input.getAddress(), function(err, result) { + self.__hasAddress(map, input.getAddress(), function(err, result) { if (err) return done(err); @@ -308,7 +391,7 @@ TXPool.prototype._add = function add(tx, callback, force) { // Add unspent outputs or resolve orphans utils.forEachSerial(tx.outputs, function(output, next, i) { // Do not add unspents for outputs that aren't ours. - self._hasAddress(output.getAddress(), function(err, result) { + self.__hasAddress(map, output.getAddress(), function(err, result) { var key, coin; if (err) @@ -382,7 +465,7 @@ TXPool.prototype._add = function add(tx, callback, force) { } function finish(err) { - var type, adddress; + var type, adddress, uaddr; if (err) return next(err); @@ -391,14 +474,26 @@ TXPool.prototype._add = function add(tx, callback, force) { type = output.getType(); address = output.getAddress(); - if (type === 'pubkey' || type === 'multisig') - address = null; + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; + } if (address) { - batch.put( - p + 'u/a/' + address - + '/' + hash + '/' + i, - new Buffer([])); + map[address].forEach(function(id) { + batch.put( + p + 'u/a/' + id + + '/' + hash + '/' + i, + new Buffer([])); + }); + } + + if (uaddr) { + map[uaddr].forEach(function(id) { + batch.put(p + 't/a/' + id + '/' + hash, new Buffer([])); + if (tx.ts === 0) + batch.put(p + 'p/a/' + id + '/' + hash, new Buffer([])); + }); } batch.put(p + 'u/t/' + hash + '/' + i, coin.toRaw()); @@ -424,23 +519,17 @@ TXPool.prototype._add = function add(tx, callback, force) { else batch.put(p + 't/h/' + tx.height + '/' + hash, new Buffer([])); - tx.getAddresses().forEach(function(address) { - batch.put(p + 't/a/' + address + '/' + hash, new Buffer([])); - if (tx.ts === 0) - batch.put(p + 'p/a/' + address + '/' + hash, new Buffer([])); - }); - batch.write(function(err) { if (err) return done(err); - self.emit('tx', tx); + self.emit('tx', tx, map); if (updated) { if (tx.ts !== 0) - self.emit('confirmed', tx); + self.emit('confirmed', tx, map); - self.emit('updated', tx); + self.emit('updated', tx, map); } return done(null, true); @@ -452,12 +541,37 @@ TXPool.prototype._add = function add(tx, callback, force) { TXPool.prototype.remove = function remove(hash, callback) { var self = this; + + if (Array.isArray(hash)) { + return utils.forEachSerial(hash, function(hash, next) { + self.remove(hash, next); + }, callback); + } + + if (hash.hash) + hash = hash.hash('hex'); + + this.getTX(hash, function(err, tx) { + if (err) + return callback(err); + return self._getIDs(tx.getAddresses(), function(err, map) { + if (err) + return callback(err); + + return self._remove(tx, map, callback); + }); + }); +}; + +TXPool.prototype._remove = function remove(hash, map, callback) { + var self = this; var p = this.prefix + '/'; var uniq = {}; if (hash.hash) hash = hash.hash('hex'); + // XXX Remove this this.getTX(hash, function(err, tx) { var batch; @@ -489,29 +603,26 @@ TXPool.prototype.remove = function remove(hash, callback) { if (input.isCoinbase()) return; - if (type === 'pubkey' || type === 'multisig') - address = null; - - uaddr = address; - - if (uaddr) { - if (!uniq[uaddr]) - uniq[uaddr] = true; - else - uaddr = null; + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; } if (uaddr) { - batch.del(p + 't/a/' + uaddr + '/' + hash); - if (tx.ts === 0) - batch.del(p + 'p/a/' + uaddr + '/' + hash); + map[uaddr].forEach(function(id) { + batch.del(p + 't/a/' + id + '/' + hash); + if (tx.ts === 0) + batch.del(p + 'p/a/' + id + '/' + hash); + }); } if (address) { - batch.put(p + 'u/a/' + address - + '/' + input.prevout.hash - + '/' + input.prevout.index, - new Buffer([])); + map[address].forEach(function(id) { + batch.put(p + 'u/a/' + id + + '/' + input.prevout.hash + + '/' + input.prevout.index, + new Buffer([])); + }); } if (input.output) { @@ -529,26 +640,24 @@ TXPool.prototype.remove = function remove(hash, callback) { var address = output.getAddress(); var uaddr; - if (type === 'pubkey' || type === 'multisig') - address = null; - - uaddr = address; - - if (uaddr) { - if (!uniq[uaddr]) - uniq[uaddr] = true; - else - uaddr = null; + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; } if (uaddr) { - batch.del(p + 't/a/' + uaddr + '/' + hash); - if (tx.ts === 0) - batch.del(p + 'p/a/' + uaddr + '/' + hash); + map[uaddr].forEach(function(id) { + batch.del(p + 't/a/' + id + '/' + hash); + if (tx.ts === 0) + batch.del(p + 'p/a/' + id + '/' + hash); + }); } - if (address) - batch.del(p + 'u/a/' + address + '/' + hash + '/' + i); + if (address) { + map[address].forEach(function(id) { + batch.del(p + 'u/a/' + id + '/' + hash + '/' + i); + }); + } batch.del(p + 'u/t/' + hash + '/' + i); }); @@ -566,12 +675,37 @@ TXPool.prototype.remove = function remove(hash, callback) { TXPool.prototype.unconfirm = function unconfirm(hash, callback) { var self = this; + + if (Array.isArray(hash)) { + return utils.forEachSerial(hash, function(hash, next) { + self.unconfirm(hash, next); + }, callback); + } + + if (hash.hash) + hash = hash.hash('hex'); + + this.getTX(hash, function(err, tx) { + if (err) + return callback(err); + return self._getIDs(tx.getAddresses(), function(err, map) { + if (err) + return callback(err); + + return self._unconfirm(tx, map, callback); + }); + }); +}; + +TXPool.prototype._unconfirm = function unconfirm(hash, map, callback) { + var self = this; var p = this.prefix + '/'; var uniq = {}; if (hash.hash) hash = hash.hash('hex'); + // XXX Remove this. this.getTX(hash, function(err, tx) { var batch, height; @@ -600,20 +734,16 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) { if (input.isCoinbase()) return; - if (type === 'pubkey' || type === 'multisig') - address = null; - - uaddr = address; - - if (uaddr) { - if (!uniq[uaddr]) - uniq[uaddr] = true; - else - uaddr = null; + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; } - if (uaddr) - batch.put(p + 'p/a/' + uaddr + '/' + hash, new Buffer([])); + if (uaddr) { + map[uaddr].forEach(function(id) { + batch.put(p + 'p/a/' + id + '/' + hash, new Buffer([])); + }); + } }); utils.forEachSerial(tx.outputs, function(output, next, i) { @@ -621,20 +751,16 @@ TXPool.prototype.unconfirm = function unconfirm(hash, callback) { var address = output.getAddress(); var uaddr; - if (type === 'pubkey' || type === 'multisig') - address = null; - - uaddr = address; - - if (uaddr) { - if (!uniq[uaddr]) - uniq[uaddr] = true; - else - uaddr = null; + if (address && !uniq[address]) { + uniq[address] = true; + uaddr = address; } - if (uaddr) - batch.put(p + 'p/a/' + uaddr + '/' + hash, new Buffer([])); + if (uaddr) { + map[uaddr].forEach(function(id) { + batch.put(p + 'p/a/' + id + '/' + hash, new Buffer([])); + }); + } self.getCoin(hash, i, function(err, coin) { if (err)