diff --git a/src/coins.cpp b/src/coins.cpp index a7c074c80..962fa4405 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -360,6 +360,15 @@ CCoinsModifier CCoinsViewCache::ModifyCoins(const uint256 &txid) { return CCoinsModifier(*this, ret.first, cachedCoinUsage); } +CCoinsModifier CCoinsViewCache::ModifyNewCoins(const uint256 &txid) { + assert(!hasModifier); + std::pair ret = cacheCoins.insert(std::make_pair(txid, CCoinsCacheEntry())); + ret.first->second.coins.Clear(); + ret.first->second.flags = CCoinsCacheEntry::FRESH; + ret.first->second.flags |= CCoinsCacheEntry::DIRTY; + return CCoinsModifier(*this, ret.first, 0); +} + const CCoins* CCoinsViewCache::AccessCoins(const uint256 &txid) const { CCoinsMap::const_iterator it = FetchCoins(txid); if (it == cacheCoins.end()) { diff --git a/src/coins.h b/src/coins.h index d67c7da4c..4f782ee17 100644 --- a/src/coins.h +++ b/src/coins.h @@ -503,6 +503,16 @@ public: */ CCoinsModifier ModifyCoins(const uint256 &txid); + /** + * Return a modifiable reference to a CCoins. Assumes that no entry with the given + * txid exists and creates a new one. This saves a database access in the case where + * the coins were to be wiped out by FromTx anyway. We rely on Zcash-derived block chains + * having no duplicate transactions, since BIP 30 and (except for the genesis block) + * BIP 34 have been enforced since launch. See the Zcash protocol specification, section + * "Bitcoin Improvement Proposals". Simultaneous modifications are not allowed. + */ + CCoinsModifier ModifyNewCoins(const uint256 &txid); + /** * Push the modifications applied to this cache to its base. * Failure to call this method before destruction will cause the changes to be forgotten. diff --git a/src/init.cpp b/src/init.cpp index add2fc1b1..4e1f55382 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -28,6 +28,7 @@ #include "rpc/server.h" #include "rpc/register.h" #include "script/standard.h" +#include "script/sigcache.h" #include "scheduler.h" #include "txdb.h" #include "torcontrol.h" @@ -470,7 +471,7 @@ std::string HelpMessage(HelpMessageMode mode) { strUsage += HelpMessageOpt("-limitfreerelay=", strprintf("Continuously rate-limit free transactions to *1000 bytes per minute (default: %u)", 15)); strUsage += HelpMessageOpt("-relaypriority", strprintf("Require high priority for relaying free or low-fee transactions (default: %u)", 0)); - strUsage += HelpMessageOpt("-maxsigcachesize=", strprintf("Limit size of signature cache to entries (default: %u)", 50000)); + strUsage += HelpMessageOpt("-maxsigcachesize=", strprintf("Limit size of signature cache to MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE)); strUsage += HelpMessageOpt("-maxtipage=", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE)); } strUsage += HelpMessageOpt("-minrelaytxfee=", strprintf(_("Fees (in %s/kB) smaller than this are considered zero fee for relaying (default: %s)"), diff --git a/src/main.cpp b/src/main.cpp index 0c27e3f67..e1fac0a88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1947,7 +1947,7 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund inputs.SetNullifiers(tx, true); // add outputs - inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight); + inputs.ModifyNewCoins(tx.GetHash())->FromTx(tx, nHeight); } void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight) @@ -2531,7 +2531,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin nFees += view.GetValueIn(tx)-tx.GetValueOut(); std::vector vChecks; - if (!ContextualCheckInputs(tx, state, view, fExpensiveChecks, flags, false, txdata[i], chainparams.GetConsensus(), consensusBranchId, nScriptCheckThreads ? &vChecks : NULL)) + bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ + if (!ContextualCheckInputs(tx, state, view, fExpensiveChecks, flags, fCacheResults, txdata[i], chainparams.GetConsensus(), consensusBranchId, nScriptCheckThreads ? &vChecks : NULL)) return false; control.Add(vChecks); } diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 35b9f0e03..eee96e7c2 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -5,16 +5,29 @@ #include "sigcache.h" +#include "memusage.h" #include "pubkey.h" #include "random.h" #include "uint256.h" #include "util.h" #include -#include +#include namespace { +/** + * We're hashing a nonce into the entries themselves, so we don't need extra + * blinding in the set hash computation. + */ +class CSignatureCacheHasher +{ +public: + size_t operator()(const uint256& key) const { + return key.GetCheapHash(); + } +}; + /** * Valid signature cache, to avoid doing expensive ECDSA signature checking * twice for every transaction (once when accepted into memory pool, and @@ -23,52 +36,54 @@ namespace { class CSignatureCache { private: - //! sigdata_type is (signature hash, signature, public key): - typedef boost::tuple, CPubKey> sigdata_type; - std::set< sigdata_type> setValid; + //! Entries are SHA256(nonce || signature hash || public key || signature): + uint256 nonce; + typedef boost::unordered_set map_type; + map_type setValid; boost::shared_mutex cs_sigcache; -public: - bool - Get(const uint256 &hash, const std::vector& vchSig, const CPubKey& pubKey) - { - boost::shared_lock lock(cs_sigcache); - sigdata_type k(hash, vchSig, pubKey); - std::set::iterator mi = setValid.find(k); - if (mi != setValid.end()) - return true; - return false; +public: + CSignatureCache() + { + GetRandBytes(nonce.begin(), 32); } - void Set(const uint256 &hash, const std::vector& vchSig, const CPubKey& pubKey) + void + ComputeEntry(uint256& entry, const uint256 &hash, const std::vector& vchSig, const CPubKey& pubkey) { - // DoS prevention: limit cache size to less than 10MB - // (~200 bytes per cache entry times 50,000 entries) - // Since there can be no more than 20,000 signature operations per block - // 50,000 is a reasonable default. - int64_t nMaxCacheSize = GetArg("-maxsigcachesize", 50000); + CSHA256().Write(nonce.begin(), 32).Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(&vchSig[0], vchSig.size()).Finalize(entry.begin()); + } + + bool + Get(const uint256& entry) + { + boost::shared_lock lock(cs_sigcache); + return setValid.count(entry); + } + + void Erase(const uint256& entry) + { + boost::unique_lock lock(cs_sigcache); + setValid.erase(entry); + } + + void Set(const uint256& entry) + { + size_t nMaxCacheSize = GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20); if (nMaxCacheSize <= 0) return; boost::unique_lock lock(cs_sigcache); - - while (static_cast(setValid.size()) > nMaxCacheSize) + while (memusage::DynamicUsage(setValid) > nMaxCacheSize) { - // Evict a random entry. Random because that helps - // foil would-be DoS attackers who might try to pre-generate - // and re-use a set of valid signatures just-slightly-greater - // than our cache size. - uint256 randomHash = GetRandHash(); - std::vector unused; - std::set::iterator it = - setValid.lower_bound(sigdata_type(randomHash, unused, unused)); - if (it == setValid.end()) - it = setValid.begin(); - setValid.erase(*it); + map_type::size_type s = GetRand(setValid.bucket_count()); + map_type::local_iterator it = setValid.begin(s); + if (it != setValid.end(s)) { + setValid.erase(*it); + } } - sigdata_type k(hash, vchSig, pubKey); - setValid.insert(k); + setValid.insert(entry); } }; @@ -78,13 +93,21 @@ bool CachingTransactionSignatureChecker::VerifySignature(const std::vector +// DoS prevention: limit cache size to less than 40MB (over 500000 +// entries on 64-bit systems). +static const unsigned int DEFAULT_MAX_SIG_CACHE_SIZE = 40; + class CPubKey; class CachingTransactionSignatureChecker : public TransactionSignatureChecker diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index bb6432a81..1612ab449 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -132,25 +132,32 @@ public: void BatchWriteNullifiers(CNullifiersMap& mapNullifiers, std::map& cacheNullifiers) { for (CNullifiersMap::iterator it = mapNullifiers.begin(); it != mapNullifiers.end(); ) { - if (it->second.entered) { - cacheNullifiers[it->first] = true; - } else { - cacheNullifiers.erase(it->first); + if (it->second.flags & CNullifiersCacheEntry::DIRTY) { + // Same optimization used in CCoinsViewDB is to only write dirty entries. + if (it->second.entered) { + cacheNullifiers[it->first] = true; + } else { + cacheNullifiers.erase(it->first); + } } mapNullifiers.erase(it++); } - mapNullifiers.clear(); } - template + template void BatchWriteAnchors(Map& mapAnchors, std::map& cacheAnchors) { for (auto it = mapAnchors.begin(); it != mapAnchors.end(); ) { - if (it->second.entered) { - auto ret = cacheAnchors.insert(std::make_pair(it->first, Tree())).first; - ret->second = it->second.tree; - } else { - cacheAnchors.erase(it->first); + if (it->second.flags & MapEntry::DIRTY) { + // Same optimization used in CCoinsViewDB is to only write dirty entries. + if (it->second.entered) { + if (it->first != Tree::empty_root()) { + auto ret = cacheAnchors.insert(std::make_pair(it->first, Tree())).first; + ret->second = it->second.tree; + } + } else { + cacheAnchors.erase(it->first); + } } mapAnchors.erase(it++); } @@ -166,26 +173,29 @@ public: CNullifiersMap& mapSaplingNullifiers) { for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) { - map_[it->first] = it->second.coins; - if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) { - // Randomly delete empty entries on write. - map_.erase(it->first); + if (it->second.flags & CCoinsCacheEntry::DIRTY) { + // Same optimization used in CCoinsViewDB is to only write dirty entries. + map_[it->first] = it->second.coins; + if (it->second.coins.IsPruned() && insecure_rand() % 3 == 0) { + // Randomly delete empty entries on write. + map_.erase(it->first); + } } mapCoins.erase(it++); } - BatchWriteAnchors(mapSproutAnchors, mapSproutAnchors_); - BatchWriteAnchors(mapSaplingAnchors, mapSaplingAnchors_); + BatchWriteAnchors(mapSproutAnchors, mapSproutAnchors_); + BatchWriteAnchors(mapSaplingAnchors, mapSaplingAnchors_); BatchWriteNullifiers(mapSproutNullifiers, mapSproutNullifiers_); BatchWriteNullifiers(mapSaplingNullifiers, mapSaplingNullifiers_); - mapCoins.clear(); - mapSproutAnchors.clear(); - mapSaplingAnchors.clear(); - hashBestBlock_ = hashBlock; - hashBestSproutAnchor_ = hashSproutAnchor; - hashBestSaplingAnchor_ = hashSaplingAnchor; + if (!hashBlock.IsNull()) + hashBestBlock_ = hashBlock; + if (!hashSproutAnchor.IsNull()) + hashBestSproutAnchor_ = hashSproutAnchor; + if (!hashSaplingAnchor.IsNull()) + hashBestSaplingAnchor_ = hashSaplingAnchor; return true; } @@ -913,6 +923,105 @@ BOOST_AUTO_TEST_CASE(coins_coinbase_spends) } } +// This test is similar to the previous test +// except the emphasis is on testing the functionality of UpdateCoins +// random txs are created and UpdateCoins is used to update the cache stack +BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) +{ + // A simple map to track what we expect the cache stack to represent. + std::map result; + + // The cache stack. + CCoinsViewTest base; // A CCoinsViewTest at the bottom. + std::vector stack; // A stack of CCoinsViewCaches on top. + stack.push_back(new CCoinsViewCacheTest(&base)); // Start with one cache. + + // Track the txids we've used and whether they have been spent or not + std::map coinbaseids; + std::set alltxids; + + for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) { + { + CMutableTransaction tx; + tx.vin.resize(1); + tx.vout.resize(1); + tx.vout[0].nValue = i; //Keep txs unique + unsigned int height = insecure_rand(); + + // 1/10 times create a coinbase + if (insecure_rand() % 10 == 0 || coinbaseids.size() < 10) { + coinbaseids[tx.GetHash()] = tx.vout[0].nValue; + assert(CTransaction(tx).IsCoinBase()); + } + // 9/10 times create a regular tx + else { + uint256 prevouthash; + // equally likely to spend coinbase or non coinbase + std::set::iterator txIt = alltxids.lower_bound(GetRandHash()); + if (txIt == alltxids.end()) { + txIt = alltxids.begin(); + } + prevouthash = *txIt; + + // Construct the tx to spend the coins of prevouthash + tx.vin[0].prevout.hash = prevouthash; + tx.vin[0].prevout.n = 0; + + // Update the expected result of prevouthash to know these coins are spent + CCoins& oldcoins = result[prevouthash]; + oldcoins.Clear(); + + alltxids.erase(prevouthash); + coinbaseids.erase(prevouthash); + + assert(!CTransaction(tx).IsCoinBase()); + } + // Track this tx to possibly spend later + alltxids.insert(tx.GetHash()); + + // Update the expected result to know about the new output coins + CCoins &coins = result[tx.GetHash()]; + coins.FromTx(tx, height); + + UpdateCoins(tx, *(stack.back()), height); + } + + // Once every 1000 iterations and at the end, verify the full cache. + if (insecure_rand() % 1000 == 1 || i == NUM_SIMULATION_ITERATIONS - 1) { + for (std::map::iterator it = result.begin(); it != result.end(); it++) { + const CCoins* coins = stack.back()->AccessCoins(it->first); + if (coins) { + BOOST_CHECK(*coins == it->second); + } else { + BOOST_CHECK(it->second.IsPruned()); + } + } + } + + if (insecure_rand() % 100 == 0) { + // Every 100 iterations, change the cache stack. + if (stack.size() > 0 && insecure_rand() % 2 == 0) { + stack.back()->Flush(); + delete stack.back(); + stack.pop_back(); + } + if (stack.size() == 0 || (stack.size() < 4 && insecure_rand() % 2)) { + CCoinsView* tip = &base; + if (stack.size() > 0) { + tip = stack.back(); + } + stack.push_back(new CCoinsViewCacheTest(tip)); + } + } + } + + // Clean up the stack. + while (stack.size() > 0) { + delete stack.back(); + stack.pop_back(); + } +} + BOOST_AUTO_TEST_CASE(ccoins_serialization) { // Good example