Skip to content

Commit

Permalink
rpc, index: Add verbose amounts tracking to Coinstats index
Browse files Browse the repository at this point in the history
Summary:
This is a backport of [[bitcoin/bitcoin#19521 | core#19521]] [11/17] and [[bitcoin/bitcoin#22047 | core#22047]] [2/3]
bitcoin/bitcoin@2501576
bitcoin/bitcoin@fb65dde

Depends on D11604

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Differential Revision: https://reviews.bitcoinabc.org/D11605
  • Loading branch information
fjahr authored and PiRK committed Jun 13, 2022
1 parent 6b6d8c9 commit 13d1d58
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 36 deletions.
2 changes: 2 additions & 0 deletions doc/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ This release includes the following features and fixes:
- Users can specify a height or block hash when calling `gettxoutsetinfo` to
see coin statistics at a specific block height when they use the `-coinstatsindex`
option.
- Additional amount tracking information has been added to the output of
`gettxoutsetinfo` when the `-coinstatsindex` option is set.
142 changes: 137 additions & 5 deletions src/index/coinstatsindex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,30 @@ struct DBVal {
uint64_t transaction_output_count;
uint64_t bogo_size;
Amount total_amount;
Amount total_subsidy;
Amount total_unspendable_amount;
Amount total_prevout_spent_amount;
Amount total_new_outputs_ex_coinbase_amount;
Amount total_coinbase_amount;
Amount total_unspendables_genesis_block;
Amount total_unspendables_bip30;
Amount total_unspendables_scripts;
Amount total_unspendables_unclaimed_rewards;

SERIALIZE_METHODS(DBVal, obj) {
READWRITE(obj.muhash);
READWRITE(obj.transaction_output_count);
READWRITE(obj.bogo_size);
READWRITE(obj.total_amount);
READWRITE(obj.total_subsidy);
READWRITE(obj.total_unspendable_amount);
READWRITE(obj.total_prevout_spent_amount);
READWRITE(obj.total_new_outputs_ex_coinbase_amount);
READWRITE(obj.total_coinbase_amount);
READWRITE(obj.total_unspendables_genesis_block);
READWRITE(obj.total_unspendables_bip30);
READWRITE(obj.total_unspendables_scripts);
READWRITE(obj.total_unspendables_unclaimed_rewards);
}
};

Expand Down Expand Up @@ -89,6 +107,9 @@ CoinStatsIndex::CoinStatsIndex(size_t n_cache_size, bool f_memory,
bool CoinStatsIndex::WriteBlock(const CBlock &block,
const CBlockIndex *pindex) {
CBlockUndo block_undo;
const Amount block_subsidy{
GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())};
m_total_subsidy += block_subsidy;

// Ignore genesis block
if (pindex->nHeight > 0) {
Expand Down Expand Up @@ -128,6 +149,8 @@ bool CoinStatsIndex::WriteBlock(const CBlock &block,

// Skip duplicate txid coinbase transactions (BIP30).
if (is_bip30_block && tx->IsCoinBase()) {
m_total_unspendable_amount += block_subsidy;
m_total_unspendables_bip30 += block_subsidy;
continue;
}

Expand All @@ -139,11 +162,20 @@ bool CoinStatsIndex::WriteBlock(const CBlock &block,

// Skip unspendable coins
if (coin.GetTxOut().scriptPubKey.IsUnspendable()) {
m_total_unspendable_amount += coin.GetTxOut().nValue;
m_total_unspendables_scripts += coin.GetTxOut().nValue;
continue;
}

m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));

if (tx->IsCoinBase()) {
m_total_coinbase_amount += coin.GetTxOut().nValue;
} else {
m_total_new_outputs_ex_coinbase_amount +=
coin.GetTxOut().nValue;
}

++m_transaction_output_count;
m_total_amount += coin.GetTxOut().nValue;
m_bogo_size += GetBogoSize(coin.GetTxOut().scriptPubKey);
Expand All @@ -160,19 +192,48 @@ bool CoinStatsIndex::WriteBlock(const CBlock &block,

m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));

m_total_prevout_spent_amount += coin.GetTxOut().nValue;

--m_transaction_output_count;
m_total_amount -= coin.GetTxOut().nValue;
m_bogo_size -= GetBogoSize(coin.GetTxOut().scriptPubKey);
}
}
}
} else {
// genesis block
m_total_unspendable_amount += block_subsidy;
m_total_unspendables_genesis_block += block_subsidy;
}

