Branch data Line data Source code
1 : : // Copyright (c) 2020-present 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 : :
6 : : #include <chain.h>
7 : : #include <chainparams.h>
8 : : #include <flatfile.h>
9 : : #include <primitives/block.h>
10 : : #include <primitives/transaction.h>
11 : : #include <test/fuzz/FuzzedDataProvider.h>
12 : : #include <test/fuzz/fuzz.h>
13 : : #include <test/fuzz/util.h>
14 : : #include <test/util/setup_common.h>
15 : : #include <test/util/time.h>
16 : : #include <test/util/validation.h>
17 : : #include <validation.h>
18 : :
19 : : #include <ranges>
20 : : #include <vector>
21 : :
22 : : const TestingSetup* g_setup;
23 : :
24 : 74257 : CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter)
25 : : {
26 : 74257 : CBlockHeader header;
27 : 74257 : header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>();
28 : 74257 : header.hashPrevBlock = prev_hash;
29 : 74257 : header.hashMerkleRoot = uint256{}; // never used
30 : 74257 : header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>();
31 : 74257 : header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked).
32 : 74257 : header.nNonce = nonce_counter++; // prevent creating multiple block headers with the same hash
33 : 74257 : return header;
34 : : }
35 : :
36 : 1 : void initialize_block_index_tree()
37 : : {
38 [ + - + - : 1 : static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
+ - ]
39 : 1 : g_setup = testing_setup.get();
40 : 1 : }
41 : :
42 [ + - ]: 1003 : FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)
43 : : {
44 : 527 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
45 : 527 : FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};
46 [ + - ]: 527 : auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
47 : 527 : auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);
48 [ + - - + ]: 527 : CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];
49 : 527 : int nonce_counter = 0;
50 : 527 : std::vector<CBlockIndex*> blocks;
51 [ + - ]: 527 : blocks.push_back(genesis);
52 : 527 : bool abort_run{false};
53 : :
54 : 527 : std::vector<CBlockIndex*> pruned_blocks;
55 : :
56 [ + + + + ]: 137967 : LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 1000) {
57 [ + + ]: 137445 : if (abort_run) break;
58 [ + - ]: 137440 : CallOneOf(
59 : : fuzzed_data_provider,
60 : 85047 : [&] {
61 : : // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.
62 : 85047 : LOCK(cs_main);
63 : 85047 : CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks);
64 [ + + ]: 85047 : if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) {
65 [ + - ]: 74257 : CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter);
66 [ + - ]: 74257 : CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);
67 [ - + ]: 74257 : assert(index->nStatus & BLOCK_VALID_TREE);
68 [ - + ]: 74257 : assert(index->pprev == prev_block);
69 [ + - ]: 74257 : blocks.push_back(index);
70 : : }
71 : 85047 : },
72 : 21091 : [&] {
73 : : // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet
74 : 21091 : LOCK(cs_main);
75 : 21091 : CBlockIndex* index = PickValue(fuzzed_data_provider, blocks);
76 : : // Must be new to us and not known to be invalid (e.g. because of an invalid ancestor).
77 [ + + + + ]: 21091 : if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) {
78 [ + + ]: 14033 : if (fuzzed_data_provider.ConsumeBool()) { // Invalid
79 [ + - ]: 5141 : BlockValidationState state;
80 [ + - + - : 10282 : state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
+ - ]
81 [ + - ]: 5141 : chainman.InvalidBlockFound(index, state);
82 : 5141 : } else {
83 : 8892 : size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000);
84 : 8892 : CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value.
85 [ + - ]: 17784 : block.vtx = std::vector<CTransactionRef>(nTx);
86 [ + - ]: 8892 : FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));
87 [ + - ]: 8892 : chainman.ReceivedBlockTransactions(block, index, pos);
88 [ - + ]: 8892 : assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
89 [ - + ]: 8892 : assert(index->nStatus & BLOCK_HAVE_DATA);
90 : 8892 : }
91 : : }
92 : 21091 : },
93 : 7253 : [&] {
94 : : // Simplified ActivateBestChain(): Try to move to a chain with more work - with the possibility of finding blocks to be invalid on the way
95 : 7253 : LOCK(cs_main);
96 [ + - ]: 7253 : auto& chain = chainman.ActiveChain();
97 [ - + ]: 7253 : CBlockIndex* old_tip = chain.Tip();
98 [ - + ]: 7253 : assert(old_tip);
99 : 8340 : do {
100 [ + - ]: 8340 : CBlockIndex* best_tip = chainman.FindMostWorkChain();
101 [ - + ]: 8340 : assert(best_tip); // Should at least return current tip
102 [ - + + + ]: 16680 : if (best_tip == chain.Tip()) break; // Nothing to do
103 : : // Rewind chain to forking point
104 [ + - ]: 2699 : const CBlockIndex* fork = chain.FindFork(*best_tip);
105 : : // If we can't go back to the fork point due to pruned data, abort this run. In reality, a pruned node would also currently just crash in this scenario.
106 : : // This is very unlikely to happen due to the minimum pruning threshold of 550MiB.
107 [ - + ]: 2699 : CBlockIndex* it = chain.Tip();
108 [ + - + + ]: 4973 : while (it && it->nHeight != fork->nHeight) {
109 [ + + ]: 2282 : if (!(it->nStatus & BLOCK_HAVE_UNDO)) {
110 [ - + ]: 8 : assert(blockman.m_have_pruned);
111 : 8 : abort_run = true;
112 [ + - ]: 8 : return;
113 : : }
114 : 2274 : it = it->pprev;
115 : : }
116 [ + - + - ]: 5382 : chain.SetTip(*chain[fork->nHeight]);
117 : :
118 : : // Prepare new blocks to connect
119 : 2691 : std::vector<CBlockIndex*> to_connect;
120 : 2691 : it = best_tip;
121 [ + - + + ]: 8870 : while (it && it->nHeight != fork->nHeight) {
122 [ + - ]: 6179 : to_connect.push_back(it);
123 : 6179 : it = it->pprev;
124 : : }
125 : : // Connect blocks, possibly fail
126 [ + + ]: 5519 : for (CBlockIndex* block : to_connect | std::views::reverse) {
127 [ - + ]: 4814 : assert(!(block->nStatus & BLOCK_FAILED_VALID));
128 [ - + ]: 4814 : assert(block->nStatus & BLOCK_HAVE_DATA);
129 [ + + ]: 4814 : if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {
130 [ + + ]: 2801 : if (fuzzed_data_provider.ConsumeBool()) { // Invalid
131 [ + - ]: 1579 : BlockValidationState state;
132 [ + - + - : 3158 : state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
+ - ]
133 [ + - ]: 1579 : chainman.InvalidBlockFound(block, state);
134 : : // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation
135 [ + - ]: 1579 : chainman.InvalidChainFound(to_connect.front());
136 : 1579 : break;
137 : 1579 : } else {
138 : 1222 : block->RaiseValidity(BLOCK_VALID_SCRIPTS);
139 : 1222 : block->nStatus |= BLOCK_HAVE_UNDO;
140 : : }
141 : : }
142 [ + - ]: 3235 : chain.SetTip(*block);
143 [ + - + - ]: 3235 : chainman.ActiveChainstate().PruneBlockIndexCandidates();
144 : : // ActivateBestChainStep may release cs_main / not connect all blocks in one go - but only if we have at least as much chain work as we had at the start.
145 [ + - + + : 3235 : if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) {
+ + ]
146 : : break;
147 : : }
148 : : }
149 [ - + + - : 5382 : } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip));
+ + ]
150 [ - + + - : 14490 : assert(chain.Tip()->nChainWork >= old_tip->nChainWork);
- + ]
151 : 7253 : },
152 : 14730 : [&] {
153 : : // Prune chain - dealing with block files is beyond the scope of this test, so just prune random blocks, making no assumptions
154 : : // about what blocks are pruned together because they are in the same block file.
155 : : // Also don't prune blocks outside of the chain for now - this would make the fuzzer crash because of the problem described in
156 : : // https://github.com/bitcoin/bitcoin/issues/31512
157 : 14730 : LOCK(cs_main);
158 [ + - ]: 14730 : auto& chain = chainman.ActiveChain();
159 [ - + ]: 14730 : int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height());
160 [ + - ]: 14730 : CBlockIndex* prune_block{chain[prune_height]};
161 [ - + + + : 29460 : if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) {
+ + ]
162 : 5568 : blockman.m_have_pruned = true;
163 : 5568 : prune_block->nStatus &= ~BLOCK_HAVE_DATA;
164 : 5568 : prune_block->nStatus &= ~BLOCK_HAVE_UNDO;
165 : 5568 : prune_block->nFile = 0;
166 : 5568 : prune_block->nDataPos = 0;
167 : 5568 : prune_block->nUndoPos = 0;
168 : 5568 : auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev);
169 [ - + ]: 5568 : while (range.first != range.second) {
170 : 0 : std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first;
171 : 0 : range.first++;
172 [ # # ]: 0 : if (_it->second == prune_block) {
173 : 0 : blockman.m_blocks_unlinked.erase(_it);
174 : : }
175 : : }
176 [ + - ]: 5568 : pruned_blocks.push_back(prune_block);
177 : : }
178 : 14730 : },
179 : 9319 : [&] {
180 : : // Download a previously pruned block
181 : 9319 : LOCK(cs_main);
182 [ - + ]: 9319 : size_t num_pruned = pruned_blocks.size();
183 [ + + + - ]: 9319 : if (num_pruned == 0) return;
184 : 5261 : size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1);
185 [ - + ]: 5261 : CBlockIndex* index = pruned_blocks[i];
186 [ - + ]: 5261 : assert(!(index->nStatus & BLOCK_HAVE_DATA));
187 : 5261 : CBlock block;
188 [ + - ]: 10522 : block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value.
189 [ + - ]: 5261 : FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));
190 [ + - ]: 5261 : chainman.ReceivedBlockTransactions(block, index, pos);
191 [ - + ]: 5261 : assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
192 [ - + ]: 5261 : assert(index->nStatus & BLOCK_HAVE_DATA);
193 : 5261 : pruned_blocks.erase(pruned_blocks.begin() + i);
194 [ + - ]: 14580 : });
195 : : }
196 [ + + ]: 527 : if (!abort_run) {
197 [ + - ]: 519 : chainman.CheckBlockIndex();
198 : : }
199 : :
200 : : // clean up global state changed by last iteration and prepare for next iteration
201 : 527 : {
202 [ + - ]: 527 : LOCK(cs_main);
203 : 527 : genesis->nStatus |= BLOCK_HAVE_DATA;
204 : 527 : genesis->nStatus |= BLOCK_HAVE_UNDO;
205 : 527 : chainman.m_best_header = genesis;
206 [ + - ]: 527 : chainman.ResetBestInvalid();
207 : 527 : chainman.nBlockSequenceId = 2;
208 [ + - + - ]: 527 : chainman.ActiveChain().SetTip(*genesis);
209 [ + - ]: 527 : chainman.ActiveChainstate().setBlockIndexCandidates.clear();
210 : 527 : chainman.m_cached_is_ibd = true;
211 : 527 : blockman.m_blocks_unlinked.clear();
212 : 527 : blockman.m_have_pruned = false;
213 [ + - ]: 527 : blockman.CleanupForFuzzing();
214 : : // Delete all blocks but Genesis from block index
215 : 527 : uint256 genesis_hash = genesis->GetBlockHash();
216 [ + + ]: 75311 : for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) {
217 [ + + ]: 74784 : if (it->first != genesis_hash) {
218 : 74257 : it = blockman.m_block_index.erase(it);
219 : : } else {
220 : 527 : ++it;
221 : : }
222 : : }
223 [ + - + - ]: 527 : chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis);
224 [ - + ]: 527 : assert(blockman.m_block_index.size() == 1);
225 [ + - - + ]: 527 : assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1);
226 [ + - - + : 527 : assert(chainman.ActiveChain().Height() == 0);
- + ]
227 : 527 : }
228 : 527 : }
|