New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rpc: Add dumpcoinstats #19792
rpc: Add dumpcoinstats #19792
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, 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. |
Taking this out of draft status since I am interested in conceptual feedback on something like this but of course, #19521 still has a long way to go before this can be seriously considered. |
Tested ACK 21c42c2 |
Concept ACK, I wish we had gotten streaming to work though so that the file can be downloaded (see my experiment in #7759). RPCs that creates a server-local file, although there are already some others, are not that great from a security and usability point of view. |
c2d0006
to
86b2fb4
Compare
Rebased on top of the latest changes in #19521 |
re-tACK 3b3e3d9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about the cs_main locking assumptions
src/rpc/blockchain.cpp
Outdated
NodeContext& node = EnsureAnyNodeContext(request.context); | ||
ChainstateManager& chainman = EnsureChainman(node); | ||
CChainState& active_chainstate = chainman.ActiveChainstate(); | ||
active_chainstate.ForceFlushStateToDisk(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty sure flushing and getting the cursor will need to happen in the same cs_main scope, otherwise the flush seems best-effort at best?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I would have described it as best effort so I think it can be omitted here as well. And this made me realize that we don't actually need coins_view and blockman, so I adapted this as well.
else: | ||
file = "test.csv" | ||
|
||
path = os.path.join(get_datadir_path(self.options.tmpdir, 1), file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably time to introduce a helper and use that:
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 7d2db391b6..aa9ff9831f 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -381,9 +381,13 @@ class TestNode():
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT):
wait_until_helper(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor)
+ @property
+ def datadir_path(self) -> Path:
+ return Path(self.datadir)
+
@property
def chain_path(self) -> Path:
- return Path(self.datadir) / self.chain
+ return self.datadir_path / self.chain
@property
def debug_log_path(self) -> Path:
path = os.path.join(get_datadir_path(self.options.tmpdir, 1), file) | |
path = self.nodes[1].datadir_path / file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the helper but using the /
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but using the / operator would mean file would also need to be a Path object iiuc
Does it? (It doesn't for me locally)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
2022-06-11T16:28:24.998000Z TestFramework (INFO): Test dumpcoinstats RPC
2022-06-11T16:28:24.998000Z TestFramework (ERROR): Unexpected exception caught during testing
Traceback (most recent call last):
File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/test_framework.py", line 133, in main
self.run_test()
File "/Users/FJ/projects/clones/bitcoin/test/functional/feature_coinstatsindex.py", line 58, in run_test
self._test_dumpcoinstats_rpc()
File "/Users/FJ/projects/clones/bitcoin/test/functional/feature_coinstatsindex.py", line 319, in _test_dumpcoinstats_rpc
self.nodes[1].dumpcoinstats(path, cumulative)
File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/coverage.py", line 49, in __call__
return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/authproxy.py", line 141, in __call__
postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii)
File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/authproxy.py", line 131, in get_request
json.dumps(args or argsn, default=EncodeDecimal, ensure_ascii=self.ensure_ascii),
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
return cls(
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
chunks = self.iterencode(o, _one_shot=True)
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
return _iterencode(o, 0)
File "/Users/FJ/projects/clones/bitcoin/test/functional/test_framework/authproxy.py", line 66, in EncodeDecimal
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: PosixPath('/var/folders/9z/n7rz_6cj3bq__11k5kcrsvvm0000gn/T/bitcoin_func_test_ru27w0qw/node1/ctest.csv') is not JSON serializable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could do str(path)
before passing it as json?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that works too and I just pushed a version with that. But I find it a little less elegant overall.
c8b5d0a
to
42763a2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
Diff
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 74877d7da..7bb6e2927 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1039,16 +1039,21 @@ static RPCHelpMan dumpcoinstats()
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");
}
+ if (!g_coin_stats_index) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Coinstats index is not enabled");
+ }
+
+ const auto summary{g_coin_stats_index->GetSummary()};
+ if (!summary.synced) {
+ throw JSONRPCError(RPC_MISC_ERROR, strprintf("coinstatsindex is not synced. Current best block height: %d", summary.best_block_height));
+ }
+
std::ofstream file;
file.open(filepath);
if (!file.is_open()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open dump file");
}
- if (!g_coin_stats_index) {
- throw JSONRPCError(RPC_MISC_ERROR, "Coinstats index is not enabled");
- }
-
const bool cumulative{request.params[1].isNull() ? false : request.params[1].get_bool()};
file << strprintf("height,"
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. |
Thanks @fanquake , lightning speed :) |
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?
|
There doesn't seem to be much interest in this feature currently. If someone needs it, feel free to ping me here and I will reorg/rebase for you. |
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.