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