Bitcoin Core  31.0.0
P2P Digital Currency
txospenderindex.cpp
Go to the documentation of this file.
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 
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;
48 
49  explicit DBKey(const uint64_t& hash_in, const CDiskTxPos& pos_in) : hash(hash_in), pos(pos_in) {}
50 
52  {
53  uint8_t prefix{DB_TXOSPENDERINDEX};
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 
74 {
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  }
124  }
125 
126  return items;
127 }
128 
129 
131 {
133  return true;
134 }
135 
137 {
139  return true;
140 }
141 
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 
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 
std::string GetHex() const
SERIALIZE_METHODS(DBKey, obj)
constexpr uint8_t DB_TXOSPENDERINDEX
Optimized SipHash-2-4 implementation for uint256.
Definition: siphash.h:55
Batch of changes queued to be written to a CDBWrapper.
Definition: dbwrapper.h:71
node::BlockManager & m_blockman
Reference to a BlockManager instance which itself is shared across all Chainstate instances...
Definition: validation.h:573
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1172
const char * prefix
Definition: rest.cpp:1141
bool CustomRemove(const interfaces::BlockInfo &block) override
Rewind index by one block during a chain reorg.
void Erase(const K &key)
Definition: dbwrapper.h:108
static DBKey CreateKey(std::pair< uint64_t, uint64_t > siphash_key, const COutPoint &vout, const CDiskTxPos &pos)
uint256 block_hash
Definition: common.h:29
bool IsBlockPruned(const CBlockIndex &block) const EXCLUSIVE_LOCKS_REQUIRED(void UpdatePruneLock(const std::string &name, const PruneLockInfo &lock_info) EXCLUSIVE_LOCKS_REQUIRED(AutoFile OpenBlockFile(const FlatFilePos &pos, bool fReadOnly) const
Check whether the block associated with this index entry is pruned or not.
Definition: blockstorage.h:459
bool CustomAppend(const interfaces::BlockInfo &block) override
Write update index entries for a newly connected block.
std::pair< uint64_t, uint64_t > m_siphash_key
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:372
The database stores a block locator of the chain the database is synced to so that the index can effi...
Definition: base.h:64
CTransactionRef tx
interfaces::Chain::NotifyOptions CustomOptions() override
Return custom notification options for index.
unsigned data_pos
Definition: chain.h:24
CDiskTxPos pos
Block data sent with blockConnected, blockDisconnected notifications.
Definition: chain.h:19
Base class for indices of blockchain data.
Definition: base.h:54
const CBlock * data
Definition: chain.h:25
The util::Unexpected class represents an unexpected value stored in util::Expected.
Definition: expected.h:21
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition: args.h:239
uint32_t nTxOffset
Definition: disktxpos.h:13
std::unique_ptr< TxoSpenderIndex > g_txospenderindex
The global txo spender index. May be null.
uint64_t hash
Options specifying which chain notifications are required.
Definition: chain.h:318
Fast randomness source.
Definition: random.h:385
Txid hash
Definition: transaction.h:31
static std::vector< std::pair< COutPoint, CDiskTxPos > > BuildSpenderPositions(const interfaces::BlockInfo &block)
uint32_t n
Definition: transaction.h:32
util::Expected< TxoSpender, std::string > ReadTransaction(const CDiskTxPos &pos) const
uint64_t GetSerializeSize(const T &t)
Definition: serialize.h:1095
void Write(const K &key, const V &value)
Definition: dbwrapper.h:96
static uint64_t CreateKeyPrefix(std::pair< uint64_t, uint64_t > siphash_key, const COutPoint &vout)
void WriteSpenderInfos(const std::vector< std::pair< COutPoint, CDiskTxPos >> &items)
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:28
DBKey(const uint64_t &hash_in, const CDiskTxPos &pos_in)
ArgsManager gArgs
Definition: args.cpp:40
BaseIndex::DB & GetDB() const override
bool disconnect_data
Include block data with block disconnected notifications.
Definition: chain.h:323
std::vector< CTransactionRef > vtx
Definition: block.h:77
The util::Expected class provides a standard way for low-level functions to return either error value...
Definition: expected.h:44
std::unique_ptr< BaseIndex::DB > m_db
Chainstate * m_chainstate
Definition: base.h:118
const uint256 & ToUint256() const LIFETIMEBOUND
util::Expected< std::optional< TxoSpender >, std::string > FindSpender(const COutPoint &txo) const
Search the index for a transaction that spends the given outpoint.
void EraseSpenderInfos(const std::vector< std::pair< COutPoint, CDiskTxPos >> &items)
#define READWRITE(...)
Definition: serialize.h:145
constexpr unsigned int GetSizeOfCompactSize(uint64_t nSize)
Compact Size size < 253 – 1 byte size <= USHRT_MAX – 3 bytes (253 + 2 bytes) size <= UINT_MAX – 5 ...
Definition: serialize.h:288
Nodes collect new transactions into a block, hash them into a hash tree, and scan through nonce value...
Definition: block.h:26
TxoSpenderIndex(std::unique_ptr< interfaces::Chain > chain, size_t n_cache_size, bool f_memory=false, bool f_wipe=false)
#define LogError(...)
Definition: log.h:97
static constexpr TransactionSerParams TX_WITH_WITNESS
Definition: transaction.h:180