// If spent prevouts + block subsidy are still a higher amount than
// new outputs + coinbase + current unspendable amount this means
// the miner did not claim the full block reward. Unclaimed block
// rewards are also unspendable.
const Amount 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_total_unspendable_amount += unclaimed_rewards;
m_total_unspendables_unclaimed_rewards += unclaimed_rewards;

std::pair<BlockHash, DBVal> value;
value.first = pindex->GetBlockHash();
value.second.transaction_output_count = m_transaction_output_count;
value.second.bogo_size = m_bogo_size;
value.second.total_amount = m_total_amount;
value.second.total_subsidy = m_total_subsidy;
value.second.total_unspendable_amount = m_total_unspendable_amount;
value.second.total_prevout_spent_amount = m_total_prevout_spent_amount;
value.second.total_new_outputs_ex_coinbase_amount =
m_total_new_outputs_ex_coinbase_amount;
value.second.total_coinbase_amount = m_total_coinbase_amount;
value.second.total_unspendables_genesis_block =
m_total_unspendables_genesis_block;
value.second.total_unspendables_bip30 = m_total_unspendables_bip30;
value.second.total_unspendables_scripts = m_total_unspendables_scripts;
value.second.total_unspendables_unclaimed_rewards =
m_total_unspendables_unclaimed_rewards;

uint256 out;
m_muhash.Finalize(out);
Expand Down Expand Up @@ -281,6 +342,18 @@ bool CoinStatsIndex::LookUpStats(const CBlockIndex *block_index,
coins_stats.nTransactionOutputs = entry.transaction_output_count;
coins_stats.nBogoSize = entry.bogo_size;
coins_stats.nTotalAmount = entry.total_amount;
coins_stats.total_subsidy = entry.total_subsidy;
coins_stats.total_unspendable_amount = entry.total_unspendable_amount;
coins_stats.total_prevout_spent_amount = entry.total_prevout_spent_amount;
coins_stats.total_new_outputs_ex_coinbase_amount =
entry.total_new_outputs_ex_coinbase_amount;
coins_stats.total_coinbase_amount = entry.total_coinbase_amount;
coins_stats.total_unspendables_genesis_block =
entry.total_unspendables_genesis_block;
coins_stats.total_unspendables_bip30 = entry.total_unspendables_bip30;
coins_stats.total_unspendables_scripts = entry.total_unspendables_scripts;
coins_stats.total_unspendables_unclaimed_rewards =
entry.total_unspendables_unclaimed_rewards;

return true;
}
Expand Down Expand Up @@ -309,6 +382,18 @@ bool CoinStatsIndex::Init() {
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 true;
Expand All @@ -323,6 +408,10 @@ bool CoinStatsIndex::ReverseBlock(const CBlock &block,
CBlockUndo block_undo;
std::pair<BlockHash, DBVal> read_out;

const Amount block_subsidy{
GetBlockSubsidy(pindex->nHeight, Params().GetConsensus())};
m_total_subsidy -= block_subsidy;

// Ignore genesis block
if (pindex->nHeight > 0) {
if (!UndoReadFromDisk(block_undo, pindex)) {
Expand Down Expand Up @@ -356,10 +445,23 @@ bool CoinStatsIndex::ReverseBlock(const CBlock &block,

// Skip unspendable coins
if (coin.GetTxOut().scriptPubKey.IsUnspendable()) {
m_total_unspendable_amount -= coin.GetTxOut().nValue;
m_total_unspendables_scripts -= coin.GetTxOut().nValue;
continue;
}

m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));

if (tx->IsCoinBase()) {
m_total_coinbase_amount -= coin.GetTxOut().nValue;
} else {
m_total_new_outputs_ex_coinbase_amount -=
coin.GetTxOut().nValue;
}

--m_transaction_output_count;
m_total_amount -= coin.GetTxOut().nValue;
m_bogo_size -= GetBogoSize(coin.GetTxOut().scriptPubKey);
}

// The coinbase tx has no undo data since no former output is spent
Expand All @@ -372,19 +474,49 @@ bool CoinStatsIndex::ReverseBlock(const CBlock &block,
tx->vin[j].prevout.GetN()};

m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));

