index: deduplicate Hash / Height handling
What changed, and why it matters
This commit is a routine code cleanup: it moves duplicated database-key handling code shared by two Bitcoin Core indexes into one shared header file. The actual behavior is essentially unchanged, except that a couple of error messages now say 'index' generically instead of naming the specific index. There is no security fix or vulnerability here.
No security action needed; treat as normal refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch deduplicates DBHeightKey, DBHashKey, and CopyHeightIndexToHashIndex between blockfilterindex and coinstatsindex by placing them in the new src/index/db_key.h. Both indexes now include this header and instantiate the templated helper. Serialization/deserialization logic and key byte values (’s’ and ‘t’) are preserved. The only functional difference noted by the author is that error messages no longer embed the index name.
Changed components
src/index/blockfilterindex.cppsrc/index/coinstatsindex.cppsrc/index/db_key.hInspect captured patch +99 / −137
diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp
index 4bf2f5e6..62a78016 100644
--- a/src/index/blockfilterindex.cpp
+++ b/src/index/blockfilterindex.cpp
@@ -11,6 +11,7 @@
#include <flatfile.h>
#include <hash.h>
#include <index/base.h>
+#include <index/db_key.h>
#include <interfaces/chain.h>
#include <interfaces/types.h>
#include <logging.h>
@@ -25,7 +26,6 @@
#include <cerrno>
#include <exception>
-#include <ios>
#include <map>
#include <optional>
#include <span>
@@ -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<typename Stream>
- void Serialize(Stream& s) const
- {
- ser_writedata8(s, DB_BLOCK_HEIGHT);
- ser_writedata32be(s, height);
- }
-
- template<typename Stream>
- 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<BlockFilterType, BlockFilterIndex> 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<uint256, DBVal> 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<DBVal>(*db_it, batch, m_name, block.height)) {
return false;
}
diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp
index 72bc09ce..a12c52d0 100644
--- a/src/index/coinstatsindex.cpp
+++ b/src/index/coinstatsindex.cpp
@@ -13,6 +13,7 @@
#include <crypto/muhash.h>
#include <dbwrapper.h>
#include <index/base.h>
+#include <index/db_key.h>
#include <interfaces/chain.h>
#include <interfaces/types.h>
#include <kernel/coinstats.h>
@@ -28,7 +29,6 @@
#include <validation.h>
#include <compare>
-#include <ios>
#include <limits>
#include <span>
#include <string>
@@ -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 <typename Stream>
- void Serialize(Stream& s) const
- {
- ser_writedata8(s, DB_BLOCK_HEIGHT);
- ser_writedata32be(s, height);
- }
-
- template <typename Stream>
- 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<CoinStatsIndex> 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<uint256, DBVal> 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<DBVal>(*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 00000000..c5369675
--- /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 <dbwrapper.h>
+#include <interfaces/types.h>
+#include <logging.h>
+#include <serialize.h>
+#include <uint256.h>
+
+#include <cstdint>
+#include <ios>
+#include <string>
+#include <utility>
+
+/*
+ * 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<typename Stream>
+ void Serialize(Stream& s) const
+ {
+ ser_writedata8(s, DB_BLOCK_HEIGHT);
+ ser_writedata32be(s, height);
+ }
+
+ template<typename Stream>
+ 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 <typename DBVal>
+[[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<uint256, DBVal> 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
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.