Skip to content

Commit

Permalink
Cache full script execution results.
Browse files Browse the repository at this point in the history
Reference: bitcoin#10192
  • Loading branch information
wqking authored and tohsnoom committed Jun 11, 2020
1 parent c698f9e commit d5f9fbf
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/init.cpp
Expand Up @@ -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()));
Expand Down
56 changes: 47 additions & 9 deletions src/main.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -505,6 +506,18 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<CBl

} // anon namespace

static CuckooCache::cache<uint256, SignatureCacheHasher> 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);
Expand Down Expand Up @@ -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();
}
Expand All @@ -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());
}

Expand Down Expand Up @@ -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());
}

Expand Down Expand Up @@ -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<CScriptCheck>* pvChecks)
bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, std::vector<CScriptCheck>* pvChecks)
{
if (!tx.IsCoinBase() && !tx.IsZerocoinSpend()) {
if (pvChecks)
Expand All @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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())));
}
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 3 additions & 1 deletion src/main.h
Expand Up @@ -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<CScriptCheck>* pvChecks = NULL);
bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& view, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, std::vector<CScriptCheck>* 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);
Expand Down Expand Up @@ -659,4 +659,6 @@ struct CBlockTemplate {
int64_t GetVirtualTransactionSize(const CTransaction& tx);
int64_t GetVirtualTransactionSize(int64_t nCost);

void InitScriptExecutionCache();

#endif // BITCOIN_MAIN_H
2 changes: 1 addition & 1 deletion src/miner.cpp
Expand Up @@ -413,7 +413,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;
Expand Down

0 comments on commit d5f9fbf

Please sign in to comment.