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 wallet keypool and interaction with wallet encryption/locking."""7from decimal import Decimal9from test_framework.test_framework import BitcoinTestFramework10from test_framework.descriptors import descsum_create11from test_framework.extendedkey import ExtendedPrivateKey12from test_framework.util import (13assert_equal,14assert_not_equal,15assert_raises_rpc_error,16)17from test_framework.wallet_util import WalletUnlock19class KeyPoolTest(BitcoinTestFramework):20def set_test_params(self):21self.num_nodes = 123def skip_test_if_missing_module(self):24self.skip_if_no_wallet()26def run_test(self):27nodes = self.nodes28addr_before_encrypting = nodes[0].getnewaddress()29addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting)31# Encrypt wallet and wait to terminate32nodes[0].encryptwallet('test')33# Import hardened derivation only descriptors34nodes[0].walletpassphrase('test', 10)35nodes[0].importdescriptors([36{37"desc": descsum_create(f"wpkh({ExtendedPrivateKey.generate().to_string()}/0h/*h)"),38"timestamp": "now",39"range": [0,0],40"active": True41},42{43"desc": descsum_create(f"pkh({ExtendedPrivateKey.generate().to_string()}/1h/*h)"),44"timestamp": "now",45"range": [0,0],46"active": True47},48{49"desc": descsum_create(f"sh(wpkh({ExtendedPrivateKey.generate().to_string()}/2h/*h))"),50"timestamp": "now",51"range": [0,0],52"active": True53},54{55"desc": descsum_create(f"wpkh({ExtendedPrivateKey.generate().to_string()}/3h/*h)"),56"timestamp": "now",57"range": [0,0],58"active": True,59"internal": True60},61{62"desc": descsum_create(f"pkh({ExtendedPrivateKey.generate().to_string()}/4h/*h)"),63"timestamp": "now",64"range": [0,0],65"active": True,66"internal": True67},68{69"desc": descsum_create(f"sh(wpkh({ExtendedPrivateKey.generate().to_string()}/5h/*h))"),70"timestamp": "now",71"range": [0,0],72"active": True,73"internal": True74}75])76nodes[0].walletlock()77# Keep creating keys78addr = nodes[0].getnewaddress()79addr_data = nodes[0].getaddressinfo(addr)80assert_not_equal(addr_before_encrypting_data['hdmasterfingerprint'], addr_data['hdmasterfingerprint'])81assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)83# put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min)84with WalletUnlock(nodes[0], 'test'):85nodes[0].keypoolrefill(6)86wi = nodes[0].getwalletinfo()87assert_equal(wi['keypoolsize_hd_internal'], 24)88assert_equal(wi['keypoolsize'], 24)90# drain the internal keys91nodes[0].getrawchangeaddress()92nodes[0].getrawchangeaddress()93nodes[0].getrawchangeaddress()94nodes[0].getrawchangeaddress()95nodes[0].getrawchangeaddress()96nodes[0].getrawchangeaddress()97# remember keypool sizes98wi = nodes[0].getwalletinfo()99kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]100# the next one should fail101assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress)102# check that keypool sizes did not change103wi = nodes[0].getwalletinfo()104kp_size_after = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]105assert_equal(kp_size_before, kp_size_after)107# drain the external keys108addr = set()109addr.add(nodes[0].getnewaddress(address_type="bech32"))110addr.add(nodes[0].getnewaddress(address_type="bech32"))111addr.add(nodes[0].getnewaddress(address_type="bech32"))112addr.add(nodes[0].getnewaddress(address_type="bech32"))113addr.add(nodes[0].getnewaddress(address_type="bech32"))114addr.add(nodes[0].getnewaddress(address_type="bech32"))115assert_equal(len(addr), 6)116# remember keypool sizes117wi = nodes[0].getwalletinfo()118kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]119# the next one should fail120assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)121# check that keypool sizes did not change122wi = nodes[0].getwalletinfo()123kp_size_after = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]124assert_equal(kp_size_before, kp_size_after)126# refill keypool with three new addresses127nodes[0].walletpassphrase('test', 1)128nodes[0].keypoolrefill(3)130# test walletpassphrase timeout131# CScheduler relies on condition_variable::wait_until() which does not132# guarantee accurate timing. We'll wait up to 5 seconds to execute a 1133# second scheduled event.134nodes[0].wait_until(lambda: nodes[0].getwalletinfo()["unlocked_until"] == 0, timeout=5)136# drain the keypool137for _ in range(3):138nodes[0].getnewaddress()139assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress)141with WalletUnlock(nodes[0], 'test'):142nodes[0].keypoolrefill(100)143wi = nodes[0].getwalletinfo()144assert_equal(wi['keypoolsize_hd_internal'], 400)145assert_equal(wi['keypoolsize'], 400)147# create a blank wallet148nodes[0].createwallet(wallet_name='w2', blank=True, disable_private_keys=True)149w2 = nodes[0].get_wallet_rpc('w2')151# refer to initial wallet as w1152w1 = nodes[0].get_wallet_rpc(self.default_wallet_name)154# import private key and fund it155address = addr.pop()156desc = w1.getaddressinfo(address)['desc']157res = w2.importdescriptors([{'desc': desc, 'timestamp': 'now'}])158assert_equal(res[0]['success'], True)160with WalletUnlock(w1, 'test'):161res = w1.sendtoaddress(address=address, amount=0.00010000)162self.generate(nodes[0], 1)163destination = addr.pop()165# Using a fee rate (10 sat / byte) well above the minimum relay rate166# creating a 5,000 sat transaction with change should not be possible167assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], subtractFeeFromOutputs=[0], feeRate=0.00010)169# creating a 10,000 sat transaction without change, with a manual input, should still be possible170res = w2.walletcreatefundedpsbt(inputs=w2.listunspent(), outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.00010)171assert_equal("psbt" in res, True)173# creating a 10,000 sat transaction without change should still be possible174res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.00010)175assert_equal("psbt" in res, True)176# should work without subtractFeeFromOutputs if the exact fee is subtracted from the amount177res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008900}], feeRate=0.00010)178assert_equal("psbt" in res, True)180# dust change should be removed181res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008800}], feeRate=0.00010)182assert_equal("psbt" in res, True)184# create a transaction without change at the maximum fee rate, such that the output is still spendable:185res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.0008823)186assert_equal("psbt" in res, True)187assert_equal(res["fee"], Decimal("0.00009706"))189# creating a 10,000 sat transaction with a manual change address should be possible190res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.00010, changeAddress=addr.pop())191assert_equal("psbt" in res, True)193if __name__ == '__main__':194KeyPoolTest(__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.