diff --git a/lib/bcoin/blockdb.js b/lib/bcoin/blockdb.js index 7ca41a32..dbb291e0 100644 --- a/lib/bcoin/blockdb.js +++ b/lib/bcoin/blockdb.js @@ -267,10 +267,8 @@ BlockDB.prototype.saveBlock = function saveBlock(block, callback) { BlockDB.prototype.removeBlock = function removeBlock(hash, callback) { var self = this; - callback = utils.once(callback); - this.getBlock(hash, function(err, block) { - var batch, pending; + var batch; if (err) return callback(err); @@ -278,8 +276,6 @@ BlockDB.prototype.removeBlock = function removeBlock(hash, callback) { if (!block) return callback(); - pending = block.txs.length; - batch = self.index.batch(); if (typeof hash === 'string') @@ -288,29 +284,7 @@ BlockDB.prototype.removeBlock = function removeBlock(hash, callback) { batch.del('b/b/' + block.hash('hex')); batch.del('b/h/' + block.height); - function done() { - batch.write(function(err) { - if (err) - return callback(err); - // TODO: Add check to make sure we - // can ONLY remove the last block. - assert(block._fileOffset >= 0); - assert(block._fileOffset < self.data.size); - // XXX This seems to be truncating too much right now - return callback(null, block); - self.data.truncateAsync(block._fileOffset, function(err) { - if (err) - return callback(err); - self.emit('remove block', block); - return callback(null, block); - }); - }); - } - - if (!pending) - return done(); - - block.txs.forEach(function(tx, i) { + utils.forEach(block.txs, function(tx, next, i) { var hash = tx.hash('hex'); var uniq = {}; @@ -321,7 +295,7 @@ BlockDB.prototype.removeBlock = function removeBlock(hash, callback) { self.fillTX(tx, function(err) { if (err) - return callback(err); + return next(err); tx.inputs.forEach(function(input) { var type = input.getType(); @@ -396,8 +370,26 @@ BlockDB.prototype.removeBlock = function removeBlock(hash, callback) { self.cache.unspent.remove(hash + '/' + i); }); - if (!--pending) - done(); + next(); + }, function(err) { + if (err) + return callback(err); + batch.write(function(err) { + if (err) + return callback(err); + // TODO: Add check to make sure we + // can ONLY remove the last block. + assert(block._fileOffset >= 0); + assert(block._fileOffset < self.data.size); + // XXX This seems to be truncating too much right now + return callback(null, block); + self.data.truncateAsync(block._fileOffset, function(err) { + if (err) + return callback(err); + self.emit('remove block', block); + return callback(null, block); + }); + }); }); }); }); @@ -408,62 +400,44 @@ BlockDB.prototype.fillCoins = function fillCoins(txs, callback) { var pending = txs.length; callback = utils.asyncify(callback); - callback = utils.once(callback); - if (!pending) - return callback(); - - txs.forEach(function(tx) { + utils.forEach(txs, function(tx, next) { self.fillCoin(tx, function(err) { if (err) - return callback(err); + return next(err); - if (!--pending) - callback(); + next(); }); - }); + }, callback); }; BlockDB.prototype.fillTXs = function fillTXs(txs, callback) { var self = this; - var pending = txs.length; callback = utils.asyncify(callback); - callback = utils.once(callback); - if (!pending) - return callback(); - - txs.forEach(function(tx) { + utils.forEach(txs, function(err) { self.fillTX(tx, function(err) { if (err) - return callback(err); + return next(err); - if (!--pending) - callback(); + next(); }); - }); + }, callback); }; BlockDB.prototype.fillCoin = function fillCoin(tx, callback) { var self = this; - var pending = tx.inputs.length; callback = utils.asyncify(callback); - callback = utils.once(callback); - - if (!pending) - return callback(); if (tx.isCoinbase()) return callback(); - tx.inputs.forEach(function(input) { - if (input.output) { - if (!--pending) - callback(null, tx); - return; - } + utils.forEach(tx.inputs, function(input, next) { + if (input.output) + return next(); + self.getCoin(input.prevout.hash, input.prevout.index, function(err, coin) { if (err) return callback(err); @@ -471,71 +445,68 @@ BlockDB.prototype.fillCoin = function fillCoin(tx, callback) { if (coin) input.output = coin; - if (!--pending) - callback(null, tx); + next(); }); + }, function(err) { + if (err) + return callback(err); + return callback(null, tx); }); }; BlockDB.prototype.fillTX = function fillTX(tx, callback) { var self = this; - var pending = tx.inputs.length; callback = utils.asyncify(callback); - callback = utils.once(callback); - - if (!pending) - return callback(); if (tx.isCoinbase()) return callback(); - tx.inputs.forEach(function(input) { - if (input.output) { - if (!--pending) - callback(null, tx); - return; - } + utils.forEach(tx.inputs, function(input, next) { + if (input.output) + return next(); + self.getTX(input.prevout.hash, function(err, tx) { if (err) - return callback(err); + return next(err); if (tx) { input.output = bcoin.coin(tx, input.prevout.index); input.output._fileOffset = tx._fileOffset + input.output._offset; } - if (!--pending) - callback(null, tx); + next(); }); + }, function(err) { + if (err) + return callback(err); + return callback(null, tx); }); }; BlockDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, callback) { var self = this; var coins = []; - var pending; - - callback = utils.once(callback); if (typeof addresses === 'string') addresses = [addresses]; addresses = utils.uniqs(addresses); - pending = addresses.length; - - addresses.forEach(function(address) { + utils.forEach(addresses, function(address) { self._getCoinsByAddress(address, function(err, coin) { if (err) - return callback(err); + return next(err); if (coin) coins = coins.concat(coin); - if (!--pending) - return callback(null, coins); + next(); }); + }, function(err) { + if (err) + return callback(err); + return callback(null, coins); }); }; @@ -691,31 +662,26 @@ BlockDB.prototype.getCoin = function getCoin(hash, index, callback) { BlockDB.prototype.getTXByAddress = function getTXByAddress(addresses, callback) { var self = this; var txs = []; - var pending; - - callback = utils.once(callback); if (typeof addresses === 'string') addresses = [addresses]; addresses = utils.uniqs(addresses); - pending = addresses.length; - - if (!pending) - return callback(null, txs); - - addresses.forEach(function(address) { + utils.forEach(addresses, function(address) { self._getTXByAddress(address, function(err, tx) { if (err) - return callback(err); + return next(err); if (tx) txs = txs.concat(tx); - if (!--pending) - return callback(null, txs); + next(); }); + }, function(err) { + if (err) + return callback(err); + return callback(null, txs); }); }; @@ -966,9 +932,7 @@ BlockDB.prototype.hasCoin = function hasCoin(hash, index, callback) { BlockDB.prototype.hasUnspentTX = function hasUnspentTX(hash, callback) { var self = this; this.getTX(hash, function(err, tx) { - var hash, pending, spent; - - callback = utils.once(callback); + var hash, spent; if (err) return callback(err); @@ -977,30 +941,23 @@ BlockDB.prototype.hasUnspentTX = function hasUnspentTX(hash, callback) { return callback(null, false); hash = tx.hash('hex'); - pending = tx.outputs.length; spent = 0; - if (!pending) - return callback(null, false); - - function done(err) { - if (err) - return callback(err); - return callback(null, spent < tx.outputs.length); - } - - tx.outputs.forEach(function(output, i) { + utils.forEach(tx.outputs, function(output, next, i) { self.isSpent(hash, i, function(err, result) { if (err) - return done(err); + return next(err); if (result) spent++; - if (!--pending) - done(); + next(); }); }); + }, function(err) { + if (err) + return callback(err); + return callback(null, spent < tx.outputs.length); }); }; diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index e3faeda6..935c2856 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -571,7 +571,6 @@ Chain.prototype._verify = function _verify(block, prev) { Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callback) { var self = this; var height = prev.height + 1; - var pending = block.txs.length; if (!this.blockdb || block.subtype !== 'block') return callback(null, true); @@ -579,18 +578,14 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba if (block.isGenesis()) return callback(null, true); - assert(pending); - - callback = utils.once(callback); - // Check all transactions - block.txs.forEach(function(tx) { + utils.every(block.txs, function(tx, next) { var hash = tx.hash('hex'); // BIP30 - Ensure there are no duplicate txids self.blockdb.hasTX(hash, function(err, result) { if (err) - return callback(err); + return next(err); // Blocks 91842 and 91880 created duplicate // txids by using the same exact output script @@ -598,13 +593,12 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba if (result) { utils.debug('Block is overwriting txids: %s', block.rhash); if (!(network.type === 'main' && (height === 91842 || height === 91880))) - return callback(null, false); + return next(null, false); } - if (!--pending) - return callback(null, true); + next(null, true); }); - }); + }, callback); }; Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) { @@ -1579,14 +1573,12 @@ Chain.prototype.getHashRangeAsync = function getHashRangeAsync(start, end, callb callback(err, result); } - done = utils.once(done); - this.byTimeAsync(start, function(err, start) { if (err) return done(err); self.byTimeAsync(end, function(err, end) { - var hashes, i, pending; + var hashes, i; if (err) return done(err); @@ -1596,25 +1588,23 @@ Chain.prototype.getHashRangeAsync = function getHashRangeAsync(start, end, callb if (!start || !end) return done(null, hashes); - pending = (end.height + 1) - start.height; - - for (i = start.height; i < end.height + 1; i++) - getHash(i); - - function getHash(i) { + utils.forRange(start.height, end.height + 1, function(i, next) { self.db.getAsync(i, function(err, entry) { if (err) - return done(err); + return next(err); if (!entry) - return done(new Error('No entry for hash range.')); + return next(new Error('No entry for hash range.')); hashes[i - start.height] = entry.hash; - if (!--pending) - return done(null, hashes); + next(); }); - } + }, function(err) { + if (err) + return done(err); + return done(null, hashes); + }); }, true); }, true); }; @@ -1672,7 +1662,7 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback, forc var hashes = []; var top = this.height; var step = 1; - var i, pending; + var i; var unlock = this._lock(getLocatorAsync, [start, callback], force); if (!unlock) @@ -1704,15 +1694,6 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback, forc callback = utils.asyncify(callback); - function done(err) { - unlock(); - if (err) - return callback(err); - return callback(null, hashes); - } - - done = utils.once(done); - i = top; for (;;) { hashes.push(i); @@ -1726,26 +1707,25 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback, forc step *= 2; } - pending = hashes.length; - - hashes.forEach(function(height, i) { - if (typeof height === 'string') { - if (!--pending) - done(); - return; - } + utils.forEach(hashes, function(height, next, i) { + if (typeof height === 'string') + return next(); self.db.getAsync(height, function(err, existing) { if (err) - return done(err); + return next(err); assert(existing); hashes[i] = existing.hash; - if (!--pending) - done(); + next(); }); + }, function(err) { + unlock(); + if (err) + return callback(err); + return callback(null, hashes); }); }; diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index 7f57400d..b3bcf89e 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -510,7 +510,7 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback, var osize = this.size; var ohighest = this.highest; var otip = this.tip; - var size, count, pending, called; + var size, count; callback = utils.asyncify(callback); @@ -522,7 +522,6 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback, size = (height + 1) * BLOCK_SIZE; count = this.getSize(); - pending = count - (height + 1); if (height > count - 1) return callback(new Error('Height too high')); @@ -544,13 +543,23 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback, self.height = self.tip.height; self.emit('tip', self.tip); - for (i = height + 1; i < count; i++) - dropEntry(i); + function finish(err) { + if (err) { + self.size = osize; + self.highest = ohighest; + self.tip = otip; + self.height = self.tip.height; + self.emit('tip', self.tip); + return callback(err); + } - function dropEntry(i) { + callback(); + } + + utils.forRange(height + 1, count, function(i, next) { self.getAsync(i, function(err, existing) { if (err) - return done(err); + return next(err); assert(existing); @@ -566,17 +575,9 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback, delete self.cache[i]; delete self.heightLookup[existing.hash]; - if (!--pending) - done(); + return next(); }, true); - } - - function done(err) { - if (called) - return; - - called = true; - + }, function(err) { if (err) return finish(err); @@ -593,20 +594,7 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback, return finish(); }); }); - } - - function finish(err) { - if (err) { - self.size = osize; - self.highest = ohighest; - self.tip = otip; - self.height = self.tip.height; - self.emit('tip', self.tip); - return callback(err); - } - - callback(); - } + }); }); }; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 8a2d074d..a42c60e2 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -1475,7 +1475,7 @@ utils.ccmp = function(a, b) { utils.forRange = function forRange(from, to, iter, callback) { var pending = to - from; - var called, i; + var i, error; callback = utils.asyncify(callback); @@ -1483,16 +1483,10 @@ utils.forRange = function forRange(from, to, iter, callback) { return callback(); function next(err) { - if (called) - return; - if (err) { - called = true; - return callback(err); - } - if (!--pending) { - called = true; - callback(); - } + if (err) + error = err; + if (!--pending) + callback(error); } for (i = from; i < to; i++) @@ -1501,7 +1495,7 @@ utils.forRange = function forRange(from, to, iter, callback) { utils.forEach = function forEach(arr, iter, callback) { var pending = arr.length; - var called, i; + var i, error; callback = utils.asyncify(callback); @@ -1509,16 +1503,10 @@ utils.forEach = function forEach(arr, iter, callback) { return callback(); function next(err) { - if (called) - return; - if (err) { - called = true; - return callback(err); - } - if (!--pending) { - called = true; - callback(); - } + if (err) + error = err; + if (!--pending) + callback(error); } arr.forEach(function(item, i) { @@ -1535,7 +1523,9 @@ utils.forRangeSerial = function forRangeSerial(from, to, iter, callback) { if (from >= to) return callback(); from++; - iter(from - 1, next, from - 1); + utils.nextTick(function() { + iter(from - 1, next, from - 1); + }); })(); }; @@ -1552,7 +1542,57 @@ utils.forEachSerial = function forEachSerial(arr, iter, callback) { return callback(); item = arr[i]; i++; - iter(item, next, i - 1); + utils.nextTick(function() { + iter(item, next, i - 1); + }); + })(); +}; + +utils.every = function every(arr, iter, callback) { + var pending = arr.length; + var result = true; + var i, error; + + callback = utils.asyncify(callback); + + if (!pending) + return callback(null, result); + + function next(err, res) { + if (err) + error = err; + if (!res) + result = false; + if (!--pending) { + if (error) + return callback(error); + callback(null, result); + } + } + + arr.forEach(function(item, i) { + iter(item, next, i); + }); +}; + +utils.everySerial = function everySerial(arr, iter, callback) { + var i = 0; + + callback = utils.asyncify(callback); + + (function next(err, res) { + var item; + if (err) + return callback(err); + if (!result) + return callback(null, false); + if (i >= arr.length) + return callback(null, true); + item = arr[i]; + i++; + utils.nextTick(function() { + iter(item, next, i - 1); + }); })(); };