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