Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/bench/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_executable(bench_bitcoin
checkblockindex.cpp
checkqueue.cpp
cluster_linearize.cpp
coinsviewcacheasync.cpp
connectblock.cpp
crypto_hash.cpp
descriptors.cpp
Expand Down
51 changes: 51 additions & 0 deletions src/bench/coinsviewcacheasync.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <bench/bench.h>
#include <bench/data/block413567.raw.h>
#include <coins.h>
#include <coinsviewcacheasync.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <serialize.h>
#include <streams.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <txdb.h>
#include <validation.h>

#include <cassert>
#include <ranges>
#include <utility>

static void CoinsViewCacheAsyncBenchmark(benchmark::Bench& bench)
{
CBlock block;
DataStream{benchmark::data::block413567} >> TX_WITH_WITNESS(block);
const auto testing_setup{MakeNoLogFileContext<const TestingSetup>(ChainType::MAIN, { .coins_db_in_memory = false })};
Chainstate& chainstate{testing_setup->m_node.chainman->ActiveChainstate()};
auto& coins_tip{WITH_LOCK(testing_setup->m_node.chainman->GetMutex(), return chainstate.CoinsTip();)};

for (const auto& tx : block.vtx | std::views::drop(1)) {
for (const auto& in : tx->vin) {
Coin coin{};
coin.out.nValue = 1;
coins_tip.EmplaceCoinInternalDANGER(COutPoint{in.prevout}, std::move(coin));
}
}
chainstate.ForceFlushStateToDisk();
CoinsViewCacheAsyncController controller{&coins_tip};

bench.run([&] {
auto view{controller.StartFetching(block)};
for (const auto& tx : block.vtx | std::views::drop(1)) {
for (const auto& in : tx->vin) {
const auto have{view->HaveCoin(in.prevout)};
assert(have);
}
}
});
}

BENCHMARK(CoinsViewCacheAsyncBenchmark, benchmark::PriorityLevel::HIGH);
24 changes: 23 additions & 1 deletion src/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ TRACEPOINT_SEMAPHORE(utxocache, spent);
TRACEPOINT_SEMAPHORE(utxocache, uncache);

std::optional<Coin> CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; }
std::optional<Coin> CCoinsView::PeekCoin(const COutPoint& outpoint) const { return GetCoin(outpoint); }
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
void CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock)
Expand All @@ -30,6 +31,7 @@ bool CCoinsView::HaveCoin(const COutPoint &outpoint) const

CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
std::optional<Coin> CCoinsViewBacked::GetCoin(const COutPoint& outpoint) const { return base->GetCoin(outpoint); }
std::optional<Coin> CCoinsViewBacked::PeekCoin(const COutPoint& outpoint) const { return base->PeekCoin(outpoint); }
bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
std::vector<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
Expand All @@ -38,6 +40,14 @@ void CCoinsViewBacked::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& h
std::unique_ptr<CCoinsViewCursor> CCoinsViewBacked::Cursor() const { return base->Cursor(); }
size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); }

std::optional<Coin> CCoinsViewCache::PeekCoin(const COutPoint& outpoint) const
{
if (auto it{cacheCoins.find(outpoint)}; it != cacheCoins.end()) {
return it->second.coin.IsSpent() ? std::nullopt : std::optional{it->second.coin};
}
return base->PeekCoin(outpoint);
}

CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) :
CCoinsViewBacked(baseIn), m_deterministic(deterministic),
cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &m_cache_coins_memory_resource)
Expand All @@ -49,10 +59,15 @@ size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) + cachedCoinsUsage;
}

std::optional<Coin> CCoinsViewCache::GetCoinFromBase(const COutPoint& outpoint) const
{
return base->GetCoin(outpoint);
}

CCoinsMap::iterator CCoinsViewCache::FetchCoin(const COutPoint &outpoint) const {
const auto [ret, inserted] = cacheCoins.try_emplace(outpoint);
if (inserted) {
if (auto coin{base->GetCoin(outpoint)}) {
if (auto coin{GetCoinFromBase(outpoint)}) {
ret->second.coin = std::move(*coin);
cachedCoinsUsage += ret->second.coin.DynamicMemoryUsage();
if (ret->second.coin.IsSpent()) { // TODO GetCoin cannot return spent coins
Expand Down Expand Up @@ -274,6 +289,13 @@ void CCoinsViewCache::Sync()
}
}

void CCoinsViewCache::Reset() noexcept
{
cacheCoins.clear();
cachedCoinsUsage = 0;
hashBlock.SetNull();
}

void CCoinsViewCache::Uncache(const COutPoint& hash)
{
CCoinsMap::iterator it = cacheCoins.find(hash);
Expand Down
18 changes: 15 additions & 3 deletions src/coins.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ class CCoinsView
//! Retrieve the Coin (unspent transaction output) for a given outpoint.
virtual std::optional<Coin> GetCoin(const COutPoint& outpoint) const;

//! Retrieve the Coin (unspent transaction output) for a given outpoint, without caching results.
//!
//! Unlike CCoinsViewCache::GetCoin(), this method does not populate intermediate CCoinsViewCache layers.
virtual std::optional<Coin> PeekCoin(const COutPoint& outpoint) const;

//! Just check whether a given outpoint is unspent.
virtual bool HaveCoin(const COutPoint &outpoint) const;

Expand Down Expand Up @@ -346,10 +351,11 @@ class CCoinsViewBacked : public CCoinsView
public:
CCoinsViewBacked(CCoinsView *viewIn);
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
std::optional<Coin> PeekCoin(const COutPoint& outpoint) const override;
bool HaveCoin(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const override;
std::vector<uint256> GetHeadBlocks() const override;
void SetBackend(CCoinsView &viewIn);
virtual void SetBackend(CCoinsView &viewIn);
void BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) override;
std::unique_ptr<CCoinsViewCursor> Cursor() const override;
size_t EstimateSize() const override;
Expand All @@ -376,6 +382,8 @@ class CCoinsViewCache : public CCoinsViewBacked
/* Cached dynamic memory usage for the inner Coin objects. */
mutable size_t cachedCoinsUsage{0};

/* Get the coin from base. Used for cache misses in FetchCoin. */
virtual std::optional<Coin> GetCoinFromBase(const COutPoint& outpoint) const;
public:
CCoinsViewCache(CCoinsView *baseIn, bool deterministic = false);

Expand All @@ -386,6 +394,7 @@ class CCoinsViewCache : public CCoinsViewBacked

// Standard CCoinsView methods
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
std::optional<Coin> PeekCoin(const COutPoint& outpoint) const override;
bool HaveCoin(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const override;
void SetBestBlock(const uint256 &hashBlock);
Expand Down Expand Up @@ -442,15 +451,18 @@ class CCoinsViewCache : public CCoinsViewBacked
* If will_reuse_cache is false, the cache will retain the same memory footprint
* after flushing and should be destroyed to deallocate.
*/
void Flush(bool will_reuse_cache = true);
virtual void Flush(bool will_reuse_cache = true);

/**
* Push the modifications applied to this cache to its base while retaining
* the contents of this cache (except for spent coins, which we erase).
* Failure to call this method or Flush() before destruction will cause the changes
* to be forgotten.
*/
void Sync();
virtual void Sync();

//! Wipe local state.
virtual void Reset() noexcept;

/**
* Removes the UTXO with the given outpoint from the cache, if it is
Expand Down
Loading
Loading