Skip to content

Commit

Permalink
lib: on Windows, migrate the watchonly wallet from bitcoind datadir
Browse files Browse the repository at this point in the history
We used to store it there, if it's not within our own datadir copy it
from where it would have been stored by Liana v1.

Note we don't conditionally compile this on Windows so the codepath can
be tested with a functional test.
  • Loading branch information
darosior committed Aug 30, 2023
1 parent c9d86f1 commit 0d15edf
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
82 changes: 82 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<datadir>/<network>/wallets/<wallet_name>/" and
// the cookie file in "<datadir>/<network>/".
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(
Expand Down Expand Up @@ -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()?;
Expand Down
21 changes: 21 additions & 0 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import pytest
import shutil
import time

from fixtures import *
Expand Down Expand Up @@ -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.")

0 comments on commit 0d15edf

Please sign in to comment.