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