Skip to content

Commit 8a08eef

Browse files
committed
tests: Check that the last hardened cache upgrade occurs
When loading an older wallet without the last hardened cache, an automatic upgrade should be performed. Check this in wallet_backwards_compatibility.py When migrating a wallet, the migrated wallet should always have the last hardened cache, so verify in wallet_migration.py
1 parent 0060429 commit 8a08eef

File tree

3 files changed

+66
-14
lines changed

3 files changed

+66
-14
lines changed

test/functional/test_framework/test_framework.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,3 +1082,15 @@ def convert_to_json_for_cli(self, text):
10821082
if self.options.usecli:
10831083
return json.dumps(text)
10841084
return text
1085+
1086+
def inspect_sqlite_db(self, path, fn, *args, **kwargs):
1087+
try:
1088+
import sqlite3 # type: ignore[import]
1089+
conn = sqlite3.connect(path)
1090+
with conn:
1091+
result = fn(conn, *args, **kwargs)
1092+
conn.close()
1093+
return result
1094+
except ImportError:
1095+
self.log.warning("sqlite3 module not available, skipping tests that inspect the database")
1096+

test/functional/wallet_backwards_compatibility.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
from test_framework.blocktools import COINBASE_MATURITY
2222
from test_framework.test_framework import BitcoinTestFramework
2323
from test_framework.descriptors import descsum_create
24+
from test_framework.messages import ser_string
2425

2526
from test_framework.util import (
2627
assert_equal,
28+
assert_greater_than,
2729
assert_raises_rpc_error,
2830
)
2931

@@ -149,18 +151,13 @@ def test_v22_inactivehdchain_path(self):
149151
assert_equal(bad_deriv_wallet_master.getaddressinfo(bad_path_addr)["hdkeypath"], good_deriv_path)
150152
bad_deriv_wallet_master.unloadwallet()
151153

152-
# If we have sqlite3, verify that there are no keymeta records
153-
try:
154-
import sqlite3
155-
wallet_db = node_master.wallets_path / wallet_name / "wallet.dat"
156-
conn = sqlite3.connect(wallet_db)
157-
with conn:
158-
# Retrieve all records that have the "keymeta" prefix. The remaining key data varies for each record.
159-
keymeta_rec = conn.execute("SELECT value FROM main where key >= x'076b65796d657461' AND key < x'076b65796d657462'").fetchone()
160-
assert_equal(keymeta_rec, None)
161-
conn.close()
162-
except ImportError:
163-
self.log.warning("sqlite3 module not available, skipping lack of keymeta records check")
154+
def check_keymeta(conn):
155+
# Retrieve all records that have the "keymeta" prefix. The remaining key data varies for each record.
156+
keymeta_rec = conn.execute(f"SELECT value FROM main where key >= x'{ser_string(b'keymeta').hex()}' AND key < x'{ser_string(b'keymetb').hex()}'").fetchone()
157+
assert_equal(keymeta_rec, None)
158+
159+
wallet_db = node_master.wallets_path / wallet_name / "wallet.dat"
160+
self.inspect_sqlite_db(wallet_db, check_keymeta)
164161

165162
def test_ignore_legacy_during_startup(self, legacy_nodes, node_master):
166163
self.log.info("Test that legacy wallets are ignored during startup on v29+")
@@ -342,6 +339,13 @@ def run_test(self):
342339
# Remove the wallet from old node
343340
wallet_prev.unloadwallet()
344341

342+
# Open backup with sqlite and get flags
343+
def get_flags(conn):
344+
flags_rec = conn.execute(f"SELECT value FROM main WHERE key = x'{ser_string(b'flags').hex()}'").fetchone()
345+
return int.from_bytes(flags_rec[0], byteorder="little")
346+
347+
old_flags = self.inspect_sqlite_db(backup_path, get_flags)
348+
345349
# Restore the wallet to master
346350
load_res = node_master.restorewallet(wallet_name, backup_path)
347351

@@ -378,6 +382,21 @@ def run_test(self):
378382

379383
wallet.unloadwallet()
380384

