rpc: support writing UTXO set dump (`dumptxoutset`) to a named pipe
What changed, and why it matters
This change lets Bitcoin Core's `dumptxoutset` RPC command write its UTXO snapshot directly to a named pipe (FIFO) instead of only to a regular file. Previously the command always wrote to a temporary file and then renamed it into place, which doesn't work for a pipe. The patch detects a named pipe and, if one is supplied, writes directly to it and skips the rename step. It also skips the 'file already exists' error for pipes because a pipe is a communication channel, not a stored file. This is a convenience feature for external tools, not a fix for a known security bug.
No immediate action required. Operators using `dumptxoutset` with a named pipe should ensure the pipe path is trusted and that the reading process is prepared for partial or interrupted output, since the atomic rename safeguard is skipped for FIFOs. Code reviewers may want to confirm that `fs::is_fifo` correctly handles dangling symlinks and race conditions between the status check and the open.
Security signals we found
RPC command now accepts and writes to FIFO special files
Pre-existing 'file already exists' guard is bypassed for FIFOs
Final atomic rename is skipped for FIFOs, so partial/interrupted writes may be observable by the reader
No change to permissions, path validation, or sandboxing
Evidence from the diff
The patch modifies src/rpc/blockchain.cpp and src/util/fs.h. It adds a fs::exists(const std::filesystem::file_status&) overload and uses fs::status() to inspect the target path once. If the target is a FIFO (fs::is_fifo), dumptxoutset sets temppath = path and bypasses the final fs::rename(temppath, path). It also relaxes the pre-existing existence check so a FIFO does not trigger the ‘already exists’ error. The change is narrowly scoped to named-pipe handling and does not alter serialization, hashing, or consensus logic.
Changed components
src/rpc/blockchain.cppsrc/util/fs.hdumptxoutset RPCInspect captured patch +11 / −4
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 475c5ca4..3beea79f 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -3109,11 +3109,12 @@ static RPCHelpMan dumptxoutset()
const ArgsManager& args{EnsureAnyArgsman(request.context)};
const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(self.Arg<std::string_view>("path")));
+ const auto path_info{fs::status(path)};
// Write to a temporary path and then move into `path` on completion
- // to avoid confusion due to an interruption.
- const fs::path temppath = path + ".incomplete";
+ // to avoid confusion due to an interruption. If a named pipe passed, write directly to it.
+ const fs::path temppath = fs::is_fifo(path_info) ? path : path + ".incomplete";
- if (fs::exists(path)) {
+ if (fs::exists(path_info) && !fs::is_fifo(path_info)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
path.utf8string() + " already exists. If you are sure this is what you want, "
@@ -3197,7 +3198,9 @@ static RPCHelpMan dumptxoutset()
path,
temppath,
node.rpc_interruption_point);
- fs::rename(temppath, path);
+ if (!fs::is_fifo(path_info)) {
+ fs::rename(temppath, path);
+ }
result.pushKV("path", path.utf8string());
return result;
diff --git a/src/util/fs.h b/src/util/fs.h
index 147904d0..dce371cc 100644
--- a/src/util/fs.h
+++ b/src/util/fs.h
@@ -96,6 +96,10 @@ static inline bool exists(const path& p)
{
return std::filesystem::exists(p);
}
+static inline bool exists(const std::filesystem::file_status& s)
+{
+ return std::filesystem::exists(s);
+}
// Allow explicit quoted stream I/O.
static inline auto quoted(const std::string& s)
Why this scored 22/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.