Browse Source

Switch from per-tx to per-txout CCoinsViewCache methods in some places

0.15
Pieter Wuille 8 years ago
parent
commit
f68cdfe92b
  1. 4
      src/bench/ccoins_caching.cpp
  2. 26
      src/bitcoin-tx.cpp
  3. 14
      src/consensus/tx_verify.cpp
  4. 37
      src/rpc/rawtransaction.cpp
  5. 2
      src/test/script_P2SH_tests.cpp
  6. 2
      src/test/sigopcount_tests.cpp
  7. 4
      src/test/transaction_tests.cpp
  8. 87
      src/validation.cpp

4
src/bench/ccoins_caching.cpp

@ -35,14 +35,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG;
dummyTransactions[0].vout[1].nValue = 50 * CENT; dummyTransactions[0].vout[1].nValue = 50 * CENT;
dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG;
coinsRet.ModifyCoins(dummyTransactions[0].GetHash())->FromTx(dummyTransactions[0], 0); AddCoins(coinsRet, dummyTransactions[0], 0);
dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout.resize(2);
dummyTransactions[1].vout[0].nValue = 21 * CENT; dummyTransactions[1].vout[0].nValue = 21 * CENT;
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
dummyTransactions[1].vout[1].nValue = 22 * CENT; dummyTransactions[1].vout[1].nValue = 22 * CENT;
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID());
coinsRet.ModifyCoins(dummyTransactions[1].GetHash())->FromTx(dummyTransactions[1], 0); AddCoins(coinsRet, dummyTransactions[1], 0);
return dummyTransactions; return dummyTransactions;
} }

26
src/bitcoin-tx.cpp

@ -556,24 +556,26 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
if (nOut < 0) if (nOut < 0)
throw std::runtime_error("vout must be positive"); throw std::runtime_error("vout must be positive");
COutPoint out(txid, nOut);
std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey")); std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey"));
CScript scriptPubKey(pkData.begin(), pkData.end()); CScript scriptPubKey(pkData.begin(), pkData.end());
{ {
CCoinsModifier coins = view.ModifyCoins(txid); const Coin& coin = view.AccessCoin(out);
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { if (!coin.IsPruned() && coin.out.scriptPubKey != scriptPubKey) {
std::string err("Previous output scriptPubKey mismatch:\n"); std::string err("Previous output scriptPubKey mismatch:\n");
err = err + ScriptToAsmStr(coins->vout[nOut].scriptPubKey) + "\nvs:\n"+ err = err + ScriptToAsmStr(coin.out.scriptPubKey) + "\nvs:\n"+
ScriptToAsmStr(scriptPubKey); ScriptToAsmStr(scriptPubKey);
throw std::runtime_error(err); throw std::runtime_error(err);
} }
if ((unsigned int)nOut >= coins->vout.size()) Coin newcoin;
coins->vout.resize(nOut+1); newcoin.out.scriptPubKey = scriptPubKey;
coins->vout[nOut].scriptPubKey = scriptPubKey; newcoin.out.nValue = 0;
coins->vout[nOut].nValue = 0;
if (prevOut.exists("amount")) { if (prevOut.exists("amount")) {
coins->vout[nOut].nValue = AmountFromValue(prevOut["amount"]); newcoin.out.nValue = AmountFromValue(prevOut["amount"]);
} }
newcoin.nHeight = 1;
view.AddCoin(out, std::move(newcoin), true);
} }
// if redeemScript given and private keys given, // if redeemScript given and private keys given,
@ -595,13 +597,13 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
// Sign what we can: // Sign what we can:
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
CTxIn& txin = mergedTx.vin[i]; CTxIn& txin = mergedTx.vin[i];
const CCoins* coins = view.AccessCoins(txin.prevout.hash); const Coin& coin = view.AccessCoin(txin.prevout);
if (!coins || !coins->IsAvailable(txin.prevout.n)) { if (coin.IsPruned()) {
fComplete = false; fComplete = false;
continue; continue;
} }
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey; const CScript& prevPubKey = coin.out.scriptPubKey;
const CAmount& amount = coins->vout[txin.prevout.n].nValue; const CAmount& amount = coin.out.nValue;
SignatureData sigdata; SignatureData sigdata;
// Only sign SIGHASH_SINGLE if there's a corresponding output: // Only sign SIGHASH_SINGLE if there's a corresponding output:

14
src/consensus/tx_verify.cpp

