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