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_cardano_node_chain::chain_observer::ChainObserverType;
9use mithril_cli_helper::{register_config_value, serde_deserialization};
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::{CardanoNetwork, StdResult};
17use mithril_doc::{Documenter, DocumenterDefault, StructDoc};
18use mithril_era::adapters::EraReaderAdapterType;
19
20use crate::entities::AggregatorEpochSettings;
21use crate::http_server::SERVER_BASE_PATH;
22use crate::services::ancillary_signer::GcpCryptoKeyVersionResourceName;
23use crate::tools::DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR;
24use crate::tools::url_sanitizer::SanitizedUrlWithTrailingSlash;
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 {
66 panic!("cardano_node_socket_path is not implemented.");
67 }
68
69 fn dmq_node_socket_path(&self) -> Option<PathBuf> {
71 panic!("dmq_node_socket_path is not implemented.");
72 }
73
74 fn cardano_node_version(&self) -> String {
80 panic!("cardano_node_version is not implemented.");
81 }
82
83 fn network_magic(&self) -> Option<u64> {
87 panic!("network_magic is not implemented.");
88 }
89
90 fn network(&self) -> String {
92 panic!("network is not implemented.");
93 }
94
95 fn chain_observer_type(&self) -> ChainObserverType {
97 panic!("chain_observer_type is not implemented.");
98 }
99
100 fn protocol_parameters(&self) -> ProtocolParameters {
102 panic!("protocol_parameters is not implemented.");
103 }
104
105 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
107 panic!("snapshot_uploader_type is not implemented.");
108 }
109
110 fn snapshot_bucket_name(&self) -> Option<String> {
112 panic!("snapshot_bucket_name is not implemented.");
113 }
114
115 fn snapshot_use_cdn_domain(&self) -> bool {
117 panic!("snapshot_use_cdn_domain is not implemented.");
118 }
119
120 fn server_ip(&self) -> String {
122 panic!("server_ip is not implemented.");
123 }
124
125 fn server_port(&self) -> u16 {
127 panic!("server_port is not implemented.");
128 }
129
130 fn public_server_url(&self) -> Option<String> {
132 panic!("public_server_url is not implemented.");
133 }
134
135 fn run_interval(&self) -> u64 {
137 panic!("run_interval is not implemented.");
138 }
139
140 fn db_directory(&self) -> PathBuf {
142 panic!("db_directory is not implemented.");
143 }
144
145 fn snapshot_directory(&self) -> PathBuf {
147 panic!("snapshot_directory is not implemented.");
148 }
149
150 fn data_stores_directory(&self) -> PathBuf {
152 panic!("data_stores_directory is not implemented.");
153 }
154
155 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
157 panic!("genesis_verification_key is not implemented.");
158 }
159
160 fn reset_digests_cache(&self) -> bool {
162 panic!("reset_digests_cache is not implemented.");
163 }
164
165 fn disable_digests_cache(&self) -> bool {
167 panic!("disable_digests_cache is not implemented.");
168 }
169
170 fn store_retention_limit(&self) -> Option<usize> {
175 panic!("store_retention_limit is not implemented.");
176 }
177
178 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
180 panic!("era_reader_adapter_type is not implemented.");
181 }
182
183 fn era_reader_adapter_params(&self) -> Option<String> {
185 panic!("era_reader_adapter_params is not implemented.");
186 }
187
188 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
192 panic!("ancillary_files_signer_config is not implemented.");
193 }
194
195 fn signed_entity_types(&self) -> Option<String> {
201 panic!("signed_entity_types is not implemented.");
202 }
203
204 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
206 panic!("snapshot_compression_algorithm is not implemented.");
207 }
208
209 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
212 panic!("zstandard_parameters is not implemented.");
213 }
214
215 fn cexplorer_pools_url(&self) -> Option<String> {
217 panic!("cexplorer_pools_url is not implemented.");
218 }
219
220 fn signer_importer_run_interval(&self) -> u64 {
222 panic!("signer_importer_run_interval is not implemented.");
223 }
224
225 fn allow_unparsable_block(&self) -> bool {
229 panic!("allow_unparsable_block is not implemented.");
230 }
231
232 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
234 panic!("cardano_transactions_prover_cache_pool_size is not implemented.");
235 }
236
237 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
239 panic!("cardano_transactions_database_connection_pool_size is not implemented.");
240 }
241
242 fn cardano_transactions_signing_config(&self) -> CardanoTransactionsSigningConfig {
244 panic!("cardano_transactions_signing_config is not implemented.");
245 }
246
247 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
249 panic!("cardano_transactions_prover_max_hashes_allowed_by_request is not implemented.");
250 }
251
252 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
254 panic!(
255 "cardano_transactions_block_streamer_max_roll_forwards_per_poll is not implemented."
256 );
257 }
258
259 fn enable_metrics_server(&self) -> bool {
261 panic!("enable_metrics_server is not implemented.");
262 }
263
264 fn metrics_server_ip(&self) -> String {
266 panic!("metrics_server_ip is not implemented.");
267 }
268
269 fn metrics_server_port(&self) -> u16 {
271 panic!("metrics_server_port is not implemented.");
272 }
273
274 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
276 panic!("persist_usage_report_interval_in_seconds is not implemented.");
277 }
278
279 fn leader_aggregator_endpoint(&self) -> Option<String> {
285 panic!("leader_aggregator_endpoint is not implemented.");
286 }
287
288 fn custom_origin_tag_white_list(&self) -> Option<String> {
291 panic!("custom_origin_tag_white_list is not implemented.");
292 }
293
294 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
296 panic!("get_server_url is not implemented.");
297 }
298
299 fn get_network(&self) -> StdResult<CardanoNetwork> {
301 CardanoNetwork::from_code(self.network(), self.network_magic())
302 .with_context(|| "Invalid network configuration")
303 }
304
305 fn get_sqlite_dir(&self) -> PathBuf {
307 let store_dir = &self.data_stores_directory();
308
309 if !store_dir.exists() {
310 std::fs::create_dir_all(store_dir).unwrap();
311 }
312
313 self.data_stores_directory()
314 }
315
316 fn get_snapshot_dir(&self) -> StdResult<PathBuf> {
318 if !&self.snapshot_directory().exists() {
319 std::fs::create_dir_all(self.snapshot_directory())?;
320 }
321
322 Ok(self.snapshot_directory())
323 }
324
325 fn safe_epoch_retention_limit(&self) -> Option<u64> {
327 self.store_retention_limit()
328 .map(|limit| if limit > 3 { limit as u64 } else { 3 })
329 }
330
331 fn compute_allowed_signed_entity_types_discriminants(
333 &self,
334 ) -> StdResult<BTreeSet<SignedEntityTypeDiscriminants>> {
335 let allowed_discriminants = self
336 .signed_entity_types()
337 .as_ref()
338 .map(SignedEntityTypeDiscriminants::parse_list)
339 .transpose()
340 .with_context(|| "Invalid 'signed_entity_types' configuration")?
341 .unwrap_or_default();
342 let allowed_discriminants =
343 SignedEntityConfig::append_allowed_signed_entity_types_discriminants(
344 allowed_discriminants,
345 );
346
347 Ok(allowed_discriminants)
348 }
349
350 fn allow_http_serve_directory(&self) -> bool {
352 match self.snapshot_uploader_type() {
353 SnapshotUploaderType::Local => true,
354 SnapshotUploaderType::Gcp => false,
355 }
356 }
357
358 fn get_epoch_settings_configuration(&self) -> AggregatorEpochSettings {
360 AggregatorEpochSettings {
361 protocol_parameters: self.protocol_parameters(),
362 cardano_transactions_signing_config: self.cardano_transactions_signing_config(),
363 }
364 }
365
366 fn is_follower_aggregator(&self) -> bool {
368 self.leader_aggregator_endpoint().is_some()
369 }
370
371 fn compute_origin_tag_white_list(&self) -> HashSet<String> {
373 let mut white_list = HashSet::from([
374 "EXPLORER".to_string(),
375 "BENCHMARK".to_string(),
376 "CI".to_string(),
377 "NA".to_string(),
378 ]);
379 if let Some(custom_tags) = &self.custom_origin_tag_white_list() {
380 white_list.extend(custom_tags.split(',').map(|tag| tag.trim().to_string()));
381 }
382
383 white_list
384 }
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize, Documenter)]
389pub struct ServeCommandConfiguration {
390 pub environment: ExecutionEnvironment,
392
393 #[example = "`cardano-cli`"]
395 pub cardano_cli_path: PathBuf,
396
397 #[example = "`/ipc/node.socket`"]
399 pub cardano_node_socket_path: PathBuf,
400
401 #[example = "`/ipc/dmq.socket`"]
403 pub dmq_node_socket_path: Option<PathBuf>,
404
405 pub cardano_node_version: String,
411
412 #[example = "`1097911063` or `42`"]
416 pub network_magic: Option<u64>,
417
418 #[example = "`testnet` or `mainnet` or `devnet`"]
420 pub network: String,
421
422 pub chain_observer_type: ChainObserverType,
424
425 #[example = "`{ k: 5, m: 100, phi_f: 0.65 }`"]
427 pub protocol_parameters: ProtocolParameters,
428
429 #[example = "`gcp` or `local`"]
431 pub snapshot_uploader_type: SnapshotUploaderType,
432
433 pub snapshot_bucket_name: Option<String>,
435
436 pub snapshot_use_cdn_domain: bool,
438
439 pub server_ip: String,
441
442 pub server_port: u16,
444
445 pub public_server_url: Option<String>,
447
448 #[example = "`60000`"]
450 pub run_interval: u64,
451
452 pub db_directory: PathBuf,
454
455 pub snapshot_directory: PathBuf,
457
458 #[example = "`./mithril-aggregator/stores`"]
460 pub data_stores_directory: PathBuf,
461
462 pub genesis_verification_key: HexEncodedGenesisVerificationKey,
464
465 pub reset_digests_cache: bool,
467
468 pub disable_digests_cache: bool,
470
471 pub store_retention_limit: Option<usize>,
476
477 pub era_reader_adapter_type: EraReaderAdapterType,
479
480 pub era_reader_adapter_params: Option<String>,
482
483 #[example = "\
489 - secret-key:<br/>`{ \"type\": \"secret-key\", \"secret_key\": \"136372c3138312c3138382c3130352c3233312c3135\" }`<br/>\
490 - Gcp kms:<br/>`{ \"type\": \"gcp-kms\", \"resource_name\": \"projects/project_name/locations/_location_name/keyRings/key_ring_name/cryptoKeys/key_name/cryptoKeyVersions/key_version\" }`\
491 "]
492 #[serde(deserialize_with = "serde_deserialization::string_or_struct")]
493 pub ancillary_files_signer_config: AncillaryFilesSignerConfig,
494
495 #[example = "`CardanoImmutableFilesFull,CardanoStakeDistribution,CardanoDatabase,CardanoTransactions`"]
500 pub signed_entity_types: Option<String>,
501
502 #[example = "`gzip` or `zstandard`"]
504 pub snapshot_compression_algorithm: CompressionAlgorithm,
505
506 #[example = "`{ level: 9, number_of_workers: 4 }`"]
509 pub zstandard_parameters: Option<ZstandardCompressionParameters>,
510
511 pub cexplorer_pools_url: Option<String>,
513
514 pub signer_importer_run_interval: u64,
516
517 pub allow_unparsable_block: bool,
521
522 pub cardano_transactions_prover_cache_pool_size: usize,
524
525 pub cardano_transactions_database_connection_pool_size: usize,
527
528 #[example = "`{ security_parameter: 3000, step: 120 }`"]
530 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
531
532 pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
534
535 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
537
538 pub enable_metrics_server: bool,
540
541 pub metrics_server_ip: String,
543
544 pub metrics_server_port: u16,
546
547 pub persist_usage_report_interval_in_seconds: u64,
549
550 pub leader_aggregator_endpoint: Option<String>,
556
557 pub custom_origin_tag_white_list: Option<String>,
560}
561
562#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
564#[serde(rename_all = "lowercase")]
565pub enum SnapshotUploaderType {
566 Gcp,
568 Local,
570}
571
572#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
574pub struct ZstandardCompressionParameters {
575 pub level: i32,
577
578 pub number_of_workers: u32,
580}
581
582impl Default for ZstandardCompressionParameters {
583 fn default() -> Self {
584 Self {
585 level: 9,
586 number_of_workers: 4,
587 }
588 }
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
595#[serde(rename_all = "kebab-case", tag = "type")]
596pub enum AncillaryFilesSignerConfig {
597 SecretKey {
599 secret_key: HexEncodedKey,
601 },
602 GcpKms {
604 resource_name: GcpCryptoKeyVersionResourceName,
606 #[serde(default = "default_gcp_kms_credentials_json_env_var")]
608 credentials_json_env_var: String,
609 },
610}
611
612fn default_gcp_kms_credentials_json_env_var() -> String {
613 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
614}
615
616impl FromStr for AncillaryFilesSignerConfig {
617 type Err = serde_json::Error;
618
619 fn from_str(s: &str) -> Result<Self, Self::Err> {
620 serde_json::from_str(s)
621 }
622}
623
624impl ServeCommandConfiguration {
625 pub fn new_sample(tmp_path: PathBuf) -> Self {
627 let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
628 .create_verifier()
629 .to_verification_key();
630 let ancillary_files_signer_secret_key =
631 ManifestSigner::create_deterministic_signer().secret_key();
632
633 Self {
634 environment: ExecutionEnvironment::Test,
635 cardano_cli_path: PathBuf::new(),
636 cardano_node_socket_path: PathBuf::new(),
637 dmq_node_socket_path: None,
638 cardano_node_version: "0.0.1".to_string(),
639 network_magic: Some(42),
640 network: "devnet".to_string(),
641 chain_observer_type: ChainObserverType::Fake,
642 protocol_parameters: ProtocolParameters {
643 k: 5,
644 m: 100,
645 phi_f: 0.95,
646 },
647 snapshot_uploader_type: SnapshotUploaderType::Local,
648 snapshot_bucket_name: None,
649 snapshot_use_cdn_domain: false,
650 server_ip: "0.0.0.0".to_string(),
651 server_port: 8000,
652 public_server_url: None,
653 run_interval: 5000,
654 db_directory: PathBuf::new(),
655 snapshot_directory: tmp_path,
662 data_stores_directory: PathBuf::from(":memory:"),
663 genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
664 reset_digests_cache: false,
665 disable_digests_cache: false,
666 store_retention_limit: None,
667 era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
668 era_reader_adapter_params: None,
669 ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
670 secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
671 },
672 signed_entity_types: None,
673 snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
674 zstandard_parameters: Some(ZstandardCompressionParameters::default()),
675 cexplorer_pools_url: None,
676 signer_importer_run_interval: 1,
677 allow_unparsable_block: false,
678 cardano_transactions_prover_cache_pool_size: 3,
679 cardano_transactions_database_connection_pool_size: 5,
680 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
681 security_parameter: BlockNumber(120),
682 step: BlockNumber(15),
683 },
684 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
685 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
686 enable_metrics_server: true,
687 metrics_server_ip: "0.0.0.0".to_string(),
688 metrics_server_port: 9090,
689 persist_usage_report_interval_in_seconds: 10,
690 leader_aggregator_endpoint: None,
691 custom_origin_tag_white_list: None,
692 }
693 }
694
695 pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
697 SanitizedUrlWithTrailingSlash::parse(&format!(
698 "http://{}:{}/{SERVER_BASE_PATH}/",
699 self.server_ip, self.server_port
700 ))
701 }
702}
703
704impl ConfigurationSource for ServeCommandConfiguration {
705 fn environment(&self) -> ExecutionEnvironment {
706 self.environment.clone()
707 }
708
709 fn cardano_cli_path(&self) -> PathBuf {
710 self.cardano_cli_path.clone()
711 }
712
713 fn cardano_node_socket_path(&self) -> PathBuf {
714 self.cardano_node_socket_path.clone()
715 }
716
717 fn dmq_node_socket_path(&self) -> Option<PathBuf> {
718 self.dmq_node_socket_path.clone()
719 }
720
721 fn cardano_node_version(&self) -> String {
722 self.cardano_node_version.clone()
723 }
724
725 fn network_magic(&self) -> Option<u64> {
726 self.network_magic
727 }
728
729 fn network(&self) -> String {
730 self.network.clone()
731 }
732
733 fn chain_observer_type(&self) -> ChainObserverType {
734 self.chain_observer_type.clone()
735 }
736
737 fn protocol_parameters(&self) -> ProtocolParameters {
738 self.protocol_parameters.clone()
739 }
740
741 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
742 self.snapshot_uploader_type
743 }
744
745 fn snapshot_bucket_name(&self) -> Option<String> {
746 self.snapshot_bucket_name.clone()
747 }
748
749 fn snapshot_use_cdn_domain(&self) -> bool {
750 self.snapshot_use_cdn_domain
751 }
752
753 fn server_ip(&self) -> String {
754 self.server_ip.clone()
755 }
756
757 fn server_port(&self) -> u16 {
758 self.server_port
759 }
760
761 fn public_server_url(&self) -> Option<String> {
762 self.public_server_url.clone()
763 }
764
765 fn run_interval(&self) -> u64 {
766 self.run_interval
767 }
768
769 fn db_directory(&self) -> PathBuf {
770 self.db_directory.clone()
771 }
772
773 fn snapshot_directory(&self) -> PathBuf {
774 self.snapshot_directory.clone()
775 }
776
777 fn data_stores_directory(&self) -> PathBuf {
778 self.data_stores_directory.clone()
779 }
780
781 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
782 self.genesis_verification_key.clone()
783 }
784
785 fn reset_digests_cache(&self) -> bool {
786 self.reset_digests_cache
787 }
788
789 fn disable_digests_cache(&self) -> bool {
790 self.disable_digests_cache
791 }
792
793 fn store_retention_limit(&self) -> Option<usize> {
794 self.store_retention_limit
795 }
796
797 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
798 self.era_reader_adapter_type.clone()
799 }
800
801 fn era_reader_adapter_params(&self) -> Option<String> {
802 self.era_reader_adapter_params.clone()
803 }
804
805 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
806 self.ancillary_files_signer_config.clone()
807 }
808
809 fn signed_entity_types(&self) -> Option<String> {
810 self.signed_entity_types.clone()
811 }
812
813 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
814 self.snapshot_compression_algorithm
815 }
816
817 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
818 self.zstandard_parameters
819 }
820
821 fn cexplorer_pools_url(&self) -> Option<String> {
822 self.cexplorer_pools_url.clone()
823 }
824
825 fn signer_importer_run_interval(&self) -> u64 {
826 self.signer_importer_run_interval
827 }
828
829 fn allow_unparsable_block(&self) -> bool {
830 self.allow_unparsable_block
831 }
832
833 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
834 self.cardano_transactions_prover_cache_pool_size
835 }
836
837 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
838 self.cardano_transactions_database_connection_pool_size
839 }
840
841 fn cardano_transactions_signing_config(&self) -> CardanoTransactionsSigningConfig {
842 self.cardano_transactions_signing_config.clone()
843 }
844
845 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
846 self.cardano_transactions_prover_max_hashes_allowed_by_request
847 }
848
849 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
850 self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
851 }
852
853 fn enable_metrics_server(&self) -> bool {
854 self.enable_metrics_server
855 }
856
857 fn metrics_server_ip(&self) -> String {
858 self.metrics_server_ip.clone()
859 }
860
861 fn metrics_server_port(&self) -> u16 {
862 self.metrics_server_port
863 }
864
865 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
866 self.persist_usage_report_interval_in_seconds
867 }
868
869 fn leader_aggregator_endpoint(&self) -> Option<String> {
870 self.leader_aggregator_endpoint.clone()
871 }
872
873 fn custom_origin_tag_white_list(&self) -> Option<String> {
874 self.custom_origin_tag_white_list.clone()
875 }
876
877 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
878 match &self.public_server_url {
879 Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
880 None => self.get_local_server_url(),
881 }
882 }
883}
884
885#[derive(Debug, Clone, DocumenterDefault)]
887pub struct DefaultConfiguration {
888 pub environment: ExecutionEnvironment,
890
891 pub server_ip: String,
893
894 pub server_port: String,
896
897 pub db_directory: String,
899
900 pub snapshot_directory: String,
902
903 pub snapshot_uploader_type: String,
905
906 pub era_reader_adapter_type: String,
908
909 pub chain_observer_type: String,
911
912 pub reset_digests_cache: String,
914
915 pub disable_digests_cache: String,
917
918 pub snapshot_compression_algorithm: String,
920
921 pub snapshot_use_cdn_domain: String,
923
924 pub signer_importer_run_interval: u64,
926
927 pub allow_unparsable_block: String,
931
932 pub cardano_transactions_prover_cache_pool_size: u32,
934
935 pub cardano_transactions_database_connection_pool_size: u32,
937
938 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
940
941 pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
943
944 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
946
947 pub enable_metrics_server: String,
949
950 pub metrics_server_ip: String,
952
953 pub metrics_server_port: u16,
955
956 pub persist_usage_report_interval_in_seconds: u64,
958}
959
960impl Default for DefaultConfiguration {
961 fn default() -> Self {
962 Self {
963 environment: ExecutionEnvironment::Production,
964 server_ip: "0.0.0.0".to_string(),
965 server_port: "8080".to_string(),
966 db_directory: "/db".to_string(),
967 snapshot_directory: ".".to_string(),
968 snapshot_uploader_type: "gcp".to_string(),
969 era_reader_adapter_type: "bootstrap".to_string(),
970 chain_observer_type: "pallas".to_string(),
971 reset_digests_cache: "false".to_string(),
972 disable_digests_cache: "false".to_string(),
973 snapshot_compression_algorithm: "zstandard".to_string(),
974 snapshot_use_cdn_domain: "false".to_string(),
975 signer_importer_run_interval: 720,
976 allow_unparsable_block: "false".to_string(),
977 cardano_transactions_prover_cache_pool_size: 10,
978 cardano_transactions_database_connection_pool_size: 10,
979 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
980 security_parameter: BlockNumber(3000),
981 step: BlockNumber(120),
982 },
983 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
984 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
985 enable_metrics_server: "false".to_string(),
986 metrics_server_ip: "0.0.0.0".to_string(),
987 metrics_server_port: 9090,
988 persist_usage_report_interval_in_seconds: 10,
989 }
990 }
991}
992
993impl DefaultConfiguration {
994 fn namespace() -> String {
995 "default configuration".to_string()
996 }
997}
998
999impl From<ExecutionEnvironment> for ValueKind {
1000 fn from(value: ExecutionEnvironment) -> Self {
1001 match value {
1002 ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
1003 ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
1004 }
1005 }
1006}
1007
1008impl Source for DefaultConfiguration {
1009 fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
1010 Box::new(self.clone())
1011 }
1012
1013 fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1014 let mut result = Map::new();
1015
1016 let namespace = DefaultConfiguration::namespace();
1017
1018 let myself = self.clone();
1019 register_config_value!(result, &namespace, myself.environment);
1020 register_config_value!(result, &namespace, myself.server_ip);
1021 register_config_value!(result, &namespace, myself.server_port);
1022 register_config_value!(result, &namespace, myself.db_directory);
1023 register_config_value!(result, &namespace, myself.snapshot_directory);
1024 register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1025 register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1026 register_config_value!(result, &namespace, myself.reset_digests_cache);
1027 register_config_value!(result, &namespace, myself.disable_digests_cache);
1028 register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1029 register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1030 register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1031 register_config_value!(result, &namespace, myself.allow_unparsable_block);
1032 register_config_value!(
1033 result,
1034 &namespace,
1035 myself.cardano_transactions_prover_cache_pool_size
1036 );
1037 register_config_value!(
1038 result,
1039 &namespace,
1040 myself.cardano_transactions_database_connection_pool_size
1041 );
1042 register_config_value!(
1043 result,
1044 &namespace,
1045 myself.cardano_transactions_prover_max_hashes_allowed_by_request
1046 );
1047 register_config_value!(
1048 result,
1049 &namespace,
1050 myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1051 );
1052 register_config_value!(result, &namespace, myself.enable_metrics_server);
1053 register_config_value!(result, &namespace, myself.metrics_server_ip);
1054 register_config_value!(result, &namespace, myself.metrics_server_port);
1055 register_config_value!(
1056 result,
1057 &namespace,
1058 myself.persist_usage_report_interval_in_seconds
1059 );
1060 register_config_value!(
1061 result,
1062 &namespace,
1063 myself.cardano_transactions_signing_config,
1064 |v: CardanoTransactionsSigningConfig| HashMap::from([
1065 (
1066 "security_parameter".to_string(),
1067 ValueKind::from(*v.security_parameter,),
1068 ),
1069 ("step".to_string(), ValueKind::from(*v.step),)
1070 ])
1071 );
1072 Ok(result)
1073 }
1074}
1075
1076#[cfg(test)]
1077mod test {
1078 use mithril_common::temp_dir;
1079
1080 use super::*;
1081
1082 #[test]
1083 fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1084 for limit in 4..=10u64 {
1085 let configuration = ServeCommandConfiguration {
1086 store_retention_limit: Some(limit as usize),
1087 ..ServeCommandConfiguration::new_sample(temp_dir!())
1088 };
1089 assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1090 }
1091 }
1092
1093 #[test]
1094 fn safe_epoch_retention_limit_wont_change_a_none_value() {
1095 let configuration = ServeCommandConfiguration {
1096 store_retention_limit: None,
1097 ..ServeCommandConfiguration::new_sample(temp_dir!())
1098 };
1099 assert_eq!(configuration.safe_epoch_retention_limit(), None);
1100 }
1101
1102 #[test]
1103 fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1104 for limit in 0..=3 {
1105 let configuration = ServeCommandConfiguration {
1106 store_retention_limit: Some(limit),
1107 ..ServeCommandConfiguration::new_sample(temp_dir!())
1108 };
1109 assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1110 }
1111 }
1112
1113 #[test]
1114 fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1115 #[derive(Debug, Deserialize)]
1116 struct TargetConfig {
1117 cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1118 }
1119
1120 let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1121 let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1122
1123 assert_eq!(
1124 target.cardano_transactions_signing_config,
1125 DefaultConfiguration::default().cardano_transactions_signing_config
1126 );
1127 }
1128
1129 #[test]
1130 fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1131 let config = ServeCommandConfiguration {
1132 signed_entity_types: None,
1133 ..ServeCommandConfiguration::new_sample(temp_dir!())
1134 };
1135
1136 assert_eq!(
1137 config.compute_allowed_signed_entity_types_discriminants().unwrap(),
1138 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1139 );
1140 }
1141
1142 #[test]
1143 fn allow_http_serve_directory() {
1144 let config = ServeCommandConfiguration {
1145 snapshot_uploader_type: SnapshotUploaderType::Local,
1146 ..ServeCommandConfiguration::new_sample(temp_dir!())
1147 };
1148
1149 assert!(config.allow_http_serve_directory());
1150
1151 let config = ServeCommandConfiguration {
1152 snapshot_uploader_type: SnapshotUploaderType::Gcp,
1153 ..ServeCommandConfiguration::new_sample(temp_dir!())
1154 };
1155
1156 assert!(!config.allow_http_serve_directory());
1157 }
1158
1159 #[test]
1160 fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1161 let config = ServeCommandConfiguration {
1162 server_ip: "1.2.3.4".to_string(),
1163 server_port: 5678,
1164 public_server_url: None,
1165 ..ServeCommandConfiguration::new_sample(temp_dir!())
1166 };
1167
1168 assert_eq!(
1169 config.get_server_url().unwrap().as_str(),
1170 &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1171 );
1172 }
1173
1174 #[test]
1175 fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1176 let config = ServeCommandConfiguration {
1177 server_ip: "1.2.3.4".to_string(),
1178 server_port: 5678,
1179 public_server_url: Some("https://example.com".to_string()),
1180 ..ServeCommandConfiguration::new_sample(temp_dir!())
1181 };
1182
1183 assert_eq!(
1184 config.get_server_url().unwrap().as_str(),
1185 "https://example.com/"
1186 );
1187 }
1188
1189 #[test]
1190 fn joining_to_local_server_url_keep_base_path() {
1191 let config = ServeCommandConfiguration {
1192 server_ip: "1.2.3.4".to_string(),
1193 server_port: 6789,
1194 public_server_url: None,
1195 ..ServeCommandConfiguration::new_sample(temp_dir!())
1196 };
1197
1198 let joined_url = config.get_local_server_url().unwrap().join("some/path").unwrap();
1199 assert!(
1200 joined_url.as_str().contains(SERVER_BASE_PATH),
1201 "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1202 );
1203 }
1204
1205 #[test]
1206 fn joining_to_public_server_url_without_trailing_slash() {
1207 let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1208 let config = ServeCommandConfiguration {
1209 public_server_url: Some(format!(
1210 "https://example.com/{subpath_without_trailing_slash}"
1211 )),
1212 ..ServeCommandConfiguration::new_sample(temp_dir!())
1213 };
1214
1215 let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1216 assert!(
1217 joined_url.as_str().contains(subpath_without_trailing_slash),
1218 "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1219 );
1220 }
1221
1222 #[test]
1223 fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1224 let config = ServeCommandConfiguration {
1225 leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1226 ..ServeCommandConfiguration::new_sample(temp_dir!())
1227 };
1228
1229 assert!(config.is_follower_aggregator());
1230 }
1231
1232 #[test]
1233 fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1234 let config = ServeCommandConfiguration {
1235 leader_aggregator_endpoint: None,
1236 ..ServeCommandConfiguration::new_sample(temp_dir!())
1237 };
1238
1239 assert!(!config.is_follower_aggregator());
1240 }
1241
1242 #[test]
1243 fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value()
1244 {
1245 let serialized_json = r#"{
1246 "type": "secret-key",
1247 "secret_key": "whatever"
1248 }"#;
1249
1250 let deserialized: AncillaryFilesSignerConfig =
1251 serde_json::from_str(serialized_json).unwrap();
1252 assert_eq!(
1253 deserialized,
1254 AncillaryFilesSignerConfig::SecretKey {
1255 secret_key: "whatever".to_string()
1256 }
1257 );
1258 }
1259
1260 #[test]
1261 fn deserializing_ancillary_signing_gcp_kms_configuration() {
1262 let serialized_json = r#"{
1263 "type": "gcp-kms",
1264 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1265 "credentials_json_env_var": "CUSTOM_ENV_VAR"
1266 }"#;
1267
1268 let deserialized: AncillaryFilesSignerConfig =
1269 serde_json::from_str(serialized_json).unwrap();
1270 assert_eq!(
1271 deserialized,
1272 AncillaryFilesSignerConfig::GcpKms {
1273 resource_name: GcpCryptoKeyVersionResourceName {
1274 project: "123456789".to_string(),
1275 location: "global".to_string(),
1276 key_ring: "my-keyring".to_string(),
1277 key_name: "my-key".to_string(),
1278 version: "1".to_string(),
1279 },
1280 credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1281 }
1282 );
1283 }
1284
1285 #[test]
1286 fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default()
1287 {
1288 let serialized_json = r#"{
1289 "type": "gcp-kms",
1290 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1291 }"#;
1292
1293 let deserialized: AncillaryFilesSignerConfig =
1294 serde_json::from_str(serialized_json).unwrap();
1295 if let AncillaryFilesSignerConfig::GcpKms {
1296 credentials_json_env_var,
1297 ..
1298 } = deserialized
1299 {
1300 assert_eq!(
1301 credentials_json_env_var,
1302 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1303 );
1304 } else {
1305 panic!("Expected GcpKms variant but got {deserialized:?}");
1306 }
1307 }
1308
1309 mod origin_tag {
1310 use super::*;
1311
1312 #[test]
1313 fn default_origin_tag_white_list_is_not_empty() {
1314 let config = ServeCommandConfiguration {
1315 custom_origin_tag_white_list: None,
1316 ..ServeCommandConfiguration::new_sample(temp_dir!())
1317 };
1318 assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1319 }
1320
1321 #[test]
1322 fn custom_origin_tag_are_added_to_default_white_list() {
1323 let config = ServeCommandConfiguration {
1324 custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1325 ..ServeCommandConfiguration::new_sample(temp_dir!())
1326 };
1327
1328 let default_white_list = ServeCommandConfiguration {
1329 custom_origin_tag_white_list: None,
1330 ..ServeCommandConfiguration::new_sample(temp_dir!())
1331 }
1332 .compute_origin_tag_white_list();
1333
1334 let mut expected_white_list = default_white_list.clone();
1335 assert!(expected_white_list.insert("TAG_A".to_string()));
1336 assert!(expected_white_list.insert("TAG_B".to_string()));
1337 assert!(expected_white_list.insert("TAG_C".to_string()));
1338
1339 assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1340 }
1341 }
1342}