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.
This commit is contained in:
Martin Zumsande
2025-07-09 17:18:28 -04:00
parent 9890058b37
commit a67d3eb91d
3 changed files with 99 additions and 137 deletions

View File

@@ -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;
}

View File

@@ -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;
}

94
src/index/db_key.h Normal file
View File

@@ -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