Skip to content

Commit

Permalink
Merge #1673: [Core] Separate Consensus CheckTxInputs and GetSpendHeig…
Browse files Browse the repository at this point in the history
…ht in CheckInputs

e27420e Separate Consensus::CheckTxInputs and GetSpendHeight in CheckInputs (furszy)

Pull request description:

  Coming from bitcoin#6061

  Refactor needed for an upcoming work, no functional changes.

  Had to do something little bit dirty to be able to get Consensus::Params from inside the Consensus namespace (struct `Params` name clashes with global method `Params()`) and not have any functional change there.

  Point of discussion for a later PR: could be moved to a function argument or check if there is another workaround to distinguish between the name clash.

ACKs for top commit:
  random-zebra:
    utACK e27420e
  Fuzzbawls:
    utACK e27420e

Tree-SHA512: 953921659a7ab41d954a8110d2c1b6911bc44b2b458ffbfd97f01262e4c946444bb6537081f34c4ba3ff6f5fca5e803b1d99a5fc84ff902f42a1c8f2956ff4d6
  • Loading branch information
Fuzzbawls committed Jun 17, 2020
2 parents fb14a8f + e27420e commit a5ba720
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 49 deletions.
114 changes: 65 additions & 49 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1913,60 +1913,76 @@ bool ValidOutPoint(const COutPoint& out, int nHeight)
return !isInvalid;
}

bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck>* pvChecks)
int GetSpendHeight(const CCoinsViewCache& inputs)
{
if (!tx.IsCoinBase() && !tx.HasZerocoinSpendInputs()) {
if (pvChecks)
pvChecks->reserve(tx.vin.size());
LOCK(cs_main);
CBlockIndex* pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second;
return pindexPrev->nHeight + 1;
}

// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!inputs.HaveInputs(tx))
return state.Invalid(error("CheckInputs() : %s inputs unavailable", tx.GetHash().ToString()));
namespace Consensus {
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight)
{
// This doesn't trigger the DoS code on purpose; if it did, it would make it easier
// for an attacker to attempt to split the network.
if (!inputs.HaveInputs(tx))
return state.Invalid(error("CheckInputs() : %s inputs unavailable", tx.GetHash().ToString()));

// While checking, GetBestBlock() refers to the parent block.
// This is also true for mempool checks.
const Consensus::Params& consensus = Params().GetConsensus();
CBlockIndex* pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second;
int nSpendHeight = pindexPrev->nHeight + 1;
CAmount nValueIn = 0;
CAmount nFees = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint& prevout = tx.vin[i].prevout;
const CCoins* coins = inputs.AccessCoins(prevout.hash);
assert(coins);

// If prev is coinbase, check that it's matured
if (coins->IsCoinBase() || coins->IsCoinStake()) {
if (nSpendHeight - coins->nHeight < Params().GetConsensus().nCoinbaseMaturity)
return state.Invalid(
error("CheckInputs() : tried to spend coinbase at depth %d, coinstake=%d", nSpendHeight - coins->nHeight, coins->IsCoinStake()),
const Consensus::Params& consensus = ::Params().GetConsensus();
CAmount nValueIn = 0;
CAmount nFees = 0;
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout;
const CCoins *coins = inputs.AccessCoins(prevout.hash);
assert(coins);

// If prev is coinbase, check that it's matured
if (coins->IsCoinBase() || coins->IsCoinStake()) {
if (nSpendHeight - coins->nHeight < consensus.nCoinbaseMaturity)
return state.Invalid(
error("CheckInputs() : tried to spend coinbase at depth %d, coinstake=%d",
nSpendHeight - coins->nHeight, coins->IsCoinStake()),
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase");
}

// Check for negative or overflow input values
nValueIn += coins->vout[prevout.n].nValue;
if (!consensus.MoneyRange(coins->vout[prevout.n].nValue) || !consensus.MoneyRange(nValueIn))
return state.DoS(100, error("CheckInputs() : txin values out of range"),
REJECT_INVALID, "bad-txns-inputvalues-outofrange");
}

if (!tx.IsCoinStake()) {
if (nValueIn < tx.GetValueOut())
return state.DoS(100, error("CheckInputs() : %s value in (%s) < value out (%s)",
tx.GetHash().ToString(), FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())),
REJECT_INVALID, "bad-txns-in-belowout");

// Tally transaction fees
CAmount nTxFee = nValueIn - tx.GetValueOut();
if (nTxFee < 0)
return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", tx.GetHash().ToString()),
REJECT_INVALID, "bad-txns-fee-negative");
nFees += nTxFee;
if (!consensus.MoneyRange(nFees))
return state.DoS(100, error("CheckInputs() : nFees out of range"),
REJECT_INVALID, "bad-txns-fee-outofrange");
}

// Check for negative or overflow input values
nValueIn += coins->vout[prevout.n].nValue;
if (!consensus.MoneyRange(coins->vout[prevout.n].nValue) || !consensus.MoneyRange(nValueIn))
return state.DoS(100, error("CheckInputs() : txin values out of range"),
REJECT_INVALID, "bad-txns-inputvalues-outofrange");
}

if (!tx.IsCoinStake()) {
if (nValueIn < tx.GetValueOut())
return state.DoS(100, error("CheckInputs() : %s value in (%s) < value out (%s)",
tx.GetHash().ToString(), FormatMoney(nValueIn),
FormatMoney(tx.GetValueOut())),
REJECT_INVALID, "bad-txns-in-belowout");

// Tally transaction fees
CAmount nTxFee = nValueIn - tx.GetValueOut();
if (nTxFee < 0)
return state.DoS(100, error("CheckInputs() : %s nTxFee < 0", tx.GetHash().ToString()),
REJECT_INVALID, "bad-txns-fee-negative");
nFees += nTxFee;
if (!consensus.MoneyRange(nFees))
return state.DoS(100, error("CheckInputs() : nFees out of range"),
REJECT_INVALID, "bad-txns-fee-outofrange");
}
return true;
}
}// namespace Consensus

bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector<CScriptCheck> *pvChecks)
{
if (!tx.IsCoinBase() && !tx.HasZerocoinSpendInputs()) {

if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs)))
return false;

if (pvChecks)
pvChecks->reserve(tx.vin.size());

// The first loop above does all the inexpensive checks.
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
// Helps prevent CPU exhaustion attacks.
Expand Down
7 changes: 7 additions & 0 deletions src/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -416,4 +416,11 @@ extern CZerocoinDB* zerocoinDB;
/** Global variable that points to the spork database (protected by cs_main) */
extern CSporkDB* pSporkDB;

/**
* Return the spend height, which is one more than the inputs.GetBestBlock().
* While checking, GetBestBlock() refers to the parent block. (protected by cs_main)
* This is also true for mempool checks.
*/
int GetSpendHeight(const CCoinsViewCache& inputs);

#endif // BITCOIN_MAIN_H

0 comments on commit a5ba720

Please sign in to comment.