Skip to content

Commit cd43a84

Browse files
committed
Merge bitcoin/bitcoin#27460: rpc: Add importmempool RPC
fa776e6 Add importmempool RPC (MarcoFalke) fa20d73 refactor: Add and use kernel::ImportMempoolOptions (MarcoFalke) fa88669 doc: Clarify the getmempoolinfo.loaded RPC field documentation (MarcoFalke) 6888886 Remove Chainstate::LoadMempool (MarcoFalke) Pull request description: Currently it is possible to import a mempool by placing it in the datadir and starting the node. However this has many issues: * Users aren't expected to fiddle with the datadir, possibly corrupting it * An existing mempool file in the datadir may be overwritten * The node needs to be restarted * Importing an untrusted file this way is dangerous, because it can corrupt the mempool Fix all issues by adding a new RPC. ACKs for top commit: ajtowns: utACK fa776e6 achow101: ACK fa776e6 glozow: reACK fa776e6 Tree-SHA512: fcb1a92d6460839283c546c47a2d930c363ac1013c4c50dc5215ddf9fe5e51921d23fe0abfae0a5a7631983cfc7e2fff3788b70f95937d0a989a203be4d67546
2 parents 80d70cb + fa776e6 commit cd43a84

File tree

11 files changed

+160
-32
lines changed

11 files changed

+160
-32
lines changed

src/init.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
#endif
117117

118118
using kernel::DumpMempool;
119+
using kernel::LoadMempool;
119120
using kernel::ValidationCacheSizes;
120121

121122
using node::ApplyArgsManOptions;
@@ -1676,7 +1677,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
16761677
return;
16771678
}
16781679
// Load mempool from disk
1679-
chainman.ActiveChainstate().LoadMempool(ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{});
1680+
if (auto* pool{chainman.ActiveChainstate().GetMempool()}) {
1681+
LoadMempool(*pool, ShouldPersistMempool(args) ? MempoolPath(args) : fs::path{}, chainman.ActiveChainstate(), {});
1682+
pool->SetLoadTried(!chainman.m_interrupt);
1683+
}
16801684
});
16811685

16821686
// Wait for genesis block to be processed

src/kernel/mempool_persist.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include <util/time.h>
2020
#include <validation.h>
2121

22-
#include <chrono>
2322
#include <cstdint>
2423
#include <cstdio>
2524
#include <exception>
@@ -37,11 +36,11 @@ namespace kernel {
3736

3837
static const uint64_t MEMPOOL_DUMP_VERSION = 1;
3938

40-
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, FopenFn mockable_fopen_function)
39+
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts)
4140
{
4241
if (load_path.empty()) return false;
4342

44-
FILE* filestr{mockable_fopen_function(load_path, "rb")};
43+
FILE* filestr{opts.mockable_fopen_function(load_path, "rb")};
4544
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
4645
if (file.IsNull()) {
4746
LogPrintf("Failed to open mempool file from disk. Continuing anyway.\n");
@@ -53,7 +52,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
5352
int64_t failed = 0;
5453
int64_t already_there = 0;
5554
int64_t unbroadcast = 0;
56-
auto now = NodeClock::now();
55+
const auto now{NodeClock::now()};
5756

5857
try {
5958
uint64_t version;
@@ -72,8 +71,12 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
7271
file >> nTime;
7372
file >> nFeeDelta;
7473

74+
if (opts.use_current_time) {
75+
nTime = TicksSinceEpoch<std::chrono::seconds>(now);
76+
}
77+
7578
CAmount amountdelta = nFeeDelta;
76-
if (amountdelta) {
79+
if (amountdelta && opts.apply_fee_delta_priority) {
7780
pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
7881
}
7982
if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_expiry)) {
@@ -101,17 +104,21 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
101104
std::map<uint256, CAmount> mapDeltas;
102105
file >> mapDeltas;
103106

104-
for (const auto& i : mapDeltas) {
105-
pool.PrioritiseTransaction(i.first, i.second);
107+
if (opts.apply_fee_delta_priority) {
108+
for (const auto& i : mapDeltas) {
109+
pool.PrioritiseTransaction(i.first, i.second);
110+
}
106111
}
107112

108113
std::set<uint256> unbroadcast_txids;
109114
file >> unbroadcast_txids;
110-
unbroadcast = unbroadcast_txids.size();
111-
for (const auto& txid : unbroadcast_txids) {
112-
// Ensure transactions were accepted to mempool then add to
113-
// unbroadcast set.
114-
if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid);
115+
if (opts.apply_unbroadcast_set) {
116+
unbroadcast = unbroadcast_txids.size();
117+
for (const auto& txid : unbroadcast_txids) {
118+
// Ensure transactions were accepted to mempool then add to
119+
// unbroadcast set.
120+
if (pool.get(txid) != nullptr) pool.AddUnbroadcastTx(txid);
121+
}
115122
}
116123
} catch (const std::exception& e) {
117124
LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what());

src/kernel/mempool_persist.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ class CTxMemPool;
1212

1313
namespace kernel {
1414

15-
/** Dump the mempool to disk. */
15+
/** Dump the mempool to a file. */
1616
bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path,
1717
fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen,
1818
bool skip_file_commit = false);
1919

20-
/** Load the mempool from disk. */
20+
struct ImportMempoolOptions {
21+
fsbridge::FopenFn mockable_fopen_function{fsbridge::fopen};
22+
bool use_current_time{false};
23+
bool apply_fee_delta_priority{true};
24+
bool apply_unbroadcast_set{true};
25+
};
26+
/** Import the file and attempt to add its contents to the mempool. */
2127
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path,
2228
Chainstate& active_chainstate,
23-
fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen);
29+
ImportMempoolOptions&& opts);
2430

2531
} // namespace kernel
2632

