Page MenuHomePhabricator

D11599.id34003.diff
No OneTemporary

D11599.id34003.diff

diff --git a/contrib/debian/examples/bitcoin.conf b/contrib/debian/examples/bitcoin.conf
--- a/contrib/debian/examples/bitcoin.conf
+++ b/contrib/debian/examples/bitcoin.conf
@@ -127,8 +127,11 @@
# be validated sooner.
#paytxfee=0.00
+# Maintain coinstats index used by the gettxoutsetinfo RPC (default: 0).
+#coinstatsindex=1
+
# Enable pruning to reduce storage requirements by deleting old blocks.
-# This mode is incompatible with -txindex and -rescan.
+# This mode is incompatible with -txindex, -coinstatsindex and -rescan.
# 0 = default (no pruning).
# 1 = allows manual pruning via RPC.
# >=550 = target to stay under in MiB.
diff --git a/doc/release-notes.md b/doc/release-notes.md
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -5,3 +5,5 @@
<https://download.bitcoinabc.org/0.25.8/>
This release includes the following features and fixes:
+- Users can start their node with the option `-coinstatsindex` which syncs an
+ index of coin statistics in the background.
diff --git a/src/init.cpp b/src/init.cpp
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -29,6 +29,7 @@
#include <httprpc.h>
#include <httpserver.h>
#include <index/blockfilterindex.h>
+#include <index/coinstatsindex.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
#include <interfaces/node.h>
@@ -175,6 +176,9 @@
g_txindex->Interrupt();
}
ForEachBlockFilterIndex([](BlockFilterIndex &index) { index.Interrupt(); });
+ if (g_coin_stats_index) {
+ g_coin_stats_index->Interrupt();
+ }
}
void Shutdown(NodeContext &node) {
@@ -269,6 +273,10 @@
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();
@@ -467,6 +475,11 @@
" not affected. (default: %u)",
DEFAULT_BLOCKSONLY),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ argsman.AddArg("-coinstatsindex",
+ strprintf("Maintain coinstats index used by the "
+ "gettxoutsetinfo RPC (default: %u)",
+ DEFAULT_COINSTATSINDEX),
+ ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg(
"-conf=<file>",
strprintf("Specify path to read-only configuration file. Relative "
@@ -567,11 +580,12 @@
"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 and -rescan. "
- "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)",
+ "This mode is incompatible with -txindex, -coinstatsindex "
+ "and -rescan. 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(
@@ -1846,11 +1860,15 @@
nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS);
}
- // if using block pruning, then disallow txindex
+ // if using block pruning, then disallow txindex and coinstatsindex
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."));
+ }
}
// -bind and -whitebind can't be set when not listening
@@ -2855,6 +2873,11 @@
GetBlockFilterIndex(filter_type)->Start();
}
+ if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX)) {
+ g_coin_stats_index = std::make_unique<CoinStatsIndex>(
+ /* cache size */ 0, false, fReindex);
+ g_coin_stats_index->Start();
+ }
// Step 9: load wallet
for (const auto &client : node.chain_clients) {
if (!client->load()) {
diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp
--- a/src/node/coinstats.cpp
+++ b/src/node/coinstats.cpp
@@ -8,6 +8,7 @@
#include <coins.h>
#include <crypto/muhash.h>
#include <hash.h>
+#include <index/coinstatsindex.h>
#include <primitives/txid.h>
#include <serialize.h>
#include <util/check.h>
@@ -91,15 +92,24 @@
const std::function<void()> &interruption_point) {
std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
assert(pcursor);
-
stats.hashBlock = pcursor->GetBestBlock();
+
+ const CBlockIndex *pindex;
{
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;
+ pindex = blockman.LookupBlockIndex(stats.hashBlock);
+ stats.nHeight = Assert(pindex)->nHeight;
+ }
+
+ // Use CoinStatsIndex if it is 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) {
+ return g_coin_stats_index->LookUpStats(pindex, stats);
}
PrepareHash(hash_obj, stats);
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -16,6 +16,7 @@
#include <core_io.h>
#include <hash.h>
#include <index/blockfilterindex.h>
+#include <index/coinstatsindex.h>
#include <node/blockstorage.h>
#include <node/coinstats.h>
#include <node/context.h>
@@ -1321,6 +1322,19 @@
ret.pushKV("disk_size", stats.nDiskSize);
ret.pushKV("total_amount", stats.nTotalAmount);
} else {
+ if (g_coin_stats_index) {
+ const IndexSummary summary{
+ g_coin_stats_index->GetSummary()};
+
+ if (!summary.synced) {
+ throw JSONRPCError(
+ RPC_INTERNAL_ERROR,
+ strprintf("Unable to read UTXO set because "
+ "coinstatsindex is still syncing. "
+ "Current height: %d",
+ summary.best_block_height));
+ }
+ }
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to read UTXO set");
}
diff --git a/src/validation.h b/src/validation.h
--- a/src/validation.h
+++ b/src/validation.h
@@ -89,6 +89,7 @@
static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60;
static const bool DEFAULT_CHECKPOINTS_ENABLED = true;
static const bool DEFAULT_TXINDEX = false;
+static constexpr bool DEFAULT_COINSTATSINDEX{false};
static const char *const DEFAULT_BLOCKFILTERINDEX = "0";
/** Default for -persistmempool */
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py
new file mode 100755
--- /dev/null
+++ b/test/functional/feature_coinstatsindex.py
@@ -0,0 +1,77 @@
+#!/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.
+"""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal, try_rpc
+
+
+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()
+
+ 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_000_000,
+ subtractfeefromamount=True)
+ node.generate(1)
+
+ self.sync_blocks(timeout=120)
+
+ self.log.info(
+ "Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
+ self.wait_until(lambda: not try_rpc(-32603,
+ "Unable to read UTXO set", node.gettxoutsetinfo))
+ res0 = node.gettxoutsetinfo('none')
+
+ # The fields 'disk_size' and 'transactions' do not work on the index, so
+ # don't check them.
+ del res0['disk_size'], res0['transactions']
+
+ self.wait_until(lambda: not try_rpc(-32603,
+ "Unable to read UTXO set",
+ index_node.gettxoutsetinfo,
+ 'muhash'))
+ for hash_option in index_hash_options:
+ res1 = index_node.gettxoutsetinfo(hash_option)
+ res1.pop('muhash', None)
+
+ # The fields 'disk_size' and 'transactions' do not work on the index
+ # so don't check them (they will be removed from the index in the
+ # next commit).
+ del res1['disk_size'], res1['transactions']
+
+ # Everything left should be the same
+ assert_equal(res1, res0)
+
+
+if __name__ == '__main__':
+ CoinStatsIndexTest().main()
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -794,8 +794,8 @@
# Remove empty wallets dir
os.rmdir(cache_path('wallets'))
for entry in os.listdir(cache_path()):
- # Only keep chainstate and blocks folder
- if entry not in ['chainstate', 'blocks']:
+ # Only keep indexes, chainstate and blocks folders
+ if entry not in ['chainstate', 'blocks', 'indexes']:
os.remove(cache_path(entry))
for i in range(self.num_nodes):
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -17,6 +17,7 @@
"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"
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel"
"qt/bitcoingui -> qt/walletframe -> qt/bitcoingui"
"qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel"

File Metadata

Mime Type
text/plain
Expires
Sat, Apr 26, 11:38 (14 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5570824
Default Alt Text
D11599.id34003.diff (11 KB)

Event Timeline