From 33dfea0723e0ab9f847b5e95cef7d9495327d0d1 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 17:48:08 -0800 Subject: [PATCH 01/11] Overhaul settings --- README.md | 10 +- docs/src/guides/sat-hunting.md | 2 +- docs/src/guides/settings.md | 53 +- ord.yaml | 27 +- src/arguments.rs | 6 +- src/chain.rs | 10 +- src/config.rs | 20 - src/index.rs | 32 +- src/index/fetcher.rs | 8 +- src/index/testing.rs | 7 +- src/index/updater.rs | 6 +- src/inscriptions.rs | 2 +- src/lib.rs | 5 +- src/options.rs | 99 ++-- src/settings.rs | 978 ++++++++++++++++----------------- src/subcommand/env.rs | 10 +- src/subcommand/server.rs | 55 +- tests/command_builder.rs | 26 +- tests/expected.rs | 10 +- tests/lib.rs | 1 + tests/server.rs | 10 +- tests/settings.rs | 71 ++- tests/test_server.rs | 4 +- tests/wallet/authentication.rs | 6 +- 24 files changed, 724 insertions(+), 734 deletions(-) delete mode 100644 src/config.rs diff --git a/README.md b/README.md index c13455623e..6189b16dc6 100644 --- a/README.md +++ b/README.md @@ -216,22 +216,22 @@ Alternatively, `ord` can be supplied with a username and password on the command line: ``` -ord --bitcoin-rpc-user foo --bitcoin-rpc-pass bar server +ord --bitcoin-username foo --bitcoin-password bar server ``` Using environment variables: ``` -export ORD_BITCOIN_RPC_USER=foo -export ORD_BITCOIN_RPC_PASS=bar +export ORD_BITCOIN_USERNAME=foo +export ORD_BITCOIN_PASSWORD=bar ord server ``` Or in the config file: ```yaml -bitcoin_rpc_user: foo -bitcoin_rpc_pass: bar +bitcoin_username: foo +bitcoin_password: bar ``` Logging diff --git a/docs/src/guides/sat-hunting.md b/docs/src/guides/sat-hunting.md index def8331dbb..5bc93772c4 100644 --- a/docs/src/guides/sat-hunting.md +++ b/docs/src/guides/sat-hunting.md @@ -67,7 +67,7 @@ wallet is named `foo`: 2. Display any rare ordinals wallet `foo`'s UTXOs: ```sh - ord --wallet foo --index-sats wallet sats + ord --index-sats wallet --wallet foo sats ``` ### Searching for Rare Ordinals in a Non-Bitcoin Core Wallet diff --git a/docs/src/guides/settings.md b/docs/src/guides/settings.md index aadded39ea..e8e615a83d 100644 --- a/docs/src/guides/settings.md +++ b/docs/src/guides/settings.md @@ -1,27 +1,48 @@ Settings ======== -`ord` can be configured with command line options, environment variables, a +`ord` can be configured with the command line, 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 command line take precedence over environment variables, which take +precedence over the configuration file, which takes precedence over defaults. -The path to the configuration can be given with `--config `. `ord` -will error if `` doesn't exist. The path to a configuration +The path to the configuration file 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. +`/ord.yaml` does not exist. -All settings can be configured with command line options, but not all settings -can yet be configured with environmnet variables or a configuration file. +For a setting named `--setting-name` on the command line, the environment +variable will be named `ORD_SETTING_NAME`, and the config file field will be +named `setting_name`. For example, the data directory can be configured with +`--data-dir` on the command line, the `ORD_DATA_DIR` environment variable, or +`data_dir` in the config file. -`ord`'s configuration can be viewd as JSON with `ord settings`. +See `ord --help` for documentation of all the settings. -| 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 | +`ord`'s current configuration can be viewed as JSON with the `ord settings` +command. + +Hiding Inscription Content +-------------------------- + +Inscription content can be selectively prevented from being served by `ord +server`. + +Unlike other settings, this can only be configured with the configuration file +or environment variables. + +To hide inscriptions with an environment variable: + +``` +export ORD_HIDDEN='6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0' +``` + +Or with the configuration file: + +```yaml +hidden: +- 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 +- 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0 +``` diff --git a/ord.yaml b/ord.yaml index 5412955b67..ebdd76f9d6 100644 --- a/ord.yaml +++ b/ord.yaml @@ -1,12 +1,25 @@ -# Example Config +# example config +bitcoin_data_dir: /var/lib/bitcoin +bitcoin_password: bar +bitcoin_url: https://localhost:8000 +bitcoin_username: foo chain: mainnet - -# use username `bar` and password `foo` for bitcoind RPC calls -bitcoin_rpc_user: bar -bitcoin_rpc_pass: foo - -# prevent `ord server` from serving the content of the inscriptions below +commit_interval: 10000 +cookie_file: /var/lib/bitcoin/.cookie +data_dir: /var/lib/ord +first_inscription_height: 100 +height_limit: 1000 hidden: - 6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 - 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0 +index: /var/lib/ord/index.redb +index_cache_size: 1000000000 +index_runes: true +index_sats: true +index_spent_sats: true +index_transactions: true +integration_test: true +no_index_inscriptions: true +server_password: bar +server_username: foo diff --git a/src/arguments.rs b/src/arguments.rs index 72ec0eb97b..2e9b0bd2da 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -43,10 +43,6 @@ impl Arguments { ); } - let config = self.options.config()?; - - self - .subcommand - .run(Settings::new(self.options, env, config)?) + self.subcommand.run(Settings::load(self.options)?) } } diff --git a/src/chain.rs b/src/chain.rs index c1b9404dfd..fe925a2d63 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -84,12 +84,12 @@ impl Chain { Address::from_script(script, self.network()) } - pub(crate) fn join_with_data_dir(self, data_dir: &Path) -> PathBuf { + pub(crate) fn join_with_data_dir(self, data_dir: impl AsRef) -> PathBuf { match self { - Self::Mainnet => data_dir.to_owned(), - Self::Testnet => data_dir.join("testnet3"), - Self::Signet => data_dir.join("signet"), - Self::Regtest => data_dir.join("regtest"), + Self::Mainnet => data_dir.as_ref().to_owned(), + Self::Testnet => data_dir.as_ref().join("testnet3"), + Self::Signet => data_dir.as_ref().join("signet"), + Self::Regtest => data_dir.as_ref().join("regtest"), } } } diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 6ef1e95ce2..0000000000 --- a/src/config.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::*; - -#[derive(Deserialize, Default, PartialEq, Debug, Clone)] -#[serde(deny_unknown_fields)] -pub(crate) struct Config { - pub(crate) bitcoin_rpc_pass: Option, - pub(crate) bitcoin_rpc_user: Option, - pub(crate) chain: Option, - pub(crate) hidden: Option>, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[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 73fcfbe49f..460df7e901 100644 --- a/src/index.rs +++ b/src/index.rs @@ -230,10 +230,7 @@ impl Index { ) -> Result { let client = settings.bitcoin_rpc_client(None)?; - let path = settings - .index - .clone() - .unwrap_or_else(|| settings.data_dir().clone().join("index.redb")); + let path = settings.index().to_owned(); if let Err(err) = fs::create_dir_all(path.parent().unwrap()) { bail!( @@ -242,16 +239,9 @@ impl Index { ); } - let db_cache_size = match settings.db_cache_size { - Some(db_cache_size) => db_cache_size, - None => { - let mut sys = System::new(); - sys.refresh_memory(); - usize::try_from(sys.total_memory() / 4)? - } - }; + let index_cache_size = settings.index_cache_size(); - log::info!("Setting DB cache size to {} bytes", db_cache_size); + log::info!("Setting index cache size to {} bytes", index_cache_size); let durability = if cfg!(test) { redb::Durability::None @@ -262,7 +252,7 @@ impl Index { let index_path = path.clone(); let once = Once::new(); let progress_bar = Mutex::new(None); - let integration_test = settings.integration_test; + 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())); @@ -284,7 +274,7 @@ impl Index { }; let database = match Database::builder() - .set_cache_size(db_cache_size) + .set_cache_size(index_cache_size) .set_repair_callback(repair_callback) .open(&path) { @@ -319,7 +309,7 @@ impl Index { if error.kind() == io::ErrorKind::NotFound => { let database = Database::builder() - .set_cache_size(db_cache_size) + .set_cache_size(index_cache_size) .create(&path)?; let mut tx = database.begin_write()?; @@ -350,7 +340,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.index_sats { + if settings.index_sats() { outpoint_to_sat_ranges.insert(&OutPoint::null().store(), [].as_slice())?; } @@ -363,19 +353,19 @@ impl Index { Self::set_statistic( &mut statistics, Statistic::IndexSats, - u64::from(settings.index_sats || settings.index_spent_sats), + u64::from(settings.index_sats() || settings.index_spent_sats()), )?; Self::set_statistic( &mut statistics, Statistic::IndexSpentSats, - u64::from(settings.index_spent_sats), + u64::from(settings.index_spent_sats()), )?; Self::set_statistic( &mut statistics, Statistic::IndexTransactions, - u64::from(settings.index_transactions), + u64::from(settings.index_transactions()), )?; Self::set_statistic(&mut statistics, Statistic::Schema, SCHEMA_VERSION)?; @@ -413,7 +403,7 @@ impl Index { event_sender, first_inscription_height: settings.first_inscription_height(), genesis_block_coinbase_transaction, - height_limit: settings.height_limit, + height_limit: settings.height_limit(), index_runes, index_sats, index_spent_sats, diff --git a/src/index/fetcher.rs b/src/index/fetcher.rs index 6e15160faa..175119f417 100644 --- a/src/index/fetcher.rs +++ b/src/index/fetcher.rs @@ -28,15 +28,15 @@ impl Fetcher { pub(crate) fn new(settings: &Settings) -> Result { let client = Client::new(); - let url = if settings.rpc_url(None).starts_with("http://") { - settings.rpc_url(None) + let url = if settings.bitcoin_url(None).starts_with("http://") { + settings.bitcoin_url(None) } else { - "http://".to_string() + &settings.rpc_url(None) + "http://".to_string() + &settings.bitcoin_url(None) }; let url = Uri::try_from(&url).map_err(|e| anyhow!("Invalid rpc url {url}: {e}"))?; - let (user, password) = settings.auth()?.get_user_pass()?; + let (user, password) = settings.bitcoin_credentials()?.get_user_pass()?; let auth = format!("{}:{}", user.unwrap(), password.unwrap()); let auth = format!( "Basic {}", diff --git a/src/index/testing.rs b/src/index/testing.rs index 12b271c50e..627dac89df 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -23,7 +23,7 @@ impl ContextBuilder { let command: Vec = vec![ "ord".into(), - "--rpc-url".into(), + "--bitcoin-url".into(), rpc_server.url().into(), "--data-dir".into(), tempdir.path().into(), @@ -33,7 +33,10 @@ impl ContextBuilder { ]; 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)?; + let index = Index::open_with_event_sender( + &Settings::from_options(options).or_defaults().unwrap(), + self.event_sender, + )?; index.update().unwrap(); Ok(Context { diff --git a/src/index/updater.rs b/src/index/updater.rs index 509a73dc2c..73a1327f5c 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 - || self.index.settings.integration_test + || self.index.settings.integration_test() { None } else { @@ -98,7 +98,7 @@ impl<'index> Updater<'index> { uncommitted += 1; - if uncommitted == self.index.settings.commit_interval { + if uncommitted == self.index.settings.commit_interval() { self.commit(wtx, value_cache)?; value_cache = HashMap::new(); uncommitted = 0; @@ -326,7 +326,7 @@ impl<'index> Updater<'index> { let mut outpoint_to_value = wtx.open_table(OUTPOINT_TO_VALUE)?; let index_inscriptions = self.height >= self.index.first_inscription_height - && !self.index.settings.no_index_inscriptions; + && self.index.settings.index_inscriptions(); if index_inscriptions { // Send all missing input outpoints to be fetched right away diff --git a/src/inscriptions.rs b/src/inscriptions.rs index 913609a1a1..1c32ff8ae6 100644 --- a/src/inscriptions.rs +++ b/src/inscriptions.rs @@ -9,7 +9,7 @@ pub use self::{envelope::Envelope, inscription::Inscription, inscription_id::Ins mod charm; mod envelope; mod inscription; -mod inscription_id; +pub(crate) mod inscription_id; pub(crate) mod media; mod tag; pub(crate) mod teleburn; diff --git a/src/lib.rs b/src/lib.rs index 0a0d638cca..5775ae7e85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,9 +15,9 @@ use { self::{ arguments::Arguments, blocktime::Blocktime, - config::Config, decimal::Decimal, inscriptions::{ + inscription_id, media::{self, ImageRendering, Media}, teleburn, Charm, ParsedEnvelope, }, @@ -107,7 +107,6 @@ pub mod api; pub mod arguments; mod blocktime; pub mod chain; -mod config; mod decimal; mod fee_rate; pub mod index; @@ -186,7 +185,7 @@ pub fn parse_ord_server_args(args: &str) -> (Settings, subcommand::server::Serve match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { Subcommand::Server(server) => ( - Settings::new( + Settings::merge( arguments.options, vec![("INTEGRATION_TEST".into(), "1".into())] .into_iter() diff --git a/src/options.rs b/src/options.rs index 0c40e758a4..29233316b1 100644 --- a/src/options.rs +++ b/src/options.rs @@ -7,35 +7,30 @@ use super::*; .args(&["chain_argument", "signet", "regtest", "testnet"]), ))] pub struct Options { - #[arg(long, help = "Minify JSON output.")] - pub(crate) minify: bool, #[arg(long, help = "Load Bitcoin Core data dir from .")] pub(crate) bitcoin_data_dir: Option, - #[arg(long, help = "Authenticate to Bitcoin Core RPC with .")] - pub(crate) bitcoin_rpc_pass: Option, - #[arg(long, help = "Authenticate to Bitcoin Core RPC as .")] - pub(crate) bitcoin_rpc_user: Option, + #[arg( + long, + help = "Authenticate to Bitcoin Core RPC with ." + )] + pub(crate) bitcoin_password: Option, + #[arg(long, help = "Authenticate to Bitcoin Core RPC as .")] + pub(crate) bitcoin_username: Option, #[arg(long = "chain", value_enum, help = "Use . [default: mainnet]")] pub(crate) chain_argument: Option, + #[arg( + long, + help = "Commit to index every blocks. [default: 5000]" + )] + pub(crate) commit_interval: Option, #[arg(long, help = "Load configuration from .")] pub(crate) config: Option, #[arg(long, help = "Load configuration from .")] pub(crate) config_dir: Option, #[arg(long, help = "Load Bitcoin Core RPC cookie file from .")] pub(crate) cookie_file: Option, - #[arg(long, help = "Store index in .", default_value_os_t = Options::default_data_dir())] - pub(crate) data_dir: PathBuf, - #[arg( - long, - help = "Set index cache to bytes. By default takes 1/4 of available RAM." - )] - pub(crate) db_cache_size: Option, - #[arg( - long, - default_value = "5000", - help = "Commit to index every blocks." - )] - pub(crate) commit_interval: usize, + #[arg(long, help = "Store index in .")] + pub(crate) data_dir: Option, #[arg( long, help = "Don't look for inscriptions below ." @@ -45,6 +40,11 @@ pub struct Options { pub(crate) height_limit: Option, #[arg(long, help = "Use index at .")] pub(crate) index: Option, + #[arg( + long, + help = "Set index cache size to bytes. [default: 1/4 available RAM]" + )] + pub(crate) index_cache_size: Option, #[arg( long, help = "Track location of runes. RUNES ARE IN AN UNFINISHED PRE-ALPHA STATE AND SUBJECT TO CHANGE AT ANY TIME." @@ -56,6 +56,10 @@ pub struct Options { pub(crate) index_spent_sats: bool, #[arg(long, help = "Store transactions in index.")] pub(crate) index_transactions: bool, + #[arg(long, help = "Run in integration test mode.")] + pub(crate) integration_test: bool, + #[arg(long, help = "Minify JSON output.")] + pub(crate) minify: bool, #[arg( long, short, @@ -65,59 +69,20 @@ pub struct Options { pub(crate) no_index_inscriptions: bool, #[arg( long, - requires = "username", - help = "Require basic HTTP authentication with . Credentials are sent in cleartext. Consider using authentication in conjunction with HTTPS." + help = "Require basic HTTP authentication with . Credentials are sent in cleartext. Consider using authentication in conjunction with HTTPS." )] - pub(crate) password: Option, + pub(crate) server_password: Option, + #[arg( + long, + help = "Require basic HTTP authentication with . Credentials are sent in cleartext. Consider using authentication in conjunction with HTTPS." + )] + pub(crate) server_username: Option, #[arg(long, short, help = "Use regtest. Equivalent to `--chain regtest`.")] pub(crate) regtest: bool, - #[arg(long, help = "Connect to Bitcoin Core RPC at .")] - pub(crate) rpc_url: Option, + #[arg(long, help = "Connect to Bitcoin Core RPC at .")] + pub(crate) bitcoin_url: Option, #[arg(long, short, help = "Use signet. Equivalent to `--chain signet`.")] pub(crate) signet: bool, #[arg(long, short, help = "Use testnet. Equivalent to `--chain testnet`.")] pub(crate) testnet: bool, - #[arg( - long, - requires = "password", - help = "Require basic HTTP authentication with . Credentials are sent in cleartext. Consider using authentication in conjunction with HTTPS." - )] - pub(crate) username: Option, -} - -impl Options { - fn default_data_dir() -> PathBuf { - dirs::data_dir() - .map(|dir| dir.join("ord")) - .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()) - } } diff --git a/src/settings.rs b/src/settings.rs index 9b4a55da8c..50170d806c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,123 +1,350 @@ use {super::*, bitcoincore_rpc::Auth}; -#[derive(Default, Debug, Clone, Serialize)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(default, deny_unknown_fields)] 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) commit_interval: usize, - pub(crate) first_inscription_height: Option, - pub(crate) height_limit: Option, - pub(crate) hidden: HashSet, - 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) integration_test: bool, - pub(crate) no_index_inscriptions: bool, - pub(crate) rpc_url: Option, + bitcoin_data_dir: Option, + bitcoin_password: Option, + bitcoin_url: Option, + bitcoin_username: Option, + chain: Option, + commit_interval: Option, + cookie_file: Option, + data_dir: Option, + first_inscription_height: Option, + height_limit: Option, + hidden: Option>, + index: Option, + index_cache_size: Option, + index_runes: bool, + index_sats: bool, + index_spent_sats: bool, + index_transactions: bool, + integration_test: bool, + no_index_inscriptions: bool, + server_password: Option, + server_username: Option, } -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 load(options: Options) -> Result { + let config_path = match &options.config { + Some(path) => Some(path.into()), + None => match &options.config_dir { + Some(dir) => { + let path = dir.join("ord.yaml"); + if path.exists() { + Some(path) + } else { + None + } + } + None => None, + }, + }; + + let config = match config_path { + Some(config_path) => serde_yaml::from_reader(File::open(&config_path).context(anyhow!( + "failed to open config file `{}`", + config_path.display() + ))?) + .context(anyhow!( + "failed to deserialize config file `{}`", + config_path.display() + ))?, + None => Settings::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() + ) + })?, + ); + } + + Self::merge(options, env, config) } -} -impl Settings { - pub(crate) fn new( + pub(crate) fn merge( options: Options, env: BTreeMap, - config: Config, + config: Settings, ) -> Result { - let chain = Self::setting( - &env, - 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, - )?; - - let rpc_user = Self::setting_opt( - &env, - options.bitcoin_rpc_user.as_deref(), - Some("BITCOIN_RPC_USER"), - config.bitcoin_rpc_user.as_deref(), - ); + let settings = Settings::from_options(options) + .or(Settings::from_env(env)?) + .or(config) + .or_defaults()?; + + match (&settings.bitcoin_username, &settings.bitcoin_password) { + (None, Some(_rpc_pass)) => bail!("no bitcoin RPC username specified"), + (Some(_rpc_user), None) => bail!("no bitcoin RPC password specified"), + _ => {} + }; - let rpc_pass = Self::setting_opt( - &env, - options.bitcoin_rpc_pass.as_deref(), - Some("BITCOIN_RPC_PASS"), - config.bitcoin_rpc_pass.as_deref(), - ); + match (&settings.server_username, &settings.server_password) { + (None, Some(_rpc_pass)) => bail!("no username specified"), + (Some(_rpc_user), None) => bail!("no password specified"), + _ => {} + }; - let integration_test = Self::setting_opt(&env, None, Some("INTEGRATION_TEST"), None) - .map(|value| !value.is_empty()) - .unwrap_or_default(); + Ok(settings) + } - 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"), - (Some(_rpc_user), None) => bail!("no bitcoind rpc password specified"), - _ => None, - }; + pub(crate) fn or(self, source: Settings) -> Self { + Self { + bitcoin_data_dir: self.bitcoin_data_dir.or(source.bitcoin_data_dir), + bitcoin_password: self.bitcoin_password.or(source.bitcoin_password), + bitcoin_url: self.bitcoin_url.or(source.bitcoin_url), + bitcoin_username: self.bitcoin_username.or(source.bitcoin_username), + chain: self.chain.or(source.chain), + commit_interval: self.commit_interval.or(source.commit_interval), + cookie_file: self.cookie_file.or(source.cookie_file), + data_dir: self.data_dir.or(source.data_dir), + first_inscription_height: self + .first_inscription_height + .or(source.first_inscription_height), + height_limit: self.height_limit.or(source.height_limit), + hidden: Some( + self + .hidden + .iter() + .flatten() + .chain(source.hidden.iter().flatten()) + .cloned() + .collect(), + ), + index: self.index.or(source.index), + index_cache_size: self.index_cache_size.or(source.index_cache_size), + index_runes: self.index_runes || source.index_runes, + index_sats: self.index_sats || source.index_sats, + index_spent_sats: self.index_spent_sats || source.index_spent_sats, + index_transactions: self.index_transactions || source.index_transactions, + integration_test: self.integration_test || source.integration_test, + no_index_inscriptions: self.no_index_inscriptions || source.no_index_inscriptions, + server_password: self.server_password.or(source.server_password), + server_username: self.server_username.or(source.server_username), + } + } - Ok(Self { - auth, + pub(crate) fn from_options(options: Options) -> Self { + Self { bitcoin_data_dir: options.bitcoin_data_dir, - chain, + bitcoin_password: options.bitcoin_password, + bitcoin_url: options.bitcoin_url, + bitcoin_username: options.bitcoin_username, + chain: options + .signet + .then_some(Chain::Signet) + .or(options.regtest.then_some(Chain::Regtest)) + .or(options.testnet.then_some(Chain::Testnet)) + .or(options.chain_argument), + commit_interval: options.commit_interval, cookie_file: options.cookie_file, - credentials: options.username.zip(options.password), data_dir: options.data_dir, - db_cache_size: options.db_cache_size, - commit_interval: options.commit_interval, first_inscription_height: options.first_inscription_height, height_limit: options.height_limit, - hidden: config.hidden.unwrap_or_default(), + hidden: None, index: options.index, + index_cache_size: options.index_cache_size, index_runes: options.index_runes, index_sats: options.index_sats, index_spent_sats: options.index_spent_sats, index_transactions: options.index_transactions, - integration_test, + integration_test: options.integration_test, no_index_inscriptions: options.no_index_inscriptions, - rpc_url: options.rpc_url, + server_password: options.server_password, + server_username: options.server_username, + } + } + + pub(crate) fn from_env(env: BTreeMap) -> Result { + let get_bool = |key| { + env + .get(key) + .map(|value| !value.is_empty()) + .unwrap_or_default() + }; + + let get_string = |key| env.get(key).cloned(); + + let get_path = |key| env.get(key).map(PathBuf::from); + + let get_chain = |key| { + env + .get(key) + .map(|chain| chain.parse::()) + .transpose() + .with_context(|| format!("failed to parse environment variable ORD_{key} as chain")) + }; + + let inscriptions = |key| { + env + .get(key) + .map(|inscriptions| { + inscriptions + .split_whitespace() + .map(|inscription_id| inscription_id.parse::()) + .collect::, inscription_id::ParseError>>() + }) + .transpose() + .with_context(|| { + format!("failed to parse environment variable ORD_{key} as inscription list") + }) + }; + + let get_u32 = |key| { + env + .get(key) + .map(|int| int.parse::()) + .transpose() + .with_context(|| format!("failed to parse environment variable ORD_{key} as u32")) + }; + let get_usize = |key| { + env + .get(key) + .map(|int| int.parse::()) + .transpose() + .with_context(|| format!("failed to parse environment variable ORD_{key} as usize")) + }; + + Ok(Self { + bitcoin_data_dir: get_path("BITCOIN_DATA_DIR"), + bitcoin_password: get_string("BITCOIN_PASSWORD"), + bitcoin_url: get_string("BITCOIN_URL"), + bitcoin_username: get_string("BITCOIN_USERNAME"), + chain: get_chain("CHAIN")?, + commit_interval: get_usize("COMMIT_INTERVAL")?, + cookie_file: get_path("COOKIE_FILE"), + data_dir: get_path("DATA_DIR"), + first_inscription_height: get_u32("FIRST_INSCRIPTION_HEIGHT")?, + height_limit: get_u32("HEIGHT_LIMIT")?, + hidden: inscriptions("HIDDEN")?, + index: get_path("INDEX"), + index_cache_size: get_usize("INDEX_CACHE_SIZE")?, + index_runes: get_bool("INDEX_RUNES"), + index_sats: get_bool("INDEX_SATS"), + index_spent_sats: get_bool("INDEX_SPENT_SATS"), + index_transactions: get_bool("INDEX_TRANSACTIONS"), + integration_test: get_bool("INTEGRATION_TEST"), + no_index_inscriptions: get_bool("NO_INDEX_INSCRIPTIONS"), + server_password: get_string("SERVER_PASSWORD"), + server_username: get_string("SERVER_USERNAME"), }) } - pub(crate) fn auth(&self) -> Result { - if let Some(auth) = &self.auth { - Ok(auth.clone()) + pub(crate) fn or_defaults(self) -> Result { + let chain = self.chain.unwrap_or_default(); + + let bitcoin_data_dir = match &self.bitcoin_data_dir { + Some(bitcoin_data_dir) => bitcoin_data_dir.clone(), + None => { + 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 cookie_file = match self.cookie_file { + Some(cookie_file) => cookie_file, + None => chain.join_with_data_dir(&bitcoin_data_dir).join(".cookie"), + }; + + let data_dir = chain.join_with_data_dir(match &self.data_dir { + Some(data_dir) => data_dir.clone(), + None => dirs::data_dir() + .context("could not get data dir")? + .join("ord"), + }); + + let index = match &self.index { + Some(path) => path.clone(), + None => data_dir.join("index.redb"), + }; + + Ok(Self { + bitcoin_data_dir: Some(bitcoin_data_dir), + bitcoin_password: self.bitcoin_password, + bitcoin_url: Some( + self + .bitcoin_url + .clone() + .unwrap_or_else(|| format!("127.0.0.1:{}", chain.default_rpc_port())), + ), + bitcoin_username: self.bitcoin_username, + chain: Some(chain), + commit_interval: Some(self.commit_interval.unwrap_or(5000)), + cookie_file: Some(cookie_file), + data_dir: Some(data_dir), + first_inscription_height: Some(if self.integration_test { + 0 + } else { + self + .first_inscription_height + .unwrap_or_else(|| chain.first_inscription_height()) + }), + height_limit: self.height_limit, + hidden: self.hidden, + index: Some(index), + index_cache_size: Some(match self.index_cache_size { + Some(index_cache_size) => index_cache_size, + None => { + let mut sys = System::new(); + sys.refresh_memory(); + usize::try_from(sys.total_memory() / 4)? + } + }), + index_runes: self.index_runes, + index_sats: self.index_sats, + index_spent_sats: self.index_spent_sats, + index_transactions: self.index_transactions, + integration_test: self.integration_test, + no_index_inscriptions: self.no_index_inscriptions, + server_password: self.server_password, + server_username: self.server_username, + }) + } + + pub(crate) fn bitcoin_credentials(&self) -> Result { + if let Some((user, pass)) = &self + .bitcoin_username + .as_ref() + .zip(self.bitcoin_password.as_ref()) + { + Ok(Auth::UserPass((*user).clone(), (*pass).clone())) } else { Ok(Auth::CookieFile(self.cookie_file()?)) } } pub(crate) fn bitcoin_rpc_client(&self, wallet: Option) -> Result { - let rpc_url = self.rpc_url(wallet); + let rpc_url = self.bitcoin_url(wallet); - let auth = self.auth()?; + let bitcoin_credentials = self.bitcoin_credentials()?; - log::info!("Connecting to Bitcoin Core at {}", self.rpc_url(None)); + log::info!("Connecting to Bitcoin Core at {}", self.bitcoin_url(None)); - if let Auth::CookieFile(cookie_file) = &auth { + if let Auth::CookieFile(cookie_file) = &bitcoin_credentials { log::info!( "Using credentials from cookie file at `{}`", cookie_file.display() @@ -130,7 +357,7 @@ impl Settings { ); } - let client = Client::new(&rpc_url, auth) + let client = Client::new(&rpc_url, bitcoin_credentials) .with_context(|| format!("failed to connect to Bitcoin Core RPC at `{rpc_url}`"))?; let mut checks = 0; @@ -169,7 +396,11 @@ impl Settings { } pub(crate) fn chain(&self) -> Chain { - self.chain + self.chain.unwrap() + } + + pub(crate) fn commit_interval(&self) -> usize { + self.commit_interval.unwrap() } pub(crate) fn cookie_file(&self) -> Result { @@ -189,103 +420,84 @@ impl Settings { .join("Bitcoin") }; - let path = self.chain().join_with_data_dir(&path); + let path = self.chain().join_with_data_dir(path); Ok(path.join(".cookie")) } pub(crate) fn credentials(&self) -> Option<(&str, &str)> { self - .credentials - .as_ref() - .map(|(username, password)| (username.as_ref(), password.as_ref())) + .server_username + .as_deref() + .zip(self.server_password.as_deref()) } pub(crate) fn data_dir(&self) -> PathBuf { - self.chain().join_with_data_dir(&self.data_dir) + self.data_dir.as_ref().unwrap().into() } pub(crate) fn first_inscription_height(&self) -> u32 { - if self.integration_test { - 0 - } else { - self - .first_inscription_height - .unwrap_or_else(|| self.chain().first_inscription_height()) - } + self.first_inscription_height.unwrap() } pub(crate) fn first_rune_height(&self) -> u32 { if self.integration_test { 0 } else { - self.chain().first_rune_height() + self.chain.unwrap().first_rune_height() } } - pub(crate) fn index_runes(&self) -> bool { - self.index_runes && self.chain() != Chain::Mainnet + pub(crate) fn height_limit(&self) -> Option { + self.height_limit } - pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { - self.hidden.contains(&inscription_id) + pub(crate) fn index(&self) -> &Path { + self.index.as_ref().unwrap() } - 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())); + pub(crate) fn index_inscriptions(&self) -> bool { + !self.no_index_inscriptions + } - match wallet_name { - Some(wallet_name) => format!("{base_url}/wallet/{wallet_name}"), - None => format!("{base_url}/"), - } + pub(crate) fn index_runes(&self) -> bool { + self.index_runes && self.chain() != Chain::Mainnet } - fn setting>( - env: &BTreeMap, - arg_value: Option, - env_key: Option<&'static str>, - config_value: Option, - default_value: T, - ) -> Result { - if let Some(arg_value) = arg_value { - return Ok(arg_value); - } + pub(crate) fn index_cache_size(&self) -> usize { + self.index_cache_size.unwrap() + } - if let Some(env_key) = env_key { - if let Some(env_value) = env.get(env_key) { - return env_value - .parse() - .with_context(|| anyhow!("failed to parse {env_key}")); - } - } + pub(crate) fn index_sats(&self) -> bool { + self.index_sats + } - if let Some(config_value) = config_value { - return Ok(config_value); - } + pub(crate) fn index_spent_sats(&self) -> bool { + self.index_spent_sats + } - Ok(default_value) + pub(crate) fn index_transactions(&self) -> bool { + self.index_transactions } - fn setting_opt( - env: &BTreeMap, - arg_value: Option<&str>, - env_key: Option<&'static str>, - config_value: Option<&str>, - ) -> Option { - if let Some(arg_value) = arg_value { - return Some(arg_value.into()); - } + pub(crate) fn integration_test(&self) -> bool { + self.integration_test + } - if let Some(env_key) = env_key { - if let Some(env_value) = env.get(env_key) { - return Some(env_value.into()); - } - } + pub(crate) fn is_hidden(&self, inscription_id: InscriptionId) -> bool { + self + .hidden + .as_ref() + .map(|hidden| hidden.contains(&inscription_id)) + .unwrap_or_default() + } - config_value.map(str::to_string) + pub(crate) fn bitcoin_url(&self, wallet_name: Option) -> String { + let base_url = self.bitcoin_url.as_ref().unwrap(); + match wallet_name { + Some(wallet_name) => format!("{base_url}/wallet/{wallet_name}"), + None => format!("{base_url}/"), + } } } @@ -293,19 +505,24 @@ impl Settings { mod tests { use super::*; - fn settings(args: &[&str]) -> Settings { - Settings::new( - Options::try_parse_from(args).unwrap(), - Default::default(), - Default::default(), - ) - .unwrap() + fn parse(args: &[&str]) -> Settings { + let args = iter::once("ord") + .chain(args.iter().copied()) + .collect::>(); + Settings::from_options(Options::try_parse_from(args).unwrap()) + .or_defaults() + .unwrap() } - fn parse_wallet_args(args: &str) -> (Options, subcommand::wallet::WalletCommand) { + fn wallet(args: &str) -> (Settings, subcommand::wallet::WalletCommand) { match Arguments::try_parse_from(args.split_whitespace()) { Ok(arguments) => match arguments.subcommand { - Subcommand::Wallet(wallet) => (arguments.options, wallet), + Subcommand::Wallet(wallet) => ( + Settings::from_options(arguments.options) + .or_defaults() + .unwrap(), + wallet, + ), subcommand => panic!("unexpected subcommand: {subcommand:?}"), }, Err(err) => panic!("error parsing arguments: {err}"), @@ -315,9 +532,9 @@ mod tests { #[test] fn auth_missing_rpc_pass_is_an_error() { assert_eq!( - Settings::new( + Settings::merge( Options { - bitcoin_rpc_user: Some("foo".into()), + bitcoin_username: Some("foo".into()), ..Default::default() }, Default::default(), @@ -325,16 +542,16 @@ mod tests { ) .unwrap_err() .to_string(), - "no bitcoind rpc password specified" + "no bitcoin RPC password specified" ); } #[test] fn auth_missing_rpc_user_is_an_error() { assert_eq!( - Settings::new( + Settings::merge( Options { - bitcoin_rpc_pass: Some("foo".into()), + bitcoin_password: Some("foo".into()), ..Default::default() }, Default::default(), @@ -342,39 +559,26 @@ mod tests { ) .unwrap_err() .to_string(), - "no bitcoind rpc user specified" + "no bitcoin RPC username specified" ); } #[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() - }, - Default::default(), - Default::default(), - ) - .unwrap() - .auth() - .unwrap(), + parse(&["--bitcoin-username=foo", "--bitcoin-password=bar"]) + .bitcoin_credentials() + .unwrap(), Auth::UserPass("foo".into(), "bar".into()) ); } #[test] fn auth_with_cookie_file() { - let settings = Options { - cookie_file: Some("/var/lib/Bitcoin/.cookie".into()), - ..Default::default() - } - .settings() - .unwrap(); assert_eq!( - settings.auth().unwrap(), + parse(&["--cookie-file=/var/lib/Bitcoin/.cookie"]) + .bitcoin_credentials() + .unwrap(), Auth::CookieFile("/var/lib/Bitcoin/.cookie".into()) ); } @@ -382,16 +586,11 @@ mod tests { #[test] fn cookie_file_does_not_exist_error() { assert_eq!( - Options { - cookie_file: Some("/foo/bar/baz/qux/.cookie".into()), - ..Default::default() - } - .settings() - .unwrap() - .bitcoin_rpc_client(None) - .map(|_| "") - .unwrap_err() - .to_string(), + parse(&["--cookie-file=/foo/bar/baz/qux/.cookie"]) + .bitcoin_rpc_client(None) + .err() + .unwrap() + .to_string(), "cookie file `/foo/bar/baz/qux/.cookie` does not exist" ); } @@ -402,11 +601,10 @@ mod tests { .network(Network::Testnet) .build(); - let settings = settings(&[ - "ord", + let settings = parse(&[ "--cookie-file", rpc_server.cookie_file().to_str().unwrap(), - "--rpc-url", + "--bitcoin-url", &rpc_server.url(), ]); @@ -416,106 +614,10 @@ mod tests { ); } - #[test] - fn setting() { - assert_eq!( - Settings::setting(&Default::default(), None, None, None, Chain::Mainnet).unwrap(), - Chain::Mainnet, - ); - - assert_eq!( - Settings::setting( - &Default::default(), - None, - None, - Some(Chain::Testnet), - Chain::Mainnet - ) - .unwrap(), - Chain::Testnet, - ); - - assert_eq!( - Settings::setting( - &vec![("CHAIN".to_string(), "signet".to_string())] - .into_iter() - .collect(), - None, - Some("CHAIN"), - Some(Chain::Testnet), - Chain::Mainnet - ) - .unwrap(), - Chain::Signet, - ); - - assert_eq!( - Settings::setting( - &vec![("CHAIN".to_string(), "signet".to_string())] - .into_iter() - .collect(), - Some(Chain::Regtest), - Some("CHAIN"), - Some(Chain::Testnet), - Chain::Mainnet - ) - .unwrap(), - Chain::Regtest, - ); - } - - #[test] - fn setting_opt() { - assert_eq!( - Settings::setting_opt(&Default::default(), None, None, None), - None - ); - - assert_eq!( - Settings::setting_opt(&Default::default(), None, None, Some("config")), - Some("config".into()), - ); - - assert_eq!( - Settings::setting_opt( - &vec![("env_key".into(), "env_value".into())] - .into_iter() - .collect(), - None, - Some("env_key"), - Some("config") - ), - 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") - ), - 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), + parse(&["--bitcoin-url=127.0.0.1:1234", "--chain=signet"]).bitcoin_url(None), "127.0.0.1:1234/" ); } @@ -523,45 +625,27 @@ mod tests { #[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(), + parse(&["--cookie-file=/foo/bar", "--chain=signet"]) + .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(); + let settings = parse(&[]); - assert_eq!(settings.rpc_url(None), "127.0.0.1:8332/"); + assert_eq!(settings.bitcoin_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(); + let settings = parse(&["--chain=signet"]); - assert_eq!(settings.rpc_url(None), "127.0.0.1:38332/"); + assert_eq!(settings.bitcoin_url(None), "127.0.0.1:38332/"); assert!(settings .cookie_file() @@ -577,15 +661,7 @@ mod tests { #[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(); + let cookie_file = parse(&[]).cookie_file().unwrap().display().to_string(); assert!(cookie_file.ends_with(if cfg!(target_os = "linux") { "/.bitcoin/.cookie" @@ -598,13 +674,7 @@ mod tests { #[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() + let cookie_file = parse(&["--chain=signet"]) .cookie_file() .unwrap() .display() @@ -621,19 +691,7 @@ mod tests { #[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() + let cookie_file = parse(&["--bitcoin-data-dir=foo", "--chain=signet"]) .cookie_file() .unwrap() .display() @@ -648,14 +706,7 @@ mod tests { #[test] fn mainnet_data_dir() { - let data_dir = Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .data_dir() - .display() - .to_string(); + let data_dir = parse(&[]).data_dir().display().to_string(); assert!( data_dir.ends_with(if cfg!(windows) { r"\ord" } else { "/ord" }), "{data_dir}" @@ -664,14 +715,7 @@ mod tests { #[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(); + let data_dir = parse(&["--chain=signet"]).data_dir().display().to_string(); assert!( data_dir.ends_with(if cfg!(windows) { r"\ord\signet" @@ -684,21 +728,10 @@ mod tests { #[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(); + let data_dir = parse(&["--chain=signet", "--data-dir=foo"]) + .data_dir() + .display() + .to_string(); assert!( data_dir.ends_with(if cfg!(windows) { r"foo\signet" @@ -712,14 +745,7 @@ mod tests { #[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(); + let data_dir = parse(&["--chain", alias]).data_dir().display().to_string(); assert!(data_dir.ends_with(suffix), "{data_dir}"); } @@ -764,138 +790,64 @@ mod tests { 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 - ); + assert_eq!(parse(&["--signet"]).chain(), Chain::Signet); + assert_eq!(parse(&["-s"]).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 - ); + assert_eq!(parse(&["--regtest"]).chain(), Chain::Regtest); + assert_eq!(parse(&["-r"]).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 - ); + assert_eq!(parse(&["--testnet"]).chain(), Chain::Testnet); + assert_eq!(parse(&["-t"]).chain(), Chain::Testnet); } #[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") + assert_eq!(wallet("ord wallet create").1.name, "ord"); + assert_eq!(wallet("ord wallet --name foo create").1.name, "foo") } #[test] fn uses_wallet_rpc() { - let (options, _) = parse_wallet_args("ord wallet --name foo balance"); + let (settings, _) = wallet("ord wallet --name foo balance"); assert_eq!( - options.settings().unwrap().rpc_url(Some("foo".into())), + settings.bitcoin_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)); + fn setting_index_cache_size() { + assert_eq!( + parse(&["--index-cache-size=16000000000",]).index_cache_size(), + 16000000000 + ); } #[test] fn setting_commit_interval() { let arguments = Arguments::try_parse_from(["ord", "--commit-interval", "500", "index", "update"]).unwrap(); - assert_eq!(arguments.options.commit_interval, 500); + assert_eq!(arguments.options.commit_interval, Some(500)); } #[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!(parse(&["--chain=signet", "--index-runes",]).index_runes()); - assert!( - !Arguments::try_parse_from(["ord", "--index-runes", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .index_runes() - ); + assert!(!parse(&["--index-runes"]).index_runes()); - assert!(!Arguments::try_parse_from(["ord", "index", "update"]) - .unwrap() - .options - .settings() - .unwrap() - .index_runes()); + assert!(!parse(&[]).index_runes()); } #[test] - fn chain_setting() { + fn merge() { assert_eq!( - Settings::new( + Settings::merge( Options { regtest: true, ..Default::default() @@ -903,7 +855,7 @@ mod tests { vec![("CHAIN".into(), "signet".into())] .into_iter() .collect(), - Config { + Settings { chain: Some(Chain::Testnet), ..Default::default() } @@ -914,12 +866,12 @@ mod tests { ); assert_eq!( - Settings::new( + Settings::merge( Default::default(), vec![("CHAIN".into(), "signet".into())] .into_iter() .collect(), - Config { + Settings { chain: Some(Chain::Testnet), ..Default::default() } @@ -930,10 +882,10 @@ mod tests { ); assert_eq!( - Settings::new( + Settings::merge( Default::default(), Default::default(), - Config { + Settings { chain: Some(Chain::Testnet), ..Default::default() } @@ -944,7 +896,7 @@ mod tests { ); assert_eq!( - Settings::new(Default::default(), Default::default(), Default::default()) + Settings::merge(Default::default(), Default::default(), Default::default(),) .unwrap() .chain(), Chain::Mainnet, @@ -954,72 +906,78 @@ mod tests { #[test] fn bitcoin_rpc_and_pass_setting() { assert_eq!( - Settings::new( + Settings::merge( Options { - bitcoin_rpc_user: Some("option_user".into()), - bitcoin_rpc_pass: Some("option_pass".into()), + bitcoin_username: Some("option_user".into()), + bitcoin_password: Some("option_pass".into()), ..Default::default() }, vec![ - ("BITCOIN_RPC_USER".into(), "env_user".into()), - ("BITCOIN_RPC_PASS".into(), "env_pass".into()), + ("BITCOIN_USER".into(), "env_user".into()), + ("BITCOIN_PASS".into(), "env_pass".into()), ] .into_iter() .collect(), - Config { - bitcoin_rpc_user: Some("config_user".into()), - bitcoin_rpc_pass: Some("config_pass".into()), + Settings { + bitcoin_username: Some("config_user".into()), + bitcoin_password: Some("config_pass".into()), ..Default::default() } ) .unwrap() - .auth() + .bitcoin_credentials() .unwrap(), Auth::UserPass("option_user".into(), "option_pass".into()), ); assert_eq!( - Settings::new( + Settings::merge( Default::default(), vec![ - ("BITCOIN_RPC_USER".into(), "env_user".into()), - ("BITCOIN_RPC_PASS".into(), "env_pass".into()), + ("BITCOIN_USERNAME".into(), "env_user".into()), + ("BITCOIN_PASSWORD".into(), "env_pass".into()), ] .into_iter() .collect(), - Config { - bitcoin_rpc_user: Some("config_user".into()), - bitcoin_rpc_pass: Some("config_pass".into()), + Settings { + bitcoin_username: Some("config_user".into()), + bitcoin_password: Some("config_pass".into()), ..Default::default() } ) .unwrap() - .auth() + .bitcoin_credentials() .unwrap(), Auth::UserPass("env_user".into(), "env_pass".into()), ); assert_eq!( - Settings::new( + Settings::merge( Default::default(), Default::default(), - Config { - bitcoin_rpc_user: Some("config_user".into()), - bitcoin_rpc_pass: Some("config_pass".into()), + Settings { + bitcoin_username: Some("config_user".into()), + bitcoin_password: Some("config_pass".into()), ..Default::default() } ) .unwrap() - .auth() + .bitcoin_credentials() .unwrap(), Auth::UserPass("config_user".into(), "config_pass".into()), ); - assert_eq!( - Settings::new(Default::default(), Default::default(), Default::default()) + assert_matches!( + Settings::merge(Default::default(), Default::default(), Default::default(),) .unwrap() - .auth, - None, + .bitcoin_credentials() + .unwrap(), + Auth::CookieFile(_), ); } + + #[test] + fn example_config_file_is_valid() { + let _: Settings = serde_yaml::from_reader(File::open("ord.yaml").unwrap()).unwrap(); + } } diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs index 0b56d38213..3aa49189be 100644 --- a/src/subcommand/env.rs +++ b/src/subcommand/env.rs @@ -87,7 +87,7 @@ rpcport={bitcoind_port} .arg(&absolute) .arg("--data-dir") .arg(&absolute) - .arg("--rpc-url") + .arg("--bitcoin-url") .arg(&rpc_url) .arg("server") .arg("--polling-interval=100ms") @@ -107,7 +107,7 @@ rpcport={bitcoind_port} .arg(&absolute) .arg("--data-dir") .arg(&absolute) - .arg("--rpc-url") + .arg("--bitcoin-url") .arg(&rpc_url) .arg("wallet") .arg("create") @@ -121,7 +121,7 @@ rpcport={bitcoind_port} .arg(&absolute) .arg("--data-dir") .arg(&absolute) - .arg("--rpc-url") + .arg("--bitcoin-url") .arg(&rpc_url) .arg("wallet") .arg("--server-url") @@ -162,7 +162,7 @@ rpcport={bitcoind_port} relative.clone(), "--data-dir".into(), relative.clone(), - "--rpc-url".into(), + "--bitcoin-url".into(), rpc_url.clone(), "wallet".into(), "--server-url".into(), @@ -175,7 +175,7 @@ rpcport={bitcoind_port} "{} bitcoin-cli -datadir='{relative}' getblockchaininfo {} -{} --regtest --bitcoin-data-dir '{relative}' --data-dir '{relative}' --rpc-url '{}' wallet --server-url {} balance", +{} --regtest --bitcoin-data-dir '{relative}' --data-dir '{relative}' --bitcoin-url '{}' wallet --server-url {} balance", "Example `bitcoin-cli` command:".blue().bold(), "Example `ord` command:".blue().bold(), ord.display(), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 54adaa808e..61e5cd4dd3 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -152,6 +152,7 @@ impl Server { pub fn run(self, settings: Settings, index: Arc, handle: Handle) -> SubcommandResult { Runtime::new()?.block_on(async { let index_clone = index.clone(); + let integration_test = settings.integration_test(); let index_thread = thread::spawn(move || loop { if SHUTTING_DOWN.load(atomic::Ordering::Relaxed) { @@ -164,7 +165,7 @@ impl Server { } } - thread::sleep(if settings.integration_test { + thread::sleep(if integration_test { Duration::from_millis(100) } else { self.polling_interval.into() @@ -351,7 +352,7 @@ impl Server { let address = match &self.address { Some(address) => address.as_str(), None => { - if cfg!(test) || settings.integration_test { + if cfg!(test) || settings.integration_test() { "127.0.0.1" } else { "0.0.0.0" @@ -364,7 +365,7 @@ impl Server { .next() .ok_or_else(|| anyhow!("failed to get socket addrs"))?; - if !settings.integration_test && !cfg!(test) { + if !settings.integration_test() && !cfg!(test) { eprintln!( "Listening on {}://{addr}", match config { @@ -405,9 +406,10 @@ impl Server { } fn acme_cache(acme_cache: Option<&PathBuf>, settings: &Settings) -> PathBuf { - acme_cache - .unwrap_or(&settings.data_dir().join("acme-cache")) - .to_path_buf() + match acme_cache { + Some(acme_cache) => acme_cache.clone(), + None => settings.data_dir().join("acme-cache"), + } } fn acme_domains(&self) -> Result> { @@ -1859,7 +1861,7 @@ mod tests { let mut args = vec!["ord".to_string()]; - args.push("--rpc-url".into()); + args.push("--bitcoin-url".into()); args.push(bitcoin_rpc_server.url()); args.push("--cookie-file".into()); @@ -1906,9 +1908,10 @@ mod tests { panic!("unexpected subcommand: {:?}", arguments.subcommand); }; - let config = serde_yaml::from_str(&self.config).unwrap(); - - let settings = Settings::new(arguments.options, Default::default(), config).unwrap(); + let settings = Settings::from_options(arguments.options) + .or(serde_yaml::from_str::(&self.config).unwrap()) + .or_defaults() + .unwrap(); let index = Arc::new(Index::open(&settings).unwrap()); let ord_server_handle = Handle::new(); @@ -2088,7 +2091,12 @@ mod tests { 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.settings().unwrap(), server), + Subcommand::Server(server) => ( + Settings::from_options(arguments.options) + .or_defaults() + .unwrap(), + server, + ), subcommand => panic!("unexpected subcommand: {subcommand:?}"), }, Err(err) => panic!("error parsing arguments: {err}"), @@ -2218,7 +2226,9 @@ mod tests { fn acme_cache_defaults_to_data_dir() { let arguments = Arguments::try_parse_from(["ord", "--data-dir", "foo", "server"]).unwrap(); - let settings = arguments.options.settings().unwrap(); + let settings = Settings::from_options(arguments.options) + .or_defaults() + .unwrap(); let acme_cache = Server::acme_cache(None, &settings).display().to_string(); assert!( @@ -2237,7 +2247,9 @@ mod tests { Arguments::try_parse_from(["ord", "--data-dir", "foo", "server", "--acme-cache", "bar"]) .unwrap(); - let settings = arguments.options.settings().unwrap(); + let settings = Settings::from_options(arguments.options) + .or_defaults() + .unwrap(); let acme_cache = Server::acme_cache(Some(&"bar".into()), &settings) .display() @@ -5605,12 +5617,17 @@ next #[test] fn authentication_requires_username_and_password() { - assert!(Arguments::try_parse_from(["ord", "--username", "server", "foo"]).is_err()); - assert!(Arguments::try_parse_from(["ord", "--password", "server", "bar"]).is_err()); - assert!( - Arguments::try_parse_from(["ord", "--username", "foo", "--password", "bar", "server"]) - .is_ok() - ); + assert!(Arguments::try_parse_from(["ord", "--server-username", "server", "foo"]).is_err()); + assert!(Arguments::try_parse_from(["ord", "--server-password", "server", "bar"]).is_err()); + assert!(Arguments::try_parse_from([ + "ord", + "--server-username", + "foo", + "--server-password", + "bar", + "server" + ]) + .is_ok()); } #[test] diff --git a/tests/command_builder.rs b/tests/command_builder.rs index 5823cf95cc..640a45c501 100644 --- a/tests/command_builder.rs +++ b/tests/command_builder.rs @@ -32,10 +32,11 @@ pub(crate) struct CommandBuilder { args: Vec, bitcoin_rpc_server_cookie_file: Option, bitcoin_rpc_server_url: Option, - env: BTreeMap, + env: BTreeMap, expected_exit_code: i32, expected_stderr: Expected, expected_stdout: Expected, + integration_test: bool, ord_rpc_server_url: Option, stdin: Vec, tempdir: Arc, @@ -45,23 +46,31 @@ impl CommandBuilder { pub(crate) fn new(args: impl ToArgs) -> Self { Self { args: args.to_args(), + bitcoin_rpc_server_cookie_file: None, + bitcoin_rpc_server_url: None, env: BTreeMap::new(), expected_exit_code: 0, expected_stderr: Expected::String(String::new()), expected_stdout: Expected::String(String::new()), + integration_test: true, ord_rpc_server_url: None, - bitcoin_rpc_server_cookie_file: None, - bitcoin_rpc_server_url: None, stdin: Vec::new(), tempdir: Arc::new(TempDir::new().unwrap()), } } - pub(crate) fn env(mut self, key: &str, value: &str) -> Self { - self.env.insert(key.into(), value.into()); + pub(crate) fn env(mut self, key: &str, value: impl AsRef) -> Self { + self.env.insert(key.into(), value.as_ref().into()); self } + pub(crate) fn integration_test(self, integration_test: bool) -> Self { + Self { + integration_test, + ..self + } + } + pub(crate) fn write(self, path: impl AsRef, contents: impl AsRef<[u8]>) -> Self { fs::write(self.tempdir.path().join(path), contents).unwrap(); self @@ -126,7 +135,7 @@ impl CommandBuilder { if let Some(rpc_server_url) = &self.bitcoin_rpc_server_url { command.args([ - "--rpc-url", + "--bitcoin-url", rpc_server_url, "--cookie-file", self @@ -154,8 +163,11 @@ impl CommandBuilder { command.env(key, value); } + if self.integration_test { + command.env("ORD_INTEGRATION_TEST", "1"); + } + command - .env("ORD_INTEGRATION_TEST", "1") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) diff --git a/tests/expected.rs b/tests/expected.rs index 3c37912b9e..3fdb122579 100644 --- a/tests/expected.rs +++ b/tests/expected.rs @@ -15,10 +15,12 @@ impl Expected { pub(crate) fn assert_match(&self, output: &str) { match self { Self::String(string) => pretty_assert_eq!(output, string), - Self::Regex(regex) => assert!( - regex.is_match(output), - "regex:\n{regex}\ndid not match output:\n{output}", - ), + Self::Regex(regex) => { + if !regex.is_match(output) { + eprintln!("Regex did not match:"); + pretty_assert_eq!(regex.as_str(), output); + } + } } } } diff --git a/tests/lib.rs b/tests/lib.rs index 024b34b01c..0d859cc5d5 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -22,6 +22,7 @@ use { std::sync::Arc, std::{ collections::BTreeMap, + ffi::{OsStr, OsString}, fs, io::Write, net::TcpListener, diff --git a/tests/server.rs b/tests/server.rs index e0abd07c2a..b944da103b 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -405,16 +405,16 @@ fn expected_sat_time_is_rounded() { fn missing_credentials() { let rpc_server = test_bitcoincore_rpc::spawn(); - CommandBuilder::new("--bitcoin-rpc-user foo server") + CommandBuilder::new("--bitcoin-username foo server") .bitcoin_rpc_server(&rpc_server) .expected_exit_code(1) - .expected_stderr("error: no bitcoind rpc password specified\n") + .expected_stderr("error: no bitcoin RPC password specified\n") .run_and_extract_stdout(); - CommandBuilder::new("--bitcoin-rpc-pass bar server") + CommandBuilder::new("--bitcoin-password bar server") .bitcoin_rpc_server(&rpc_server) .expected_exit_code(1) - .expected_stderr("error: no bitcoind rpc user specified\n") + .expected_stderr("error: no bitcoin RPC username specified\n") .run_and_extract_stdout(); } @@ -586,7 +586,7 @@ fn authentication() { .port(); let builder = CommandBuilder::new(format!( - " --username foo --password bar server --address 127.0.0.1 --http-port {port}" + " --server-username foo --server-password bar server --address 127.0.0.1 --http-port {port}" )) .bitcoin_rpc_server(&rpc_server); diff --git a/tests/settings.rs b/tests/settings.rs index 5f3f932b7e..c7b5fa0404 100644 --- a/tests/settings.rs +++ b/tests/settings.rs @@ -1,15 +1,40 @@ use super::*; #[test] -fn config_is_loaded_from_config_option() { +fn default() { CommandBuilder::new("settings") + .integration_test(false) .stdout_regex( - r#".* + r#"\{ + "bitcoin_data_dir": ".*(Bitcoin|bitcoin)", + "bitcoin_password": null, + "bitcoin_url": "127.0.0.1:8332", + "bitcoin_username": null, "chain": "mainnet", -.*"#, + "commit_interval": 5000, + "cookie_file": ".*(Bitcoin|bitcoin).\.cookie", + "data_dir": ".*", + "first_inscription_height": 767430, + "height_limit": null, + "hidden": \[\], + "index": ".*index\.redb", + "index_cache_size": \d+, + "index_runes": false, + "index_sats": false, + "index_spent_sats": false, + "index_transactions": false, + "integration_test": false, + "no_index_inscriptions": false, + "server_password": null, + "server_username": null +\} +"#, ) .run_and_extract_stdout(); +} +#[test] +fn config_is_loaded_from_config_option() { let tempdir = TempDir::new().unwrap(); let config = tempdir.path().join("ord.yaml"); @@ -26,15 +51,21 @@ fn config_is_loaded_from_config_option() { } #[test] -fn config_not_found_error_message() { - CommandBuilder::new("settings") - .stdout_regex( - r#".* - "chain": "mainnet", -.*"#, - ) +fn config_invalid_error_message() { + let tempdir = TempDir::new().unwrap(); + + let config = tempdir.path().join("ord.yaml"); + + fs::write(&config, "foo").unwrap(); + + CommandBuilder::new(format!("--config {} settings", config.to_str().unwrap())) + .stderr_regex("error: failed to deserialize config file `.*ord.yaml`\nbecause:.*") + .expected_exit_code(1) .run_and_extract_stdout(); +} +#[test] +fn config_not_found_error_message() { let tempdir = TempDir::new().unwrap(); let config = tempdir.path().join("ord.yaml"); @@ -47,14 +78,6 @@ fn config_not_found_error_message() { #[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(); @@ -90,3 +113,15 @@ fn env_is_loaded() { ) .run_and_extract_stdout(); } + +#[cfg(unix)] +#[test] +fn invalid_env_error_message() { + use std::os::unix::ffi::OsStringExt; + + CommandBuilder::new("settings") + .env("ORD_BAR", OsString::from_vec(b"\xFF".into())) + .stderr_regex("error: environment variable `ORD_BAR` not valid unicode: `�`\n") + .expected_exit_code(1) + .run_and_extract_stdout(); +} diff --git a/tests/test_server.rs b/tests/test_server.rs index 96fccdb4f9..4afc15d4bc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -31,8 +31,6 @@ impl TestServer { ord_args: &[&str], ord_server_args: &[&str], ) -> Self { - std::env::set_var("ORD_INTEGRATION_TEST", "1"); - let tempdir = TempDir::new().unwrap(); let cookiefile = tempdir.path().join("cookie"); @@ -46,7 +44,7 @@ impl TestServer { .port(); 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", + "ord --bitcoin-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(), tempdir.path().display(), diff --git a/tests/wallet/authentication.rs b/tests/wallet/authentication.rs index 4e02ddf294..d639ac1a43 100644 --- a/tests/wallet/authentication.rs +++ b/tests/wallet/authentication.rs @@ -6,14 +6,14 @@ fn authentication() { let ord_rpc_server = TestServer::spawn_with_server_args( &bitcoin_rpc_server, - &["--username", "foo", "--password", "bar"], + &["--server-username", "foo", "--server-password", "bar"], &[], ); create_wallet(&bitcoin_rpc_server, &ord_rpc_server); assert_eq!( - CommandBuilder::new("--username foo --password bar wallet balance") + CommandBuilder::new("--server-username foo --server-password bar wallet balance") .bitcoin_rpc_server(&bitcoin_rpc_server) .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::() @@ -24,7 +24,7 @@ fn authentication() { bitcoin_rpc_server.mine_blocks(1); assert_eq!( - CommandBuilder::new("--username foo --password bar wallet balance") + CommandBuilder::new("--server-username foo --server-password bar wallet balance") .bitcoin_rpc_server(&bitcoin_rpc_server) .ord_rpc_server(&ord_rpc_server) .run_and_deserialize_output::(), From 48176c94f9574d36034ccc7eb731c1ac7e9b47ea Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 18:25:40 -0800 Subject: [PATCH 02/11] Amend --- src/settings.rs | 218 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 158 insertions(+), 60 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 50170d806c..9cd090c48f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,6 +1,6 @@ use {super::*, bitcoincore_rpc::Auth}; -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(default, deny_unknown_fields)] pub struct Settings { bitcoin_data_dir: Option, @@ -844,65 +844,6 @@ mod tests { assert!(!parse(&[]).index_runes()); } - #[test] - fn merge() { - assert_eq!( - Settings::merge( - Options { - regtest: true, - ..Default::default() - }, - vec![("CHAIN".into(), "signet".into())] - .into_iter() - .collect(), - Settings { - chain: Some(Chain::Testnet), - ..Default::default() - } - ) - .unwrap() - .chain(), - Chain::Regtest, - ); - - assert_eq!( - Settings::merge( - Default::default(), - vec![("CHAIN".into(), "signet".into())] - .into_iter() - .collect(), - Settings { - chain: Some(Chain::Testnet), - ..Default::default() - } - ) - .unwrap() - .chain(), - Chain::Signet, - ); - - assert_eq!( - Settings::merge( - Default::default(), - Default::default(), - Settings { - chain: Some(Chain::Testnet), - ..Default::default() - } - ) - .unwrap() - .chain(), - Chain::Testnet, - ); - - assert_eq!( - Settings::merge(Default::default(), Default::default(), Default::default(),) - .unwrap() - .chain(), - Chain::Mainnet, - ); - } - #[test] fn bitcoin_rpc_and_pass_setting() { assert_eq!( @@ -980,4 +921,161 @@ mod tests { fn example_config_file_is_valid() { let _: Settings = serde_yaml::from_reader(File::open("ord.yaml").unwrap()).unwrap(); } + + #[test] + fn from_env() { + let env = vec![ + ("BITCOIN_DATA_DIR", "/bitcoin/data/dir"), + ("BITCOIN_PASSWORD", "bitcoin password"), + ("BITCOIN_URL", "url"), + ("BITCOIN_USERNAME", "bitcoin username"), + ("CHAIN", "signet"), + ("COMMIT_INTERVAL", "1"), + ("COOKIE_FILE", "cookie file"), + ("DATA_DIR", "/data/dir"), + ("FIRST_INSCRIPTION_HEIGHT", "2"), + ("HEIGHT_LIMIT", "3"), + ("HIDDEN", "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0 703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0"), + ("INDEX", "index"), + ("INDEX_CACHE_SIZE", "4"), + ("INDEX_RUNES", "1"), + ("INDEX_SATS", "1"), + ("INDEX_SPENT_SATS", "1"), + ("INDEX_TRANSACTIONS", "1"), + ("INTEGRATION_TEST", "1"), + ("NO_INDEX_INSCRIPTIONS", "1"), + ("SERVER_PASSWORD", "server password"), + ("SERVER_USERNAME", "server username"), + ] + .into_iter() + .map(|(key, value)| (key.into(), value.into())) + .collect::>(); + + pretty_assert_eq!( + Settings::from_env(env).unwrap(), + Settings { + bitcoin_data_dir: Some("/bitcoin/data/dir".into()), + bitcoin_password: Some("bitcoin password".into()), + bitcoin_url: Some("url".into()), + bitcoin_username: Some("bitcoin username".into()), + chain: Some(Chain::Signet), + commit_interval: Some(1), + cookie_file: Some("cookie file".into()), + data_dir: Some("/data/dir".into()), + first_inscription_height: Some(2), + height_limit: Some(3), + hidden: Some( + vec![ + "6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0" + .parse() + .unwrap(), + "703e5f7c49d82aab99e605af306b9a30e991e57d42f982908a962a81ac439832i0" + .parse() + .unwrap() + ] + .into_iter() + .collect() + ), + index: Some("index".into()), + index_cache_size: Some(4), + index_runes: true, + index_sats: true, + index_spent_sats: true, + index_transactions: true, + integration_test: true, + no_index_inscriptions: true, + server_password: Some("server password".into()), + server_username: Some("server username".into()), + } + ); + } + + #[test] + fn from_options() { + pretty_assert_eq!( + Settings::from_options( + Options::try_parse_from(&[ + "ord", + "--bitcoin-data-dir=/bitcoin/data/dir", + "--bitcoin-password=bitcoin password", + "--bitcoin-url=url", + "--bitcoin-username=bitcoin username", + "--chain=signet", + "--commit-interval=1", + "--cookie-file=cookie file", + "--data-dir=/data/dir", + "--first-inscription-height=2", + "--height-limit=3", + "--index=index", + "--index-cache-size=4", + "--index-runes", + "--index-sats", + "--index-spent-sats", + "--index-transactions", + "--integration-test", + "--no-index-inscriptions", + "--server-password=server password", + "--server-username=server username", + ]) + .unwrap() + ), + Settings { + bitcoin_data_dir: Some("/bitcoin/data/dir".into()), + bitcoin_password: Some("bitcoin password".into()), + bitcoin_url: Some("url".into()), + bitcoin_username: Some("bitcoin username".into()), + chain: Some(Chain::Signet), + commit_interval: Some(1), + cookie_file: Some("cookie file".into()), + data_dir: Some("/data/dir".into()), + first_inscription_height: Some(2), + height_limit: Some(3), + hidden: None, + index: Some("index".into()), + index_cache_size: Some(4), + index_runes: true, + index_sats: true, + index_spent_sats: true, + index_transactions: true, + integration_test: true, + no_index_inscriptions: true, + server_password: Some("server password".into()), + server_username: Some("server username".into()), + } + ); + } + + #[test] + fn merge() { + let env = vec![("INDEX", "env")] + .into_iter() + .map(|(key, value)| (key.into(), value.into())) + .collect::>(); + + let options = Options::try_parse_from(&["ord", "--index=option"]).unwrap(); + + let config = Settings { + index: Some("config".into()), + ..Default::default() + }; + + pretty_assert_eq!( + Settings::merge(Default::default(), Default::default(), config.clone()) + .unwrap() + .index, + Some("config".into()), + ); + + pretty_assert_eq!( + Settings::merge(Default::default(), env.clone(), config.clone()) + .unwrap() + .index, + Some("env".into()), + ); + + pretty_assert_eq!( + Settings::merge(options, env, config.clone()).unwrap().index, + Some("option".into()), + ); + } } From aeb3c3e745baae745c66b51344dd7087b06d3c35 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 18:28:03 -0800 Subject: [PATCH 03/11] Tweak --- tests/settings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/settings.rs b/tests/settings.rs index c7b5fa0404..8bc5274f37 100644 --- a/tests/settings.rs +++ b/tests/settings.rs @@ -12,7 +12,7 @@ fn default() { "bitcoin_username": null, "chain": "mainnet", "commit_interval": 5000, - "cookie_file": ".*(Bitcoin|bitcoin).\.cookie", + "cookie_file": ".*\.cookie", "data_dir": ".*", "first_inscription_height": 767430, "height_limit": null, From 1a67a98c0429ad75a420d2ca9149c3d5d210d7da Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 18:29:37 -0800 Subject: [PATCH 04/11] Modify --- src/settings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/settings.rs b/src/settings.rs index 9cd090c48f..e4352de7d5 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -994,7 +994,7 @@ mod tests { fn from_options() { pretty_assert_eq!( Settings::from_options( - Options::try_parse_from(&[ + Options::try_parse_from([ "ord", "--bitcoin-data-dir=/bitcoin/data/dir", "--bitcoin-password=bitcoin password", @@ -1052,7 +1052,7 @@ mod tests { .map(|(key, value)| (key.into(), value.into())) .collect::>(); - let options = Options::try_parse_from(&["ord", "--index=option"]).unwrap(); + let options = Options::try_parse_from(["ord", "--index=option"]).unwrap(); let config = Settings { index: Some("config".into()), From 7817a0929c3dc07e21552985bd2d186e28881c9d Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 18:57:21 -0800 Subject: [PATCH 05/11] Enhance --- docs/src/guides/sat-hunting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/guides/sat-hunting.md b/docs/src/guides/sat-hunting.md index 5bc93772c4..c6bf9cd50a 100644 --- a/docs/src/guides/sat-hunting.md +++ b/docs/src/guides/sat-hunting.md @@ -67,7 +67,7 @@ wallet is named `foo`: 2. Display any rare ordinals wallet `foo`'s UTXOs: ```sh - ord --index-sats wallet --wallet foo sats + ord --index-sats wallet --name foo sats ``` ### Searching for Rare Ordinals in a Non-Bitcoin Core Wallet From ad15da0d801564cbea7eea140423c48538635860 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 18:58:03 -0800 Subject: [PATCH 06/11] Amend --- docs/src/guides/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/guides/settings.md b/docs/src/guides/settings.md index e8e615a83d..90587d2a1a 100644 --- a/docs/src/guides/settings.md +++ b/docs/src/guides/settings.md @@ -4,7 +4,7 @@ Settings `ord` can be configured with the command line, environment variables, a configuration file, and default values. -The command line take precedence over environment variables, which take +The command line takes precedence over environment variables, which take precedence over the configuration file, which takes precedence over defaults. The path to the configuration file can be given with `--config `. From 47ec2882626da323d8d15a17aaaa6f6f96f83330 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 19:01:52 -0800 Subject: [PATCH 07/11] Enhance --- ord.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ord.yaml b/ord.yaml index ebdd76f9d6..90a45c99f1 100644 --- a/ord.yaml +++ b/ord.yaml @@ -1,5 +1,7 @@ # example config +# see `ord --help` for setting documentation + bitcoin_data_dir: /var/lib/bitcoin bitcoin_password: bar bitcoin_url: https://localhost:8000 From 3d0387ab4d66f5b8bcc7052ab8db383299e2351f Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 19:12:44 -0800 Subject: [PATCH 08/11] Amend --- ord.yaml | 2 +- src/index/fetcher.rs | 6 +++--- src/options.rs | 4 ++-- src/settings.rs | 37 ++++++++++++++++++++----------------- tests/settings.rs | 2 +- 5 files changed, 27 insertions(+), 24 deletions(-) diff --git a/ord.yaml b/ord.yaml index 90a45c99f1..e9d33d1b92 100644 --- a/ord.yaml +++ b/ord.yaml @@ -4,7 +4,7 @@ bitcoin_data_dir: /var/lib/bitcoin bitcoin_password: bar -bitcoin_url: https://localhost:8000 +bitcoin_rpc_url: https://localhost:8000 bitcoin_username: foo chain: mainnet commit_interval: 10000 diff --git a/src/index/fetcher.rs b/src/index/fetcher.rs index 175119f417..4fcf1dfc0f 100644 --- a/src/index/fetcher.rs +++ b/src/index/fetcher.rs @@ -28,10 +28,10 @@ impl Fetcher { pub(crate) fn new(settings: &Settings) -> Result { let client = Client::new(); - let url = if settings.bitcoin_url(None).starts_with("http://") { - settings.bitcoin_url(None) + let url = if settings.bitcoin_rpc_url(None).starts_with("http://") { + settings.bitcoin_rpc_url(None) } else { - "http://".to_string() + &settings.bitcoin_url(None) + "http://".to_string() + &settings.bitcoin_rpc_url(None) }; let url = Uri::try_from(&url).map_err(|e| anyhow!("Invalid rpc url {url}: {e}"))?; diff --git a/src/options.rs b/src/options.rs index 29233316b1..f40afdca75 100644 --- a/src/options.rs +++ b/src/options.rs @@ -14,6 +14,8 @@ pub struct Options { help = "Authenticate to Bitcoin Core RPC with ." )] pub(crate) bitcoin_password: Option, + #[arg(long, help = "Connect to Bitcoin Core RPC at .")] + pub(crate) bitcoin_rpc_url: Option, #[arg(long, help = "Authenticate to Bitcoin Core RPC as .")] pub(crate) bitcoin_username: Option, #[arg(long = "chain", value_enum, help = "Use . [default: mainnet]")] @@ -79,8 +81,6 @@ pub struct Options { pub(crate) server_username: Option, #[arg(long, short, help = "Use regtest. Equivalent to `--chain regtest`.")] pub(crate) regtest: bool, - #[arg(long, help = "Connect to Bitcoin Core RPC at .")] - pub(crate) bitcoin_url: Option, #[arg(long, short, help = "Use signet. Equivalent to `--chain signet`.")] pub(crate) signet: bool, #[arg(long, short, help = "Use testnet. Equivalent to `--chain testnet`.")] diff --git a/src/settings.rs b/src/settings.rs index e4352de7d5..cc5b6849cd 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -5,7 +5,7 @@ use {super::*, bitcoincore_rpc::Auth}; pub struct Settings { bitcoin_data_dir: Option, bitcoin_password: Option, - bitcoin_url: Option, + bitcoin_rpc_url: Option, bitcoin_username: Option, chain: Option, commit_interval: Option, @@ -109,7 +109,7 @@ impl Settings { Self { bitcoin_data_dir: self.bitcoin_data_dir.or(source.bitcoin_data_dir), bitcoin_password: self.bitcoin_password.or(source.bitcoin_password), - bitcoin_url: self.bitcoin_url.or(source.bitcoin_url), + bitcoin_rpc_url: self.bitcoin_rpc_url.or(source.bitcoin_rpc_url), bitcoin_username: self.bitcoin_username.or(source.bitcoin_username), chain: self.chain.or(source.chain), commit_interval: self.commit_interval.or(source.commit_interval), @@ -145,7 +145,7 @@ impl Settings { Self { bitcoin_data_dir: options.bitcoin_data_dir, bitcoin_password: options.bitcoin_password, - bitcoin_url: options.bitcoin_url, + bitcoin_rpc_url: options.bitcoin_rpc_url, bitcoin_username: options.bitcoin_username, chain: options .signet @@ -225,7 +225,7 @@ impl Settings { Ok(Self { bitcoin_data_dir: get_path("BITCOIN_DATA_DIR"), bitcoin_password: get_string("BITCOIN_PASSWORD"), - bitcoin_url: get_string("BITCOIN_URL"), + bitcoin_rpc_url: get_string("BITCOIN_RPC_URL"), bitcoin_username: get_string("BITCOIN_USERNAME"), chain: get_chain("CHAIN")?, commit_interval: get_usize("COMMIT_INTERVAL")?, @@ -285,9 +285,9 @@ impl Settings { Ok(Self { bitcoin_data_dir: Some(bitcoin_data_dir), bitcoin_password: self.bitcoin_password, - bitcoin_url: Some( + bitcoin_rpc_url: Some( self - .bitcoin_url + .bitcoin_rpc_url .clone() .unwrap_or_else(|| format!("127.0.0.1:{}", chain.default_rpc_port())), ), @@ -338,11 +338,14 @@ impl Settings { } pub(crate) fn bitcoin_rpc_client(&self, wallet: Option) -> Result { - let rpc_url = self.bitcoin_url(wallet); + let rpc_url = self.bitcoin_rpc_url(wallet); let bitcoin_credentials = self.bitcoin_credentials()?; - log::info!("Connecting to Bitcoin Core at {}", self.bitcoin_url(None)); + log::info!( + "Connecting to Bitcoin Core at {}", + self.bitcoin_rpc_url(None) + ); if let Auth::CookieFile(cookie_file) = &bitcoin_credentials { log::info!( @@ -492,8 +495,8 @@ impl Settings { .unwrap_or_default() } - pub(crate) fn bitcoin_url(&self, wallet_name: Option) -> String { - let base_url = self.bitcoin_url.as_ref().unwrap(); + pub(crate) fn bitcoin_rpc_url(&self, wallet_name: Option) -> String { + let base_url = self.bitcoin_rpc_url.as_ref().unwrap(); match wallet_name { Some(wallet_name) => format!("{base_url}/wallet/{wallet_name}"), None => format!("{base_url}/"), @@ -617,7 +620,7 @@ mod tests { #[test] fn rpc_url_overrides_network() { assert_eq!( - parse(&["--bitcoin-url=127.0.0.1:1234", "--chain=signet"]).bitcoin_url(None), + parse(&["--bitcoin-url=127.0.0.1:1234", "--chain=signet"]).bitcoin_rpc_url(None), "127.0.0.1:1234/" ); } @@ -636,7 +639,7 @@ mod tests { fn use_default_network() { let settings = parse(&[]); - assert_eq!(settings.bitcoin_url(None), "127.0.0.1:8332/"); + assert_eq!(settings.bitcoin_rpc_url(None), "127.0.0.1:8332/"); assert!(settings.cookie_file().unwrap().ends_with(".cookie")); } @@ -645,7 +648,7 @@ mod tests { fn uses_network_defaults() { let settings = parse(&["--chain=signet"]); - assert_eq!(settings.bitcoin_url(None), "127.0.0.1:38332/"); + assert_eq!(settings.bitcoin_rpc_url(None), "127.0.0.1:38332/"); assert!(settings .cookie_file() @@ -815,7 +818,7 @@ mod tests { let (settings, _) = wallet("ord wallet --name foo balance"); assert_eq!( - settings.bitcoin_url(Some("foo".into())), + settings.bitcoin_rpc_url(Some("foo".into())), "127.0.0.1:8332/wallet/foo" ); } @@ -927,7 +930,7 @@ mod tests { let env = vec![ ("BITCOIN_DATA_DIR", "/bitcoin/data/dir"), ("BITCOIN_PASSWORD", "bitcoin password"), - ("BITCOIN_URL", "url"), + ("BITCOIN_RPC_URL", "url"), ("BITCOIN_USERNAME", "bitcoin username"), ("CHAIN", "signet"), ("COMMIT_INTERVAL", "1"), @@ -956,7 +959,7 @@ mod tests { Settings { bitcoin_data_dir: Some("/bitcoin/data/dir".into()), bitcoin_password: Some("bitcoin password".into()), - bitcoin_url: Some("url".into()), + bitcoin_rpc_url: Some("url".into()), bitcoin_username: Some("bitcoin username".into()), chain: Some(Chain::Signet), commit_interval: Some(1), @@ -1022,7 +1025,7 @@ mod tests { Settings { bitcoin_data_dir: Some("/bitcoin/data/dir".into()), bitcoin_password: Some("bitcoin password".into()), - bitcoin_url: Some("url".into()), + bitcoin_rpc_url: Some("url".into()), bitcoin_username: Some("bitcoin username".into()), chain: Some(Chain::Signet), commit_interval: Some(1), diff --git a/tests/settings.rs b/tests/settings.rs index 8bc5274f37..2db9cb5592 100644 --- a/tests/settings.rs +++ b/tests/settings.rs @@ -8,7 +8,7 @@ fn default() { r#"\{ "bitcoin_data_dir": ".*(Bitcoin|bitcoin)", "bitcoin_password": null, - "bitcoin_url": "127.0.0.1:8332", + "bitcoin_rpc_url": "127.0.0.1:8332", "bitcoin_username": null, "chain": "mainnet", "commit_interval": 5000, From 56c384951082c978672bf4a073d38d77bf0fe98b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 19:16:13 -0800 Subject: [PATCH 09/11] Enhance --- src/index/testing.rs | 2 +- src/settings.rs | 6 +++--- src/subcommand/env.rs | 10 +++++----- src/subcommand/server.rs | 2 +- tests/command_builder.rs | 2 +- tests/test_server.rs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/index/testing.rs b/src/index/testing.rs index 627dac89df..7788480a02 100644 --- a/src/index/testing.rs +++ b/src/index/testing.rs @@ -23,7 +23,7 @@ impl ContextBuilder { let command: Vec = vec![ "ord".into(), - "--bitcoin-url".into(), + "--bitcoin-rpc-url".into(), rpc_server.url().into(), "--data-dir".into(), tempdir.path().into(), diff --git a/src/settings.rs b/src/settings.rs index cc5b6849cd..5e0eef33e2 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -607,7 +607,7 @@ mod tests { let settings = parse(&[ "--cookie-file", rpc_server.cookie_file().to_str().unwrap(), - "--bitcoin-url", + "--bitcoin-rpc-url", &rpc_server.url(), ]); @@ -620,7 +620,7 @@ mod tests { #[test] fn rpc_url_overrides_network() { assert_eq!( - parse(&["--bitcoin-url=127.0.0.1:1234", "--chain=signet"]).bitcoin_rpc_url(None), + parse(&["--bitcoin-rpc-url=127.0.0.1:1234", "--chain=signet"]).bitcoin_rpc_url(None), "127.0.0.1:1234/" ); } @@ -1001,7 +1001,7 @@ mod tests { "ord", "--bitcoin-data-dir=/bitcoin/data/dir", "--bitcoin-password=bitcoin password", - "--bitcoin-url=url", + "--bitcoin-rpc-url=url", "--bitcoin-username=bitcoin username", "--chain=signet", "--commit-interval=1", diff --git a/src/subcommand/env.rs b/src/subcommand/env.rs index 3aa49189be..c86924e133 100644 --- a/src/subcommand/env.rs +++ b/src/subcommand/env.rs @@ -87,7 +87,7 @@ rpcport={bitcoind_port} .arg(&absolute) .arg("--data-dir") .arg(&absolute) - .arg("--bitcoin-url") + .arg("--bitcoin-rpc-url") .arg(&rpc_url) .arg("server") .arg("--polling-interval=100ms") @@ -107,7 +107,7 @@ rpcport={bitcoind_port} .arg(&absolute) .arg("--data-dir") .arg(&absolute) - .arg("--bitcoin-url") + .arg("--bitcoin-rpc-url") .arg(&rpc_url) .arg("wallet") .arg("create") @@ -121,7 +121,7 @@ rpcport={bitcoind_port} .arg(&absolute) .arg("--data-dir") .arg(&absolute) - .arg("--bitcoin-url") + .arg("--bitcoin-rpc-url") .arg(&rpc_url) .arg("wallet") .arg("--server-url") @@ -162,7 +162,7 @@ rpcport={bitcoind_port} relative.clone(), "--data-dir".into(), relative.clone(), - "--bitcoin-url".into(), + "--bitcoin-rpc-url".into(), rpc_url.clone(), "wallet".into(), "--server-url".into(), @@ -175,7 +175,7 @@ rpcport={bitcoind_port} "{} bitcoin-cli -datadir='{relative}' getblockchaininfo {} -{} --regtest --bitcoin-data-dir '{relative}' --data-dir '{relative}' --bitcoin-url '{}' wallet --server-url {} balance", +{} --regtest --bitcoin-data-dir '{relative}' --data-dir '{relative}' --bitcoin-rpc-url '{}' wallet --server-url {} balance", "Example `bitcoin-cli` command:".blue().bold(), "Example `ord` command:".blue().bold(), ord.display(), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 61e5cd4dd3..ce8eaebea5 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1861,7 +1861,7 @@ mod tests { let mut args = vec!["ord".to_string()]; - args.push("--bitcoin-url".into()); + args.push("--bitcoin-rpc-url".into()); args.push(bitcoin_rpc_server.url()); args.push("--cookie-file".into()); diff --git a/tests/command_builder.rs b/tests/command_builder.rs index 640a45c501..7a03d1a5d8 100644 --- a/tests/command_builder.rs +++ b/tests/command_builder.rs @@ -135,7 +135,7 @@ impl CommandBuilder { if let Some(rpc_server_url) = &self.bitcoin_rpc_server_url { command.args([ - "--bitcoin-url", + "--bitcoin-rpc-url", rpc_server_url, "--cookie-file", self diff --git a/tests/test_server.rs b/tests/test_server.rs index 4afc15d4bc..03781b33e0 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -44,7 +44,7 @@ impl TestServer { .port(); let (settings, server) = parse_ord_server_args(&format!( - "ord --bitcoin-url {} --cookie-file {} --bitcoin-data-dir {} --data-dir {} {} server {} --http-port {port} --address 127.0.0.1", + "ord --bitcoin-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(), tempdir.path().display(), From 10a271cc11a63f10010a3e156380b5b0a875922f Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 19:21:34 -0800 Subject: [PATCH 10/11] Modify --- README.md | 10 +++--- ord.yaml | 4 +-- src/options.rs | 11 ++++--- src/settings.rs | 77 ++++++++++++++++++++++++----------------------- tests/settings.rs | 4 +-- 5 files changed, 56 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 6189b16dc6..2ac2dcb628 100644 --- a/README.md +++ b/README.md @@ -216,22 +216,22 @@ Alternatively, `ord` can be supplied with a username and password on the command line: ``` -ord --bitcoin-username foo --bitcoin-password bar server +ord --bitcoin-rpc-username foo --bitcoin-rpc-password bar server ``` Using environment variables: ``` -export ORD_BITCOIN_USERNAME=foo -export ORD_BITCOIN_PASSWORD=bar +export ORD_BITCOIN_RPC_USERNAME=foo +export ORD_BITCOIN_RPC_PASSWORD=bar ord server ``` Or in the config file: ```yaml -bitcoin_username: foo -bitcoin_password: bar +bitcoin_rpc_username: foo +bitcoin_rpc_password: bar ``` Logging diff --git a/ord.yaml b/ord.yaml index e9d33d1b92..d2f56e88c0 100644 --- a/ord.yaml +++ b/ord.yaml @@ -3,9 +3,9 @@ # see `ord --help` for setting documentation bitcoin_data_dir: /var/lib/bitcoin -bitcoin_password: bar +bitcoin_rpc_password: bar bitcoin_rpc_url: https://localhost:8000 -bitcoin_username: foo +bitcoin_rpc_username: foo chain: mainnet commit_interval: 10000 cookie_file: /var/lib/bitcoin/.cookie diff --git a/src/options.rs b/src/options.rs index f40afdca75..9a93442f4e 100644 --- a/src/options.rs +++ b/src/options.rs @@ -11,13 +11,16 @@ pub struct Options { pub(crate) bitcoin_data_dir: Option, #[arg( long, - help = "Authenticate to Bitcoin Core RPC with ." + help = "Authenticate to Bitcoin Core RPC with ." )] - pub(crate) bitcoin_password: Option, + pub(crate) bitcoin_rpc_password: Option, #[arg(long, help = "Connect to Bitcoin Core RPC at .")] pub(crate) bitcoin_rpc_url: Option, - #[arg(long, help = "Authenticate to Bitcoin Core RPC as .")] - pub(crate) bitcoin_username: Option, + #[arg( + long, + help = "Authenticate to Bitcoin Core RPC as ." + )] + pub(crate) bitcoin_rpc_username: Option, #[arg(long = "chain", value_enum, help = "Use . [default: mainnet]")] pub(crate) chain_argument: Option, #[arg( diff --git a/src/settings.rs b/src/settings.rs index 5e0eef33e2..d25e1e695a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -4,9 +4,9 @@ use {super::*, bitcoincore_rpc::Auth}; #[serde(default, deny_unknown_fields)] pub struct Settings { bitcoin_data_dir: Option, - bitcoin_password: Option, + bitcoin_rpc_password: Option, bitcoin_rpc_url: Option, - bitcoin_username: Option, + bitcoin_rpc_username: Option, chain: Option, commit_interval: Option, cookie_file: Option, @@ -90,7 +90,10 @@ impl Settings { .or(config) .or_defaults()?; - match (&settings.bitcoin_username, &settings.bitcoin_password) { + match ( + &settings.bitcoin_rpc_username, + &settings.bitcoin_rpc_password, + ) { (None, Some(_rpc_pass)) => bail!("no bitcoin RPC username specified"), (Some(_rpc_user), None) => bail!("no bitcoin RPC password specified"), _ => {} @@ -108,9 +111,9 @@ impl Settings { pub(crate) fn or(self, source: Settings) -> Self { Self { bitcoin_data_dir: self.bitcoin_data_dir.or(source.bitcoin_data_dir), - bitcoin_password: self.bitcoin_password.or(source.bitcoin_password), + bitcoin_rpc_password: self.bitcoin_rpc_password.or(source.bitcoin_rpc_password), bitcoin_rpc_url: self.bitcoin_rpc_url.or(source.bitcoin_rpc_url), - bitcoin_username: self.bitcoin_username.or(source.bitcoin_username), + bitcoin_rpc_username: self.bitcoin_rpc_username.or(source.bitcoin_rpc_username), chain: self.chain.or(source.chain), commit_interval: self.commit_interval.or(source.commit_interval), cookie_file: self.cookie_file.or(source.cookie_file), @@ -144,9 +147,9 @@ impl Settings { pub(crate) fn from_options(options: Options) -> Self { Self { bitcoin_data_dir: options.bitcoin_data_dir, - bitcoin_password: options.bitcoin_password, + bitcoin_rpc_password: options.bitcoin_rpc_password, bitcoin_rpc_url: options.bitcoin_rpc_url, - bitcoin_username: options.bitcoin_username, + bitcoin_rpc_username: options.bitcoin_rpc_username, chain: options .signet .then_some(Chain::Signet) @@ -224,9 +227,9 @@ impl Settings { Ok(Self { bitcoin_data_dir: get_path("BITCOIN_DATA_DIR"), - bitcoin_password: get_string("BITCOIN_PASSWORD"), + bitcoin_rpc_password: get_string("BITCOIN_RPC_PASSWORD"), bitcoin_rpc_url: get_string("BITCOIN_RPC_URL"), - bitcoin_username: get_string("BITCOIN_USERNAME"), + bitcoin_rpc_username: get_string("BITCOIN_RPC_USERNAME"), chain: get_chain("CHAIN")?, commit_interval: get_usize("COMMIT_INTERVAL")?, cookie_file: get_path("COOKIE_FILE"), @@ -284,14 +287,14 @@ impl Settings { Ok(Self { bitcoin_data_dir: Some(bitcoin_data_dir), - bitcoin_password: self.bitcoin_password, + bitcoin_rpc_password: self.bitcoin_rpc_password, bitcoin_rpc_url: Some( self .bitcoin_rpc_url .clone() .unwrap_or_else(|| format!("127.0.0.1:{}", chain.default_rpc_port())), ), - bitcoin_username: self.bitcoin_username, + bitcoin_rpc_username: self.bitcoin_rpc_username, chain: Some(chain), commit_interval: Some(self.commit_interval.unwrap_or(5000)), cookie_file: Some(cookie_file), @@ -327,9 +330,9 @@ impl Settings { pub(crate) fn bitcoin_credentials(&self) -> Result { if let Some((user, pass)) = &self - .bitcoin_username + .bitcoin_rpc_username .as_ref() - .zip(self.bitcoin_password.as_ref()) + .zip(self.bitcoin_rpc_password.as_ref()) { Ok(Auth::UserPass((*user).clone(), (*pass).clone())) } else { @@ -537,7 +540,7 @@ mod tests { assert_eq!( Settings::merge( Options { - bitcoin_username: Some("foo".into()), + bitcoin_rpc_username: Some("foo".into()), ..Default::default() }, Default::default(), @@ -554,7 +557,7 @@ mod tests { assert_eq!( Settings::merge( Options { - bitcoin_password: Some("foo".into()), + bitcoin_rpc_password: Some("foo".into()), ..Default::default() }, Default::default(), @@ -569,7 +572,7 @@ mod tests { #[test] fn auth_with_user_and_pass() { assert_eq!( - parse(&["--bitcoin-username=foo", "--bitcoin-password=bar"]) + parse(&["--bitcoin-rpc-username=foo", "--bitcoin-rpc-password=bar"]) .bitcoin_credentials() .unwrap(), Auth::UserPass("foo".into(), "bar".into()) @@ -852,19 +855,19 @@ mod tests { assert_eq!( Settings::merge( Options { - bitcoin_username: Some("option_user".into()), - bitcoin_password: Some("option_pass".into()), + bitcoin_rpc_username: Some("option_user".into()), + bitcoin_rpc_password: Some("option_pass".into()), ..Default::default() }, vec![ - ("BITCOIN_USER".into(), "env_user".into()), - ("BITCOIN_PASS".into(), "env_pass".into()), + ("BITCOIN_RPC_USERNAME".into(), "env_user".into()), + ("BITCOIN_RPC_PASSWORD".into(), "env_pass".into()), ] .into_iter() .collect(), Settings { - bitcoin_username: Some("config_user".into()), - bitcoin_password: Some("config_pass".into()), + bitcoin_rpc_username: Some("config_user".into()), + bitcoin_rpc_password: Some("config_pass".into()), ..Default::default() } ) @@ -878,14 +881,14 @@ mod tests { Settings::merge( Default::default(), vec![ - ("BITCOIN_USERNAME".into(), "env_user".into()), - ("BITCOIN_PASSWORD".into(), "env_pass".into()), + ("BITCOIN_RPC_USERNAME".into(), "env_user".into()), + ("BITCOIN_RPC_PASSWORD".into(), "env_pass".into()), ] .into_iter() .collect(), Settings { - bitcoin_username: Some("config_user".into()), - bitcoin_password: Some("config_pass".into()), + bitcoin_rpc_username: Some("config_user".into()), + bitcoin_rpc_password: Some("config_pass".into()), ..Default::default() } ) @@ -900,8 +903,8 @@ mod tests { Default::default(), Default::default(), Settings { - bitcoin_username: Some("config_user".into()), - bitcoin_password: Some("config_pass".into()), + bitcoin_rpc_username: Some("config_user".into()), + bitcoin_rpc_password: Some("config_pass".into()), ..Default::default() } ) @@ -929,9 +932,9 @@ mod tests { fn from_env() { let env = vec![ ("BITCOIN_DATA_DIR", "/bitcoin/data/dir"), - ("BITCOIN_PASSWORD", "bitcoin password"), + ("BITCOIN_RPC_PASSWORD", "bitcoin password"), ("BITCOIN_RPC_URL", "url"), - ("BITCOIN_USERNAME", "bitcoin username"), + ("BITCOIN_RPC_USERNAME", "bitcoin username"), ("CHAIN", "signet"), ("COMMIT_INTERVAL", "1"), ("COOKIE_FILE", "cookie file"), @@ -958,9 +961,9 @@ mod tests { Settings::from_env(env).unwrap(), Settings { bitcoin_data_dir: Some("/bitcoin/data/dir".into()), - bitcoin_password: Some("bitcoin password".into()), + bitcoin_rpc_password: Some("bitcoin password".into()), bitcoin_rpc_url: Some("url".into()), - bitcoin_username: Some("bitcoin username".into()), + bitcoin_rpc_username: Some("bitcoin username".into()), chain: Some(Chain::Signet), commit_interval: Some(1), cookie_file: Some("cookie file".into()), @@ -1000,21 +1003,21 @@ mod tests { Options::try_parse_from([ "ord", "--bitcoin-data-dir=/bitcoin/data/dir", - "--bitcoin-password=bitcoin password", + "--bitcoin-rpc-password=bitcoin password", "--bitcoin-rpc-url=url", - "--bitcoin-username=bitcoin username", + "--bitcoin-rpc-username=bitcoin username", "--chain=signet", "--commit-interval=1", "--cookie-file=cookie file", "--data-dir=/data/dir", "--first-inscription-height=2", "--height-limit=3", - "--index=index", "--index-cache-size=4", "--index-runes", "--index-sats", "--index-spent-sats", "--index-transactions", + "--index=index", "--integration-test", "--no-index-inscriptions", "--server-password=server password", @@ -1024,9 +1027,9 @@ mod tests { ), Settings { bitcoin_data_dir: Some("/bitcoin/data/dir".into()), - bitcoin_password: Some("bitcoin password".into()), + bitcoin_rpc_password: Some("bitcoin password".into()), bitcoin_rpc_url: Some("url".into()), - bitcoin_username: Some("bitcoin username".into()), + bitcoin_rpc_username: Some("bitcoin username".into()), chain: Some(Chain::Signet), commit_interval: Some(1), cookie_file: Some("cookie file".into()), diff --git a/tests/settings.rs b/tests/settings.rs index 2db9cb5592..78c2cc2e6e 100644 --- a/tests/settings.rs +++ b/tests/settings.rs @@ -7,9 +7,9 @@ fn default() { .stdout_regex( r#"\{ "bitcoin_data_dir": ".*(Bitcoin|bitcoin)", - "bitcoin_password": null, + "bitcoin_rpc_password": null, "bitcoin_rpc_url": "127.0.0.1:8332", - "bitcoin_username": null, + "bitcoin_rpc_username": null, "chain": "mainnet", "commit_interval": 5000, "cookie_file": ".*\.cookie", From 20cf035b2a0aa30cba219efd7522a5762ad762cd Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 6 Mar 2024 19:22:34 -0800 Subject: [PATCH 11/11] Tweak --- tests/server.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/server.rs b/tests/server.rs index b944da103b..0654992ba4 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -405,13 +405,13 @@ fn expected_sat_time_is_rounded() { fn missing_credentials() { let rpc_server = test_bitcoincore_rpc::spawn(); - CommandBuilder::new("--bitcoin-username foo server") + CommandBuilder::new("--bitcoin-rpc-username foo server") .bitcoin_rpc_server(&rpc_server) .expected_exit_code(1) .expected_stderr("error: no bitcoin RPC password specified\n") .run_and_extract_stdout(); - CommandBuilder::new("--bitcoin-password bar server") + CommandBuilder::new("--bitcoin-rpc-password bar server") .bitcoin_rpc_server(&rpc_server) .expected_exit_code(1) .expected_stderr("error: no bitcoin RPC username specified\n")