Accept a custom database interface when starting the daemon

This commit is contained in:
Antoine Poinsot 2022-08-07 13:01:46 +02:00
parent f365effacd
commit ea3595349d
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
5 changed files with 53 additions and 21 deletions

View File

@ -40,7 +40,7 @@ fn update_tip(bit: &impl BitcoinInterface, db_conn: &mut Box<dyn DatabaseConnect
/// `shutdown` atomic. /// `shutdown` atomic.
pub fn looper( pub fn looper(
bit: sync::Arc<sync::Mutex<dyn BitcoinInterface>>, bit: sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
db: impl DatabaseInterface, db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
shutdown: sync::Arc<atomic::AtomicBool>, shutdown: sync::Arc<atomic::AtomicBool>,
poll_interval: time::Duration, poll_interval: time::Duration,
) { ) {

View File

@ -19,7 +19,7 @@ pub struct Poller {
impl Poller { impl Poller {
pub fn start( pub fn start(
bit: sync::Arc<sync::Mutex<dyn BitcoinInterface>>, bit: sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
db: impl DatabaseInterface + 'static, db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
poll_interval: time::Duration, poll_interval: time::Duration,
) -> Poller { ) -> Poller {
let shutdown = sync::Arc::from(atomic::AtomicBool::from(false)); let shutdown = sync::Arc::from(atomic::AtomicBool::from(false));

View File

@ -2,7 +2,7 @@
//! //!
//! External interface to the Minisafe daemon. //! External interface to the Minisafe daemon.
use crate::{bitcoin::BitcoinInterface, DaemonControl, VERSION}; use crate::{bitcoin::BitcoinInterface, database::DatabaseInterface, DaemonControl, VERSION};
use miniscript::{ use miniscript::{
bitcoin, bitcoin,

View File

@ -8,6 +8,8 @@ use crate::{
database::sqlite::{schema::DbTip, SqliteConn, SqliteDb}, database::sqlite::{schema::DbTip, SqliteConn, SqliteDb},
}; };
use std::sync;
use miniscript::bitcoin::util::bip32; use miniscript::bitcoin::util::bip32;
pub trait DatabaseInterface: Send { 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<sync::Mutex<dyn DatabaseInterface>> {
fn connection(&self) -> Box<dyn DatabaseConnection> {
self.lock().unwrap().connection()
}
}
pub trait DatabaseConnection { pub trait DatabaseConnection {
/// Get the tip of the best chain we've seen. /// Get the tip of the best chain we've seen.
fn chain_tip(&mut self) -> Option<BlockChainTip>; fn chain_tip(&mut self) -> Option<BlockChainTip>;

View File

@ -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<SqliteDb, StartupError> {
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. // Connect to bitcoind. Setup the watchonly wallet, and do some sanity checks.
// If all went well, returns the interface to bitcoind. // If all went well, returns the interface to bitcoind.
fn setup_bitcoind( fn setup_bitcoind(
@ -177,7 +202,8 @@ fn setup_bitcoind(
pub struct DaemonControl { pub struct DaemonControl {
config: Config, config: Config,
bitcoin: sync::Arc<sync::Mutex<dyn BitcoinInterface>>, bitcoin: sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
db: Box<dyn DatabaseInterface>, // FIXME: Should we require Sync on DatabaseInterface rather than using a Mutex?
db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
secp: secp256k1::Secp256k1<secp256k1::VerifyOnly>, secp: secp256k1::Secp256k1<secp256k1::VerifyOnly>,
} }
@ -185,7 +211,7 @@ impl DaemonControl {
pub fn new( pub fn new(
config: Config, config: Config,
bitcoin: sync::Arc<sync::Mutex<dyn BitcoinInterface>>, bitcoin: sync::Arc<sync::Mutex<dyn BitcoinInterface>>,
db: Box<dyn DatabaseInterface>, db: sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
) -> DaemonControl { ) -> DaemonControl {
let secp = secp256k1::Secp256k1::verification_only(); let secp = secp256k1::Secp256k1::verification_only();
DaemonControl { DaemonControl {
@ -207,12 +233,15 @@ impl DaemonHandle {
/// ///
/// You may specify a custom Bitcoin interface through the `bitcoin` parameter. If `None`, the /// You may specify a custom Bitcoin interface through the `bitcoin` parameter. If `None`, the
/// default Bitcoin interface (`bitcoind` JSONRPC) will be used. /// 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 /// **Note**: we internally use threads, and set a panic hook. A downstream application must
/// not overwrite this panic hook. /// not overwrite this panic hook.
pub fn start( pub fn start(
config: Config, config: Config,
bitcoin: Option<impl BitcoinInterface + 'static>, bitcoin: Option<impl BitcoinInterface + 'static>,
db: Option<impl DatabaseInterface + 'static>,
) -> Result<Self, StartupError> { ) -> Result<Self, StartupError> {
#[cfg(not(test))] #[cfg(not(test))]
setup_panic_hook(); setup_panic_hook();
@ -229,20 +258,14 @@ impl DaemonHandle {
} }
// Then set up the database // Then set up the database
let db_path: path::PathBuf = [data_dir.as_path(), path::Path::new("minisafed.sqlite3")] let db = match db {
.iter() Some(db) => sync::Arc::from(sync::Mutex::from(db)),
.collect(); None => sync::Arc::from(sync::Mutex::from(setup_sqlite(
let options = if fresh_data_dir { &config,
Some(FreshDbOptions { &data_dir,
bitcoind_network: config.bitcoind_config.network, fresh_data_dir,
main_descriptor: config.main_descriptor.clone(), )?)) as sync::Arc<sync::Mutex<dyn DatabaseInterface>>,
})
} 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.");
// Now, set up the Bitcoin interface. // Now, set up the Bitcoin interface.
let bit = match bitcoin { 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. // Spawn the bitcoind poller with a retry limit high enough that we'd fail after that.
let bitcoin_poller = poller::Poller::start( let bitcoin_poller = poller::Poller::start(
bit.clone(), bit.clone(),
sqlite.clone(), db.clone(),
config.bitcoind_config.poll_interval_secs, config.bitcoind_config.poll_interval_secs,
); );
// Finally, set up the API. // Finally, set up the API.
let control = DaemonControl::new(config, bit, Box::from(sqlite)); let control = DaemonControl::new(config, bit, db);
Ok(Self { Ok(Self {
control, control,
@ -287,7 +310,7 @@ impl DaemonHandle {
/// Start the Minisafe daemon with the default Bitcoin and database interfaces (`bitcoind` RPC /// Start the Minisafe daemon with the default Bitcoin and database interfaces (`bitcoind` RPC
/// and SQLite). /// and SQLite).
pub fn start_default(config: Config) -> Result<DaemonHandle, StartupError> { pub fn start_default(config: Config) -> Result<DaemonHandle, StartupError> {
DaemonHandle::start(config, Option::<BitcoinD>::None) DaemonHandle::start(config, Option::<BitcoinD>::None, Option::<SqliteDb>::None)
} }
/// Start the JSONRPC server and listen for incoming commands until we die. /// Start the JSONRPC server and listen for incoming commands until we die.