diff --git a/lib/bcoin/lowlevelup.js b/lib/bcoin/lowlevelup.js index 2383b642..c3111b3f 100644 --- a/lib/bcoin/lowlevelup.js +++ b/lib/bcoin/lowlevelup.js @@ -173,4 +173,156 @@ LowlevelUp.prototype.approximateSize = function approximateSize(start, end, call return this.binding.approximateSize(start, end, callback); }; +/** + * Test whether a key exists. + * @param {String} key + * @param {Function} callback - Returns [Error, Boolean]. + */ + +LowlevelUp.prototype.has = function has(key, callback) { + return this.get(key, function(err, value) { + if (err && err.type !== 'NotFoundError') + return callback(err); + + return callback(null, value != null); + }); +}; + +/** + * Get and deserialize a record with a callback. + * @param {String} key + * @param {Function} parse + * @param {Function} callback - Returns [Error, Object]. + */ + +LowlevelUp.prototype.fetch = function fetch(key, parse, callback) { + return this.get(key, function(err, value) { + if (err && err.type !== 'NotFoundError') + return callback(err); + + if (!value) + return callback(); + + try { + value = parse(value, key); + } catch (e) { + return callback(e); + } + + return callback(null, value); + }); +}; + +/** + * Collect all keys from iterator options. + * @param {Object} options - Iterator options. + * @param {Function} callback - Returns [Error, Array]. + */ + +LowlevelUp.prototype.iterate = function iterate(options, callback) { + var items = []; + var iter; + + iter = this.iterator({ + gte: options.gte, + lte: options.lte, + keys: true, + values: options.values, + fillCache: false, + keyAsBuffer: false, + limit: options.limit, + reverse: options.reverse + }); + + (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); + return callback(null, items); + }); + } + + if (options.values) { + if (options.parse) { + try { + value = options.parse(value); + } catch (e) { + return iter.end(function() { + return callback(e); + }); + } + } + if (value) + items.push(value); + return next(); + } + + if (options.transform) + key = options.transform(key); + + if (key) + items.push(key); + + next(); + }); + })(); +}; + +/** + * Collect all keys from iterator options. + * Proxy the keys to further lookups. + * @param {Object} options - Iterator options. + * @param {Function} callback - Returns [Error, Array]. + */ + +LowlevelUp.prototype.lookup = function lookup(options, callback) { + var self = this; + var items = []; + + options.values = false; + + return this.iterate(options, function(err, keys) { + if (err) + return callback(err); + + utils.forEachSerial(keys, function(key, next) { + self.get(key, function(err, value) { + if (err && err.type !== 'NotFoundError') + return callback(err); + + if (!value) + return next(); + + if (!options.parse) { + items.push(value); + return next(); + } + + try { + value = options.parse(value, key); + } catch (e) { + return callback(e); + } + + if (value) + items.push(value); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + return callback(null, items); + }); + }); +}; + module.exports = LowlevelUp; diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index 3f88894e..5b1d40c6 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -72,7 +72,6 @@ function Mempool(options) { this.writeLock = new bcoin.locker(this); this.db = null; - this.tx = null; this.size = 0; this.waiting = {}; this.orphans = {}; @@ -140,9 +139,6 @@ Mempool.prototype._init = function _init() { self.db = bcoin.ldb(options); - // Use the txdb object for its get methods. - self.tx = new TXDB(self.db); - self.db.open(function(err) { if (err) { unlock(); @@ -167,59 +163,6 @@ Mempool.prototype._init = function _init() { }); }; -function TXDB(db, options) { - bcoin.txdb.call(this, db, options); -} - -utils.inherits(TXDB, bcoin.txdb); - -TXDB.prototype.getTX = function getTX(hash, callback) { - return this.db.get('t/' + hash, function(err, entry) { - if (err && err.type !== 'NotFoundError') - return callback(err); - - if (!entry) - return callback(); - - try { - entry = MempoolEntry.fromRaw(entry); - } catch (e) { - return callback(e); - } - - return callback(null, entry.tx, entry); - }); -}; - -TXDB.prototype.getRangeEntries = function getRangeEntries(options, callback) { - var self = this; - var entries = []; - - return this.getRangeHashes(options, function(err, hashes) { - if (err) - return callback(err); - - utils.forEachSerial(hashes, function(hash, next) { - self.getTX(hash, function(err, tx, entry) { - if (err) - return callback(err); - - if (!entry) - return next(); - - entries.push(entry); - - next(); - }); - }, function(err) { - if (err) - return callback(err); - - return callback(null, entries); - }); - }); -}; - /** * Tally up total memory usage from database. * @param {Function} callback - Returns [Error, Number]. @@ -409,7 +352,7 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) { if (this.getSize() <= this.maxSize) return callback(null, true); - this.tx.getRangeEntries({ + this.getRange({ start: 0, end: utils.now() - constants.mempool.MEMPOOL_EXPIRY }, function(err, entries) { @@ -418,27 +361,40 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(callback) { utils.forEachSerial(function(entry, next) { if (self.getSize() <= self.maxSize) - return next(); + return callback(null, true); - self.removeUnchecked(entry, true, function(err) { - if (err) - return next(err); - - rate = entry.fees.muln(1000).divn(entry.size).toNumber(); - rate += self.minReasonableFee; - - if (rate > self.minFeeRate) { - self.minFeeRate = rate; - self.blockSinceBump = false; - } - - next(); - }, true); + self.removeUnchecked(entry, true, next, true); }, function(err) { if (err) return callback(err); - return callback(null, self.getSize() <= self.maxSize); + if (self.getSize() <= self.maxSize) + return callback(null, true); + + self.getSnapshot(function(err, hashes) { + if (err) + return callback(err); + + utils.forEachSerial(hashes, function(hash, next) { + if (self.getSize() <= self.maxSize) + return callback(null, true); + + self.getEntry(hash, function(err, entry) { + if (err) + return next(err); + + if (!entry) + return next(); + + self.removeUnchecked(entry, true, next, true); + }); + }, function(err) { + if (err) + return callback(err); + + return callback(null, self.getSize() <= self.maxSize); + }); + }); }); }); }; @@ -475,7 +431,15 @@ Mempool.prototype.limitOrphans = function limitOrphans(callback) { */ Mempool.prototype.getTX = function getTX(hash, callback) { - return this.tx.getTX(hash, callback); + return this.getEntry(hash, function(err, entry) { + if (err) + return callback(err); + + if (!entry) + return callback(); + + return callback(null, entry.tx); + }); }; /** @@ -486,15 +450,9 @@ Mempool.prototype.getTX = function getTX(hash, callback) { */ Mempool.prototype.getEntry = function getEntry(hash, callback) { - return this.tx.getTX(hash, function(err, tx, entry) { - if (err) - return callback(err); - - if (!entry) - return callback(); - - return callback(null, entry); - }); + return this.db.fetch('t/' + hash, function(entry) { + return MempoolEntry.fromRaw(entry); + }, callback); }; /** @@ -505,7 +463,12 @@ Mempool.prototype.getEntry = function getEntry(hash, callback) { */ Mempool.prototype.getCoin = function getCoin(hash, index, callback) { - return this.tx.getCoin(hash, index, callback); + return this.db.fetch('c/' + hash + '/' + index, function(coin) { + coin = bcoin.coin.fromRaw(coin); + coin.hash = hash; + coin.index = index; + return coin; + }, callback); }; /** @@ -519,27 +482,52 @@ Mempool.prototype.getCoin = function getCoin(hash, index, callback) { */ Mempool.prototype.isSpent = function isSpent(hash, index, callback) { - return this.tx.isSpent(hash, index, callback); + return this.db.fetch('s/' + hash, function(spender) { + return spender.toString('hex'); + }, callback); }; /** * Find all coins pertaining to a certain address. - * @param {Base58Address[]|Base58Address} addresses + * @param {Base58Address} address * @param {Function} callback - Returns [Error, {@link Coin}[]]. */ -Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, callback) { - return this.tx.getCoinsByAddress(addresses, callback); +Mempool.prototype.getCoinsByAddress = function getCoinsByAddress(address, callback) { + return this.db.lookup({ + gte: 'C/' + address, + lte: 'C/' + address + '~', + transform: function(key) { + key = key.split('/'); + return 'c/' + key[2] + '/' + key[3]; + }, + parse: function(data, key) { + var coin = bcoin.coin.fromRaw(data); + var hash = key.split('/'); + coin.hash = hash[1]; + coin.index = +hash[2]; + return coin; + } + }, callback); }; /** * Find all transactions pertaining to a certain address. - * @param {Base58Address[]|Base58Address} addresses + * @param {Base58Address} address * @param {Function} callback - Returns [Error, {@link TX}[]]. */ -Mempool.prototype.getTXByAddress = function getTXByAddress(addresses, callback) { - return this.tx.getTXByAddress(addresses, callback); +Mempool.prototype.getTXByAddress = function getTXByAddress(address, callback) { + return this.db.lookup({ + gte: 'T/' + address, + lte: 'T/' + address + '~', + transform: function(key) { + return 't/' + key.split('/')[2]; + }, + parse: function(data, key) { + return bcoin.tx.fromRaw(data); + } + }, callback); }; /** @@ -552,7 +540,37 @@ Mempool.prototype.getTXByAddress = function getTXByAddress(addresses, callback) */ Mempool.prototype.fillHistory = function fillHistory(tx, callback) { - return this.tx.fillHistory(tx, callback); + var self = this; + + if (Array.isArray(tx)) { + return utils.forEachSerial(tx, function(tx, next) { + self.fillHistory(tx, next); + }, callback); + } + + callback = utils.asyncify(callback); + + if (tx.isCoinbase()) + return callback(null, tx); + + utils.forEach(tx.inputs, function(input, next) { + if (input.coin) + return next(); + + self.getTX(input.prevout.hash, function(err, tx) { + if (err) + return next(err); + + if (tx) + input.coin = bcoin.coin(tx, input.prevout.index); + + next(); + }); + }, function(err) { + if (err) + return callback(err); + return callback(null, tx); + }); }; /** @@ -563,7 +581,37 @@ Mempool.prototype.fillHistory = function fillHistory(tx, callback) { */ Mempool.prototype.fillCoins = function fillCoins(tx, callback) { - return this.tx.fillCoins(tx, callback); + var self = this; + + if (Array.isArray(tx)) { + return utils.forEachSerial(tx, function(tx, next) { + self.fillCoins(tx, next); + }, callback); + } + + callback = utils.asyncify(callback); + + if (tx.isCoinbase()) + return callback(null, tx); + + utils.forEach(tx.inputs, function(input, next) { + if (input.coin) + return next(); + + self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) { + if (err) + return callback(err); + + if (coin) + input.coin = coin; + + next(); + }); + }, function(err) { + if (err) + return callback(err); + return callback(null, tx); + }); }; /** @@ -573,7 +621,7 @@ Mempool.prototype.fillCoins = function fillCoins(tx, callback) { */ Mempool.prototype.hasTX = function hasTX(hash, callback) { - return this.tx.hasTX(hash, callback); + return this.db.has('t/' + hash, callback); }; /** @@ -583,7 +631,18 @@ Mempool.prototype.hasTX = function hasTX(hash, callback) { */ Mempool.prototype.getRange = function getRange(options, callback) { - return this.tx.getRange(options, callback); + return this.db.lookup({ + gte: 'm/' + pad32(options.start) + '/', + lte: 'm/' + pad32(options.end) + '/~', + transform: function(key) { + return 't/' + key.split('/')[2]; + }, + parse: function(data, key) { + return MempoolEntry.fromRaw(data); + }, + limit: options.limit, + reverse: options.reverse + }, callback); }; /** @@ -834,6 +893,15 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit, callb self.size -= self.memUsage(entry.tx); self.total--; + if (limit) { + rate = entry.fees.muln(1000).divn(entry.size).toNumber(); + rate += self.minReasonableFee; + if (rate > self.minFeeRate) { + self.minFeeRate = rate; + self.blockSinceBump = false; + } + } + self.emit('remove tx', entry.tx); return callback(); @@ -1113,7 +1181,29 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) { */ Mempool.prototype.getBalance = function getBalance(callback) { - return this.tx.getBalance(callback); + var total = new bn(0); + var i; + + return this.db.iterate({ + gte: 'c', + lte: 'c~', + values: true, + parse: function(data, key) { + return bcoin.coin.fromRaw(data); + } + }, function(err, coins) { + if (err) + return callback(err); + + for (i = 0; i < coins.length; i++) + total.iadd(coins[i].value); + + return callback(null, { + confirmed: new bn(0), + unconfirmed: total, + total: total + }); + }); }; /** @@ -1122,7 +1212,14 @@ Mempool.prototype.getBalance = function getBalance(callback) { */ Mempool.prototype.getHistory = function getHistory(callback) { - return this.tx.getHistory(callback); + return this.db.iterate({ + gte: 't', + lte: 't~', + values: true, + parse: function(data, key) { + return bcoin.tx.fromRaw(data); + } + }, callback); }; /** @@ -1359,7 +1456,13 @@ Mempool.prototype.fillAllCoins = function fillAllCoins(tx, callback) { */ Mempool.prototype.getSnapshot = function getSnapshot(callback) { - return this.tx.getHistoryHashes(callback); + return this.db.iterate({ + gte: 't', + lte: 't~', + transform: function(key) { + return key.split('/')[1]; + } + }, callback); }; /** @@ -1384,7 +1487,19 @@ Mempool.prototype.checkLocks = function checkLocks(tx, flags, callback) { */ Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) { - return this.tx.isDoubleSpend(tx, callback); + var self = this; + utils.everySerial(tx.inputs, function(input, next) { + self.isSpent(input.prevout.hash, input.prevout.index, function(err, spent) { + if (err) + return next(err); + return next(null, !spent); + }); + }, function(err, result) { + if (err) + return callback(err); + + return callback(null, !result); + }); }; /**