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