Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2018-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Tests some generic aspects of the RPC interface."""7import json8import os9from dataclasses import dataclass10from test_framework.test_framework import BitcoinTestFramework11from test_framework.util import JSONRPCException, assert_equal, assert_greater_than_or_equal12from threading import Thread13from typing import Optional16RPC_INVALID_PARAMETER = -817RPC_METHOD_NOT_FOUND = -3260118RPC_INVALID_REQUEST = -3260019RPC_PARSE_ERROR = -3270022@dataclass23class BatchOptions:24version: Optional[int] = None25notification: bool = False26request_fields: Optional[dict] = None27response_fields: Optional[dict] = None30def format_request(options, idx, fields):31request = {}32if options.version == 1:33request.update(version="1.1")34elif options.version == 2:35request.update(jsonrpc="2.0")36elif options.version is not None:37raise NotImplementedError(f"Unknown JSONRPC version {options.version}")38if not options.notification:39request.update(id=idx)40request.update(fields)41if options.request_fields:42request.update(options.request_fields)43return request46def format_response(options, idx, fields):47if options.version == 2 and options.notification:48return None49response = {}50if not options.notification:51response.update(id=idx)52if options.version == 2:53response.update(jsonrpc="2.0")54else:55response.update(result=None, error=None)56response.update(fields)57if options.response_fields:58response.update(options.response_fields)59return response62def send_raw_rpc(node, raw_body: bytes) -> tuple[object, int]:63return node._request("POST", "/", raw_body)66def send_json_rpc(node, body: object) -> tuple[object, int]:67raw = json.dumps(body).encode("utf-8")68return send_raw_rpc(node, raw)71def expect_http_rpc_status(expected_http_status, expected_rpc_error_code, node, method, params, version=1, notification=False):72req = format_request(BatchOptions(version, notification), 0, {"method": method, "params": params})73response, status = send_json_rpc(node, req)75if expected_rpc_error_code is not None:76assert_equal(response["error"]["code"], expected_rpc_error_code)78assert_equal(status, expected_http_status)81def test_work_queue_getblock(node, got_exceeded_error):82while not got_exceeded_error:83try:84node.cli("waitfornewblock", "500").send_cli()85except JSONRPCException as e:86assert_equal(e.error["message"], "non-JSON HTTP response with '503 Service Unavailable' from server: Work queue depth exceeded")87got_exceeded_error.append(True)90class RPCInterfaceTest(BitcoinTestFramework):91def set_test_params(self):92self.num_nodes = 193self.setup_clean_chain = True94self.supports_cli = False96def test_getrpcinfo(self):97self.log.info("Testing getrpcinfo...")99info = self.nodes[0].getrpcinfo()100assert_equal(len(info['active_commands']), 1)102command = info['active_commands'][0]103assert_equal(command['method'], 'getrpcinfo')104assert_greater_than_or_equal(command['duration'], 0)105assert_equal(info['logpath'], os.path.join(self.nodes[0].chain_path, 'debug.log'))107def test_batch_request(self, call_options):108calls = [109# A basic request that will work fine.110{"method": "getblockcount"},111# Request that will fail. The whole batch request should still112# work fine.113{"method": "invalidmethod"},114# Another call that should succeed.115{"method": "getblockhash", "params": [0]},116# Invalid request format117{"pizza": "sausage"}118]119results = [120{"result": 0},121{"error": {"code": RPC_METHOD_NOT_FOUND, "message": "Method not found"}},122{"result": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"},123{"error": {"code": RPC_INVALID_REQUEST, "message": "Missing method"}},124]126request = []127response = []128for idx, (call, result) in enumerate(zip(calls, results), 1):129options = call_options(idx)130if options is None:131continue132request.append(format_request(options, idx, call))133r = format_response(options, idx, result)134if r is not None:135response.append(r)137rpc_response, http_status = send_json_rpc(self.nodes[0], request)138if len(response) == 0 and len(request) > 0:139assert_equal(http_status, 204)140assert_equal(rpc_response, None)141else:142assert_equal(http_status, 200)143assert_equal(rpc_response, response)145def test_batch_requests(self):146self.log.info("Testing empty batch request...")147self.test_batch_request(lambda idx: None)149self.log.info("Testing basic JSON-RPC 2.0 batch request...")150self.test_batch_request(lambda idx: BatchOptions(version=2))152self.log.info("Testing JSON-RPC 2.0 batch with notifications...")153self.test_batch_request(lambda idx: BatchOptions(version=2, notification=idx < 2))155self.log.info("Testing JSON-RPC 2.0 batch of ALL notifications...")156self.test_batch_request(lambda idx: BatchOptions(version=2, notification=True))158# JSONRPC 1.1 does not support batch requests, but test them for backwards compatibility.159self.log.info("Testing nonstandard JSON-RPC 1.1 batch request...")160self.test_batch_request(lambda idx: BatchOptions(version=1))162self.log.info("Testing nonstandard mixed JSON-RPC 1.1/2.0 batch request...")163self.test_batch_request(lambda idx: BatchOptions(version=2 if idx % 2 else 1))165self.log.info("Testing nonstandard batch request without version numbers...")166self.test_batch_request(lambda idx: BatchOptions())168self.log.info("Testing nonstandard batch request without version numbers or ids...")169self.test_batch_request(lambda idx: BatchOptions(notification=True))171self.log.info("Testing nonstandard jsonrpc 1.0 version number is accepted...")172self.test_batch_request(lambda idx: BatchOptions(request_fields={"jsonrpc": "1.0"}))174self.log.info("Testing unrecognized jsonrpc version number is rejected...")175self.test_batch_request(lambda idx: BatchOptions(176request_fields={"jsonrpc": "2.1"},177response_fields={"result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "JSON-RPC version not supported"}}))179def test_http_status_codes(self):180self.log.info("Testing HTTP status codes for JSON-RPC 1.1 requests...")181# OK182expect_http_rpc_status(200, None, self.nodes[0], "getblockhash", [0])183# Errors184expect_http_rpc_status(404, RPC_METHOD_NOT_FOUND, self.nodes[0], "invalidmethod", [])185expect_http_rpc_status(500, RPC_INVALID_PARAMETER, self.nodes[0], "getblockhash", [42])186# force-send empty request187response, status = send_raw_rpc(self.nodes[0], b"")188assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_PARSE_ERROR, "message": "Parse error"}})189assert_equal(status, 500)190# force-send invalidly formatted request191response, status = send_raw_rpc(self.nodes[0], b"this is bad")192assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_PARSE_ERROR, "message": "Parse error"}})193assert_equal(status, 500)195self.log.info("Testing HTTP status codes for JSON-RPC 2.0 requests...")196# OK197expect_http_rpc_status(200, None, self.nodes[0], "getblockhash", [0], 2, False)198# RPC errors but not HTTP errors199expect_http_rpc_status(200, RPC_METHOD_NOT_FOUND, self.nodes[0], "invalidmethod", [], 2, False)200expect_http_rpc_status(200, RPC_INVALID_PARAMETER, self.nodes[0], "getblockhash", [42], 2, False)201# force-send invalidly formatted requests202response, status = send_json_rpc(self.nodes[0], {"jsonrpc": 2, "method": "getblockcount"})203assert_equal(response, {"result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "jsonrpc field must be a string"}})204assert_equal(status, 400)205response, status = send_json_rpc(self.nodes[0], {"jsonrpc": "3.0", "method": "getblockcount"})206assert_equal(response, {"result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "JSON-RPC version not supported"}})207assert_equal(status, 400)209self.log.info("Testing HTTP status codes for JSON-RPC 2.0 notifications...")210# Not notification: id exists211response, status = send_json_rpc(self.nodes[0], {"jsonrpc": "2.0", "id": None, "method": "getblockcount"})212assert_equal(response["result"], 0)213assert_equal(status, 200)214# Not notification: JSON 1.1215expect_http_rpc_status(200, None, self.nodes[0], "getblockcount", [], 1)216# Not notification: has "id" field217expect_http_rpc_status(200, None, self.nodes[0], "getblockcount", [], 2, False)218block_count = self.nodes[0].getblockcount()219# Notification response status code: HTTP_NO_CONTENT220expect_http_rpc_status(204, None, self.nodes[0], "generatetoaddress", [1, "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqdku202"], 2, True)221# The command worked even though there was no response222assert_equal(block_count + 1, self.nodes[0].getblockcount())223# No error response for notifications even if they are invalid224expect_http_rpc_status(204, None, self.nodes[0], "generatetoaddress", [1, "invalid_address"], 2, True)225# Sanity check: command was not executed226assert_equal(block_count + 1, self.nodes[0].getblockcount())228def test_work_queue_exceeded(self):229self.log.info("Testing work queue exceeded...")230self.restart_node(0, ['-rpcworkqueue=1', '-rpcthreads=1'])231got_exceeded_error = []232threads = []233for _ in range(3):234t = Thread(target=test_work_queue_getblock, args=(self.nodes[0], got_exceeded_error))235t.start()236threads.append(t)237for t in threads:238t.join()240def run_test(self):241self.test_getrpcinfo()242self.test_batch_requests()243self.test_http_status_codes()244self.test_work_queue_exceeded()247if __name__ == '__main__':248RPCInterfaceTest(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.