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