test: coverage for migration failure when last sync is beyond prune height
What changed, and why it matters
This commit only adds a new automated test to Bitcoin Core. It checks that when a user tries to migrate an old wallet on a pruned node, and the wallet's last known block has already been pruned (deleted) from the local copy of the blockchain, the migration fails with a clear error message instead of doing something unsafe. There is no change to production wallet or node code.
No security action needed; review as ordinary test coverage improvement.
Security signals we found
No production code changes
Adds regression/behavioral test only
Tests graceful failure path for wallet migration on pruned nodes
Evidence from the diff
The diff adds one test method, unsynced_wallet_on_pruned_node_fails(), to test/functional/wallet_migration.py. The test creates a legacy wallet, copies it to a pruned master node, prunes the blockchain past the wallet’s lastprocessedblock, and asserts that migratewallet raises RPC error -4 with the expected message about needing to -reindex. It also verifies the wallet remains BDB and a backup file is created. No C++/Python production logic is modified.
Changed components
test/functional/wallet_migration.pyInspect captured patch +34 / −0
diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py
index f802c186..e90c48fa 100755
--- a/test/functional/wallet_migration.py
+++ b/test/functional/wallet_migration.py
@@ -1603,6 +1603,37 @@ class WalletMigrationTest(BitcoinTestFramework):
self.start_node(self.old_node.index)
self.connect_nodes(1, 0)
+ def unsynced_wallet_on_pruned_node_fails(self):
+ self.log.info("Test migration of an unsynced wallet on a pruned node fails gracefully")
+ wallet = self.create_legacy_wallet("", load_on_startup=False)
+ last_wallet_synced_block = wallet.getwalletinfo()['lastprocessedblock']['height']
+ wallet.unloadwallet()
+
+ shutil.copyfile(self.old_node.wallets_path / "wallet.dat", self.master_node.wallets_path / "wallet.dat")
+
+ # Generate blocks just so the wallet best block is pruned
+ self.restart_node(0, ["-fastprune", "-prune=1", "-nowallet"])
+ self.connect_nodes(0, 1)
+ self.generate(self.master_node, 450, sync_fun=self.no_op)
+ self.master_node.pruneblockchain(250)
+ # Ensure next block to sync is unavailable
+ assert_raises_rpc_error(-1, "Block not available (pruned data)", self.master_node.getblock, self.master_node.getblockhash(last_wallet_synced_block + 1))
+
+ # Check migration failure
+ mocked_time = int(time.time())
+ self.master_node.setmocktime(mocked_time)
+ assert_raises_rpc_error(-4, "last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)", self.master_node.migratewallet, wallet_name="")
+ self.master_node.setmocktime(0)
+
+ # Verify the /wallets/ path exists, the wallet is still BDB and the backup file is there.
+ assert self.master_node.wallets_path.exists()
+ self.assert_is_bdb("")
+ backup_path = self.master_node.wallets_path / f"default_wallet_{mocked_time}.legacy.bak"
+ assert backup_path.exists()
+
+ self.clear_default_wallet(backup_path)
+
+
def run_test(self):
self.master_node = self.nodes[0]
self.old_node = self.nodes[1]
@@ -1643,5 +1674,8 @@ class WalletMigrationTest(BitcoinTestFramework):
self.test_solvable_no_privs()
self.test_loading_failure_after_migration()
+ # Note: After this test the first 250 blocks of 'master_node' are pruned
+ self.unsynced_wallet_on_pruned_node_fails()
+
if __name__ == '__main__':
WalletMigrationTest(__file__).main()
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.