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