@ -213,20 +213,20 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
for (unsigned int i = 0; i < tx.vin.size(); i++) for (unsigned int i = 0; i < tx.vin.size(); i++)
{ {
const COutPoint &prevout = tx.vin[i].prevout; const COutPoint &prevout = tx.vin[i].prevout;
const CCoins *coins = inputs.AccessCoins(prevout.hash); const Coin& coin = inputs.AccessCoin(prevout);
assert(coins); assert(!coin.IsPruned());
// If prev is coinbase, check that it's matured // If prev is coinbase, check that it's matured
if (coins->IsCoinBase()) { if (coin.IsCoinBase()) {
if (nSpendHeight - coins->nHeight < COINBASE_MATURITY) if (nSpendHeight - coin.nHeight < COINBASE_MATURITY)
return state.Invalid(false, return state.Invalid(false,
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coins->nHeight)); strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
} }
// Check for negative or overflow input values // Check for negative or overflow input values
nValueIn += coins->vout[prevout.n].nValue; nValueIn += coin.out.nValue;
if (!MoneyRange(coins->vout[prevout.n].nValue) || !MoneyRange(nValueIn)) if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
} }

37
src/rpc/rawtransaction.cpp

@ -637,9 +637,7 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) { BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) {
const uint256& prevHash = txin.prevout.hash; view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
CCoins coins;
view.AccessCoins(prevHash); // this is certainly allowed to fail
} }
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
@ -691,24 +689,26 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
if (nOut < 0) if (nOut < 0)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive"); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive");
COutPoint out(txid, nOut);
std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey")); std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
CScript scriptPubKey(pkData.begin(), pkData.end()); CScript scriptPubKey(pkData.begin(), pkData.end());
{ {
CCoinsModifier coins = view.ModifyCoins(txid); const Coin& coin = view.AccessCoin(out);
if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { if (!coin.IsPruned() && coin.out.scriptPubKey != scriptPubKey) {
std::string err("Previous output scriptPubKey mismatch:\n"); std::string err("Previous output scriptPubKey mismatch:\n");
err = err + ScriptToAsmStr(coins->vout[nOut].scriptPubKey) + "\nvs:\n"+ err = err + ScriptToAsmStr(coin.out.scriptPubKey) + "\nvs:\n"+
ScriptToAsmStr(scriptPubKey); ScriptToAsmStr(scriptPubKey);
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
} }
if ((unsigned int)nOut >= coins->vout.size()) Coin newcoin;
coins->vout.resize(nOut+1); newcoin.out.scriptPubKey = scriptPubKey;
coins->vout[nOut].scriptPubKey = scriptPubKey; newcoin.out.nValue = 0;
coins->vout[nOut].nValue = 0;
if (prevOut.exists("amount")) { if (prevOut.exists("amount")) {
coins->vout[nOut].nValue = AmountFromValue(find_value(prevOut, "amount")); newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount"));
} }
newcoin.nHeight = 1;
view.AddCoin(out, std::move(newcoin), true);
} }
// if redeemScript given and not using the local wallet (private keys // if redeemScript given and not using the local wallet (private keys
@ -766,13 +766,13 @@ UniValue signrawtransaction(const JSONRPCRequest& request)
// Sign what we can: // Sign what we can:
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
CTxIn& txin = mergedTx.vin[i]; CTxIn& txin = mergedTx.vin[i];
const CCoins* coins = view.AccessCoins(txin.prevout.hash); const Coin& coin = view.AccessCoin(txin.prevout);
if (coins == NULL || !coins->IsAvailable(txin.prevout.n)) { if (coin.IsPruned()) {
TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
continue; continue;
} }
const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey; const CScript& prevPubKey = coin.out.scriptPubKey;
const CAmount& amount = coins->vout[txin.prevout.n].nValue; const CAmount& amount = coin.out.nValue;
SignatureData sigdata; SignatureData sigdata;
// Only sign SIGHASH_SINGLE if there's a corresponding output: // Only sign SIGHASH_SINGLE if there's a corresponding output:
@ -844,9 +844,12 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
nMaxRawTxFee = 0; nMaxRawTxFee = 0;
CCoinsViewCache &view = *pcoinsTip; CCoinsViewCache &view = *pcoinsTip;
const CCoins* existingCoins = view.AccessCoins(hashTx); bool fHaveChain = false;
for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o));
fHaveChain = !existingCoin.IsPruned();
}
bool fHaveMempool = mempool.exists(hashTx); bool fHaveMempool = mempool.exists(hashTx);
bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000;
if (!fHaveMempool && !fHaveChain) { if (!fHaveMempool && !fHaveChain) {
// push to local node and sync with wallets // push to local node and sync with wallets
CValidationState state; CValidationState state;

2
src/test/script_P2SH_tests.cpp

@ -316,7 +316,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txFrom.vout[6].scriptPubKey = GetScriptForDestination(CScriptID(twentySigops)); txFrom.vout[6].scriptPubKey = GetScriptForDestination(CScriptID(twentySigops));
txFrom.vout[6].nValue = 6000; txFrom.vout[6].nValue = 6000;
coins.ModifyCoins(txFrom.GetHash())->FromTx(txFrom, 0); AddCoins(coins, txFrom, 0);
CMutableTransaction txTo; CMutableTransaction txTo;
txTo.vout.resize(1); txTo.vout.resize(1);

2
src/test/sigopcount_tests.cpp

@ -102,7 +102,7 @@ void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableT
spendingTx.vout[0].nValue = 1; spendingTx.vout[0].nValue = 1;
spendingTx.vout[0].scriptPubKey = CScript(); spendingTx.vout[0].scriptPubKey = CScript();
coins.ModifyCoins(creationTx.GetHash())->FromTx(creationTx, 0); AddCoins(coins, creationTx, 0);
} }
BOOST_AUTO_TEST_CASE(GetTxSigOpCost) BOOST_AUTO_TEST_CASE(GetTxSigOpCost)

