Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
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 <util/check.h>
18#include <validation.h>
19
20#include <ranges>
21#include <unordered_map>
22#include <vector>
23
25
27{
28 CBlockHeader header;
29 header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>();
30 header.hashPrevBlock = prev_hash;
31 header.hashMerkleRoot = uint256{}; // never used
32 header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>();
33 header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked).
34 header.nNonce = nonce_counter++; // prevent creating multiple block headers with the same hash
35 return header;
36}
37
39{
40 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
41 g_setup = testing_setup.get();
42}
43
45{
46 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
48 auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);
49 auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);
50 CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];
51 int nonce_counter = 0;
52 std::vector<CBlockIndex*> blocks;
53 blocks.push_back(genesis);
54 bool abort_run{false};
55
56 std::vector<CBlockIndex*> pruned_blocks;
57
59 if (abort_run) break;
62 [&] {
63 // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.
66 if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) {
68 CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);
69 assert(index->nStatus & BLOCK_VALID_TREE);
70 assert(index->pprev == prev_block);
71 blocks.push_back(index);
72 }
73 },
74 [&] {
75 // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet
78 // Must be new to us and not known to be invalid (e.g. because of an invalid ancestor).
79 if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) {
80 if (fuzzed_data_provider.ConsumeBool()) { // Invalid
82 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
83 chainman.InvalidBlockFound(index, state);
84 } else {
85 size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000);
86 CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value.
87 block.vtx = std::vector<CTransactionRef>(nTx);
89 chainman.ReceivedBlockTransactions(block, index, pos);
90 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
91 assert(index->nStatus & BLOCK_HAVE_DATA);
92 }
93 }
94 },
95 [&] {
96 // Simplified ActivateBestChain(): Try to move to a chain with more work - with the possibility of finding blocks to be invalid on the way
98 auto& chain = chainman.ActiveChain();
99 CBlockIndex* old_tip = chain.Tip();
101 do {
102 CBlockIndex* best_tip = chainman.FindMostWorkChain();
103 assert(best_tip); // Should at least return current tip
104 if (best_tip == chain.Tip()) break; // Nothing to do
105 // Rewind chain to forking point
106 const CBlockIndex* fork = chain.FindFork(*best_tip);
107 // 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.
108 // This is very unlikely to happen due to the minimum pruning threshold of 550MiB.
109 CBlockIndex* it = chain.Tip();
110 while (it && it->nHeight != fork->nHeight) {
111 if (!(it->nStatus & BLOCK_HAVE_UNDO)) {
112 assert(blockman.m_have_pruned);
113 abort_run = true;
114 return;
115 }
116 it = it->pprev;
117 }
118 chain.SetTip(*chain[fork->nHeight]);
119
120 // Prepare new blocks to connect
121 std::vector<CBlockIndex*> to_connect;
122 it = best_tip;
123 while (it && it->nHeight != fork->nHeight) {
124 to_connect.push_back(it);
125 it = it->pprev;
126 }
127 // Connect blocks, possibly fail
128 for (CBlockIndex* block : to_connect | std::views::reverse) {
129 assert(!(block->nStatus & BLOCK_FAILED_VALID));
130 assert(block->nStatus & BLOCK_HAVE_DATA);
131 if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {
132 if (fuzzed_data_provider.ConsumeBool()) { // Invalid
134 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");
135 chainman.InvalidBlockFound(block, state);
136 // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation
137 chainman.InvalidChainFound(to_connect.front());
138 break;
139 } else {
140 block->RaiseValidity(BLOCK_VALID_SCRIPTS);
141 block->nStatus |= BLOCK_HAVE_UNDO;
142 }
143 }
144 chain.SetTip(*block);
145 chainman.ActiveChainstate().PruneBlockIndexCandidates();
146 // 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.
147 if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) {
148 break;
149 }
150 }
151 } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip));
152 assert(chain.Tip()->nChainWork >= old_tip->nChainWork);
153 },
154 [&] {
155 // Prune chain - dealing with block files is beyond the scope of this test, so just prune random blocks, making no assumptions
156 // about what blocks are pruned together because they are in the same block file.
157 // Also don't prune blocks outside of the chain for now - this would make the fuzzer crash because of the problem described in
158 // https://github.com/bitcoin/bitcoin/issues/31512
159 LOCK(cs_main);
160 auto& chain = chainman.ActiveChain();
161 int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height());
163 if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) {
164 blockman.m_have_pruned = true;
165 prune_block->nStatus &= ~BLOCK_HAVE_DATA;
166 prune_block->nStatus &= ~BLOCK_HAVE_UNDO;
167 prune_block->nFile = 0;
168 prune_block->nDataPos = 0;
169 prune_block->nUndoPos = 0;
170 auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev);
171 while (range.first != range.second) {
172 std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first;
173 range.first++;
174 if (_it->second == prune_block) {
175 blockman.m_blocks_unlinked.erase(_it);
176 }
177 }
178 pruned_blocks.push_back(prune_block);
179 }
180 },
181 [&] {
182 // Download a previously pruned block
183 LOCK(cs_main);
184 size_t num_pruned = pruned_blocks.size();
185 if (num_pruned == 0) return;
186 size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1);
187 CBlockIndex* index = pruned_blocks[i];
188 assert(!(index->nStatus & BLOCK_HAVE_DATA));
189 CBlock block;
190 block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value.
192 chainman.ReceivedBlockTransactions(block, index, pos);
193 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);
194 assert(index->nStatus & BLOCK_HAVE_DATA);
195 pruned_blocks.erase(pruned_blocks.begin() + i);
196 });
197 }
198 if (!abort_run) {
199 chainman.CheckBlockIndex();
200 }
201
202 // clean up global state changed by last iteration and prepare for next iteration
203 {
204 LOCK(cs_main);
205 genesis->nStatus |= BLOCK_HAVE_DATA;
206 genesis->nStatus |= BLOCK_HAVE_UNDO;
207 chainman.m_best_header = genesis;
208 chainman.ResetBestInvalid();
209 chainman.nBlockSequenceId = 2;
210 chainman.ActiveChain().SetTip(*genesis);
211 chainman.ActiveChainstate().setBlockIndexCandidates.clear();
212 chainman.m_cached_is_ibd = true;
213 blockman.m_blocks_unlinked.clear();
214 blockman.m_have_pruned = false;
215 blockman.CleanupForFuzzing();
216 // Delete all blocks but Genesis from block index
217 uint256 genesis_hash = genesis->GetBlockHash();
218 std::erase_if(blockman.m_block_index, [&](const auto& entry) { return entry.first != genesis_hash; });
219 chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis);
220 assert(blockman.m_block_index.size() == 1);
221 assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1);
222 assert(chainman.ActiveChain().Height() == 0);
223 }
224}
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
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
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition time.h:54
T ConsumeIntegralInRange(T min, T max)
bool Invalid(Result result, const std::string &reject_reason="", const std::string &debug_message="")
Definition validation.h:95
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 FUZZ_TARGET(...)
Definition fuzz.h:35
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition fuzz.h:22
Definition basic.cpp:11
node::NodeContext m_node
Testing setup that configures a complete environment.
std::unique_ptr< ChainstateManager > chainman
Definition context.h:74
#define LOCK(cs)
Definition sync.h:268
FuzzedDataProvider provider
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:58
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition util.h:38
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82
assert(!tx.IsCoinBase())
FuzzedDataProvider & fuzzed_data_provider
Definition fees.cpp:46
FakeNodeClock clock