fuzz coverage

Coverage Report

Created: 2025-09-17 22:41

/Users/eugenesiegel/btc/bitcoin/src/node/blockstorage.h
Line
Count
Source
1
// Copyright (c) 2011-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
#ifndef BITCOIN_NODE_BLOCKSTORAGE_H
6
#define BITCOIN_NODE_BLOCKSTORAGE_H
7
8
#include <attributes.h>
9
#include <chain.h>
10
#include <dbwrapper.h>
11
#include <flatfile.h>
12
#include <kernel/blockmanager_opts.h>
13
#include <kernel/chainparams.h>
14
#include <kernel/cs_main.h>
15
#include <kernel/messagestartchars.h>
16
#include <primitives/block.h>
17
#include <streams.h>
18
#include <sync.h>
19
#include <uint256.h>
20
#include <util/fs.h>
21
#include <util/hasher.h>
22
23
#include <array>
24
#include <atomic>
25
#include <cstdint>
26
#include <functional>
27
#include <limits>
28
#include <map>
29
#include <memory>
30
#include <optional>
31
#include <set>
32
#include <span>
33
#include <string>
34
#include <unordered_map>
35
#include <utility>
36
#include <vector>
37
38
class BlockValidationState;
39
class CBlockUndo;
40
class Chainstate;
41
class ChainstateManager;
42
namespace Consensus {
43
struct Params;
44
}
45
namespace util {
46
class SignalInterrupt;
47
} // namespace util
48
49
namespace kernel {
50
/** Access to the block database (blocks/index/) */
51
class BlockTreeDB : public CDBWrapper
52
{
53
public:
54
    using CDBWrapper::CDBWrapper;
55
    bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*>>& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo);
56
    bool ReadBlockFileInfo(int nFile, CBlockFileInfo& info);
57
    bool ReadLastBlockFile(int& nFile);
58
    bool WriteReindexing(bool fReindexing);
59
    void ReadReindexing(bool& fReindexing);
60
    bool WriteFlag(const std::string& name, bool fValue);
61
    bool ReadFlag(const std::string& name, bool& fValue);
62
    bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, const util::SignalInterrupt& interrupt)
63
        EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
64
};
65
} // namespace kernel
66
67
namespace node {
68
using kernel::BlockTreeDB;
69
70
/** The pre-allocation chunk size for blk?????.dat files (since 0.8) */
71
static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB
72
/** The pre-allocation chunk size for rev?????.dat files (since 0.8) */
73
static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB
74
/** The maximum size of a blk?????.dat file (since 0.8) */
75
static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB
76
77
/** Size of header written by WriteBlock before a serialized CBlock (8 bytes) */
78
static constexpr uint32_t STORAGE_HEADER_BYTES{std::tuple_size_v<MessageStartChars> + sizeof(unsigned int)};
79
80
/** Total overhead when writing undo data: header (8 bytes) plus checksum (32 bytes) */
81
static constexpr uint32_t UNDO_DATA_DISK_OVERHEAD{STORAGE_HEADER_BYTES + uint256::size()};
82
83
// Because validation code takes pointers to the map's CBlockIndex objects, if
84
// we ever switch to another associative container, we need to either use a
85
// container that has stable addressing (true of all std associative
86
// containers), or make the key a `std::unique_ptr<CBlockIndex>`
87
using BlockMap = std::unordered_map<uint256, CBlockIndex, BlockHasher>;
88
89
struct CBlockIndexWorkComparator {
90
    bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
91
};
92
93
struct CBlockIndexHeightOnlyComparator {
94
    /* Only compares the height of two block indices, doesn't try to tie-break */
95
    bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
96
};
97
98
struct CBlockIndexBlockHashComparator {
99
    /* Only used in fuzzing */
100
    bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
101
};
102
103
struct PruneLockInfo {
104
    int height_first{std::numeric_limits<int>::max()}; //! Height of earliest block that should be kept and not pruned
105
};
106
107
enum BlockfileType {
108
    // Values used as array indexes - do not change carelessly.
109
    NORMAL = 0,
110
    ASSUMED = 1,
111
    NUM_TYPES = 2,
112
};
113
114
std::ostream& operator<<(std::ostream& os, const BlockfileType& type);
115
116
struct BlockfileCursor {
117
    // The latest blockfile number.
118
    int file_num{0};
119
120
    // Track the height of the highest block in file_num whose undo
121
    // data has been written. Block data is written to block files in download
122
    // order, but is written to undo files in validation order, which is
123
    // usually in order by height. To avoid wasting disk space, undo files will
124
    // be trimmed whenever the corresponding block file is finalized and
125
    // the height of the highest block written to the block file equals the
126
    // height of the highest block written to the undo file. This is a
127
    // heuristic and can sometimes preemptively trim undo files that will write
128
    // more data later, and sometimes fail to trim undo files that can't have
129
    // more data written later.
130
    int undo_height{0};
131
};
132
133
std::ostream& operator<<(std::ostream& os, const BlockfileCursor& cursor);
134
135
136
/**
137
 * Maintains a tree of blocks (stored in `m_block_index`) which is consulted
138
 * to determine where the most-work tip is.
139
 *
140
 * This data is used mostly in `Chainstate` - information about, e.g.,
141
 * candidate tips is not maintained here.
142
 */
143
class BlockManager
144
{
145
    friend Chainstate;
146
    friend ChainstateManager;
147
148
private:
149
480k
    const CChainParams& GetParams() const { return m_opts.chainparams; }
150
544k
    const Consensus::Params& GetConsensus() const { return m_opts.chainparams.GetConsensus(); }
151
    /**
152
     * Load the blocktree off disk and into memory. Populate certain metadata
153
     * per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral
154
     * collections like m_dirty_blockindex.
155
     */
156
    bool LoadBlockIndex(const std::optional<uint256>& snapshot_blockhash)
157
        EXCLUSIVE_LOCKS_REQUIRED(cs_main);
158
159
    /** Return false if block file or undo file flushing fails. */
160
    [[nodiscard]] bool FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo);
161
162
    /** Return false if undo file flushing fails. */
163
    [[nodiscard]] bool FlushUndoFile(int block_file, bool finalize = false);
164
165
    /**
166
     * Helper function performing various preparations before a block can be saved to disk:
167
     * Returns the correct position for the block to be saved, which may be in the current or a new
168
     * block file depending on nAddSize. May flush the previous blockfile to disk if full, updates
169
     * blockfile info, and checks if there is enough disk space to save the block.
170
     *
171
     * The nAddSize argument passed to this function should include not just the size of the serialized CBlock, but also the size of
172
     * separator fields (STORAGE_HEADER_BYTES).
173
     */
174
    [[nodiscard]] FlatFilePos FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime);
