test: restorewallet, coverage for existing dirs, unnamed wallet and prune failure
What changed, and why it matters
This commit only adds new automated tests for the wallet restore feature in Bitcoin Core. It does not change any production wallet code. The tests verify that restoring a wallet backup behaves correctly in edge cases: restoring into an empty directory, a directory with unrelated files, a directory that already contains a wallet database file, the default unnamed wallet file, and a pruned node where the backup is too old. Because no actual wallet logic is modified, this commit does not introduce or fix a live security issue on its own.
No security action required. Review the tests as normal QA/regression coverage. If these tests were added because of a known bug, look for the corresponding production-code fix in a separate commit.
Security signals we found
test-only change
no production code modified
no RPC behavior changes
adds regression/edge-case coverage for restorewallet
Evidence from the diff
The diff extends test/functional/wallet_backup.py with three new test methods: test_restore_existent_dir checks restorewallet behavior against existing directories and pre-existing .dat files; test_restore_into_unnamed_wallet exercises restoring into the default wallet.dat (empty wallet name); and test_pruned_wallet_backup is extended to assert that restore fails with the expected prune error when the backup’s last sync is beyond the prune height. The commit imports sha256sum_file and adds 65 lines of test code. No C++/Python production code in the wallet or node implementation is changed.
Changed components
test/functional/wallet_backup.pyInspect captured patch +65 / −0
diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py
index 2c78f038..5cf02ba3 100755
--- a/test/functional/wallet_backup.py
+++ b/test/functional/wallet_backup.py
@@ -39,6 +39,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
+ sha256sum_file,
)
@@ -136,6 +137,61 @@ class WalletBackupTest(BitcoinTestFramework):
assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)
assert wallet_file.exists()
+ def test_restore_existent_dir(self):
+ self.log.info("Test restore on an existent empty directory")
+ node = self.nodes[3]
+ backup_file = self.nodes[0].datadir_path / 'wallet.bak'
+ wallet_name = "restored_wallet"
+ wallet_dir = node.wallets_path / wallet_name
+ os.mkdir(wallet_dir)
+ res = node.restorewallet(wallet_name, backup_file)
+ assert_equal(res['name'], wallet_name)
+ node.unloadwallet(wallet_name)
+
+ self.log.info("Test restore succeeds when the target directory contains non-wallet files")
+ wallet_file = node.wallets_path / wallet_name / "wallet.dat"
+ os.remove(wallet_file)
+ extra_file = node.wallets_path / wallet_name / "not_a_wallet.txt"
+ extra_file.touch()
+ res = node.restorewallet(wallet_name, backup_file)
+ assert_equal(res['name'], wallet_name)
+ assert extra_file.exists() # extra file was not removed by mistake
+ node.unloadwallet(wallet_name)
+
+ self.log.info("Test restore failure due to existing db file in the destination directory")
+ original_shasum = sha256sum_file(wallet_file)
+ error_message = "Failed to restore wallet. Database file exists in '{}'.".format(wallet_dir / "wallet.dat")
+ assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)
+ # Ensure the wallet file remains untouched
+ assert wallet_dir.exists()
+ assert_equal(original_shasum, sha256sum_file(wallet_file))
+
+ self.log.info("Test restore succeeds when the .dat file in the destination has a different name")
+ second_wallet = wallet_dir / "hidden_storage.dat"
+ os.rename(wallet_dir / "wallet.dat", second_wallet)
+ original_shasum = sha256sum_file(second_wallet)
+ res = node.restorewallet(wallet_name, backup_file)
+ assert_equal(res['name'], wallet_name)
+ assert (wallet_dir / "hidden_storage.dat").exists()
+ assert_equal(original_shasum, sha256sum_file(second_wallet))
+ node.unloadwallet(wallet_name)
+
+ # Clean for follow-up tests
+ os.remove(wallet_file)
+
+ def test_restore_into_unnamed_wallet(self):
+ self.log.info("Test restore into a default unnamed wallet")
+ # This is also useful to test the migration recovery after failure logic
+ node = self.nodes[3]
+ backup_file = self.nodes[0].datadir_path / 'wallet.bak'
+ wallet_name = ""
+ res = node.restorewallet(wallet_name, backup_file)
+ assert_equal(res['name'], "")
+ assert (node.wallets_path / "wallet.dat").exists()
+ # Clean for follow-up tests
+ node.unloadwallet("")
+ os.remove(node.wallets_path / "wallet.dat")
+
def test_pruned_wallet_backup(self):
self.log.info("Test loading backup on a pruned node when the backup was created close to the prune height of the restoring node")
node = self.nodes[3]
@@ -155,6 +211,13 @@ class WalletBackupTest(BitcoinTestFramework):
# the backup to load successfully this close to the prune height
node.restorewallet('pruned', node.datadir_path / 'wallet_pruned.bak')
+ self.log.info("Test restore on a pruned node when the backup was beyond the pruning point")
+ backup_file = self.nodes[0].datadir_path / 'wallet.bak'
+ wallet_name = ""
+ error_message = "Wallet loading failed. Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)"
+ assert_raises_rpc_error(-4, error_message, node.restorewallet, wallet_name, backup_file)
+ assert node.wallets_path.exists() # ensure the wallets dir exists
+
def run_test(self):
self.log.info("Generating initial blockchain")
self.generate(self.nodes[0], 1)
@@ -219,6 +282,8 @@ class WalletBackupTest(BitcoinTestFramework):
assert_equal(res2_rpc.getbalance(), balance2)
self.restore_wallet_existent_name()
+ self.test_restore_existent_dir()
+ self.test_restore_into_unnamed_wallet()
# Backup to source wallet file must fail
sourcePaths = [
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.