fedimint_core/
envs.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::{cmp, env};
4
5use anyhow::Context;
6use fedimint_core::util::SafeUrl;
7use fedimint_derive::{Decodable, Encodable};
8use fedimint_logging::LOG_CORE;
9use jsonrpsee_core::Serialize;
10use serde::Deserialize;
11use tracing::warn;
12
13use crate::util::FmtCompact as _;
14
15/// In tests we want to routinely enable an extra unknown module to ensure
16/// all client code handles correct modules that client doesn't know about.
17pub const FM_USE_UNKNOWN_MODULE_ENV: &str = "FM_USE_UNKNOWN_MODULE";
18
19pub const FM_ENABLE_MODULE_LNV2_ENV: &str = "FM_ENABLE_MODULE_LNV2";
20
21/// Enable mint base fees for testing and development environments
22pub const FM_ENABLE_MINT_BASE_FEES_ENV: &str = "FM_ENABLE_MINT_BASE_FEES";
23
24/// Print sensitive secrets without redacting them. Use only for debugging.
25pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
26
27/// Check if env variable is set and not equal `0` or `false` which are common
28/// ways to disable something.
29pub fn is_env_var_set(var: &str) -> bool {
30    std::env::var_os(var).is_some_and(|v| v != "0" && v != "false")
31}
32
33/// Use to detect if running in a test environment, either `cargo test` or
34/// `devimint`.
35pub fn is_running_in_test_env() -> bool {
36    let unit_test = cfg!(test);
37
38    unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
39}
40
41/// Use to allow `process_output` to process RBF withdrawal outputs.
42pub fn is_rbf_withdrawal_enabled() -> bool {
43    is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
44}
45
46/// Get value of `FEDIMINT_BUILD_CODE_VERSION` at compile time
47#[macro_export]
48macro_rules! fedimint_build_code_version_env {
49    () => {
50        env!("FEDIMINT_BUILD_CODE_VERSION")
51    };
52}
53
54/// Env var for bitcoin RPC kind (obsolete, use FM_DEFAULT_* instead)
55pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
56/// Env var for bitcoin URL (obsolete, use FM_DEFAULT_* instead)
57pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
58/// Env var how often to poll bitcoin source
59pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
60
61/// Env var for bitcoin RPC kind (default, used only as a default value for DKG
62/// config settings)
63pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
64pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
65/// Env var for bitcoin URL (default, used only as a default value for DKG
66/// config settings)
67pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
68pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
69
70/// Env var for bitcoin RPC kind (forced, takes priority over config settings)
71pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
72pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
73/// Env var for bitcoin URL (default, takes priority over config settings)
74pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
75pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
76
77/// Env var to override iroh connectivity
78///
79/// Comma separated key-value list (`<node_id>=<ticket>,<node_id>=<ticket>,...`)
80pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
81
82/// Env var to override iroh DNS server
83pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
84
85/// Env var to override iroh relays server
86pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
87
88/// Env var to override tcp api connectivity
89///
90/// Comma separated key-value list (`peer_id=url,peer_id=url`)
91pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
92
93pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
94pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
95
96/// List of json api endpoint sources to use as a source of
97/// fee rate estimation.
98///
99/// `;`-separated list of urls with part after `#`
100/// ("fragment") specifying jq filter to extract sats/vB fee rate.
101/// Eg. `https://mempool.space/api/v1/fees/recommended#.halfHourFee`
102///
103/// Note that `#` is a standalone separator and *not* parsed as a part of the
104/// Url. Which means there's no need to escape it.
105pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
106
107/// `devimint` will set when code is running inside `devimint`
108pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
109
110/// Configuration for the bitcoin RPC
111#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
112pub struct BitcoinRpcConfig {
113    pub kind: String,
114    pub url: SafeUrl,
115}
116
117impl BitcoinRpcConfig {
118    pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
119        Ok(Self {
120        kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
121            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
122            .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
123                warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
124            }))
125            .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
126                warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
127            }))
128            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
129                warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
130            }))
131            .with_context(|| {
132                anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
133            })?,
134        url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
135            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
136            .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
137                warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
138            }))
139            .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
140                warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
141            }))
142            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
143                warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
144            }))
145            .with_context(|| {
146                anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
147            })?
148            .parse()
149            .with_context(|| {
150                anyhow::anyhow!("failure parsing Bitcoin RPC URL")
151            })?,
152    })
153    }
154}
155
156pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
157where
158    K: FromStr + cmp::Ord,
159    <K as FromStr>::Err: std::error::Error,
160    V: FromStr,
161    <V as FromStr>::Err: std::error::Error,
162{
163    let mut map = BTreeMap::new();
164    let Ok(env_value) = std::env::var(env) else {
165        return Ok(BTreeMap::new());
166    };
167    for kv in env_value.split(',') {
168        let kv = kv.trim();
169
170        if kv.is_empty() {
171            continue;
172        }
173
174        if let Some((k, v)) = kv.split_once('=') {
175            let Some(k) = K::from_str(k)
176                .inspect_err(|err| {
177                    warn!(
178                        target: LOG_CORE,
179                        err = %err.fmt_compact(),
180                        "Error parsing value"
181                    );
182                })
183                .ok()
184            else {
185                continue;
186            };
187            let Some(v) = V::from_str(v)
188                .inspect_err(|err| {
189                    warn!(
190                        target: LOG_CORE,
191                        err = %err.fmt_compact(),
192                        "Error parsing value"
193                    );
194                })
195                .ok()
196            else {
197                continue;
198            };
199
200            map.insert(k, v);
201        }
202    }
203
204    Ok(map)
205}