merge bitcoin#22047: Coinstatsindex follow-ups

This commit is contained in:
Kittywhiskers Van Gogh 2023-07-19 18:42:49 +00:00 committed by PastaPastaPasta
parent 28dcd381cd
commit d97dcb22e1
5 changed files with 150 additions and 141 deletions

View File

@ -25,14 +25,14 @@ struct DBVal {
uint64_t bogo_size; uint64_t bogo_size;
CAmount total_amount; CAmount total_amount;
CAmount total_subsidy; CAmount total_subsidy;
CAmount block_unspendable_amount; CAmount total_unspendable_amount;
CAmount block_prevout_spent_amount; CAmount total_prevout_spent_amount;
CAmount block_new_outputs_ex_coinbase_amount; CAmount total_new_outputs_ex_coinbase_amount;
CAmount block_coinbase_amount; CAmount total_coinbase_amount;
CAmount unspendables_genesis_block; CAmount total_unspendables_genesis_block;
CAmount unspendables_bip30; CAmount total_unspendables_bip30;
CAmount unspendables_scripts; CAmount total_unspendables_scripts;
CAmount unspendables_unclaimed_rewards; CAmount total_unspendables_unclaimed_rewards;
SERIALIZE_METHODS(DBVal, obj) SERIALIZE_METHODS(DBVal, obj)
{ {
@ -41,14 +41,14 @@ struct DBVal {
READWRITE(obj.bogo_size); READWRITE(obj.bogo_size);
READWRITE(obj.total_amount); READWRITE(obj.total_amount);
READWRITE(obj.total_subsidy); READWRITE(obj.total_subsidy);
READWRITE(obj.block_unspendable_amount); READWRITE(obj.total_unspendable_amount);
READWRITE(obj.block_prevout_spent_amount); READWRITE(obj.total_prevout_spent_amount);
READWRITE(obj.block_new_outputs_ex_coinbase_amount); READWRITE(obj.total_new_outputs_ex_coinbase_amount);
READWRITE(obj.block_coinbase_amount); READWRITE(obj.total_coinbase_amount);
READWRITE(obj.unspendables_genesis_block); READWRITE(obj.total_unspendables_genesis_block);
READWRITE(obj.unspendables_bip30); READWRITE(obj.total_unspendables_bip30);
READWRITE(obj.unspendables_scripts); READWRITE(obj.total_unspendables_scripts);
READWRITE(obj.unspendables_unclaimed_rewards); READWRITE(obj.total_unspendables_unclaimed_rewards);
} }
}; };
@ -123,9 +123,12 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
uint256 expected_block_hash{pindex->pprev->GetBlockHash()}; uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
if (read_out.first != expected_block_hash) { if (read_out.first != expected_block_hash) {
LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
read_out.first.ToString(), expected_block_hash.ToString());
if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) { if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
return error("%s: previous block header belongs to unexpected block %s; expected %s", return error("%s: previous block header not found; expected %s",
__func__, read_out.first.ToString(), expected_block_hash.ToString()); __func__, expected_block_hash.ToString());
} }
} }
@ -139,29 +142,29 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
// Skip duplicate txid coinbase transactions (BIP30). // Skip duplicate txid coinbase transactions (BIP30).
if (is_bip30_block && tx->IsCoinBase()) { if (is_bip30_block && tx->IsCoinBase()) {
m_block_unspendable_amount += block_subsidy; m_total_unspendable_amount += block_subsidy;
m_unspendables_bip30 += block_subsidy; m_total_unspendables_bip30 += block_subsidy;
continue; continue;
} }
for (size_t j = 0; j < tx->vout.size(); ++j) { for (uint32_t j = 0; j < tx->vout.size(); ++j) {
const CTxOut& out{tx->vout[j]}; const CTxOut& out{tx->vout[j]};
Coin coin{out, pindex->nHeight, tx->IsCoinBase()}; Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)}; COutPoint outpoint{tx->GetHash(), j};
// Skip unspendable coins // Skip unspendable coins
if (coin.out.scriptPubKey.IsUnspendable()) { if (coin.out.scriptPubKey.IsUnspendable()) {
m_block_unspendable_amount += coin.out.nValue; m_total_unspendable_amount += coin.out.nValue;
m_unspendables_scripts += coin.out.nValue; m_total_unspendables_scripts += coin.out.nValue;
continue; continue;
} }
m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin))); m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
if (tx->IsCoinBase()) { if (tx->IsCoinBase()) {
m_block_coinbase_amount += coin.out.nValue; m_total_coinbase_amount += coin.out.nValue;
} else { } else {
m_block_new_outputs_ex_coinbase_amount += coin.out.nValue; m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;
} }
++m_transaction_output_count; ++m_transaction_output_count;
@ -179,7 +182,7 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin))); m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
m_block_prevout_spent_amount += coin.out.nValue; m_total_prevout_spent_amount += coin.out.nValue;
--m_transaction_output_count; --m_transaction_output_count;
m_total_amount -= coin.out.nValue; m_total_amount -= coin.out.nValue;
@ -189,17 +192,17 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
} }
} else { } else {
// genesis block // genesis block
m_block_unspendable_amount += block_subsidy; m_total_unspendable_amount += block_subsidy;
m_unspendables_genesis_block += block_subsidy; m_total_unspendables_genesis_block += block_subsidy;
} }
// If spent prevouts + block subsidy are still a higher amount than // If spent prevouts + block subsidy are still a higher amount than
// new outputs + coinbase + current unspendable amount this means // new outputs + coinbase + current unspendable amount this means
// the miner did not claim the full block reward. Unclaimed block // the miner did not claim the full block reward. Unclaimed block
// rewards are also unspendable. // rewards are also unspendable.
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)}; const CAmount unclaimed_rewards{(m_total_prevout_spent_amount + m_total_subsidy) - (m_total_new_outputs_ex_coinbase_amount + m_total_coinbase_amount + m_total_unspendable_amount)};
m_block_unspendable_amount += unclaimed_rewards; m_total_unspendable_amount += unclaimed_rewards;
m_unspendables_unclaimed_rewards += unclaimed_rewards; m_total_unspendables_unclaimed_rewards += unclaimed_rewards;
std::pair<uint256, DBVal> value; std::pair<uint256, DBVal> value;
value.first = pindex->GetBlockHash(); value.first = pindex->GetBlockHash();
@ -207,20 +210,23 @@ bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
value.second.bogo_size = m_bogo_size; value.second.bogo_size = m_bogo_size;
value.second.total_amount = m_total_amount; value.second.total_amount = m_total_amount;
value.second.total_subsidy = m_total_subsidy; value.second.total_subsidy = m_total_subsidy;
value.second.block_unspendable_amount = m_block_unspendable_amount; value.second.total_unspendable_amount = m_total_unspendable_amount;
value.second.block_prevout_spent_amount = m_block_prevout_spent_amount; value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
value.second.block_new_outputs_ex_coinbase_amount = m_block_new_outputs_ex_coinbase_amount; value.second.total_new_outputs_ex_coinbase_amount = m_total_new_outputs_ex_coinbase_amount;
value.second.block_coinbase_amount = m_block_coinbase_amount; value.second.total_coinbase_amount = m_total_coinbase_amount;
value.second.unspendables_genesis_block = m_unspendables_genesis_block; value.second.total_unspendables_genesis_block = m_total_unspendables_genesis_block;
value.second.unspendables_bip30 = m_unspendables_bip30; value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
value.second.unspendables_scripts = m_unspendables_scripts; value.second.total_unspendables_scripts = m_total_unspendables_scripts;
value.second.unspendables_unclaimed_rewards = m_unspendables_unclaimed_rewards; value.second.total_unspendables_unclaimed_rewards = m_total_unspendables_unclaimed_rewards;
uint256 out; uint256 out;
m_muhash.Finalize(out); m_muhash.Finalize(out);
value.second.muhash = out; value.second.muhash = out;
return m_db->Write(DBHeightKey(pindex->nHeight), value) && m_db->Write(DB_MUHASH, m_muhash); CDBBatch batch(*m_db);
batch.Write(DBHeightKey(pindex->nHeight), value);
batch.Write(DB_MUHASH, m_muhash);
return m_db->WriteBatch(batch);
} }
static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch, static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
@ -318,14 +324,14 @@ bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& co
coins_stats.nBogoSize = entry.bogo_size; coins_stats.nBogoSize = entry.bogo_size;
coins_stats.nTotalAmount = entry.total_amount; coins_stats.nTotalAmount = entry.total_amount;
coins_stats.total_subsidy = entry.total_subsidy; coins_stats.total_subsidy = entry.total_subsidy;
coins_stats.block_unspendable_amount = entry.block_unspendable_amount; coins_stats.total_unspendable_amount = entry.total_unspendable_amount;
coins_stats.block_prevout_spent_amount = entry.block_prevout_spent_amount; coins_stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
coins_stats.block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount; coins_stats.total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
coins_stats.block_coinbase_amount = entry.block_coinbase_amount; coins_stats.total_coinbase_amount = entry.total_coinbase_amount;
coins_stats.unspendables_genesis_block = entry.unspendables_genesis_block; coins_stats.total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
coins_stats.unspendables_bip30 = entry.unspendables_bip30; coins_stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
coins_stats.unspendables_scripts = entry.unspendables_scripts; coins_stats.total_unspendables_scripts = entry.total_unspendables_scripts;
coins_stats.unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards; coins_stats.total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
return true; return true;
} }
@ -342,33 +348,31 @@ bool CoinStatsIndex::Init()
} }
} }
if (BaseIndex::Init()) { if (!BaseIndex::Init()) return false;
const CBlockIndex* pindex{CurrentIndex()};
if (pindex) { const CBlockIndex* pindex{CurrentIndex()};
DBVal entry;
if (!LookUpOne(*m_db, pindex, entry)) {
return false;
}
m_transaction_output_count = entry.transaction_output_count; if (pindex) {
m_bogo_size = entry.bogo_size; DBVal entry;
m_total_amount = entry.total_amount; if (!LookUpOne(*m_db, pindex, entry)) {
m_total_subsidy = entry.total_subsidy; return false;
m_block_unspendable_amount = entry.block_unspendable_amount;
m_block_prevout_spent_amount = entry.block_prevout_spent_amount;
m_block_new_outputs_ex_coinbase_amount = entry.block_new_outputs_ex_coinbase_amount;
m_block_coinbase_amount = entry.block_coinbase_amount;
m_unspendables_genesis_block = entry.unspendables_genesis_block;
m_unspendables_bip30 = entry.unspendables_bip30;
m_unspendables_scripts = entry.unspendables_scripts;
m_unspendables_unclaimed_rewards = entry.unspendables_unclaimed_rewards;
} }
return true; m_transaction_output_count = entry.transaction_output_count;
m_bogo_size = entry.bogo_size;
m_total_amount = entry.total_amount;
m_total_subsidy = entry.total_subsidy;
m_total_unspendable_amount = entry.total_unspendable_amount;
m_total_prevout_spent_amount = entry.total_prevout_spent_amount;
m_total_new_outputs_ex_coinbase_amount = entry.total_new_outputs_ex_coinbase_amount;
m_total_coinbase_amount = entry.total_coinbase_amount;
m_total_unspendables_genesis_block = entry.total_unspendables_genesis_block;
m_total_unspendables_bip30 = entry.total_unspendables_bip30;
m_total_unspendables_scripts = entry.total_unspendables_scripts;
m_total_unspendables_unclaimed_rewards = entry.total_unspendables_unclaimed_rewards;
} }
return false; return true;
} }
// Reverse a single block as part of a reorg // Reverse a single block as part of a reorg
@ -392,9 +396,12 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
uint256 expected_block_hash{pindex->pprev->GetBlockHash()}; uint256 expected_block_hash{pindex->pprev->GetBlockHash()};
if (read_out.first != expected_block_hash) { if (read_out.first != expected_block_hash) {
LogPrintf("WARNING: previous block header belongs to unexpected block %s; expected %s\n",
read_out.first.ToString(), expected_block_hash.ToString());
if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) { if (!m_db->Read(DBHashKey(expected_block_hash), read_out)) {
return error("%s: previous block header belongs to unexpected block %s; expected %s", return error("%s: previous block header not found; expected %s",
__func__, read_out.first.ToString(), expected_block_hash.ToString()); __func__, expected_block_hash.ToString());
} }
} }
} }
@ -403,24 +410,24 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
for (size_t i = 0; i < block.vtx.size(); ++i) { for (size_t i = 0; i < block.vtx.size(); ++i) {
const auto& tx{block.vtx.at(i)}; const auto& tx{block.vtx.at(i)};
for (size_t j = 0; j < tx->vout.size(); ++j) { for (uint32_t j = 0; j < tx->vout.size(); ++j) {
const CTxOut& out{tx->vout[j]}; const CTxOut& out{tx->vout[j]};
COutPoint outpoint{tx->GetHash(), static_cast<uint32_t>(j)}; COutPoint outpoint{tx->GetHash(), j};
Coin coin{out, pindex->nHeight, tx->IsCoinBase()}; Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
// Skip unspendable coins // Skip unspendable coins
if (coin.out.scriptPubKey.IsUnspendable()) { if (coin.out.scriptPubKey.IsUnspendable()) {
m_block_unspendable_amount -= coin.out.nValue; m_total_unspendable_amount -= coin.out.nValue;
m_unspendables_scripts -= coin.out.nValue; m_total_unspendables_scripts -= coin.out.nValue;
continue; continue;
} }
m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin))); m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
if (tx->IsCoinBase()) { if (tx->IsCoinBase()) {
m_block_coinbase_amount -= coin.out.nValue; m_total_coinbase_amount -= coin.out.nValue;
} else { } else {
m_block_new_outputs_ex_coinbase_amount -= coin.out.nValue; m_total_new_outputs_ex_coinbase_amount -= coin.out.nValue;
} }
--m_transaction_output_count; --m_transaction_output_count;
@ -438,7 +445,7 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin))); m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
m_block_prevout_spent_amount -= coin.out.nValue; m_total_prevout_spent_amount -= coin.out.nValue;
m_transaction_output_count++; m_transaction_output_count++;
m_total_amount += coin.out.nValue; m_total_amount += coin.out.nValue;
@ -447,9 +454,9 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
} }
} }
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)}; const CAmount unclaimed_rewards{(m_total_new_outputs_ex_coinbase_amount + m_total_coinbase_amount + m_total_unspendable_amount) - (m_total_prevout_spent_amount + m_total_subsidy)};
m_block_unspendable_amount -= unclaimed_rewards; m_total_unspendable_amount -= unclaimed_rewards;
m_unspendables_unclaimed_rewards -= unclaimed_rewards; m_total_unspendables_unclaimed_rewards -= unclaimed_rewards;
// Check that the rolled back internal values are consistent with the DB read out // Check that the rolled back internal values are consistent with the DB read out
uint256 out; uint256 out;
@ -460,14 +467,14 @@ bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex
Assert(m_total_amount == read_out.second.total_amount); Assert(m_total_amount == read_out.second.total_amount);
Assert(m_bogo_size == read_out.second.bogo_size); Assert(m_bogo_size == read_out.second.bogo_size);
Assert(m_total_subsidy == read_out.second.total_subsidy); Assert(m_total_subsidy == read_out.second.total_subsidy);
Assert(m_block_unspendable_amount == read_out.second.block_unspendable_amount); Assert(m_total_unspendable_amount == read_out.second.total_unspendable_amount);
Assert(m_block_prevout_spent_amount == read_out.second.block_prevout_spent_amount); Assert(m_total_prevout_spent_amount == read_out.second.total_prevout_spent_amount);
Assert(m_block_new_outputs_ex_coinbase_amount == read_out.second.block_new_outputs_ex_coinbase_amount); Assert(m_total_new_outputs_ex_coinbase_amount == read_out.second.total_new_outputs_ex_coinbase_amount);
Assert(m_block_coinbase_amount == read_out.second.block_coinbase_amount); Assert(m_total_coinbase_amount == read_out.second.total_coinbase_amount);
Assert(m_unspendables_genesis_block == read_out.second.unspendables_genesis_block); Assert(m_total_unspendables_genesis_block == read_out.second.total_unspendables_genesis_block);
Assert(m_unspendables_bip30 == read_out.second.unspendables_bip30); Assert(m_total_unspendables_bip30 == read_out.second.total_unspendables_bip30);
Assert(m_unspendables_scripts == read_out.second.unspendables_scripts); Assert(m_total_unspendables_scripts == read_out.second.total_unspendables_scripts);
Assert(m_unspendables_unclaimed_rewards == read_out.second.unspendables_unclaimed_rewards); Assert(m_total_unspendables_unclaimed_rewards == read_out.second.total_unspendables_unclaimed_rewards);
return m_db->Write(DB_MUHASH, m_muhash); return m_db->Write(DB_MUHASH, m_muhash);
} }

