diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1845,6 +1845,22 @@ return ret; } +UniValue savemempool(const Config &config, const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 0) { + throw std::runtime_error("savemempool\n" + "\nDumps the mempool to disk.\n" + "\nExamples:\n" + + HelpExampleCli("savemempool", "") + + HelpExampleRpc("savemempool", "")); + } + + if (!DumpMempool()) { + throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); + } + + return NullUniValue; +} + // clang-format off static const ContextFreeRPCCommand commands[] = { // category name actor (function) argNames @@ -1866,6 +1882,7 @@ { "blockchain", "gettxout", gettxout, {"txid","n","include_mempool"} }, { "blockchain", "gettxoutsetinfo", gettxoutsetinfo, {} }, { "blockchain", "pruneblockchain", pruneblockchain, {"height"} }, + { "blockchain", "savemempool", savemempool, {} }, { "blockchain", "verifychain", verifychain, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", preciousblock, {"blockhash"} }, diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -719,7 +719,7 @@ CBlockFileInfo *GetBlockFileInfo(size_t n); /** Dump the mempool to disk. */ -void DumpMempool(); +bool DumpMempool(); /** Load the mempool from disk. */ bool LoadMempool(const Config &config); diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5639,7 +5639,7 @@ return true; } -void DumpMempool(void) { +bool DumpMempool(void) { int64_t start = GetTimeMicros(); std::map mapDeltas; @@ -5659,7 +5659,7 @@ try { FILE *filestr = fsbridge::fopen(GetDataDir() / "mempool.dat.new", "wb"); if (!filestr) { - return; + return false; } CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); @@ -5685,7 +5685,9 @@ (mid - start) * MICRO, (last - mid) * MICRO); } catch (const std::exception &e) { LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what()); + return false; } + return true; } //! Guess how far we are in the verification process at the given block index diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -28,13 +28,23 @@ - Restart node0 with -persistmempool. Verify that it has 5 transactions in its mempool. This tests that -persistmempool=0 does not overwrite a previously valid mempool stored on disk. + - Remove node0 mempool.dat and verify savemempool RPC recreates it + and verify that node1 can load it and has 5 transaction in its + mempool. + - Verify that savemempool throws when the RPC is called if + node1 can't write to disk. """ from decimal import Decimal +import os import time from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, wait_until +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + wait_until +) class MempoolPersistTest(BitcoinTestFramework): @@ -85,6 +95,34 @@ self.start_node(0) wait_until(lambda: len(self.nodes[0].getrawmempool()) == 5) + mempooldat0 = os.path.join( + self.options.tmpdir, 'node0', 'regtest', 'mempool.dat') + mempooldat1 = os.path.join( + self.options.tmpdir, 'node1', 'regtest', 'mempool.dat') + self.log.debug( + "Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it") + os.remove(mempooldat0) + self.nodes[0].savemempool() + assert os.path.isfile(mempooldat0) + + self.log.debug( + "Stop nodes, make node1 use mempool.dat from node0. Verify it has 5 transactions") + os.rename(mempooldat0, mempooldat1) + self.stop_nodes() + self.start_node(1, extra_args=[]) + wait_until(lambda: len(self.nodes[1].getrawmempool()) == 5) + + self.log.debug( + "Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") + # to test the exception we are setting bad permissions on a tmp file called mempool.dat.new + # which is an implementation detail that could change and break this test + mempooldotnew1 = mempooldat1 + '.new' + with os.fdopen(os.open(mempooldotnew1, os.O_CREAT, 0o000), 'w'): + pass + assert_raises_rpc_error(-1, "Unable to dump mempool to disk", + self.nodes[1].savemempool) + os.remove(mempooldotnew1) + if __name__ == '__main__': MempoolPersistTest().main()