Compare commits

...

12 commits

Author SHA1 Message Date
Luke Dashjr
cf926e3f92
Merge 524f981bb8 into 65714c162c 2025-04-29 01:14:56 +02:00
Ava Chow
65714c162c
Merge bitcoin/bitcoin#32327: test: Add missing check for empty stderr in util tester
Some checks are pending
CI / test each commit (push) Waiting to run
CI / macOS 14 native, arm64, no depends, sqlite only, gui (push) Waiting to run
CI / macOS 14 native, arm64, fuzz (push) Waiting to run
CI / Windows native, VS 2022 (push) Waiting to run
CI / Windows native, fuzz, VS 2022 (push) Waiting to run
CI / Linux->Windows cross, no tests (push) Waiting to run
CI / Windows, test cross-built (push) Blocked by required conditions
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Waiting to run
fadf12a56c test: Add missing check for empty stderr in util tester (MarcoFalke)

Pull request description:

  Now that wine support was removed from the CI in 25b56fd9b4, it can probably be removed from the util tester as well.

  If someone really needs this, they can comment the new check out, or submit a patch to add an option/env var to silence the new check.

ACKs for top commit:
  achow101:
    ACK fadf12a56c
  i-am-yuvi:
    tACK fadf12a56c
  BrandonOdiwuor:
    Code Review ACK fadf12a56c
  ismaelsadeeq:
    Tested ACK fadf12a56c

Tree-SHA512: d9e4d7a7f724e114391070ea7f17b585a7e4c4f3221c3bf510eeb11df6ccd089b881ab5654adfef8d3a1f8fa7ec6bf5e3a3feeb0cdfe724a8f3e5a146c388e66
2025-04-28 14:43:32 -07:00
merge-script
a4eee6d50b
Merge bitcoin/bitcoin#29124: test: Test that migration automatically repairs corrupted metadata with doubled derivation path
c7e2b9e264 tests: Test migration cleans up bad inactive chain derivation path (Ava Chow)

Pull request description:

  A bug in 0.21.x and 22.x resulted in some wallets having invalid derivation paths that are the concatenation of two derivation paths. These appear only when inactive hd chains are topped up.

  Since key metadata is a legacy wallet only record, migrating legacy wallets to descriptor wallets will fix this issue as all key metadata records are deleted. The derivation path information is derived on-the-fly from the descriptor that is produced for the inactive hd chain.

  Thus we only need a test to verify that the derivation paths are good, and that all key metadata records are deleted from the migrated wallet.

ACKs for top commit:
  murchandamus:
    re-ACK c7e2b9e264 via range-diff:
  rkrux:
    re-ACK c7e2b9e264
  furszy:
    utACK c7e2b9e264

Tree-SHA512: 3117c4a43798972109fe2d3539341a8b69db70c6457fcabdd019e6044834dc4b17212abbc006d7b8008f560dce4b7856142b057981b9404f406d58fa0955cbd9
2025-04-28 17:13:42 -04:00
Ava Chow
af6cffa36d
Merge bitcoin/bitcoin#32350: test: Slim down previous releases bdb check
fa58f40b89 test: Slim down previous releases bdb check (MarcoFalke)

Pull request description:

  The check iterates over several previous BDB-only releases to check that descriptor wallets are considered "corrupt" when loading. It is unclear why this needs to be done for more than one release.

  Avoid the confusion by removing the unused releases from the test and from the download script.

ACKs for top commit:
  achow101:
    ACK fa58f40b89
  rkrux:
    ACK fa58f40b89

Tree-SHA512: 8084392481bfe1fba9b80bb865ffbdfa454e9e6e14e02c39fa3f61c1a596b1def2c531c5da1c7566e5fddb77ac7e56f19feabaaf9b5af043fa6c230d9e2370b5
2025-04-28 12:56:22 -07:00
Ava Chow
33e6538b30
Merge bitcoin/bitcoin#32360: test: Force named args for RPCOverloadWrapper optional args
fa48be3ba4 test: Force named args for RPCOverloadWrapper optional args (MarcoFalke)
aaaa45399c test: Remove unused createwallet_passthrough (MarcoFalke)
cccc1f4e91 test: Remove unused RPCOverloadWrapper is_cli field (MarcoFalke)

