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 })
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 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: 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}