Bitcoin Core 31.99.0
P2P Digital Currency
coinsviewoverlay_tests.cpp
Go to the documentation of this file.
1// Copyright (c) The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <coins.h>
7#include <primitives/block.h>
10#include <txdb.h>
11#include <uint256.h>
12#include <util/byte_units.h>
13#include <util/hasher.h>
14#include <util/threadpool.h>
15
16#include <boost/test/unit_test.hpp>
17
18#include <cstdint>
19#include <cstring>
20#include <memory>
21#include <ranges>
22#include <unordered_set>
23#include <vector>
24
25namespace {
26
27std::shared_ptr<ThreadPool> MakeStartedThreadPool()
28{
29 auto pool{std::make_shared<ThreadPool>("fetch_test")};
31 return pool;
32}
33
34CBlock CreateBlock() noexcept
35{
36 static constexpr auto NUM_TXS{100};
37 CBlock block;
38 CMutableTransaction coinbase;
39 coinbase.vin.emplace_back();
40 block.vtx.push_back(MakeTransactionRef(coinbase));
41
42 Txid prevhash{Txid::FromUint256(uint256{1})};
43
44 for (const auto i : std::views::iota(1, NUM_TXS)) {
46 const Txid txid{i % 20 == 0 ? prevhash : Txid::FromUint256(uint256(i))};
47 tx.vin.emplace_back(txid, 0);
48 prevhash = tx.GetHash();
49 block.vtx.push_back(MakeTransactionRef(tx));
50 }
51
52 return block;
53}
54
55void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
56{
57 CCoinsViewCache cache{&view};
58 cache.SetBestBlock(uint256::ONE);
59
60 std::unordered_set<Txid, SaltedTxidHasher> txids{};
61 txids.reserve(block.vtx.size() - 1);
62 for (const auto& tx : block.vtx | std::views::drop(1)) {
63 for (const auto& in : tx->vin) {
64 if (txids.contains(in.prevout.hash)) continue;
65 Coin coin{};
66 if (!spent) coin.out.nValue = 1;
67 cache.EmplaceCoinInternalDANGER(COutPoint{in.prevout}, std::move(coin));
68 }
69 txids.emplace(tx->GetHash());
70 }
71
72 cache.Flush();
73}
74
75void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
76{
77 uint32_t counter{0};
78 std::unordered_set<Txid, SaltedTxidHasher> txids{};
79 txids.reserve(block.vtx.size() - 1);
80
81 for (const auto& tx : block.vtx) {
82 if (tx->IsCoinBase()) {
83 BOOST_CHECK(!cache.HaveCoinInCache(tx->vin[0].prevout));
84 } else {
85 for (const auto& in : tx->vin) {
86 const auto& outpoint{in.prevout};
87 const auto& first{cache.AccessCoin(outpoint)};
88 const auto& second{cache.AccessCoin(outpoint)};
89 BOOST_CHECK_EQUAL(&first, &second);
90 const auto have{cache.HaveCoinInCache(outpoint)};
91 BOOST_CHECK_NE(txids.contains(outpoint.hash), have);
92 counter += have;
93 }
94 txids.emplace(tx->GetHash());
95 }
96 }
97 BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);
98}
99
100} // namespace
101
102BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests)
103
104BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)
105{
106 const auto block{CreateBlock()};
107 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
108 PopulateView(block, db);
109 CCoinsViewCache main_cache{&db};
110 CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
111 const auto reset_guard{view.StartFetching(block)};
112 const auto& outpoint{block.vtx[1]->vin[0].prevout};
113
114 BOOST_CHECK(view.HaveCoin(outpoint));
115 BOOST_CHECK(view.GetCoin(outpoint).has_value());
116 BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
117
118 CheckCache(block, view);
119 // Check that no coins have been moved up to main cache from db
120 for (const auto& tx : block.vtx) {
121 for (const auto& in : tx->vin) {
122 BOOST_CHECK(!main_cache.HaveCoinInCache(in.prevout));
123 }
124 }
125
126 view.SetBestBlock(uint256::ONE);
127 BOOST_CHECK(view.SpendCoin(outpoint));
128 view.Flush();
129 BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
130}
131
132BOOST_AUTO_TEST_CASE(fetch_inputs_from_cache)
133{
134 const auto block{CreateBlock()};
135 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
136 CCoinsViewCache main_cache{&db};
137 PopulateView(block, main_cache);
138 CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
139 const auto reset_guard{view.StartFetching(block)};
140 CheckCache(block, view);
141
142 const auto& outpoint{block.vtx[1]->vin[0].prevout};
143 view.SetBestBlock(uint256::ONE);
144 BOOST_CHECK(view.SpendCoin(outpoint));
145 view.Flush();
146 BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
147}
148
149// Test for the case where a block spends coins that are spent in the cache, but
150// the spentness has not been flushed to the db.
151BOOST_AUTO_TEST_CASE(fetch_no_double_spend)
152{
153 const auto block{CreateBlock()};
154 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
155 PopulateView(block, db);
156 CCoinsViewCache main_cache{&db};
157 // Add all inputs as spent already in cache
158 PopulateView(block, main_cache, /*spent=*/true);
159 CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
160 const auto reset_guard{view.StartFetching(block)};
161 for (const auto& tx : block.vtx) {
162 for (const auto& in : tx->vin) {
163 const auto& c{view.AccessCoin(in.prevout)};
164 BOOST_CHECK(c.IsSpent());
165 BOOST_CHECK(!view.HaveCoin(in.prevout));
166 BOOST_CHECK(!view.GetCoin(in.prevout));
167 }
168 }
169 // Coins are not added to the view, even though they exist unspent in the parent db
170 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
171}
172
173BOOST_AUTO_TEST_CASE(fetch_no_inputs)
174{
175 const auto block{CreateBlock()};
176 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
177 CCoinsViewCache main_cache{&db};
178 CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
179 const auto reset_guard{view.StartFetching(block)};
180 for (const auto& tx : block.vtx) {
181 for (const auto& in : tx->vin) {
182 const auto& c{view.AccessCoin(in.prevout)};
183 BOOST_CHECK(c.IsSpent());
184 BOOST_CHECK(!view.HaveCoin(in.prevout));
185 BOOST_CHECK(!view.GetCoin(in.prevout));
186 }
187 }
188 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
189}
190
191// Access coins that are not block inputs
192BOOST_AUTO_TEST_CASE(access_non_input_coins)
193{
194 CBlock block;
195 CMutableTransaction coinbase;
196 coinbase.vin.emplace_back();
197 block.vtx.push_back(MakeTransactionRef(coinbase));
198 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
199 CCoinsViewCache main_cache{&db};
200 Coin coin{};
201 coin.out.nValue = 1;
202 const COutPoint outpoint{Txid::FromUint256(uint256::ZERO), 0};
203 main_cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, std::move(coin));
204
205 CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
206 const auto reset_guard{view.StartFetching(block)};
207
208 // Non-input fallback hit.
209 BOOST_CHECK(!view.AccessCoin(outpoint).IsSpent());
210
211 // Non-input fallback miss.
212 const COutPoint missing_outpoint{Txid::FromUint256(uint256::ONE), 0};
213 BOOST_CHECK(view.AccessCoin(missing_outpoint).IsSpent());
214 BOOST_CHECK(!view.HaveCoinInCache(missing_outpoint));
215}
216
217// Access a fetched input out of order (i.e. not the next one in m_inputs).
218// FetchCoinFromBase must fall back to base->PeekCoin, and the coin must still
219// be inserted into the cache.
220BOOST_AUTO_TEST_CASE(fetch_out_of_order_input_uses_normal_lookup)
221{
222 const auto block{CreateBlock()};
223 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
224 CCoinsViewCache main_cache{&db};
225 PopulateView(block, main_cache);
226
227 std::vector<COutPoint> fetched_inputs;
228 std::unordered_set<Txid, SaltedTxidHasher> txids;
229 txids.reserve(block.vtx.size() - 1);
230 for (const auto& tx : block.vtx | std::views::drop(1)) {
231 for (const auto& input : tx->vin) {
232 if (!txids.contains(input.prevout.hash)) fetched_inputs.push_back(input.prevout);
233 }
234 txids.emplace(tx->GetHash());
235 }
236 BOOST_REQUIRE_GE(fetched_inputs.size(), 2U);
237
238 CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
239 const auto reset_guard{view.StartFetching(block)};
240
241 const auto& out_of_order_input{fetched_inputs[1]};
242 BOOST_CHECK(!view.HaveCoinInCache(out_of_order_input));
243 BOOST_CHECK(!view.AccessCoin(out_of_order_input).IsSpent());
244 BOOST_CHECK(view.HaveCoinInCache(out_of_order_input));
245
246 CheckCache(block, view);
247}
248
249// The ResetGuard returned by StartFetching must clear all per-block state when
250// it goes out of scope, so the overlay can be reused for a subsequent block.
251// Flush must also clear all per-block state to be reused.
252BOOST_AUTO_TEST_CASE(fetch_state_is_reusable_after_teardown)
253{
254 const auto block{CreateBlock()};
255 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
256 CCoinsViewCache main_cache{&db};
257 PopulateView(block, main_cache);
258 CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
259
260 for (const bool use_flush : {false, true, false}) {
261 {
262 const auto reset_guard{view.StartFetching(block)};
263 CheckCache(block, view);
264 BOOST_CHECK_GT(view.GetCacheSize(), 0U);
265 if (use_flush) {
266 view.SetBestBlock(uint256::ONE);
267 view.Flush();
268 }
269 }
270 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0U);
271 }
272}
273
275
276BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests_noworkers)
277
278// Test that disabled input fetching falls back to normal cache lookups via base->PeekCoin.
279BOOST_AUTO_TEST_CASE(fetch_unstarted_thread_pool)
280{
281 const auto block{CreateBlock()};
282 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
283 CCoinsViewCache main_cache{&db};
284 PopulateView(block, main_cache);
285 auto thread_pool{std::make_shared<ThreadPool>("fetch_none")};
286 CoinsViewOverlay view{&main_cache, thread_pool};
287 const auto reset_guard{view.StartFetching(block)};
288 CheckCache(block, view);
289}
290
291// Test that an interrupted thread pool falls back to normal cache lookups via base->PeekCoin.
292BOOST_AUTO_TEST_CASE(fetch_interrupted_thread_pool_uses_normal_lookup)
293{
294 const auto block{CreateBlock()};
295 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
296 CCoinsViewCache main_cache{&db};
297 PopulateView(block, main_cache);
298
299 auto thread_pool{std::make_shared<ThreadPool>("fetch_intr")};
300 thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
301 thread_pool->Interrupt();
302 CoinsViewOverlay view{&main_cache, thread_pool};
303 const auto reset_guard{view.StartFetching(block)};
304 CheckCache(block, view);
305}
306
static constexpr int32_t DEFAULT_PREVOUTFETCH_THREADS
Definition: block.h:74
std::vector< CTransactionRef > vtx
Definition: block.h:77
CCoinsView that adds a memory cache for transactions to another CCoinsView.
Definition: coins.h:400
unsigned int GetCacheSize() const
Size of the cache (in number of transaction outputs)
Definition: coins.cpp:311
bool HaveCoinInCache(const COutPoint &outpoint) const
Check if we have the given utxo already loaded in this cache.
Definition: coins.cpp:178
const Coin & AccessCoin(const COutPoint &output) const
Return a reference to Coin in the cache, or coinEmpty if not found.
Definition: coins.cpp:163
CCoinsView backed by the coin database (chainstate/)
Definition: txdb.h:37
Pure abstract view on the open txout dataset.
Definition: coins.h:319
virtual std::optional< Coin > GetCoin(const COutPoint &outpoint) const =0
Retrieve the Coin (unspent transaction output) for a given outpoint.
virtual bool HaveCoin(const COutPoint &outpoint) const =0
Just check whether a given outpoint is unspent.
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:29
CAmount nValue
Definition: transaction.h:142
A UTXO entry.
Definition: coins.h:46
CTxOut out
unspent transaction output
Definition: coins.h:49
CCoinsViewCache subclass that asynchronously fetches most block input prevouts in parallel during Con...
Definition: coins.h:617
static transaction_identifier FromUint256(const uint256 &id)
256-bit opaque blob.
Definition: uint256.h:196
static const uint256 ONE
Definition: uint256.h:205
static const uint256 ZERO
Definition: uint256.h:204
BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_GT(excessive_headers.size(), MAX_HEADERS_SIZE)
BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value")
#define BOOST_CHECK(expr)
Definition: object.cpp:16
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
A mutable version of CTransaction.
Definition: transaction.h:358
Txid GetHash() const
Compute the hash of this CMutableTransaction.
Definition: transaction.cpp:69
std::vector< CTxIn > vin
Definition: transaction.h:359
CDBWrapper db
Definition: dbwrapper.cpp:371