diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index 58ba1bc4..63d3b9d1 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -63,6 +63,8 @@ function TXDB(db, options) { this.jobs = []; this.locker = new bcoin.locker(this); + this.coinCache = new bcoin.lru(10000, 1); + // Try to optimize for up to 1m addresses. // We use a regular bloom filter here // because we never want members to @@ -394,6 +396,8 @@ TXDB.prototype._add = function add(tx, map, callback, force) { batch.del('c/' + key); + self.coinCache.remove(key); + return next(); } @@ -528,7 +532,12 @@ TXDB.prototype._add = function add(tx, map, callback, force) { batch.put('C/' + id + '/' + key, DUMMY); } - batch.put('c/' + key, coin.toRaw()); + coin = coin.toRaw(); + + batch.put('c/' + key, coin); + + self.coinCache.set(key, coin); + updated = true; } @@ -759,6 +768,7 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) { utils.forEachSerial(tx.outputs, function(output, next, i) { var address = output.getHash('hex'); + var key = hash + '/' + i; // Only update coins if this output is ours. if (!map.hasPaths(address)) @@ -772,8 +782,11 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) { return next(); coin.height = tx.height; + coin = coin.toRaw(); - batch.put('c/' + hash + '/' + i, coin.toRaw()); + batch.put('c/' + key, coin); + + self.coinCache.set(key, coin); next(); }); @@ -864,7 +877,8 @@ TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) { TXDB.prototype._remove = function remove(tx, map, callback, force) { var self = this; - var unlock, hash, batch, i, j, path, id, key, paths, address, input, output; + var unlock, hash, batch, i, j, path, id; + var key, paths, address, input, output, coin; unlock = this._lock(remove, [tx, map, callback], force); @@ -926,9 +940,13 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { batch.put('C/' + id + '/' + key, DUMMY); } - batch.put('c/' + key, input.coin.toRaw()); + coin = input.coin.toRaw(); + + batch.put('c/' + key, coin); batch.del('s/' + key); batch.del('o/' + key); + + self.coinCache.set(key, coin); } for (i = 0; i < tx.outputs.length; i++) { @@ -951,6 +969,8 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { } batch.del('c/' + key); + + self.coinCache.remove(key); } batch.write(function(err) { @@ -1049,6 +1069,7 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) { } utils.forEachSerial(tx.outputs, function(output, next, i) { + var key = hash + '/' + i; self.getCoin(hash, i, function(err, coin) { if (err) return next(err); @@ -1057,8 +1078,11 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) { return next(); coin.height = tx.height; + coin = coin.toRaw(); - batch.put('c/' + hash + '/' + i, coin.toRaw()); + batch.put('c/' + key, coin); + + self.coinCache.set(key, coin); next(); }); @@ -1550,10 +1574,25 @@ TXDB.prototype.hasTX = function hasTX(hash, callback) { */ TXDB.prototype.getCoin = function getCoin(hash, index, callback) { - this.db.fetch('c/' + hash + '/' + index, function(data) { + var key = hash + '/' + index; + var coin = this.coinCache.get(key); + + if (coin) { + try { + coin = bcoin.coin.fromRaw(coin); + } catch (e) { + return callback(e); + } + coin.hash = hash; + coin.index = index; + return callback(null, coin); + } + + this.db.fetch('c/' + key, function(data) { var coin = bcoin.coin.fromRaw(data); coin.hash = hash; coin.index = index; + self.coinCache.set(key, data); return coin; }, callback); }; @@ -1565,7 +1604,12 @@ TXDB.prototype.getCoin = function getCoin(hash, index, callback) { */ TXDB.prototype.hasCoin = function hasCoin(hash, index, callback) { - return this.db.has('c/' + hash + '/' + index, callback); + var key = hash + '/' + index; + + if (this.coinCache.has(key)) + return callback(null, true); + + return this.db.has('c/' + key, callback); }; /** @@ -1578,28 +1622,59 @@ TXDB.prototype.getBalance = function getBalance(id, callback) { var self = this; var confirmed = 0; var unconfirmed = 0; + var key, coin; if (typeof id === 'function') { callback = id; id = null; } + function parse(data) { + var height = data.readUInt32LE(4, true); + var value = utils.read64N(data, 8); + + assert(data.length >= 16); + + if (height === 0x7fffffff) + unconfirmed += value; + else + confirmed += value; + } + return this.getCoinHashes(id, function(err, hashes) { if (err) return callback(err); utils.forEachSerial(hashes, function(hash, next) { - self.db.fetch('c/' + hash[0] + '/' + hash[1], function(data, key) { - var height = data.readUInt32LE(4, true); - var value = utils.read64N(data, 8); + key = hash[0] + '/' + hash[1]; + coin = self.coinCache.get(key); - assert(data.length >= 16); + if (coin) { + try { + parse(coin); + } catch (e) { + return next(e); + } + return next(); + } - if (height === 0x7fffffff) - unconfirmed += value; - else - confirmed += value; - }, next); + self.db.get('c/' + key, function(err, data) { + if (err) + return next(err); + + if (!data) + return next(); + + try { + parse(data); + } catch (e) { + return callback(e); + } + + self.coinCache.set(key, data); + + next(); + }); }, function(err) { if (err) return callback(err); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index c94b9d09..c49d2657 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -157,7 +157,6 @@ Wallet.prototype.open = function open(callback) { /** * Close the wallet, unregister with the database. - * @method * @param {Function} callback */ @@ -2421,7 +2420,7 @@ MasterKey.fromOptions = function fromOptions(options) { * Decrypt the key and set a timeout to destroy decrypted data. * @param {Buffer|String} passphrase - Zero this yourself. * @param {Number} [timeout=60000] timeout in ms. - * @returns {HDPrivateKey} + * @param {Function} callback - Returns [Error, {@link HDPrivateKey}]. */ MasterKey.prototype.unlock = function _unlock(passphrase, timeout, callback) { @@ -2515,6 +2514,7 @@ MasterKey.prototype.destroy = function destroy() { /** * Decrypt the key permanently. * @param {Buffer|String} passphrase - Zero this yourself. + * @param {Function} callback */ MasterKey.prototype.decrypt = function decrypt(passphrase, callback) { @@ -2559,6 +2559,7 @@ MasterKey.prototype.decrypt = function decrypt(passphrase, callback) { /** * Encrypt the key permanently. * @param {Buffer|String} passphrase - Zero this yourself. + * @param {Function} callback */ MasterKey.prototype.encrypt = function encrypt(passphrase, callback) {