Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2018-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <index/blockfilterindex.h>7#include <blockfilter.h>8#include <chain.h>9#include <common/args.h>10#include <dbwrapper.h>11#include <flatfile.h>12#include <hash.h>13#include <index/base.h>14#include <index/db_key.h>15#include <interfaces/chain.h>16#include <interfaces/types.h>17#include <serialize.h>18#include <streams.h>19#include <sync.h>20#include <uint256.h>21#include <util/check.h>22#include <util/byte_units.h>23#include <util/fs.h>24#include <util/hasher.h>25#include <util/log.h>26#include <util/syserror.h>28#include <cerrno>29#include <exception>30#include <map>31#include <optional>32#include <stdexcept>33#include <string>34#include <tuple>35#include <utility>36#include <vector>38/* The index database stores three items for each block: the disk location of the encoded filter,39* its dSHA256 hash, and the header. Those belonging to blocks on the active chain are indexed by40* height, and those belonging to blocks that have been reorganized out of the active chain are41* indexed by block hash. This ensures that filter data for any block that becomes part of the42* active chain can always be retrieved, alleviating timing concerns.43*44* The filters themselves are stored in flat files and referenced by the LevelDB entries. This45* minimizes the amount of data written to LevelDB and keeps the database values constant size. The46* disk location of the next block filter to be written (represented as a FlatFilePos) is stored47* under the DB_FILTER_POS key.48*49* The logic for keys is shared with other indexes, see index/db_key.h.50*/51constexpr uint8_t DB_FILTER_POS{'P'};53constexpr unsigned int MAX_FLTR_FILE_SIZE{16_MiB};54/** The pre-allocation chunk size for fltr?????.dat files */55constexpr unsigned int FLTR_FILE_CHUNK_SIZE{1_MiB};56/** Maximum size of the cfheaders cache57* We have a limit to prevent a bug in filling this cache58* potentially turning into an OOM. At 2000 entries, this cache59* is big enough for a 2,000,000 length block chain, which60* we should be enough until ~2047. */61constexpr size_t CF_HEADERS_CACHE_MAX_SZ{2000};63namespace {65std::string BlockFilterThreadName(BlockFilterType filter_type)66{67switch (filter_type) {68case BlockFilterType::BASIC: return "blkfltbscidx";69case BlockFilterType::INVALID: return "";70} // no default case, so the compiler can warn about missing cases71assert(false);72}74struct DBVal {75uint256 hash;76uint256 header;77FlatFilePos pos;79SERIALIZE_METHODS(DBVal, obj) { READWRITE(obj.hash, obj.header, obj.pos); }80};82}; // namespace84static std::map<BlockFilterType, BlockFilterIndex> g_filter_indexes;86BlockFilterIndex::BlockFilterIndex(std::unique_ptr<interfaces::Chain> chain, BlockFilterType filter_type,87size_t n_cache_size, bool f_memory, bool f_wipe)88: BaseIndex(std::move(chain), BlockFilterTypeName(filter_type) + " block filter index", BlockFilterThreadName(filter_type))89, m_filter_type(filter_type)90{91const std::string& filter_name = BlockFilterTypeName(filter_type);92if (filter_name.empty()) throw std::invalid_argument("unknown filter_type");94fs::path path = gArgs.GetDataDirNet() / "indexes" / "blockfilter" / fs::u8path(filter_name);95fs::create_directories(path);97m_db = std::make_unique<BaseIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);98m_filter_fileseq = std::make_unique<FlatFileSeq>(std::move(path), "fltr", FLTR_FILE_CHUNK_SIZE);99}101interfaces::Chain::NotifyOptions BlockFilterIndex::CustomOptions()102{103interfaces::Chain::NotifyOptions options;104options.connect_undo_data = true;105return options;106}108bool BlockFilterIndex::CustomInit(const std::optional<interfaces::BlockRef>& block)109{110if (!m_db->Read(DB_FILTER_POS, m_next_filter_pos)) {111// Check that the cause of the read failure is that the key does not exist. Any other errors112// indicate database corruption or a disk failure, and starting the index would cause113// further corruption.114if (m_db->Exists(DB_FILTER_POS)) {115LogError("Cannot read current %s state; index may be corrupted",116GetName());117return false;118}120// If the DB_FILTER_POS is not set, then initialize to the first location.121m_next_filter_pos.nFile = 0;122m_next_filter_pos.nPos = 0;123}125if (block) {126auto op_last_header = ReadFilterHeader(block->height, block->hash);127if (!op_last_header) {128LogError("Cannot read last block filter header; index may be corrupted");129return false;130}131m_last_header = *op_last_header;132}134return true;135}137bool BlockFilterIndex::CustomCommit(CDBBatch& batch)138{139const FlatFilePos& pos = m_next_filter_pos;141// Flush current filter file to disk.142AutoFile file{m_filter_fileseq->Open(pos)};143if (file.IsNull()) {144LogError("Failed to open filter file %d", pos.nFile);145return false;146}147if (!file.Commit()) {148LogError("Failed to commit filter file %d", pos.nFile);149(void)file.fclose();150return false;151}152if (file.fclose() != 0) {153LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));154return false;155}157batch.Write(DB_FILTER_POS, pos);158return true;159}161bool BlockFilterIndex::ReadFilterFromDisk(const FlatFilePos& pos, const uint256& hash, BlockFilter& filter) const162{163AutoFile filein{m_filter_fileseq->Open(pos, true)};164if (filein.IsNull()) {165return false;166}168// Check that the hash of the encoded_filter matches the one stored in the db.169uint256 block_hash;170std::vector<uint8_t> encoded_filter;171try {172filein >> block_hash >> encoded_filter;173if (Hash(encoded_filter) != hash) {174LogError("Checksum mismatch in filter decode.");175return false;176}177filter = BlockFilter(GetFilterType(), block_hash, std::move(encoded_filter), /*skip_decode_check=*/true);178}179catch (const std::exception& e) {180LogError("Failed to deserialize block filter from disk: %s", e.what());181return false;182}184return true;185}187size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter& filter)188{189assert(filter.GetFilterType() == GetFilterType());191uint64_t data_size{192GetSerializeSize(filter.GetBlockHash()) +193GetSerializeSize(filter.GetEncodedFilter())};195// If writing the filter would overflow the file, flush and move to the next one.196if (pos.nPos + data_size > MAX_FLTR_FILE_SIZE) {197AutoFile last_file{m_filter_fileseq->Open(pos)};198if (last_file.IsNull()) {199LogError("Failed to open filter file %d", pos.nFile);200return 0;201}202if (!last_file.Truncate(pos.nPos)) {203LogError("Failed to truncate filter file %d", pos.nFile);204return 0;205}206if (!last_file.Commit()) {207LogError("Failed to commit filter file %d", pos.nFile);208(void)last_file.fclose();209return 0;210}211if (last_file.fclose() != 0) {212LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));213return 0;214}216pos.nFile++;217pos.nPos = 0;218}220// Pre-allocate sufficient space for filter data.221bool out_of_space;222m_filter_fileseq->Allocate(pos, data_size, out_of_space);223if (out_of_space) {224LogError("out of disk space");225return 0;226}228AutoFile fileout{m_filter_fileseq->Open(pos)};229if (fileout.IsNull()) {230LogError("Failed to open filter file %d", pos.nFile);231return 0;232}234fileout << filter.GetBlockHash() << filter.GetEncodedFilter();236if (fileout.fclose() != 0) {237LogError("Failed to close filter file %d: %s", pos.nFile, SysErrorString(errno));238return 0;239}241return data_size;242}244std::optional<uint256> BlockFilterIndex::ReadFilterHeader(int height, const uint256& expected_block_hash)245{246std::pair<uint256, DBVal> read_out;247if (!m_db->Read(index_util::DBHeightKey(height), read_out)) {248return std::nullopt;249}