1use base_sdk::{
7 core::Shard,
8 crypto::{
9 ed25519,
10 read_keypair,
11 },
12};
13use proving_clients::{
14 Config as ProvingConfig,
15 ConfigFile as ProvingConfigFile,
16};
17use serde::{
18 de::DeserializeOwned,
19 Deserialize,
20 Serialize,
21};
22use snafu::{
23 OptionExt,
24 ResultExt,
25 Snafu,
26};
27use std::{
28 net::SocketAddr,
29 num::NonZero,
30 path::{
31 Path,
32 PathBuf,
33 },
34};
35
36const DOMAIN_ENV_PREFIX: &str = "DOMAIN";
37const APP_CONFIG_KEY: &str = "app";
38
39pub use domain_runtime::config::{
41 BaseLayerRetryConfig,
42 SdlRetentionConfig,
43 SignatureVerificationConfig,
44};
45
46#[derive(Debug, Snafu)]
48pub enum Error {
49 #[snafu(display("Could not read config: {source}"))]
51 ReadConfig {
52 source: config::ConfigError,
54 },
55 #[snafu(display("Could not read app config: {source}"))]
57 ReadAppConfig {
58 source: config::ConfigError,
60 },
61 #[snafu(display("App config not available for deserialization"))]
63 MissingAppConfig,
64 #[snafu(display("Could not read keypair: {source}"))]
66 ReadKey {
67 source: base_sdk::crypto::IoError,
69 },
70 #[snafu(display("Shard 0 is reserved and cannot be a domain"))]
72 ShardZeroReserved,
73 #[snafu(display("Error getting current directory: check permission or existence"))]
75 BadCurrentDirectory,
76 #[snafu(display("Error while resolving the proving configuration: {source}"))]
78 ProvingConfig {
79 source: proving_clients::config::Error,
81 },
82}
83
84#[derive(Debug, Deserialize, Serialize, Clone)]
89pub struct ConfigFile {
90 pub shard: Shard,
92
93 pub keypair: PathBuf,
99
100 pub rpc_url: Option<String>,
103
104 pub storage: Option<StorageConfig>,
106
107 pub admin_api: Option<SocketAddr>,
109
110 pub proving: Option<ProvingConfigFile>,
112
113 pub signature_verification: Option<SignatureVerificationConfig>,
115
116 pub sdl_retention: Option<SdlRetentionConfig>,
118
119 pub base_layer_retry: Option<BaseLayerRetryConfig>,
121}
122
123#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
125#[serde(rename_all = "snake_case")]
126pub enum StorageConfig {
127 Rocksdb {
129 path: PathBuf,
131 },
132 InMemory,
134}
135
136#[doc = include_str!("../doc/domain.yaml")]
143#[derive(Debug, Clone)]
153pub struct Config {
154 pub shard: NonZero<Shard>,
156
157 pub keypair: ed25519::PrivKey,
160
161 pub rpc_url: Option<String>,
164
165 pub storage: Option<StorageConfig>,
167
168 pub proving: Option<ProvingConfig>,
170
171 pub admin_api: Option<SocketAddr>,
173
174 pub signature_verification: Option<SignatureVerificationConfig>,
176
177 pub sdl_retention: Option<SdlRetentionConfig>,
179
180 pub base_layer_retry: Option<BaseLayerRetryConfig>,
182
183 pub app_config: Option<config::Value>,
185}
186
187impl Config {
188 pub const fn with_defaults(shard: NonZero<Shard>, keypair: ed25519::PrivKey) -> Self {
190 Self {
191 shard,
192 keypair,
193 rpc_url: None,
194 storage: None,
195 proving: None,
196 admin_api: None,
197 signature_verification: None,
198 sdl_retention: None,
199 base_layer_retry: None,
200 app_config: None,
201 }
202 }
203
204 pub fn load() -> Result<Self, Error> {
211 let current_directory = std::env::current_dir().map_err(|_| Error::BadCurrentDirectory)?;
212 Self::read(config::File::with_name("domain"), current_directory)
213 }
214
215 pub fn load_from(path: impl AsRef<Path>) -> Result<Self, Error> {
222 let path = path.as_ref();
223 let config_dir = path
224 .parent()
225 .map(|p| p.to_path_buf())
226 .unwrap_or_else(|| PathBuf::from("."));
227 Self::read(config::File::from(path), config_dir)
228 }
229
230 fn read<S>(src: S, config_dir: PathBuf) -> Result<Self, Error>
231 where
232 S: config::Source + Send + Sync + 'static,
233 {
234 let raw_config = config::Config::builder()
235 .add_source(src)
236 .add_source(config::Environment::with_prefix(DOMAIN_ENV_PREFIX))
237 .build()
238 .context(ReadConfigSnafu)?;
239
240 let app_config = match raw_config.get::<config::Value>(APP_CONFIG_KEY) {
241 Ok(value) => Some(value),
242 Err(config::ConfigError::NotFound(_)) => None,
243 Err(e) => Err(e).context(ReadAppConfigSnafu)?,
244 };
245
246 let ConfigFile {
247 shard,
248 keypair,
249 rpc_url,
250 storage,
251 proving,
252 admin_api,
253 signature_verification,
254 sdl_retention,
255 base_layer_retry,
256 } = raw_config.try_deserialize().context(ReadConfigSnafu)?;
257
258 let shard = NonZero::new(shard).context(ShardZeroReservedSnafu)?;
259
260 let keypair_path = if keypair.is_relative() {
262 config_dir.join(&keypair)
263 } else {
264 keypair
265 };
266 tracing::debug!("Reading keypair from {}", keypair_path.display());
267
268 let keypair = read_keypair(keypair_path).context(ReadKeySnafu)?;
269 tracing::info!("Using public key: {}", keypair.pub_key());
270
271 Ok(Self {
272 shard,
273 keypair,
274 rpc_url,
275 storage,
276 proving: proving
277 .map(|p| p.resolve(&config_dir))
278 .transpose()
279 .context(ProvingConfigSnafu)?,
280 admin_api,
281 signature_verification,
282 sdl_retention,
283 base_layer_retry,
284 app_config,
285 })
286 }
287
288 pub fn app_config<T: DeserializeOwned>(&self) -> Result<T, Error> {
290 self.app_config
291 .clone()
292 .context(MissingAppConfigSnafu)?
293 .try_deserialize()
294 .context(ReadAppConfigSnafu)
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use base_sdk::crypto::ed25519::test_helpers::KeyDispenser as Ed25519KeyDispenser;
302 use crypto::test_helper::KeyDispenser;
303 use proving_clients::config::ProverConfig;
304 #[cfg(feature = "sp1")]
305 use proving_clients::sp1::config::{
306 BackendConfig,
307 FulfillmentStrategy,
308 ProofMode,
309 };
310 use serial_test::{
311 parallel,
312 serial,
313 };
314 use serializers::json;
315 use std::net::Ipv4Addr;
316
317 macro_rules! assert_config_matches {
319 ($actual:expr, $expected:expr $(,)?) => {{
320 let actual = $actual;
321 let expected = $expected;
322 assert!(matches!(
323 actual,
324 Config {
325 shard,
326 keypair,
327 rpc_url,
328 storage,
329 proving,
330 admin_api,
331 signature_verification,
332 ..
333 } if shard == expected.shard
334 && keypair == expected.keypair
335 && rpc_url == expected.rpc_url
336 && storage == expected.storage
337 && proving == expected.proving
338 && admin_api == expected.admin_api
339 && signature_verification == expected.signature_verification
340 ));
341 }};
342 }
343
344 fn setup_path(
345 prefix: impl AsRef<Path>,
346 keys: &mut impl KeyDispenser<ed25519::PrivKey>,
347 ) -> (PathBuf, ed25519::PrivKey) {
348 let dir = std::env::temp_dir().join(prefix);
349 std::fs::create_dir_all(&dir).unwrap();
350 let keypair = keys.dispense();
351 let keypath = dir.join("key.json");
352 json::write_path(keypath, &keypair).unwrap();
353 (dir, keypair)
354 }
355
356 fn setup_path_with_elf(
357 prefix: impl AsRef<Path>,
358 keys: &mut impl KeyDispenser<ed25519::PrivKey>,
359 ) -> (PathBuf, ed25519::PrivKey, Vec<u8>) {
360 let (dir, keypair) = setup_path(prefix, keys);
361 let test_elf_content = vec![0x7F, 0x45, 0x4C, 0x46, 0x01, 0x01, 0x01, 0x00];
363 let elf_path = dir.join("test_local_laws.elf");
364 std::fs::write(&elf_path, &test_elf_content).unwrap();
365 (dir, keypair, test_elf_content)
366 }
367
368 #[test]
369 #[parallel]
370 fn test_from_yaml() {
371 let mut keys = Ed25519KeyDispenser::default();
372 let (path, keypair) = setup_path("yml", &mut keys);
373 let yaml_path = path.join("domain.yaml");
374
375 let yaml = format!(
376 r#"
377 shard: 123
378 keypair: {}/key.json
379 rpc_url: http://localhost:9000
380 storage:
381 rocksdb:
382 path: /tmp/db
383 "#,
384 path.display()
385 );
386 std::fs::write(&yaml_path, yaml).unwrap();
387
388 assert_config_matches!(
389 Config::load_from(&yaml_path).unwrap(),
390 Config {
391 rpc_url: Some("http://localhost:9000".to_string()),
392 storage: Some(StorageConfig::Rocksdb {
393 path: "/tmp/db".into()
394 }),
395 ..Config::with_defaults(NonZero::new(123).unwrap(), keypair.clone())
396 },
397 );
398
399 let yaml = format!(
400 r#"
401 shard: 123
402 keypair: {}/key.json
403 signature_verification: disabled
404 "#,
405 path.display()
406 );
407 std::fs::write(&yaml_path, yaml).unwrap();
408
409 assert_config_matches!(
410 Config::load_from(&yaml_path).unwrap(),
411 Config {
412 signature_verification: Some(SignatureVerificationConfig::Disabled),
413 ..Config::with_defaults(NonZero::new(123).unwrap(), keypair)
414 },
415 );
416
417 let yaml = format!(
418 r#"
419 shard: hello
420 keypair: {}/key.json
421 "#,
422 path.display()
423 );
424 std::fs::write(&yaml_path, yaml).unwrap();
425 assert!(Config::load_from(&yaml_path).is_err());
426
427 let yaml = r#"
428 shard: 1
429 keypair: ../wrong/path/key.json
430 "#;
431 std::fs::write(&yaml_path, yaml).unwrap();
432 assert!(Config::load_from(&yaml_path).is_err());
433
434 let yaml = format!("keypair: {}/key.json", path.display());
435 std::fs::write(&yaml_path, yaml).unwrap();
436 assert!(Config::load_from(yaml_path).is_err());
437 }
438
439 #[test]
440 #[parallel]
441 fn test_from_toml() {
442 let mut keys = Ed25519KeyDispenser::default();
443 let (path, keypair) = setup_path("toml", &mut keys);
444 let toml = format!(
445 r#"
446 shard = 123
447 keypair = "{}/key.json"
448 "#,
449 path.display()
450 );
451 let path = path.join("domain.toml");
452 std::fs::write(&path, toml).unwrap();
453
454 assert_config_matches!(
455 Config::load_from(path).unwrap(),
456 Config::with_defaults(NonZero::new(123).unwrap(), keypair),
457 );
458 }
459
460 #[test]
461 #[parallel]
462 fn test_from_json() {
463 let mut keys = Ed25519KeyDispenser::default();
464 let (path, keypair) = setup_path("json_basic", &mut keys);
465 let json = format!(
466 r#"{{
467 "shard": "123",
468 "keypair": "{}/key.json",
469 "signature_verification": "enabled"
470 }}"#,
471 path.display()
472 );
473 let path = path.join("domain.json");
474 std::fs::write(&path, json).unwrap();
475
476 assert_config_matches!(
477 Config::load_from(path).unwrap(),
478 Config {
479 signature_verification: Some(SignatureVerificationConfig::Enabled),
480 ..Config::with_defaults(NonZero::new(123).unwrap(), keypair)
481 },
482 );
483 }
484
485 #[test]
486 #[parallel]
487 fn test_relative_from_json() {
488 let mut keys = Ed25519KeyDispenser::default();
491 let (path, keypair) = setup_path("json_rel", &mut keys);
492
493 let json = r#"{
494 "shard": "123",
495 "keypair": "key.json"
496 }"#
497 .to_string();
498 let path1 = path.join("domain_relative_straight.json");
499 std::fs::write(&path1, json).unwrap();
500
501 assert_config_matches!(
502 Config::load_from(path1).unwrap(),
503 Config::with_defaults(NonZero::new(123).unwrap(), keypair.clone()),
504 );
505
506 let json = r#"{
507 "shard": "123",
508 "keypair": "./key.json"
509 }"#
510 .to_string();
511 let path2 = path.join("domain_relative_dot.json");
512 std::fs::write(&path2, json).unwrap();
513
514 assert_config_matches!(
515 Config::load_from(path2).unwrap(),
516 Config::with_defaults(NonZero::new(123).unwrap(), keypair),
517 );
518
519 let mut keys = Ed25519KeyDispenser::default();
522 let (path, keypair) = setup_path("some/json/subfolder", &mut keys);
523
524 let dir_name = path
527 .components()
528 .next_back()
529 .unwrap()
531 .as_os_str()
532 .to_str()
533 .unwrap();
534
535 let json = format!(
536 r#"{{
537 "shard": "123",
538 "keypair": "../{dir_name}/key.json"
539 }}"#
540 );
541 let path = path.join("domain_relative_dotdot.json");
542 std::fs::write(&path, json).unwrap();
543
544 assert_config_matches!(
545 Config::load_from(path).unwrap(),
546 Config::with_defaults(NonZero::new(123).unwrap(), keypair),
547 );
548 }
549
550 #[test]
551 #[serial]
552 fn test_admin_api_address() {
553 let mut keys = Ed25519KeyDispenser::default();
554 let (path, keypair) = setup_path("admin_api", &mut keys);
555 let json = format!(
556 r#"{{
557 "shard": "123",
558 "keypair": "{}/key.json",
559 "rpc_url": "https://dummy.net/rpc",
560 "admin_api": "0.0.0.0:1234"
561 }}"#,
562 path.display()
563 );
564 let path = path.join("domain.json");
565 std::fs::write(&path, json).unwrap();
566
567 assert_config_matches!(
568 Config::load_from(path).unwrap(),
569 Config {
570 admin_api: Some((Ipv4Addr::UNSPECIFIED, 1234).into()),
571 rpc_url: Some("https://dummy.net/rpc".to_string()),
572 ..Config::with_defaults(NonZero::new(123).unwrap(), keypair)
573 },
574 )
575 }
576
577 #[test]
578 #[serial]
579 fn test_env_override() {
580 let mut keys = Ed25519KeyDispenser::default();
581 let (path, keypair) = setup_path("env", &mut keys);
582 let json = format!(
583 r#"{{
584 "keypair": "{}/key.json",
585 "rpc_url": "https://awesome.net/rpc"
586 }}"#,
587 path.display()
588 );
589 let path = path.join("domain.json");
590 std::fs::write(&path, json).unwrap();
591
592 std::env::set_var("DOMAIN_SHARD", "666");
594 std::env::set_var("DOMAIN_RPC_URL", "http://boring.but/real");
595 std::env::set_var("DOMAIN_SIGNATURE_VERIFICATION", "enabled");
596
597 assert_config_matches!(
598 Config::load_from(path).unwrap(),
599 Config {
600 rpc_url: Some("http://boring.but/real".to_string()),
601 signature_verification: Some(SignatureVerificationConfig::Enabled),
602 ..Config::with_defaults(NonZero::new(666).unwrap(), keypair)
603 },
604 );
605
606 std::env::remove_var("DOMAIN_SHARD");
608 std::env::remove_var("DOMAIN_RPC_URL");
609 std::env::remove_var("DOMAIN_SIGNATURE_VERIFICATION");
610 }
611
612 #[test]
613 #[parallel]
614 fn test_from_yaml_with_mock_prover() {
615 let mut keys = Ed25519KeyDispenser::default();
616 let (path, keypair, _) = setup_path_with_elf("yml_mock_prover", &mut keys);
617 let yaml_path = path.join("domain.yaml");
618
619 let yaml = format!(
620 r#"
621 shard: 123
622 keypair: {}/key.json
623 rpc_url: http://localhost:9000
624 proving:
625 global_laws:
626 type: mock
627 "#,
628 path.display(),
629 );
630 std::fs::write(&yaml_path, yaml).unwrap();
631
632 assert_config_matches!(
633 Config::load_from(&yaml_path).unwrap(),
634 Config {
635 rpc_url: Some("http://localhost:9000".to_string()),
636 proving: Some(ProvingConfig {
637 global_laws: ProverConfig::Mock,
638 local_laws: None,
639 }),
640 ..Config::with_defaults(NonZero::new(123).unwrap(), keypair)
641 },
642 );
643 }
644
645 #[test]
646 #[parallel]
647 fn test_app_config_from_yaml() {
648 #[derive(Debug, Deserialize, PartialEq, Eq)]
649 struct AppConfig {
650 api_port: u16,
651 feature_flag: bool,
652 }
653
654 let mut keys = Ed25519KeyDispenser::default();
655 let (path, _keypair) = setup_path("yml_app_config", &mut keys);
656 let yaml_path = path.join("domain.yaml");
657
658 let yaml = format!(
659 r#"
660 shard: 123
661 keypair: {}/key.json
662 app:
663 api_port: 8081
664 feature_flag: true
665 "#,
666 path.display()
667 );
668 std::fs::write(&yaml_path, yaml).unwrap();
669
670 let config = Config::load_from(&yaml_path).unwrap();
671 let app_config = config.app_config::<AppConfig>().unwrap();
672
673 assert_config_matches!(
674 config,
675 Config::with_defaults(NonZero::new(123).unwrap(), _keypair),
676 );
677 assert_eq!(
678 app_config,
679 AppConfig {
680 api_port: 8081,
681 feature_flag: true
682 }
683 );
684 }
685
686 #[test]
687 #[parallel]
688 fn test_from_json_with_mock_prover() {
689 let mut keys = Ed25519KeyDispenser::default();
690 let (path, keypair, _) = setup_path_with_elf("json_mock_prover", &mut keys);
691 let json = format!(
692 r#"{{
693 "shard": "456",
694 "keypair": "{}/key.json",
695 "proving": {{
696 "global_laws": {{
697 "type": "mock"
698 }}
699 }}
700 }}"#,
701 path.display()
702 );
703 let json_path = path.join("domain.json");
704 std::fs::write(&json_path, json).unwrap();
705
706 assert_config_matches!(
707 Config::load_from(&json_path).unwrap(),
708 Config {
709 proving: Some(ProvingConfig {
710 global_laws: ProverConfig::Mock,
711 local_laws: None
712 }),
713 ..Config::with_defaults(NonZero::new(456).unwrap(), keypair)
714 },
715 );
716 }
717
718 #[cfg(feature = "sp1")]
719 #[test]
720 #[parallel]
721 fn test_from_yaml_with_sp1_cpu_prover() {
722 let mut keys = Ed25519KeyDispenser::default();
723 let (path, keypair, test_elf_content) = setup_path_with_elf("yml_sp1_cpu", &mut keys);
724 let yaml_path = path.join("domain.yaml");
725
726 let yaml = format!(
727 r#"
728 shard: 789
729 keypair: {}/key.json
730 proving:
731 global_laws:
732 type: sp1
733 backend:
734 type: cpu
735 mode: compressed
736 local_laws:
737 program: {}/test_local_laws.elf
738 "#,
739 path.display(),
740 path.display()
741 );
742 std::fs::write(&yaml_path, yaml).unwrap();
743
744 assert_config_matches!(
745 Config::load_from(&yaml_path).unwrap(),
746 Config {
747 proving: Some(ProvingConfig {
748 global_laws: ProverConfig::Sp1 {
749 backend: BackendConfig::Cpu,
750 mode: ProofMode::Compressed,
751 },
752 local_laws: Some(crate::proving::config::LocalLawsProverConfig {
753 program: test_elf_content,
754 }),
755 }),
756 ..Config::with_defaults(NonZero::new(789).unwrap(), keypair)
757 },
758 );
759 }
760
761 #[cfg(feature = "sp1")]
762 #[test]
763 #[parallel]
764 fn test_from_yaml_with_sp1_mock_prover() {
765 let mut keys = Ed25519KeyDispenser::default();
766 let (path, keypair, test_elf_content) = setup_path_with_elf("yml_sp1_mock", &mut keys);
767 let yaml_path = path.join("domain.yaml");
768
769 let yaml = format!(
770 r#"
771 shard: 345
772 keypair: {}/key.json
773 proving:
774 global_laws:
775 type: sp1
776 backend:
777 type: mock
778 mode: compressed
779 local_laws:
780 program: {}/test_local_laws.elf
781 "#,
782 path.display(),
783 path.display()
784 );
785 std::fs::write(&yaml_path, yaml).unwrap();
786
787 assert_config_matches!(
788 Config::load_from(&yaml_path).unwrap(),
789 Config {
790 proving: Some(ProvingConfig {
791 global_laws: ProverConfig::Sp1 {
792 backend: BackendConfig::Mock,
793 mode: ProofMode::Compressed,
794 },
795 local_laws: Some(crate::proving::config::LocalLawsProverConfig {
796 program: test_elf_content,
797 }),
798 }),
799 ..Config::with_defaults(NonZero::new(345).unwrap(), keypair)
800 },
801 );
802 }
803
804 #[cfg(feature = "sp1")]
805 #[test]
806 #[parallel]
807 fn test_from_yaml_with_sp1_succinct_prover() {
808 let mut keys = Ed25519KeyDispenser::default();
809 let (path, keypair, test_elf_content) = setup_path_with_elf("yml_sp1_succinct", &mut keys);
810 let yaml_path = path.join("domain.yaml");
811
812 let yaml = format!(
813 r#"
814 shard: 999
815 keypair: {}/key.json
816 proving:
817 global_laws:
818 type: sp1
819 backend:
820 type: succinct
821 private_key: test_private_key_123
822 rpc_url: https://test.succinct.xyz
823 strategy: hosted
824 mode: compressed
825 local_laws:
826 program: {}/test_local_laws.elf
827 prover:
828 type: sp1
829 backend:
830 type: succinct
831 private_key: test_local_key_456
832 rpc_url: https://local.succinct.xyz
833 strategy: hosted
834 mode: compressed
835 "#,
836 path.display(),
837 path.display()
838 );
839 std::fs::write(&yaml_path, yaml).unwrap();
840
841 assert_config_matches!(
842 Config::load_from(&yaml_path).unwrap(),
843 Config {
844 proving: Some(ProvingConfig {
845 global_laws: ProverConfig::Sp1 {
846 backend: BackendConfig::Succinct {
847 private_key: "test_private_key_123".to_string(),
848 rpc_url: "https://test.succinct.xyz".to_string(),
849 strategy: Some(FulfillmentStrategy::Hosted),
850 },
851 mode: ProofMode::Compressed,
852 },
853 local_laws: Some(crate::proving::config::LocalLawsProverConfig {
854 program: test_elf_content,
855 }),
856 }),
857 ..Config::with_defaults(NonZero::new(999).unwrap(), keypair)
858 },
859 );
860 }
861
862 #[test]
863 #[parallel]
864 fn test_from_toml_with_mock_prover() {
865 let mut keys = Ed25519KeyDispenser::default();
866 let (path, keypair, _) = setup_path_with_elf("toml_mock_prover", &mut keys);
867 let toml = format!(
868 r#"
869 shard = 321
870 keypair = "{}/key.json"
871
872 [proving.global_laws]
873 type = "mock"
874 "#,
875 path.display(),
876 );
877 let toml_path = path.join("domain.toml");
878 std::fs::write(&toml_path, toml).unwrap();
879
880 assert_config_matches!(
881 Config::load_from(&toml_path).unwrap(),
882 Config {
883 proving: Some(ProvingConfig {
884 global_laws: ProverConfig::Mock,
885 local_laws: None
886 }),
887 ..Config::with_defaults(NonZero::new(321).unwrap(), keypair)
888 },
889 );
890 }
891}