mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
Merge pull request #5501 from kwvg/assumeutxo5
backport: merge bitcoin#19521, #22047, #21796, #21767, #24133, #24921, #27988, partial #24117, #24138, #27405 (assumeutxo: part 5)
This commit is contained in:
commit
0b8e501b51
@ -199,6 +199,7 @@ BITCOIN_CORE_H = \
|
||||
i2p.h \
|
||||
index/base.h \
|
||||
index/blockfilterindex.h \
|
||||
index/coinstatsindex.h \
|
||||
index/disktxpos.h \
|
||||
index/txindex.h \
|
||||
indirectmap.h \
|
||||
@ -408,6 +409,7 @@ libbitcoin_server_a_SOURCES = \
|
||||
i2p.cpp \
|
||||
index/base.cpp \
|
||||
index/blockfilterindex.cpp \
|
||||
index/coinstatsindex.cpp \
|
||||
index/txindex.cpp \
|
||||
init.cpp \
|
||||
governance/governance.cpp \
|
||||
|
@ -87,6 +87,7 @@ BITCOIN_TESTS =\
|
||||
test/cachemap_tests.cpp \
|
||||
test/cachemultimap_tests.cpp \
|
||||
test/coins_tests.cpp \
|
||||
test/coinstatsindex_tests.cpp \
|
||||
test/compilerbug_tests.cpp \
|
||||
test/compress_tests.cpp \
|
||||
test/crypto_tests.cpp \
|
||||
|
@ -9,6 +9,7 @@ EXTRA_LIBRARIES += \
|
||||
|
||||
TEST_UTIL_H = \
|
||||
test/util/blockfilter.h \
|
||||
test/util/index.h \
|
||||
test/util/logging.h \
|
||||
test/util/mining.h \
|
||||
test/util/net.h \
|
||||
@ -21,6 +22,7 @@ libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAG
|
||||
libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
libtest_util_a_SOURCES = \
|
||||
test/util/blockfilter.cpp \
|
||||
test/util/index.cpp \
|
||||
test/util/logging.cpp \
|
||||
test/util/mining.cpp \
|
||||
test/util/net.cpp \
|
||||
|
@ -42,7 +42,7 @@ static void AssembleBlock(benchmark::Bench& bench)
|
||||
|
||||
for (const auto& txr : txs) {
|
||||
TxValidationState state;
|
||||
bool ret{::AcceptToMemoryPool(::ChainstateActive(), *test_setup.m_node.mempool, state, txr, false /* bypass_limits */, /* nAbsurdFee */ 0)};
|
||||
bool ret{::AcceptToMemoryPool(test_setup.m_node.chainman->ActiveChainstate(), *test_setup.m_node.mempool, state, txr, false /* bypass_limits */, /* nAbsurdFee */ 0)};
|
||||
assert(ret);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ static void DuplicateInputs(benchmark::Bench& bench)
|
||||
CMutableTransaction coinbaseTx{};
|
||||
CMutableTransaction naughtyTx{};
|
||||
|
||||
CBlockIndex* pindexPrev = ::ChainActive().Tip();
|
||||
assert(std::addressof(::ChainActive()) == std::addressof(test_setup.m_node.chainman->ActiveChain()));
|
||||
CBlockIndex* pindexPrev = test_setup.m_node.chainman->ActiveChain().Tip();
|
||||
assert(pindexPrev != nullptr);
|
||||
block.nBits = GetNextWorkRequired(pindexPrev, &block, chainparams.GetConsensus());
|
||||
block.nNonce = 0;
|
||||
|
@ -341,6 +341,6 @@ MuHash3072& MuHash3072::Insert(Span<const unsigned char> in) noexcept {
|
||||
}
|
||||
|
||||
MuHash3072& MuHash3072::Remove(Span<const unsigned char> in) noexcept {
|
||||
m_numerator.Divide(ToNum3072(in));
|
||||
m_denominator.Multiply(ToNum3072(in));
|
||||
return *this;
|
||||
}
|
||||
|
@ -59,33 +59,34 @@ bool BaseIndex::Init()
|
||||
}
|
||||
|
||||
LOCK(cs_main);
|
||||
CChain& active_chain = m_chainstate->m_chain;
|
||||
if (locator.IsNull()) {
|
||||
m_best_block_index = nullptr;
|
||||
} else {
|
||||
m_best_block_index = g_chainman.m_blockman.FindForkInGlobalIndex(::ChainActive(), locator);
|
||||
m_best_block_index = m_chainstate->m_blockman.FindForkInGlobalIndex(active_chain, locator);
|
||||
}
|
||||
m_synced = m_best_block_index.load() == ::ChainActive().Tip();
|
||||
m_synced = m_best_block_index.load() == active_chain.Tip();
|
||||
if (!m_synced) {
|
||||
bool prune_violation = false;
|
||||
if (!m_best_block_index) {
|
||||
// index is not built yet
|
||||
// make sure we have all block data back to the genesis
|
||||
const CBlockIndex* block = ::ChainActive().Tip();
|
||||
const CBlockIndex* block = active_chain.Tip();
|
||||
while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
|
||||
block = block->pprev;
|
||||
}
|
||||
prune_violation = block != ::ChainActive().Genesis();
|
||||
prune_violation = block != active_chain.Genesis();
|
||||
}
|
||||
// in case the index has a best block set and is not fully synced
|
||||
// check if we have the required blocks to continue building the index
|
||||
else {
|
||||
const CBlockIndex* block_to_test = m_best_block_index.load();
|
||||
if (!ChainActive().Contains(block_to_test)) {
|
||||
if (!active_chain.Contains(block_to_test)) {
|
||||
// if the bestblock is not part of the mainchain, find the fork
|
||||
// and make sure we have all data down to the fork
|
||||
block_to_test = ::ChainActive().FindFork(block_to_test);
|
||||
block_to_test = active_chain.FindFork(block_to_test);
|
||||
}
|
||||
const CBlockIndex* block = ::ChainActive().Tip();
|
||||
const CBlockIndex* block = active_chain.Tip();
|
||||
prune_violation = true;
|
||||
// check backwards from the tip if we have all block data until we reach the indexes bestblock
|
||||
while (block_to_test && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
|
||||
@ -97,28 +98,26 @@ bool BaseIndex::Init()
|
||||
}
|
||||
}
|
||||
if (prune_violation) {
|
||||
// throw error and graceful shutdown if we can't build the index
|
||||
FatalError("%s: %s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)", __func__, GetName());
|
||||
return false;
|
||||
return InitError(strprintf(Untranslated("%s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"), GetName()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev, CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
|
||||
if (!pindex_prev) {
|
||||
return ::ChainActive().Genesis();
|
||||
return chain.Genesis();
|
||||
}
|
||||
|
||||
const CBlockIndex* pindex = ::ChainActive().Next(pindex_prev);
|
||||
const CBlockIndex* pindex = chain.Next(pindex_prev);
|
||||
if (pindex) {
|
||||
return pindex;
|
||||
}
|
||||
|
||||
return ::ChainActive().Next(::ChainActive().FindFork(pindex_prev));
|
||||
return chain.Next(chain.FindFork(pindex_prev));
|
||||
}
|
||||
|
||||
void BaseIndex::ThreadSync()
|
||||
@ -141,7 +140,7 @@ void BaseIndex::ThreadSync()
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
const CBlockIndex* pindex_next = NextSyncBlock(pindex);
|
||||
const CBlockIndex* pindex_next = NextSyncBlock(pindex, m_chainstate->m_chain);
|
||||
if (!pindex_next) {
|
||||
m_best_block_index = pindex;
|
||||
m_synced = true;
|
||||
@ -204,7 +203,12 @@ bool BaseIndex::Commit()
|
||||
bool BaseIndex::CommitInternal(CDBBatch& batch)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
GetDB().WriteBestBlock(batch, ::ChainActive().GetLocator(m_best_block_index));
|
||||
// Don't commit anything if we haven't indexed any block yet
|
||||
// (this could happen if init is interrupted).
|
||||
if (m_best_block_index == nullptr) {
|
||||
return false;
|
||||
}
|
||||
GetDB().WriteBestBlock(batch, m_chainstate->m_chain.GetLocator(m_best_block_index));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -280,7 +284,7 @@ void BaseIndex::ChainStateFlushed(const CBlockLocator& locator)
|
||||
const CBlockIndex* locator_tip_index;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
locator_tip_index = g_chainman.m_blockman.LookupBlockIndex(locator_tip_hash);
|
||||
locator_tip_index = m_chainstate->m_blockman.LookupBlockIndex(locator_tip_hash);
|
||||
}
|
||||
|
||||
if (!locator_tip_index) {
|
||||
@ -321,7 +325,7 @@ bool BaseIndex::BlockUntilSyncedToCurrentChain() const
|
||||
// Skip the queue-draining stuff if we know we're caught up with
|
||||
// ::ChainActive().Tip().
|
||||
LOCK(cs_main);
|
||||
const CBlockIndex* chain_tip = ::ChainActive().Tip();
|
||||
const CBlockIndex* chain_tip = m_chainstate->m_chain.Tip();
|
||||
const CBlockIndex* best_block_index = m_best_block_index.load();
|
||||
if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) {
|
||||
return true;
|
||||
@ -338,18 +342,20 @@ void BaseIndex::Interrupt()
|
||||
m_interrupt();
|
||||
}
|
||||
|
||||
void BaseIndex::Start()
|
||||
bool BaseIndex::Start(CChainState& active_chainstate)
|
||||
{
|
||||
assert(std::addressof(::ChainstateActive()) == std::addressof(active_chainstate));
|
||||
m_chainstate = &active_chainstate;
|
||||
// Need to register this ValidationInterface before running Init(), so that
|
||||
// callbacks are not missed if Init sets m_synced to true.
|
||||
RegisterValidationInterface(this);
|
||||
if (!Init()) {
|
||||
FatalError("%s: %s failed to initialize", __func__, GetName());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_thread_sync = std::thread(&TraceThread<std::function<void()>>, GetName(),
|
||||
std::bind(&BaseIndex::ThreadSync, this));
|
||||
return true;
|
||||
}
|
||||
|
||||
void BaseIndex::Stop()
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <validationinterface.h>
|
||||
|
||||
class CBlockIndex;
|
||||
class CChainState;
|
||||
|
||||
struct IndexSummary {
|
||||
std::string name;
|
||||
@ -40,10 +41,10 @@ protected:
|
||||
DB(const fs::path& path, size_t n_cache_size,
|
||||
bool f_memory = false, bool f_wipe = false, bool f_obfuscate = false);
|
||||
|
||||
/// Read block locator of the chain that the txindex is in sync with.
|
||||
/// Read block locator of the chain that the index is in sync with.
|
||||
bool ReadBestBlock(CBlockLocator& locator) const;
|
||||
|
||||
/// Write block locator of the chain that the txindex is in sync with.
|
||||
/// Write block locator of the chain that the index is in sync with.
|
||||
void WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator);
|
||||
};
|
||||
|
||||
@ -75,14 +76,17 @@ private:
|
||||
/// to a chain reorganization), the index must halt until Commit succeeds or else it could end up
|
||||
/// getting corrupted.
|
||||
bool Commit();
|
||||
|
||||
protected:
|
||||
CChainState* m_chainstate{nullptr};
|
||||
|
||||
void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override;
|
||||
|
||||
void ChainStateFlushed(const CBlockLocator& locator) override;
|
||||
|
||||
const CBlockIndex* CurrentIndex() { return m_best_block_index.load(); };
|
||||
|
||||
/// Initialize internal state from the database and block index.
|
||||
virtual bool Init();
|
||||
[[nodiscard]] virtual bool Init();
|
||||
|
||||
/// Write update index entries for a newly connected block.
|
||||
virtual bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) { return true; }
|
||||
@ -115,7 +119,7 @@ public:
|
||||
|
||||
/// Start initializes the sync state and registers the instance as a
|
||||
/// ValidationInterface so that it stays in sync with blockchain updates.
|
||||
void Start();
|
||||
[[nodiscard]] bool Start(CChainState& active_chainstate);
|
||||
|
||||
/// Stops the instance from staying in sync with blockchain updates.
|
||||
void Stop();
|
||||
|
493
src/index/coinstatsindex.cpp
Normal file
493
src/index/coinstatsindex.cpp
Normal file
@ -0,0 +1,493 @@
|
||||
// Copyright (c) 2020-2021 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <chainparams.h>
|
||||
#include <coins.h>
|
||||
#include <crypto/muhash.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <node/blockstorage.h>
|
||||
#include <serialize.h>
|
||||
#include <txdb.h>
|
||||
#include <undo.h>
|
||||
#include <validation.h>
|
||||
#include <util/check.h>
|
||||
|
||||
static constexpr char DB_BLOCK_HASH = 's';
|
||||
static constexpr char DB_BLOCK_HEIGHT = 't';
|
||||
static constexpr char DB_MUHASH = 'M';
|
||||
|
||||
namespace {
|
||||
|
||||
struct DBVal {
|
||||
uint256 muhash;
|
||||
uint64_t transaction_output_count;
|
||||
uint64_t bogo_size;
|
||||
CAmount total_amount;
|
||||
CAmount total_subsidy;
|
||||
CAmount total_unspendable_amount;
|
||||
CAmount total_prevout_spent_amount;
|
||||
CAmount total_new_outputs_ex_coinbase_amount;
|
||||
CAmount total_coinbase_amount;
|
||||
CAmount total_unspendables_genesis_block;
|
||||
CAmount total_unspendables_bip30;
|
||||
CAmount total_unspendables_scripts;
|
||||
CAmount 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);
|
||||
}
|
||||
};
|
||||
|
||||
struct DBHeightKey {
|
||||
int height;
|
||||
|
||||
explicit DBHeightKey(int height_in) : height(height_in) {}
|
||||
|
||||
template <typename Stream>
|
||||
void Serialize(Stream& s) const
|
||||
{
|
||||
ser_writedata8(s, DB_BLOCK_HEIGHT);
|
||||
ser_writedata32be(s, height);
|
||||
}
|
||||
|
||||
template <typename Stream>
|
||||
void Unserialize(Stream& s)
|
||||
{
|
||||
char prefix{static_cast<char>(ser_readdata8(s))};
|
||||
if (prefix != DB_BLOCK_HEIGHT) {
|
||||
throw std::ios_base::failure("Invalid format for coinstatsindex DB height key");
|
||||
}
|
||||
height = ser_readdata32be(s);
|
||||
}
|
||||
};
|
||||
|
||||
struct DBHashKey {
|
||||
uint256 block_hash;
|
||||
|
||||
explicit DBHashKey(const uint256& hash_in) : block_hash(hash_in) {}
|
||||
|
||||
SERIALIZE_METHODS(DBHashKey, obj)
|
||||
{
|
||||
char prefix{DB_BLOCK_HASH};
|
||||
READWRITE(prefix);
|
||||
if (prefix != DB_BLOCK_HASH) {
|
||||
throw std::ios_base::failure("Invalid format for coinstatsindex DB hash key");
|
||||
}
|
||||
|
||||
READWRITE(obj.block_hash);
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace
|
||||
|
||||
std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
|
||||
|
||||
CoinStatsIndex::CoinStatsIndex(size_t n_cache_size, bool f_memory, bool f_wipe)
|
||||
{
|
||||
fs::path path{GetDataDir() / "indexes" / "coinstats"};
|
||||
fs::create_directories(path);
|
||||
|
||||
m_db = std::make_unique<CoinStatsIndex::DB>(path / "db", n_cache_size, f_memory, f_wipe);
|
||||
}
|
||||
|
||||
bool CoinStatsIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
|
||||
{
|
||||
CBlockUndo block_undo;
|
||||
const CAmount block_subsidy{pindex->nHeight > 0 ? GetBlockSubsidy(pindex->pprev->nBits, pindex->pprev->nHeight, Params().GetConsensus()) : Params().GenesisBlock().vtx[0]->GetValueOut()};
|
||||
m_total_subsidy += block_subsidy;
|
||||
|
||||
// Ignore genesis block
|
||||
if (pindex->nHeight > 0) {
|
||||
if (!UndoReadFromDisk(block_undo, pindex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<uint256, DBVal> read_out;
|
||||
if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 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 not found; expected %s",
|
||||
__func__, expected_block_hash.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Deduplicate BIP30 related code
|
||||
bool is_bip30_block{(pindex->nHeight == 91722 && pindex->GetBlockHash() == uint256S("0x00000000000271a2dc26e7667f8419f2e15416dc6955e5a6c6cdf3f2574dd08e")) ||
|
||||
(pindex->nHeight == 91812 && pindex->GetBlockHash() == uint256S("0x00000000000af0aed4792b1acee3d966af36cf5def14935db8de83d6f9306f2f"))};
|
||||
|
||||
// Add the new utxos created from the block
|
||||
for (size_t i = 0; i < block.vtx.size(); ++i) {
|
||||
const auto& tx{block.vtx.at(i)};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
for (uint32_t j = 0; j < tx->vout.size(); ++j) {
|
||||
const CTxOut& out{tx->vout[j]};
|
||||
Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
|
||||
COutPoint outpoint{tx->GetHash(), j};
|
||||
|
||||
// Skip unspendable coins
|
||||
if (coin.out.scriptPubKey.IsUnspendable()) {
|
||||
m_total_unspendable_amount += coin.out.nValue;
|
||||
m_total_unspendables_scripts += coin.out.nValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
|
||||
|
||||
if (tx->IsCoinBase()) {
|
||||
m_total_coinbase_amount += coin.out.nValue;
|
||||
} else {
|
||||
m_total_new_outputs_ex_coinbase_amount += coin.out.nValue;
|
||||
}
|
||||
|
||||
++m_transaction_output_count;
|
||||
m_total_amount += coin.out.nValue;
|
||||
m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
|
||||
}
|
||||
|
||||
// The coinbase tx has no undo data since no former output is spent
|
||||
if (!tx->IsCoinBase()) {
|
||||
const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
|
||||
|
||||
for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
|
||||
Coin coin{tx_undo.vprevout[j]};
|
||||
COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
|
||||
|
||||
m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
|
||||
|
||||
m_total_prevout_spent_amount += coin.out.nValue;
|
||||
|
||||
--m_transaction_output_count;
|
||||
m_total_amount -= coin.out.nValue;
|
||||
m_bogo_size -= GetBogoSize(coin.out.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 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_total_unspendable_amount += unclaimed_rewards;
|
||||
m_total_unspendables_unclaimed_rewards += unclaimed_rewards;
|
||||
|
||||
std::pair<uint256, 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);
|
||||
value.second.muhash = out;
|
||||
|
||||
// Intentionally do not update DB_MUHASH here so it stays in sync with
|
||||
// DB_BEST_BLOCK, and the index is not corrupted if there is an unclean shutdown.
|
||||
return m_db->Write(DBHeightKey(pindex->nHeight), value);
|
||||
}
|
||||
|
||||
static bool CopyHeightIndexToHashIndex(CDBIterator& db_it, CDBBatch& batch,
|
||||
const std::string& index_name,
|
||||
int start_height, int stop_height)
|
||||
{
|
||||
DBHeightKey key{start_height};
|
||||
db_it.Seek(key);
|
||||
|
||||
for (int height = start_height; height <= stop_height; ++height) {
|
||||
if (!db_it.GetKey(key) || key.height != height) {
|
||||
return error("%s: unexpected key in %s: expected (%c, %d)",
|
||||
__func__, index_name, DB_BLOCK_HEIGHT, height);
|
||||
}
|
||||
|
||||
std::pair<uint256, DBVal> value;
|
||||
if (!db_it.GetValue(value)) {
|
||||
return error("%s: unable to read value in %s at key (%c, %d)",
|
||||
__func__, index_name, DB_BLOCK_HEIGHT, height);
|
||||
}
|
||||
|
||||
batch.Write(DBHashKey(value.first), std::move(value.second));
|
||||
|
||||
db_it.Next();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CoinStatsIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
|
||||
{
|
||||
assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
|
||||
|
||||
CDBBatch batch(*m_db);
|
||||
std::unique_ptr<CDBIterator> db_it(m_db->NewIterator());
|
||||
|
||||
// During a reorg, we need to copy all hash digests for blocks that are
|
||||
// getting disconnected from the height index to the hash index so we can
|
||||
// still find them when the height index entries are overwritten.
|
||||
if (!CopyHeightIndexToHashIndex(*db_it, batch, m_name, new_tip->nHeight, current_tip->nHeight)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->WriteBatch(batch)) return false;
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CBlockIndex* iter_tip{m_chainstate->m_blockman.LookupBlockIndex(current_tip->GetBlockHash())};
|
||||
const auto& consensus_params{Params().GetConsensus()};
|
||||
|
||||
do {
|
||||
CBlock block;
|
||||
|
||||
if (!ReadBlockFromDisk(block, iter_tip, consensus_params)) {
|
||||
return error("%s: Failed to read block %s from disk",
|
||||
__func__, iter_tip->GetBlockHash().ToString());
|
||||
}
|
||||
|
||||
ReverseBlock(block, iter_tip);
|
||||
|
||||
iter_tip = iter_tip->GetAncestor(iter_tip->nHeight - 1);
|
||||
} while (new_tip != iter_tip);
|
||||
}
|
||||
|
||||
return BaseIndex::Rewind(current_tip, new_tip);
|
||||
}
|
||||
|
||||
static bool LookUpOne(const CDBWrapper& db, const CBlockIndex* block_index, DBVal& result)
|
||||
{
|
||||
// First check if the result is stored under the height index and the value
|
||||
// there matches the block hash. This should be the case if the block is on
|
||||
// the active chain.
|
||||
std::pair<uint256, DBVal> read_out;
|
||||
if (!db.Read(DBHeightKey(block_index->nHeight), read_out)) {
|
||||
return false;
|
||||
}
|
||||
if (read_out.first == block_index->GetBlockHash()) {
|
||||
result = std::move(read_out.second);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If value at the height index corresponds to an different block, the
|
||||
// result will be stored in the hash index.
|
||||
return db.Read(DBHashKey(block_index->GetBlockHash()), result);
|
||||
}
|
||||
|
||||
bool CoinStatsIndex::LookUpStats(const CBlockIndex* block_index, CCoinsStats& coins_stats) const
|
||||
{
|
||||
DBVal entry;
|
||||
if (!LookUpOne(*m_db, block_index, entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
coins_stats.hashSerialized = entry.muhash;
|
||||
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;
|
||||
}
|
||||
|
||||
bool CoinStatsIndex::Init()
|
||||
{
|
||||
if (!m_db->Read(DB_MUHASH, m_muhash)) {
|
||||
// Check that the cause of the read failure is that the key does not
|
||||
// exist. Any other errors indicate database corruption or a disk
|
||||
// failure, and starting the index would cause further corruption.
|
||||
if (m_db->Exists(DB_MUHASH)) {
|
||||
return error("%s: Cannot read current %s state; index may be corrupted",
|
||||
__func__, GetName());
|
||||
}
|
||||
}
|
||||
|
||||
if (!BaseIndex::Init()) return false;
|
||||
|
||||
const CBlockIndex* pindex{CurrentIndex()};
|
||||
|
||||
if (pindex) {
|
||||
DBVal entry;
|
||||
if (!LookUpOne(*m_db, pindex, entry)) {
|
||||
return error("%s: Cannot read current %s state; index may be corrupted",
|
||||
__func__, GetName());
|
||||
}
|
||||
uint256 out;
|
||||
m_muhash.Finalize(out);
|
||||
if (entry.muhash != out) {
|
||||
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 true;
|
||||
}
|
||||
|
||||
bool CoinStatsIndex::CommitInternal(CDBBatch& batch)
|
||||
{
|
||||
// DB_MUHASH should always be committed in a batch together with DB_BEST_BLOCK
|
||||
// to prevent an inconsistent state of the DB.
|
||||
batch.Write(DB_MUHASH, m_muhash);
|
||||
return BaseIndex::CommitInternal(batch);
|
||||
}
|
||||
|
||||
// Reverse a single block as part of a reorg
|
||||
bool CoinStatsIndex::ReverseBlock(const CBlock& block, const CBlockIndex* pindex)
|
||||
{
|
||||
CBlockUndo block_undo;
|
||||
std::pair<uint256, DBVal> read_out;
|
||||
|
||||
const CAmount block_subsidy{pindex->nHeight > 0 ? GetBlockSubsidy(pindex->pprev->nBits, pindex->pprev->nHeight, Params().GetConsensus()) : Params().GenesisBlock().vtx[0]->GetValueOut()};
|
||||
m_total_subsidy -= block_subsidy;
|
||||
|
||||
// Ignore genesis block
|
||||
if (pindex->nHeight > 0) {
|
||||
if (!UndoReadFromDisk(block_undo, pindex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_db->Read(DBHeightKey(pindex->nHeight - 1), read_out)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint256 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 not found; expected %s",
|
||||
__func__, expected_block_hash.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the new UTXOs that were created from the block
|
||||
for (size_t i = 0; i < block.vtx.size(); ++i) {
|
||||
const auto& tx{block.vtx.at(i)};
|
||||
|
||||
for (uint32_t j = 0; j < tx->vout.size(); ++j) {
|
||||
const CTxOut& out{tx->vout[j]};
|
||||
COutPoint outpoint{tx->GetHash(), j};
|
||||
Coin coin{out, pindex->nHeight, tx->IsCoinBase()};
|
||||
|
||||
// Skip unspendable coins
|
||||
if (coin.out.scriptPubKey.IsUnspendable()) {
|
||||
m_total_unspendable_amount -= coin.out.nValue;
|
||||
m_total_unspendables_scripts -= coin.out.nValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
m_muhash.Remove(MakeUCharSpan(TxOutSer(outpoint, coin)));
|
||||
|
||||
if (tx->IsCoinBase()) {
|
||||
m_total_coinbase_amount -= coin.out.nValue;
|
||||
} else {
|
||||
m_total_new_outputs_ex_coinbase_amount -= coin.out.nValue;
|
||||
}
|
||||
|
||||
--m_transaction_output_count;
|
||||
m_total_amount -= coin.out.nValue;
|
||||
m_bogo_size -= GetBogoSize(coin.out.scriptPubKey);
|
||||
}
|
||||
|
||||
// The coinbase tx has no undo data since no former output is spent
|
||||
if (!tx->IsCoinBase()) {
|
||||
const auto& tx_undo{block_undo.vtxundo.at(i - 1)};
|
||||
|
||||
for (size_t j = 0; j < tx_undo.vprevout.size(); ++j) {
|
||||
Coin coin{tx_undo.vprevout[j]};
|
||||
COutPoint outpoint{tx->vin[j].prevout.hash, tx->vin[j].prevout.n};
|
||||
|
||||
m_muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
|
||||
|
||||
m_total_prevout_spent_amount -= coin.out.nValue;
|
||||
|
||||
m_transaction_output_count++;
|
||||
m_total_amount += coin.out.nValue;
|
||||
m_bogo_size += GetBogoSize(coin.out.scriptPubKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_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);
|
||||
|
||||
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 true;
|
||||
}
|
63
src/index/coinstatsindex.h
Normal file
63
src/index/coinstatsindex.h
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2020-2021 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_INDEX_COINSTATSINDEX_H
|
||||
#define BITCOIN_INDEX_COINSTATSINDEX_H
|
||||
|
||||
#include <chain.h>
|
||||
#include <crypto/muhash.h>
|
||||
#include <flatfile.h>
|
||||
#include <index/base.h>
|
||||
#include <node/coinstats.h>
|
||||
|
||||
/**
|
||||
* CoinStatsIndex maintains statistics on the UTXO set.
|
||||
*/
|
||||
class CoinStatsIndex final : public BaseIndex
|
||||
{
|
||||
private:
|
||||
std::string m_name;
|
||||
std::unique_ptr<BaseIndex::DB> m_db;
|
||||
|
||||
MuHash3072 m_muhash;
|
||||
uint64_t m_transaction_output_count{0};
|
||||
uint64_t m_bogo_size{0};
|
||||
CAmount m_total_amount{0};
|
||||
CAmount m_total_subsidy{0};
|
||||
CAmount m_total_unspendable_amount{0};
|
||||
CAmount m_total_prevout_spent_amount{0};
|
||||
CAmount m_total_new_outputs_ex_coinbase_amount{0};
|
||||
CAmount m_total_coinbase_amount{0};
|
||||
CAmount m_total_unspendables_genesis_block{0};
|
||||
CAmount m_total_unspendables_bip30{0};
|
||||
CAmount m_total_unspendables_scripts{0};
|
||||
CAmount m_total_unspendables_unclaimed_rewards{0};
|
||||
|
||||
bool ReverseBlock(const CBlock& block, const CBlockIndex* pindex);
|
||||
|
||||
protected:
|
||||
bool Init() override;
|
||||
|
||||
bool CommitInternal(CDBBatch& batch) override;
|
||||
|
||||
bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) override;
|
||||
|
||||
bool Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip) override;
|
||||
|
||||
BaseIndex::DB& GetDB() const override { return *m_db; }
|
||||
|
||||
const char* GetName() const override { return "coinstatsindex"; }
|
||||
|
||||
public:
|
||||
// Constructs the index, which becomes available to be queried.
|
||||
explicit CoinStatsIndex(size_t n_cache_size, bool f_memory = false, bool f_wipe = false);
|
||||
|
||||
// Look up stats for a specific block using CBlockIndex
|
||||
bool LookUpStats(const CBlockIndex* block_index, CCoinsStats& coins_stats) const;
|
||||
};
|
||||
|
||||
/// The global UTXO set hash object.
|
||||
extern std::unique_ptr<CoinStatsIndex> g_coin_stats_index;
|
||||
|
||||
#endif // BITCOIN_INDEX_COINSTATSINDEX_H
|
@ -204,7 +204,7 @@ bool TxIndex::Init()
|
||||
// Attempt to migrate txindex from the old database to the new one. Even if
|
||||
// chain_tip is null, the node could be reindexing and we still want to
|
||||
// delete txindex records in the old database.
|
||||
if (!m_db->MigrateData(*pblocktree, ::ChainActive().GetLocator())) {
|
||||
if (!m_db->MigrateData(*pblocktree, m_chainstate->m_chain.GetLocator())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
34
src/init.cpp
34
src/init.cpp
@ -26,6 +26,7 @@
|
||||
#include <httprpc.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <index/txindex.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <key.h>
|
||||
@ -205,6 +206,9 @@ void Interrupt(NodeContext& node)
|
||||
g_txindex->Interrupt();
|
||||
}
|
||||
ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Interrupt(); });
|
||||
if (g_coin_stats_index) {
|
||||
g_coin_stats_index->Interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/** Preparing steps before shutting down or restarting the wallet */
|
||||
@ -327,6 +331,10 @@ void PrepareShutdown(NodeContext& node)
|
||||
g_txindex->Stop();
|
||||
g_txindex.reset();
|
||||
}
|
||||
if (g_coin_stats_index) {
|
||||
g_coin_stats_index->Stop();
|
||||
g_coin_stats_index.reset();
|
||||
}
|
||||
ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Stop(); });
|
||||
DestroyAllBlockFilterIndexes();
|
||||
|
||||
@ -521,6 +529,7 @@ void SetupServerArgs(NodeContext& node)
|
||||
#endif
|
||||
argsman.AddArg("-blockreconstructionextratxn=<n>", strprintf("Extra transactions to keep in memory for compact block reconstructions (default: %u)", DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless the peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-coinstatsindex", strprintf("Maintain coinstats index used by the gettxoutset RPC (default: %u)", DEFAULT_COINSTATSINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-conf=<file>", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
|
||||
@ -537,7 +546,7 @@ void SetupServerArgs(NodeContext& node)
|
||||
-GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex, -rescan and -disablegovernance=false. "
|
||||
argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex, -coinstatsindex, -rescan and -disablegovernance=false. "
|
||||
"Warning: Reverting this setting requires re-downloading the entire blockchain. "
|
||||
"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-settings=<file>", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
@ -866,9 +875,9 @@ static void CleanupBlockRevFiles()
|
||||
static void PeriodicStats(ArgsManager& args, const CTxMemPool& mempool)
|
||||
{
|
||||
assert(args.GetBoolArg("-statsenabled", DEFAULT_STATSD_ENABLE));
|
||||
CCoinsStats stats;
|
||||
CCoinsStats stats{CoinStatsHashType::NONE};
|
||||
::ChainstateActive().ForceFlushStateToDisk();
|
||||
if (WITH_LOCK(cs_main, return GetUTXOStats(&::ChainstateActive().CoinsDB(), std::ref(g_chainman.m_blockman), stats, CoinStatsHashType::NONE, RpcInterruptionPoint))) {
|
||||
if (WITH_LOCK(cs_main, return GetUTXOStats(&::ChainstateActive().CoinsDB(), std::ref(g_chainman.m_blockman), stats, RpcInterruptionPoint, ::ChainActive().Tip()))) {
|
||||
statsClient.gauge("utxoset.tx", stats.nTransactions, 1.0f);
|
||||
statsClient.gauge("utxoset.txOutputs", stats.nTransactionOutputs, 1.0f);
|
||||
statsClient.gauge("utxoset.dbSizeBytes", stats.nDiskSize, 1.0f);
|
||||
@ -1204,10 +1213,12 @@ bool AppInitParameterInteraction(const ArgsManager& args)
|
||||
nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS);
|
||||
}
|
||||
|
||||
// if using block pruning, then disallow txindex and require disabling governance validation
|
||||
// if using block pruning, then disallow txindex, coinstatsindex and require disabling governance validation
|
||||
if (args.GetArg("-prune", 0)) {
|
||||
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX))
|
||||
return InitError(_("Prune mode is incompatible with -txindex."));
|
||||
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX))
|
||||
return InitError(_("Prune mode is incompatible with -coinstatsindex."));
|
||||
if (!args.GetBoolArg("-disablegovernance", false)) {
|
||||
return InitError(_("Prune mode is incompatible with -disablegovernance=false."));
|
||||
}
|
||||
@ -2175,12 +2186,23 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc
|
||||
// ********************************************************* Step 8: start indexers
|
||||
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
|
||||
g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex);
|
||||
g_txindex->Start();
|
||||
if (!g_txindex->Start(::ChainstateActive())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& filter_type : g_enabled_filter_types) {
|
||||
InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex);
|
||||
GetBlockFilterIndex(filter_type)->Start();
|
||||
if (!GetBlockFilterIndex(filter_type)->Start(::ChainstateActive())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
|
||||
g_coin_stats_index = std::make_unique<CoinStatsIndex>(/* cache size */ 0, false, fReindex);
|
||||
if (!g_coin_stats_index->Start(::ChainstateActive())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************* Step 9: load wallet
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <coins.h>
|
||||
#include <crypto/muhash.h>
|
||||
#include <hash.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <serialize.h>
|
||||
#include <uint256.h>
|
||||
// #include <util/system.h>
|
||||
@ -17,44 +18,22 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
static uint64_t GetBogoSize(const CScript& scriptPubKey)
|
||||
uint64_t GetBogoSize(const CScript& script_pub_key)
|
||||
{
|
||||
return 32 /* txid */ +
|
||||
4 /* vout index */ +
|
||||
4 /* height + coinbase */ +
|
||||
8 /* amount */ +
|
||||
2 /* scriptPubKey len */ +
|
||||
scriptPubKey.size() /* scriptPubKey */;
|
||||
script_pub_key.size() /* scriptPubKey */;
|
||||
}
|
||||
|
||||
static void ApplyHash(CCoinsStats& stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it)
|
||||
{
|
||||
if (it == outputs.begin()) {
|
||||
ss << hash;
|
||||
ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
|
||||
}
|
||||
|
||||
ss << VARINT(it->first + 1);
|
||||
ss << it->second.out.scriptPubKey;
|
||||
ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
|
||||
|
||||
if (it == std::prev(outputs.end())) {
|
||||
ss << VARINT(0u);
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyHash(CCoinsStats& stats, std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it) {}
|
||||
|
||||
static void ApplyHash(CCoinsStats& stats, MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it)
|
||||
{
|
||||
COutPoint outpoint = COutPoint(hash, it->first);
|
||||
Coin coin = it->second;
|
||||
|
||||
CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin) {
|
||||
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
|
||||
ss << outpoint;
|
||||
ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase);
|
||||
ss << coin.out;
|
||||
muhash.Insert(MakeUCharSpan(ss));
|
||||
return ss;
|
||||
}
|
||||
|
||||
//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot
|
||||
@ -69,14 +48,40 @@ static void ApplyHash(CCoinsStats& stats, MuHash3072& muhash, const uint256& has
|
||||
//! It is also possible, though very unlikely, that a change in this
|
||||
//! construction could cause a previously invalid (and potentially malicious)
|
||||
//! UTXO snapshot to be considered valid.
|
||||
template <typename T>
|
||||
static void ApplyStats(CCoinsStats& stats, T& hash_obj, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
||||
static void ApplyHash(CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
||||
{
|
||||
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
|
||||
if (it == outputs.begin()) {
|
||||
ss << hash;
|
||||
ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u);
|
||||
}
|
||||
|
||||
ss << VARINT(it->first + 1);
|
||||
ss << it->second.out.scriptPubKey;
|
||||
ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
|
||||
|
||||
if (it == std::prev(outputs.end())) {
|
||||
ss << VARINT(0u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyHash(std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) {}
|
||||
|
||||
static void ApplyHash(MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
||||
{
|
||||
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
|
||||
COutPoint outpoint = COutPoint(hash, it->first);
|
||||
Coin coin = it->second;
|
||||
muhash.Insert(MakeUCharSpan(TxOutSer(outpoint, coin)));
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyStats(CCoinsStats& stats, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
|
||||
{
|
||||
assert(!outputs.empty());
|
||||
stats.nTransactions++;
|
||||
for (auto it = outputs.begin(); it != outputs.end(); ++it) {
|
||||
ApplyHash(stats, hash_obj, hash, outputs, it);
|
||||
|
||||
stats.nTransactionOutputs++;
|
||||
stats.nTotalAmount += it->second.out.nValue;
|
||||
stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey);
|
||||
@ -96,18 +101,25 @@ static void ApplyStats(CCoinsStats& stats, std::nullptr_t, const uint256& hash,
|
||||
|
||||
//! Calculate statistics about the unspent transaction output set
|
||||
template <typename T>
|
||||
static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point)
|
||||
static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
|
||||
{
|
||||
stats = CCoinsStats();
|
||||
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
|
||||
assert(pcursor);
|
||||
|
||||
stats.hashBlock = pcursor->GetBestBlock();
|
||||
{
|
||||
LOCK(cs_main);
|
||||
assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
|
||||
const CBlockIndex* block = blockman.LookupBlockIndex(stats.hashBlock);
|
||||
stats.nHeight = Assert(block)->nHeight;
|
||||
if (!pindex) {
|
||||
{
|
||||
LOCK(cs_main);
|
||||
assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman));
|
||||
pindex = blockman.LookupBlockIndex(view->GetBestBlock());
|
||||
}
|
||||
}
|
||||
stats.nHeight = Assert(pindex)->nHeight;
|
||||
stats.hashBlock = pindex->GetBlockHash();
|
||||
|
||||
// Use CoinStatsIndex if it is requested and available and a hash_type of Muhash or None was requested
|
||||
if ((stats.m_hash_type == CoinStatsHashType::MUHASH || stats.m_hash_type == CoinStatsHashType::NONE) && g_coin_stats_index && stats.index_requested) {
|
||||
stats.index_used = true;
|
||||
return g_coin_stats_index->LookUpStats(pindex, stats);
|
||||
}
|
||||
|
||||
PrepareHash(hash_obj, stats);
|
||||
@ -120,7 +132,8 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
|
||||
Coin coin;
|
||||
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
|
||||
if (!outputs.empty() && key.hash != prevkey) {
|
||||
ApplyStats(stats, hash_obj, prevkey, outputs);
|
||||
ApplyStats(stats, prevkey, outputs);
|
||||
ApplyHash(hash_obj, prevkey, outputs);
|
||||
outputs.clear();
|
||||
}
|
||||
prevkey = key.hash;
|
||||
@ -132,7 +145,8 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
|
||||
pcursor->Next();
|
||||
}
|
||||
if (!outputs.empty()) {
|
||||
ApplyStats(stats, hash_obj, prevkey, outputs);
|
||||
ApplyStats(stats, prevkey, outputs);
|
||||
ApplyHash(hash_obj, prevkey, outputs);
|
||||
}
|
||||
|
||||
FinalizeHash(hash_obj, stats);
|
||||
@ -141,19 +155,19 @@ static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats&
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point)
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point, const CBlockIndex* pindex)
|
||||
{
|
||||
switch (hash_type) {
|
||||
switch (stats.m_hash_type) {
|
||||
case(CoinStatsHashType::HASH_SERIALIZED): {
|
||||
CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
|
||||
return GetUTXOStats(view, blockman, stats, ss, interruption_point);
|
||||
return GetUTXOStats(view, blockman, stats, ss, interruption_point, pindex);
|
||||
}
|
||||
case(CoinStatsHashType::MUHASH): {
|
||||
MuHash3072 muhash;
|
||||
return GetUTXOStats(view, blockman, stats, muhash, interruption_point);
|
||||
return GetUTXOStats(view, blockman, stats, muhash, interruption_point, pindex);
|
||||
}
|
||||
case(CoinStatsHashType::NONE): {
|
||||
return GetUTXOStats(view, blockman, stats, nullptr, interruption_point);
|
||||
return GetUTXOStats(view, blockman, stats, nullptr, interruption_point, pindex);
|
||||
}
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
assert(false);
|
||||
|
@ -7,6 +7,9 @@
|
||||
#define BITCOIN_NODE_COINSTATS_H
|
||||
|
||||
#include <amount.h>
|
||||
#include <chain.h>
|
||||
#include <coins.h>
|
||||
#include <streams.h>
|
||||
#include <uint256.h>
|
||||
|
||||
#include <cstdint>
|
||||
@ -23,6 +26,7 @@ enum class CoinStatsHashType {
|
||||
|
||||
struct CCoinsStats
|
||||
{
|
||||
CoinStatsHashType m_hash_type;
|
||||
int nHeight{0};
|
||||
uint256 hashBlock{};
|
||||
uint64_t nTransactions{0};
|
||||
@ -34,9 +38,41 @@ struct CCoinsStats
|
||||
|
||||
//! The number of coins contained.
|
||||
uint64_t coins_count{0};
|
||||
|
||||
//! Signals if the coinstatsindex should be used (when available).
|
||||
bool index_requested{true};
|
||||
//! Signals if the coinstatsindex was used to retrieve the statistics.
|
||||
bool index_used{false};
|
||||
|
||||
// Following values are only available from coinstats index
|
||||
|
||||
//! Total cumulative amount of block subsidies up to and including this block
|
||||
CAmount total_subsidy{0};
|
||||
//! Total cumulative amount of unspendable coins up to and including this block
|
||||
CAmount total_unspendable_amount{0};
|
||||
//! Total cumulative amount of prevouts spent up to and including this block
|
||||
CAmount total_prevout_spent_amount{0};
|
||||
//! Total cumulative amount of outputs created up to and including this block
|
||||
CAmount total_new_outputs_ex_coinbase_amount{0};
|
||||
//! Total cumulative amount of coinbase outputs up to and including this block
|
||||
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) {}
|
||||
};
|
||||
|
||||
//! Calculate statistics about the unspent transaction output set
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {});
|
||||
bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const std::function<void()>& interruption_point = {}, const CBlockIndex* pindex = nullptr);
|
||||
|
||||
uint64_t GetBogoSize(const CScript& script_pub_key);
|
||||
|
||||
CDataStream TxOutSer(const COutPoint& outpoint, const Coin& coin);
|
||||
|
||||
#endif // BITCOIN_NODE_COINSTATS_H
|
||||
|
11
src/random.h
11
src/random.h
@ -208,6 +208,17 @@ public:
|
||||
/** Generate a random boolean. */
|
||||
bool randbool() noexcept { return randbits(1); }
|
||||
|
||||
/** Return the time point advanced by a uniform random duration. */
|
||||
template <typename Tp>
|
||||
Tp rand_uniform_delay(const Tp& time, typename Tp::duration range)
|
||||
{
|
||||
using Dur = typename Tp::duration;
|
||||
Dur dur{range.count() > 0 ? /* interval [0..range) */ Dur{randrange(range.count())} :
|
||||
range.count() < 0 ? /* interval (range..0] */ -Dur{randrange(-range.count())} :
|
||||
/* interval [0..0] */ Dur{0}};
|
||||
return time + dur;
|
||||
}
|
||||
|
||||
// Compatibility with the C++11 UniformRandomBitGenerator concept
|
||||
typedef uint64_t result_type;
|
||||
static constexpr uint64_t min() { return 0; }
|
||||
|
37
src/rest.cpp
37
src/rest.cpp
@ -106,6 +106,27 @@ static CTxMemPool* GetMemPool(const CoreContext& context, HTTPRequest* req)
|
||||
return node_context->mempool.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node context chainstatemanager.
|
||||
*
|
||||
* @param[in] req The HTTP request, whose status code will be set if node
|
||||
* context chainstatemanager is not found.
|
||||
* @returns Pointer to the chainstatemanager or nullptr if none found.
|
||||
*/
|
||||
static ChainstateManager* GetChainman(const CoreContext& context, HTTPRequest* req)
|
||||
{
|
||||
auto node_context = GetContext<NodeContext>(context);
|
||||
if (!node_context || !node_context->chainman) {
|
||||
RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
|
||||
strprintf("%s:%d (%s)\n"
|
||||
"Internal bug detected: Chainman disabled or instance not found!\n"
|
||||
"You may report this issue here: %s\n",
|
||||
__FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
|
||||
return nullptr;
|
||||
}
|
||||
return node_context->chainman;
|
||||
}
|
||||
|
||||
static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
|
||||
{
|
||||
const std::string::size_type pos = strReq.rfind('.');
|
||||
@ -179,7 +200,9 @@ static bool rest_headers(const CoreContext& context,
|
||||
std::vector<const CBlockIndex *> headers;
|
||||
headers.reserve(count);
|
||||
{
|
||||
ChainstateManager& chainman = EnsureAnyChainman(context);
|
||||
ChainstateManager* maybe_chainman = GetChainman(context, req);
|
||||
if (!maybe_chainman) return false;
|
||||
ChainstateManager& chainman = *maybe_chainman;
|
||||
LOCK(cs_main);
|
||||
CChain& active_chain = chainman.ActiveChain();
|
||||
tip = active_chain.Tip();
|
||||
@ -250,7 +273,9 @@ static bool rest_block(const CoreContext& context,
|
||||
CBlockIndex* pblockindex = nullptr;
|
||||
CBlockIndex* tip = nullptr;
|
||||
{
|
||||
ChainstateManager& chainman = EnsureAnyChainman(context);
|
||||
ChainstateManager* maybe_chainman = GetChainman(context, req);
|
||||
if (!maybe_chainman) return false;
|
||||
ChainstateManager& chainman = *maybe_chainman;
|
||||
LOCK(cs_main);
|
||||
tip = chainman.ActiveChain().Tip();
|
||||
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
|
||||
@ -538,7 +563,9 @@ static bool rest_getutxos(const CoreContext& context, HTTPRequest* req, const st
|
||||
std::string bitmapStringRepresentation;
|
||||
std::vector<bool> hits;
|
||||
bitmap.resize((vOutPoints.size() + 7) / 8);
|
||||
ChainstateManager& chainman = EnsureAnyChainman(context);
|
||||
ChainstateManager* maybe_chainman = GetChainman(context, req);
|
||||
if (!maybe_chainman) return false;
|
||||
ChainstateManager& chainman = *maybe_chainman;
|
||||
{
|
||||
auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool& mempool) {
|
||||
for (const COutPoint& vOutPoint : vOutPoints) {
|
||||
@ -641,7 +668,9 @@ static bool rest_blockhash_by_height(const CoreContext& context, HTTPRequest* re
|
||||
|
||||
CBlockIndex* pblockindex = nullptr;
|
||||
{
|
||||
ChainstateManager& chainman = EnsureAnyChainman(context);
|
||||
ChainstateManager* maybe_chainman = GetChainman(context, req);
|
||||
if (!maybe_chainman) return false;
|
||||
ChainstateManager& chainman = *maybe_chainman;
|
||||
LOCK(cs_main);
|
||||
const CChain& active_chain = chainman.ActiveChain();
|
||||
if (blockheight > active_chain.Height()) {
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <core_io.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <index/txindex.h>
|
||||
#include <llmq/context.h>
|
||||
#include <node/blockstorage.h>
|
||||
@ -146,6 +147,33 @@ static int ComputeNextBlockAndDepth(const CBlockIndex* tip, const CBlockIndex* b
|
||||
return blockindex == tip ? 1 : -1;
|
||||
}
|
||||
|
||||
CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainman) {
|
||||
LOCK(::cs_main);
|
||||
CChain& active_chain = chainman.ActiveChain();
|
||||
|
||||
if (param.isNum()) {
|
||||
const int height{param.get_int()};
|
||||
if (height < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
|
||||
}
|
||||
const int current_tip{active_chain.Height()};
|
||||
if (height > current_tip) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
|
||||
}
|
||||
|
||||
return active_chain[height];
|
||||
} else {
|
||||
const uint256 hash{ParseHashV(param, "hash_or_height")};
|
||||
CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
|
||||
|
||||
if (!pindex) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
|
||||
return pindex;
|
||||
}
|
||||
}
|
||||
|
||||
UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex, llmq::CChainLocksHandler& clhandler, llmq::CInstantSendManager& isman)
|
||||
{
|
||||
// Serialize passed information without accessing chain state of the active chain!
|
||||
@ -1295,50 +1323,100 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
||||
{
|
||||
RPCHelpMan{"gettxoutsetinfo",
|
||||
"\nReturns statistics about the unspent transaction output set.\n"
|
||||
"Note this call may take some time.\n",
|
||||
"Note this call may take some time if you are not using coinstatsindex.\n",
|
||||
{
|
||||
{"hash_type", RPCArg::Type::STR, /* default */ "hash_serialized_2", "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."},
|
||||
{"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The block hash or height of the target height (only available with coinstatsindex)", "", {"", "string or numeric"}},
|
||||
{"use_index", RPCArg::Type::BOOL, /* default */ "true", "Use coinstatsindex, if available."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "height", "The current block height (index)"},
|
||||
{RPCResult::Type::STR_HEX, "bestblock", "The hash of the block at the tip of the chain"},
|
||||
{RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs"},
|
||||
{RPCResult::Type::NUM, "txouts", "The number of unspent transaction outputs"},
|
||||
{RPCResult::Type::NUM, "bogosize", "A meaningless metric for UTXO set size"},
|
||||
{RPCResult::Type::NUM, "bogosize", "Database-independent, meaningless metric indicating the UTXO set size"},
|
||||
{RPCResult::Type::STR_HEX, "hash_serialized_2", /* optional */ true, "The serialized hash (only present if 'hash_serialized_2' hash_type is chosen)"},
|
||||
{RPCResult::Type::STR_HEX, "muhash", /* optional */ true, "The serialized hash (only present if 'muhash' hash_type is chosen)"},
|
||||
{RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk"},
|
||||
{RPCResult::Type::NUM, "transactions", "The number of transactions with unspent outputs (not available when coinstatsindex is used)"},
|
||||
{RPCResult::Type::NUM, "disk_size", "The estimated size of the chainstate on disk (not available when coinstatsindex is used)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount"},
|
||||
{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::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", "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, "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"},
|
||||
}}
|
||||
}},
|
||||
}},
|
||||
RPCExamples{
|
||||
HelpExampleCli("gettxoutsetinfo", "")
|
||||
+ HelpExampleRpc("gettxoutsetinfo", "")
|
||||
HelpExampleCli("gettxoutsetinfo", "") +
|
||||
HelpExampleCli("gettxoutsetinfo", R"("none")") +
|
||||
HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
|
||||
HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
|
||||
HelpExampleRpc("gettxoutsetinfo", "") +
|
||||
HelpExampleRpc("gettxoutsetinfo", R"("none")") +
|
||||
HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
|
||||
HelpExampleRpc("gettxoutsetinfo", R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")
|
||||
},
|
||||
}.Check(request);
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
||||
CCoinsStats stats;
|
||||
CBlockIndex* pindex{nullptr};
|
||||
const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
|
||||
CCoinsStats stats{hash_type};
|
||||
stats.index_requested = request.params[2].isNull() || request.params[2].get_bool();
|
||||
|
||||
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
ChainstateManager& chainman = EnsureChainman(node);
|
||||
CChainState& active_chainstate = chainman.ActiveChainstate();
|
||||
active_chainstate.ForceFlushStateToDisk();
|
||||
|
||||
const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
|
||||
|
||||
CCoinsView* coins_view;
|
||||
BlockManager* blockman;
|
||||
{
|
||||
LOCK(::cs_main);
|
||||
coins_view = &active_chainstate.CoinsDB();
|
||||
blockman = &active_chainstate.m_blockman;
|
||||
pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
|
||||
}
|
||||
if (GetUTXOStats(coins_view, *blockman, stats, hash_type, node.rpc_interruption_point)) {
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
if (!g_coin_stats_index) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Querying specific block heights requires coinstatsindex");
|
||||
}
|
||||
|
||||
if (stats.m_hash_type == CoinStatsHashType::HASH_SERIALIZED) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block");
|
||||
}
|
||||
|
||||
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);
|
||||
ret.pushKV("bestblock", stats.hashBlock.GetHex());
|
||||
ret.pushKV("transactions", (int64_t)stats.nTransactions);
|
||||
ret.pushKV("txouts", (int64_t)stats.nTransactionOutputs);
|
||||
ret.pushKV("bogosize", (int64_t)stats.nBogoSize);
|
||||
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
|
||||
@ -1347,8 +1425,34 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
|
||||
if (hash_type == CoinStatsHashType::MUHASH) {
|
||||
ret.pushKV("muhash", stats.hashSerialized.GetHex());
|
||||
}
|
||||
ret.pushKV("disk_size", stats.nDiskSize);
|
||||
ret.pushKV("total_amount", ValueFromAmount(stats.nTotalAmount));
|
||||
if (!stats.index_used) {
|
||||
ret.pushKV("transactions", static_cast<int64_t>(stats.nTransactions));
|
||||
ret.pushKV("disk_size", stats.nDiskSize);
|
||||
} else {
|
||||
ret.pushKV("total_unspendable_amount", ValueFromAmount(stats.total_unspendable_amount));
|
||||
|
||||
CCoinsStats prev_stats{hash_type};
|
||||
|
||||
if (pindex->nHeight > 0) {
|
||||
GetUTXOStats(coins_view, *blockman, prev_stats, node.rpc_interruption_point, pindex->pprev);
|
||||
}
|
||||
|
||||
UniValue block_info(UniValue::VOBJ);
|
||||
block_info.pushKV("prevout_spent", ValueFromAmount(stats.total_prevout_spent_amount - prev_stats.total_prevout_spent_amount));
|
||||
block_info.pushKV("coinbase", ValueFromAmount(stats.total_coinbase_amount - prev_stats.total_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.total_unspendable_amount - prev_stats.total_unspendable_amount));
|
||||
|
||||
UniValue unspendables(UniValue::VOBJ);
|
||||
unspendables.pushKV("genesis_block", ValueFromAmount(stats.total_unspendables_genesis_block - prev_stats.total_unspendables_genesis_block));
|
||||
unspendables.pushKV("bip30", ValueFromAmount(stats.total_unspendables_bip30 - prev_stats.total_unspendables_bip30));
|
||||
unspendables.pushKV("scripts", ValueFromAmount(stats.total_unspendables_scripts - prev_stats.total_unspendables_scripts));
|
||||
unspendables.pushKV("unclaimed_rewards", ValueFromAmount(stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards));
|
||||
block_info.pushKV("unspendables", unspendables);
|
||||
|
||||
ret.pushKV("block_info", block_info);
|
||||
}
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
|
||||
}
|
||||
@ -2156,31 +2260,7 @@ static UniValue getblockstats(const JSONRPCRequest& request)
|
||||
|
||||
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
||||
LOCK(cs_main);
|
||||
CChain& active_chain = chainman.ActiveChain();
|
||||
|
||||
CBlockIndex* pindex;
|
||||
if (request.params[0].isNum()) {
|
||||
const int height = request.params[0].get_int();
|
||||
const int current_tip = active_chain.Height();
|
||||
if (height < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d is negative", height));
|
||||
}
|
||||
if (height > current_tip) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Target block height %d after current tip %d", height, current_tip));
|
||||
}
|
||||
|
||||
pindex = active_chain[height];
|
||||
} else {
|
||||
const uint256 hash(ParseHashV(request.params[0], "hash_or_height"));
|
||||
pindex = chainman.m_blockman.LookupBlockIndex(hash);
|
||||
if (!pindex) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
if (!active_chain.Contains(pindex)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Block is not in chain %s", Params().NetworkIDString()));
|
||||
}
|
||||
}
|
||||
|
||||
CBlockIndex* pindex{ParseHashOrHeight(request.params[0], chainman)};
|
||||
CHECK_NONFATAL(pindex != nullptr);
|
||||
|
||||
std::set<std::string> stats;
|
||||
@ -2808,7 +2888,7 @@ UniValue dumptxoutset(const JSONRPCRequest& request)
|
||||
UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFile& afile)
|
||||
{
|
||||
std::unique_ptr<CCoinsViewCursor> pcursor;
|
||||
CCoinsStats stats;
|
||||
CCoinsStats stats{CoinStatsHashType::NONE};
|
||||
CBlockIndex* tip;
|
||||
|
||||
{
|
||||
@ -2828,7 +2908,7 @@ UniValue CreateUTXOSnapshot(NodeContext& node, CChainState& chainstate, CAutoFil
|
||||
|
||||
chainstate.ForceFlushStateToDisk();
|
||||
|
||||
if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, CoinStatsHashType::NONE, node.rpc_interruption_point)) {
|
||||
if (!GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, stats, node.rpc_interruption_point)) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
|
||||
}
|
||||
|
||||
@ -2891,7 +2971,7 @@ static const CRPCCommand commands[] =
|
||||
{ "blockchain", "getrawmempool", &getrawmempool, {"verbose"} },
|
||||
{ "blockchain", "getspecialtxes", &getspecialtxes, {"blockhash", "type", "count", "skip", "verbosity"} },
|
||||
{ "blockchain", "gettxout", &gettxout, {"txid","n","include_mempool"} },
|
||||
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {"hash_type"} },
|
||||
{ "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {"hash_type", "hash_or_height", "use_index"} },
|
||||
{ "blockchain", "pruneblockchain", &pruneblockchain, {"height"} },
|
||||
{ "blockchain", "savemempool", &savemempool, {} },
|
||||
{ "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },
|
||||
|
@ -137,6 +137,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "gettxout", 1, "n" },
|
||||
{ "gettxout", 2, "include_mempool" },
|
||||
{ "gettxoutproof", 0, "txids" },
|
||||
{ "gettxoutsetinfo", 1, "hash_or_height" },
|
||||
{ "gettxoutsetinfo", 2, "use_index"},
|
||||
{ "lockunspent", 0, "unlock" },
|
||||
{ "lockunspent", 1, "transactions" },
|
||||
{ "importprivkey", 2, "rescan" },
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <evo/mnauth.h>
|
||||
#include <httpserver.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <index/txindex.h>
|
||||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
@ -1353,6 +1354,10 @@ static RPCHelpMan getindexinfo()
|
||||
result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name));
|
||||
}
|
||||
|
||||
if (g_coin_stats_index) {
|
||||
result.pushKVs(SummaryToJSON(g_coin_stats_index->GetSummary(), index_name));
|
||||
}
|
||||
|
||||
ForEachBlockFilterIndex([&result, &index_name](const BlockFilterIndex& index) {
|
||||
result.pushKVs(SummaryToJSON(index.GetSummary(), index_name));
|
||||
});
|
||||
|
@ -17,8 +17,8 @@
|
||||
#include <script/standard.h>
|
||||
#include <spork.h>
|
||||
#include <test/util/blockfilter.h>
|
||||
#include <test/util/index.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/time.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
@ -138,15 +138,10 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
|
||||
// BlockUntilSyncedToCurrentChain should return false before index is started.
|
||||
BOOST_CHECK(!filter_index.BlockUntilSyncedToCurrentChain());
|
||||
|
||||
filter_index.Start();
|
||||
BOOST_REQUIRE(filter_index.Start(::ChainstateActive()));
|
||||
|
||||
// Allow filter index to catch up with the block index.
|
||||
constexpr int64_t timeout_ms = 10 * 1000;
|
||||
int64_t time_start = GetTimeMillis();
|
||||
while (!filter_index.BlockUntilSyncedToCurrentChain()) {
|
||||
BOOST_REQUIRE(time_start + timeout_ms > GetTimeMillis());
|
||||
UninterruptibleSleep(std::chrono::milliseconds{100});
|
||||
}
|
||||
IndexWaitSynced(filter_index);
|
||||
|
||||
// Check that filter index has all blocks that were in the chain before it started.
|
||||
{
|
||||
|
71
src/test/coinstatsindex_tests.cpp
Normal file
71
src/test/coinstatsindex_tests.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <test/util/index.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/time.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(coinstatsindex_tests)
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
|
||||
{
|
||||
CoinStatsIndex coin_stats_index{1 << 20, true};
|
||||
|
||||
CCoinsStats coin_stats{CoinStatsHashType::MUHASH};
|
||||
const CBlockIndex* block_index;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
block_index = ChainActive().Tip();
|
||||
}
|
||||
|
||||
// CoinStatsIndex should not be found before it is started.
|
||||
BOOST_CHECK(!coin_stats_index.LookUpStats(block_index, coin_stats));
|
||||
|
||||
// BlockUntilSyncedToCurrentChain should return false before CoinStatsIndex
|
||||
// is started.
|
||||
BOOST_CHECK(!coin_stats_index.BlockUntilSyncedToCurrentChain());
|
||||
|
||||
BOOST_REQUIRE(coin_stats_index.Start(::ChainstateActive()));
|
||||
|
||||
IndexWaitSynced(coin_stats_index);
|
||||
|
||||
// Check that CoinStatsIndex works for genesis block.
|
||||
const CBlockIndex* genesis_block_index;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
genesis_block_index = ChainActive().Genesis();
|
||||
}
|
||||
BOOST_CHECK(coin_stats_index.LookUpStats(genesis_block_index, coin_stats));
|
||||
|
||||
// Check that CoinStatsIndex updates with new blocks.
|
||||
coin_stats_index.LookUpStats(block_index, coin_stats);
|
||||
|
||||
const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG};
|
||||
std::vector<CMutableTransaction> noTxns;
|
||||
CreateAndProcessBlock(noTxns, script_pub_key);
|
||||
|
||||
// Let the CoinStatsIndex to catch up again.
|
||||
BOOST_CHECK(coin_stats_index.BlockUntilSyncedToCurrentChain());
|
||||
|
||||
CCoinsStats new_coin_stats{CoinStatsHashType::MUHASH};
|
||||
const CBlockIndex* new_block_index;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
new_block_index = ChainActive().Tip();
|
||||
}
|
||||
coin_stats_index.LookUpStats(new_block_index, new_coin_stats);
|
||||
|
||||
BOOST_CHECK(block_index != new_block_index);
|
||||
|
||||
// Shutdown sequence (c.f. Shutdown() in init.cpp)
|
||||
coin_stats_index.Stop();
|
||||
|
||||
// Rest of shutdown sequence and destructors happen in ~TestingSetup()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
@ -262,10 +262,10 @@ FUZZ_TARGET_INIT(coins_view, initialize_coins_view)
|
||||
(void)GetTransactionSigOpCount(transaction, coins_view_cache, flags);
|
||||
},
|
||||
[&] {
|
||||
CCoinsStats stats;
|
||||
CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED};
|
||||
bool expected_code_path = false;
|
||||
try {
|
||||
(void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats, CoinStatsHashType::HASH_SERIALIZED);
|
||||
(void)GetUTXOStats(&coins_view_cache, WITH_LOCK(::cs_main, return std::ref(g_chainman.m_blockman)), stats);
|
||||
} catch (const std::logic_error&) {
|
||||
expected_code_path = true;
|
||||
}
|
||||
|
@ -28,6 +28,16 @@ BOOST_AUTO_TEST_CASE(fastrandom_tests)
|
||||
BOOST_CHECK_EQUAL(GetRand(std::numeric_limits<uint64_t>::max()), uint64_t{10393729187455219830U});
|
||||
BOOST_CHECK_EQUAL(GetRandInt(std::numeric_limits<int>::max()), int{769702006});
|
||||
}
|
||||
{
|
||||
constexpr SteadySeconds time_point{1s};
|
||||
FastRandomContext ctx{true};
|
||||
BOOST_CHECK_EQUAL(7, ctx.rand_uniform_delay(time_point, 9s).time_since_epoch().count());
|
||||
BOOST_CHECK_EQUAL(-6, ctx.rand_uniform_delay(time_point, -9s).time_since_epoch().count());
|
||||
BOOST_CHECK_EQUAL(1, ctx.rand_uniform_delay(time_point, 0s).time_since_epoch().count());
|
||||
BOOST_CHECK_EQUAL(1467825113502396065, ctx.rand_uniform_delay(time_point, 9223372036854775807s).time_since_epoch().count());
|
||||
BOOST_CHECK_EQUAL(-970181367944767837, ctx.rand_uniform_delay(time_point, -9223372036854775807s).time_since_epoch().count());
|
||||
BOOST_CHECK_EQUAL(24761, ctx.rand_uniform_delay(time_point, 9h).time_since_epoch().count());
|
||||
}
|
||||
BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32());
|
||||
BOOST_CHECK_EQUAL(ctx1.rand32(), ctx2.rand32());
|
||||
BOOST_CHECK_EQUAL(ctx1.rand64(), ctx2.rand64());
|
||||
|
@ -5,8 +5,9 @@
|
||||
#include <chainparams.h>
|
||||
#include <index/txindex.h>
|
||||
#include <script/standard.h>
|
||||
#include <test/util/index.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/time.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
@ -27,15 +28,10 @@ BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup)
|
||||
// BlockUntilSyncedToCurrentChain should return false before txindex is started.
|
||||
BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain());
|
||||
|
||||
txindex.Start();
|
||||
BOOST_REQUIRE(txindex.Start(::ChainstateActive()));
|
||||
|
||||
// Allow tx index to catch up with the block index.
|
||||
constexpr int64_t timeout_ms = 10 * 1000;
|
||||
int64_t time_start = GetTimeMillis();
|
||||
while (!txindex.BlockUntilSyncedToCurrentChain()) {
|
||||
BOOST_REQUIRE(time_start + timeout_ms > GetTimeMillis());
|
||||
UninterruptibleSleep(std::chrono::milliseconds{100});
|
||||
}
|
||||
IndexWaitSynced(txindex);
|
||||
|
||||
// Check that txindex excludes genesis block transactions.
|
||||
const CBlock& genesis_block = Params().GenesisBlock();
|
||||
|
18
src/test/util/index.cpp
Normal file
18
src/test/util/index.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2020-2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <test/util/index.h>
|
||||
|
||||
#include <index/base.h>
|
||||
#include <util/check.h>
|
||||
#include <util/time.h>
|
||||
|
||||
void IndexWaitSynced(BaseIndex& index)
|
||||
{
|
||||
const auto timeout{SteadyClock::now() + 120s};
|
||||
while (!index.BlockUntilSyncedToCurrentChain()) {
|
||||
Assert(timeout > SteadyClock::now());
|
||||
UninterruptibleSleep(100ms);
|
||||
}
|
||||
}
|
13
src/test/util/index.h
Normal file
13
src/test/util/index.h
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) 2020-2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_TEST_UTIL_INDEX_H
|
||||
#define BITCOIN_TEST_UTIL_INDEX_H
|
||||
|
||||
class BaseIndex;
|
||||
|
||||
/** Block until the index is synced to the current chain */
|
||||
void IndexWaitSynced(BaseIndex& index);
|
||||
|
||||
#endif // BITCOIN_TEST_UTIL_INDEX_H
|
@ -40,6 +40,7 @@
|
||||
#include <streams.h>
|
||||
#include <spork.h>
|
||||
#include <txdb.h>
|
||||
#include <test/util/index.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/string.h>
|
||||
#include <util/time.h>
|
||||
@ -313,15 +314,10 @@ void TestChainSetup::mineBlocks(int num_blocks)
|
||||
}
|
||||
|
||||
g_txindex = std::make_unique<TxIndex>(1 << 20, true);
|
||||
g_txindex->Start();
|
||||
assert(g_txindex->Start(::ChainstateActive()));
|
||||
|
||||
// Allow tx index to catch up with the block index.
|
||||
constexpr int64_t timeout_ms = 10 * 1000;
|
||||
int64_t time_start = GetTimeMillis();
|
||||
while (!g_txindex->BlockUntilSyncedToCurrentChain()) {
|
||||
assert(time_start + timeout_ms > GetTimeMillis());
|
||||
UninterruptibleSleep(std::chrono::milliseconds{100});
|
||||
}
|
||||
IndexWaitSynced(*g_txindex);
|
||||
}
|
||||
|
||||
CBlock TestChainSetup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey)
|
||||
@ -410,14 +406,9 @@ TestChainSetup::~TestChainSetup()
|
||||
// Allow tx index to catch up with the block index cause otherwise
|
||||
// we might be destroying it while scheduler still has some work for it
|
||||
// e.g. via BlockConnected signal
|
||||
int64_t time_start = GetTimeMillis();
|
||||
while (!g_txindex->BlockUntilSyncedToCurrentChain()) {
|
||||
static constexpr int64_t timeout_ms = 10 * 1000;
|
||||
assert(time_start + timeout_ms > GetTimeMillis());
|
||||
UninterruptibleSleep(std::chrono::milliseconds{100});
|
||||
}
|
||||
g_txindex->Interrupt();
|
||||
IndexWaitSynced(*g_txindex);
|
||||
g_txindex->Stop();
|
||||
SyncWithValidationInterfaceQueue();
|
||||
g_txindex.reset();
|
||||
SetMockTime(0);
|
||||
}
|
||||
|
@ -1380,8 +1380,8 @@ BOOST_AUTO_TEST_CASE(util_time_GetTime)
|
||||
{
|
||||
SetMockTime(111);
|
||||
// Check that mock time does not change after a sleep
|
||||
for (const auto& num_sleep : {0, 1}) {
|
||||
UninterruptibleSleep(std::chrono::milliseconds{num_sleep});
|
||||
for (const auto& num_sleep : {0ms, 1ms}) {
|
||||
UninterruptibleSleep(num_sleep);
|
||||
BOOST_CHECK_EQUAL(111, GetTime()); // Deprecated time getter
|
||||
BOOST_CHECK_EQUAL(111, GetTime<std::chrono::seconds>().count());
|
||||
BOOST_CHECK_EQUAL(111000, GetTime<std::chrono::milliseconds>().count());
|
||||
@ -1389,10 +1389,14 @@ BOOST_AUTO_TEST_CASE(util_time_GetTime)
|
||||
}
|
||||
|
||||
SetMockTime(0);
|
||||
// Check that system time changes after a sleep
|
||||
// Check that steady time and system time changes after a sleep
|
||||
const auto steady_ms_0 = Now<SteadyMilliseconds>();
|
||||
const auto steady_0 = std::chrono::steady_clock::now();
|
||||
const auto ms_0 = GetTime<std::chrono::milliseconds>();
|
||||
const auto us_0 = GetTime<std::chrono::microseconds>();
|
||||
UninterruptibleSleep(std::chrono::milliseconds{1});
|
||||
UninterruptibleSleep(1ms);
|
||||
BOOST_CHECK(steady_ms_0 < Now<SteadyMilliseconds>());
|
||||
BOOST_CHECK(steady_0 + 1ms <= std::chrono::steady_clock::now());
|
||||
BOOST_CHECK(ms_0 < GetTime<std::chrono::milliseconds>());
|
||||
BOOST_CHECK(us_0 < GetTime<std::chrono::microseconds>());
|
||||
}
|
||||
|
@ -14,6 +14,11 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
using SteadyClock = std::chrono::steady_clock;
|
||||
using SteadySeconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::seconds>;
|
||||
using SteadyMilliseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::milliseconds>;
|
||||
using SteadyMicroseconds = std::chrono::time_point<std::chrono::steady_clock, std::chrono::microseconds>;
|
||||
|
||||
void UninterruptibleSleep(const std::chrono::microseconds& n);
|
||||
|
||||
/**
|
||||
@ -51,6 +56,15 @@ int64_t GetMockTime();
|
||||
/** Return system time (or mocked time, if set) */
|
||||
template <typename T>
|
||||
T GetTime();
|
||||
/**
|
||||
* Return the current time point cast to the given precicion. Only use this
|
||||
* when an exact precicion is needed, otherwise use T::clock::now() directly.
|
||||
*/
|
||||
template <typename T>
|
||||
T Now()
|
||||
{
|
||||
return std::chrono::time_point_cast<typename T::duration>(T::clock::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date,Time}
|
||||
|
@ -5954,14 +5954,14 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
|
||||
|
||||
assert(coins_cache.GetBestBlock() == base_blockhash);
|
||||
|
||||
CCoinsStats stats;
|
||||
CCoinsStats stats{CoinStatsHashType::HASH_SERIALIZED};
|
||||
auto breakpoint_fnc = [] { /* TODO insert breakpoint here? */ };
|
||||
|
||||
// As above, okay to immediately release cs_main here since no other context knows
|
||||
// about the snapshot_chainstate.
|
||||
CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB());
|
||||
|
||||
if (!GetUTXOStats(snapshot_coinsdb, WITH_LOCK(::cs_main, return std::ref(m_blockman)), stats, CoinStatsHashType::HASH_SERIALIZED, breakpoint_fnc)) {
|
||||
if (!GetUTXOStats(snapshot_coinsdb, WITH_LOCK(::cs_main, return std::ref(m_blockman)), stats, breakpoint_fnc)) {
|
||||
LogPrintf("[snapshot] failed to generate coins stats\n");
|
||||
return false;
|
||||
}
|
||||
|
@ -98,6 +98,7 @@ static const int64_t DEFAULT_MAX_TIP_AGE = 6 * 60 * 60; // ~144 blocks behind ->
|
||||
|
||||
static const bool DEFAULT_CHECKPOINTS_ENABLED = true;
|
||||
static const bool DEFAULT_TXINDEX = true;
|
||||
static constexpr bool DEFAULT_COINSTATSINDEX{false};
|
||||
static const bool DEFAULT_ADDRESSINDEX = false;
|
||||
static const bool DEFAULT_TIMESTAMPINDEX = false;
|
||||
static const bool DEFAULT_SPENTINDEX = false;
|
||||
|
@ -55,11 +55,13 @@ class FeatureBlockfilterindexPruneTest(BitcoinTestFramework):
|
||||
self.stop_node(0, expected_stderr='Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node.')
|
||||
|
||||
self.log.info("make sure we get an init error when starting the node again with block filters")
|
||||
with self.nodes[0].assert_debug_log(["basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"]):
|
||||
self.nodes[0].assert_start_raises_init_error(extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"])
|
||||
self.nodes[0].assert_start_raises_init_error(
|
||||
extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"],
|
||||
expected_msg="Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node.\nError: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)",
|
||||
)
|
||||
|
||||
self.log.info("make sure the node starts again with the -reindex arg")
|
||||
self.start_node(0, extra_args = ["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"])
|
||||
self.start_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex", "-reindex"])
|
||||
self.stop_nodes(expected_stderr='Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node.')
|
||||
|
||||
|
||||
|
316
test/functional/feature_coinstatsindex.py
Executable file
316
test/functional/feature_coinstatsindex.py
Executable file
@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2020 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test coinstatsindex across nodes.
|
||||
|
||||
Test that the values returned by gettxoutsetinfo are consistent
|
||||
between a node running the coinstatsindex and a node without
|
||||
the index.
|
||||
"""
|
||||
|
||||
import struct
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from test_framework.blocktools import (
|
||||
create_block,
|
||||
create_coinbase,
|
||||
)
|
||||
from test_framework.messages import (
|
||||
COIN,
|
||||
COutPoint,
|
||||
CTransaction,
|
||||
CTxIn,
|
||||
CTxOut,
|
||||
ToHex,
|
||||
)
|
||||
from test_framework.script import (
|
||||
CScript,
|
||||
OP_FALSE,
|
||||
OP_RETURN,
|
||||
)
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
|
||||
class CoinStatsIndexTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 2
|
||||
self.supports_cli = False
|
||||
self.extra_args = [
|
||||
[],
|
||||
["-coinstatsindex"]
|
||||
]
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
self._test_coin_stats_index()
|
||||
self._test_use_index_option()
|
||||
self._test_reorg_index()
|
||||
self._test_index_rejects_hash_serialized()
|
||||
|
||||
def block_subsidy(self, prev_bits):
|
||||
# Subsidy calculations valid till block 4500
|
||||
diff = 0x0000ffff / (prev_bits & 0x00ffffff)
|
||||
subsidy = (1111.0 / (pow((diff+1.0),2.0)))
|
||||
return min(500, max(1, subsidy))
|
||||
|
||||
def get_block_subsidy(self, prev_height):
|
||||
prev_block = self.nodes[0].getblockheader(self.nodes[0].getblockhash(prev_height), True)
|
||||
return self.block_subsidy(struct.unpack('!I', bytes.fromhex(prev_block['bits']))[0])
|
||||
|
||||
def block_sanity_check(self, block_info, prev_height):
|
||||
if prev_height != -1:
|
||||
block_subsidy = self.get_block_subsidy(prev_height)
|
||||
else:
|
||||
block_subsidy = 50 # see chainparams.cpp
|
||||
assert_equal(
|
||||
block_info['prevout_spent'] + block_subsidy,
|
||||
block_info['new_outputs_ex_coinbase'] + block_info['coinbase'] + block_info['unspendable']
|
||||
)
|
||||
|
||||
def _test_coin_stats_index(self):
|
||||
node = self.nodes[0]
|
||||
index_node = self.nodes[1]
|
||||
# Both none and muhash options allow the usage of the index
|
||||
index_hash_options = ['none', 'muhash']
|
||||
|
||||
# Generate a normal transaction and mine it
|
||||
node.generate(101)
|
||||
address = self.nodes[0].get_deterministic_priv_key().address
|
||||
node.sendtoaddress(address=address, amount=10, subtractfeefromamount=True)
|
||||
node.generate(1)
|
||||
|
||||
self.sync_blocks(timeout=120)
|
||||
|
||||
self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
|
||||
res0 = node.gettxoutsetinfo('none')
|
||||
|
||||
# The fields 'disk_size' and 'transactions' do not exist on the index
|
||||
del res0['disk_size'], res0['transactions']
|
||||
|
||||
for hash_option in index_hash_options:
|
||||
res1 = index_node.gettxoutsetinfo(hash_option)
|
||||
# The fields 'block_info' and 'total_unspendable_amount' only exist on the index
|
||||
del res1['block_info'], res1['total_unspendable_amount']
|
||||
res1.pop('muhash', None)
|
||||
|
||||
# Everything left should be the same
|
||||
assert_equal(res1, res0)
|
||||
|
||||
self.log.info("Test that gettxoutsetinfo() can get fetch data on specific heights with index")
|
||||
|
||||
# Generate a new tip
|
||||
node.generate(5)
|
||||
|
||||
for hash_option in index_hash_options:
|
||||
# Fetch old stats by height
|
||||
res2 = index_node.gettxoutsetinfo(hash_option, 102)
|
||||
del res2['block_info'], res2['total_unspendable_amount']
|
||||
res2.pop('muhash', None)
|
||||
assert_equal(res0, res2)
|
||||
|
||||
# Fetch old stats by hash
|
||||
res3 = index_node.gettxoutsetinfo(hash_option, res0['bestblock'])
|
||||
del res3['block_info'], res3['total_unspendable_amount']
|
||||
res3.pop('muhash', None)
|
||||
assert_equal(res0, res3)
|
||||
|
||||
# It does not work without coinstatsindex
|
||||
assert_raises_rpc_error(-8, "Querying specific block heights requires coinstatsindex", node.gettxoutsetinfo, hash_option, 102)
|
||||
|
||||
self.log.info("Test gettxoutsetinfo() with index and verbose flag")
|
||||
|
||||
for hash_option in index_hash_options:
|
||||
# Genesis block is unspendable
|
||||
res4 = index_node.gettxoutsetinfo(hash_option, 0)
|
||||
assert_equal(res4['total_unspendable_amount'], 50)
|
||||
assert_equal(res4['block_info'], {
|
||||
'unspendable': 50,
|
||||
'prevout_spent': 0,
|
||||
'new_outputs_ex_coinbase': 0,
|
||||
'coinbase': 0,
|
||||
'unspendables': {
|
||||
'genesis_block': 50,
|
||||
'bip30': 0,
|
||||
'scripts': 0,
|
||||
'unclaimed_rewards': 0
|
||||
}
|
||||
})
|
||||
self.block_sanity_check(res4['block_info'], -1)
|
||||
|
||||
# Test an older block height that included a normal tx
|
||||
res5 = index_node.gettxoutsetinfo(hash_option, 102)
|
||||
assert_equal(res5['total_unspendable_amount'], 50)
|
||||
assert_equal(res5['block_info'], {
|
||||
'unspendable': 0,
|
||||
'prevout_spent': 500,
|
||||
'new_outputs_ex_coinbase': Decimal('499.99999775'),
|
||||
'coinbase': Decimal('500.00000225'),
|
||||
'unspendables': {
|
||||
'genesis_block': 0,
|
||||
'bip30': 0,
|
||||
'scripts': 0,
|
||||
'unclaimed_rewards': 0
|
||||
}
|
||||
})
|
||||
self.block_sanity_check(res5['block_info'], 101)
|
||||
|
||||
# Generate and send a normal tx with two outputs
|
||||
tx1_inputs = []
|
||||
tx1_outputs = {self.nodes[0].getnewaddress(): 21, self.nodes[0].getnewaddress(): 42}
|
||||
raw_tx1 = self.nodes[0].createrawtransaction(tx1_inputs, tx1_outputs)
|
||||
funded_tx1 = self.nodes[0].fundrawtransaction(raw_tx1)
|
||||
signed_tx1 = self.nodes[0].signrawtransactionwithwallet(funded_tx1['hex'])
|
||||
tx1_txid = self.nodes[0].sendrawtransaction(signed_tx1['hex'])
|
||||
|
||||
# Find the right position of the 21 BTC output
|
||||
tx1_final = self.nodes[0].gettransaction(tx1_txid)
|
||||
for output in tx1_final['details']:
|
||||
if output['amount'] == Decimal('21.00000000') and output['category'] == 'receive':
|
||||
n = output['vout']
|
||||
|
||||
# Generate and send another tx with an OP_RETURN output (which is unspendable)
|
||||
tx2 = CTransaction()
|
||||
tx2.vin.append(CTxIn(COutPoint(int(tx1_txid, 16), n), b''))
|
||||
tx2.vout.append(CTxOut(int(20.99 * COIN), CScript([OP_RETURN] + [OP_FALSE]*30)))
|
||||
tx2_hex = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))['hex']
|
||||
self.nodes[0].sendrawtransaction(tx2_hex)
|
||||
|
||||
# Include both txs in a block
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
for hash_option in index_hash_options:
|
||||
# Check all amounts were registered correctly
|
||||
res6 = index_node.gettxoutsetinfo(hash_option, 108)
|
||||
assert_equal(res6['total_unspendable_amount'], Decimal('70.98999999'))
|
||||
assert_equal(res6['block_info'], {
|
||||
'unspendable': Decimal('20.98999999'),
|
||||
'prevout_spent': 511,
|
||||
'new_outputs_ex_coinbase': Decimal('489.99999741'),
|
||||
'coinbase': Decimal('500.01000260'),
|
||||
'unspendables': {
|
||||
'genesis_block': 0,
|
||||
'bip30': 0,
|
||||
'scripts': Decimal('20.98999999'),
|
||||
'unclaimed_rewards': 0
|
||||
}
|
||||
})
|
||||
self.block_sanity_check(res6['block_info'], 107)
|
||||
|
||||
# Create a coinbase that does not claim full subsidy and also
|
||||
# has two outputs
|
||||
cb = create_coinbase(109, nValue=35)
|
||||
cb.vout.append(CTxOut(5 * COIN, CScript([OP_FALSE])))
|
||||
cb.rehash()
|
||||
|
||||
# Generate a block that includes previous coinbase
|
||||
tip = self.nodes[0].getbestblockhash()
|
||||
block_time = self.nodes[0].getblock(tip)['time'] + 1
|
||||
block = create_block(int(tip, 16), cb, block_time)
|
||||
block.solve()
|
||||
self.nodes[0].submitblock(ToHex(block))
|
||||
self.sync_all()
|
||||
|
||||
for hash_option in index_hash_options:
|
||||
res7 = index_node.gettxoutsetinfo(hash_option, 109)
|
||||
assert_equal(res7['total_unspendable_amount'], Decimal('530.98999999'))
|
||||
assert_equal(res7['block_info'], {
|
||||
'unspendable': 460,
|
||||
'prevout_spent': 0,
|
||||
'new_outputs_ex_coinbase': 0,
|
||||
'coinbase': 40,
|
||||
'unspendables': {
|
||||
'genesis_block': 0,
|
||||
'bip30': 0,
|
||||
'scripts': 0,
|
||||
'unclaimed_rewards': 460
|
||||
}
|
||||
})
|
||||
self.block_sanity_check(res7['block_info'], 108)
|
||||
|
||||
self.log.info("Test that the index is robust across restarts")
|
||||
|
||||
res8 = index_node.gettxoutsetinfo('muhash')
|
||||
self.restart_node(1, extra_args=self.extra_args[1])
|
||||
res9 = index_node.gettxoutsetinfo('muhash')
|
||||
assert_equal(res8, res9)
|
||||
|
||||
index_node.generate(1)
|
||||
res10 = index_node.gettxoutsetinfo('muhash')
|
||||
assert(res8['txouts'] < res10['txouts'])
|
||||
|
||||
def _test_use_index_option(self):
|
||||
self.log.info("Test use_index option for nodes running the index")
|
||||
|
||||
self.connect_nodes(0, 1)
|
||||
self.nodes[0].waitforblockheight(110)
|
||||
res = self.nodes[0].gettxoutsetinfo('muhash')
|
||||
option_res = self.nodes[1].gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)
|
||||
del res['disk_size'], option_res['disk_size']
|
||||
assert_equal(res, option_res)
|
||||
|
||||
def _test_reorg_index(self):
|
||||
self.log.info("Test that index can handle reorgs")
|
||||
|
||||
# Generate two block, let the index catch up, then invalidate the blocks
|
||||
index_node = self.nodes[1]
|
||||
reorg_blocks = index_node.generatetoaddress(2, index_node.getnewaddress())
|
||||
reorg_block = reorg_blocks[1]
|
||||
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 = index_node.generate(2)[1]
|
||||
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
|
||||
res2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
|
||||
assert_equal(res["bestblock"], block)
|
||||
assert_equal(res["muhash"], res2["muhash"])
|
||||
assert(res["muhash"] != res_invalid["muhash"])
|
||||
|
||||
# Test that requesting reorged out block by hash is still returning correct results
|
||||
res_invalid2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=reorg_block)
|
||||
assert_equal(res_invalid2["muhash"], res_invalid["muhash"])
|
||||
assert(res["muhash"] != res_invalid2["muhash"])
|
||||
|
||||
# Add another block, so we don't depend on reconsiderblock remembering which
|
||||
# blocks were touched by invalidateblock
|
||||
index_node.generate(1)
|
||||
|
||||
# Ensure that removing and re-adding blocks yields consistent results
|
||||
block = index_node.getblockhash(99)
|
||||
index_node.invalidateblock(block)
|
||||
index_node.reconsiderblock(block)
|
||||
res3 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
|
||||
assert_equal(res2, res3)
|
||||
|
||||
self.log.info("Test that a node aware of stale blocks syncs them as well")
|
||||
node = self.nodes[0]
|
||||
# Ensure the node is aware of a stale block prior to restart
|
||||
node.getblock(reorg_block)
|
||||
|
||||
self.restart_node(0, ["-coinstatsindex"])
|
||||
assert_raises_rpc_error(-32603, "Unable to get data because coinstatsindex is still syncing.", node.gettxoutsetinfo, 'muhash', reorg_block)
|
||||
|
||||
def _test_index_rejects_hash_serialized(self):
|
||||
self.log.info("Test that the rpc raises if the legacy hash is passed with the index")
|
||||
|
||||
msg = "hash_serialized_2 hash type cannot be queried for a specific block"
|
||||
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_2', hash_or_height=111)
|
||||
|
||||
for use_index in {True, False, None}:
|
||||
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_2', hash_or_height=111, use_index=use_index)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
CoinStatsIndexTest().main()
|
@ -68,25 +68,22 @@ class RpcMiscTest(BitcoinTestFramework):
|
||||
assert_equal(node.getindexinfo(), {})
|
||||
|
||||
# Restart the node with indices and wait for them to sync
|
||||
self.restart_node(0, ["-txindex", "-blockfilterindex"])
|
||||
self.restart_node(0, ["-txindex", "-blockfilterindex", "-coinstatsindex"])
|
||||
wait_until(lambda: all(i["synced"] for i in node.getindexinfo().values()))
|
||||
|
||||
# Returns a list of all running indices by default
|
||||
values = {"synced": True, "best_block_height": 200}
|
||||
assert_equal(
|
||||
node.getindexinfo(),
|
||||
{
|
||||
"txindex": {"synced": True, "best_block_height": 200},
|
||||
"basic block filter index": {"synced": True, "best_block_height": 200}
|
||||
"txindex": values,
|
||||
"basic block filter index": values,
|
||||
"coinstatsindex": values,
|
||||
}
|
||||
)
|
||||
|
||||
# Specifying an index by name returns only the status of that index
|
||||
assert_equal(
|
||||
node.getindexinfo("txindex"),
|
||||
{
|
||||
"txindex": {"synced": True, "best_block_height": 200},
|
||||
}
|
||||
)
|
||||
for i in {"txindex", "basic block filter index", "coinstatsindex"}:
|
||||
assert_equal(node.getindexinfo(i), {i: values})
|
||||
|
||||
# Specifying an unknown index name returns an empty result
|
||||
assert_equal(node.getindexinfo("foo"), {})
|
||||
|
@ -48,7 +48,7 @@ def script_BIP34_coinbase_height(height):
|
||||
return CScript([CScriptNum(height)])
|
||||
|
||||
|
||||
def create_coinbase(height, pubkey=None, dip4_activated=False, v20_activated=False):
|
||||
def create_coinbase(height, pubkey=None, dip4_activated=False, v20_activated=False, nValue=500):
|
||||
"""Create a coinbase transaction, assuming no miner fees.
|
||||
|
||||
If pubkey is passed in, the coinbase output will be a P2PK output;
|
||||
@ -56,9 +56,10 @@ def create_coinbase(height, pubkey=None, dip4_activated=False, v20_activated=Fal
|
||||
coinbase = CTransaction()
|
||||
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff))
|
||||
coinbaseoutput = CTxOut()
|
||||
coinbaseoutput.nValue = 500 * COIN
|
||||
halvings = int(height / 150) # regtest
|
||||
coinbaseoutput.nValue >>= halvings
|
||||
coinbaseoutput.nValue = nValue * COIN
|
||||
if nValue == 500:
|
||||
halvings = int(height / 150) # regtest
|
||||
coinbaseoutput.nValue >>= halvings
|
||||
if (pubkey is not None):
|
||||
coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG])
|
||||
else:
|
||||
|
@ -262,6 +262,7 @@ BASE_SCRIPTS = [
|
||||
'rpc_deriveaddresses.py --usecli',
|
||||
'rpc_scantxoutset.py',
|
||||
'feature_logging.py',
|
||||
'feature_coinstatsindex.py',
|
||||
'p2p_node_network_limited.py',
|
||||
'p2p_permissions.py',
|
||||
'feature_blocksdir.py',
|
||||
|
@ -14,6 +14,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
|
||||
"node/blockstorage -> validation -> node/blockstorage"
|
||||
"index/blockfilterindex -> node/blockstorage -> validation -> index/blockfilterindex"
|
||||
"index/base -> validation -> index/blockfilterindex -> index/base"
|
||||
"index/coinstatsindex -> node/coinstats -> index/coinstatsindex"
|
||||
"policy/fees -> txmempool -> policy/fees"
|
||||
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel"
|
||||
"qt/bitcoingui -> qt/walletframe -> qt/bitcoingui"
|
||||
|
Loading…
Reference in New Issue
Block a user