lib: implement a superior workaround for the watchonly wallet on Windows

See the added comment for the details. No need to store the watchonly
wallet under bitcoind's datadir anymore. 🎉 🎉
This commit is contained in:
Antoine Poinsot 2023-08-30 15:53:48 +02:00
parent 19e5e45918
commit c9d86f1c75
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
2 changed files with 14 additions and 84 deletions

View File

@ -607,7 +607,7 @@ impl BitcoinD {
.collect()
}
pub fn maybe_unload_watchonly_wallet(&self, watchonly_wallet_path: String) {
fn maybe_unload_watchonly_wallet(&self, watchonly_wallet_path: String) {
while self.list_wallets().contains(&watchonly_wallet_path) {
log::info!("Found a leftover watchonly wallet loaded on bitcoind. Removing it.");
if let Some(e) = self.unload_wallet(watchonly_wallet_path.clone()) {

View File

@ -89,8 +89,6 @@ pub enum StartupError {
DefaultDataDirNotFound,
DatadirCreation(path::PathBuf, io::Error),
MissingBitcoindConfig,
WindowsCantGuessBitcoindDatadir(path::PathBuf),
WindowsBitcoindWatchonlyDeletion(path::PathBuf, io::Error),
Database(SqliteDbError),
Bitcoind(BitcoindError),
#[cfg(unix)]
@ -113,15 +111,6 @@ impl fmt::Display for StartupError {
f,
"Our Bitcoin interface is bitcoind but we have no 'bitcoind_config' entry in the configuration."
),
Self::WindowsCantGuessBitcoindDatadir(cookie_path) => write!(
f,
"Cannot guess the path to the bitcoind data directory from the cookie file whose path is '{}'.",
cookie_path.as_path().to_string_lossy()
),
Self::WindowsBitcoindWatchonlyDeletion(path, e) => write!(
f,
"Error deleting bitcoind watchonly wallet at '{}': {}", path.as_path().to_string_lossy(), e
),
Self::Database(e) => write!(f, "Error initializing database: '{}'.", e),
Self::Bitcoind(e) => write!(f, "Error setting up bitcoind interface: '{}'.", e),
#[cfg(unix)]
@ -198,54 +187,6 @@ fn setup_sqlite(
Ok(sqlite)
}
// Windows-specific utility to remove a leftover watchonly wallet within bitcoind's datadir.
#[cfg(windows)]
fn maybe_delete_watchonly_wallet(
bitcoind: &BitcoinD,
bitcoind_cookie_path: &path::Path,
bitcoin_net: miniscript::bitcoin::Network,
wallet_name: &str,
) -> Result<(), StartupError> {
log::info!(
"Trying to guess where the watchonly wallet would be stored in bitcoind's data directory from the cookie path. \
This might not work if you are using a custom path for the cookie file (very unlikely). In this case please delete the \
leftover watchonly wallet in bitcoind's datadir by hand if there is any."
);
// 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 = bitcoind_cookie_path.parent().ok_or_else(|| {
StartupError::WindowsCantGuessBitcoindDatadir(bitcoind_cookie_path.to_path_buf())
})?;
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),
net => panic!(
"Unsupported network '{}', unknown at the time of writing.",
net
),
};
if wallet_path.exists() {
bitcoind.maybe_unload_watchonly_wallet(wallet_path.to_string_lossy().to_string());
log::info!(
"Deleting the leftover watchonly wallet at '{}'.",
wallet_path.as_path().to_string_lossy()
);
fs::remove_dir_all(&wallet_path)
.map_err(|e| StartupError::WindowsBitcoindWatchonlyDeletion(wallet_path, e))?;
} else {
log::info!(
"No leftover watchonly wallet found at '{}'.",
wallet_path.as_path().to_string_lossy()
);
}
Ok(())
}
// Connect to bitcoind. Setup the watchonly wallet, and do some sanity checks.
// If all went well, returns the interface to bitcoind.
fn setup_bitcoind(
@ -253,40 +194,29 @@ fn setup_bitcoind(
data_dir: &path::Path,
fresh_data_dir: bool,
) -> Result<BitcoinD, StartupError> {
// NOTE: this is a hack! We normally store the watchonly wallet within our data directory.
// But on windows bitcoind would prefix the wallet path with "C:\\\\?" when calling
// 'loadwallet'. Therefore instead on Windows store the wallet.dat in bitcoind's data directory
// instead by not providing an absolute path but the name of a wallet.
#[cfg(not(windows))]
let wo_path: path::PathBuf = [data_dir, path::Path::new("lianad_watchonly_wallet")]
.iter()
.collect();
#[cfg(windows)]
let wo_name = "lianad_watchonly_wallet";
#[cfg(windows)]
let wo_path = path::Path::new(wo_name);
let wo_path_str = wo_path.to_str().expect("Must be valid unicode").to_string();
// NOTE: On Windows, paths are canonicalized with a "\\?\" prefix to tell Windows to interpret
// the string "as is" and to ignore the maximum size of a path. HOWEVER this is not properly
// handled by most implementations of the C++ STL's std::filesystem. Therefore bitcoind would
// fail to find the wallet if we didn't strip this prefix. It's not ideal, but a lesser evil
// than other workarounds i could think about.
// See https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-file-namespaces
// about the prefix.
// See https://stackoverflow.com/questions/71590689/how-to-properly-handle-windows-paths-with-the-long-path-prefix-with-stdfilesys
// for a discussion of how one C++ STL implementation handles this.
#[cfg(target_os = "windows")]
let wo_path_str = wo_path_str.replace("\\\\?\\", "").replace("\\\\?", "");
let bitcoind_config = config
.bitcoind_config
.as_ref()
.ok_or(StartupError::MissingBitcoindConfig)?;
let bitcoind = BitcoinD::new(
bitcoind_config,
wo_path.to_str().expect("Must be valid unicode").to_string(),
)?;
let bitcoind = BitcoinD::new(bitcoind_config, wo_path_str)?;
bitcoind.node_sanity_checks(config.bitcoin_config.network)?;
if fresh_data_dir {
// Because of the hack above, the assumption that whenever the data directory is fresh a
// watchonly wallet doesn't exist doesn't hold for Windows. Make sure it does by removing
// any leftover Liana watchonly wallet from bitcoind's data dir.
#[cfg(windows)]
maybe_delete_watchonly_wallet(
&bitcoind,
&bitcoind_config.cookie_path,
config.bitcoin_config.network,
wo_name,
)?;
log::info!("Creating a new watchonly wallet on bitcoind.");
bitcoind.create_watchonly_wallet(&config.main_descriptor)?;
log::info!("Watchonly wallet created.");