aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/init.cpp1
-rw-r--r--src/script/sigcache.cpp2
-rw-r--r--src/test/test_bitcoin.cpp1
-rw-r--r--src/validation.cpp68
-rw-r--r--src/validation.h3
5 files changed, 61 insertions, 14 deletions
diff --git a/src/init.cpp b/src/init.cpp
index 04d1280c92..aab5670115 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1211,6 +1211,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD);
InitSignatureCache();
+ InitScriptExecutionCache();
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
if (nScriptCheckThreads) {
diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp
index 7bb8d9941b..bf0e006068 100644
--- a/src/script/sigcache.cpp
+++ b/src/script/sigcache.cpp
@@ -74,7 +74,7 @@ void InitSignatureCache()
{
// 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)), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
+ 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 = signatureCache.setup_bytes(nMaxCacheSize);
LogPrintf("Using %zu MiB out of %zu requested for signature cache, able to store %zu elements\n",
(nElems*sizeof(uint256)) >>20, nMaxCacheSize>>20, nElems);
diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp
index cb625bda11..c6a5b12a97 100644
--- a/src/test/test_bitcoin.cpp
+++ b/src/test/test_bitcoin.cpp
@@ -39,6 +39,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName)
SetupEnvironment();
SetupNetworking();
InitSignatureCache();
+ InitScriptExecutionCache();
fPrintToDebugLog = false; // don't want to write to debug.log file
fCheckBlockIndex = true;
SelectParams(chainName);
diff --git a/src/validation.cpp b/src/validation.cpp
index 5b746229c9..9a02e9684a 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -14,6 +14,7 @@
#include "consensus/merkle.h"
#include "consensus/tx_verify.h"
#include "consensus/validation.h"
+#include "cuckoocache.h"
#include "fs.h"
#include "hash.h"
#include "init.h"
@@ -189,7 +190,7 @@ enum FlushStateMode {
static bool FlushStateToDisk(const CChainParams& chainParams, CValidationState &state, FlushStateMode mode, int nManualPruneHeight=0);
static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight);
static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight);
-static bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = NULL);
+static bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr);
static FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
bool CheckFinalTx(const CTransaction &tx, int flags)
@@ -752,29 +753,36 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
PrecomputedTransactionData txdata(tx);
- if (!CheckInputs(tx, state, view, true, scriptVerifyFlags, true, txdata)) {
+ if (!CheckInputs(tx, state, view, true, scriptVerifyFlags, true, false, txdata)) {
// 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.
CValidationState stateDummy; // Want reported failures to be from first CheckInputs
- if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, txdata) &&
- !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, txdata)) {
+ if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
+ !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
// Only the witness is missing, so the transaction itself may be fine.
state.SetCorruptionPossible();
}
return false; // state filled in by CheckInputs
}
- // Check again against just the consensus-critical mandatory script
- // verification flags, in case of bugs in the standard flags that cause
+ // Check again against the current block tip's script verification
+ // flags to cache our script execution flags. This is, of course,
+ // useless if the next block has different script flags from the
+ // previous one, but because the cache tracks script flags for us it
+ // will auto-invalidate and we'll just have a few blocks of extra
+ // misses on soft-fork activation.
+ //
+ // This is also useful in case of bugs in the standard flags that cause
// transactions to pass as valid when they're actually invalid. For
// instance the STRICTENC flag was incorrectly allowing certain
// CHECKSIG NOT scripts to pass, even though they were invalid.
//
// 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, txdata))
+ // invalid blocks (using TestBlockValidity), however allowing such
+ // transactions into the mempool can be exploited as a DoS attack.
+ unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(chainActive.Tip(), Params().GetConsensus());
+ if (!CheckInputs(tx, state, view, true, currentBlockScriptVerifyFlags, true, true, txdata))
{
return error("%s: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s, %s",
__func__, hash.ToString(), FormatStateMessage(state));
@@ -1152,12 +1160,25 @@ int GetSpendHeight(const CCoinsViewCache& inputs)
return pindexPrev->nHeight + 1;
}
+
+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 requested for script execution cache, able to store %zu elements\n",
+ (nElems*sizeof(uint256)) >>20, nMaxCacheSize>>20, nElems);
+}
+
/**
* Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
* This does not modify the UTXO set. If pvChecks is not NULL, script checks are pushed onto it
* instead of being performed inline.
*/
-static bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks)
+static bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks)
{
if (!tx.IsCoinBase())
{
@@ -1177,6 +1198,21 @@ static bool CheckInputs(const CTransaction& tx, CValidationState &state, const C
// Of course, if an assumed valid block is invalid due to false scriptSigs
// this optimization would allow an invalid chain to be accepted.
if (fScriptChecks) {
+ // 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;
+ }
+
for (unsigned int i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout;
const Coin& coin = inputs.AccessCoin(prevout);
@@ -1191,7 +1227,7 @@ static bool CheckInputs(const CTransaction& tx, CValidationState &state, const C
const CAmount amount = coin.out.nValue;
// Verify signature
- CScriptCheck check(scriptPubKey, amount, tx, i, flags, cacheStore, &txdata);
+ CScriptCheck check(scriptPubKey, amount, tx, i, flags, cacheSigStore, &txdata);
if (pvChecks) {
pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back());
@@ -1204,7 +1240,7 @@ static bool CheckInputs(const CTransaction& tx, CValidationState &state, const C
// avoid splitting the network between upgraded and
// non-upgraded nodes.
CScriptCheck check2(scriptPubKey, amount, tx, i,
- flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata);
+ flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
if (check2())
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
}
@@ -1218,6 +1254,12 @@ static bool CheckInputs(const CTransaction& tx, CValidationState &state, const C
return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
}
}
+
+ if (cacheFullScriptStore && !pvChecks) {
+ // We executed all of the provided scripts, and were told to
+ // cache the result. Do so now.
+ scriptExecutionCache.insert(hashCacheEntry);
+ }
}
}
@@ -1684,7 +1726,7 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd
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))
+ if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : NULL))
return error("ConnectBlock(): CheckInputs on %s failed with %s",
tx.GetHash().ToString(), FormatStateMessage(state));
control.Add(vChecks);
diff --git a/src/validation.h b/src/validation.h
index 3a7f7cf1bc..d097343126 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -394,6 +394,9 @@ public:
ScriptError GetScriptError() const { return error; }
};
+/** Initializes the script-execution cache */
+void InitScriptExecutionCache();
+
/** Functions for disk access for blocks */
bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos, const Consensus::Params& consensusParams);