Requires #19521 (this PR only adds the last commit).
The rpc dumpcoinstats
dumps the content of the coinstats index into a CSV file for auditing purposes.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
See the guideline for information on the review process.
Type | Reviewers |
---|---|
Concept ACK | laanwj, promag |
Approach ACK | Zero-1729 |
Stale ACK | PierreRochard, w0xlt, Sjors |
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
1249+ CHECK_NONFATAL(stats.total_amount.has_value());
1250+ file << strprintf("%s,", FormatMoney(stats.total_amount.value()));
1251+ file << strprintf("%s,", FormatMoney(stats.total_unspendable_amount));
1252+ file << strprintf("%s,", FormatMoney(stats.total_prevout_spent_amount));
1253+ file << strprintf("%s,", FormatMoney(stats.total_new_outputs_ex_coinbase_amount));
1254+ file << strprintf("%s,", FormatMoney(stats.total_coinbase_amount));
coinstatsindex
but we can just have both. So now there is a bool argument to switch between cummulative and per-block.
Concept ACK. Tested and reviewed 87fd7b8, but see inline
Also, not to scope creep, but can you add a few goodies from CBlockIndex
to the CSV?
nTx
)nChainWork
(or per block)nVersion
Also, not to scope creep, but can you add a few goodies from
CBlockIndex
to the CSV?
- the number of transactions per block (
nTx
)- timestamp (and maybe MTP, but that can be derived in a spreadsheet)
nChainWork
(or per block)nVersion
Easy enough, if it enables more use cases we can easily add more. I have added all of these.
1172+{
1173+ return RPCHelpMan{"dumpcoinstats",
1174+ "\nDumps content of coinstats index to a CSV file.\n",
1175+ {
1176+ {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (absolute path recommended)"},
1177+ {"cummulative", RPCArg::Type::BOOL, RPCArg::Default{false}, "The numbers coming out of coinstatsindex will be cummulative"},
1269+ file << strprintf("%s,", FormatMoney(stats.total_unspendables_genesis_block));
1270+ file << strprintf("%s,", FormatMoney(stats.total_unspendables_bip30));
1271+ file << strprintf("%s,", FormatMoney(stats.total_unspendables_scripts));
1272+ file << strprintf("%s,", FormatMoney(stats.total_unspendables_unclaimed_rewards));
1273+ } else {
1274+ file << strprintf("%s,", FormatMoney(stats.total_unspendable_amount - prev_stats.total_unspendable_amount));
Nit: maybe calculate these values before the if (cummulative)
to remove the repetition here:
0const unspendable_amount = tats.total_unspendable_amount - cumulative ? 0 : prev_stats.total_prevout_spent_amount
1...
2file << strprintf("%s,", FormatMoney(unspendable_amount));
1208+ }
1209+
1210+ const bool cummulative{request.params[1].isNull() ? false : request.params[1].get_bool()};
1211+
1212+ file << strprintf("height,"
1213+ "bestblock,"
block_hash
?
1212+ file << strprintf("height,"
1213+ "bestblock,"
1214+ "txouts,"
1215+ "bogosize,"
1216+ "muhash,"
1217+ "total_amount,"
cumulative ? "total_amount" : "amount"
154@@ -155,6 +155,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
155 { "getblockstats", 0, "hash_or_height" },
156 { "getblockstats", 1, "stats" },
157 { "pruneblockchain", 0, "height" },
158+ { "dumpcoinstats", 1, "cummulative"},
Windows CI seems unhappy:
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: ‘C:\Users\ContainerAdministrator\AppData\Local\Temp\test_runner_₿_🏃_20220207_231436\feature_coinstatsindex_17\node1\test.csv’
1162@@ -1161,6 +1163,162 @@ static RPCHelpMan pruneblockchain()
1163 };
1164 }
1165
1166+static RPCHelpMan dumpcoinstats()
1167+{
1168+ return RPCHelpMan{"dumpcoinstats",
1169+ "\nDumps content of coinstats index to a CSV file.\n",
nit:
0 "Dumps content of coinstats index to a CSV file.",
Can remove some whitespace and \n
.
28@@ -29,7 +29,7 @@ enum class CoinStatsHashType {
29
30 struct CCoinsStats {
31 //! Which hash type to use
32- const CoinStatsHashType m_hash_type;
33+ CoinStatsHashType m_hash_type;
1181+ HelpExampleCli("dumpcoinstats", "\"test\"") +
1182+ HelpExampleRpc("dumpcoinstats", "\"test\"")
1183+ },
1184+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1185+{
1186+ if (request.params[0].isNull())
{
here and elsewhere?
1301+ file << strprintf("%s,", FormatMoney(unspendables_unclaimed_rewards));
1302+
1303+ file << strprintf("%d,", pindex->nTx);
1304+ file << strprintf("%d,", pindex->nTime);
1305+ file << strprintf("%d,", pindex->GetMedianTimePast());
1306+ file << strprintf("%s,", pindex->nChainWork.GetHex());
1296@@ -1291,7 +1297,13 @@ static RPCHelpMan dumpcoinstats()
1297 file << strprintf("%s,", FormatMoney(unspendables_scripts));
1298
1299 const CAmount unspendables_unclaimed_rewards = cumulative ? stats.total_unspendables_unclaimed_rewards : stats.total_unspendables_unclaimed_rewards - prev_stats.total_unspendables_unclaimed_rewards;
1300- file << strprintf("%s\n", FormatMoney(unspendables_unclaimed_rewards));
1301+ file << strprintf("%s,", FormatMoney(unspendables_unclaimed_rewards));
1302+
1303+ file << strprintf("%d,", pindex->nTx);
tACK 3772c09e16544c7876da13f33e6cb11401c9e4bd
Remaining feedback can wait for a followup, but I’m also happy to review it again.
Maybe document the fields in the help, since some are not obvious. For each item, note if it has a cumulative version.
A slightly more logical ordering of columns would be:
txins
to count the number of outputs spent. I think you can use coin_count
between two blocks and txouts
to calculate it.amount_spent
)amount_created
)This puts the most useful fields first.
There may be some metrics that are useful for both cumulative and non-cumulative. In particular it’s both useful to know how many outputs were created and destroyed in any given block (and their value, and the delta UTXO set size), and how many outputs existed at any given block (and much value they represented and disk space they used).
1263+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
1264+
1265+ file << strprintf("%d,", static_cast<int64_t>(stats.nHeight));
1266+ file << strprintf("%s,", stats.hashBlock.GetHex());
1267+ file << strprintf("%d,", static_cast<int64_t>(stats.nTransactionOutputs));
1268+ file << strprintf("%d,", static_cast<int64_t>(stats.nBogoSize));
Addressed @Sjors feedback and also squashed some commits because it was just easier to do the reorganization this way.
Maybe document the fields in the help, since some are not obvious. For each item, note if it has a cumulative version.
I was a bit unsure where to put this and how to format it but I tried my best.
A slightly more logical ordering of columns would be:
I applied that order
- maybe insert:
txins
to count the number of outputs spent. I think you can usecoin_count
between two blocks andtxouts
to calculate it.
I think it’s not possible without updating coinstatsindex
. The coin_count
isn’t included there, I am not sure why but probably just bad timing since it was merged while I was already working on the index. Leaving this as a follow-up since I first have to think about updating the index. I also thought about calculating it with txouts but I think it would be (txouts - prev_txouts) - coinbase_txouts - unspendable_txouts
and from those I am missing the unspendable counts as well.
- txouts (document for cumulative: this is the total number of outputs ever created, not the total in existence at this height)
I actually didn’t have this as non-cummulative yet, but done as well.
- maybe insert: the number txouts in existence at this height (the same for cumulative). It may be useful to track its ratio to bogosize over time.
I think you mean UTXOs here which would basically be coin_count
? Or should it also include unspendable outputs?
- prevout_spent_amount (maybe rename to
amount_spent
)- amount (or
amount_created
)
renamed both
- unspendable_amount (docs should clarify this includes both immature and OP_RETURN)
No, immature is not included here. This is the just the total of the other unspendable values (genesis, bip30, scripts and unclaimed rewards)
1292+ file << strprintf("%s,", stats.hashBlock.GetHex());
1293+ file << strprintf("%d,", pindex->nTime);
1294+ file << strprintf("%d,", pindex->GetMedianTimePast());
1295+ file << strprintf("%s,", pindex->nVersion);
1296+
1297+ const int num_tx = cumulative ? pindex->nTx : pindex->nTx - (pindex->pprev ? pindex->pprev->nTx : 0);
nTx
is not cumulative
0 num_tx_sum += pindex->nTx;
1 const int num_tx = cumulative ? num_tx_sum : pindex->nTx;
1281+ coins_view = &active_chainstate.CoinsDB();
1282+ blockman = &active_chainstate.m_blockman;
1283+ }
1284+
1285+ CCoinsStats prev_stats{CoinStatsHashType::MUHASH};
1286+
0 uint64_t num_tx_sum = 0;
1171+ "- block_hash\n"
1172+ "- timestamp\n"
1173+ "- mediantime\n"
1174+ "- version\n"
1175+ "- (total_)txs - Number of transactions included\n"
1176+ "- (total_)txouts - Number of transaction outputs created\n"
“Nett number of” or “outputs added”
See e.g. block 170 that has a coinbase output and a regular transaction with one input and two outputs, yet txouts
is 2.
https://www.blockchain.com/btc/block/00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee
(it’d be nice to actually know how many were created, but see discussion elsewhere)
1173+ "- mediantime\n"
1174+ "- version\n"
1175+ "- (total_)txs - Number of transactions included\n"
1176+ "- (total_)txouts - Number of transaction outputs created\n"
1177+ "- (total_)amount_spent - Amount of inputs spent\n"
1178+ "- (total_)amount_created - Amount of coins added to the UTXO set\n"
amount_generated
and add “(never more than the block subsidy)
1176+ "- (total_)txouts - Number of transaction outputs created\n"
1177+ "- (total_)amount_spent - Amount of inputs spent\n"
1178+ "- (total_)amount_created - Amount of coins added to the UTXO set\n"
1179+ "- (total_)coinbase_amount - Coinbase subsidy amount\n"
1180+ "- (total_)new_outputs_ex_coinbase_amount - Amount of new outputs created by this block\n"
1181+ "- (total_)subsidy - Total subsidy paid out\n"
Should be “Maximum subsidy”
Block 501,726 (0000000000000000004b27f9ee7ba33d6f048f684aaeb0eea4befd80f1701126) claimed 0 rewards, yet its subsidy
column says 12.5
.
Alternatively we can keep subsidy
what it is, but then amount_generated
should be negative.
https://bitcoin.stackexchange.com/questions/38994/will-there-be-21-million-bitcoins-eventually
Unrelated bug: the unspendable_amount
for this block is set to 12.5 but should be zero (also wrong in gettxoutsetinfo
). Same issue for block 526,591 which claimed half its reward (the other half is not unspendable, it doesn’t exist). This is different than the situation with the genesis block: that block does exist and does claim a reward, but it just can’t be spent.
Hehe, we are really getting into the weeds here :)
Yeah, the “paid out” part is definitely wrong. It wasn’t paid out but also it can never be paid out in the the future, I still find this hard to express concisely so changed to “Maximum subsidy”.
For the unspendable_amount
comment: that is not a bug by my definition. When I wrote the coinstatsindex one of my goals was to make these non-existent amounts visible, and I summed this up with the other categories of lost coins under the umbrella term unspendables . See this code block in coinstatsindex:
0 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)};
1 m_total_unspendable_amount += unclaimed_rewards;
2 m_total_unspendables_unclaimed_rewards += unclaimed_rewards;
If the definition for anything “unspendable” was that it needed have to existed before as an output in a tx, then yes, the unclaimed rewards should not be part of this category. But I think this is confusing to some without deep knowledge of how the subsidies work. They might think that these amounts could still be claimed some time in the future, since they haven’t existed yet and there will be 21M, right? So I was more looking at this from the side of the 21M: for example the 12.5 are part of the 21M total (and part of the >19M that are considered mined) even though they will never exist because the moment to claim them has passed. Thus, I consider them unspendable even though they never even existed in the first place, i.e. “unspendable” includes “never spendable in the future”. If we don’t keep track of these to some degree we can never really do the full accounting that this export was intended for.
I see, in that case just document that “unspendable” means “unspendable coins plus unclaimed subsidies”.
Though I think it’s cleaner to track unspendable
and unclaimed_rewards
separately, and then explain in the docs that “unclaimed” means it can never be claimed. I suspect anyone using an RPC like this will understand that.
Done, amended the doc for the unspendable_amount
.
I see your reasoning but at least for now let’s keep it consistent with the index code and gettxoutsetinfo
where the unclaimed rewards are in the unspendable ‘category’.
1174+ "- version\n"
1175+ "- (total_)txs - Number of transactions included\n"
1176+ "- (total_)txouts - Number of transaction outputs created\n"
1177+ "- (total_)amount_spent - Amount of inputs spent\n"
1178+ "- (total_)amount_created - Amount of coins added to the UTXO set\n"
1179+ "- (total_)coinbase_amount - Coinbase subsidy amount\n"
87f0cc381c77e7d321d9e01a11a4725878c81810 almost there
Leaving this as a follow-up since I first have to think about updating the index
Yeah, let’s not touch the index itself here.
I think you mean UTXOs here which would basically be
coin_count
? Or should it also include unspendable outputs?
Excluding unspendable sounds good to me. Other than the coinbase, they’re not really part of the UTXO set in practice, e.g. they don’t use RAM and their bogo_size
is zero too.
Seeing this in a spreadsheet slightly changed my thoughts on the optimal order. After txouts let’s do:
(then continue as it is now, from unspendable_scripts)
Seeing this in a spreadsheet slightly changed my thoughts on the optimal order. After txouts let’s do:
- coinbase_amount
- subsidy (which represents the maximum allowed subsidy)
- amount_generated (in theory this can be less than zero, but afaik there has never been a block that included transactions but forgot to claim both the subsidy and fees)
- unclaimed_rewards
- amount_spent
- new_outputs_ex_coinbase_amount
(then continue as it is now, from unspendable_scripts)
Done
tACK 418331f35424bdc95dedba6023bf0d8f7819fabe
TIL block 367,853 has the highest number of transactions ever: 12,239
Added a wish list for more stats: https://github.com/bitcoin/bitcoin/issues/24581
836+ HelpExampleCli("dumpcoinstats", "\"test\"") +
837+ HelpExampleRpc("dumpcoinstats", "\"test\"")
838+ },
839+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
840+{
841+ if (request.params[0].isNull()) {
919+ CCoinsStats prev_stats{CoinStatsHashType::MUHASH};
920+ uint64_t num_tx_sum = 0;
921+
922+ for (; pindex; pindex = active_chainstate.m_chain.Next(pindex)) {
923+ if (!GetUTXOStats(coins_view, *blockman, stats, node.rpc_interruption_point, pindex))
924+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
nit: Perhaps the error message could be more specific?
0 throw JSONRPCError(RPC_INTERNAL_ERROR, "Failed to generate coins stats");
986@@ -985,6 +987,202 @@ static RPCHelpMan gettxoutsetinfo()
987 };
988 }
989
990+static RPCHelpMan dumpcoinstats()
991+{
992+ return RPCHelpMan{"dumpcoinstats",
993+ "Dumps content of coinstats index to a CSV file. The resulting file contains the following columns:\n"
1039+ throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.u8string() + " already exists. If you are sure this is what you want, move it out of the way first");
1040+ }
1041+
1042+ std::ofstream file;
1043+ file.open(filepath);
1044+ if (!file.is_open())
0 if (!file.is_open()) {
1092+ }
1093+
1094+ NodeContext& node = EnsureAnyNodeContext(request.context);
1095+ ChainstateManager& chainman = EnsureChainman(node);
1096+ CChainState& active_chainstate = chainman.ActiveChainstate();
1097+ active_chainstate.ForceFlushStateToDisk();
1109+ uint64_t num_tx_sum = 0;
1110+
1111+ for (; pindex; pindex = active_chainstate.m_chain.Next(pindex)) {
1112+ const std::optional<CCoinsStats> maybe_stats{GetUTXOStats(coins_view, *blockman, CoinStatsHashType::MUHASH, node.rpc_interruption_point, pindex)};
1113+
1114+ if (!maybe_stats)
0 if (!maybe_stats) {
Also, could mention that the file may have been created and may need to be manually cleared now?
1112+ const std::optional<CCoinsStats> maybe_stats{GetUTXOStats(coins_view, *blockman, CoinStatsHashType::MUHASH, node.rpc_interruption_point, pindex)};
1113+
1114+ if (!maybe_stats)
1115+ throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Failed to get coins stats at height %d", pindex->nHeight));
1116+
1117+ CCoinsStats stats{maybe_stats.value()};
310+ if cumulative:
311+ file = "ctest.csv"
312+ else:
313+ file = "test.csv"
314+
315+ path = os.path.join(get_datadir_path(self.options.tmpdir, 1), file)
Probably time to introduce a helper and use that:
0diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
1index 7d2db391b6..aa9ff9831f 100755
2--- a/test/functional/test_framework/test_node.py
3+++ b/test/functional/test_framework/test_node.py
4@@ -381,9 +381,13 @@ class TestNode():
5 def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT):
6 wait_until_helper(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor)
7
8+ [@property](/bitcoin-bitcoin/contributor/property/)
9+ def datadir_path(self) -> Path:
10+ return Path(self.datadir)
11+ [@property](/bitcoin-bitcoin/contributor/property/)
12 def chain_path(self) -> Path:
13- return Path(self.datadir) / self.chain
14+ return self.datadir_path / self.chain [@property](/bitcoin-bitcoin/contributor/property/)
15 def debug_log_path(self) -> Path:
0 path = self.nodes[1].datadir_path / file
/
operator would mean file
would also need to be a Path object iiuc. So, instead I opted for a slightly different approach that I liked a little better using another helper datadir_file_path
. What do you think?
but using the / operator would mean file would also need to be a Path object iiuc
Does it? (It doesn’t for me locally)
Hm, seems like I misinterpreted the error, actually the next line fails. And I am now also seeing it fail with the Path
object but I am sure it previously passed at least twice with it. Very strange. Maybe a python version or platform issue, need to look deeper into this.
02022-06-11T16:28:24.998000Z TestFramework (INFO): Test dumpcoinstats RPC
12022-06-11T16:28:24.998000Z TestFramework (ERROR): Unexpected exception caught during testing
2Traceback (most recent call last):
3 File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/test_framework.py", line 133, in main
4 self.run_test()
5 File "/Users/FJ/projects/clones/bitcoin/test/functional/feature_coinstatsindex.py", line 58, in run_test
6 self._test_dumpcoinstats_rpc()
7 File "/Users/FJ/projects/clones/bitcoin/test/functional/feature_coinstatsindex.py", line 319, in _test_dumpcoinstats_rpc
8 self.nodes[1].dumpcoinstats(path, cumulative)
9 File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/coverage.py", line 49, in __call__
10 return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
11 File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/authproxy.py", line 141, in __call__
12 postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
13 File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/authproxy.py", line 131, in get_request
14 json.dumps(args or argsn, default=EncodeDecimal, ensure_ascii=self.ensure_ascii),
15 File "/opt/homebrew/Cellar/python@3.9/3.9.12/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py", line 234, in dumps
16 return cls(
17 File "/opt/homebrew/Cellar/python@3.9/3.9.12/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 199, in encode
18 chunks = self.iterencode(o, _one_shot=True)
19 File "/opt/homebrew/Cellar/python@3.9/3.9.12/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/encoder.py", line 257, in iterencode
20 return _iterencode(o, 0)
21 File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/authproxy.py", line 66, in EncodeDecimal
22 raise TypeError(repr(o) + " is not JSON serializable")
23TypeError: PosixPath('/var/folders/9z/n7rz_6cj3bq__11k5kcrsvvm0000gn/T/bitcoin_func_test_ru27w0qw/node1/ctest.csv') is not JSON serializable
str(path)
before passing it as json?
111@@ -112,7 +112,7 @@ def run_test(self):
112 self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar$baz'])
113
114 self.log.info('Check that failure to write cookie file will abort the node gracefully')
115- cookie_file = os.path.join(get_datadir_path(self.options.tmpdir, 0), self.chain, '.cookie.tmp')
116+ cookie_file = str(self.nodes[0].datadir_path / self.chain / '.cookie.tmp')
0 cookie_file = self.nodes[0].chain_path / '.cookie.tmp'
nit?
If the index has not fully been synced yet, the error thrown would be:
Failed to get coins stats at height X. You may have to remove an incomplete dump file before running this command again.
It’s not very clear that the index has not finished syncing. Also, the csv file would be created before this error is thrown, so the file would have to be removed manually.
I would suggest checking if the index is synced before dumping and create the file only if the index is ready+enabled, with something like this:
0diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
1index 74877d7da..7bb6e2927 100644
2--- a/src/rpc/blockchain.cpp
3+++ b/src/rpc/blockchain.cpp
4@@ -1039,16 +1039,21 @@ static RPCHelpMan dumpcoinstats()
5 throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.u8string() + " already exists. If you are sure this is what you want, move it out of the way first");
6 }
7
8+ if (!g_coin_stats_index) {
9+ throw JSONRPCError(RPC_MISC_ERROR, "Coinstats index is not enabled");
10+ }
11+
12+ const auto summary{g_coin_stats_index->GetSummary()};
13+ if (!summary.synced) {
14+ throw JSONRPCError(RPC_MISC_ERROR, strprintf("coinstatsindex is not synced. Current best block height: %d", summary.best_block_height));
15+ }
16+
17 std::ofstream file;
18 file.open(filepath);
19 if (!file.is_open()) {
20 throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open dump file");
21 }
22
23- if (!g_coin_stats_index) {
24- throw JSONRPCError(RPC_MISC_ERROR, "Coinstats index is not enabled");
25- }
26-
27 const bool cumulative{request.params[1].isNull() ? false : request.params[1].get_bool()};
28
29 file << strprintf("height,"
1166+ const uint64_t bogo_size = cumulative ? stats.nBogoSize : stats.nBogoSize - prev_stats.nBogoSize;
1167+ file << strprintf("%d\n", static_cast<int64_t>(bogo_size));
1168+
1169+ prev_stats = stats;
1170+
1171+ if (pindex->nHeight >= active_chainstate.m_chain.Tip()->nHeight) break;
Is it possible that the index has not synced up to active_chainstate.m_chain.Tip()
if the tip moved during the dump? I don’t see any lock for that. Could be fixed with this?
0 if (pindex->nHeight >= g_coin_stats_index->GetSummary().best_block_height) break;
Addressed comments from @aureleoules , thanks for the review!
Further note: I can’t change the status back it seems, but please treat this as a draft for now. This PR will need to be updated and rebased soon when I have resolved the overflow issue noted in #26362.
EDIT: Rebased as well since there was a silent merge conflict.
This RPC dumps the full content of the coinstats index together with some other statistics into a CSV file.
🐙 This pull request conflicts with the target branch and needs rebase.
There hasn’t been much activity lately and the patch still needs rebase. What is the status here?