mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-11 21:20:39 +02:00
test: add unit tests for CoinsViewOverlay::StartFetching
Co-authored-by: l0rinc <pap.lorinc@gmail.com>
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -37,10 +39,13 @@ CBlock CreateBlock() noexcept
|
||||
coinbase.vin.emplace_back();
|
||||
block.vtx.push_back(MakeTransactionRef(coinbase));
|
||||
|
||||
Txid prevhash{Txid::FromUint256(uint256{1})};
|
||||
|
||||
for (const auto i : std::views::iota(1, NUM_TXS)) {
|
||||
CMutableTransaction tx;
|
||||
Txid txid{Txid::FromUint256(uint256(i))};
|
||||
const Txid txid{i % 20 == 0 ? prevhash : Txid::FromUint256(uint256(i))};
|
||||
tx.vin.emplace_back(txid, 0);
|
||||
prevhash = tx.GetHash();
|
||||
block.vtx.push_back(MakeTransactionRef(tx));
|
||||
}
|
||||
|
||||
@@ -52,12 +57,16 @@ void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
|
||||
CCoinsViewCache cache{&view};
|
||||
cache.SetBestBlock(uint256::ONE);
|
||||
|
||||
std::unordered_set<Txid, SaltedTxidHasher> txids{};
|
||||
txids.reserve(block.vtx.size() - 1);
|
||||
for (const auto& tx : block.vtx | std::views::drop(1)) {
|
||||
for (const auto& in : tx->vin) {
|
||||
if (txids.contains(in.prevout.hash)) continue;
|
||||
Coin coin{};
|
||||
if (!spent) coin.out.nValue = 1;
|
||||
cache.EmplaceCoinInternalDANGER(COutPoint{in.prevout}, std::move(coin));
|
||||
}
|
||||
txids.emplace(tx->GetHash());
|
||||
}
|
||||
|
||||
cache.Flush();
|
||||
@@ -66,6 +75,8 @@ void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
|
||||
void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
|
||||
{
|
||||
uint32_t counter{0};
|
||||
std::unordered_set<Txid, SaltedTxidHasher> txids{};
|
||||
txids.reserve(block.vtx.size() - 1);
|
||||
|
||||
for (const auto& tx : block.vtx) {
|
||||
if (tx->IsCoinBase()) {
|
||||
@@ -76,9 +87,11 @@ void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
|
||||
const auto& first{cache.AccessCoin(outpoint)};
|
||||
const auto& second{cache.AccessCoin(outpoint)};
|
||||
BOOST_CHECK_EQUAL(&first, &second);
|
||||
++counter;
|
||||
BOOST_CHECK(cache.HaveCoinInCache(outpoint));
|
||||
const auto have{cache.HaveCoinInCache(outpoint)};
|
||||
BOOST_CHECK_NE(txids.contains(outpoint.hash), have);
|
||||
counter += have;
|
||||
}
|
||||
txids.emplace(tx->GetHash());
|
||||
}
|
||||
}
|
||||
BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);
|
||||
@@ -95,6 +108,7 @@ BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)
|
||||
PopulateView(block, db);
|
||||
CCoinsViewCache main_cache{&db};
|
||||
CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
const auto& outpoint{block.vtx[1]->vin[0].prevout};
|
||||
|
||||
BOOST_CHECK(view.HaveCoin(outpoint));
|
||||
@@ -122,6 +136,7 @@ BOOST_AUTO_TEST_CASE(fetch_inputs_from_cache)
|
||||
CCoinsViewCache main_cache{&db};
|
||||
PopulateView(block, main_cache);
|
||||
CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
CheckCache(block, view);
|
||||
|
||||
const auto& outpoint{block.vtx[1]->vin[0].prevout};
|
||||
@@ -142,6 +157,7 @@ BOOST_AUTO_TEST_CASE(fetch_no_double_spend)
|
||||
// Add all inputs as spent already in cache
|
||||
PopulateView(block, main_cache, /*spent=*/true);
|
||||
CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
for (const auto& tx : block.vtx) {
|
||||
for (const auto& in : tx->vin) {
|
||||
const auto& c{view.AccessCoin(in.prevout)};
|
||||
@@ -160,6 +176,7 @@ BOOST_AUTO_TEST_CASE(fetch_no_inputs)
|
||||
CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
|
||||
CCoinsViewCache main_cache{&db};
|
||||
CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
for (const auto& tx : block.vtx) {
|
||||
for (const auto& in : tx->vin) {
|
||||
const auto& c{view.AccessCoin(in.prevout)};
|
||||
@@ -171,5 +188,120 @@ BOOST_AUTO_TEST_CASE(fetch_no_inputs)
|
||||
BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
|
||||
}
|
||||
|
||||
// Access coins that are not block inputs
|
||||
BOOST_AUTO_TEST_CASE(access_non_input_coins)
|
||||
{
|
||||
CBlock block;
|
||||
CMutableTransaction coinbase;
|
||||
coinbase.vin.emplace_back();
|
||||
block.vtx.push_back(MakeTransactionRef(coinbase));
|
||||
CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
|
||||
CCoinsViewCache main_cache{&db};
|
||||
Coin coin{};
|
||||
coin.out.nValue = 1;
|
||||
const COutPoint outpoint{Txid::FromUint256(uint256::ZERO), 0};
|
||||
main_cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, std::move(coin));
|
||||
|
||||
CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
|
||||
// Non-input fallback hit.
|
||||
BOOST_CHECK(!view.AccessCoin(outpoint).IsSpent());
|
||||
|
||||
// Non-input fallback miss.
|
||||
const COutPoint missing_outpoint{Txid::FromUint256(uint256::ONE), 0};
|
||||
BOOST_CHECK(view.AccessCoin(missing_outpoint).IsSpent());
|
||||
BOOST_CHECK(!view.HaveCoinInCache(missing_outpoint));
|
||||
}
|
||||
|
||||
// Access a fetched input out of order (i.e. not the next one in m_inputs).
|
||||
// FetchCoinFromBase must fall back to base->PeekCoin, and the coin must still
|
||||
// be inserted into the cache.
|
||||
BOOST_AUTO_TEST_CASE(fetch_out_of_order_input_uses_normal_lookup)
|
||||
{
|
||||
const auto block{CreateBlock()};
|
||||
CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
|
||||
CCoinsViewCache main_cache{&db};
|
||||
PopulateView(block, main_cache);
|
||||
|
||||
std::vector<COutPoint> fetched_inputs;
|
||||
std::unordered_set<Txid, SaltedTxidHasher> txids;
|
||||
txids.reserve(block.vtx.size() - 1);
|
||||
for (const auto& tx : block.vtx | std::views::drop(1)) {
|
||||
for (const auto& input : tx->vin) {
|
||||
if (!txids.contains(input.prevout.hash)) fetched_inputs.push_back(input.prevout);
|
||||
}
|
||||
txids.emplace(tx->GetHash());
|
||||
}
|
||||
BOOST_REQUIRE_GE(fetched_inputs.size(), 2U);
|
||||
|
||||
CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
|
||||
const auto& out_of_order_input{fetched_inputs[1]};
|
||||
BOOST_CHECK(!view.HaveCoinInCache(out_of_order_input));
|
||||
BOOST_CHECK(!view.AccessCoin(out_of_order_input).IsSpent());
|
||||
BOOST_CHECK(view.HaveCoinInCache(out_of_order_input));
|
||||
|
||||
CheckCache(block, view);
|
||||
}
|
||||
|
||||
// The ResetGuard returned by StartFetching must clear all per-block state when
|
||||
// it goes out of scope, so the overlay can be reused for a subsequent block.
|
||||
// Flush must also clear all per-block state to be reused.
|
||||
BOOST_AUTO_TEST_CASE(fetch_state_is_reusable_after_teardown)
|
||||
{
|
||||
const auto block{CreateBlock()};
|
||||
CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
|
||||
CCoinsViewCache main_cache{&db};
|
||||
PopulateView(block, main_cache);
|
||||
CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
|
||||
|
||||
for (const bool use_flush : {false, true, false}) {
|
||||
{
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
CheckCache(block, view);
|
||||
BOOST_CHECK_GT(view.GetCacheSize(), 0U);
|
||||
if (use_flush) {
|
||||
view.SetBestBlock(uint256::ONE);
|
||||
view.Flush();
|
||||
}
|
||||
}
|
||||
BOOST_CHECK_EQUAL(view.GetCacheSize(), 0U);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests_noworkers)
|
||||
|
||||
// Test that disabled input fetching falls back to normal cache lookups via base->PeekCoin.
|
||||
BOOST_AUTO_TEST_CASE(fetch_unstarted_thread_pool)
|
||||
{
|
||||
const auto block{CreateBlock()};
|
||||
CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
|
||||
CCoinsViewCache main_cache{&db};
|
||||
PopulateView(block, main_cache);
|
||||
auto thread_pool{std::make_shared<ThreadPool>("fetch_none")};
|
||||
CoinsViewOverlay view{&main_cache, thread_pool};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
CheckCache(block, view);
|
||||
}
|
||||
|
||||
// Test that an interrupted thread pool falls back to normal cache lookups via base->PeekCoin.
|
||||
BOOST_AUTO_TEST_CASE(fetch_interrupted_thread_pool_uses_normal_lookup)
|
||||
{
|
||||
const auto block{CreateBlock()};
|
||||
CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
|
||||
CCoinsViewCache main_cache{&db};
|
||||
PopulateView(block, main_cache);
|
||||
|
||||
auto thread_pool{std::make_shared<ThreadPool>("fetch_intr")};
|
||||
thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
|
||||
thread_pool->Interrupt();
|
||||
CoinsViewOverlay view{&main_cache, thread_pool};
|
||||
const auto reset_guard{view.StartFetching(block)};
|
||||
CheckCache(block, view);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
Reference in New Issue
Block a user