Skip to main content

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_LNV1_ENV: &str = "FM_ENABLE_MODULE_LNV1";
20pub const FM_ENABLE_MODULE_LNV2_ENV: &str = "FM_ENABLE_MODULE_LNV2";
21
22/// Disable mint base fees for testing and development environments
23pub const FM_DISABLE_BASE_FEES_ENV: &str = "FM_DISABLE_BASE_FEES";
24
25/// Print sensitive secrets without redacting them. Use only for debugging.
26pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
27
28/// Check if env variable is set and not equal `0` or `false` which are common
29/// ways to disable something.
30pub fn is_env_var_set(var: &str) -> bool {
31    let Some(val) = std::env::var_os(var) else {
32        return false;
33    };
34    match val.as_encoded_bytes() {
35        b"0" | b"false" => false,
36        b"1" | b"true" => true,
37        _ => {
38            warn!(
39                target: LOG_CORE,
40                %var,
41                val = %val.to_string_lossy(),
42                "Env var value invalid is invalid and ignored, assuming `true`"
43            );
44            true
45        }
46    }
47}
48
49/// Check if env variable is set and not equal `0` or `false` which are common
50/// ways to disable a setting. `None` if env var not set at all, which allows
51/// handling the default value.
52pub fn is_env_var_set_opt(var: &str) -> Option<bool> {
53    let val = std::env::var_os(var)?;
54    match val.as_encoded_bytes() {
55        b"0" | b"false" => Some(false),
56        b"1" | b"true" => Some(true),
57        _ => {
58            warn!(
59                target: LOG_CORE,
60                %var,
61                val = %val.to_string_lossy(),
62                "Env var value invalid is invalid and ignored"
63            );
64            None
65        }
66    }
67}
68
69/// Use to detect if running in a test environment, either `cargo test` or
70/// `devimint`.
71pub fn is_running_in_test_env() -> bool {
72    let unit_test = cfg!(test);
73
74    unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
75}
76
77/// How long to wait before polling peers for their supported module consensus
78/// version again.
79///
80/// The first poll of a freshly started process races the peers' API servers
81/// binding and normally loses, so a round that failed to reach everyone is
82/// retried soon rather than after the full interval -- otherwise activation is
83/// dead for that interval after every restart. A peer that stays unreachable is
84/// then polled at the short interval indefinitely, which is a few requests per
85/// minute.
86pub fn next_poll_delay(reached_all_peers: bool) -> std::time::Duration {
87    if is_running_in_test_env() {
88        std::time::Duration::from_secs(5)
89    } else if reached_all_peers {
90        std::time::Duration::from_secs(600)
91    } else {
92        std::time::Duration::from_secs(30)
93    }
94}
95
96/// Get value of `FEDIMINT_BUILD_CODE_VERSION` at compile time
97#[macro_export]
98macro_rules! fedimint_build_code_version_env {
99    () => {
100        env!("FEDIMINT_BUILD_CODE_VERSION")
101    };
102}
103
104/// Env var for bitcoin RPC kind (obsolete, use FM_DEFAULT_* instead)
105pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
106/// Env var for bitcoin URL (obsolete, use FM_DEFAULT_* instead)
107pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
108/// Env var how often to poll bitcoin source
109pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
110
111/// Env var for bitcoin RPC kind (default, used only as a default value for DKG
112/// config settings)
113pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
114pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
115/// Env var for bitcoin URL (default, used only as a default value for DKG
116/// config settings)
117pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
118pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
119
120/// Env var for bitcoin RPC kind (forced, takes priority over config settings)
121pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
122pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
123/// Env var for bitcoin URL (default, takes priority over config settings)
124pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
125pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
126
127/// Env var to override iroh connectivity
128///
129/// Comma separated key-value list (`<node_id>=<ticket>,<node_id>=<ticket>,...`)
130pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
131
132/// Env var to override iroh connectivity
133///
134/// Comma separated key-value list (`<node_id>=<ticket>,<node_id>=<ticket>,...`)
135pub const FM_GW_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES";
136
137/// Env var to override iroh DNS server
138pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
139
140/// Env var to override iroh relays server
141pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
142
143/// Env var to disable Iroh's use of DHT
144pub const FM_IROH_DHT_ENABLE_ENV: &str = "FM_IROH_DHT_ENABLE";
145
146/// Env var to disable default n0 discovery
147pub const FM_IROH_N0_DISCOVERY_ENABLE_ENV: &str = "FM_IROH_N0_DISCOVERY_ENABLE";
148
149/// Env var to disable default pkarr resolver
150pub const FM_IROH_PKARR_RESOLVER_ENABLE_ENV: &str = "FM_IROH_PKARR_RESOLVER_ENABLE";
151
152/// Env var to disable default pkarr publisher
153pub const FM_IROH_PKARR_PUBLISHER_ENABLE_ENV: &str = "FM_IROH_PKARR_PUBLISHER_ENABLE";
154
155/// Env var to disable Iroh's use of relays
156pub const FM_IROH_RELAYS_ENABLE_ENV: &str = "FM_IROH_RELAYS_ENABLE";
157
158/// Env var to override tcp api connectivity
159///
160/// Comma separated key-value list (`peer_id=url,peer_id=url`)
161pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
162
163pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
164pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
165
166/// List of json api endpoint sources to use as a source of
167/// fee rate estimation.
168///
169/// `;`-separated list of urls with part after `#`
170/// ("fragment") specifying jq filter to extract sats/vB fee rate.
171/// Eg. `https://mempool.space/api/v1/fees/recommended#.halfHourFee`
172///
173/// Note that `#` is a standalone separator and *not* parsed as a part of the
174/// Url. Which means there's no need to escape it.
175pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
176
177/// `devimint` will set when code is running inside `devimint`
178pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
179
180/// Configuration for the bitcoin RPC
181#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
182pub struct BitcoinRpcConfig {
183    pub kind: String,
184    pub url: SafeUrl,
185}
186
187impl BitcoinRpcConfig {
188    pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
189        Ok(Self {
190        kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
191            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
192            .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
193                warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
194            }))
195            .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
196                warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
197            }))
198            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
199                warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
200            }))
201            .with_context(|| {
202                anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
203            })?,
204        url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
205            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
206            .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
207                warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
208            }))
209            .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
210                warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
211            }))
212            .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
213                warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
214            }))
215            .with_context(|| {
216                anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
217            })?
218            .parse()
219            .with_context(|| {
220                anyhow::anyhow!("failure parsing Bitcoin RPC URL")
221            })?,
222    })
223    }
224}
225
226pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
227where
228    K: FromStr + cmp::Ord,
229    <K as FromStr>::Err: std::error::Error,
230    V: FromStr,
231    <V as FromStr>::Err: std::error::Error,
232{
233    let mut map = BTreeMap::new();
234    let Ok(env_value) = std::env::var(env) else {
235        return Ok(BTreeMap::new());
236    };
237    for kv in env_value.split(',') {
238        let kv = kv.trim();
239
240        if kv.is_empty() {
241            continue;
242        }
243
244        if let Some((k, v)) = kv.split_once('=') {
245            let Some(k) = K::from_str(k)
246                .inspect_err(|err| {
247                    warn!(
248                        target: LOG_CORE,
249                        err = %err.fmt_compact(),
250                        "Error parsing value"
251                    );
252                })
253                .ok()
254            else {
255                continue;
256            };
257            let Some(v) = V::from_str(v)
258                .inspect_err(|err| {
259                    warn!(
260                        target: LOG_CORE,
261                        err = %err.fmt_compact(),
262                        "Error parsing value"
263                    );
264                })
265                .ok()
266            else {
267                continue;
268            };
269
270            map.insert(k, v);
271        }
272    }
273
274    Ok(map)
275}