From a67d3eb91d5ef840fc49167d7048a33872ecddf8 Mon Sep 17 00:00:00 2001 From: Martin Zumsande Date: Wed, 9 Jul 2025 17:18:28 -0400 Subject: [PATCH] index: deduplicate Hash / Height handling The code was largely duplicated between coinstatsindex and blockfilterindex. Deduplicate it by moving it to a shared file. slight change in behavior: the index name is no longer part of the error msg in case of (un)serialization errors. --- src/index/blockfilterindex.cpp | 72 ++------------------------ src/index/coinstatsindex.cpp | 70 +------------------------ src/index/db_key.h | 94 ++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 137 deletions(-) create mode 100644 src/index/db_key.h diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index 4bf2f5e66d9..62a78016ea0 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -25,7 +26,6 @@ #include #include -#include #include #include #include @@ -46,12 +46,8 @@ * disk location of the next block filter to be written (represented as a FlatFilePos) is stored * under the DB_FILTER_POS key. * - * Keys for the height index have the type [DB_BLOCK_HEIGHT, uint32 (BE)]. The height is represented - * as big-endian so that sequential reads of filters by height are fast. - * Keys for the hash index have the type [DB_BLOCK_HASH, uint256]. + * The logic for keys is shared with other indexes, see index/db_key.h. */ -constexpr uint8_t DB_BLOCK_HASH{'s'}; -constexpr uint8_t DB_BLOCK_HEIGHT{'t'}; constexpr uint8_t DB_FILTER_POS{'P'}; constexpr unsigned int MAX_FLTR_FILE_SIZE = 0x1000000; // 16 MiB @@ -74,45 +70,6 @@ struct DBVal { SERIALIZE_METHODS(DBVal, obj) { READWRITE(obj.hash, obj.header, obj.pos); } }; -struct DBHeightKey { - int height; - - explicit DBHeightKey(int height_in) : height(height_in) {} - - template - void Serialize(Stream& s) const - { - ser_writedata8(s, DB_BLOCK_HEIGHT); - ser_writedata32be(s, height); - } - - template - void Unserialize(Stream& s) - { - const uint8_t prefix{ser_readdata8(s)}; - if (prefix != DB_BLOCK_HEIGHT) { - throw std::ios_base::failure("Invalid format for block filter index DB height key"); - } - height = ser_readdata32be(s); - } -}; - -struct DBHashKey { - uint256 hash; - - explicit DBHashKey(const uint256& hash_in) : hash(hash_in) {} - - SERIALIZE_METHODS(DBHashKey, obj) { - uint8_t prefix{DB_BLOCK_HASH}; - READWRITE(prefix); - if (prefix != DB_BLOCK_HASH) { - throw std::ios_base::failure("Invalid format for block filter index DB hash key"); - } - - READWRITE(obj.hash); - } -}; - }; // namespace static std::map g_filter_indexes; @@ -317,29 +274,6 @@ bool BlockFilterIndex::Write(const BlockFilter& filter, uint32_t block_height, c return true; } -[[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, - const std::string& index_name, int height) -{ - DBHeightKey key(height); - db_it.Seek(key); - - if (!db_it.GetKey(key) || key.height != height) { - LogError("unexpected key in %s: expected (%c, %d)", - index_name, DB_BLOCK_HEIGHT, height); - return false; - } - - std::pair value; - if (!db_it.GetValue(value)) { - LogError("unable to read value in %s at key (%c, %d)", - index_name, DB_BLOCK_HEIGHT, height); - return false; - } - - batch.Write(DBHashKey(value.first), std::move(value.second)); - return true; -} - bool BlockFilterIndex::CustomRemove(const interfaces::BlockInfo& block) { CDBBatch batch(*m_db); @@ -348,7 +282,7 @@ bool BlockFilterIndex::CustomRemove(const interfaces::BlockInfo& block) // During a reorg, we need to copy block filter that is getting disconnected from the // height index to the hash index so we can still find it when the height index entry // is overwritten. - if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) { + if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) { return false; } diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp index 72bc09ce45c..a12c52d0658 100644 --- a/src/index/coinstatsindex.cpp +++ b/src/index/coinstatsindex.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,6 @@ #include #include -#include #include #include #include @@ -40,8 +40,6 @@ using kernel::CCoinsStats; using kernel::GetBogoSize; using kernel::RemoveCoinHash; -static constexpr uint8_t DB_BLOCK_HASH{'s'}; -static constexpr uint8_t DB_BLOCK_HEIGHT{'t'}; static constexpr uint8_t DB_MUHASH{'M'}; namespace { @@ -85,47 +83,6 @@ struct DBVal { SER_READ(obj, obj.total_coinbase_amount = UintToArith256(coinbase)); } }; - -struct DBHeightKey { - int height; - - explicit DBHeightKey(int height_in) : height(height_in) {} - - template - void Serialize(Stream& s) const - { - ser_writedata8(s, DB_BLOCK_HEIGHT); - ser_writedata32be(s, height); - } - - template - void Unserialize(Stream& s) - { - const uint8_t prefix{ser_readdata8(s)}; - if (prefix != DB_BLOCK_HEIGHT) { - throw std::ios_base::failure("Invalid format for coinstatsindex DB height key"); - } - height = ser_readdata32be(s); - } -}; - -struct DBHashKey { - uint256 block_hash; - - explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {} - - SERIALIZE_METHODS(DBHashKey, obj) - { - uint8_t prefix{DB_BLOCK_HASH}; - READWRITE(prefix); - if (prefix != DB_BLOCK_HASH) { - throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key"); - } - - READWRITE(obj.block_hash); - } -}; - }; // namespace std::unique_ptr g_coin_stats_index; @@ -257,29 +214,6 @@ bool CoinStatsIndex::CustomAppend(const interfaces::BlockInfo& block) return true; } -[[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, - const std::string& index_name, int height) -{ - DBHeightKey key{height}; - db_it.Seek(key); - - if (!db_it.GetKey(key) || key.height != height) { - LogError("unexpected key in %s: expected (%c, %d)", - index_name, DB_BLOCK_HEIGHT, height); - return false; - } - - std::pair value; - if (!db_it.GetValue(value)) { - LogError("unable to read value in %s at key (%c, %d)", - index_name, DB_BLOCK_HEIGHT, height); - return false; - } - - batch.Write(DBHashKey(value.first), value.second); - return true; -} - bool CoinStatsIndex::CustomRemove(const interfaces::BlockInfo& block) { CDBBatch batch(*m_db); @@ -287,7 +221,7 @@ bool CoinStatsIndex::CustomRemove(const interfaces::BlockInfo& block) // During a reorg, copy the block's hash digest from the height index to the hash index, // ensuring it's still accessible after the height index entry is overwritten. - if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) { + if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) { return false; } diff --git a/src/index/db_key.h b/src/index/db_key.h new file mode 100644 index 00000000000..c5369675e46 --- /dev/null +++ b/src/index/db_key.h @@ -0,0 +1,94 @@ +// Copyright (c) 2025-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INDEX_DB_KEY_H +#define BITCOIN_INDEX_DB_KEY_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* + * This file includes the logic for the db keys used by blockfilterindex and coinstatsindex. + * Index data is usually indexed by height, but in case of a reorg, entries of blocks no + * longer in the main chain will be copied to a hash index by which they can still be queried. + * Keys for the height index have the type [DB_BLOCK_HEIGHT, uint32 (BE)]. The height is represented + * as big-endian so that sequential reads of filters by height are fast. + * Keys for the hash index have the type [DB_BLOCK_HASH, uint256]. + */ + +static constexpr uint8_t DB_BLOCK_HASH{'s'}; +static constexpr uint8_t DB_BLOCK_HEIGHT{'t'}; + +struct DBHeightKey { + int height; + + explicit DBHeightKey(int height_in) : height(height_in) {} + + template + void Serialize(Stream& s) const + { + ser_writedata8(s, DB_BLOCK_HEIGHT); + ser_writedata32be(s, height); + } + + template + void Unserialize(Stream& s) + { + const uint8_t prefix{ser_readdata8(s)}; + if (prefix != DB_BLOCK_HEIGHT) { + throw std::ios_base::failure("Invalid format for index DB height key"); + } + height = ser_readdata32be(s); + } +}; + +struct DBHashKey { + uint256 hash; + + explicit DBHashKey(const uint256& hash_in) : hash(hash_in) {} + + SERIALIZE_METHODS(DBHashKey, obj) { + uint8_t prefix{DB_BLOCK_HASH}; + READWRITE(prefix); + if (prefix != DB_BLOCK_HASH) { + throw std::ios_base::failure("Invalid format for index DB hash key"); + } + + READWRITE(obj.hash); + } +}; + +template +[[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, + const std::string& index_name, int height) +{ + DBHeightKey key(height); + db_it.Seek(key); + + if (!db_it.GetKey(key) || key.height != height) { + LogError("unexpected key in %s: expected (%c, %d)", + index_name, DB_BLOCK_HEIGHT, height); + return false; + } + + std::pair value; + if (!db_it.GetValue(value)) { + LogError("unable to read value in %s at key (%c, %d)", + index_name, DB_BLOCK_HEIGHT, height); + return false; + } + + batch.Write(DBHashKey(value.first), value.second); + return true; +} + +#endif // BITCOIN_INDEX_DB_KEY_H