View File

@ -25,14 +25,14 @@ private:
uint64_t m_bogo_size{0}; uint64_t m_bogo_size{0};
CAmount m_total_amount{0}; CAmount m_total_amount{0};
CAmount m_total_subsidy{0}; CAmount m_total_subsidy{0};
CAmount m_block_unspendable_amount{0}; CAmount m_total_unspendable_amount{0};
CAmount m_block_prevout_spent_amount{0}; CAmount m_total_prevout_spent_amount{0};
CAmount m_block_new_outputs_ex_coinbase_amount{0}; CAmount m_total_new_outputs_ex_coinbase_amount{0};
CAmount m_block_coinbase_amount{0}; CAmount m_total_coinbase_amount{0};
CAmount m_unspendables_genesis_block{0}; CAmount m_total_unspendables_genesis_block{0};
CAmount m_unspendables_bip30{0}; CAmount m_total_unspendables_bip30{0};
CAmount m_unspendables_scripts{0}; CAmount m_total_unspendables_scripts{0};
CAmount m_unspendables_unclaimed_rewards{0}; CAmount m_total_unspendables_unclaimed_rewards{0};
bool ReverseBlock(const CBlock& block, const CBlockIndex* pindex); bool ReverseBlock(const CBlock& block, const CBlockIndex* pindex);

