chain: refactor spending.

This commit is contained in:
Christopher Jeffrey 2016-11-30 19:35:59 -08:00
parent 453eccbabd
commit 371f6b1fa0
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 77 additions and 24 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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;
};
/**