dash/test/functional/wallet_upgradetohd.py

212 lines
9.6 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
# Copyright (c) 2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
wallet_upgradetohd.py
Test upgrade to a Hierarchical Deterministic wallet via upgradetohd rpc
"""
import shutil
import os
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
class WalletUpgradeToHDTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
feat: enable HD wallets by default (#5807) ## Issue being fixed or feature implemented HD wallets are old-existsing feature, appeared in Dash years ago, but enabling HD wallets is not trivial task that requires multiple steps and command line/rpc calls. Let's have them enabled by default. ## What was done? - HD wallets are enabled by default. Currently behavior `dashd`, `dash-qt` are similar to run with option `-usehd=1` - the rpc `upgradewallet` do not let to upgrade from non-HD wallet to HD wallet to don't encourage user use non-crypted wallets (postponed till v21) - the initialization of ScriptPubKey is updated to be sure that encypted HD seed is never written on disk (if passphrase is provided) - enabled and dashified a script `wallet_upgradewallet.py` which test compatibility between different versions of wallet ## What is not done? - wallet tool still does not support passhprase, HD seed can appear on disk - there's no dialog that show user a mnemonic phrase and encourage him to make a paper backup Before removing a command line 'usehd' (backport bitcoin#11250) need to make at least one major release for fail-over option (if someone wish to use non-HD wallets only). ## How Has This Been Tested? Run unit and functional tests. Enabled new functional test `wallet_upgradewallet.py` that has been backported long time ago but waited this PR to be enabled. ## Breaking Changes HD wallets are created by default. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone --------- Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
2024-02-09 18:36:14 +01:00
self.extra_args = [['-usehd=0']]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
feat: enable HD wallets by default (#5807) ## Issue being fixed or feature implemented HD wallets are old-existsing feature, appeared in Dash years ago, but enabling HD wallets is not trivial task that requires multiple steps and command line/rpc calls. Let's have them enabled by default. ## What was done? - HD wallets are enabled by default. Currently behavior `dashd`, `dash-qt` are similar to run with option `-usehd=1` - the rpc `upgradewallet` do not let to upgrade from non-HD wallet to HD wallet to don't encourage user use non-crypted wallets (postponed till v21) - the initialization of ScriptPubKey is updated to be sure that encypted HD seed is never written on disk (if passphrase is provided) - enabled and dashified a script `wallet_upgradewallet.py` which test compatibility between different versions of wallet ## What is not done? - wallet tool still does not support passhprase, HD seed can appear on disk - there's no dialog that show user a mnemonic phrase and encourage him to make a paper backup Before removing a command line 'usehd' (backport bitcoin#11250) need to make at least one major release for fail-over option (if someone wish to use non-HD wallets only). ## How Has This Been Tested? Run unit and functional tests. Enabled new functional test `wallet_upgradewallet.py` that has been backported long time ago but waited this PR to be enabled. ## Breaking Changes HD wallets are created by default. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone --------- Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
2024-02-09 18:36:14 +01:00
self.add_nodes(self.num_nodes, self.extra_args)
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
def recover_non_hd(self):
self.log.info("Recover non-HD wallet to check different upgrade paths")
node = self.nodes[0]
self.stop_node(0)
2023-02-14 09:48:36 +01:00
shutil.copyfile(os.path.join(node.datadir, "non_hd.bak"), os.path.join(node.datadir, self.chain, self.default_wallet_name, self.wallet_data_filename))
self.start_node(0)
2021-08-27 21:03:02 +02:00
assert 'hdchainid' not in node.getwalletinfo()
def run_test(self):
node = self.nodes[0]
node.backupwallet(os.path.join(node.datadir, "non_hd.bak"))
self.log.info("No mnemonic, no mnemonic passphrase, no wallet passphrase")
2021-08-27 21:03:02 +02:00
assert 'hdchainid' not in node.getwalletinfo()
balance_before = node.getbalance()
2021-08-27 21:03:02 +02:00
assert node.upgradetohd()
mnemonic = node.dumphdinfo()['mnemonic']
chainid = node.getwalletinfo()['hdchainid']
assert_equal(len(chainid), 64)
assert_equal(balance_before, node.getbalance())
self.log.info("Should be spendable and should use correct paths")
for i in range(5):
txid = node.sendtoaddress(node.getnewaddress(), 1)
outs = node.decoderawtransaction(node.gettransaction(txid)['hex'])['vout']
for out in outs:
if out['value'] == 1:
keypath = node.getaddressinfo(out['scriptPubKey']['address'])['hdkeypath']
assert_equal(keypath, "m/44'/1'/0'/0/%d" % i)
else:
keypath = node.getaddressinfo(out['scriptPubKey']['address'])['hdkeypath']
assert_equal(keypath, "m/44'/1'/0'/1/%d" % i)
self.bump_mocktime(1)
self.generate(node, 1, sync_fun=self.no_op)
self.log.info("Should no longer be able to start it with HD disabled")
self.stop_node(0)
2023-02-14 09:48:36 +01:00
node.assert_start_raises_init_error(['-usehd=0'], "Error: Error loading %s: You can't disable HD on an already existing HD wallet" % self.default_wallet_name)
feat: enable HD wallets by default (#5807) ## Issue being fixed or feature implemented HD wallets are old-existsing feature, appeared in Dash years ago, but enabling HD wallets is not trivial task that requires multiple steps and command line/rpc calls. Let's have them enabled by default. ## What was done? - HD wallets are enabled by default. Currently behavior `dashd`, `dash-qt` are similar to run with option `-usehd=1` - the rpc `upgradewallet` do not let to upgrade from non-HD wallet to HD wallet to don't encourage user use non-crypted wallets (postponed till v21) - the initialization of ScriptPubKey is updated to be sure that encypted HD seed is never written on disk (if passphrase is provided) - enabled and dashified a script `wallet_upgradewallet.py` which test compatibility between different versions of wallet ## What is not done? - wallet tool still does not support passhprase, HD seed can appear on disk - there's no dialog that show user a mnemonic phrase and encourage him to make a paper backup Before removing a command line 'usehd' (backport bitcoin#11250) need to make at least one major release for fail-over option (if someone wish to use non-HD wallets only). ## How Has This Been Tested? Run unit and functional tests. Enabled new functional test `wallet_upgradewallet.py` that has been backported long time ago but waited this PR to be enabled. ## Breaking Changes HD wallets are created by default. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone --------- Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
2024-02-09 18:36:14 +01:00
self.extra_args = []
self.start_node(0, [])
balance_after = node.getbalance()
self.recover_non_hd()
# We spent some coins from non-HD keys to HD ones earlier
balance_non_HD = node.getbalance()
2021-08-27 21:03:02 +02:00
assert balance_before != balance_non_HD
self.log.info("No mnemonic, no mnemonic passphrase, no wallet passphrase, should result in completely different keys")
2021-08-27 21:03:02 +02:00
assert node.upgradetohd()
assert mnemonic != node.dumphdinfo()['mnemonic']
assert chainid != node.getwalletinfo()['hdchainid']
assert_equal(balance_non_HD, node.getbalance())
node.keypoolrefill(5)
node.rescanblockchain()
# Completely different keys, no HD coins should be recovered
assert_equal(balance_non_HD, node.getbalance())
self.recover_non_hd()
self.log.info("No mnemonic, no mnemonic passphrase, no wallet passphrase, should result in completely different keys")
self.restart_node(0, extra_args=['-keypool=10'])
assert node.upgradetohd("", "", "", True)
# Completely different keys, no HD coins should be recovered
assert mnemonic != node.dumphdinfo()['mnemonic']
assert chainid != node.getwalletinfo()['hdchainid']
assert_equal(balance_non_HD, node.getbalance())
self.recover_non_hd()
self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys")
new_mnemonic_passphrase = "somewords"
2021-08-27 21:03:02 +02:00
assert node.upgradetohd(mnemonic, new_mnemonic_passphrase)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
new_chainid = node.getwalletinfo()['hdchainid']
2021-08-27 21:03:02 +02:00
assert chainid != new_chainid
assert_equal(balance_non_HD, node.getbalance())
node.keypoolrefill(5)
node.rescanblockchain()
# A different set of keys, no HD coins should be recovered
new_addresses = (node.getnewaddress(), node.getrawchangeaddress())
assert_equal(balance_non_HD, node.getbalance())
self.recover_non_hd()
self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys (again)")
2021-08-27 21:03:02 +02:00
assert node.upgradetohd(mnemonic, new_mnemonic_passphrase)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(new_chainid, node.getwalletinfo()['hdchainid'])
assert_equal(balance_non_HD, node.getbalance())
node.keypoolrefill(5)
node.rescanblockchain()
# A different set of keys, no HD coins should be recovered, keys should be the same as they were the previous time
assert_equal(new_addresses, (node.getnewaddress(), node.getrawchangeaddress()))
assert_equal(balance_non_HD, node.getbalance())
self.recover_non_hd()
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, should recover all coins after rescan")
2021-08-27 21:03:02 +02:00
assert node.upgradetohd(mnemonic)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
node.keypoolrefill(5)
assert balance_after != node.getbalance()
node.rescanblockchain()
assert_equal(balance_after, node.getbalance())
self.recover_non_hd()
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, should recover all coins with no extra rescan")
self.restart_node(0, extra_args=['-keypool=10'])
2021-08-27 21:03:02 +02:00
assert node.upgradetohd(mnemonic)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
# All coins should be recovered
assert_equal(balance_after, node.getbalance())
self.recover_non_hd()
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, rescan is skipped initially, should recover all coins after rescanblockchain")
self.restart_node(0, extra_args=['-keypool=10'])
assert node.upgradetohd(mnemonic, "", "", False)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
assert balance_after != node.getbalance()
node.rescanblockchain()
# All coins should be recovered
assert_equal(balance_after, node.getbalance())
self.recover_non_hd()
self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet on upgrade, should recover all coins after rescan")
walletpass = "111pass222"
Merge #12493: [wallet] Reopen CDBEnv after encryption instead of shutting down c1dde3a949b36ce9c2155777b3fa1372e7ed97d8 No longer shutdown after encrypting the wallet (Andrew Chow) d7637c5a3f1d62922594cdfb6272e30dacf60ce9 After encrypting the wallet, reload the database environment (Andrew Chow) 5d296ac810755dc47f105eb95b52b7e2bcb8aea8 Add function to close all Db's and reload the databae environment (Andrew Chow) a769461d5e37ddcb771ae836254fdc69177a28c4 Move BerkeleyEnvironment deletion from internal method to callsite (Andrew Chow) Pull request description: This is the replacement for #11678 which implements @ryanofsky's [suggestion](https://github.com/bitcoin/bitcoin/pull/11678#pullrequestreview-76464511). Shutting down the software was to prevent the BDB environment from writing unencrypted private keys to disk in the database log files, as was noted [here](https://bitcointalk.org/index.php?topic=51474.msg616068#msg616068). This PR replaces the shutdown behavior with a CDBEnv flush, close, and reopen which achieves the same effect: everything is cleanly flushed and closed, the log files are removed, and then the environment reopened to continue normal operation. To ensure that no unencrypted private keys are in the log files after encrypting the wallet, I wrote [this script](https://gist.github.com/achow101/7f7143e6c3d3fdc034d3470e72823e9d) to pull private keys from the original wallet file and searches for these keys in the log files (note that you will have to change your file paths to make it work on your own machine). As for concerns about private keys being written to slack space or being kept in memory, these behaviors no longer exist after the original wallet encryption PR and the shutting down solution from 2011. cc @ryanofsky Tree-SHA512: 34b894283b0677a873d06dee46dff8424dec85a2973009ac9b84bcf3d22d05f227c494168c395219d9aee3178e420cf70d4b3eeacc9785aa86b6015d25758e75
2018-09-14 10:28:27 +02:00
assert node.upgradetohd(mnemonic, "", walletpass)
node.stop()
node.wait_until_stopped()
self.start_node(0, extra_args=['-rescan'])
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
node.walletpassphrase(walletpass, 100)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
# Note: wallet encryption results in additional keypool topup,
# so we can't compare new balance to balance_non_HD here,
# assert_equal(balance_non_HD, node.getbalance()) # won't work
2021-08-27 21:03:02 +02:00
assert balance_non_HD != node.getbalance()
node.keypoolrefill(4)
node.rescanblockchain()
# All coins should be recovered
assert_equal(balance_after, node.getbalance())
self.recover_non_hd()
self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet first, should recover all coins on upgrade after rescan")
walletpass = "111pass222"
node.encryptwallet(walletpass)
Merge #12493: [wallet] Reopen CDBEnv after encryption instead of shutting down c1dde3a949b36ce9c2155777b3fa1372e7ed97d8 No longer shutdown after encrypting the wallet (Andrew Chow) d7637c5a3f1d62922594cdfb6272e30dacf60ce9 After encrypting the wallet, reload the database environment (Andrew Chow) 5d296ac810755dc47f105eb95b52b7e2bcb8aea8 Add function to close all Db's and reload the databae environment (Andrew Chow) a769461d5e37ddcb771ae836254fdc69177a28c4 Move BerkeleyEnvironment deletion from internal method to callsite (Andrew Chow) Pull request description: This is the replacement for #11678 which implements @ryanofsky's [suggestion](https://github.com/bitcoin/bitcoin/pull/11678#pullrequestreview-76464511). Shutting down the software was to prevent the BDB environment from writing unencrypted private keys to disk in the database log files, as was noted [here](https://bitcointalk.org/index.php?topic=51474.msg616068#msg616068). This PR replaces the shutdown behavior with a CDBEnv flush, close, and reopen which achieves the same effect: everything is cleanly flushed and closed, the log files are removed, and then the environment reopened to continue normal operation. To ensure that no unencrypted private keys are in the log files after encrypting the wallet, I wrote [this script](https://gist.github.com/achow101/7f7143e6c3d3fdc034d3470e72823e9d) to pull private keys from the original wallet file and searches for these keys in the log files (note that you will have to change your file paths to make it work on your own machine). As for concerns about private keys being written to slack space or being kept in memory, these behaviors no longer exist after the original wallet encryption PR and the shutting down solution from 2011. cc @ryanofsky Tree-SHA512: 34b894283b0677a873d06dee46dff8424dec85a2973009ac9b84bcf3d22d05f227c494168c395219d9aee3178e420cf70d4b3eeacc9785aa86b6015d25758e75
2018-09-14 10:28:27 +02:00
node.stop()
node.wait_until_stopped()
self.start_node(0, extra_args=['-rescan'])
assert_raises_rpc_error(-13, "Error: Wallet encrypted but passphrase not supplied to RPC.", node.upgradetohd, mnemonic)
assert_raises_rpc_error(-1, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
2021-08-27 21:03:02 +02:00
assert node.upgradetohd(mnemonic, "", walletpass)
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
node.walletpassphrase(walletpass, 100)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
# Note: wallet encryption results in additional keypool topup,
# so we can't compare new balance to balance_non_HD here,
# assert_equal(balance_non_HD, node.getbalance()) # won't work
2021-08-27 21:03:02 +02:00
assert balance_non_HD != node.getbalance()
node.keypoolrefill(4)
node.rescanblockchain()
# All coins should be recovered
assert_equal(balance_after, node.getbalance())
if __name__ == '__main__':
WalletUpgradeToHDTest().main ()