aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorMatthew Zipkin <pinheadmz@gmail.com>2023-07-07 15:06:35 -0400
committerMatthew Zipkin <pinheadmz@gmail.com>2024-05-14 11:28:43 -0400
commite7ee80dcf2b68684eae96070875ea13a60e3e7b0 (patch)
treefb396c1ed541789358998482c5ac86c8fe946ad4 /test
parentbf1a1f1662427fbf1a43bb951364eface469bdb7 (diff)
downloadbitcoin-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 'test')
-rwxr-xr-xtest/functional/interface_rpc.py27
-rw-r--r--test/functional/test_framework/authproxy.py9
2 files changed, 28 insertions, 8 deletions
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
index 824a7200cd..b08ca42796 100755
--- a/test/functional/interface_rpc.py
+++ b/test/functional/interface_rpc.py
@@ -46,8 +46,11 @@ def format_request(options, idx, fields):
def format_response(options, idx, fields):
+ if options.version == 2 and options.notification:
+ return None
response = {}
- response.update(id=None if options.notification else idx)
+ if not options.notification:
+ response.update(id=idx)
if options.version == 2:
response.update(jsonrpc="2.0")
else:
@@ -129,11 +132,17 @@ class RPCInterfaceTest(BitcoinTestFramework):
if options is None:
continue
request.append(format_request(options, idx, call))
- response.append(format_response(options, idx, result))
+ r = format_response(options, idx, result)
+ if r is not None:
+ response.append(r)
rpc_response, http_status = send_json_rpc(self.nodes[0], request)
- assert_equal(http_status, 200)
- assert_equal(rpc_response, response)
+ if len(response) == 0 and len(request) > 0:
+ assert_equal(http_status, 204)
+ assert_equal(rpc_response, None)
+ else:
+ assert_equal(http_status, 200)
+ assert_equal(rpc_response, response)
def test_batch_requests(self):
self.log.info("Testing empty batch request...")
@@ -193,10 +202,10 @@ class RPCInterfaceTest(BitcoinTestFramework):
expect_http_rpc_status(200, 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, {"id": None, "result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "jsonrpc field must be a string"}})
+ assert_equal(response, {"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, {"id": None, "result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "JSON-RPC version not supported"}})
+ assert_equal(response, {"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...")
@@ -209,10 +218,12 @@ class RPCInterfaceTest(BitcoinTestFramework):
# Not notification: has "id" field
expect_http_rpc_status(200, None, self.nodes[0], "getblockcount", [], 2, False)
block_count = self.nodes[0].getblockcount()
- expect_http_rpc_status(200, None, self.nodes[0], "generatetoaddress", [1, "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqdku202"], 2, True)
+ # Notification response status code: HTTP_NO_CONTENT
+ expect_http_rpc_status(204, None, self.nodes[0], "generatetoaddress", [1, "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqdku202"], 2, True)
# The command worked even though there was no response
assert_equal(block_count + 1, self.nodes[0].getblockcount())
- expect_http_rpc_status(200, RPC_INVALID_ADDRESS_OR_KEY, self.nodes[0], "generatetoaddress", [1, "invalid_address"], 2, True)
+ # No error response for notifications even if they are invalid
+ expect_http_rpc_status(204, None, self.nodes[0], "generatetoaddress", [1, "invalid_address"], 2, True)
# Sanity check: command was not executed
assert_equal(block_count + 1, self.nodes[0].getblockcount())
diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py
index 03042877b2..7edf9f3679 100644
--- a/test/functional/test_framework/authproxy.py
+++ b/test/functional/test_framework/authproxy.py
@@ -160,6 +160,15 @@ class AuthServiceProxy():
raise JSONRPCException({
'code': -342, 'message': 'missing HTTP response from server'})
+ # Check for no-content HTTP status code, which can be returned when an
+ # RPC client requests a JSON-RPC 2.0 "notification" with no response.
+ # Currently this is only possible if clients call the _request() method
+ # directly to send a raw request.
+ if http_response.status == HTTPStatus.NO_CONTENT:
+ if len(http_response.read()) != 0:
+ raise JSONRPCException({'code': -342, 'message': 'Content received with NO CONTENT status code'})
+ return None, http_response.status
+
content_type = http_response.getheader('Content-Type')
if content_type != 'application/json':
raise JSONRPCException(