M1.1: Extract CTxDBBase abstract storage interface

First step of the multi-phase chaindb modernization plan. Introduces a
backend-agnostic abstraction over the chain database:

  * CTxDBBase — abstract class owning all serialization and named
    operations (ReadTxIndex, WriteBlockIndex, ReadAddressBalance, etc.).
    Templated Read/Write/Erase/Exists dispatch to byte-level virtuals
    (ReadRaw/WriteRaw/EraseRaw/ExistsRaw) so every backend produces
    bit-identical key bytes — required for migration and dual-backend
    parity testing later.

  * CTxDBIteratorBase — abstract iterator. Backends implement Seek,
    Valid, Next, KeyStr, ValueStr.

  * CTxDB now inherits from CTxDBBase and only implements the byte-level
    I/O, batch lifecycle, NewIterator, and LoadBlockIndex (which still
    uses leveldb directly during the v3 dbformat upgrade — extracted to
    base in a later phase).

  * UTXO read-through cache moved to txdb-base.cpp under an anonymous
    namespace — backend-agnostic so RocksDB will get it for free.

  * GetAddressUtxos / GetAddressTxIds / SumUtxoValues moved to base,
    using NewIterator() instead of pdb->NewIterator().

No call-site changes — every existing CTxDB user keeps working exactly
as before. Stack allocations like `CTxDB txdb("r")` still work because
CTxDB remains a concrete, cheap-to-construct class. Behavior is
bit-identical: same key serialization, same batch semantics, same
LoadBlockIndex flow.

