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