config: separate the Bitcoin and bitcoind-specifc settings

This commit is contained in:
Antoine Poinsot 2022-08-09 12:58:35 +02:00
parent ea3595349d
commit e510c0a30d
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
5 changed files with 58 additions and 31 deletions

View File

@ -93,7 +93,7 @@ fn socket_file(conf_file: Option<PathBuf>) -> PathBuf {
[
data_dir,
config.bitcoind_config.network.to_string().as_str(),
config.bitcoin_config.network.to_string().as_str(),
"minisafed_rpc",
]
.iter()

View File

@ -16,7 +16,7 @@ impl DaemonControl {
pub fn get_info(&self) -> GetInfoResult {
GetInfoResult {
version: VERSION.to_string(),
network: self.config.bitcoind_config.network,
network: self.config.bitcoin_config.network,
blockheight: self.bitcoin.chain_tip().height,
sync: self.bitcoin.sync_progress(),
descriptors: GetInfoDescriptors {
@ -39,7 +39,7 @@ impl DaemonControl {
.derive(index.into())
.translate_pk2(|xpk| xpk.derive_public_key(&self.secp))
.expect("All pubkeys were derived, no wildcard.")
.address(self.config.bitcoind_config.network)
.address(self.config.bitcoin_config.network)
.expect("It's a wsh() descriptor");
GetAddressResult { address }
}

View File

@ -54,13 +54,17 @@ fn default_daemon() -> bool {
/// Everything we need to know for talking to bitcoind serenely
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BitcoindConfig {
/// The network we are operating on, one of "bitcoin", "testnet", "regtest"
pub network: Network,
/// Path to bitcoind's cookie file, to authenticate the RPC connection
pub cookie_path: PathBuf,
/// The IP:port bitcoind's RPC is listening on
pub addr: SocketAddr,
/// The poll interval for bitcoind
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct BitcoinConfig {
/// The network we are operating on, one of "bitcoin", "testnet", "regtest", "signet"
pub network: Network,
/// The poll interval for the Bitcoin interface
#[serde(
deserialize_with = "deserialize_duration",
serialize_with = "serialize_duration",
@ -91,8 +95,10 @@ pub struct Config {
serialize_with = "serialize_to_string"
)]
pub main_descriptor: Descriptor<DescriptorPublicKey>,
/// Everything we need to know to talk to bitcoind
pub bitcoind_config: BitcoindConfig,
/// Settings for the Bitcoin interface
pub bitcoin_config: BitcoinConfig,
/// Settings specific to bitcoind as the Bitcoin interface
pub bitcoind_config: Option<BitcoindConfig>,
}
impl Config {
@ -195,7 +201,7 @@ impl Config {
/// Make sure the settings are sane.
pub fn check(&self) -> Result<(), ConfigError> {
// Check the network of the xpubs in the descriptors
let expected_network = match self.bitcoind_config.network {
let expected_network = match self.bitcoin_config.network {
Network::Bitcoin => Network::Bitcoin,
_ => Network::Testnet,
};
@ -214,7 +220,7 @@ impl Config {
if unexpected_net {
return Err(ConfigError::Unexpected(format!(
"Our bitcoin network is {} but one xpub is not for network {}",
self.bitcoind_config.network, expected_network
self.bitcoin_config.network, expected_network
)));
}
@ -238,11 +244,13 @@ mod tests {
log_level = "debug"
main_descriptor = "wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k8uvf"
[bitcoind_config]
[bitcoin_config]
network = "bitcoin"
poll_interval_secs = 18
[bitcoind_config]
cookie_path = "/home/user/.bitcoin/.cookie"
addr = "127.0.0.1:8332"
poll_interval_secs = 18
"#.trim_start().replace(" ", "");
toml::from_str::<Config>(&toml_str).expect("Deserializing toml_str");
@ -253,11 +261,13 @@ mod tests {
log_level = 'TRACE'
main_descriptor = 'wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k8uvf'
[bitcoind_config]
[bitcoin_config]
network = 'bitcoin'
poll_interval_secs = 18
[bitcoind_config]
cookie_path = '/home/user/.bitcoin/.cookie'
addr = '127.0.0.1:8332'
poll_interval_secs = 18
"#.trim_start().replace(" ", "");
let parsed = toml::from_str::<Config>(&toml_str).expect("Deserializing toml_str");
let serialized = toml::to_string_pretty(&parsed).expect("Serializing to toml");
@ -273,16 +283,18 @@ mod tests {
# The main descriptor semantics aren't checked, yet.
main_descriptor = "wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k88vf"
[bitcoind_config]
[bitcoin_config]
network = "bitcoin"
poll_interval_secs = 18
[bitcoind_config]
cookie_path = "/home/user/.bitcoin/.cookie"
addr = "127.0.0.1:8332"
poll_interval_secs = 18
"#;
let config_res: Result<Config, toml::de::Error> = toml::from_str(toml_str);
config_res.expect_err("Deserializing an invalid toml_str");
// Not enough parameters: missing the network
// Not enough parameters: missing the Bitcoin network
let toml_str = r#"
daemon = false
log_level = "trace"
@ -291,10 +303,12 @@ mod tests {
# The main descriptor semantics aren't checked, yet.
main_descriptor = "wsh(andor(thresh(1,pk(xpub6BaZSKgpaVvibu2k78QsqeDWXp92xLHZxiu1WoqLB9hKhsBf3miBUDX7PJLgSPvkj66ThVHTqdnbXpeu8crXFmDUd4HeM4s4miQS2xsv3Qb/*)),and_v(v:multi(2,03b506a1dbe57b4bf48c95e0c7d417b87dd3b4349d290d2e7e9ba72c912652d80a,0295e7f5d12a2061f1fd2286cefec592dff656a19f55f4f01305d6aa56630880ce),older(4)),thresh(2,pkh(xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU/*),a:pkh(xpub6AaffFGfH6WXfm6pwWzmUMuECQnoLeB3agMKaLyEBZ5ZVfwtnS5VJKqXBt8o5ooCWVy2H87GsZshp7DeKE25eWLyd1Ccuh2ZubQUkgpiVux/*))))#532k8uvf"
[bitcoin_config]
poll_interval_secs = 18
[bitcoind_config]
cookie_path = "/home/user/.bitcoin/.cookie"
addr = "127.0.0.1:8332"
poll_interval_secs = 18
"#;
let config_res: Result<Config, toml::de::Error> = toml::from_str(toml_str);
config_res.expect_err("Deserializing an invalid toml_str");

View File

@ -80,6 +80,7 @@ pub enum StartupError {
Io(io::Error),
DefaultDataDirNotFound,
DatadirCreation(path::PathBuf, io::Error),
MissingBitcoindConfig,
Database(SqliteDbError),
Bitcoind(BitcoindError),
#[cfg(unix)]
@ -98,6 +99,10 @@ impl fmt::Display for StartupError {
f,
"Could not create data directory at '{}': '{}'", dir_path.display(), e
),
Self::MissingBitcoindConfig => write!(
f,
"Our Bitcoin interface is bitcoind but we have no 'bitcoind_config' entry in the configuration."
),
Self::Database(e) => write!(f, "Error initializing database: '{}'.", e),
Self::Bitcoind(e) => write!(f, "Error setting up bitcoind interface: '{}'.", e),
#[cfg(unix)]
@ -160,14 +165,14 @@ fn setup_sqlite(
.collect();
let options = if fresh_data_dir {
Some(FreshDbOptions {
bitcoind_network: config.bitcoind_config.network,
bitcoind_network: config.bitcoin_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)?;
sqlite.sanity_check(config.bitcoin_config.network, &config.main_descriptor)?;
log::info!("Database initialized and checked.");
Ok(sqlite)
@ -185,7 +190,10 @@ fn setup_bitcoind(
.iter()
.collect();
let bitcoind = BitcoinD::new(
&config.bitcoind_config,
config
.bitcoind_config
.as_ref()
.ok_or(StartupError::MissingBitcoindConfig)?,
wo_path.to_str().expect("Must be valid unicode").to_string(),
)?;
if fresh_data_dir {
@ -193,7 +201,7 @@ fn setup_bitcoind(
log::info!("Created a new watchonly wallet on bitcoind.");
}
bitcoind.try_load_watchonly_wallet();
bitcoind.sanity_check(&config.main_descriptor, config.bitcoind_config.network)?;
bitcoind.sanity_check(&config.main_descriptor, config.bitcoin_config.network)?;
log::info!("Connection to bitcoind established and checked.");
Ok(bitcoind.with_retry_limit(None))
@ -250,7 +258,7 @@ impl DaemonHandle {
let mut data_dir = config
.data_dir()
.ok_or(StartupError::DefaultDataDirNotFound)?;
data_dir.push(config.bitcoind_config.network.to_string());
data_dir.push(config.bitcoin_config.network.to_string());
let fresh_data_dir = !data_dir.as_path().exists();
if fresh_data_dir {
create_datadir(&data_dir)?;
@ -295,7 +303,7 @@ impl DaemonHandle {
let bitcoin_poller = poller::Poller::start(
bit.clone(),
db.clone(),
config.bitcoind_config.poll_interval_secs,
config.bitcoin_config.poll_interval_secs,
);
// Finally, set up the API.
@ -328,7 +336,7 @@ impl DaemonHandle {
.data_dir()
.expect("Didn't fail at startup, must not now")
.as_path(),
path::Path::new(&control.config.bitcoind_config.network.to_string()),
path::Path::new(&control.config.bitcoin_config.network.to_string()),
path::Path::new("minisafed_rpc"),
]
.iter()
@ -356,7 +364,7 @@ impl DaemonHandle {
#[cfg(all(test, unix))]
mod tests {
use super::*;
use crate::config::BitcoindConfig;
use crate::config::{BitcoinConfig, BitcoindConfig};
use miniscript::{bitcoin, Descriptor, DescriptorPublicKey};
use std::{
@ -543,18 +551,21 @@ mod tests {
net::SocketAddrV4::new(net::Ipv4Addr::new(127, 0, 0, 1), 0).into();
let server = net::TcpListener::bind(&addr).unwrap();
let addr = server.local_addr().unwrap();
let bitcoind_config = BitcoindConfig {
let bitcoin_config = BitcoinConfig {
network,
poll_interval_secs: time::Duration::from_secs(2),
};
let bitcoind_config = BitcoindConfig {
addr,
cookie_path: cookie.clone(),
poll_interval_secs: time::Duration::from_secs(2),
};
// Create a dummy config with this bitcoind
let desc_str = "wsh(andor(pk(xpub68JJTXc1MWK8KLW4HGLXZBJknja7kDUJuFHnM424LbziEXsfkh1WQCiEjjHw4zLqSUm4rvhgyGkkuRowE9tCJSgt3TQB5J3SKAbZ2SdcKST/*),older(10000),pk(xpub68JJTXc1MWK8PEQozKsRatrUHXKFNkD1Cb1BuQU9Xr5moCv87anqGyXLyUd4KpnDyZgo3gz4aN1r3NiaoweFW8UutBsBbgKHzaD5HkTkifK/*)))#tk6wzexy";
let desc = Descriptor::<DescriptorPublicKey>::from_str(desc_str).unwrap();
let config = Config {
bitcoind_config,
bitcoin_config,
bitcoind_config: Some(bitcoind_config),
data_dir: Some(data_dir.clone()),
#[cfg(unix)]
daemon: false,

View File

@ -33,11 +33,13 @@ class Minisafed(TailableProc):
f.write(f'main_descriptor = "{main_desc}"\n')
f.write("[bitcoind_config]\n")
f.write("[bitcoin_config]\n")
f.write('network = "regtest"\n')
f.write("poll_interval_secs = 1\n")
f.write("[bitcoind_config]\n")
f.write(f"cookie_path = '{bitcoind_cookie_path}'\n")
f.write(f"addr = '127.0.0.1:{bitcoind_rpc_port}'\n")
f.write("poll_interval_secs = 1\n")
def start(self):
TailableProc.start(self)