4
src/test/transaction_tests.cpp

@ -307,14 +307,14 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[0].scriptPubKey << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG;
dummyTransactions[0].vout[1].nValue = 50*CENT; dummyTransactions[0].vout[1].nValue = 50*CENT;
dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG; dummyTransactions[0].vout[1].scriptPubKey << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG;
coinsRet.ModifyCoins(dummyTransactions[0].GetHash())->FromTx(dummyTransactions[0], 0); AddCoins(coinsRet, dummyTransactions[0], 0);
dummyTransactions[1].vout.resize(2); dummyTransactions[1].vout.resize(2);
dummyTransactions[1].vout[0].nValue = 21*CENT; dummyTransactions[1].vout[0].nValue = 21*CENT;
dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID()); dummyTransactions[1].vout[0].scriptPubKey = GetScriptForDestination(key[2].GetPubKey().GetID());
dummyTransactions[1].vout[1].nValue = 22*CENT; dummyTransactions[1].vout[1].nValue = 22*CENT;
dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID()); dummyTransactions[1].vout[1].scriptPubKey = GetScriptForDestination(key[3].GetPubKey().GetID());
coinsRet.ModifyCoins(dummyTransactions[1].GetHash())->FromTx(dummyTransactions[1], 0); AddCoins(coinsRet, dummyTransactions[1], 0);
return dummyTransactions; return dummyTransactions;
} }

87
src/validation.cpp

