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
15pub 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
21pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
23
24pub fn is_env_var_set(var: &str) -> bool {
27 std::env::var_os(var).is_some_and(|v| v != "0" && v != "false")
28}
29
30pub fn is_running_in_test_env() -> bool {
33 let unit_test = cfg!(test);
34
35 unit_test || is_env_var_set("NEXTEST") || is_env_var_set(FM_IN_DEVIMINT_ENV)
36}
37
38pub fn is_rbf_withdrawal_enabled() -> bool {
40 is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
41}
42
43#[macro_export]
45macro_rules! fedimint_build_code_version_env {
46 () => {
47 env!("FEDIMINT_BUILD_CODE_VERSION")
48 };
49}
50
51pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
53pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
55pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
57
58pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
61pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
62pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
65pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
66
67pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
69pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
70pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
72pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
73
74pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
78
79pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
83
84pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
85pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
86
87pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
97
98pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
100
101#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
103pub struct BitcoinRpcConfig {
104 pub kind: String,
105 pub url: SafeUrl,
106}
107
108impl BitcoinRpcConfig {
109 pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
110 Ok(Self {
111 kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
112 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
113 .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
114 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
115 }))
116 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
117 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
118 }))
119 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
120 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
121 }))
122 .with_context(|| {
123 anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
124 })?,
125 url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
126 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
127 .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
128 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
129 }))
130 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
131 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
132 }))
133 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
134 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
135 }))
136 .with_context(|| {
137 anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
138 })?
139 .parse()
140 .with_context(|| {
141 anyhow::anyhow!("failure parsing Bitcoin RPC URL")
142 })?,
143 })
144 }
145}
146
147pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
148where
149 K: FromStr + cmp::Ord,
150 <K as FromStr>::Err: std::error::Error,
151 V: FromStr,
152 <V as FromStr>::Err: std::error::Error,
153{
154 let mut map = BTreeMap::new();
155 let Ok(env_value) = std::env::var(env) else {
156 return Ok(BTreeMap::new());
157 };
158 for kv in env_value.split(',') {
159 let kv = kv.trim();
160
161 if kv.is_empty() {
162 continue;
163 }
164
165 if let Some((k, v)) = kv.split_once('=') {
166 let Some(k) = K::from_str(k)
167 .inspect_err(|err| {
168 warn!(
169 target: LOG_CORE,
170 err = %err.fmt_compact(),
171 "Error parsing value"
172 );
173 })
174 .ok()
175 else {
176 continue;
177 };
178 let Some(v) = V::from_str(v)
179 .inspect_err(|err| {
180 warn!(
181 target: LOG_CORE,
182 err = %err.fmt_compact(),
183 "Error parsing value"
184 );
185 })
186 .ok()
187 else {
188 continue;
189 };
190
191 map.insert(k, v);
192 }
193 }
194
195 Ok(map)
196}