-
Notifications
You must be signed in to change notification settings - Fork 37.6k
Bugfix: RPC: savemempool: Don't save until LoadMempool() is finished #12172
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
Conversation
6a52e42
to
e2b17a5
Compare
@@ -1583,14 +1583,14 @@ UniValue savemempool(const JSONRPCRequest& request) | |||
if (request.fHelp || request.params.size() != 0) { | |||
throw std::runtime_error( | |||
"savemempool\n" | |||
"\nDumps the mempool to disk.\n" | |||
"\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n" |
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.
this also cannot complete when -persistmempool
is false, yes? Might want to add that to RPC notes, or set it to true always if persist is false.
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 think it's probably important that this works if -persistmempool
is false.
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.
Indeed, definitely needs to work with -persistmempool false, that's the whole point of the savemempool command. AFAICT the current implementation does work in that case.
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 agree it's best it works in that case, just didn't thought about it initially. But for the record, I'm using savemempool with -persistmempool to true, because I want to read it on restart too!
I think the whole point of savemempool is to cover use cases where for whatever reason (in my case, an insufficient knowledge of docker-compose) you can't or don't always let the daemon exit gracefully.
test/functional/mempool_persist.py
Outdated
for i in range(NUM_TXS_IN_MEMPOOL): | ||
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("0.00001")) | ||
self.stop_nodes() | ||
self.start_node(0) |
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.
is this test essentially racing the mempool acceptance?
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.
stopping the node the mempool is dumped and starting the node, LoadMempool gets called again, hopefully with enough work ahead to catch the error when calling savemempool on the next line.
e2b17a5
to
4b30711
Compare
src/rpc/blockchain.cpp
Outdated
@@ -1583,14 +1583,14 @@ UniValue savemempool(const JSONRPCRequest& request) | |||
if (request.fHelp || request.params.size() != 0) { | |||
throw std::runtime_error( | |||
"savemempool\n" | |||
"\nDumps the mempool to disk.\n" | |||
"\nDumps the mempool to disk. It will fail until the previous dump is fully loaded or when -persistmempool=0.\n" |
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 don't see a reason to make it fail when nopersistmempool
src/init.cpp
Outdated
@@ -679,6 +679,7 @@ void ThreadImport(std::vector<fs::path> vImportFiles) | |||
} // End scope of CImportingNow | |||
if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { | |||
LoadMempool(); | |||
g_is_mempool_loaded = true; |
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.
Move down two lines to break out of this scope?
4b30711
to
d494001
Compare
Modified so that savemempool keeps working when -persistmempool=0 as discussed. |
src/validation.cpp
Outdated
@@ -227,6 +227,7 @@ CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE; | |||
|
|||
CBlockPolicyEstimator feeEstimator; | |||
CTxMemPool mempool(&feeEstimator); | |||
std::atomic_bool g_is_mempool_loaded(false); |
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.
style-nit: Might use g_is_mempool_loaded{false}
for compile-time narrowing check.
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.
Fixed
d494001
to
e685d0b
Compare
src/init.cpp
Outdated
@@ -681,6 +681,7 @@ void ThreadImport(std::vector<fs::path> vImportFiles) | |||
LoadMempool(); | |||
fDumpMempoolLater = !fRequestShutdown; | |||
} | |||
g_is_mempool_loaded = true; |
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.
It would be nice to drop fDumpMempoolLater and only have one global for "mempool was loaded". Should be able to set g_is_mempool_loaded to !fRequestShutdown here, and then check -persistmempool before flushing during shutdown to avoid changing behavior without adding another boolean.
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.
Perhaps I'm not understanding the code, but what I assumed is that fDumpMempoolLater is set to !fRequestShutdown instead of true to avoid more than one thread dumping the mempool. But in this case we want all threads to work with savemempool once the mempool is loaded.
Am I confused about this or missing something?
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.
There is only one shutdown thread, not sure what you mean "more than one thread dumping the mempool". !fRequestShutdown is a check to make sure LoadMempool() didn't quit early, but its also safe to re-use that in RPC as if fRequestShutdown is set we'll be shutting down soon anyway, so RPCs can fail.
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.
Alright, I wasn't understanding why !fRequestShutdown was set, it seems we can have a single boolean then and drop fDumpMempoolLater.
src/rpc/blockchain.cpp
Outdated
"\nExamples:\n" | ||
+ HelpExampleCli("savemempool", "") | ||
+ HelpExampleRpc("savemempool", "") | ||
); | ||
} | ||
|
||
if (!DumpMempool()) { | ||
if (!g_is_mempool_loaded || !DumpMempool()) { |
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.
Would be nice to give a different error message here.
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.
Agree, with the corresponding test.
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.
Should the g_is_mempool_loaded
check be inside of DumpMempool
? No need to see g_is_mempool_loaded
in the RPC code.
Note that with this change there is no need for a functional test, just a c++ test DumpMempool
.
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.
Well, if I put it inside DumpMempool() then I can't have the different meesage, right?
I don't have a strong opinion about putting it inside and removing the functional test, I would like to hear what others think before doing it. I'll change the error message for now.
@@ -1583,14 +1583,14 @@ UniValue savemempool(const JSONRPCRequest& request) | |||
if (request.fHelp || request.params.size() != 0) { | |||
throw std::runtime_error( | |||
"savemempool\n" | |||
"\nDumps the mempool to disk.\n" | |||
"\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n" |
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.
Indeed, definitely needs to work with -persistmempool false, that's the whole point of the savemempool command. AFAICT the current implementation does work in that case.
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.
Concept ACK.
BTW, Should the RPC server start after the mempool is loaded?
src/rpc/blockchain.cpp
Outdated
"\nExamples:\n" | ||
+ HelpExampleCli("savemempool", "") | ||
+ HelpExampleRpc("savemempool", "") | ||
); | ||
} | ||
|
||
if (!DumpMempool()) { | ||
if (!g_is_mempool_loaded || !DumpMempool()) { |
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.
Agree, with the corresponding test.
src/rpc/blockchain.cpp
Outdated
"\nExamples:\n" | ||
+ HelpExampleCli("savemempool", "") | ||
+ HelpExampleRpc("savemempool", "") | ||
); | ||
} | ||
|
||
if (!DumpMempool()) { | ||
if (!g_is_mempool_loaded || !DumpMempool()) { |
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.
Should the g_is_mempool_loaded
check be inside of DumpMempool
? No need to see g_is_mempool_loaded
in the RPC code.
Note that with this change there is no need for a functional test, just a c++ test DumpMempool
.
e685d0b
to
4e9f522
Compare
I guess starting the rpc server only after the mempool is loaded is another solution, but I'm not sure it is better. |
src/rpc/blockchain.cpp
Outdated
"\nExamples:\n" | ||
+ HelpExampleCli("savemempool", "") | ||
+ HelpExampleRpc("savemempool", "") | ||
); | ||
} | ||
|
||
if (!g_is_mempool_loaded && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { |
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.
Why is there still an argument check here? The setting of g_is_mempool_loaded appears to me to be unconditional now.
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.
The code seems correct, but is just badly documented.
What about
const bool is_supposed_to_load_mempool{gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)};
if (!g_is_mempool_loaded && is_supposed_to_load_mempool) {
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.
Well, it is now not needed since the g_is_mempool_loaded is set out of the if gArgs.GetArg("-persistmempool"...
. I was thinking that perhaps we should allow to dump even before g_is_mempool_loaded is set if -persistmempool=0 (since in that case it's not loading it anyway).
But perhaps it's better that it waits for that line to be run when -persistmempool=0 too.
Or perhaps the we can set g_is_mempool_loaded only conditionally like fDumpMempoolLater was.
No strong opinion, I'm happy with any of the possible variations on this. But I don't think it's a matter of documentation. It's just choosing what we want here.
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.
Why would there be anything in mempool, when -persistmempool=0 and you are not yet connected to any peers? So there wouldn't be any need to allow savemempool
.
We are mostly nitpicking here, and I don't care too much what variation you pick, but the current code seems confusion/hard to read, otherwise we woulnd't have this conversation.
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.
Alright, I'll just remove the gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL) condition from here and leave it as it was right before.
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.
Fixed.
4e9f522
to
7333159
Compare
utACK 5b80af6322036c141ffce8fb7f10f5d40b8bc0f0 (didn't look at tests) |
test/functional/mempool_persist.py
Outdated
@@ -112,5 +112,17 @@ def run_test(self): | |||
assert_raises_rpc_error(-1, "Unable to dump mempool to disk", self.nodes[1].savemempool) | |||
os.remove(mempooldotnew1) | |||
|
|||
NUM_TXS_IN_MEMPOOL = 900 | |||
self.log.debug("Send %d transactions from node0 (to its own address)" % NUM_TXS_IN_MEMPOOL) | |||
self.stop_nodes() |
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.
A comment explaining why this is needed?
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 guess perhaps it is not needed...
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.
Please remove it, when it is not needed. Starting and stopping nodes is where a ton of time is wasted in the test framework.
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 assume you can modify the self.stop_nodes()
a few lines above to say self.stop_node(1)
test/functional/mempool_persist.py
Outdated
self.start_node(0) | ||
self.nodes[0].generate(12) | ||
for i in range(NUM_TXS_IN_MEMPOOL): | ||
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("0.00001")) |
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.
How will this not create too long mempool chains?
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'm not sure I understand the question. What's the problem?
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.
Currently the mempool only accepts a chain of up to 26 unconfirmed transactions, since coin selection is not deterministic, I assume we will run into travis failures at some point.
Also, IIRC the wallet is extremely slow with coin selection, when a lot of the coins are unconfirmed change. There might be some helper functions in the test framework that can create a ton of spam transactions.
Test takes 7-8 Minutes on travis. Should be fixed before merge. |
Test is failing for me locally. You might as well leave out the test and ask for some Tested ACKs instead. |
@MarcoFalke Sure, I'm happy to leave out the test if I get some tested acks. It's an edge case anyway and seems expensive to test this way, filling the mempool enough so that the loading takes long enough to get the error (plus that varies with the machine and machine load [this seems close to the minimum in my machine but it's not enough on yours and it's too much on travis]). |
7333159
to
31c1565
Compare
Needed rebase |
I am guessing you wouldn't have to rebase if you just dropped the test commit. Since I am going to NACK on merging the test commit for the reasons provided above, you might as well remove it. |
Yes, I'm also for removing it. I was just waiting for more people to agree with us and some tested ACKs as you said. EDIT: but, no, it needed rebase and not for the tests, new code close to the one I was changing was changed in https://github.com/bitcoin/bitcoin/pull/12266/files#diff-c865a8939105e6350a50af02766291b7R204 |
Seems to me this issue is not just about saving mempool but also about any RPC that interacts with the mempool like fetching raw transactions and so on - thus I also think it may be a good idea to not start the rpc server until the service is up and loaded the mempool - am I missing something? |
Rebased and added several commits inspired by @promag 's last suggestion but without looking much at WalletRescanReserver::reserver() and trying to do things with atomic booleans in mempool instead. We can squash, leave for later PRs or reject forever each of the newly proposed commits. I wish I could go as far as s/LoadMempool(/mempool.Load(/ and s/DumpMempool(/mempool.Dump(/ , moving code from validation.cpp to txmempool.cpp, but at some point we need to just fix #12142 and leave later improvements for later PRs. |
1a21663
to
14c00f1
Compare
Any reason to do differently? Not sure about representing the state with 2 atomic booleans. Will review later. |
@promag Yeah, sorry for pushing a half-baked thought. Please, let me come back to this again before you review it, perhaps I can convince myself it doesn't make sense and I save you some time. EDIT: Or perhaps we can just leave it as it previously was and leave your request for a following PR, but I think it makes a lot of sense and it shouldn't be that hard if I hadn't tried to simplify the wheel. |
src/txmempool.h
Outdated
@@ -452,6 +455,7 @@ class CTxMemPool | |||
public: | |||
|
|||
static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12; // public only for testing | |||
static const uint64_t MEMPOOL_DUMP_VERSION = 1; |
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.
Please remove unrelated move of variables from a bug fix pull request
What is the status of this? Seems stale and can be closed? |
I'm sorry @MarcoFalke IIRC the status is that the first commit has been around for long and tested by myself many times (but I'm trusting my rebases on the same ting now), but then after @promag 's review and test (he gave a tested ack for the first commit), I got very excited about a suggestion he made to upgrade this PR to fix more related things. I think he is totally right in his suggestion of apart from not allowing saving the mempool until it has been loaded, also just return an error as suggested when you call the rpc to save the mempool and it's already in the process of saving. But instead of listening to @promag and copy a part of the codebase that already works for the same king of thing, I tried to re-invent the wheel because I thought I could implement it simpler and I failed so far. I suggest that we merge the first commit which is very simple and even has had tests in the past (until people asked them to be removed for being slow and non-deterministic) and also been tested manually (not very hard to test manually, btw). Then someone (ehem, promag) could create another so that no thread dares to save the mempool while already saving it. Please ping me to test that second part, since it's as easy to test manually as this one, As a user that never gives more the daemon more than 60 secs to gracefully exit and thus depends on this rpc call to persist the mempool, I can just always wait longer for the first save, I can at most miss mempool data while re-deploying too often. Being prevented from calling savemempool twice is I think a cool improvement but not that much for users that are calling savemempool with a cron process like me (and I imagine, most savemempool users). |
I'm fine with @jtimon suggestion to move the fix forward. I can then submit a PR to improve concurrency. |
f2c13eb
to
cb1e319
Compare
I'm assuming an agreement from @MarcoFalke 's lack of response. |
utACK cb1e319 |
…) is finished cb1e319 Bugfix: RPC: savemempool: Don't save until LoadMempool() is finished (Jorge Timón) Pull request description: Fixes #12142 The tests are a little bit slow, mempool_persist.py goes from about 20 s to about 120 s in my hardware. Perhaps there's a better way to test this. Tree-SHA512: 9e6c24b32a9cf3774e8f0bd81c035b0deb53fba5ac3eb2532d85900579d21cef8a1135b75a4fa0a9d883e3822eb35e7d4b47a0838abf99789039205041962629
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of #12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
…empool() is finished cb1e319 Bugfix: RPC: savemempool: Don't save until LoadMempool() is finished (Jorge Timón) Pull request description: Fixes bitcoin#12142 The tests are a little bit slow, mempool_persist.py goes from about 20 s to about 120 s in my hardware. Perhaps there's a better way to test this. Tree-SHA512: 9e6c24b32a9cf3774e8f0bd81c035b0deb53fba5ac3eb2532d85900579d21cef8a1135b75a4fa0a9d883e3822eb35e7d4b47a0838abf99789039205041962629
…empool() is finished cb1e319 Bugfix: RPC: savemempool: Don't save until LoadMempool() is finished (Jorge Timón) Pull request description: Fixes bitcoin#12142 The tests are a little bit slow, mempool_persist.py goes from about 20 s to about 120 s in my hardware. Perhaps there's a better way to test this. Tree-SHA512: 9e6c24b32a9cf3774e8f0bd81c035b0deb53fba5ac3eb2532d85900579d21cef8a1135b75a4fa0a9d883e3822eb35e7d4b47a0838abf99789039205041962629
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of bitcoin#12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of bitcoin#12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of bitcoin#12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of bitcoin#12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of bitcoin#12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of bitcoin#12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
585b47c rpc: Prevent concurrent savemempool (João Barbosa) Pull request description: Follow up of bitcoin#12172, this change prevents calling `savemempool` RPC concurrently. Tree-SHA512: 4759a7107658a9794f5c6ab7e3e3002276abadd64996010be67a2791914d284db6fe0377c071a8d6a42387bfb0178f219b73aeec077ce5c4fe5c634a30b3e081
Fixes #12142
The tests are a little bit slow, mempool_persist.py goes from about 20 s to about 120 s in my hardware.
Perhaps there's a better way to test this.