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 next_poll_delay(reached_all_peers: bool) -> std::time::Duration {
87 if is_running_in_test_env() {
88 std::time::Duration::from_secs(5)
89 } else if reached_all_peers {
90 std::time::Duration::from_secs(600)
91 } else {
92 std::time::Duration::from_secs(30)
93 }
94}
95
96#[macro_export]
98macro_rules! fedimint_build_code_version_env {
99 () => {
100 env!("FEDIMINT_BUILD_CODE_VERSION")
101 };
102}
103
104pub const FM_BITCOIN_RPC_KIND_ENV: &str = "FM_BITCOIN_RPC_KIND";
106pub const FM_BITCOIN_RPC_URL_ENV: &str = "FM_BITCOIN_RPC_URL";
108pub const FM_BITCOIN_POLLING_INTERVAL_SECS_ENV: &str = "FM_BITCOIN_POLLING_INTERVAL_SECS";
110
111pub const FM_DEFAULT_BITCOIN_RPC_KIND_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_KIND";
114pub const FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_KIND";
115pub const FM_DEFAULT_BITCOIN_RPC_URL_ENV: &str = "FM_DEFAULT_BITCOIN_RPC_URL";
118pub const FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_DEFAULT_BITCOIND_RPC_URL";
119
120pub const FM_FORCE_BITCOIN_RPC_KIND_ENV: &str = "FM_FORCE_BITCOIN_RPC_KIND";
122pub const FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_BAD_KIND";
123pub const FM_FORCE_BITCOIN_RPC_URL_ENV: &str = "FM_FORCE_BITCOIN_RPC_URL";
125pub const FM_FORCE_BITCOIN_RPC_URL_BAD_ENV: &str = "FM_FORCE_BITCOIND_RPC_URL";
126
127pub const FM_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_IROH_CONNECT_OVERRIDES";
131
132pub const FM_GW_IROH_CONNECT_OVERRIDES_ENV: &str = "FM_GW_IROH_CONNECT_OVERRIDES";
136
137pub const FM_IROH_DNS_ENV: &str = "FM_IROH_DNS";
139
140pub const FM_IROH_RELAY_ENV: &str = "FM_IROH_RELAY";
142
143pub const FM_IROH_DHT_ENABLE_ENV: &str = "FM_IROH_DHT_ENABLE";
145
146pub const FM_IROH_N0_DISCOVERY_ENABLE_ENV: &str = "FM_IROH_N0_DISCOVERY_ENABLE";
148
149pub const FM_IROH_PKARR_RESOLVER_ENABLE_ENV: &str = "FM_IROH_PKARR_RESOLVER_ENABLE";
151
152pub const FM_IROH_PKARR_PUBLISHER_ENABLE_ENV: &str = "FM_IROH_PKARR_PUBLISHER_ENABLE";
154
155pub const FM_IROH_RELAYS_ENABLE_ENV: &str = "FM_IROH_RELAYS_ENABLE";
157
158pub const FM_WS_API_CONNECT_OVERRIDES_ENV: &str = "FM_WS_API_CONNECT_OVERRIDES";
162
163pub const FM_IROH_API_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_API_SECRET_KEY_OVERRIDE";
164pub const FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV: &str = "FM_IROH_P2P_SECRET_KEY_OVERRIDE";
165
166pub const FM_WALLET_FEERATE_SOURCES_ENV: &str = "FM_WALLET_FEERATE_SOURCES";
176
177pub const FM_IN_DEVIMINT_ENV: &str = "FM_IN_DEVIMINT";
179
180#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
182pub struct BitcoinRpcConfig {
183 pub kind: String,
184 pub url: SafeUrl,
185}
186
187impl BitcoinRpcConfig {
188 pub fn get_defaults_from_env_vars() -> anyhow::Result<Self> {
189 Ok(Self {
190 kind: env::var(FM_FORCE_BITCOIN_RPC_KIND_ENV)
191 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_ENV))
192 .or_else(|_| env::var(FM_BITCOIN_RPC_KIND_ENV).inspect(|_v| {
193 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_KIND_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
194 }))
195 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
196 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_KIND_ENV} instead");
197 }))
198 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV).inspect(|_v| {
199 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_KIND_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_KIND_ENV} instead");
200 }))
201 .with_context(|| {
202 anyhow::anyhow!("failure looking up env var for Bitcoin RPC kind")
203 })?,
204 url: env::var(FM_FORCE_BITCOIN_RPC_URL_ENV)
205 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_ENV))
206 .or_else(|_| env::var(FM_BITCOIN_RPC_URL_ENV).inspect(|_v| {
207 warn!(target: LOG_CORE, "{FM_BITCOIN_RPC_URL_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
208 }))
209 .or_else(|_| env::var(FM_FORCE_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
210 warn!(target: LOG_CORE, "{FM_FORCE_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_FORCE_BITCOIN_RPC_URL_ENV} instead");
211 }))
212 .or_else(|_| env::var(FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV).inspect(|_v| {
213 warn!(target: LOG_CORE, "{FM_DEFAULT_BITCOIN_RPC_URL_BAD_ENV} is obsolete, use {FM_DEFAULT_BITCOIN_RPC_URL_ENV} instead");
214 }))
215 .with_context(|| {
216 anyhow::anyhow!("failure looking up env var for Bitcoin RPC URL")
217 })?
218 .parse()
219 .with_context(|| {
220 anyhow::anyhow!("failure parsing Bitcoin RPC URL")
221 })?,
222 })
223 }
224}
225
226pub fn parse_kv_list_from_env<K, V>(env: &str) -> anyhow::Result<BTreeMap<K, V>>
227where
228 K: FromStr + cmp::Ord,
229 <K as FromStr>::Err: std::error::Error,
230 V: FromStr,
231 <V as FromStr>::Err: std::error::Error,
232{
233 let mut map = BTreeMap::new();
234 let Ok(env_value) = std::env::var(env) else {
235 return Ok(BTreeMap::new());
236 };
237 for kv in env_value.split(',') {
238 let kv = kv.trim();
239
240 if kv.is_empty() {
241 continue;
242 }
243
244 if let Some((k, v)) = kv.split_once('=') {
245 let Some(k) = K::from_str(k)
246 .inspect_err(|err| {
247 warn!(
248 target: LOG_CORE,
249 err = %err.fmt_compact(),
250 "Error parsing value"
251 );
252 })
253 .ok()
254 else {
255 continue;
256 };
257 let Some(v) = V::from_str(v)
258 .inspect_err(|err| {
259 warn!(
260 target: LOG_CORE,
261 err = %err.fmt_compact(),
262 "Error parsing value"
263 );
264 })
265 .ok()
266 else {
267 continue;
268 };
269
270 map.insert(k, v);
271 }
272 }
273
274 Ok(map)
275}