diff options
author | Matthew Zipkin <pinheadmz@gmail.com> | 2023-07-07 15:06:35 -0400 |
---|---|---|
committer | Matthew Zipkin <pinheadmz@gmail.com> | 2024-05-14 11:28:43 -0400 |
commit | e7ee80dcf2b68684eae96070875ea13a60e3e7b0 (patch) | |
tree | fb396c1ed541789358998482c5ac86c8fe946ad4 /src | |
parent | bf1a1f1662427fbf1a43bb951364eface469bdb7 (diff) | |
download | bitcoin-e7ee80dcf2b68684eae96070875ea13a60e3e7b0.tar.xz |
rpc: JSON-RPC 2.0 should not respond to "notifications"
For JSON-RPC 2.0 requests we need to distinguish between
a missing "id" field and "id":null. This is accomplished
by making the JSONRPCRequest id property a
std::optional<UniValue> with a default value of
UniValue::VNULL.
A side-effect of this change for non-2.0 requests is that request which do not
specify an "id" field will no longer return "id": null in the response.
Diffstat (limited to 'src')
-rw-r--r-- | src/httprpc.cpp | 31 | ||||
-rw-r--r-- | src/rpc/protocol.h | 1 | ||||
-rw-r--r-- | src/rpc/request.cpp | 10 | ||||
-rw-r--r-- | src/rpc/request.h | 6 |
4 files changed, 39 insertions, 9 deletions
diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 777ad32bbe..3eb34dbe6a 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -211,6 +211,12 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) const bool catch_errors{jreq.m_json_version == JSONRPCVersion::V2}; reply = JSONRPCExec(jreq, catch_errors); + if (jreq.IsNotification()) { + // Even though we do execute notifications, we do not respond to them + req->WriteReply(HTTP_NO_CONTENT); + return true; + } + // array of requests } else if (valRequest.isArray()) { // Check authorization for each request's method @@ -235,15 +241,32 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) reply = UniValue::VARR; for (size_t i{0}; i < valRequest.size(); ++i) { // Batches never throw HTTP errors, they are always just included - // in "HTTP OK" responses. + // in "HTTP OK" responses. Notifications never get any response. + UniValue response; try { jreq.parse(valRequest[i]); - reply.push_back(JSONRPCExec(jreq, /*catch_errors=*/true)); + response = JSONRPCExec(jreq, /*catch_errors=*/true); } catch (UniValue& e) { - reply.push_back(JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version)); + response = JSONRPCReplyObj(NullUniValue, std::move(e), jreq.id, jreq.m_json_version); } catch (const std::exception& e) { - reply.push_back(JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version)); + response = JSONRPCReplyObj(NullUniValue, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id, jreq.m_json_version); } + if (!jreq.IsNotification()) { + reply.push_back(std::move(response)); + } + } + // Return no response for an all-notification batch, but only if the + // batch request is non-empty. Technically according to the JSON-RPC + // 2.0 spec, an empty batch request should also return no response, + // However, if the batch request is empty, it means the request did + // not contain any JSON-RPC version numbers, so returning an empty + // response could break backwards compatibility with old RPC clients + // relying on previous behavior. Return an empty array instead of an + // empty response in this case to favor being backwards compatible + // over complying with the JSON-RPC 2.0 spec in this case. + if (reply.size() == 0 && valRequest.size() > 0) { + req->WriteReply(HTTP_NO_CONTENT); + return true; } } else diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h index 75e42e4c88..83a9010681 100644 --- a/src/rpc/protocol.h +++ b/src/rpc/protocol.h @@ -10,6 +10,7 @@ enum HTTPStatusCode { HTTP_OK = 200, + HTTP_NO_CONTENT = 204, HTTP_BAD_REQUEST = 400, HTTP_UNAUTHORIZED = 401, HTTP_FORBIDDEN = 403, diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index b5bf1e5ffa..ca424b4953 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -37,7 +37,7 @@ UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, return request; } -UniValue JSONRPCReplyObj(UniValue result, UniValue error, UniValue id, JSONRPCVersion jsonrpc_version) +UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version) { UniValue reply(UniValue::VOBJ); // Add JSON-RPC version number field in v2 only. @@ -52,7 +52,7 @@ UniValue JSONRPCReplyObj(UniValue result, UniValue error, UniValue id, JSONRPCVe if (jsonrpc_version == JSONRPCVersion::V1_LEGACY) reply.pushKV("result", NullUniValue); reply.pushKV("error", std::move(error)); } - reply.pushKV("id", std::move(id)); + if (id.has_value()) reply.pushKV("id", std::move(id.value())); return reply; } @@ -172,7 +172,11 @@ void JSONRPCRequest::parse(const UniValue& valRequest) const UniValue& request = valRequest.get_obj(); // Parse id now so errors from here on will have the id - id = request.find_value("id"); + if (request.exists("id")) { + id = request.find_value("id"); + } else { + id = std::nullopt; + } // Check for JSON-RPC 2.0 (default 1.1) m_json_version = JSONRPCVersion::V1_LEGACY; diff --git a/src/rpc/request.h b/src/rpc/request.h index a7c9af2216..e47f90af86 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -7,6 +7,7 @@ #define BITCOIN_RPC_REQUEST_H #include <any> +#include <optional> #include <string> #include <univalue.h> @@ -17,7 +18,7 @@ enum class JSONRPCVersion { }; UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); -UniValue JSONRPCReplyObj(UniValue result, UniValue error, UniValue id, JSONRPCVersion jsonrpc_version); +UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version); UniValue JSONRPCError(int code, const std::string& message); /** Generate a new RPC authentication cookie and write it to disk */ @@ -32,7 +33,7 @@ std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in); class JSONRPCRequest { public: - UniValue id; + std::optional<UniValue> id = UniValue::VNULL; std::string strMethod; UniValue params; enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE; @@ -43,6 +44,7 @@ public: JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY; void parse(const UniValue& valRequest); + [[nodiscard]] bool IsNotification() const { return !id.has_value() && m_json_version == JSONRPCVersion::V2; }; }; #endif // BITCOIN_RPC_REQUEST_H |