Changeset View
Changeset View
Standalone View
Standalone View
src/script/scriptcache.cpp
// Copyright (c) 2017 The Bitcoin developers | // Copyright (c) 2017 The Bitcoin developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <script/scriptcache.h> | #include <script/scriptcache.h> | ||||
#include <crypto/sha256.h> | #include <crypto/sha256.h> | ||||
#include <cuckoocache.h> | #include <cuckoocache.h> | ||||
#include <primitives/transaction.h> | #include <primitives/transaction.h> | ||||
#include <random.h> | #include <random.h> | ||||
#include <script/sigcache.h> | #include <script/sigcache.h> | ||||
#include <sync.h> | #include <sync.h> | ||||
#include <util/system.h> | #include <util/system.h> | ||||
#include <validation.h> | #include <validation.h> | ||||
static CuckooCache::cache<CuckooCache::KeyOnly<uint256>, SignatureCacheHasher> | #include <limits> | ||||
/** | |||||
* In future if many more values are added, it should be considered to | |||||
* expand the element size to 64 bytes (with padding the spare space as | |||||
* needed) so the key can be long. Shortening the key too much risks | |||||
* opening ourselves up to consensus-failing collisions, however it should | |||||
* be noted that our cache nonce is private and unique, so collisions would | |||||
* affect only one node and attackers have no way of offline-preparing a | |||||
* collision attack even on short keys. | |||||
*/ | |||||
struct ScriptCacheElement { | |||||
using KeyType = ScriptCacheKey; | |||||
KeyType key; | |||||
uint16_t nSigChecks; | |||||
static_assert(SCRIPT_CACHE_MAX_SIGCHECKS == | |||||
std::numeric_limits<decltype(nSigChecks)>::max(), | |||||
"the declared maximum must match the storage type"); | |||||
ScriptCacheElement() = default; | |||||
ScriptCacheElement(const KeyType &keyIn, uint16_t nSigChecksIn) | |||||
: key(keyIn), nSigChecks(nSigChecksIn) {} | |||||
const KeyType &getKey() const { return key; } | |||||
}; | |||||
static_assert(sizeof(ScriptCacheElement) == 32, | |||||
"ScriptCacheElement must be 32 bytes"); | |||||
class ScriptCacheHasher { | |||||
public: | |||||
template <uint8_t hash_select> | |||||
uint32_t operator()(const ScriptCacheKey &k) const { | |||||
static_assert(hash_select < 8, "only has 8 hashes available."); | |||||
const auto &d = k.data; | |||||
static_assert(sizeof(d) == 30, | |||||
"modify the following if key size changes"); | |||||
uint32_t u; | |||||
if (hash_select < 7) { | |||||
std::memcpy(&u, d.data() + 4 * hash_select, 4); | |||||
} else { | |||||
// We are required to produce 8 subhashes. We have only two | |||||
// key bytes left over but this is fine. We put the leftovers | |||||
// on the higher bits, as these are what primarily get used to | |||||
// determine the index. The lower bits are filled by reusing | |||||
// some other bytes. | |||||
u = (uint32_t(d[28]) << 24) + (uint32_t(d[29]) << 16) + | |||||
(uint32_t(d[3]) << 8) + uint32_t(d[7]); | |||||
} | |||||
return u; | |||||
} | |||||
}; | |||||
static CuckooCache::cache<ScriptCacheElement, ScriptCacheHasher> | |||||
scriptExecutionCache; | scriptExecutionCache; | ||||
static uint256 scriptExecutionCacheNonce(GetRandHash()); | static uint256 scriptExecutionCacheNonce(GetRandHash()); | ||||
void InitScriptExecutionCache() { | void InitScriptExecutionCache() { | ||||
// nMaxCacheSize is unsigned. If -maxscriptcachesize is set to zero, | // nMaxCacheSize is unsigned. If -maxscriptcachesize is set to zero, | ||||
// setup_bytes creates the minimum possible cache (2 elements). | // setup_bytes creates the minimum possible cache (2 elements). | ||||
size_t nMaxCacheSize = | size_t nMaxCacheSize = | ||||
std::min( | std::min( | ||||
std::max(int64_t(0), gArgs.GetArg("-maxscriptcachesize", | std::max(int64_t(0), gArgs.GetArg("-maxscriptcachesize", | ||||
DEFAULT_MAX_SCRIPT_CACHE_SIZE)), | DEFAULT_MAX_SCRIPT_CACHE_SIZE)), | ||||
MAX_MAX_SCRIPT_CACHE_SIZE) * | MAX_MAX_SCRIPT_CACHE_SIZE) * | ||||
(size_t(1) << 20); | (size_t(1) << 20); | ||||
size_t nElems = scriptExecutionCache.setup_bytes(nMaxCacheSize); | size_t nElems = scriptExecutionCache.setup_bytes(nMaxCacheSize); | ||||
LogPrintf("Using %zu MiB out of %zu requested for script execution cache, " | LogPrintf("Using %zu MiB out of %zu requested for script execution cache, " | ||||
"able to store %zu elements\n", | "able to store %zu elements\n", | ||||
(nElems * sizeof(uint256)) >> 20, nMaxCacheSize >> 20, nElems); | (nElems * sizeof(uint256)) >> 20, nMaxCacheSize >> 20, nElems); | ||||
} | } | ||||
uint256 GetScriptCacheKey(const CTransaction &tx, uint32_t flags) { | ScriptCacheKey::ScriptCacheKey(const CTransaction &tx, uint32_t flags) { | ||||
uint256 key; | std::array<uint8_t, 32> hash; | ||||
// We only use the first 19 bytes of nonce to avoid a second SHA round - | // 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) | // giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64) | ||||
static_assert(55 - sizeof(flags) - 32 >= 128 / 8, | static_assert(55 - sizeof(flags) - 32 >= 128 / 8, | ||||
"Want at least 128 bits of nonce for script execution cache"); | "Want at least 128 bits of nonce for script execution cache"); | ||||
CSHA256() | CSHA256() | ||||
.Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32) | .Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32) | ||||
.Write(tx.GetHash().begin(), 32) | .Write(tx.GetHash().begin(), 32) | ||||
.Write((uint8_t *)&flags, sizeof(flags)) | .Write((uint8_t *)&flags, sizeof(flags)) | ||||
.Finalize(key.begin()); | .Finalize(hash.begin()); | ||||
return key; | assert(data.size() < hash.size()); | ||||
std::copy(hash.begin(), hash.begin() + data.size(), data.begin()); | |||||
} | } | ||||
bool IsKeyInScriptCache(uint256 key, bool erase) { | bool IsKeyInScriptCache(ScriptCacheKey key, bool erase, | ||||
ScriptExecutionMetrics &metricsOut) { | |||||
// TODO: Remove this requirement by making CuckooCache not require external | // TODO: Remove this requirement by making CuckooCache not require external | ||||
// locks | // locks | ||||
AssertLockHeld(cs_main); | AssertLockHeld(cs_main); | ||||
return scriptExecutionCache.contains(key, erase); | |||||
ScriptCacheElement elem(key, 0); | |||||
bool ret = scriptExecutionCache.get(elem, erase); | |||||
// use constructor initialization here rather than initializer list / | |||||
// setting individual items, to make sure we don't miss any. | |||||
metricsOut = ScriptExecutionMetrics(elem.nSigChecks); | |||||
return ret; | |||||
} | } | ||||
void AddKeyInScriptCache(uint256 key) { | bool TryAddKeyInScriptCache(ScriptCacheKey key, | ||||
ScriptExecutionMetrics metricsIn) { | |||||
// TODO: Remove this requirement by making CuckooCache not require external | // TODO: Remove this requirement by making CuckooCache not require external | ||||
// locks | // locks | ||||
AssertLockHeld(cs_main); | AssertLockHeld(cs_main); | ||||
scriptExecutionCache.insert(key); | |||||
if (metricsIn.nSigChecks < 0 || | |||||
metricsIn.nSigChecks > SCRIPT_CACHE_MAX_SIGCHECKS) { | |||||
return false; | |||||
} | |||||
ScriptCacheElement elem(key, metricsIn.nSigChecks); | |||||
scriptExecutionCache.insert(elem); | |||||
return true; | |||||
} | } |