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) 2017-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 bitcoin-cli"""7from decimal import Decimal8import re9import subprocess11from test_framework.blocktools import COINBASE_MATURITY12from test_framework.netutil import test_ipv6_local13from test_framework.test_framework import BitcoinTestFramework14from test_framework.util import (15assert_equal,16assert_greater_than_or_equal,17assert_raises_process_error,18assert_raises_rpc_error,19get_auth_cookie,20rpc_port,21)22import time24# The block reward of coinbaseoutput.nValue (50) BTC/block matures after25# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect26# node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 50 BTC/block.27BLOCKS = COINBASE_MATURITY + 128BALANCE = (BLOCKS - 100) * 5030JSON_PARSING_ERROR = 'error: Error parsing JSON: foo'31BLOCKS_VALUE_OF_ZERO = 'error: the first argument (number of blocks to generate, default: 1) must be an integer value greater than zero'32TOO_MANY_ARGS = 'error: too many arguments (maximum 2 for nblocks and maxtries)'33WALLET_NOT_LOADED = 'Requested wallet does not exist or is not loaded'34WALLET_NOT_SPECIFIED = (35"Multiple wallets are loaded. Please select which wallet to use by requesting the RPC "36"through the /wallet/<walletname> URI path. Or for the CLI, specify the \"-rpcwallet=<walletname>\" "37"option before the command (run \"bitcoin-cli -h\" for help or \"bitcoin-cli listwallets\" to see "38"which wallets are currently loaded)."39)42def cli_get_info_string_to_dict(cli_get_info_string):43"""Helper method to convert human-readable -getinfo into a dictionary"""44cli_get_info = {}45lines = cli_get_info_string.splitlines()46line_idx = 047ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')48while line_idx < len(lines):49# Remove ansi colour code50line = ansi_escape.sub('', lines[line_idx])51if "Balances" in line:52# When "Balances" appears in a line, all of the following lines contain "balance: wallet" until an empty line53cli_get_info["Balances"] = {}54while line_idx < len(lines) and not (lines[line_idx + 1] == ''):55line_idx += 156balance, wallet = lines[line_idx].strip().split(" ")57# Remove right justification padding58wallet = wallet.strip()59if wallet == '""':60# Set default wallet("") to empty string61wallet = ''62cli_get_info["Balances"][wallet] = balance.strip()63elif ": " in line:64key, value = line.split(": ")65if key == 'Wallet' and value == '""':66# Set default wallet("") to empty string67value = ''68if key == "Proxies" and value == "n/a":69# Set N/A to empty string to represent no proxy70value = ''71cli_get_info[key.strip()] = value.strip()72line_idx += 173return cli_get_info76class TestBitcoinCli(BitcoinTestFramework):77def set_test_params(self):78self.setup_clean_chain = True79self.num_nodes = 180self.uses_wallet = None82def skip_test_if_missing_module(self):83self.skip_if_no_cli()85def test_netinfo(self):86"""Test -netinfo output format."""87self.log.info("Test -netinfo header and separate local services line")88out = self.nodes[0].cli('-netinfo').send_cli().splitlines()89assert out[0].startswith(f"{self.config['environment']['CLIENT_NAME']} client ")90assert any(re.match(r"^Local services:.+network", line) for line in out)92self.log.info("Test -netinfo local services are moved to header if details are requested")93det = self.nodes[0].cli('-netinfo', '1').send_cli().splitlines()94self.log.debug(f"Test -netinfo 1 header output: {det[0]}")95assert re.match(rf"^{re.escape(self.config['environment']['CLIENT_NAME'])} client.+services nwl2?$", det[0])96assert not any(line.startswith("Local services:") for line in det)98def test_echojson_positional_equals(self):99"""Test JSON parameter parsing containing '=' with -named echojson"""100self.log.info("Test JSON parameter parsing containing '=' is handled correctly with -named")102# This should be treated as a positional JSON argument, not as a named103result = self.nodes[0].cli("-named", "echojson", '["key=value"]').send_cli()104assert_equal(result, [["key=value"]])106result = self.nodes[0].cli("-named", "echojson", '["key=value", "another=test"]').send_cli()107assert_equal(result, [["key=value", "another=test"]])109result = self.nodes[0].cli("-named", "echojson", '["data=test"]', "42").send_cli()110expected = [["data=test"], 42]111assert_equal(result, expected)113# This should be treated as a named parameter, as arg0 and arg1 are valid parameter names114result = self.nodes[0].cli("-named", "echojson", 'arg0=["data=test"]', 'arg1=42').send_cli()115expected = [["data=test"], 42]116assert_equal(result, expected)118def run_test(self):119"""Main test logic"""120self.test_echojson_positional_equals()122self.generate(self.nodes[0], BLOCKS)124self.log.info("Compare responses from getblockchaininfo RPC and `bitcoin-cli getblockchaininfo`")125cli_response = self.nodes[0].cli.getblockchaininfo()126rpc_response = self.nodes[0].getblockchaininfo()127assert_equal(cli_response, rpc_response)129self.log.info("Test named arguments")130assert_equal(self.nodes[0].cli.echo(0, 1, arg3=3, arg5=5), ['0', '1', None, '3', None, '5'])131assert_raises_rpc_error(-8, "Parameter arg1 specified twice both as positional and named argument", self.nodes[0].cli.echo, 0, 1, arg1=1)132assert_raises_rpc_error(-8, "Parameter arg1 specified twice both as positional and named argument", self.nodes[0].cli.echo, 0, None, 2, arg1=1)134self.log.info("Test that later cli named arguments values silently overwrite earlier ones")135assert_equal(self.nodes[0].cli("-named", "echo", "arg0=0", "arg1=1", "arg2=2", "arg1=3").send_cli(), ['0', '3', '2'])136assert_raises_rpc_error(-8, "Parameter args specified multiple times", self.nodes[0].cli("-named", "echo", "args=[0,1,2,3]", "4", "5", "6", ).send_cli)138user, password = get_auth_cookie(self.nodes[0].datadir_path, self.chain)140self.log.info("Test -stdinrpcpass option")141assert_equal(BLOCKS, self.nodes[0].cli(f'-rpcuser={user}', '-stdinrpcpass', input=password).getblockcount())142assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword were specified', self.nodes[0].cli(f'-rpcuser={user}', '-stdinrpcpass', input='foo').echo)144self.log.info("Test -stdin and -stdinrpcpass")145assert_equal(['foo', 'bar'], self.nodes[0].cli(f'-rpcuser={user}', '-stdin', '-stdinrpcpass', input=f'{password}\nfoo\nbar').echo())146assert_raises_process_error(1, 'Incorrect rpcuser or rpcpassword were specified', self.nodes[0].cli(f'-rpcuser={user}', '-stdin', '-stdinrpcpass', input='foo').echo)148self.log.info("Test -rpcclienttimeout=0 (no timeout)")149assert_equal(BLOCKS, self.nodes[0].cli('-rpcclienttimeout=0').getblockcount())151self.log.info("Test connecting to a non-existing server")152assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo)154self.log.info("Test handling of invalid ports in rpcconnect")155assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:notaport", self.nodes[0].cli("-rpcconnect=127.0.0.1:notaport").echo)156assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:-1", self.nodes[0].cli("-rpcconnect=127.0.0.1:-1").echo)157assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:0", self.nodes[0].cli("-rpcconnect=127.0.0.1:0").echo)158assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:65536", self.nodes[0].cli("-rpcconnect=127.0.0.1:65536").echo)160self.log.info("Checking for IPv6")161have_ipv6 = test_ipv6_local()162if not have_ipv6:163self.log.info("Skipping IPv6 tests")165if have_ipv6:166assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:notaport", self.nodes[0].cli("-rpcconnect=[::1]:notaport").echo)167assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:-1", self.nodes[0].cli("-rpcconnect=[::1]:-1").echo)168assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:0", self.nodes[0].cli("-rpcconnect=[::1]:0").echo)169assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:65536", self.nodes[0].cli("-rpcconnect=[::1]:65536").echo)171self.log.info("Test handling of invalid ports in rpcport")172assert_raises_process_error(1, "Invalid port provided in -rpcport: notaport", self.nodes[0].cli("-rpcport=notaport").echo)173assert_raises_process_error(1, "Invalid port provided in -rpcport: -1", self.nodes[0].cli("-rpcport=-1").echo)174assert_raises_process_error(1, "Invalid port provided in -rpcport: 0", self.nodes[0].cli("-rpcport=0").echo)175assert_raises_process_error(1, "Invalid port provided in -rpcport: 65536", self.nodes[0].cli("-rpcport=65536").echo)177self.log.info("Test port usage preferences")178node_rpc_port = rpc_port(self.nodes[0].index)179# Prevent bitcoin-cli from using existing rpcport in conf180conf_rpcport = "rpcport=" + str(node_rpc_port)181self.nodes[0].replace_in_config([(conf_rpcport, "#" + conf_rpcport)])182# prefer rpcport over rpcconnect183assert_raises_process_error(1, "Error while attempting to communicate with server 127.0.0.1:1 (Could not connect to the server)", self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}", "-rpcport=1").echo)184if have_ipv6:185assert_raises_process_error(1, "Error while attempting to communicate with server ::1:1 (Could not connect to the server)", self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}", "-rpcport=1").echo)187assert_equal(BLOCKS, self.nodes[0].cli("-rpcconnect=127.0.0.1:18999", f'-rpcport={node_rpc_port}').getblockcount())188if have_ipv6:189assert_equal(BLOCKS, self.nodes[0].cli("-rpcconnect=[::1]:18999", f'-rpcport={node_rpc_port}').getblockcount())191# prefer rpcconnect port over default192assert_equal(BLOCKS, self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}").getblockcount())193if have_ipv6:194assert_equal(BLOCKS, self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}").getblockcount())196# prefer rpcport over default197assert_equal(BLOCKS, self.nodes[0].cli(f'-rpcport={node_rpc_port}').getblockcount())198# Re-enable rpcport in conf if present199self.nodes[0].replace_in_config([("#" + conf_rpcport, conf_rpcport)])201self.log.info("Test connecting with non-existing RPC cookie file")202assert_raises_process_error(1, "Failed to read cookie file and no rpcpassword was specified.", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo)204self.log.info("Test connecting with neither cookie file, nor password")205assert_raises_process_error(1, "Cookie file was disabled via -norpccookiefile and no rpcpassword was specified.", self.nodes[0].cli("-norpccookiefile").echo)206assert_raises_process_error(1, "Cookie file was disabled via -norpccookiefile and no rpcpassword was specified.", self.nodes[0].cli("-norpccookiefile", "-rpcpassword=").echo)208self.log.info("Test connecting with invalid cookie file")209assert_raises_process_error(1, "Cookie file credentials were invalid and no rpcpassword was specified.", self.nodes[0].cli(f"-rpccookiefile={self.nodes[0].datadir_path / 'bitcoin.conf'}").echo)211self.log.info("Test connecting without RPC cookie file and with password arg")212assert_equal(BLOCKS, self.nodes[0].cli('-norpccookiefile', f'-rpcuser={user}', f'-rpcpassword={password}').getblockcount())214self.log.info("Test -getinfo with arguments fails")215assert_raises_process_error(1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help)217self.log.info("Test -getinfo with -color=never does not return ANSI escape codes")218assert "\u001b[0m" not in self.nodes[0].cli('-getinfo', '-color=never').send_cli()220self.log.info("Test -getinfo with -color=always returns ANSI escape codes")221assert "\u001b[0m" in self.nodes[0].cli('-getinfo', '-color=always').send_cli()223self.log.info("Test -getinfo with invalid value for -color option")224assert_raises_process_error(1, "Invalid value for -color option. Valid values: always, auto, never.", self.nodes[0].cli('-getinfo', '-color=foo').send_cli)226self.log.info("Test -getinfo returns expected network and blockchain info")227if self.is_wallet_compiled():228self.import_deterministic_coinbase_privkeys()229self.nodes[0].encryptwallet(password)230cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()231cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)233network_info = self.nodes[0].getnetworkinfo()234blockchain_info = self.nodes[0].getblockchaininfo()235assert_equal(int(cli_get_info['Version']), network_info['version'])236assert_equal(cli_get_info['Verification progress'], "%.4f%%" % (blockchain_info['verificationprogress'] * 100))237assert_equal(int(cli_get_info['Blocks']), blockchain_info['blocks'])238assert_equal(int(cli_get_info['Headers']), blockchain_info['headers'])239assert_equal(int(cli_get_info['Time offset (s)']), network_info['timeoffset'])240expected_network_info = f"in {network_info['connections_in']}, out {network_info['connections_out']}, total {network_info['connections']}"241assert_equal(cli_get_info["Network"], expected_network_info)242assert_equal(cli_get_info['Proxies'], network_info['networks'][0]['proxy'])243assert_equal(Decimal(cli_get_info['Difficulty']), blockchain_info['difficulty'])244assert_equal(cli_get_info['Chain'], blockchain_info['chain'])246self.log.info("Test -getinfo and bitcoin-cli return all proxies")247self.restart_node(0, extra_args=["-proxy=127.0.0.1:9050", "-i2psam=127.0.0.1:7656"])248network_info = self.nodes[0].getnetworkinfo()249cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli()250cli_get_info = cli_get_info_string_to_dict(cli_get_info_string)
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.