What changed, and why it matters
This commit replaces the HTTP client library used by bitcoin-cli (libevent) with a new custom-built HTTP client. It is a large refactoring change that touches network communication, credential handling, and error messages. There is no direct evidence in the commit that this fixes a known security bug, but any rewrite of network and authentication code carries a risk of introducing subtle bugs in how bitcoin-cli talks to the Bitcoin server.
Treat this as a high-risk refactoring commit. Review the new HTTPClient implementation for parser bugs (e.g., chunked encoding edge cases, header injection, integer overflows in content-length/chunk-size handling), timeout behavior, and correct propagation of authentication errors. Run the updated functional tests and consider additional fuzzing or manual testing of malformed HTTP responses against bitcoin-cli.
Security signals we found
Large rewrite of RPC client network stack (libevent removal, custom HTTP parser)
Custom HTTP response parsing now handles status line, Content-Length, and chunked encoding
Authorization header construction moved from libevent evkeyvalq to manual string building
Connection timeout and retry logic rewritten
Wallet endpoint URL encoding changed from evhttp_uriencode to UrlEncode helper
No explicit security bug fix or vulnerability disclosure in commit message or diff
Evidence from the diff
The commit removes libevent from bitcoin-cli and introduces a new synchronous HTTPClient class implemented directly on top of the project’s Sock abstraction. It re-implements HTTP/1.1 request construction, response parsing (status line, headers, Content-Length, chunked transfer encoding, and read-until-close fallback), timeout handling, and connection logic. The change also removes the raii_evhttp_request and raii_evhttp_connection helpers from support/events.h. Test expectations for connection error messages are updated to match the new implementation. No CVE, advisory, or vendor security disclosure is referenced in the commit or supplied references.
Changed components
src/bitcoin-cli.cppsrc/support/events.htest/functional/interface_bitcoin_cli.pytest/functional/interface_ipc_cli.pyInspect captured patch +359 / −147
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 55604d04..6a28dacb 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -10,11 +10,13 @@
#include <common/args.h>
#include <common/license_info.h>
#include <common/system.h>
+#include <common/url.h>
#include <compat/compat.h>
#include <compat/stdin.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/rpc.h>
+#include <netbase.h>
#include <policy/feerate.h>
#include <rpc/client.h>
#include <rpc/mining.h>
@@ -24,6 +26,7 @@
#include <univalue.h>
#include <util/chaintype.h>
#include <util/exception.h>
+#include <util/sock.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>
@@ -34,20 +37,19 @@
#include <cmath>
#include <cstdio>
#include <functional>
+#include <limits>
#include <memory>
#include <optional>
+#include <span>
#include <string>
#include <string_view>
+#include <thread>
#include <tuple>
#ifndef WIN32
#include <unistd.h>
#endif
-#include <event2/buffer.h>
-#include <event2/keyvalq_struct.h>
-#include <support/events.h>
-
using util::Join;
using util::ToString;
@@ -185,15 +187,6 @@ std::optional<std::string> RpcWalletName(const ArgsManager& args)
return args.GetArg("-rpcwallet");
}
-/** libevent event log callback */
-static void libevent_log_cb(int severity, const char *msg)
-{
- // Ignore everything other than errors
- if (severity >= EVENT_LOG_ERR) {
- throw std::runtime_error(strprintf("libevent error: %s", msg));
- }
-}
-
//
// Exception thrown on connection error. This error is used to determine
// when to wait if -rpcwait is given.
@@ -261,68 +254,12 @@ static int AppInitRPC(int argc, char* argv[])
return CONTINUE_EXECUTION;
}
-
-/** Reply structure for request_done to fill in */
-struct HTTPReply
+struct HTTPResponse
{
- HTTPReply() = default;
-
int status{0};
- int error{-1};
std::string body;
};
-static std::string http_errorstring(int code)
-{
- switch(code) {
- case EVREQ_HTTP_TIMEOUT:
- return "timeout reached";
- case EVREQ_HTTP_EOF:
- return "EOF reached";
- case EVREQ_HTTP_INVALID_HEADER:
- return "error while reading header, or invalid header";
- case EVREQ_HTTP_BUFFER_ERROR:
- return "error encountered while reading or writing";
- case EVREQ_HTTP_REQUEST_CANCEL:
- return "request was canceled";
- case EVREQ_HTTP_DATA_TOO_LONG:
- return "response body is larger than allowed";
- default:
- return "unknown";
- }
-}
-
-static void http_request_done(struct evhttp_request *req, void *ctx)
-{
- HTTPReply *reply = static_cast<HTTPReply*>(ctx);
-
- if (req == nullptr) {
- /* If req is nullptr, it means an error occurred while connecting: the
- * error code will have been passed to http_error_cb.
- */
- reply->status = 0;
- return;
- }
-
- reply->status = evhttp_request_get_response_code(req);
-
- struct evbuffer *buf = evhttp_request_get_input_buffer(req);
- if (buf)
- {
- size_t size = evbuffer_get_length(buf);
- const char *data = (const char*)evbuffer_pullup(buf, size);
- if (data)
- reply->body = std::string(data, size);
- evbuffer_drain(buf, size);
- }
-}
-
-static void http_error_cb(enum evhttp_request_error err, void *ctx)
-{
- HTTPReply *reply = static_cast<HTTPReply*>(ctx);
- reply->error = err;
-}
-
static int8_t NetworkStringToId(const std::string& str)
{
for (size_t i = 0; i < NETWORKS.size(); ++i) {
@@ -893,6 +830,328 @@ static std::optional<UniValue> CallIPC(BaseRequestHandler* rh, const std::string
return rh->ProcessReply(reply);
}
+/**
+ * Simple synchronous HTTP client using Sock class.
+ */
+class HTTPClient
+{
+public:
+ static HTTPClient Connect(const std::string& host, uint16_t port, std::chrono::seconds timeout);
+
+ HTTPResponse Post(const std::string& endpoint,
+ std::span<const std::pair<std::string, std::string>> headers,
+ const std::string& body);
+
+private:
+ // Signal that the peer closed the connection cleanly. Used in the read-until-close fallback.
+ struct RecvEOF : CConnectionFailed { using CConnectionFailed::CConnectionFailed; };
+
+ std::unique_ptr<Sock> m_socket;
+ std::string m_host;
+ std::chrono::seconds m_timeout;
+
+ HTTPClient(std::unique_ptr<Sock>&& socket, const std::string& host, std::chrono::seconds timeout)
+ : m_socket(std::move(socket)), m_host(host), m_timeout(timeout) {}
+ bool SendRequest(std::string_view request);
+ HTTPResponse ReadResponse();
+ std::optional<std::string> Recv(std::chrono::time_point<std::chrono::steady_clock> deadline);
+};
+
+HTTPClient HTTPClient::Connect(const std::string& host, uint16_t port, std::chrono::seconds timeout)
+{
+ std::vector<CService> services = Lookup(host, port, /*fAllowLookup=*/true, /*nMaxSolutions=*/256);
+ if (services.empty()) {
+ throw CConnectionFailed(strprintf("Could not resolve host: %s", host));
+ }
+
+ const auto deadline{std::chrono::steady_clock::now() + timeout};
+ for (const CService& service : services) {
+ const auto time_left{std::chrono::duration_cast<std::chrono::milliseconds>(deadline - std::chrono::steady_clock::now())};
+ if (time_left.count() <= 0) break;
+
+ auto sock = ConnectDirectly(service, /*manual_connection=*/true, time_left);
+ if (sock) return HTTPClient{std::move(sock), host, timeout};
+ }
+
+ throw CConnectionFailed{"Could not connect to the server"};
+}
+
+HTTPResponse HTTPClient::Post(const std::string& endpoint,
+ std::span<const std::pair<std::string, std::string>> headers,
+ const std::string& body)
+{
+ try {
+ // Build HTTP request
+ std::string request = strprintf("POST %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "Connection: close\r\n"
+ "Content-Length: %d\r\n",
+ endpoint, m_host, body.size());
+
+ for (const auto& [name, value] : headers) {
+ request += strprintf("%s: %s\r\n", name, value);
+ }
+ request += "\r\n";
+ request += body;
+
+ if (!SendRequest(request)) {
+ throw CConnectionFailed("Failed to send HTTP request");
+ }
+
+ return ReadResponse();
+ } catch (const HTTPError& e) {
+ throw CConnectionFailed(strprintf("HTTP error: %s", e.what()));
+ }
+}
+
+bool HTTPClient::SendRequest(std::string_view request)
+{
+ const auto deadline{std::chrono::steady_clock::now() + m_timeout};
+
+ while (!request.empty()) {
+ Sock::Event event{0};
+ auto time_left = std::chrono::duration_cast<std::chrono::milliseconds>(
+ deadline - std::chrono::steady_clock::now());
+ if (time_left.count() <= 0 || !m_socket->Wait(time_left, Sock::SEND, &event)) {
+ return false;
+ }
+
+ if (!(event & Sock::SEND)) {
+ continue;
+ }
+
+ ssize_t sent = m_socket->Send(request.data(), request.size(), MSG_NOSIGNAL);
+ if (sent < 0) {
+ int err = WSAGetLastError();
+ if (!IOErrorIsPermanent(err)) {
+ std::this_thread::yield();
+ continue;
+ }
+ return false;
+ }
+ request.remove_prefix(sent);
+ }
+ return true;
+}
+
+HTTPResponse HTTPClient::ReadResponse()
+{
+ HTTPResponse response;
+ std::string buffer;
+ const auto deadline{std::chrono::steady_clock::now() + m_timeout};
+
+ // Read data until we have complete headers
+ size_t headers_end = 0;
+
+ while (headers_end == 0) {
+ if (auto result{Recv(deadline)}) {
+ buffer.append(*result);
+ } else {
+ std::this_thread::yield();
+ continue;
+ }
+
+ // Check for header terminator
+ size_t pos = buffer.find("\r\n\r\n");
+ if (pos != std::string::npos) {
+ headers_end = pos + 4;
+ }
+ }
+
+ // Parse http status
+ util::LineReader reader(std::string_view{buffer.data(), headers_end}, headers_end);
+ auto status_line = reader.ReadLine();
+ if (!status_line) {
+ throw HTTPError{"Failed to read status line"};
+ }
+
+ const std::string& status_str = *status_line;
+ // Minimum status line is "HTTP/X.Y NNN" (e.g. "HTTP/1.1 200"), 12 characters.
+ if (status_str.size() < 12 || !status_str.starts_with("HTTP/")) {
+ throw HTTPError{"Invalid status line"};
+ }
+
+ size_t space1 = status_str.find(' ');
+ if (space1 == std::string::npos || space1 + 4 > status_str.size()) {
+ throw HTTPError{"Invalid status line format"};
+ }
+
+ std::string status_code_str = status_str.substr(space1 + 1, 3);
+ auto status_code = ToIntegral<int>(status_code_str);
+ if (!status_code) {
+ throw HTTPError{"Invalid status code"};
+ }
+ response.status = *status_code;
+
+ HTTPResponseHeaders headers;
+ headers.Read(reader);
+
+ // Determine body length
+ size_t content_length = 0;
+ bool chunked = false;
+
+ // RFC 9112 §6.3 says responses with both Transfer-Encoding and Content-Length
+ // must be rejected. We are more lenient: Transfer-Encoding takes precedence
+ // and Content-Length is ignored.
+ auto transfer_encoding = headers.FindFirst("transfer-encoding");
+ if (transfer_encoding && ToLower(*transfer_encoding).find("chunked") != std::string::npos) {
+ chunked = true;
+ } else {
+ auto content_length_header = headers.FindFirst("content-length");
+ if (content_length_header) {
+ auto maybe_len = ToIntegral<size_t>(*content_length_header);
+ if (!maybe_len) {
+ throw HTTPError{"Invalid Content-Length"};
+ }
+ content_length = *maybe_len;
+ }
+ }
+
+ // Remove headers data from buffer, so only initial body data remains
+ buffer.erase(0, headers_end);
+
+ // Read remaining body
+ if (chunked) {
+ // Handle chunked transfer encoding
+ std::string body;
+
+ while (true) {
+ // Try to parse a chunk from current buffer
+ std::string_view chunk_data{buffer};
+ size_t line_end = chunk_data.find("\r\n");
+
+ if (line_end != std::string::npos) {
+ // Parse chunk size
+ std::string_view size_str = chunk_data.substr(0, line_end);
+ // Ignore chunk extensions
+ size_t semi = size_str.find(';');
+ if (semi != std::string::npos) {
+ size_str = size_str.substr(0, semi);
+ }
+
+ const auto chunk_size{ToIntegral<uint64_t>(util::TrimStringView(size_str), /*base=*/16)};
+ if (!chunk_size) {
+ throw HTTPError{"Invalid chunk size"};
+ }
+
+ if (*chunk_size == 0) {
+ // Allow (but ignore) Chunked Trailer section, by
+ // reading CRLF-terminated lines until we read an empty line,
+ // which indicates the end of this response.
+ // See https://httpwg.org/specs/rfc9112.html#rfc.section.7.1.2
+ buffer.erase(0, line_end + 2);
+ while (true) {
+ size_t crlf_pos = buffer.find("\r\n");
+ if (crlf_pos == std::string::npos) {
+ // Need more data
+ if (auto result{Recv(deadline)}) {
+ buffer.append(*result);
+ } else {
+ std::this_thread::yield();
+ }
+ continue;
+ }
+ buffer.erase(0, crlf_pos + 2);
+ if (crlf_pos == 0) break;
+ }
+ break;
+ }
+
+ // Check if we have the full chunk
+ size_t chunk_start = line_end + 2;
+ if (*chunk_size > std::numeric_limits<size_t>::max() - chunk_start - 2) {
+ throw HTTPError{"Chunk size too large"};
+ }
+ size_t chunk_end = chunk_start + *chunk_size + 2; // +2 for trailing CRLF
+
+ if (buffer.size() >= chunk_end) {
+ // Extract chunk data
+ body.append(buffer, chunk_start, *chunk_size);
+
+ // Remove processed data
+ buffer.erase(0, chunk_end);
+ continue;
+ }
+ }
+
+ // Need more data
+ while (true) {
+ if (auto result{Recv(deadline)}) {
+ buffer.append(*result);
+ break;
+ } else {
+ std::this_thread::yield();
+ }
+ }
+ }
+
+ response.body = std::move(body);
+ } else if (content_length > 0) {
+ // Fixed content length
+ while (buffer.size() < content_length) {
+ if (auto result{Recv(deadline)}) {
+ buffer.append(*result);
+ } else {
+ std::this_thread::yield();
+ }
+ }
+
+ // Possibly shrink buffer in case we got a larger response than
+ // originally specified.
+ buffer.resize(content_length);
+ response.body = std::move(buffer);
+ } else {
+ // No Content-Length and not chunked: read until the peer closes the
+ // connection (RFC 9112 §6.3, HTTP/1.0 fallback).
+ try {
+ while (true) {
+ if (auto result{Recv(deadline)}) {
+ buffer.append(*result);
+ } else {
+ std::this_thread::yield();
+ }
+ }
+ } catch (const RecvEOF&) {}
+ response.body = std::move(buffer);
+ }
+
+ return response;
+}
+
+std::optional<std::string> HTTPClient::Recv(const std::chrono::time_point<std::chrono::steady_clock> deadline)
+{
+ auto wait_for_readable{[this](std::chrono::milliseconds timeout) -> bool {
+ Sock::Event event{0};
+ if (!m_socket->Wait(timeout, Sock::RECV, &event)) {
+ return false;
+ }
+ return (event & Sock::RECV) != 0;
+ }};
+
+ auto time_left = std::chrono::duration_cast<std::chrono::milliseconds>(
+ deadline - std::chrono::steady_clock::now());
+ if (time_left.count() <= 0 || !wait_for_readable(time_left)) {
+ throw CConnectionFailed{"timeout"};
+ }
+
+ char recv_buf[4096];
+ ssize_t nrecv = m_socket->Recv(recv_buf, sizeof(recv_buf), /*flags=*/0);
+
+ if (nrecv < 0) {
+ int err = WSAGetLastError();
+ if (!IOErrorIsPermanent(err)) {
+ return std::nullopt;
+ }
+ throw CConnectionFailed{strprintf("Read error: %s", NetworkErrorString(err))};
+ }
+
+ if (nrecv == 0) {
+ throw RecvEOF{"EOF"};
+ }
+
+ return std::string{recv_buf, static_cast<size_t>(nrecv)};
+}
+
static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, const std::vector<std::string>& args, const std::string& endpoint, const std::string& username)
{
std::string host;
@@ -937,34 +1196,16 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
}
}
- // Obtain event base
- raii_event_base base = obtain_event_base();
-
- // Synchronously look up hostname
- raii_evhttp_connection evcon = obtain_evhttp_connection_base(base.get(), host, port);
-
// Set connection timeout
- {
- const int timeout = gArgs.GetIntArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT);
- if (timeout > 0) {
- evhttp_connection_set_timeout(evcon.get(), timeout);
- } else {
- // Indefinite request timeouts are not possible in libevent-http, so we
- // set the timeout to a very long time period instead.
-
- constexpr int YEAR_IN_SECONDS = 31556952; // Average length of year in Gregorian calendar
- evhttp_connection_set_timeout(evcon.get(), 5 * YEAR_IN_SECONDS);
- }
- }
-
- HTTPReply response;
- raii_evhttp_request req = obtain_evhttp_request(http_request_done, (void*)&response);
- if (req == nullptr) {
- throw std::runtime_error("create http request failed");
+ const int timeout = gArgs.GetIntArg("-rpcclienttimeout", DEFAULT_HTTP_CLIENT_TIMEOUT);
+ std::chrono::seconds timeout_duration;
+ if (timeout > 0) {
+ timeout_duration = std::chrono::seconds(timeout);
+ } else {
+ // Use 5 year timeout for "indefinite"
+ timeout_duration = std::chrono::years(5);
}
- evhttp_request_set_error_cb(req.get(), http_error_cb);
-
// Get credentials
std::string rpc_credentials;
std::optional<AuthCookieResult> auth_cookie_result;
@@ -975,36 +1216,25 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
rpc_credentials = username + ":" + gArgs.GetArg("-rpcpassword", "");
}
- struct evkeyvalq* output_headers = evhttp_request_get_output_headers(req.get());
- assert(output_headers);
- evhttp_add_header(output_headers, "Host", host.c_str());
- evhttp_add_header(output_headers, "Connection", "close");
- evhttp_add_header(output_headers, "Content-Type", "application/json");
- evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(rpc_credentials)).c_str());
-
- // Attach request data
+ const std::pair<std::string, std::string> headers[]{
+ {"Content-Type", "application/json"},
+ {"Authorization", "Basic " + EncodeBase64(rpc_credentials)},
+ };
std::string strRequest = rh->PrepareRequest(strMethod, args).write() + "\n";
- struct evbuffer* output_buffer = evhttp_request_get_output_buffer(req.get());
- assert(output_buffer);
- evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
-
- int r = evhttp_make_request(evcon.get(), req.release(), EVHTTP_REQ_POST, endpoint.c_str());
- if (r != 0) {
- throw CConnectionFailed("send http request failed");
- }
- event_base_dispatch(base.get());
-
- if (response.status == 0) {
- std::string responseErrorMessage;
- if (response.error != -1) {
- responseErrorMessage = strprintf(" (error code %d - \"%s\")", response.error, http_errorstring(response.error));
- }
- throw CConnectionFailed(strprintf("Could not connect to the server %s:%d%s\n\n"
+ HTTPResponse response;
+ try {
+ HTTPClient client{HTTPClient::Connect(host, port, timeout_duration)};
+ response = client.Post(endpoint, headers, strRequest);
+ } catch (const CConnectionFailed& e) {
+ const std::string formatted_error{*e.what() ? strprintf(" (%s)", e.what()) : ""};
+ throw CConnectionFailed(strprintf("Error while attempting to communicate with server %s:%d%s\n\n"
"Make sure the bitcoind server is running and that you are connecting to the correct RPC port.\n"
"Use \"bitcoin-cli -help\" for more info.",
- host, port, responseErrorMessage));
- } else if (response.status == HTTP_UNAUTHORIZED) {
+ host, port, formatted_error));
+ }
+
+ if (response.status == HTTP_UNAUTHORIZED) {
std::string error{"Authorization failed: "};
if (auth_cookie_result.has_value()) {
switch (*auth_cookie_result) {
@@ -1061,13 +1291,7 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str
// check if we should use a special wallet endpoint
std::string endpoint = "/";
if (rpcwallet) {
- char* encodedURI = evhttp_uriencode(rpcwallet->data(), rpcwallet->size(), false);
- if (encodedURI) {
- endpoint = "/wallet/" + std::string(encodedURI);
- free(encodedURI);
- } else {
- throw CConnectionFailed("uri-encode failed");
- }
+ endpoint = "/wallet/" + UrlEncode(*rpcwallet);
}
std::string username{gArgs.GetArg("-rpcuser", "")};
@@ -1088,8 +1312,10 @@ static UniValue ConnectAndCallRPC(BaseRequestHandler* rh, const std::string& str
} catch (const CConnectionFailed& e) {
if (fWait && (timeout <= 0 || std::chrono::steady_clock::now() < deadline)) {
UninterruptibleSleep(1s);
- } else {
+ } else if (fWait) {
throw CConnectionFailed(strprintf("timeout on transient error: %s", e.what()));
+ } else {
+ throw;
}
}
} while (fWait);
@@ -1448,7 +1674,6 @@ MAIN_FUNCTION
tfm::format(std::cerr, "Error: Initializing networking failed\n");
return EXIT_FAILURE;
}
- event_set_log_callback(&libevent_log_cb);
try {
int ret = AppInitRPC(argc, argv);
diff --git a/src/support/events.h b/src/support/events.h
index f89daf6d..a118ba32 100644
--- a/src/support/events.h
+++ b/src/support/events.h
@@ -24,8 +24,6 @@ typedef std::unique_ptr<struct type, type##_deleter> raii_##type
MAKE_RAII(event_base);
MAKE_RAII(event);
MAKE_RAII(evhttp);
-MAKE_RAII(evhttp_request);
-MAKE_RAII(evhttp_connection);
inline raii_event_base obtain_event_base() {
auto result = raii_event_base(event_base_new());
@@ -42,15 +40,4 @@ inline raii_evhttp obtain_evhttp(struct event_base* base) {
return raii_evhttp(evhttp_new(base));
}
-inline raii_evhttp_request obtain_evhttp_request(void(*cb)(struct evhttp_request *, void *), void *arg) {
- return raii_evhttp_request(evhttp_request_new(cb, arg));
-}
-
-inline raii_evhttp_connection obtain_evhttp_connection_base(struct event_base* base, std::string host, uint16_t port) {
- auto result = raii_evhttp_connection(evhttp_connection_base_new(base, nullptr, host.c_str(), port));
- if (!result.get())
- throw std::runtime_error("create connection failed");
- return result;
-}
-
#endif // BITCOIN_SUPPORT_EVENTS_H
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index e908ba8a..effd45fd 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -177,9 +177,9 @@ class TestBitcoinCli(BitcoinTestFramework):
conf_rpcport = "rpcport=" + str(node_rpc_port)
self.nodes[0].replace_in_config([(conf_rpcport, "#" + conf_rpcport)])
# prefer rpcport over rpcconnect
- assert_raises_process_error(1, "Could not connect to the server 127.0.0.1:1", self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}", "-rpcport=1").echo)
+ assert_raises_process_error(1, "Error while attempting to communicate with server 127.0.0.1:1 (Could not connect to the server)", self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}", "-rpcport=1").echo)
if have_ipv6:
- assert_raises_process_error(1, "Could not connect to the server ::1:1", self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}", "-rpcport=1").echo)
+ assert_raises_process_error(1, "Error while attempting to communicate with server ::1:1 (Could not connect to the server)", self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}", "-rpcport=1").echo)
assert_equal(BLOCKS, self.nodes[0].cli("-rpcconnect=127.0.0.1:18999", f'-rpcport={node_rpc_port}').getblockcount())
if have_ipv6:
diff --git a/test/functional/interface_ipc_cli.py b/test/functional/interface_ipc_cli.py
index 9ad704d3..93711937 100755
--- a/test/functional/interface_ipc_cli.py
+++ b/test/functional/interface_ipc_cli.py
@@ -41,8 +41,8 @@ class TestBitcoinIpcCli(BitcoinTestFramework):
self.log.info("Skipping a few checks because temporary directory path is too long")
http_auth_error = "error: Authorization failed: Incorrect rpcuser or rpcpassword were specified."
- http_connect_error = f"error: timeout on transient error: Could not connect to the server 127.0.0.1:{rpc_port(node.index)}\n\nMake sure the bitcoind server is running and that you are connecting to the correct RPC port.\nUse \"bitcoin-cli -help\" for more info.\n"
- ipc_connect_error = "error: timeout on transient error: Connection refused\n\nProbably bitcoin-node is not running or not listening on a unix socket. Can be started with:\n\n bitcoin-node -chain=regtest -ipcbind=unix\n"
+ http_connect_error = f"error: Error while attempting to communicate with server 127.0.0.1:{rpc_port(node.index)} (Could not connect to the server)\n\nMake sure the bitcoind server is running and that you are connecting to the correct RPC port.\nUse \"bitcoin-cli -help\" for more info.\n"
+ ipc_connect_error = "error: Connection refused\n\nProbably bitcoin-node is not running or not listening on a unix socket. Can be started with:\n\n bitcoin-node -chain=regtest -ipcbind=unix\n"
ipc_http_conflict = "error: -rpcconnect and -ipcconnect options cannot both be enabled\n"
for started in (True, False):
Why this scored 28/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.