Bitcoin Core 29.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 <chainparams.h>
6#include <coins.h>
7#include <common/args.h>
8#include <crypto/muhash.h>
10#include <kernel/coinstats.h>
11#include <logging.h>
12#include <node/blockstorage.h>
13#include <serialize.h>
14#include <txdb.h>
15#include <undo.h>
16#include <validation.h>
17
22
23static constexpr uint8_t DB_BLOCK_HASH{'s'};
24static constexpr uint8_t DB_BLOCK_HEIGHT{'t'};
25static constexpr uint8_t DB_MUHASH{'M'};
26
27namespace {
28
29struct DBVal {
30 uint256 muhash;
31 uint64_t transaction_output_count;
32 uint64_t bogo_size;
33 CAmount total_amount;
34 CAmount total_subsidy;
35 CAmount total_unspendable_amount;
36 CAmount total_prevout_spent_amount;
37 CAmount total_new_outputs_ex_coinbase_amount;
38 CAmount total_coinbase_amount;
39 CAmount total_unspendables_genesis_block;
40 CAmount total_unspendables_bip30;
41 CAmount total_unspendables_scripts;
42 CAmount total_unspendables_unclaimed_rewards;
43
44 SERIALIZE_METHODS(DBVal, obj)
45 {
46 READWRITE(obj.muhash);
47 READWRITE(obj.transaction_output_count);
48 READWRITE(obj.bogo_size);
49 READWRITE(obj.total_amount);
50 READWRITE(obj.total_subsidy);
51 READWRITE(obj.total_unspendable_amount);
52 READWRITE(obj.total_prevout_spent_amount);
53 READWRITE(obj.total_new_outputs_ex_coinbase_amount);
54 READWRITE(obj.total_coinbase_amount);
55 READWRITE(obj.total_unspendables_genesis_block);
56 READWRITE(obj.total_unspendables_bip30);
57 READWRITE(obj.total_unspendables_scripts);
58 READWRITE(obj.total_unspendables_unclaimed_rewards);
59 }
60};
61
62struct DBHeightKey {
63 int height;
64
65 explicit DBHeightKey(int height_in) : height(height_in) {}
66
67 template <typename Stream>
68 void Serialize(Stream& s) const
69 {
71 ser_writedata32be(s, height);
72 }
73
74 template <typename Stream>
75 void Unserialize(Stream& s)
76 {
77 const uint8_t prefix{ser_readdata8(s)};
78 if (prefix != DB_BLOCK_HEIGHT) {
79 throw std::ios_base::failure("Invalid format for coinstatsindex DB height key");
80 }
81 height = ser_readdata32be(s);
82 }
83};
84
85struct DBHashKey {
86 uint256 block_hash;
87
88 explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
89
90 SERIALIZE_METHODS(DBHashKey, obj)
91 {
92 uint8_t prefix{DB_BLOCK_HASH};
94 if (prefix != DB_BLOCK_HASH) {
95 throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key");
96 }
97
98 READWRITE(obj.block_hash);
99 }
100};
101
102}; // namespace
103
104std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
105
106CoinStatsIndex::CoinStatsIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
107 : BaseIndex(std::move(chain), "coinstatsindex")
108{
109 fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"};
111
112 m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
113}
114
116{
117 const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())};
118 m_total_subsidy += block_subsidy;
119
120 // Ignore genesis block
121 if (block.height > 0) {
122 std::pair<uint256, DBVal> read_out;
123 if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) {
124 return false;
125 }
126
127 uint256 expected_block_hash{*Assert(block.prev_hash)};
128 if (read_out.first != expected_block_hash) {
129 LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
130 read_out.first.ToString(), expected_block_hash.ToString());
131
132 if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
133 LogError("%s: previous block header not found; expected %s\n",
134 __func__, expected_block_hash.ToString());
135 return false;
136 }
137 }
138
139 // Add the new utxos created from the block
140 assert(block.data);
141 for (size_t i = 0; i < block.data->vtx.size(); ++i) {
142 const auto& tx{block.data->vtx.at(i)};
143
144 // Skip duplicate txid coinbase transactions (BIP30).
145 if (IsBIP30Unspendable(block.hash, block.height) && tx->IsCoinBase()) {
146 m_total_unspendable_amount += block_subsidy;
147 m_total_unspendables_bip30 += block_subsidy;
148 continue;
149 }
150
151 for (uint32_t j = 0; j < tx->vout.size(); ++j) {
152 const CTxOut& out{tx->vout[j]};
153 Coin coin{out, block.height, tx->IsCoinBase()};
154 COutPoint outpoint{tx->GetHash(), j};
155
156 // Skip unspendable coins
157 if (coin.out.scriptPubKey.IsUnspendable()) {
158 m_total_unspendable_amount += coin.out.nValue;
159 m_total_unspendables_scripts += coin.out.nValue;
160 continue;
161 }
162
163 ApplyCoinHash(m_muhash, outpoint, coin);
164
165 if (tx->IsCoinBase()) {
166 m_total_coinbase_amount += coin.out.nValue;
167 } else {
169 }
170
172 m_total_amount += coin.out.nValue;
173 m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
174 }
175
176 // The coinbase tx has no undo data since no former output is spent
177 if (!tx->IsCoinBase()) {
178 const auto& tx_undo{Assert(block.undo_data)->vtxundo.at(i - 1)};
179
180 for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
181 Coin coin{tx_undo.vprevout[j]};
182 COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
183
184 RemoveCoinHash(m_muhash, outpoint, coin);
185
186 m_total_prevout_spent_amount += coin.out.nValue;
187
189 m_total_amount -= coin.out.nValue;
190 m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
191 }
192 }
193 }
194 } else {
195 // genesis block
196 m_total_unspendable_amount += block_subsidy;
197 m_total_unspendables_genesis_block += block_subsidy;
198 }
199
200 // If spent prevouts + block subsidy are still a higher amount than
201 // new outputs + coinbase + current unspendable amount this means
202 // the miner did not claim the full block reward. Unclaimed block
203 // rewards are also unspendable.
205 m_total_unspendable_amount += unclaimed_rewards;
206 m_total_unspendables_unclaimed_rewards += unclaimed_rewards;
207
208 std::pair<uint256, DBVal> value;
209 value.first = block.hash;
210 value.second.transaction_output_count = m_transaction_output_count;
211 value.second.bogo_size = m_bogo_size;
212 value.second.total_amount = m_total_amount;
213 value.second.total_subsidy = m_total_subsidy;
214 value.second.total_unspendable_amount = m_total_unspendable_amount;
215 value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
216 value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
217 value.second.total_coinbase_amount = m_total_coinbase_amount;
218 value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
219 value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
220 value.second.total_unspendables_scripts = m_total_unspendables_scripts;
221 value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
222
223 uint256 out;
225 value.second.muhash = out;
226
227 // Intentionally do not update DB_MUHASH here so it stays in sync with
228 // DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.
229 return m_db->Write(DBHeightKey(block.height), value);
230}
231
232[[nodiscard]] static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
233 const std::string& index_name, int height)
234{
235 DBHeightKey key{height};
236 db_it.Seek(key);
237
238 if (!db_it.GetKey(key) || key.height != height) {
239 LogError("%s: unexpected key in %s: expected (%c, %d)\n",
240 __func__, index_name, DB_BLOCK_HEIGHT, height);
241 return false;
242 }
243
244 std::pair<uint256, DBVal> value;
245 if (!db_it.GetValue(value)) {
246 LogError("%s: unable to read value in %s at key (%c, %d)\n",
247 __func__, index_name, DB_BLOCK_HEIGHT, height);
248 return false;
249 }
250
251 batch.Write(DBHashKey(value.first), std::move(value.second));
252 return true;
253}
254
256{
257 CDBBatch batch(*m_db);
258 std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
259
260 // During a reorg, copy the block's hash digest from the height index to the hash index,
261 // ensuring it's still accessible after the height index entry is overwritten.
262 if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, block.height)) {
263 return false;
264 }
265
266 if (!m_db->WriteBatch(batch)) return false;
267
268 if (!ReverseBlock(block)) {
269 return false; // failure cause logged internally
270 }
271
272 return true;
273}
274
275static bool LookUpOne(const CDBWrapper& db, const interfaces::BlockRef& block, DBVal& result)
276{
277 // First check if the result is stored under the height index and the value
278 // there matches the block hash. This should be the case if the block is on
279 // the active chain.
280 std::pair<uint256, DBVal> read_out;
281 if (!db.Read(DBHeightKey(block.height), read_out)) {
282 return false;
283 }
284 if (read_out.first == block.hash) {
285 result = std::move(read_out.second);
286 return true;
287 }
288
289 // If value at the height index corresponds to an different block, the
290 // result will be stored in the hash index.
291 return db.Read(DBHashKey(block.hash), result);
292}
293
294std::optional<CCoinsStats> CoinStatsIndex::LookUpStats(const CBlockIndex& block_index) const
295{
296 CCoinsStats stats{block_index.nHeight, block_index.GetBlockHash()};
297 stats.index_used = true;
298
299 DBVal entry;
300 if (!LookUpOne(*m_db, {block_index.GetBlockHash(), block_index.nHeight}, entry)) {
301 return std::nullopt;
302 }
303
304 stats.hashSerialized = entry.muhash;
305 stats.nTransactionOutputs = entry.transaction_output_count;
306 stats.nBogoSize = entry.bogo_size;
307 stats.total_amount = entry.total_amount;
308 stats.total_subsidy = entry.total_subsidy;
309 stats.total_unspendable_amount = entry.total_unspendable_amount;
310 stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
311 stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
312 stats.total_coinbase_amount = entry.total_coinbase_amount;
313 stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
314 stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
315 stats.total_unspendables_scripts = entry.total_unspendables_scripts;
316 stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
317
318 return stats;
319}
320
321bool CoinStatsIndex::CustomInit(const std::optional<interfaces::BlockRef>& block)
322{
323 if (!m_db->Read(DB_MUHASH, m_muhash)) {
324 // Check that the cause of the read failure is that the key does not
325 // exist. Any other errors indicate database corruption or a disk
326 // failure, and starting the index would cause further corruption.
327 if (m_db->Exists(DB_MUHASH)) {
328 LogError("%s: Cannot read current %s state; index may be corrupted\n",
329 __func__, GetName());
330 return false;
331 }
332 }
333
334 if (block) {
335 DBVal entry;
336 if (!LookUpOne(*m_db, *block, entry)) {
337 LogError("%s: Cannot read current %s state; index may be corrupted\n",
338 __func__, GetName());
339 return false;
340 }
341
342 uint256 out;
344 if (entry.muhash != out) {
345 LogError("%s: Cannot read current %s state; index may be corrupted\n",
346 __func__, GetName());
347 return false;
348 }
349
350 m_transaction_output_count = entry.transaction_output_count;
351 m_bogo_size = entry.bogo_size;
352 m_total_amount = entry.total_amount;
353 m_total_subsidy = entry.total_subsidy;
354 m_total_unspendable_amount = entry.total_unspendable_amount;
355 m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
356 m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
357 m_total_coinbase_amount = entry.total_coinbase_amount;
358 m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
359 m_total_unspendables_bip30 = entry.total_unspendables_bip30;
360 m_total_unspendables_scripts = entry.total_unspendables_scripts;
361 m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
362 }
363
364 return true;
365}
366
368{
369 // DB_MUHASH should always be committed in a batch together with DB_BEST_BLOCK
370 // to prevent an inconsistent state of the DB.
371 batch.Write(DB_MUHASH, m_muhash);
372 return true;
373}
374
376{
378 options.connect_undo_data = true;
379 options.disconnect_data = true;
380 options.disconnect_undo_data = true;
381 return options;
382}
383
384// Reverse a single block as part of a reorg
386{
387 std::pair<uint256, DBVal> read_out;
388
389 const CAmount block_subsidy{GetBlockSubsidy(block.height, Params().GetConsensus())};
390 m_total_subsidy -= block_subsidy;
391
392 // Ignore genesis block
393 if (block.height > 0) {
394 if (!m_db->Read(DBHeightKey(block.height - 1), read_out)) {
395 return false;
396 }
397
398 uint256 expected_block_hash{*block.prev_hash};
399 if (read_out.first != expected_block_hash) {
400 LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
401 read_out.first.ToString(), expected_block_hash.ToString());
402
403 if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
404 LogError("%s: previous block header not found; expected %s\n",
405 __func__, expected_block_hash.ToString());
406 return false;
407 }
408 }
409 }
410
411 // Remove the new UTXOs that were created from the block
412 assert(block.data);
413 assert(block.undo_data);
414 for (size_t i = 0; i < block.data->vtx.size(); ++i) {
415 const auto& tx{block.data->vtx.at(i)};
416
417 for (uint32_t j = 0; j < tx->vout.size(); ++j) {
418 const CTxOut& out{tx->vout[j]};
419 COutPoint outpoint{tx->GetHash(), j};
420 Coin coin{out, block.height, tx->IsCoinBase()};
421
422 // Skip unspendable coins
423 if (coin.out.scriptPubKey.IsUnspendable()) {
424 m_total_unspendable_amount -= coin.out.nValue;
425 m_total_unspendables_scripts -= coin.out.nValue;
426 continue;
427 }
428
429 RemoveCoinHash(m_muhash, outpoint, coin);
430
431 if (tx->IsCoinBase()) {
432 m_total_coinbase_amount -= coin.out.nValue;
433 } else {
435 }
436
438 m_total_amount -= coin.out.nValue;
439 m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
440 }
441
442 // The coinbase tx has no undo data since no former output is spent
443 if (!tx->IsCoinBase()) {
444 const auto& tx_undo{block.undo_data->vtxundo.at(i - 1)};
445
446 for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
447 Coin coin{tx_undo.vprevout[j]};
448 COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
449
450 ApplyCoinHash(m_muhash, outpoint, coin);
451
452 m_total_prevout_spent_amount -= coin.out.nValue;
453
455 m_total_amount += coin.out.nValue;
456 m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
457 }
458 }
459 }
460
462 m_total_unspendable_amount -= unclaimed_rewards;
463 m_total_unspendables_unclaimed_rewards -= unclaimed_rewards;
464
465 // Check that the rolled back internal values are consistent with the DB read out
466 uint256 out;
468 Assert(read_out.second.muhash == out);
469
470 Assert(m_transaction_output_count == read_out.second.transaction_output_count);
471 Assert(m_total_amount == read_out.second.total_amount);
472 Assert(m_bogo_size == read_out.second.bogo_size);
473 Assert(m_total_subsidy == read_out.second.total_subsidy);
474 Assert(m_total_unspendable_amount == read_out.second.total_unspendable_amount);
475 Assert(m_total_prevout_spent_amount == read_out.second.total_prevout_spent_amount);
476 Assert(m_total_new_outputs_ex_coinbase_amount == read_out.second.total_new_outputs_ex_coinbase_amount);
477 Assert(m_total_coinbase_amount == read_out.second.total_coinbase_amount);
478 Assert(m_total_unspendables_genesis_block == read_out.second.total_unspendables_genesis_block);
479 Assert(m_total_unspendables_bip30 == read_out.second.total_unspendables_bip30);
480 Assert(m_total_unspendables_scripts == read_out.second.total_unspendables_scripts);
481 Assert(m_total_unspendables_unclaimed_rewards == read_out.second.total_unspendables_unclaimed_rewards);
482
483 return true;
484}
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
ArgsManager gArgs
Definition: args.cpp:42
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:74
void Write(const K &key, const V &value)
Definition: dbwrapper.h:98
bool GetValue(V &value)
Definition: dbwrapper.h:166
bool GetKey(K &key)
Definition: dbwrapper.h:156
void Seek(const K &key)
Definition: dbwrapper.h:147
bool Read(const K &key, V &value) const
Definition: dbwrapper.h:220
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
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.
CAmount m_total_unspendable_amount
CAmount m_total_new_outputs_ex_coinbase_amount
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
bool ReverseBlock(const interfaces::BlockInfo &block)
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.
CAmount m_total_prevout_spent_amount
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...
CAmount m_total_coinbase_amount
CAmount m_total_unspendables_bip30
CAmount m_total_unspendables_unclaimed_rewards
void Finalize(uint256 &out) noexcept
Definition: muhash.cpp:551
256-bit opaque blob.
Definition: uint256.h:196
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
#define LogError(...)
Definition: logging.h:263
#define LogPrintf(...)
Definition: logging.h:266
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:1009
void Serialize(Stream &, V)=delete
uint8_t ser_readdata8(Stream &s)
Definition: serialize.h:83
void ser_writedata32be(Stream &s, uint32_t obj)
Definition: serialize.h:73
#define SERIALIZE_METHODS(cls, obj)
Implement the Serialize and Unserialize methods by delegating to a single templated static method tha...
Definition: serialize.h:240
void Unserialize(Stream &, V)=delete
void ser_writedata8(Stream &s, uint8_t obj)
Definition: serialize.h:54
uint32_t ser_readdata32be(Stream &s)
Definition: serialize.h:107
#define READWRITE(...)
Definition: serialize.h:156
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:332
bool connect_undo_data
Include undo data with block connected notifications.
Definition: chain.h:334
bool disconnect_undo_data
Include undo data with block disconnected notifications.
Definition: chain.h:338
bool disconnect_data
Include block data with block disconnected notifications.
Definition: chain.h:336
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())