aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Zipkin <pinheadmz@gmail.com>2023-12-31 07:55:03 -0500
committerRyan Ofsky <ryan@ofsky.org>2024-03-07 19:13:10 -0500
commit09416f9ec445e4d6bb277400758083b0b4e8b174 (patch)
tree7ea49a7cff6313ea89c6e1ac281fdee0fd0ebf46
parent4202c170da37a3203e05a9f39f303d7df19b6d81 (diff)
downloadbitcoin-09416f9ec445e4d6bb277400758083b0b4e8b174.tar.xz
test: cover JSONRPC 2.0 requests, batches, and notifications
-rwxr-xr-xtest/functional/interface_rpc.py97
1 files changed, 87 insertions, 10 deletions
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
index 501a841805..748d83858a 100755
--- a/test/functional/interface_rpc.py
+++ b/test/functional/interface_rpc.py
@@ -14,9 +14,11 @@ from typing import Optional
import subprocess
-RPC_INVALID_PARAMETER = -8
-RPC_METHOD_NOT_FOUND = -32601
-RPC_INVALID_REQUEST = -32600
+RPC_INVALID_ADDRESS_OR_KEY = -5
+RPC_INVALID_PARAMETER = -8
+RPC_METHOD_NOT_FOUND = -32601
+RPC_INVALID_REQUEST = -32600
+RPC_PARSE_ERROR = -32700
@dataclass
@@ -98,9 +100,7 @@ class RPCInterfaceTest(BitcoinTestFramework):
assert_greater_than_or_equal(command['duration'], 0)
assert_equal(info['logpath'], os.path.join(self.nodes[0].chain_path, 'debug.log'))
- def test_batch_request(self):
- self.log.info("Testing basic JSON-RPC batch request...")
-
+ def test_batch_request(self, call_options):
calls = [
# A basic request that will work fine.
{"method": "getblockcount"},
@@ -109,6 +109,8 @@ class RPCInterfaceTest(BitcoinTestFramework):
{"method": "invalidmethod"},
# Another call that should succeed.
{"method": "getblockhash", "params": [0]},
+ # Invalid request format
+ {"pizza": "sausage"}
]
results = [
{"result": 0},
@@ -120,7 +122,9 @@ class RPCInterfaceTest(BitcoinTestFramework):
request = []
response = []
for idx, (call, result) in enumerate(zip(calls, results), 1):
- options = BatchOptions()
+ options = call_options(idx)
+ if options is None:
+ continue
request.append(format_request(options, idx, call))
response.append(format_response(options, idx, result))
@@ -128,11 +132,84 @@ class RPCInterfaceTest(BitcoinTestFramework):
assert_equal(http_status, 200)
assert_equal(rpc_response, response)
- def test_http_status_codes(self):
- self.log.info("Testing HTTP status codes for JSON-RPC requests...")
+ def test_batch_requests(self):
+ self.log.info("Testing empty batch request...")
+ self.test_batch_request(lambda idx: None)
+
+ self.log.info("Testing basic JSON-RPC 2.0 batch request...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2))
+
+ self.log.info("Testing JSON-RPC 2.0 batch with notifications...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2, notification=idx < 2))
+
+ self.log.info("Testing JSON-RPC 2.0 batch of ALL notifications...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2, notification=True))
+
+ # JSONRPC 1.1 does not support batch requests, but test them for backwards compatibility.
+ self.log.info("Testing nonstandard JSON-RPC 1.1 batch request...")
+ self.test_batch_request(lambda idx: BatchOptions(version=1))
+ self.log.info("Testing nonstandard mixed JSON-RPC 1.1/2.0 batch request...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2 if idx % 2 else 1))
+
+ self.log.info("Testing nonstandard batch request without version numbers...")
+ self.test_batch_request(lambda idx: BatchOptions())
+
+ self.log.info("Testing nonstandard batch request without version numbers or ids...")
+ self.test_batch_request(lambda idx: BatchOptions(notification=True))
+
+ 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"}))
+
+ def test_http_status_codes(self):
+ self.log.info("Testing HTTP status codes for JSON-RPC 1.1 requests...")
+ # OK
+ expect_http_rpc_status(200, None, self.nodes[0], "getblockhash", [0])
+ # Errors
expect_http_rpc_status(404, RPC_METHOD_NOT_FOUND, self.nodes[0], "invalidmethod", [])
expect_http_rpc_status(500, RPC_INVALID_PARAMETER, self.nodes[0], "getblockhash", [42])
+ # force-send empty request
+ response, status = send_raw_rpc(self.nodes[0], b"")
+ assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_PARSE_ERROR, "message": "Parse error"}})
+ assert_equal(status, 500)
+ # force-send invalidly formatted request
+ response, status = send_raw_rpc(self.nodes[0], b"this is bad")
+ assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_PARSE_ERROR, "message": "Parse error"}})
+ assert_equal(status, 500)
+
+ self.log.info("Testing HTTP status codes for JSON-RPC 2.0 requests...")
+ # OK
+ expect_http_rpc_status(200, None, self.nodes[0], "getblockhash", [0], 2, False)
+ # RPC errors and HTTP errors
+ expect_http_rpc_status(404, RPC_METHOD_NOT_FOUND, self.nodes[0], "invalidmethod", [], 2, False)
+ 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)
+ 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)
+
+ self.log.info("Testing HTTP status codes for JSON-RPC 2.0 notifications...")
+ # Not notification: id exists
+ response, status = send_json_rpc(self.nodes[0], {"jsonrpc": "2.0", "id": None, "method": "getblockcount"})
+ assert_equal(response["result"], 0)
+ assert_equal(status, 200)
+ # Not notification: JSON 1.1
+ expect_http_rpc_status(200, None, self.nodes[0], "getblockcount", [], 1)
+ # 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)
+ # The command worked even though there was no response
+ assert_equal(block_count + 1, self.nodes[0].getblockcount())
+ expect_http_rpc_status(500, RPC_INVALID_ADDRESS_OR_KEY, self.nodes[0], "generatetoaddress", [1, "invalid_address"], 2, True)
+ # Sanity check: command was not executed
+ assert_equal(block_count + 1, self.nodes[0].getblockcount())
def test_work_queue_exceeded(self):
self.log.info("Testing work queue exceeded...")
@@ -148,7 +225,7 @@ class RPCInterfaceTest(BitcoinTestFramework):
def run_test(self):
self.test_getrpcinfo()
- self.test_batch_request()
+ self.test_batch_requests()
self.test_http_status_codes()
self.test_work_queue_exceeded()