/root/bitcoin/src/test/fuzz/block_index_tree.cpp
Line | Count | Source |
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 | | CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter) |
25 | 0 | { |
26 | 0 | CBlockHeader header; |
27 | 0 | header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>(); |
28 | 0 | header.hashPrevBlock = prev_hash; |
29 | 0 | header.hashMerkleRoot = uint256{}; // never used |
30 | 0 | header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>(); |
31 | 0 | header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked). |
32 | 0 | header.nNonce = nonce_counter++; // prevent creating multiple block headers with the same hash |
33 | 0 | return header; |
34 | 0 | } |
35 | | |
36 | | void initialize_block_index_tree() |
37 | 0 | { |
38 | 0 | static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); |
39 | 0 | g_setup = testing_setup.get(); |
40 | 0 | } |
41 | | |
42 | | FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree) |
43 | 0 | { |
44 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
45 | 0 | FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)}; |
46 | 0 | auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman); |
47 | 0 | auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman); |
48 | 0 | CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0]; |
49 | 0 | int nonce_counter = 0; |
50 | 0 | std::vector<CBlockIndex*> blocks; |
51 | 0 | blocks.push_back(genesis); |
52 | 0 | bool abort_run{false}; |
53 | |
|
54 | 0 | std::vector<CBlockIndex*> pruned_blocks; |
55 | |
|
56 | 0 | LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 1000) { |
57 | 0 | if (abort_run) break; Branch (57:13): [True: 0, False: 0]
|
58 | 0 | CallOneOf( |
59 | 0 | fuzzed_data_provider, |
60 | 0 | [&] { |
61 | | // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here. |
62 | 0 | LOCK(cs_main); |
63 | 0 | CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks); |
64 | 0 | if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) { Branch (64:21): [True: 0, False: 0]
|
65 | 0 | CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter); |
66 | 0 | CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header); |
67 | 0 | assert(index->nStatus & BLOCK_VALID_TREE); Branch (67:21): [True: 0, False: 0]
|
68 | 0 | assert(index->pprev == prev_block); Branch (68:21): [True: 0, False: 0]
|
69 | 0 | blocks.push_back(index); |
70 | 0 | } |
71 | 0 | }, |
72 | 0 | [&] { |
73 | | // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet |
74 | 0 | LOCK(cs_main); |
75 | 0 | 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 | 0 | if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) { Branch (77:21): [True: 0, False: 0]
Branch (77:40): [True: 0, False: 0]
|
78 | 0 | if (fuzzed_data_provider.ConsumeBool()) { // Invalid Branch (78:25): [True: 0, False: 0]
|
79 | 0 | BlockValidationState state; |
80 | 0 | state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid"); |
81 | 0 | chainman.InvalidBlockFound(index, state); |
82 | 0 | } else { |
83 | 0 | size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000); |
84 | 0 | CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value. |
85 | 0 | block.vtx = std::vector<CTransactionRef>(nTx); |
86 | 0 | FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000)); |
87 | 0 | chainman.ReceivedBlockTransactions(block, index, pos); |
88 | 0 | assert(index->nStatus & BLOCK_VALID_TRANSACTIONS); Branch (88:25): [True: 0, False: 0]
|
89 | 0 | assert(index->nStatus & BLOCK_HAVE_DATA); Branch (89:25): [True: 0, False: 0]
|
90 | 0 | } |
91 | 0 | } |
92 | 0 | }, |
93 | 0 | [&] { |
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 | 0 | LOCK(cs_main); |
96 | 0 | auto& chain = chainman.ActiveChain(); |
97 | 0 | CBlockIndex* old_tip = chain.Tip(); |
98 | 0 | assert(old_tip); Branch (98:17): [True: 0, False: 0]
|
99 | 0 | do { |
100 | 0 | CBlockIndex* best_tip = chainman.FindMostWorkChain(); |
101 | 0 | assert(best_tip); // Should at least return current tip Branch (101:21): [True: 0, False: 0]
|
102 | 0 | if (best_tip == chain.Tip()) break; // Nothing to do Branch (102:25): [True: 0, False: 0]
|
103 | | // Rewind chain to forking point |
104 | 0 | 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 | 0 | CBlockIndex* it = chain.Tip(); |
108 | 0 | while (it && it->nHeight != fork->nHeight) { Branch (108:28): [True: 0, False: 0]
Branch (108:34): [True: 0, False: 0]
|
109 | 0 | if (!(it->nStatus & BLOCK_HAVE_UNDO)) { Branch (109:29): [True: 0, False: 0]
|
110 | 0 | assert(blockman.m_have_pruned); Branch (110:29): [True: 0, False: 0]
|
111 | 0 | abort_run = true; |
112 | 0 | return; |
113 | 0 | } |
114 | 0 | it = it->pprev; |
115 | 0 | } |
116 | 0 | chain.SetTip(*chain[fork->nHeight]); |
117 | | |
118 | | // Prepare new blocks to connect |
119 | 0 | std::vector<CBlockIndex*> to_connect; |
120 | 0 | it = best_tip; |
121 | 0 | while (it && it->nHeight != fork->nHeight) { Branch (121:28): [True: 0, False: 0]
Branch (121:34): [True: 0, False: 0]
|
122 | 0 | to_connect.push_back(it); |
123 | 0 | it = it->pprev; |
124 | 0 | } |
125 | | // Connect blocks, possibly fail |
126 | 0 | for (CBlockIndex* block : to_connect | std::views::reverse) { Branch (126:45): [True: 0, False: 0]
|
127 | 0 | assert(!(block->nStatus & BLOCK_FAILED_VALID)); Branch (127:25): [True: 0, False: 0]
|
128 | 0 | assert(block->nStatus & BLOCK_HAVE_DATA); Branch (128:25): [True: 0, False: 0]
|
129 | 0 | if (!block->IsValid(BLOCK_VALID_SCRIPTS)) { Branch (129:29): [True: 0, False: 0]
|
130 | 0 | if (fuzzed_data_provider.ConsumeBool()) { // Invalid Branch (130:33): [True: 0, False: 0]
|
131 | 0 | BlockValidationState state; |
132 | 0 | state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid"); |
133 | 0 | chainman.InvalidBlockFound(block, state); |
134 | | // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation |
135 | 0 | chainman.InvalidChainFound(to_connect.front()); |
136 | 0 | break; |
137 | 0 | } else { |
138 | 0 | block->RaiseValidity(BLOCK_VALID_SCRIPTS); |
139 | 0 | block->nStatus |= BLOCK_HAVE_UNDO; |
140 | 0 | } |
141 | 0 | } |
142 | 0 | chain.SetTip(*block); |
143 | 0 | 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 | 0 | if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) { Branch (145:29): [True: 0, False: 0]
Branch (145:72): [True: 0, False: 0]
|
146 | 0 | break; |
147 | 0 | } |
148 | 0 | } |
149 | 0 | } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip)); Branch (149:26): [True: 0, False: 0]
|
150 | 0 | assert(chain.Tip()->nChainWork >= old_tip->nChainWork); Branch (150:17): [True: 0, False: 0]
|
151 | 0 | }, |
152 | 0 | [&] { |
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 | 0 | LOCK(cs_main); |
158 | 0 | auto& chain = chainman.ActiveChain(); |
159 | 0 | int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height()); |
160 | 0 | CBlockIndex* prune_block{chain[prune_height]}; |
161 | 0 | if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) { Branch (161:21): [True: 0, False: 0]
Branch (161:51): [True: 0, False: 0]
|
162 | 0 | blockman.m_have_pruned = true; |
163 | 0 | prune_block->nStatus &= ~BLOCK_HAVE_DATA; |
164 | 0 | prune_block->nStatus &= ~BLOCK_HAVE_UNDO; |
165 | 0 | prune_block->nFile = 0; |
166 | 0 | prune_block->nDataPos = 0; |
167 | 0 | prune_block->nUndoPos = 0; |
168 | 0 | auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev); |
169 | 0 | while (range.first != range.second) { Branch (169:28): [True: 0, False: 0]
|
170 | 0 | std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first; |
171 | 0 | range.first++; |
172 | 0 | if (_it->second == prune_block) { Branch (172:29): [True: 0, False: 0]
|
173 | 0 | blockman.m_blocks_unlinked.erase(_it); |
174 | 0 | } |
175 | 0 | } |
176 | 0 | pruned_blocks.push_back(prune_block); |
177 | 0 | } |
178 | 0 | }, |
179 | 0 | [&] { |
180 | | // Download a previously pruned block |
181 | 0 | LOCK(cs_main); |
182 | 0 | size_t num_pruned = pruned_blocks.size(); |
183 | 0 | if (num_pruned == 0) return; Branch (183:21): [True: 0, False: 0]
|
184 | 0 | size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1); |
185 | 0 | CBlockIndex* index = pruned_blocks[i]; |
186 | 0 | assert(!(index->nStatus & BLOCK_HAVE_DATA)); Branch (186:17): [True: 0, False: 0]
|
187 | 0 | CBlock block; |
188 | 0 | block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value. |
189 | 0 | FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000)); |
190 | 0 | chainman.ReceivedBlockTransactions(block, index, pos); |
191 | 0 | assert(index->nStatus & BLOCK_VALID_TRANSACTIONS); Branch (191:17): [True: 0, False: 0]
|
192 | 0 | assert(index->nStatus & BLOCK_HAVE_DATA); Branch (192:17): [True: 0, False: 0]
|
193 | 0 | pruned_blocks.erase(pruned_blocks.begin() + i); |
194 | 0 | }); |
195 | 0 | } |
196 | 0 | if (!abort_run) { Branch (196:9): [True: 0, False: 0]
|
197 | 0 | chainman.CheckBlockIndex(); |
198 | 0 | } |
199 | | |
200 | | // clean up global state changed by last iteration and prepare for next iteration |
201 | 0 | { |
202 | 0 | LOCK(cs_main); |
203 | 0 | genesis->nStatus |= BLOCK_HAVE_DATA; |
204 | 0 | genesis->nStatus |= BLOCK_HAVE_UNDO; |
205 | 0 | chainman.m_best_header = genesis; |
206 | 0 | chainman.ResetBestInvalid(); |
207 | 0 | chainman.nBlockSequenceId = 2; |
208 | 0 | chainman.ActiveChain().SetTip(*genesis); |
209 | 0 | chainman.ActiveChainstate().setBlockIndexCandidates.clear(); |
210 | 0 | chainman.m_cached_is_ibd = true; |
211 | 0 | blockman.m_blocks_unlinked.clear(); |
212 | 0 | blockman.m_have_pruned = false; |
213 | 0 | blockman.CleanupForFuzzing(); |
214 | | // Delete all blocks but Genesis from block index |
215 | 0 | uint256 genesis_hash = genesis->GetBlockHash(); |
216 | 0 | for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) { Branch (216:56): [True: 0, False: 0]
|
217 | 0 | if (it->first != genesis_hash) { Branch (217:17): [True: 0, False: 0]
|
218 | 0 | it = blockman.m_block_index.erase(it); |
219 | 0 | } else { |
220 | 0 | ++it; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis); |
224 | 0 | assert(blockman.m_block_index.size() == 1); Branch (224:9): [True: 0, False: 0]
|
225 | 0 | assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1); Branch (225:9): [True: 0, False: 0]
|
226 | 0 | assert(chainman.ActiveChain().Height() == 0); Branch (226:9): [True: 0, False: 0]
|
227 | 0 | } |
228 | 0 | } |