Pull request description:

  This can avoid bugs and makes the test code easier to read, because the
  order of positional args does not have to be known or assumed.

  Also, contains two commits to remove dead code.

ACKs for top commit:
  achow101:
    ACK fa48be3ba4
  rkrux:
    tACK fa48be3ba4
  janb84:
    tACK [fa48be3](fa48be3ba4)

Tree-SHA512: d938fbc18be5035ad0d0e1ad2bf7297b2b66ede3bb2d3f10b8d27aa2a19d27a897b024a5f5a2a1cceca467837890729c26054928cb06acbe282b9e9eea94ae69
2025-04-28 12:41:00 -07:00
MarcoFalke
fa48be3ba4
test: Force named args for RPCOverloadWrapper optional args
This can avoid bugs and makes the test code easier to read, because the
order of positional args does not have to be known or assumed.
2025-04-28 15:15:05 +02:00
MarcoFalke
aaaa45399c
test: Remove unused createwallet_passthrough 2025-04-28 15:14:52 +02:00
MarcoFalke
cccc1f4e91
test: Remove unused RPCOverloadWrapper is_cli field 2025-04-28 10:18:59 +02:00
Luke Dashjr
524f981bb8 Bugfix: Miner: Don't reuse block_reserved_weight for "block is full enough to give up" weight delta
PR #30356 incorrectly changed a constant of `4000` to `m_options.coinbase_max_additional_weight` in the check for when to give up finding another transaction to fill the block:

```diff
             if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
-                    m_options.nBlockMaxWeight - 4000) {
+                    m_options.nBlockMaxWeight - m_options.block_reserved_weight) {
                 // Give up if we're close to full and haven't succeeded in a while
                 break;
             }
```

But this constant did not deal with the reserved weight at all. It was in fact simply checking if the block was close to full, and if so, giving up finding another transaction to pad it with after `MAX_CONSECUTIVE_FAILURES` failed attempts.

It doesn't seem very logical to reuse the reserve weight for this purpose, and it would be overcomplicated to add yet another setting, so this PR changes it to a new constexpr.
2025-04-26 07:45:12 +00:00
Ava Chow
c7e2b9e264 tests: Test migration cleans up bad inactive chain derivation path
A bug in 0.21.x and 22.x resulted in some wallets having invalid
derivation paths that are the concatenation of two derivation paths.
These appear only when inactive hd chains are topped up.

Since key metadata is a legacy wallet only record, migrating legacy
wallets to descriptor wallets will fix this issue as all key metadata
records are deleted. The derivation path information is derived
on-the-fly from the descriptor that is produced for the inactive hd
chain.

Thus we only need a test to verify that the derivation paths are good,
and that all key metadata records are deleted from the migrated wallet.
2025-04-25 09:12:53 -07:00
MarcoFalke
fa58f40b89
test: Slim down previous releases bdb check 2025-04-25 16:04:07 +02:00
MarcoFalke
fadf12a56c
test: Add missing check for empty stderr in util tester 2025-04-22 17:54:38 +02:00
9 changed files with 124 additions and 79 deletions

View file

@ -311,6 +311,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
// close to full; this is just a simple heuristic to finish quickly if the // close to full; this is just a simple heuristic to finish quickly if the
// mempool has a lot of entries. // mempool has a lot of entries.
const int64_t MAX_CONSECUTIVE_FAILURES = 1000; const int64_t MAX_CONSECUTIVE_FAILURES = 1000;
constexpr int32_t BLOCK_FULL_ENOUGH_WEIGHT_DELTA = 4000;
int64_t nConsecutiveFailed = 0; int64_t nConsecutiveFailed = 0;
while (mi != mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) { while (mi != mempool.mapTx.get<ancestor_score>().end() || !mapModifiedTx.empty()) {
@ -392,7 +393,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
++nConsecutiveFailed; ++nConsecutiveFailed;
if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight > if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
m_options.nBlockMaxWeight - m_options.block_reserved_weight) { m_options.nBlockMaxWeight - BLOCK_FULL_ENOUGH_WEIGHT_DELTA) {
// Give up if we're close to full and haven't succeeded in a while // Give up if we're close to full and haven't succeeded in a while
break; break;
} }

View file

