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) 2016-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 label RPCs.7RPCs tested are:8- getaddressesbylabel9- listaddressgroupings10- setlabel11"""12from collections import defaultdict14from test_framework.blocktools import COINBASE_MATURITY15from test_framework.descriptors import descsum_create16from test_framework.test_framework import BitcoinTestFramework17from test_framework.util import assert_equal, assert_raises_rpc_error18from test_framework.wallet_util import test_address21class WalletLabelsTest(BitcoinTestFramework):22def set_test_params(self):23self.setup_clean_chain = True24self.num_nodes = 226def skip_test_if_missing_module(self):27self.skip_if_no_wallet()29def invalid_label_name_test(self):30node = self.nodes[0]31address = node.getnewaddress()32pubkey = node.getaddressinfo(address)['pubkey']33rpc_calls = [34[node.getnewaddress],35[node.setlabel, address],36[node.getaddressesbylabel],37[node.getreceivedbylabel],38[node.listsinceblock, node.getblockhash(0), 1, False, True, False],39]40response = node.importdescriptors([{41'desc': f'pkh({pubkey})',42'label': '*',43'timestamp': 'now',44}])46assert_equal(response[0]['success'], False)47assert_equal(response[0]['error']['code'], -11)48assert_equal(response[0]['error']['message'], "Invalid label name")50for rpc_call in rpc_calls:51assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, label="*")53def test_label_named_parameter_handling(self):54"""Test that getnewaddress with labels containing '=' characters is handled correctly in -named mode"""55self.log.info("Test getnewaddress label parameter handling")56node = self.nodes[0]58# Test getnewaddress with explicit named parameter containing '='59label_with_equals = "wallet=wallet"60result = node.cli("-named", "getnewaddress", f"label={label_with_equals}").send_cli()61address = result.strip()62addr_info = node.getaddressinfo(address)63assert_equal(addr_info.get('labels', []), [label_with_equals])65self.log.info("Test bitcoin-cli -named passes parameter containing '=' by position if it does not specify a known parameter name and is in a string position")66equals_label = "my=label"67result = node.cli("-named", "getnewaddress", equals_label).send_cli()68address = result.strip()69addr_info = node.getaddressinfo(address)70assert_equal(addr_info.get('labels', []), [equals_label])72self.log.info("getnewaddress label parameter handling test completed successfully")74def run_test(self):75# Check that there's no UTXO on the node76node = self.nodes[0]77assert_equal(len(node.listunspent()), 0)79self.log.info("Checking listlabels' invalid parameters")80assert_raises_rpc_error(-8, "Invalid 'purpose' argument, must be a known purpose string, typically 'send', or 'receive'.", node.listlabels, "notavalidpurpose")81assert_raises_rpc_error(-8, "Invalid 'purpose' argument, must be a known purpose string, typically 'send', or 'receive'.", node.listlabels, "unknown")83# Note each time we call generate, all generated coins go into84# the same address, so we call twice to get two addresses w/50 each85self.generatetoaddress(node, nblocks=1, address=node.getnewaddress(label='coinbase'))86self.generatetoaddress(node, nblocks=COINBASE_MATURITY + 1, address=node.getnewaddress(label='coinbase'))87assert_equal(node.getbalance(), 100)89# there should be 2 address groups90# each with 1 address with a balance of 50 Bitcoins91address_groups = node.listaddressgroupings()92assert_equal(len(address_groups), 2)93# the addresses aren't linked now, but will be after we send to the94# common address95linked_addresses = set()96for address_group in address_groups:97assert_equal(len(address_group), 1)98assert_equal(len(address_group[0]), 3)99assert_equal(address_group[0][1], 50)100assert_equal(address_group[0][2], 'coinbase')101linked_addresses.add(address_group[0][0])103# send 50 from each address to a third address not in this wallet104common_address = "msf4WtN1YQKXvNtvdFYt9JBnUD2FB41kjr"105node.sendmany(106amounts={common_address: 100},107subtractfeefrom=[common_address],108minconf=1,109)110# there should be 1 address group, with the previously111# unlinked addresses now linked (they both have 0 balance)112address_groups = node.listaddressgroupings()113assert_equal(len(address_groups), 1)114assert_equal(len(address_groups[0]), 2)115assert_equal(set([a[0] for a in address_groups[0]]), linked_addresses)116assert_equal([a[1] for a in address_groups[0]], [0, 0])118self.generate(node, 1)120# we want to reset so that the "" label has what's expected.121# otherwise we're off by exactly the fee amount as that's mined122# and matures in the next 100 blocks123amount_to_send = 1.0125# Create labels and make sure subsequent label API calls126# recognize the label/address associations.127labels = [Label(name) for name in ("a", "b", "c", "d", "e")]128for label in labels:129address = node.getnewaddress(label.name)130label.add_receive_address(address)131label.verify(node)133# Check listlabels when passing 'purpose'134node2_addr = self.nodes[1].getnewaddress()135node.setlabel(node2_addr, "node2_addr")136assert_equal(node.listlabels(purpose="send"), ["node2_addr"])137assert_equal(node.listlabels(purpose="receive"), sorted(['coinbase'] + [label.name for label in labels]))139# Check all labels are returned by listlabels.140assert_equal(node.listlabels(), sorted(['coinbase'] + [label.name for label in labels] + ["node2_addr"]))142# Send a transaction to each label.143for label in labels:144node.sendtoaddress(label.addresses[0], amount_to_send)145label.verify(node)147# Check the amounts received.148self.generate(node, 1)149for label in labels:150assert_equal(151node.getreceivedbyaddress(label.addresses[0]), amount_to_send)152assert_equal(node.getreceivedbylabel(label.name), amount_to_send)154for i, label in enumerate(labels):155to_label = labels[(i + 1) % len(labels)]156node.sendtoaddress(to_label.addresses[0], amount_to_send)157self.generate(node, 1)158for label in labels:159address = node.getnewaddress(label.name)160label.add_receive_address(address)161label.verify(node)162assert_equal(node.getreceivedbylabel(label.name), 2)163label.verify(node)164self.generate(node, COINBASE_MATURITY + 1)166# Check that setlabel can assign a label to a new unused address.167for label in labels:168address = node.getnewaddress()169node.setlabel(address, label.name)170label.add_address(address)171label.verify(node)172assert_raises_rpc_error(-11, "No addresses with label", node.getaddressesbylabel, "")174# Check that setlabel can change the label of an address from a175# different label.176change_label(node, labels[0].addresses[0], labels[0], labels[1])178# Check that setlabel can set the label of an address already179# in the label. This is a no-op.180change_label(node, labels[2].addresses[0], labels[2], labels[2])182self.invalid_label_name_test()183self.test_label_named_parameter_handling()185# This is a descriptor wallet test because of segwit v1+ addresses186self.log.info('Check watchonly labels')187node.createwallet(wallet_name='watch_only', disable_private_keys=True)188wallet_watch_only = node.get_wallet_rpc('watch_only')189BECH32_VALID = {190'✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn',191'✔️_VER16_PROG03': 'bcrt1sqqqqq8uhdgr',192'✔️_VER16_PROB02': 'bcrt1sqqqq4wstyw',193}194BECH32_INVALID = {195'❌_VER15_PROG41': 'bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqajlxj8',196'❌_VER16_PROB01': 'bcrt1sqq5r4036',197}198for l in BECH32_VALID:199ad = BECH32_VALID[l]200import_res = wallet_watch_only.importdescriptors([{"desc": descsum_create(f"addr({ad})"), "timestamp": "now", "label": l}])201assert_equal(import_res[0]["success"], True)202self.generatetoaddress(node, 1, ad)203assert_equal(wallet_watch_only.getaddressesbylabel(label=l), {ad: {'purpose': 'receive'}})204assert_equal(wallet_watch_only.getreceivedbylabel(label=l), 0)205for l in BECH32_INVALID:206ad = BECH32_INVALID[l]207import_res = wallet_watch_only.importdescriptors([{"desc": descsum_create(f"addr({ad})"), "timestamp": "now", "label": l}])208assert_equal(import_res[0]["success"], False)209assert_equal(import_res[0]["error"]["code"], -5)210assert_equal(import_res[0]["error"]["message"], "Address is not valid")213class Label:214def __init__(self, name):215# Label name216self.name = name217# Current receiving address associated with this label.218self.receive_address = None219# List of all addresses assigned with this label220self.addresses = []221# Map of address to address purpose222self.purpose = defaultdict(lambda: "receive")224def add_address(self, address):225assert_equal(address not in self.addresses, True)226self.addresses.append(address)228def add_receive_address(self, address):229self.add_address(address)231def verify(self, node):232if self.receive_address is not None:233assert self.receive_address in self.addresses234for address in self.addresses:235test_address(node, address, labels=[self.name])236assert self.name in node.listlabels()237assert_equal(238node.getaddressesbylabel(self.name),239{address: {"purpose": self.purpose[address]} for address in self.addresses})241def change_label(node, address, old_label, new_label):242assert_equal(address in old_label.addresses, True)243node.setlabel(address, new_label.name)245old_label.addresses.remove(address)246new_label.add_address(address)248old_label.verify(node)249new_label.verify(node)
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.