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_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
31pub const FM_DISABLE_BASE_FEES_ENV: &str = "FM_DISABLE_BASE_FEES";
33
34pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
36
37pub 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
58pub 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
78pub 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
86pub 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
105pub fn is_automatic_consensus_version_voting_disabled() -> bool {
108 is_env_var_set(FM_WALLET_DISABLE_AUTOMATIC_CONSENSUS_VERSION_VOTING_ENV)
109}
110
111#[macro_export]
113macro_rules! fedimint_build_code_version_env {
114 () => {
115 env!("FEDIMINT_BUILD_CODE_VERSION")
116 };
117}
118
119pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
121pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
123pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
125
126pub 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";
130pub 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
135pub 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";
138pub 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
142pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
153
154pub const FM_GW_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES";
157
158pub const FM_IROH_CONNECT_OVERRIDES_PLAIN_ENV: &str = "FM_IROH_CONNECT_OVERRIDES_PLAIN";
166
167pub const FM_GW_IROH_CONNECT_OVERRIDES_PLAIN_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES_PLAIN";
170
171pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
173
174pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
176
177pub const FM_IROH_DHT_ENABLE_ENV: &str = "FM_IROH_DHT_ENABLE";
179
180pub const FM_IROH_N0_DISCOVERY_ENABLE_ENV: &str = "FM_IROH_N0_DISCOVERY_ENABLE";
182
183pub const FM_IROH_PKARR_RESOLVER_ENABLE_ENV: &str = "FM_IROH_PKARR_RESOLVER_ENABLE";
185
186pub const FM_IROH_PKARR_PUBLISHER_ENABLE_ENV: &str = "FM_IROH_PKARR_PUBLISHER_ENABLE";
188
189pub const FM_IROH_RELAYS_ENABLE_ENV: &str = "FM_IROH_RELAYS_ENABLE";
191
192pub const FM_PKARR_ENABLE_ENV: &str = "FM_PKARR_ENABLE";
194
195pub const FM_PKARR_DHT_ENABLE_ENV: &str = "FM_PKARR_DHT_ENABLE";
197
198pub const FM_PKARR_RELAYS_ENABLE_ENV: &str = "FM_PKARR_RELAYS_ENABLE";
200
201pub 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
209pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
219
220pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
222
223#[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}