txospenderindex.cpp raw

   1  // Copyright (c) The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <index/txospenderindex.h>
   6  
   7  #include <common/args.h>
   8  #include <crypto/siphash.h>
   9  #include <dbwrapper.h>
  10  #include <flatfile.h>
  11  #include <index/base.h>
  12  #include <index/disktxpos.h>
  13  #include <interfaces/chain.h>
  14  #include <logging.h>
  15  #include <node/blockstorage.h>
  16  #include <primitives/block.h>
  17  #include <primitives/transaction.h>
  18  #include <random.h>
  19  #include <serialize.h>
  20  #include <streams.h>
  21  #include <tinyformat.h>
  22  #include <uint256.h>
  23  #include <util/fs.h>
  24  #include <validation.h>
  25  
  26  #include <cstddef>
  27  #include <cstdio>
  28  #include <exception>
  29  #include <ios>
  30  #include <span>
  31  #include <string>
  32  #include <utility>
  33  #include <vector>
  34  
  35  /* The database is used to find the spending transaction of a given utxo.
  36   * For every input of every transaction it stores a key that is a pair(siphash(input outpoint), transaction location on disk) and a zero-byte value.
  37   * To find the spending transaction of an outpoint, we perform a range query on siphash(outpoint), and for each returned key load the transaction
  38   * and return it if it does spend the provided outpoint.
  39   */
  40  
  41  // LevelDB key prefix. We only have one key for now but it will make it easier to add others if needed.
  42  constexpr uint8_t DB_TXOSPENDERINDEX{'s'};
  43  
  44  std::unique_ptr<TxoSpenderIndex> g_txospenderindex;
  45  
  46  struct DBKey {
  47      uint64_t hash;
  48      CDiskTxPos pos;
  49  
  50      explicit DBKey(const uint64_t& hash_in, const CDiskTxPos& pos_in) : hash(hash_in), pos(pos_in) {}
  51  
  52      SERIALIZE_METHODS(DBKey, obj)
  53      {
  54          uint8_t prefix{DB_TXOSPENDERINDEX};
  55          READWRITE(prefix);
  56          if (prefix != DB_TXOSPENDERINDEX) {
  57              throw std::ios_base::failure("Invalid format for spender index DB key");
  58          }
  59          READWRITE(obj.hash);
  60          READWRITE(obj.pos);
  61      }
  62  };
  63  
  64  TxoSpenderIndex::TxoSpenderIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
  65      : BaseIndex(std::move(chain), "txospenderindex", "txospenderidx"), m_db{std::make_unique<DB>(gArgs.GetDataDirNet() / "indexes" / "txospenderindex" / "db", n_cache_size, f_memory, f_wipe, /*f_obfuscate=*/false, /*f_bloom=*/false)}
  66  {
  67      if (!m_db->Read("siphash_key", m_siphash_key)) {
  68          FastRandomContext rng(false);
  69          m_siphash_key = {rng.rand64(), rng.rand64()};
  70          m_db->Write("siphash_key", m_siphash_key, /*fSync=*/ true);
  71      }
  72  }
  73  
  74  interfaces::Chain::NotifyOptions TxoSpenderIndex::CustomOptions()
  75  {
  76      interfaces::Chain::NotifyOptions options;
  77      options.disconnect_data = true;
  78      return options;
  79  }
  80  
  81  static uint64_t CreateKeyPrefix(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout)
  82  {
  83      return PresaltedSipHasher(siphash_key.first, siphash_key.second)(vout.hash.ToUint256(), vout.n);
  84  }
  85  
  86  static DBKey CreateKey(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout, const CDiskTxPos& pos)
  87  {
  88      return DBKey(CreateKeyPrefix(siphash_key, vout), pos);
  89  }
  90  
  91  void TxoSpenderIndex::WriteSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)
  92  {
  93      CDBBatch batch(*m_db);
  94      for (const auto& [outpoint, pos] : items) {
  95          DBKey key(CreateKey(m_siphash_key, outpoint, pos));
  96          // The key encodes the spent outpoint hash and disk position. The value is only a marker.
  97          // Older entries may contain serialized empty strings; FindSpender() reads only keys.
  98          batch.Write(key, std::span<const std::byte>{});
  99      }
 100      m_db->WriteBatch(batch);
 101  }
 102  
 103  
 104  void TxoSpenderIndex::EraseSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)
 105  {
 106      CDBBatch batch(*m_db);
 107      for (const auto& [outpoint, pos] : items) {
 108          batch.Erase(CreateKey(m_siphash_key, outpoint, pos));
 109      }
 110      m_db->WriteBatch(batch);
 111  }
 112  
 113  static std::vector<std::pair<COutPoint, CDiskTxPos>> BuildSpenderPositions(const interfaces::BlockInfo& block)
 114  {
 115      std::vector<std::pair<COutPoint, CDiskTxPos>> items;
 116      items.reserve(block.data->vtx.size());
 117  
 118      CDiskTxPos pos({block.file_number, block.data_pos}, GetSizeOfCompactSize(block.data->vtx.size()));
 119      for (const auto& tx : block.data->vtx) {
 120          if (!tx->IsCoinBase()) {
 121              for (const auto& input : tx->vin) {
 122                  items.emplace_back(input.prevout, pos);
 123              }
 124          }
 125          pos.nTxOffset += ::GetSerializeSize(TX_WITH_WITNESS(*tx));
 126      }
 127  
 128      return items;
 129  }
 130  
 131  
 132  bool TxoSpenderIndex::CustomAppend(const interfaces::BlockInfo& block)
 133  {
 134      WriteSpenderInfos(BuildSpenderPositions(block));
 135      return true;
 136  }
 137  
 138  bool TxoSpenderIndex::CustomRemove(const interfaces::BlockInfo& block)
 139  {
 140      EraseSpenderInfos(BuildSpenderPositions(block));
 141      return true;
 142  }
 143  
 144  util::Expected<TxoSpender, std::string> TxoSpenderIndex::ReadTransaction(const CDiskTxPos& tx_pos) const
 145  {
 146      AutoFile file{m_chainstate->m_blockman.OpenBlockFile(tx_pos, /*fReadOnly=*/true)};
 147      if (file.IsNull()) {
 148          return util::Unexpected("cannot open block");
 149      }
 150      CBlockHeader header;
 151      TxoSpender spender;
 152      try {
 153          file >> header;
 154          file.seek(tx_pos.nTxOffset, SEEK_CUR);
 155          file >> TX_WITH_WITNESS(spender.tx);
 156          spender.block_hash = header.GetHash();
 157          return spender;
 158      } catch (const std::exception& e) {
 159          return util::Unexpected(e.what());
 160      }
 161  }
 162  
 163  util::Expected<std::optional<TxoSpender>, std::string> TxoSpenderIndex::FindSpender(const COutPoint& txo) const
 164  {
 165      const uint64_t prefix{CreateKeyPrefix(m_siphash_key, txo)};
 166      std::unique_ptr<CDBIterator> it(m_db->NewIterator());
 167      DBKey key(prefix, CDiskTxPos());
 168  
 169      // find all keys that start with the outpoint hash, load the transaction at the location specified in the key
 170      // and return it if it does spend the provided outpoint
 171      for (it->Seek(std::pair{DB_TXOSPENDERINDEX, prefix}); it->Valid() && it->GetKey(key) && key.hash == prefix; it->Next()) {
 172          if (const auto spender{ReadTransaction(key.pos)}) {
 173              for (const auto& input : spender->tx->vin) {
 174                  if (input.prevout == txo) {
 175                      return std::optional{*spender};
 176                  }
 177              }
 178          } else {
 179              LogError("Deserialize or I/O error - %s", spender.error());
 180              return util::Unexpected{strprintf("IO error finding spending tx for outpoint %s:%d.", txo.hash.GetHex(), txo.n)};
 181          }
 182      }
 183      return util::Expected<std::optional<TxoSpender>, std::string>(std::nullopt);
 184  }
 185  
 186  BaseIndex::DB& TxoSpenderIndex::GetDB() const { return *m_db; }
 187