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