txindex: skip bloom filters and legacy lookups for new databases

New databases never contain legacy ('t' + txid) entries.
Peek at the database before opening it, and if no legacy
entries are found, skip building bloom filters (hashed
entries are only read via iterators, which do not consult
them) and return early from lookups instead of checking
for legacy entries.
This commit is contained in:
Andrew Toth
2026-07-15 19:30:13 -04:00
parent 004d7c098c
commit b75efa19ba
3 changed files with 58 additions and 4 deletions

View File

@@ -153,6 +153,30 @@ static leveldb::Options GetOptions(size_t nCacheSize, bool bloom_filter)
return options;
}
bool CDBWrapper::HasKeyStartingWith(const fs::path& path, uint8_t prefix)
{
if (!fs::exists(path / "CURRENT")) return false;
CBitcoinLevelDBLogger logger;
leveldb::Options options;
options.paranoid_checks = true;
// Avoid creating or rotating LevelDB's LOG files during this probe.
options.info_log = &logger;
leveldb::DB* raw_db;
HandleError(leveldb::DB::Open(options, fs::PathToString(path), &raw_db));
const std::unique_ptr<leveldb::DB> db{raw_db};
leveldb::ReadOptions iteroptions;
iteroptions.verify_checksums = true;
iteroptions.fill_cache = false;
const std::unique_ptr<leveldb::Iterator> it{db->NewIterator(iteroptions)};
const leveldb::Slice prefix_slice{reinterpret_cast<const char*>(&prefix), sizeof(prefix)};
it->Seek(prefix_slice);
HandleError(it->status());
return it->Valid() && it->key().starts_with(prefix_slice);
}
struct CDBBatch::WriteBatchImpl {
leveldb::WriteBatch batch;
};

View File

@@ -279,6 +279,11 @@ public:
*/
bool IsEmpty();
//! Probe an unopened database for a key prefix. Return true if a database at
//! path exists and contains at least 1 entry beginning with prefix; missing
//! or empty databases return false, and database errors throw dbwrapper_error.
static bool HasKeyStartingWith(const fs::path& path, uint8_t prefix);
template<typename K>
size_t EstimateSize(const K& key_begin, const K& key_end) const
{

View File

@@ -65,13 +65,32 @@ public:
/// Used to hash the txid to compute the prefix.
const SipHasher13UJ m_hasher;
/// Whether the database contains any legacy ('t' + txid) entries.
const bool m_has_legacy;
CBlockLocator ReadBestBlock() const override;
void WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator) override;
private:
DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy);
};
static fs::path TxIndexDBPath() { return gArgs.GetDataDirNet() / "indexes" / "txindex"; }
TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) :
BaseIndex::DB(gArgs.GetDataDirNet() / "indexes" / "txindex", n_cache_size, f_memory, f_wipe),
m_hasher{ReadOrCreateTxidHasher(*this)}
// Bloom filters are built for every key but only consulted by point reads,
// which iterators bypass: the per-tx hashed ('x') lookups seek with an
// iterator, and the 's'/'h' point reads are at most one per block against a
// tiny keyspace. Only the legacy entries' per-tx point lookups benefit, so
// enable the filters only for databases still containing them.
DB(n_cache_size, f_memory, f_wipe,
/*has_legacy=*/!f_memory && !f_wipe && CDBWrapper::HasKeyStartingWith(TxIndexDBPath(), txindex::DB_TXINDEX))
{}
TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy) :
BaseIndex::DB(TxIndexDBPath(), n_cache_size, f_memory, f_wipe, /*f_obfuscate=*/false, /*f_bloom=*/has_legacy),
m_hasher{ReadOrCreateTxidHasher(*this)},
m_has_legacy{has_legacy}
{}
CBlockLocator TxIndex::DB::ReadBestBlock() const
@@ -115,7 +134,13 @@ void TxIndex::DB::WriteTxs(const interfaces::BlockInfo& block)
TxIndex::TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
: BaseIndex(std::move(chain), "txindex", "txidx"), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe))
{}
{
if (m_db->m_has_legacy) {
LogInfo("txindex contains entries in the legacy format, which uses excessive disk space. "
"To reclaim disk space, stop the node, delete %s and restart to rebuild the index.",
fs::PathToString(TxIndexDBPath()));
}
}
TxIndex::~TxIndex() = default;
@@ -189,7 +214,7 @@ std::optional<TxIndexResult> TxIndex::FindTx(const Txid& tx_hash) const
}
// Fall back to legacy if no hashed entry matched. This makes misses pay an
// extra lookup, but keeps existing full-txid entries readable after upgrade.
return FindLegacyTx(tx_hash);
return m_db->m_has_legacy ? FindLegacyTx(tx_hash) : std::nullopt;
}
std::optional<TxIndexResult> TxIndex::FindLegacyTx(const Txid& tx_hash) const