From da4ebaa92c7d96c803794ad4b52681b1d605a64b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 21 Feb 2024 17:52:16 -0800 Subject: [PATCH 01/35] Add settings --- src/arguments.rs | 2 +- src/chain.rs | 15 ++ src/config.rs | 5 +- src/index.rs | 45 ++-- src/index/fetcher.rs | 10 +- src/index/reorg.rs | 2 +- src/index/testing.rs | 6 +- src/index/updater.rs | 12 +- src/lib.rs | 19 +- src/options.rs | 385 +++++------------------------ src/settings.rs | 404 +++++++++++++++++++++++++++++++ src/subcommand.rs | 20 +- src/subcommand/balances.rs | 4 +- src/subcommand/decode.rs | 4 +- src/subcommand/find.rs | 4 +- src/subcommand/index.rs | 8 +- src/subcommand/index/export.rs | 4 +- src/subcommand/index/info.rs | 4 +- src/subcommand/index/update.rs | 4 +- src/subcommand/list.rs | 4 +- src/subcommand/runes.rs | 4 +- src/subcommand/server.rs | 57 +++-- src/subcommand/wallet.rs | 8 +- src/subcommand/wallet/create.rs | 4 +- src/subcommand/wallet/restore.rs | 8 +- src/wallet.rs | 57 +++-- tests/test_server.rs | 6 +- 27 files changed, 638 insertions(+), 467 deletions(-) create mode 100644 src/settings.rs diff --git a/src/arguments.rs b/src/arguments.rs index f348c5cfa3..791bd14cd0 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -21,6 +21,6 @@ pub(crate) struct Arguments { impl Arguments { pub(crate) fn run(self) -> SubcommandResult { - self.subcommand.run(self.options) + self.subcommand.run(Settings::new(self.options)?) } } diff --git a/src/chain.rs b/src/chain.rs index e77e6cab0c..b4638151e8 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -108,3 +108,18 @@ impl Display for Chain { ) } } + +// todo: test this +impl FromStr for Chain { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "mainnet" => Ok(Self::Mainnet), + "regtest" => Ok(Self::Regtest), + "signet" => Ok(Self::Signet), + "testnet" => Ok(Self::Testnet), + _ => bail!("invalid chain: {s}"), + } + } +} diff --git a/src/config.rs b/src/config.rs index 15e6189171..be346e50dc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,12 @@ use super::*; -#[derive(Deserialize, Default, PartialEq, Debug)] +#[derive(Deserialize, Default, PartialEq, Debug, Clone)] #[serde(deny_unknown_fields)] pub(crate) struct Config { - pub(crate) hidden: HashSet, pub(crate) bitcoin_rpc_pass: Option, pub(crate) bitcoin_rpc_user: Option, + pub(crate) chain: Option, + pub(crate) hidden: HashSet, } impl Config { diff --git a/src/index.rs b/src/index.rs index a8982c104b..c40d489da1 100644 --- a/src/index.rs +++ b/src/index.rs @@ -213,27 +213,28 @@ pub struct Index { index_sats: bool, index_spent_sats: bool, index_transactions: bool, - options: Options, + settings: Settings, path: PathBuf, started: DateTime, unrecoverably_reorged: AtomicBool, } impl Index { - pub fn open(options: &Options) -> Result { - Index::open_with_event_sender(options, None) + pub fn open(settings: &Settings) -> Result { + Index::open_with_event_sender(settings, None) } pub fn open_with_event_sender( - options: &Options, + settings: &Settings, event_sender: Option>, ) -> Result { - let client = options.bitcoin_rpc_client(None)?; + let client = settings.bitcoin_rpc_client(None)?; - let path = options + let path = settings + .options .index .clone() - .unwrap_or(options.data_dir().clone().join("index.redb")); + .unwrap_or_else(|| settings.data_dir().clone().join("index.redb")); if let Err(err) = fs::create_dir_all(path.parent().unwrap()) { bail!( @@ -242,7 +243,7 @@ impl Index { ); } - let db_cache_size = match options.db_cache_size { + let db_cache_size = match settings.options.db_cache_size { Some(db_cache_size) => db_cache_size, None => { let mut sys = System::new(); @@ -349,32 +350,32 @@ impl Index { let mut outpoint_to_sat_ranges = tx.open_table(OUTPOINT_TO_SAT_RANGES)?; let mut statistics = tx.open_table(STATISTIC_TO_COUNT)?; - if options.index_sats { + if settings.options.index_sats { outpoint_to_sat_ranges.insert(&OutPoint::null().store(), [].as_slice())?; } Self::set_statistic( &mut statistics, Statistic::IndexRunes, - u64::from(options.index_runes()), + u64::from(settings.index_runes()), )?; Self::set_statistic( &mut statistics, Statistic::IndexSats, - u64::from(options.index_sats || options.index_spent_sats), + u64::from(settings.options.index_sats || settings.options.index_spent_sats), )?; Self::set_statistic( &mut statistics, Statistic::IndexSpentSats, - u64::from(options.index_spent_sats), + u64::from(settings.options.index_spent_sats), )?; Self::set_statistic( &mut statistics, Statistic::IndexTransactions, - u64::from(options.index_transactions), + u64::from(settings.options.index_transactions), )?; Self::set_statistic(&mut statistics, Statistic::Schema, SCHEMA_VERSION)?; @@ -402,7 +403,7 @@ impl Index { } let genesis_block_coinbase_transaction = - options.chain().genesis_block().coinbase().unwrap().clone(); + settings.chain().genesis_block().coinbase().unwrap().clone(); Ok(Self { genesis_block_coinbase_txid: genesis_block_coinbase_transaction.txid(), @@ -410,14 +411,14 @@ impl Index { database, durability, event_sender, - first_inscription_height: options.first_inscription_height(), + first_inscription_height: settings.first_inscription_height(), genesis_block_coinbase_transaction, - height_limit: options.height_limit, + height_limit: settings.options.height_limit, index_runes, index_sats, index_spent_sats, index_transactions, - options: options.clone(), + settings: settings.clone(), path, started: Utc::now(), unrecoverably_reorged: AtomicBool::new(false), @@ -486,14 +487,14 @@ impl Index { Ok(StatusHtml { blessed_inscriptions, - chain: self.options.chain(), + chain: self.settings.chain(), content_type_counts, cursed_inscriptions, height, inscriptions: blessed_inscriptions + cursed_inscriptions, lost_sats: statistic(Statistic::LostSats)?, minimum_rune_for_next_block: Rune::minimum_at_height( - self.options.chain(), + self.settings.chain(), Height(next_height), ), rune_index: statistic(Statistic::IndexRunes)? != 0, @@ -670,7 +671,7 @@ impl Index { .nth(satpoint.outpoint.vout.try_into().unwrap()) .unwrap(); self - .options + .settings .chain() .address_from_script(&output.script_pubkey) .map(|address| address.to_string()) @@ -1482,7 +1483,7 @@ impl Index { pub(crate) fn is_output_spent(&self, outpoint: OutPoint) -> Result { Ok( outpoint != OutPoint::null() - && outpoint != self.options.chain().genesis_coinbase_outpoint() + && outpoint != self.settings.chain().genesis_coinbase_outpoint() && self .client .get_tx_out(&outpoint.txid, outpoint.vout, Some(false))? @@ -1495,7 +1496,7 @@ impl Index { return Ok(true); } - if outpoint == self.options.chain().genesis_coinbase_outpoint() { + if outpoint == self.settings.chain().genesis_coinbase_outpoint() { return Ok(true); } diff --git a/src/index/fetcher.rs b/src/index/fetcher.rs index 46d160da4b..e781e6b854 100644 --- a/src/index/fetcher.rs +++ b/src/index/fetcher.rs @@ -25,18 +25,18 @@ struct JsonError { } impl Fetcher { - pub(crate) fn new(options: &Options) -> Result { + pub(crate) fn new(settings: &Settings) -> Result { let client = Client::new(); - let url = if options.rpc_url(None).starts_with("http://") { - options.rpc_url(None) + let url = if settings.rpc_url(None).starts_with("http://") { + settings.rpc_url(None) } else { - "http://".to_string() + &options.rpc_url(None) + "http://".to_string() + &settings.rpc_url(None) }; let url = Uri::try_from(&url).map_err(|e| anyhow!("Invalid rpc url {url}: {e}"))?; - let (user, password) = options.auth()?.get_user_pass()?; + let (user, password) = settings.auth()?.get_user_pass()?; let auth = format!("{}:{}", user.unwrap(), password.unwrap()); let auth = format!( "Basic {}", diff --git a/src/index/reorg.rs b/src/index/reorg.rs index e9db3471e7..8953a4bb81 100644 --- a/src/index/reorg.rs +++ b/src/index/reorg.rs @@ -86,7 +86,7 @@ impl Reorg { if (height < SAVEPOINT_INTERVAL || height % SAVEPOINT_INTERVAL == 0) && u32::try_from( index - .options + .settings .bitcoin_rpc_client(None)? .get_blockchain_info()? .headers, diff --git a/src/index/testing.rs b/src/index/testing.rs index 3bfe1acec8..ae030dc17d 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -33,7 +33,11 @@ impl ContextBuilder { ]; let options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap(); - let index = Index::open_with_event_sender(&options, self.event_sender)?; + let settings = Settings { + options, + config: Default::default(), + }; + let index = Index::open_with_event_sender(&settings, self.event_sender)?; index.update().unwrap(); Ok(Context { diff --git a/src/index/updater.rs b/src/index/updater.rs index db5c120939..af4a6ee6ab 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -164,7 +164,7 @@ impl<'index> Updater<'_> { let height_limit = index.height_limit; - let client = index.options.bitcoin_rpc_client(None)?; + let client = index.settings.bitcoin_rpc_client(None)?; let first_inscription_height = index.first_inscription_height; @@ -241,7 +241,7 @@ impl<'index> Updater<'_> { } fn spawn_fetcher(index: &Index) -> Result<(Sender, Receiver)> { - let fetcher = Fetcher::new(&index.options)?; + let fetcher = Fetcher::new(&index.settings)?; // Not sure if any block has more than 20k inputs, but none so far after first inscription block const CHANNEL_BUFFER_SIZE: usize = 20_000; @@ -339,7 +339,7 @@ impl<'index> Updater<'_> { let mut outpoint_to_value = wtx.open_table(OUTPOINT_TO_VALUE)?; let index_inscriptions = self.height >= self.index.first_inscription_height - && !self.index.options.no_index_inscriptions; + && !self.index.settings.options.no_index_inscriptions; if index_inscriptions { // Send all missing input outpoints to be fetched right away @@ -423,7 +423,7 @@ impl<'index> Updater<'_> { let mut inscription_updater = InscriptionUpdater { blessed_inscription_count, - chain: self.index.options.chain(), + chain: self.index.settings.chain(), content_type_to_count: &mut content_type_to_count, cursed_inscription_count, event_sender: self.index.event_sender.as_ref(), @@ -583,7 +583,7 @@ impl<'index> Updater<'_> { &inscription_updater.unbound_inscriptions, )?; - if self.index.index_runes && self.height >= self.index.options.first_rune_height() { + if self.index.index_runes && self.height >= self.index.settings.first_rune_height() { let mut outpoint_to_rune_balances = wtx.open_table(OUTPOINT_TO_RUNE_BALANCES)?; let mut rune_id_to_rune_entry = wtx.open_table(RUNE_ID_TO_RUNE_ENTRY)?; let mut rune_to_rune_id = wtx.open_table(RUNE_TO_RUNE_ID)?; @@ -599,7 +599,7 @@ impl<'index> Updater<'_> { height: self.height, id_to_entry: &mut rune_id_to_rune_entry, inscription_id_to_sequence_number: &mut inscription_id_to_sequence_number, - minimum: Rune::minimum_at_height(self.index.options.chain(), Height(self.height)), + minimum: Rune::minimum_at_height(self.index.settings.chain(), Height(self.height)), outpoint_to_balances: &mut outpoint_to_rune_balances, rune_to_id: &mut rune_to_rune_id, runes, diff --git a/src/lib.rs b/src/lib.rs index 4b09ba1832..94a67d387c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ use { }, representation::Representation, runes::{Etching, Pile, SpacedRune}, + settings::Settings, subcommand::{Subcommand, SubcommandResult}, tally::Tally, }, @@ -94,13 +95,12 @@ mod test; use self::test::*; macro_rules! tprintln { - ($($arg:tt)*) => { - - if cfg!(test) { - eprint!("==> "); - eprintln!($($arg)*); - } - }; + ($($arg:tt)*) => { + if cfg!(test) { + eprint!("==> "); + eprintln!($($arg)*); + } + }; } pub mod arguments; @@ -117,6 +117,7 @@ pub mod outgoing; mod representation; pub mod runes; mod server_config; +mod settings; pub mod subcommand; mod tally; pub mod templates; @@ -186,10 +187,10 @@ fn unbound_outpoint() -> OutPoint { } } -pub fn parse_ord_server_args(args: &str) -> (Options, crate::subcommand::server::Server) { +pub fn parse_ord_server_args(args: &str) -> (Settings, crate::subcommand::server::Server) { match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { - Subcommand::Server(server) => (arguments.options, server), + Subcommand::Server(server) => (Settings::new(arguments.options).unwrap(), server), subcommand => panic!("unexpected subcommand: {subcommand:?}"), }, Err(err) => panic!("error parsing arguments: {err}"), diff --git a/src/options.rs b/src/options.rs index ad69a44ec8..72f52d7abf 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,4 +1,4 @@ -use {super::*, bitcoincore_rpc::Auth}; +use super::*; #[derive(Clone, Default, Debug, Parser)] #[command(group( @@ -15,13 +15,8 @@ pub struct Options { pub(crate) bitcoin_rpc_pass: Option, #[arg(long, help = "Authenticate to Bitcoin Core RPC as .")] pub(crate) bitcoin_rpc_user: Option, - #[arg( - long = "chain", - value_enum, - default_value = "mainnet", - help = "Use ." - )] - pub(crate) chain_argument: Chain, + #[arg(long = "chain", value_enum, help = "Use . [default: maiinnet]")] + pub(crate) chain_argument: Option, #[arg(long, help = "Load configuration from .")] pub(crate) config: Option, #[arg(long, help = "Load configuration from .")] @@ -85,211 +80,17 @@ pub struct Options { } impl Options { - pub(crate) fn chain(&self) -> Chain { - if self.signet { - Chain::Signet - } else if self.regtest { - Chain::Regtest - } else if self.testnet { - Chain::Testnet - } else { - self.chain_argument - } - } - - pub(crate) fn first_inscription_height(&self) -> u32 { - if integration_test() { - 0 - } else { - self - .first_inscription_height - .unwrap_or_else(|| self.chain().first_inscription_height()) - } - } - - pub(crate) fn first_rune_height(&self) -> u32 { - if integration_test() { - 0 - } else { - self.chain().first_rune_height() - } - } - - pub(crate) fn index_runes(&self) -> bool { - self.index_runes && self.chain() != Chain::Mainnet - } - - pub(crate) fn rpc_url(&self, wallet_name: Option) -> String { - let base_url = self - .rpc_url - .clone() - .unwrap_or(format!("127.0.0.1:{}", self.chain().default_rpc_port())); - - match wallet_name { - Some(wallet_name) => format!("{base_url}/wallet/{wallet_name}"), - None => format!("{base_url}/"), - } - } - - pub(crate) fn cookie_file(&self) -> Result { - if let Some(cookie_file) = &self.cookie_file { - return Ok(cookie_file.clone()); - } - - let path = if let Some(bitcoin_data_dir) = &self.bitcoin_data_dir { - bitcoin_data_dir.clone() - } else if cfg!(target_os = "linux") { - dirs::home_dir() - .ok_or_else(|| anyhow!("failed to get cookie file path: could not get home dir"))? - .join(".bitcoin") - } else { - dirs::data_dir() - .ok_or_else(|| anyhow!("failed to get cookie file path: could not get data dir"))? - .join("Bitcoin") - }; - - let path = self.chain().join_with_data_dir(&path); - - Ok(path.join(".cookie")) - } - - pub(crate) fn credentials(&self) -> Option<(&str, &str)> { - self.username.as_deref().zip(self.password.as_deref()) - } - fn default_data_dir() -> PathBuf { dirs::data_dir() .map(|dir| dir.join("ord")) .expect("failed to retrieve data dir") } - - pub(crate) fn data_dir(&self) -> PathBuf { - self.chain().join_with_data_dir(&self.data_dir) - } - - pub(crate) fn load_config(&self) -> Result { - match &self.config { - Some(path) => Ok(serde_yaml::from_reader(File::open(path)?)?), - None => match &self.config_dir { - Some(dir) if dir.join("ord.yaml").exists() => { - Ok(serde_yaml::from_reader(File::open(dir.join("ord.yaml"))?)?) - } - Some(_) | None => Ok(Default::default()), - }, - } - } - - fn derive_var( - arg_value: Option<&str>, - env_key: Option<&str>, - config_value: Option<&str>, - default_value: Option<&str>, - ) -> Result> { - let env_value = match env_key { - Some(env_key) => match env::var(format!("ORD_{env_key}")) { - Ok(env_value) => Some(env_value), - Err(err @ env::VarError::NotUnicode(_)) => return Err(err.into()), - Err(env::VarError::NotPresent) => None, - }, - None => None, - }; - - Ok( - arg_value - .or(env_value.as_deref()) - .or(config_value) - .or(default_value) - .map(str::to_string), - ) - } - - pub(crate) fn auth(&self) -> Result { - let config = self.load_config()?; - - let rpc_user = Options::derive_var( - self.bitcoin_rpc_user.as_deref(), - Some("BITCOIN_RPC_USER"), - config.bitcoin_rpc_user.as_deref(), - None, - )?; - - let rpc_pass = Options::derive_var( - self.bitcoin_rpc_pass.as_deref(), - Some("BITCOIN_RPC_PASS"), - config.bitcoin_rpc_pass.as_deref(), - None, - )?; - - match (rpc_user, rpc_pass) { - (Some(rpc_user), Some(rpc_pass)) => Ok(Auth::UserPass(rpc_user, rpc_pass)), - (None, Some(_rpc_pass)) => Err(anyhow!("no bitcoind rpc user specified")), - (Some(_rpc_user), None) => Err(anyhow!("no bitcoind rpc password specified")), - _ => Ok(Auth::CookieFile(self.cookie_file()?)), - } - } - - pub(crate) fn bitcoin_rpc_client(&self, wallet: Option) -> Result { - let rpc_url = self.rpc_url(wallet); - - let auth = self.auth()?; - - log::info!("Connecting to Bitcoin Core at {}", self.rpc_url(None)); - - if let Auth::CookieFile(cookie_file) = &auth { - log::info!( - "Using credentials from cookie file at `{}`", - cookie_file.display() - ); - - ensure!( - cookie_file.is_file(), - "cookie file `{}` does not exist", - cookie_file.display() - ); - } - - let client = Client::new(&rpc_url, auth) - .with_context(|| format!("failed to connect to Bitcoin Core RPC at `{rpc_url}`"))?; - - let mut checks = 0; - let rpc_chain = loop { - match client.get_blockchain_info() { - Ok(blockchain_info) => { - break match blockchain_info.chain.as_str() { - "main" => Chain::Mainnet, - "test" => Chain::Testnet, - "regtest" => Chain::Regtest, - "signet" => Chain::Signet, - other => bail!("Bitcoin RPC server on unknown chain: {other}"), - } - } - Err(bitcoincore_rpc::Error::JsonRpc(bitcoincore_rpc::jsonrpc::Error::Rpc(err))) - if err.code == -28 => {} - Err(err) => bail!("Failed to connect to Bitcoin Core RPC at `{rpc_url}`: {err}"), - } - - ensure! { - checks < 100, - "Failed to connect to Bitcoin Core RPC at `{rpc_url}`", - } - - checks += 1; - thread::sleep(Duration::from_millis(100)); - }; - - let ord_chain = self.chain(); - - if rpc_chain != ord_chain { - bail!("Bitcoin RPC server is on {rpc_chain} but ord is on {ord_chain}"); - } - - Ok(client) - } } +// todo: move these into settings #[cfg(test)] mod tests { - use {super::*, bitcoin::Network, std::path::Path, tempfile::TempDir}; + use {super::*, std::path::Path, tempfile::TempDir}; #[test] fn rpc_url_overrides_network() { @@ -303,6 +104,8 @@ mod tests { ]) .unwrap() .options + .settings() + .unwrap() .rpc_url(None), "127.0.0.1:1234/" ); @@ -320,6 +123,8 @@ mod tests { ]) .unwrap() .options + .settings() + .unwrap() .cookie_file() .unwrap(), Path::new("/foo/bar") @@ -328,26 +133,28 @@ mod tests { #[test] fn use_default_network() { - let arguments = Arguments::try_parse_from(["ord", "index", "update"]).unwrap(); + let settings = Arguments::try_parse_from(["ord", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap(); - assert_eq!(arguments.options.rpc_url(None), "127.0.0.1:8332/"); + assert_eq!(settings.rpc_url(None), "127.0.0.1:8332/"); - assert!(arguments - .options - .cookie_file() - .unwrap() - .ends_with(".cookie")); + assert!(settings.cookie_file().unwrap().ends_with(".cookie")); } #[test] fn uses_network_defaults() { - let arguments = - Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]).unwrap(); + let settings = Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap(); - assert_eq!(arguments.options.rpc_url(None), "127.0.0.1:38332/"); + assert_eq!(settings.rpc_url(None), "127.0.0.1:38332/"); - assert!(arguments - .options + assert!(settings .cookie_file() .unwrap() .display() @@ -364,6 +171,8 @@ mod tests { let cookie_file = Arguments::try_parse_from(["ord", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .cookie_file() .unwrap() .display() @@ -385,6 +194,8 @@ mod tests { let cookie_file = arguments .options + .settings() + .unwrap() .cookie_file() .unwrap() .display() @@ -412,6 +223,8 @@ mod tests { let cookie_file = arguments .options + .settings() + .unwrap() .cookie_file() .unwrap() .display() @@ -429,6 +242,8 @@ mod tests { let data_dir = Arguments::try_parse_from(["ord", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .data_dir() .display() .to_string(); @@ -443,6 +258,8 @@ mod tests { let data_dir = Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .data_dir() .display() .to_string(); @@ -468,6 +285,8 @@ mod tests { ]) .unwrap() .options + .settings() + .unwrap() .data_dir() .display() .to_string(); @@ -487,6 +306,8 @@ mod tests { let data_dir = Arguments::try_parse_from(["ord", "--chain", alias, "index", "update"]) .unwrap() .options + .settings() + .unwrap() .data_dir() .display() .to_string(); @@ -530,27 +351,6 @@ mod tests { ); } - #[test] - fn rpc_server_chain_must_match() { - let rpc_server = test_bitcoincore_rpc::builder() - .network(Network::Testnet) - .build(); - - let options = Options::try_parse_from([ - "ord", - "--cookie-file", - rpc_server.cookie_file().to_str().unwrap(), - "--rpc-url", - &rpc_server.url(), - ]) - .unwrap(); - - assert_eq!( - options.bitcoin_rpc_client(None).unwrap_err().to_string(), - "Bitcoin RPC server is on testnet but ord is on mainnet" - ); - } - #[test] fn chain_flags() { Arguments::try_parse_from(["ord", "--signet", "--chain", "signet", "index", "update"]) @@ -559,6 +359,8 @@ mod tests { Arguments::try_parse_from(["ord", "--signet", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .chain(), Chain::Signet ); @@ -566,6 +368,8 @@ mod tests { Arguments::try_parse_from(["ord", "-s", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .chain(), Chain::Signet ); @@ -576,6 +380,8 @@ mod tests { Arguments::try_parse_from(["ord", "--regtest", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .chain(), Chain::Regtest ); @@ -583,6 +389,8 @@ mod tests { Arguments::try_parse_from(["ord", "-r", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .chain(), Chain::Regtest ); @@ -593,6 +401,8 @@ mod tests { Arguments::try_parse_from(["ord", "--testnet", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .chain(), Chain::Testnet ); @@ -600,6 +410,8 @@ mod tests { Arguments::try_parse_from(["ord", "-t", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .chain(), Chain::Testnet ); @@ -641,7 +453,7 @@ mod tests { let (options, _) = parse_wallet_args("ord wallet --name foo balance"); assert_eq!( - options.rpc_url(Some("foo".into())), + options.settings().unwrap().rpc_url(Some("foo".into())), "127.0.0.1:8332/wallet/foo" ); } @@ -726,80 +538,6 @@ mod tests { ); } - #[test] - fn test_derive_var() { - assert_eq!(Options::derive_var(None, None, None, None).unwrap(), None); - - assert_eq!( - Options::derive_var(None, None, None, Some("foo")).unwrap(), - Some("foo".into()) - ); - - assert_eq!( - Options::derive_var(None, None, Some("bar"), Some("foo")).unwrap(), - Some("bar".into()) - ); - - assert_eq!( - Options::derive_var(Some("qux"), None, Some("bar"), Some("foo")).unwrap(), - Some("qux".into()) - ); - - assert_eq!( - Options::derive_var(Some("qux"), None, None, Some("foo")).unwrap(), - Some("qux".into()), - ); - } - - #[test] - fn auth_missing_rpc_pass_is_an_error() { - let options = Options { - bitcoin_rpc_user: Some("foo".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap_err().to_string(), - "no bitcoind rpc password specified" - ); - } - - #[test] - fn auth_missing_rpc_user_is_an_error() { - let options = Options { - bitcoin_rpc_pass: Some("bar".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap_err().to_string(), - "no bitcoind rpc user specified" - ); - } - - #[test] - fn auth_with_user_and_pass() { - let options = Options { - bitcoin_rpc_user: Some("foo".into()), - bitcoin_rpc_pass: Some("bar".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap(), - Auth::UserPass("foo".into(), "bar".into()) - ); - } - - #[test] - fn auth_with_cookie_file() { - let options = Options { - cookie_file: Some("/var/lib/Bitcoin/.cookie".into()), - ..Default::default() - }; - assert_eq!( - options.auth().unwrap(), - Auth::CookieFile("/var/lib/Bitcoin/.cookie".into()) - ); - } - #[test] fn setting_db_cache_size() { let arguments = @@ -819,31 +557,24 @@ mod tests { ]) .unwrap() .options + .settings() + .unwrap() .index_runes()); + assert!( !Arguments::try_parse_from(["ord", "--index-runes", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .index_runes() ); + assert!(!Arguments::try_parse_from(["ord", "index", "update"]) .unwrap() .options + .settings() + .unwrap() .index_runes()); } - - #[test] - fn cookie_file_does_not_exist_error() { - assert_eq!( - Options { - cookie_file: Some("/foo/bar/baz/qux/.cookie".into()), - ..Default::default() - } - .bitcoin_rpc_client(None) - .map(|_| "") - .unwrap_err() - .to_string(), - "cookie file `/foo/bar/baz/qux/.cookie` does not exist" - ); - } } diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000000..a6a6aca27f --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,404 @@ +use {super::*, bitcoincore_rpc::Auth}; + +// todo: +// enable JSON API +// --username and --password +// --bitcoin-data-dir /var/lib/bitcoind \ +// --chain ${CHAIN} \ +// --config-dir /var/lib/ord \ +// --data-dir /var/lib/ord \ +// --index-runes \ +// --index-sats \ +// server \ +// --acme-contact mailto:casey@rodarmor.com \ +// --csp-origin https://${CSP_ORIGIN} \ +// --http \ +// --https \ +// --disable-json-api + +#[derive(Default, Debug, Clone)] +pub struct Settings { + // todo: make these private + pub(crate) options: Options, + pub(crate) config: Config, + pub(crate) chain: Chain, +} + +impl Settings { + pub(crate) fn new(options: Options) -> Result { + let config: Config = match &options.config { + Some(path) => serde_yaml::from_reader(File::open(path)?)?, + None => match &options.config_dir { + Some(dir) if dir.join("ord.yaml").exists() => { + serde_yaml::from_reader(File::open(dir.join("ord.yaml"))?)? + } + Some(_) | None => Default::default(), + }, + }; + + let chain = Self::setting_typed( + options + .signet + .then_some(Chain::Signet) + .or(options.regtest.then_some(Chain::Regtest)) + .or(options.testnet.then_some(Chain::Testnet)) + .or(options.chain_argument), + Some("CHAIN"), + config.chain, + Chain::Mainnet, + )?; + + Ok(Self { + config, + options, + chain, + }) + } + + pub(crate) fn auth(&self) -> Result { + let rpc_user = Self::setting( + self.options.bitcoin_rpc_user.as_deref(), + Some("BITCOIN_RPC_USER"), + self.config.bitcoin_rpc_user.as_deref(), + None, + )?; + + let rpc_pass = Self::setting( + self.options.bitcoin_rpc_pass.as_deref(), + Some("BITCOIN_RPC_PASS"), + self.config.bitcoin_rpc_pass.as_deref(), + None, + )?; + + match (rpc_user, rpc_pass) { + (Some(rpc_user), Some(rpc_pass)) => Ok(Auth::UserPass(rpc_user, rpc_pass)), + (None, Some(_rpc_pass)) => Err(anyhow!("no bitcoind rpc user specified")), + (Some(_rpc_user), None) => Err(anyhow!("no bitcoind rpc password specified")), + _ => Ok(Auth::CookieFile(self.cookie_file()?)), + } + } + + pub(crate) fn bitcoin_rpc_client(&self, wallet: Option) -> Result { + let rpc_url = self.rpc_url(wallet); + + let auth = self.auth()?; + + log::info!("Connecting to Bitcoin Core at {}", self.rpc_url(None)); + + if let Auth::CookieFile(cookie_file) = &auth { + log::info!( + "Using credentials from cookie file at `{}`", + cookie_file.display() + ); + + ensure!( + cookie_file.is_file(), + "cookie file `{}` does not exist", + cookie_file.display() + ); + } + + let client = Client::new(&rpc_url, auth) + .with_context(|| format!("failed to connect to Bitcoin Core RPC at `{rpc_url}`"))?; + + let mut checks = 0; + let rpc_chain = loop { + match client.get_blockchain_info() { + Ok(blockchain_info) => { + break match blockchain_info.chain.as_str() { + "main" => Chain::Mainnet, + "test" => Chain::Testnet, + "regtest" => Chain::Regtest, + "signet" => Chain::Signet, + other => bail!("Bitcoin RPC server on unknown chain: {other}"), + } + } + Err(bitcoincore_rpc::Error::JsonRpc(bitcoincore_rpc::jsonrpc::Error::Rpc(err))) + if err.code == -28 => {} + Err(err) => bail!("Failed to connect to Bitcoin Core RPC at `{rpc_url}`: {err}"), + } + + ensure! { + checks < 100, + "Failed to connect to Bitcoin Core RPC at `{rpc_url}`", + } + + checks += 1; + thread::sleep(Duration::from_millis(100)); + }; + + let ord_chain = self.chain(); + + if rpc_chain != ord_chain { + bail!("Bitcoin RPC server is on {rpc_chain} but ord is on {ord_chain}"); + } + + Ok(client) + } + + pub(crate) fn chain(&self) -> Chain { + self.chain + } + + pub(crate) fn cookie_file(&self) -> Result { + if let Some(cookie_file) = &self.options.cookie_file { + return Ok(cookie_file.clone()); + } + + let path = if let Some(bitcoin_data_dir) = &self.options.bitcoin_data_dir { + bitcoin_data_dir.clone() + } else if cfg!(target_os = "linux") { + dirs::home_dir() + .ok_or_else(|| anyhow!("failed to get cookie file path: could not get home dir"))? + .join(".bitcoin") + } else { + dirs::data_dir() + .ok_or_else(|| anyhow!("failed to get cookie file path: could not get data dir"))? + .join("Bitcoin") + }; + + let path = self.chain().join_with_data_dir(&path); + + Ok(path.join(".cookie")) + } + + pub(crate) fn credentials(&self) -> Option<(&str, &str)> { + self + .options + .username + .as_deref() + .zip(self.options.password.as_deref()) + } + + pub(crate) fn data_dir(&self) -> PathBuf { + self.chain().join_with_data_dir(&self.options.data_dir) + } + + pub(crate) fn first_inscription_height(&self) -> u32 { + if integration_test() { + 0 + } else { + self + .options + .first_inscription_height + .unwrap_or_else(|| self.chain().first_inscription_height()) + } + } + + pub(crate) fn first_rune_height(&self) -> u32 { + if integration_test() { + 0 + } else { + self.chain().first_rune_height() + } + } + + pub(crate) fn index_runes(&self) -> bool { + self.options.index_runes && self.chain() != Chain::Mainnet + } + + pub(crate) fn rpc_url(&self, wallet_name: Option) -> String { + let base_url = self + .options + .rpc_url + .clone() + .unwrap_or(format!("127.0.0.1:{}", self.chain().default_rpc_port())); + + match wallet_name { + Some(wallet_name) => format!("{base_url}/wallet/{wallet_name}"), + None => format!("{base_url}/"), + } + } + + fn setting_typed>( + arg_value: Option, + env_key: Option<&str>, + config_value: Option, + default_value: T, + ) -> Result { + if let Some(arg_value) = arg_value { + return Ok(arg_value); + } + + if let Some(env_key) = env_key { + let key = format!("ORD_{env_key}"); + match env::var(key) { + Ok(env_value) => { + return Ok( + env_value + .parse() + .with_context(|| anyhow!("failed to parse {env_key}"))?, + ) + } + Err(err @ env::VarError::NotUnicode(_)) => return Err(err.into()), + Err(env::VarError::NotPresent) => {} + } + } + + if let Some(config_value) = config_value { + return Ok(config_value); + } + + Ok(default_value) + } + + fn setting( + arg_value: Option<&str>, + env_key: Option<&str>, + config_value: Option<&str>, + default_value: Option<&str>, + ) -> Result> { + if let Some(arg_value) = arg_value { + return Ok(Some(arg_value.into())); + } + + if let Some(env_key) = env_key { + match env::var(format!("ORD_{env_key}")) { + Ok(env_value) => return Ok(Some(env_value)), + Err(err @ env::VarError::NotUnicode(_)) => return Err(err.into()), + Err(env::VarError::NotPresent) => {} + } + } + + Ok(config_value.or(default_value).map(str::to_string)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn settings(args: &[&str]) -> Settings { + let options = Options::try_parse_from(args).unwrap(); + + Settings { + options, + ..Default::default() + } + } + + #[test] + fn auth_missing_rpc_pass_is_an_error() { + let settings = Settings { + options: Options { + bitcoin_rpc_user: Some("foo".into()), + ..Default::default() + }, + ..Default::default() + }; + + assert_eq!( + settings.auth().unwrap_err().to_string(), + "no bitcoind rpc password specified" + ); + } + + #[test] + fn auth_missing_rpc_user_is_an_error() { + let settings = Settings { + options: Options { + bitcoin_rpc_pass: Some("bar".into()), + ..Default::default() + }, + ..Default::default() + }; + assert_eq!( + settings.auth().unwrap_err().to_string(), + "no bitcoind rpc user specified" + ); + } + + #[test] + fn auth_with_user_and_pass() { + let settings = Settings { + options: Options { + bitcoin_rpc_user: Some("foo".into()), + bitcoin_rpc_pass: Some("bar".into()), + ..Default::default() + }, + ..Default::default() + }; + assert_eq!( + settings.auth().unwrap(), + Auth::UserPass("foo".into(), "bar".into()) + ); + } + + #[test] + fn auth_with_cookie_file() { + let settings = Settings { + options: Options { + cookie_file: Some("/var/lib/Bitcoin/.cookie".into()), + ..Default::default() + }, + ..Default::default() + }; + assert_eq!( + settings.auth().unwrap(), + Auth::CookieFile("/var/lib/Bitcoin/.cookie".into()) + ); + } + + #[test] + fn cookie_file_does_not_exist_error() { + assert_eq!( + Settings { + options: Options { + cookie_file: Some("/foo/bar/baz/qux/.cookie".into()), + ..Default::default() + }, + ..Default::default() + } + .bitcoin_rpc_client(None) + .map(|_| "") + .unwrap_err() + .to_string(), + "cookie file `/foo/bar/baz/qux/.cookie` does not exist" + ); + } + + #[test] + fn rpc_server_chain_must_match() { + let rpc_server = test_bitcoincore_rpc::builder() + .network(Network::Testnet) + .build(); + + let settings = settings(&[ + "ord", + "--cookie-file", + rpc_server.cookie_file().to_str().unwrap(), + "--rpc-url", + &rpc_server.url(), + ]); + + assert_eq!( + settings.bitcoin_rpc_client(None).unwrap_err().to_string(), + "Bitcoin RPC server is on testnet but ord is on mainnet" + ); + } + + #[test] + fn setting() { + assert_eq!(Settings::setting(None, None, None, None).unwrap(), None); + + assert_eq!( + Settings::setting(None, None, None, Some("foo")).unwrap(), + Some("foo".into()) + ); + + assert_eq!( + Settings::setting(None, None, Some("bar"), Some("foo")).unwrap(), + Some("bar".into()) + ); + + assert_eq!( + Settings::setting(Some("qux"), None, Some("bar"), Some("foo")).unwrap(), + Some("qux".into()) + ); + + assert_eq!( + Settings::setting(Some("qux"), None, None, Some("foo")).unwrap(), + Some("qux".into()), + ); + } +} diff --git a/src/subcommand.rs b/src/subcommand.rs index 06c105e611..a48fa5dca9 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -51,28 +51,28 @@ pub(crate) enum Subcommand { } impl Subcommand { - pub(crate) fn run(self, options: Options) -> SubcommandResult { + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { match self { - Self::Balances => balances::run(options), - Self::Decode(decode) => decode.run(options), + Self::Balances => balances::run(settings), + Self::Decode(decode) => decode.run(settings), Self::Env(env) => env.run(), Self::Epochs => epochs::run(), - Self::Find(find) => find.run(options), - Self::Index(index) => index.run(options), - Self::List(list) => list.run(options), + Self::Find(find) => find.run(settings), + Self::Index(index) => index.run(settings), + Self::List(list) => list.run(settings), Self::Parse(parse) => parse.run(), - Self::Runes => runes::run(options), + Self::Runes => runes::run(settings), Self::Server(server) => { - let index = Arc::new(Index::open(&options)?); + let index = Arc::new(Index::open(&settings)?); let handle = axum_server::Handle::new(); LISTENERS.lock().unwrap().push(handle.clone()); - server.run(options, index, handle) + server.run(settings, index, handle) } Self::Subsidy(subsidy) => subsidy.run(), Self::Supply => supply::run(), Self::Teleburn(teleburn) => teleburn.run(), Self::Traits(traits) => traits.run(), - Self::Wallet(wallet) => wallet.run(options), + Self::Wallet(wallet) => wallet.run(settings), } } } diff --git a/src/subcommand/balances.rs b/src/subcommand/balances.rs index 35424bc879..8643c926ac 100644 --- a/src/subcommand/balances.rs +++ b/src/subcommand/balances.rs @@ -5,8 +5,8 @@ pub struct Output { pub runes: BTreeMap>, } -pub(crate) fn run(options: Options) -> SubcommandResult { - let index = Index::open(&options)?; +pub(crate) fn run(settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; ensure!( index.has_rune_index(), diff --git a/src/subcommand/decode.rs b/src/subcommand/decode.rs index 3d3184ff86..f0562a126a 100644 --- a/src/subcommand/decode.rs +++ b/src/subcommand/decode.rs @@ -74,9 +74,9 @@ pub(crate) struct Decode { } impl Decode { - pub(crate) fn run(self, options: Options) -> SubcommandResult { + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { let transaction = if let Some(txid) = self.txid { - options + settings .bitcoin_rpc_client(None)? .get_raw_transaction(&txid, None)? } else if let Some(file) = self.file { diff --git a/src/subcommand/find.rs b/src/subcommand/find.rs index 1f35bc518e..602710f656 100644 --- a/src/subcommand/find.rs +++ b/src/subcommand/find.rs @@ -21,8 +21,8 @@ pub struct FindRangeOutput { } impl Find { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; if !index.has_sat_index() { bail!("find requires index created with `--index-sats` flag"); diff --git a/src/subcommand/index.rs b/src/subcommand/index.rs index a76b9dca42..cf24863ed0 100644 --- a/src/subcommand/index.rs +++ b/src/subcommand/index.rs @@ -15,11 +15,11 @@ pub(crate) enum IndexSubcommand { } impl IndexSubcommand { - pub(crate) fn run(self, options: Options) -> SubcommandResult { + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { match self { - Self::Export(export) => export.run(options), - Self::Info(info) => info.run(options), - Self::Update => update::run(options), + Self::Export(export) => export.run(settings), + Self::Info(info) => info.run(settings), + Self::Update => update::run(settings), } } } diff --git a/src/subcommand/index/export.rs b/src/subcommand/index/export.rs index 4cb45af531..5e93eaec18 100644 --- a/src/subcommand/index/export.rs +++ b/src/subcommand/index/export.rs @@ -9,8 +9,8 @@ pub(crate) struct Export { } impl Export { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; index.update()?; index.export(&self.tsv, self.include_addresses)?; diff --git a/src/subcommand/index/info.rs b/src/subcommand/index/info.rs index 3773e43eb3..11143e8e36 100644 --- a/src/subcommand/index/info.rs +++ b/src/subcommand/index/info.rs @@ -15,8 +15,8 @@ pub struct TransactionsOutput { } impl Info { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; index.update()?; diff --git a/src/subcommand/index/update.rs b/src/subcommand/index/update.rs index 62a82ff838..ffdbce09a2 100644 --- a/src/subcommand/index/update.rs +++ b/src/subcommand/index/update.rs @@ -1,7 +1,7 @@ use super::*; -pub(crate) fn run(options: Options) -> SubcommandResult { - let index = Index::open(&options)?; +pub(crate) fn run(settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; index.update()?; diff --git a/src/subcommand/list.rs b/src/subcommand/list.rs index d42b570443..290dcf4db1 100644 --- a/src/subcommand/list.rs +++ b/src/subcommand/list.rs @@ -23,8 +23,8 @@ pub struct Range { } impl List { - pub(crate) fn run(self, options: Options) -> SubcommandResult { - let index = Index::open(&options)?; + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; if !index.has_sat_index() { bail!("list requires index created with `--index-sats` flag"); diff --git a/src/subcommand/runes.rs b/src/subcommand/runes.rs index 311229aa5d..dd99e79eab 100644 --- a/src/subcommand/runes.rs +++ b/src/subcommand/runes.rs @@ -23,8 +23,8 @@ pub struct RuneInfo { pub timestamp: DateTime, } -pub(crate) fn run(options: Options) -> SubcommandResult { - let index = Index::open(&options)?; +pub(crate) fn run(settings: Settings) -> SubcommandResult { + let index = Index::open(&settings)?; ensure!( index.has_rune_index(), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 0461c37bae..1897480bbd 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -190,7 +190,7 @@ pub struct Server { } impl Server { - pub fn run(self, options: Options, index: Arc, handle: Handle) -> SubcommandResult { + pub fn run(self, settings: Settings, index: Arc, handle: Handle) -> SubcommandResult { Runtime::new()?.block_on(async { let index_clone = index.clone(); @@ -214,11 +214,11 @@ impl Server { INDEXER.lock().unwrap().replace(index_thread); - let config = Arc::new(options.load_config()?); + let settings = Arc::new(settings); let acme_domains = self.acme_domains()?; let server_config = Arc::new(ServerConfig { - chain: options.chain(), + chain: settings.chain(), csp_origin: self.csp_origin.clone(), domain: acme_domains.first().cloned(), index_sats: index.has_sat_index(), @@ -304,7 +304,7 @@ impl Server { .route("/tx/:txid", get(Self::transaction)) .layer(Extension(index)) .layer(Extension(server_config.clone())) - .layer(Extension(config)) + .layer(Extension(settings.clone())) .layer(SetResponseHeaderLayer::if_not_present( header::CONTENT_SECURITY_POLICY, HeaderValue::from_static("default-src 'self'"), @@ -321,7 +321,7 @@ impl Server { .layer(CompressionLayer::new()) .with_state(server_config); - let router = if let Some((username, password)) = options.credentials() { + let router = if let Some((username, password)) = settings.credentials() { router.layer(ValidateRequestHeaderLayer::basic(username, password)) } else { router @@ -339,7 +339,7 @@ impl Server { router, handle, https_port, - SpawnConfig::Https(self.acceptor(&options)?), + SpawnConfig::Https(self.acceptor(&settings)?), )? .await?? } @@ -360,7 +360,7 @@ impl Server { router, handle, https_port, - SpawnConfig::Https(self.acceptor(&options)?), + SpawnConfig::Https(self.acceptor(&settings)?), )? ); http_result.and(https_result)??; @@ -435,9 +435,9 @@ impl Server { })) } - fn acme_cache(acme_cache: Option<&PathBuf>, options: &Options) -> PathBuf { + fn acme_cache(acme_cache: Option<&PathBuf>, settings: &Settings) -> PathBuf { acme_cache - .unwrap_or(&options.data_dir().join("acme-cache")) + .unwrap_or(&settings.data_dir().join("acme-cache")) .to_path_buf() } @@ -467,12 +467,12 @@ impl Server { } } - fn acceptor(&self, options: &Options) -> Result { + fn acceptor(&self, settings: &Settings) -> Result { let config = AcmeConfig::new(self.acme_domains()?) .contact(&self.acme_contact) .cache_option(Some(DirCache::new(Self::acme_cache( self.acme_cache.as_ref(), - options, + settings, )))) .directory(if cfg!(test) { LETS_ENCRYPT_STAGING_DIRECTORY @@ -1232,13 +1232,13 @@ impl Server { async fn content( Extension(index): Extension>, - Extension(config): Extension>, + Extension(settings): Extension>, Extension(server_config): Extension>, Path(inscription_id): Path, accept_encoding: AcceptEncoding, ) -> ServerResult { task::block_in_place(|| { - if config.is_hidden(inscription_id) { + if settings.config.is_hidden(inscription_id) { return Ok(PreviewUnknownHtml.into_response()); } @@ -1332,13 +1332,13 @@ impl Server { async fn preview( Extension(index): Extension>, - Extension(config): Extension>, + Extension(settings): Extension>, Extension(server_config): Extension>, Path(inscription_id): Path, accept_encoding: AcceptEncoding, ) -> ServerResult { task::block_in_place(|| { - if config.is_hidden(inscription_id) { + if settings.config.is_hidden(inscription_id) { return Ok(PreviewUnknownHtml.into_response()); } @@ -1877,7 +1877,7 @@ mod tests { None => "".to_string(), }; - let (options, server) = parse_server_args(&format!( + let (settings, server) = parse_server_args(&format!( "ord --rpc-url {} --cookie-file {} --data-dir {} {config_args} {} server --http-port {} --address 127.0.0.1 {}", bitcoin_rpc_server.url(), cookiefile.to_str().unwrap(), @@ -1887,13 +1887,13 @@ mod tests { server_args.join(" "), )); - let index = Arc::new(Index::open(&options).unwrap()); + let index = Arc::new(Index::open(&settings).unwrap()); let ord_server_handle = Handle::new(); { let index = index.clone(); let ord_server_handle = ord_server_handle.clone(); - thread::spawn(|| server.run(options, index, ord_server_handle).unwrap()); + thread::spawn(|| server.run(settings, index, ord_server_handle).unwrap()); } while index.statistic(crate::index::Statistic::Commits) == 0 { @@ -1956,6 +1956,7 @@ mod tests { self.url.join(url).unwrap() } + #[track_caller] fn assert_response(&self, path: impl AsRef, status: StatusCode, expected_response: &str) { let response = self.get(path); assert_eq!(response.status(), status, "{}", response.text().unwrap()); @@ -2026,10 +2027,10 @@ mod tests { } } - fn parse_server_args(args: &str) -> (Options, Server) { + fn parse_server_args(args: &str) -> (Settings, Server) { match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { - Subcommand::Server(server) => (arguments.options, server), + Subcommand::Server(server) => (arguments.options.settings().unwrap(), server), subcommand => panic!("unexpected subcommand: {subcommand:?}"), }, Err(err) => panic!("error parsing arguments: {err}"), @@ -2158,9 +2159,13 @@ mod tests { #[test] fn acme_cache_defaults_to_data_dir() { let arguments = Arguments::try_parse_from(["ord", "--data-dir", "foo", "server"]).unwrap(); - let acme_cache = Server::acme_cache(None, &arguments.options) - .display() - .to_string(); + + let settings = Settings { + options: arguments.options, + config: Default::default(), + }; + + let acme_cache = Server::acme_cache(None, &settings).display().to_string(); assert!( acme_cache.contains(if cfg!(windows) { r"foo\acme-cache" @@ -2176,7 +2181,11 @@ mod tests { let arguments = Arguments::try_parse_from(["ord", "--data-dir", "foo", "server", "--acme-cache", "bar"]) .unwrap(); - let acme_cache = Server::acme_cache(Some(&"bar".into()), &arguments.options) + let settings = Settings { + options: arguments.options, + config: Default::default(), + }; + let acme_cache = Server::acme_cache(Some(&"bar".into()), &settings) .display() .to_string(); assert_eq!(acme_cache, "bar") diff --git a/src/subcommand/wallet.rs b/src/subcommand/wallet.rs index 8a54219d63..8518f50b35 100644 --- a/src/subcommand/wallet.rs +++ b/src/subcommand/wallet.rs @@ -70,17 +70,17 @@ pub(crate) enum Subcommand { } impl WalletCommand { - pub(crate) fn run(self, options: Options) -> SubcommandResult { + pub(crate) fn run(self, settings: Settings) -> SubcommandResult { match self.subcommand { - Subcommand::Create(create) => return create.run(self.name, &options), - Subcommand::Restore(restore) => return restore.run(self.name, &options), + Subcommand::Create(create) => return create.run(self.name, &settings), + Subcommand::Restore(restore) => return restore.run(self.name, &settings), _ => {} }; let wallet = Wallet::build( self.name.clone(), self.no_sync, - options.clone(), + settings.clone(), self.server_url, )?; diff --git a/src/subcommand/wallet/create.rs b/src/subcommand/wallet/create.rs index 10612704c8..768071b0bd 100644 --- a/src/subcommand/wallet/create.rs +++ b/src/subcommand/wallet/create.rs @@ -20,13 +20,13 @@ pub(crate) struct Create { } impl Create { - pub(crate) fn run(self, name: String, options: &Options) -> SubcommandResult { + pub(crate) fn run(self, name: String, settings: &Settings) -> SubcommandResult { let mut entropy = [0; 16]; rand::thread_rng().fill_bytes(&mut entropy); let mnemonic = Mnemonic::from_entropy(&entropy)?; - Wallet::initialize(name, options, mnemonic.to_seed(&self.passphrase))?; + Wallet::initialize(name, settings, mnemonic.to_seed(&self.passphrase))?; Ok(Some(Box::new(Output { mnemonic, diff --git a/src/subcommand/wallet/restore.rs b/src/subcommand/wallet/restore.rs index ed58ab8c7a..98923e10bd 100644 --- a/src/subcommand/wallet/restore.rs +++ b/src/subcommand/wallet/restore.rs @@ -15,9 +15,9 @@ enum Source { } impl Restore { - pub(crate) fn run(self, name: String, options: &Options) -> SubcommandResult { + pub(crate) fn run(self, name: String, settings: &Settings) -> SubcommandResult { ensure!( - !options + !settings .bitcoin_rpc_client(None)? .list_wallet_dir()? .iter() @@ -36,13 +36,13 @@ impl Restore { "descriptor does not take a passphrase" ); let wallet_descriptors: ListDescriptorsResult = serde_json::from_str(&buffer)?; - Wallet::initialize_from_descriptors(name, options, wallet_descriptors.descriptors)?; + Wallet::initialize_from_descriptors(name, settings, wallet_descriptors.descriptors)?; } Source::Mnemonic => { let mnemonic = Mnemonic::from_str(&buffer)?; Wallet::initialize( name, - options, + settings, mnemonic.to_seed(self.passphrase.unwrap_or_default()), )?; } diff --git a/src/wallet.rs b/src/wallet.rs index f4696f2e12..2d6cc42735 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -39,21 +39,26 @@ impl OrdClient { } pub(crate) struct Wallet { - rpc_url: Url, - options: Options, bitcoin_client: bitcoincore_rpc::Client, - ord_client: reqwest::blocking::Client, - has_sat_index: bool, has_rune_index: bool, - utxos: BTreeMap, + has_sat_index: bool, + inscription_info: BTreeMap, + inscriptions: BTreeMap>, locked_utxos: BTreeMap, + ord_client: reqwest::blocking::Client, output_info: BTreeMap, - inscriptions: BTreeMap>, - inscription_info: BTreeMap, + rpc_url: Url, + settings: Settings, + utxos: BTreeMap, } impl Wallet { - pub(crate) fn build(name: String, no_sync: bool, options: Options, rpc_url: Url) -> Result { + pub(crate) fn build( + name: String, + no_sync: bool, + settings: Settings, + rpc_url: Url, + ) -> Result { let mut headers = header::HeaderMap::new(); headers.insert( @@ -61,7 +66,7 @@ impl Wallet { header::HeaderValue::from_static("application/json"), ); - if let Some((username, password)) = options.credentials() { + if let Some((username, password)) = settings.credentials() { use base64::Engine; let credentials = base64::engine::general_purpose::STANDARD.encode(format!("{username}:{password}")); @@ -80,7 +85,7 @@ impl Wallet { .build()? .block_on(async move { let bitcoin_client = { - let client = Self::check_version(options.bitcoin_rpc_client(Some(name.clone()))?)?; + let client = Self::check_version(settings.bitcoin_rpc_client(Some(name.clone()))?)?; if !client.list_wallets()?.contains(&name) { client.load_wallet(&name)?; @@ -166,17 +171,17 @@ impl Wallet { } Ok(Wallet { - options, - rpc_url, bitcoin_client, - ord_client, - has_sat_index: status.sat_index, has_rune_index: status.rune_index, - utxos, + has_sat_index: status.sat_index, + inscription_info, + inscriptions, locked_utxos, + ord_client, output_info, - inscriptions, - inscription_info, + rpc_url, + settings, + utxos, }) }) } @@ -464,7 +469,7 @@ impl Wallet { } pub(crate) fn chain(&self) -> Chain { - self.options.chain() + self.settings.chain() } fn check_descriptors(wallet_name: &str, descriptors: Vec) -> Result> { @@ -487,10 +492,10 @@ impl Wallet { pub(crate) fn initialize_from_descriptors( name: String, - options: &Options, + settings: &Settings, descriptors: Vec, ) -> Result { - let client = Self::check_version(options.bitcoin_rpc_client(Some(name.clone()))?)?; + let client = Self::check_version(settings.bitcoin_rpc_client(Some(name.clone()))?)?; let descriptors = Self::check_descriptors(&name, descriptors)?; @@ -521,8 +526,8 @@ impl Wallet { Ok(()) } - pub(crate) fn initialize(name: String, options: &Options, seed: [u8; 64]) -> Result { - Self::check_version(options.bitcoin_rpc_client(None)?)?.create_wallet( + pub(crate) fn initialize(name: String, settings: &Settings, seed: [u8; 64]) -> Result { + Self::check_version(settings.bitcoin_rpc_client(None)?)?.create_wallet( &name, None, Some(true), @@ -530,7 +535,7 @@ impl Wallet { None, )?; - let network = options.chain().network(); + let network = settings.chain().network(); let secp = Secp256k1::new(); @@ -550,7 +555,7 @@ impl Wallet { for change in [false, true] { Self::derive_and_import_descriptor( name.clone(), - options, + settings, &secp, (fingerprint, derivation_path.clone()), derived_private_key, @@ -563,7 +568,7 @@ impl Wallet { fn derive_and_import_descriptor( name: String, - options: &Options, + settings: &Settings, secp: &Secp256k1, origin: (Fingerprint, DerivationPath), derived_private_key: ExtendedPrivKey, @@ -585,7 +590,7 @@ impl Wallet { let descriptor = miniscript::descriptor::Descriptor::new_tr(public_key, None)?; - options + settings .bitcoin_rpc_client(Some(name.clone()))? .import_descriptors(vec![ImportDescriptors { descriptor: descriptor.to_string_with_secret(&key_map), diff --git a/tests/test_server.rs b/tests/test_server.rs index e027914f17..97464ef7be 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -47,7 +47,7 @@ impl TestServer { .unwrap() .port(); - let (options, server) = parse_ord_server_args(&format!( + let (settings, server) = parse_ord_server_args(&format!( "ord --rpc-url {} --cookie-file {} --bitcoin-data-dir {} --data-dir {} {} server {} --http-port {port} --address 127.0.0.1", bitcoin_rpc_server.url(), cookiefile.to_str().unwrap(), @@ -57,13 +57,13 @@ impl TestServer { ord_server_args.join(" "), )); - let index = Arc::new(Index::open(&options).unwrap()); + let index = Arc::new(Index::open(&settings).unwrap()); let ord_server_handle = Handle::new(); { let index = index.clone(); let ord_server_handle = ord_server_handle.clone(); - thread::spawn(|| server.run(options, index, ord_server_handle).unwrap()); + thread::spawn(|| server.run(settings, index, ord_server_handle).unwrap()); } for i in 0.. { From 5884e11e5223e059552409606f659397f9e6372c Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 21 Feb 2024 19:08:24 -0800 Subject: [PATCH 02/35] Add a test --- src/chain.rs | 14 +++++++++++++- src/index/testing.rs | 3 ++- src/settings.rs | 16 ---------------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index b4638151e8..c52fe68234 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -109,7 +109,6 @@ impl Display for Chain { } } -// todo: test this impl FromStr for Chain { type Err = Error; @@ -123,3 +122,16 @@ impl FromStr for Chain { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_str() { + assert_eq!("mainnet".parse::().unwrap(), Chain::Mainnet); + assert_eq!("regtest".parse::().unwrap(), Chain::Regtest); + assert_eq!("signet".parse::().unwrap(), Chain::Signet); + assert_eq!("testnet".parse::().unwrap(), Chain::Testnet); + } +} diff --git a/src/index/testing.rs b/src/index/testing.rs index ae030dc17d..7a7e10ddd2 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -34,8 +34,9 @@ impl ContextBuilder { let options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap(); let settings = Settings { - options, + chain: self.chain, config: Default::default(), + options, }; let index = Index::open_with_event_sender(&settings, self.event_sender)?; index.update().unwrap(); diff --git a/src/settings.rs b/src/settings.rs index a6a6aca27f..36dc062e00 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,21 +1,5 @@ use {super::*, bitcoincore_rpc::Auth}; -// todo: -// enable JSON API -// --username and --password -// --bitcoin-data-dir /var/lib/bitcoind \ -// --chain ${CHAIN} \ -// --config-dir /var/lib/ord \ -// --data-dir /var/lib/ord \ -// --index-runes \ -// --index-sats \ -// server \ -// --acme-contact mailto:casey@rodarmor.com \ -// --csp-origin https://${CSP_ORIGIN} \ -// --http \ -// --https \ -// --disable-json-api - #[derive(Default, Debug, Clone)] pub struct Settings { // todo: make these private From a36804add299f64d47d2e514c94ffb74a76ba24a Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 21 Feb 2024 19:15:35 -0800 Subject: [PATCH 03/35] Enhance --- src/options.rs | 26 +++++++++++++++++--------- src/settings.rs | 8 +++----- src/subcommand/server.rs | 4 ++-- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/options.rs b/src/options.rs index 72f52d7abf..3563d7e523 100644 --- a/src/options.rs +++ b/src/options.rs @@ -85,9 +85,13 @@ impl Options { .map(|dir| dir.join("ord")) .expect("failed to retrieve data dir") } + + #[cfg(test)] + pub(crate) fn settings(self) -> Result { + Settings::new(self) + } } -// todo: move these into settings #[cfg(test)] mod tests { use {super::*, std::path::Path, tempfile::TempDir}; @@ -442,8 +446,9 @@ mod tests { Arguments::try_parse_from(["ord", "index", "update"]) .unwrap() .options - .load_config() - .unwrap(), + .settings() + .unwrap() + .config, Default::default() ); } @@ -472,8 +477,9 @@ mod tests { Arguments::try_parse_from(["ord", "--config", path.to_str().unwrap(), "index", "update"]) .unwrap() .options - .load_config() - .unwrap(), + .settings() + .unwrap() + .config, Config { hidden: iter::once(id).collect(), ..Default::default() @@ -495,8 +501,9 @@ mod tests { Arguments::try_parse_from(["ord", "--config", path.to_str().unwrap(), "index", "update"]) .unwrap() .options - .load_config() - .unwrap(), + .settings() + .unwrap() + .config, Config { bitcoin_rpc_user: Some("foo".into()), bitcoin_rpc_pass: Some("bar".into()), @@ -529,8 +536,9 @@ mod tests { ]) .unwrap() .options - .load_config() - .unwrap(), + .settings() + .unwrap() + .config, Config { hidden: iter::once(id).collect(), ..Default::default() diff --git a/src/settings.rs b/src/settings.rs index 36dc062e00..98027cad8b 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -208,11 +208,9 @@ impl Settings { let key = format!("ORD_{env_key}"); match env::var(key) { Ok(env_value) => { - return Ok( - env_value - .parse() - .with_context(|| anyhow!("failed to parse {env_key}"))?, - ) + return env_value + .parse() + .with_context(|| anyhow!("failed to parse {env_key}")) } Err(err @ env::VarError::NotUnicode(_)) => return Err(err.into()), Err(env::VarError::NotPresent) => {} diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 1897480bbd..54e6f0bbc1 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -2162,7 +2162,7 @@ mod tests { let settings = Settings { options: arguments.options, - config: Default::default(), + ..Default::default() }; let acme_cache = Server::acme_cache(None, &settings).display().to_string(); @@ -2183,7 +2183,7 @@ mod tests { .unwrap(); let settings = Settings { options: arguments.options, - config: Default::default(), + ..Default::default() }; let acme_cache = Server::acme_cache(Some(&"bar".into()), &settings) .display() From ae30ad09d4b6213794fcd2562832d43c7718f0fd Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 21 Feb 2024 19:15:53 -0800 Subject: [PATCH 04/35] Enhance --- src/settings.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 98027cad8b..a838c35094 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,10 +2,9 @@ use {super::*, bitcoincore_rpc::Auth}; #[derive(Default, Debug, Clone)] pub struct Settings { - // todo: make these private - pub(crate) options: Options, - pub(crate) config: Config, pub(crate) chain: Chain, + pub(crate) config: Config, + pub(crate) options: Options, } impl Settings { From b2679469d6992c7878757b087ac8795b87051ea3 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 21 Feb 2024 19:24:01 -0800 Subject: [PATCH 05/35] Rename --- src/settings.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index a838c35094..d6a303885e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -39,18 +39,16 @@ impl Settings { } pub(crate) fn auth(&self) -> Result { - let rpc_user = Self::setting( + let rpc_user = Self::setting_opt( self.options.bitcoin_rpc_user.as_deref(), Some("BITCOIN_RPC_USER"), self.config.bitcoin_rpc_user.as_deref(), - None, )?; - let rpc_pass = Self::setting( + let rpc_pass = Self::setting_opt( self.options.bitcoin_rpc_pass.as_deref(), Some("BITCOIN_RPC_PASS"), self.config.bitcoin_rpc_pass.as_deref(), - None, )?; match (rpc_user, rpc_pass) { @@ -193,7 +191,7 @@ impl Settings { } } - fn setting_typed>( + fn setting>( arg_value: Option, env_key: Option<&str>, config_value: Option, @@ -223,11 +221,10 @@ impl Settings { Ok(default_value) } - fn setting( + fn setting_opt( arg_value: Option<&str>, env_key: Option<&str>, config_value: Option<&str>, - default_value: Option<&str>, ) -> Result> { if let Some(arg_value) = arg_value { return Ok(Some(arg_value.into())); @@ -241,7 +238,7 @@ impl Settings { } } - Ok(config_value.or(default_value).map(str::to_string)) + Ok(config_value.map(str::to_string)) } } From cd3f06523af016de498810b3df7136d189a3bf54 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 23 Feb 2024 16:35:56 -0800 Subject: [PATCH 06/35] Modify --- src/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings.rs b/src/settings.rs index d6a303885e..484e0a40ad 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -19,7 +19,7 @@ impl Settings { }, }; - let chain = Self::setting_typed( + let chain = Self::setting( options .signet .then_some(Chain::Signet) From 409779a52e1047efc133fe38ef777db35c233a56 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 23 Feb 2024 19:13:05 -0800 Subject: [PATCH 07/35] Add settings subcommand --- src/arguments.rs | 36 ++++++- src/config.rs | 27 +---- src/index.rs | 13 ++- src/index/testing.rs | 10 +- src/index/updater.rs | 2 +- src/lib.rs | 5 +- src/options.rs | 80 +-------------- src/settings.rs | 202 ++++++++++++++++++++++--------------- src/subcommand.rs | 4 + src/subcommand/server.rs | 71 ++----------- src/subcommand/settings.rs | 5 + tests/command_builder.rs | 17 +++- tests/index.rs | 31 +----- tests/lib.rs | 45 +++++---- tests/settings.rs | 72 +++++++++++++ 15 files changed, 297 insertions(+), 323 deletions(-) create mode 100644 src/subcommand/settings.rs create mode 100644 tests/settings.rs diff --git a/src/arguments.rs b/src/arguments.rs index 791bd14cd0..93dbcf0b13 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -21,6 +21,40 @@ pub(crate) struct Arguments { impl Arguments { pub(crate) fn run(self) -> SubcommandResult { - self.subcommand.run(Settings::new(self.options)?) + let mut env: BTreeMap = BTreeMap::new(); + + for (var, value) in env::vars_os() { + let Some(var) = var.to_str() else { + continue; + }; + + let Some(key) = var.strip_prefix("ORD_") else { + continue; + }; + + env.insert( + key.into(), + value.into_string().map_err(|value| { + anyhow!( + "environment variable `{var}` not valid unicode: {}", + value.to_string_lossy() + ) + })?, + ); + } + + let config: Config = match &self.options.config { + Some(path) => serde_yaml::from_reader(File::open(path)?)?, + None => match &self.options.config_dir { + Some(dir) if dir.join("ord.yaml").exists() => { + serde_yaml::from_reader(File::open(dir.join("ord.yaml"))?)? + } + Some(_) | None => Default::default(), + }, + }; + + self + .subcommand + .run(Settings::new(self.options, env, config)?) } } diff --git a/src/config.rs b/src/config.rs index be346e50dc..6ef1e95ce2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,38 +6,13 @@ pub(crate) struct Config { pub(crate) bitcoin_rpc_pass: Option, pub(crate) bitcoin_rpc_user: Option, pub(crate) chain: Option, - pub(crate) hidden: HashSet, -} - -impl Config { - pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { - self.hidden.contains(&inscription_id) - } + pub(crate) hidden: Option>, } #[cfg(test)] mod tests { use super::*; - #[test] - fn inscriptions_can_be_hidden() { - let a = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" - .parse::() - .unwrap(); - - let b = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi1" - .parse::() - .unwrap(); - - let config = Config { - hidden: iter::once(a).collect(), - ..Default::default() - }; - - assert!(config.is_hidden(a)); - assert!(!config.is_hidden(b)); - } - #[test] fn example_config_file_is_valid() { let _: Config = serde_yaml::from_reader(File::open("ord.yaml").unwrap()).unwrap(); diff --git a/src/index.rs b/src/index.rs index c60357160f..584f46632d 100644 --- a/src/index.rs +++ b/src/index.rs @@ -231,7 +231,6 @@ impl Index { let client = settings.bitcoin_rpc_client(None)?; let path = settings - .options .index .clone() .unwrap_or_else(|| settings.data_dir().clone().join("index.redb")); @@ -243,7 +242,7 @@ impl Index { ); } - let db_cache_size = match settings.options.db_cache_size { + let db_cache_size = match settings.db_cache_size { Some(db_cache_size) => db_cache_size, None => { let mut sys = System::new(); @@ -350,7 +349,7 @@ impl Index { let mut outpoint_to_sat_ranges = tx.open_table(OUTPOINT_TO_SAT_RANGES)?; let mut statistics = tx.open_table(STATISTIC_TO_COUNT)?; - if settings.options.index_sats { + if settings.index_sats { outpoint_to_sat_ranges.insert(&OutPoint::null().store(), [].as_slice())?; } @@ -363,19 +362,19 @@ impl Index { Self::set_statistic( &mut statistics, Statistic::IndexSats, - u64::from(settings.options.index_sats || settings.options.index_spent_sats), + u64::from(settings.index_sats || settings.index_spent_sats), )?; Self::set_statistic( &mut statistics, Statistic::IndexSpentSats, - u64::from(settings.options.index_spent_sats), + u64::from(settings.index_spent_sats), )?; Self::set_statistic( &mut statistics, Statistic::IndexTransactions, - u64::from(settings.options.index_transactions), + u64::from(settings.index_transactions), )?; Self::set_statistic(&mut statistics, Statistic::Schema, SCHEMA_VERSION)?; @@ -413,7 +412,7 @@ impl Index { event_sender, first_inscription_height: settings.first_inscription_height(), genesis_block_coinbase_transaction, - height_limit: settings.options.height_limit, + height_limit: settings.height_limit, index_runes, index_sats, index_spent_sats, diff --git a/src/index/testing.rs b/src/index/testing.rs index 576a95e3aa..2e3ba71bf1 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -32,13 +32,9 @@ impl ContextBuilder { format!("--chain={}", self.chain).into(), ]; - let options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap(); - let settings = Settings { - chain: self.chain, - options, - ..Default::default() - }; - let index = Index::open_with_event_sender(&settings, self.event_sender)?; + let mut options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap(); + options.chain_argument = Some(self.chain); + let index = Index::open_with_event_sender(&options.settings().unwrap(), self.event_sender)?; index.update().unwrap(); Ok(Context { diff --git a/src/index/updater.rs b/src/index/updater.rs index af4a6ee6ab..f347fc942b 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -339,7 +339,7 @@ impl<'index> Updater<'_> { let mut outpoint_to_value = wtx.open_table(OUTPOINT_TO_VALUE)?; let index_inscriptions = self.height >= self.index.first_inscription_height - && !self.index.settings.options.no_index_inscriptions; + && !self.index.settings.no_index_inscriptions; if index_inscriptions { // Send all missing input outpoints to be fetched right away diff --git a/src/lib.rs b/src/lib.rs index 0032d6dcba..7fe3ebe0aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,10 @@ fn unbound_outpoint() -> OutPoint { pub fn parse_ord_server_args(args: &str) -> (Settings, crate::subcommand::server::Server) { match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { - Subcommand::Server(server) => (Settings::new(arguments.options).unwrap(), server), + Subcommand::Server(server) => ( + Settings::new(arguments.options, Default::default(), Default::default()).unwrap(), + server, + ), subcommand => panic!("unexpected subcommand: {subcommand:?}"), }, Err(err) => panic!("error parsing arguments: {err}"), diff --git a/src/options.rs b/src/options.rs index 8b992135e8..38b5f11daa 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,4 +1,4 @@ -use {super::*, bitcoincore_rpc::Auth}; +use super::*; #[derive(Clone, Default, Debug, Parser)] #[command(group( @@ -88,13 +88,13 @@ impl Options { #[cfg(test)] pub(crate) fn settings(self) -> Result { - Settings::new(self) + Settings::new(self, Default::default(), Default::default()) } } #[cfg(test)] mod tests { - use {super::*, std::path::Path, tempfile::TempDir}; + use {super::*, std::path::Path}; #[test] fn rpc_url_overrides_network() { @@ -450,80 +450,6 @@ mod tests { ); } - #[test] - fn config_is_loaded_from_config_option_path() { - let id = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" - .parse::() - .unwrap(); - - let tempdir = TempDir::new().unwrap(); - let path = tempdir.path().join("ord.yaml"); - fs::write(&path, format!("hidden:\n- \"{id}\"")).unwrap(); - - assert_eq!( - Arguments::try_parse_from(["ord", "--config", path.to_str().unwrap(), "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .hidden, - iter::once(id).collect(), - ); - } - - #[test] - fn config_with_rpc_user_pass() { - let tempdir = TempDir::new().unwrap(); - let path = tempdir.path().join("ord.yaml"); - fs::write( - &path, - "hidden:\nbitcoin_rpc_user: foo\nbitcoin_rpc_pass: bar", - ) - .unwrap(); - - assert_eq!( - Arguments::try_parse_from(["ord", "--config", path.to_str().unwrap(), "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .auth() - .unwrap(), - Auth::UserPass("foo".into(), "bar".into()), - ); - } - - #[test] - fn config_is_loaded_from_config_dir_option_path() { - let id = "8d363b28528b0cb86b5fd48615493fb175bdf132d2a3d20b4251bba3f130a5abi0" - .parse::() - .unwrap(); - - let tempdir = TempDir::new().unwrap(); - - fs::write( - tempdir.path().join("ord.yaml"), - format!("hidden:\n- \"{id}\""), - ) - .unwrap(); - - assert_eq!( - Arguments::try_parse_from([ - "ord", - "--config-dir", - tempdir.path().to_str().unwrap(), - "index", - "update" - ]) - .unwrap() - .options - .settings() - .unwrap() - .hidden, - iter::once(id).collect(), - ); - } - #[test] fn setting_db_cache_size() { let arguments = diff --git a/src/settings.rs b/src/settings.rs index 995820f71e..f35c2e0a6d 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,47 +1,44 @@ use {super::*, bitcoincore_rpc::Auth}; -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Serialize)] pub struct Settings { + #[serde(serialize_with = "serialize_auth")] pub(crate) auth: Option, + pub(crate) bitcoin_data_dir: Option, pub(crate) chain: Chain, + pub(crate) cookie_file: Option, + pub(crate) credentials: Option<(String, String)>, + pub(crate) data_dir: PathBuf, + pub(crate) db_cache_size: Option, + pub(crate) first_inscription_height: Option, + pub(crate) height_limit: Option, pub(crate) hidden: HashSet, - pub(crate) options: Options, + pub(crate) index: Option, + pub(crate) index_runes: bool, + pub(crate) index_sats: bool, + pub(crate) index_spent_sats: bool, + pub(crate) index_transactions: bool, + pub(crate) no_index_inscriptions: bool, + pub(crate) rpc_url: Option, } -impl Settings { - pub(crate) fn new(options: Options) -> Result { - let config: Config = match &options.config { - Some(path) => serde_yaml::from_reader(File::open(path)?)?, - None => match &options.config_dir { - Some(dir) if dir.join("ord.yaml").exists() => { - serde_yaml::from_reader(File::open(dir.join("ord.yaml"))?)? - } - Some(_) | None => Default::default(), - }, - }; - - let mut env: BTreeMap = BTreeMap::new(); - - for (var, value) in env::vars_os() { - let Some(var) = var.to_str() else { - continue; - }; - - let Some(key) = var.strip_prefix("ORD_") else { - continue; - }; - - env.insert( - key.into(), - value.into_string().map_err(|value| { - anyhow!( - "environment variable `{var}` not valid unicode: {}", - value.to_string_lossy() - ) - })?, - ); - } +fn serialize_auth(auth: &Option, serializer: S) -> Result +where + S: Serializer, +{ + match auth { + Some(Auth::UserPass(user, pass)) => serializer.serialize_str(&format!("{user}:{pass}")), + None => serializer.serialize_none(), + _ => unreachable!(), + } +} +impl Settings { + pub(crate) fn new( + options: Options, + env: BTreeMap, + config: Config, + ) -> Result { let chain = Self::setting( &env, options @@ -78,9 +75,22 @@ impl Settings { Ok(Self { auth, + bitcoin_data_dir: options.bitcoin_data_dir, chain, - hidden: config.hidden, - options, + cookie_file: options.cookie_file, + credentials: options.username.zip(options.password), + data_dir: options.data_dir, + db_cache_size: options.db_cache_size, + first_inscription_height: options.first_inscription_height, + height_limit: options.height_limit, + hidden: config.hidden.unwrap_or_default(), + index: options.index, + index_runes: options.index_runes, + index_sats: options.index_sats, + index_spent_sats: options.index_spent_sats, + index_transactions: options.index_transactions, + no_index_inscriptions: options.no_index_inscriptions, + rpc_url: options.rpc_url, }) } @@ -155,11 +165,11 @@ impl Settings { } pub(crate) fn cookie_file(&self) -> Result { - if let Some(cookie_file) = &self.options.cookie_file { + if let Some(cookie_file) = &self.cookie_file { return Ok(cookie_file.clone()); } - let path = if let Some(bitcoin_data_dir) = &self.options.bitcoin_data_dir { + let path = if let Some(bitcoin_data_dir) = &self.bitcoin_data_dir { bitcoin_data_dir.clone() } else if cfg!(target_os = "linux") { dirs::home_dir() @@ -178,14 +188,13 @@ impl Settings { pub(crate) fn credentials(&self) -> Option<(&str, &str)> { self - .options - .username - .as_deref() - .zip(self.options.password.as_deref()) + .credentials + .as_ref() + .map(|(username, password)| (username.as_ref(), password.as_ref())) } pub(crate) fn data_dir(&self) -> PathBuf { - self.chain().join_with_data_dir(&self.options.data_dir) + self.chain().join_with_data_dir(&self.data_dir) } pub(crate) fn first_inscription_height(&self) -> u32 { @@ -193,7 +202,6 @@ impl Settings { 0 } else { self - .options .first_inscription_height .unwrap_or_else(|| self.chain().first_inscription_height()) } @@ -208,7 +216,7 @@ impl Settings { } pub(crate) fn index_runes(&self) -> bool { - self.options.index_runes && self.chain() != Chain::Mainnet + self.index_runes && self.chain() != Chain::Mainnet } pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { @@ -217,7 +225,6 @@ impl Settings { pub(crate) fn rpc_url(&self, wallet_name: Option) -> String { let base_url = self - .options .rpc_url .clone() .unwrap_or(format!("127.0.0.1:{}", self.chain().default_rpc_port())); @@ -279,21 +286,25 @@ mod tests { use super::*; fn settings(args: &[&str]) -> Settings { - let options = Options::try_parse_from(args).unwrap(); - - Settings { - options, - ..Default::default() - } + Settings::new( + Options::try_parse_from(args).unwrap(), + Default::default(), + Default::default(), + ) + .unwrap() } #[test] fn auth_missing_rpc_pass_is_an_error() { assert_eq!( - Settings::new(Options { - bitcoin_rpc_user: Some("foo".into()), - ..Default::default() - },) + Settings::new( + Options { + bitcoin_rpc_user: Some("foo".into()), + ..Default::default() + }, + Default::default(), + Default::default(), + ) .unwrap_err() .to_string(), "no bitcoind rpc password specified" @@ -303,10 +314,14 @@ mod tests { #[test] fn auth_missing_rpc_user_is_an_error() { assert_eq!( - Settings::new(Options { - bitcoin_rpc_pass: Some("foo".into()), - ..Default::default() - },) + Settings::new( + Options { + bitcoin_rpc_pass: Some("foo".into()), + ..Default::default() + }, + Default::default(), + Default::default(), + ) .unwrap_err() .to_string(), "no bitcoind rpc user specified" @@ -316,11 +331,15 @@ mod tests { #[test] fn auth_with_user_and_pass() { assert_eq!( - Settings::new(Options { - bitcoin_rpc_user: Some("foo".into()), - bitcoin_rpc_pass: Some("bar".into()), - ..Default::default() - }) + Settings::new( + Options { + bitcoin_rpc_user: Some("foo".into()), + bitcoin_rpc_pass: Some("bar".into()), + ..Default::default() + }, + Default::default(), + Default::default(), + ) .unwrap() .auth() .unwrap(), @@ -330,13 +349,12 @@ mod tests { #[test] fn auth_with_cookie_file() { - let settings = Settings { - options: Options { - cookie_file: Some("/var/lib/Bitcoin/.cookie".into()), - ..Default::default() - }, + let settings = Options { + cookie_file: Some("/var/lib/Bitcoin/.cookie".into()), ..Default::default() - }; + } + .settings() + .unwrap(); assert_eq!( settings.auth().unwrap(), Auth::CookieFile("/var/lib/Bitcoin/.cookie".into()) @@ -346,13 +364,12 @@ mod tests { #[test] fn cookie_file_does_not_exist_error() { assert_eq!( - Settings { - options: Options { - cookie_file: Some("/foo/bar/baz/qux/.cookie".into()), - ..Default::default() - }, + Options { + cookie_file: Some("/foo/bar/baz/qux/.cookie".into()), ..Default::default() } + .settings() + .unwrap() .bitcoin_rpc_client(None) .map(|_| "") .unwrap_err() @@ -431,21 +448,40 @@ mod tests { #[test] fn setting_opt() { - // todo: test env var override - assert_eq!( Settings::setting_opt(&Default::default(), None, None, None).unwrap(), None ); assert_eq!( - Settings::setting_opt(&Default::default(), None, None, Some("foo")).unwrap(), - Some("foo".into()), + Settings::setting_opt(&Default::default(), None, None, Some("config")).unwrap(), + Some("config".into()), ); assert_eq!( - Settings::setting_opt(&Default::default(), Some("bar"), None, Some("foo")).unwrap(), - Some("bar".into()), + Settings::setting_opt( + &vec![("env_key".into(), "env_value".into())] + .into_iter() + .collect(), + None, + Some("env_key"), + Some("config") + ) + .unwrap(), + Some("env_value".into()), + ); + + assert_eq!( + Settings::setting_opt( + &vec![("env_key".into(), "env_value".into())] + .into_iter() + .collect(), + Some("option"), + Some("env_key"), + Some("config") + ) + .unwrap(), + Some("option".into()), ); } } diff --git a/src/subcommand.rs b/src/subcommand.rs index a48fa5dca9..2da87735db 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -10,6 +10,7 @@ pub mod list; pub mod parse; pub mod runes; pub(crate) mod server; +mod settings; pub mod subsidy; pub mod supply; pub mod teleburn; @@ -38,6 +39,8 @@ pub(crate) enum Subcommand { Runes, #[command(about = "Run the explorer server")] Server(server::Server), + #[command(about = "Display settings")] + Settings, #[command(about = "Display information about a block's subsidy")] Subsidy(subsidy::Subsidy), #[command(about = "Display Bitcoin supply information")] @@ -68,6 +71,7 @@ impl Subcommand { LISTENERS.lock().unwrap().push(handle.clone()); server.run(settings, index, handle) } + Self::Settings => settings::run(settings), Self::Subsidy(subsidy) => subsidy.run(), Self::Supply => supply::run(), Self::Teleburn(teleburn) => teleburn.run(), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 10130f8abe..91be9c4e1a 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1755,7 +1755,7 @@ mod tests { } fn new_with_args(ord_args: &[&str], server_args: &[&str]) -> Self { - Self::new_server(test_bitcoincore_rpc::spawn(), None, ord_args, server_args) + Self::new_server(test_bitcoincore_rpc::spawn(), ord_args, server_args) } fn new_with_regtest() -> Self { @@ -1763,7 +1763,6 @@ mod tests { test_bitcoincore_rpc::builder() .network(bitcoin::network::constants::Network::Regtest) .build(), - None, &["--chain", "regtest"], &[], ) @@ -1774,7 +1773,6 @@ mod tests { test_bitcoincore_rpc::builder() .network(bitcoin::network::constants::Network::Regtest) .build(), - None, &["--chain", "regtest"], &[], ) @@ -1785,7 +1783,6 @@ mod tests { test_bitcoincore_rpc::builder() .network(bitcoin::Network::Regtest) .build(), - None, &["--chain", "regtest", "--index-sats"], &[], ) @@ -1796,22 +1793,13 @@ mod tests { test_bitcoincore_rpc::builder() .network(bitcoin::Network::Regtest) .build(), - None, &["--chain", "regtest", "--index-runes"], &[], ) } - fn new_with_bitcoin_rpc_server_and_config( - bitcoin_rpc_server: test_bitcoincore_rpc::Handle, - config: String, - ) -> Self { - Self::new_server(bitcoin_rpc_server, Some(config), &[], &[]) - } - fn new_server( bitcoin_rpc_server: test_bitcoincore_rpc::Handle, - config: Option, ord_args: &[&str], server_args: &[&str], ) -> Self { @@ -1829,17 +1817,8 @@ mod tests { let url = Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(); - let config_args = match config { - Some(config) => { - let config_path = tempdir.path().join("ord.yaml"); - fs::write(&config_path, config).unwrap(); - format!("--config {}", config_path.display()) - } - None => "".to_string(), - }; - let (settings, server) = parse_server_args(&format!( - "ord --rpc-url {} --cookie-file {} --data-dir {} {config_args} {} server --http-port {} --address 127.0.0.1 {}", + "ord --rpc-url {} --cookie-file {} --data-dir {} {} server --http-port {} --address 127.0.0.1 {}", bitcoin_rpc_server.url(), cookiefile.to_str().unwrap(), tempdir.path().to_str().unwrap(), @@ -2121,10 +2100,7 @@ mod tests { fn acme_cache_defaults_to_data_dir() { let arguments = Arguments::try_parse_from(["ord", "--data-dir", "foo", "server"]).unwrap(); - let settings = Settings { - options: arguments.options, - ..Default::default() - }; + let settings = arguments.options.settings().unwrap(); let acme_cache = Server::acme_cache(None, &settings).display().to_string(); assert!( @@ -2142,10 +2118,9 @@ mod tests { let arguments = Arguments::try_parse_from(["ord", "--data-dir", "foo", "server", "--acme-cache", "bar"]) .unwrap(); - let settings = Settings { - options: arguments.options, - ..Default::default() - }; + + let settings = arguments.options.settings().unwrap(); + let acme_cache = Server::acme_cache(Some(&"bar".into()), &settings) .display() .to_string(); @@ -4328,40 +4303,6 @@ next ); } - #[test] - fn inscriptions_can_be_hidden_with_config() { - let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); - bitcoin_rpc_server.mine_blocks(1); - let txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { - inputs: &[( - 1, - 0, - 0, - inscription("text/plain;charset=utf-8", "hello").to_witness(), - )], - ..Default::default() - }); - let inscription = InscriptionId { txid, index: 0 }; - bitcoin_rpc_server.mine_blocks(1); - - let server = TestServer::new_with_bitcoin_rpc_server_and_config( - bitcoin_rpc_server, - format!("\"hidden\":\n - {inscription}"), - ); - - server.assert_response( - format!("/preview/{inscription}"), - StatusCode::OK, - &fs::read_to_string("templates/preview-unknown.html").unwrap(), - ); - - server.assert_response( - format!("/content/{inscription}"), - StatusCode::OK, - &fs::read_to_string("templates/preview-unknown.html").unwrap(), - ); - } - #[test] fn inscription_links_to_parent() { let server = TestServer::new_with_regtest_with_json_api(); diff --git a/src/subcommand/settings.rs b/src/subcommand/settings.rs new file mode 100644 index 0000000000..bbdce8d26a --- /dev/null +++ b/src/subcommand/settings.rs @@ -0,0 +1,5 @@ +use super::*; + +pub(crate) fn run(settings: Settings) -> SubcommandResult { + Ok(Some(Box::new(settings))) +} diff --git a/tests/command_builder.rs b/tests/command_builder.rs index 2ef347d448..21acb44499 100644 --- a/tests/command_builder.rs +++ b/tests/command_builder.rs @@ -30,12 +30,13 @@ impl ToArgs for Vec { pub(crate) struct CommandBuilder { args: Vec, + bitcoin_rpc_server_cookie_file: Option, + bitcoin_rpc_server_url: Option, + env: BTreeMap, expected_exit_code: i32, expected_stderr: Expected, expected_stdout: Expected, ord_rpc_server_url: Option, - bitcoin_rpc_server_cookie_file: Option, - bitcoin_rpc_server_url: Option, stdin: Vec, tempdir: Arc, } @@ -44,6 +45,7 @@ impl CommandBuilder { pub(crate) fn new(args: impl ToArgs) -> Self { Self { args: args.to_args(), + env: BTreeMap::new(), expected_exit_code: 0, expected_stderr: Expected::String(String::new()), expected_stdout: Expected::String(String::new()), @@ -55,6 +57,11 @@ impl CommandBuilder { } } + pub(crate) fn env(mut self, key: &str, value: &str) -> Self { + self.env.insert(key.into(), value.into()); + self + } + pub(crate) fn write(self, path: impl AsRef, contents: impl AsRef<[u8]>) -> Self { fs::write(self.tempdir.path().join(path), contents).unwrap(); self @@ -143,6 +150,10 @@ impl CommandBuilder { } } + for (key, value) in &self.env { + command.env(key, value); + } + command .env("ORD_INTEGRATION_TEST", "1") .stdin(Stdio::piped()) @@ -156,7 +167,7 @@ impl CommandBuilder { command } - #[track_caller] + // #[track_caller] fn run(self) -> (TempDir, String) { let mut command = self.command(); let child = command.spawn().unwrap(); diff --git a/tests/index.rs b/tests/index.rs index 5d44449cac..f25e6cd43e 100644 --- a/tests/index.rs +++ b/tests/index.rs @@ -1,4 +1,4 @@ -use {super::*, crate::command_builder::ToArgs}; +use super::*; #[test] fn run_is_an_alias_for_update() { @@ -52,35 +52,6 @@ fn re_opening_database_does_not_trigger_schema_check() { .run_and_extract_stdout(); } -#[test] -fn index_runs_with_rpc_user_and_pass_as_env_vars() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); - - let tempdir = TempDir::new().unwrap(); - - let ord = Command::new(executable_path("ord")) - .args( - format!( - "--rpc-url {} --bitcoin-data-dir {} --data-dir {} index update", - rpc_server.url(), - tempdir.path().display(), - tempdir.path().display() - ) - .to_args(), - ) - .env("ORD_BITCOIN_RPC_PASS", "bar") - .env("ORD_BITCOIN_RPC_USER", "foo") - .env("ORD_INTEGRATION_TEST", "1") - .current_dir(&tempdir) - .spawn() - .unwrap(); - - rpc_server.mine_blocks(1); - - assert_eq!(ord.wait_with_output().unwrap().status.code(), Some(0)); -} - #[test] fn export_inscription_number_to_id_tsv() { let bitcoin_rpc_server = test_bitcoincore_rpc::spawn(); diff --git a/tests/lib.rs b/tests/lib.rs index fc0933d4eb..024b34b01c 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -47,6 +47,29 @@ macro_rules! assert_regex_match { }; } +mod command_builder; +mod expected; +mod test_server; + +mod balances; +mod decode; +mod epochs; +mod etch; +mod find; +mod index; +mod info; +mod json_api; +mod list; +mod parse; +mod runes; +mod server; +mod settings; +mod subsidy; +mod supply; +mod traits; +mod version; +mod wallet; + const RUNE: u128 = 99246114928149462; type Inscribe = ord::wallet::inscribe::Output; @@ -131,25 +154,3 @@ fn runes(rpc_server: &test_bitcoincore_rpc::Handle) -> BTreeMap .run_and_deserialize_output::() .runes } - -mod command_builder; -mod expected; -mod test_server; - -mod balances; -mod decode; -mod epochs; -mod etch; -mod find; -mod index; -mod info; -mod json_api; -mod list; -mod parse; -mod runes; -mod server; -mod subsidy; -mod supply; -mod traits; -mod version; -mod wallet; diff --git a/tests/settings.rs b/tests/settings.rs new file mode 100644 index 0000000000..2d42fc2b64 --- /dev/null +++ b/tests/settings.rs @@ -0,0 +1,72 @@ +use super::*; + +#[test] +fn config_is_loaded_from_config_option() { + CommandBuilder::new("settings") + .stdout_regex( + r#".* + "chain": "mainnet", +.*"#, + ) + .run_and_extract_stdout(); + + let tempdir = TempDir::new().unwrap(); + + let config = tempdir.path().join("ord.yaml"); + + fs::write(&config, "chain: regtest").unwrap(); + + CommandBuilder::new(format!("--config {} settings", config.to_str().unwrap())) + .stdout_regex( + r#".* + "chain": "regtest", +.*"#, + ) + .run_and_extract_stdout(); +} + +#[test] +fn config_is_loaded_from_config_dir() { + CommandBuilder::new("settings") + .stdout_regex( + r#".* + "chain": "mainnet", +.*"#, + ) + .run_and_extract_stdout(); + + let tempdir = TempDir::new().unwrap(); + + fs::write(tempdir.path().join("ord.yaml"), "chain: regtest").unwrap(); + + CommandBuilder::new(format!( + "--config-dir {} settings", + tempdir.path().to_str().unwrap() + )) + .stdout_regex( + r#".* + "chain": "regtest", +.*"#, + ) + .run_and_extract_stdout(); +} + +#[test] +fn env_is_loaded() { + CommandBuilder::new("settings") + .stdout_regex( + r#".* + "chain": "mainnet", +.*"#, + ) + .run_and_extract_stdout(); + + CommandBuilder::new("settings") + .env("ORD_CHAIN", "regtest") + .stdout_regex( + r#".* + "chain": "regtest", +.*"#, + ) + .run_and_extract_stdout(); +} From 0500cc5bbc3c319363716c65d407a597023a2254 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 23 Feb 2024 19:25:04 -0800 Subject: [PATCH 08/35] Move tests --- src/options.rs | 399 -------------------------------------- src/settings.rs | 402 +++++++++++++++++++++++++++++++++++++++ tests/command_builder.rs | 2 +- 3 files changed, 403 insertions(+), 400 deletions(-) diff --git a/src/options.rs b/src/options.rs index 38b5f11daa..b5bec0590b 100644 --- a/src/options.rs +++ b/src/options.rs @@ -91,402 +91,3 @@ impl Options { Settings::new(self, Default::default(), Default::default()) } } - -#[cfg(test)] -mod tests { - use {super::*, std::path::Path}; - - #[test] - fn rpc_url_overrides_network() { - assert_eq!( - Arguments::try_parse_from([ - "ord", - "--rpc-url=127.0.0.1:1234", - "--chain=signet", - "index", - "update" - ]) - .unwrap() - .options - .settings() - .unwrap() - .rpc_url(None), - "127.0.0.1:1234/" - ); - } - - #[test] - fn cookie_file_overrides_network() { - assert_eq!( - Arguments::try_parse_from([ - "ord", - "--cookie-file=/foo/bar", - "--chain=signet", - "index", - "update" - ]) - .unwrap() - .options - .settings() - .unwrap() - .cookie_file() - .unwrap(), - Path::new("/foo/bar") - ); - } - - #[test] - fn use_default_network() { - let settings = Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap(); - - assert_eq!(settings.rpc_url(None), "127.0.0.1:8332/"); - - assert!(settings.cookie_file().unwrap().ends_with(".cookie")); - } - - #[test] - fn uses_network_defaults() { - let settings = Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap(); - - assert_eq!(settings.rpc_url(None), "127.0.0.1:38332/"); - - assert!(settings - .cookie_file() - .unwrap() - .display() - .to_string() - .ends_with(if cfg!(windows) { - r"\signet\.cookie" - } else { - "/signet/.cookie" - })); - } - - #[test] - fn mainnet_cookie_file_path() { - let cookie_file = Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .cookie_file() - .unwrap() - .display() - .to_string(); - - assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { - "/.bitcoin/.cookie" - } else if cfg!(windows) { - r"\Bitcoin\.cookie" - } else { - "/Bitcoin/.cookie" - })) - } - - #[test] - fn othernet_cookie_file_path() { - let arguments = - Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]).unwrap(); - - let cookie_file = arguments - .options - .settings() - .unwrap() - .cookie_file() - .unwrap() - .display() - .to_string(); - - assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { - "/.bitcoin/signet/.cookie" - } else if cfg!(windows) { - r"\Bitcoin\signet\.cookie" - } else { - "/Bitcoin/signet/.cookie" - })); - } - - #[test] - fn cookie_file_defaults_to_bitcoin_data_dir() { - let arguments = Arguments::try_parse_from([ - "ord", - "--bitcoin-data-dir=foo", - "--chain=signet", - "index", - "update", - ]) - .unwrap(); - - let cookie_file = arguments - .options - .settings() - .unwrap() - .cookie_file() - .unwrap() - .display() - .to_string(); - - assert!(cookie_file.ends_with(if cfg!(windows) { - r"foo\signet\.cookie" - } else { - "foo/signet/.cookie" - })); - } - - #[test] - fn mainnet_data_dir() { - let data_dir = Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .data_dir() - .display() - .to_string(); - assert!( - data_dir.ends_with(if cfg!(windows) { r"\ord" } else { "/ord" }), - "{data_dir}" - ); - } - - #[test] - fn othernet_data_dir() { - let data_dir = Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .data_dir() - .display() - .to_string(); - assert!( - data_dir.ends_with(if cfg!(windows) { - r"\ord\signet" - } else { - "/ord/signet" - }), - "{data_dir}" - ); - } - - #[test] - fn network_is_joined_with_data_dir() { - let data_dir = Arguments::try_parse_from([ - "ord", - "--chain=signet", - "--data-dir", - "foo", - "index", - "update", - ]) - .unwrap() - .options - .settings() - .unwrap() - .data_dir() - .display() - .to_string(); - assert!( - data_dir.ends_with(if cfg!(windows) { - r"foo\signet" - } else { - "foo/signet" - }), - "{data_dir}" - ); - } - - #[test] - fn network_accepts_aliases() { - fn check_network_alias(alias: &str, suffix: &str) { - let data_dir = Arguments::try_parse_from(["ord", "--chain", alias, "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .data_dir() - .display() - .to_string(); - - assert!(data_dir.ends_with(suffix), "{data_dir}"); - } - - check_network_alias("main", "ord"); - check_network_alias("mainnet", "ord"); - check_network_alias( - "regtest", - if cfg!(windows) { - r"ord\regtest" - } else { - "ord/regtest" - }, - ); - check_network_alias( - "signet", - if cfg!(windows) { - r"ord\signet" - } else { - "ord/signet" - }, - ); - check_network_alias( - "test", - if cfg!(windows) { - r"ord\testnet3" - } else { - "ord/testnet3" - }, - ); - check_network_alias( - "testnet", - if cfg!(windows) { - r"ord\testnet3" - } else { - "ord/testnet3" - }, - ); - } - - #[test] - fn chain_flags() { - Arguments::try_parse_from(["ord", "--signet", "--chain", "signet", "index", "update"]) - .unwrap_err(); - assert_eq!( - Arguments::try_parse_from(["ord", "--signet", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .chain(), - Chain::Signet - ); - assert_eq!( - Arguments::try_parse_from(["ord", "-s", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .chain(), - Chain::Signet - ); - - Arguments::try_parse_from(["ord", "--regtest", "--chain", "signet", "index", "update"]) - .unwrap_err(); - assert_eq!( - Arguments::try_parse_from(["ord", "--regtest", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .chain(), - Chain::Regtest - ); - assert_eq!( - Arguments::try_parse_from(["ord", "-r", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .chain(), - Chain::Regtest - ); - - Arguments::try_parse_from(["ord", "--testnet", "--chain", "signet", "index", "update"]) - .unwrap_err(); - assert_eq!( - Arguments::try_parse_from(["ord", "--testnet", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .chain(), - Chain::Testnet - ); - assert_eq!( - Arguments::try_parse_from(["ord", "-t", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .chain(), - Chain::Testnet - ); - } - - fn parse_wallet_args(args: &str) -> (Options, subcommand::wallet::WalletCommand) { - match Arguments::try_parse_from(args.split_whitespace()) { - Ok(arguments) => match arguments.subcommand { - Subcommand::Wallet(wallet) => (arguments.options, wallet), - subcommand => panic!("unexpected subcommand: {subcommand:?}"), - }, - Err(err) => panic!("error parsing arguments: {err}"), - } - } - - #[test] - fn wallet_flag_overrides_default_name() { - let (_, wallet) = parse_wallet_args("ord wallet create"); - assert_eq!(wallet.name, "ord"); - - let (_, wallet) = parse_wallet_args("ord wallet --name foo create"); - assert_eq!(wallet.name, "foo") - } - - #[test] - fn uses_wallet_rpc() { - let (options, _) = parse_wallet_args("ord wallet --name foo balance"); - - assert_eq!( - options.settings().unwrap().rpc_url(Some("foo".into())), - "127.0.0.1:8332/wallet/foo" - ); - } - - #[test] - fn setting_db_cache_size() { - let arguments = - Arguments::try_parse_from(["ord", "--db-cache-size", "16000000000", "index", "update"]) - .unwrap(); - assert_eq!(arguments.options.db_cache_size, Some(16000000000)); - } - - #[test] - fn index_runes_only_returns_true_if_index_runes_flag_is_passed_and_not_on_mainnnet() { - assert!(Arguments::try_parse_from([ - "ord", - "--chain=signet", - "--index-runes", - "index", - "update" - ]) - .unwrap() - .options - .settings() - .unwrap() - .index_runes()); - - assert!( - !Arguments::try_parse_from(["ord", "--index-runes", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .index_runes() - ); - - assert!(!Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .index_runes()); - } -} diff --git a/src/settings.rs b/src/settings.rs index f35c2e0a6d..96774c8142 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,5 +1,13 @@ use {super::*, bitcoincore_rpc::Auth}; +// todo: +// - inscriptions can be hidden with config +// - move optoins tests into settings +// +// - setting tests: +// - chain: flags, then arg, then env var, then config, then mainnet +// - bitcoin rpc username and pass + #[derive(Default, Debug, Clone, Serialize)] pub struct Settings { #[serde(serialize_with = "serialize_auth")] @@ -484,4 +492,398 @@ mod tests { Some("option".into()), ); } + + #[test] + fn rpc_url_overrides_network() { + assert_eq!( + Arguments::try_parse_from([ + "ord", + "--rpc-url=127.0.0.1:1234", + "--chain=signet", + "index", + "update" + ]) + .unwrap() + .options + .settings() + .unwrap() + .rpc_url(None), + "127.0.0.1:1234/" + ); + } + + #[test] + fn cookie_file_overrides_network() { + assert_eq!( + Arguments::try_parse_from([ + "ord", + "--cookie-file=/foo/bar", + "--chain=signet", + "index", + "update" + ]) + .unwrap() + .options + .settings() + .unwrap() + .cookie_file() + .unwrap(), + Path::new("/foo/bar") + ); + } + + #[test] + fn use_default_network() { + let settings = Arguments::try_parse_from(["ord", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap(); + + assert_eq!(settings.rpc_url(None), "127.0.0.1:8332/"); + + assert!(settings.cookie_file().unwrap().ends_with(".cookie")); + } + + #[test] + fn uses_network_defaults() { + let settings = Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap(); + + assert_eq!(settings.rpc_url(None), "127.0.0.1:38332/"); + + assert!(settings + .cookie_file() + .unwrap() + .display() + .to_string() + .ends_with(if cfg!(windows) { + r"\signet\.cookie" + } else { + "/signet/.cookie" + })); + } + + #[test] + fn mainnet_cookie_file_path() { + let cookie_file = Arguments::try_parse_from(["ord", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .cookie_file() + .unwrap() + .display() + .to_string(); + + assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { + "/.bitcoin/.cookie" + } else if cfg!(windows) { + r"\Bitcoin\.cookie" + } else { + "/Bitcoin/.cookie" + })) + } + + #[test] + fn othernet_cookie_file_path() { + let arguments = + Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]).unwrap(); + + let cookie_file = arguments + .options + .settings() + .unwrap() + .cookie_file() + .unwrap() + .display() + .to_string(); + + assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { + "/.bitcoin/signet/.cookie" + } else if cfg!(windows) { + r"\Bitcoin\signet\.cookie" + } else { + "/Bitcoin/signet/.cookie" + })); + } + + #[test] + fn cookie_file_defaults_to_bitcoin_data_dir() { + let arguments = Arguments::try_parse_from([ + "ord", + "--bitcoin-data-dir=foo", + "--chain=signet", + "index", + "update", + ]) + .unwrap(); + + let cookie_file = arguments + .options + .settings() + .unwrap() + .cookie_file() + .unwrap() + .display() + .to_string(); + + assert!(cookie_file.ends_with(if cfg!(windows) { + r"foo\signet\.cookie" + } else { + "foo/signet/.cookie" + })); + } + + #[test] + fn mainnet_data_dir() { + let data_dir = Arguments::try_parse_from(["ord", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .data_dir() + .display() + .to_string(); + assert!( + data_dir.ends_with(if cfg!(windows) { r"\ord" } else { "/ord" }), + "{data_dir}" + ); + } + + #[test] + fn othernet_data_dir() { + let data_dir = Arguments::try_parse_from(["ord", "--chain=signet", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .data_dir() + .display() + .to_string(); + assert!( + data_dir.ends_with(if cfg!(windows) { + r"\ord\signet" + } else { + "/ord/signet" + }), + "{data_dir}" + ); + } + + #[test] + fn network_is_joined_with_data_dir() { + let data_dir = Arguments::try_parse_from([ + "ord", + "--chain=signet", + "--data-dir", + "foo", + "index", + "update", + ]) + .unwrap() + .options + .settings() + .unwrap() + .data_dir() + .display() + .to_string(); + assert!( + data_dir.ends_with(if cfg!(windows) { + r"foo\signet" + } else { + "foo/signet" + }), + "{data_dir}" + ); + } + + #[test] + fn network_accepts_aliases() { + fn check_network_alias(alias: &str, suffix: &str) { + let data_dir = Arguments::try_parse_from(["ord", "--chain", alias, "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .data_dir() + .display() + .to_string(); + + assert!(data_dir.ends_with(suffix), "{data_dir}"); + } + + check_network_alias("main", "ord"); + check_network_alias("mainnet", "ord"); + check_network_alias( + "regtest", + if cfg!(windows) { + r"ord\regtest" + } else { + "ord/regtest" + }, + ); + check_network_alias( + "signet", + if cfg!(windows) { + r"ord\signet" + } else { + "ord/signet" + }, + ); + check_network_alias( + "test", + if cfg!(windows) { + r"ord\testnet3" + } else { + "ord/testnet3" + }, + ); + check_network_alias( + "testnet", + if cfg!(windows) { + r"ord\testnet3" + } else { + "ord/testnet3" + }, + ); + } + + #[test] + fn chain_flags() { + Arguments::try_parse_from(["ord", "--signet", "--chain", "signet", "index", "update"]) + .unwrap_err(); + assert_eq!( + Arguments::try_parse_from(["ord", "--signet", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .chain(), + Chain::Signet + ); + assert_eq!( + Arguments::try_parse_from(["ord", "-s", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .chain(), + Chain::Signet + ); + + Arguments::try_parse_from(["ord", "--regtest", "--chain", "signet", "index", "update"]) + .unwrap_err(); + assert_eq!( + Arguments::try_parse_from(["ord", "--regtest", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .chain(), + Chain::Regtest + ); + assert_eq!( + Arguments::try_parse_from(["ord", "-r", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .chain(), + Chain::Regtest + ); + + Arguments::try_parse_from(["ord", "--testnet", "--chain", "signet", "index", "update"]) + .unwrap_err(); + assert_eq!( + Arguments::try_parse_from(["ord", "--testnet", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .chain(), + Chain::Testnet + ); + assert_eq!( + Arguments::try_parse_from(["ord", "-t", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .chain(), + Chain::Testnet + ); + } + + fn parse_wallet_args(args: &str) -> (Options, subcommand::wallet::WalletCommand) { + match Arguments::try_parse_from(args.split_whitespace()) { + Ok(arguments) => match arguments.subcommand { + Subcommand::Wallet(wallet) => (arguments.options, wallet), + subcommand => panic!("unexpected subcommand: {subcommand:?}"), + }, + Err(err) => panic!("error parsing arguments: {err}"), + } + } + + #[test] + fn wallet_flag_overrides_default_name() { + let (_, wallet) = parse_wallet_args("ord wallet create"); + assert_eq!(wallet.name, "ord"); + + let (_, wallet) = parse_wallet_args("ord wallet --name foo create"); + assert_eq!(wallet.name, "foo") + } + + #[test] + fn uses_wallet_rpc() { + let (options, _) = parse_wallet_args("ord wallet --name foo balance"); + + assert_eq!( + options.settings().unwrap().rpc_url(Some("foo".into())), + "127.0.0.1:8332/wallet/foo" + ); + } + + #[test] + fn setting_db_cache_size() { + let arguments = + Arguments::try_parse_from(["ord", "--db-cache-size", "16000000000", "index", "update"]) + .unwrap(); + assert_eq!(arguments.options.db_cache_size, Some(16000000000)); + } + + #[test] + fn index_runes_only_returns_true_if_index_runes_flag_is_passed_and_not_on_mainnnet() { + assert!(Arguments::try_parse_from([ + "ord", + "--chain=signet", + "--index-runes", + "index", + "update" + ]) + .unwrap() + .options + .settings() + .unwrap() + .index_runes()); + + assert!( + !Arguments::try_parse_from(["ord", "--index-runes", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .index_runes() + ); + + assert!(!Arguments::try_parse_from(["ord", "index", "update"]) + .unwrap() + .options + .settings() + .unwrap() + .index_runes()); + } } diff --git a/tests/command_builder.rs b/tests/command_builder.rs index 21acb44499..5823cf95cc 100644 --- a/tests/command_builder.rs +++ b/tests/command_builder.rs @@ -167,7 +167,7 @@ impl CommandBuilder { command } - // #[track_caller] + #[track_caller] fn run(self) -> (TempDir, String) { let mut command = self.command(); let child = command.spawn().unwrap(); From 1452f48bf543fe39b1d07b59a26bae892a44cb99 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 23 Feb 2024 19:43:07 -0800 Subject: [PATCH 09/35] Test rpc user pass setting --- src/settings.rs | 140 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 134 insertions(+), 6 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 96774c8142..63127f5409 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,11 +2,6 @@ use {super::*, bitcoincore_rpc::Auth}; // todo: // - inscriptions can be hidden with config -// - move optoins tests into settings -// -// - setting tests: -// - chain: flags, then arg, then env var, then config, then mainnet -// - bitcoin rpc username and pass #[derive(Default, Debug, Clone, Serialize)] pub struct Settings { @@ -68,12 +63,14 @@ impl Settings { )?; let rpc_pass = Self::setting_opt( - &Default::default(), + &env, options.bitcoin_rpc_pass.as_deref(), Some("BITCOIN_RPC_PASS"), config.bitcoin_rpc_pass.as_deref(), )?; + dbg!(&rpc_user, &rpc_pass); + let auth = match (rpc_user, rpc_pass) { (Some(rpc_user), Some(rpc_pass)) => Some(Auth::UserPass(rpc_user, rpc_pass)), (None, Some(_rpc_pass)) => bail!("no bitcoind rpc user specified"), @@ -886,4 +883,135 @@ mod tests { .unwrap() .index_runes()); } + + #[test] + fn chain_setting() { + assert_eq!( + Settings::new( + Options { + regtest: true, + ..Default::default() + }, + vec![("CHAIN".into(), "signet".into())] + .into_iter() + .collect(), + Config { + chain: Some(Chain::Testnet), + ..Default::default() + } + ) + .unwrap() + .chain(), + Chain::Regtest, + ); + + assert_eq!( + Settings::new( + Default::default(), + vec![("CHAIN".into(), "signet".into())] + .into_iter() + .collect(), + Config { + chain: Some(Chain::Testnet), + ..Default::default() + } + ) + .unwrap() + .chain(), + Chain::Signet, + ); + + assert_eq!( + Settings::new( + Default::default(), + Default::default(), + Config { + chain: Some(Chain::Testnet), + ..Default::default() + } + ) + .unwrap() + .chain(), + Chain::Testnet, + ); + + assert_eq!( + Settings::new(Default::default(), Default::default(), Default::default()) + .unwrap() + .chain(), + Chain::Mainnet, + ); + } + + #[test] + fn bitcoin_rpc_and_pass_setting() { + assert_eq!( + Settings::new( + Options { + bitcoin_rpc_user: Some("option_user".into()), + bitcoin_rpc_pass: Some("option_pass".into()), + ..Default::default() + }, + vec![ + ("BITCOIN_RPC_USER".into(), "env_user".into()), + ("BITCOIN_RPC_PASS".into(), "env_pass".into()), + ] + .into_iter() + .collect(), + Config { + bitcoin_rpc_user: Some("config_user".into()), + bitcoin_rpc_pass: Some("config_pass".into()), + ..Default::default() + } + ) + .unwrap() + .auth() + .unwrap(), + Auth::UserPass("option_user".into(), "option_pass".into()), + ); + + assert_eq!( + Settings::new( + Default::default(), + vec![ + ("BITCOIN_RPC_USER".into(), "env_user".into()), + ("BITCOIN_RPC_PASS".into(), "env_pass".into()), + ] + .into_iter() + .collect(), + Config { + bitcoin_rpc_user: Some("config_user".into()), + bitcoin_rpc_pass: Some("config_pass".into()), + ..Default::default() + } + ) + .unwrap() + .auth() + .unwrap(), + Auth::UserPass("env_user".into(), "env_pass".into()), + ); + + assert_eq!( + Settings::new( + Default::default(), + Default::default(), + Config { + bitcoin_rpc_user: Some("config_user".into()), + bitcoin_rpc_pass: Some("config_pass".into()), + ..Default::default() + } + ) + .unwrap() + .auth() + .unwrap(), + Auth::UserPass("config_user".into(), "config_pass".into()), + ); + + assert_eq!( + Settings::new(Default::default(), Default::default(), Default::default()) + .unwrap() + .auth, + None, + ); + } } From 630cda0ace7dc2ef9a08eb733b0bf544ee05a388 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 23 Feb 2024 23:51:26 -0800 Subject: [PATCH 10/35] Test that inscriptions can be hidden with config --- crates/test-bitcoincore-rpc/src/state.rs | 1 + src/settings.rs | 5 - src/subcommand/server.rs | 160 ++++++++++++++++++++++- 3 files changed, 160 insertions(+), 6 deletions(-) diff --git a/crates/test-bitcoincore-rpc/src/state.rs b/crates/test-bitcoincore-rpc/src/state.rs index ab98f4bf66..72bf46bd7c 100644 --- a/crates/test-bitcoincore-rpc/src/state.rs +++ b/crates/test-bitcoincore-rpc/src/state.rs @@ -1,5 +1,6 @@ use super::*; +#[derive(Debug)] pub(crate) struct State { pub(crate) blocks: BTreeMap, pub(crate) change_addresses: Vec
, diff --git a/src/settings.rs b/src/settings.rs index 63127f5409..982472cca2 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,8 +1,5 @@ use {super::*, bitcoincore_rpc::Auth}; -// todo: -// - inscriptions can be hidden with config - #[derive(Default, Debug, Clone, Serialize)] pub struct Settings { #[serde(serialize_with = "serialize_auth")] @@ -69,8 +66,6 @@ impl Settings { config.bitcoin_rpc_pass.as_deref(), )?; - dbg!(&rpc_user, &rpc_pass); - let auth = match (rpc_user, rpc_pass) { (Some(rpc_user), Some(rpc_pass)) => Some(Auth::UserPass(rpc_user, rpc_pass)), (None, Some(_rpc_pass)) => bail!("no bitcoind rpc user specified"), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 91be9c4e1a..212781d3eb 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1736,6 +1736,114 @@ mod tests { const RUNE: u128 = 99246114928149462; + #[derive(Default)] + struct Builder { + config: Config, + bitcoin_rpc_server: Option, + } + + impl Builder { + fn bitcoin_rpc_server(self, bitcoin_rpc_server: test_bitcoincore_rpc::Handle) -> Self { + Self { + bitcoin_rpc_server: Some(bitcoin_rpc_server), + ..self + } + } + + fn config(self, config: Config) -> Self { + Self { config, ..self } + } + + fn build(self) -> TestServer { + let bitcoin_rpc_server = self + .bitcoin_rpc_server + .unwrap_or_else(|| test_bitcoincore_rpc::builder().build()); + + let tempdir = TempDir::new().unwrap(); + + let cookiefile = tempdir.path().join("cookie"); + + fs::write(&cookiefile, "username:password").unwrap(); + + let port = TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap() + .port(); + + let url = Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(); + + let mut arguments: Vec = vec![ + "ord".into(), + "--rpc-url".into(), + bitcoin_rpc_server.url(), + "--cookie-file".into(), + cookiefile.to_str().unwrap().into(), + "--data-dir".into(), + tempdir.path().to_str().unwrap().into(), + ]; + + arguments.extend(vec!["--chain".into(), bitcoin_rpc_server.network()]); + + arguments.extend(vec![ + "server".into(), + "--http-port".into(), + port.to_string(), + "--address".into(), + "127.0.0.1".into(), + ]); + + let (options, server) = match Arguments::try_parse_from(arguments) { + Ok(arguments) => match arguments.subcommand { + Subcommand::Server(server) => (arguments.options, server), + subcommand => panic!("unexpected subcommand: {subcommand:?}"), + }, + Err(err) => panic!("error parsing arguments: {err}"), + }; + + let settings = Settings::new(options, Default::default(), self.config).unwrap(); + + let index = Arc::new(Index::open(&settings).unwrap()); + let ord_server_handle = Handle::new(); + + { + let index = index.clone(); + let ord_server_handle = ord_server_handle.clone(); + thread::spawn(|| server.run(settings, index, ord_server_handle).unwrap()); + } + + while index.statistic(crate::index::Statistic::Commits) == 0 { + thread::sleep(Duration::from_millis(50)); + } + + let client = reqwest::blocking::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap(); + + for i in 0.. { + match client.get(format!("http://127.0.0.1:{port}/status")).send() { + Ok(_) => break, + Err(err) => { + if i == 400 { + panic!("ord server failed to start: {err}"); + } + } + } + + thread::sleep(Duration::from_millis(50)); + } + + TestServer { + bitcoin_rpc_server, + index, + ord_server_handle, + tempdir, + url, + } + } + } + struct TestServer { bitcoin_rpc_server: test_bitcoincore_rpc::Handle, index: Arc, @@ -1746,6 +1854,10 @@ mod tests { } impl TestServer { + fn builder() -> Builder { + Default::default() + } + fn new() -> Self { Self::new_with_args(&[], &[]) } @@ -1817,7 +1929,7 @@ mod tests { let url = Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(); - let (settings, server) = parse_server_args(&format!( + let (options, server) = parse_server_options(&format!( "ord --rpc-url {} --cookie-file {} --data-dir {} {} server --http-port {} --address 127.0.0.1 {}", bitcoin_rpc_server.url(), cookiefile.to_str().unwrap(), @@ -1827,6 +1939,8 @@ mod tests { server_args.join(" "), )); + let settings = Settings::new(options, Default::default(), Default::default()).unwrap(); + let index = Arc::new(Index::open(&settings).unwrap()); let ord_server_handle = Handle::new(); @@ -1967,6 +2081,16 @@ mod tests { } } + fn parse_server_options(args: &str) -> (Options, Server) { + match Arguments::try_parse_from(args.split_whitespace()) { + Ok(arguments) => match arguments.subcommand { + Subcommand::Server(server) => (arguments.options, server), + subcommand => panic!("unexpected subcommand: {subcommand:?}"), + }, + Err(err) => panic!("error parsing arguments: {err}"), + } + } + fn parse_server_args(args: &str) -> (Settings, Server) { match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { @@ -5376,4 +5500,38 @@ next .is_ok() ); } + + #[test] + fn inscriptions_can_be_hidden_with_config() { + let bitcoin_rpc_server = test_bitcoincore_rpc::builder() + .network(Chain::Regtest.network()) + .build(); + + bitcoin_rpc_server.mine_blocks(1); + + let txid = bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/foo", "hello").to_witness())], + ..Default::default() + }); + + bitcoin_rpc_server.mine_blocks(1); + + let inscription = InscriptionId { txid, index: 0 }; + + let server = TestServer::builder() + .bitcoin_rpc_server(bitcoin_rpc_server) + .config(Config { + hidden: Some(vec![inscription].into_iter().collect()), + ..Default::default() + }) + .build(); + + server.assert_response_regex(format!("/inscription/{inscription}"), StatusCode::OK, ".*"); + + server.assert_response_regex( + format!("/content/{inscription}"), + StatusCode::OK, + PreviewUnknownHtml.to_string(), + ); + } } From 8512aa42aa6f8ffcbc21c3377c62fafa8069d347 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sat, 24 Feb 2024 00:33:33 -0800 Subject: [PATCH 11/35] Use builder --- src/subcommand/server.rs | 513 +++++++++++++++++++-------------------- 1 file changed, 256 insertions(+), 257 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 212781d3eb..595b1730f8 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1738,8 +1738,13 @@ mod tests { #[derive(Default)] struct Builder { - config: Config, bitcoin_rpc_server: Option, + chain: Option, + config: Config, + https: bool, + index_runes: bool, + index_sats: bool, + redirect_http_to_https: bool, } impl Builder { @@ -1750,14 +1755,23 @@ mod tests { } } + fn chain(self, chain: Chain) -> Self { + Self { + chain: Some(chain), + ..self + } + } + fn config(self, config: Config) -> Self { Self { config, ..self } } fn build(self) -> TestServer { - let bitcoin_rpc_server = self - .bitcoin_rpc_server - .unwrap_or_else(|| test_bitcoincore_rpc::builder().build()); + let bitcoin_rpc_server = self.bitcoin_rpc_server.unwrap_or_else(|| { + test_bitcoincore_rpc::builder() + .network(self.chain.unwrap_or_default().network()) + .build() + }); let tempdir = TempDir::new().unwrap(); @@ -1771,34 +1785,35 @@ mod tests { .unwrap() .port(); - let url = Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(); - - let mut arguments: Vec = vec![ - "ord".into(), - "--rpc-url".into(), - bitcoin_rpc_server.url(), - "--cookie-file".into(), - cookiefile.to_str().unwrap().into(), - "--data-dir".into(), - tempdir.path().to_str().unwrap().into(), - ]; - - arguments.extend(vec!["--chain".into(), bitcoin_rpc_server.network()]); - - arguments.extend(vec![ - "server".into(), - "--http-port".into(), - port.to_string(), - "--address".into(), - "127.0.0.1".into(), - ]); - - let (options, server) = match Arguments::try_parse_from(arguments) { - Ok(arguments) => match arguments.subcommand { - Subcommand::Server(server) => (arguments.options, server), - subcommand => panic!("unexpected subcommand: {subcommand:?}"), - }, - Err(err) => panic!("error parsing arguments: {err}"), + let options = Options { + index_sats: self.index_sats, + index_runes: self.index_runes, + rpc_url: Some(bitcoin_rpc_server.url()), + cookie_file: Some(cookiefile), + data_dir: tempdir.path().into(), + chain_argument: Some( + self + .chain + .unwrap_or_else(|| bitcoin_rpc_server.network().parse().unwrap()), + ), + ..Default::default() + }; + + let server = Server { + acme_cache: None, + acme_contact: Vec::new(), + acme_domain: Vec::new(), + address: Some("127.0.0.1".into()), + csp_origin: None, + decompress: false, + disable_json_api: false, + http: false, + http_port: Some(port), + https: self.https, + https_port: None, + no_sync: false, + polling_interval: Duration::from_millis(100).into(), + redirect_http_to_https: self.redirect_http_to_https, }; let settings = Settings::new(options, Default::default(), self.config).unwrap(); @@ -1839,7 +1854,35 @@ mod tests { index, ord_server_handle, tempdir, - url, + url: Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(), + } + } + + fn https(self) -> Self { + Self { + https: true, + ..self + } + } + + fn index_runes(self) -> Self { + Self { + index_runes: true, + ..self + } + } + + fn index_sats(self) -> Self { + Self { + index_sats: true, + ..self + } + } + + fn redirect_http_to_https(self) -> Self { + Self { + redirect_http_to_https: true, + ..self } } } @@ -1848,9 +1891,9 @@ mod tests { bitcoin_rpc_server: test_bitcoincore_rpc::Handle, index: Arc, ord_server_handle: Handle, - url: Url, #[allow(unused)] tempdir: TempDir, + url: Url, } impl TestServer { @@ -1859,126 +1902,7 @@ mod tests { } fn new() -> Self { - Self::new_with_args(&[], &[]) - } - - fn new_with_sat_index() -> Self { - Self::new_with_args(&["--index-sats"], &[]) - } - - fn new_with_args(ord_args: &[&str], server_args: &[&str]) -> Self { - Self::new_server(test_bitcoincore_rpc::spawn(), ord_args, server_args) - } - - fn new_with_regtest() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::network::constants::Network::Regtest) - .build(), - &["--chain", "regtest"], - &[], - ) - } - - fn new_with_regtest_with_json_api() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::network::constants::Network::Regtest) - .build(), - &["--chain", "regtest"], - &[], - ) - } - - fn new_with_regtest_with_index_sats() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::Network::Regtest) - .build(), - &["--chain", "regtest", "--index-sats"], - &[], - ) - } - - fn new_with_regtest_with_index_runes() -> Self { - Self::new_server( - test_bitcoincore_rpc::builder() - .network(bitcoin::Network::Regtest) - .build(), - &["--chain", "regtest", "--index-runes"], - &[], - ) - } - - fn new_server( - bitcoin_rpc_server: test_bitcoincore_rpc::Handle, - ord_args: &[&str], - server_args: &[&str], - ) -> Self { - let tempdir = TempDir::new().unwrap(); - - let cookiefile = tempdir.path().join("cookie"); - - fs::write(&cookiefile, "username:password").unwrap(); - - let port = TcpListener::bind("127.0.0.1:0") - .unwrap() - .local_addr() - .unwrap() - .port(); - - let url = Url::parse(&format!("http://127.0.0.1:{port}")).unwrap(); - - let (options, server) = parse_server_options(&format!( - "ord --rpc-url {} --cookie-file {} --data-dir {} {} server --http-port {} --address 127.0.0.1 {}", - bitcoin_rpc_server.url(), - cookiefile.to_str().unwrap(), - tempdir.path().to_str().unwrap(), - ord_args.join(" "), - port, - server_args.join(" "), - )); - - let settings = Settings::new(options, Default::default(), Default::default()).unwrap(); - - let index = Arc::new(Index::open(&settings).unwrap()); - let ord_server_handle = Handle::new(); - - { - let index = index.clone(); - let ord_server_handle = ord_server_handle.clone(); - thread::spawn(|| server.run(settings, index, ord_server_handle).unwrap()); - } - - while index.statistic(crate::index::Statistic::Commits) == 0 { - thread::sleep(Duration::from_millis(50)); - } - - let client = reqwest::blocking::Client::builder() - .redirect(reqwest::redirect::Policy::none()) - .build() - .unwrap(); - - for i in 0.. { - match client.get(format!("http://127.0.0.1:{port}/status")).send() { - Ok(_) => break, - Err(err) => { - if i == 400 { - panic!("ord server failed to start: {err}"); - } - } - } - - thread::sleep(Duration::from_millis(50)); - } - - Self { - bitcoin_rpc_server, - index, - ord_server_handle, - tempdir, - url, - } + Builder::default().build() } fn get(&self, path: impl AsRef) -> reqwest::blocking::Response { @@ -2081,16 +2005,6 @@ mod tests { } } - fn parse_server_options(args: &str) -> (Options, Server) { - match Arguments::try_parse_from(args.split_whitespace()) { - Ok(arguments) => match arguments.subcommand { - Subcommand::Server(server) => (arguments.options, server), - subcommand => panic!("unexpected subcommand: {subcommand:?}"), - }, - Err(err) => panic!("error parsing arguments: {err}"), - } - } - fn parse_server_args(args: &str) -> (Settings, Server) { match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { @@ -2366,7 +2280,10 @@ mod tests { #[test] fn search_by_rune_id_returns_rune() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2408,7 +2325,10 @@ mod tests { #[test] fn runes_can_be_queried_by_rune_id() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2447,7 +2367,10 @@ mod tests { #[test] fn runes_are_displayed_on_runes_page() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2516,7 +2439,10 @@ mod tests { #[test] fn runes_are_displayed_on_rune_page() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2624,7 +2550,10 @@ mod tests { #[test] fn runes_are_spaced() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2721,7 +2650,10 @@ mod tests { #[test] fn transactions_link_to_etching() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2789,7 +2721,10 @@ mod tests { #[test] fn runes_are_displayed_on_output_page() { - let server = TestServer::new_with_regtest_with_index_runes(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_runes() + .build(); server.mine_blocks(1); @@ -2897,21 +2832,28 @@ mod tests { #[test] fn http_to_https_redirect_with_path() { - TestServer::new_with_args(&[], &["--redirect-http-to-https", "--https"]).assert_redirect( - "/sat/0", - &format!("https://{}/sat/0", System::host_name().unwrap()), - ); + TestServer::builder() + .redirect_http_to_https() + .https() + .build() + .assert_redirect( + "/sat/0", + &format!("https://{}/sat/0", System::host_name().unwrap()), + ); } #[test] fn http_to_https_redirect_with_empty() { - TestServer::new_with_args(&[], &["--redirect-http-to-https", "--https"]) + TestServer::builder() + .redirect_http_to_https() + .https() + .build() .assert_redirect("/", &format!("https://{}/", System::host_name().unwrap())); } #[test] fn status() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(3); @@ -3181,11 +3123,14 @@ mod tests { #[test] fn output_with_sat_index() { let txid = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"; - TestServer::new_with_sat_index().assert_response_regex( - format!("/output/{txid}:0"), - StatusCode::OK, - format!( - ".*Output {txid}:0.*