View File

@ -45,15 +45,25 @@ struct CCoinsStats
bool index_used{false}; bool index_used{false};
// Following values are only available from coinstats index // Following values are only available from coinstats index
//! Total cumulative amount of block subsidies up to and including this block
CAmount total_subsidy{0}; CAmount total_subsidy{0};
CAmount block_unspendable_amount{0}; //! Total cumulative amount of unspendable coins up to and including this block
CAmount block_prevout_spent_amount{0}; CAmount total_unspendable_amount{0};
CAmount block_new_outputs_ex_coinbase_amount{0}; //! Total cumulative amount of prevouts spent up to and including this block
CAmount block_coinbase_amount{0}; CAmount total_prevout_spent_amount{0};
CAmount unspendables_genesis_block{0}; //! Total cumulative amount of outputs created up to and including this block
CAmount unspendables_bip30{0}; CAmount total_new_outputs_ex_coinbase_amount{0};
CAmount unspendables_scripts{0}; //! Total cumulative amount of coinbase outputs up to and including this block
CAmount unspendables_unclaimed_rewards{0}; CAmount total_coinbase_amount{0};
//! The unspendable coinbase amount from the genesis block
CAmount total_unspendables_genesis_block{0};
//! The two unspendable coinbase outputs total amount caused by BIP30
CAmount total_unspendables_bip30{0};
//! Total cumulative amount of outputs sent to unspendable scripts (OP_RETURN for example) up to and including this block
CAmount total_unspendables_scripts{0};
//! Total cumulative amount of coins lost due to unclaimed miner rewards up to and including this block
CAmount total_unspendables_unclaimed_rewards{0};
CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {} CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
}; };

