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.
1582@@ -1583,14 +1583,14 @@ UniValue savemempool(const JSONRPCRequest& request)
1583 if (request.fHelp || request.params.size() != 0) {
1584 throw std::runtime_error(
1585 "savemempool\n"
1586- "\nDumps the mempool to disk.\n"
1587+ "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n"
-persistmempool
is false, yes? Might want to add that to RPC notes, or set it to true always if persist is false.
-persistmempool
is false.
118+ self.start_node(0)
119+ self.nodes[0].generate(12)
120+ for i in range(NUM_TXS_IN_MEMPOOL):
121+ self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("0.00001"))
122+ self.stop_nodes()
123+ self.start_node(0)
1582@@ -1583,14 +1583,14 @@ UniValue savemempool(const JSONRPCRequest& request)
1583 if (request.fHelp || request.params.size() != 0) {
1584 throw std::runtime_error(
1585 "savemempool\n"
1586- "\nDumps the mempool to disk.\n"
1587+ "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded or when -persistmempool=0.\n"
nopersistmempool
678@@ -679,6 +679,7 @@ void ThreadImport(std::vector<fs::path> vImportFiles)
679 } // End scope of CImportingNow
680 if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
681 LoadMempool();
682+ g_is_mempool_loaded = true;
226@@ -227,6 +227,7 @@ CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
227
228 CBlockPolicyEstimator feeEstimator;
229 CTxMemPool mempool(&feeEstimator);
230+std::atomic_bool g_is_mempool_loaded(false);
g_is_mempool_loaded{false}
for compile-time narrowing check.
680@@ -681,6 +681,7 @@ void ThreadImport(std::vector<fs::path> vImportFiles)
681 LoadMempool();
682 fDumpMempoolLater = !fRequestShutdown;
683 }
684+ g_is_mempool_loaded = true;
1590 + HelpExampleRpc("savemempool", "")
1591 );
1592 }
1593
1594- if (!DumpMempool()) {
1595+ if (!g_is_mempool_loaded || !DumpMempool()) {
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
.
Concept ACK.
BTW, Should the RPC server start after the mempool is loaded?
1589 + HelpExampleCli("savemempool", "")
1590 + HelpExampleRpc("savemempool", "")
1591 );
1592 }
1593
1594+ if (!g_is_mempool_loaded && gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
The code seems correct, but is just badly documented.
What about
0const bool is_supposed_to_load_mempool{gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)};
1if (!g_is_mempool_loaded && is_supposed_to_load_mempool) {
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.
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.
111@@ -112,5 +112,17 @@ def run_test(self):
112 assert_raises_rpc_error(-1, "Unable to dump mempool to disk", self.nodes[1].savemempool)
113 os.remove(mempooldotnew1)
114
115+ NUM_TXS_IN_MEMPOOL = 900
116+ self.log.debug("Send %d transactions from node0 (to its own address)" % NUM_TXS_IN_MEMPOOL)
117+ self.stop_nodes()
self.stop_nodes()
a few lines above to say self.stop_node(1)
116+ self.log.debug("Send %d transactions from node0 (to its own address)" % NUM_TXS_IN_MEMPOOL)
117+ self.stop_nodes()
118+ self.start_node(0)
119+ self.nodes[0].generate(12)
120+ for i in range(NUM_TXS_IN_MEMPOOL):
121+ self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("0.00001"))
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.
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
BTW, Should the RPC server start after the mempool is loaded?
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?
685@@ -687,8 +686,8 @@ void ThreadImport(std::vector<fs::path> vImportFiles)
686 } // End scope of CImportingNow
687 if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
688 LoadMempool();
689- fDumpMempoolLater = !fRequestShutdown;
690 }
691+ g_is_mempool_loaded = !fRequestShutdown;
StartShutdown()
calls we should return?
685@@ -687,8 +686,8 @@ void ThreadImport(std::vector<fs::path> vImportFiles)
686 } // End scope of CImportingNow
687 if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
688 LoadMempool();
IMO we should do this instead:
0g_is_mempool_loaded = LoadMempool();
because, for instance, in LoadMempool()
:
https://github.com/bitcoin/bitcoin/blob/67447ba06057b8e83f962c82491d2fe6c5211f50/src/validation.cpp#L4592-L4593
LoadMempool()
doesn’t lock the mempool and since the node starts concurrently it is possible to have new transactions in the mempool after LoadMempool()
finishes. If that’s the case (?) I guess it’s ok to discard them in this fast startup-shutdown case.
Tested ACK The mempool was not loaded yet
error.
I also think we should prevent concurrent savemempool
calls, currently the 2nd waits for the first to end, but IMO the 2nd should fail. Much like rescanblockchain
:
https://github.com/bitcoin/bitcoin/blob/67447ba06057b8e83f962c82491d2fe6c5211f50/src/wallet/rpcwallet.cpp#L3458-L3461
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.
without looking much at WalletRescanReserver::reserver() and trying to do things with atomic booleans in mempool instead.
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.
454@@ -452,6 +455,7 @@ class CTxMemPool
455 public:
456
457 static const int ROLLING_FEE_HALFLIFE = 60 * 60 * 12; // public only for testing
458+ static const uint64_t MEMPOOL_DUMP_VERSION = 1;
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).