diff --git a/src/lib.rs b/src/lib.rs index dd8ee8a5b..896911f05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,6 +187,70 @@ fn setup_sqlite( Ok(sqlite) } +// Try to copy the watchonly wallet from its former location on Windows to its new location. +fn copy_watchonly_wallet( + bitcoind_cookie_path: &path::Path, + bitcoin_net: miniscript::bitcoin::Network, + wallet_name: &str, + new_wallet_path: &path::Path, +) -> bool { + // For the main network both the wallet and the cookie file are stored at the root of the + // datadir. For test networks the wallet is in "//wallets//" and + // the cookie file in "//". + let parent_dir = match bitcoind_cookie_path.parent() { + Some(d) => d, + None => { + log::error!("Could not find the parent directory of the bitcoind cookie file."); + return false; + } + }; + let wallet_path = match bitcoin_net { + miniscript::bitcoin::Network::Bitcoin => parent_dir.join(wallet_name), + miniscript::bitcoin::Network::Testnet + | miniscript::bitcoin::Network::Signet + | miniscript::bitcoin::Network::Regtest => parent_dir + .join("wallets") + .join(wallet_name) + .join("wallet.dat"), + net => panic!( + "Unsupported network '{}', unknown at the time of writing.", + net + ), + }; + + if wallet_path.exists() { + log::info!( + "Found the watchonly wallet file at the former location: '{}'. Copying it to our own datadir.", + wallet_path.as_path().to_string_lossy() + ); + if let Err(e) = fs::create_dir(&new_wallet_path) { + log::error!( + "Error while creating the watchonly wallet directory at {}: {}", + wallet_path.to_string_lossy(), + e + ); + return false; + } + let new_wallet_dat_path = new_wallet_path.join(format!("{}.dat", wallet_name)); + if let Err(e) = fs::copy(wallet_path, new_wallet_dat_path) { + log::error!( + "Error while copying the watchonly wallet to our own datadir: {}", + e + ); + false + } else { + log::info!("Successfully copied the watchonly wallet file."); + true + } + } else { + log::error!( + "No watchonly wallet found at '{}'.", + wallet_path.as_path().to_string_lossy() + ); + false + } +} + // Connect to bitcoind. Setup the watchonly wallet, and do some sanity checks. // If all went well, returns the interface to bitcoind. fn setup_bitcoind( @@ -220,6 +284,24 @@ fn setup_bitcoind( log::info!("Creating a new watchonly wallet on bitcoind."); bitcoind.create_watchonly_wallet(&config.main_descriptor)?; log::info!("Watchonly wallet created."); + } else if !wo_path.exists() && !cfg!(test) { + // TODO: remove this hack. + // NOTE: this is Windows-specific but we don't gate it upon a windows target to be able to + // test this codepath in a functional test. + log::info!( + "A data directory exists with no watchonly wallet. This is most likely due to \ + having been created to an older version of Liana on Windows, where the watchonly \ + wallet would live under bitcoind's datadir. Trying to find the older wallet and copy it in our own datadir." + ); + let wo_name = "lianad_watchonly_wallet"; + if !copy_watchonly_wallet( + &bitcoind_config.cookie_path, + config.bitcoin_config.network, + wo_name, + wo_path.as_path(), + ) { + panic!("Cannot continue without a watchonly wallet. Please contact support."); + } } log::info!("Loading our watchonly wallet on bitcoind."); bitcoind.maybe_load_watchonly_wallet()?; diff --git a/tests/test_misc.py b/tests/test_misc.py index 6156a8653..558417595 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,5 +1,6 @@ import logging import pytest +import shutil import time from fixtures import * @@ -337,3 +338,23 @@ def test_retry_on_workqueue_exceeded(lianad, bitcoind, executor): # We should have retried the request to bitcoind, which should now succeed along with the call. # This just checks the response we get is sane, nothing particular with this field. assert "block_height" in f_liana.result(TIMEOUT) + + +def test_wo_wallet_copy_from_bitcoind_datadir(lianad, bitcoind): + """Simulate an old install on Windows and make sure the watchonly wallet gets migrated.""" + wo_name = "lianad_watchonly_wallet" + old_wo_path = os.path.join(bitcoind.bitcoin_dir, "regtest", "wallets", wo_name) + new_wo_path = os.path.join(lianad.datadir, "regtest", wo_name) + + # Start lianad. It'll have created the watchonly within our datadir. Move it where it would + # have been created by Liana v1 on Windows. + lianad.stop() + shutil.copytree(new_wo_path, old_wo_path) + shutil.rmtree(new_wo_path) + assert not os.path.isdir(new_wo_path) + + # Now restart lianad. It should detect it within the bitcoind datadir and copy it to its own. + lianad.start() + assert os.path.isdir(new_wo_path) + assert lianad.is_in_log("A data directory exists with no watchonly wallet. This is most likely due to.*") + assert lianad.is_in_log("Successfully copied the watchonly wallet file.")