-
Notifications
You must be signed in to change notification settings - Fork 38.7k
Near-Bugfix: Optimization: Minimize the number of times it is checked that no money... #8498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3f0ee3e
832e074
3e8c916
4e955c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |
| #include "chain.h" | ||
| #include "coins.h" | ||
| #include "utilmoneystr.h" | ||
|
|
||
| bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime) | ||
| { | ||
| if (tx.nLockTime == 0) | ||
|
|
@@ -205,46 +205,46 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe | |
| return true; | ||
| } | ||
|
|
||
| bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight) | ||
| bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) | ||
| { | ||
| // 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(false, 0, "", "Inputs unavailable"); | ||
|
|
||
| CAmount nValueIn = 0; | ||
| CAmount nFees = 0; | ||
| for (unsigned int i = 0; i < tx.vin.size(); i++) | ||
| { | ||
| const COutPoint &prevout = tx.vin[i].prevout; | ||
| const Coin& coin = inputs.AccessCoin(prevout); | ||
| assert(!coin.IsSpent()); | ||
|
|
||
| // If prev is coinbase, check that it's matured | ||
| if (coin.IsCoinBase()) { | ||
| if (nSpendHeight - coin.nHeight < COINBASE_MATURITY) | ||
| return state.Invalid(false, | ||
| REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", | ||
| strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); | ||
| } | ||
|
|
||
| // Check for negative or overflow input values | ||
| nValueIn += coin.out.nValue; | ||
| if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); | ||
| // are the actual inputs available? | ||
| if (!inputs.HaveInputs(tx)) { | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", false, | ||
| strprintf("%s: inputs missing/spent", __func__)); | ||
| } | ||
|
|
||
| CAmount nValueIn = 0; | ||
| for (unsigned int i = 0; i < tx.vin.size(); ++i) { | ||
|
||
| const COutPoint &prevout = tx.vin[i].prevout; | ||
| const Coin& coin = inputs.AccessCoin(prevout); | ||
|
||
| assert(!coin.IsSpent()); | ||
|
|
||
| // If prev is coinbase, check that it's matured | ||
| if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { | ||
| return state.Invalid(false, | ||
| REJECT_INVALID, "bad-txns-premature-spend-of-coinbase", | ||
| strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); | ||
| } | ||
|
|
||
| // Check for negative or overflow input values | ||
| nValueIn += coin.out.nValue; | ||
| if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); | ||
| } | ||
| } | ||
|
|
||
| const CAmount value_out = tx.GetValueOut(); | ||
| if (nValueIn < value_out) { | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, | ||
| strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out))); | ||
| } | ||
|
|
||
| // Tally transaction fees | ||
| const CAmount txfee_aux = nValueIn - value_out; | ||
| if (!MoneyRange(txfee_aux)) { | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); | ||
| } | ||
|
|
||
| if (nValueIn < tx.GetValueOut()) | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, | ||
| strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(tx.GetValueOut()))); | ||
|
|
||
| // Tally transaction fees | ||
| CAmount nTxFee = nValueIn - tx.GetValueOut(); | ||
| if (nTxFee < 0) | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-negative"); | ||
| nFees += nTxFee; | ||
| if (!MoneyRange(nFees)) | ||
| return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); | ||
| txfee = txfee_aux; | ||
| return true; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -484,7 +484,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool | |
| CCoinsView dummy; | ||
| CCoinsViewCache view(&dummy); | ||
|
|
||
| CAmount nValueIn = 0; | ||
| LockPoints lp; | ||
| { | ||
| LOCK(pool.cs); | ||
|
|
@@ -519,8 +518,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool | |
| // Bring the best block into scope | ||
| view.GetBestBlock(); | ||
|
|
||
| nValueIn = view.GetValueIn(tx); | ||
|
|
||
| // we have all inputs cached now, so switch back to dummy, so we don't need to keep lock on mempool | ||
| view.SetBackend(dummy); | ||
|
|
||
|
|
@@ -531,6 +528,12 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool | |
| // CoinsViewCache instead of create its own | ||
| if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) | ||
| return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); | ||
|
|
||
| } // end LOCK(pool.cs) | ||
|
|
||
| CAmount nFees = 0; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be more extra disruption only for style. We can do that at any time. Including potentially more rebase work fixing conflicts with other things touching AcceptToMemoryPoolWorker. Can we leave that for later? Note we're not creating a new variable but just moving its declaration here. |
||
| if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) { | ||
| return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); | ||
| } | ||
|
|
||
| // Check for non-standard pay-to-script-hash in inputs | ||
|
|
@@ -543,8 +546,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool | |
|
|
||
| int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS); | ||
|
|
||
| CAmount nValueOut = tx.GetValueOut(); | ||
| CAmount nFees = nValueIn-nValueOut; | ||
| // nModifiedFees includes any fee deltas from PrioritiseTransaction | ||
| CAmount nModifiedFees = nFees; | ||
| pool.ApplyDelta(hash, nModifiedFees); | ||
|
|
@@ -1161,9 +1162,6 @@ static bool CheckInputs(const CTransaction& tx, CValidationState &state, const C | |
| { | ||
| if (!tx.IsCoinBase()) | ||
| { | ||
| if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs))) | ||
| return false; | ||
|
|
||
| if (pvChecks) | ||
| pvChecks->reserve(tx.vin.size()); | ||
|
|
||
|
|
@@ -1635,9 +1633,15 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd | |
|
|
||
| if (!tx.IsCoinBase()) | ||
| { | ||
| if (!view.HaveInputs(tx)) | ||
| return state.DoS(100, error("ConnectBlock(): inputs missing/spent"), | ||
| REJECT_INVALID, "bad-txns-inputs-missingorspent"); | ||
| CAmount txfee = 0; | ||
| if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) { | ||
| return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); | ||
| } | ||
| nFees += txfee; | ||
| if (!MoneyRange(nFees)) { | ||
| return state.DoS(100, error("%s: accumulated fee in the block out of range.", __func__), | ||
| REJECT_INVALID, "bad-txns-accumulated-fee-outofrange"); | ||
| } | ||
|
|
||
| // Check that transaction is BIP68 final | ||
| // BIP68 lock checks (as opposed to nLockTime checks) must | ||
|
|
@@ -1665,8 +1669,6 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd | |
| txdata.emplace_back(tx); | ||
| if (!tx.IsCoinBase()) | ||
| { | ||
| nFees += view.GetValueIn(tx)-tx.GetValueOut(); | ||
|
|
||
| std::vector<CScriptCheck> vChecks; | ||
| bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ | ||
| if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : NULL)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current code has an explicit comment about not triggering the DoS code on purpose, did you remove it (and invoke the DoS code) intentionally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment was moved to the separated HaveInputs call in AcceptToMemoryPoolWorker.
Perhaps lost in rebase, I'll look at it.
Perhaps it's fine since we now have: c93ceb2#diff-24efdb00bfbe56b140fb006b562cc70bR514
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not just the comment, as you can see in the diff previously the code used
state.Invalid(and stated it does so on purpose), and now both the comment is gone and the code usesstate.DoSI don't know to say on my own whether that change is valid, but it definitely requires explanation due to the fact that previously there was a specific comment suggesting using
state.DoSthere is bad.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be OK, the DoS from ConnectBlock is important, but the DoS shouldn't be trigger'able in ATMP - we should have already returned false int he pfMissingInputs branch further up.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TheBlueMatt
Which DoS? The one previously returned from
CheckTxInputsin case of missing inputs? If so, IIUC it wouldn't trigger after this change, there would just be an error returned, since it replaced the previous DoS inConnectBlockc93ceb2#diff-24efdb00bfbe56b140fb006b562cc70bR1638There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DoS values persist through the state object (and should only be set once through the entire call tree). Thus, when ConnectBlock calls CheckTxInputs, which sets DoS, ConnectBlock should return only error() and not call DoS again, but the DoS points will still be returned through the state object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TheBlueMatt Ah, I see. Thanks!