diff --git a/src/index/coinstatsindex.cpp b/src/index/coinstatsindex.cpp --- a/src/index/coinstatsindex.cpp +++ b/src/index/coinstatsindex.cpp @@ -130,11 +130,14 @@ BlockHash expected_block_hash{pindex->pprev->GetBlockHash()}; 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)) { - return error("%s: previous block header belongs to unexpected " - "block %s; expected %s", - __func__, read_out.first.ToString(), - expected_block_hash.ToString()); + return error("%s: previous block header not found; expected %s", + __func__, expected_block_hash.ToString()); } } @@ -376,37 +379,37 @@ } } - if (BaseIndex::Init()) { - const CBlockIndex *pindex{CurrentIndex()}; + if (!BaseIndex::Init()) { + return false; + } - if (pindex) { - DBVal entry; - if (!LookUpOne(*m_db, pindex, entry)) { - return error( - "%s: Cannot read current %s state; index may be corrupted", - __func__, GetName()); - } - 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; - } + const CBlockIndex *pindex{CurrentIndex()}; - return true; + if (pindex) { + DBVal entry; + if (!LookUpOne(*m_db, pindex, entry)) { + return error( + "%s: Cannot read current %s state; index may be corrupted", + __func__, GetName()); + } + 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 @@ -431,11 +434,14 @@ BlockHash expected_block_hash{pindex->pprev->GetBlockHash()}; 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)) { - return error("%s: previous block header belongs to unexpected " - "block %s; expected %s", - __func__, read_out.first.ToString(), - expected_block_hash.ToString()); + return error("%s: previous block header not found; expected %s", + __func__, expected_block_hash.ToString()); } } } diff --git a/src/node/coinstats.h b/src/node/coinstats.h --- a/src/node/coinstats.h +++ b/src/node/coinstats.h @@ -48,14 +48,30 @@ bool index_used{false}; // Following values are only available from coinstats index + + //! Total cumulative amount of block subsidies up to and including this + //! block Amount total_subsidy{Amount::zero()}; + //! Total cumulative amount of unspendable coins up to and including this + //! block Amount total_unspendable_amount{Amount::zero()}; + //! Total cumulative amount of prevouts spent up to and including this block Amount total_prevout_spent_amount{Amount::zero()}; + //! Total cumulative amount of outputs created up to and including this + //! block Amount total_new_outputs_ex_coinbase_amount{Amount::zero()}; + //! Total cumulative amount of coinbase outputs up to and including this + //! block Amount total_coinbase_amount{Amount::zero()}; + //! The unspendable coinbase amount from the genesis block Amount total_unspendables_genesis_block{Amount::zero()}; + //! The two unspendable coinbase outputs total amount caused by BIP30 Amount total_unspendables_bip30{Amount::zero()}; + //! Total cumulative amount of outputs sent to unspendable scripts + //! (OP_RETURN for example) up to and including this block Amount total_unspendables_scripts{Amount::zero()}; + //! Total cumulative amount of coins lost due to unclaimed miner rewards up + //! to and including this block Amount total_unspendables_unclaimed_rewards{Amount::zero()}; CCoinsStats(CoinStatsHashType hash_type) : m_hash_type(hash_type) {} diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1414,15 +1414,20 @@ "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, "coinbase", ""}, - {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", ""}, - {RPCResult::Type::STR_AMOUNT, "unspendable", ""}, + {{RPCResult::Type::STR_AMOUNT, "prevout_spent", + "Total amount of all prevouts spent in this block"}, + {RPCResult::Type::STR_AMOUNT, "coinbase", + "Coinbase subsidy amount of this block"}, + {RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase", + "Total amount of new outputs created by this block"}, + {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::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)"}, @@ -1490,6 +1495,25 @@ 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)) { ret.pushKV("height", int64_t(stats.nHeight)); @@ -1556,19 +1580,6 @@ ret.pushKV("block_info", block_info); } } 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"); } diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -15,7 +15,7 @@ from test_framework.messages import XEC, CTxOut, ToHex from test_framework.script import OP_FALSE, OP_RETURN, CScript from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, try_rpc +from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.wallet import MiniWallet, getnewdestination @@ -64,19 +64,11 @@ "Test that gettxoutsetinfo() output is consistent with or without" " coinstatsindex option" ) - self.wait_until( - lambda: not try_rpc(-32603, "Unable to read UTXO set", node.gettxoutsetinfo) - ) res0 = node.gettxoutsetinfo("none") # The fields 'disk_size' and 'transactions' do not exist on the index del res0["disk_size"], res0["transactions"] - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, "muhash" - ) - ) for hash_option in index_hash_options: res1 = index_node.gettxoutsetinfo(hash_option) # The fields 'block_info' and 'total_unspendable_amount' only exist @@ -95,11 +87,6 @@ # Generate a new tip self.generate(node, 5) - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, "muhash" - ) - ) for hash_option in index_hash_options: # Fetch old stats by height res2 = index_node.gettxoutsetinfo(hash_option, 102) @@ -189,11 +176,6 @@ # Include both txs in a block self.generate(self.nodes[0], 1) - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, "muhash" - ) - ) for hash_option in index_hash_options: # Check all amounts were registered correctly res6 = index_node.gettxoutsetinfo(hash_option, 108) @@ -229,11 +211,6 @@ self.nodes[0].submitblock(ToHex(block)) self.sync_all() - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, "muhash" - ) - ) for hash_option in index_hash_options: res7 = index_node.gettxoutsetinfo(hash_option, 109) assert_equal(res7["total_unspendable_amount"], Decimal("80990000.00")) @@ -262,11 +239,6 @@ assert_equal(res8, res9) self.generate(index_node, 1, sync_fun=self.no_op) - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, "muhash" - ) - ) res10 = index_node.gettxoutsetinfo("muhash") assert res8["txouts"] < res10["txouts"] @@ -290,22 +262,12 @@ index_node = self.nodes[1] reorg_blocks = self.generatetoaddress(index_node, 2, getnewdestination()[2]) reorg_block = reorg_blocks[1] - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, "muhash" - ) - ) res_invalid = index_node.gettxoutsetinfo("muhash") index_node.invalidateblock(reorg_blocks[0]) assert_equal(index_node.gettxoutsetinfo("muhash")["height"], 110) # Add two new blocks block = self.generate(index_node, 2, sync_fun=self.no_op)[1] - self.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 ) @@ -332,17 +294,7 @@ # Ensure that removing and re-adding blocks yields consistent results block = index_node.getblockhash(99) index_node.invalidateblock(block) - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", index_node.gettxoutsetinfo, "muhash" - ) - ) index_node.reconsiderblock(block) - self.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) assert_equal(res2, res3) @@ -352,14 +304,9 @@ node.getblock(reorg_block) self.restart_node(0, ["-coinstatsindex"]) - self.wait_until( - lambda: not try_rpc( - -32603, "Unable to read UTXO set", node.gettxoutsetinfo, "muhash" - ) - ) assert_raises_rpc_error( -32603, - "Unable to read UTXO set", + "Unable to get data because coinstatsindex is still syncing.", node.gettxoutsetinfo, "muhash", reorg_block,