@ -111,7 +111,7 @@ class PSBTTest(BitcoinTestFramework):
# Mine a transaction that credits the offline address # Mine a transaction that credits the offline address
offline_addr = offline_node.getnewaddress(address_type="bech32m") offline_addr = offline_node.getnewaddress(address_type="bech32m")
online_addr = w2.getnewaddress(address_type="bech32m") online_addr = w2.getnewaddress(address_type="bech32m")
wonline.importaddress(offline_addr, "", False) wonline.importaddress(offline_addr, label="", rescan=False)
mining_wallet = mining_node.get_wallet_rpc(self.default_wallet_name) mining_wallet = mining_node.get_wallet_rpc(self.default_wallet_name)
mining_wallet.sendtoaddress(address=offline_addr, amount=1.0) mining_wallet.sendtoaddress(address=offline_addr, amount=1.0)
self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node])) self.generate(mining_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, mining_node]))
@ -312,9 +312,9 @@ class PSBTTest(BitcoinTestFramework):
wmulti = self.nodes[2].get_wallet_rpc('wmulti') wmulti = self.nodes[2].get_wallet_rpc('wmulti')
# Create all the addresses # Create all the addresses
p2sh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address'] p2sh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], label="", address_type="legacy")["address"]
p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address'] p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], label="", address_type="bech32")["address"]
p2sh_p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address'] p2sh_p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], label="", address_type="p2sh-segwit")["address"]
p2wpkh = self.nodes[1].getnewaddress("", "bech32") p2wpkh = self.nodes[1].getnewaddress("", "bech32")
p2pkh = self.nodes[1].getnewaddress("", "legacy") p2pkh = self.nodes[1].getnewaddress("", "legacy")
p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit") p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit")

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Copyright (c) 2017-2022 The Bitcoin Core developers # Copyright (c) 2017-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Class for bitcoind node under test""" """Class for bitcoind node under test"""
@ -209,7 +209,7 @@ class TestNode():
def __getattr__(self, name): def __getattr__(self, name):
"""Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" """Dispatches any unrecognised messages to the RPC connection or a CLI instance."""
if self.use_cli: if self.use_cli:
return getattr(RPCOverloadWrapper(self.cli, True), name) return getattr(RPCOverloadWrapper(self.cli), name)
else: else:
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection") assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
return getattr(RPCOverloadWrapper(self.rpc), name) return getattr(RPCOverloadWrapper(self.rpc), name)
@ -374,7 +374,7 @@ class TestNode():
def get_wallet_rpc(self, wallet_name): def get_wallet_rpc(self, wallet_name):
if self.use_cli: if self.use_cli:
return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)), True) return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)))
else: else:
assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected") assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
@ -925,17 +925,13 @@ class TestNodeCLI():
return cli_stdout.rstrip("\n") return cli_stdout.rstrip("\n")
class RPCOverloadWrapper(): class RPCOverloadWrapper():
def __init__(self, rpc, cli=False): def __init__(self, rpc):
self.rpc = rpc self.rpc = rpc
self.is_cli = cli
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.rpc, name) return getattr(self.rpc, name)
def createwallet_passthrough(self, *args, **kwargs): def importprivkey(self, privkey, *, label=None, rescan=None):
return self.__getattr__("createwallet")(*args, **kwargs)
def importprivkey(self, privkey, label=None, rescan=None):
wallet_info = self.getwalletinfo() wallet_info = self.getwalletinfo()
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
return self.__getattr__('importprivkey')(privkey, label, rescan) return self.__getattr__('importprivkey')(privkey, label, rescan)
@ -943,13 +939,13 @@ class RPCOverloadWrapper():
req = [{ req = [{
'desc': desc, 'desc': desc,
'timestamp': 0 if rescan else 'now', 'timestamp': 0 if rescan else 'now',
'label': label if label else '' 'label': label if label else '',
}] }]
import_res = self.importdescriptors(req) import_res = self.importdescriptors(req)
if not import_res[0]['success']: if not import_res[0]['success']:
raise JSONRPCException(import_res[0]['error']) raise JSONRPCException(import_res[0]['error'])
def addmultisigaddress(self, nrequired, keys, label=None, address_type=None): def addmultisigaddress(self, nrequired, keys, *, label=None, address_type=None):
wallet_info = self.getwalletinfo() wallet_info = self.getwalletinfo()
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
return self.__getattr__('addmultisigaddress')(nrequired, keys, label, address_type) return self.__getattr__('addmultisigaddress')(nrequired, keys, label, address_type)
@ -957,14 +953,14 @@ class RPCOverloadWrapper():
req = [{ req = [{
'desc': cms['descriptor'], 'desc': cms['descriptor'],
'timestamp': 0, 'timestamp': 0,
'label': label if label else '' 'label': label if label else '',
}] }]
import_res = self.importdescriptors(req) import_res = self.importdescriptors(req)
if not import_res[0]['success']: if not import_res[0]['success']:
raise JSONRPCException(import_res[0]['error']) raise JSONRPCException(import_res[0]['error'])
return cms return cms
def importpubkey(self, pubkey, label=None, rescan=None): def importpubkey(self, pubkey, *, label=None, rescan=None):
wallet_info = self.getwalletinfo() wallet_info = self.getwalletinfo()
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
return self.__getattr__('importpubkey')(pubkey, label, rescan) return self.__getattr__('importpubkey')(pubkey, label, rescan)
@ -972,13 +968,13 @@ class RPCOverloadWrapper():
req = [{ req = [{
'desc': desc, 'desc': desc,
'timestamp': 0 if rescan else 'now', 'timestamp': 0 if rescan else 'now',
'label': label if label else '' 'label': label if label else '',
}] }]
import_res = self.importdescriptors(req) import_res = self.importdescriptors(req)
if not import_res[0]['success']: if not import_res[0]['success']:
raise JSONRPCException(import_res[0]['error']) raise JSONRPCException(import_res[0]['error'])
def importaddress(self, address, label=None, rescan=None, p2sh=None): def importaddress(self, address, *, label=None, rescan=None, p2sh=None):
wallet_info = self.getwalletinfo() wallet_info = self.getwalletinfo()
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
return self.__getattr__('importaddress')(address, label, rescan, p2sh) return self.__getattr__('importaddress')(address, label, rescan, p2sh)
@ -992,13 +988,13 @@ class RPCOverloadWrapper():
reqs = [{ reqs = [{
'desc': desc, 'desc': desc,
'timestamp': 0 if rescan else 'now', 'timestamp': 0 if rescan else 'now',
'label': label if label else '' 'label': label if label else '',
}] }]
if is_hex and p2sh: if is_hex and p2sh:
reqs.append({ reqs.append({
'desc': descsum_create('p2sh(raw(' + address + '))'), 'desc': descsum_create('p2sh(raw(' + address + '))'),
'timestamp': 0 if rescan else 'now', 'timestamp': 0 if rescan else 'now',
'label': label if label else '' 'label': label if label else '',
}) })
import_res = self.importdescriptors(reqs) import_res = self.importdescriptors(reqs)
for res in import_res: for res in import_res:

View file

@ -344,7 +344,7 @@ class AddressTypeTest(BitcoinTestFramework):
self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32') self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32')
self.log.info('test invalid address type arguments') self.log.info('test invalid address type arguments')
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].addmultisigaddress, 2, [compressed_1, compressed_2], None, '') assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].addmultisigaddress, 2, [compressed_1, compressed_2], address_type="")
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getnewaddress, None, '') assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getnewaddress, None, '')
assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getrawchangeaddress, '') assert_raises_rpc_error(-5, "Unknown address type ''", self.nodes[3].getrawchangeaddress, '')
assert_raises_rpc_error(-5, "Unknown address type 'bech23'", self.nodes[3].getrawchangeaddress, 'bech23') assert_raises_rpc_error(-5, "Unknown address type 'bech23'", self.nodes[3].getrawchangeaddress, 'bech23')

View file

@ -26,11 +26,12 @@ from test_framework.util import (
assert_raises_rpc_error, assert_raises_rpc_error,
) )
LAST_KEYPOOL_INDEX = 9 # Index of the last derived address with the keypool size of 10
class BackwardsCompatibilityTest(BitcoinTestFramework): class BackwardsCompatibilityTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.setup_clean_chain = True self.setup_clean_chain = True
self.num_nodes = 10 self.num_nodes = 8
# Add new version after each release: # Add new version after each release:
self.extra_args = [ self.extra_args = [
["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay ["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay
@ -38,11 +39,9 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v25.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v25.0
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v24.0.1 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v24.0.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v22.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1", f"-keypool={LAST_KEYPOOL_INDEX + 1}"], # v22.0
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.20.1 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.20.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.19.1
["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1"], # v0.18.1
] ]
self.wallet_names = [self.default_wallet_name] self.wallet_names = [self.default_wallet_name]
@ -60,8 +59,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
220000, 220000,
210000, 210000,
200100, 200100,
190100,
180100,
]) ])
self.start_nodes() self.start_nodes()
@ -85,21 +82,98 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
node_major, _, _ = self.split_version(node) node_major, _, _ = self.split_version(node)
return node_major >= major return node_major >= major
def test_v22_inactivehdchain_path(self):
self.log.info("Testing inactive hd chain bad derivation path cleanup")
# 0.21.x and 22.x would both produce bad derivation paths when topping up an inactive hd chain
# Make sure that this is being automatically cleaned up by migration
node_master = self.nodes[1]
node_v22 = self.nodes[self.num_nodes - 5]
wallet_name = "bad_deriv_path"
node_v22.createwallet(wallet_name=wallet_name, descriptors=False)
bad_deriv_wallet = node_v22.get_wallet_rpc(wallet_name)
# Make a dump of the wallet to get an unused address
dump_path = node_v22.wallets_path / f"{wallet_name}.dump"
bad_deriv_wallet.dumpwallet(dump_path)
addr = None
seed = None
with open(dump_path, encoding="utf8") as f:
for line in f:
if f"hdkeypath=m/0'/0'/{LAST_KEYPOOL_INDEX}'" in line:
addr = line.split(" ")[4].split("=")[1]
elif " hdseed=1 " in line:
seed = line.split(" ")[0]
assert addr is not None
assert seed is not None
# Rotate seed and unload
bad_deriv_wallet.sethdseed()
bad_deriv_wallet.unloadwallet()
# Receive at addr to trigger inactive chain topup on next load
self.nodes[0].sendtoaddress(addr, 1)
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
self.sync_all(nodes=[self.nodes[0], node_master, node_v22])
node_v22.loadwallet(wallet_name)
# Dump again to find bad hd keypath
bad_deriv_path = f"m/0'/0'/{LAST_KEYPOOL_INDEX}'/0'/0'/{LAST_KEYPOOL_INDEX + 1}'"
good_deriv_path = f"m/0h/0h/{LAST_KEYPOOL_INDEX + 1}h"
os.unlink(dump_path)
bad_deriv_wallet.dumpwallet(dump_path)
bad_path_addr = None
with open(dump_path, encoding="utf8") as f:
for line in f:
if f"hdkeypath={bad_deriv_path}" in line:
bad_path_addr = line.split(" ")[4].split("=")[1]
assert bad_path_addr is not None
assert_equal(bad_deriv_wallet.getaddressinfo(bad_path_addr)["hdkeypath"], bad_deriv_path)
# Verify that this bad derivation path addr is actually at m/0'/0'/10' by making a new wallet with the same seed but larger keypool
node_v22.createwallet(wallet_name="path_verify", descriptors=False, blank=True)
verify_wallet = node_v22.get_wallet_rpc("path_verify")
verify_wallet.sethdseed(True, seed)
# Bad addr is after keypool, so need to generate it by refilling
verify_wallet.keypoolrefill(LAST_KEYPOOL_INDEX + 2)
assert_equal(verify_wallet.getaddressinfo(bad_path_addr)["hdkeypath"], good_deriv_path.replace("h", "'"))
# Migrate with master
# Since all keymeta records are now deleted after migration, the derivation path
# should now be correct as it is derived on-the-fly from the inactive hd chain's descriptor
backup_path = node_v22.wallets_path / f"{wallet_name}.bak"
bad_deriv_wallet.backupwallet(backup_path)
wallet_dir_master = os.path.join(node_master.wallets_path, wallet_name)
os.makedirs(wallet_dir_master, exist_ok=True)
shutil.copy(backup_path, os.path.join(wallet_dir_master, "wallet.dat"))
node_master.migratewallet(wallet_name)
bad_deriv_wallet_master = node_master.get_wallet_rpc(wallet_name)
assert_equal(bad_deriv_wallet_master.getaddressinfo(bad_path_addr)["hdkeypath"], good_deriv_path)
bad_deriv_wallet_master.unloadwallet()
# If we have sqlite3, verify that there are no keymeta records
try:
import sqlite3
wallet_db = node_master.wallets_path / wallet_name / "wallet.dat"
conn = sqlite3.connect(wallet_db)
with conn:
# Retrieve all records that have the "keymeta" prefix. The remaining key data varies for each record.
keymeta_rec = conn.execute("SELECT value FROM main where key >= x'076b65796d657461' AND key < x'076b65796d657462'").fetchone()
assert_equal(keymeta_rec, None)
conn.close()
except ImportError:
self.log.warning("sqlite3 module not available, skipping lack of keymeta records check")
def run_test(self): def run_test(self):
node_miner = self.nodes[0] node_miner = self.nodes[0]
node_master = self.nodes[1] node_master = self.nodes[1]
node_v21 = self.nodes[self.num_nodes - 4] node_v21 = self.nodes[self.num_nodes - 2]
node_v18 = self.nodes[self.num_nodes - 1] node_v20 = self.nodes[self.num_nodes - 1] # bdb only
legacy_nodes = self.nodes[2:] # Nodes that support legacy wallets legacy_nodes = self.nodes[2:] # Nodes that support legacy wallets
legacy_only_nodes = self.nodes[-3:] # Nodes that only support legacy wallets descriptors_nodes = self.nodes[2:-1] # Nodes that support descriptor wallets
descriptors_nodes = self.nodes[2:-3] # Nodes that support descriptor wallets
self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress()) self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress())
# Sanity check the test framework: # Sanity check the test framework:
res = node_v18.getblockchaininfo() assert_equal(node_v20.getblockchaininfo()["blocks"], COINBASE_MATURITY + 1)
assert_equal(res['blocks'], COINBASE_MATURITY + 1)
self.log.info("Test wallet backwards compatibility...") self.log.info("Test wallet backwards compatibility...")
# Create a number of wallets and open them in older versions: # Create a number of wallets and open them in older versions:
@ -206,13 +280,11 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
) )
# Check that descriptor wallets don't work on legacy only nodes # Check that descriptor wallets don't work on legacy only nodes
self.log.info("Test descriptor wallet incompatibility on:") self.log.info("Test descriptor wallet incompatibility on v0.20")
for node in legacy_only_nodes: # Descriptor wallets appear to be corrupted wallets to old software
self.log.info(f"- {node.version}") assert self.major_version_equals(node_v20, 20)
# Descriptor wallets appear to be corrupted wallets to old software for wallet_name in ["w1", "w2", "w3"]:
assert self.major_version_less_than(node, 21) assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v20.loadwallet, wallet_name)
for wallet_name in ["w1", "w2", "w3"]:
assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node.loadwallet, wallet_name)
# w1 cannot be opened by 0.21 since it contains a taproot descriptor # w1 cannot be opened by 0.21 since it contains a taproot descriptor
self.log.info("Test that 0.21 cannot open wallet containing tr() descriptors") self.log.info("Test that 0.21 cannot open wallet containing tr() descriptors")
@ -308,5 +380,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# Legacy wallets are no longer supported. Trying to load these should result in an error # Legacy wallets are no longer supported. Trying to load these should result in an error
assert_raises_rpc_error(-18, "The wallet appears to be a Legacy wallet, please use the wallet migration tool (migratewallet RPC)", node_master.restorewallet, wallet_name, backup_path) assert_raises_rpc_error(-18, "The wallet appears to be a Legacy wallet, please use the wallet migration tool (migratewallet RPC)", node_master.restorewallet, wallet_name, backup_path)
self.test_v22_inactivehdchain_path()
if __name__ == '__main__': if __name__ == '__main__':
BackwardsCompatibilityTest(__file__).main() BackwardsCompatibilityTest(__file__).main()

View file

@ -192,7 +192,7 @@ class RawTransactionsTest(BitcoinTestFramework):
watchonly_address = self.nodes[0].getnewaddress() watchonly_address = self.nodes[0].getnewaddress()
watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"] watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"]
self.watchonly_amount = Decimal(200) self.watchonly_amount = Decimal(200)
wwatch.importpubkey(watchonly_pubkey, "", True) wwatch.importpubkey(watchonly_pubkey, label="", rescan=True)
self.watchonly_utxo = self.create_outpoints(self.nodes[0], outputs=[{watchonly_address: self.watchonly_amount}])[0] self.watchonly_utxo = self.create_outpoints(self.nodes[0], outputs=[{watchonly_address: self.watchonly_amount}])[0]
# Lock UTXO so nodes[0] doesn't accidentally spend it # Lock UTXO so nodes[0] doesn't accidentally spend it

View file

@ -49,7 +49,7 @@ class WalletLabelsTest(BitcoinTestFramework):
assert_equal(response[0]['error']['message'], "Invalid label name") assert_equal(response[0]['error']['message'], "Invalid label name")
for rpc_call in rpc_calls: for rpc_call in rpc_calls:
assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*") assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, label="*")
def run_test(self): def run_test(self):
# Check that there's no UTXO on the node # Check that there's no UTXO on the node

View file

@ -24,34 +24,7 @@ SHA256_SUMS = {
"d86fc90824a85c38b25c8488115178d5785dbc975f5ff674f9f5716bc8ad6e65": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-arm-linux-gnueabihf.tar.gz"}, "d86fc90824a85c38b25c8488115178d5785dbc975f5ff674f9f5716bc8ad6e65": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-arm-linux-gnueabihf.tar.gz"},
"1b0a7408c050e3d09a8be8e21e183ef7ee570385dc41216698cc3ab392a484e7": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-osx64.tar.gz"}, "1b0a7408c050e3d09a8be8e21e183ef7ee570385dc41216698cc3ab392a484e7": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-osx64.tar.gz"},
"706e0472dbc933ed2757650d54cbcd780fd3829ebf8f609b32780c7eedebdbc9": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-x86_64-linux-gnu.tar.gz"}, "706e0472dbc933ed2757650d54cbcd780fd3829ebf8f609b32780c7eedebdbc9": {"tag": "v0.14.3", "tarball": "bitcoin-0.14.3-x86_64-linux-gnu.tar.gz"},
#
"d40f18b4e43c6e6370ef7db9131f584fbb137276ec2e3dba67a4b267f81cb644": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-aarch64-linux-gnu.tar.gz"},
"54fb877a148a6ad189a1e1ab1ff8b11181e58ff2aaf430da55b3fd46ae549a6b": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-arm-linux-gnueabihf.tar.gz"},
"87e9340ff3d382d543b2b69112376077f0c8b4f7450d372e83b68f5a1e22b2df": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-osx64.tar.gz"},
"566be44190fd76daa01f13d428939dadfb8e3daacefc8fa17f433cad28f73bd5": {"tag": "v0.15.2", "tarball": "bitcoin-0.15.2-x86_64-linux-gnu.tar.gz"},
#
"0768c6c15caffbaca6524824c9563b42c24f70633c681c2744649158aa3fd484": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-aarch64-linux-gnu.tar.gz"},
"fb2818069854a6ad20ea03b28b55dbd35d8b1f7d453e90b83eace5d0098a2a87": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-arm-linux-gnueabihf.tar.gz"},
"78c3bff3b619a19aed575961ea43cc9e142959218835cf51aede7f0b764fc25d": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-osx64.tar.gz"},
"5d422a9d544742bc0df12427383f9c2517433ce7b58cf672b9a9b17c2ef51e4f": {"tag": "v0.16.3", "tarball": "bitcoin-0.16.3-x86_64-linux-gnu.tar.gz"},
#
"5a6b35d1a348a402f2d2d6ab5aed653a1a1f13bc63aaaf51605e3501b0733b7a": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-aarch64-linux-gnu.tar.gz"},
"d1913a5d19c8e8da4a67d1bd5205d03c8614dfd2e02bba2fe3087476643a729e": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-arm-linux-gnueabihf.tar.gz"},
"a783ba20706dbfd5b47fbedf42165fce70fbbc7d78003305d964f6b3da14887f": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-osx64.tar.gz"},
"943f9362b9f11130177839116f48f809d83478b4c28591d486ee9a7e35179da6": {"tag": "v0.17.2", "tarball": "bitcoin-0.17.2-x86_64-linux-gnu.tar.gz"},
#
"88f343af72803b851c7da13874cc5525026b0b55e63e1b5e1298390c4688adc6": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-aarch64-linux-gnu.tar.gz"},
"cc7d483e4b20c5dabd4dcaf304965214cf4934bcc029ca99cbc9af00d3771a1f": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-arm-linux-gnueabihf.tar.gz"},
"b7bbcee7a7540f711b171d6981f939ca8482005fde22689bc016596d80548bb1": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-osx64.tar.gz"},
"425ee5ec631ae8da71ebc1c3f5c0269c627cf459379b9b030f047107a28e3ef8": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-riscv64-linux-gnu.tar.gz"},
"600d1db5e751fa85903e935a01a74f5cc57e1e7473c15fd3e17ed21e202cfe5a": {"tag": "v0.18.1", "tarball": "bitcoin-0.18.1-x86_64-linux-gnu.tar.gz"},
#
"3a80431717842672df682bdb619e66523b59541483297772a7969413be3502ff": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-aarch64-linux-gnu.tar.gz"},
"657f28213823d240dd3324d14829702f9ad6f0710f8bdd1c379cb3c447197f48": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-arm-linux-gnueabihf.tar.gz"},
"1ae1b87de26487075cd2fd22e0d4ead87d969bd55c44f2f1d873ecdc6147ebb3": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-osx64.tar.gz"},
"aa7a9563b48aa79252c8e7b6a41c07a5441bd9f14c5e4562cc72720ea6cb0ee5": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-riscv64-linux-gnu.tar.gz"},
"5fcac9416e486d4960e1a946145566350ca670f9aaba99de6542080851122e4c": {"tag": "v0.19.1", "tarball": "bitcoin-0.19.1-x86_64-linux-gnu.tar.gz"},
#
"60c93e3462c303eb080be7cf623f1a7684b37fd47a018ad3848bc23e13c84e1c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-aarch64-linux-gnu.tar.gz"}, "60c93e3462c303eb080be7cf623f1a7684b37fd47a018ad3848bc23e13c84e1c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-aarch64-linux-gnu.tar.gz"},
"55b577e0fb306fb429d4be6c9316607753e8543e5946b542d75d876a2f08654c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-arm-linux-gnueabihf.tar.gz"}, "55b577e0fb306fb429d4be6c9316607753e8543e5946b542d75d876a2f08654c": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-arm-linux-gnueabihf.tar.gz"},
"b9024dde373ea7dad707363e07ec7e265383204127539ae0c234bff3a61da0d1": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-osx64.tar.gz"}, "b9024dde373ea7dad707363e07ec7e265383204127539ae0c234bff3a61da0d1": {"tag": "v0.20.1", "tarball": "bitcoin-0.20.1-osx64.tar.gz"},

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Copyright 2014 BitPay Inc. # Copyright 2014 BitPay Inc.
# Copyright 2016-2017 The Bitcoin Core developers # Copyright 2016-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test framework for bitcoin utils. """Test framework for bitcoin utils.
@ -155,15 +155,16 @@ def bctest(testDir, testObj, buildenv):
if "error_txt" in testObj: if "error_txt" in testObj:
want_error = testObj["error_txt"] want_error = testObj["error_txt"]
# Compare error text # A partial match instead of an exact match makes writing tests easier
# TODO: ideally, we'd compare the strings exactly and also assert # and should be sufficient.
# That stderr is empty if no errors are expected. However, bitcoin-tx
# emits DISPLAY errors when running as a windows application on
# linux through wine. Just assert that the expected error text appears
# somewhere in stderr.
if want_error not in res.stderr: if want_error not in res.stderr:
logging.error(f"Error mismatch:\nExpected: {want_error}\nReceived: {res.stderr.rstrip()}\nres: {str(res)}") logging.error(f"Error mismatch:\nExpected: {want_error}\nReceived: {res.stderr.rstrip()}\nres: {str(res)}")
raise Exception raise Exception
else:
if res.stderr:
logging.error(f"Unexpected error received: {res.stderr.rstrip()}\nres: {str(res)}")
raise Exception
def parse_output(a, fmt): def parse_output(a, fmt):
"""Parse the output according to specified format. """Parse the output according to specified format.