175
    [[nodiscard]] bool FlushChainstateBlockFile(int tip_height);
176
    bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize);
177
178
    AutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const;
179
180
    /* Calculate the block/rev files to delete based on height specified by user with RPC command pruneblockchain */
181
    void FindFilesToPruneManual(
182
        std::set<int>& setFilesToPrune,
183
        int nManualPruneHeight,
184
        const Chainstate& chain,
185
        ChainstateManager& chainman);
186
187
    /**
188
     * Prune block and undo files (blk???.dat and rev???.dat) so that the disk space used is less than a user-defined target.
189
     * The user sets the target (in MB) on the command line or in config file.  This will be run on startup and whenever new
190
     * space is allocated in a block or undo file, staying below the target. Changing back to unpruned requires a reindex
191
     * (which in this case means the blockchain must be re-downloaded.)
192
     *
193
     * Pruning functions are called from FlushStateToDisk when the m_check_for_pruning flag has been set.
194
     * Block and undo files are deleted in lock-step (when blk00003.dat is deleted, so is rev00003.dat.)
195
     * Pruning cannot take place until the longest chain is at least a certain length (CChainParams::nPruneAfterHeight).
196
     * Pruning will never delete a block within a defined distance (currently 288) from the active chain's tip.
197
     * The block index is updated by unsetting HAVE_DATA and HAVE_UNDO for any blocks that were stored in the deleted files.
198
     * A db flag records the fact that at least some block files have been pruned.
199
     *
200
     * @param[out]   setFilesToPrune   The set of file indices that can be unlinked will be returned
201
     * @param        last_prune        The last height we're able to prune, according to the prune locks
202
     */
203
    void FindFilesToPrune(
204
        std::set<int>& setFilesToPrune,
205
        int last_prune,
206
        const Chainstate& chain,
207
        ChainstateManager& chainman);
208
209
    RecursiveMutex cs_LastBlockFile;
210
    std::vector<CBlockFileInfo> m_blockfile_info;
211
212
    //! Since assumedvalid chainstates may be syncing a range of the chain that is very
213
    //! far away from the normal/background validation process, we should segment blockfiles
214
    //! for assumed chainstates. Otherwise, we might have wildly different height ranges
215
    //! mixed into the same block files, which would impair our ability to prune
216
    //! effectively.
217
    //!
218
    //! This data structure maintains separate blockfile number cursors for each
219
    //! BlockfileType. The ASSUMED state is initialized, when necessary, in FindNextBlockPos().
220
    //!
221
    //! The first element is the NORMAL cursor, second is ASSUMED.
