From 2ca1460ae3a7217eaa8c5972515bf622bedadfce Mon Sep 17 00:00:00 2001 From: Matthew Zipkin Date: Fri, 7 Jul 2023 14:31:18 -0400 Subject: [PATCH] rpc: identify JSON-RPC 2.0 requests --- src/rpc/request.cpp | 19 +++++++++++++++++++ src/rpc/request.h | 6 ++++++ test/functional/interface_rpc.py | 14 ++++++++------ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index 12726ecc885..08e0658561b 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -167,6 +167,25 @@ void JSONRPCRequest::parse(const UniValue& valRequest) // Parse id now so errors from here on will have the id id = request.find_value("id"); + // Check for JSON-RPC 2.0 (default 1.1) + m_json_version = JSONRPCVersion::V1_LEGACY; + const UniValue& jsonrpc_version = request.find_value("jsonrpc"); + if (!jsonrpc_version.isNull()) { + if (!jsonrpc_version.isStr()) { + throw JSONRPCError(RPC_INVALID_REQUEST, "jsonrpc field must be a string"); + } + // The "jsonrpc" key was added in the 2.0 spec, but some older documentation + // incorrectly included {"jsonrpc":"1.0"} in a request object, so we + // maintain that for backwards compatibility. + if (jsonrpc_version.get_str() == "1.0") { + m_json_version = JSONRPCVersion::V1_LEGACY; + } else if (jsonrpc_version.get_str() == "2.0") { + m_json_version = JSONRPCVersion::V2; + } else { + throw JSONRPCError(RPC_INVALID_REQUEST, "JSON-RPC version not supported"); + } + } + // Parse method const UniValue& valMethod{request.find_value("method")}; if (valMethod.isNull()) diff --git a/src/rpc/request.h b/src/rpc/request.h index 116ebb8aac1..8b721726953 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -11,6 +11,11 @@ #include +enum class JSONRPCVersion { + V1_LEGACY, + V2 +}; + UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id); UniValue JSONRPCReplyObj(UniValue result, UniValue error, UniValue id); UniValue JSONRPCError(int code, const std::string& message); @@ -35,6 +40,7 @@ public: std::string authUser; std::string peerAddr; std::any context; + JSONRPCVersion m_json_version = JSONRPCVersion::V1_LEGACY; void parse(const UniValue& valRequest); }; diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py index 748d83858ae..075dd1c2bcb 100755 --- a/test/functional/interface_rpc.py +++ b/test/functional/interface_rpc.py @@ -161,8 +161,10 @@ class RPCInterfaceTest(BitcoinTestFramework): self.log.info("Testing nonstandard jsonrpc 1.0 version number is accepted...") self.test_batch_request(lambda idx: BatchOptions(request_fields={"jsonrpc": "1.0"})) - self.log.info("Testing unrecognized jsonrpc version number is accepted...") - self.test_batch_request(lambda idx: BatchOptions(request_fields={"jsonrpc": "2.1"})) + self.log.info("Testing unrecognized jsonrpc version number is rejected...") + self.test_batch_request(lambda idx: BatchOptions( + request_fields={"jsonrpc": "2.1"}, + response_fields={"result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "JSON-RPC version not supported"}})) def test_http_status_codes(self): self.log.info("Testing HTTP status codes for JSON-RPC 1.1 requests...") @@ -188,11 +190,11 @@ class RPCInterfaceTest(BitcoinTestFramework): expect_http_rpc_status(500, RPC_INVALID_PARAMETER, self.nodes[0], "getblockhash", [42], 2, False) # force-send invalidly formatted requests response, status = send_json_rpc(self.nodes[0], {"jsonrpc": 2, "method": "getblockcount"}) - assert_equal(response, {"error": None, "id": None, "result": 0}) - assert_equal(status, 200) + assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "jsonrpc field must be a string"}}) + assert_equal(status, 400) response, status = send_json_rpc(self.nodes[0], {"jsonrpc": "3.0", "method": "getblockcount"}) - assert_equal(response, {"error": None, "id": None, "result": 0}) - assert_equal(status, 200) + assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "JSON-RPC version not supported"}}) + assert_equal(status, 400) self.log.info("Testing HTTP status codes for JSON-RPC 2.0 notifications...") # Not notification: id exists