refactor: make CCoinsView a purely virtual abstract base class #34124

pull l0rinc wants to merge 8 commits into bitcoin:master from l0rinc:l0rinc/pure-virtual-CCoinsView changing 15 files +84 −90
  1. l0rinc commented at 8:47 am on December 20, 2025: contributor

    Problem

    CCoinsView serves as the interface for the coins database layer, but historically had a dual nature that caused confusion:

    • It provided default noop implementations (returning std::nullopt, uint256(), false, nullptr) instead of being pure virtual
    • Callers could instantiate a bare CCoinsView and get silent no-op behavior
    • Mixing the interface definition with a built-in dummy implementation blurred the abstraction boundary

    Context

    This is part of ongoing coins caching cleanup efforts such as #34132, #33866, #33018, and #33512.

    Fix

    This PR makes CCoinsView a pure abstract interface and introduces an explicit noop implementation, in tiny incremental steps to ease review:

    • Add CCoinsViewEmpty as an explicit noop coins view for tests, benchmarks, and dummy backends
    • Replace all direct CCoinsView instantiations with CCoinsViewEmpty
    • Incrementally make all CCoinsView methods pure virtual (GetCoin, GetBestBlock, BatchWrite, EstimateSize, Cursor, GetHeadBlocks, HaveCoin)
    • Remove the legacy default implementations from coins.cpp
    • Update the coins view fuzzer to switch between a real database-backed view and CCoinsViewEmpty
  2. DrahtBot added the label Refactoring on Dec 20, 2025
  3. DrahtBot commented at 8:47 am on December 20, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34124.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK andrewtoth, ajtowns, sipa

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34165 (coins: don’t mutate main cache when connecting block by andrewtoth)
    • #34125 (refactor: reuse should_empty for chainstate flush condition by l0rinc)
    • #33866 (refactor: Let CCoinsViewCache::BatchWrite return void by sedited)
    • #32840 (test: Enhance GetTxSigOpCost tests for coinbase transactions by average-gary)
    • #31132 (validation: fetch block inputs on parallel threads 3x faster IBD by andrewtoth)
    • #29060 (Policy: Report debug message why inputs are non standard by ismaelsadeeq)
    • #21283 (Implement BIP 370 PSBTv2 by achow101)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. andrewtoth commented at 7:02 pm on December 20, 2025: contributor

    Concept ACK

    Makes sense to make an empty view explicit, and having the base class be purely virtual.

  5. l0rinc renamed this:
    refactor: make `CCoinsView` a purely abstract base class
    refactor: make `CCoinsView` a purely virtual abstract base class
    on Dec 20, 2025
  6. in src/coins.cpp:20 in 38ae4082a1 outdated
    12@@ -13,17 +13,6 @@ TRACEPOINT_SEMAPHORE(utxocache, add);
    13 TRACEPOINT_SEMAPHORE(utxocache, spent);
    14 TRACEPOINT_SEMAPHORE(utxocache, uncache);
    15 
    16-std::optional<Coin> CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; }
    17-uint256 CCoinsView::GetBestBlock() const { return uint256(); }
    18-std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
    19-bool CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashBlock) { return false; }
    20-std::unique_ptr<CCoinsViewCursor> CCoinsView::Cursor() const { return nullptr; }
    


    ajtowns commented at 4:18 am on December 22, 2025:
    Don’t see any value in deleting these in a separate commit from making them pure virtual.

    l0rinc commented at 9:19 pm on December 22, 2025:
    Indeed, moved each deletion to the commit that made the method pure, thanks. Also made the empty class a singleton which simplified all nested usages significantly. And inlined the trivial implementation of *Backing to the header to reduce the clutter in the cpp a bit more. https://github.com/bitcoin/bitcoin/compare/38ae4082a13b2e6dd07e84d228c9e58d3df27b11..d5c77c71428b667b9c9f1a8eb84a75d5874b85af
  7. in src/coins.cpp:25 in 38ae4082a1 outdated
    20-std::unique_ptr<CCoinsViewCursor> CCoinsView::Cursor() const { return nullptr; }
    21-
    22-bool CCoinsView::HaveCoin(const COutPoint &outpoint) const
    23-{
    24-    return GetCoin(outpoint).has_value();
    25-}
    


    ajtowns commented at 4:19 am on December 22, 2025:
    I could see keeping this one; it seems like a reasonable default?

    l0rinc commented at 9:19 pm on December 22, 2025:

    We need this method because of the specialized database access which doesn’t need to deserialize the read value:

    Otherwise we could of course replace it with !!GetCoin(outpoint):

    We’re not even really using the former in production code anyway (see #34132), we could probably easily inline the !!GetCoin(outpoint) to the few remaining call sites instead. Regardless, the current change should simplify usage and we can investigate that later.


    Also, the underlying problem is that having a separate CCoinsView makes adding defaults weird, there’s no logical default behavior. But we could inline it into CCoinsViewBacked (and delete CCoinsView and rename CCoinsViewBacked back to CCoinsView) and use that as the base for the other caches, with the default implementation simply delegating to base, something like:

      0diff --git a/src/coins.cpp b/src/coins.cpp
      1index 9bdcb42f45..4f66ed447b 100644
      2--- a/src/coins.cpp
      3+++ b/src/coins.cpp
      4@@ -14,7 +14,7 @@ TRACEPOINT_SEMAPHORE(utxocache, spent);
      5 TRACEPOINT_SEMAPHORE(utxocache, uncache);
      6 
      7 CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) :
      8-    CCoinsViewBacked(baseIn), m_deterministic(deterministic),
      9+    CCoinsView(baseIn), m_deterministic(deterministic),
     10     cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &m_cache_coins_memory_resource)
     11 {
     12     m_sentinel.second.SelfRef(m_sentinel);
     13@@ -362,10 +362,10 @@ static ReturnType ExecuteBackedWrapper(Func func, const std::vector<std::functio
     14 
     15 std::optional<Coin> CCoinsViewErrorCatcher::GetCoin(const COutPoint& outpoint) const
     16 {
     17-    return ExecuteBackedWrapper<std::optional<Coin>>([&]() { return CCoinsViewBacked::GetCoin(outpoint); }, m_err_callbacks);
     18+    return ExecuteBackedWrapper<std::optional<Coin>>([&]() { return CCoinsView::GetCoin(outpoint); }, m_err_callbacks);
     19 }
     20 
     21 bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint& outpoint) const
     22 {
     23-    return ExecuteBackedWrapper<bool>([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks);
     24+    return ExecuteBackedWrapper<bool>([&]() { return CCoinsView::HaveCoin(outpoint); }, m_err_callbacks);
     25 }
     26diff --git a/src/coins.h b/src/coins.h
     27index a64f30376d..7e8120f710 100644
     28--- a/src/coins.h
     29+++ b/src/coins.h
     30@@ -303,37 +303,46 @@ private:
     31     bool m_will_erase;
     32 };
     33 
     34-/** Pure abstract view on the open txout dataset. */
     35+/** Coins view backed by another CCoinsView. */
     36 class CCoinsView
     37 {
     38+protected:
     39+    CCoinsView* base{nullptr};
     40+
     41+    CCoinsView() = default;
     42+
     43 public:
     44+    explicit CCoinsView(CCoinsView* viewIn) : base(Assert(viewIn)) {}
     45+
     46     //! As we use CCoinsViews polymorphically, have a virtual destructor
     47     virtual ~CCoinsView() = default;
     48 
     49+    void SetBackend(CCoinsView& viewIn) { base = &viewIn; }
     50+
     51     //! Retrieve the Coin (unspent transaction output) for a given outpoint.
     52-    virtual std::optional<Coin> GetCoin(const COutPoint& outpoint) const = 0;
     53+    virtual std::optional<Coin> GetCoin(const COutPoint& outpoint) const { return base->GetCoin(outpoint); }
     54 
     55     //! Just check whether a given outpoint is unspent.
     56-    virtual bool HaveCoin(const COutPoint& outpoint) const = 0;
     57+    virtual bool HaveCoin(const COutPoint& outpoint) const { return base->HaveCoin(outpoint); }
     58 
     59     //! Retrieve the block hash whose state this CCoinsView currently represents
     60-    virtual uint256 GetBestBlock() const = 0;
     61+    virtual uint256 GetBestBlock() const { return base->GetBestBlock(); }
     62 
     63     //! Retrieve the range of blocks that may have been only partially written.
     64     //! If the database is in a consistent state, the result is the empty vector.
     65     //! Otherwise, a two-element vector is returned consisting of the new and
     66     //! the old block hash, in that order.
     67-    virtual std::vector<uint256> GetHeadBlocks() const = 0;
     68+    virtual std::vector<uint256> GetHeadBlocks() const { return base->GetHeadBlocks(); }
     69 
     70     //! Do a bulk modification (multiple Coin changes + BestBlock change).
     71     //! The passed cursor is used to iterate through the coins.
     72-    virtual bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) = 0;
     73+    virtual bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) { return base->BatchWrite(cursor, hashBlock); }
     74 
     75     //! Get a cursor to iterate over the whole state
     76-    virtual std::unique_ptr<CCoinsViewCursor> Cursor() const = 0;
     77+    virtual std::unique_ptr<CCoinsViewCursor> Cursor() const { return base->Cursor(); }
     78 
     79     //! Estimate database size
     80-    virtual size_t EstimateSize() const = 0;
     81+    virtual size_t EstimateSize() const { return base->EstimateSize(); }
     82 };
     83 
     84 /** Noop coins view. */
     85@@ -361,28 +370,8 @@ public:
     86     size_t EstimateSize() const override { return 0; }
     87 };
     88 
     89-/** CCoinsView backed by another CCoinsView */
     90-class CCoinsViewBacked : public CCoinsView
     91-{
     92-protected:
     93-    CCoinsView* base;
     94-
     95-public:
     96-    explicit CCoinsViewBacked(CCoinsView* viewIn) : base{viewIn} {}
     97-
     98-    void SetBackend(CCoinsView& viewIn) { base = &viewIn; }
     99-
    100-    std::optional<Coin> GetCoin(const COutPoint& outpoint) const override { return base->GetCoin(outpoint); }
    101-    bool HaveCoin(const COutPoint& outpoint) const override { return base->HaveCoin(outpoint); }
    102-    uint256 GetBestBlock() const override { return base->GetBestBlock(); }
    103-    std::vector<uint256> GetHeadBlocks() const override { return base->GetHeadBlocks(); }
    104-    bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) override { return base->BatchWrite(cursor, hashBlock); }
    105-    std::unique_ptr<CCoinsViewCursor> Cursor() const override { return base->Cursor(); }
    106-    size_t EstimateSize() const override { return base->EstimateSize(); }
    107-};
    108-
    109 /** CCoinsView that adds a memory cache for transactions to another CCoinsView */
    110-class CCoinsViewCache : public CCoinsViewBacked
    111+class CCoinsViewCache : public CCoinsView
    112 {
    113 private:
    114     const bool m_deterministic;
    115@@ -534,10 +523,10 @@ const Coin& AccessByTxid(const CCoinsViewCache& cache, const Txid& txid);
    116  *
    117  * Writes do not need similar protection, as failure to write is handled by the caller.
    118 */
    119-class CCoinsViewErrorCatcher final : public CCoinsViewBacked
    120+class CCoinsViewErrorCatcher final : public CCoinsView
    121 {
    122 public:
    123-    explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsViewBacked(view) {}
    124+    explicit CCoinsViewErrorCatcher(CCoinsView* view) : CCoinsView(view) {}
    125 
    126     void AddReadErrCallback(std::function<void()> f) {
    127         m_err_callbacks.emplace_back(std::move(f));
    128diff --git a/src/txmempool.cpp b/src/txmempool.cpp
    129index 4dc692bfd8..3e47428e8e 100644
    130--- a/src/txmempool.cpp
    131+++ b/src/txmempool.cpp
    132@@ -720,7 +720,7 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
    133     return true;
    134 }
    135 
    136-CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
    137+CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView* baseIn, const CTxMemPool& mempoolIn) : CCoinsView(baseIn), mempool(mempoolIn) { }
    138 
    139 std::optional<Coin> CCoinsViewMemPool::GetCoin(const COutPoint& outpoint) const
    140 {
    141diff --git a/src/txmempool.h b/src/txmempool.h
    142index 166a024823..4c24766c77 100644
    143--- a/src/txmempool.h
    144+++ b/src/txmempool.h
    145@@ -778,7 +778,7 @@ public:
    146  * signrawtransactionwithkey and signrawtransactionwithwallet,
    147  * as long as the conflicting transaction is not yet confirmed.
    148  */
    149-class CCoinsViewMemPool : public CCoinsViewBacked
    150+class CCoinsViewMemPool : public CCoinsView
    151 {
    152     /**
    153     * Coins made available by transactions being validated. Tracking these allows for package
    

    This would introduce a sensible default as the base class behavior (always delegating) and we’d instantiate it with the empty sigleton, which would always do nothin’. I haven’t included this yet, we can discuss if it’s a good idea.

  8. ajtowns commented at 4:23 am on December 22, 2025: contributor

    Concept ACK, this seemed better separated as an interface and a dummy implementation to me too.

    I think squashing all the commits after the first into a single commit would be better though.

  9. l0rinc force-pushed on Dec 22, 2025
  10. l0rinc force-pushed on Dec 28, 2025
  11. l0rinc commented at 11:04 am on December 28, 2025: contributor
    This change also revealed a potential UB in MemPoolAccept member init order. Since class members are constructed in declaration order (not initializer-list order), calls to a virtual method (having m_view depend on m_dummy but declared before it) can result in a virtual call through an uninitialized vptr and crash. Since the constructor didn’t dereference m_dummy during m_view construction, the latent ordering hazard stayed dormant. This cleanup accidentally fixed it, but to make it explicit, I have extracted it to the first commit as a temporary fix.
  12. in src/validation.cpp:729 in 436765c23a outdated
    721@@ -722,6 +722,11 @@ class MemPoolAccept
    722     }
    723 
    724 private:
    725+    /** When m_view is connected to m_dummy, it can no longer look up coins from the mempool or UTXO set (meaning no disk
    726+     * operations happen), but can still return coins it accessed previously. Useful for keeping track of which coins
    727+     * were pulled from disk. */
    728+    CCoinsView m_dummy;
    729+
    


    andrewtoth commented at 1:33 am on December 29, 2025:

    In commit 436765c23a009c18a2a8e29e8b9534924e80d1d0, it looks like you forgot to include the removal diff.

    Edit: It’s doubly removed in 148a0a94c65425b7ea9b6cc667502167b8c1c7a0, but that second removal should be here.

  13. in src/coins.h:406 in 784769b5b1 outdated
    402@@ -378,6 +403,7 @@ class CCoinsViewCache : public CCoinsViewBacked
    403 
    404 public:
    405     CCoinsViewCache(CCoinsView *baseIn, bool deterministic = false);
    406+    CCoinsViewCache() : CCoinsViewCache{&CCoinsViewEmpty::Get()} {}
    


    andrewtoth commented at 5:39 am on December 29, 2025:
    Should this constructor be on CCoinsViewBacked instead?

    l0rinc commented at 8:21 pm on December 29, 2025:
    How so? We have to init m_deterministic as well…
  14. validation: fix `MemPoolAccept` member init order
    `MemPoolAccept` constructs `m_view` with a backend pointer to a `CCoinsView`.
    Since class members are constructed in declaration order (not initializer-list order), calls to a virtual method (having `m_view` declared before `m_dummy`) can result in a virtual call through an uninitialized vptr and crash.
    
    Without the move:
    ```
    The following tests FAILED:
            112 - txdownload_tests (SEGFAULT)
            115 - txpackage_tests (SEGFAULT)
            118 - txvalidation_tests (SEGFAULT)
            119 - txvalidationcache_tests (SEGFAULT)
            127 - validation_block_tests (SEGFAULT)
            146 - wallet_tests (SEGFAULT)
    ```
    
    Move `m_dummy` before `m_view` so it is constructed first.
    In a follow-up `m_dummy` will be completely removed, this commit was meant to demonstrate why that was necessarry.
    5f5ff97980
  15. in src/coins.cpp:20 in 784769b5b1 outdated
    36-
    37 CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) :
    38     CCoinsViewBacked(baseIn), m_deterministic(deterministic),
    39     cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &m_cache_coins_memory_resource)
    40 {
    41+    (void)base->GetBestBlock(); // Sanity check validity of base
    


    andrewtoth commented at 5:46 am on December 29, 2025:
    I’m not sure about this sanity check. We’re checking that a call to this method will work? Is this better than an assertion that baseIn is non-null? Why not refactor baseIn to be a reference?

    l0rinc commented at 12:07 pm on December 29, 2025:
    it’s not null, it will give illegal access since it points to a random address. Try reverting and running any of the mentioned tests from the commit message.

    andrewtoth commented at 2:58 pm on December 29, 2025:

    Ok, but if it’s a pointer it is legal for it to be a nullptr. So we should check first if it is not nullptr. Also, instead of accessing a method, we could do a dynamic cast to CCoinsView* and assert the result.

    0    assert(!base || dynamic_cast<CCoinsView*>(base));
    

    l0rinc commented at 8:20 pm on December 29, 2025:

    assert(!base || dynamic_cast<CCoinsView*>(base)); doesn’t trigger the failure.

    Reverting m_dummy (which I did accidentally in the first commit already):

     0diff --git a/src/validation.cpp b/src/validation.cpp
     1--- a/src/validation.cpp	(revision 5f5ff9798098a7cf9c5311dc54c615245fec1a3b)
     2+++ b/src/validation.cpp	(date 1767039346685)
     3@@ -722,11 +722,6 @@
     4     }
     5 
     6 private:
     7-    /** When m_view is connected to m_dummy, it can no longer look up coins from the mempool or UTXO set (meaning no disk
     8-     * operations happen), but can still return coins it accessed previously. Useful for keeping track of which coins
     9-     * were pulled from disk. */
    10-    CCoinsView m_dummy;
    11-
    12     CTxMemPool& m_pool;
    13 
    14     /** Holds a cached view of available coins from the UTXO set, mempool, and artificial temporary coins (to enable package validation).
    15@@ -746,6 +741,10 @@
    16     /** When m_view is connected to m_viewmempool as its backend, it can pull coins from the mempool and from the UTXO
    17      * set. This is also where temporary coins are stored. */
    18     CCoinsViewMemPool m_viewmempool;
    19+    /** When m_view is connected to m_dummy, it can no longer look up coins from the mempool or UTXO set (meaning no disk
    20+     * operations happen), but can still return coins it accessed previously. Useful for keeping track of which coins
    21+     * were pulled from disk. */
    22+    CCoinsView m_dummy;
    23 
    24     Chainstate& m_active_chainstate;
    

    And running:

    cmake -B build -DCMAKE_BUILD_TYPE=Debug && cmake –build build && ./build/bin/test_bitcoin –run_test=wallet_tests

    fails correctly with:

    Running 13 test cases… ******** errors disabling the alternate stack: #error:22 Invalid argument unknown location:0: fatal error: in “wallet_tests/CreateWallet”: memory access violation at address: 0x5f4d1884: invalid permissions wallet/test/wallet_tests.cpp:622: last checkpoint

    Moved the proper removal to the first commit: https://github.com/bitcoin/bitcoin/compare/784769b5b1a8465d1a09ec489a303c4616d11896..a69ea84b83d22c66ddeeba6d27b761036d6fec40


    andrewtoth commented at 1:08 am on December 30, 2025:
    Hmm dynamic casting uninitialized memory should be UB as well, but I guess it doesn’t trigger a memory access violation. But, we still need to check that base is not nullptr before accessing, otherwise passing nullptr would trigger undefined behavior.

    l0rinc commented at 7:21 am on December 30, 2025:
    i don’t think we support nullptr currently - it’s why we have a default implementation with dummy values.

    andrewtoth commented at 2:35 pm on December 30, 2025:
    The API supports it. I use it in #31132. If we want to disallow it then we should change the parameter to a reference.

    sipa commented at 3:31 pm on December 30, 2025:

    As I understand it, this is sanity testing through triggering UB in the case the bug is present. I don’t think that is ideal, as the compiler is allowed to assume it won’t happen and optimize it away. This isn’t too far fetched - if it could be inferred that (all implementations of) CCoinsView::GetBestBlock are side-effect free, then the statement (void)base->GetBestBlock(); could just be optimized away.

    Testing for bugs due to initialization order seems more like a job for UBSan. Unfortunately, it seems none of the current fuzz tests (with UBSan enabled) trigger with the following patch (on master):

     0diff --git a/src/coins.cpp b/src/coins.cpp
     1index 14381ecb0aa..c7206c22554 100644
     2--- a/src/coins.cpp
     3+++ b/src/coins.cpp
     4@@ -38,6 +38,7 @@ CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) :
     5     CCoinsViewBacked(baseIn), m_deterministic(deterministic),
     6     cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &m_cache_coins_memory_resource)
     7 {
     8+    assert(!base || dynamic_cast<CCoinsView*>(base));
     9     m_sentinel.second.SelfRef(m_sentinel);
    10 }
    

    I’m not objecting to this change, but I think it would be better if we could test for bugs of this class in a more systematic way.


    l0rinc commented at 10:07 pm on December 30, 2025:

    The API supports it. I use it in #31132. If we want to disallow it then we should change the parameter to a reference.

    My understanding is that it was stored as a pointer since references cannot be reallocated and we need to call SetBackend currently in a few places (which takes a reference, also proving that nullptr was never the goal). We can change it to std::reference_wrapper<CCoinsView> base but it would trigger a huge changeset if it percolates to getting rid of all pointers in the constructors. But I don’t mind adding an Assert to forbid that at least during runtime and fix the constructor and call sites after this PR is merged:

     0class CCoinsViewBacked : public CCoinsView
     1{
     2protected:
     3    std::reference_wrapper<CCoinsView> base;
     4
     5public:
     6    explicit CCoinsViewBacked(CCoinsView* viewIn) : base{*Assert(viewIn)} {} // TODO change to reference
     7
     8    void SetBackend(CCoinsView& viewIn) { base = std::ref(viewIn); }
     9
    10    std::optional<Coin> GetCoin(const COutPoint& outpoint) const override { return base.get().GetCoin(outpoint); }
    11    bool HaveCoin(const COutPoint& outpoint) const override { return base.get().HaveCoin(outpoint); }
    12    uint256 GetBestBlock() const override { return base.get().GetBestBlock(); }
    13    std::vector<uint256> GetHeadBlocks() const override { return base.get().GetHeadBlocks(); }
    14    bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) override { return base.get().BatchWrite(cursor, hashBlock); }
    15    std::unique_ptr<CCoinsViewCursor> Cursor() const override { return base.get().Cursor(); }
    16    size_t EstimateSize() const override { return base.get().EstimateSize(); }
    17};
    

    andrewtoth commented at 10:09 pm on December 30, 2025:
    Sorry if I was unclear. base should remain a pointer. viewIn should be a reference in the constructor, as it is in SetBackend.

    l0rinc commented at 10:17 pm on December 30, 2025:

    viewIn should be a reference in the constructor

    yeah, that’s what I meant by:

    it would trigger a huge changeset if it percolates to getting rid of all pointers in the constructors

    It triggered changes in 22 files so far - I have stopped refactoring after a while. We can prohibit new nullptr bases via an Assert for now, and clean up this area in a separate PR since it’s a bit messy.


    l0rinc commented at 11:18 pm on December 30, 2025:

    I don’t think that is ideal, as the compiler is allowed to assume it won’t happen and optimize it away.

    I needed something to demonstrate the UB - and that it’s fixed now, so I have removed this reproducer in the second commit. Would be cool if we could instrument the code with something like -fsanitize=undefined or -fsanitize=vptr or -Wreorder-ctor (haven’t tried any of them, not sure they would have found this one).

    I’ve also added the Assert to prohibit nullptr bases in CCoinsViewBacked - we should use CCoinsViewEmpty::Get() from now on to stop the propagation.

  16. l0rinc force-pushed on Dec 29, 2025
  17. sipa commented at 4:21 pm on December 30, 2025: member
    Concept ACK
  18. refactor: add `CCoinsViewEmpty` as a noop implementation for base and testing
    This enables making `CCoinsView` purely virtual - which is done step-by-step in the following commits.
    
    `CCoinsViewEmpty` is now only constructible via `CCoinsViewEmpty::Get()`.
    Simplified all usages to delegate to the stateless singleton instead.
    
    The fuzzer was also updated to switch randomly between real db backing and empty view.
    1612ec25be
  19. refactor: make `GetCoin`, `GetBestBlock`, and `BatchWrite` purely virtual in `CCoinsView` 8e90366714
  20. refactor: make `CCoinsView::EstimateSize` purely virtual daa402714f
  21. refactor: make `CCoinsView::Cursor` purely virtual 4dde673293
  22. refactor: make `CCoinsView::GetHeadBlocks` purely virtual 8f3a43ff58
  23. refactor: make `CCoinsView::HaveCoin` purely virtual 3c009bfb5a
  24. refactor: inline `CCoinsViewBacked` implementation to header
    It's a simple delegate, the cpp is already really crowded, let's do these inline.
    
    Also note that the `CCoinsViewBacked` constructor is asserted to not take any `nullptr` values anymore, since the reason why `base` was stored as a pointer was that references cannot be reallocated, and we need to call `SetBackend` in a few places - which takes a reference, also proving that `nullptr` was never the goal.
    b23d784b6b
  25. l0rinc force-pushed on Dec 30, 2025

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-01-01 09:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me