Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
[RPC][mempool]: Add savemempool RPC #11099
Conversation
promag
reviewed
Aug 20, 2017
Please add a test.
The function can be changed to know if it failed or not.
| + if (request.fHelp || request.params.size() > 0) | ||
| + throw std::runtime_error( | ||
| + "dumpmempool\n" | ||
| + "\nDumps to disk the mempool (mempool.dat).\n" |
| + ); | ||
| + | ||
| + DumpMempool(); | ||
| + return "Mempool dumped"; |
|
Suggestion, reword |
fanquake
added
the
RPC/REST/ZMQ
label
Aug 20, 2017
greenaddress
changed the title from
[RPC][mempool]: add rpc command to dump the mempool to disk
to
[RPC][mempool]: Add dumpmempool RPC
Aug 21, 2017
|
@promag thanks, updated as for feedback and squashed |
promag
reviewed
Aug 21, 2017
I wonder if it should throw RPC_INTERNAL_ERROR, since DumpMempool handles it's errors.
Please split in two commits:
Add return value to DumpMempoolAdd dumpmempool RPC.
| + + HelpExampleRpc("dumpmempool", "") | ||
| + ); | ||
| + | ||
| + if (!DumpMempool()) |
| + | ||
| + if (!DumpMempool()) | ||
| + throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to dump mempool to disk"); | ||
| + return "Mempool dumped"; |
| @@ -1552,6 +1568,7 @@ static const CRPCCommand commands[] = | ||
| { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, {} }, | ||
| { "blockchain", "pruneblockchain", &pruneblockchain, true, {"height"} }, | ||
| { "blockchain", "verifychain", &verifychain, true, {"checklevel","nblocks"} }, | ||
| + { "blockchain", "dumpmempool", &dumpmempool, true, {} }, |
|
@promag thanks, updated as for feedback and split in two commits re: RPC_INTERNAL_ERROR - didn't immediately found something better, very open to suggestions Anything else you would add to the test? |
|
I meant maybe not throw, and have the bool in the response. |
|
I think the condition is exceptional and deserve a throw of an exception & consistent with the other rpcs (though I don't know if there's a more appropriate exception for it) |
|
I almost agree with that if it wasn't the fact that the function doesn't throw.. but I have no strong opinion on that. If the throw stays, then maybe |
| + if (request.fHelp || request.params.size() > 0) | ||
| + throw std::runtime_error( | ||
| + "dumpmempool\n" | ||
| + "\nDumps to disk the mempool.\n" |
jonasschnelli
Aug 22, 2017
Member
I'm not the one to complain about this: but should it be `"Dumps the mempool to disk"?
| + throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to dump mempool to disk"); | ||
| + } | ||
| + | ||
| + return "Mempool dumped"; |
jonasschnelli
Aug 22, 2017
Member
Because this is machine to machine communication, use true as ret val at this point?
|
thanks @jonasschnelli - good points added a commit (which i think should be squashed at some point together with "Add dumpmempool RPC") |
| + ); | ||
| + | ||
| + if (!DumpMempool()) { | ||
| + throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to dump mempool to disk"); |
promag
Aug 23, 2017
Contributor
Maybe RPC_MISC_ERROR. See what causes RPC_INTERNAL_ERROR, sounds like really bad exceptions.
|
utACK Maybe add an rational (possible use-cases) to the PR description. |
|
@promag updated the exception, thanks @jonasschnelli updated the description. Please note your utACK points to a commit from @TheBlueMatt. GitHub also tells me there's some changes required, should I ignore that? |
| + | ||
| + def run_test(self): | ||
| + self.log.debug("Verify mempool file doesn't exists") | ||
| + mempooldat = self.options.tmpdir + "/node0/regtest/mempool.dat" |
MarcoFalke
Aug 23, 2017
Member
Use os.path.join, so that it works on other operating systems as well.
| + def run_test(self): | ||
| + self.log.debug("Verify mempool file doesn't exists") | ||
| + mempooldat = self.options.tmpdir + "/node0/regtest/mempool.dat" | ||
| + assert_raises(OSError, lambda: os.remove(mempooldat)) |
| + mempooldat = self.options.tmpdir + "/node0/regtest/mempool.dat" | ||
| + assert_raises(OSError, lambda: os.remove(mempooldat)) | ||
| + self.log.debug("Dump mempool to disk") | ||
| + assert_equal(self.nodes[0].dumpmempool(), True) |
| + self.log.debug("Dump mempool to disk") | ||
| + assert_equal(self.nodes[0].dumpmempool(), True) | ||
| + assert_equal(os.path.isfile(mempooldat), True) | ||
| + os.rename(self.options.tmpdir + "/node0/regtest", self.options.tmpdir + "/node0/regtest_bk") |
MarcoFalke
Aug 23, 2017
Member
I don't think we allow the datadir to be moved while running. The tests should not rely on this.
MarcoFalke
Aug 23, 2017
Member
You might be able to achieve the same error condition by changing the file permissions, though.
|
You might even combine the tests with the existing mempool persist test. |
|
@MarcoFalke I tried with permissions and didn't work. I couldn't find another way to trigger the error. Will address the other feedback now |
| } catch (const std::exception& e) { | ||
| LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); | ||
| } | ||
| + return false; |
jnewbery
Aug 23, 2017
Member
Not sure what the convention is, or what's best practice, but to me having the return false statement within the catch block is more obvious.
| + | ||
| +from test_framework.test_framework import BitcoinTestFramework | ||
| +from test_framework.util import * | ||
| +import os |
jnewbery
Aug 23, 2017
Member
nit: PEP8 import ordering please: std library imports first, then local project imports.
| + self.log.debug("Dump mempool to disk") | ||
| + assert self.nodes[0].dumpmempool() | ||
| + assert os.path.isfile(mempooldat) | ||
| + moveddir = os.path.join(self.options.tmpdir, 'node0', 'regtest_bk') |
jnewbery
Aug 23, 2017
Member
I'm not sure if this failure test is necessary. If bitcoind can't write to its data directory, I'm sure all kinds of bad things happen.
greenaddress
Aug 23, 2017
Contributor
I think it makes sense to check that there is no file, you dump and there is a file.
MarcoFalke
Aug 23, 2017
Member
I think it makes sense to check that there is no file, you dump and there is a file.
You do exactly this in the lines above. The comment is about assert_raises_jsonrpc, which raises due to the missing datadir. As mentioned earlier, this will likely introduce hard to reproduce test failures now or in the future.
With the current flakiness of the test framework in mind I am against adding such a failure test.
MarcoFalke
Aug 23, 2017
Member
What you might try instead is to copy the dumped mempool from node0 to node1 and check that it can be loaded. (The nodes are not connected, but share the same blockchain)
Anyway, I just noticed you are using a clean blockchain and dump the empty mempool. Might be better to populate it a bit.
greenaddress
Aug 24, 2017
Contributor
@MarcoFalke I made that change (about to push commit) and moved the test to mempool_persist as advised - however your suggestion still doesn't test the exception so I left that in, can easily remove the last 4 lines of the test if other agrees is better to not have it.
I think either is fine. bitcoind should always be able to write to its data directory, so I would say failure to do so counts as an internal error.
I also don't like the test framework messing with bitcoind's data directory. I don't think the failure testcase is necessary. If bitcoind can't write to its datadirectory, there are more pressing issues than the RPC raising the correct error. Agree with @MarcoFalke that it might be a good idea to combine this with the mempool_persist.py test (if it can be done tidily). |
|
@jnewbery thanks, addressed some feedback. Re: testing I also don't like messing with the datadir but unless someone has better suggestions the options are keeping it or removing it. |
I vote for removing it |
@jnewbery |
|
@MarcoFalke I've merged the tests and added your suggestion "copy the dumped mempool from node0 to node1 and check that it can be loaded. (The nodes are not connected, but share the same blockchain)" I left in the exception test for now. |
|
Updated slightly the tests only: removed the ugly rename of the node directory in favor of using permissions to make it fail in the test @MarcoFalke Permissions initially didn't work because there was a rename involved from a tmp file but it works if I use permissions on the tmp file that gets renamed ('mempool.dat.new') so switched to that I also removed from the test some redundant asserts re: os.remove doing its job (it already throws if it can't find a file or fails to remove it) |
|
utACK d0bfa6a @jonasschnelli Have your concerns been addressed? |
jnewbery
reviewed
Aug 28, 2017
I much, much prefer using file permissions on the mempool.dat file instead of deleting directories.
A few nits in the testcase, but otherwise looks great. Thanks for indulging all of my review comments :)
| @@ -85,5 +91,29 @@ def run_test(self): | ||
| self.nodes.append(self.start_node(0, self.options.tmpdir)) | ||
| wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5) | ||
| + regtestdir0 = os.path.join(self.options.tmpdir, 'node0', 'regtest') |
jnewbery
Aug 28, 2017
Member
Intermediate regtestdir0 variable not required. os.path.join() can take any number of arguments.
Same for regtestdir1 below.
| + mempooldat0 = os.path.join(regtestdir0, 'mempool.dat') | ||
| + regtestdir1 = os.path.join(self.options.tmpdir, 'node1', 'regtest') | ||
| + mempooldat1 = os.path.join(regtestdir1, 'mempool.dat') | ||
| + self.log.debug("Remove the mempool.dat file. Verify that dumpmempool to disk via RPC re-creates it") |
jnewbery
Aug 28, 2017
Member
Supernit: I like logging in the test script to be 'info' level, so when running the test locally, progress can be seen on the console by default.
greenaddress
Aug 29, 2017
Contributor
All the other logs in the test are debug. Either we change them all or I don't think it makes sense for me to just change mine.
| + os.rename(mempooldat0, mempooldat1) | ||
| + self.nodes.append(self.start_node(1, self.options.tmpdir)) | ||
| + # Give bitcoind a second to reload the mempool | ||
| + time.sleep(1) |
greenaddress
Aug 29, 2017
Contributor
I thought so too (and indeed it works without time.sleep()) - however the test has that as a pattern and I think we either remove it for the rest of the calls (at least within the same test) or it's inconsistent.
jnewbery
Aug 29, 2017
Member
I'm all for trying to match local style, but there's no benefit in copying bad patterns.
The time.sleep in L76 should be removed (and the assert_equal() in L78 replaced with a wait_until()) in a different PR.
The time.sleep in L85 serves a purpose - the mempool takes a while to load so we want to give it time before asserting it's empty. There's probably a better way of testing this, but we can't just replace the assert_equal() with a wait_until().
|
@jnewbery thanks! Keep in mind we are not doing permissions on the mempool.dat file as that doesn't work (the dump gets done on a tmp file which then replace the mempool.dat and ignores permissions so we are acting on the tmp file instead) |
Ah. Very crafty! Can you add a comment to the test to explain that ( |
|
@jnewbery I added the comment and removed the sleep. |
|
New commits look great. Can you squash? |
|
@jnewbery done, didn't rebase however happy to if needed |
deno49
commented
Aug 31, 2017
|
strictly q
…
|
| + assert self.nodes[0].dumpmempool() | ||
| + assert os.path.isfile(mempooldat0) | ||
| + | ||
| + self.log.debug("Stop nodes, make node1 use mempool.dat from node0. Verify it has 5 transactions") |
jnewbery
Sep 1, 2017
Member
I think the intent of this section of the tests is to verify that the dumped mempool from the dumpmempool RPC contains all the transactions. Correct? The way it's written right now doesn't test this. The act of stopping node0 causes it to dump its mempool on shutdown, and hence this test is just a repeat of the test on line 77. I think you need to:
- run
dumpmempoolon a running node - move that mempool.dat to a temp location
- shutdown the node
- move the mempool.dat back to the correct location
- start the node
- check that its mempool contains the correct transactions
| + self.nodes.append(self.start_node(1, self.options.tmpdir)) | ||
| + wait_until(lambda: len(self.nodes[1].getrawmempool()) == 5) | ||
| + | ||
| + self.log.debug("Force core to fail to save file to disk. Check it errors.") |
jnewbery
Sep 1, 2017
Member
nit: debug message could be clearer. Perhaps:
"Prevent bitcoind from writing mempool.dat to disk. Verify that `dumpmempool` fails"
|
Needed slight rebase changes after #11121 (apologies - I keep making changes to the test_framework API). I've done that and pushed here: https://github.com/jnewbery/bitcoin/tree/pr11099 . Feel free to reset your branch onto that commit. |
|
@jnewbery thank you! I've updated the PR with your rebase, no need to apologize especially as you did all the work :) |
promag
reviewed
Sep 1, 2017
There is only one test for dumpmempool failure, which is the can't write to mempool.dat.new. However there is another return false; inside the catch (which I can't understand why) that should be tested.
Also, RenameOver() doesn't throw, and IMO we should fail if the mempool.dat can't be updated.
| @@ -1532,9 +1532,28 @@ UniValue getchaintxstats(const JSONRPCRequest& request) | ||
| return ret; | ||
| } | ||
| +UniValue dumpmempool(const JSONRPCRequest& request) | ||
| +{ | ||
| + if (request.fHelp || request.params.size() != 0) |
|
utACK 67d307f |
|
Suggest renaming it to The name |
added a commit
to bitcoinknots/bitcoin
that referenced
this pull request
Sep 2, 2017
added a commit
to bitcoinknots/bitcoin
that referenced
this pull request
Sep 2, 2017
greenaddress
changed the title from
[RPC][mempool]: Add dumpmempool RPC
to
[RPC][mempool]: Add savemempool RPC
Sep 2, 2017
added a commit
to bitcoinknots/bitcoin
that referenced
this pull request
Sep 3, 2017
|
utACK 8ef87d2 |
|
@greenaddress Will review after rebase |
| + throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); | ||
| + } | ||
| + | ||
| + return true; |
MarcoFalke
Sep 5, 2017
Member
This return value is not documented. And I think we shouldn't return anything on this rpc.
promag
Sep 5, 2017
Contributor
Right, since on failure an exception is raised, on success it can be void.
jnewbery
Sep 6, 2017
Member
Yes, it appears that many RPCs that perform an action which is expected to succeed return NullUniValue on success. See several of the methods in src/rpc/net.cpp for example.
| + throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); | ||
| + } | ||
| + | ||
| + return true; |
promag
Sep 5, 2017
Contributor
Right, since on failure an exception is raised, on success it can be void.
| os.remove(mempooldat0) | ||
| - assert self.nodes[0].dumpmempool() | ||
| + assert self.nodes[0].savemempool() |
added a commit
to bitcoinknots/bitcoin
that referenced
this pull request
Sep 5, 2017
greenaddress
added some commits
Aug 21, 2017
|
rebased & squashed and reworded the second commit to the new @promag @jnewbery @MarcoFalke I changed the return value to NullUniValue (and accordingly updated the test) |
|
utACK 1aa97ee. |
|
utACK 1aa97ee |
|
@greenaddress Thanks for sticking with the pull. Quite a pain to go through so much review iterations. |
MarcoFalke
merged commit 1aa97ee
into
bitcoin:master
Sep 6, 2017
1 check passed
added a commit
that referenced
this pull request
Sep 6, 2017
You mean fun? |
greenaddress commentedAug 20, 2017
•
Edited 1 time
-
greenaddress
Aug 21, 2017
Adds a simple parameterless rpc command to dump the mempool.
Rationale:
Sometimes there can be a crash for whatever reason (bug, power loss, etc) causing the mempool.dat file to not be saved.
This change allows to script/cron the rpc call to have more regular saves to the file as well as cli/ad-hoc.
This should solve issue #11086