Branch data Line data Source code
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>
6 : : #include <kernel/chainstatemanager_opts.h>
7 : : #include <primitives/block.h>
8 : : #include <primitives/transaction.h>
9 : : #include <primitives/transaction_identifier.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 : :
25 : : namespace {
26 : :
27 : 7 : std::shared_ptr<ThreadPool> MakeStartedThreadPool()
28 : : {
29 : 7 : auto pool{std::make_shared<ThreadPool>("fetch_test")};
30 [ + - ]: 7 : pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
31 : 7 : return pool;
32 : 0 : }
33 : :
34 : 8 : CBlock CreateBlock() noexcept
35 : : {
36 : 8 : static constexpr auto NUM_TXS{100};
37 : 8 : CBlock block;
38 : 8 : CMutableTransaction coinbase;
39 : 8 : coinbase.vin.emplace_back();
40 [ - + ]: 8 : block.vtx.push_back(MakeTransactionRef(coinbase));
41 : :
42 : 8 : Txid prevhash{Txid::FromUint256(uint256{1})};
43 : :
44 [ + + ]: 800 : for (const auto i : std::views::iota(1, NUM_TXS)) {
45 : 792 : CMutableTransaction tx;
46 [ + + ]: 792 : const Txid txid{i % 20 == 0 ? prevhash : Txid::FromUint256(uint256(i))};
47 : 792 : tx.vin.emplace_back(txid, 0);
48 : 792 : prevhash = tx.GetHash();
49 [ - + ]: 792 : block.vtx.push_back(MakeTransactionRef(tx));
50 : 792 : }
51 : :
52 : 8 : return block;
53 : 8 : }
54 : :
55 : 8 : void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
56 : : {
57 : 8 : CCoinsViewCache cache{&view};
58 [ + - ]: 8 : cache.SetBestBlock(uint256::ONE);
59 : :
60 [ + - ]: 8 : std::unordered_set<Txid, SaltedTxidHasher> txids{};
61 [ - + + - ]: 8 : txids.reserve(block.vtx.size() - 1);
62 [ + + ]: 800 : for (const auto& tx : block.vtx | std::views::drop(1)) {
63 [ + + ]: 1584 : for (const auto& in : tx->vin) {
64 [ + + ]: 792 : if (txids.contains(in.prevout.hash)) continue;
65 : 760 : Coin coin{};
66 [ + + ]: 760 : if (!spent) coin.out.nValue = 1;
67 [ + - ]: 760 : cache.EmplaceCoinInternalDANGER(COutPoint{in.prevout}, std::move(coin));
68 : 760 : }
69 [ + - ]: 792 : txids.emplace(tx->GetHash());
70 : : }
71 : :
72 [ + - ]: 8 : cache.Flush();
73 : 8 : }
74 : :
75 : 8 : void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
76 : : {
77 : 8 : uint32_t counter{0};
78 : 8 : std::unordered_set<Txid, SaltedTxidHasher> txids{};
79 [ - + + - ]: 8 : txids.reserve(block.vtx.size() - 1);
80 : :
81 [ + + ]: 808 : for (const auto& tx : block.vtx) {
82 [ + + ]: 800 : if (tx->IsCoinBase()) {
83 [ + - + - : 16 : BOOST_CHECK(!cache.HaveCoinInCache(tx->vin[0].prevout));
+ - + - ]
84 : : } else {
85 [ + + ]: 1584 : for (const auto& in : tx->vin) {
86 : 792 : const auto& outpoint{in.prevout};
87 [ + - ]: 792 : const auto& first{cache.AccessCoin(outpoint)};
88 [ + - ]: 792 : const auto& second{cache.AccessCoin(outpoint)};
89 [ + - + - ]: 792 : BOOST_CHECK_EQUAL(&first, &second);
90 [ + - ]: 792 : const auto have{cache.HaveCoinInCache(outpoint)};
91 [ + - + - ]: 792 : BOOST_CHECK_NE(txids.contains(outpoint.hash), have);
92 : 792 : counter += have;
93 : : }
94 [ + - ]: 792 : txids.emplace(tx->GetHash());
95 : : }
96 : : }
97 [ + - + - : 8 : BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);
+ - ]
98 : 8 : }
99 : :
100 : : } // namespace
101 : :
102 : : BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests)
103 : :
104 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
105 : : {
106 : 1 : const auto block{CreateBlock()};
107 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
108 [ + - ]: 1 : PopulateView(block, db);
109 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
110 [ + - - + ]: 1 : CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
111 : 1 : const auto reset_guard{view.StartFetching(block)};
112 [ + - ]: 1 : const auto& outpoint{block.vtx[1]->vin[0].prevout};
113 : :
114 [ + - + - : 2 : BOOST_CHECK(view.HaveCoin(outpoint));
+ - + - ]
115 [ + - + - : 2 : BOOST_CHECK(view.GetCoin(outpoint).has_value());
+ - + - ]
116 [ + - + - : 2 : BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
+ - + - ]
117 : :
118 [ + - ]: 1 : CheckCache(block, view);
119 : : // Check that no coins have been moved up to main cache from db
120 [ + + ]: 101 : for (const auto& tx : block.vtx) {
121 [ + + ]: 200 : for (const auto& in : tx->vin) {
122 [ + - + - : 200 : BOOST_CHECK(!main_cache.HaveCoinInCache(in.prevout));
+ - ]
123 : : }
124 : : }
125 : :
126 [ + - ]: 1 : view.SetBestBlock(uint256::ONE);
127 [ + - + - : 2 : BOOST_CHECK(view.SpendCoin(outpoint));
+ - + - ]
128 [ + - ]: 1 : view.Flush();
129 [ + - + - : 2 : BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
+ - ]
130 [ + - + - ]: 2 : }
131 : :
132 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_inputs_from_cache)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
133 : : {
134 : 1 : const auto block{CreateBlock()};
135 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
136 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
137 [ + - ]: 1 : PopulateView(block, main_cache);
138 [ + - - + ]: 1 : CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
139 : 1 : const auto reset_guard{view.StartFetching(block)};
140 [ + - ]: 1 : CheckCache(block, view);
141 : :
142 [ + - ]: 1 : const auto& outpoint{block.vtx[1]->vin[0].prevout};
143 [ + - ]: 1 : view.SetBestBlock(uint256::ONE);
144 [ + - + - : 2 : BOOST_CHECK(view.SpendCoin(outpoint));
+ - + - ]
145 [ + - ]: 1 : view.Flush();
146 [ + - + - : 2 : BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
+ - ]
147 [ + - + - ]: 2 : }
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.
151 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_no_double_spend)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
152 : : {
153 : 1 : const auto block{CreateBlock()};
154 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
155 [ + - ]: 1 : PopulateView(block, db);
156 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
157 : : // Add all inputs as spent already in cache
158 [ + - ]: 1 : PopulateView(block, main_cache, /*spent=*/true);
159 [ + - - + ]: 1 : CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
160 : 1 : const auto reset_guard{view.StartFetching(block)};
161 [ + + ]: 101 : for (const auto& tx : block.vtx) {
162 [ + + ]: 200 : for (const auto& in : tx->vin) {
163 [ + - ]: 100 : const auto& c{view.AccessCoin(in.prevout)};
164 [ + - + - : 200 : BOOST_CHECK(c.IsSpent());
+ - ]
165 [ + - + - : 200 : BOOST_CHECK(!view.HaveCoin(in.prevout));
+ - + - ]
166 [ + - + - : 200 : 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 [ + - + - : 1 : BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
+ - ]
171 [ + - + - ]: 2 : }
172 : :
173 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_no_inputs)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
174 : : {
175 : 1 : const auto block{CreateBlock()};
176 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
177 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
178 [ + - - + ]: 1 : CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
179 : 1 : const auto reset_guard{view.StartFetching(block)};
180 [ + + ]: 101 : for (const auto& tx : block.vtx) {
181 [ + + ]: 200 : for (const auto& in : tx->vin) {
182 [ + - ]: 100 : const auto& c{view.AccessCoin(in.prevout)};
183 [ + - + - : 200 : BOOST_CHECK(c.IsSpent());
+ - ]
184 [ + - + - : 200 : BOOST_CHECK(!view.HaveCoin(in.prevout));
+ - + - ]
185 [ + - + - : 200 : BOOST_CHECK(!view.GetCoin(in.prevout));
+ - ]
186 : : }
187 : : }
188 [ + - + - : 1 : BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
+ - ]
189 [ + - + - ]: 2 : }
190 : :
191 : : // Access coins that are not block inputs
192 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(access_non_input_coins)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
193 : : {
194 : 1 : CBlock block;
195 [ + - ]: 1 : CMutableTransaction coinbase;
196 [ + - ]: 1 : coinbase.vin.emplace_back();
197 [ + - + - : 2 : block.vtx.push_back(MakeTransactionRef(coinbase));
- + ]
198 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
199 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
200 : 1 : Coin coin{};
201 : 1 : coin.out.nValue = 1;
202 [ + - ]: 1 : const COutPoint outpoint{Txid::FromUint256(uint256::ZERO), 0};
203 [ + - ]: 1 : main_cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, std::move(coin));
204 : :
205 [ + - - + ]: 1 : CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
206 : 1 : const auto reset_guard{view.StartFetching(block)};
207 : :
208 : : // Non-input fallback hit.
209 [ + - + - : 2 : BOOST_CHECK(!view.AccessCoin(outpoint).IsSpent());
+ - + - ]
210 : :
211 : : // Non-input fallback miss.
212 [ + - ]: 1 : const COutPoint missing_outpoint{Txid::FromUint256(uint256::ONE), 0};
213 [ + - + - : 2 : BOOST_CHECK(view.AccessCoin(missing_outpoint).IsSpent());
+ - + - ]
214 [ + - + - : 2 : BOOST_CHECK(!view.HaveCoinInCache(missing_outpoint));
+ - ]
215 [ + - + - ]: 3 : }
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.
220 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_out_of_order_input_uses_normal_lookup)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
221 : : {
222 : 1 : const auto block{CreateBlock()};
223 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
224 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
225 [ + - ]: 1 : PopulateView(block, main_cache);
226 : :
227 : 1 : std::vector<COutPoint> fetched_inputs;
228 [ + - ]: 1 : std::unordered_set<Txid, SaltedTxidHasher> txids;
229 [ - + + - ]: 1 : txids.reserve(block.vtx.size() - 1);
230 [ + + ]: 100 : for (const auto& tx : block.vtx | std::views::drop(1)) {
231 [ + + ]: 198 : for (const auto& input : tx->vin) {
232 [ + + + - ]: 99 : if (!txids.contains(input.prevout.hash)) fetched_inputs.push_back(input.prevout);
233 : : }
234 [ + - ]: 99 : txids.emplace(tx->GetHash());
235 : : }
236 [ + - - + : 1 : BOOST_REQUIRE_GE(fetched_inputs.size(), 2U);
+ - ]
237 : :
238 [ + - - + ]: 1 : CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
239 : 1 : const auto reset_guard{view.StartFetching(block)};
240 : :
241 [ + - ]: 1 : const auto& out_of_order_input{fetched_inputs[1]};
242 [ + - + - : 2 : BOOST_CHECK(!view.HaveCoinInCache(out_of_order_input));
+ - + - ]
243 [ + - + - : 2 : BOOST_CHECK(!view.AccessCoin(out_of_order_input).IsSpent());
+ - + - ]
244 [ + - + - : 2 : BOOST_CHECK(view.HaveCoinInCache(out_of_order_input));
+ - + - ]
245 : :
246 [ + - ]: 1 : CheckCache(block, view);
247 [ + - + - ]: 2 : }
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.
252 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_state_is_reusable_after_teardown)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
253 : : {
254 : 1 : const auto block{CreateBlock()};
255 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
256 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
257 [ + - ]: 1 : PopulateView(block, main_cache);
258 [ + - - + ]: 1 : CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
259 : :
260 [ + + ]: 4 : for (const bool use_flush : {false, true, false}) {
261 : 3 : {
262 : 3 : const auto reset_guard{view.StartFetching(block)};
263 [ + - ]: 3 : CheckCache(block, view);
264 [ + - + - : 3 : BOOST_CHECK_GT(view.GetCacheSize(), 0U);
+ - ]
265 [ + + ]: 3 : if (use_flush) {
266 [ + - ]: 1 : view.SetBestBlock(uint256::ONE);
267 [ + - ]: 1 : view.Flush();
268 : : }
269 : 0 : }
270 [ + - + - : 3 : BOOST_CHECK_EQUAL(view.GetCacheSize(), 0U);
+ - ]
271 : : }
272 [ + - + - ]: 2 : }
273 : :
274 : : BOOST_AUTO_TEST_SUITE_END()
275 : :
276 : : BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests_noworkers)
277 : :
278 : : // Test that disabled input fetching falls back to normal cache lookups via base->PeekCoin.
279 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_unstarted_thread_pool)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
280 : : {
281 : 1 : const auto block{CreateBlock()};
282 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
283 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
284 [ + - ]: 1 : PopulateView(block, main_cache);
285 [ + - ]: 1 : auto thread_pool{std::make_shared<ThreadPool>("fetch_none")};
286 [ + - - + ]: 2 : CoinsViewOverlay view{&main_cache, thread_pool};
287 : 1 : const auto reset_guard{view.StartFetching(block)};
288 [ + - ]: 1 : CheckCache(block, view);
289 [ + - + - : 3 : }
+ - ]
290 : :
291 : : // Test that an interrupted thread pool falls back to normal cache lookups via base->PeekCoin.
292 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(fetch_interrupted_thread_pool_uses_normal_lookup)
+ - + - -
+ + - + -
+ - + - +
- - + + -
+ - + - +
- + - - +
+ - + - +
- + - + -
- + + - +
- + - + -
+ - - + +
- ]
293 : : {
294 : 1 : const auto block{CreateBlock()};
295 : 0 : CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
296 [ + - ]: 1 : CCoinsViewCache main_cache{&db};
297 [ + - ]: 1 : PopulateView(block, main_cache);
298 : :
299 [ + - ]: 1 : auto thread_pool{std::make_shared<ThreadPool>("fetch_intr")};
300 [ + - ]: 1 : thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
301 [ + - ]: 1 : thread_pool->Interrupt();
302 [ + - - + ]: 2 : CoinsViewOverlay view{&main_cache, thread_pool};
303 : 1 : const auto reset_guard{view.StartFetching(block)};
304 [ + - ]: 1 : CheckCache(block, view);
305 [ + - + - : 3 : }
+ - ]
306 : :
307 : : BOOST_AUTO_TEST_SUITE_END()
|