|
4 | 4 | # file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
5 | 5 | """Tests some generic aspects of the RPC interface."""
|
6 | 6 |
|
| 7 | +import json |
7 | 8 | import os
|
8 |
| -from test_framework.authproxy import JSONRPCException |
| 9 | +from dataclasses import dataclass |
9 | 10 | from test_framework.test_framework import BitcoinTestFramework
|
10 | 11 | from test_framework.util import assert_equal, assert_greater_than_or_equal
|
11 | 12 | from threading import Thread
|
| 13 | +from typing import Optional |
12 | 14 | import subprocess
|
13 | 15 |
|
14 | 16 |
|
15 |
| -def expect_http_status(expected_http_status, expected_rpc_code, |
16 |
| - fcn, *args): |
17 |
| - try: |
18 |
| - fcn(*args) |
19 |
| - raise AssertionError(f"Expected RPC error {expected_rpc_code}, got none") |
20 |
| - except JSONRPCException as exc: |
21 |
| - assert_equal(exc.error["code"], expected_rpc_code) |
22 |
| - assert_equal(exc.http_status, expected_http_status) |
| 17 | +RPC_INVALID_PARAMETER = -8 |
| 18 | +RPC_METHOD_NOT_FOUND = -32601 |
| 19 | +RPC_INVALID_REQUEST = -32600 |
| 20 | + |
| 21 | + |
| 22 | +@dataclass |
| 23 | +class BatchOptions: |
| 24 | + version: Optional[int] = None |
| 25 | + notification: bool = False |
| 26 | + request_fields: Optional[dict] = None |
| 27 | + response_fields: Optional[dict] = None |
| 28 | + |
| 29 | + |
| 30 | +def format_request(options, idx, fields): |
| 31 | + request = {} |
| 32 | + if options.version == 1: |
| 33 | + request.update(version="1.1") |
| 34 | + elif options.version == 2: |
| 35 | + request.update(jsonrpc="2.0") |
| 36 | + elif options.version is not None: |
| 37 | + raise NotImplementedError(f"Unknown JSONRPC version {options.version}") |
| 38 | + if not options.notification: |
| 39 | + request.update(id=idx) |
| 40 | + request.update(fields) |
| 41 | + if options.request_fields: |
| 42 | + request.update(options.request_fields) |
| 43 | + return request |
| 44 | + |
| 45 | + |
| 46 | +def format_response(options, idx, fields): |
| 47 | + response = {} |
| 48 | + response.update(id=None if options.notification else idx) |
| 49 | + response.update(result=None, error=None) |
| 50 | + response.update(fields) |
| 51 | + if options.response_fields: |
| 52 | + response.update(options.response_fields) |
| 53 | + return response |
| 54 | + |
| 55 | + |
| 56 | +def send_raw_rpc(node, raw_body: bytes) -> tuple[object, int]: |
| 57 | + return node._request("POST", "/", raw_body) |
| 58 | + |
| 59 | + |
| 60 | +def send_json_rpc(node, body: object) -> tuple[object, int]: |
| 61 | + raw = json.dumps(body).encode("utf-8") |
| 62 | + return send_raw_rpc(node, raw) |
| 63 | + |
| 64 | + |
| 65 | +def expect_http_rpc_status(expected_http_status, expected_rpc_error_code, node, method, params, version=1, notification=False): |
| 66 | + req = format_request(BatchOptions(version, notification), 0, {"method": method, "params": params}) |
| 67 | + response, status = send_json_rpc(node, req) |
| 68 | + |
| 69 | + if expected_rpc_error_code is not None: |
| 70 | + assert_equal(response["error"]["code"], expected_rpc_error_code) |
| 71 | + |
| 72 | + assert_equal(status, expected_http_status) |
23 | 73 |
|
24 | 74 |
|
25 | 75 | def test_work_queue_getblock(node, got_exceeded_error):
|
@@ -51,34 +101,38 @@ def test_getrpcinfo(self):
|
51 | 101 | def test_batch_request(self):
|
52 | 102 | self.log.info("Testing basic JSON-RPC batch request...")
|
53 | 103 |
|
54 |
| - results = self.nodes[0].batch([ |
| 104 | + calls = [ |
55 | 105 | # A basic request that will work fine.
|
56 |
| - {"method": "getblockcount", "id": 1}, |
| 106 | + {"method": "getblockcount"}, |
57 | 107 | # Request that will fail. The whole batch request should still
|
58 | 108 | # work fine.
|
59 |
| - {"method": "invalidmethod", "id": 2}, |
| 109 | + {"method": "invalidmethod"}, |
60 | 110 | # Another call that should succeed.
|
61 |
| - {"method": "getblockhash", "id": 3, "params": [0]}, |
62 |
| - ]) |
63 |
| - |
64 |
| - result_by_id = {} |
65 |
| - for res in results: |
66 |
| - result_by_id[res["id"]] = res |
67 |
| - |
68 |
| - assert_equal(result_by_id[1]['error'], None) |
69 |
| - assert_equal(result_by_id[1]['result'], 0) |
70 |
| - |
71 |
| - assert_equal(result_by_id[2]['error']['code'], -32601) |
72 |
| - assert_equal(result_by_id[2]['result'], None) |
73 |
| - |
74 |
| - assert_equal(result_by_id[3]['error'], None) |
75 |
| - assert result_by_id[3]['result'] is not None |
| 111 | + {"method": "getblockhash", "params": [0]}, |
| 112 | + ] |
| 113 | + results = [ |
| 114 | + {"result": 0}, |
| 115 | + {"error": {"code": RPC_METHOD_NOT_FOUND, "message": "Method not found"}}, |
| 116 | + {"result": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"}, |
| 117 | + {"error": {"code": RPC_INVALID_REQUEST, "message": "Missing method"}}, |
| 118 | + ] |
| 119 | + |
| 120 | + request = [] |
| 121 | + response = [] |
| 122 | + for idx, (call, result) in enumerate(zip(calls, results), 1): |
| 123 | + options = BatchOptions() |
| 124 | + request.append(format_request(options, idx, call)) |
| 125 | + response.append(format_response(options, idx, result)) |
| 126 | + |
| 127 | + rpc_response, http_status = send_json_rpc(self.nodes[0], request) |
| 128 | + assert_equal(http_status, 200) |
| 129 | + assert_equal(rpc_response, response) |
76 | 130 |
|
77 | 131 | def test_http_status_codes(self):
|
78 | 132 | self.log.info("Testing HTTP status codes for JSON-RPC requests...")
|
79 | 133 |
|
80 |
| - expect_http_status(404, -32601, self.nodes[0].invalidmethod) |
81 |
| - expect_http_status(500, -8, self.nodes[0].getblockhash, 42) |
| 134 | + expect_http_rpc_status(404, RPC_METHOD_NOT_FOUND, self.nodes[0], "invalidmethod", []) |
| 135 | + expect_http_rpc_status(500, RPC_INVALID_PARAMETER, self.nodes[0], "getblockhash", [42]) |
82 | 136 |
|
83 | 137 | def test_work_queue_exceeded(self):
|
84 | 138 | self.log.info("Testing work queue exceeded...")
|
|
0 commit comments