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 if (spent) {
65 BOOST_CHECK(cache.SpendCoin(in.prevout));
66 } else {
67 Coin coin{};
68 coin.out.nValue = 1;
69 cache.AddCoin(in.prevout, std::move(coin), /*possible_overwrite=*/true);
70 }
71 }
72 txids.emplace(tx->GetHash());
73 }
74
75 cache.Flush();
76}
77
78void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
79{
80 uint32_t counter{0};
81 std::unordered_set<Txid, SaltedCoinsCacheHasher> txids{};
82 txids.reserve(block.vtx.size() - 1);
83
84 for (const auto& tx : block.vtx) {
85 if (tx->IsCoinBase()) {
86 BOOST_CHECK(!cache.HaveCoinInCache(tx->vin[0].prevout));
87 } else {
88 for (const auto& in : tx->vin) {
89 const auto& outpoint{in.prevout};
90 const auto& first{cache.AccessCoin(outpoint)};
91 const auto& second{cache.AccessCoin(outpoint)};
93 const auto have{cache.HaveCoinInCache(outpoint)};
94 BOOST_CHECK_NE(txids.contains(outpoint.hash), have);
95 counter += have;
96 }
97 txids.emplace(tx->GetHash());
98 }
99 }
100 BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);
101}
102
103} // namespace
104
106
108{
109 const auto block{CreateBlock()};
110 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
111 PopulateView(block, db);
114 const auto reset_guard{view.StartFetching(block)};
115 const auto& outpoint{block.vtx[1]->vin[0].prevout};
116
117 BOOST_CHECK(view.HaveCoin(outpoint));
118 BOOST_CHECK(view.GetCoin(outpoint).has_value());
119 BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
120
121 CheckCache(block, view);
122 // Check that no coins have been moved up to main cache from db
123 for (const auto& tx : block.vtx) {
124 for (const auto& in : tx->vin) {
125 BOOST_CHECK(!main_cache.HaveCoinInCache(in.prevout));
126 }
127 }
128
129 view.SetBestBlock(uint256::ONE);
130 BOOST_CHECK(view.SpendCoin(outpoint));
131 view.Flush();
132 BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
133}
134
136{
137 const auto block{CreateBlock()};
138 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
140 PopulateView(block, main_cache);
142 const auto reset_guard{view.StartFetching(block)};
143 CheckCache(block, view);
144
145 const auto& outpoint{block.vtx[1]->vin[0].prevout};
146 view.SetBestBlock(uint256::ONE);
147 BOOST_CHECK(view.SpendCoin(outpoint));
148 view.Flush();
149 BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
150}
151
152// Test for the case where a block spends coins that are spent in the cache, but
153// the spentness has not been flushed to the db.
155{
156 const auto block{CreateBlock()};
157 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
158 PopulateView(block, db);
160 // Add all inputs as spent already in cache
161 PopulateView(block, main_cache, /*spent=*/true);
163 const auto reset_guard{view.StartFetching(block)};
164 for (const auto& tx : block.vtx) {
165 for (const auto& in : tx->vin) {
166 const auto& c{view.AccessCoin(in.prevout)};
167 BOOST_CHECK(c.IsSpent());
168 BOOST_CHECK(!view.HaveCoin(in.prevout));
169 BOOST_CHECK(!view.GetCoin(in.prevout));
170 }
171 }
172 // Coins are not added to the view, even though they exist unspent in the parent db
173 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
174}
175
177{
178 const auto block{CreateBlock()};
179 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
182 const auto reset_guard{view.StartFetching(block)};
183 for (const auto& tx : block.vtx) {
184 for (const auto& in : tx->vin) {
185 const auto& c{view.AccessCoin(in.prevout)};
186 BOOST_CHECK(c.IsSpent());
187 BOOST_CHECK(!view.HaveCoin(in.prevout));
188 BOOST_CHECK(!view.GetCoin(in.prevout));
189 }
190 }
191 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
192}
193
194// Access coins that are not block inputs
196{
197 CBlock block;
199 coinbase.vin.emplace_back();
200 block.vtx.push_back(MakeTransactionRef(coinbase));
201 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
203 Coin coin{};
204 coin.out.nValue = 1;
205 const COutPoint outpoint{Txid::FromUint256(uint256::ZERO), 0};
206 main_cache.AddCoin(outpoint, std::move(coin), /*possible_overwrite=*/true);
207
209 // The block has no non-coinbase transactions, so this fetches nothing and only creates the
210 // reset guard. All lookups below use the fallback path.
211 const auto reset_guard{view.StartFetching(block)};
212
213 // Non-input fallback hit.
214 BOOST_CHECK(!view.AccessCoin(outpoint).IsSpent());
215
216 // Non-input fallback miss.
218 BOOST_CHECK(view.AccessCoin(missing_outpoint).IsSpent());
219 BOOST_CHECK(!view.HaveCoinInCache(missing_outpoint));
220}
221
222// Access a fetched input out of order (i.e. not the next one in m_inputs).
223// FetchCoinFromBase must fall back to base->PeekCoin, and the coin must still
224// be inserted into the cache.
226{
227 const auto block{CreateBlock()};
228 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
230 PopulateView(block, main_cache);
231
232 std::vector<COutPoint> fetched_inputs;
233 std::unordered_set<Txid, SaltedCoinsCacheHasher> txids;
234 txids.reserve(block.vtx.size() - 1);
235 for (const auto& tx : block.vtx | std::views::drop(1)) {
236 for (const auto& input : tx->vin) {
237 if (!txids.contains(input.prevout.hash)) fetched_inputs.push_back(input.prevout);
238 }
239 txids.emplace(tx->GetHash());
240 }
242
244 const auto reset_guard{view.StartFetching(block)};
245
246 const auto& out_of_order_input{fetched_inputs[1]};
247 BOOST_CHECK(!view.HaveCoinInCache(out_of_order_input));
248 BOOST_CHECK(!view.AccessCoin(out_of_order_input).IsSpent());
249 BOOST_CHECK(view.HaveCoinInCache(out_of_order_input));
250
251 CheckCache(block, view);
252}
253
254// The ResetGuard returned by StartFetching must clear all per-block state when
255// it goes out of scope, so the overlay can be reused for a subsequent block.
256// Flush must also clear all per-block state to be reused.
258{
259 const auto block{CreateBlock()};
260 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
262 PopulateView(block, main_cache);
264
265 for (const bool use_flush : {false, true, false}) {
266 {
267 const auto reset_guard{view.StartFetching(block)};
268 CheckCache(block, view);
269 BOOST_CHECK_GT(view.GetCacheSize(), 0U);
270 if (use_flush) {
271 view.SetBestBlock(uint256::ONE);
272 view.Flush();
273 }
274 }
275 BOOST_CHECK_EQUAL(view.GetCacheSize(), 0U);
276 }
277}
278
280
282
283// Test that disabled input fetching falls back to normal cache lookups via base->PeekCoin.
285{
286 const auto block{CreateBlock()};
287 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
289 PopulateView(block, main_cache);
290 util::NotNull thread_pool{std::make_shared<ThreadPool>("fetch_none")};
292 const auto reset_guard{view.StartFetching(block)};
293 CheckCache(block, view);
294}
295
296// Test that an interrupted thread pool falls back to normal cache lookups via base->PeekCoin.
298{
299 const auto block{CreateBlock()};
300 CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
302 PopulateView(block, main_cache);
303
304 util::NotNull thread_pool{std::make_shared<ThreadPool>("fetch_intr")};
306 thread_pool->Interrupt();
308 const auto reset_guard{view.StartFetching(block)};
309 CheckCache(block, view);
310}
311
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