Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
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/threadpool.h>
14
15#include <boost/test/unit_test.hpp>
16
17#include <cstdint>
18#include <cstring>
19#include <memory>
20#include <ranges>
21#include <unordered_set>
22#include <vector>
23
24namespace {
25
27{
28 util::NotNull pool{std::make_shared<ThreadPool>("fetch_test")};
30 return pool;
31}
32
33CBlock CreateBlock() noexcept
34{
35 static constexpr auto NUM_TXS{100};
36 CBlock block;
38 coinbase.vin.emplace_back();
39 block.vtx.push_back(MakeTransactionRef(coinbase));
40
42
43 for (const auto i : std::views::iota(1, NUM_TXS)) {
45 const Txid txid{i % 20 == 0 ? prevhash : Txid::FromUint256(uint256(i))};
46 tx.vin.emplace_back(txid, 0);
47 prevhash = tx.GetHash();
48 block.vtx.push_back(MakeTransactionRef(tx));
49 }
50
51 return block;
52}
53
54void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
55{
56 CCoinsViewCache cache{&view};
58
59 std::unordered_set<Txid, SaltedCoinsCacheHasher> txids{};
60 txids.reserve(block.vtx.size() - 1);
61 for (const auto& tx : block.vtx | std::views::drop(1)) {
62 for (const auto& in : tx->vin) {
63 if (txids.contains(in.prevout.hash)) continue;
64 Coin coin{};
65 if (!spent) coin.out.nValue = 1;
66 cache.EmplaceCoinInternalDANGER(in.prevout, std::move(coin));
67 }
68 txids.emplace(tx->GetHash());
69 }
70
71 cache.Flush();
72}
73
74void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
75{
76 uint32_t counter{0};
77 std::unordered_set<Txid, SaltedCoinsCacheHasher> txids{};
78 txids.reserve(block.vtx.size() - 1);
79
80 for (const auto& tx : block.vtx) {
81 if (tx->IsCoinBase()) {
82 BOOST_CHECK(!cache.HaveCoinInCache(tx->vin[0].prevout));
83 } else {
84 for (const auto& in : tx->vin) {
85 const auto& outpoint{in.prevout};
86 const auto& first{cache.AccessCoin(outpoint)};
87 const auto& second{cache.AccessCoin(outpoint)};
89 const auto have{cache.HaveCoinInCache(outpoint)};
90 BOOST_CHECK_NE(txids.contains(outpoint.hash), have);
91 counter += have;
92 }
93 txids.emplace(tx->GetHash());
94 }
95 }
96 BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);
97}
98
99} // namespace
100
102
104{
105 const auto block{CreateBlock()};
106 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
107 PopulateView(block, db);
110 const auto reset_guard{view.StartFetching(block)};
111 const auto& outpoint{block.vtx[1]->vin[0].prevout};
112
113 BOOST_CHECK(view.HaveCoin(outpoint));
114 BOOST_CHECK(view.GetCoin(outpoint).has_value());
115 BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
116
117 CheckCache(block, view);
118 // Check that no coins have been moved up to main cache from db
119 for (const auto& tx : block.vtx) {
120 for (const auto& in : tx->vin) {
121 BOOST_CHECK(!main_cache.HaveCoinInCache(in.prevout));
122 }
123 }
124
125 view.SetBestBlock(uint256::ONE);
126 BOOST_CHECK(view.SpendCoin(outpoint));
127 view.Flush();
128 BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
129}
130
132{
133 const auto block{CreateBlock()};
134 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
136 PopulateView(block, main_cache);
138 const auto reset_guard{view.StartFetching(block)};
139 CheckCache(block, view);
140
141 const auto& outpoint{block.vtx[1]->vin[0].prevout};
142 view.SetBestBlock(uint256::ONE);
143 BOOST_CHECK(view.SpendCoin(outpoint));
144 view.Flush();
145 BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
146}
147
148// Test for the case where a block spends coins that are spent in the cache, but
149// the spentness has not been flushed to the db.
151{
152 const auto block{CreateBlock()};
153 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
154 PopulateView(block, db);
156 // Add all inputs as spent already in cache
157 PopulateView(block, main_cache, /*spent=*/true);
159 const auto reset_guard{view.StartFetching(block)};
160 for (const auto& tx : block.vtx) {
161 for (const auto& in : tx->vin) {
162 const auto& c{view.AccessCoin(in.prevout)};
163 BOOST_CHECK(c.IsSpent());
164 BOOST_CHECK(!view.HaveCoin(in.prevout));
165 BOOST_CHECK(!view.GetCoin(in.prevout));
166 }
167 }
168 // Coins are not added to the view, even though they exist unspent in the parent db
169 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
170}
171
173{
174 const auto block{CreateBlock()};
175 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
178 const auto reset_guard{view.StartFetching(block)};
179 for (const auto& tx : block.vtx) {
180 for (const auto& in : tx->vin) {
181 const auto& c{view.AccessCoin(in.prevout)};
182 BOOST_CHECK(c.IsSpent());
183 BOOST_CHECK(!view.HaveCoin(in.prevout));
184 BOOST_CHECK(!view.GetCoin(in.prevout));
185 }
186 }
187 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
188}
189
190// Access coins that are not block inputs
192{
193 CBlock block;
195 coinbase.vin.emplace_back();
196 block.vtx.push_back(MakeTransactionRef(coinbase));
197 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
199 Coin coin{};
200 coin.out.nValue = 1;
201 const COutPoint outpoint{Txid::FromUint256(uint256::ZERO), 0};
202 main_cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, std::move(coin));
203
205 // The block has no non-coinbase transactions, so this fetches nothing and only creates the
206 // reset guard. All lookups below use the fallback path.
207 const auto reset_guard{view.StartFetching(block)};
208
209 // Non-input fallback hit.
210 BOOST_CHECK(!view.AccessCoin(outpoint).IsSpent());
211
212 // Non-input fallback miss.
214 BOOST_CHECK(view.AccessCoin(missing_outpoint).IsSpent());
215 BOOST_CHECK(!view.HaveCoinInCache(missing_outpoint));
216}
217
218// Access a fetched input out of order (i.e. not the next one in m_inputs).
219// FetchCoinFromBase must fall back to base->PeekCoin, and the coin must still
220// be inserted into the cache.
222{
223 const auto block{CreateBlock()};
224 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
226 PopulateView(block, main_cache);
227
228 std::vector<COutPoint> fetched_inputs;
229 std::unordered_set<Txid, SaltedCoinsCacheHasher> txids;
230 txids.reserve(block.vtx.size() - 1);
231 for (const auto& tx : block.vtx | std::views::drop(1)) {
232 for (const auto& input : tx->vin) {
233 if (!txids.contains(input.prevout.hash)) fetched_inputs.push_back(input.prevout);
234 }
235 txids.emplace(tx->GetHash());
236 }
238
240 const auto reset_guard{view.StartFetching(block)};
241
242 const auto& out_of_order_input{fetched_inputs[1]};
243 BOOST_CHECK(!view.HaveCoinInCache(out_of_order_input));
244 BOOST_CHECK(!view.AccessCoin(out_of_order_input).IsSpent());
245 BOOST_CHECK(view.HaveCoinInCache(out_of_order_input));
246
247 CheckCache(block, view);
248}
249
250// The ResetGuard returned by StartFetching must clear all per-block state when
251// it goes out of scope, so the overlay can be reused for a subsequent block.
252// Flush must also clear all per-block state to be reused.
254{
255 const auto block{CreateBlock()};
256 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
258 PopulateView(block, main_cache);
260
261 for (const bool use_flush : {false, true, false}) {
262 {
263 const auto reset_guard{view.StartFetching(block)};
264 CheckCache(block, view);
265 BOOST_CHECK_GT(view.GetCacheSize(), 0U);
266 if (use_flush) {
267 view.SetBestBlock(uint256::ONE);
268 view.Flush();
269 }
270 }
271 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0U);
272 }
273}
274
276
278
279// Test that disabled input fetching falls back to normal cache lookups via base->PeekCoin.
281{
282 const auto block{CreateBlock()};
283 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
285 PopulateView(block, main_cache);
286 util::NotNull thread_pool{std::make_shared<ThreadPool>("fetch_none")};
288 const auto reset_guard{view.StartFetching(block)};
289 CheckCache(block, view);
290}
291
292// Test that an interrupted thread pool falls back to normal cache lookups via base->PeekCoin.
294{
295 const auto block{CreateBlock()};
296 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
298 PopulateView(block, main_cache);
299
300 util::NotNull thread_pool{std::make_shared<ThreadPool>("fetch_intr")};
302 thread_pool->Interrupt();
304 const auto reset_guard{view.StartFetching(block)};
305 CheckCache(block, view);
306}
307
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:437
void SetBestBlock(const uint256 &block_hash)
Definition coins.cpp:196
unsigned int GetCacheSize() const
Size of the cache (in number of transaction outputs)
Definition coins.cpp:318
bool HaveCoinInCache(const COutPoint &outpoint) const
Check if we have the given utxo already loaded in this cache.
Definition coins.cpp:185
const Coin & AccessCoin(const COutPoint &output) const
Return a reference to Coin in the cache, or coinEmpty if not found.
Definition coins.cpp:170
CCoinsView backed by the coin database (chainstate/)
Definition txdb.h:37
Pure abstract view on the open txout dataset.
Definition coins.h:356
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition transaction.h:30
CAmount nValue
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:656
ResetGuard StartFetching(const CBlock &block LIFETIMEBOUND) noexcept
Start fetching inputs from block.
Definition coins.cpp:376
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
NotNull is intended to be used to denote that a smart pointer type can not hold a nullptr value.
Definition not_null.h:45
BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_GT(excessive_headers.size(), MAX_HEADERS_SIZE)
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:17
#define BOOST_CHECK(expr)
Definition object.cpp:16
static CTransactionRef MakeTransactionRef(Tx &&txIn)
A mutable version of CTransaction.
Txid GetHash() const
Compute the hash of this CMutableTransaction.
std::vector< CTxIn > vin
CDBWrapper db
BOOST_CHECK_NE(OneL.ToString(), ArrayToString(ZeroArray, 32))
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82