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