1use std::collections::{BTreeSet, HashMap, HashSet};
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use anyhow::Context;
6use config::{ConfigError, Map, Source, Value, ValueKind};
7use serde::{Deserialize, Serialize};
8
9use mithril_cardano_node_chain::chain_observer::ChainObserverType;
10use mithril_cli_helper::{register_config_value, serde_deserialization};
11use mithril_common::crypto_helper::{ManifestSigner, ProtocolGenesisSigner};
12use mithril_common::entities::{
13 BlockNumber, CardanoTransactionsSigningConfig, CompressionAlgorithm,
14 HexEncodedGenesisVerificationKey, HexEncodedKey, ProtocolParameters, SignedEntityConfig,
15 SignedEntityTypeDiscriminants,
16};
17use mithril_common::{AggregateSignatureType, CardanoNetwork, StdResult};
18#[cfg(feature = "future_dmq")]
19use mithril_dmq::DmqNetwork;
20use mithril_doc::{Documenter, DocumenterDefault, StructDoc};
21use mithril_era::adapters::EraReaderAdapterType;
22
23use crate::entities::AggregatorEpochSettings;
24use crate::http_server::SERVER_BASE_PATH;
25use crate::services::ancillary_signer::GcpCryptoKeyVersionResourceName;
26use crate::tools::DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR;
27use crate::tools::url_sanitizer::SanitizedUrlWithTrailingSlash;
28
29#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
31pub enum ExecutionEnvironment {
32 Test,
34
35 Production,
38}
39
40impl FromStr for ExecutionEnvironment {
41 type Err = ConfigError;
42
43 fn from_str(s: &str) -> Result<Self, Self::Err> {
44 match s {
45 "production" => Ok(Self::Production),
46 "test" => Ok(Self::Test),
47 _ => Err(ConfigError::Message(format!(
48 "Unknown execution environment {s}"
49 ))),
50 }
51 }
52}
53
54pub trait ConfigurationSource {
59 fn environment(&self) -> ExecutionEnvironment;
61
62 fn cardano_cli_path(&self) -> PathBuf {
64 panic!("cardano_cli_path is not implemented.");
65 }
66
67 fn cardano_node_socket_path(&self) -> PathBuf {
69 panic!("cardano_node_socket_path is not implemented.");
70 }
71
72 fn dmq_node_socket_path(&self) -> Option<PathBuf> {
74 panic!("dmq_node_socket_path is not implemented.");
75 }
76
77 fn cardano_node_version(&self) -> String {
83 panic!("cardano_node_version is not implemented.");
84 }
85
86 fn network(&self) -> String {
88 panic!("network is not implemented.");
89 }
90
91 fn network_magic(&self) -> Option<u64> {
95 panic!("network_magic is not implemented.");
96 }
97
98 #[cfg(feature = "future_dmq")]
102 fn dmq_network_magic(&self) -> Option<u64> {
103 panic!("dmq_network_magic is not implemented.");
104 }
105
106 fn chain_observer_type(&self) -> ChainObserverType {
108 panic!("chain_observer_type is not implemented.");
109 }
110
111 fn protocol_parameters(&self) -> Option<ProtocolParameters> {
113 panic!("protocol_parameters is not implemented.");
114 }
115
116 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
118 panic!("snapshot_uploader_type is not implemented.");
119 }
120
121 fn snapshot_bucket_name(&self) -> Option<String> {
123 panic!("snapshot_bucket_name is not implemented.");
124 }
125
126 fn snapshot_use_cdn_domain(&self) -> bool {
128 panic!("snapshot_use_cdn_domain is not implemented.");
129 }
130
131 fn server_ip(&self) -> String {
133 panic!("server_ip is not implemented.");
134 }
135
136 fn server_port(&self) -> u16 {
138 panic!("server_port is not implemented.");
139 }
140
141 fn public_server_url(&self) -> Option<String> {
143 panic!("public_server_url is not implemented.");
144 }
145
146 fn run_interval(&self) -> u64 {
148 panic!("run_interval is not implemented.");
149 }
150
151 fn db_directory(&self) -> PathBuf {
153 panic!("db_directory is not implemented.");
154 }
155
156 fn snapshot_directory(&self) -> PathBuf {
158 panic!("snapshot_directory is not implemented.");
159 }
160
161 fn data_stores_directory(&self) -> PathBuf {
163 panic!("data_stores_directory is not implemented.");
164 }
165
166 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
168 panic!("genesis_verification_key is not implemented.");
169 }
170
171 fn reset_digests_cache(&self) -> bool {
173 panic!("reset_digests_cache is not implemented.");
174 }
175
176 fn disable_digests_cache(&self) -> bool {
178 panic!("disable_digests_cache is not implemented.");
179 }
180
181 fn store_retention_limit(&self) -> Option<usize> {
186 panic!("store_retention_limit is not implemented.");
187 }
188
189 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
191 panic!("era_reader_adapter_type is not implemented.");
192 }
193
194 fn era_reader_adapter_params(&self) -> Option<String> {
196 panic!("era_reader_adapter_params is not implemented.");
197 }
198
199 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
203 panic!("ancillary_files_signer_config is not implemented.");
204 }
205
206 fn signed_entity_types(&self) -> Option<String> {
212 panic!("signed_entity_types is not implemented.");
213 }
214
215 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
217 panic!("snapshot_compression_algorithm is not implemented.");
218 }
219
220 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
223 panic!("zstandard_parameters is not implemented.");
224 }
225
226 fn cexplorer_pools_url(&self) -> Option<String> {
228 panic!("cexplorer_pools_url is not implemented.");
229 }
230
231 fn signer_importer_run_interval(&self) -> u64 {
233 panic!("signer_importer_run_interval is not implemented.");
234 }
235
236 fn allow_unparsable_block(&self) -> bool {
240 panic!("allow_unparsable_block is not implemented.");
241 }
242
243 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
245 panic!("cardano_transactions_prover_cache_pool_size is not implemented.");
246 }
247
248 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
250 panic!("cardano_transactions_database_connection_pool_size is not implemented.");
251 }
252
253 fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
255 panic!("cardano_transactions_signing_config is not implemented.");
256 }
257
258 fn preload_security_parameter(&self) -> BlockNumber {
260 panic!("preload_security_parameter is not implemented.");
261 }
262
263 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
265 panic!("cardano_transactions_prover_max_hashes_allowed_by_request is not implemented.");
266 }
267
268 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
270 panic!(
271 "cardano_transactions_block_streamer_max_roll_forwards_per_poll is not implemented."
272 );
273 }
274
275 fn enable_metrics_server(&self) -> bool {
277 panic!("enable_metrics_server is not implemented.");
278 }
279
280 fn metrics_server_ip(&self) -> String {
282 panic!("metrics_server_ip is not implemented.");
283 }
284
285 fn metrics_server_port(&self) -> u16 {
287 panic!("metrics_server_port is not implemented.");
288 }
289
290 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
292 panic!("persist_usage_report_interval_in_seconds is not implemented.");
293 }
294
295 fn leader_aggregator_endpoint(&self) -> Option<String> {
301 panic!("leader_aggregator_endpoint is not implemented.");
302 }
303
304 fn custom_origin_tag_white_list(&self) -> Option<String> {
307 panic!("custom_origin_tag_white_list is not implemented.");
308 }
309
310 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
312 panic!("get_server_url is not implemented.");
313 }
314
315 fn get_network(&self) -> StdResult<CardanoNetwork> {
317 CardanoNetwork::from_code(self.network(), self.network_magic())
318 .with_context(|| "Invalid network configuration")
319 }
320
321 #[cfg(feature = "future_dmq")]
323 fn get_dmq_network(&self) -> StdResult<DmqNetwork> {
324 DmqNetwork::from_code(self.network(), self.dmq_network_magic())
325 .with_context(|| "Invalid DMQ network configuration")
326 }
327
328 fn get_sqlite_dir(&self) -> PathBuf {
330 let store_dir = &self.data_stores_directory();
331
332 if !store_dir.exists() {
333 std::fs::create_dir_all(store_dir).unwrap();
334 }
335
336 self.data_stores_directory()
337 }
338
339 fn get_snapshot_dir(&self) -> StdResult<PathBuf> {
341 if !&self.snapshot_directory().exists() {
342 std::fs::create_dir_all(self.snapshot_directory())?;
343 }
344
345 Ok(self.snapshot_directory())
346 }
347
348 fn safe_epoch_retention_limit(&self) -> Option<u64> {
350 self.store_retention_limit()
351 .map(|limit| if limit > 3 { limit as u64 } else { 3 })
352 }
353
354 fn compute_allowed_signed_entity_types_discriminants(
356 &self,
357 ) -> StdResult<BTreeSet<SignedEntityTypeDiscriminants>> {
358 let allowed_discriminants = self
359 .signed_entity_types()
360 .as_ref()
361 .map(SignedEntityTypeDiscriminants::parse_list)
362 .transpose()
363 .with_context(|| "Invalid 'signed_entity_types' configuration")?
364 .unwrap_or_default();
365 let allowed_discriminants =
366 SignedEntityConfig::append_allowed_signed_entity_types_discriminants(
367 allowed_discriminants,
368 );
369
370 Ok(allowed_discriminants)
371 }
372
373 fn allow_http_serve_directory(&self) -> bool {
375 match self.snapshot_uploader_type() {
376 SnapshotUploaderType::Local => true,
377 SnapshotUploaderType::Gcp => false,
378 }
379 }
380
381 fn get_leader_aggregator_epoch_settings_configuration(
383 &self,
384 ) -> StdResult<AggregatorEpochSettings> {
385 let allowed_discriminants = self.compute_allowed_signed_entity_types_discriminants()?;
386
387 let cardano_transactions_signing_config = if allowed_discriminants
388 .contains(&SignedEntityTypeDiscriminants::CardanoTransactions)
389 {
390 let cardano_transactions_signing_config =
391 self.cardano_transactions_signing_config().with_context(
392 || "Configuration `cardano_transactions_signing_config` is mandatory for a Leader Aggregator when `CardanoTransactions` is enabled in `signed_entity_types`"
393 )?;
394 Some(cardano_transactions_signing_config)
395 } else {
396 None
397 };
398
399 Ok(AggregatorEpochSettings {
400 protocol_parameters: self.protocol_parameters().with_context(
401 || "Configuration `protocol_parameters` is mandatory for a Leader Aggregator",
402 )?,
403 cardano_transactions_signing_config,
404 })
405 }
406
407 fn is_follower_aggregator(&self) -> bool {
409 self.leader_aggregator_endpoint().is_some()
410 }
411
412 fn compute_origin_tag_white_list(&self) -> HashSet<String> {
414 let mut white_list = HashSet::from([
415 "EXPLORER".to_string(),
416 "BENCHMARK".to_string(),
417 "CI".to_string(),
418 "NA".to_string(),
419 ]);
420 if let Some(custom_tags) = &self.custom_origin_tag_white_list() {
421 white_list.extend(custom_tags.split(',').map(|tag| tag.trim().to_string()));
422 }
423
424 white_list
425 }
426
427 fn aggregate_signature_type(&self) -> AggregateSignatureType {
429 panic!("get_aggregate_signature_type is not implemented.");
430 }
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize, Documenter)]
435pub struct ServeCommandConfiguration {
436 pub environment: ExecutionEnvironment,
438
439 #[example = "`cardano-cli`"]
441 pub cardano_cli_path: PathBuf,
442
443 #[example = "`/ipc/node.socket`"]
445 pub cardano_node_socket_path: PathBuf,
446
447 #[example = "`/ipc/dmq.socket`"]
449 pub dmq_node_socket_path: Option<PathBuf>,
450
451 pub cardano_node_version: String,
457
458 #[example = "`mainnet` or `preprod` or `devnet`"]
460 pub network: String,
461
462 #[example = "`1097911063` or `42`"]
466 pub network_magic: Option<u64>,
467
468 #[example = "`1097911063` or `42`"]
472 pub dmq_network_magic: Option<u64>,
473
474 pub chain_observer_type: ChainObserverType,
476
477 #[example = "`{ k: 5, m: 100, phi_f: 0.65 }`"]
479 pub protocol_parameters: Option<ProtocolParameters>,
480
481 #[example = "`gcp` or `local`"]
483 pub snapshot_uploader_type: SnapshotUploaderType,
484
485 pub snapshot_bucket_name: Option<String>,
487
488 pub snapshot_use_cdn_domain: bool,
490
491 pub server_ip: String,
493
494 pub server_port: u16,
496
497 pub public_server_url: Option<String>,
499
500 #[example = "`60000`"]
502 pub run_interval: u64,
503
504 pub db_directory: PathBuf,
506
507 pub snapshot_directory: PathBuf,
509
510 #[example = "`./mithril-aggregator/stores`"]
512 pub data_stores_directory: PathBuf,
513
514 pub genesis_verification_key: HexEncodedGenesisVerificationKey,
516
517 pub reset_digests_cache: bool,
519
520 pub disable_digests_cache: bool,
522
523 pub store_retention_limit: Option<usize>,
528
529 pub era_reader_adapter_type: EraReaderAdapterType,
531
532 pub era_reader_adapter_params: Option<String>,
534
535 #[example = "\
541 - secret-key:<br/>`{ \"type\": \"secret-key\", \"secret_key\": \"136372c3138312c3138382c3130352c3233312c3135\" }`<br/>\
542 - Gcp kms:<br/>`{ \"type\": \"gcp-kms\", \"resource_name\": \"projects/project_name/locations/_location_name/keyRings/key_ring_name/cryptoKeys/key_name/cryptoKeyVersions/key_version\" }`\
543 "]
544 #[serde(deserialize_with = "serde_deserialization::string_or_struct")]
545 pub ancillary_files_signer_config: AncillaryFilesSignerConfig,
546
547 #[example = "`CardanoImmutableFilesFull,CardanoStakeDistribution,CardanoDatabase,CardanoTransactions`"]
552 pub signed_entity_types: Option<String>,
553
554 #[example = "`gzip` or `zstandard`"]
556 pub snapshot_compression_algorithm: CompressionAlgorithm,
557
558 #[example = "`{ level: 9, number_of_workers: 4 }`"]
561 pub zstandard_parameters: Option<ZstandardCompressionParameters>,
562
563 pub cexplorer_pools_url: Option<String>,
565
566 pub signer_importer_run_interval: u64,
568
569 pub allow_unparsable_block: bool,
573
574 pub cardano_transactions_prover_cache_pool_size: usize,
576
577 pub cardano_transactions_database_connection_pool_size: usize,
579
580 #[example = "`{ security_parameter: 3000, step: 120 }`"]
582 pub cardano_transactions_signing_config: Option<CardanoTransactionsSigningConfig>,
583
584 #[example = "`2160`"]
587 pub preload_security_parameter: BlockNumber,
588
589 pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
591
592 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
594
595 pub enable_metrics_server: bool,
597
598 pub metrics_server_ip: String,
600
601 pub metrics_server_port: u16,
603
604 pub persist_usage_report_interval_in_seconds: u64,
606
607 pub leader_aggregator_endpoint: Option<String>,
613
614 pub custom_origin_tag_white_list: Option<String>,
617
618 pub aggregate_signature_type: AggregateSignatureType,
620}
621
622#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
624#[serde(rename_all = "lowercase")]
625pub enum SnapshotUploaderType {
626 Gcp,
628 Local,
630}
631
632#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
634pub struct ZstandardCompressionParameters {
635 pub level: i32,
637
638 pub number_of_workers: u32,
640}
641
642impl Default for ZstandardCompressionParameters {
643 fn default() -> Self {
644 Self {
645 level: 9,
646 number_of_workers: 4,
647 }
648 }
649}
650
651#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
655#[serde(rename_all = "kebab-case", tag = "type")]
656pub enum AncillaryFilesSignerConfig {
657 SecretKey {
659 secret_key: HexEncodedKey,
661 },
662 GcpKms {
664 resource_name: GcpCryptoKeyVersionResourceName,
666 #[serde(default = "default_gcp_kms_credentials_json_env_var")]
668 credentials_json_env_var: String,
669 },
670}
671
672fn default_gcp_kms_credentials_json_env_var() -> String {
673 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
674}
675
676impl FromStr for AncillaryFilesSignerConfig {
677 type Err = serde_json::Error;
678
679 fn from_str(s: &str) -> Result<Self, Self::Err> {
680 serde_json::from_str(s)
681 }
682}
683
684impl ServeCommandConfiguration {
685 pub fn new_sample(tmp_path: PathBuf) -> Self {
687 let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
688 .create_verifier()
689 .to_verification_key();
690 let ancillary_files_signer_secret_key =
691 ManifestSigner::create_deterministic_signer().secret_key();
692
693 Self {
694 environment: ExecutionEnvironment::Test,
695 cardano_cli_path: PathBuf::new(),
696 cardano_node_socket_path: PathBuf::new(),
697 dmq_node_socket_path: None,
698 cardano_node_version: "0.0.1".to_string(),
699 network: "devnet".to_string(),
700 network_magic: Some(42),
701 dmq_network_magic: Some(3141592),
702 chain_observer_type: ChainObserverType::Fake,
703 protocol_parameters: Some(ProtocolParameters {
704 k: 5,
705 m: 100,
706 phi_f: 0.95,
707 }),
708 snapshot_uploader_type: SnapshotUploaderType::Local,
709 snapshot_bucket_name: None,
710 snapshot_use_cdn_domain: false,
711 server_ip: "0.0.0.0".to_string(),
712 server_port: 8000,
713 public_server_url: None,
714 run_interval: 5000,
715 db_directory: PathBuf::new(),
716 snapshot_directory: tmp_path,
723 data_stores_directory: PathBuf::from(":memory:"),
724 genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
725 reset_digests_cache: false,
726 disable_digests_cache: false,
727 store_retention_limit: None,
728 era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
729 era_reader_adapter_params: None,
730 ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
731 secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
732 },
733 signed_entity_types: None,
734 snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
735 zstandard_parameters: Some(ZstandardCompressionParameters::default()),
736 cexplorer_pools_url: None,
737 signer_importer_run_interval: 1,
738 allow_unparsable_block: false,
739 cardano_transactions_prover_cache_pool_size: 3,
740 cardano_transactions_database_connection_pool_size: 5,
741 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
742 security_parameter: BlockNumber(120),
743 step: BlockNumber(15),
744 }),
745 preload_security_parameter: BlockNumber(30),
746 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
747 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
748 enable_metrics_server: true,
749 metrics_server_ip: "0.0.0.0".to_string(),
750 metrics_server_port: 9090,
751 persist_usage_report_interval_in_seconds: 10,
752 leader_aggregator_endpoint: None,
753 custom_origin_tag_white_list: None,
754 aggregate_signature_type: AggregateSignatureType::Concatenation,
755 }
756 }
757
758 pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
760 SanitizedUrlWithTrailingSlash::parse(&format!(
761 "http://{}:{}/{SERVER_BASE_PATH}/",
762 self.server_ip, self.server_port
763 ))
764 }
765}
766
767impl ConfigurationSource for ServeCommandConfiguration {
768 fn environment(&self) -> ExecutionEnvironment {
769 self.environment.clone()
770 }
771
772 fn cardano_cli_path(&self) -> PathBuf {
773 self.cardano_cli_path.clone()
774 }
775
776 fn cardano_node_socket_path(&self) -> PathBuf {
777 self.cardano_node_socket_path.clone()
778 }
779
780 fn dmq_node_socket_path(&self) -> Option<PathBuf> {
781 self.dmq_node_socket_path.clone()
782 }
783
784 fn cardano_node_version(&self) -> String {
785 self.cardano_node_version.clone()
786 }
787
788 fn network(&self) -> String {
789 self.network.clone()
790 }
791
792 fn network_magic(&self) -> Option<u64> {
793 self.network_magic
794 }
795
796 #[cfg(feature = "future_dmq")]
797 fn dmq_network_magic(&self) -> Option<u64> {
798 self.dmq_network_magic
799 }
800
801 fn chain_observer_type(&self) -> ChainObserverType {
802 self.chain_observer_type.clone()
803 }
804
805 fn protocol_parameters(&self) -> Option<ProtocolParameters> {
806 self.protocol_parameters.clone()
807 }
808
809 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
810 self.snapshot_uploader_type
811 }
812
813 fn snapshot_bucket_name(&self) -> Option<String> {
814 self.snapshot_bucket_name.clone()
815 }
816
817 fn snapshot_use_cdn_domain(&self) -> bool {
818 self.snapshot_use_cdn_domain
819 }
820
821 fn server_ip(&self) -> String {
822 self.server_ip.clone()
823 }
824
825 fn server_port(&self) -> u16 {
826 self.server_port
827 }
828
829 fn public_server_url(&self) -> Option<String> {
830 self.public_server_url.clone()
831 }
832
833 fn run_interval(&self) -> u64 {
834 self.run_interval
835 }
836
837 fn db_directory(&self) -> PathBuf {
838 self.db_directory.clone()
839 }
840
841 fn snapshot_directory(&self) -> PathBuf {
842 self.snapshot_directory.clone()
843 }
844
845 fn data_stores_directory(&self) -> PathBuf {
846 self.data_stores_directory.clone()
847 }
848
849 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
850 self.genesis_verification_key.clone()
851 }
852
853 fn reset_digests_cache(&self) -> bool {
854 self.reset_digests_cache
855 }
856
857 fn disable_digests_cache(&self) -> bool {
858 self.disable_digests_cache
859 }
860
861 fn store_retention_limit(&self) -> Option<usize> {
862 self.store_retention_limit
863 }
864
865 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
866 self.era_reader_adapter_type.clone()
867 }
868
869 fn era_reader_adapter_params(&self) -> Option<String> {
870 self.era_reader_adapter_params.clone()
871 }
872
873 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
874 self.ancillary_files_signer_config.clone()
875 }
876
877 fn signed_entity_types(&self) -> Option<String> {
878 self.signed_entity_types.clone()
879 }
880
881 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
882 self.snapshot_compression_algorithm
883 }
884
885 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
886 self.zstandard_parameters
887 }
888
889 fn cexplorer_pools_url(&self) -> Option<String> {
890 self.cexplorer_pools_url.clone()
891 }
892
893 fn signer_importer_run_interval(&self) -> u64 {
894 self.signer_importer_run_interval
895 }
896
897 fn allow_unparsable_block(&self) -> bool {
898 self.allow_unparsable_block
899 }
900
901 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
902 self.cardano_transactions_prover_cache_pool_size
903 }
904
905 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
906 self.cardano_transactions_database_connection_pool_size
907 }
908
909 fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
910 self.cardano_transactions_signing_config.clone()
911 }
912
913 fn preload_security_parameter(&self) -> BlockNumber {
914 self.preload_security_parameter
915 }
916
917 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
918 self.cardano_transactions_prover_max_hashes_allowed_by_request
919 }
920
921 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
922 self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
923 }
924
925 fn enable_metrics_server(&self) -> bool {
926 self.enable_metrics_server
927 }
928
929 fn metrics_server_ip(&self) -> String {
930 self.metrics_server_ip.clone()
931 }
932
933 fn metrics_server_port(&self) -> u16 {
934 self.metrics_server_port
935 }
936
937 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
938 self.persist_usage_report_interval_in_seconds
939 }
940
941 fn leader_aggregator_endpoint(&self) -> Option<String> {
942 self.leader_aggregator_endpoint.clone()
943 }
944
945 fn custom_origin_tag_white_list(&self) -> Option<String> {
946 self.custom_origin_tag_white_list.clone()
947 }
948
949 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
950 match &self.public_server_url {
951 Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
952 None => self.get_local_server_url(),
953 }
954 }
955
956 fn aggregate_signature_type(&self) -> AggregateSignatureType {
957 self.aggregate_signature_type
958 }
959}
960
961#[derive(Debug, Clone, DocumenterDefault)]
963pub struct DefaultConfiguration {
964 pub environment: ExecutionEnvironment,
966
967 pub server_ip: String,
969
970 pub server_port: String,
972
973 pub db_directory: String,
975
976 pub snapshot_directory: String,
978
979 pub snapshot_uploader_type: String,
981
982 pub era_reader_adapter_type: String,
984
985 pub chain_observer_type: String,
987
988 pub reset_digests_cache: String,
990
991 pub disable_digests_cache: String,
993
994 pub snapshot_compression_algorithm: String,
996
997 pub snapshot_use_cdn_domain: String,
999
1000 pub signer_importer_run_interval: u64,
1002
1003 pub allow_unparsable_block: String,
1007
1008 pub cardano_transactions_prover_cache_pool_size: u32,
1010
1011 pub cardano_transactions_database_connection_pool_size: u32,
1013
1014 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1016
1017 pub preload_security_parameter: u64,
1019
1020 pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
1022
1023 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
1025
1026 pub enable_metrics_server: String,
1028
1029 pub metrics_server_ip: String,
1031
1032 pub metrics_server_port: u16,
1034
1035 pub persist_usage_report_interval_in_seconds: u64,
1037
1038 pub aggregate_signature_type: String,
1040}
1041
1042impl Default for DefaultConfiguration {
1043 fn default() -> Self {
1044 Self {
1045 environment: ExecutionEnvironment::Production,
1046 server_ip: "0.0.0.0".to_string(),
1047 server_port: "8080".to_string(),
1048 db_directory: "/db".to_string(),
1049 snapshot_directory: ".".to_string(),
1050 snapshot_uploader_type: "gcp".to_string(),
1051 era_reader_adapter_type: "bootstrap".to_string(),
1052 chain_observer_type: "pallas".to_string(),
1053 reset_digests_cache: "false".to_string(),
1054 disable_digests_cache: "false".to_string(),
1055 snapshot_compression_algorithm: "zstandard".to_string(),
1056 snapshot_use_cdn_domain: "false".to_string(),
1057 signer_importer_run_interval: 720,
1058 allow_unparsable_block: "false".to_string(),
1059 cardano_transactions_prover_cache_pool_size: 10,
1060 cardano_transactions_database_connection_pool_size: 10,
1061 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
1062 security_parameter: BlockNumber(3000),
1063 step: BlockNumber(120),
1064 },
1065 preload_security_parameter: 2160,
1066 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
1067 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
1068 enable_metrics_server: "false".to_string(),
1069 metrics_server_ip: "0.0.0.0".to_string(),
1070 metrics_server_port: 9090,
1071 persist_usage_report_interval_in_seconds: 10,
1072 aggregate_signature_type: "Concatenation".to_string(),
1073 }
1074 }
1075}
1076
1077impl DefaultConfiguration {
1078 fn namespace() -> String {
1079 "default configuration".to_string()
1080 }
1081}
1082
1083impl From<ExecutionEnvironment> for ValueKind {
1084 fn from(value: ExecutionEnvironment) -> Self {
1085 match value {
1086 ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
1087 ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
1088 }
1089 }
1090}
1091
1092impl Source for DefaultConfiguration {
1093 fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
1094 Box::new(self.clone())
1095 }
1096
1097 fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1098 let mut result = Map::new();
1099
1100 let namespace = DefaultConfiguration::namespace();
1101
1102 let myself = self.clone();
1103 register_config_value!(result, &namespace, myself.environment);
1104 register_config_value!(result, &namespace, myself.server_ip);
1105 register_config_value!(result, &namespace, myself.server_port);
1106 register_config_value!(result, &namespace, myself.db_directory);
1107 register_config_value!(result, &namespace, myself.snapshot_directory);
1108 register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1109 register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1110 register_config_value!(result, &namespace, myself.reset_digests_cache);
1111 register_config_value!(result, &namespace, myself.disable_digests_cache);
1112 register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1113 register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1114 register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1115 register_config_value!(result, &namespace, myself.allow_unparsable_block);
1116 register_config_value!(
1117 result,
1118 &namespace,
1119 myself.cardano_transactions_prover_cache_pool_size
1120 );
1121 register_config_value!(
1122 result,
1123 &namespace,
1124 myself.cardano_transactions_database_connection_pool_size
1125 );
1126 register_config_value!(
1127 result,
1128 &namespace,
1129 myself.cardano_transactions_prover_max_hashes_allowed_by_request
1130 );
1131 register_config_value!(
1132 result,
1133 &namespace,
1134 myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1135 );
1136 register_config_value!(result, &namespace, myself.enable_metrics_server);
1137 register_config_value!(result, &namespace, myself.metrics_server_ip);
1138 register_config_value!(result, &namespace, myself.metrics_server_port);
1139 register_config_value!(
1140 result,
1141 &namespace,
1142 myself.persist_usage_report_interval_in_seconds
1143 );
1144 register_config_value!(result, &namespace, myself.preload_security_parameter);
1145 register_config_value!(
1146 result,
1147 &namespace,
1148 myself.cardano_transactions_signing_config,
1149 |v: CardanoTransactionsSigningConfig| HashMap::from([
1150 (
1151 "security_parameter".to_string(),
1152 ValueKind::from(*v.security_parameter),
1153 ),
1154 ("step".to_string(), ValueKind::from(*v.step),)
1155 ])
1156 );
1157 register_config_value!(result, &namespace, myself.aggregate_signature_type);
1158 Ok(result)
1159 }
1160}
1161
1162#[cfg(test)]
1163mod test {
1164 use mithril_common::temp_dir;
1165 use mithril_common::test::double::fake_data;
1166
1167 use super::*;
1168
1169 #[test]
1170 fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1171 for limit in 4..=10u64 {
1172 let configuration = ServeCommandConfiguration {
1173 store_retention_limit: Some(limit as usize),
1174 ..ServeCommandConfiguration::new_sample(temp_dir!())
1175 };
1176 assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1177 }
1178 }
1179
1180 #[test]
1181 fn safe_epoch_retention_limit_wont_change_a_none_value() {
1182 let configuration = ServeCommandConfiguration {
1183 store_retention_limit: None,
1184 ..ServeCommandConfiguration::new_sample(temp_dir!())
1185 };
1186 assert_eq!(configuration.safe_epoch_retention_limit(), None);
1187 }
1188
1189 #[test]
1190 fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1191 for limit in 0..=3 {
1192 let configuration = ServeCommandConfiguration {
1193 store_retention_limit: Some(limit),
1194 ..ServeCommandConfiguration::new_sample(temp_dir!())
1195 };
1196 assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1197 }
1198 }
1199
1200 #[test]
1201 fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1202 #[derive(Debug, Deserialize)]
1203 struct TargetConfig {
1204 cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1205 }
1206
1207 let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1208 let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1209
1210 assert_eq!(
1211 target.cardano_transactions_signing_config,
1212 DefaultConfiguration::default().cardano_transactions_signing_config
1213 );
1214 }
1215
1216 #[test]
1217 fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1218 let config = ServeCommandConfiguration {
1219 signed_entity_types: None,
1220 ..ServeCommandConfiguration::new_sample(temp_dir!())
1221 };
1222
1223 assert_eq!(
1224 config.compute_allowed_signed_entity_types_discriminants().unwrap(),
1225 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1226 );
1227 }
1228
1229 #[test]
1230 fn allow_http_serve_directory() {
1231 let config = ServeCommandConfiguration {
1232 snapshot_uploader_type: SnapshotUploaderType::Local,
1233 ..ServeCommandConfiguration::new_sample(temp_dir!())
1234 };
1235
1236 assert!(config.allow_http_serve_directory());
1237
1238 let config = ServeCommandConfiguration {
1239 snapshot_uploader_type: SnapshotUploaderType::Gcp,
1240 ..ServeCommandConfiguration::new_sample(temp_dir!())
1241 };
1242
1243 assert!(!config.allow_http_serve_directory());
1244 }
1245
1246 #[test]
1247 fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1248 let config = ServeCommandConfiguration {
1249 server_ip: "1.2.3.4".to_string(),
1250 server_port: 5678,
1251 public_server_url: None,
1252 ..ServeCommandConfiguration::new_sample(temp_dir!())
1253 };
1254
1255 assert_eq!(
1256 config.get_server_url().unwrap().as_str(),
1257 &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1258 );
1259 }
1260
1261 #[test]
1262 fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1263 let config = ServeCommandConfiguration {
1264 server_ip: "1.2.3.4".to_string(),
1265 server_port: 5678,
1266 public_server_url: Some("https://example.com".to_string()),
1267 ..ServeCommandConfiguration::new_sample(temp_dir!())
1268 };
1269
1270 assert_eq!(
1271 config.get_server_url().unwrap().as_str(),
1272 "https://example.com/"
1273 );
1274 }
1275
1276 #[test]
1277 fn joining_to_local_server_url_keep_base_path() {
1278 let config = ServeCommandConfiguration {
1279 server_ip: "1.2.3.4".to_string(),
1280 server_port: 6789,
1281 public_server_url: None,
1282 ..ServeCommandConfiguration::new_sample(temp_dir!())
1283 };
1284
1285 let joined_url = config.get_local_server_url().unwrap().join("some/path").unwrap();
1286 assert!(
1287 joined_url.as_str().contains(SERVER_BASE_PATH),
1288 "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1289 );
1290 }
1291
1292 #[test]
1293 fn joining_to_public_server_url_without_trailing_slash() {
1294 let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1295 let config = ServeCommandConfiguration {
1296 public_server_url: Some(format!(
1297 "https://example.com/{subpath_without_trailing_slash}"
1298 )),
1299 ..ServeCommandConfiguration::new_sample(temp_dir!())
1300 };
1301
1302 let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1303 assert!(
1304 joined_url.as_str().contains(subpath_without_trailing_slash),
1305 "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1306 );
1307 }
1308
1309 #[test]
1310 fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1311 let config = ServeCommandConfiguration {
1312 leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1313 ..ServeCommandConfiguration::new_sample(temp_dir!())
1314 };
1315
1316 assert!(config.is_follower_aggregator());
1317 }
1318
1319 #[test]
1320 fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1321 let config = ServeCommandConfiguration {
1322 leader_aggregator_endpoint: None,
1323 ..ServeCommandConfiguration::new_sample(temp_dir!())
1324 };
1325
1326 assert!(!config.is_follower_aggregator());
1327 }
1328
1329 mod get_leader_aggregator_epoch_settings_configuration {
1330 use super::*;
1331
1332 #[test]
1333 fn succeed_when_cardano_transactions_is_disabled_and_cardano_transactions_signing_config_is_not_set()
1334 {
1335 let epoch_settings = ServeCommandConfiguration {
1336 signed_entity_types: None,
1337 cardano_transactions_signing_config: None,
1338 protocol_parameters: Some(ProtocolParameters::new(1, 2, 3.1)),
1339 ..ServeCommandConfiguration::new_sample(temp_dir!())
1340 }
1341 .get_leader_aggregator_epoch_settings_configuration()
1342 .unwrap();
1343
1344 assert_eq!(
1345 AggregatorEpochSettings {
1346 protocol_parameters: ProtocolParameters::new(1, 2, 3.1),
1347 cardano_transactions_signing_config: None
1348 },
1349 epoch_settings
1350 );
1351 }
1352
1353 #[test]
1354 fn succeed_when_cardano_transactions_is_enabled_and_cardano_transactions_signing_config_is_set()
1355 {
1356 let epoch_settings = ServeCommandConfiguration {
1357 signed_entity_types: Some(
1358 SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1359 ),
1360 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1361 security_parameter: BlockNumber(10),
1362 step: BlockNumber(30),
1363 }),
1364 protocol_parameters: Some(ProtocolParameters::new(2, 3, 4.1)),
1365 ..ServeCommandConfiguration::new_sample(temp_dir!())
1366 }
1367 .get_leader_aggregator_epoch_settings_configuration()
1368 .unwrap();
1369
1370 assert_eq!(
1371 AggregatorEpochSettings {
1372 protocol_parameters: ProtocolParameters::new(2, 3, 4.1),
1373 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1374 security_parameter: BlockNumber(10),
1375 step: BlockNumber(30),
1376 },)
1377 },
1378 epoch_settings
1379 );
1380 }
1381
1382 #[test]
1383 fn fails_when_cardano_transactions_is_enabled_without_associated_config() {
1384 let error = ServeCommandConfiguration {
1385 cardano_transactions_signing_config: None,
1386 signed_entity_types: Some(
1387 SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1388 ),
1389 protocol_parameters: Some(fake_data::protocol_parameters()),
1390 ..ServeCommandConfiguration::new_sample(temp_dir!())
1391 }
1392 .get_leader_aggregator_epoch_settings_configuration()
1393 .unwrap_err();
1394
1395 assert!(
1396 error
1397 .to_string()
1398 .contains("Configuration `cardano_transactions_signing_config` is mandatory")
1399 );
1400 }
1401 }
1402
1403 #[test]
1404 fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value()
1405 {
1406 let serialized_json = r#"{
1407 "type": "secret-key",
1408 "secret_key": "whatever"
1409 }"#;
1410
1411 let deserialized: AncillaryFilesSignerConfig =
1412 serde_json::from_str(serialized_json).unwrap();
1413 assert_eq!(
1414 deserialized,
1415 AncillaryFilesSignerConfig::SecretKey {
1416 secret_key: "whatever".to_string()
1417 }
1418 );
1419 }
1420
1421 #[test]
1422 fn deserializing_ancillary_signing_gcp_kms_configuration() {
1423 let serialized_json = r#"{
1424 "type": "gcp-kms",
1425 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1426 "credentials_json_env_var": "CUSTOM_ENV_VAR"
1427 }"#;
1428
1429 let deserialized: AncillaryFilesSignerConfig =
1430 serde_json::from_str(serialized_json).unwrap();
1431 assert_eq!(
1432 deserialized,
1433 AncillaryFilesSignerConfig::GcpKms {
1434 resource_name: GcpCryptoKeyVersionResourceName {
1435 project: "123456789".to_string(),
1436 location: "global".to_string(),
1437 key_ring: "my-keyring".to_string(),
1438 key_name: "my-key".to_string(),
1439 version: "1".to_string(),
1440 },
1441 credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1442 }
1443 );
1444 }
1445
1446 #[test]
1447 fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default()
1448 {
1449 let serialized_json = r#"{
1450 "type": "gcp-kms",
1451 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1452 }"#;
1453
1454 let deserialized: AncillaryFilesSignerConfig =
1455 serde_json::from_str(serialized_json).unwrap();
1456 if let AncillaryFilesSignerConfig::GcpKms {
1457 credentials_json_env_var,
1458 ..
1459 } = deserialized
1460 {
1461 assert_eq!(
1462 credentials_json_env_var,
1463 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1464 );
1465 } else {
1466 panic!("Expected GcpKms variant but got {deserialized:?}");
1467 }
1468 }
1469
1470 mod origin_tag {
1471 use super::*;
1472
1473 #[test]
1474 fn default_origin_tag_white_list_is_not_empty() {
1475 let config = ServeCommandConfiguration {
1476 custom_origin_tag_white_list: None,
1477 ..ServeCommandConfiguration::new_sample(temp_dir!())
1478 };
1479 assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1480 }
1481
1482 #[test]
1483 fn custom_origin_tag_are_added_to_default_white_list() {
1484 let config = ServeCommandConfiguration {
1485 custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1486 ..ServeCommandConfiguration::new_sample(temp_dir!())
1487 };
1488
1489 let default_white_list = ServeCommandConfiguration {
1490 custom_origin_tag_white_list: None,
1491 ..ServeCommandConfiguration::new_sample(temp_dir!())
1492 }
1493 .compute_origin_tag_white_list();
1494
1495 let mut expected_white_list = default_white_list.clone();
1496 assert!(expected_white_list.insert("TAG_A".to_string()));
1497 assert!(expected_white_list.insert("TAG_B".to_string()));
1498 assert!(expected_white_list.insert("TAG_C".to_string()));
1499
1500 assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1501 }
1502 }
1503}