222
    std::array<std::optional<BlockfileCursor>, BlockfileType::NUM_TYPES>
223
        m_blockfile_cursors GUARDED_BY(cs_LastBlockFile) = {
224
            BlockfileCursor{},
225
            std::nullopt,
226
    };
227
    int MaxBlockfileNum() const EXCLUSIVE_LOCKS_REQUIRED(cs_LastBlockFile)
228
53.1k
    {
229
53.1k
        static const BlockfileCursor empty_cursor;
230
53.1k
        const auto& normal = m_blockfile_cursors[BlockfileType::NORMAL].value_or(empty_cursor);
231
53.1k
        const auto& assumed = m_blockfile_cursors[BlockfileType::ASSUMED].value_or(empty_cursor);
232
53.1k
        return std::max(normal.file_num, assumed.file_num);
233
53.1k
    }
234
235
    /** Global flag to indicate we should check to see if there are
236
     *  block/undo files that should be deleted.  Set on startup
237
     *  or if we allocate more file space when we're in prune mode
238
     */
239
    bool m_check_for_pruning = false;
240
241
    const bool m_prune_mode;
242
243
    const Obfuscation m_obfuscation;
244
245
    /** Dirty block index entries. */
246
    std::set<CBlockIndex*, CBlockIndexBlockHashComparator> m_dirty_blockindex;
247
248
    /** Dirty block file entries. */
249
    std::set<int> m_dirty_fileinfo;
250
251
    /**
252
     * Map from external index name to oldest block that must not be pruned.
253
     *
254
     * @note Internally, only blocks at height (height_first - PRUNE_LOCK_BUFFER - 1) and
255
     * below will be pruned, but callers should avoid assuming any particular buffer size.
256
     */
257
    std::unordered_map<std::string, PruneLockInfo> m_prune_locks GUARDED_BY(::cs_main);
258
259
    BlockfileType BlockfileTypeForHeight(int height);
260
261
    const kernel::BlockManagerOpts m_opts;
262
263
    const FlatFileSeq m_block_file_seq;
264
    const FlatFileSeq m_undo_file_seq;
265
266
public:
267
    using Options = kernel::BlockManagerOpts;
268
269
    explicit BlockManager(const util::SignalInterrupt& interrupt, Options opts);
270
271
    const util::SignalInterrupt& m_interrupt;
272
    std::atomic<bool> m_importing{false};
273
274
    /**
275
     * Whether all blockfiles have been added to the block tree database.
276
     * Normally true, but set to false when a reindex is requested and the
277
     * database is wiped. The value is persisted in the database across restarts
278
     * and will be false until reindexing completes.
279
     */
280
    std::atomic_bool m_blockfiles_indexed{true};
281
282
    BlockMap m_block_index GUARDED_BY(cs_main);
283
284
    /**
285
     * The height of the base block of an assumeutxo snapshot, if one is in use.
286
     *
287
     * This controls how blockfiles are segmented by chainstate type to avoid
288
     * comingling different height regions of the chain when an assumedvalid chainstate
289
     * is in use. If heights are drastically different in the same blockfile, pruning
290
     * suffers.
291
     *
292
     * This is set during ActivateSnapshot() or upon LoadBlockIndex() if a snapshot
293
     * had been previously loaded. After the snapshot is validated, this is unset to
294
     * restore normal LoadBlockIndex behavior.
295
     */
296
    std::optional<int> m_snapshot_height;
297
298
    std::vector<CBlockIndex*> GetAllBlockIndices() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
299
300
    /**
301
     * All pairs A->B, where A (or one of its ancestors) misses transactions, but B has transactions.
302
     * Pruned nodes may have entries where B is missing data.
303
     */
304
    std::multimap<CBlockIndex*, CBlockIndex*> m_blocks_unlinked;
305
306
    std::unique_ptr<BlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main);
307
308
    bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
309
    bool LoadBlockIndexDB(const std::optional<uint256>& snapshot_blockhash)
310
        EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
311
312
    /**
313
     * Remove any pruned block & undo files that are still on disk.
314
     * This could happen on some systems if the file was still being read while unlinked,
315
     * or if we crash before unlinking.
316
     */
317
    void ScanAndUnlinkAlreadyPrunedFiles() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
318
319
    CBlockIndex* AddToBlockIndex(const CBlockHeader& block, CBlockIndex*& best_header) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
320
    /** Create a new block index entry for a given block hash */
321
    CBlockIndex* InsertBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
322
323
    //! Mark one block file as pruned (modify associated database entries)
324
    void PruneOneBlockFile(const int fileNumber) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
325
326
    CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