Sets up M1.2 (factory + caller conversion to CTxDBBase&) and M1.3
(RocksDB backend) — neither requires touching consensus paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-25 02:37:45 -07:00
parent b9d631e968
commit b28525057a
5 changed files with 789 additions and 658 deletions
+211
View File
@@ -0,0 +1,211 @@
// Copyright (c) 2009-2012 The Bitcoin developers.
// Copyright (c) 2026 The Triangles developers.
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef TRIANGLES_TXDB_BASE_H
#define TRIANGLES_TXDB_BASE_H
#include "main.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
class CScript;
class CTransaction;
class CDiskTxPos;
class CTxIndex;
class CDiskBlockIndex;
class CUtxoEntry;
class CBigNum;
// ----------------------------------------------------------------------------
// Backend-agnostic key/value iterator.
//
// Each CTxDBBase backend returns a std::unique_ptr<CTxDBIteratorBase> from
// NewIterator(). Iterators yield raw serialized key/value bytes; callers
// deserialize using the same SER_DISK / CLIENT_VERSION conventions used by
// CTxDBBase's templated Read/Write paths.
//
// Iterators do NOT see uncommitted writes in an active batch. All current
// iteration sites (block-index scan, address-index range queries, UTXO sum)
// run outside transactions, so this is safe.
// ----------------------------------------------------------------------------
class CTxDBIteratorBase
{
public:
virtual ~CTxDBIteratorBase() = default;
virtual void Seek(const std::string& key) = 0;
virtual bool Valid() const = 0;
virtual void Next() = 0;
virtual std::string KeyStr() const = 0;
virtual std::string ValueStr() const = 0;
};
// ----------------------------------------------------------------------------
// Abstract chain database interface.
//
// All key/value serialization happens in this base class via CDataStream with
// SER_DISK / CLIENT_VERSION. Backends only implement byte-level I/O, so every
// backend produces bit-identical key bytes — required for migration and
// dual-backend parity testing.
//
// Named operations (ReadTxIndex, WriteBlockIndex, etc.) are implemented in
// terms of the templated Read/Write/Erase/Exists, which dispatch to the
// virtual byte-level methods. To add a new backend:
//
// 1. Subclass CTxDBBase.
// 2. Implement Close, TxnBegin/Commit/Abort.
// 3. Implement ReadRaw, WriteRaw, EraseRaw, ExistsRaw.
// 4. Implement NewIterator (return a subclass of CTxDBIteratorBase).
// 5. Implement LoadBlockIndex (still backend-specific in M1; will be
// extracted to the base in a later phase).
// ----------------------------------------------------------------------------
class CTxDBBase
{
public:
virtual ~CTxDBBase() = default;
// Destroys the underlying shared global state accessed by this DB.
virtual void Close() = 0;
// Batches (transaction-like atomic groups of writes/deletes).
virtual bool TxnBegin() = 0;
virtual bool TxnCommit() = 0;
virtual bool TxnAbort() = 0;
bool IsReadOnly() const { return fReadOnly; }
// ── Schema versioning ────────────────────────────────────────────────────
bool ReadVersion(int& nVersion);
bool WriteVersion(int nVersion);
bool ReadDbFormat(int& nDbFormat);
bool WriteDbFormat(int nDbFormat);
// ── Tx index ─────────────────────────────────────────────────────────────
bool ReadTxIndex(uint256 hash, CTxIndex& txindex);
bool UpdateTxIndex(uint256 hash, const CTxIndex& txindex);
bool AddTxIndex(const CTransaction& tx, const CDiskTxPos& pos, int nHeight);
bool EraseTxIndex(const CTransaction& tx);
bool ContainsTx(uint256 hash);
bool ReadDiskTx(uint256 hash, CTransaction& tx, CTxIndex& txindex);
bool ReadDiskTx(uint256 hash, CTransaction& tx);
bool ReadDiskTx(COutPoint outpoint, CTransaction& tx, CTxIndex& txindex);
bool ReadDiskTx(COutPoint outpoint, CTransaction& tx);
// ── Block index ──────────────────────────────────────────────────────────
bool WriteBlockIndex(const CDiskBlockIndex& blockindex);
// ── Best chain / checkpoint metadata ─────────────────────────────────────
bool ReadHashBestChain(uint256& hashBestChain);
bool WriteHashBestChain(uint256 hashBestChain);
bool ReadAddressIndexBestChain(uint256& hashBestChain);
bool WriteAddressIndexBestChain(uint256 hashBestChain);
bool ReadAddressIndexStartHeight(int& nHeight);
bool WriteAddressIndexStartHeight(int nHeight);
bool ReadBestInvalidTrust(CBigNum& bnBestInvalidTrust);
bool WriteBestInvalidTrust(CBigNum bnBestInvalidTrust);
bool ReadSyncCheckpoint(uint256& hashCheckpoint);
bool WriteSyncCheckpoint(uint256 hashCheckpoint);
bool ReadCheckpointPubKey(std::string& strPubKey);
bool WriteCheckpointPubKey(const std::string& strPubKey);
virtual bool LoadBlockIndex() = 0;
// ── Address index ────────────────────────────────────────────────────────
bool ReadAddressBalance(int nType, const uint160& hashBytes, int64_t& nBalance);
bool WriteAddressBalance(int nType, const uint160& hashBytes, int64_t nBalance);
bool ReadAddressUtxo(int nType, const uint160& hashBytes, const uint256& txhash,
int nIndex, int64_t& nValue, int& nHeight);
bool WriteAddressUtxo(int nType, const uint160& hashBytes, const uint256& txhash,
int nIndex, int64_t nValue, int nHeight, const CScript& script);
bool EraseAddressUtxo(int nType, const uint160& hashBytes, const uint256& txhash,
int nIndex);
bool WriteAddressTxId(int nType, const uint160& hashBytes, int nHeight,
int nTxIndex, const uint256& txhash);
bool EraseAddressTxId(int nType, const uint160& hashBytes, int nHeight,
int nTxIndex, const uint256& txhash);
bool GetAddressUtxos(int nType, const uint160& hashBytes,
std::vector<std::pair<COutPoint, std::pair<int64_t, int> > >& vUtxos);
bool GetAddressTxIds(int nType, const uint160& hashBytes, int nStartHeight,
int nEndHeight, std::vector<uint256>& vTxIds);
// ── UTXO set ─────────────────────────────────────────────────────────────
bool ReadUtxo(const uint256& hash, unsigned int n, CUtxoEntry& entry);
bool WriteUtxo(const uint256& hash, unsigned int n, const CUtxoEntry& entry);
bool EraseUtxo(const uint256& hash, unsigned int n);
bool HaveUtxo(const uint256& hash, unsigned int n);
int64_t SumUtxoValues(int& nCount);
protected:
bool fReadOnly = false;
// Byte-level I/O — backends implement these.
virtual bool ReadRaw(const std::string& key, std::string& value) const = 0;
virtual bool WriteRaw(const std::string& key, const std::string& value) = 0;
virtual bool EraseRaw(const std::string& key) = 0;
virtual bool ExistsRaw(const std::string& key) const = 0;
virtual std::unique_ptr<CTxDBIteratorBase> NewIterator() const = 0;
// Templated Read/Write/Erase/Exists are non-virtual (templates can't be
// virtual in C++) — they serialize and dispatch to the byte-level virtuals.
template<typename K, typename T>
bool Read(const K& key, T& value) const
{
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
std::string strValue;
if (!ReadRaw(ssKey.str(), strValue))
return false;
try {
CDataStream ssValue(strValue.data(),
strValue.data() + strValue.size(),
SER_DISK, CLIENT_VERSION);
ssValue >> value;
} catch (std::exception&) {
return false;
}
return true;
}
template<typename K, typename T>
bool Write(const K& key, const T& value)
{
if (fReadOnly)
assert(!"Write called on database in read-only mode");
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue.reserve(10000);
ssValue << value;
return WriteRaw(ssKey.str(), ssValue.str());
}
template<typename K>
bool Erase(const K& key)
{
if (fReadOnly)
assert(!"Erase called on database in read-only mode");
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
return EraseRaw(ssKey.str());
}
template<typename K>
bool Exists(const K& key) const
{
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
return ExistsRaw(ssKey.str());
}
};
#endif // TRIANGLES_TXDB_BASE_H