1use anyhow::Context;
2use config::{ConfigError, Map, Source, Value, ValueKind};
3use serde::{Deserialize, Serialize};
4use std::collections::{BTreeSet, HashMap, HashSet};
5use std::path::PathBuf;
6use std::str::FromStr;
7
8use mithril_cli_helper::{register_config_value, serde_deserialization};
9use mithril_common::chain_observer::ChainObserverType;
10use mithril_common::crypto_helper::{ManifestSigner, ProtocolGenesisSigner};
11use mithril_common::entities::{
12 BlockNumber, CardanoTransactionsSigningConfig, CompressionAlgorithm,
13 HexEncodedGenesisVerificationKey, HexEncodedKey, ProtocolParameters, SignedEntityConfig,
14 SignedEntityTypeDiscriminants,
15};
16use mithril_common::era::adapters::EraReaderAdapterType;
17use mithril_common::{CardanoNetwork, StdResult};
18use mithril_doc::{Documenter, DocumenterDefault, StructDoc};
19
20use crate::entities::AggregatorEpochSettings;
21use crate::http_server::SERVER_BASE_PATH;
22use crate::services::ancillary_signer::GcpCryptoKeyVersionResourceName;
23use crate::tools::url_sanitizer::SanitizedUrlWithTrailingSlash;
24use crate::tools::DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR;
25
26#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
28pub enum ExecutionEnvironment {
29 Test,
31
32 Production,
35}
36
37impl FromStr for ExecutionEnvironment {
38 type Err = ConfigError;
39
40 fn from_str(s: &str) -> Result<Self, Self::Err> {
41 match s {
42 "production" => Ok(Self::Production),
43 "test" => Ok(Self::Test),
44 _ => Err(ConfigError::Message(format!(
45 "Unknown execution environment {s}"
46 ))),
47 }
48 }
49}
50
51pub trait ConfigurationSource {
56 fn environment(&self) -> ExecutionEnvironment;
58
59 fn cardano_cli_path(&self) -> PathBuf {
61 panic!("cardano_cli_path is not implemented.");
62 }
63
64 fn cardano_node_socket_path(&self) -> PathBuf {
67 panic!("cardano_node_socket_path is not implemented.");
68 }
69
70 fn cardano_node_version(&self) -> String {
76 panic!("cardano_node_version is not implemented.");
77 }
78
79 fn network_magic(&self) -> Option<u64> {
83 panic!("network_magic is not implemented.");
84 }
85
86 fn network(&self) -> String {
88 panic!("network is not implemented.");
89 }
90
91 fn chain_observer_type(&self) -> ChainObserverType {
93 panic!("chain_observer_type is not implemented.");
94 }
95
96 fn protocol_parameters(&self) -> ProtocolParameters {
98 panic!("protocol_parameters is not implemented.");
99 }
100
101 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
103 panic!("snapshot_uploader_type is not implemented.");
104 }
105
106 fn snapshot_bucket_name(&self) -> Option<String> {
108 panic!("snapshot_bucket_name is not implemented.");
109 }
110
111 fn snapshot_use_cdn_domain(&self) -> bool {
113 panic!("snapshot_use_cdn_domain is not implemented.");
114 }
115
116 fn server_ip(&self) -> String {
118 panic!("server_ip is not implemented.");
119 }
120
121 fn server_port(&self) -> u16 {
123 panic!("server_port is not implemented.");
124 }
125
126 fn public_server_url(&self) -> Option<String> {
128 panic!("public_server_url is not implemented.");
129 }
130
131 fn run_interval(&self) -> u64 {
133 panic!("run_interval is not implemented.");
134 }
135
136 fn db_directory(&self) -> PathBuf {
138 panic!("db_directory is not implemented.");
139 }
140
141 fn snapshot_directory(&self) -> PathBuf {
143 panic!("snapshot_directory is not implemented.");
144 }
145
146 fn data_stores_directory(&self) -> PathBuf {
148 panic!("data_stores_directory is not implemented.");
149 }
150
151 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
153 panic!("genesis_verification_key is not implemented.");
154 }
155
156 fn reset_digests_cache(&self) -> bool {
158 panic!("reset_digests_cache is not implemented.");
159 }
160
161 fn disable_digests_cache(&self) -> bool {
163 panic!("disable_digests_cache is not implemented.");
164 }
165
166 fn store_retention_limit(&self) -> Option<usize> {
171 panic!("store_retention_limit is not implemented.");
172 }
173
174 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
176 panic!("era_reader_adapter_type is not implemented.");
177 }
178
179 fn era_reader_adapter_params(&self) -> Option<String> {
181 panic!("era_reader_adapter_params is not implemented.");
182 }
183
184 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
188 panic!("ancillary_files_signer_config is not implemented.");
189 }
190
191 fn signed_entity_types(&self) -> Option<String> {
197 panic!("signed_entity_types is not implemented.");
198 }
199
200 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
202 panic!("snapshot_compression_algorithm is not implemented.");
203 }
204
205 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
208 panic!("zstandard_parameters is not implemented.");
209 }
210
211 fn cexplorer_pools_url(&self) -> Option<String> {
213 panic!("cexplorer_pools_url is not implemented.");
214 }
215
216 fn signer_importer_run_interval(&self) -> u64 {
218 panic!("signer_importer_run_interval is not implemented.");
219 }
220
221 fn allow_unparsable_block(&self) -> bool {
225 panic!("allow_unparsable_block is not implemented.");
226 }
227
228 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
230 panic!("cardano_transactions_prover_cache_pool_size is not implemented.");
231 }
232
233 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
235 panic!("cardano_transactions_database_connection_pool_size is not implemented.");
236 }
237
238 fn cardano_transactions_signing_config(&self) -> CardanoTransactionsSigningConfig {
240 panic!("cardano_transactions_signing_config is not implemented.");
241 }
242
243 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
245 panic!("cardano_transactions_prover_max_hashes_allowed_by_request is not implemented.");
246 }
247
248 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
250 panic!(
251 "cardano_transactions_block_streamer_max_roll_forwards_per_poll is not implemented."
252 );
253 }
254
255 fn enable_metrics_server(&self) -> bool {
257 panic!("enable_metrics_server is not implemented.");
258 }
259
260 fn metrics_server_ip(&self) -> String {
262 panic!("metrics_server_ip is not implemented.");
263 }
264
265 fn metrics_server_port(&self) -> u16 {
267 panic!("metrics_server_port is not implemented.");
268 }
269
270 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
272 panic!("persist_usage_report_interval_in_seconds is not implemented.");
273 }
274
275 fn leader_aggregator_endpoint(&self) -> Option<String> {
281 panic!("leader_aggregator_endpoint is not implemented.");
282 }
283
284 fn custom_origin_tag_white_list(&self) -> Option<String> {
287 panic!("custom_origin_tag_white_list is not implemented.");
288 }
289
290 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
292 panic!("get_server_url is not implemented.");
293 }
294
295 fn get_network(&self) -> StdResult<CardanoNetwork> {
297 CardanoNetwork::from_code(self.network(), self.network_magic())
298 .with_context(|| "Invalid network configuration")
299 }
300
301 fn get_sqlite_dir(&self) -> PathBuf {
303 let store_dir = &self.data_stores_directory();
304
305 if !store_dir.exists() {
306 std::fs::create_dir_all(store_dir).unwrap();
307 }
308
309 self.data_stores_directory()
310 }
311
312 fn get_snapshot_dir(&self) -> StdResult<PathBuf> {
314 if !&self.snapshot_directory().exists() {
315 std::fs::create_dir_all(self.snapshot_directory())?;
316 }
317
318 Ok(self.snapshot_directory())
319 }
320
321 fn safe_epoch_retention_limit(&self) -> Option<u64> {
323 self.store_retention_limit()
324 .map(|limit| if limit > 3 { limit as u64 } else { 3 })
325 }
326
327 fn compute_allowed_signed_entity_types_discriminants(
329 &self,
330 ) -> StdResult<BTreeSet<SignedEntityTypeDiscriminants>> {
331 let allowed_discriminants = self
332 .signed_entity_types()
333 .as_ref()
334 .map(SignedEntityTypeDiscriminants::parse_list)
335 .transpose()
336 .with_context(|| "Invalid 'signed_entity_types' configuration")?
337 .unwrap_or_default();
338 let allowed_discriminants =
339 SignedEntityConfig::append_allowed_signed_entity_types_discriminants(
340 allowed_discriminants,
341 );
342
343 Ok(allowed_discriminants)
344 }
345
346 fn allow_http_serve_directory(&self) -> bool {
348 match self.snapshot_uploader_type() {
349 SnapshotUploaderType::Local => true,
350 SnapshotUploaderType::Gcp => false,
351 }
352 }
353
354 fn get_epoch_settings_configuration(&self) -> AggregatorEpochSettings {
356 AggregatorEpochSettings {
357 protocol_parameters: self.protocol_parameters(),
358 cardano_transactions_signing_config: self.cardano_transactions_signing_config(),
359 }
360 }
361
362 fn is_follower_aggregator(&self) -> bool {
364 self.leader_aggregator_endpoint().is_some()
365 }
366
367 fn compute_origin_tag_white_list(&self) -> HashSet<String> {
369 let mut white_list = HashSet::from([
370 "EXPLORER".to_string(),
371 "BENCHMARK".to_string(),
372 "CI".to_string(),
373 "NA".to_string(),
374 ]);
375 if let Some(custom_tags) = &self.custom_origin_tag_white_list() {
376 white_list.extend(custom_tags.split(',').map(|tag| tag.trim().to_string()));
377 }
378
379 white_list
380 }
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize, Documenter)]
385pub struct ServeCommandConfiguration {
386 pub environment: ExecutionEnvironment,
388
389 #[example = "`cardano-cli`"]
391 pub cardano_cli_path: PathBuf,
392
393 #[example = "`/tmp/cardano.sock`"]
396 pub cardano_node_socket_path: PathBuf,
397
398 pub cardano_node_version: String,
404
405 #[example = "`1097911063` or `42`"]
409 pub network_magic: Option<u64>,
410
411 #[example = "`testnet` or `mainnet` or `devnet`"]
413 pub network: String,
414
415 pub chain_observer_type: ChainObserverType,
417
418 #[example = "`{ k: 5, m: 100, phi_f: 0.65 }`"]
420 pub protocol_parameters: ProtocolParameters,
421
422 #[example = "`gcp` or `local`"]
424 pub snapshot_uploader_type: SnapshotUploaderType,
425
426 pub snapshot_bucket_name: Option<String>,
428
429 pub snapshot_use_cdn_domain: bool,
431
432 pub server_ip: String,
434
435 pub server_port: u16,
437
438 pub public_server_url: Option<String>,
440
441 #[example = "`60000`"]
443 pub run_interval: u64,
444
445 pub db_directory: PathBuf,
447
448 pub snapshot_directory: PathBuf,
450
451 #[example = "`./mithril-aggregator/stores`"]
453 pub data_stores_directory: PathBuf,
454
455 pub genesis_verification_key: HexEncodedGenesisVerificationKey,
457
458 pub reset_digests_cache: bool,
460
461 pub disable_digests_cache: bool,
463
464 pub store_retention_limit: Option<usize>,
469
470 pub era_reader_adapter_type: EraReaderAdapterType,
472
473 pub era_reader_adapter_params: Option<String>,
475
476 #[example = "\
482 - secret-key:<br/>`{ \"type\": \"secret-key\", \"secret_key\": \"136372c3138312c3138382c3130352c3233312c3135\" }`<br/>\
483 - Gcp kms:<br/>`{ \"type\": \"gcp-kms\", \"resource_name\": \"projects/project_name/locations/_location_name/keyRings/key_ring_name/cryptoKeys/key_name/cryptoKeyVersions/key_version\" }`\
484 "]
485 #[serde(deserialize_with = "serde_deserialization::string_or_struct")]
486 pub ancillary_files_signer_config: AncillaryFilesSignerConfig,
487
488 #[example = "`MithrilStakeDistribution,CardanoImmutableFilesFull,CardanoStakeDistribution`"]
494 pub signed_entity_types: Option<String>,
495
496 #[example = "`gzip` or `zstandard`"]
498 pub snapshot_compression_algorithm: CompressionAlgorithm,
499
500 #[example = "`{ level: 9, number_of_workers: 4 }`"]
503 pub zstandard_parameters: Option<ZstandardCompressionParameters>,
504
505 pub cexplorer_pools_url: Option<String>,
507
508 pub signer_importer_run_interval: u64,
510
511 pub allow_unparsable_block: bool,
515
516 pub cardano_transactions_prover_cache_pool_size: usize,
518
519 pub cardano_transactions_database_connection_pool_size: usize,
521
522 #[example = "`{ security_parameter: 3000, step: 120 }`"]
524 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
525
526 pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
528
529 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
531
532 pub enable_metrics_server: bool,
534
535 pub metrics_server_ip: String,
537
538 pub metrics_server_port: u16,
540
541 pub persist_usage_report_interval_in_seconds: u64,
543
544 pub leader_aggregator_endpoint: Option<String>,
550
551 pub custom_origin_tag_white_list: Option<String>,
554}
555
556#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
558#[serde(rename_all = "lowercase")]
559pub enum SnapshotUploaderType {
560 Gcp,
562 Local,
564}
565
566#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
568pub struct ZstandardCompressionParameters {
569 pub level: i32,
571
572 pub number_of_workers: u32,
574}
575
576impl Default for ZstandardCompressionParameters {
577 fn default() -> Self {
578 Self {
579 level: 9,
580 number_of_workers: 4,
581 }
582 }
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
589#[serde(rename_all = "kebab-case", tag = "type")]
590pub enum AncillaryFilesSignerConfig {
591 SecretKey {
593 secret_key: HexEncodedKey,
595 },
596 GcpKms {
598 resource_name: GcpCryptoKeyVersionResourceName,
600 #[serde(default = "default_gcp_kms_credentials_json_env_var")]
602 credentials_json_env_var: String,
603 },
604}
605
606fn default_gcp_kms_credentials_json_env_var() -> String {
607 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
608}
609
610impl FromStr for AncillaryFilesSignerConfig {
611 type Err = serde_json::Error;
612
613 fn from_str(s: &str) -> Result<Self, Self::Err> {
614 serde_json::from_str(s)
615 }
616}
617
618impl ServeCommandConfiguration {
619 pub fn new_sample(tmp_path: PathBuf) -> Self {
621 let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
622 .create_verifier()
623 .to_verification_key();
624 let ancillary_files_signer_secret_key =
625 ManifestSigner::create_deterministic_signer().secret_key();
626
627 Self {
628 environment: ExecutionEnvironment::Test,
629 cardano_cli_path: PathBuf::new(),
630 cardano_node_socket_path: PathBuf::new(),
631 cardano_node_version: "0.0.1".to_string(),
632 network_magic: Some(42),
633 network: "devnet".to_string(),
634 chain_observer_type: ChainObserverType::Fake,
635 protocol_parameters: ProtocolParameters {
636 k: 5,
637 m: 100,
638 phi_f: 0.95,
639 },
640 snapshot_uploader_type: SnapshotUploaderType::Local,
641 snapshot_bucket_name: None,
642 snapshot_use_cdn_domain: false,
643 server_ip: "0.0.0.0".to_string(),
644 server_port: 8000,
645 public_server_url: None,
646 run_interval: 5000,
647 db_directory: PathBuf::new(),
648 snapshot_directory: tmp_path,
655 data_stores_directory: PathBuf::from(":memory:"),
656 genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
657 reset_digests_cache: false,
658 disable_digests_cache: false,
659 store_retention_limit: None,
660 era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
661 era_reader_adapter_params: None,
662 ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
663 secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
664 },
665 signed_entity_types: None,
666 snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
667 zstandard_parameters: Some(ZstandardCompressionParameters::default()),
668 cexplorer_pools_url: None,
669 signer_importer_run_interval: 1,
670 allow_unparsable_block: false,
671 cardano_transactions_prover_cache_pool_size: 3,
672 cardano_transactions_database_connection_pool_size: 5,
673 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
674 security_parameter: BlockNumber(120),
675 step: BlockNumber(15),
676 },
677 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
678 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
679 enable_metrics_server: true,
680 metrics_server_ip: "0.0.0.0".to_string(),
681 metrics_server_port: 9090,
682 persist_usage_report_interval_in_seconds: 10,
683 leader_aggregator_endpoint: None,
684 custom_origin_tag_white_list: None,
685 }
686 }
687
688 pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
690 SanitizedUrlWithTrailingSlash::parse(&format!(
691 "http://{}:{}/{SERVER_BASE_PATH}/",
692 self.server_ip, self.server_port
693 ))
694 }
695}
696
697impl ConfigurationSource for ServeCommandConfiguration {
698 fn environment(&self) -> ExecutionEnvironment {
699 self.environment.clone()
700 }
701
702 fn cardano_cli_path(&self) -> PathBuf {
703 self.cardano_cli_path.clone()
704 }
705
706 fn cardano_node_socket_path(&self) -> PathBuf {
707 self.cardano_node_socket_path.clone()
708 }
709
710 fn cardano_node_version(&self) -> String {
711 self.cardano_node_version.clone()
712 }
713
714 fn network_magic(&self) -> Option<u64> {
715 self.network_magic
716 }
717
718 fn network(&self) -> String {
719 self.network.clone()
720 }
721
722 fn chain_observer_type(&self) -> ChainObserverType {
723 self.chain_observer_type.clone()
724 }
725
726 fn protocol_parameters(&self) -> ProtocolParameters {
727 self.protocol_parameters.clone()
728 }
729
730 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
731 self.snapshot_uploader_type
732 }
733
734 fn snapshot_bucket_name(&self) -> Option<String> {
735 self.snapshot_bucket_name.clone()
736 }
737
738 fn snapshot_use_cdn_domain(&self) -> bool {
739 self.snapshot_use_cdn_domain
740 }
741
742 fn server_ip(&self) -> String {
743 self.server_ip.clone()
744 }
745
746 fn server_port(&self) -> u16 {
747 self.server_port
748 }
749
750 fn public_server_url(&self) -> Option<String> {
751 self.public_server_url.clone()
752 }
753
754 fn run_interval(&self) -> u64 {
755 self.run_interval
756 }
757
758 fn db_directory(&self) -> PathBuf {
759 self.db_directory.clone()
760 }
761
762 fn snapshot_directory(&self) -> PathBuf {
763 self.snapshot_directory.clone()
764 }
765
766 fn data_stores_directory(&self) -> PathBuf {
767 self.data_stores_directory.clone()
768 }
769
770 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
771 self.genesis_verification_key.clone()
772 }
773
774 fn reset_digests_cache(&self) -> bool {
775 self.reset_digests_cache
776 }
777
778 fn disable_digests_cache(&self) -> bool {
779 self.disable_digests_cache
780 }
781
782 fn store_retention_limit(&self) -> Option<usize> {
783 self.store_retention_limit
784 }
785
786 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
787 self.era_reader_adapter_type.clone()
788 }
789
790 fn era_reader_adapter_params(&self) -> Option<String> {
791 self.era_reader_adapter_params.clone()
792 }
793
794 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
795 self.ancillary_files_signer_config.clone()
796 }
797
798 fn signed_entity_types(&self) -> Option<String> {
799 self.signed_entity_types.clone()
800 }
801
802 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
803 self.snapshot_compression_algorithm
804 }
805
806 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
807 self.zstandard_parameters
808 }
809
810 fn cexplorer_pools_url(&self) -> Option<String> {
811 self.cexplorer_pools_url.clone()
812 }
813
814 fn signer_importer_run_interval(&self) -> u64 {
815 self.signer_importer_run_interval
816 }
817
818 fn allow_unparsable_block(&self) -> bool {
819 self.allow_unparsable_block
820 }
821
822 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
823 self.cardano_transactions_prover_cache_pool_size
824 }
825
826 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
827 self.cardano_transactions_database_connection_pool_size
828 }
829
830 fn cardano_transactions_signing_config(&self) -> CardanoTransactionsSigningConfig {
831 self.cardano_transactions_signing_config.clone()
832 }
833
834 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
835 self.cardano_transactions_prover_max_hashes_allowed_by_request
836 }
837
838 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
839 self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
840 }
841
842 fn enable_metrics_server(&self) -> bool {
843 self.enable_metrics_server
844 }
845
846 fn metrics_server_ip(&self) -> String {
847 self.metrics_server_ip.clone()
848 }
849
850 fn metrics_server_port(&self) -> u16 {
851 self.metrics_server_port
852 }
853
854 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
855 self.persist_usage_report_interval_in_seconds
856 }
857
858 fn leader_aggregator_endpoint(&self) -> Option<String> {
859 self.leader_aggregator_endpoint.clone()
860 }
861
862 fn custom_origin_tag_white_list(&self) -> Option<String> {
863 self.custom_origin_tag_white_list.clone()
864 }
865
866 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
867 match &self.public_server_url {
868 Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
869 None => self.get_local_server_url(),
870 }
871 }
872}
873
874#[derive(Debug, Clone, DocumenterDefault)]
876pub struct DefaultConfiguration {
877 pub environment: ExecutionEnvironment,
879
880 pub server_ip: String,
882
883 pub server_port: String,
885
886 pub db_directory: String,
888
889 pub snapshot_directory: String,
891
892 pub snapshot_uploader_type: String,
894
895 pub era_reader_adapter_type: String,
897
898 pub chain_observer_type: String,
900
901 pub reset_digests_cache: String,
903
904 pub disable_digests_cache: String,
906
907 pub snapshot_compression_algorithm: String,
909
910 pub snapshot_use_cdn_domain: String,
912
913 pub signer_importer_run_interval: u64,
915
916 pub allow_unparsable_block: String,
920
921 pub cardano_transactions_prover_cache_pool_size: u32,
923
924 pub cardano_transactions_database_connection_pool_size: u32,
926
927 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
929
930 pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
932
933 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
935
936 pub enable_metrics_server: String,
938
939 pub metrics_server_ip: String,
941
942 pub metrics_server_port: u16,
944
945 pub persist_usage_report_interval_in_seconds: u64,
947}
948
949impl Default for DefaultConfiguration {
950 fn default() -> Self {
951 Self {
952 environment: ExecutionEnvironment::Production,
953 server_ip: "0.0.0.0".to_string(),
954 server_port: "8080".to_string(),
955 db_directory: "/db".to_string(),
956 snapshot_directory: ".".to_string(),
957 snapshot_uploader_type: "gcp".to_string(),
958 era_reader_adapter_type: "bootstrap".to_string(),
959 chain_observer_type: "pallas".to_string(),
960 reset_digests_cache: "false".to_string(),
961 disable_digests_cache: "false".to_string(),
962 snapshot_compression_algorithm: "zstandard".to_string(),
963 snapshot_use_cdn_domain: "false".to_string(),
964 signer_importer_run_interval: 720,
965 allow_unparsable_block: "false".to_string(),
966 cardano_transactions_prover_cache_pool_size: 10,
967 cardano_transactions_database_connection_pool_size: 10,
968 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
969 security_parameter: BlockNumber(3000),
970 step: BlockNumber(120),
971 },
972 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
973 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
974 enable_metrics_server: "false".to_string(),
975 metrics_server_ip: "0.0.0.0".to_string(),
976 metrics_server_port: 9090,
977 persist_usage_report_interval_in_seconds: 10,
978 }
979 }
980}
981
982impl DefaultConfiguration {
983 fn namespace() -> String {
984 "default configuration".to_string()
985 }
986}
987
988impl From<ExecutionEnvironment> for ValueKind {
989 fn from(value: ExecutionEnvironment) -> Self {
990 match value {
991 ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
992 ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
993 }
994 }
995}
996
997impl Source for DefaultConfiguration {
998 fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
999 Box::new(self.clone())
1000 }
1001
1002 fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1003 let mut result = Map::new();
1004
1005 let namespace = DefaultConfiguration::namespace();
1006
1007 let myself = self.clone();
1008 register_config_value!(result, &namespace, myself.environment);
1009 register_config_value!(result, &namespace, myself.server_ip);
1010 register_config_value!(result, &namespace, myself.server_port);
1011 register_config_value!(result, &namespace, myself.db_directory);
1012 register_config_value!(result, &namespace, myself.snapshot_directory);
1013 register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1014 register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1015 register_config_value!(result, &namespace, myself.reset_digests_cache);
1016 register_config_value!(result, &namespace, myself.disable_digests_cache);
1017 register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1018 register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1019 register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1020 register_config_value!(result, &namespace, myself.allow_unparsable_block);
1021 register_config_value!(
1022 result,
1023 &namespace,
1024 myself.cardano_transactions_prover_cache_pool_size
1025 );
1026 register_config_value!(
1027 result,
1028 &namespace,
1029 myself.cardano_transactions_database_connection_pool_size
1030 );
1031 register_config_value!(
1032 result,
1033 &namespace,
1034 myself.cardano_transactions_prover_max_hashes_allowed_by_request
1035 );
1036 register_config_value!(
1037 result,
1038 &namespace,
1039 myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1040 );
1041 register_config_value!(result, &namespace, myself.enable_metrics_server);
1042 register_config_value!(result, &namespace, myself.metrics_server_ip);
1043 register_config_value!(result, &namespace, myself.metrics_server_port);
1044 register_config_value!(
1045 result,
1046 &namespace,
1047 myself.persist_usage_report_interval_in_seconds
1048 );
1049 register_config_value!(
1050 result,
1051 &namespace,
1052 myself.cardano_transactions_signing_config,
1053 |v: CardanoTransactionsSigningConfig| HashMap::from([
1054 (
1055 "security_parameter".to_string(),
1056 ValueKind::from(*v.security_parameter,),
1057 ),
1058 ("step".to_string(), ValueKind::from(*v.step),)
1059 ])
1060 );
1061 Ok(result)
1062 }
1063}
1064
1065#[cfg(test)]
1066mod test {
1067 use mithril_common::temp_dir;
1068
1069 use super::*;
1070
1071 #[test]
1072 fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1073 for limit in 4..=10u64 {
1074 let configuration = ServeCommandConfiguration {
1075 store_retention_limit: Some(limit as usize),
1076 ..ServeCommandConfiguration::new_sample(temp_dir!())
1077 };
1078 assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1079 }
1080 }
1081
1082 #[test]
1083 fn safe_epoch_retention_limit_wont_change_a_none_value() {
1084 let configuration = ServeCommandConfiguration {
1085 store_retention_limit: None,
1086 ..ServeCommandConfiguration::new_sample(temp_dir!())
1087 };
1088 assert_eq!(configuration.safe_epoch_retention_limit(), None);
1089 }
1090
1091 #[test]
1092 fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1093 for limit in 0..=3 {
1094 let configuration = ServeCommandConfiguration {
1095 store_retention_limit: Some(limit),
1096 ..ServeCommandConfiguration::new_sample(temp_dir!())
1097 };
1098 assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1099 }
1100 }
1101
1102 #[test]
1103 fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1104 #[derive(Debug, Deserialize)]
1105 struct TargetConfig {
1106 cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1107 }
1108
1109 let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1110 let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1111
1112 assert_eq!(
1113 target.cardano_transactions_signing_config,
1114 DefaultConfiguration::default().cardano_transactions_signing_config
1115 );
1116 }
1117
1118 #[test]
1119 fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1120 let config = ServeCommandConfiguration {
1121 signed_entity_types: None,
1122 ..ServeCommandConfiguration::new_sample(temp_dir!())
1123 };
1124
1125 assert_eq!(
1126 config
1127 .compute_allowed_signed_entity_types_discriminants()
1128 .unwrap(),
1129 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1130 );
1131 }
1132
1133 #[test]
1134 fn allow_http_serve_directory() {
1135 let config = ServeCommandConfiguration {
1136 snapshot_uploader_type: SnapshotUploaderType::Local,
1137 ..ServeCommandConfiguration::new_sample(temp_dir!())
1138 };
1139
1140 assert!(config.allow_http_serve_directory());
1141
1142 let config = ServeCommandConfiguration {
1143 snapshot_uploader_type: SnapshotUploaderType::Gcp,
1144 ..ServeCommandConfiguration::new_sample(temp_dir!())
1145 };
1146
1147 assert!(!config.allow_http_serve_directory());
1148 }
1149
1150 #[test]
1151 fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1152 let config = ServeCommandConfiguration {
1153 server_ip: "1.2.3.4".to_string(),
1154 server_port: 5678,
1155 public_server_url: None,
1156 ..ServeCommandConfiguration::new_sample(temp_dir!())
1157 };
1158
1159 assert_eq!(
1160 config.get_server_url().unwrap().as_str(),
1161 &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1162 );
1163 }
1164
1165 #[test]
1166 fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1167 let config = ServeCommandConfiguration {
1168 server_ip: "1.2.3.4".to_string(),
1169 server_port: 5678,
1170 public_server_url: Some("https://example.com".to_string()),
1171 ..ServeCommandConfiguration::new_sample(temp_dir!())
1172 };
1173
1174 assert_eq!(
1175 config.get_server_url().unwrap().as_str(),
1176 "https://example.com/"
1177 );
1178 }
1179
1180 #[test]
1181 fn joining_to_local_server_url_keep_base_path() {
1182 let config = ServeCommandConfiguration {
1183 server_ip: "1.2.3.4".to_string(),
1184 server_port: 6789,
1185 public_server_url: None,
1186 ..ServeCommandConfiguration::new_sample(temp_dir!())
1187 };
1188
1189 let joined_url = config
1190 .get_local_server_url()
1191 .unwrap()
1192 .join("some/path")
1193 .unwrap();
1194 assert!(
1195 joined_url.as_str().contains(SERVER_BASE_PATH),
1196 "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1197 );
1198 }
1199
1200 #[test]
1201 fn joining_to_public_server_url_without_trailing_slash() {
1202 let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1203 let config = ServeCommandConfiguration {
1204 public_server_url: Some(format!(
1205 "https://example.com/{subpath_without_trailing_slash}"
1206 )),
1207 ..ServeCommandConfiguration::new_sample(temp_dir!())
1208 };
1209
1210 let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1211 assert!(
1212 joined_url.as_str().contains(subpath_without_trailing_slash),
1213 "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1214 );
1215 }
1216
1217 #[test]
1218 fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1219 let config = ServeCommandConfiguration {
1220 leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1221 ..ServeCommandConfiguration::new_sample(temp_dir!())
1222 };
1223
1224 assert!(config.is_follower_aggregator());
1225 }
1226
1227 #[test]
1228 fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1229 let config = ServeCommandConfiguration {
1230 leader_aggregator_endpoint: None,
1231 ..ServeCommandConfiguration::new_sample(temp_dir!())
1232 };
1233
1234 assert!(!config.is_follower_aggregator());
1235 }
1236
1237 #[test]
1238 fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value(
1239 ) {
1240 let serialized_json = r#"{
1241 "type": "secret-key",
1242 "secret_key": "whatever"
1243 }"#;
1244
1245 let deserialized: AncillaryFilesSignerConfig =
1246 serde_json::from_str(serialized_json).unwrap();
1247 assert_eq!(
1248 deserialized,
1249 AncillaryFilesSignerConfig::SecretKey {
1250 secret_key: "whatever".to_string()
1251 }
1252 );
1253 }
1254
1255 #[test]
1256 fn deserializing_ancillary_signing_gcp_kms_configuration() {
1257 let serialized_json = r#"{
1258 "type": "gcp-kms",
1259 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1260 "credentials_json_env_var": "CUSTOM_ENV_VAR"
1261 }"#;
1262
1263 let deserialized: AncillaryFilesSignerConfig =
1264 serde_json::from_str(serialized_json).unwrap();
1265 assert_eq!(
1266 deserialized,
1267 AncillaryFilesSignerConfig::GcpKms {
1268 resource_name: GcpCryptoKeyVersionResourceName {
1269 project: "123456789".to_string(),
1270 location: "global".to_string(),
1271 key_ring: "my-keyring".to_string(),
1272 key_name: "my-key".to_string(),
1273 version: "1".to_string(),
1274 },
1275 credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1276 }
1277 );
1278 }
1279
1280 #[test]
1281 fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default(
1282 ) {
1283 let serialized_json = r#"{
1284 "type": "gcp-kms",
1285 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1286 }"#;
1287
1288 let deserialized: AncillaryFilesSignerConfig =
1289 serde_json::from_str(serialized_json).unwrap();
1290 if let AncillaryFilesSignerConfig::GcpKms {
1291 credentials_json_env_var,
1292 ..
1293 } = deserialized
1294 {
1295 assert_eq!(
1296 credentials_json_env_var,
1297 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1298 );
1299 } else {
1300 panic!("Expected GcpKms variant but got {deserialized:?}");
1301 }
1302 }
1303
1304 mod origin_tag {
1305 use super::*;
1306
1307 #[test]
1308 fn default_origin_tag_white_list_is_not_empty() {
1309 let config = ServeCommandConfiguration {
1310 custom_origin_tag_white_list: None,
1311 ..ServeCommandConfiguration::new_sample(temp_dir!())
1312 };
1313 assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1314 }
1315
1316 #[test]
1317 fn custom_origin_tag_are_added_to_default_white_list() {
1318 let config = ServeCommandConfiguration {
1319 custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1320 ..ServeCommandConfiguration::new_sample(temp_dir!())
1321 };
1322
1323 let default_white_list = ServeCommandConfiguration {
1324 custom_origin_tag_white_list: None,
1325 ..ServeCommandConfiguration::new_sample(temp_dir!())
1326 }
1327 .compute_origin_tag_white_list();
1328
1329 let mut expected_white_list = default_white_list.clone();
1330 assert!(expected_white_list.insert("TAG_A".to_string()));
1331 assert!(expected_white_list.insert("TAG_B".to_string()));
1332 assert!(expected_white_list.insert("TAG_C".to_string()));
1333
1334 assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1335 }
1336 }
1337}