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 fn is_env_var_set(var: &str) -> bool {
24 std::env::var_os(var).is_some_and(|v| v != "0" && v != "false")
25}
26
27pub 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
35pub fn is_rbf_withdrawal_enabled() -> bool {
37 is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
38}
39
40#[macro_export]
42macro_rules! fedimint_build_code_version_env {
43 () => {
44 env!("FEDIMINT_BUILD_CODE_VERSION")
45 };
46}
47
48pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
50pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
52pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
54
55pub 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";
59pub 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
64pub 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";
67pub 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
71pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
75
76pub 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
84pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
94
95pub const FM_BITCOIND_COOKIE_FILE_ENV: &str = "FM_BITCOIND_COOKIE_FILE";
98
99pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
101
102#[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}