View File

@ -1344,13 +1344,13 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
{RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"}, {RPCResult::Type::STR_AMOUNT, "total_unspendable_amount", "The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used)"},
{RPCResult::Type::OBJ, "block_info", "Info on amounts in the block at this block height (only available if coinstatsindex is used)", {RPCResult::Type::OBJ, "block_info", "Info on amounts in the block at this block height (only available if coinstatsindex is used)",
{ {
{RPCResult::Type::STR_AMOUNT, "prevout_spent", ""}, {RPCResult::Type::STR_AMOUNT, "prevout_spent", "Total amount of all prevouts spent in this block"},
{RPCResult::Type::STR_AMOUNT, "coinbase", ""}, {RPCResult::Type::STR_AMOUNT, "coinbase", "Coinbase subsidy amount of this block"},
{RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", ""}, {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", "Total amount of new outputs created by this block"},
{RPCResult::Type::STR_AMOUNT, "unspendable", ""}, {RPCResult::Type::STR_AMOUNT, "unspendable", "Total amount of unspendable outputs created in this block"},
{RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories", {RPCResult::Type::OBJ, "unspendables", "Detailed view of the unspendable categories",
{ {
{RPCResult::Type::STR_AMOUNT, "genesis_block", ""}, {RPCResult::Type::STR_AMOUNT, "genesis_block", "The unspendable amount of the Genesis block subsidy"},
{RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"}, {RPCResult::Type::STR_AMOUNT, "bip30", "Transactions overridden by duplicates (no longer possible with BIP30)"},
{RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"}, {RPCResult::Type::STR_AMOUNT, "scripts", "Amounts sent to scripts that are unspendable (for example OP_RETURN outputs)"},
{RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"}, {RPCResult::Type::STR_AMOUNT, "unclaimed_rewards", "Fee rewards that miners did not claim in their coinbase transaction"},
@ -1402,6 +1402,18 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
pindex = ParseHashOrHeight(request.params[1], chainman); pindex = ParseHashOrHeight(request.params[1], chainman);
} }
if (stats.index_requested && g_coin_stats_index) {
if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
const IndexSummary summary{g_coin_stats_index->GetSummary()};
// If a specific block was requested and the index has already synced past that height, we can return the
// data already even though the index is not fully synced yet.
if (pindex->nHeight > summary.best_block_height) {
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to get data because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
}
}
}
if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) { if (GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex)) {
ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("height", (int64_t)stats.nHeight);
ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("bestblock", stats.hashBlock.GetHex());
@ -1418,7 +1430,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions)); ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
ret.pushKV("disk_size", stats.nDiskSize); ret.pushKV("disk_size", stats.nDiskSize);
} else { } else {
ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.block_unspendable_amount)); ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
CCoinsStats prev_stats{hash_type}; CCoinsStats prev_stats{hash_type};
@ -1427,28 +1439,21 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
} }
UniValue block_info(UniValue::VOBJ); UniValue block_info(UniValue::VOBJ);
block_info.pushKV("prevout_spent", ValueFromAmount(stats.block_prevout_spent_amount - prev_stats.block_prevout_spent_amount)); block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount));
block_info.pushKV("coinbase", ValueFromAmount(stats.block_coinbase_amount - prev_stats.block_coinbase_amount)); block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_coinbase_amount));
block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.block_new_outputs_ex_coinbase_amount - prev_stats.block_new_outputs_ex_coinbase_amount)); block_info.pushKV("new_outputs_ex_coinbase", ValueFromAmount(stats.total_new_outputs_ex_coinbase_amount - prev_stats.total_new_outputs_ex_coinbase_amount));
block_info.pushKV("unspendable", ValueFromAmount(stats.block_unspendable_amount - prev_stats.block_unspendable_amount)); block_info.pushKV("unspendable", ValueFromAmount(stats.total_unspendable_amount - prev_stats.total_unspendable_amount));
UniValue unspendables(UniValue::VOBJ); UniValue unspendables(UniValue::VOBJ);
unspendables.pushKV("genesis_block", ValueFromAmount(stats.unspendables_genesis_block - prev_stats.unspendables_genesis_block)); unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block));
unspendables.pushKV("bip30", ValueFromAmount(stats.unspendables_bip30 - prev_stats.unspendables_bip30)); unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30));
unspendables.pushKV("scripts", ValueFromAmount(stats.unspendables_scripts - prev_stats.unspendables_scripts)); unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts));
unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.unspendables_unclaimed_rewards - prev_stats.unspendables_unclaimed_rewards)); unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards));
block_info.pushKV("unspendables", unspendables); block_info.pushKV("unspendables", unspendables);
ret.pushKV("block_info", block_info); ret.pushKV("block_info", block_info);
} }
} else { } else {
if (g_coin_stats_index) {
const IndexSummary summary{g_coin_stats_index->GetSummary()};
if (!summary.synced) {
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to read UTXO set because coinstatsindex is still syncing. Current height: %d", summary.best_block_height));
}
}
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
} }
return ret; return ret;