Output {txid}:0

+ TestServer::builder() + .index_sats() + .build() + .assert_response_regex( + format!("/output/{txid}:0"), + StatusCode::OK, + format!( + ".*Output {txid}:0.*

Output {txid}:0

value
5000000000
script pubkey
OP_PUSHBYTES_65 [[:xdigit:]]{{130}} OP_CHECKSIG
@@ -3196,8 +3141,8 @@ mod tests { .*" - ), - ); + ), + ); } #[test] @@ -3221,7 +3166,7 @@ mod tests { #[test] fn null_output_is_initially_empty() { let txid = "0000000000000000000000000000000000000000000000000000000000000000"; - TestServer::new_with_sat_index().assert_response_regex( + TestServer::builder().index_sats().build().assert_response_regex( format!("/output/{txid}:4294967295"), StatusCode::OK, format!( @@ -3241,7 +3186,7 @@ mod tests { #[test] fn null_output_receives_lost_sats() { - let server = TestServer::new_with_sat_index(); + let server = TestServer::builder().index_sats().build(); server.mine_blocks_with_subsidy(1, 0); @@ -3268,7 +3213,10 @@ mod tests { #[test] fn unbound_output_receives_unbound_inscriptions() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(1); @@ -3337,7 +3285,7 @@ mod tests { #[test] fn home() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); @@ -3403,11 +3351,14 @@ mod tests { #[test] fn nav_displays_chain() { - TestServer::new_with_regtest().assert_response_regex( - "/", - StatusCode::OK, - ".*Ordinalsregtest.*", - ); + TestServer::builder() + .chain(Chain::Regtest) + .build() + .assert_response_regex( + "/", + StatusCode::OK, + ".*Ordinalsregtest.*", + ); } #[test] @@ -3562,7 +3513,7 @@ mod tests { #[test] fn rare_with_sat_index() { - TestServer::new_with_sat_index().assert_response( + TestServer::builder().index_sats().build().assert_response( "/rare.txt", StatusCode::OK, "sat\tsatpoint @@ -3583,22 +3534,28 @@ mod tests { #[test] fn show_rare_txt_in_header_with_sat_index() { - TestServer::new_with_sat_index().assert_response_regex( - "/", - StatusCode::OK, - ".* + TestServer::builder() + .index_sats() + .build() + .assert_response_regex( + "/", + StatusCode::OK, + ".* .* .*.*", - ); + ); } #[test] fn rare_sat_location() { - TestServer::new_with_sat_index().assert_response_regex( - "/sat/0", - StatusCode::OK, - ".*>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0<.*", - ); + TestServer::builder() + .index_sats() + .build() + .assert_response_regex( + "/sat/0", + StatusCode::OK, + ".*>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0:0<.*", + ); } #[test] @@ -3667,7 +3624,7 @@ mod tests { #[test] fn outputs_traversed_are_tracked() { - let server = TestServer::new_with_sat_index(); + let server = TestServer::builder().index_sats().build(); assert_eq!( server @@ -3699,7 +3656,7 @@ mod tests { #[test] fn coinbase_sat_ranges_are_tracked() { - let server = TestServer::new_with_sat_index(); + let server = TestServer::builder().index_sats().build(); assert_eq!( server.index.statistic(crate::index::Statistic::SatRanges), @@ -3723,7 +3680,7 @@ mod tests { #[test] fn split_sat_ranges_are_tracked() { - let server = TestServer::new_with_sat_index(); + let server = TestServer::builder().index_sats().build(); assert_eq!( server.index.statistic(crate::index::Statistic::SatRanges), @@ -3747,7 +3704,7 @@ mod tests { #[test] fn fee_sat_ranges_are_tracked() { - let server = TestServer::new_with_sat_index(); + let server = TestServer::builder().index_sats().build(); assert_eq!( server.index.statistic(crate::index::Statistic::SatRanges), @@ -3830,7 +3787,7 @@ mod tests { #[test] fn code_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -3883,7 +3840,7 @@ mod tests { #[test] fn text_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -3910,7 +3867,7 @@ mod tests { #[test] fn audio_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -3930,7 +3887,7 @@ mod tests { #[test] fn font_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -3950,7 +3907,7 @@ mod tests { #[test] fn pdf_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -3975,7 +3932,7 @@ mod tests { #[test] fn markdown_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -3995,7 +3952,7 @@ mod tests { #[test] fn image_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4016,7 +3973,7 @@ mod tests { #[test] fn iframe_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4041,7 +3998,7 @@ mod tests { #[test] fn unknown_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4061,7 +4018,7 @@ mod tests { #[test] fn video_preview() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4081,7 +4038,10 @@ mod tests { #[test] fn inscription_page_title() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4100,7 +4060,10 @@ mod tests { #[test] fn inscription_page_has_sat_when_sats_are_tracked() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4119,7 +4082,7 @@ mod tests { #[test] fn inscription_page_does_not_have_sat_when_sats_are_not_tracked() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4150,7 +4113,10 @@ mod tests { #[test] fn feed() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(1); server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4169,7 +4135,10 @@ mod tests { #[test] fn inscription_with_unknown_type_and_no_body_has_unknown_preview() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4195,7 +4164,10 @@ mod tests { #[test] fn inscription_with_known_type_and_no_body_has_unknown_preview() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4221,7 +4193,7 @@ mod tests { #[test] fn content_responses_have_cache_control_headers() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4242,7 +4214,7 @@ mod tests { #[test] fn error_content_responses_have_max_age_zero_cache_control_headers() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); let response = server.get("/content/6ac5cacb768794f4fd7a78bf00f2074891fce68bd65c4ff36e77177237aacacai0"); @@ -4255,16 +4227,19 @@ mod tests { #[test] fn inscriptions_page_with_no_prev_or_next() { - TestServer::new_with_regtest_with_index_sats().assert_response_regex( - "/inscriptions", - StatusCode::OK, - ".*prev\nnext.*", - ); + TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build() + .assert_response_regex("/inscriptions", StatusCode::OK, ".*prev\nnext.*"); } #[test] fn inscriptions_page_with_no_next() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); for i in 0..101 { server.mine_blocks(1); @@ -4285,7 +4260,10 @@ mod tests { #[test] fn inscriptions_page_with_no_prev() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); for i in 0..101 { server.mine_blocks(1); @@ -4306,7 +4284,10 @@ mod tests { #[test] fn collections_page_prev_and_next() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); let mut parent_ids = Vec::new(); @@ -4429,7 +4410,7 @@ next #[test] fn inscription_links_to_parent() { - let server = TestServer::new_with_regtest_with_json_api(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let parent_txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4496,7 +4477,7 @@ next #[test] fn inscription_with_and_without_children_page() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let parent_txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4549,7 +4530,7 @@ next #[test] fn inscriptions_page_shows_max_four_children() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let parent_txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4651,7 +4632,7 @@ next #[test] fn inscription_number_endpoint() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(2); let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -4703,7 +4684,7 @@ next #[test] fn charm_cursed() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(2); @@ -4742,7 +4723,7 @@ next #[test] fn charm_vindicated() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(110); @@ -4778,7 +4759,10 @@ next #[test] fn charm_coin() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(2); @@ -4811,7 +4795,10 @@ next #[test] fn charm_uncommon() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(2); @@ -4844,7 +4831,10 @@ next #[test] fn charm_nineball() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); server.mine_blocks(9); @@ -4877,7 +4867,7 @@ next #[test] fn charm_reinscription() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); @@ -4920,7 +4910,7 @@ next #[test] fn charm_reinscription_in_same_tx_input() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); @@ -4998,7 +4988,7 @@ next #[test] fn charm_reinscription_in_same_tx_with_pointer() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(3); @@ -5057,7 +5047,7 @@ next #[test] fn charm_unbound() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); @@ -5093,7 +5083,7 @@ next #[test] fn charm_lost() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); @@ -5153,7 +5143,10 @@ next #[test] fn sat_recursive_endpoints() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); assert_eq!( server.get_json::("/r/sat/5000000000"), @@ -5250,7 +5243,7 @@ next #[test] fn children_recursive_endpoint() { - let server = TestServer::new_with_regtest_with_json_api(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); let parent_txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { @@ -5322,7 +5315,10 @@ next #[test] fn inscriptions_in_block_page() { - let server = TestServer::new_with_regtest_with_index_sats(); + let server = TestServer::builder() + .chain(Chain::Regtest) + .index_sats() + .build(); for _ in 0..101 { server.mine_blocks(1); @@ -5361,16 +5357,19 @@ next #[test] fn inscription_not_found() { - TestServer::new_with_regtest_with_json_api().assert_response( - "/inscription/0", - StatusCode::NOT_FOUND, - "inscription 0 not found", - ); + TestServer::builder() + .chain(Chain::Regtest) + .build() + .assert_response( + "/inscription/0", + StatusCode::NOT_FOUND, + "inscription 0 not found", + ); } #[test] fn delegate() { - let server = TestServer::new_with_regtest(); + let server = TestServer::builder().chain(Chain::Regtest).build(); server.mine_blocks(1); From 9f74749f87155ffd963dc386dd713384e96b3bc5 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sat, 24 Feb 2024 23:47:00 -0800 Subject: [PATCH 12/35] Enhance --- src/chain.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/chain.rs b/src/chain.rs index c52fe68234..b711e792d8 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -118,7 +118,7 @@ impl FromStr for Chain { "regtest" => Ok(Self::Regtest), "signet" => Ok(Self::Signet), "testnet" => Ok(Self::Testnet), - _ => bail!("invalid chain: {s}"), + _ => bail!("invalid chain: `{s}`"), } } } @@ -133,5 +133,9 @@ mod tests { assert_eq!("regtest".parse::().unwrap(), Chain::Regtest); assert_eq!("signet".parse::().unwrap(), Chain::Signet); assert_eq!("testnet".parse::().unwrap(), Chain::Testnet); + assert_eq!( + "foo".parse::().unwrap_err().to_string(), + "invalid chain: `foo`" + ); } } From ad17e2479548f287350dfcd46534a84f478d7a66 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sat, 24 Feb 2024 23:56:16 -0800 Subject: [PATCH 13/35] Remove --- src/index/testing.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index/testing.rs b/src/index/testing.rs index 2e3ba71bf1..12b271c50e 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -32,8 +32,7 @@ impl ContextBuilder { format!("--chain={}", self.chain).into(), ]; - let mut options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap(); - options.chain_argument = Some(self.chain); + let options = Options::try_parse_from(command.into_iter().chain(self.args)).unwrap(); let index = Index::open_with_event_sender(&options.settings().unwrap(), self.event_sender)?; index.update().unwrap(); From 9281020cd6567f2a53a00ef42e6b3200d1e4eb1e Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 00:05:21 -0800 Subject: [PATCH 14/35] Note failing test --- src/subcommand/server.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 595b1730f8..42a3ddb934 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -42,6 +42,9 @@ use { }, }; +// todo: +// subcommand::server::tests::collections_page_prev_and_next + mod accept_encoding; mod accept_json; mod error; From d320b6fc47b7c567dbdb80ced19659bb2b351ce6 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 00:38:03 -0800 Subject: [PATCH 15/35] Only pass settings in --- src/index/updater.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index/updater.rs b/src/index/updater.rs index f347fc942b..397df80d8f 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -84,7 +84,7 @@ impl<'index> Updater<'_> { let rx = Self::fetch_blocks_from(self.index, self.height, self.index.index_sats)?; - let (mut outpoint_sender, mut value_receiver) = Self::spawn_fetcher(self.index)?; + let (mut outpoint_sender, mut value_receiver) = Self::spawn_fetcher(&self.index.settings)?; let mut uncommitted = 0; let mut value_cache = HashMap::new(); @@ -240,8 +240,8 @@ impl<'index> Updater<'_> { } } - fn spawn_fetcher(index: &Index) -> Result<(Sender, Receiver)> { - let fetcher = Fetcher::new(&index.settings)?; + fn spawn_fetcher(settings: &Settings) -> Result<(Sender, Receiver)> { + let fetcher = Fetcher::new(&settings)?; // Not sure if any block has more than 20k inputs, but none so far after first inscription block const CHANNEL_BUFFER_SIZE: usize = 20_000; From 1e4ee45b998d16911a20242042ecd0210b416075 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 01:07:29 -0800 Subject: [PATCH 16/35] Handle some errors --- src/index.rs | 6 +++--- src/index/rtx.rs | 4 ++-- src/index/updater.rs | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/index.rs b/src/index.rs index 584f46632d..ccc57af5b5 100644 --- a/src/index.rs +++ b/src/index.rs @@ -558,7 +558,7 @@ impl Index { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height.value() + 1) .unwrap_or(0), branch_pages: stats.branch_pages(), @@ -628,7 +628,7 @@ impl Index { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height.value() + 1) .unwrap_or(0); @@ -1532,7 +1532,7 @@ impl Index { let current = height_to_block_header .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height) .map(|x| x.value()) .unwrap_or(0); diff --git a/src/index/rtx.rs b/src/index/rtx.rs index de7a712952..fb02a06453 100644 --- a/src/index/rtx.rs +++ b/src/index/rtx.rs @@ -10,7 +10,7 @@ impl Rtx<'_> { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| Height(height.value())), ) } @@ -22,7 +22,7 @@ impl Rtx<'_> { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _header)| height.value() + 1) .unwrap_or(0), ) diff --git a/src/index/updater.rs b/src/index/updater.rs index 397df80d8f..5a162fb7e4 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -55,6 +55,18 @@ impl<'index> Updater<'_> { pub(crate) fn update_index(&mut self) -> Result { let mut wtx = self.index.begin_write()?; + + // assert_eq!( + // self.height, + // wtx + // .open_table(HEIGHT_TO_BLOCK_HEADER)? + // .range(0..)? + // .next_back() + // .and_then(|result| result.ok()) + // .map(|(height, _header)| height.value() + 1) + // .unwrap_or(0), + // ); + let starting_height = u32::try_from(self.index.client.get_block_count()?).unwrap() + 1; wtx @@ -120,7 +132,7 @@ impl<'index> Updater<'_> { .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(height, _hash)| height.value() + 1) .unwrap_or(0); if height != self.height { @@ -415,7 +427,7 @@ impl<'index> Updater<'_> { let next_sequence_number = sequence_number_to_inscription_entry .iter()? .next_back() - .and_then(|result| result.ok()) + .transpose()? .map(|(number, _id)| number.value() + 1) .unwrap_or(0); From 26078b18b5d3bc37195991a408dd7ee6c614d8da Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 01:10:43 -0800 Subject: [PATCH 17/35] Modify --- src/index/updater.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index/updater.rs b/src/index/updater.rs index 5a162fb7e4..fb3f62a23c 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -253,7 +253,7 @@ impl<'index> Updater<'_> { } fn spawn_fetcher(settings: &Settings) -> Result<(Sender, Receiver)> { - let fetcher = Fetcher::new(&settings)?; + let fetcher = Fetcher::new(settings)?; // Not sure if any block has more than 20k inputs, but none so far after first inscription block const CHANNEL_BUFFER_SIZE: usize = 20_000; From 17c0f84b13c885d65c79ae00dd5d9319b42400a8 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 01:35:44 -0800 Subject: [PATCH 18/35] fuck race conditions --- src/index.rs | 24 +++++++++++++++++++----- src/index/reorg.rs | 2 +- src/index/updater.rs | 41 ++++++++--------------------------------- 3 files changed, 28 insertions(+), 39 deletions(-) diff --git a/src/index.rs b/src/index.rs index ccc57af5b5..0b8a90dc51 100644 --- a/src/index.rs +++ b/src/index.rs @@ -593,10 +593,26 @@ impl Index { } pub fn update(&self) -> Result { - let mut updater = Updater::new(self)?; - loop { - match updater.update_index() { + let wtx = self.begin_write()?; + + let mut updater = Updater { + range_cache: HashMap::new(), + height: wtx + .open_table(HEIGHT_TO_BLOCK_HEADER)? + .range(0..)? + .next_back() + .transpose()? + .map(|(height, _header)| height.value() + 1) + .unwrap_or(0), + index: &self, + sat_ranges_since_flush: 0, + outputs_cached: 0, + outputs_inserted_since_flush: 0, + outputs_traversed: 0, + }; + + match updater.update_index(wtx) { Ok(ok) => return Ok(ok), Err(err) => { log::info!("{}", err.to_string()); @@ -604,8 +620,6 @@ impl Index { match err.downcast_ref() { Some(&ReorgError::Recoverable { height, depth }) => { Reorg::handle_reorg(self, height, depth)?; - - updater = Updater::new(self)?; } Some(&ReorgError::Unrecoverable) => { self diff --git a/src/index/reorg.rs b/src/index/reorg.rs index 8953a4bb81..e1869e2fd4 100644 --- a/src/index/reorg.rs +++ b/src/index/reorg.rs @@ -72,7 +72,7 @@ impl Reorg { log::info!( "successfully rolled back database to height {}", - index.block_count()? + index.begin_read()?.block_count()? ); Ok(()) diff --git a/src/index/updater.rs b/src/index/updater.rs index fb3f62a23c..22cbefd992 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -31,42 +31,17 @@ impl From for BlockData { } pub(crate) struct Updater<'index> { - range_cache: HashMap>, - height: u32, - index: &'index Index, - sat_ranges_since_flush: u64, - outputs_cached: u64, - outputs_inserted_since_flush: u64, - outputs_traversed: u64, + pub(super) range_cache: HashMap>, + pub(super) height: u32, + pub(super) index: &'index Index, + pub(super) sat_ranges_since_flush: u64, + pub(super) outputs_cached: u64, + pub(super) outputs_inserted_since_flush: u64, + pub(super) outputs_traversed: u64, } impl<'index> Updater<'_> { - pub(crate) fn new(index: &'index Index) -> Result> { - Ok(Updater { - range_cache: HashMap::new(), - height: index.block_count()?, - index, - sat_ranges_since_flush: 0, - outputs_cached: 0, - outputs_inserted_since_flush: 0, - outputs_traversed: 0, - }) - } - - pub(crate) fn update_index(&mut self) -> Result { - let mut wtx = self.index.begin_write()?; - - // assert_eq!( - // self.height, - // wtx - // .open_table(HEIGHT_TO_BLOCK_HEADER)? - // .range(0..)? - // .next_back() - // .and_then(|result| result.ok()) - // .map(|(height, _header)| height.value() + 1) - // .unwrap_or(0), - // ); - + pub(crate) fn update_index<'a>(&'a mut self, mut wtx: WriteTransaction<'a>) -> Result { let starting_height = u32::try_from(self.index.client.get_block_count()?).unwrap() + 1; wtx From d24289dc0ec4c33ac82af69c244689342ba6640b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 01:37:58 -0800 Subject: [PATCH 19/35] Clean up --- src/index.rs | 6 +++--- src/index/updater.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/index.rs b/src/index.rs index 0b8a90dc51..475ce86c1c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -597,7 +597,6 @@ impl Index { let wtx = self.begin_write()?; let mut updater = Updater { - range_cache: HashMap::new(), height: wtx .open_table(HEIGHT_TO_BLOCK_HEADER)? .range(0..)? @@ -605,11 +604,12 @@ impl Index { .transpose()? .map(|(height, _header)| height.value() + 1) .unwrap_or(0), - index: &self, - sat_ranges_since_flush: 0, + index: self, outputs_cached: 0, outputs_inserted_since_flush: 0, outputs_traversed: 0, + range_cache: HashMap::new(), + sat_ranges_since_flush: 0, }; match updater.update_index(wtx) { diff --git a/src/index/updater.rs b/src/index/updater.rs index 22cbefd992..7cd5cc98b8 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -31,16 +31,16 @@ impl From for BlockData { } pub(crate) struct Updater<'index> { - pub(super) range_cache: HashMap>, pub(super) height: u32, pub(super) index: &'index Index, - pub(super) sat_ranges_since_flush: u64, pub(super) outputs_cached: u64, pub(super) outputs_inserted_since_flush: u64, pub(super) outputs_traversed: u64, + pub(super) range_cache: HashMap>, + pub(super) sat_ranges_since_flush: u64, } -impl<'index> Updater<'_> { +impl<'index> Updater<'index> { pub(crate) fn update_index<'a>(&'a mut self, mut wtx: WriteTransaction<'a>) -> Result { let starting_height = u32::try_from(self.index.client.get_block_count()?).unwrap() + 1; From eb5e2af721c15cceb35e1fef7b82163328e61fc1 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 01:39:20 -0800 Subject: [PATCH 20/35] Tweak --- src/subcommand/server.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 42a3ddb934..595b1730f8 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -42,9 +42,6 @@ use { }, }; -// todo: -// subcommand::server::tests::collections_page_prev_and_next - mod accept_encoding; mod accept_json; mod error; From b96f3e3186e3cf5e07c59842d2c484cbe0cd8058 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 01:43:52 -0800 Subject: [PATCH 21/35] Enhance --- src/chain.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chain.rs b/src/chain.rs index b711e792d8..c1b9404dfd 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -118,7 +118,7 @@ impl FromStr for Chain { "regtest" => Ok(Self::Regtest), "signet" => Ok(Self::Signet), "testnet" => Ok(Self::Testnet), - _ => bail!("invalid chain: `{s}`"), + _ => bail!("invalid chain `{s}`"), } } } @@ -135,7 +135,7 @@ mod tests { assert_eq!("testnet".parse::().unwrap(), Chain::Testnet); assert_eq!( "foo".parse::().unwrap_err().to_string(), - "invalid chain: `foo`" + "invalid chain `foo`" ); } } From 8ac809e0fc83e10d6f9e3d0690b188664b14979c Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 02:17:11 -0800 Subject: [PATCH 22/35] Enhance --- src/subcommand/server.rs | 2 +- tests/test_server.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 595b1730f8..157074836e 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -349,7 +349,7 @@ impl Server { .next() .ok_or_else(|| anyhow!("failed to get socket addrs"))?; - if !integration_test() { + if !integration_test() && !cfg!(test) { eprintln!( "Listening on {}://{addr}", match config { diff --git a/tests/test_server.rs b/tests/test_server.rs index 97464ef7be..96fccdb4f9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -7,8 +7,6 @@ use { }; pub(crate) struct TestServer { - #[allow(unused)] - index: Arc, bitcoin_rpc_url: String, ord_server_handle: Handle, port: u16, @@ -80,7 +78,6 @@ impl TestServer { } Self { - index, bitcoin_rpc_url: bitcoin_rpc_server.url(), ord_server_handle, port, From 4c951446c64d46d8bfe74c7bd380c87f352fad65 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 05:03:58 -0800 Subject: [PATCH 23/35] Remove env var test --- tests/server.rs | 58 ------------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/tests/server.rs b/tests/server.rs index 6e30e5bdf6..757c79a317 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -404,64 +404,6 @@ fn expected_sat_time_is_rounded() { ); } -#[test] -#[ignore] -fn server_runs_with_rpc_user_and_pass_as_env_vars() { - let rpc_server = test_bitcoincore_rpc::spawn(); - rpc_server.mine_blocks(1); - - let tempdir = TempDir::new().unwrap(); - let port = TcpListener::bind("127.0.0.1:0") - .unwrap() - .local_addr() - .unwrap() - .port(); - - let mut child = Command::new(executable_path("ord")) - .args(format!( - "--rpc-url {} --bitcoin-data-dir {} --data-dir {} server --http-port {port} --address 127.0.0.1", - rpc_server.url(), - tempdir.path().display(), - tempdir.path().display()).to_args() - ) - .env("ORD_BITCOIN_RPC_PASS", "bar") - .env("ORD_BITCOIN_RPC_USER", "foo") - .env("ORD_INTEGRATION_TEST", "1") - .current_dir(&tempdir) - .spawn().unwrap(); - - for i in 0.. { - match reqwest::blocking::get(format!("http://127.0.0.1:{port}/status")) { - Ok(_) => break, - Err(err) => { - if i == 400 { - panic!("ord server failed to start: {err}"); - } - } - } - - thread::sleep(Duration::from_millis(50)); - } - - rpc_server.mine_blocks(1); - - for i in 0.. { - let response = reqwest::blocking::get(format!("http://127.0.0.1:{port}/blockcount")).unwrap(); - assert_eq!(response.status(), StatusCode::OK); - if response.text().unwrap() == "2" { - break; - } - - if i == 400 { - panic!("ord server failed to sync"); - } - - thread::sleep(Duration::from_millis(50)); - } - - child.kill().unwrap(); -} - #[test] fn missing_credentials() { let rpc_server = test_bitcoincore_rpc::spawn(); From 405276617488ff1400db056bc6ecd61eb4ade053 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 05:09:37 -0800 Subject: [PATCH 24/35] Make setting_opt infallible --- src/settings.rs | 23 +++++++++++------------ tests/server.rs | 5 +---- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 982472cca2..ae92f93241 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,6 +18,7 @@ pub struct Settings { pub(crate) index_sats: bool, pub(crate) index_spent_sats: bool, pub(crate) index_transactions: bool, + // pub(crate) integration_test: bool, pub(crate) no_index_inscriptions: bool, pub(crate) rpc_url: Option, } @@ -57,14 +58,14 @@ impl Settings { options.bitcoin_rpc_user.as_deref(), Some("BITCOIN_RPC_USER"), config.bitcoin_rpc_user.as_deref(), - )?; + ); let rpc_pass = Self::setting_opt( &env, options.bitcoin_rpc_pass.as_deref(), Some("BITCOIN_RPC_PASS"), config.bitcoin_rpc_pass.as_deref(), - )?; + ); let auth = match (rpc_user, rpc_pass) { (Some(rpc_user), Some(rpc_pass)) => Some(Auth::UserPass(rpc_user, rpc_pass)), @@ -266,18 +267,18 @@ impl Settings { arg_value: Option<&str>, env_key: Option<&str>, config_value: Option<&str>, - ) -> Result> { + ) -> Option { if let Some(arg_value) = arg_value { - return Ok(Some(arg_value.into())); + return Some(arg_value.into()); } if let Some(env_key) = env_key { if let Some(env_value) = env.get(env_key) { - return Ok(Some(env_value.into())); + return Some(env_value.into()); } } - Ok(config_value.map(str::to_string)) + config_value.map(str::to_string) } } @@ -449,12 +450,12 @@ mod tests { #[test] fn setting_opt() { assert_eq!( - Settings::setting_opt(&Default::default(), None, None, None).unwrap(), + Settings::setting_opt(&Default::default(), None, None, None), None ); assert_eq!( - Settings::setting_opt(&Default::default(), None, None, Some("config")).unwrap(), + Settings::setting_opt(&Default::default(), None, None, Some("config")), Some("config".into()), ); @@ -466,8 +467,7 @@ mod tests { None, Some("env_key"), Some("config") - ) - .unwrap(), + ), Some("env_value".into()), ); @@ -479,8 +479,7 @@ mod tests { Some("option"), Some("env_key"), Some("config") - ) - .unwrap(), + ), Some("option".into()), ); } diff --git a/tests/server.rs b/tests/server.rs index 757c79a317..e0abd07c2a 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1,7 +1,4 @@ -use { - super::*, crate::command_builder::ToArgs, ciborium::value::Integer, - ord::subcommand::wallet::send::Output, -}; +use {super::*, ciborium::value::Integer, ord::subcommand::wallet::send::Output}; #[test] fn run() { From baf933368decd8ec2b211447b2842a512055c21c Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 05:28:49 -0800 Subject: [PATCH 25/35] Make ORD_INTEGRATION_TEST a normal env variable --- src/index.rs | 3 ++- src/index/updater.rs | 2 +- src/lib.rs | 15 ++++++++------- src/settings.rs | 15 ++++++++++----- src/subcommand/server.rs | 19 ++++++++++++++----- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/index.rs b/src/index.rs index 475ce86c1c..0ac78725cf 100644 --- a/src/index.rs +++ b/src/index.rs @@ -262,11 +262,12 @@ impl Index { let index_path = path.clone(); let once = Once::new(); let progress_bar = Mutex::new(None); + let integration_test = settings.integration_test; let repair_callback = move |progress: &mut RepairSession| { once.call_once(|| println!("Index file `{}` needs recovery. This can take a long time, especially for the --index-sats index.", index_path.display())); - if !(cfg!(test) || log_enabled!(log::Level::Info) || integration_test()) { + if !(cfg!(test) || log_enabled!(log::Level::Info) || integration_test) { let mut guard = progress_bar.lock().unwrap(); let progress_bar = guard.get_or_insert_with(|| { diff --git a/src/index/updater.rs b/src/index/updater.rs index 7cd5cc98b8..ff0887bcd9 100644 --- a/src/index/updater.rs +++ b/src/index/updater.rs @@ -57,7 +57,7 @@ impl<'index> Updater<'index> { let mut progress_bar = if cfg!(test) || log_enabled!(log::Level::Info) || starting_height <= self.height - || integration_test() + || self.index.settings.integration_test { None } else { diff --git a/src/lib.rs b/src/lib.rs index 7fe3ebe0aa..ed77eba0aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,12 +166,6 @@ fn fund_raw_transaction( ) } -fn integration_test() -> bool { - env::var_os("ORD_INTEGRATION_TEST") - .map(|value| value.len() > 0) - .unwrap_or(false) -} - pub fn timestamp(seconds: u32) -> DateTime { Utc.timestamp_opt(seconds.into(), 0).unwrap() } @@ -191,7 +185,14 @@ pub fn parse_ord_server_args(args: &str) -> (Settings, crate::subcommand::server match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { Subcommand::Server(server) => ( - Settings::new(arguments.options, Default::default(), Default::default()).unwrap(), + Settings::new( + arguments.options, + vec![("INTEGRATION_TEST".into(), "1".into())] + .into_iter() + .collect(), + Default::default(), + ) + .unwrap(), server, ), subcommand => panic!("unexpected subcommand: {subcommand:?}"), diff --git a/src/settings.rs b/src/settings.rs index ae92f93241..5acec18d2e 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -18,7 +18,7 @@ pub struct Settings { pub(crate) index_sats: bool, pub(crate) index_spent_sats: bool, pub(crate) index_transactions: bool, - // pub(crate) integration_test: bool, + pub(crate) integration_test: bool, pub(crate) no_index_inscriptions: bool, pub(crate) rpc_url: Option, } @@ -67,6 +67,10 @@ impl Settings { config.bitcoin_rpc_pass.as_deref(), ); + let integration_test = Self::setting_opt(&env, None, Some("INTEGRATION_TEST"), None) + .map(|value| value.len() > 0) + .unwrap_or_default(); + let auth = match (rpc_user, rpc_pass) { (Some(rpc_user), Some(rpc_pass)) => Some(Auth::UserPass(rpc_user, rpc_pass)), (None, Some(_rpc_pass)) => bail!("no bitcoind rpc user specified"), @@ -90,6 +94,7 @@ impl Settings { index_sats: options.index_sats, index_spent_sats: options.index_spent_sats, index_transactions: options.index_transactions, + integration_test, no_index_inscriptions: options.no_index_inscriptions, rpc_url: options.rpc_url, }) @@ -199,7 +204,7 @@ impl Settings { } pub(crate) fn first_inscription_height(&self) -> u32 { - if integration_test() { + if self.integration_test { 0 } else { self @@ -209,7 +214,7 @@ impl Settings { } pub(crate) fn first_rune_height(&self) -> u32 { - if integration_test() { + if self.integration_test { 0 } else { self.chain().first_rune_height() @@ -239,7 +244,7 @@ impl Settings { fn setting>( env: &BTreeMap, arg_value: Option, - env_key: Option<&str>, + env_key: Option<&'static str>, config_value: Option, default_value: T, ) -> Result { @@ -265,7 +270,7 @@ impl Settings { fn setting_opt( env: &BTreeMap, arg_value: Option<&str>, - env_key: Option<&str>, + env_key: Option<&'static str>, config_value: Option<&str>, ) -> Option { if let Some(arg_value) = arg_value { diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 157074836e..2bb9cbf35a 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -159,7 +159,7 @@ impl Server { } } - thread::sleep(if integration_test() { + thread::sleep(if settings.integration_test { Duration::from_millis(100) } else { self.polling_interval.into() @@ -284,12 +284,13 @@ impl Server { match (self.http_port(), self.https_port()) { (Some(http_port), None) => { self - .spawn(router, handle, http_port, SpawnConfig::Http)? + .spawn(&settings, router, handle, http_port, SpawnConfig::Http)? .await?? } (None, Some(https_port)) => { self .spawn( + &settings, router, handle, https_port, @@ -309,8 +310,15 @@ impl Server { }; let (http_result, https_result) = tokio::join!( - self.spawn(router.clone(), handle.clone(), http_port, http_spawn_config)?, self.spawn( + &settings, + router.clone(), + handle.clone(), + http_port, + http_spawn_config + )?, + self.spawn( + &settings, router, handle, https_port, @@ -328,6 +336,7 @@ impl Server { fn spawn( &self, + settings: &Settings, router: Router, handle: Handle, port: u16, @@ -336,7 +345,7 @@ impl Server { let address = match &self.address { Some(address) => address.as_str(), None => { - if cfg!(test) || integration_test() { + if cfg!(test) || settings.integration_test { "127.0.0.1" } else { "0.0.0.0" @@ -349,7 +358,7 @@ impl Server { .next() .ok_or_else(|| anyhow!("failed to get socket addrs"))?; - if !integration_test() && !cfg!(test) { + if !settings.integration_test && !cfg!(test) { eprintln!( "Listening on {}://{addr}", match config { From 23fcef725b67c63f3a6df8cb526a384911ad67d4 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sun, 25 Feb 2024 18:06:59 -0800 Subject: [PATCH 26/35] Tweak --- src/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings.rs b/src/settings.rs index 5acec18d2e..5cc80119ea 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -68,7 +68,7 @@ impl Settings { ); let integration_test = Self::setting_opt(&env, None, Some("INTEGRATION_TEST"), None) - .map(|value| value.len() > 0) + .map(|value| !value.is_empty()) .unwrap_or_default(); let auth = match (rpc_user, rpc_pass) { From dcc1747fdc30148aa55c006139c385ccbad54e56 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 14:26:01 -0800 Subject: [PATCH 27/35] Enhance --- src/arguments.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arguments.rs b/src/arguments.rs index 93dbcf0b13..d9de964255 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -36,7 +36,7 @@ impl Arguments { key.into(), value.into_string().map_err(|value| { anyhow!( - "environment variable `{var}` not valid unicode: {}", + "environment variable `{var}` not valid unicode: `{}`", value.to_string_lossy() ) })?, From d776d968dc67d6e26f0f7893b306421611e4da02 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 14:26:25 -0800 Subject: [PATCH 28/35] Remove unused core workflow --- .github/workflows/ci.yaml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7899e38af3..2f753fd1fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -116,23 +116,3 @@ jobs: - name: Test run: cargo test --all - - core: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install Rust Toolchain Components - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - - - uses: Swatinem/rust-cache@v2 - - - name: Install Bitcoin Core - run: ./bin/install-bitcoin-core-linux - - - name: Test - run: cargo test --all -- --ignored From 8b1a928af0927d35a985619162115b082b560970 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 14:37:40 -0800 Subject: [PATCH 29/35] Better error messages when loading config file --- src/arguments.rs | 10 +--------- src/options.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/arguments.rs b/src/arguments.rs index d9de964255..72ec0eb97b 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -43,15 +43,7 @@ impl Arguments { ); } - let config: Config = match &self.options.config { - Some(path) => serde_yaml::from_reader(File::open(path)?)?, - None => match &self.options.config_dir { - Some(dir) if dir.join("ord.yaml").exists() => { - serde_yaml::from_reader(File::open(dir.join("ord.yaml"))?)? - } - Some(_) | None => Default::default(), - }, - }; + let config = self.options.config()?; self .subcommand diff --git a/src/options.rs b/src/options.rs index b5bec0590b..0263ffe910 100644 --- a/src/options.rs +++ b/src/options.rs @@ -86,6 +86,30 @@ impl Options { .expect("failed to retrieve data dir") } + pub(crate) fn config(&self) -> Result { + let path = match &self.config { + Some(path) => path.clone(), + None => match &self.config_dir { + Some(dir) => { + let path = dir.join("ord.yaml"); + if !path.exists() { + return Ok(Default::default()); + } + path + } + None => return Ok(Default::default()), + }, + }; + + serde_yaml::from_reader( + File::open(&path).context(anyhow!("failed to open config file `{}`", path.display()))?, + ) + .context(anyhow!( + "failed to deserialize config file `{}`", + path.display() + )) + } + #[cfg(test)] pub(crate) fn settings(self) -> Result { Settings::new(self, Default::default(), Default::default()) From 8e2198309a6437dc9cbba611b208a5f230d444da Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 14:45:08 -0800 Subject: [PATCH 30/35] Modify --- tests/settings.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/settings.rs b/tests/settings.rs index 2d42fc2b64..caf7872cca 100644 --- a/tests/settings.rs +++ b/tests/settings.rs @@ -25,6 +25,26 @@ fn config_is_loaded_from_config_option() { .run_and_extract_stdout(); } +#[test] +fn config_not_found_error_message() { + CommandBuilder::new("settings") + .stdout_regex( + r#".* + "chain": "mainnet", +.*"#, + ) + .run_and_extract_stdout(); + + let tempdir = TempDir::new().unwrap(); + + let config = tempdir.path().join("ord.yaml"); + + CommandBuilder::new(format!("--config {} settings", config.to_str().unwrap())) + .stderr_regex("error: failed to open config file `.*/ord.yaml`\nbecause:.*") + .expected_exit_code(1) + .run_and_extract_stdout(); +} + #[test] fn config_is_loaded_from_config_dir() { CommandBuilder::new("settings") From 0f62edd8f45393f36932de6a1871a18295248362 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 15:21:01 -0800 Subject: [PATCH 31/35] Add settings guide --- docs/src/SUMMARY.md | 11 ++++++----- docs/src/guides/settings.md | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 docs/src/guides/settings.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index f284d5f0cc..024671c460 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -15,16 +15,17 @@ Summary - [Contributing](contributing.md) - [Donate](donate.md) - [Guides](guides.md) - - [Explorer](guides/explorer.md) - - [Inscriptions](guides/inscriptions.md) - [Batch Inscribing](guides/batch-inscribing.md) - - [Sat Hunting](guides/sat-hunting.md) - - [Teleburning](guides/teleburning.md) - [Collecting](guides/collecting.md) - [Sparrow Wallet](guides/collecting/sparrow-wallet.md) - - [Testing](guides/testing.md) + - [Explorer](guides/explorer.md) + - [Inscriptions](guides/inscriptions.md) - [Moderation](guides/moderation.md) - [Reindexing](guides/reindexing.md) + - [Sat Hunting](guides/sat-hunting.md) + - [Settings](guides/settings.md) + - [Teleburning](guides/teleburning.md) + - [Testing](guides/testing.md) - [Bounties](bounties.md) - [Bounty 0: 100,000 sats Claimed!](bounty/0.md) - [Bounty 1: 200,000 sats Claimed!](bounty/1.md) diff --git a/docs/src/guides/settings.md b/docs/src/guides/settings.md new file mode 100644 index 0000000000..747906ed4f --- /dev/null +++ b/docs/src/guides/settings.md @@ -0,0 +1,25 @@ +Settings +======== + +`ord` can be configured with command line options, environment variables, a +configuration file, and default values. + +When multiple sources configure the same thing, precedence is in order of +command line options, then environment variables, then the configuration file, +and finally default values. + +The path to the configuration can be given with `--config `. `ord` +will error if `` doesn't exist. The path to a configuration +directory can be given with `--config-dir `, in which case the +config path is `/ord.yaml`. It is not an error if +`/ord.yaml` does not exist, and `ord` will use a configuration +with default values. + +All settings can be configured with command line options, but not all settings +can yet be configured with environmnet variables or a configuration file. + +| setting | CLI | environment variable | default value | +| --- | --- | --- | --- | +| bitcoin RPC password | `--bitcoin-rpc-pass ` | `ORD_BITCOIN_RPC_PASS` | none | +| bitcoin RPC username | `--bitcoin-rpc-user ` | `ORD_BITCOIN_RPC_USER` | none | +| chain | `--chain ` | `ORD_CHAIN` | mainnet | From 0d7bc3b3524c0b5420e80a0ca7e9b1d7e7d51bcf Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 15:21:58 -0800 Subject: [PATCH 32/35] Tweak --- docs/src/guides/settings.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/guides/settings.md b/docs/src/guides/settings.md index 747906ed4f..aadded39ea 100644 --- a/docs/src/guides/settings.md +++ b/docs/src/guides/settings.md @@ -18,6 +18,8 @@ with default values. All settings can be configured with command line options, but not all settings can yet be configured with environmnet variables or a configuration file. +`ord`'s configuration can be viewd as JSON with `ord settings`. + | setting | CLI | environment variable | default value | | --- | --- | --- | --- | | bitcoin RPC password | `--bitcoin-rpc-pass ` | `ORD_BITCOIN_RPC_PASS` | none | From 42a2a91e993aeeee2b64d8a7b310a0d2f5f8decf Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 15:23:35 -0800 Subject: [PATCH 33/35] Enhance --- src/settings.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 5cc80119ea..b7a8a31963 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -300,6 +300,16 @@ mod tests { .unwrap() } + fn parse_wallet_args(args: &str) -> (Options, subcommand::wallet::WalletCommand) { + match Arguments::try_parse_from(args.split_whitespace()) { + Ok(arguments) => match arguments.subcommand { + Subcommand::Wallet(wallet) => (arguments.options, wallet), + subcommand => panic!("unexpected subcommand: {subcommand:?}"), + }, + Err(err) => panic!("error parsing arguments: {err}"), + } + } + #[test] fn auth_missing_rpc_pass_is_an_error() { assert_eq!( @@ -814,16 +824,6 @@ mod tests { ); } - fn parse_wallet_args(args: &str) -> (Options, subcommand::wallet::WalletCommand) { - match Arguments::try_parse_from(args.split_whitespace()) { - Ok(arguments) => match arguments.subcommand { - Subcommand::Wallet(wallet) => (arguments.options, wallet), - subcommand => panic!("unexpected subcommand: {subcommand:?}"), - }, - Err(err) => panic!("error parsing arguments: {err}"), - } - } - #[test] fn wallet_flag_overrides_default_name() { let (_, wallet) = parse_wallet_args("ord wallet create"); From 75986efa9d46b5eb32222b31fafe25a41c7c4602 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 15:26:13 -0800 Subject: [PATCH 34/35] Amend --- src/options.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/options.rs b/src/options.rs index 0263ffe910..c892a0715c 100644 --- a/src/options.rs +++ b/src/options.rs @@ -15,7 +15,7 @@ pub struct Options { pub(crate) bitcoin_rpc_pass: Option, #[arg(long, help = "Authenticate to Bitcoin Core RPC as .")] pub(crate) bitcoin_rpc_user: Option, - #[arg(long = "chain", value_enum, help = "Use . [default: maiinnet]")] + #[arg(long = "chain", value_enum, help = "Use . [default: mainnet]")] pub(crate) chain_argument: Option, #[arg(long, help = "Load configuration from .")] pub(crate) config: Option, From 65462ff90cad379066f5f96d82bdcadcf4a1e7e0 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 27 Feb 2024 15:35:00 -0800 Subject: [PATCH 35/35] Modify --- tests/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/settings.rs b/tests/settings.rs index caf7872cca..5f3f932b7e 100644 --- a/tests/settings.rs +++ b/tests/settings.rs @@ -40,7 +40,7 @@ fn config_not_found_error_message() { let config = tempdir.path().join("ord.yaml"); CommandBuilder::new(format!("--config {} settings", config.to_str().unwrap())) - .stderr_regex("error: failed to open config file `.*/ord.yaml`\nbecause:.*") + .stderr_regex("error: failed to open config file `.*ord.yaml`\nbecause:.*") .expected_exit_code(1) .run_and_extract_stdout(); }