diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index 2621d24f..f3849430 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -92,8 +92,8 @@ function ChainDB(chain, options) { // Average number of outputs per tx: 2.2 // Average size of outputs per tx: 74b // Average number of txs: 2300 - // Key size: 68b (* 2) - this.coinWindow = ((165 * 1024 + 5000 * 9) + (5000 * 68 * 2)) * 5; + // Key size: 66b (* 2) + this.coinWindow = ((165 * 1024 + 2300 * 4) + (2300 * 66 * 2)) * 5; this.coinCache = new bcoin.lru(this.coinWindow); this.cacheHash = new bcoin.lru(this.cacheWindow, 1); @@ -782,7 +782,7 @@ ChainDB.prototype.removeBlock = function removeBlock(hash, batch, callback) { ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) { var self = this; var undo = new BufferWriter(); - var i, j, tx, input, output, key, addresses, address, hash, coin; + var i, j, tx, input, output, key, addresses, address, hash, view, coins, raw; if (this.options.spv) { self.emit('add block', block); @@ -830,31 +830,38 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) { batch.del('C/' + address + '/' + key); } - batch.del('c/' + key); - Framer.coin(input.coin, false, undo); - self.coinCache.remove(key); + block.view.spend(input.prevout.hash, input.prevout.index); } for (j = 0; j < tx.outputs.length; j++) { output = tx.outputs[j]; - key = hash + '/' + j; if (output.script.isUnspendable()) continue; - coin = bcoin.coin.fromTX(tx, j).toRaw(); - if (self.options.indexAddress) { address = output.getHash(); if (address) batch.put('C/' + address + '/' + key, DUMMY); } + } - batch.put('c/' + key, coin); + block.view.add(tx.toCoins()); + } - self.coinCache.set(key, coin); + view = block.view.toArray(); + + for (i = 0; i < view.length; i++) { + coins = view[i]; + if (coins.count() === 0) { + batch.del('c/' + coins.hash); + self.coinCache.remove(coins.hash); + } else { + raw = coins.toRaw(); + batch.put('c/' + coins.hash, raw); + self.coinCache.set(coins.hash, raw); } } @@ -880,7 +887,7 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) { ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callback) { var self = this; - var i, j, tx, input, output, key, addresses, address, hash, coin; + var i, j, tx, input, output, key, addresses, address, hash, view, coins, raw; if (this.options.spv) return utils.nextTick(callback); @@ -922,13 +929,11 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb batch.put('C/' + address + '/' + key, DUMMY); } - coin = input.coin.toRaw(); - - batch.put('c/' + key, coin); - - self.coinCache.set(key, coin); + block.view.addCoin(input.coin); } + block.view.add(tx.toCoins()); + for (j = 0; j < tx.outputs.length; j++) { output = tx.outputs[j]; key = hash + '/' + j; @@ -942,9 +947,21 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb batch.del('C/' + address + '/' + key); } - batch.del('c/' + key); + block.view.spend(hash, j); + } + } - self.coinCache.remove(key); + view = block.view.toArray(); + + for (i = 0; i < view.length; i++) { + coins = view[i]; + if (coins.count() === 0) { + batch.del('c/' + coins.hash); + self.coinCache.remove(coins.hash); + } else { + raw = coins.toRaw(); + batch.put('c/' + coins.hash, raw); + self.coinCache.set(coins.hash, raw); } } @@ -1048,29 +1065,62 @@ ChainDB.prototype.fillHistory = function fillHistory(tx, callback) { */ ChainDB.prototype.getCoin = function getCoin(hash, index, callback) { - var self = this; - var key = hash + '/' + index; var coin; - coin = this.coinCache.get(key); - if (coin) { - callback = utils.asyncify(callback); + this.getCoins(hash, true, function(err, coins) { + if (err) + return callback(err); + + if (!coins) + return callback(); + try { - coin = bcoin.coin.fromRaw(coin); - coin.hash = hash; - coin.index = index; + coin = bcoin.coins.parseCoin(coins, hash, index); } catch (e) { return callback(e); } + return callback(null, coin); + }); +}; + +/** + * Get coins (unspents only). + * @param {Hash} hash + * @param {Function} callback - Returns [Error, {@link Coins}]. + */ + +ChainDB.prototype.getCoins = function getCoins(hash, raw, callback) { + var self = this; + var coins; + + if (!callback) { + callback = raw; + raw = false; } - 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; + coins = this.coinCache.get(hash); + + if (coins) { + callback = utils.asyncify(callback); + + if (raw) + return callback(null, coins); + + try { + coins = bcoin.coins.fromRaw(coins, hash); + } catch (e) { + return callback(e); + } + + return callback(null, coins); + } + + this.db.fetch('c/' + hash, function(data) { + self.coinCache.set(hash, data); + if (raw) + return data; + return bcoin.coins.fromRaw(data, hash); }, callback); }; @@ -1128,27 +1178,32 @@ ChainDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, call utils.forEachSerial(addresses, function(address, next) { address = bcoin.address.getHash(address); + if (!address) return next(); - self.db.lookup({ + + self.db.iterate({ 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; + return [key[2], +key[3]]; } - }, function(err, coin) { + }, function(err, keys) { if (err) return next(err); - coins = coins.concat(coin); - next(); + + utils.forEachSerial(keys, function(key, next) { + self.getCoin(key[0], key[1], function(err, coin) { + if (err) + return callback(err); + + if (coin) + coins.push(coin); + + return next(); + }); + }, next); }); }, function(err) { if (err) @@ -1284,9 +1339,21 @@ ChainDB.prototype._ensureHistory = function _ensureHistory(hash, callback) { */ ChainDB.prototype.fillBlock = function fillBlock(block, callback) { + var self = this; + var view = new bcoin.coinview(); var coins, spent, i, tx, hash, j, input, key; - return this.fillCoins(block.txs, function(err) { + utils.forEachSerial(block.getPrevout(), function(prevout, next) { + self.getCoins(prevout, function(err, coins) { + if (err) + return next(err); + + if (coins) + view.add(coins); + + next(); + }); + }, function(err) { if (err) return callback(err); @@ -1297,6 +1364,8 @@ ChainDB.prototype.fillBlock = function fillBlock(block, callback) { tx = block.txs[i]; hash = tx.hash('hex'); + view.fill(tx); + for (j = 0; j < tx.inputs.length; j++) { input = tx.inputs[j]; key = input.prevout.hash + '/' + input.prevout.index; @@ -1318,6 +1387,8 @@ ChainDB.prototype.fillBlock = function fillBlock(block, callback) { coins[hash + '/' + j] = bcoin.coin.fromTX(tx, j); } + block.view = view; + return callback(null, block); }); }; @@ -1350,30 +1421,36 @@ ChainDB.prototype.getUndoCoins = function getUndoCoins(hash, callback) { */ ChainDB.prototype.fillHistoryBlock = function fillHistoryBlock(block, callback) { + var self = this; var i, j, k, tx, input; - return this.getUndoCoins(block.hash('hex'), function(err, coins) { + return this.fillBlock(block, function(err) { if (err) return callback(err); - if (!coins) - return callback(null, block); + return self.getUndoCoins(block.hash('hex'), function(err, coins) { + if (err) + return callback(err); - for (i = 0, k = 0; i < block.txs.length; i++) { - tx = block.txs[i]; + if (!coins) + return callback(null, block); - if (tx.isCoinbase()) - continue; + for (i = 0, k = 0; i < block.txs.length; i++) { + tx = block.txs[i]; - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - input.coin = coins[k++]; - input.coin.hash = input.prevout.hash; - input.coin.index = input.prevout.index; + if (tx.isCoinbase()) + continue; + + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + input.coin = coins[k++]; + input.coin.hash = input.prevout.hash; + input.coin.index = input.prevout.index; + } } - } - return callback(null, block); + return callback(null, block); + }); }); }; @@ -1427,32 +1504,13 @@ ChainDB.prototype.isUnspentTX = function isUnspentTX(hash, callback) { */ ChainDB.prototype.isSpentTX = function isSpentTX(hash, callback) { - var iter; - if (hash.hash) hash = hash.hash('hex'); - iter = this.db.iterator({ - gte: 'c/' + hash, - lte: 'c/' + hash + '~', - keys: true, - values: false, - fillCache: false, - keyAsBuffer: false - }); - - iter.next(function(err, key, value) { - if (err) { - return iter.end(function() { - callback(err); - }); - } - - iter.end(function(err) { - if (err) - return callback(err); - return callback(null, key === undefined); - }); + this.getCoins(hash, function(err, coins) { + if (err) + return callback(err); + return callback(null, !coins); }); };