diff --git a/lib/bcoin/config.js b/lib/bcoin/config.js index 1b70d451..eb2f3bb7 100644 --- a/lib/bcoin/config.js +++ b/lib/bcoin/config.js @@ -284,10 +284,10 @@ config.parseArg = function parseArg(argv) { if (arg[0] === '-') { // e.g. -abc - arg = arg.substring(1).split(''); + arg = arg.substring(1); for (i = 0; i < arg.length; i++) { - key = arg[i].trim(); + key = arg[i]; alias = config.alias.arg[key]; if (alias) key = alias; diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index bf1eb171..52b0f988 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -64,6 +64,7 @@ function TXDB(db, options) { this.busy = false; this.jobs = []; this.locker = new bcoin.locker(this); + this.current = null; this.coinCache = new bcoin.lru(10000, 1); @@ -90,6 +91,77 @@ TXDB.prototype._lock = function _lock(func, args, force) { return this.locker.lock(func, args, force); }; +/** + * Start a batch. + * @returns {Batch} + */ + +TXDB.prototype.start = function start() { + assert(!this.current); + this.current = this.db.batch(); + return this.current; +}; + +/** + * Put key and value to current batch. + * @param {String} key + * @param {Buffer} value + */ + +TXDB.prototype.put = function put(key, value) { + assert(this.current); + this.current.put(key, value); +}; + +/** + * Delete key from current batch. + * @param {String} key + */ + +TXDB.prototype.del = function del(key) { + assert(this.current); + this.current.del(key); +}; + +/** + * Get current batch. + * @returns {Batch} + */ + +TXDB.prototype.batch = function batch() { + assert(this.current); + return this.current; +}; + +/** + * Drop current batch. + * @returns {Batch} + */ + +TXDB.prototype.drop = function drop() { + assert(this.current); + this.current.clear(); + this.current = null; +}; + +/** + * Commit current batch. + * @param {Function} callback + */ + +TXDB.prototype.commit = function commit(callback) { + var self = this; + assert(this.current); + this.current.write(function(err) { + if (err) { + self.current = null; + return callback(err); + } + self.current = null; + return callback(); + }); +}; + /** * Load the bloom filter into memory. * @private @@ -146,7 +218,7 @@ TXDB.prototype.getInfo = function getInfo(tx, callback) { if (!this.testFilter(addresses)) return callback(); - this.mapAddresses(addresses, function(err, table) { + this.getTable(addresses, function(err, table) { if (err) return callback(err); @@ -160,12 +232,12 @@ TXDB.prototype.getInfo = function getInfo(tx, callback) { }; /** - * Map address hashes to a wallet ID. + * Map address hashes to paths. * @param {Hash[]} address - Address hashes. * @param {Function} callback - Returns [Error, {@link AddressTable}]. */ -TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { +TXDB.prototype.getTable = function getTable(address, callback) { var self = this; var table = {}; var count = 0; @@ -217,20 +289,21 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) { var self = this; + var batch = this.batch(); var p; - this.db.get('o/' + key, function(err, buf) { + this.db.get('o/' + key, function(err, data) { if (err) return callback(err); p = new BufferWriter(); - if (buf) - p.writeBytes(buf); + if (data) + p.writeBytes(data); p.writeBytes(outpoint); - self.batch().put('o/' + key, p.render()); + batch.put('o/' + key, p.render()); return callback(); }); @@ -245,10 +318,10 @@ TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) { TXDB.prototype._getOrphans = function _getOrphans(key, callback) { var self = this; - var out = []; + var items = []; - this.db.fetch('o/' + key, function(buf) { - var p = new BufferReader(buf); + this.db.fetch('o/' + key, function(data) { + var p = new BufferReader(data); var orphans = []; while (p.left()) @@ -267,7 +340,7 @@ TXDB.prototype._getOrphans = function _getOrphans(key, callback) { if (err) return next(err); - out.push([orphan, tx]); + items.push([orphan, tx]); next(); }); @@ -275,7 +348,7 @@ TXDB.prototype._getOrphans = function _getOrphans(key, callback) { if (err) return callback(err); - return callback(null, out); + return callback(null, items); }); }); }; @@ -296,7 +369,7 @@ TXDB.prototype.writeGenesis = function writeGenesis(callback) { callback = utils.wrap(callback, unlock); - self.db.has('R', function(err, result) { + this.db.has('R', function(err, result) { if (err) return callback(err); @@ -304,6 +377,7 @@ TXDB.prototype.writeGenesis = function writeGenesis(callback) { return callback(); hash = new Buffer(self.network.genesis.hash, 'hex'); + self.db.put('R', hash, callback); }); }; @@ -399,46 +473,6 @@ TXDB.prototype.removeBlock = function removeBlock(block, callback, force) { }); }; -TXDB.prototype.start = function start() { - assert(!this.current); - this.current = this.db.batch(); - return this.current; -}; - -TXDB.prototype.put = function put(key, value) { - assert(this.current); - this.current.put(key, value); -}; - -TXDB.prototype.del = function del(key, value) { - assert(this.current); - this.current.del(key); -}; - -TXDB.prototype.batch = function batch() { - assert(this.current); - return this.current; -}; - -TXDB.prototype.drop = function drop() { - assert(this.current); - this.current.clear(); - this.current = null; -}; - -TXDB.prototype.commit = function commit(callback) { - var self = this; - assert(this.current); - this.current.write(function(err) { - if (err) { - self.current = null; - return callback(err); - } - self.current = null; - return callback(); - }); -}; - /** * Add a transaction to the database, map addresses * to wallet IDs, potentially store orphans, resolve @@ -456,7 +490,7 @@ TXDB.prototype.add = function add(tx, callback, force) { callback = utils.wrap(callback, unlock); - return this.getInfo(tx, function(err, info) { + this.getInfo(tx, function(err, info) { if (err) return callback(err); @@ -469,12 +503,22 @@ TXDB.prototype.add = function add(tx, callback, force) { self.logger.debug(info.paths); - return self._add(tx, info, callback); + self._add(tx, info, callback); }); }; +/** + * Retrieve coins for own inputs, remove + * double spenders, and verify inputs. + * @private + * @param {TX} tx + * @param {PathInfo} info + * @param {Function} callback - Returns [Error]. + */ + TXDB.prototype._verify = function _verify(tx, info, callback) { var self = this; + utils.forEachSerial(tx.inputs, function(input, next, i) { var prevout = input.prevout; var address, paths; @@ -555,12 +599,20 @@ TXDB.prototype._verify = function _verify(tx, info, callback) { }); }; -TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) { +/** + * Attempt to resolve orphans for an output. + * @private + * @param {TX} tx + * @param {Number} index + * @param {Function} callback + */ + +TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, index, callback) { var self = this; var batch = this.batch(); var hash = tx.hash('hex'); - var output = tx.outputs[i]; - var key = hash + '/' + i; + var output = tx.outputs[index]; + var key = hash + '/' + index; var coin; this._getOrphans(key, function(err, orphans) { @@ -572,12 +624,12 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) { batch.del('o/' + key); - coin = bcoin.coin.fromTX(tx, i); + coin = bcoin.coin.fromTX(tx, index); // Add input to orphan - utils.forEachSerial(orphans, function(pair, next) { - var input = pair[0]; - var orphan = pair[1]; + utils.forEachSerial(orphans, function(item, next) { + var input = item[0]; + var orphan = item[1]; // Probably removed by some other means. if (!orphan) @@ -586,7 +638,7 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) { orphan.inputs[input.index].coin = coin; assert(orphan.inputs[input.index].prevout.hash === hash); - assert(orphan.inputs[input.index].prevout.index === i); + assert(orphan.inputs[input.index].prevout.index === index); // Verify that input script is correct, if not - add // output to unspent and remove orphan from storage @@ -605,10 +657,19 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, i, callback) { }); }; +/** + * Add transaction, runs _confirm (separate batch) and + * verify (separate batch for double spenders). + * @private + * @param {TX} tx + * @param {PathInfo} info + * @param {Function} callback + */ + TXDB.prototype._add = function add(tx, info, callback) { var self = this; var updated = false; - var batch, hash, i, j, unlock, path, paths, id; + var batch, hash, i, j, path, paths, id; if (tx.mutable) tx = tx.toTX(); @@ -692,8 +753,10 @@ TXDB.prototype._add = function add(tx, info, callback) { next(); }, function(err) { - if (err) + if (err) { + self.drop(); return callback(err); + } // Add unspent outputs or resolve orphans utils.forEachSerial(tx.outputs, function(output, next, i) { @@ -712,7 +775,7 @@ TXDB.prototype._add = function add(tx, info, callback) { self._resolveOrphans(tx, i, function(err, orphans) { if (err) - return callback(err); + return next(err); if (orphans) return next(); @@ -736,8 +799,10 @@ TXDB.prototype._add = function add(tx, info, callback) { next(); }); }, function(err) { - if (err) + if (err) { + self.drop(); return callback(err); + } self.commit(function(err) { if (err) @@ -834,20 +899,19 @@ TXDB.prototype._removeRecursive = function _removeRecursive(tx, callback) { if (err) return next(err); + if (!spent) + return next(); + // Remove all of the spender's spenders first. - if (spent) { - return self.getTX(spent.hash, function(err, tx) { - if (err) - return callback(err); + self.getTX(spent.hash, function(err, tx) { + if (err) + return next(err); - if (!tx) - return callback(new Error('Could not find spender.')); + if (!tx) + return next(new Error('Could not find spender.')); - return self._removeRecursive(tx, next); - }); - } - - next(); + self._removeRecursive(tx, next); + }); }); }, function(err) { if (err) @@ -856,14 +920,16 @@ TXDB.prototype._removeRecursive = function _removeRecursive(tx, callback) { self.start(); // Remove the spender. - self._lazyRemove(tx, function(err, res1, res2) { - if (err) + self._lazyRemove(tx, function(err, result, info) { + if (err) { + self.drop(); return callback(err); + } self.commit(function(err) { if (err) return callback(err); - callback(null, res1, res2); + return callback(null, result, info); }); }); }); @@ -880,7 +946,8 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) { var self = this; utils.everySerial(tx.inputs, function(input, next) { - self.isSpent(input.prevout.hash, input.prevout.index, function(err, spent) { + var prevout = input.prevout; + self.isSpent(prevout.hash, prevout.index, function(err, spent) { if (err) return next(err); return next(null, !spent); @@ -901,7 +968,7 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) { TXDB.prototype.isSpent = function isSpent(hash, index, callback) { var key = 's/' + hash + '/' + index; - return this.db.fetch(key, function(data) { + this.db.fetch(key, function(data) { return bcoin.outpoint.fromRaw(data); }, callback); }; @@ -918,9 +985,8 @@ TXDB.prototype.isSpent = function isSpent(hash, index, callback) { TXDB.prototype._confirm = function _confirm(tx, info, callback) { var self = this; - var hash, batch, unlock, i, id; - - hash = tx.hash('hex'); + var hash = tx.hash('hex'); + var batch, i, id; this.getTX(hash, function(err, existing) { if (err) @@ -982,8 +1048,10 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback) { next(); }); }, function(err) { - if (err) + if (err) { + self.drop(); return callback(err); + } self.emit('confirmed', tx, info); self.emit('tx', tx, info); @@ -1005,40 +1073,18 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback) { TXDB.prototype.remove = function remove(hash, callback, force) { var self = this; + var unlock = this._lock(remove, [hash, callback], force); - if (hash.hash) - hash = hash.hash('hex'); + if (!unlock) + return; - this.getTX(hash, function(err, tx) { + callback = utils.wrap(callback, unlock); + + this._removeRecursive(hash, function(err, result, info) { if (err) return callback(err); - if (!tx) - return callback(null, true); - - assert(tx.hash('hex') === hash); - - return self.getInfo(tx, function(err, info) { - if (err) - return callback(err); - - if (!info) - return callback(null, false); - - self.start(); - - return self._remove(tx, info, function(err, res1, res2) { - if (err) { - self.drop(); - return callback(err); - } - self.commit(function(err) { - if (err) - return callback(err); - callback(null, res1, res2); - }); - }); - }); + return callback(null, !!result, info); }); }; @@ -1059,7 +1105,7 @@ TXDB.prototype._lazyRemove = function lazyRemove(tx, callback) { if (!info) return callback(null, false); - return self._remove(tx, info, callback); + self._remove(tx, info, callback); }); }; @@ -1073,12 +1119,9 @@ TXDB.prototype._lazyRemove = function lazyRemove(tx, callback) { TXDB.prototype._remove = function remove(tx, info, callback) { var self = this; - var unlock, hash, batch, i, j, path, id; - var key, paths, address, input, output, coin; - - hash = tx.hash('hex'); - - batch = this.batch(); + var hash = tx.hash('hex'); + var batch = this.batch(); + var i, j, path, id, key, paths, address, input, output, coin; batch.del('t/' + hash); @@ -1190,9 +1233,7 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) { if (!tx) return callback(null, true); - assert(tx.hash('hex') === hash); - - return self.getInfo(tx, function(err, info) { + self.getInfo(tx, function(err, info) { if (err) return callback(err); @@ -1201,7 +1242,7 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) { self.start(); - return self._unconfirm(tx, info, function(err, res1, res2) { + self._unconfirm(tx, info, function(err, res1, res2) { if (err) { self.drop(); return callback(err); @@ -1225,11 +1266,10 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) { TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) { var self = this; - var batch, unlock, hash, height, i, id; - - batch = this.batch(); - hash = tx.hash('hex'); - height = tx.height; + var batch = this.batch(); + var hash = tx.hash('hex'); + var height = tx.height; + var i, id; if (height !== -1) return callback(null, false, info); @@ -1453,7 +1493,7 @@ TXDB.prototype.getRange = function getLast(id, options, callback) { id = null; } - return this.getRangeHashes(id, options, function(err, hashes) { + this.getRangeHashes(id, options, function(err, hashes) { if (err) return callback(err); @@ -1492,7 +1532,7 @@ TXDB.prototype.getLast = function getLast(id, limit, callback) { id = null; } - return this.getRange(id, { + this.getRange(id, { start: 0, end: 0xffffffff, reverse: true, @@ -1515,7 +1555,7 @@ TXDB.prototype.getHistory = function getHistory(id, callback) { id = null; } - return this.getHistoryHashes(id, function(err, hashes) { + this.getHistoryHashes(id, function(err, hashes) { if (err) return callback(err); @@ -1554,7 +1594,7 @@ TXDB.prototype.getLastTime = function getLastTime(id, callback) { id = null; } - return this.getHistory(id, function(err, txs) { + this.getHistory(id, function(err, txs) { if (err) return callback(err); @@ -1590,7 +1630,7 @@ TXDB.prototype.getUnconfirmed = function getUnconfirmed(id, callback) { id = null; } - return this.getUnconfirmedHashes(id, function(err, hashes) { + this.getUnconfirmedHashes(id, function(err, hashes) { if (err) return callback(err); @@ -1630,7 +1670,7 @@ TXDB.prototype.getCoins = function getCoins(id, callback) { id = null; } - return this.getCoinHashes(id, function(err, hashes) { + this.getCoinHashes(id, function(err, hashes) { if (err) return callback(err); @@ -1707,10 +1747,12 @@ TXDB.prototype.fillCoins = function fillCoins(tx, callback) { } utils.forEachSerial(tx.inputs, function(input, next) { + var prevout = input.prevout; + if (input.coin) return next(); - self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) { + self.getCoin(prevout.hash, prevout.index, function(err, coin) { if (err) return callback(err); @@ -1771,7 +1813,7 @@ TXDB.prototype.toDetails = function toDetails(id, tx, callback) { if (Array.isArray(tx)) { out = []; - utils.forEachSerial(tx, function(tx, next) { + return utils.forEachSerial(tx, function(tx, next) { self.toDetails(tx, function(err, details) { if (err) return next(err); @@ -1812,7 +1854,7 @@ TXDB.prototype.toDetails = function toDetails(id, tx, callback) { */ TXDB.prototype.hasTX = function hasTX(hash, callback) { - return this.db.has('t/' + hash, callback); + this.db.has('t/' + hash, callback); }; /** @@ -1859,7 +1901,7 @@ TXDB.prototype.hasCoin = function hasCoin(hash, index, callback) { if (this.coinCache.has(key)) return callback(null, true); - return this.db.has('c/' + key, callback); + this.db.has('c/' + key, callback); }; /** @@ -1891,7 +1933,7 @@ TXDB.prototype.getBalance = function getBalance(id, callback) { confirmed += value; } - return this.getCoinHashes(id, function(err, hashes) { + this.getCoinHashes(id, function(err, hashes) { if (err) return callback(err); @@ -1965,23 +2007,37 @@ TXDB.prototype.zap = function zap(id, age, callback, force) { if (!utils.isNumber(age)) return callback(new Error('Age must be a number.')); - return this.getRange(id, { + this.getRange(id, { start: 0, end: bcoin.now() - age }, function(err, txs) { if (err) return callback(err); - self.fillHistory(txs, function(err) { - if (err) - return callback(err); + utils.forEachSerial(txs, function(tx, next) { + if (tx.ts !== 0) + return next(); + self.remove(tx.hash('hex'), next, true); + }, callback); + }); +}; - utils.forEachSerial(txs, function(tx, next) { - if (tx.ts !== 0) - return next(); - self.remove(tx.hash('hex'), next, true); - }, callback); - }); +/** + * Abandon transaction. + * @param {Hash} hash + * @param {Function} callback + */ + +TXDB.prototype.abandon = function abandon(hash, callback, force) { + var self = this; + this.db.has('p/' + hash, function(err, result) { + if (err) + return callback(err); + + if (!result) + return callback(new Error('TX not found.')); + + self.remove(hash, callback, force); }); };