Skip to content

Commit d616d30

Browse files
committed
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.
1 parent 118f2d7 commit d616d30

File tree

2 files changed

+62
-24
lines changed

2 files changed

+62
-24
lines changed

src/wallet/wallet.cpp

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4083,6 +4083,10 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
40834083
DatabaseOptions options;
40844084
options.require_existing = false;
40854085
options.require_create = true;
4086+
options.require_format = DatabaseFormat::SQLITE;
4087+
4088+
WalletContext empty_context;
4089+
empty_context.args = context.args;
40864090

40874091
// Make the wallets
40884092
options.create_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_DESCRIPTORS;
@@ -4098,8 +4102,14 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
40984102
DatabaseStatus status;
40994103
std::vector<bilingual_str> warnings;
41004104
std::string wallet_name = wallet.GetName() + "_watchonly";
4101-
data->watchonly_wallet = CreateWallet(context, wallet_name, std::nullopt, options, status, error, warnings);
4102-
if (status != DatabaseStatus::SUCCESS) {
4105+
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
4106+
if (!database) {
4107+
error = strprintf(_("Wallet file creation failed: %s"), error);
4108+
return false;
4109+
}
4110+
4111+
data->watchonly_wallet = CWallet::Create(empty_context, wallet_name, std::move(database), options.create_flags, error, warnings);
4112+
if (!data->watchonly_wallet) {
41034113
error = _("Error: Failed to create new watchonly wallet");
41044114
return false;
41054115
}
@@ -4129,8 +4139,14 @@ bool DoMigration(CWallet& wallet, WalletContext& context, bilingual_str& error,
41294139
DatabaseStatus status;
41304140
std::vector<bilingual_str> warnings;
41314141
std::string wallet_name = wallet.GetName() + "_solvables";
4132-
data->solvable_wallet = CreateWallet(context, wallet_name, std::nullopt, options, status, error, warnings);
4133-
if (status != DatabaseStatus::SUCCESS) {
4142+
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
4143+
if (!database) {
4144+
error = strprintf(_("Wallet file creation failed: %s"), error);
4145+
return false;
4146+
}
4147+
4148+
data->solvable_wallet = CWallet::Create(empty_context, wallet_name, std::move(database), options.create_flags, error, warnings);
4149+
if (!data->solvable_wallet) {
41344150
error = _("Error: Failed to create new watchonly wallet");
41354151
return false;
41364152
}
@@ -4233,47 +4249,69 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
42334249
success = DoMigration(*local_wallet, context, error, res);
42344250
}
42354251

4252+
// In case of reloading failure, we need to remember the wallet dirs to remove
4253+
// Set is used as it may be populated with the same wallet directory paths multiple times,
4254+
// both before and after reloading. This ensures the set is complete even if one of the wallets
4255+
// fails to reload.
4256+
std::set<fs::path> wallet_dirs;
42364257
if (success) {
4237-
// Migration successful, unload the wallet locally, then reload it.
4238-
assert(local_wallet.use_count() == 1);
4239-
local_wallet.reset();
4240-
res.wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
4258+
// Migration successful, unload all wallets locally, then reload them.
4259+
const auto& reload_wallet = [&](std::shared_ptr<CWallet>& to_reload) {
4260+
assert(to_reload.use_count() == 1);
4261+
std::string name = to_reload->GetName();
4262+
wallet_dirs.insert(fs::PathFromString(to_reload->GetDatabase().Filename()).parent_path());
4263+
to_reload.reset();
4264+
to_reload = LoadWallet(context, name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
4265+
return to_reload != nullptr;
4266+
};
4267+
// Reload the main wallet
4268+
success = reload_wallet(local_wallet);
4269+
res.wallet = local_wallet;
42414270
res.wallet_name = wallet_name;
4242-
} else {
4271+
if (success && res.watchonly_wallet) {
4272+
// Reload watchonly
4273+
success = reload_wallet(res.watchonly_wallet);
4274+
}
4275+
if (success && res.solvables_wallet) {
4276+
// Reload solvables
4277+
success = reload_wallet(res.solvables_wallet);
4278+
}
4279+
}
4280+
if (!success) {
42434281
// Migration failed, cleanup
42444282
// Copy the backup to the actual wallet dir
42454283
fs::path temp_backup_location = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename);
42464284
fs::copy_file(backup_path, temp_backup_location, fs::copy_options::none);
42474285

4248-
// Remember this wallet's walletdir to remove after unloading
4249-
std::vector<fs::path> wallet_dirs;
4250-
wallet_dirs.emplace_back(fs::PathFromString(local_wallet->GetDatabase().Filename()).parent_path());
4251-
4252-
// Unload the wallet locally
4253-
assert(local_wallet.use_count() == 1);
4254-
local_wallet.reset();
4255-
42564286
// Make list of wallets to cleanup
42574287
std::vector<std::shared_ptr<CWallet>> created_wallets;
4288+
if (local_wallet) created_wallets.push_back(std::move(local_wallet));
42584289
if (res.watchonly_wallet) created_wallets.push_back(std::move(res.watchonly_wallet));
42594290
if (res.solvables_wallet) created_wallets.push_back(std::move(res.solvables_wallet));
42604291

42614292
// Get the directories to remove after unloading
42624293
for (std::shared_ptr<CWallet>& w : created_wallets) {
4263-
wallet_dirs.emplace_back(fs::PathFromString(w->GetDatabase().Filename()).parent_path());
4294+
wallet_dirs.emplace(fs::PathFromString(w->GetDatabase().Filename()).parent_path());
42644295
}
42654296

42664297
// Unload the wallets
42674298
for (std::shared_ptr<CWallet>& w : created_wallets) {
4268-
if (!RemoveWallet(context, w, /*load_on_start=*/false)) {
4269-
error += _("\nUnable to cleanup failed migration");
4270-
return util::Error{error};
4299+
if (w->HaveChain()) {
4300+
// Unloading for wallets that were loaded for normal use
4301+
if (!RemoveWallet(context, w, /*load_on_start=*/false)) {
4302+
error += _("\nUnable to cleanup failed migration");
4303+
return util::Error{error};
4304+
}
4305+
UnloadWallet(std::move(w));
4306+
} else {
4307+
// Unloading for wallets in local context
4308+
assert(w.use_count() == 1);
4309+
w.reset();
42714310
}
4272-
UnloadWallet(std::move(w));
42734311
}
42744312

42754313
// Delete the wallet directories
4276-
for (fs::path& dir : wallet_dirs) {
4314+
for (const fs::path& dir : wallet_dirs) {
42774315
fs::remove_all(dir);
42784316
}
42794317

test/functional/wallet_migration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ def test_failed_migration_cleanup(self):
848848
wallet.addmultisigaddress(2, [wallet.getnewaddress(), get_generate_key().pubkey])
849849
wallet.importaddress(get_generate_key().p2pkh_addr)
850850

851-
assert_raises_rpc_error(-4, "Failed to create new watchonly wallet", wallet.migratewallet)
851+
assert_raises_rpc_error(-4, "Failed to create database", wallet.migratewallet)
852852

853853
assert "failed" in self.nodes[0].listwallets()
854854
assert "failed_watchonly" not in self.nodes[0].listwallets()

0 commit comments

Comments
 (0)