385+
# Open the wallet with sqlite and inspect the flags and records
386+
def check_upgraded_records(conn, old_flags):
387+
flags_rec = conn.execute(f"SELECT value FROM main WHERE key = x'{ser_string(b'flags').hex()}'").fetchone()
388+
new_flags = int.from_bytes(flags_rec[0], byteorder="little")
389+
diff_flags = new_flags & ~old_flags
390+
391+
# Check for last hardened xpubs if the flag is newly set
392+
if diff_flags & (1 << 2):
393+
self.log.debug("Checking descriptor cache was upgraded")
394+
# Fetch all records with the walletdescriptorlhcache prefix
395+
lh_cache_recs = conn.execute(f"SELECT value FROM main where key >= x'{ser_string(b'walletdescriptorlhcache').hex()}' AND key < x'{ser_string(b'walletdescriptorlhcachf').hex()}'").fetchall()
396+
assert_greater_than(len(lh_cache_recs), 0)
397+
398+
self.inspect_sqlite_db(down_backup_path, check_upgraded_records, old_flags)
399+
381400
# Check that no automatic upgrade broke downgrading the wallet
382401
target_dir = node.wallets_path / down_wallet_name
383402
os.makedirs(target_dir, exist_ok=True)

test/functional/wallet_migration.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
from test_framework.descriptors import descsum_create
1919
from test_framework.key import ECPubKey
2020
from test_framework.test_framework import BitcoinTestFramework
21-
from test_framework.messages import COIN, CTransaction, CTxOut
21+
from test_framework.messages import COIN, CTransaction, CTxOut, ser_string
2222
from test_framework.script import hash160
2323
from test_framework.script_util import key_to_p2pkh_script, key_to_p2pk_script, script_to_p2sh_script, script_to_p2wsh_script
2424
from test_framework.util import (
2525
assert_equal,
26+
assert_greater_than,
2627
assert_raises_rpc_error,
2728
find_vout_for_address,
2829
sha256sum_file,
@@ -139,7 +140,8 @@ def migrate_and_get_rpc(self, wallet_name, **kwargs):
139140
# (in which case the wallet name would be suffixed by the 'watchonly' term)
140141
migrated_wallet_name = migrate_info['wallet_name']
141142
wallet = self.master_node.get_wallet_rpc(migrated_wallet_name)
142-
assert_equal(wallet.getwalletinfo()["descriptors"], True)
143+
wallet_info = wallet.getwalletinfo()
144+
assert_equal(wallet_info["descriptors"], True)
143145
self.assert_is_sqlite(migrated_wallet_name)
144146
# Always verify the backup path exist after migration
145147
assert os.path.exists(migrate_info['backup_path'])
@@ -151,6 +153,25 @@ def migrate_and_get_rpc(self, wallet_name, **kwargs):
151153
expected_backup_path = self.master_node.wallets_path / f"{backup_prefix}_{mocked_time}.legacy.bak"
152154
assert_equal(str(expected_backup_path), migrate_info['backup_path'])
153155

156+
# Open the wallet with sqlite and verify that the wallet has the last hardened cache flag
157+
# set and the last hardened cache entries
158+
def check_last_hardened(conn):
159+
flags_rec = conn.execute(f"SELECT value FROM main WHERE key = x'{ser_string(b'flags').hex()}'").fetchone()
160+
flags = int.from_bytes(flags_rec[0], byteorder="little")
161+
162+
# All wallets should have the upgrade flag set
163+
assert_equal(bool(flags & (1 << 2)), True)
164+
165+
# Fetch all records with the walletdescriptorlhcache prefix
166+
# if the wallet has private keys and is not blank
167+
if wallet_info["private_keys_enabled"] and not wallet_info["blank"]:
168+
lh_cache_recs = conn.execute(f"SELECT value FROM main where key >= x'{ser_string(b'walletdescriptorlhcache').hex()}' AND key < x'{ser_string(b'walletdescriptorlhcachf').hex()}'").fetchall()
169+
assert_greater_than(len(lh_cache_recs), 0)
170+
171+
inspect_path = os.path.join(self.options.tmpdir, os.path.basename(f"{migrated_wallet_name}_inspect.dat"))
172+
wallet.backupwallet(inspect_path)
173+
self.inspect_sqlite_db(inspect_path, check_last_hardened)
174+
154175
return migrate_info, wallet
155176

156177
def test_basic(self):

0 commit comments

Comments
 (0)