src/rpc/client.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
229229
{ "importaddress", 2, "rescan" },
230230
{ "importaddress", 3, "p2sh" },
231231
{ "importpubkey", 2, "rescan" },
232+
{ "importmempool", 1, "options" },
233+
{ "importmempool", 1, "apply_fee_delta_priority" },
234+
{ "importmempool", 1, "use_current_time" },
235+
{ "importmempool", 1, "apply_unbroadcast_set" },
232236
{ "importmulti", 0, "requests" },
233237
{ "importmulti", 1, "options" },
234238
{ "importmulti", 1, "rescan" },

src/rpc/mempool.cpp

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,7 @@ static RPCHelpMan getmempoolinfo()
696696
RPCResult{
697697
RPCResult::Type::OBJ, "", "",
698698
{
699-
{RPCResult::Type::BOOL, "loaded", "True if the mempool is fully loaded"},
699+
{RPCResult::Type::BOOL, "loaded", "True if the initial load attempt of the persisted mempool finished"},
700700
{RPCResult::Type::NUM, "size", "Current tx count"},
701701
{RPCResult::Type::NUM, "bytes", "Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted"},
702702
{RPCResult::Type::NUM, "usage", "Total memory usage for the mempool"},
@@ -719,6 +719,66 @@ static RPCHelpMan getmempoolinfo()
719719
};
720720
}
721721