m_total_prevout_spent_amount -= coin.GetTxOut().nValue;

m_transaction_output_count++;
m_total_amount += coin.GetTxOut().nValue;
m_bogo_size += GetBogoSize(coin.GetTxOut().scriptPubKey);
}
}
}

// Check that the rolled back internal value of muhash is consistent with
// the DB read out
const Amount 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_total_unspendable_amount -= unclaimed_rewards;
m_total_unspendables_unclaimed_rewards -= unclaimed_rewards;

// Check that the rolled back internal values are consistent with the DB
// read out
uint256 out;
m_muhash.Finalize(out);
Assert(read_out.second.muhash == out);

m_transaction_output_count = read_out.second.transaction_output_count;
m_total_amount = read_out.second.total_amount;
m_bogo_size = read_out.second.bogo_size;
Assert(m_transaction_output_count ==
read_out.second.transaction_output_count);
Assert(m_total_amount == read_out.second.total_amount);
Assert(m_bogo_size == read_out.second.bogo_size);
Assert(m_total_subsidy == read_out.second.total_subsidy);
Assert(m_total_unspendable_amount ==
read_out.second.total_unspendable_amount);
Assert(m_total_prevout_spent_amount ==
read_out.second.total_prevout_spent_amount);
Assert(m_total_new_outputs_ex_coinbase_amount ==
read_out.second.total_new_outputs_ex_coinbase_amount);
Assert(m_total_coinbase_amount == read_out.second.total_coinbase_amount);
Assert(m_total_unspendables_genesis_block ==
read_out.second.total_unspendables_genesis_block);
Assert(m_total_unspendables_bip30 ==
read_out.second.total_unspendables_bip30);
Assert(m_total_unspendables_scripts ==
read_out.second.total_unspendables_scripts);
Assert(m_total_unspendables_unclaimed_rewards ==
read_out.second.total_unspendables_unclaimed_rewards);

return m_db->Write(DB_MUHASH, m_muhash);
}
9 changes: 9 additions & 0 deletions src/index/coinstatsindex.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ class CoinStatsIndex final : public BaseIndex {
uint64_t m_transaction_output_count{0};
uint64_t m_bogo_size{0};
Amount m_total_amount{Amount::zero()};
Amount m_total_subsidy{Amount::zero()};
Amount m_total_unspendable_amount{Amount::zero()};
Amount m_total_prevout_spent_amount{Amount::zero()};
Amount m_total_new_outputs_ex_coinbase_amount{Amount::zero()};
Amount m_total_coinbase_amount{Amount::zero()};
Amount m_total_unspendables_genesis_block{Amount::zero()};
Amount m_total_unspendables_bip30{Amount::zero()};
Amount m_total_unspendables_scripts{Amount::zero()};
Amount m_total_unspendables_unclaimed_rewards{Amount::zero()};

bool ReverseBlock(const CBlock &block, const CBlockIndex *pindex);

Expand Down
11 changes: 11 additions & 0 deletions src/node/coinstats.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ struct CCoinsStats {

bool from_index{false};

// Following values are only available from coinstats index
Amount total_subsidy{Amount::zero()};
Amount total_unspendable_amount{Amount::zero()};
Amount total_prevout_spent_amount{Amount::zero()};
Amount total_new_outputs_ex_coinbase_amount{Amount::zero()};
Amount total_coinbase_amount{Amount::zero()};
Amount total_unspendables_genesis_block{Amount::zero()};
Amount total_unspendables_bip30{Amount::zero()};
Amount total_unspendables_scripts{Amount::zero()};
Amount total_unspendables_unclaimed_rewards{Amount::zero()};

CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {}
};

Expand Down
Loading

0 comments on commit 13d1d58

Please sign in to comment.