Skip to main content

devimint/
vars.rs

1#![allow(non_snake_case)]
2
3mod net_overrides;
4use std::path::{Path, PathBuf};
5use std::str::FromStr as _;
6pub trait ToEnvVar {
7    fn to_env_value(&self) -> Option<String> {
8        panic!("Must implement one of the two ToEnvVar methods");
9    }
10
11    fn to_env_values(&self, base: &str) -> impl Iterator<Item = (String, String)> {
12        self.to_env_value()
13            .into_iter()
14            .map(|v| (base.to_owned(), v))
15    }
16}
17
18macro_rules! declare_vars {
19    ($struct:ident = ($($args:tt)*) =>
20        {
21            $($name:ident : $ty:ty = $value:expr_2021; env: $env:expr_2021;)*
22        }
23    ) => {
24        #[derive(Clone, Debug)]
25        pub struct $struct {
26            $(
27                #[allow(unused)]
28                pub $name: $ty
29            ),*
30        }
31
32        impl $struct {
33            pub async fn init($($args)*) -> ::anyhow::Result<Self> {
34                $(let $name: $ty = $value.into();)*
35                Ok(Self {
36                    $($name),*
37                })
38            }
39
40            pub fn vars(&self) -> impl Iterator<Item = (String, String)> + use<> {
41                let mut env = ::std::vec::Vec::new();
42                $(
43                    for (env_name, value) in $crate::vars::ToEnvVar::to_env_values(&self.$name, $env) {
44                        env.push((env_name, value));
45                    }
46                )*
47                env.into_iter()
48            }
49        }
50    };
51}
52
53impl ToEnvVar for PathBuf {
54    fn to_env_value(&self) -> Option<String> {
55        Some(self.as_os_str().to_str().expect("must be utf8").to_owned())
56    }
57}
58
59impl ToEnvVar for String {
60    fn to_env_value(&self) -> Option<String> {
61        Some(self.to_owned())
62    }
63}
64
65impl ToEnvVar for usize {
66    fn to_env_value(&self) -> Option<String> {
67        Some(self.to_string())
68    }
69}
70
71impl ToEnvVar for u16 {
72    fn to_env_value(&self) -> Option<String> {
73        Some(self.to_string())
74    }
75}
76
77impl<T: ToEnvVar> ToEnvVar for Option<T> {
78    fn to_env_value(&self) -> Option<String> {
79        self.as_ref().and_then(ToEnvVar::to_env_value)
80    }
81}
82
83impl ToEnvVar for ApiSecrets {
84    fn to_env_value(&self) -> Option<String> {
85        if self.is_empty() {
86            return None;
87        }
88        Some(self.get_all().join(","))
89    }
90}
91
92pub async fn mkdir(dir: PathBuf) -> anyhow::Result<PathBuf> {
93    if !dir.exists() {
94        tokio::fs::create_dir(&dir).await?;
95    }
96    Ok(dir)
97}
98
99use fedimint_core::envs::{
100    FM_DEFAULT_BITCOIN_RPC_KIND_ENV, FM_DEFAULT_BITCOIN_RPC_URL_ENV, FM_FORCE_BITCOIN_RPC_KIND_ENV,
101    FM_FORCE_BITCOIN_RPC_URL_ENV, FM_IN_DEVIMINT_ENV, FM_IROH_API_SECRET_KEY_OVERRIDE_ENV,
102    FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV, FM_USE_UNKNOWN_MODULE_ENV,
103};
104use fedimint_core::{NumPeers, PeerId};
105use fedimint_portalloc::port_alloc;
106use fedimint_server::net::api::ApiSecrets;
107use fedimintd_envs::FM_FORCE_API_SECRETS_ENV;
108use format as f;
109use net_overrides::{FederationsNetOverrides, FedimintdPeerOverrides};
110
111use crate::federation::{
112    FEDIMINTD_METRICS_PORT_OFFSET, FEDIMINTD_UI_PORT_OFFSET, PORTS_PER_FEDIMINTD,
113};
114use crate::vars::net_overrides::GatewaydNetOverrides;
115
116pub fn utf8(path: &Path) -> &str {
117    path.as_os_str().to_str().expect("must be valid utf8")
118}
119
120// Port offsets from FM_GATEWAY_BASE_PORT when set: 0-2 are Iroh ports for each
121// gateway, 3-4 LND, 5-6 LDK, 7-8 LDK2
122const GATEWAY_PORT_OFFSET_LND: u16 = 3;
123const GATEWAY_PORT_OFFSET_LND_METRICS: u16 = 4;
124const GATEWAY_PORT_OFFSET_LDK: u16 = 5;
125const GATEWAY_PORT_OFFSET_LDK_METRICS: u16 = 6;
126const GATEWAY_PORT_OFFSET_LDK2: u16 = 7;
127const GATEWAY_PORT_OFFSET_LDK2_METRICS: u16 = 8;
128
129declare_vars! {
130    Global = (test_dir: &Path, num_feds: usize, fed_size: usize, offline_nodes: usize, federation_base_ports: u16, num_gateways: usize, gateway_base_port: Option<u16>) =>
131    {
132        FM_USE_UNKNOWN_MODULE: String = std::env::var(FM_USE_UNKNOWN_MODULE_ENV).unwrap_or_else(|_| "1".into()); env: "FM_USE_UNKNOWN_MODULE";
133
134        FM_FORCE_API_SECRETS: ApiSecrets = std::env::var(FM_FORCE_API_SECRETS_ENV).ok().and_then(|s| {
135            ApiSecrets::from_str(&s).ok()
136        }).unwrap_or_default(); env: FM_FORCE_API_SECRETS_ENV;
137
138        FM_API_SECRET: Option<String> = std::env::var("FM_API_SECRET").ok().or_else(|| FM_FORCE_API_SECRETS.get_active()); env: "FM_API_SECRET";
139
140        FM_IN_DEVIMINT: String = "1".to_string(); env: FM_IN_DEVIMINT_ENV;
141        FM_SKIP_REL_NOTES_ACK: String = "1".to_string(); env: "FM_SKIP_REL_NOTES_ACK";
142
143        FM_FED_SIZE: usize = fed_size; env: "FM_FED_SIZE";
144        FM_NUM_FEDS: usize = num_feds; env: "FM_NUM_FEDS";
145        FM_OFFLINE_NODES: usize = offline_nodes; env: "FM_OFFLINE_NODES";
146        FM_TMP_DIR: PathBuf = mkdir(test_dir.into()).await?; env: "FM_TMP_DIR";
147        FM_TEST_DIR: PathBuf = FM_TMP_DIR.clone(); env: "FM_TEST_DIR";
148        FM_TEST_FAST_WEAK_CRYPTO: String = "1"; env: "FM_TEST_FAST_WEAK_CRYPTO";
149        FM_LOGS_DIR: PathBuf = mkdir(FM_TEST_DIR.join("logs")).await?; env: "FM_LOGS_DIR";
150
151        FM_PORT_BTC_RPC: u16 = port_alloc(1)?; env: "FM_PORT_BTC_RPC";
152        FM_PORT_BTC_P2P: u16 = port_alloc(1)?; env: "FM_PORT_BTC_P2P";
153        FM_PORT_BTC_ZMQ_PUB_RAW_BLOCK: u16 = port_alloc(1)?; env: "FM_PORT_BTC_ZMQ_PUB_RAW_BLOCK";
154        FM_PORT_BTC_ZMQ_PUB_RAW_TX: u16 = port_alloc(1)?; env: "FM_PORT_BTC_ZMQ_PUB_RAW_TX";
155        FM_PORT_LND_LISTEN: u16 = port_alloc(1)?; env: "FM_PORT_LND_LISTEN";
156        FM_PORT_LDK: u16 = port_alloc(1)?; env: "FM_PORT_LDK";
157        FM_PORT_LDK2: u16 = port_alloc(1)?; env: "FM_PORT_LDK";
158        FM_PORT_LND_RPC: u16 = port_alloc(1)?; env: "FM_PORT_LND_RPC";
159        FM_PORT_LND_REST: u16 = port_alloc(1)?; env: "FM_PORT_LND_REST";
160        FM_PORT_ESPLORA: u16 = port_alloc(1)?; env: "FM_PORT_ESPLORA";
161        FM_PORT_ESPLORA_MONITORING: u16 = port_alloc(1)?; env: "FM_PORT_ESPLORA_MONITORING";
162        FM_PORT_GW_LND: u16 = match gateway_base_port {
163            Some(b) => b + GATEWAY_PORT_OFFSET_LND,
164            None => port_alloc(1)?,
165        }; env: "FM_PORT_GW_LND";
166        FM_PORT_GW_LND_METRICS: u16 = match gateway_base_port {
167            Some(b) => b + GATEWAY_PORT_OFFSET_LND_METRICS,
168            None => port_alloc(1)?,
169        }; env: "FM_PORT_GW_LND_METRICS";
170        FM_PORT_GW_LDK: u16 = match gateway_base_port {
171            Some(b) => b + GATEWAY_PORT_OFFSET_LDK,
172            None => port_alloc(1)?,
173        }; env: "FM_PORT_GW_LDK";
174        FM_PORT_GW_LDK_METRICS: u16 = match gateway_base_port {
175            Some(b) => b + GATEWAY_PORT_OFFSET_LDK_METRICS,
176            None => port_alloc(1)?,
177        }; env: "FM_PORT_GW_LDK_METRICS";
178        FM_PORT_GW_LDK2: u16 = match gateway_base_port {
179            Some(b) => b + GATEWAY_PORT_OFFSET_LDK2,
180            None => port_alloc(1)?,
181        }; env: "FM_PORT_GW_LDK2";
182        FM_PORT_GW_LDK2_METRICS: u16 = match gateway_base_port {
183            Some(b) => b + GATEWAY_PORT_OFFSET_LDK2_METRICS,
184            None => port_alloc(1)?,
185        }; env: "FM_PORT_GW_LDK2_METRICS";
186        FM_PORT_FAUCET: u16 = 15243u16; env: "FM_PORT_FAUCET";
187        FM_PORT_RECURRINGD: u16 = port_alloc(1)?; env: "FM_PORT_RECURRINGD";
188        FM_PORT_RECURRINGDV2: u16 = port_alloc(1)?; env: "FM_PORT_RECURRINGDV2";
189
190        FM_FEDERATION_BASE_PORT: u16 = federation_base_ports; env: "FM_FEDERATION_BASE_PORT";
191        fedimintd_overrides: FederationsNetOverrides = FederationsNetOverrides::new(FM_FEDERATION_BASE_PORT, num_feds, NumPeers::from(fed_size)); env: "NOT_USED_FOR_ANYTHING";
192        gw_base_port: u16 = match gateway_base_port {
193            Some(b) => b,
194            None => port_alloc(3)?,
195        }; env: "NOT_USED_FOR_ANYTHING";
196        gatewayd_overrides: GatewaydNetOverrides = GatewaydNetOverrides::new(gw_base_port, num_gateways); env: "NOT_USED_FOR_ANYTHING";
197
198        FM_LND_DIR: PathBuf = mkdir(FM_TEST_DIR.join("lnd")).await?; env: "FM_LND_DIR";
199        FM_LDK_DIR: PathBuf = mkdir(FM_TEST_DIR.join("ldk")).await?; env: "FM_LDK_DIR";
200        FM_BTC_DIR: PathBuf = mkdir(FM_TEST_DIR.join("bitcoin")).await?; env: "FM_BTC_DIR";
201        FM_DATA_DIR: PathBuf = FM_TEST_DIR.clone(); env: "FM_DATA_DIR";
202        FM_CLIENT_BASE_DIR: PathBuf = mkdir(FM_TEST_DIR.join("clients")).await?; env: "FM_CLIENT_BASE_DIR";
203        FM_CLIENT_DIR: PathBuf = mkdir(FM_TEST_DIR.join("clients").join("default-0")).await?; env: "FM_CLIENT_DIR";
204        FM_ESPLORA_DIR: PathBuf = mkdir(FM_TEST_DIR.join("esplora")).await?; env: "FM_ESPLORA_DIR";
205        FM_READY_FILE: PathBuf = FM_TEST_DIR.join("ready"); env: "FM_READY_FILE";
206
207        FM_LND_RPC_ADDR: String = f!("https://localhost:{FM_PORT_LND_RPC}"); env: "FM_LND_RPC_ADDR";
208        FM_LND_TLS_CERT: PathBuf = FM_LND_DIR.join("tls.cert"); env: "FM_LND_TLS_CERT";
209        FM_LND_MACAROON: PathBuf = FM_LND_DIR.join("data/chain/bitcoin/regtest/admin.macaroon"); env: "FM_LND_MACAROON";
210
211        // TODO(support:v0.5): Remove this. It was used prior to `FM_GATEWAY_BCRYPT_PASSWORD_HASH` to provide a plaintext password to the gateway.
212        FM_GATEWAY_PASSWORD: String = "theresnosecondbest"; env: "FM_GATEWAY_PASSWORD";
213        FM_GATEWAY_SKIP_SETUP: String = "true"; env: "FM_GATEWAY_SKIP_SETUP";
214
215        // Bcrypt hash of "theresnosecondbest" with a cost of 10.
216        FM_GATEWAY_BCRYPT_PASSWORD_HASH: String = "$2y$10$Q/UTDeO84VGG1mRncxw.Nubqyi/HsNRJ40k0TSexFy9eVess1yi/u"; env: "FM_GATEWAY_BCRYPT_PASSWORD_HASH";
217        // Bcrypt hash of "secondbest" with a cost of 10.
218        FM_GATEWAY_USER_BCRYPT_PASSWORD_HASH: String = "$2b$10$//kZb7XfJy5eSmvkIGNWTOiCjRJ5NeXmcoAL541fqxj531Zld4gli"; env: "FM_GATEWAY_USER_BCRYPT_PASSWORD_HASH";
219
220        FM_GATEWAY_SKIP_WAIT_FOR_SYNC: String = "1"; env: "FM_GATEWAY_SKIP_WAIT_FOR_SYNC";
221        FM_GATEWAY_NETWORK: String = "regtest"; env: "FM_GATEWAY_NETWORK";
222        FM_DEFAULT_ROUTING_FEES: String = "0,0"; env: "FM_DEFAULT_ROUTING_FEES";
223
224        FM_FAUCET_BIND_ADDR: String = f!("0.0.0.0:{FM_PORT_FAUCET}"); env: "FM_FAUCET_BIND_ADDR";
225
226        // clients env: "// ";
227        FM_LNCLI: String = f!("{lncli} -n regtest --lnddir={lnddir} --rpcserver=localhost:{FM_PORT_LND_RPC}",
228            lncli = crate::util::get_lncli_path().join(" "),
229            lnddir = utf8(&FM_LND_DIR)); env: "FM_LNCLI";
230        FM_BTC_CLIENT: String = f!("{bitcoin_cli} -regtest -rpcuser=bitcoin -rpcpassword=bitcoin -datadir={datadir}",             bitcoin_cli = crate::util::get_bitcoin_cli_path().join(" "),
231            datadir = utf8(&FM_BTC_DIR)); env: "FM_BTC_CLIENT";
232
233        FM_MINT_CLIENT: String = f!("{fedimint_cli} --data-dir {datadir}",
234            fedimint_cli = crate::util::get_fedimint_cli_path().join(" "),
235            datadir = utf8(&FM_CLIENT_DIR));  env: "FM_MINT_CLIENT";
236        FM_MINT_RPC_CLIENT: String = f!("mint-rpc-client"); env: "FM_MINT_RPC_CLIENT";
237        FM_GWCLI_LND: String = f!("{gateway_cli} --rpcpassword=theresnosecondbest -a http://127.0.0.1:{FM_PORT_GW_LND}/",
238            gateway_cli = crate::util::get_gateway_cli_path().join(" "),); env: "FM_GWCLI_LND";
239        FM_GWCLI_LDK: String = f!("{gateway_cli} --rpcpassword=theresnosecondbest -a http://127.0.0.1:{FM_PORT_GW_LDK}/",
240            gateway_cli = crate::util::get_gateway_cli_path().join(" "),); env: "FM_GWCLI_LDK";
241        FM_DB_TOOL: String = f!("{fedimint_dbtool}", fedimint_dbtool = crate::util::get_fedimint_dbtool_cli_path().join(" ")); env: "FM_DB_TOOL";
242
243        // fedimint config variables
244        FM_TEST_BITCOIND_RPC: String = f!("http://bitcoin:bitcoin@127.0.0.1:{FM_PORT_BTC_RPC}"); env: "FM_TEST_BITCOIND_RPC";
245        FM_BITCOIN_RPC_URL: String = f!("http://bitcoin:bitcoin@127.0.0.1:{FM_PORT_BTC_RPC}"); env: "FM_BITCOIN_RPC_URL";
246        FM_BITCOIN_RPC_KIND: String = "bitcoind"; env: "FM_BITCOIN_RPC_KIND";
247        FM_BITCOIND_URL: String = f!("http://bitcoin:bitcoin@127.0.0.1:{FM_PORT_BTC_RPC}"); env: "FM_BITCOIND_URL";
248        FM_BITCOIND_USERNAME: String = "bitcoin"; env: "FM_BITCOIND_USERNAME";
249        FM_BITCOIND_PASSWORD: String = "bitcoin"; env: "FM_BITCOIND_PASSWORD";
250        FM_DEFAULT_BITCOIN_RPC_URL: String = f!("http://bitcoin:bitcoin@127.0.0.1:{FM_PORT_BTC_RPC}"); env: FM_DEFAULT_BITCOIN_RPC_URL_ENV;
251        FM_DEFAULT_BITCOIN_RPC_KIND: String = "bitcoind"; env: FM_DEFAULT_BITCOIN_RPC_KIND_ENV;
252
253        FM_ROCKSDB_WRITE_BUFFER_SIZE : String = (1 << 20).to_string(); env: "FM_ROCKSDB_WRITE_BUFFER_SIZE";
254    }
255}
256
257impl Global {
258    pub async fn new(
259        test_dir: &Path,
260        num_feds: usize,
261        fed_size: usize,
262        offline_nodes: usize,
263        federations_base_port: Option<u16>,
264        gateway_base_port: Option<u16>,
265    ) -> anyhow::Result<Self> {
266        let federations_base_port = if let Some(federations_base_port) = federations_base_port {
267            federations_base_port
268        } else {
269            port_alloc(
270                (PORTS_PER_FEDIMINTD as usize * fed_size * num_feds)
271                    .try_into()
272                    .unwrap(),
273            )?
274        };
275        let num_gateways: usize = 3;
276        let this = Self::init(
277            test_dir,
278            num_feds,
279            fed_size,
280            offline_nodes,
281            federations_base_port,
282            num_gateways,
283            gateway_base_port,
284        )
285        .await?;
286        Ok(this)
287    }
288}
289
290declare_vars! {
291    Fedimintd = (globals: &Global, federation_name: String, peer_id: PeerId, overrides: &FedimintdPeerOverrides) => {
292        FM_IN_DEVIMINT: String = "1".to_string(); env: FM_IN_DEVIMINT_ENV;
293        FM_BIND_P2P: String = format!("127.0.0.1:{}", overrides.p2p.port()); env: "FM_BIND_P2P";
294        FM_BIND_API_WS: String = format!("127.0.0.1:{}", overrides.api.port()); env: "FM_BIND_API_WS";
295        FM_BIND_API_IROH: String = format!("127.0.0.1:{}", overrides.api.port()); env: "FM_BIND_API_IROH";
296        // for backwards compatibility with old versions
297        FM_BIND_API: String = format!("127.0.0.1:{}", overrides.api.port()); env: "FM_BIND_API";
298        FM_P2P_URL: String =  format!("fedimint://127.0.0.1:{}", overrides.p2p.port()); env: "FM_P2P_URL";
299        FM_API_URL: String =  format!("ws://127.0.0.1:{}", overrides.api.port()); env: "FM_API_URL";
300        FM_BIND_UI: String = format!("127.0.0.1:{}", overrides.base_port + FEDIMINTD_UI_PORT_OFFSET); env: "FM_BIND_UI";
301        FM_BIND_METRICS_API: String = format!("127.0.0.1:{}", overrides.base_port + FEDIMINTD_METRICS_PORT_OFFSET); env: "FM_BIND_METRICS_API";
302        FM_BIND_METRICS: String = format!("127.0.0.1:{}", overrides.base_port + FEDIMINTD_METRICS_PORT_OFFSET); env: "FM_BIND_METRICS";
303        FM_DATA_DIR: PathBuf = mkdir(globals.FM_DATA_DIR.join(format!("fedimintd-{federation_name}-{peer_id}"))).await?; env: "FM_DATA_DIR";
304
305        FM_IROH_P2P_SECRET_KEY_OVERRIDE : String = overrides.p2p.secret_key(); env: FM_IROH_P2P_SECRET_KEY_OVERRIDE_ENV;
306        FM_IROH_API_SECRET_KEY_OVERRIDE : String = overrides.api.secret_key(); env: FM_IROH_API_SECRET_KEY_OVERRIDE_ENV;
307
308        // We only need to force the current bitcoind rpc on fedimintd, other daemons take their
309        // rpc settings over command-line etc. so always will use the right ones.
310        FM_FORCE_BITCOIN_RPC_URL: String = f!("http://bitcoin:bitcoin@127.0.0.1:{}", globals.FM_PORT_BTC_RPC); env: FM_FORCE_BITCOIN_RPC_URL_ENV;
311        FM_FORCE_BITCOIN_RPC_KIND: String = "bitcoind"; env: FM_FORCE_BITCOIN_RPC_KIND_ENV;
312    }
313}