Browse Source

error() in disconnect for disk corruption, not inconsistency

The error() function unconditionally reports an error. It should only
be used for actually exception situations, and not for the type of
inconsistencies that ApplyTxInUndo/DisconnectBlock can graciously deal
with.

This also makes a subtle semantics change: in ApplyTxInUndo, when a
record with metadata is encountered (indicating it is the last spend
from a tx), don't wipe the CCoins record if it wasn't empty at that
point. This makes sure that UTXO operations never affect any other
UTXOs (including those from the same tx).
0.15
Pieter Wuille 8 years ago
parent
commit
f54580e7e4
  1. 2
      src/test/coins_tests.cpp
  2. 38
      src/validation.cpp

2
src/test/coins_tests.cpp

@ -17,7 +17,7 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out); int ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out);
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight); void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);
namespace namespace

38
src/validation.cpp

@ -1248,46 +1248,42 @@ bool AbortNode(CValidationState& state, const std::string& strMessage, const std
} // anon namespace } // anon namespace
enum DisconnectResult
{
DISCONNECT_OK, // All good.
DISCONNECT_UNCLEAN, // Rolled back, but UTXO set was inconsistent with block.
DISCONNECT_FAILED // Something else went wrong.
};
/** /**
* Apply the undo operation of a CTxInUndo to the given chain state. * Apply the undo operation of a CTxInUndo to the given chain state.
* @param undo The undo object. * @param undo The undo object.
* @param view The coins view to which to apply the changes. * @param view The coins view to which to apply the changes.
* @param out The out point that corresponds to the tx input. * @param out The out point that corresponds to the tx input.
* @return True on success. * @return A DisconnectResult as an int
*/ */
bool ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out) int ApplyTxInUndo(const CTxInUndo& undo, CCoinsViewCache& view, const COutPoint& out)
{ {
bool fClean = true; bool fClean = true;
CCoinsModifier coins = view.ModifyCoins(out.hash); CCoinsModifier coins = view.ModifyCoins(out.hash);
if (undo.nHeight != 0) { if (undo.nHeight != 0) {
// undo data contains height: this is the last output of the prevout tx being spent // undo data contains height: this is the last output of the prevout tx being spent
if (!coins->IsPruned()) if (!coins->IsPruned()) fClean = false; // overwriting existing transaction
fClean = fClean && error("%s: undo data overwriting existing transaction", __func__);
coins->Clear();
coins->fCoinBase = undo.fCoinBase; coins->fCoinBase = undo.fCoinBase;
coins->nHeight = undo.nHeight; coins->nHeight = undo.nHeight;
coins->nVersion = undo.nVersion; coins->nVersion = undo.nVersion;
} else { } else {
if (coins->IsPruned()) if (coins->IsPruned()) fClean = false; // adding output to missing transaction
fClean = fClean && error("%s: undo data adding output to missing transaction", __func__);
} }
if (coins->IsAvailable(out.n)) if (coins->IsAvailable(out.n)) fClean = false; // overwriting existing output
fClean = fClean && error("%s: undo data overwriting existing output", __func__);
if (coins->vout.size() < out.n+1) if (coins->vout.size() < out.n+1)
coins->vout.resize(out.n+1); coins->vout.resize(out.n+1);
coins->vout[out.n] = undo.txout; coins->vout[out.n] = undo.txout;
return fClean; return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;
} }
enum DisconnectResult
{
DISCONNECT_OK, // All good.
DISCONNECT_UNCLEAN, // Rolled back, but UTXO set was inconsistent with block.
DISCONNECT_FAILED // Something else went wrong.
};
/** Undo the effects of this block (with given index) on the UTXO set represented by coins. /** Undo the effects of this block (with given index) on the UTXO set represented by coins.
* When UNCLEAN or FAILED is returned, view is left in an indeterminate state. */ * When UNCLEAN or FAILED is returned, view is left in an indeterminate state. */
static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
@ -1329,8 +1325,7 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex*
// but it must be corrected before txout nversion ever influences a network rule. // but it must be corrected before txout nversion ever influences a network rule.
if (outsBlock.nVersion < 0) if (outsBlock.nVersion < 0)
outs->nVersion = outsBlock.nVersion; outs->nVersion = outsBlock.nVersion;
if (*outs != outsBlock) if (*outs != outsBlock) fClean = false; // transaction mismatch
fClean = fClean && error("DisconnectBlock(): added transaction mismatch? database corrupted");
// remove outputs // remove outputs
outs->Clear(); outs->Clear();
@ -1346,8 +1341,9 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex*
for (unsigned int j = tx.vin.size(); j-- > 0;) { for (unsigned int j = tx.vin.size(); j-- > 0;) {
const COutPoint &out = tx.vin[j].prevout; const COutPoint &out = tx.vin[j].prevout;
const CTxInUndo &undo = txundo.vprevout[j]; const CTxInUndo &undo = txundo.vprevout[j];
if (!ApplyTxInUndo(undo, view, out)) int res = ApplyTxInUndo(undo, view, out);
fClean = false; if (res == DISCONNECT_FAILED) return DISCONNECT_FAILED;
fClean = fClean && res != DISCONNECT_UNCLEAN;
} }
} }
} }

Loading…
Cancel
Save