diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -10,6 +10,15 @@ #include #include +/** Interval between compact filter checkpoints. See BIP 157. */ +static constexpr int CFCHECKPT_INTERVAL = 1000; + +struct FilterHeaderHasher { + size_t operator()(const uint256 &hash) const { + return ReadLE64(hash.begin()); + } +}; + /** * BlockFilterIndex is used to store and retrieve block filters, hashes, and * headers for a range of blocks by height. An index is constructed for each @@ -30,6 +39,10 @@ bool ReadFilterFromDisk(const FlatFilePos &pos, BlockFilter &filter) const; size_t WriteFilterToDisk(FlatFilePos &pos, const BlockFilter &filter); + Mutex m_cs_headers_cache; + std::unordered_map + m_headers_cache GUARDED_BY(m_cs_headers_cache); + protected: bool Init() override; @@ -57,7 +70,7 @@ /** Get a single filter header by block. */ bool LookupFilterHeader(const CBlockIndex *block_index, - uint256 &header_out) const; + uint256 &header_out); /** Get a range of filters between two heights on a chain. */ bool LookupFilterRange(int start_height, const CBlockIndex *stop_index, diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -39,6 +39,15 @@ // 1 MiB constexpr unsigned int FLTR_FILE_CHUNK_SIZE = 0x100000; +/** + * Maximum size of the cfheaders cache. + * We have a limit to prevent a bug in filling this cache + * potentially turning into an OOM. At 2000 entries, this cache + * is big enough for a 2,000,000 length block chain, which + * we should be enough until ~2047. + */ +constexpr size_t CF_HEADERS_CACHE_MAX_SZ{2000}; + namespace { struct DBVal { @@ -417,12 +426,31 @@ } bool BlockFilterIndex::LookupFilterHeader(const CBlockIndex *block_index, - uint256 &header_out) const { + uint256 &header_out) { + LOCK(m_cs_headers_cache); + + bool is_checkpoint{block_index->nHeight % CFCHECKPT_INTERVAL == 0}; + + if (is_checkpoint) { + // Try to find the block in the headers cache if this is a checkpoint + // height. + auto header = m_headers_cache.find(block_index->GetBlockHash()); + if (header != m_headers_cache.end()) { + header_out = header->second; + return true; + } + } + DBVal entry; if (!LookupOne(*m_db, block_index, entry)) { return false; } + if (is_checkpoint && m_headers_cache.size() < CF_HEADERS_CACHE_MAX_SZ) { + // Add to the headers cache if this is a checkpoint height. + m_headers_cache.emplace(block_index->GetBlockHash(), entry.header); + } + header_out = entry.header; return true; } diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -166,9 +166,6 @@ */ static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60; -/** Interval between compact filter checkpoints. See BIP 157. */ -static constexpr int CFCHECKPT_INTERVAL = 1000; - // Internal stuff namespace { /** Number of nodes with fSyncStarted. */ @@ -2257,7 +2254,7 @@ BlockFilterType filter_type, const BlockHash &stop_hash, const CBlockIndex *&stop_index, - const BlockFilterIndex *&filter_index) { + BlockFilterIndex *&filter_index) { const bool supported_filter_type = (filter_type == BlockFilterType::BASIC && gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)); @@ -2316,7 +2313,7 @@ static_cast(filter_type_ser); const CBlockIndex *stop_index; - const BlockFilterIndex *filter_index; + BlockFilterIndex *filter_index; if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, stop_hash, stop_index, filter_index)) { return;