rpc: Add in_memory option to dumptxoutset with rollback
What changed, and why it matters
This commit adds a new optional 'in_memory' flag to the Bitcoin Core 'dumptxoutset' RPC command. When enabled, the temporary database used during rollback snapshot creation is kept in RAM instead of written to disk. This is a performance feature, not a security fix. There is no direct evidence in the commit that it addresses a vulnerability, and the change does not appear to introduce an obvious security flaw. The main risk is operational: a user could run out of memory if they enable this on mainnet without sufficient RAM.
No immediate security action required. Operators should ensure RPC access remains restricted and document that 'in_memory=true' can consume large amounts of RAM. Reviewers may want to confirm that memory-only LevelDB failure modes (e.g., OOM handling) do not corrupt the main chainstate or snapshot file.
Security signals we found
New RPC option added to existing privileged command
Memory-only database path selected under user control
No input validation changes beyond boolean parsing
No security-relevant bug fix language in commit message or diff
Evidence from the diff
The patch extends CreateRolledBackUTXOSnapshot() with an ‘in_memory’ boolean parameter. When true, it skips creating the on-disk temporary directory and sets DBParams.memory_only = true, causing LevelDB to operate in memory. The RPC help text warns that mainnet usage may require over 10 GB of RAM. The change is gated behind the existing ‘rollback’ option of the dumptxoutset RPC, which is already restricted to users with RPC access. No authentication bypass, buffer overflow, or cryptographic weakness is visible in the diff.
Changed components
src/rpc/blockchain.cppsrc/rpc/client.cpptest/functional/rpc_dumptxoutset.pyInspect captured patch +26 / −6
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 50697a53..22981974 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -97,7 +97,8 @@ UniValue CreateRolledBackUTXOSnapshot(
const CBlockIndex* target,
AutoFile&& afile,
const fs::path& path,
- const fs::path& tmppath);
+ const fs::path& tmppath,
+ bool in_memory);
/* Calculate the difficulty for a given block index.
*/
@@ -3081,6 +3082,7 @@ static RPCMethod dumptxoutset()
{"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
"Height or hash of the block to roll back to before creating the snapshot. Note: The further this number is from the tip, the longer this process will take. Consider setting a higher -rpcclienttimeout value in this case.",
RPCArgOptions{.skip_type_check = true, .type_str = {"", "string or numeric"}}},
+ {"in_memory", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, the temporary UTXO-set database used during rollback is kept entirely in memory. This can significantly speed up the process but requires sufficient free RAM (over 10 GB on mainnet)."},
},
},
},
@@ -3098,7 +3100,8 @@ static RPCMethod dumptxoutset()
RPCExamples{
HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat latest") +
HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat rollback") +
- HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456)")
+ HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456)") +
+ HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456 in_memory=true)")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
@@ -3164,12 +3167,14 @@ static RPCMethod dumptxoutset()
temp_prune_lock.emplace(node.chainman->m_blockman, target_index->nHeight);
}
+ const bool in_memory{options.exists("in_memory") ? options["in_memory"].get_bool() : false};
result = CreateRolledBackUTXOSnapshot(node,
chainstate,
target_index,
std::move(afile),
path,
- temppath);
+ temppath,
+ in_memory);
}
if (!fs::is_fifo(path_info)) {
@@ -3206,18 +3211,26 @@ UniValue CreateRolledBackUTXOSnapshot(
const CBlockIndex* target,
AutoFile&& afile,
const fs::path& path,
- const fs::path& tmppath)
+ const fs::path& tmppath,
+ const bool in_memory)
{
// Create a temporary leveldb to store the UTXO set that is being rolled back
std::string temp_db_name{strprintf("temp_utxo_%d", target->nHeight)};
fs::path temp_db_path{fsbridge::AbsPathJoin(tmppath.parent_path(), fs::u8path(temp_db_name))};
- TemporaryUTXODatabase temp_db_cleaner{temp_db_path};
+
+ // Only create the on-disk temp directory when not using in-memory mode
+ std::optional<TemporaryUTXODatabase> temp_db_cleaner;
+ if (!in_memory) {
+ temp_db_cleaner.emplace(temp_db_path);
+ } else {
+ LogInfo("Using in-memory database for UTXO-set rollback (this may require significant RAM).");
+ }
// Create temporary database
DBParams db_params{
.path = temp_db_path,
.cache_bytes = 0,
- .memory_only = false,
+ .memory_only = in_memory,
.wipe_data = true,
.obfuscate = false,
.options = DBOptions{}
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 8422af15..8d3e551d 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -238,6 +238,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "dumptxoutset", 1, "type", ParamFormat::STRING },
{ "dumptxoutset", 2, "options" },
{ "dumptxoutset", 2, "rollback", ParamFormat::JSON_OR_STRING },
+ { "dumptxoutset", 2, "in_memory" },
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
{ "lockunspent", 2, "persistent" },
diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py
index 0f68aeb9..80c4bf6f 100755
--- a/test/functional/rpc_dumptxoutset.py
+++ b/test/functional/rpc_dumptxoutset.py
@@ -43,6 +43,12 @@ class DumptxoutsetTest(BitcoinTestFramework):
assert_equal(out['base_height'], target_height)
assert_equal(out['base_hash'], target_hash)
+ # Cover the same case as above with an in-memory database
+ out_mem = node.dumptxoutset("txoutset_fork_mem.dat", "rollback", {"rollback": target_height, "in_memory": True})
+ assert_equal(out_mem['base_height'], target_height)
+ assert_equal(out_mem['base_hash'], target_hash)
+
+
def run_test(self):
"""Test a trivial usage of the dumptxoutset RPC command."""
node = self.nodes[0]
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.