diff --git a/src/index/blockfilterindex.h b/src/index/blockfilterindex.h --- a/src/index/blockfilterindex.h +++ b/src/index/blockfilterindex.h @@ -27,6 +27,7 @@ FlatFilePos m_next_filter_pos; std::unique_ptr m_filter_fileseq; + bool ReadFilterFromDisk(const FlatFilePos &pos, BlockFilter &filter) const; size_t WriteFilterToDisk(FlatFilePos &pos, const BlockFilter &filter); protected: @@ -49,6 +50,22 @@ bool f_memory = false, bool f_wipe = false); BlockFilterType GetFilterType() const { return m_filter_type; } + + /** Get a single filter by block. */ + bool LookupFilter(const CBlockIndex *block_index, + BlockFilter &filter_out) const; + + /** Get a single filter header by block. */ + bool LookupFilterHeader(const CBlockIndex *block_index, + uint256 &header_out) const; + + /** Get a range of filters between two heights on a chain. */ + bool LookupFilterRange(int start_height, const CBlockIndex *stop_index, + std::vector &filters_out) const; + + /** Get a range of filter hashes between two heights on a chain. */ + bool LookupFilterHashRange(int start_height, const CBlockIndex *stop_index, + std::vector &hashes_out) const; }; #endif // BITCOIN_INDEX_BLOCKFILTERINDEX_H diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include #include @@ -153,6 +154,28 @@ return BaseIndex::CommitInternal(batch); } +bool BlockFilterIndex::ReadFilterFromDisk(const FlatFilePos &pos, + BlockFilter &filter) const { + CAutoFile filein(m_filter_fileseq->Open(pos, true), SER_DISK, + CLIENT_VERSION); + if (filein.IsNull()) { + return false; + } + + BlockHash block_hash; + std::vector encoded_filter; + try { + filein >> block_hash >> encoded_filter; + filter = + BlockFilter(GetFilterType(), block_hash, std::move(encoded_filter)); + } catch (const std::exception &e) { + return error("%s: Failed to deserialize block filter from disk: %s", + __func__, e.what()); + } + + return true; +} + size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos &pos, const BlockFilter &filter) { assert(filter.GetFilterType() == GetFilterType()); @@ -302,3 +325,140 @@ return BaseIndex::Rewind(current_tip, new_tip); } + +static bool LookupOne(const CDBWrapper &db, const CBlockIndex *block_index, + DBVal &result) { + // First check if the result is stored under the height index and the value + // there matches the block hash. This should be the case if the block is on + // the active chain. + std::pair read_out; + if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) { + return false; + } + if (read_out.first == block_index->GetBlockHash()) { + result = std::move(read_out.second); + return true; + } + + // If value at the height index corresponds to an different block, the + // result will be stored in the hash index. + return db.Read(DBHashKey(block_index->GetBlockHash()), result); +} + +static bool LookupRange(CDBWrapper &db, const std::string &index_name, + int start_height, const CBlockIndex *stop_index, + std::vector &results) { + if (start_height < 0) { + return error("%s: start height (%d) is negative", __func__, + start_height); + } + if (start_height > stop_index->nHeight) { + return error("%s: start height (%d) is greater than stop height (%d)", + __func__, start_height, stop_index->nHeight); + } + + size_t results_size = + static_cast(stop_index->nHeight - start_height + 1); + std::vector> values(results_size); + + DBHeightKey key(start_height); + std::unique_ptr db_it(db.NewIterator()); + db_it->Seek(DBHeightKey(start_height)); + for (int height = start_height; height <= stop_index->nHeight; ++height) { + if (!db_it->Valid() || !db_it->GetKey(key) || key.height != height) { + return false; + } + + size_t i = static_cast(height - start_height); + if (!db_it->GetValue(values[i])) { + return error("%s: unable to read value in %s at key (%c, %d)", + __func__, index_name, DB_BLOCK_HEIGHT, height); + } + + db_it->Next(); + } + + results.resize(results_size); + + // Iterate backwards through block indexes collecting results in order to + // access the block hash of each entry in case we need to look it up in the + // hash index. + for (const CBlockIndex *block_index = stop_index; + block_index && block_index->nHeight >= start_height; + block_index = block_index->pprev) { + BlockHash block_hash = block_index->GetBlockHash(); + + size_t i = static_cast(block_index->nHeight - start_height); + if (block_hash == values[i].first) { + results[i] = std::move(values[i].second); + continue; + } + + if (!db.Read(DBHashKey(block_hash), results[i])) { + return error("%s: unable to read value in %s at key (%c, %s)", + __func__, index_name, DB_BLOCK_HASH, + block_hash.ToString()); + } + } + + return true; +} + +bool BlockFilterIndex::LookupFilter(const CBlockIndex *block_index, + BlockFilter &filter_out) const { + DBVal entry; + if (!LookupOne(*m_db, block_index, entry)) { + return false; + } + + return ReadFilterFromDisk(entry.pos, filter_out); +} + +bool BlockFilterIndex::LookupFilterHeader(const CBlockIndex *block_index, + uint256 &header_out) const { + DBVal entry; + if (!LookupOne(*m_db, block_index, entry)) { + return false; + } + + header_out = entry.header; + return true; +} + +bool BlockFilterIndex::LookupFilterRange( + int start_height, const CBlockIndex *stop_index, + std::vector &filters_out) const { + std::vector entries; + if (!LookupRange(*m_db, m_name, start_height, stop_index, entries)) { + return false; + } + + filters_out.resize(entries.size()); + auto filter_pos_it = filters_out.begin(); + for (const auto &entry : entries) { + if (!ReadFilterFromDisk(entry.pos, *filter_pos_it)) { + return false; + } + ++filter_pos_it; + } + + return true; +} + +bool BlockFilterIndex::LookupFilterHashRange( + int start_height, const CBlockIndex *stop_index, + std::vector &hashes_out) const + +{ + std::vector entries; + if (!LookupRange(*m_db, m_name, start_height, stop_index, entries)) { + return false; + } + + hashes_out.clear(); + hashes_out.reserve(entries.size()); + for (const auto &entry : entries) { + hashes_out.push_back(entry.hash); + } + return true; +}