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"""Test RPC help output."""7from test_framework.test_framework import BitcoinTestFramework8from test_framework.util import assert_equal, assert_raises_rpc_error10from collections import defaultdict11import json12import os13import re16def parse_string(s):17assert_equal(s[0], '"')18assert_equal(s[-1], '"')19return s[1:-1]21def process_mapping(fname):22"""Find and parse conversion table in implementation file `fname`."""23cmds = []24string_params = []25in_rpcs = False26with open(fname, "r") as f:27for line in f:28line = line.rstrip()29if not in_rpcs:30if re.match(r'static const CRPCConvertParam vRPCConvertParams\[] =', line):31in_rpcs = True32else:33if line.startswith('};'):34in_rpcs = False35elif '{' in line and '"' in line:36# Match lines with ParamFormat::STRING37m_string = re.search(r'{ *("[^"]*") *, *([0-9]+) *, *("[^"]*") *, *ParamFormat::STRING *},?', line)38if m_string:39name = parse_string(m_string.group(1))40idx = int(m_string.group(2))41argname = parse_string(m_string.group(3))42string_params.append((name, idx, argname))43continue45# Match lines with ParamFormat::JSON and ParamFormat::JSON_OR_STRING46m_json = re.search(r'{ *("[^"]*") *, *([0-9]+) *, *("[^"]*") *(?:, *ParamFormat::(JSON_OR_STRING|JSON))? *},?', line)47if m_json:48name = parse_string(m_json.group(1))49idx = int(m_json.group(2))50argname = parse_string(m_json.group(3))51cmds.append((name, idx, argname))53assert not in_rpcs54return cmds, string_params56class HelpRpcTest(BitcoinTestFramework):57def set_test_params(self):58self.num_nodes = 159self.uses_wallet = None61def run_test(self):62self.test_client_conversion_table()63self.test_client_string_conversion_table()64self.test_categories()65self.dump_help()66if self.is_wallet_compiled():67self.wallet_help()69def test_client_conversion_table(self):70file_conversion_table = os.path.join(self.config["environment"]["SRCDIR"], 'src', 'rpc', 'client.cpp')71mapping_client, _ = process_mapping(file_conversion_table)72# Ignore echojson in client table73mapping_client = [m for m in mapping_client if m[0] != 'echojson']75mapping_server = self.nodes[0].help("dump_all_command_conversions")76# Filter all RPCs whether they need conversion77mapping_server_conversion = [tuple(m[:3]) for m in mapping_server if not m[3]]79# Only check if all RPC methods have been compiled (i.e. wallet is enabled)80if self.is_wallet_compiled() and sorted(mapping_client) != sorted(mapping_server_conversion):81raise AssertionError("RPC client conversion table ({}) and RPC server named arguments mismatch!\n{}".format(82file_conversion_table,83set(mapping_client).symmetric_difference(mapping_server_conversion),84))86# Check for conversion difference by argument name.87# It is preferable for API consistency that arguments with the same name88# have the same conversion, so bin by argument name.89all_methods_by_argname = defaultdict(list)90converts_by_argname = defaultdict(list)91for m in mapping_server:92all_methods_by_argname[m[2]].append(m[0])93converts_by_argname[m[2]].append(m[3])95for argname, convert in converts_by_argname.items():96if all(convert) != any(convert):97# Only allow dummy and psbt to fail consistency check98assert argname in ['dummy', "psbt"], ('WARNING: conversion mismatch for argument named %s (%s)' % (argname, list(zip(all_methods_by_argname[argname], converts_by_argname[argname]))))100def test_client_string_conversion_table(self):101file_conversion_table = os.path.join(self.config["environment"]["SRCDIR"], 'src', 'rpc', 'client.cpp')102_, string_params_client = process_mapping(file_conversion_table)103mapping_server = self.nodes[0].help("dump_all_command_conversions")104server_tuples = {tuple(m[:3]) for m in mapping_server}106# Filter string parameters based on wallet compilation status107if self.is_wallet_compiled():108# Check that every entry in string parameters exists on the server109stale_entries = [entry for entry in string_params_client if entry not in server_tuples]110if stale_entries:111raise AssertionError(f"String parameters contains entries not present on the server: {stale_entries}")112filtered_string_params = string_params_client113else:114available_string_params = [entry for entry in string_params_client if entry in server_tuples]115filtered_string_params = available_string_params117# Validate that all entries are legitimate server parameters118server_method_param_tuples = {(m[0], m[1], m[2]) for m in mapping_server}119invalid_entries = [entry for entry in filtered_string_params if entry not in server_method_param_tuples]120if invalid_entries:121raise AssertionError(f"String parameters contains invalid entries: {invalid_entries}")123def test_categories(self):124node = self.nodes[0]126# wrong argument count127assert_raises_rpc_error(-1, 'help', node.help, 'foo', 'bar')129# invalid argument130if not self.options.usecli:131assert_raises_rpc_error(-3, "JSON value of type number is not of expected type string", node.help, 0)133# help of unknown command134assert_equal(node.help('foo'), 'help: unknown command: foo')136# command titles137titles = [line[3:-3] for line in node.help().splitlines() if line.startswith('==')]139components = ['Blockchain', 'Control', 'Mining', 'Network', 'Rawtransactions', 'Util']141if self.is_wallet_compiled():142components.append('Wallet')144if self.is_external_signer_compiled():145components.append('Signer')147if self.is_zmq_compiled():148components.append('Zmq')150assert_equal(titles, sorted(components))152def dump_help(self):153dump_dir = os.path.join(self.options.tmpdir, 'rpc_help_dump')154os.mkdir(dump_dir)155calls = [line.split(' ', 1)[0] for line in self.nodes[0].help().splitlines() if line and not line.startswith('==')]156for call in calls:157help_text = self.nodes[0].help(call)158with open(os.path.join(dump_dir, call), 'w') as f:159# Make sure the node can generate the help at runtime without crashing160f.write(help_text)161# Make sure any curl examples have a JSON-RPC payload that is valid JSON162for match in re.finditer(r"--data-binary '(.*)' -H", help_text):163payload = match.group(1)164try:165json.loads(payload)166except json.JSONDecodeError as e:167raise AssertionError(f"HelpExampleRpc for '{call}' is not valid JSON: {payload!r}\n{e}")169def wallet_help(self):170assert 'getnewaddress ( "label" "address_type" )' in self.nodes[0].help('getnewaddress')171self.restart_node(0, extra_args=['-nowallet=1'])172assert 'getnewaddress ( "label" "address_type" )' in self.nodes[0].help('getnewaddress')174if __name__ == '__main__':175HelpRpcTest(__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.