1 // Copyright (c) 2020-2021 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 <crypto/muhash.h>
8 #include <index/coinstatsindex.h>
9 #include <node/blockstorage.h>
10 #include <serialize.h>
11 #include <txdb.h>
12 #include <undo.h>
13 #include <validation.h>
14 
15 static constexpr uint8_t DB_BLOCK_HASH{'s'};
16 static constexpr uint8_t DB_BLOCK_HEIGHT{'t'};
17 static constexpr uint8_t DB_MUHASH{'M'};
18 
19 namespace {
20 
21 struct DBVal {
22     uint256 muhash;
23     uint64_t transaction_output_count;
24     uint64_t bogo_size;
25     CAmount total_amount;
26     CAmount total_subsidy;
27     CAmount block_unspendable_amount;
28     CAmount block_prevout_spent_amount;
29     CAmount block_new_outputs_ex_coinbase_amount;
30     CAmount block_coinbase_amount;
31     CAmount unspendables_genesis_block;
32     CAmount unspendables_bip30;
33     CAmount unspendables_scripts;
34     CAmount unspendables_unclaimed_rewards;
35 
SERIALIZE_METHODS__anon9e7ad97b0111::DBVal36     SERIALIZE_METHODS(DBVal, obj)
37     {
38         READWRITE(obj.muhash);
39         READWRITE(obj.transaction_output_count);
40         READWRITE(obj.bogo_size);
41         READWRITE(obj.total_amount);
42         READWRITE(obj.total_subsidy);
43         READWRITE(obj.block_unspendable_amount);
44         READWRITE(obj.block_prevout_spent_amount);
45         READWRITE(obj.block_new_outputs_ex_coinbase_amount);
46         READWRITE(obj.block_coinbase_amount);
47         READWRITE(obj.unspendables_genesis_block);
48         READWRITE(obj.unspendables_bip30);
49         READWRITE(obj.unspendables_scripts);
50         READWRITE(obj.unspendables_unclaimed_rewards);
51     }
52 };
53 
54 struct DBHeightKey {
55     int height;
56 
DBHeightKey__anon9e7ad97b0111::DBHeightKey57     explicit DBHeightKey(int height_in) : height(height_in) {}
58 
59     template <typename Stream>
Serialize__anon9e7ad97b0111::DBHeightKey60     void Serialize(Stream& s) const
61     {
62         ser_writedata8(s, DB_BLOCK_HEIGHT);
63         ser_writedata32be(s, height);
64     }
65 
66     template <typename Stream>
Unserialize__anon9e7ad97b0111::DBHeightKey67     void Unserialize(Stream& s)
68     {
69         const uint8_t prefix{ser_readdata8(s)};
70         if (prefix != DB_BLOCK_HEIGHT) {
71             throw std::ios_base::failure("Invalid format for coinstatsindex DB height key");
72         }
73         height = ser_readdata32be(s);
74     }
75 };
76 
77 struct DBHashKey {
78     uint256 block_hash;
79 
DBHashKey__anon9e7ad97b0111::DBHashKey80     explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
81 
SERIALIZE_METHODS__anon9e7ad97b0111::DBHashKey82     SERIALIZE_METHODS(DBHashKey, obj)
83     {
84         uint8_t prefix{DB_BLOCK_HASH};
85         READWRITE(prefix);
86         if (prefix != DB_BLOCK_HASH) {
87             throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key");
88         }
89 
90         READWRITE(obj.block_hash);
91     }
92 };
93 
94 }; // namespace
95 
96 std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
97 
CoinStatsIndex(size_t n_cache_size,bool f_memory,bool f_wipe)98 CoinStatsIndex::CoinStatsIndex(size_t n_cache_size, bool f_memory, bool f_wipe)
99 {
100     fs::path path{gArgs.GetDataDirNet() / "indexes" / "coinstats"};
101     fs::create_directories(path);
102 
103     m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
104 }
105 
WriteBlock(const CBlock & block,const CBlockIndex * pindex)106 bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
107 {
108     CBlockUndo block_undo;
109     const CAmount block_subsidy{GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())};
110     m_total_subsidy += block_subsidy;
111 
112     // Ignore genesis block
113     if (pindex->nHeight > 0) {
114         if (!UndoReadFromDisk(block_undo, pindex)) {
115             return false;
116         }
117 
118         std::pair<uint256, DBVal> read_out;
119         if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
120             return false;
121         }
122 
123         uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
124         if (read_out.first != expected_block_hash) {
125             if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
126                 return error("%s: previous block header belongs to unexpected block %s; expected %s",
127                              __func__, read_out.first.ToString(), expected_block_hash.ToString());
128             }
129         }
130 
131         // TODO: Deduplicate BIP30 related code
132         bool is_bip30_block{(pindex->nHeight == 91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) ||
133                             (pindex->nHeight == 91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))};
134 
135         // Add the new utxos created from the block
136         for (size_t i = 0; i < block.vtx.size(); ++i) {
137             const auto& tx{block.vtx.at(i)};
138 
139             // Skip duplicate txid coinbase transactions (BIP30).
140             if (is_bip30_block && tx->IsCoinBase()) {
141                 m_block_unspendable_amount += block_subsidy;
142                 m_unspendables_bip30 += block_subsidy;
143                 continue;
144             }
145 
146             for (size_t j = 0; j < tx->vout.size(); ++j) {
147                 const CTxOut& out{tx->vout[j]};
148                 Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
149                 COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)};
150 
151                 // Skip unspendable coins
152                 if (coin.out.scriptPubKey.IsUnspendable()) {
153                     m_block_unspendable_amount += coin.out.nValue;
154                     m_unspendables_scripts += coin.out.nValue;
155                     continue;
156                 }
157 
158                 m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
159 
160                 if (tx->IsCoinBase()) {
161                     m_block_coinbase_amount += coin.out.nValue;
162                 } else {
163                     m_block_new_outputs_ex_coinbase_amount += coin.out.nValue;
164                 }
165 
166                 ++m_transaction_output_count;
167                 m_total_amount += coin.out.nValue;
168                 m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
169             }
170 
171             // The coinbase tx has no undo data since no former output is spent
172             if (!tx->IsCoinBase()) {
173                 const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
174 
175                 for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
176                     Coin coin{tx_undo.vprevout[j]};
177                     COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
178 
179                     m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
180 
181                     m_block_prevout_spent_amount += coin.out.nValue;
182 
183                     --m_transaction_output_count;
184                     m_total_amount -= coin.out.nValue;
185                     m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
186                 }
187             }
188         }
189     } else {
190         // genesis block
191         m_block_unspendable_amount += block_subsidy;
192         m_unspendables_genesis_block += block_subsidy;
193     }
194 
195     // If spent prevouts + block subsidy are still a higher amount than
196     // new outputs + coinbase + current unspendable amount this means
197     // the miner did not claim the full block reward. Unclaimed block
198     // rewards are also unspendable.
199     const CAmount unclaimed_rewards{(m_block_prevout_spent_amount + m_total_subsidy) - (m_block_new_outputs_ex_coinbase_amount + m_block_coinbase_amount + m_block_unspendable_amount)};
200     m_block_unspendable_amount += unclaimed_rewards;
201     m_unspendables_unclaimed_rewards += unclaimed_rewards;
202 
203     std::pair<uint256, DBVal> value;
204     value.first = pindex->GetBlockHash();
205     value.second.transaction_output_count = m_transaction_output_count;
206     value.second.bogo_size = m_bogo_size;
207     value.second.total_amount = m_total_amount;
208     value.second.total_subsidy = m_total_subsidy;
209     value.second.block_unspendable_amount = m_block_unspendable_amount;
210     value.second.block_prevout_spent_amount = m_block_prevout_spent_amount;
211     value.second.block_new_outputs_ex_coinbase_amount = m_block_new_outputs_ex_coinbase_amount;
212     value.second.block_coinbase_amount = m_block_coinbase_amount;
213     value.second.unspendables_genesis_block = m_unspendables_genesis_block;
214     value.second.unspendables_bip30 = m_unspendables_bip30;
215     value.second.unspendables_scripts = m_unspendables_scripts;
216     value.second.unspendables_unclaimed_rewards = m_unspendables_unclaimed_rewards;
217 
218     uint256 out;
219     m_muhash.Finalize(out);
220     value.second.muhash = out;
221 
222     return m_db->Write(DBHeightKey(pindex->nHeight), value) && m_db->Write(DB_MUHASH, m_muhash);
223 }
224 
CopyHeightIndexToHashIndex(CDBIterator & db_it,CDBBatch & batch,const std::string & index_name,int start_height,int stop_height)225 static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
226                                        const std::string& index_name,
227                                        int start_height, int stop_height)
228 {
229     DBHeightKey key{start_height};
230     db_it.Seek(key);
231 
232     for (int height = start_height; height <= stop_height; ++height) {
233         if (!db_it.GetKey(key) || key.height != height) {
234             return error("%s: unexpected key in %s: expected (%c, %d)",
235                          __func__, index_name, DB_BLOCK_HEIGHT, height);
236         }
237 
238         std::pair<uint256, DBVal> value;
239         if (!db_it.GetValue(value)) {
240             return error("%s: unable to read value in %s at key (%c, %d)",
241                          __func__, index_name, DB_BLOCK_HEIGHT, height);
242         }
243 
244         batch.Write(DBHashKey(value.first), std::move(value.second));
245 
246         db_it.Next();
247     }
248     return true;
249 }
250 
Rewind(const CBlockIndex * current_tip,const CBlockIndex * new_tip)251 bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
252 {
253     assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
254 
255     CDBBatch batch(*m_db);
256     std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
257 
258     // During a reorg, we need to copy all hash digests for blocks that are
259     // getting disconnected from the height index to the hash index so we can
260     // still find them when the height index entries are overwritten.
261     if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) {
262         return false;
263     }
264 
265     if (!m_db->WriteBatch(batch)) return false;
266 
267     {
268         LOCK(cs_main);
269         CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip->GetBlockHash())};
270         const auto& consensus_params{Params().GetConsensus()};
271 
272         do {
273             CBlock block;
274 
275             if (!ReadBlockFromDisk(block, iter_tip, consensus_params)) {
276                 return error("%s: Failed to read block %s from disk",
277                              __func__, iter_tip->GetBlockHash().ToString());
278             }
279 
280             ReverseBlock(block, iter_tip);
281 
282             iter_tip = iter_tip->GetAncestor(iter_tip->nHeight - 1);
283         } while (new_tip != iter_tip);
284     }
285 
286     return BaseIndex::Rewind(current_tip, new_tip);
287 }
288 
LookUpOne(const CDBWrapper & db,const CBlockIndex * block_index,DBVal & result)289 static bool LookUpOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVal& result)
290 {
291     // First check if the result is stored under the height index and the value
292     // there matches the block hash. This should be the case if the block is on
293     // the active chain.
294     std::pair<uint256, DBVal> read_out;
295     if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) {
296         return false;
297     }
298     if (read_out.first == block_index->GetBlockHash()) {
299         result = std::move(read_out.second);
300         return true;
301     }
302 
303     // If value at the height index corresponds to an different block, the
304     // result will be stored in the hash index.
305     return db.Read(DBHashKey(block_index->GetBlockHash()), result);
306 }
307 
LookUpStats(const CBlockIndex * block_index,CCoinsStats & coins_stats) const308 bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& coins_stats) const
309 {
310     DBVal entry;
311     if (!LookUpOne(*m_db, block_index, entry)) {
312         return false;
313     }
314 
315     coins_stats.hashSerialized = entry.muhash;
316     coins_stats.nTransactionOutputs = entry.transaction_output_count;
317     coins_stats.nBogoSize = entry.bogo_size;
318     coins_stats.nTotalAmount = entry.total_amount;
319     coins_stats.total_subsidy = entry.total_subsidy;
320     coins_stats.block_unspendable_amount = entry.block_unspendable_amount;
321     coins_stats.block_prevout_spent_amount = entry.block_prevout_spent_amount;
322     coins_stats.block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount;
323     coins_stats.block_coinbase_amount = entry.block_coinbase_amount;
324     coins_stats.unspendables_genesis_block = entry.unspendables_genesis_block;
325     coins_stats.unspendables_bip30 = entry.unspendables_bip30;
326     coins_stats.unspendables_scripts = entry.unspendables_scripts;
327     coins_stats.unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards;
328 
329     return true;
330 }
331 
Init()332 bool CoinStatsIndex::Init()
333 {
334     if (!m_db->Read(DB_MUHASH, m_muhash)) {
335         // Check that the cause of the read failure is that the key does not
336         // exist. Any other errors indicate database corruption or a disk
337         // failure, and starting the index would cause further corruption.
338         if (m_db->Exists(DB_MUHASH)) {
339             return error("%s: Cannot read current %s state; index may be corrupted",
340                          __func__, GetName());
341         }
342     }
343 
344     if (BaseIndex::Init()) {
345         const CBlockIndex* pindex{CurrentIndex()};
346 
347         if (pindex) {
348             DBVal entry;
349             if (!LookUpOne(*m_db, pindex, entry)) {
350                 return false;
351             }
352 
353             m_transaction_output_count = entry.transaction_output_count;
354             m_bogo_size = entry.bogo_size;
355             m_total_amount = entry.total_amount;
356             m_total_subsidy = entry.total_subsidy;
357             m_block_unspendable_amount = entry.block_unspendable_amount;
358             m_block_prevout_spent_amount = entry.block_prevout_spent_amount;
359             m_block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount;
360             m_block_coinbase_amount = entry.block_coinbase_amount;
361             m_unspendables_genesis_block = entry.unspendables_genesis_block;
362             m_unspendables_bip30 = entry.unspendables_bip30;
363             m_unspendables_scripts = entry.unspendables_scripts;
364             m_unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards;
365         }
366 
367         return true;
368     }
369 
370     return false;
371 }
372 
373 // Reverse a single block as part of a reorg
ReverseBlock(const CBlock & block,const CBlockIndex * pindex)374 bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex)
375 {
376     CBlockUndo block_undo;
377     std::pair<uint256, DBVal> read_out;
378 
379     const CAmount block_subsidy{GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())};
380     m_total_subsidy -= block_subsidy;
381 
382     // Ignore genesis block
383     if (pindex->nHeight > 0) {
384         if (!UndoReadFromDisk(block_undo, pindex)) {
385             return false;
386         }
387 
388         if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
389             return false;
390         }
391 
392         uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
393         if (read_out.first != expected_block_hash) {
394             if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
395                 return error("%s: previous block header belongs to unexpected block %s; expected %s",
396                              __func__, read_out.first.ToString(), expected_block_hash.ToString());
397             }
398         }
399     }
400 
401     // Remove the new UTXOs that were created from the block
402     for (size_t i = 0; i < block.vtx.size(); ++i) {
403         const auto& tx{block.vtx.at(i)};
404 
405         for (size_t j = 0; j < tx->vout.size(); ++j) {
406             const CTxOut& out{tx->vout[j]};
407             COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)};
408             Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
409 
410             // Skip unspendable coins
411             if (coin.out.scriptPubKey.IsUnspendable()) {
412                 m_block_unspendable_amount -= coin.out.nValue;
413                 m_unspendables_scripts -= coin.out.nValue;
414                 continue;
415             }
416 
417             m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
418 
419             if (tx->IsCoinBase()) {
420                 m_block_coinbase_amount -= coin.out.nValue;
421             } else {
422                 m_block_new_outputs_ex_coinbase_amount -= coin.out.nValue;
423             }
424 
425             --m_transaction_output_count;
426             m_total_amount -= coin.out.nValue;
427             m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
428         }
429 
430         // The coinbase tx has no undo data since no former output is spent
431         if (!tx->IsCoinBase()) {
432             const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
433 
434             for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
435                 Coin coin{tx_undo.vprevout[j]};
436                 COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
437 
438                 m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
439 
440                 m_block_prevout_spent_amount -= coin.out.nValue;
441 
442                 m_transaction_output_count++;
443                 m_total_amount += coin.out.nValue;
444                 m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
445             }
446         }
447     }
448 
449     const CAmount unclaimed_rewards{(m_block_new_outputs_ex_coinbase_amount + m_block_coinbase_amount + m_block_unspendable_amount) - (m_block_prevout_spent_amount + m_total_subsidy)};
450     m_block_unspendable_amount -= unclaimed_rewards;
451     m_unspendables_unclaimed_rewards -= unclaimed_rewards;
452 
453     // Check that the rolled back internal values are consistent with the DB read out
454     uint256 out;
455     m_muhash.Finalize(out);
456     Assert(read_out.second.muhash == out);
457 
458     Assert(m_transaction_output_count == read_out.second.transaction_output_count);
459     Assert(m_total_amount == read_out.second.total_amount);
460     Assert(m_bogo_size == read_out.second.bogo_size);
461     Assert(m_total_subsidy == read_out.second.total_subsidy);
462     Assert(m_block_unspendable_amount == read_out.second.block_unspendable_amount);
463     Assert(m_block_prevout_spent_amount == read_out.second.block_prevout_spent_amount);
464     Assert(m_block_new_outputs_ex_coinbase_amount == read_out.second.block_new_outputs_ex_coinbase_amount);
465     Assert(m_block_coinbase_amount == read_out.second.block_coinbase_amount);
466     Assert(m_unspendables_genesis_block == read_out.second.unspendables_genesis_block);
467     Assert(m_unspendables_bip30 == read_out.second.unspendables_bip30);
468     Assert(m_unspendables_scripts == read_out.second.unspendables_scripts);
469     Assert(m_unspendables_unclaimed_rewards == read_out.second.unspendables_unclaimed_rewards);
470 
471     return m_db->Write(DB_MUHASH, m_muhash);
472 }
473