Repository: bitcoin/bitcoin
File: src/test/coins_tests.cpp (C++)

Code before:
    CCoinsView root;
    CCoinsViewCacheTest cache{&root};

    const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};

    const Coin coin1{CTxOut{m_rng.randrange(10), CScript{} << m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};
    cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin1});
    cache.SelfTest();

    const Coin coin2{CTxOut{m_rng.randrange(20), CScript{} << m_rng.randbytes(CScriptBase::STATIC_SIZE + 2)}, 2, false};
    cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin2});
    cache.SelfTest();

    BOOST_CHECK(cache.AccessCoin(outpoint) == coin1);
}

BOOST_AUTO_TEST_CASE(ccoins_reset)
{
    CCoinsView root;
    CCoinsViewCacheTest cache{&root};

    const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};

    const Coin coin{CTxOut{m_rng.randrange(10), CScript{} << m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};
    cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin});
    cache.SetBestBlock(uint256::ONE);
    cache.SelfTest();

    BOOST_CHECK(cache.AccessCoin(outpoint) == coin);
    BOOST_CHECK(!cache.AccessCoin(outpoint).IsSpent());
    BOOST_CHECK_EQUAL(cache.GetCacheSize(), 1);
    BOOST_CHECK_EQUAL(cache.GetBestBlock(), uint256::ONE);

    cache.Reset();

    BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
    BOOST_CHECK_EQUAL(cache.GetCacheSize(), 0);
    BOOST_CHECK_EQUAL(cache.GetBestBlock(), uint256::ZERO);

    // Reset is idempotent
    cache.Reset();

    BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
    BOOST_CHECK_EQUAL(cache.GetCacheSize(), 0);
    BOOST_CHECK_EQUAL(cache.GetBestBlock(), uint256::ZERO);
}

BOOST_AUTO_TEST_SUITE_END()


Review comment (performance):
We currently set the (cache) best block to `uint256::ONE` and then expect `Reset()` to bring it back to `uint256::ZERO`.

That relies on `CCoinsViewCache::GetBestBlock()` treating `0` as uninitialized and lazily pulling from the base view, and in practice the best block should never be null.
Could we instead set the base best block to some random value and set the cache best block to a different value, then assert that after `Reset()` the cache’s `GetBestBlock()` matches the base best block? This would better exercise propagation across layers and avoids special-casing `0/1`:
```C++
BOOST_AUTO_TEST_CASE(ccoins_reset)
{
    CCoinsViewTest root{m_rng};
    CCoinsViewCache root_cache{&root};
    const uint256 base_best_block{m_rng.rand256()};
    root_cache.SetBestBlock(base_best_block);
    root_cache.Flush();

    CCoinsViewCacheTest cache{&root};

    const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};

    const Coin coin{CTxOut{m_rng.randrange(10), CScript{} << m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};
    cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin});

    const uint256 cache_best_block{uint256::ONE};
    cache.SetBestBlock(cache_best_block);
    cache.SelfTest();

    BOOST_CHECK(cache.AccessCoin(outpoint) == coin);
    BOOST_CHECK(!cache.AccessCoin(outpoint).IsSpent());
    BOOST_CHECK_EQUAL(cache.GetCacheSize(), 1);
    BOOST_CHECK_EQUAL(cache.GetBestBlock(), cache_best_block);

    for (int i = 0; i < 2; ++i) { // Reset is idempotent
        cache.Reset();
        BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
        BOOST_CHECK_EQUAL(cache.GetCacheSize(), 0);
        BOOST_CHECK_EQUAL(cache.GetBestBlock(), base_best_block);
    }
}
```

Code after:
    CCoinsView root;
    CCoinsViewCacheTest cache{&root};

    const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};

    const Coin coin1{CTxOut{m_rng.randrange(10), CScript{} << m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};
    cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin1});
    cache.SelfTest();

    const Coin coin2{CTxOut{m_rng.randrange(20), CScript{} << m_rng.randbytes(CScriptBase::STATIC_SIZE + 2)}, 2, false};
    cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin2});
    cache.SelfTest();

    BOOST_CHECK(cache.AccessCoin(outpoint) == coin1);
}

BOOST_AUTO_TEST_CASE(ccoins_reset_guard)
{
    CCoinsViewTest root{m_rng};
    CCoinsViewCache root_cache{&root};
    uint256 base_best_block{m_rng.rand256()};
    root_cache.SetBestBlock(base_best_block);
    root_cache.Flush();

    CCoinsViewCache cache{&root};

    const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};

    const Coin coin{CTxOut{m_rng.randrange(10), CScript{} << m_rng.randbytes(CScriptBase::STATIC_SIZE + 1)}, 1, false};
    cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, Coin{coin});

    uint256 cache_best_block{m_rng.rand256()};
    cache.SetBestBlock(cache_best_block);

    {
        const auto reset_guard{cache.CreateResetGuard()};
        BOOST_CHECK(cache.AccessCoin(outpoint) == coin);
        BOOST_CHECK(!cache.AccessCoin(outpoint).IsSpent());
        BOOST_CHECK_EQUAL(cache.GetCacheSize(), 1);
        BOOST_CHECK_EQUAL(cache.GetBestBlock(), cache_best_block);
        BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));
    }

    BOOST_CHECK(cache.AccessCoin(outpoint).IsSpent());
    BOOST_CHECK_EQUAL(cache.GetCacheSize(), 0);
    BOOST_CHECK_EQUAL(cache.GetBestBlock(), base_best_block);
    BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint));

    // Using a reset guard again is idempotent
    {
        const auto reset_guard{cache.CreateResetGuard()};