diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 2f9036e1..98d64772 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -624,15 +624,14 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { var sigops = 0; var jobs = []; var ret = new VerifyResult(); - var i, view, tx, valid; + var view = new CoinView(); + var i, tx, valid; if (this.options.spv) - return new CoinView(); + return view; if (this.isGenesis(block)) - return new CoinView(); - - view = yield this.db.getCoinView(block); + return view; // Check all transactions for (i = 0; i < block.txs.length; i++) { @@ -640,13 +639,15 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { // Ensure tx is not double spending an output. if (i > 0) { - if (!view.fillCoins(tx)) { + if (!(yield view.hasInputs(this.db, tx))) { assert(!historical, 'BUG: Spent inputs in historical data!'); throw new VerifyError(block, 'invalid', 'bad-txns-inputs-missingorspent', 100); } + + view.spendCoins(tx); } // Skip everything if we're diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index cdfd56ca..1cb59237 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -1768,23 +1768,18 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { } } - // Add all of the coins we are about to - // remove. This is to ensure they appear - // in the view array below. - view.addTX(tx); - for (j = 0; j < tx.outputs.length; j++) { output = tx.outputs[j]; if (output.script.isUnspendable()) continue; - // Spend added coin. - view.spend(hash, j); - this.pending.spend(output); } + // Remove any created coins. + view.removeTX(tx); + // Remove from transaction index. this.unindexTX(tx); } diff --git a/lib/blockchain/coinview.js b/lib/blockchain/coinview.js index b2a6540e..c53b66b2 100644 --- a/lib/blockchain/coinview.js +++ b/lib/blockchain/coinview.js @@ -6,6 +6,8 @@ 'use strict'; +var assert = require('assert'); +var co = require('../utils/co'); var Coins = require('./coins'); var UndoCoins = require('./undocoins'); @@ -32,6 +34,7 @@ function CoinView(coins) { CoinView.prototype.add = function add(coins) { this.coins[coins.hash] = coins; + return coins; }; /** @@ -40,7 +43,18 @@ CoinView.prototype.add = function add(coins) { */ CoinView.prototype.addTX = function addTX(tx) { - this.add(Coins.fromTX(tx)); + return this.add(Coins.fromTX(tx)); +}; + +/** + * Remove a tx from the collection. + * @param {TX} tx + */ + +CoinView.prototype.removeTX = function removeTX(tx) { + var coins = this.addTX(tx); + coins.outputs.length = 0; + return coins; }; /** @@ -93,12 +107,12 @@ CoinView.prototype.spend = function spend(hash, index) { var entry, undo; if (!coins) - return; + return null; entry = coins.spend(index); if (!entry) - return; + return null; this.undo.push(entry); @@ -113,23 +127,66 @@ CoinView.prototype.spend = function spend(hash, index) { }; /** - * Fill transaction(s) with coins. + * Retrieve coins from database. * @param {TX} tx - * @returns {Boolean} True if all inputs were filled. + * @returns {Promise} */ -CoinView.prototype.fillCoins = function fillCoins(tx) { +CoinView.prototype.getCoins = co(function* getCoins(db, hash) { + var coins = this.coins[hash]; + + if (!coins) { + coins = yield db.getCoins(hash); + + if (!coins) + return; + + this.coins[hash] = coins; + } + + return coins; +}); + +/** + * Test whether all inputs are available. + * @param {ChainDB} db + * @param {TX} tx + * @returns {Boolean} True if all inputs are available. + */ + +CoinView.prototype.hasInputs = co(function* hasInputs(db, tx) { + var i, input, prevout, coins; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + coins = yield this.getCoins(db, prevout.hash); + + if (!coins) + return false; + + if (!coins.has(prevout.index)) + return false; + } + + return true; +}); + +/** + * Spend coins for transaction. + * @param {TX} tx + * @throws on missing coin + */ + +CoinView.prototype.spendCoins = function spendCoins(tx) { var i, input, prevout; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; prevout = input.prevout; input.coin = this.spend(prevout.hash, prevout.index); - if (!input.coin) - return false; + assert(input.coin, 'Not all coins available.'); } - - return true; }; /**