From 9af87cf3485ce3fac553a284cde37a35d1085c25 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Mon, 16 Oct 2023 14:56:10 -0400 Subject: test: Check that a failed wallet migration is cleaned up --- test/functional/wallet_migration.py | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'test/functional') diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 286dcb5fda..33ab8bf3f8 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -6,11 +6,14 @@ import random import shutil +import struct + from test_framework.address import ( script_to_p2sh, key_to_p2pkh, key_to_p2wpkh, ) +from test_framework.bdb import BTREE_MAGIC from test_framework.descriptors import descsum_create from test_framework.key import ECPubKey from test_framework.test_framework import BitcoinTestFramework @@ -20,6 +23,7 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, find_vout_for_address, + sha256sum_file, ) from test_framework.wallet_util import ( get_generate_key, @@ -827,6 +831,43 @@ class WalletMigrationTest(BitcoinTestFramework): wallet.unloadwallet() + def test_failed_migration_cleanup(self): + self.log.info("Test that a failed migration is cleaned up") + wallet = self.create_legacy_wallet("failed") + + # Make a copy of the wallet with the solvables wallet name so that we are unable + # to create the solvables wallet when migrating, thus failing to migrate + wallet.unloadwallet() + solvables_path = self.nodes[0].wallets_path / "failed_solvables" + shutil.copytree(self.nodes[0].wallets_path / "failed", solvables_path) + original_shasum = sha256sum_file(solvables_path / "wallet.dat") + + self.nodes[0].loadwallet("failed") + + # Add a multisig so that a solvables wallet is created + wallet.addmultisigaddress(2, [wallet.getnewaddress(), get_generate_key().pubkey]) + wallet.importaddress(get_generate_key().p2pkh_addr) + + assert_raises_rpc_error(-4, "Failed to create new watchonly wallet", wallet.migratewallet) + + assert "failed" in self.nodes[0].listwallets() + assert "failed_watchonly" not in self.nodes[0].listwallets() + assert "failed_solvables" not in self.nodes[0].listwallets() + + assert not (self.nodes[0].wallets_path / "failed_watchonly").exists() + # Since the file in failed_solvables is one that we put there, migration shouldn't touch it + assert solvables_path.exists() + new_shasum = sha256sum_file(solvables_path / "wallet.dat") + assert_equal(original_shasum, new_shasum) + + wallet.unloadwallet() + # Check the wallet we tried to migrate is still BDB + with open(self.nodes[0].wallets_path / "failed" / "wallet.dat", "rb") as f: + data = f.read(16) + _, _, magic = struct.unpack("QII", data) + assert_equal(magic, BTREE_MAGIC) + + def run_test(self): self.generate(self.nodes[0], 101) @@ -845,6 +886,7 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_migrate_raw_p2sh() self.test_conflict_txs() self.test_hybrid_pubkey() + self.test_failed_migration_cleanup() if __name__ == '__main__': WalletMigrationTest().main() -- cgit v1.2.3 From d616d30ea5fdfb897f8375ffd8b9f4536ae7835b Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 6 Oct 2023 15:58:13 -0400 Subject: wallet: Reload watchonly and solvables wallets after migration When migrating, create the watchonly and solvables wallets without a context. Then unload and reload them after migration completes, as we do for the actual wallet. There is also additional handling for a failed reload. --- test/functional/wallet_migration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'test/functional') diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 33ab8bf3f8..ad8dbe78ed 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -848,7 +848,7 @@ class WalletMigrationTest(BitcoinTestFramework): wallet.addmultisigaddress(2, [wallet.getnewaddress(), get_generate_key().pubkey]) wallet.importaddress(get_generate_key().p2pkh_addr) - assert_raises_rpc_error(-4, "Failed to create new watchonly wallet", wallet.migratewallet) + assert_raises_rpc_error(-4, "Failed to create database", wallet.migratewallet) assert "failed" in self.nodes[0].listwallets() assert "failed_watchonly" not in self.nodes[0].listwallets() -- cgit v1.2.3 From 4814e4063e674ad9b0a5c7e56059cd6a2bf9b764 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Fri, 6 Oct 2023 17:16:39 -0400 Subject: test: Check tx metadata is migrated to watchonly --- test/functional/wallet_migration.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'test/functional') diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index ad8dbe78ed..aede9281d5 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -7,6 +7,7 @@ import random import shutil import struct +import time from test_framework.address import ( script_to_p2sh, @@ -315,12 +316,17 @@ class WalletMigrationTest(BitcoinTestFramework): sent_watchonly_txid = send["txid"] self.generate(self.nodes[0], 1) + received_watchonly_tx_info = imports0.gettransaction(received_watchonly_txid, True) + received_sent_watchonly_tx_info = imports0.gettransaction(received_sent_watchonly_txid, True) balances = imports0.getbalances() spendable_bal = balances["mine"]["trusted"] watchonly_bal = balances["watchonly"]["trusted"] assert_equal(len(imports0.listtransactions(include_watchonly=True)), 4) + # Mock time forward a bit so we can check that tx metadata is preserved + self.nodes[0].setmocktime(int(time.time()) + 100) + # Migrate imports0.migratewallet() assert_equal(imports0.getwalletinfo()["descriptors"], True) @@ -338,8 +344,12 @@ class WalletMigrationTest(BitcoinTestFramework): assert_equal(watchonly_info["descriptors"], True) self.assert_is_sqlite("imports0_watchonly") assert_equal(watchonly_info["private_keys_enabled"], False) - watchonly.gettransaction(received_watchonly_txid) - watchonly.gettransaction(received_sent_watchonly_txid) + received_migrated_watchonly_tx_info = watchonly.gettransaction(received_watchonly_txid) + assert_equal(received_watchonly_tx_info["time"], received_migrated_watchonly_tx_info["time"]) + assert_equal(received_watchonly_tx_info["timereceived"], received_migrated_watchonly_tx_info["timereceived"]) + received_sent_migrated_watchonly_tx_info = watchonly.gettransaction(received_sent_watchonly_txid) + assert_equal(received_sent_watchonly_tx_info["time"], received_sent_migrated_watchonly_tx_info["time"]) + assert_equal(received_sent_watchonly_tx_info["timereceived"], received_sent_migrated_watchonly_tx_info["timereceived"]) watchonly.gettransaction(sent_watchonly_txid) assert_equal(watchonly.getbalance(), watchonly_bal) assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", watchonly.gettransaction, received_txid) -- cgit v1.2.3