mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-02 09:46:14 +00:00
db2effaca4cf82bf806596d16f9797d3692e2da7 scripted-diff: refactor: CWallet::Create() -> CreateNew() (David Gumberg)
27e021ebc0dd3517a71f3ddb38ed265a19693d4c wallet: Correctly log stats for encrypted messages. (David Gumberg)
d8bec61be233b9cb6d5db886e8f1c1f058288fb5 wallet: remove loading logic from CWallet::Create (David Gumberg)
f35acc893fb3378b2ad39608fe254d33af6cce9f refactor: wallet: Factor out `WriteVersion()` from `PopulateWalletFromDB()` (David Gumberg)
e12ff8aca049ec7b054cb3047a167c7ce8dbd421 test: wallet: Split create and load (David Gumberg)
70dbc79b09acf7b1515532ee20c7533c938ffb70 wallet: Use CWallet::LoadExisting() for loading existing wallets. (David Gumberg)
ae66e011646266abb67b31027bc29e0ce1d08ad4 wallet: Create separate function for wallet load (David Gumberg)
bc69070416c62a88d8f4029280ec10d6f9ec8d20 refactor: Wallet stats logging in its own function (David Gumberg)
a9d64cd49c69dafd6496ccb5aef4cd6d8898966b wallet: Remove redundant birth time update (David Gumberg)
b4a49cc7275efc16d4a4179ed34b50de5bb7367e wallet: Move argument parsing to before DB load (David Gumberg)
b15a94a618c53041e97ccfface3045a0642777e1 refactor: Split out wallet argument loading (David Gumberg)
a02c4a82d88a3e9a24ec2aa0b828b8cc533dde58 refactor: Move -walletbroadcast setting init (David Gumberg)
411caf72815bdf2e176e790a4c63f745517c4bb4 wallet: refactor: PopulateWalletFromDB use switch statement. (David Gumberg)
a48e23f566ccaf9b81fe0684885972d9ee34afd3 refactor: wallet: move error handling to PopulateWalletFromDB() (David Gumberg)
0972785fd723b9b3c84844bf999d6e08e163ef9d wallet: Delete unnecessary PopulateWalletFromDB() calls (David Gumberg)
f0a046094e4c4b5f3af0e453492077f4911e0132 scripted-diff: refactor: CWallet::LoadWallet->PopulateWalletFromDB (David Gumberg)
Pull request description:
This PR is mostly a refactor which splits out logic used for creating wallets and for loading wallets, both of which are presently contained in `CWallet::Create()` into `CWallet::CreateNew()` and `CWallet::LoadExisting()`
The real win of this PR is that `CWallet::Create()` uses a very bad heuristic for trying to guess whether or not it is supposed to be creating a new wallet or loading an existing wallet:
370c592612/src/wallet/wallet.cpp (L2882-L2885)
This heuristic assumes that wallets with no `ScriptPubKeyMans` are being created, which sounds reasonable, but as demonstrated in #32112 and #32111, this can happen when the user tries to load a wallet file that is corrupted, both issues are fixed by this PR and any other misbehavior for wallet files which succeeded the broken heuristic's sniff test for new wallets.
It was already the case that every caller of `CWallet::Create()` knows whether it is creating a wallet or loading one, so we can avoid replacing this bad heuristic with another one, and just shift the burden to the caller.
ACKs for top commit:
achow101:
ACK db2effaca4cf82bf806596d16f9797d3692e2da7
polespinasa:
approach ACK db2effaca4cf82bf806596d16f9797d3692e2da7
w0xlt:
reACK db2effaca4cf82bf806596d16f9797d3692e2da7
murchandamus:
ACK db2effaca4cf82bf806596d16f9797d3692e2da7
rkrux:
ACK db2effaca4cf82bf806596d16f9797d3692e2da7
Tree-SHA512: c28d60e0a3001058da3fd2bdbe0726c7ebe742a4b900a1dee2e5132eccc22e49619cb747a99b4032b000eafd4aa2fdd4ec244c32be2012aba809fdc94b5f6ecd
187 lines
11 KiB
Python
Executable File
187 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2018-present 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 createwallet arguments.
|
|
"""
|
|
|
|
from test_framework.descriptors import descsum_create
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import (
|
|
assert_equal,
|
|
assert_raises_rpc_error,
|
|
wallet_importprivkey,
|
|
)
|
|
from test_framework.wallet_util import generate_keypair, WalletUnlock
|
|
|
|
|
|
EMPTY_PASSPHRASE_MSG = "Empty string given as passphrase, wallet will not be encrypted."
|
|
|
|
|
|
class CreateWalletTest(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):
|
|
node = self.nodes[0]
|
|
|
|
self.log.info("Run createwallet with invalid parameters.")
|
|
# Run createwallet with invalid parameters. This must not prevent a new wallet with the same name from being created with the correct parameters.
|
|
assert_raises_rpc_error(-4, "Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.",
|
|
self.nodes[0].createwallet, wallet_name='w0', disable_private_keys=True, passphrase="passphrase")
|
|
assert_raises_rpc_error(-8, "Wallet name cannot be empty", self.nodes[0].createwallet, "")
|
|
|
|
self.nodes[0].createwallet(wallet_name='w0')
|
|
w0 = node.get_wallet_rpc('w0')
|
|
address1 = w0.getnewaddress()
|
|
|
|
self.log.info("Test disableprivatekeys creation.")
|
|
self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True)
|
|
w1 = node.get_wallet_rpc('w1')
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getrawchangeaddress)
|
|
import_res = w1.importdescriptors([{"desc": w0.getaddressinfo(address1)['desc'], "timestamp": "now"}])
|
|
assert_equal(import_res[0]["success"], True)
|
|
assert_equal(sorted(w1.getwalletinfo()["flags"]), sorted(["last_hardened_xpub_cached", "descriptor_wallet", "disable_private_keys"]))
|
|
|
|
self.log.info('Test that private keys cannot be imported')
|
|
privkey, pubkey = generate_keypair(wif=True)
|
|
result = w1.importdescriptors([{'desc': descsum_create('wpkh(' + privkey + ')'), 'timestamp': 'now'}])
|
|
assert not result[0]['success']
|
|
assert 'warnings' not in result[0]
|
|
assert_equal(result[0]['error']['code'], -4)
|
|
assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
|
|
|
|
self.log.info("Test blank creation with private keys disabled.")
|
|
self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True)
|
|
w2 = node.get_wallet_rpc('w2')
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getrawchangeaddress)
|
|
import_res = w2.importdescriptors([{"desc": w0.getaddressinfo(address1)['desc'], "timestamp": "now"}])
|
|
assert_equal(import_res[0]["success"], True)
|
|
|
|
self.log.info("Test blank creation with private keys enabled.")
|
|
self.nodes[0].createwallet(wallet_name='w3', disable_private_keys=False, blank=True)
|
|
w3 = node.get_wallet_rpc('w3')
|
|
assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress)
|
|
# Import private key
|
|
wallet_importprivkey(w3, generate_keypair(wif=True)[0], "now")
|
|
# Imported private keys are currently ignored by the keypool
|
|
assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
|
|
# Set the seed
|
|
w3.importdescriptors([{
|
|
'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'),
|
|
'timestamp': 'now',
|
|
'active': True
|
|
},
|
|
{
|
|
'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'),
|
|
'timestamp': 'now',
|
|
'active': True,
|
|
'internal': True
|
|
}])
|
|
assert_equal(w3.getwalletinfo()['keypoolsize'], 1)
|
|
w3.getnewaddress()
|
|
w3.getrawchangeaddress()
|
|
|
|
self.log.info("Test blank creation with privkeys enabled and then encryption")
|
|
self.nodes[0].createwallet(wallet_name='w4', disable_private_keys=False, blank=True)
|
|
w4 = node.get_wallet_rpc('w4')
|
|
assert_equal(w4.getwalletinfo()['keypoolsize'], 0)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
|
|
# Encrypt the wallet. Nothing should change about the keypool
|
|
w4.encryptwallet('pass')
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
|
|
with WalletUnlock(w4, "pass"):
|
|
# Now set a seed and it should work. Wallet should also be encrypted
|
|
w4.importdescriptors([{
|
|
'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'),
|
|
'timestamp': 'now',
|
|
'active': True
|
|
},
|
|
{
|
|
'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'),
|
|
'timestamp': 'now',
|
|
'active': True,
|
|
'internal': True
|
|
}])
|
|
w4.getnewaddress()
|
|
w4.getrawchangeaddress()
|
|
|
|
self.log.info("Test blank creation with privkeys disabled and then encryption")
|
|
self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True)
|
|
w5 = node.get_wallet_rpc('w5')
|
|
assert_equal(w5.getwalletinfo()['keypoolsize'], 0)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
|
|
# Encrypt the wallet
|
|
assert_raises_rpc_error(-16, "Error: wallet does not contain private keys, nothing to encrypt.", w5.encryptwallet, 'pass')
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
|
|
|
|
self.log.info('New blank and encrypted wallets can be created')
|
|
self.nodes[0].createwallet(wallet_name='wblank', disable_private_keys=False, blank=True, passphrase='thisisapassphrase')
|
|
wblank = node.get_wallet_rpc('wblank')
|
|
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", wblank.signmessage, "needanargument", "test")
|
|
with WalletUnlock(wblank, "thisisapassphrase"):
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getnewaddress)
|
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getrawchangeaddress)
|
|
|
|
self.log.info('Test creating a new encrypted wallet.')
|
|
# Born encrypted wallet is created (has keys)
|
|
self.nodes[0].createwallet(wallet_name='w6', disable_private_keys=False, blank=False, passphrase='thisisapassphrase')
|
|
w6 = node.get_wallet_rpc('w6')
|
|
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", w6.signmessage, "needanargument", "test")
|
|
with WalletUnlock(w6, "thisisapassphrase"):
|
|
w6.signmessage(w6.getnewaddress('', 'legacy'), "test")
|
|
w6.keypoolrefill(1)
|
|
# There should only be 1 key for legacy, 3 for descriptors
|
|
walletinfo = w6.getwalletinfo()
|
|
keys = 4
|
|
assert_equal(walletinfo['keypoolsize'], keys)
|
|
assert_equal(walletinfo['keypoolsize_hd_internal'], keys)
|
|
# Allow empty passphrase, but there should be a warning
|
|
resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='')
|
|
assert_equal(resp["warnings"], [EMPTY_PASSPHRASE_MSG])
|
|
|
|
w7 = node.get_wallet_rpc('w7')
|
|
assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60)
|
|
|
|
self.log.info('Test making a wallet with avoid reuse flag')
|
|
self.nodes[0].createwallet('w8', False, False, '', True) # Use positional arguments to check for bug where avoid_reuse could not be set for wallets without needing them to be encrypted
|
|
w8 = node.get_wallet_rpc('w8')
|
|
assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60)
|
|
assert_equal(w8.getwalletinfo()["avoid_reuse"], True)
|
|
|
|
self.log.info('Using a passphrase with private keys disabled returns error')
|
|
assert_raises_rpc_error(-4, 'Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.', self.nodes[0].createwallet, wallet_name='w9', disable_private_keys=True, passphrase='thisisapassphrase')
|
|
|
|
self.log.info("Test that legacy wallets cannot be created")
|
|
assert_raises_rpc_error(-4, 'descriptors argument must be set to "true"; it is no longer possible to create a legacy wallet.', self.nodes[0].createwallet, wallet_name="legacy", descriptors=False)
|
|
|
|
self.log.info("Check that the version number is being logged correctly")
|
|
|
|
# Craft the expected version message.
|
|
client_version = node.getnetworkinfo()["version"]
|
|
version_message = f"Last client version = {client_version}"
|
|
|
|
# Should not be logged when creating.
|
|
with node.assert_debug_log(expected_msgs=[], unexpected_msgs=[version_message]):
|
|
node.createwallet("version_check")
|
|
node.unloadwallet("version_check")
|
|
# Should be logged when loading.
|
|
with node.assert_debug_log(expected_msgs=[version_message]):
|
|
node.loadwallet("version_check")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
CreateWalletTest(__file__).main()
|