diff --git a/src/main.cpp b/src/main.cpp index fefeabeb6..7477d08b1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1277,6 +1277,9 @@ int GetSpendHeight(const CCoinsViewCache& inputs) return pindexPrev->nHeight + 1; } +static mrumap cacheCheck(2 * MAX_BLOCK_SIZE / CTransaction().GetSerializeSize(SER_NETWORK, PROTOCOL_VERSION)); +static boost::mutex cs_cacheCheck; + namespace Consensus { bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight) { @@ -1331,6 +1334,17 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi { if (!tx.IsCoinBase()) { + if (fScriptChecks) { + boost::unique_lock lock(cs_cacheCheck); + mrumap::const_iterator iter = cacheCheck.find(tx.GetHash()); + if (iter != cacheCheck.end()) { + // The following test relies on the fact that all script validation flags are softforks (i.e. an extra bit set cannot cause a false result to become true). + if ((iter->second & flags) == flags) { + return true; + } + } + } + if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs))) return false; @@ -1381,6 +1395,11 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi } } + if (cacheStore && fScriptChecks && pvChecks == NULL) { + boost::unique_lock lock(cs_cacheCheck); + cacheCheck.insert(tx.GetHash(), flags); + } + return true; } @@ -2101,6 +2120,13 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock * BOOST_FOREACH(const CTransaction &tx, pblock->vtx) { SyncWithWallets(tx, pblock); } + // Erase block's transactions from the validation cache + { + boost::unique_lock lock(cs_cacheCheck); + BOOST_FOREACH(const CTransaction &tx, pblock->vtx) { + cacheCheck.erase(tx.GetHash()); + } + } int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001); diff --git a/src/mruset.h b/src/mruset.h index 398aa173b..9dff5694b 100644 --- a/src/mruset.h +++ b/src/mruset.h @@ -9,6 +9,10 @@ #include #include +#include +#include +#include + /** STL-like set container that only keeps the most recent N elements. */ template class mruset @@ -62,4 +66,60 @@ public: size_type max_size() const { return nMaxSize; } }; +/** STL-like map container that only keeps the most recent N elements. */ +template +class mrumap +{ +private: + struct key_extractor { + typedef K result_type; + const result_type& operator()(const std::pair& e) const { return e.first; } + result_type& operator()(std::pair* e) const { return e->first; } + }; + + typedef boost::multi_index_container< + std::pair, + boost::multi_index::indexed_by< + boost::multi_index::sequenced<>, + boost::multi_index::ordered_unique + > + > map_type; + +public: + typedef K key_type; + typedef std::pair value_type; + typedef typename map_type::iterator iterator; + typedef typename map_type::const_iterator const_iterator; + typedef typename map_type::size_type size_type; + +protected: + map_type m_; + size_type max_size_; + +public: + mrumap(size_type max_size_in = 1) { clear(max_size_in); } + iterator begin() { return m_.begin(); } + iterator end() { return m_.end(); } + const_iterator begin() const { return m_.begin(); } + const_iterator end() const { return m_.end(); } + size_type size() const { return m_.size(); } + bool empty() const { return m_.empty(); } + iterator find(const key_type& key) { return m_.template project<0>(boost::get<1>(m_).find(key)); } + const_iterator find(const key_type& key) const { return m_.template project<0>(boost::get<1>(m_).find(key)); } + size_type count(const key_type& key) const { return boost::get<1>(m_).count(key); } + void clear(size_type max_size_in) { m_.clear(); max_size_ = max_size_in; } + std::pair insert(const K& key, const V& value) + { + std::pair elem(key, value); + std::pair p = m_.push_front(elem); + if (p.second && m_.size() > max_size_) { + m_.pop_back(); + } + return p; + } + void erase(iterator it) { m_.erase(it); } + void erase(const key_type& k) { boost::get<1>(m_).erase(k); } + size_type max_size() const { return max_size_; } +}; + #endif // BITCOIN_MRUSET_H diff --git a/src/test/mruset_tests.cpp b/src/test/mruset_tests.cpp index 2b68f8899..3c0668916 100644 --- a/src/test/mruset_tests.cpp +++ b/src/test/mruset_tests.cpp @@ -78,4 +78,68 @@ BOOST_AUTO_TEST_CASE(mruset_test) } } +BOOST_AUTO_TEST_CASE(mrumap_test) +{ + // The mrumap being tested. + mrumap mru(5000); + + // Run the test 10 times. + for (int test = 0; test < 10; test++) { + // Reset mru. + mru.clear(5000); + + // A deque + set to simulate the mruset. + std::deque rep; + std::map all; + + // Insert 10000 random integers below 15000. + for (int j=0; j<10000; j++) { + int add = GetRandInt(15000); + char val = (char)GetRandInt(256); + mru.insert(add, val); + + // Add the number to rep/all as well. + if (all.count(add) == 0) { + all.insert(std::make_pair(add, val)); + rep.push_back(add); + if (all.size() == 5001) { + all.erase(rep.front()); + rep.pop_front(); + } + } + + if (GetRandInt(5) == 0) { + // With 20% chance: remove an item + int pos = GetRandInt(rep.size()); + std::deque::iterator it = rep.begin(); + while (pos--) { it++; } + int delval = *it; + mru.erase(delval); + all.erase(delval); + rep.erase(it); + } + + // Do a full comparison between mru and the simulated mru every 1000 and every 5001 elements. + if (j % 1000 == 0 || j % 5001 == 0) { + // Check that all elements that should be in there, are in there. + BOOST_FOREACH(int x, rep) { + BOOST_CHECK(mru.count(x)); + BOOST_CHECK(mru.find(x)->second == all[x]); + } + + // Check that all elements that are in there, should be in there. + for (mrumap::iterator it = mru.begin(); it != mru.end(); it++) { + BOOST_CHECK(all.count(it->first)); + BOOST_CHECK(all[it->first] == it->second); + } + + for (int t = 0; t < 10; t++) { + int r = GetRandInt(15000); + BOOST_CHECK(all.count(r) == mru.count(r)); + } + } + } + } +} + BOOST_AUTO_TEST_SUITE_END()