mirror of
https://github.com/dashpay/dash.git
synced 2024-12-27 13:03:17 +01:00
5a6b8b6b1f
14b4921a91920df25b19ff420bfe2bff8c56f71e wallet: reuse change dest when recreating TX with avoidpartialspends (Matthew Zipkin) Pull request description: Closes https://github.com/bitcoin/bitcoin/issues/27051 When the wallet creates a transaction internally, it will also create an alternative that spends using destination groups and see if the fee difference is negligible. If it costs the user the same to send the grouped version, we send it (even if the user has `avoidpartialspends` set to `false` which is default). This patch ensures that the second transaction creation attempt re-uses the change destination selected by the first attempt. Otherwise, the first change address remains reserved, will not be used in the second attempt, and then will never be used by the wallet, leaving gaps in the BIP44 chain. If the user had `avoidpartialspends` set to true, there is no second version of the created transaction and the change addresses are not affected. I believe this behavior was introduced in https://github.com/bitcoin/bitcoin/pull/14582 ACKs for top commit: achow101: ACK 14b4921a91920df25b19ff420bfe2bff8c56f71e Tree-SHA512: a3d56f251ff4b333fc11325f30d05513e34ab0a2eb703fadd0ad98d167ae074493df1a24068298336c6ed2da6b31aa2befa490bc790bbc260ed357c8f2397659
212 lines
9.6 KiB
Python
Executable File
212 lines
9.6 KiB
Python
Executable File
#!/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
|
|
self.extra_args = [['-usehd=0']]
|
|
|
|
def skip_test_if_missing_module(self):
|
|
self.skip_if_no_wallet()
|
|
|
|
def setup_network(self):
|
|
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)
|
|
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)
|
|
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")
|
|
assert 'hdchainid' not in node.getwalletinfo()
|
|
balance_before = node.getbalance()
|
|
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']['addresses'][0])['hdkeypath']
|
|
assert_equal(keypath, "m/44'/1'/0'/0/%d" % i)
|
|
else:
|
|
keypath = node.getaddressinfo(out['scriptPubKey']['addresses'][0])['hdkeypath']
|
|
assert_equal(keypath, "m/44'/1'/0'/1/%d" % i)
|
|
|
|
self.bump_mocktime(1)
|
|
node.generate(1)
|
|
|
|
self.log.info("Should no longer be able to start it with HD disabled")
|
|
self.stop_node(0)
|
|
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)
|
|
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()
|
|
assert balance_before != balance_non_HD
|
|
|
|
self.log.info("No mnemonic, no mnemonic passphrase, no wallet passphrase, should result in completely different keys")
|
|
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"
|
|
assert node.upgradetohd(mnemonic, new_mnemonic_passphrase)
|
|
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
|
|
new_chainid = node.getwalletinfo()['hdchainid']
|
|
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)")
|
|
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")
|
|
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'])
|
|
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"
|
|
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
|
|
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)
|
|
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.upgradetohd, mnemonic)
|
|
assert_raises_rpc_error(-14, "The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
|
|
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
|
|
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 ()
|