Skip to content

Commit fa776e6

Browse files
MarcoFalkeglozow
MarcoFalke
andcommitted
Add importmempool RPC
test_importmempool_union contributed by glozow Co-authored-by: glozow <[email protected]>
1 parent fa20d73 commit fa776e6

File tree

6 files changed

+138
-10
lines changed

6 files changed

+138
-10
lines changed

src/kernel/mempool_persist.cpp

+17-9
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
5252
int64_t failed = 0;
5353
int64_t already_there = 0;
5454
int64_t unbroadcast = 0;
55-
auto now = NodeClock::now();
55+
const auto now{NodeClock::now()};
5656

5757
try {
5858
uint64_t version;
@@ -71,8 +71,12 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
7171
file >> nTime;
7272
file >> nFeeDelta;
7373

74+
if (opts.use_current_time) {
75+
nTime = TicksSinceEpoch<std::chrono::seconds>(now);
76+
}
77+
7478
CAmount amountdelta = nFeeDelta;
75-
if (amountdelta) {
79+
if (amountdelta && opts.apply_fee_delta_priority) {
7680
pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
7781
}
7882
if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_expiry)) {
@@ -100,17 +104,21 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
100104
std::map<uint256, CAmount> mapDeltas;
101105
file >> mapDeltas;
102106

103-
for (const auto& i : mapDeltas) {
104-
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+
}
105111
}
106112

107113
std::set<uint256> unbroadcast_txids;
108114
file >> unbroadcast_txids;
109-
unbroadcast = unbroadcast_txids.size();
110-
for (const auto& txid : unbroadcast_txids) {
111-
// Ensure transactions were accepted to mempool then add to
112-
// unbroadcast set.
113-
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+
}
114122
}
115123
} catch (const std::exception& e) {
116124
LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing anyway.\n", e.what());

src/kernel/mempool_persist.h

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path,
1919

2020
struct ImportMempoolOptions {
2121
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};
2225
};
2326
/** Import the file and attempt to add its contents to the mempool. */
2427
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path,

src/rpc/client.cpp

+4
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

+61
Original file line numberDiff line numberDiff line change
@@ -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

+1
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

test/functional/mempool_persist.py

+52-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
assert_greater_than_or_equal,
4747
assert_raises_rpc_error,
4848
)
49-
from test_framework.wallet import MiniWallet
49+
from test_framework.wallet import MiniWallet, COIN
5050

5151

5252
class MempoolPersistTest(BitcoinTestFramework):
@@ -159,6 +159,16 @@ def run_test(self):
159159
assert self.nodes[0].getmempoolinfo()["loaded"]
160160
assert_equal(len(self.nodes[0].getrawmempool()), 0)
161161

162+
self.log.debug("Import mempool at runtime to node0.")
163+
assert_equal({}, self.nodes[0].importmempool(mempooldat0))
164+
assert_equal(len(self.nodes[0].getrawmempool()), 7)
165+
fees = self.nodes[0].getmempoolentry(txid=last_txid)["fees"]
166+
assert_equal(fees["base"], fees["modified"])
167+
assert_equal({}, self.nodes[0].importmempool(mempooldat0, {"apply_fee_delta_priority": True, "apply_unbroadcast_set": True}))
168+
assert_equal(2, self.nodes[0].getmempoolinfo()["unbroadcastcount"])
169+
fees = self.nodes[0].getmempoolentry(txid=last_txid)["fees"]
170+
assert_equal(fees["base"] + Decimal("0.00001000"), fees["modified"])
171+
162172
self.log.debug("Stop-start node0. Verify that it has the transactions in its mempool.")
163173
self.stop_nodes()
164174
self.start_node(0)
@@ -186,6 +196,7 @@ def run_test(self):
186196
assert_raises_rpc_error(-1, "Unable to dump mempool to disk", self.nodes[1].savemempool)
187197
os.rmdir(mempooldotnew1)
188198

199+
self.test_importmempool_union()
189200
self.test_persist_unbroadcast()
190201

191202
def test_persist_unbroadcast(self):
@@ -210,6 +221,46 @@ def test_persist_unbroadcast(self):
210221
node0.mockscheduler(16 * 60) # 15 min + 1 for buffer
211222
self.wait_until(lambda: len(conn.get_invs()) == 1)
212223

224+
def test_importmempool_union(self):
225+
self.log.debug("Submit different transactions to node0 and node1's mempools")
226+
self.start_node(0)
227+
self.start_node(2)
228+
tx_node0 = self.mini_wallet.send_self_transfer(from_node=self.nodes[0])
229+
tx_node1 = self.mini_wallet.send_self_transfer(from_node=self.nodes[1])
230+
tx_node01 = self.mini_wallet.create_self_transfer()
231+
tx_node01_secret = self.mini_wallet.create_self_transfer()
232+
self.nodes[0].prioritisetransaction(tx_node01["txid"], 0, COIN)
233+
self.nodes[0].prioritisetransaction(tx_node01_secret["txid"], 0, 2 * COIN)
234+
self.nodes[1].prioritisetransaction(tx_node01_secret["txid"], 0, 3 * COIN)
235+
self.nodes[0].sendrawtransaction(tx_node01["hex"])
236+
self.nodes[1].sendrawtransaction(tx_node01["hex"])
237+
assert tx_node0["txid"] in self.nodes[0].getrawmempool()
238+
assert not tx_node0["txid"] in self.nodes[1].getrawmempool()
239+
assert not tx_node1["txid"] in self.nodes[0].getrawmempool()
240+
assert tx_node1["txid"] in self.nodes[1].getrawmempool()
241+
assert tx_node01["txid"] in self.nodes[0].getrawmempool()
242+
assert tx_node01["txid"] in self.nodes[1].getrawmempool()
243+
assert not tx_node01_secret["txid"] in self.nodes[0].getrawmempool()
244+
assert not tx_node01_secret["txid"] in self.nodes[1].getrawmempool()
245+
246+
self.log.debug("Check that importmempool can add txns without replacing the entire mempool")
247+
mempooldat0 = str(self.nodes[0].chain_path / "mempool.dat")
248+
result0 = self.nodes[0].savemempool()
249+
assert_equal(mempooldat0, result0["filename"])
250+
assert_equal({}, self.nodes[1].importmempool(mempooldat0, {"apply_fee_delta_priority": True}))
251+
# All transactions should be in node1's mempool now.
252+
assert tx_node0["txid"] in self.nodes[1].getrawmempool()
253+
assert tx_node1["txid"] in self.nodes[1].getrawmempool()
254+
assert not tx_node1["txid"] in self.nodes[0].getrawmempool()
255+
# For transactions that already existed, priority should be changed
256+
entry_node01 = self.nodes[1].getmempoolentry(tx_node01["txid"])
257+
assert_equal(entry_node01["fees"]["base"] + 1, entry_node01["fees"]["modified"])
258+
# Deltas for not-yet-submitted transactions should be applied as well (prioritisation is stackable).
259+
self.nodes[1].sendrawtransaction(tx_node01_secret["hex"])
260+
entry_node01_secret = self.nodes[1].getmempoolentry(tx_node01_secret["txid"])
261+
assert_equal(entry_node01_secret["fees"]["base"] + 5, entry_node01_secret["fees"]["modified"])
262+
self.stop_nodes()
263+
213264

214265
if __name__ == "__main__":
215266
MempoolPersistTest().main()

0 commit comments

Comments
 (0)