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) 2020-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"""A limited-functionality wallet, which may replace a real wallet in tests"""7from copy import deepcopy8from decimal import Decimal9from enum import Enum10from typing import (11Any,12Optional,13)14from test_framework.address import (15address_to_scriptpubkey,16create_deterministic_address_bcrt1_p2tr_op_true,17key_to_p2pkh,18key_to_p2sh_p2wpkh,19key_to_p2wpkh,20output_key_to_p2tr,21)22from test_framework.blocktools import COINBASE_MATURITY23from test_framework.descriptors import descsum_create24from test_framework.key import (25ECKey,26compute_xonly_pubkey,27)28from test_framework.messages import (29COIN,30COutPoint,31CTransaction,32CTxIn,33CTxInWitness,34CTxOut,35hash256,36)37from test_framework.script import (38CScript,39OP_NOP,40OP_RETURN,41OP_TRUE,42sign_input_legacy,43taproot_construct,44)45from test_framework.script_util import (46bulk_vout,47key_to_p2pk_script,48key_to_p2pkh_script,49key_to_p2sh_p2wpkh_script,50key_to_p2wpkh_script,51)52from test_framework.util import (53assert_equal,54assert_greater_than_or_equal,55get_fee,56)57from test_framework.wallet_util import (58bytes_to_wif,59generate_keypair,60)62DEFAULT_FEE = Decimal("0.0001")64class MiniWalletMode(Enum):65"""Determines the transaction type the MiniWallet is creating and spending.67For most purposes, the default mode ADDRESS_OP_TRUE should be sufficient;68it simply uses a fixed bech32m P2TR address whose coins are spent with a69witness stack of OP_TRUE, i.e. following an anyone-can-spend policy.70However, if the transactions need to be modified by the user (e.g. prepending71scriptSig for testing opcodes that are activated by a soft-fork), or the txs72should contain an actual signature, the raw modes RAW_OP_TRUE and RAW_P2PK73can be useful. In order to avoid mixing of UTXOs between different MiniWallet74instances, a tag name can be passed to the default mode, to create different75output scripts. Note that the UTXOs from the pre-generated test chain can76only be spent if no tag is passed. Summary of modes:78| output | | tx is | can modify | needs79mode | description | address | standard | scriptSig | signing80----------------+-------------------+-----------+----------+------------+----------81ADDRESS_OP_TRUE | anyone-can-spend | bech32m | yes | no | no82RAW_OP_TRUE | anyone-can-spend | - (raw) | no | yes | no83RAW_P2PK | pay-to-public-key | - (raw) | yes | yes | yes84"""85ADDRESS_OP_TRUE = 186RAW_OP_TRUE = 287RAW_P2PK = 390class MiniWallet:91def __init__(self, test_node, *, mode=MiniWalletMode.ADDRESS_OP_TRUE, tag_name=None):92self._test_node = test_node93self._utxos = []94self._mode = mode96assert isinstance(mode, MiniWalletMode)97if mode == MiniWalletMode.RAW_OP_TRUE:98assert tag_name is None99self._scriptPubKey = bytes(CScript([OP_TRUE]))100elif mode == MiniWalletMode.RAW_P2PK:101# use simple deterministic private key (k=1)102assert tag_name is None103self._priv_key = ECKey()104self._priv_key.set((1).to_bytes(32, 'big'), True)105pub_key = self._priv_key.get_pubkey()106self._scriptPubKey = key_to_p2pk_script(pub_key.get_bytes())107elif mode == MiniWalletMode.ADDRESS_OP_TRUE:108internal_key = None if tag_name is None else compute_xonly_pubkey(hash256(tag_name.encode()))[0]109self._address, self._taproot_info = create_deterministic_address_bcrt1_p2tr_op_true(internal_key)110self._scriptPubKey = address_to_scriptpubkey(self._address)112# When the pre-mined test framework chain is used, it contains coinbase113# outputs to the MiniWallet's default address in blocks 76-100114# (see method BitcoinTestFramework._initialize_chain())115# The MiniWallet needs to rescan_utxos() in order to account116# for those mature UTXOs, so that all txs spend confirmed coins117self.rescan_utxos()119def _create_utxo(self, *, txid, vout, value, height, coinbase, confirmations):120return {"txid": txid, "vout": vout, "value": value, "height": height, "coinbase": coinbase, "confirmations": confirmations}122def _bulk_tx(self, tx, target_vsize):123"""Pad a transaction with extra outputs until it reaches a target vsize.124returns the tx125"""126tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN])))127bulk_vout(tx, target_vsize)130def get_balance(self):131return sum(u['value'] for u in self._utxos)133def rescan_utxos(self, *, include_mempool=True):134"""Drop all utxos and rescan the utxo set"""135self._utxos = []136res = self._test_node.scantxoutset(action="start", scanobjects=[self.get_descriptor()])137assert_equal(True, res['success'])138for utxo in res['unspents']:139self._utxos.append(140self._create_utxo(txid=utxo["txid"],141vout=utxo["vout"],142value=utxo["amount"],143height=utxo["height"],144coinbase=utxo["coinbase"],145confirmations=res["height"] - utxo["height"] + 1))146if include_mempool:147mempool = self._test_node.getrawmempool(verbose=True)148# Sort tx by ancestor count. See BlockAssembler::SortForBlock in src/node/miner.cpp149sorted_mempool = sorted(mempool.items(), key=lambda item: (item[1]["ancestorcount"], int(item[0], 16)))150for txid, _ in sorted_mempool:151self.scan_tx(self._test_node.getrawtransaction(txid=txid, verbose=True))153def scan_tx(self, tx):154"""Scan the tx and adjust the internal list of owned utxos"""155for spent in tx["vin"]:156# Mark spent. This may happen when the caller has ownership of a157# utxo that remained in this wallet. For example, by passing158# mark_as_spent=False to get_utxo or by using an utxo returned by a159# create_self_transfer* call.160try:161self.get_utxo(txid=spent["txid"], vout=spent["vout"])162except StopIteration:163pass164for out in tx['vout']:165if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():166self._utxos.append(self._create_utxo(txid=tx["txid"], vout=out["n"], value=out["value"], height=0, coinbase=False, confirmations=0))168def scan_txs(self, txs):169for tx in txs:170self.scan_tx(tx)172def sign_tx(self, tx, fixed_length=True):173if self._mode == MiniWalletMode.RAW_P2PK:174# for exact fee calculation, create only signatures with fixed size by default (>49.89% probability):175# 65 bytes: high-R val (33 bytes) + low-S val (32 bytes)176# with the DER header/skeleton data of 6 bytes added, plus 2 bytes scriptSig overhead177# (OP_PUSHn and SIGHASH_ALL), this leads to a scriptSig target size of 73 bytes178tx.vin[0].scriptSig = b''179while not len(tx.vin[0].scriptSig) == 73:180tx.vin[0].scriptSig = b''181sign_input_legacy(tx, 0, self._scriptPubKey, self._priv_key)182if not fixed_length:183break184elif self._mode == MiniWalletMode.RAW_OP_TRUE:185for i in tx.vin:186i.scriptSig = CScript([OP_NOP] * 43) # pad to identical size187elif self._mode == MiniWalletMode.ADDRESS_OP_TRUE:188tx.wit.vtxinwit = [CTxInWitness()] * len(tx.vin)189for i in tx.wit.vtxinwit:190assert_equal(len(self._taproot_info.leaves), 1)191leaf_info = list(self._taproot_info.leaves.values())[0]192i.scriptWitness.stack = [193leaf_info.script,194bytes([leaf_info.version | self._taproot_info.negflag]) + self._taproot_info.internal_pubkey,195]196else:197assert False199def generate(self, num_blocks, **kwargs):200"""Generate blocks with coinbase outputs to the internal address, and call rescan_utxos"""201blocks = self._test_node.generatetodescriptor(num_blocks, self.get_descriptor(), **kwargs)202# Calling rescan_utxos here makes sure that after a generate the utxo203# set is in a clean state. For example, the wallet will update204# - if the caller consumed utxos, but never used them205# - if the caller sent a transaction that is not mined or got rbf'd206# - after block re-orgs207# - the utxo height for mined mempool txs208# - However, the wallet will not consider remaining mempool txs209self.rescan_utxos()210return blocks212def get_output_script(self):213return self._scriptPubKey215def get_descriptor(self):216return descsum_create(f'raw({self._scriptPubKey.hex()})')218def get_address(self):219assert_equal(self._mode, MiniWalletMode.ADDRESS_OP_TRUE)220return self._address222def get_utxo(self, *, txid: str = '', vout: Optional[int] = None, mark_as_spent=True, confirmed_only=False) -> dict:223"""224Returns a utxo and marks it as spent (pops it from the internal list)226Args:227txid: get the first utxo we find from a specific transaction228"""229self._utxos = sorted(self._utxos, key=lambda k: (k['value'], -k['height'])) # Put the largest utxo last230blocks_height = self._test_node.getblockchaininfo()['blocks']231mature_coins = list(filter(lambda utxo: not utxo['coinbase'] or COINBASE_MATURITY - 1 <= blocks_height - utxo['height'], self._utxos))232if txid:233utxo_filter: Any = filter(lambda utxo: txid == utxo['txid'], self._utxos)234else:235utxo_filter = reversed(mature_coins) # By default the largest utxo236if vout is not None:237utxo_filter = filter(lambda utxo: vout == utxo['vout'], utxo_filter)238if confirmed_only:239utxo_filter = filter(lambda utxo: utxo['confirmations'] > 0, utxo_filter)240index = self._utxos.index(next(utxo_filter))241if mark_as_spent:242return self._utxos.pop(index)243else:244return self._utxos[index]246def get_utxos(self, *, include_immature_coinbase=False, mark_as_spent=True, confirmed_only=False):247"""Returns the list of all utxos and optionally mark them as spent"""248if not include_immature_coinbase:249blocks_height = self._test_node.getblockchaininfo()['blocks']250utxo_filter = filter(lambda utxo: not utxo['coinbase'] or COINBASE_MATURITY - 1 <= blocks_height - utxo['height'], self._utxos)
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.