Bitcoin Core 30.99.0
P2P Digital Currency
block_index_tree.cpp
Go to the documentation of this file.
1// Copyright (c) 2020-2022 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>
16#include <validation.h>
17
18#include <ranges>
19#include <vector>
20
22
23CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter)
24{
25 CBlockHeader header;
26 header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>();
27 header.hashPrevBlock = prev_hash;
28 header.hashMerkleRoot = uint256{}; // never used
29 header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>();
30 header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked).
31 header.nNonce = nonce_counter++; // prevent creating multiple block headers with the same hash
32 return header;
33}
34
36{
37 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
38 g_setup = testing_setup.get();
39}
40
42{
43 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
45 auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
46 auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);
47 CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];
48 int nonce_counter = 0;
49 std::vector<CBlockIndex*> blocks;
50 blocks.push_back(genesis);
51 bool abort_run{false};
52
53 std::vector<CBlockIndex*> pruned_blocks;
54
56 {
57 if (abort_run) break;
60 [&] {
61 // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.
63 CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks);
64 if (!(prev_block->nStatus & BLOCK_FAILED_MASK)) {
65 CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter);
66 CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);
67 assert(index->nStatus & BLOCK_VALID_TREE);
68 assert(index->pprev == prev_block);
69 blocks.push_back(index);
70 }
71 },
72 [&] {
73 // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet
76 // Must be new to us and not known to be invalid (e.g. because of an invalid ancestor).
77 if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_MASK)) {
78 if (fuzzed_data_provider.ConsumeBool()) { // Invalid
80 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
81 chainman.InvalidBlockFound(index, state);
82 } else {
83 size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000);
84 CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value.
85 block.vtx = std::vector<CTransactionRef>(nTx);
87 chainman.ReceivedBlockTransactions(block, index, pos);
88 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
89 assert(index->nStatus & BLOCK_HAVE_DATA);
90 }
91 }
92 },
93 [&] {
94 // Simplified ActivateBestChain(): Try to move to a chain with more work - with the possibility of finding blocks to be invalid on the way
96 auto& chain = chainman.ActiveChain();
97 CBlockIndex* old_tip = chain.Tip();
98 assert(old_tip);
99 do {
100 CBlockIndex* best_tip = chainman.FindMostWorkChain();
101 assert(best_tip); // Should at least return current tip
102 if (best_tip == chain.Tip()) break; // Nothing to do
103 // Rewind chain to forking point
104 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 CBlockIndex* it = chain.Tip();
108 while (it && it->nHeight != fork->nHeight) {
109 if (!(it->nStatus & BLOCK_HAVE_UNDO)) {
110 assert(blockman.m_have_pruned);
111 abort_run = true;
112 return;
113 }
114 it = it->pprev;
115 }
116 chain.SetTip(*chain[fork->nHeight]);
117
118 // Prepare new blocks to connect
119 std::vector<CBlockIndex*> to_connect;
120 it = best_tip;
121 while (it && it->nHeight != fork->nHeight) {
122 to_connect.push_back(it);
123 it = it->pprev;
124 }
125 // Connect blocks, possibly fail
126 for (CBlockIndex* block : to_connect | std::views::reverse) {
127 assert(!(block->nStatus & BLOCK_FAILED_MASK));
128 assert(block->nStatus & BLOCK_HAVE_DATA);
129 if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {
130 if (fuzzed_data_provider.ConsumeBool()) { // Invalid
132 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
133 chainman.InvalidBlockFound(block, state);
134 // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation
135 chainman.InvalidChainFound(to_connect.front());
136 break;
137 } else {
138 block->RaiseValidity(BLOCK_VALID_SCRIPTS);
139 block->nStatus |= BLOCK_HAVE_UNDO;
140 }
141 }
142 chain.SetTip(*block);
143 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 if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) {
146 break;
147 }
148 }
149 } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip));
150 assert(chain.Tip()->nChainWork >= old_tip->nChainWork);
151 },
152 [&] {
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 LOCK(cs_main);
158 auto& chain = chainman.ActiveChain();
159 int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height());
160 CBlockIndex* prune_block{chain[prune_height]};
161 if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) {
162 blockman.m_have_pruned = true;
163 prune_block->nStatus &= ~BLOCK_HAVE_DATA;
164 prune_block->nStatus &= ~BLOCK_HAVE_UNDO;
165 prune_block->nFile = 0;
166 prune_block->nDataPos = 0;
167 prune_block->nUndoPos = 0;
168 auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev);
169 while (range.first != range.second) {
170 std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first;
171 range.first++;
172 if (_it->second == prune_block) {
173 blockman.m_blocks_unlinked.erase(_it);
174 }
175 }
176 pruned_blocks.push_back(prune_block);
177 }
178 },
179 [&] {
180 // Download a previously pruned block
181 LOCK(cs_main);
182 size_t num_pruned = pruned_blocks.size();
183 if (num_pruned == 0) return;
184 size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1);
185 CBlockIndex* index = pruned_blocks[i];
186 assert(!(index->nStatus & BLOCK_HAVE_DATA));
187 CBlock block;
188 block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value.
190 chainman.ReceivedBlockTransactions(block, index, pos);
191 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
192 assert(index->nStatus & BLOCK_HAVE_DATA);
193 pruned_blocks.erase(pruned_blocks.begin() + i);
194 });
195 }
196 if (!abort_run) {
197 chainman.CheckBlockIndex();
198 }
199
200 // clean up global state changed by last iteration and prepare for next iteration
201 {
202 LOCK(cs_main);
203 genesis->nStatus |= BLOCK_HAVE_DATA;
204 genesis->nStatus |= BLOCK_HAVE_UNDO;
205 chainman.m_best_header = genesis;
206 chainman.ResetBestInvalid();
207 chainman.nBlockSequenceId = 2;
208 chainman.ActiveChain().SetTip(*genesis);
209 chainman.ActiveChainstate().setBlockIndexCandidates.clear();
210 chainman.m_cached_finished_ibd = false;
211 blockman.m_blocks_unlinked.clear();
212 blockman.m_have_pruned = false;
213 blockman.CleanupForFuzzing();
214 // Delete all blocks but Genesis from block index
215 uint256 genesis_hash = genesis->GetBlockHash();
216 for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) {
217 if (it->first != genesis_hash) {
218 it = blockman.m_block_index.erase(it);
219 } else {
220 ++it;
221 }
222 }
223 chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis);
224 assert(blockman.m_block_index.size() == 1);
225 assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1);
226 assert(chainman.ActiveChain().Height() == 0);
227 }
228}
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_MASK
Definition: chain.h:81
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:22
uint32_t nNonce
Definition: block.h:30
uint32_t nBits
Definition: block.h:29
uint32_t nTime
Definition: block.h:28
int32_t nVersion
Definition: block.h:25
uint256 hashPrevBlock
Definition: block.h:26
uint256 hashMerkleRoot
Definition: block.h:27
Definition: block.h:69
std::vector< CTransactionRef > vtx
Definition: block.h:72
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:95
CBlockIndex * pprev
pointer to the index of the predecessor of this block
Definition: chain.h:101
arith_uint256 nChainWork
(memory only) Total amount of work (expected number of hashes) in the chain up to and including this ...
Definition: chain.h:119
uint256 GetBlockHash() const
Definition: chain.h:199
unsigned int nTx
Number of transactions in this block.
Definition: chain.h:124
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:107
const CBlock & GenesisBlock() const
Definition: chainparams.h:95
T ConsumeIntegralInRange(T min, T max)
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:195
@ 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
node::NodeContext m_node
Definition: setup_common.h:66
Testing setup that configures a complete environment.
Definition: setup_common.h:121
std::unique_ptr< ChainstateManager > chainman
Definition: context.h:72
#define LOCK(cs)
Definition: sync.h:259
int64_t 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
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:40
assert(!tx.IsCoinBase())
FuzzedDataProvider & fuzzed_data_provider
Definition: fees.cpp:38