From 1882d75407f5d294d8fa2ca65c44f1f7c1f52c28 Mon Sep 17 00:00:00 2001 From: wqking Date: Mon, 13 Jan 2020 18:35:20 +0800 Subject: [PATCH] Cache full script execution results. Reference: https://github.com/bitcoin/bitcoin/pull/10192 --- src/init.cpp | 1 + src/main.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++--------- src/main.h | 4 +++- src/miner.cpp | 2 +- 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 94db304443501..2d4c7b40a2d8a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -897,6 +897,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler, const std InitWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench.")); InitSignatureCache(); + InitScriptExecutionCache(); // Checkmempool and checkblockindex default to true in regtest mode mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultConsistencyChecks())); diff --git a/src/main.cpp b/src/main.cpp index baee26c7f035c..ab9e495bf63aa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,6 +18,7 @@ #include "checkqueue.h" #include "consensus/merkle.h" #include "consensus/validation.h" +#include "cuckoocache.h" #include "init.h" #include "kernel.h" #include "masternode-budget.h" @@ -505,6 +506,18 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector scriptExecutionCache; +static uint256 scriptExecutionCacheNonce(GetRandHash()); + +void InitScriptExecutionCache() { + // nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero, + // setup_bytes creates the minimum possible cache (2 elements). + size_t nMaxCacheSize = std::min(std::max((int64_t)0, GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) / 2), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20); + size_t nElems = scriptExecutionCache.setup_bytes(nMaxCacheSize); + LogPrintf("Using %zu MiB out of %zu/2 requested for script execution cache, able to store %zu elements\n", + (nElems*sizeof(uint256)) >>20, (nMaxCacheSize*2)>>20, nElems); +} + bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) { LOCK(cs_main); @@ -1733,12 +1746,12 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. - if (!CheckInputs(tx, state, view, true, scriptVerifyFlags, true)) { + if (!CheckInputs(tx, state, view, true, scriptVerifyFlags, true, false)) { // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we // need to turn both off, and compare against just turning off CLEANSTACK // to see if the failure is specifically due to witness validation. - if (CheckInputs(tx, state, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true) && - !CheckInputs(tx, state, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true)) { + if (CheckInputs(tx, state, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false) && + !CheckInputs(tx, state, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false)) { // Only the witness is wrong, so the transaction itself may be fine. state.SetCorruptionPossible(); } @@ -1754,7 +1767,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa // There is a similar check in CreateNewBlock() to prevent creating // invalid blocks, however allowing such transactions into the mempool // can be exploited as a DoS attack. - if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true)) { + if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, false)) { return error("AcceptToMemoryPool: : BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString()); } @@ -1987,7 +2000,7 @@ bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransact // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. - if (!CheckInputs(tx, state, view, false, STANDARD_SCRIPT_VERIFY_FLAGS, true)) { + if (!CheckInputs(tx, state, view, false, STANDARD_SCRIPT_VERIFY_FLAGS, true, false)) { return error("AcceptableInputs: : ConnectInputs failed %s", hash.ToString()); } @@ -2354,7 +2367,7 @@ bool CScriptCheck::operator()() return true; } -bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector* pvChecks) +bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, std::vector* pvChecks) { if (!tx.IsCoinBase() && !tx.IsZerocoinSpend()) { if (pvChecks) @@ -2365,6 +2378,23 @@ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsVi if (!inputs.HaveInputs(tx)) return state.Invalid(error("CheckInputs() : %s inputs unavailable", tx.GetHash().ToString())); + // First check if script executions have been cached with the same + // flags. Note that this assumes that the inputs provided are + // correct (ie that the transaction hash which is in tx's prevouts + // properly commits to the scriptPubKey in the inputs view of that + // transaction). + uint256 hashCacheEntry; + // We only use the first 19 bytes of nonce to avoid a second SHA + // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64) + static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache"); + CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin()); + { + AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks + if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) { + return true; + } + } + // While checking, GetBestBlock() refers to the parent block. // This is also true for mempool checks. CBlockIndex* pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second; @@ -2421,7 +2451,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsVi assert(coins); // Verify signature - CScriptCheck check(*coins, tx, i, flags, cacheStore); + CScriptCheck check(*coins, tx, i, flags, cacheSigStore); if (pvChecks) { pvChecks->push_back(CScriptCheck()); check.swap(pvChecks->back()); @@ -2434,7 +2464,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsVi // avoid splitting the network between upgraded and // non-upgraded nodes. CScriptCheck check2(*coins, tx, i, - flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore); + flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore); if (check2()) return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); } @@ -2449,6 +2479,13 @@ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsVi } } } + + if (cacheFullScriptStore && !pvChecks) { + AssertLockHeld(cs_main); + // We executed all of the provided scripts, and were told to + // cache the result. Do so now. + scriptExecutionCache.insert(hashCacheEntry); + } } return true; @@ -3057,7 +3094,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock(): too many sigops"), REJECT_INVALID, "bad-blk-sigops"); - if (!CheckInputs(tx, state, view, fScriptChecks, flags, false, nScriptCheckThreads ? &vChecks : NULL)) + 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, fCacheResults, nScriptCheckThreads ? &vChecks : NULL)) return false; control.Add(vChecks); } diff --git a/src/main.h b/src/main.h index 4a6d769f0a822..6e55f0d73df68 100644 --- a/src/main.h +++ b/src/main.h @@ -403,7 +403,7 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i * This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it * instead of being performed inline. */ -bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector* pvChecks = NULL); +bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, std::vector* pvChecks = NULL); /** Apply the effects of this transaction on the UTXO set represented by view */ void UpdateCoins(const CTransaction& tx, CValidationState& state, CCoinsViewCache& inputs, CTxUndo& txundo, int nHeight); @@ -659,4 +659,6 @@ struct CBlockTemplate { int64_t GetVirtualTransactionSize(const CTransaction& tx); int64_t GetVirtualTransactionSize(int64_t nCost); +void InitScriptExecutionCache(); + #endif // BITCOIN_MAIN_H diff --git a/src/miner.cpp b/src/miner.cpp index ad97fac4527f2..392ad0dd5e5c9 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -412,7 +412,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, // policy here, but we still have to ensure that the block we // create only contains transactions that are valid in new blocks. CValidationState state; - if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true)) + if (!CheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, false)) continue; CTxUndo txundo;