Bitcoin Core 30.99.0
P2P Digital Currency
coinstatsindex.cpp
Go to the documentation of this file.
1// Copyright (c) 2020-2022 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 <arith_uint256.h>
6#include <chainparams.h>
7#include <coins.h>
8#include <common/args.h>
9#include <crypto/muhash.h>
11#include <kernel/coinstats.h>
12#include <logging.h>
13#include <node/blockstorage.h>
14#include <serialize.h>
15#include <txdb.h>
16#include <undo.h>
17#include <validation.h>
18
23
24static constexpr uint8_t DB_BLOCK_HASH{'s'};
25static constexpr uint8_t DB_BLOCK_HEIGHT{'t'};
26static constexpr uint8_t DB_MUHASH{'M'};
27
28namespace {
29
30struct DBVal {
31 uint256 muhash{uint256::ZERO};
32 uint64_t transaction_output_count{0};
33 uint64_t bogo_size{0};
34 CAmount total_amount{0};
35 CAmount total_subsidy{0};
36 arith_uint256 total_prevout_spent_amount{0};
37 arith_uint256 total_new_outputs_ex_coinbase_amount{0};
38 arith_uint256 total_coinbase_amount{0};
39 CAmount total_unspendables_genesis_block{0};
40 CAmount total_unspendables_bip30{0};
41 CAmount total_unspendables_scripts{0};
42 CAmount total_unspendables_unclaimed_rewards{0};
43
44 SERIALIZE_METHODS(DBVal, obj)
45 {
46 uint256 prevout_spent, new_outputs, coinbase;
47 SER_WRITE(obj, prevout_spent = ArithToUint256(obj.total_prevout_spent_amount));
48 SER_WRITE(obj, new_outputs = ArithToUint256(obj.total_new_outputs_ex_coinbase_amount));
49 SER_WRITE(obj, coinbase = ArithToUint256(obj.total_coinbase_amount));
50
51 READWRITE(obj.muhash);
52 READWRITE(obj.transaction_output_count);
53 READWRITE(obj.bogo_size);
54 READWRITE(obj.total_amount);
55 READWRITE(obj.total_subsidy);
56 READWRITE(prevout_spent);
57 READWRITE(new_outputs);
58 READWRITE(coinbase);
59 READWRITE(obj.total_unspendables_genesis_block);
60 READWRITE(obj.total_unspendables_bip30);
61 READWRITE(obj.total_unspendables_scripts);
62 READWRITE(obj.total_unspendables_unclaimed_rewards);
63
64 SER_READ(obj, obj.total_prevout_spent_amount = UintToArith256(prevout_spent));
65 SER_READ(obj, obj.total_new_outputs_ex_coinbase_amount = UintToArith256(new_outputs));
66 SER_READ(obj, obj.total_coinbase_amount = UintToArith256(coinbase));
67 }
68};
69
70struct DBHeightKey {
71 int height;
72
73 explicit DBHeightKey(int height_in) : height(height_in) {}
74
75 template <typename Stream>
76 void Serialize(Stream& s) const
77 {
79 ser_writedata32be(s, height);
80 }
81
82 template <typename Stream>
83 void Unserialize(Stream& s)
84 {
85 const uint8_t prefix{ser_readdata8(s)};
86 if (prefix != DB_BLOCK_HEIGHT) {
87 throw std::ios_base::failure("Invalid format for coinstatsindex DB height key");
88 }
89 height = ser_readdata32be(s);
90 }
91};
92
93struct DBHashKey {
94 uint256 block_hash;
95
96 explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
97
98 SERIALIZE_METHODS(DBHashKey, obj)
99 {
100 uint8_t prefix{DB_BLOCK_HASH};
102 if (prefix != DB_BLOCK_HASH) {
103 throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key");
104 }
105
106 READWRITE(obj.block_hash);
107 }
108};
109
110}; // namespace
111
112std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
113
114CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
115 : BaseIndex(std::move(chain), "coinstatsindex")
116{
117 // An earlier version of the index used "indexes/coinstats" but it contained
118 // a bug and is superseded by a fixed version at "indexes/coinstatsindex".
119 // The original index is kept around until the next release in case users
120 // decide to downgrade their node.
121 auto old_path = gArgs.GetDataDirNet() / "indexes" / "coinstats";
122 if (fs::exists(old_path)) {
123 // TODO: Change this to deleting the old index with v31.
124 LogWarning("Old version of coinstatsindex found at %s. This folder can be safely deleted unless you " \
125 "plan to downgrade your node to version 29 or lower.", fs::PathToString(old_path));
126 }
127 fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstatsindex"};
129
130 m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
131}
132
134{
135 const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())};
136 m_total_subsidy += block_subsidy;
137
138 // Ignore genesis block
139 if (block.height > 0) {
140 uint256 expected_block_hash{*Assert(block.prev_hash)};
141 if (m_current_block_hash != expected_block_hash) {
142 LogError("previous block header belongs to unexpected block %s; expected %s",
143 m_current_block_hash.ToString(), expected_block_hash.ToString());
144 return false;
145 }
146
147 // Add the new utxos created from the block
148 assert(block.data);
149 for (size_t i = 0; i < block.data->vtx.size(); ++i) {
150 const auto& tx{block.data->vtx.at(i)};
151 const bool is_coinbase{tx->IsCoinBase()};
152
153 // Skip duplicate txid coinbase transactions (BIP30).
154 if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {
155 m_total_unspendables_bip30 += block_subsidy;
156 continue;
157 }
158
159 for (uint32_t j = 0; j < tx->vout.size(); ++j) {
160 const CTxOut& out{tx->vout[j]};
161 const Coin coin{out, block.height, is_coinbase};
162 const COutPoint outpoint{tx->GetHash(), j};
163
164 // Skip unspendable coins
165 if (coin.out.scriptPubKey.IsUnspendable()) {
166 m_total_unspendables_scripts += coin.out.nValue;
167 continue;
168 }
169
170 ApplyCoinHash(m_muhash, outpoint, coin);
171
172 if (is_coinbase) {
173 m_total_coinbase_amount += coin.out.nValue;
174 } else {
176 }
177
179 m_total_amount += coin.out.nValue;
180 m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
181 }
182
183 // The coinbase tx has no undo data since no former output is spent
184 if (!is_coinbase) {
185 const auto& tx_undo{Assert(block.undo_data)->vtxundo.at(i - 1)};
186
187 for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
188 const Coin& coin{tx_undo.vprevout[j]};
189 const COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
190
191 RemoveCoinHash(m_muhash, outpoint, coin);
192
193 m_total_prevout_spent_amount += coin.out.nValue;
194
196 m_total_amount -= coin.out.nValue;
197 m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
198 }
199 }
200 }
201 } else {
202 // genesis block
203 m_total_unspendables_genesis_block += block_subsidy;
204 }
205
206 // If spent prevouts + block subsidy are still a higher amount than
207 // new outputs + coinbase + current unspendable amount this means
208 // the miner did not claim the full block reward. Unclaimed block
209 // rewards are also unspendable.
212 assert(unclaimed_rewards <= arith_uint256(std::numeric_limits<CAmount>::max()));
213 m_total_unspendables_unclaimed_rewards += static_cast<CAmount>(unclaimed_rewards.GetLow64());
214
215 std::pair<uint256, DBVal> value;
216 value.first = block.hash;
217 value.second.transaction_output_count = m_transaction_output_count;
218 value.second.bogo_size = m_bogo_size;
219 value.second.total_amount = m_total_amount;
220 value.second.total_subsidy = m_total_subsidy;
221 value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
222 value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
223 value.second.total_coinbase_amount = m_total_coinbase_amount;
224 value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
225 value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
226 value.second.total_unspendables_scripts = m_total_unspendables_scripts;
227 value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
228
229 uint256 out;
231 value.second.muhash = out;
232
234
235 // Intentionally do not update DB_MUHASH here so it stays in sync with
236 // DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.
237 return m_db->Write(DBHeightKey(block.height), value);
238}
239
240[[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
241 const std::string& index_name, int height)
242{
243 DBHeightKey key{height};
244 db_it.Seek(key);
245
246 if (!db_it.GetKey(key) || key.height != height) {
247 LogError("unexpected key in %s: expected (%c, %d)",
248 index_name, DB_BLOCK_HEIGHT, height);
249 return false;
250 }
251
252 std::pair<uint256, DBVal> value;
253 if (!db_it.GetValue(value)) {
254 LogError("unable to read value in %s at key (%c, %d)",
255 index_name, DB_BLOCK_HEIGHT, height);
256 return false;
257 }
258
259 batch.Write(DBHashKey(value.first), value.second);
260 return true;
261}
262
264{
265 CDBBatch batch(*m_db);
266 std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
267
268 // During a reorg, copy the block's hash digest from the height index to the hash index,
269 // ensuring it's still accessible after the height index entry is overwritten.
270 if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) {
271 return false;
272 }
273
274 if (!m_db->WriteBatch(batch)) return false;
275
276 if (!RevertBlock(block)) {
277 return false; // failure cause logged internally
278 }
279
280 return true;
281}
282
283static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockRef& block, DBVal& result)
284{
285 // First check if the result is stored under the height index and the value
286 // there matches the block hash. This should be the case if the block is on
287 // the active chain.
288 std::pair<uint256, DBVal> read_out;
289 if (!db.Read(DBHeightKey(block.height), read_out)) {
290 return false;
291 }
292 if (read_out.first == block.hash) {
293 result = std::move(read_out.second);
294 return true;
295 }
296
297 // If value at the height index corresponds to an different block, the
298 // result will be stored in the hash index.
299 return db.Read(DBHashKey(block.hash), result);
300}
301
302std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const
303{
304 CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()};
305 stats.index_used = true;
306
307 DBVal entry;
308 if (!LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) {
309 return std::nullopt;
310 }
311
312 stats.hashSerialized = entry.muhash;
313 stats.nTransactionOutputs = entry.transaction_output_count;
314 stats.nBogoSize = entry.bogo_size;
315 stats.total_amount = entry.total_amount;
316 stats.total_subsidy = entry.total_subsidy;
317 stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
318 stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
319 stats.total_coinbase_amount = entry.total_coinbase_amount;
320 stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
321 stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
322 stats.total_unspendables_scripts = entry.total_unspendables_scripts;
323 stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
324
325 return stats;
326}
327
328bool CoinStatsIndex::CustomInit(const std::optional<interfaces::BlockRef>& block)
329{
330 if (!m_db->Read(DB_MUHASH, m_muhash)) {
331 // Check that the cause of the read failure is that the key does not
332 // exist. Any other errors indicate database corruption or a disk
333 // failure, and starting the index would cause further corruption.
334 if (m_db->Exists(DB_MUHASH)) {
335 LogError("Cannot read current %s state; index may be corrupted",
336 GetName());
337 return false;
338 }
339 }
340
341 if (block) {
342 DBVal entry;
343 if (!LookUpOne(*m_db, *block, entry)) {
344 LogError("Cannot read current %s state; index may be corrupted",
345 GetName());
346 return false;
347 }
348
349 uint256 out;
351 if (entry.muhash != out) {
352 LogError("Cannot read current %s state; index may be corrupted",
353 GetName());
354 return false;
355 }
356
357 m_transaction_output_count = entry.transaction_output_count;
358 m_bogo_size = entry.bogo_size;
359 m_total_amount = entry.total_amount;
360 m_total_subsidy = entry.total_subsidy;
361 m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
362 m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
363 m_total_coinbase_amount = entry.total_coinbase_amount;
364 m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
365 m_total_unspendables_bip30 = entry.total_unspendables_bip30;
366 m_total_unspendables_scripts = entry.total_unspendables_scripts;
367 m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
368 m_current_block_hash = block->hash;
369 }
370
371 return true;
372}
373
375{
376 // DB_MUHASH should always be committed in a batch together with DB_BEST_BLOCK
377 // to prevent an inconsistent state of the DB.
378 batch.Write(DB_MUHASH, m_muhash);
379 return true;
380}
381
383{
385 options.connect_undo_data = true;
386 options.disconnect_data = true;
387 options.disconnect_undo_data = true;
388 return options;
389}
390
391// Revert a single block as part of a reorg
393{
394 std::pair<uint256, DBVal> read_out;
395
396 // Ignore genesis block
397 if (block.height > 0) {
398 if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) {
399 return false;
400 }
401
402 uint256 expected_block_hash{*block.prev_hash};
403 if (read_out.first != expected_block_hash) {
404 LogWarning("previous block header belongs to unexpected block %s; expected %s",
405 read_out.first.ToString(), expected_block_hash.ToString());
406
407 if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
408 LogError("previous block header not found; expected %s",
409 expected_block_hash.ToString());
410 return false;
411 }
412 }
413 }
414
415 // Roll back muhash by removing the new UTXOs that were created by the
416 // block and reapplying the old UTXOs that were spent by the block
417 assert(block.data);
418 assert(block.undo_data);
419 for (size_t i = 0; i < block.data->vtx.size(); ++i) {
420 const auto& tx{block.data->vtx.at(i)};
421 const bool is_coinbase{tx->IsCoinBase()};
422
423 if (is_coinbase && IsBIP30Unspendable(block.hash, block.height)) {
424 continue;
425 }
426
427 for (uint32_t j = 0; j < tx->vout.size(); ++j) {
428 const CTxOut& out{tx->vout[j]};
429 const COutPoint outpoint{tx->GetHash(), j};
430 const Coin coin{out, block.height, is_coinbase};
431
432 if (!coin.out.scriptPubKey.IsUnspendable()) {
433 RemoveCoinHash(m_muhash, outpoint, coin);
434 }
435 }
436
437 // The coinbase tx has no undo data since no former output is spent
438 if (!is_coinbase) {
439 const auto& tx_undo{block.undo_data->vtxundo.at(i - 1)};
440
441 for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
442 const Coin& coin{tx_undo.vprevout[j]};
443 const COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
444 ApplyCoinHash(m_muhash, outpoint, coin);
445 }
446 }
447 }
448
449 // Check that the rolled back muhash is consistent with the DB read out
450 uint256 out;
452 Assert(read_out.second.muhash == out);
453
454 // Apply the other values from the DB to the member variables
455 m_transaction_output_count = read_out.second.transaction_output_count;
456 m_total_amount = read_out.second.total_amount;
457 m_bogo_size = read_out.second.bogo_size;
458 m_total_subsidy = read_out.second.total_subsidy;
459 m_total_prevout_spent_amount = read_out.second.total_prevout_spent_amount;
460 m_total_new_outputs_ex_coinbase_amount = read_out.second.total_new_outputs_ex_coinbase_amount;
461 m_total_coinbase_amount = read_out.second.total_coinbase_amount;
462 m_total_unspendables_genesis_block = read_out.second.total_unspendables_genesis_block;
463 m_total_unspendables_bip30 = read_out.second.total_unspendables_bip30;
464 m_total_unspendables_scripts = read_out.second.total_unspendables_scripts;
465 m_total_unspendables_unclaimed_rewards = read_out.second.total_unspendables_unclaimed_rewards;
467
468 return true;
469}
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
ArgsManager gArgs
Definition: args.cpp:42
arith_uint256 UintToArith256(const uint256 &a)
uint256 ArithToUint256(const arith_uint256 &a)
const CChainParams & Params()
Return the currently selected parameters.
#define Assert(val)
Identity function.
Definition: check.h:106
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition: args.h:234
Base class for indices of blockchain data.
Definition: base.h:43
const std::string & GetName() const LIFETIMEBOUND
Get the name of the index for display in logs.
Definition: base.h:139
const std::string m_name
Definition: base.h:106
std::vector< CTransactionRef > vtx
Definition: block.h:72
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:141
uint256 GetBlockHash() const
Definition: chain.h:243
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:153
std::vector< CTxUndo > vtxundo
Definition: undo.h:65
Batch of changes queued to be written to a CDBWrapper.
Definition: dbwrapper.h:72
void Write(const K &key, const V &value)
Definition: dbwrapper.h:96
bool GetValue(V &value)
Definition: dbwrapper.h:164
bool GetKey(K &key)
Definition: dbwrapper.h:154
void Seek(const K &key)
Definition: dbwrapper.h:145
bool Read(const K &key, V &value) const
Definition: dbwrapper.h:213
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:29
Txid hash
Definition: transaction.h:31
An output of a transaction.
Definition: transaction.h:150
A UTXO entry.
Definition: coins.h:33
CAmount m_total_subsidy
arith_uint256 m_total_new_outputs_ex_coinbase_amount
interfaces::Chain::NotifyOptions CustomOptions() override
Return custom notification options for index.
bool CustomInit(const std::optional< interfaces::BlockRef > &block) override
Initialize internal state from the database and block index.
CAmount m_total_unspendables_genesis_block
bool CustomRemove(const interfaces::BlockInfo &block) override
Rewind index by one block during a chain reorg.
CoinStatsIndex(std::unique_ptr< interfaces::Chain > chain, size_t n_cache_size, bool f_memory=false, bool f_wipe=false)
CAmount m_total_unspendables_scripts
CAmount m_total_amount
uint64_t m_bogo_size
uint64_t m_transaction_output_count
bool CustomAppend(const interfaces::BlockInfo &block) override
Write update index entries for a newly connected block.
arith_uint256 m_total_prevout_spent_amount
uint256 m_current_block_hash
MuHash3072 m_muhash
std::optional< kernel::CCoinsStats > LookUpStats(const CBlockIndex &block_index) const
std::unique_ptr< BaseIndex::DB > m_db
bool CustomCommit(CDBBatch &batch) override
Virtual method called internally by Commit that can be overridden to atomically commit more index sta...
arith_uint256 m_total_coinbase_amount
CAmount m_total_unspendables_bip30
CAmount m_total_unspendables_unclaimed_rewards
bool RevertBlock(const interfaces::BlockInfo &block)
void Finalize(uint256 &out) noexcept
Definition: muhash.cpp:551
256-bit unsigned big integer.
std::string ToString() const
Definition: uint256.cpp:21
256-bit opaque blob.
Definition: uint256.h:196
static const uint256 ZERO
Definition: uint256.h:204
static constexpr uint8_t DB_MUHASH
std::unique_ptr< CoinStatsIndex > g_coin_stats_index
The global UTXO set hash object.
static bool LookUpOne(const CDBWrapper &db, const interfaces::BlockRef &block, DBVal &result)
static constexpr uint8_t DB_BLOCK_HASH
static bool CopyHeightIndexToHashIndex(CDBIterator &db_it, CDBBatch &batch, const std::string &index_name, int height)
static constexpr uint8_t DB_BLOCK_HEIGHT
static bool create_directories(const std::filesystem::path &p)
Create directory (and if necessary its parents), unless the leaf directory already exists or is a sym...
Definition: fs.h:190
static bool exists(const path &p)
Definition: fs.h:89
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:151
#define LogWarning(...)
Definition: logging.h:357
#define LogError(...)
Definition: logging.h:358
void RemoveCoinHash(MuHash3072 &muhash, const COutPoint &outpoint, const Coin &coin)
Definition: coinstats.cpp:70
static void ApplyCoinHash(HashWriter &ss, const COutPoint &outpoint, const Coin &coin)
Definition: coinstats.cpp:58
uint64_t GetBogoSize(const CScript &script_pub_key)
Definition: coinstats.cpp:40
const char * prefix
Definition: rest.cpp:1117
#define SER_WRITE(obj, code)
Definition: serialize.h:147
void Serialize(Stream &, V)=delete
uint8_t ser_readdata8(Stream &s)
Definition: serialize.h:78
void ser_writedata32be(Stream &s, uint32_t obj)
Definition: serialize.h:68
#define SERIALIZE_METHODS(cls, obj)
Implement the Serialize and Unserialize methods by delegating to a single templated static method tha...
Definition: serialize.h:229
void Unserialize(Stream &, V)=delete
#define SER_READ(obj, code)
Definition: serialize.h:146
void ser_writedata8(Stream &s, uint8_t obj)
Definition: serialize.h:54
uint32_t ser_readdata32be(Stream &s)
Definition: serialize.h:96
#define READWRITE(...)
Definition: serialize.h:145
Block data sent with blockConnected, blockDisconnected notifications.
Definition: chain.h:79
const uint256 * prev_hash
Definition: chain.h:81
const CBlock * data
Definition: chain.h:85
const uint256 & hash
Definition: chain.h:80
const CBlockUndo * undo_data
Definition: chain.h:86
Hash/height pair to help track and identify blocks.
Definition: types.h:13
uint256 hash
Definition: types.h:14
Options specifying which chain notifications are required.
Definition: chain.h:325
bool connect_undo_data
Include undo data with block connected notifications.
Definition: chain.h:327
bool disconnect_undo_data
Include undo data with block disconnected notifications.
Definition: chain.h:331
bool disconnect_data
Include block data with block disconnected notifications.
Definition: chain.h:329
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams)
bool IsBIP30Unspendable(const uint256 &block_hash, int block_height)
Identifies blocks which coinbase output was subsequently overwritten in the UTXO set (see BIP30)
assert(!tx.IsCoinBase())