diff --git a/src/bitcoin/poller/looper.rs b/src/bitcoin/poller/looper.rs index 4bdb7350..6e75d4f5 100644 --- a/src/bitcoin/poller/looper.rs +++ b/src/bitcoin/poller/looper.rs @@ -40,7 +40,7 @@ fn update_tip(bit: &impl BitcoinInterface, db_conn: &mut Box>, - db: impl DatabaseInterface, + db: sync::Arc>, shutdown: sync::Arc, poll_interval: time::Duration, ) { diff --git a/src/bitcoin/poller/mod.rs b/src/bitcoin/poller/mod.rs index 23dd0b57..68901d61 100644 --- a/src/bitcoin/poller/mod.rs +++ b/src/bitcoin/poller/mod.rs @@ -19,7 +19,7 @@ pub struct Poller { impl Poller { pub fn start( bit: sync::Arc>, - db: impl DatabaseInterface + 'static, + db: sync::Arc>, poll_interval: time::Duration, ) -> Poller { let shutdown = sync::Arc::from(atomic::AtomicBool::from(false)); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 733d2b56..e56904e1 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,7 +2,7 @@ //! //! External interface to the Minisafe daemon. -use crate::{bitcoin::BitcoinInterface, DaemonControl, VERSION}; +use crate::{bitcoin::BitcoinInterface, database::DatabaseInterface, DaemonControl, VERSION}; use miniscript::{ bitcoin, diff --git a/src/database/mod.rs b/src/database/mod.rs index dac70224..6820661b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -8,6 +8,8 @@ use crate::{ database::sqlite::{schema::DbTip, SqliteConn, SqliteDb}, }; +use std::sync; + use miniscript::bitcoin::util::bip32; pub trait DatabaseInterface: Send { @@ -20,6 +22,13 @@ impl DatabaseInterface for SqliteDb { } } +// FIXME: do we need to repeat the entire trait implemenation? Isn't there a nicer way? +impl DatabaseInterface for sync::Arc> { + fn connection(&self) -> Box { + self.lock().unwrap().connection() + } +} + pub trait DatabaseConnection { /// Get the tip of the best chain we've seen. fn chain_tip(&mut self) -> Option; diff --git a/src/lib.rs b/src/lib.rs index a50dcdb9..4e1f0a14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,6 +148,31 @@ fn create_datadir(datadir_path: &path::Path) -> Result<(), StartupError> { }; } +// Connect to the SQLite database. Create it if starting fresh, and do some sanity checks. +// If all went well, returns the interface to the SQLite database. +fn setup_sqlite( + config: &Config, + data_dir: &path::Path, + fresh_data_dir: bool, +) -> Result { + let db_path: path::PathBuf = [data_dir, path::Path::new("minisafed.sqlite3")] + .iter() + .collect(); + let options = if fresh_data_dir { + Some(FreshDbOptions { + bitcoind_network: config.bitcoind_config.network, + main_descriptor: config.main_descriptor.clone(), + }) + } else { + None + }; + let sqlite = SqliteDb::new(db_path, options)?; + sqlite.sanity_check(config.bitcoind_config.network, &config.main_descriptor)?; + log::info!("Database initialized and checked."); + + Ok(sqlite) +} + // Connect to bitcoind. Setup the watchonly wallet, and do some sanity checks. // If all went well, returns the interface to bitcoind. fn setup_bitcoind( @@ -177,7 +202,8 @@ fn setup_bitcoind( pub struct DaemonControl { config: Config, bitcoin: sync::Arc>, - db: Box, + // FIXME: Should we require Sync on DatabaseInterface rather than using a Mutex? + db: sync::Arc>, secp: secp256k1::Secp256k1, } @@ -185,7 +211,7 @@ impl DaemonControl { pub fn new( config: Config, bitcoin: sync::Arc>, - db: Box, + db: sync::Arc>, ) -> DaemonControl { let secp = secp256k1::Secp256k1::verification_only(); DaemonControl { @@ -207,12 +233,15 @@ impl DaemonHandle { /// /// You may specify a custom Bitcoin interface through the `bitcoin` parameter. If `None`, the /// default Bitcoin interface (`bitcoind` JSONRPC) will be used. + /// You may specify a custom Database interface through the `db` parameter. If `None`, the + /// default Database interface (SQLite) will be used. /// /// **Note**: we internally use threads, and set a panic hook. A downstream application must /// not overwrite this panic hook. pub fn start( config: Config, bitcoin: Option, + db: Option, ) -> Result { #[cfg(not(test))] setup_panic_hook(); @@ -229,20 +258,14 @@ impl DaemonHandle { } // Then set up the database - let db_path: path::PathBuf = [data_dir.as_path(), path::Path::new("minisafed.sqlite3")] - .iter() - .collect(); - let options = if fresh_data_dir { - Some(FreshDbOptions { - bitcoind_network: config.bitcoind_config.network, - main_descriptor: config.main_descriptor.clone(), - }) - } else { - None + let db = match db { + Some(db) => sync::Arc::from(sync::Mutex::from(db)), + None => sync::Arc::from(sync::Mutex::from(setup_sqlite( + &config, + &data_dir, + fresh_data_dir, + )?)) as sync::Arc>, }; - let sqlite = SqliteDb::new(db_path, options)?; - sqlite.sanity_check(config.bitcoind_config.network, &config.main_descriptor)?; - log::info!("Database initialized and checked."); // Now, set up the Bitcoin interface. let bit = match bitcoin { @@ -271,12 +294,12 @@ impl DaemonHandle { // Spawn the bitcoind poller with a retry limit high enough that we'd fail after that. let bitcoin_poller = poller::Poller::start( bit.clone(), - sqlite.clone(), + db.clone(), config.bitcoind_config.poll_interval_secs, ); // Finally, set up the API. - let control = DaemonControl::new(config, bit, Box::from(sqlite)); + let control = DaemonControl::new(config, bit, db); Ok(Self { control, @@ -287,7 +310,7 @@ impl DaemonHandle { /// Start the Minisafe daemon with the default Bitcoin and database interfaces (`bitcoind` RPC /// and SQLite). pub fn start_default(config: Config) -> Result { - DaemonHandle::start(config, Option::::None) + DaemonHandle::start(config, Option::::None, Option::::None) } /// Start the JSONRPC server and listen for incoming commands until we die.