Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2012-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <cuckoocache.h>6#include <random.h>7#include <script/sigcache.h>8#include <test/util/random.h>9#include <test/util/setup_common.h>10#include <util/byte_units.h>12#include <boost/test/unit_test.hpp>14#include <deque>15#include <mutex>16#include <shared_mutex>17#include <thread>18#include <vector>20/** Test Suite for CuckooCache21*22* 1. All tests should have a deterministic result (using insecure rand23* with deterministic seeds)24* 2. Some test methods are templated to allow for easier testing25* against new versions / comparing26* 3. Results should be treated as a regression test, i.e., did the behavior27* change significantly from what was expected. This can be OK, depending on28* the nature of the change, but requires updating the tests to reflect the new29* expected behavior. For example improving the hit rate may cause some tests30* using BOOST_CHECK_CLOSE to fail.31*32*/33BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup);35/* Test that no values not inserted into the cache are read out of it.36*37* There are no repeats in the first 200000 m_rng.rand256() calls38*/39BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes)40{41SeedRandomForTest(SeedRand::ZEROS);42CuckooCache::cache<uint256, SignatureCacheHasher> cc{};43cc.setup_bytes(4_MiB);44for (int x = 0; x < 100000; ++x) {45cc.insert(m_rng.rand256());46}47for (int x = 0; x < 100000; ++x) {48BOOST_CHECK(!cc.contains(m_rng.rand256(), false));49}50};52struct HitRateTest : BasicTestingSetup {53/** This helper returns the hit rate when bytes*load worth of entries are54* inserted into a bytes sized cache55*/56template <typename Cache>57double test_cache(size_t bytes, double load)58{59SeedRandomForTest(SeedRand::ZEROS);60std::vector<uint256> hashes;61Cache set{};62set.setup_bytes(bytes);63uint32_t n_insert = static_cast<uint32_t>(load * (bytes / sizeof(uint256)));64hashes.resize(n_insert);65for (uint32_t i = 0; i < n_insert; ++i) {66uint32_t* ptr = (uint32_t*)hashes[i].begin();67for (uint8_t j = 0; j < 8; ++j)68*(ptr++) = m_rng.rand32();69}70/** We make a copy of the hashes because future optimizations of the71* cuckoocache may overwrite the inserted element, so the test is72* "future proofed".73*/74std::vector<uint256> hashes_insert_copy = hashes;75/** Do the insert */76for (const uint256& h : hashes_insert_copy)77set.insert(h);78/** Count the hits */79uint32_t count = 0;80for (const uint256& h : hashes)81count += set.contains(h, false);82double hit_rate = ((double)count) / ((double)n_insert);83return hit_rate;84}86/** The normalized hit rate for a given load.87*88* The semantics are a little confusing, so please see the below89* explanation.90*91* Examples:92*93* 1. at load 0.5, we expect a perfect hit rate, so we multiply by94* 1.095* 2. at load 2.0, we expect to see half the entries, so a perfect hit rate96* would be 0.5. Therefore, if we see a hit rate of 0.4, 0.4*2.0 = 0.8 is the97* normalized hit rate.98*99* This is basically the right semantics, but has a bit of a glitch depending on100* how you measure around load 1.0 as after load 1.0 your normalized hit rate101* becomes effectively perfect, ignoring freshness.102*/103static double normalize_hit_rate(double hits, double load)104{105return hits * std::max(load, 1.0);106}107}; // struct HitRateTest109/** Check the hit rate on loads ranging from 0.1 to 1.6 */110BOOST_FIXTURE_TEST_CASE(cuckoocache_hit_rate_ok, HitRateTest)111{112/** Arbitrarily selected Hit Rate threshold that happens to work for this test113* as a lower bound on performance.114*/115double HitRateThresh = 0.98;116for (double load = 0.1; load < 2; load *= 2) {117double hits = test_cache<CuckooCache::cache<uint256, SignatureCacheHasher>>(4_MiB, load);118BOOST_CHECK(normalize_hit_rate(hits, load) > HitRateThresh);119}120}123struct EraseTest : BasicTestingSetup {124/** This helper checks that erased elements are preferentially inserted onto and125* that the hit rate of "fresher" keys is reasonable*/126template <typename Cache>127void test_cache_erase(size_t bytes)128{129double load = 1;130SeedRandomForTest(SeedRand::ZEROS);131std::vector<uint256> hashes;132Cache set{};133set.setup_bytes(bytes);134uint32_t n_insert = static_cast<uint32_t>(load * (bytes / sizeof(uint256)));135hashes.resize(n_insert);136for (uint32_t i = 0; i < n_insert; ++i) {137uint32_t* ptr = (uint32_t*)hashes[i].begin();138for (uint8_t j = 0; j < 8; ++j)139*(ptr++) = m_rng.rand32();140}141/** We make a copy of the hashes because future optimizations of the142* cuckoocache may overwrite the inserted element, so the test is143* "future proofed".144*/145std::vector<uint256> hashes_insert_copy = hashes;147/** Insert the first half */148for (uint32_t i = 0; i < (n_insert / 2); ++i)149set.insert(hashes_insert_copy[i]);150/** Erase the first quarter */151for (uint32_t i = 0; i < (n_insert / 4); ++i)152BOOST_CHECK(set.contains(hashes[i], true));153/** Insert the second half */154for (uint32_t i = (n_insert / 2); i < n_insert; ++i)155set.insert(hashes_insert_copy[i]);157/** elements that we marked as erased but are still there */158size_t count_erased_but_contained = 0;159/** elements that we did not erase but are older */160size_t count_stale = 0;161/** elements that were most recently inserted */162size_t count_fresh = 0;164for (uint32_t i = 0; i < (n_insert / 4); ++i)165count_erased_but_contained += set.contains(hashes[i], false);166for (uint32_t i = (n_insert / 4); i < (n_insert / 2); ++i)167count_stale += set.contains(hashes[i], false);168for (uint32_t i = (n_insert / 2); i < n_insert; ++i)169count_fresh += set.contains(hashes[i], false);171double hit_rate_erased_but_contained = double(count_erased_but_contained) / (double(n_insert) / 4.0);172double hit_rate_stale = double(count_stale) / (double(n_insert) / 4.0);173double hit_rate_fresh = double(count_fresh) / (double(n_insert) / 2.0);175// Check that our hit_rate_fresh is perfect176BOOST_CHECK_EQUAL(hit_rate_fresh, 1.0);177// Check that we have a more than 2x better hit rate on stale elements than178// erased elements.179BOOST_CHECK(hit_rate_stale > 2 * hit_rate_erased_but_contained);180}181}; // struct EraseTest183BOOST_FIXTURE_TEST_CASE(cuckoocache_erase_ok, EraseTest)184{185test_cache_erase<CuckooCache::cache<uint256, SignatureCacheHasher>>(4_MiB);186}188struct EraseParallelTest : BasicTestingSetup {189template <typename Cache>190void test_cache_erase_parallel(size_t bytes)191{192double load = 1;193SeedRandomForTest(SeedRand::ZEROS);194std::vector<uint256> hashes;195Cache set{};196set.setup_bytes(bytes);197uint32_t n_insert = static_cast<uint32_t>(load * (bytes / sizeof(uint256)));198hashes.resize(n_insert);199for (uint32_t i = 0; i < n_insert; ++i) {200uint32_t* ptr = (uint32_t*)hashes[i].begin();201for (uint8_t j = 0; j < 8; ++j)202*(ptr++) = m_rng.rand32();203}204/** We make a copy of the hashes because future optimizations of the205* cuckoocache may overwrite the inserted element, so the test is206* "future proofed".207*/208std::vector<uint256> hashes_insert_copy = hashes;209std::shared_mutex mtx;211{212/** Grab lock to make sure we release inserts */213std::unique_lock<std::shared_mutex> l(mtx);214/** Insert the first half */215for (uint32_t i = 0; i < (n_insert / 2); ++i)216set.insert(hashes_insert_copy[i]);217}219/** Spin up 3 threads to run contains with erase.220*/221std::vector<std::thread> threads;222threads.reserve(3);223/** Erase the first quarter */224for (uint32_t x = 0; x < 3; ++x)225/** Each thread is emplaced with x copy-by-value226*/227threads.emplace_back([&, x] {228std::shared_lock<std::shared_mutex> l(mtx);229size_t ntodo = (n_insert/4)/3;230size_t start = ntodo*x;231size_t end = ntodo*(x+1);232for (uint32_t i = start; i < end; ++i) {233bool contains = set.contains(hashes[i], true);234assert(contains);235}236});238/** Wait for all threads to finish239*/240for (std::thread& t : threads)241t.join();242/** Grab lock to make sure we observe erases */243std::unique_lock<std::shared_mutex> l(mtx);244/** Insert the second half */245for (uint32_t i = (n_insert / 2); i < n_insert; ++i)246set.insert(hashes_insert_copy[i]);248/** elements that we marked erased but that are still there */249size_t count_erased_but_contained = 0;250/** elements that we did not erase but are older */
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.