Bitcoin Core 31.99.0
P2P Digital Currency
block_index_tree.cpp
Go to the documentation of this file.
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>
12#include <test/fuzz/fuzz.h>
13#include <test/fuzz/util.h>
15#include <test/util/time.h>
17#include <validation.h>
18
19#include <ranges>
20#include <vector>
21
23
24CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter)
25{
26 CBlockHeader header;
27 header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>();
28 header.hashPrevBlock = prev_hash;
29 header.hashMerkleRoot = uint256{}; // never used
30 header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>();
31 header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked).
32 header.nNonce = nonce_counter++; // prevent creating multiple block headers with the same hash
33 return header;
34}
35
37{
38 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
39 g_setup = testing_setup.get();
40}
41
43{
44 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
46 auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
47 auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);
48 CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];
49 int nonce_counter = 0;
50 std::vector<CBlockIndex*> blocks;
51 blocks.push_back(genesis);
52 bool abort_run{false};
53
54 std::vector<CBlockIndex*> pruned_blocks;
55
57 {
58 if (abort_run) break;
61 [&] {
62 // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.
64 CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks);
65 if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) {
66 CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter);
67 CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);
68 assert(index->nStatus & BLOCK_VALID_TREE);
69 assert(index->pprev == prev_block);
70 blocks.push_back(index);
71 }
72 },
73 [&] {
74 // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet
77 // Must be new to us and not known to be invalid (e.g. because of an invalid ancestor).
78 if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) {
79 if (fuzzed_data_provider.ConsumeBool()) { // Invalid
81 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
82 chainman.InvalidBlockFound(index, state);
83 } else {
84 size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000);
85 CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value.
86 block.vtx = std::vector<CTransactionRef>(nTx);
88 chainman.ReceivedBlockTransactions(block, index, pos);
89 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
90 assert(index->nStatus & BLOCK_HAVE_DATA);
91 }
92 }
93 },
94 [&] {
95 // Simplified ActivateBestChain(): Try to move to a chain with more work - with the possibility of finding blocks to be invalid on the way
97 auto& chain = chainman.ActiveChain();
98 CBlockIndex* old_tip = chain.Tip();
99 assert(old_tip);
100 do {
101 CBlockIndex* best_tip = chainman.FindMostWorkChain();
102 assert(best_tip); // Should at least return current tip
103 if (best_tip == chain.Tip()) break; // Nothing to do
104 // Rewind chain to forking point
105 const CBlockIndex* fork = chain.FindFork(best_tip);
106 // 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.
107 // This is very unlikely to happen due to the minimum pruning threshold of 550MiB.
108 CBlockIndex* it = chain.Tip();
109 while (it && it->nHeight != fork->nHeight) {
110 if (!(it->nStatus & BLOCK_HAVE_UNDO)) {
111 assert(blockman.m_have_pruned);
112 abort_run = true;
113 return;
114 }
115 it = it->pprev;
116 }
117 chain.SetTip(*chain[fork->nHeight]);
118
119 // Prepare new blocks to connect
120 std::vector<CBlockIndex*> to_connect;
121 it = best_tip;
122 while (it && it->nHeight != fork->nHeight) {
123 to_connect.push_back(it);
124 it = it->pprev;
125 }
126 // Connect blocks, possibly fail
127 for (CBlockIndex* block : to_connect | std::views::reverse) {
128 assert(!(block->nStatus & BLOCK_FAILED_VALID));
129 assert(block->nStatus & BLOCK_HAVE_DATA);
130 if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {
131 if (fuzzed_data_provider.ConsumeBool()) { // Invalid
133 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
134 chainman.InvalidBlockFound(block, state);
135 // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation
136 chainman.InvalidChainFound(to_connect.front());
137 break;
138 } else {
139 block->RaiseValidity(BLOCK_VALID_SCRIPTS);
140 block->nStatus |= BLOCK_HAVE_UNDO;
141 }
142 }
143 chain.SetTip(*block);
144 chainman.ActiveChainstate().PruneBlockIndexCandidates();
145 // 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.
146 if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) {
147 break;
148 }
149 }
150 } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip));
151 assert(chain.Tip()->nChainWork >= old_tip->nChainWork);
152 },
153 [&] {
154 // Prune chain - dealing with block files is beyond the scope of this test, so just prune random blocks, making no assumptions
155 // about what blocks are pruned together because they are in the same block file.
156 // Also don't prune blocks outside of the chain for now - this would make the fuzzer crash because of the problem described in
157 // https://github.com/bitcoin/bitcoin/issues/31512
158 LOCK(cs_main);
159 auto& chain = chainman.ActiveChain();
160 int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height());
161 CBlockIndex* prune_block{chain[prune_height]};
162 if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) {
163 blockman.m_have_pruned = true;
164 prune_block->nStatus &= ~BLOCK_HAVE_DATA;
165 prune_block->nStatus &= ~BLOCK_HAVE_UNDO;
166 prune_block->nFile = 0;
167 prune_block->nDataPos = 0;
168 prune_block->nUndoPos = 0;
169 auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev);
170 while (range.first != range.second) {
171 std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first;
172 range.first++;
173 if (_it->second == prune_block) {
174 blockman.m_blocks_unlinked.erase(_it);
175 }
176 }
177 pruned_blocks.push_back(prune_block);
178 }
179 },
180 [&] {
181 // Download a previously pruned block
182 LOCK(cs_main);
183 size_t num_pruned = pruned_blocks.size();
184 if (num_pruned == 0) return;
185 size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1);
186 CBlockIndex* index = pruned_blocks[i];
187 assert(!(index->nStatus & BLOCK_HAVE_DATA));
188 CBlock block;
189 block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value.
191 chainman.ReceivedBlockTransactions(block, index, pos);
192 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
193 assert(index->nStatus & BLOCK_HAVE_DATA);
194 pruned_blocks.erase(pruned_blocks.begin() + i);
195 });
196 }
197 if (!abort_run) {
198 chainman.CheckBlockIndex();
199 }
200
201 // clean up global state changed by last iteration and prepare for next iteration
202 {
203 LOCK(cs_main);
204 genesis->nStatus |= BLOCK_HAVE_DATA;
205 genesis->nStatus |= BLOCK_HAVE_UNDO;
206 chainman.m_best_header = genesis;
207 chainman.ResetBestInvalid();
208 chainman.nBlockSequenceId = 2;
209 chainman.ActiveChain().SetTip(*genesis);
210 chainman.ActiveChainstate().setBlockIndexCandidates.clear();
211 chainman.m_cached_is_ibd = true;
212 blockman.m_blocks_unlinked.clear();
213 blockman.m_have_pruned = false;
214 blockman.CleanupForFuzzing();
215 // Delete all blocks but Genesis from block index
216 uint256 genesis_hash = genesis->GetBlockHash();
217 for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) {
218 if (it->first != genesis_hash) {
219 it = blockman.m_block_index.erase(it);
220 } else {
221 ++it;
222 }
223 }
224 chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis);
225 assert(blockman.m_block_index.size() == 1);
226 assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1);
227 assert(chainman.ActiveChain().Height() == 0);
228 }
229}
FUZZ_TARGET(block_index_tree,.init=initialize_block_index_tree)
CBlockHeader ConsumeBlockHeader(FuzzedDataProvider &provider, uint256 prev_hash, int &nonce_counter)
void initialize_block_index_tree()
const TestingSetup * g_setup
@ BLOCK_VALID_TRANSACTIONS
Only first tx is coinbase, 2 <= coinbase input script length <= 100, transactions valid,...
Definition: chain.h:61
@ BLOCK_VALID_SCRIPTS
Scripts & signatures ok.
Definition: chain.h:69
@ BLOCK_VALID_TREE
All parent headers found, difficulty matches, timestamp >= median previous.
Definition: chain.h:51
@ BLOCK_HAVE_UNDO
undo data available in rev*.dat
Definition: chain.h:76
@ BLOCK_HAVE_DATA
full block available in blk*.dat
Definition: chain.h:75
@ BLOCK_FAILED_VALID
stage after last reached validness failed
Definition: chain.h:79
const CChainParams & Params()
Return the currently selected parameters.
Nodes collect new transactions into a block, hash them into a hash tree, and scan through nonce value...
Definition: block.h:27
uint32_t nNonce
Definition: block.h:35
uint32_t nBits
Definition: block.h:34
uint32_t nTime
Definition: block.h:33
int32_t nVersion
Definition: block.h:30
uint256 hashPrevBlock
Definition: block.h:31
uint256 hashMerkleRoot
Definition: block.h:32
Definition: block.h:74
std::vector< CTransactionRef > vtx
Definition: block.h:77
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:94
CBlockIndex * pprev
pointer to the index of the predecessor of this block
Definition: chain.h:100
arith_uint256 nChainWork
(memory only) Total amount of work (expected number of hashes) in the chain up to and including this ...
Definition: chain.h:118
uint256 GetBlockHash() const
Definition: chain.h:198
unsigned int nTx
Number of transactions in this block.
Definition: chain.h:123
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:106
const CBlock & GenesisBlock() const
Definition: chainparams.h:94
T ConsumeIntegralInRange(T min, T max)
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition: time.h:40
bool Invalid(Result result, const std::string &reject_reason="", const std::string &debug_message="")
Definition: validation.h:88
256-bit opaque blob.
Definition: uint256.h:196
@ BLOCK_CONSENSUS
invalid by consensus rules (excluding any below reasons)
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:8
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
Definition: basic.cpp:8
node::NodeContext m_node
Definition: setup_common.h:63
Testing setup that configures a complete environment.
Definition: setup_common.h:118
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:72
#define LOCK(cs)
Definition: sync.h:268
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
Definition: util.cpp:34
auto & PickValue(FuzzedDataProvider &fuzzed_data_provider, Collection &col)
Definition: util.h:47
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:35
assert(!tx.IsCoinBase())
FuzzedDataProvider & fuzzed_data_provider
Definition: fees.cpp:39