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