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_LNV1_ENV: &str = "FM_ENABLE_MODULE_LNV1";
20pub const FM_ENABLE_MODULE_LNV2_ENV: &str = "FM_ENABLE_MODULE_LNV2";
21
22pub const FM_DISABLE_BASE_FEES_ENV: &str = "FM_DISABLE_BASE_FEES";
24
25pub const FM_DEBUG_SHOW_SECRETS_ENV: &str = "FM_DEBUG_SHOW_SECRETS";
27
28pub 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
49pub 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
69pub 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
77pub fn is_rbf_withdrawal_enabled() -> bool {
79 is_env_var_set("FM_UNSAFE_ENABLE_RBF_WITHDRAWAL")
80}
81
82#[macro_export]
84macro_rules! fedimint_build_code_version_env {
85 () => {
86 env!("FEDIMINT_BUILD_CODE_VERSION")
87 };
88}
89
90pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
92pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
94pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
96
97pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
100pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
101pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
104pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
105
106pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
108pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
109pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
111pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
112
113pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
117
118pub const FM_GW_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES";
122
123pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
125
126pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
128
129pub const FM_IROH_DHT_ENABLE_ENV: &str = "FM_IROH_DHT_ENABLE";
131
132pub const FM_IROH_N0_DISCOVERY_ENABLE_ENV: &str = "FM_IROH_N0_DISCOVERY_ENABLE";
134
135pub const FM_IROH_PKARR_RESOLVER_ENABLE_ENV: &str = "FM_IROH_PKARR_RESOLVER_ENABLE";
137
138pub const FM_IROH_PKARR_PUBLISHER_ENABLE_ENV: &str = "FM_IROH_PKARR_PUBLISHER_ENABLE";
140
141pub const FM_IROH_RELAYS_ENABLE_ENV: &str = "FM_IROH_RELAYS_ENABLE";
143
144pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
148
149pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
150pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
151
152pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
162
163pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
165
166#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
168pub struct BitcoinRpcConfig {
169 pub kind: String,
170 pub url: SafeUrl,
171}
172
173impl BitcoinRpcConfig {
174 pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
175 Ok(Self {
176 kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
177 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
178 .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
179 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
180 }))
181 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
182 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
183 }))
184 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
185 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
186 }))
187 .with_context(|| {
188 anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
189 })?,
190 url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
191 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
192 .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
193 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
194 }))
195 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
196 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
197 }))
198 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
199 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
200 }))
201 .with_context(|| {
202 anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
203 })?
204 .parse()
205 .with_context(|| {
206 anyhow::anyhow!("failure parsing Bitcoin RPC URL")
207 })?,
208 })
209 }
210}
211
212pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
213where
214 K: FromStr + cmp::Ord,
215 <K as FromStr>::Err: std::error::Error,
216 V: FromStr,
217 <V as FromStr>::Err: std::error::Error,
218{
219 let mut map = BTreeMap::new();
220 let Ok(env_value) = std::env::var(env) else {
221 return Ok(BTreeMap::new());
222 };
223 for kv in env_value.split(',') {
224 let kv = kv.trim();
225
226 if kv.is_empty() {
227 continue;
228 }
229
230 if let Some((k, v)) = kv.split_once('=') {
231 let Some(k) = K::from_str(k)
232 .inspect_err(|err| {
233 warn!(
234 target: LOG_CORE,
235 err = %err.fmt_compact(),
236 "Error parsing value"
237 );
238 })
239 .ok()
240 else {
241 continue;
242 };
243 let Some(v) = V::from_str(v)
244 .inspect_err(|err| {
245 warn!(
246 target: LOG_CORE,
247 err = %err.fmt_compact(),
248 "Error parsing value"
249 );
250 })
251 .ok()
252 else {
253 continue;
254 };
255
256 map.insert(k, v);
257 }
258 }
259
260 Ok(map)
261}