From 44760bc1141590a69078953a0b3e61a4e1b91af0 Mon Sep 17 00:00:00 2001 From: ShauryaaSharma Date: Sun, 21 Jun 2026 01:27:05 +0530 Subject: [PATCH] qt: prevent console hang on large RPC responses Running getrawtransaction on a very large non-standard transaction (e.g. the 3.98 MB tx in block 938523) causes the GUI to become completely unresponsive. The hang is in the Qt UI thread: GUIUtil::HtmlEscape() walks the full result string character-by-character, then QTextEdit:: append() re-lays-out an HTML document of the same size, effectively freezing the event loop. Add a 1 MiB guard in RPCExecutor::request() that fires before QString conversion. When the limit is exceeded the user sees the actual byte count and is directed to bitcoin-cli for the full output. Add a mock RPC returning >1 MiB to the Qt unit tests, confirming RPCExecuteCommandLine returns the complete result unmodified (the display guard applies only to the console widget, not the underlying RPC execution path). Fixes #932 --- src/qt/rpcconsole.cpp | 12 ++++++++++++ src/qt/test/rpcnestedtests.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 3f4da599eb6..03877655715 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -414,6 +414,18 @@ void RPCExecutor::request(const QString &command, const QString& wallet_name) return; } + // Avoid passing a very large string to the Qt text widget. Appending + // many megabytes of HTML to QTextEdit causes the UI thread to hang + // while the document is re-laid-out. Direct the user to bitcoin-cli + // for commands whose output exceeds the display limit. + if (result.size() > 1_MiB) { + Q_EMIT reply(RPCConsole::CMD_REPLY, + QString("Response too large to display (%1 bytes). " + "Use bitcoin-cli to retrieve the full result.") + .arg(result.size())); + return; + } + Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); } catch (UniValue& objError) diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 68130df88aa..32ffc6888d3 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -34,8 +35,27 @@ static RPCMethod rpcNestedTest_rpc() }; } +// Returns a hex string larger than the RPCExecutor display limit (1 MiB) to +// exercise the large-output path. RPCExecuteCommandLine returns the full +// result; the truncation for display is enforced by RPCExecutor::request(). +static RPCMethod rpcLargeOutput_rpc() +{ + return RPCMethod{ + "rpcLargeOutput", + "return a string larger than 1 MiB", + {}, + RPCResult{RPCResult::Type::STR_HEX, "", ""}, + RPCExamples{""}, + [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue { + // 1 MiB + 1 byte of hex characters + return UniValue{std::string(1_MiB + 1, 'a')}; + }, + }; +} + static const CRPCCommand vRPCCommands[] = { {"rpcNestedTest", &rpcNestedTest_rpc}, + {"rpcLargeOutput", &rpcLargeOutput_rpc}, }; void RPCNestedTests::rpcNestedTests() @@ -139,4 +159,10 @@ void RPCNestedTests::rpcNestedTests() QVERIFY_EXCEPTION_THROWN(RPCConsole::RPCExecuteCommandLine(m_node, result, "rpcNestedTest abc,,abc"), std::runtime_error); //don't tolerate empty arguments when using , QVERIFY_EXCEPTION_THROWN(RPCConsole::RPCExecuteCommandLine(m_node, result, "rpcNestedTest(abc,,abc)"), std::runtime_error); //don't tolerate empty arguments when using , QVERIFY_EXCEPTION_THROWN(RPCConsole::RPCExecuteCommandLine(m_node, result, "rpcNestedTest(abc,,)"), std::runtime_error); //don't tolerate empty arguments when using , + + // Verify that RPCExecuteCommandLine returns the full result for large + // outputs. The size guard lives in RPCExecutor::request() and only affects + // what is rendered in the console widget, not the underlying RPC call. + RPCConsole::RPCExecuteCommandLine(m_node, result, "rpcLargeOutput"); + QVERIFY(result.size() > 1_MiB); }