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) 2014-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 the listtransactions API."""7from decimal import Decimal8import time9import os10import shutil12from test_framework.blocktools import MAX_FUTURE_BLOCK_TIME13from test_framework.descriptors import descsum_create14from test_framework.extendedkey import ExtendedPrivateKey15from test_framework.test_framework import BitcoinTestFramework16from test_framework.util import (17assert_not_equal,18assert_array_result,19assert_equal,20assert_greater_than,21assert_raises_rpc_error,22find_vout_for_address,23)24from test_framework.wallet_util import get_generate_key27class ListTransactionsTest(BitcoinTestFramework):28def set_test_params(self):29self.num_nodes = 330# whitelist peers to speed up tx relay / mempool sync31self.noban_tx_relay = True33def skip_test_if_missing_module(self):34self.skip_if_no_wallet()36def run_test(self):37self.log.info("Test simple send from node0 to node1")38txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)39self.sync_all()40assert_array_result(self.nodes[0].listtransactions(),41{"txid": txid},42{"category": "send", "amount": Decimal("-0.1"), "confirmations": 0, "trusted": True})43assert_array_result(self.nodes[1].listtransactions(),44{"txid": txid},45{"category": "receive", "amount": Decimal("0.1"), "confirmations": 0, "trusted": False})46self.log.info("Test confirmations change after mining a block")47blockhash = self.generate(self.nodes[0], 1)[0]48blockheight = self.nodes[0].getblockheader(blockhash)['height']49assert_array_result(self.nodes[0].listtransactions(),50{"txid": txid},51{"category": "send", "amount": Decimal("-0.1"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight})52assert_array_result(self.nodes[1].listtransactions(),53{"txid": txid},54{"category": "receive", "amount": Decimal("0.1"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight})56self.log.info("Test send-to-self on node0")57txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2)58assert_array_result(self.nodes[0].listtransactions(),59{"txid": txid, "category": "send"},60{"amount": Decimal("-0.2")})61assert_array_result(self.nodes[0].listtransactions(),62{"txid": txid, "category": "receive"},63{"amount": Decimal("0.2")})65self.log.info("Test sendmany from node1: twice to self, twice to node0")66send_to = {self.nodes[0].getnewaddress(): 0.11,67self.nodes[1].getnewaddress(): 0.22,68self.nodes[0].getnewaddress(): 0.33,69self.nodes[1].getnewaddress(): 0.44}70txid = self.nodes[1].sendmany("", send_to)71self.sync_all()72assert_array_result(self.nodes[1].listtransactions(),73{"category": "send", "amount": Decimal("-0.11")},74{"txid": txid})75assert_array_result(self.nodes[0].listtransactions(),76{"category": "receive", "amount": Decimal("0.11")},77{"txid": txid})78assert_array_result(self.nodes[1].listtransactions(),79{"category": "send", "amount": Decimal("-0.22")},80{"txid": txid})81assert_array_result(self.nodes[1].listtransactions(),82{"category": "receive", "amount": Decimal("0.22")},83{"txid": txid})84assert_array_result(self.nodes[1].listtransactions(),85{"category": "send", "amount": Decimal("-0.33")},86{"txid": txid})87assert_array_result(self.nodes[0].listtransactions(),88{"category": "receive", "amount": Decimal("0.33")},89{"txid": txid})90assert_array_result(self.nodes[1].listtransactions(),91{"category": "send", "amount": Decimal("-0.44")},92{"txid": txid})93assert_array_result(self.nodes[1].listtransactions(),94{"category": "receive", "amount": Decimal("0.44")},95{"txid": txid})97self.run_externally_generated_address_test()98self.run_coinjoin_test()99self.run_invalid_parameters_test()100self.test_op_return()101self.test_alternate_witness_tx()102self.test_from_me_status_change()104def run_externally_generated_address_test(self):105"""Test behavior when receiving address is not in the address book."""107self.log.info("Setup the same wallet on two nodes")108# refill keypool otherwise the second node wouldn't recognize addresses generated on the first nodes109self.nodes[0].keypoolrefill(1000)110self.stop_nodes()111wallet0 = os.path.join(self.nodes[0].chain_path, self.default_wallet_name, "wallet.dat")112wallet2 = os.path.join(self.nodes[2].chain_path, self.default_wallet_name, "wallet.dat")113shutil.copyfile(wallet0, wallet2)114self.start_nodes()115# reconnect nodes116self.connect_nodes(0, 1)117self.connect_nodes(1, 2)118self.connect_nodes(2, 0)120addr1 = self.nodes[0].getnewaddress("pizza1", 'legacy')121addr2 = self.nodes[0].getnewaddress("pizza2", 'p2sh-segwit')122addr3 = self.nodes[0].getnewaddress("pizza3", 'bech32')124self.log.info("Send to externally generated addresses")125# send to an address beyond the next to be generated to test the keypool gap126self.nodes[1].sendtoaddress(addr3, "0.001")127self.generate(self.nodes[1], 1)129# send to an address that is already marked as used due to the keypool gap mechanics130self.nodes[1].sendtoaddress(addr2, "0.001")131self.generate(self.nodes[1], 1)133# send to self transaction134self.nodes[0].sendtoaddress(addr1, "0.001")135self.generate(self.nodes[0], 1)137self.log.info("Verify listtransactions is the same regardless of where the address was generated")138transactions0 = self.nodes[0].listtransactions()139transactions2 = self.nodes[2].listtransactions()141# normalize results: remove fields that normally could differ and sort142def normalize_list(txs):143for tx in txs:144tx.pop('label', None)145tx.pop('time', None)146tx.pop('timereceived', None)147txs.sort(key=lambda x: x['txid'])149normalize_list(transactions0)150normalize_list(transactions2)151assert_equal(transactions0, transactions2)153self.log.info("Verify labels are persistent on the node that generated the addresses")154assert_equal(['pizza1'], self.nodes[0].getaddressinfo(addr1)['labels'])155assert_equal(['pizza2'], self.nodes[0].getaddressinfo(addr2)['labels'])156assert_equal(['pizza3'], self.nodes[0].getaddressinfo(addr3)['labels'])158def run_coinjoin_test(self):159self.log.info('Check "coin-join" transaction')160input_0 = next(i for i in self.nodes[0].listunspent(query_options={"minimumAmount": 0.2}, include_unsafe=False))161input_1 = next(i for i in self.nodes[1].listunspent(query_options={"minimumAmount": 0.2}, include_unsafe=False))162raw_hex = self.nodes[0].createrawtransaction(163inputs=[164{165"txid": input_0["txid"],166"vout": input_0["vout"],167},168{169"txid": input_1["txid"],170"vout": input_1["vout"],171},172],173outputs={174self.nodes[0].getnewaddress(): 0.123,175self.nodes[1].getnewaddress(): 0.123,176},177)178raw_hex = self.nodes[0].signrawtransactionwithwallet(raw_hex)["hex"]179raw_hex = self.nodes[1].signrawtransactionwithwallet(raw_hex)["hex"]180txid_join = self.nodes[0].sendrawtransaction(hexstring=raw_hex, maxfeerate=0)181fee_join = self.nodes[0].getmempoolentry(txid_join)["fees"]["base"]182# Fee should be correct: assert_equal(fee_join, self.nodes[0].gettransaction(txid_join)['fee'])183# But it is not, see for example https://github.com/bitcoin/bitcoin/issues/14136:184assert_not_equal(fee_join, self.nodes[0].gettransaction(txid_join)["fee"])186def run_invalid_parameters_test(self):187self.log.info("Test listtransactions RPC parameter validity")188assert_raises_rpc_error(-8, 'Label argument must be a valid label name or "*".', self.nodes[0].listtransactions, label="")189self.nodes[0].listtransactions(label="*")190assert_raises_rpc_error(-8, "Negative count", self.nodes[0].listtransactions, count=-1)191assert_raises_rpc_error(-8, "Negative from", self.nodes[0].listtransactions, skip=-1)193def test_op_return(self):194"""Test if OP_RETURN outputs will be displayed correctly."""195raw_tx = self.nodes[0].createrawtransaction([], [{'data': 'aa'}])196funded_tx = self.nodes[0].fundrawtransaction(raw_tx)197signed_tx = self.nodes[0].signrawtransactionwithwallet(funded_tx['hex'])198tx_id = self.nodes[0].sendrawtransaction(signed_tx['hex'])200op_ret_tx = [tx for tx in self.nodes[0].listtransactions() if tx['txid'] == tx_id][0]202assert 'address' not in op_ret_tx204def test_from_me_status_change(self):205self.log.info("Test gettransaction after changing a transaction's 'from me' status")206self.nodes[0].createwallet("fromme")207default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)208wallet = self.nodes[0].get_wallet_rpc("fromme")210# The 'fee' field of gettransaction is only added when the transaction is 'from me'211# Run twice, once for a transaction in the mempool, again when it confirms212for confirm in [False, True]:213key = get_generate_key()214descriptor = descsum_create(f"wpkh({key.privkey})")215default_wallet.importdescriptors([{"desc": descriptor, "timestamp": "now"}])217send_res = default_wallet.send(outputs=[{key.p2wpkh_addr: 1}, {wallet.getnewaddress(): 1}])218assert_equal(send_res["complete"], True)219vout = find_vout_for_address(self.nodes[0], send_res["txid"], key.p2wpkh_addr)220utxos = [{"txid": send_res["txid"], "vout": vout}]221self.generate(self.nodes[0], 1, sync_fun=self.no_op)223# Send to the test wallet, ensuring that one input is for the descriptor we will import,224# and that there are other inputs belonging to only the sending wallet225send_res = default_wallet.send(outputs=[{wallet.getnewaddress(): 1.5}], inputs=utxos, add_inputs=True)226assert_equal(send_res["complete"], True)227txid = send_res["txid"]228self.nodes[0].syncwithvalidationinterfacequeue()229tx_info = wallet.gettransaction(txid)230assert "fee" not in tx_info231assert_equal(any(detail["category"] == "send" for detail in tx_info["details"]), False)233if confirm:234self.generate(self.nodes[0], 1, sync_fun=self.no_op)235# Mock time forward and generate blocks so that the import does not rescan the transaction236self.nodes[0].setmocktime(int(time.time()) + MAX_FUTURE_BLOCK_TIME + 1)237self.generate(self.nodes[0], 10, sync_fun=self.no_op)239import_res = wallet.importdescriptors([{"desc": descriptor, "timestamp": "now"}])240assert_equal(import_res[0]["success"], True)241# TODO: We should check that the fee matches, but since the transaction spends inputs242# not known to the wallet, it is incorrectly calculating the fee.243# assert_equal(wallet.gettransaction(txid)["fee"], fee)244tx_info = wallet.gettransaction(txid)245assert "fee" in tx_info246assert_equal(any(detail["category"] == "send" for detail in tx_info["details"]), True)248def check_tx_variants(self, wallet, txid, canonical_tx_hex, canonical_wtxid, alternate_wtxids):249"""Assert gettransaction and listtransactions report tx variants properly"""250tx_info = wallet.gettransaction(txid)
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.