722+
static RPCHelpMan importmempool()
723+
{
724+
return RPCHelpMan{
725+
"importmempool",
726+
"Import a mempool.dat file and attempt to add its contents to the mempool.\n"
727+
"Warning: Importing untrusted files is dangerous, especially if metadata from the file is taken over.",
728+
{
729+
{"filepath", RPCArg::Type::STR, RPCArg::Optional::NO, "The mempool file"},
730+
{"options",
731+
RPCArg::Type::OBJ_NAMED_PARAMS,
732+
RPCArg::Optional::OMITTED,
733+
"",
734+
{
735+
{"use_current_time", RPCArg::Type::BOOL, RPCArg::Default{true},
736+
"Whether to use the current system time or use the entry time metadata from the mempool file.\n"
737+
"Warning: Importing untrusted metadata may lead to unexpected issues and undesirable behavior."},
738+
{"apply_fee_delta_priority", RPCArg::Type::BOOL, RPCArg::Default{false},
739+
"Whether to apply the fee delta metadata from the mempool file.\n"
740+
"It will be added to any existing fee deltas.\n"
741+
"The fee delta can be set by the prioritisetransaction RPC.\n"
742+
"Warning: Importing untrusted metadata may lead to unexpected issues and undesirable behavior.\n"
743+
"Only set this bool if you understand what it does."},
744+
{"apply_unbroadcast_set", RPCArg::Type::BOOL, RPCArg::Default{false},
745+
"Whether to apply the unbroadcast set metadata from the mempool file.\n"
746+
"Warning: Importing untrusted metadata may lead to unexpected issues and undesirable behavior."},
747+
},
748+
RPCArgOptions{.oneline_description = "\"options\""}},
749+
},
750+
RPCResult{RPCResult::Type::OBJ, "", "", std::vector<RPCResult>{}},
751+
RPCExamples{HelpExampleCli("importmempool", "/path/to/mempool.dat") + HelpExampleRpc("importmempool", "/path/to/mempool.dat")},
752+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
753+
const NodeContext& node{EnsureAnyNodeContext(request.context)};
754+
755+
CTxMemPool& mempool{EnsureMemPool(node)};
756+
Chainstate& chainstate = EnsureChainman(node).ActiveChainstate();
757+
758+
if (chainstate.IsInitialBlockDownload()) {
759+
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Can only import the mempool after the block download and sync is done.");
760+
}
761+
762+
const fs::path load_path{fs::u8path(request.params[0].get_str())};
763+
const UniValue& use_current_time{request.params[1]["use_current_time"]};
764+
const UniValue& apply_fee_delta{request.params[1]["apply_fee_delta_priority"]};
765+
const UniValue& apply_unbroadcast{request.params[1]["apply_unbroadcast_set"]};
766+
kernel::ImportMempoolOptions opts{
767+
.use_current_time = use_current_time.isNull() ? true : use_current_time.get_bool(),
768+
.apply_fee_delta_priority = apply_fee_delta.isNull() ? false : apply_fee_delta.get_bool(),
769+
.apply_unbroadcast_set = apply_unbroadcast.isNull() ? false : apply_unbroadcast.get_bool(),
770+
};
771+
772+
if (!kernel::LoadMempool(mempool, load_path, chainstate, std::move(opts))) {
773+
throw JSONRPCError(RPC_MISC_ERROR, "Unable to import mempool file, see debug.log for details.");
774+
}
775+
776+
UniValue ret{UniValue::VOBJ};
777+
return ret;
778+
},
779+
};
780+
}
781+
722782
static RPCHelpMan savemempool()
723783
{
724784
return RPCHelpMan{"savemempool",
@@ -921,6 +981,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
921981
{"blockchain", &gettxspendingprevout},
922982
{"blockchain", &getmempoolinfo},
923983
{"blockchain", &getrawmempool},
984+
{"blockchain", &importmempool},
924985
{"blockchain", &savemempool},
925986
{"hidden", &submitpackage},
926987
};

src/test/fuzz/rpc.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const std::vector<std::string> RPC_COMMANDS_NOT_SAFE_FOR_FUZZING{
7878
"generatetoaddress", // avoid prohibitively slow execution (when `num_blocks` is large)
7979
"generatetodescriptor", // avoid prohibitively slow execution (when `nblocks` is large)
8080
"gettxoutproof", // avoid prohibitively slow execution
81+
"importmempool", // avoid reading from disk
8182
"importwallet", // avoid reading from disk
8283
"loadwallet", // avoid reading from disk
8384
"savemempool", // disabled as a precautionary measure: may take a file path argument in the future

src/test/fuzz/validation_load_mempool.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <vector>
2121

2222
using kernel::DumpMempool;
23+
using kernel::LoadMempool;
2324

2425
using node::MempoolPath;
2526

@@ -47,6 +48,10 @@ FUZZ_TARGET(validation_load_mempool, .init = initialize_validation_load_mempool)
4748
auto fuzzed_fopen = [&](const fs::path&, const char*) {
4849
return fuzzed_file_provider.open();
4950
};
50-
(void)chainstate.LoadMempool(MempoolPath(g_setup->m_args), fuzzed_fopen);
51+
(void)LoadMempool(pool, MempoolPath(g_setup->m_args), chainstate,
52+
{
53+
.mockable_fopen_function = fuzzed_fopen,
54+
});
55+
pool.SetLoadTried(true);
5156
(void)DumpMempool(pool, MempoolPath(g_setup->m_args), fuzzed_fopen, true);
5257
}

src/txmempool.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,13 +663,13 @@ class CTxMemPool
663663
void GetTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants, size_t* ancestorsize = nullptr, CAmount* ancestorfees = nullptr) const;
664664

665665
/**
666-
* @returns true if we've made an attempt to load the mempool regardless of
666+
* @returns true if an initial attempt to load the persisted mempool was made, regardless of
667667
* whether the attempt was successful or not
668668
*/
669669
bool GetLoadTried() const;
670670

671671
/**
672-
* Set whether or not we've made an attempt to load the mempool (regardless
672+
* Set whether or not an initial attempt to load the persisted mempool was made (regardless
673673
* of whether the attempt was successful or not)
674674
*/
675675
void SetLoadTried(bool load_tried);

src/validation.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
6969
using kernel::CCoinsStats;
7070
using kernel::CoinStatsHashType;
7171
using kernel::ComputeUTXOStats;
72-
using kernel::LoadMempool;
7372
using kernel::Notifications;
7473

7574
using fsbridge::FopenFn;
@@ -4126,13 +4125,6 @@ void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight
41264125
}
41274126
}
41284127

4129-
void Chainstate::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_function)
4130-
{
4131-
if (!m_mempool) return;
4132-
::LoadMempool(*m_mempool, load_path, *this, mockable_fopen_function);
4133-
m_mempool->SetLoadTried(!m_chainman.m_interrupt);
4134-
}
4135-
41364128
bool Chainstate::LoadChainTip()
41374129
{
41384130
AssertLockHeld(cs_main);

src/validation.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -712,9 +712,6 @@ class Chainstate
712712
/** Find the last common block of this chain and a locator. */
713713
const CBlockIndex* FindForkInGlobalIndex(const CBlockLocator& locator) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
714714

715-
/** Load the persisted mempool from disk */
716-
void LoadMempool(const fs::path& load_path, fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen);
717-
718715
/** Update the chain tip based on database information, i.e. CoinsTip()'s best block. */
719716
bool LoadChainTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
720717

0 commit comments

Comments
 (0)