View File

@ -34,8 +34,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
assert_equal, assert_equal,
assert_raises_rpc_error, assert_raises_rpc_error,
try_rpc,
wait_until,
) )
class CoinStatsIndexTest(BitcoinTestFramework): class CoinStatsIndexTest(BitcoinTestFramework):
@ -92,13 +90,11 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.sync_blocks(timeout=120) self.sync_blocks(timeout=120)
self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option") self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo))
res0 = node.gettxoutsetinfo('none') res0 = node.gettxoutsetinfo('none')
# The fields 'disk_size' and 'transactions' do not exist on the index # The fields 'disk_size' and 'transactions' do not exist on the index
del res0['disk_size'], res0['transactions'] del res0['disk_size'], res0['transactions']
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options: for hash_option in index_hash_options:
res1 = index_node.gettxoutsetinfo(hash_option) res1 = index_node.gettxoutsetinfo(hash_option)
# The fields 'block_info' and 'total_unspendable_amount' only exist on the index # The fields 'block_info' and 'total_unspendable_amount' only exist on the index
@ -113,7 +109,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
# Generate a new tip # Generate a new tip
node.generate(5) node.generate(5)
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options: for hash_option in index_hash_options:
# Fetch old stats by height # Fetch old stats by height
res2 = index_node.gettxoutsetinfo(hash_option, 102) res2 = index_node.gettxoutsetinfo(hash_option, 102)
@ -192,7 +187,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options: for hash_option in index_hash_options:
# Check all amounts were registered correctly # Check all amounts were registered correctly
res6 = index_node.gettxoutsetinfo(hash_option, 108) res6 = index_node.gettxoutsetinfo(hash_option, 108)
@ -225,7 +219,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
self.nodes[0].submitblock(ToHex(block)) self.nodes[0].submitblock(ToHex(block))
self.sync_all() self.sync_all()
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
for hash_option in index_hash_options: for hash_option in index_hash_options:
res7 = index_node.gettxoutsetinfo(hash_option, 109) res7 = index_node.gettxoutsetinfo(hash_option, 109)
assert_equal(res7['total_unspendable_amount'], Decimal('530.98999999')) assert_equal(res7['total_unspendable_amount'], Decimal('530.98999999'))
@ -251,7 +244,6 @@ class CoinStatsIndexTest(BitcoinTestFramework):
assert_equal(res8, res9) assert_equal(res8, res9)
index_node.generate(1) index_node.generate(1)
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res10 = index_node.gettxoutsetinfo('muhash') res10 = index_node.gettxoutsetinfo('muhash')
assert(res8['txouts'] < res10['txouts']) assert(res8['txouts'] < res10['txouts'])
@ -272,14 +264,12 @@ class CoinStatsIndexTest(BitcoinTestFramework):
index_node = self.nodes[1] index_node = self.nodes[1]
reorg_blocks = index_node.generatetoaddress(2, index_node.getnewaddress()) reorg_blocks = index_node.generatetoaddress(2, index_node.getnewaddress())
reorg_block = reorg_blocks[1] reorg_block = reorg_blocks[1]
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res_invalid = index_node.gettxoutsetinfo('muhash') res_invalid = index_node.gettxoutsetinfo('muhash')
index_node.invalidateblock(reorg_blocks[0]) index_node.invalidateblock(reorg_blocks[0])
assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110) assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110)
# Add two new blocks # Add two new blocks
block = index_node.generate(2)[1] block = index_node.generate(2)[1]
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False) res = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)
# Test that the result of the reorged block is not returned for its old block height # Test that the result of the reorged block is not returned for its old block height
@ -300,9 +290,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
# Ensure that removing and re-adding blocks yields consistent results # Ensure that removing and re-adding blocks yields consistent results
block = index_node.getblockhash(99) block = index_node.getblockhash(99)
index_node.invalidateblock(block) index_node.invalidateblock(block)
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
index_node.reconsiderblock(block) index_node.reconsiderblock(block)
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, 'muhash'))
res3 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112) res3 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
assert_equal(res2, res3) assert_equal(res2, res3)
@ -312,8 +300,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
node.getblock(reorg_block) node.getblock(reorg_block)
self.restart_node(0, ["-coinstatsindex"]) self.restart_node(0, ["-coinstatsindex"])
wait_until(lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash')) assert_raises_rpc_error(-32603, "Unable to get data because coinstatsindex is still syncing.", node.gettxoutsetinfo, 'muhash', reorg_block)
assert_raises_rpc_error(-32603, "Unable to read UTXO set", node.gettxoutsetinfo, 'muhash', reorg_block)
def _test_index_rejects_hash_serialized(self): def _test_index_rejects_hash_serialized(self):
self.log.info("Test that the rpc raises if the legacy hash is passed with the index") self.log.info("Test that the rpc raises if the legacy hash is passed with the index")