1use 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 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: 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}