clementine_core/config/
env.rs

1//! # Environment Variable Support For [`BridgeConfig`]
2
3use super::BridgeConfig;
4use crate::{
5    config::{
6        default_grpc_limits, default_tx_sender_limits, GrpcLimits, TelemetryConfig, TxSenderLimits,
7    },
8    deposit::SecurityCouncil,
9    errors::BridgeError,
10};
11use bitcoin::{address::NetworkUnchecked, secp256k1::SecretKey, Amount};
12use eyre::Context;
13use std::{path::PathBuf, str::FromStr, time::Duration};
14
15pub(crate) fn read_string_from_env(env_var: &'static str) -> Result<String, BridgeError> {
16    std::env::var(env_var).map_err(|e| BridgeError::EnvVarNotSet(e, env_var))
17}
18
19pub(crate) fn read_string_from_env_then_parse<T: std::str::FromStr>(
20    env_var: &'static str,
21) -> Result<T, BridgeError>
22where
23    <T as FromStr>::Err: std::fmt::Debug,
24{
25    read_string_from_env(env_var)?
26        .parse::<T>()
27        .map_err(|e| BridgeError::EnvVarMalformed(env_var, format!("{e:?}")))
28}
29
30impl GrpcLimits {
31    pub fn from_env() -> Result<Self, BridgeError> {
32        let defaults = default_grpc_limits();
33        Ok(GrpcLimits {
34            max_message_size: read_string_from_env_then_parse::<usize>("GRPC_MAX_MESSAGE_SIZE")
35                .unwrap_or(defaults.max_message_size),
36            timeout_secs: read_string_from_env_then_parse::<u64>("GRPC_TIMEOUT_SECS")
37                .unwrap_or(defaults.timeout_secs),
38            tcp_keepalive_secs: read_string_from_env_then_parse::<u64>("GRPC_TCP_KEEPALIVE_SECS")
39                .unwrap_or(defaults.tcp_keepalive_secs),
40            req_concurrency_limit: read_string_from_env_then_parse::<usize>(
41                "GRPC_REQ_CONCURRENCY_LIMIT",
42            )
43            .unwrap_or(defaults.req_concurrency_limit),
44            ratelimit_req_count: read_string_from_env_then_parse::<usize>(
45                "GRPC_RATELIMIT_REQ_COUNT",
46            )
47            .unwrap_or(defaults.ratelimit_req_count),
48            ratelimit_req_interval_secs: read_string_from_env_then_parse::<u64>(
49                "GRPC_RATELIMIT_REQ_INTERVAL_SECS",
50            )
51            .unwrap_or(defaults.ratelimit_req_interval_secs),
52        })
53    }
54}
55
56impl TxSenderLimits {
57    pub fn from_env() -> Result<Self, BridgeError> {
58        let defaults = default_tx_sender_limits();
59        Ok(TxSenderLimits {
60            fee_rate_hard_cap: read_string_from_env_then_parse::<u64>(
61                "TX_SENDER_FEE_RATE_HARD_CAP",
62            )
63            .unwrap_or(defaults.fee_rate_hard_cap),
64            mempool_fee_rate_multiplier: read_string_from_env_then_parse::<u64>(
65                "TX_SENDER_MEMPOOL_FEE_RATE_MULTIPLIER",
66            )
67            .unwrap_or(defaults.mempool_fee_rate_multiplier),
68            mempool_fee_rate_offset_sat_kvb: read_string_from_env_then_parse::<u64>(
69                "TX_SENDER_MEMPOOL_FEE_RATE_OFFSET_SAT_KVB",
70            )
71            .unwrap_or(defaults.mempool_fee_rate_offset_sat_kvb),
72            cpfp_fee_payer_bump_wait_time_seconds: read_string_from_env_then_parse::<u64>(
73                "TX_SENDER_CPFP_FEE_PAYER_BUMP_WAIT_TIME_SECONDS",
74            )
75            .unwrap_or(defaults.cpfp_fee_payer_bump_wait_time_seconds),
76        })
77    }
78}
79
80impl BridgeConfig {
81    pub fn from_env() -> Result<Self, BridgeError> {
82        let verifier_endpoints =
83            std::env::var("VERIFIER_ENDPOINTS")
84                .ok()
85                .map(|verifier_endpoints| {
86                    verifier_endpoints
87                        .split(",")
88                        .collect::<Vec<&str>>()
89                        .iter()
90                        .map(|x| x.to_string())
91                        .collect::<Vec<String>>()
92                });
93        let operator_endpoints =
94            std::env::var("OPERATOR_ENDPOINTS")
95                .ok()
96                .map(|operator_endpoints| {
97                    operator_endpoints
98                        .split(",")
99                        .collect::<Vec<&str>>()
100                        .iter()
101                        .map(|x| x.to_string())
102                        .collect::<Vec<String>>()
103                });
104
105        let operator_withdrawal_fee_sats = if let Ok(operator_withdrawal_fee_sats) =
106            std::env::var("OPERATOR_WITHDRAWAL_FEE_SATS")
107        {
108            Some(Amount::from_sat(
109                operator_withdrawal_fee_sats.parse::<u64>().map_err(|e| {
110                    BridgeError::EnvVarMalformed("OPERATOR_WITHDRAWAL_FEE_SATS", e.to_string())
111                })?,
112            ))
113        } else {
114            None
115        };
116
117        let header_chain_proof_path =
118            if let Ok(header_chain_proof_path) = std::env::var("HEADER_CHAIN_PROOF_PATH") {
119                Some(PathBuf::from(header_chain_proof_path))
120            } else {
121                None
122            };
123
124        let operator_reimbursement_address = if let Ok(operator_reimbursement_address) =
125            std::env::var("OPERATOR_REIMBURSEMENT_ADDRESS")
126        {
127            Some(
128                operator_reimbursement_address
129                    .parse::<bitcoin::Address<NetworkUnchecked>>()
130                    .map_err(|e| {
131                        BridgeError::EnvVarMalformed(
132                            "OPERATOR_REIMBURSEMENT_ADDRESS",
133                            e.to_string(),
134                        )
135                    })?,
136            )
137        } else {
138            None
139        };
140
141        let operator_collateral_funding_outpoint = if let Ok(operator_collateral_funding_outpoint) =
142            std::env::var("OPERATOR_COLLATERAL_FUNDING_OUTPOINT")
143        {
144            Some(
145                operator_collateral_funding_outpoint
146                    .parse::<bitcoin::OutPoint>()
147                    .map_err(|e| {
148                        BridgeError::EnvVarMalformed(
149                            "OPERATOR_COLLATERAL_FUNDING_OUTPOINT",
150                            e.to_string(),
151                        )
152                    })?,
153            )
154        } else {
155            None
156        };
157
158        let aggregator_verification_address = std::env::var("AGGREGATOR_VERIFICATION_ADDRESS")
159            .ok()
160            .map(|addr| {
161                addr.parse::<alloy::primitives::Address>()
162                    .wrap_err("Failed to parse AGGREGATOR_VERIFICATION_ADDRESS")
163            })
164            .transpose()?;
165
166        // TLS certificate and key paths
167        let server_cert_path = read_string_from_env("SERVER_CERT_PATH").map(PathBuf::from)?;
168        let server_key_path = read_string_from_env("SERVER_KEY_PATH").map(PathBuf::from)?;
169        let client_cert_path = read_string_from_env("CLIENT_CERT_PATH").map(PathBuf::from)?;
170        let ca_cert_path = read_string_from_env("CA_CERT_PATH").map(PathBuf::from)?;
171        let client_key_path = read_string_from_env("CLIENT_KEY_PATH").map(PathBuf::from)?;
172        let aggregator_cert_path =
173            read_string_from_env("AGGREGATOR_CERT_PATH").map(PathBuf::from)?;
174        let client_verification =
175            read_string_from_env("CLIENT_VERIFICATION").is_ok_and(|s| s == "true" || s == "1");
176
177        let security_council_string = read_string_from_env("SECURITY_COUNCIL")?;
178
179        let security_council = SecurityCouncil::from_str(&security_council_string)?;
180
181        let citrea_request_timeout = std::env::var("CITREA_REQUEST_TIMEOUT")
182            .ok()
183            .and_then(|timeout| timeout.parse::<u64>().ok())
184            .map(Duration::from_secs);
185
186        let config = BridgeConfig {
187            // Protocol paramset's source is independently defined
188            protocol_paramset: Default::default(),
189            host: read_string_from_env("HOST")?,
190            port: read_string_from_env_then_parse::<u16>("PORT")?,
191            secret_key: read_string_from_env_then_parse::<SecretKey>("SECRET_KEY")?,
192            operator_withdrawal_fee_sats,
193            operator_reimbursement_address,
194            operator_collateral_funding_outpoint,
195            bitcoin_rpc_url: read_string_from_env("BITCOIN_RPC_URL")?,
196            bitcoin_rpc_user: read_string_from_env("BITCOIN_RPC_USER")?.into(),
197            bitcoin_rpc_password: read_string_from_env("BITCOIN_RPC_PASSWORD")?.into(),
198            mempool_api_host: read_string_from_env("MEMPOOL_API_HOST").ok(),
199            mempool_api_endpoint: read_string_from_env("MEMPOOL_API_ENDPOINT").ok(),
200            db_host: read_string_from_env("DB_HOST")?,
201            db_port: read_string_from_env_then_parse::<usize>("DB_PORT")?,
202            db_user: read_string_from_env("DB_USER")?.into(),
203            db_password: read_string_from_env("DB_PASSWORD")?.into(),
204            db_name: read_string_from_env("DB_NAME")?,
205            citrea_rpc_url: read_string_from_env("CITREA_RPC_URL")?,
206            citrea_light_client_prover_url: read_string_from_env("CITREA_LIGHT_CLIENT_PROVER_URL")?,
207            citrea_chain_id: read_string_from_env_then_parse::<u32>("CITREA_CHAIN_ID")?,
208            citrea_request_timeout,
209            bridge_contract_address: read_string_from_env("BRIDGE_CONTRACT_ADDRESS")?,
210            header_chain_proof_batch_size: read_string_from_env_then_parse::<u32>(
211                "HEADER_CHAIN_PROOF_BATCH_SIZE",
212            )?,
213            header_chain_proof_path,
214            verifier_endpoints,
215            operator_endpoints,
216            security_council,
217            aggregator_verification_address,
218            client_verification,
219            server_cert_path,
220            server_key_path,
221            ca_cert_path,
222            client_cert_path,
223            client_key_path,
224            aggregator_cert_path,
225            emergency_stop_encryption_public_key: read_string_from_env(
226                "EMERGENCY_STOP_ENCRYPTION_PUBLIC_KEY",
227            )
228            .ok()
229            .map(|key| {
230                hex::decode(key)
231                    .expect("valid hex")
232                    .try_into()
233                    .expect("valid key")
234            }),
235
236            telemetry: TelemetryConfig::from_env().ok(),
237            grpc: GrpcLimits::from_env()?,
238            tx_sender_limits: TxSenderLimits::from_env()?,
239
240            time_to_send_watchtower_challenge: read_string_from_env_then_parse::<u16>(
241                "TIME_TO_SEND_WATCHTOWER_CHALLENGE",
242            )?,
243
244            #[cfg(test)]
245            test_params: super::TestParams::default(),
246        };
247
248        tracing::debug!("BridgeConfig from env: {:?}", config);
249        Ok(config)
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use secrecy::ExposeSecret;
256
257    use crate::config::{
258        protocol::{ProtocolParamset, REGTEST_PARAMSET},
259        BridgeConfig,
260    };
261
262    #[test]
263    #[serial_test::serial]
264    fn get_config_from_env_vars() {
265        let default_config = BridgeConfig::default();
266
267        std::env::set_var("HOST", &default_config.host);
268        std::env::set_var("PORT", default_config.port.to_string());
269        std::env::set_var(
270            "SECRET_KEY",
271            default_config.secret_key.display_secret().to_string(),
272        );
273        if let Some(ref operator_withdrawal_fee_sats) = default_config.operator_withdrawal_fee_sats
274        {
275            std::env::set_var(
276                "OPERATOR_WITHDRAWAL_FEE_SATS",
277                operator_withdrawal_fee_sats.to_sat().to_string(),
278            );
279        }
280        std::env::set_var("BITCOIN_RPC_URL", &default_config.bitcoin_rpc_url);
281        std::env::set_var(
282            "BITCOIN_RPC_USER",
283            default_config.bitcoin_rpc_user.expose_secret(),
284        );
285        std::env::set_var(
286            "BITCOIN_RPC_PASSWORD",
287            default_config.bitcoin_rpc_password.expose_secret(),
288        );
289        std::env::set_var("DB_HOST", default_config.db_host.clone());
290        std::env::set_var("DB_PORT", default_config.db_port.to_string());
291        std::env::set_var("DB_USER", default_config.db_user.expose_secret());
292        std::env::set_var("DB_PASSWORD", default_config.db_password.expose_secret());
293        std::env::set_var("DB_NAME", &default_config.db_name);
294        std::env::set_var("CITREA_RPC_URL", &default_config.citrea_rpc_url);
295        std::env::set_var(
296            "CITREA_LIGHT_CLIENT_PROVER_URL",
297            &default_config.citrea_light_client_prover_url,
298        );
299        std::env::set_var(
300            "CITREA_CHAIN_ID",
301            default_config.citrea_chain_id.to_string(),
302        );
303        std::env::set_var(
304            "BRIDGE_CONTRACT_ADDRESS",
305            &default_config.bridge_contract_address,
306        );
307        std::env::set_var(
308            "AGGREGATOR_CERT_PATH",
309            default_config.aggregator_cert_path.clone(),
310        );
311        std::env::set_var("CLIENT_CERT_PATH", default_config.client_cert_path.clone());
312        std::env::set_var("CLIENT_KEY_PATH", default_config.client_key_path.clone());
313        std::env::set_var("SERVER_CERT_PATH", default_config.server_cert_path.clone());
314        std::env::set_var("SERVER_KEY_PATH", default_config.server_key_path.clone());
315        std::env::set_var("CA_CERT_PATH", default_config.ca_cert_path.clone());
316        std::env::set_var(
317            "CLIENT_VERIFICATION",
318            default_config.client_verification.to_string(),
319        );
320
321        std::env::set_var(
322            "SECURITY_COUNCIL",
323            default_config.security_council.to_string(),
324        );
325
326        if let Some(ref header_chain_proof_path) = default_config.header_chain_proof_path {
327            std::env::set_var("HEADER_CHAIN_PROOF_PATH", header_chain_proof_path);
328        }
329        if let Some(ref verifier_endpoints) = default_config.verifier_endpoints {
330            std::env::set_var("VERIFIER_ENDPOINTS", verifier_endpoints.join(","));
331        }
332        if let Some(ref operator_endpoints) = default_config.operator_endpoints {
333            std::env::set_var("OPERATOR_ENDPOINTS", operator_endpoints.join(","));
334        }
335
336        if let Some(ref operator_reimbursement_address) =
337            default_config.operator_reimbursement_address
338        {
339            std::env::set_var(
340                "OPERATOR_REIMBURSEMENT_ADDRESS",
341                operator_reimbursement_address
342                    .to_owned()
343                    .assume_checked()
344                    .to_string(),
345            );
346        }
347
348        if let Some(ref operator_collateral_funding_outpoint) =
349            default_config.operator_collateral_funding_outpoint
350        {
351            std::env::set_var(
352                "OPERATOR_COLLATERAL_FUNDING_OUTPOINT",
353                operator_collateral_funding_outpoint.to_string(),
354            );
355        }
356
357        std::env::set_var(
358            "TELEMETRY_HOST",
359            default_config.telemetry.as_ref().unwrap().host.clone(),
360        );
361        std::env::set_var(
362            "TELEMETRY_PORT",
363            default_config.telemetry.as_ref().unwrap().port.to_string(),
364        );
365
366        std::env::set_var(
367            "TX_SENDER_FEE_RATE_HARD_CAP",
368            default_config
369                .tx_sender_limits
370                .fee_rate_hard_cap
371                .to_string(),
372        );
373
374        std::env::set_var(
375            "TX_SENDER_MEMPOOL_FEE_RATE_MULTIPLIER",
376            default_config
377                .tx_sender_limits
378                .mempool_fee_rate_multiplier
379                .to_string(),
380        );
381        std::env::set_var(
382            "TX_SENDER_MEMPOOL_FEE_RATE_OFFSET_SAT_KVB",
383            default_config
384                .tx_sender_limits
385                .mempool_fee_rate_offset_sat_kvb
386                .to_string(),
387        );
388        std::env::set_var(
389            "TX_SENDER_CPFP_FEE_PAYER_BUMP_WAIT_TIME_SECONDS",
390            default_config
391                .tx_sender_limits
392                .cpfp_fee_payer_bump_wait_time_seconds
393                .to_string(),
394        );
395        std::env::set_var(
396            "GRPC_MAX_MESSAGE_SIZE",
397            default_config.grpc.max_message_size.to_string(),
398        );
399        std::env::set_var(
400            "GRPC_TIMEOUT_SECS",
401            default_config.grpc.timeout_secs.to_string(),
402        );
403        std::env::set_var(
404            "GRPC_TCP_KEEPALIVE_SECS",
405            default_config.grpc.tcp_keepalive_secs.to_string(),
406        );
407        std::env::set_var(
408            "GRPC_CONCURRENCY_LIMIT",
409            default_config.grpc.req_concurrency_limit.to_string(),
410        );
411        std::env::set_var(
412            "GRPC_RATELIMIT_REQ_COUNT",
413            default_config.grpc.ratelimit_req_count.to_string(),
414        );
415        if let Some(ref aggregator_verification_address) =
416            default_config.aggregator_verification_address
417        {
418            std::env::set_var(
419                "AGGREGATOR_VERIFICATION_ADDRESS",
420                aggregator_verification_address.to_string(),
421            );
422        }
423
424        if let Some(ref emergency_stop_encryption_public_key) =
425            default_config.emergency_stop_encryption_public_key
426        {
427            std::env::set_var(
428                "EMERGENCY_STOP_ENCRYPTION_PUBLIC_KEY",
429                hex::encode(emergency_stop_encryption_public_key),
430            );
431        }
432
433        std::env::set_var(
434            "HEADER_CHAIN_PROOF_BATCH_SIZE",
435            default_config.header_chain_proof_batch_size.to_string(),
436        );
437
438        std::env::set_var(
439            "TIME_TO_SEND_WATCHTOWER_CHALLENGE",
440            default_config.time_to_send_watchtower_challenge.to_string(),
441        );
442
443        assert_eq!(super::BridgeConfig::from_env().unwrap(), default_config);
444    }
445
446    #[test]
447    #[serial_test::serial]
448    fn get_protocol_paramset_from_env_vars() {
449        let default_config = REGTEST_PARAMSET;
450
451        std::env::set_var("NETWORK", default_config.network.to_string());
452        std::env::set_var("NUM_ROUND_TXS", default_config.num_round_txs.to_string());
453        std::env::set_var(
454            "NUM_KICKOFFS_PER_ROUND",
455            default_config.num_kickoffs_per_round.to_string(),
456        );
457        std::env::set_var(
458            "NUM_SIGNED_KICKOFFS",
459            default_config.num_signed_kickoffs.to_string(),
460        );
461        std::env::set_var(
462            "BRIDGE_AMOUNT",
463            default_config.bridge_amount.to_sat().to_string(),
464        );
465        std::env::set_var(
466            "KICKOFF_AMOUNT",
467            default_config.kickoff_amount.to_sat().to_string(),
468        );
469        std::env::set_var(
470            "OPERATOR_CHALLENGE_AMOUNT",
471            default_config
472                .operator_challenge_amount
473                .to_sat()
474                .to_string(),
475        );
476        std::env::set_var(
477            "COLLATERAL_FUNDING_AMOUNT",
478            default_config
479                .collateral_funding_amount
480                .to_sat()
481                .to_string(),
482        );
483        std::env::set_var(
484            "KICKOFF_BLOCKHASH_COMMIT_LENGTH",
485            default_config.kickoff_blockhash_commit_length.to_string(),
486        );
487        std::env::set_var(
488            "WATCHTOWER_CHALLENGE_BYTES",
489            default_config.watchtower_challenge_bytes.to_string(),
490        );
491        std::env::set_var(
492            "WINTERNITZ_LOG_D",
493            default_config.winternitz_log_d.to_string(),
494        );
495        std::env::set_var(
496            "USER_TAKES_AFTER",
497            default_config.user_takes_after.to_string(),
498        );
499        std::env::set_var(
500            "OPERATOR_CHALLENGE_TIMEOUT_TIMELOCK",
501            default_config
502                .operator_challenge_timeout_timelock
503                .to_string(),
504        );
505        std::env::set_var(
506            "OPERATOR_CHALLENGE_NACK_TIMELOCK",
507            default_config.operator_challenge_nack_timelock.to_string(),
508        );
509        std::env::set_var(
510            "DISPROVE_TIMEOUT_TIMELOCK",
511            default_config.disprove_timeout_timelock.to_string(),
512        );
513        std::env::set_var(
514            "ASSERT_TIMEOUT_TIMELOCK",
515            default_config.assert_timeout_timelock.to_string(),
516        );
517        std::env::set_var(
518            "OPERATOR_REIMBURSE_TIMELOCK",
519            default_config.operator_reimburse_timelock.to_string(),
520        );
521        std::env::set_var(
522            "WATCHTOWER_CHALLENGE_TIMEOUT_TIMELOCK",
523            default_config
524                .watchtower_challenge_timeout_timelock
525                .to_string(),
526        );
527        std::env::set_var("FINALITY_DEPTH", default_config.finality_depth.to_string());
528        std::env::set_var("START_HEIGHT", default_config.start_height.to_string());
529        std::env::set_var("GENESIS_HEIGHT", default_config.genesis_height.to_string());
530        std::env::set_var(
531            "GENESIS_CHAIN_STATE_HASH",
532            hex::encode(default_config.genesis_chain_state_hash),
533        );
534        std::env::set_var(
535            "LATEST_BLOCKHASH_TIMEOUT_TIMELOCK",
536            default_config.latest_blockhash_timeout_timelock.to_string(),
537        );
538
539        std::env::set_var(
540            "BRIDGE_NONSTANDARD",
541            default_config.bridge_nonstandard.to_string(),
542        );
543
544        assert_eq!(ProtocolParamset::from_env().unwrap(), default_config);
545    }
546}