@ -498,8 +498,8 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
// during reorgs to ensure COINBASE_MATURITY is still met. // during reorgs to ensure COINBASE_MATURITY is still met.
bool fSpendsCoinbase = false; bool fSpendsCoinbase = false;
BOOST_FOREACH(const CTxIn &txin, tx.vin) { BOOST_FOREACH(const CTxIn &txin, tx.vin) {
const CCoins *coins = view.AccessCoins(txin.prevout.hash); const Coin &coin = view.AccessCoin(txin.prevout);
if (coins->IsCoinBase()) { if (coin.IsCoinBase()) {
fSpendsCoinbase = true; fSpendsCoinbase = true;
break; break;
} }
@ -818,15 +818,8 @@ bool GetTransaction(const uint256 &hash, CTransactionRef &txOut, const Consensus
} }
if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it if (fAllowSlow) { // use coin database to locate block that contains transaction, and scan it
int nHeight = -1; const Coin& coin = AccessByTxid(*pcoinsTip, hash);
{ if (!coin.IsPruned()) pindexSlow = chainActive[coin.nHeight];
const CCoinsViewCache& view = *pcoinsTip;
const CCoins* coins = view.AccessCoins(hash);
if (coins)
nHeight = coins->nHeight;
}
if (nHeight > 0)
pindexSlow = chainActive[nHeight];
} }
if (pindexSlow) { if (pindexSlow) {
@ -1074,19 +1067,12 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
if (!tx.IsCoinBase()) { if (!tx.IsCoinBase()) {
txundo.vprevout.reserve(tx.vin.size()); txundo.vprevout.reserve(tx.vin.size());
BOOST_FOREACH(const CTxIn &txin, tx.vin) { BOOST_FOREACH(const CTxIn &txin, tx.vin) {
CCoinsModifier coins = inputs.ModifyCoins(txin.prevout.hash); txundo.vprevout.emplace_back();
unsigned nPos = txin.prevout.n; inputs.SpendCoin(txin.prevout, &txundo.vprevout.back());
if (nPos >= coins->vout.size() || coins->vout[nPos].IsNull())
assert(false);
// mark an outpoint spent, and construct undo information
txundo.vprevout.emplace_back(coins->vout[nPos], coins->nHeight, coins->fCoinBase);
bool ret = coins->Spend(nPos);
assert(ret);
} }
} }
// add outputs // add outputs
inputs.ModifyNewCoins(tx.GetHash(), tx.IsCoinBase())->FromTx(tx, nHeight); AddCoins(inputs, tx, nHeight);
} }
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)
@ -1260,24 +1246,21 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out)
{ {
bool fClean = true; bool fClean = true;
CCoinsModifier coins = view.ModifyCoins(out.hash); if (view.HaveCoins(out)) fClean = false; // overwriting transaction output
if (undo.nHeight != 0) {
if (!coins->IsPruned()) { if (undo.nHeight == 0) {
if (coins->fCoinBase != undo.fCoinBase || (uint32_t)coins->nHeight != undo.nHeight) fClean = false; // metadata mismatch // Missing undo metadata (height and coinbase). Older versions included this
// information only in undo records for the last spend of a transactions'
// outputs. This implies that it must be present for some other output of the same tx.
const Coin& alternate = AccessByTxid(view, out.hash);
if (!alternate.IsPruned()) {
undo.nHeight = alternate.nHeight;
undo.fCoinBase = alternate.fCoinBase;
} else {
return DISCONNECT_FAILED; // adding output for transaction without known metadata
} }
// restore height/coinbase tx metadata from undo data
coins->fCoinBase = undo.fCoinBase;
coins->nHeight = undo.nHeight;
} else {
// Undo data does not contain height/coinbase. This should never happen
// for newly created undo entries. Previously, this data was only saved
// for the last spend of a transaction's outputs, so check IsPruned().
if (coins->IsPruned()) fClean = false; // adding output to missing transaction
} }
if (coins->IsAvailable(out.n)) fClean = false; // overwriting existing output view.AddCoin(out, std::move(undo), undo.fCoinBase);
if (coins->vout.size() < out.n+1)
coins->vout.resize(out.n+1);
coins->vout[out.n] = std::move(undo.out);
return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;
} }
@ -1313,15 +1296,15 @@ static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex*
// Check that all outputs are available and match the outputs in the block itself // Check that all outputs are available and match the outputs in the block itself
// exactly. // exactly.
{ for (size_t o = 0; o < tx.vout.size(); o++) {
CCoinsModifier outs = view.ModifyCoins(hash); if (!tx.vout[o].scriptPubKey.IsUnspendable()) {
outs->ClearUnspendable(); COutPoint out(hash, o);
Coin coin;
CCoins outsBlock(tx, pindex->nHeight); view.SpendCoin(out, &coin);
if (*outs != outsBlock) fClean = false; // transaction mismatch if (tx.vout[o] != coin.out) {
fClean = false; // transaction output mismatch
// remove outputs }
outs->Clear(); }
} }
// restore inputs // restore inputs
@ -1518,10 +1501,12 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
if (fEnforceBIP30) { if (fEnforceBIP30) {
for (const auto& tx : block.vtx) { for (const auto& tx : block.vtx) {
const CCoins* coins = view.AccessCoins(tx->GetHash()); for (size_t o = 0; o < tx->vout.size(); o++) {
if (coins && !coins->IsPruned()) if (view.HaveCoins(COutPoint(tx->GetHash(), o))) {
return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"), return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"),
REJECT_INVALID, "bad-txns-BIP30"); REJECT_INVALID, "bad-txns-BIP30");
}
}
} }
} }
@ -1588,7 +1573,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
// be in ConnectBlock because they require the UTXO set // be in ConnectBlock because they require the UTXO set
prevheights.resize(tx.vin.size()); prevheights.resize(tx.vin.size());
for (size_t j = 0; j < tx.vin.size(); j++) { for (size_t j = 0; j < tx.vin.size(); j++) {
prevheights[j] = view.AccessCoins(tx.vin[j].prevout.hash)->nHeight; prevheights[j] = view.AccessCoin(tx.vin[j].prevout).nHeight;
} }
if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) { if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) {

Loading…
Cancel
Save