You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
9.0 KiB
192 lines
9.0 KiB
#!/usr/bin/env python3 |
|
# Copyright (c) 2014-2020 The Bitcoin Core developers |
|
# Distributed under the MIT software license, see the accompanying |
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
|
"""Test the wallet keypool and interaction with wallet encryption/locking.""" |
|
|
|
import time |
|
from decimal import Decimal |
|
|
|
from test_framework.test_framework import BitcoinTestFramework |
|
from test_framework.util import assert_equal, assert_raises_rpc_error |
|
|
|
class KeyPoolTest(BitcoinTestFramework): |
|
def set_test_params(self): |
|
self.num_nodes = 1 |
|
|
|
def skip_test_if_missing_module(self): |
|
self.skip_if_no_wallet() |
|
|
|
def run_test(self): |
|
nodes = self.nodes |
|
addr_before_encrypting = nodes[0].getnewaddress() |
|
addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting) |
|
wallet_info_old = nodes[0].getwalletinfo() |
|
if not self.options.descriptors: |
|
assert addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid'] |
|
|
|
# Encrypt wallet and wait to terminate |
|
nodes[0].encryptwallet('test') |
|
if self.options.descriptors: |
|
# Import hardened derivation only descriptors |
|
nodes[0].walletpassphrase('test', 10) |
|
nodes[0].importdescriptors([ |
|
{ |
|
"desc": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/*h)#y4dfsj7n", |
|
"timestamp": "now", |
|
"range": [0,0], |
|
"active": True |
|
}, |
|
{ |
|
"desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1h/*h)#a0nyvl0k", |
|
"timestamp": "now", |
|
"range": [0,0], |
|
"active": True |
|
}, |
|
{ |
|
"desc": "sh(wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/2h/*h))#lmeu2axg", |
|
"timestamp": "now", |
|
"range": [0,0], |
|
"active": True |
|
}, |
|
{ |
|
"desc": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/3h/*h)#jkl636gm", |
|
"timestamp": "now", |
|
"range": [0,0], |
|
"active": True, |
|
"internal": True |
|
}, |
|
{ |
|
"desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/4h/*h)#l3crwaus", |
|
"timestamp": "now", |
|
"range": [0,0], |
|
"active": True, |
|
"internal": True |
|
}, |
|
{ |
|
"desc": "sh(wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/5h/*h))#qg8wa75f", |
|
"timestamp": "now", |
|
"range": [0,0], |
|
"active": True, |
|
"internal": True |
|
} |
|
]) |
|
nodes[0].walletlock() |
|
# Keep creating keys |
|
addr = nodes[0].getnewaddress() |
|
addr_data = nodes[0].getaddressinfo(addr) |
|
wallet_info = nodes[0].getwalletinfo() |
|
assert addr_before_encrypting_data['hdmasterfingerprint'] != addr_data['hdmasterfingerprint'] |
|
if not self.options.descriptors: |
|
assert addr_data['hdseedid'] == wallet_info['hdseedid'] |
|
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) |
|
|
|
# put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min) |
|
nodes[0].walletpassphrase('test', 12000) |
|
nodes[0].keypoolrefill(6) |
|
nodes[0].walletlock() |
|
wi = nodes[0].getwalletinfo() |
|
if self.options.descriptors: |
|
assert_equal(wi['keypoolsize_hd_internal'], 18) |
|
assert_equal(wi['keypoolsize'], 18) |
|
else: |
|
assert_equal(wi['keypoolsize_hd_internal'], 6) |
|
assert_equal(wi['keypoolsize'], 6) |
|
|
|
# drain the internal keys |
|
nodes[0].getrawchangeaddress() |
|
nodes[0].getrawchangeaddress() |
|
nodes[0].getrawchangeaddress() |
|
nodes[0].getrawchangeaddress() |
|
nodes[0].getrawchangeaddress() |
|
nodes[0].getrawchangeaddress() |
|
addr = set() |
|
# the next one should fail |
|
assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress) |
|
|
|
# drain the external keys |
|
addr.add(nodes[0].getnewaddress(address_type="bech32")) |
|
addr.add(nodes[0].getnewaddress(address_type="bech32")) |
|
addr.add(nodes[0].getnewaddress(address_type="bech32")) |
|
addr.add(nodes[0].getnewaddress(address_type="bech32")) |
|
addr.add(nodes[0].getnewaddress(address_type="bech32")) |
|
addr.add(nodes[0].getnewaddress(address_type="bech32")) |
|
assert len(addr) == 6 |
|
# the next one should fail |
|
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) |
|
|
|
# refill keypool with three new addresses |
|
nodes[0].walletpassphrase('test', 1) |
|
nodes[0].keypoolrefill(3) |
|
|
|
# test walletpassphrase timeout |
|
time.sleep(1.1) |
|
assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) |
|
|
|
# drain the keypool |
|
for _ in range(3): |
|
nodes[0].getnewaddress() |
|
assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress) |
|
|
|
nodes[0].walletpassphrase('test', 100) |
|
nodes[0].keypoolrefill(100) |
|
wi = nodes[0].getwalletinfo() |
|
if self.options.descriptors: |
|
assert_equal(wi['keypoolsize_hd_internal'], 300) |
|
assert_equal(wi['keypoolsize'], 300) |
|
else: |
|
assert_equal(wi['keypoolsize_hd_internal'], 100) |
|
assert_equal(wi['keypoolsize'], 100) |
|
|
|
# create a blank wallet |
|
nodes[0].createwallet(wallet_name='w2', blank=True, disable_private_keys=True) |
|
w2 = nodes[0].get_wallet_rpc('w2') |
|
|
|
# refer to initial wallet as w1 |
|
w1 = nodes[0].get_wallet_rpc(self.default_wallet_name) |
|
|
|
# import private key and fund it |
|
address = addr.pop() |
|
desc = w1.getaddressinfo(address)['desc'] |
|
if self.options.descriptors: |
|
res = w2.importdescriptors([{'desc': desc, 'timestamp': 'now'}]) |
|
else: |
|
res = w2.importmulti([{'desc': desc, 'timestamp': 'now'}]) |
|
assert_equal(res[0]['success'], True) |
|
w1.walletpassphrase('test', 100) |
|
|
|
res = w1.sendtoaddress(address=address, amount=0.00010000) |
|
nodes[0].generate(1) |
|
destination = addr.pop() |
|
|
|
# Using a fee rate (10 sat / byte) well above the minimum relay rate |
|
# creating a 5,000 sat transaction with change should not be possible |
|
assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) |
|
|
|
# creating a 10,000 sat transaction without change, with a manual input, should still be possible |
|
res = w2.walletcreatefundedpsbt(inputs=w2.listunspent(), outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) |
|
assert_equal("psbt" in res, True) |
|
|
|
# creating a 10,000 sat transaction without change should still be possible |
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) |
|
assert_equal("psbt" in res, True) |
|
# should work without subtractFeeFromOutputs if the exact fee is subtracted from the amount |
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008900}], options={"feeRate": 0.00010}) |
|
assert_equal("psbt" in res, True) |
|
|
|
# dust change should be removed |
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008800}], options={"feeRate": 0.00010}) |
|
assert_equal("psbt" in res, True) |
|
|
|
# create a transaction without change at the maximum fee rate, such that the output is still spendable: |
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008824}) |
|
assert_equal("psbt" in res, True) |
|
assert_equal(res["fee"], Decimal("0.00009706")) |
|
|
|
# creating a 10,000 sat transaction with a manual change address should be possible |
|
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010, "changeAddress": addr.pop()}) |
|
assert_equal("psbt" in res, True) |
|
|
|
|
|
if __name__ == '__main__': |
|
KeyPoolTest().main()
|
|
|