fix(migrate): correct fund-adjacent migrate-to-seed bugs (W3-1, W3-2, W3-4)
Migrate-to-seed (legacy -> mnemonic wallet) moves real funds; three correctness fixes: - W3-1 (High): beginAdoptSeedWallet swapped a hardcoded datadir/wallet.dat instead of the ACTIVE wallet file. With a non-default active wallet (e.g. wallet-2.dat) it installed the swept seed wallet into an unloaded wallet.dat and left the daemon reloading the emptied legacy wallet — swept funds only recoverable via the seed phrase. Now swaps datadir + "/" + getActiveWalletFile(), captured on the main thread (switching is blocked during migration, so no race). - W3-2 (High): SeedWalletCreator::create() ran remove_all(<config>/seed-migrate) unconditionally at the start, so a prior migration that swept funds into the temp wallet but was abandoned/crashed before adopting would have that fund-bearing wallet destroyed. It now refuses (with a clear message) when DRAGONX/wallet.dat already exists — a completed migration removes the dir on adopt, so a leftover means an unfinished one. - W3-4 (Med): switchToWallet blocked switching only while the migration dialog was open; closing it via "Later" mid-migration dropped the guard. Now also blocks while getSeedMigrationPending(). Build-clean; ctest 1/1. Remaining P1-A: W3-3 (persist the sweep opid). See docs/wallet-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1121,7 +1121,9 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed
|
||||
ui::Notifications::instance().warning("A rescan or repair is in progress — try again once it finishes.");
|
||||
return;
|
||||
}
|
||||
if (show_seed_migration_) {
|
||||
// W3-4: block switching while a migration is PENDING, not only while its dialog is open — closing
|
||||
// the dialog via "Later" mid-migration leaves the pending state but previously dropped this guard.
|
||||
if (show_seed_migration_ || (settings_ && settings_->getSeedMigrationPending())) {
|
||||
ui::Notifications::instance().warning("Finish or cancel the seed migration before switching wallets.");
|
||||
return;
|
||||
}
|
||||
@@ -4322,7 +4324,13 @@ void App::beginAdoptSeedWallet()
|
||||
// has its own passphrase; the user can re-enable PIN quick-unlock for it).
|
||||
if (vault_) vault_->removeVault();
|
||||
const std::string base = seed_migration_temp_dir_;
|
||||
async_tasks_.submit("Adopt seed wallet", [this, base](const util::AsyncTaskManager::Token&) {
|
||||
// W3-1: adopt must swap the ACTIVE wallet file (multi-wallet), not a hardcoded "wallet.dat" —
|
||||
// otherwise a migration run while e.g. wallet-2.dat is active would install the swept seed wallet
|
||||
// into an unloaded wallet.dat and leave the daemon loading the (now-emptied) legacy wallet.
|
||||
// Captured on the main thread; wallet switching is blocked during migration so this can't race.
|
||||
const std::string activeWalletName = (settings_ && !settings_->getActiveWalletFile().empty())
|
||||
? settings_->getActiveWalletFile() : std::string("wallet.dat");
|
||||
async_tasks_.submit("Adopt seed wallet", [this, base, activeWalletName](const util::AsyncTaskManager::Token&) {
|
||||
namespace fs = std::filesystem;
|
||||
std::string err; // fatal (swap did not happen; migration incomplete)
|
||||
std::string warn; // non-fatal (swap done but the daemon did not restart)
|
||||
@@ -4342,7 +4350,7 @@ void App::beginAdoptSeedWallet()
|
||||
// 2. Swap wallet.dat. Move the legacy one aside to a timestamped backup (NEVER
|
||||
// delete), then copy the new seed wallet in. On any failure, restore the legacy.
|
||||
const std::string datadir = util::Platform::getDragonXDataDir();
|
||||
const std::string legacy = datadir + "/wallet.dat";
|
||||
const std::string legacy = datadir + "/" + activeWalletName;
|
||||
const std::string newWallet = base + "/DRAGONX/wallet.dat";
|
||||
std::time_t t = std::time(nullptr);
|
||||
std::tm tmv{}; // thread-safe local time (the UI thread also uses localtime)
|
||||
|
||||
@@ -54,6 +54,16 @@ SeedWalletResult SeedWalletCreator::create(bool keepDatadir,
|
||||
// RPC port. So the wallet lives in <base>/DRAGONX; `base` is the migration root we clean up.
|
||||
const std::string base = util::Platform::getConfigDir() + "/seed-migrate";
|
||||
const std::string dataDir = base + "/DRAGONX";
|
||||
// W3-2: never blindly wipe a pre-existing temp seed wallet. A prior migration that swept funds into
|
||||
// it but was abandoned or crashed before adopting would otherwise have its (fund-bearing) wallet
|
||||
// destroyed here. A completed migration removes this dir on adopt, so a leftover means an unfinished
|
||||
// one — refuse and point the user at it rather than silently destroying it.
|
||||
if (fs::exists(dataDir + "/wallet.dat")) {
|
||||
r.error = "A previous seed migration looks unfinished — its temporary wallet is still at\n" + base +
|
||||
"\nResume or cancel it first. If you are certain its funds are already in your main "
|
||||
"wallet, delete that folder and try again.";
|
||||
return r;
|
||||
}
|
||||
fs::remove_all(base, ec);
|
||||
fs::create_directories(dataDir, ec);
|
||||
if (ec) { r.error = "Could not create the temporary wallet directory."; return r; }
|
||||
|
||||
Reference in New Issue
Block a user