diff --git a/doc/release-notes-31560.md b/doc/release-notes-31560.md new file mode 100644 index 00000000000..07a8f7798ed --- /dev/null +++ b/doc/release-notes-31560.md @@ -0,0 +1,8 @@ +Updated RPCs +------------ + +- The `dumptxoutset` RPC now supports writing to a named pipe + on UNIX-like systems (see mkfifo(1) and mkfifo(3) man pages). + This allows the raw UTXO set data to be consumed directly by + another process (e.g., the `contrib/utxo-tools/utxo_to_sqlite.py` + conversion script) without first writing it to disk. (#31560) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index d97b7c6c15c..5156f65813c 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("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 147904d030a..dce371cc5ef 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) diff --git a/test/functional/tool_utxo_to_sqlite.py b/test/functional/tool_utxo_to_sqlite.py index d1f3e7e1934..d3b3fc43439 100755 --- a/test/functional/tool_utxo_to_sqlite.py +++ b/test/functional/tool_utxo_to_sqlite.py @@ -4,7 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test utxo-to-sqlite conversion tool""" from itertools import product -import os.path +import os +import platform try: import sqlite3 except ImportError: @@ -135,6 +136,19 @@ class UtxoToSqliteTest(BitcoinTestFramework): assert_equal(muhash_sqlite, muhash_compact_serialized) self.log.info('') + if platform.system() != "Windows": # FIFOs are not available on Windows + self.log.info('Convert UTXO set directly (without intermediate dump) via named pipe') + fifo_filename = os.path.join(self.options.tmpdir, "utxos.fifo") + os.mkfifo(fifo_filename) + output_direct_filename = os.path.join(self.options.tmpdir, "utxos_direct.sqlite") + p = subprocess.Popen([sys.executable, utxo_to_sqlite_path, fifo_filename, output_direct_filename], + stderr=subprocess.STDOUT) + node.dumptxoutset(fifo_filename, "latest") + p.wait(timeout=10) + muhash_direct_sqlite = calculate_muhash_from_sqlite_utxos(output_direct_filename, "hex", "hex") + assert_equal(muhash_sqlite, muhash_direct_sqlite) + os.remove(fifo_filename) + if __name__ == "__main__": UtxoToSqliteTest(__file__).main()