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