diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index a8c7b2b2..4b11390a 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -856,27 +856,6 @@ ChainDB.prototype.hasCoins = function hasCoins(hash) { return this.db.has(layout.c(hash)); }; -/** - * Get a view of the existing coins necessary to verify a block. - * @param {Block} block - * @returns {Promise} - Returns {@link CoinView}. - */ - -ChainDB.prototype.getCoinView = co(function* getCoinView(block, callback) { - var view = new CoinView(); - var prevout = block.getPrevout(); - var i, prev, coins; - - for (i = 0; i < prevout.length; i++) { - prev = prevout[i]; - coins = yield this.getCoins(prev); - if (coins) - view.add(coins); - } - - return view; -}); - /** * Get coins necessary to be resurrected during a reorg. * @param {Hash} hash @@ -890,32 +869,6 @@ ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) { return UndoCoins.fromRaw(data); }); -/** - * Get a coin view containing unspent coins as - * well as the coins to be resurrected for a reorg. - * (Note: fills block with undo coins). - * @param {Block} block - * @returns {Promise} - Returns {@link CoinView}. - */ - -ChainDB.prototype.getUndoView = co(function* getUndoView(block) { - var view = yield this.getCoinView(block); - var undo = yield this.getUndoCoins(block.hash()); - var index = 0; - var i, j, tx, input; - - for (i = 1; i < block.txs.length; i++) { - tx = block.txs[i]; - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - input.coin = undo.apply(index++, view, input.prevout); - assert(input.coin); - } - } - - return view; -}); - /** * Retrieve a block from the database (not filled with coins). * @param {Hash} hash @@ -972,11 +925,24 @@ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) { ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) { var block = yield this.getBlock(hash); + var i, j, view, undo, tx, input; if (!block) return; - yield this.getUndoView(block); + view = new CoinView(); + undo = yield this.getUndoCoins(block.hash()); + + for (i = block.txs.length - 1; i > 0; i--) { + tx = block.txs[i]; + for (j = tx.inputs.length - 1; j >= 0; j--) { + input = tx.inputs[j]; + input.coin = undo.apply(view, input.prevout); + } + } + + // Undo coins should be empty. + assert(undo.items.length === 0, 'Undo coins data inconsistency.'); return block; }); @@ -1709,6 +1675,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { if (this.chain.isGenesis(block)) return; + // Update chain state value. for (i = 0; i < block.txs.length; i++) { tx = block.txs[i]; @@ -1740,6 +1707,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { if (!view.undo.isEmpty()) this.put(layout.u(block.hash()), view.undo.toRaw()); + // Prune height-288 if pruning is enabled. yield this.pruneBlock(block); }); @@ -1750,27 +1718,21 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { */ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { - var i, j, view, tx, input, output; + var i, j, view, undo, tx, input, output; if (this.options.spv) return; - view = yield this.getUndoView(block); + view = new CoinView(); + undo = yield this.getUndoCoins(block.hash()); this.pending.disconnect(block); + // Disconnect all transactions. for (i = block.txs.length - 1; i >= 0; i--) { tx = block.txs[i]; - if (i > 0) { - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - assert(input.coin); - this.pending.add(input.coin); - } - } - - for (j = 0; j < tx.outputs.length; j++) { + for (j = tx.outputs.length - 1; j >= 0; j--) { output = tx.outputs[j]; if (output.script.isUnspendable()) @@ -1782,10 +1744,23 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { // Remove any created coins. view.removeTX(tx); + if (i > 0) { + yield view.ensureInputs(this, tx); + + for (j = tx.inputs.length - 1; j >= 0; j--) { + input = tx.inputs[j]; + input.coin = undo.apply(view, input.prevout); + this.pending.add(input.coin); + } + } + // Remove from transaction index. this.unindexTX(tx); } + // Undo coins should be empty. + assert(undo.items.length === 0, 'Undo coins data inconsistency.'); + // Commit new coin state. this.saveView(view); diff --git a/lib/blockchain/coinview.js b/lib/blockchain/coinview.js index c53b66b2..3fe2b0ec 100644 --- a/lib/blockchain/coinview.js +++ b/lib/blockchain/coinview.js @@ -19,11 +19,11 @@ var UndoCoins = require('./undocoins'); * @property {Object} coins */ -function CoinView(coins) { +function CoinView() { if (!(this instanceof CoinView)) - return new CoinView(coins); + return new CoinView(); - this.coins = coins || {}; + this.unspent = {}; this.undo = new UndoCoins(); } @@ -32,8 +32,8 @@ function CoinView(coins) { * @param {Coins} coins */ -CoinView.prototype.add = function add(coins) { - this.coins[coins.hash] = coins; +CoinView.prototype.addCoins = function addCoins(coins) { + this.unspent[coins.hash] = coins; return coins; }; @@ -43,7 +43,8 @@ CoinView.prototype.add = function add(coins) { */ CoinView.prototype.addTX = function addTX(tx) { - return this.add(Coins.fromTX(tx)); + var coins = Coins.fromTX(tx); + return this.addCoins(coins); }; /** @@ -52,9 +53,9 @@ CoinView.prototype.addTX = function addTX(tx) { */ CoinView.prototype.removeTX = function removeTX(tx) { - var coins = this.addTX(tx); + var coins = Coins.fromTX(tx); coins.outputs.length = 0; - return coins; + return this.addCoins(coins); }; /** @@ -65,7 +66,7 @@ CoinView.prototype.removeTX = function removeTX(tx) { */ CoinView.prototype.get = function get(hash, index) { - var coins = this.coins[hash]; + var coins = this.unspent[hash]; var entry; if (!coins) @@ -87,7 +88,7 @@ CoinView.prototype.get = function get(hash, index) { */ CoinView.prototype.has = function has(hash, index) { - var coins = this.coins[hash]; + var coins = this.unspent[hash]; if (!coins) return false; @@ -103,7 +104,7 @@ CoinView.prototype.has = function has(hash, index) { */ CoinView.prototype.spend = function spend(hash, index) { - var coins = this.coins[hash]; + var coins = this.unspent[hash]; var entry, undo; if (!coins) @@ -133,7 +134,7 @@ CoinView.prototype.spend = function spend(hash, index) { */ CoinView.prototype.getCoins = co(function* getCoins(db, hash) { - var coins = this.coins[hash]; + var coins = this.unspent[hash]; if (!coins) { coins = yield db.getCoins(hash); @@ -141,7 +142,7 @@ CoinView.prototype.getCoins = co(function* getCoins(db, hash) { if (!coins) return; - this.coins[hash] = coins; + this.unspent[hash] = coins; } return coins; @@ -172,6 +173,21 @@ CoinView.prototype.hasInputs = co(function* hasInputs(db, tx) { return true; }); +/** + * Read all input coins into unspent map. + * @param {ChainDB} db + * @param {TX} tx + */ + +CoinView.prototype.ensureInputs = co(function* ensureInputs(db, tx) { + var i, input; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + yield this.getCoins(db, input.prevout.hash); + } +}); + /** * Spend coins for transaction. * @param {TX} tx @@ -195,13 +211,13 @@ CoinView.prototype.spendCoins = function spendCoins(tx) { */ CoinView.prototype.toArray = function toArray() { - var keys = Object.keys(this.coins); + var keys = Object.keys(this.unspent); var out = []; var i, hash; for (i = 0; i < keys.length; i++) { hash = keys[i]; - out.push(this.coins[hash]); + out.push(this.unspent[hash]); } return out; diff --git a/lib/blockchain/undocoins.js b/lib/blockchain/undocoins.js index 1a5c1b76..a3e6f3d2 100644 --- a/lib/blockchain/undocoins.js +++ b/lib/blockchain/undocoins.js @@ -26,6 +26,9 @@ var decompress = compressor.decompress; */ function UndoCoins() { + if (!(this instanceof UndoCoins)) + return new UndoCoins(); + this.items = []; } @@ -107,14 +110,13 @@ UndoCoins.prototype.top = function top() { /** * Re-apply undo coins to a view, effectively unspending them. - * @param {Number} i * @param {CoinView} view * @param {Outpoint} outpoint * @returns {Coin} */ -UndoCoins.prototype.apply = function apply(i, view, outpoint) { - var undo = this.items[i]; +UndoCoins.prototype.apply = function apply(view, outpoint) { + var undo = this.items.pop(); var hash = outpoint.hash; var index = outpoint.index; var coins; @@ -124,19 +126,21 @@ UndoCoins.prototype.apply = function apply(i, view, outpoint) { if (undo.height !== -1) { coins = new Coins(); - assert(!view.coins[hash]); - view.coins[hash] = coins; + assert(!view.unspent[hash]); + view.unspent[hash] = coins; coins.coinbase = undo.coinbase; coins.height = undo.height; coins.version = undo.version; } else { - coins = view.coins[hash]; + coins = view.unspent[hash]; assert(coins); } coins.add(index, undo.toOutput()); + assert(coins.has(index)); + return coins.getCoin(index); };