327
    const CBlockIndex* LookupBlockIndex(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
328
329
    /** Get block file info entry for one block file */
330
    CBlockFileInfo* GetBlockFileInfo(size_t n);
331
332
    bool WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
333
        EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
334
335
    /** Store block on disk and update block file statistics.
336
     *
337
     * @param[in]  block        the block to be stored
338
     * @param[in]  nHeight      the height of the block
339
     *
340
     * @returns in case of success, the position to which the block was written to
341
     *          in case of an error, an empty FlatFilePos
342
     */
343
    FlatFilePos WriteBlock(const CBlock& block, int nHeight);
344
345
    /** Update blockfile info while processing a block during reindex. The block must be available on disk.
346
     *
347
     * @param[in]  block        the block being processed
348
     * @param[in]  nHeight      the height of the block
349
     * @param[in]  pos          the position of the serialized CBlock on disk
350
     */
351
    void UpdateBlockInfo(const CBlock& block, unsigned int nHeight, const FlatFilePos& pos);
352
353
    /** Whether running in -prune mode. */
354
996k
    [[nodiscard]] bool IsPruneMode() const { return m_prune_mode; }
355
356
    /** Attempt to stay below this number of bytes of block files. */
357
77.7k
    [[nodiscard]] uint64_t GetPruneTarget() const { return m_opts.prune_target; }
358
    static constexpr auto PRUNE_TARGET_MANUAL{std::numeric_limits<uint64_t>::max()};
359
360
5.78M
    [[nodiscard]] bool LoadingBlocks() const { return m_importing || !m_blockfiles_indexed; }
361
362
    /** Calculate the amount of disk space the block & undo files currently use */
363
    uint64_t CalculateCurrentUsage();
364
365
    //! Check if all blocks in the [upper_block, lower_block] range have data available.
366
    //! The caller is responsible for ensuring that lower_block is an ancestor of upper_block
367
    //! (part of the same chain).
368
    bool CheckBlockDataAvailability(const CBlockIndex& upper_block LIFETIMEBOUND, const CBlockIndex& lower_block LIFETIMEBOUND) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
369
370
    /**
371
     * @brief Returns the earliest block with specified `status_mask` flags set after
372
     * the latest block _not_ having those flags.
373
     *
374
     * This function starts from `upper_block`, which must have all
375
     * `status_mask` flags set, and iterates backwards through its ancestors. It
376
     * continues as long as each block has all `status_mask` flags set, until
377
     * reaching the oldest ancestor or `lower_block`.
378
     *
379
     * @pre `upper_block` must have all `status_mask` flags set.
380
     * @pre `lower_block` must be null or an ancestor of `upper_block`
381
     *
382
     * @param upper_block The starting block for the search, which must have all
383
     *                    `status_mask` flags set.
384
     * @param status_mask Bitmask specifying required status flags.
385
     * @param lower_block The earliest possible block to return. If null, the
386
     *                    search can extend to the genesis block.
387
     *
388
     * @return A non-null pointer to the earliest block between `upper_block`
389
     *         and `lower_block`, inclusive, such that every block between the
390
     *         returned block and `upper_block` has `status_mask` flags set.
391
     */
392
    const CBlockIndex* GetFirstBlock(
393
        const CBlockIndex& upper_block LIFETIMEBOUND,
394
        uint32_t status_mask,
395
        const CBlockIndex* lower_block = nullptr
396
    ) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
397
398
    /** True if any block files have ever been pruned. */
399
    bool m_have_pruned = false;
400
401
    //! Check whether the block associated with this index entry is pruned or not.
402
    bool IsBlockPruned(const CBlockIndex& block) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
403
404
    //! Create or update a prune lock identified by its name
405
    void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
406
407
    /** Open a block file (blk?????.dat) */
408
    AutoFile OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const;
409
410
    /** Translation to a filesystem path */
411
    fs::path GetBlockPosFilename(const FlatFilePos& pos) const;
412
413
    /**
414
     *  Actually unlink the specified files
415
     */
416
    void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune) const;
417
418
    /** Functions for disk access for blocks */
419
    bool ReadBlock(CBlock& block, const FlatFilePos& pos, const std::optional<uint256>& expected_hash) const;
420
    bool ReadBlock(CBlock& block, const CBlockIndex& index) const;
421
    bool ReadRawBlock(std::vector<std::byte>& block, const FlatFilePos& pos) const;
422
423
    bool ReadBlockUndo(CBlockUndo& blockundo, const CBlockIndex& index) const;
424
425
    void CleanupBlockRevFiles() const;
426
};
427
428
// Calls ActivateBestChain() even if no blocks are imported.
429
void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths);
430
} // namespace node
431
432
#endif // BITCOIN_NODE_BLOCKSTORAGE_H