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) -> 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) -> CardanoTransactionsSigningConfig {
255 panic!("cardano_transactions_signing_config is not implemented.");
256 }
257
258 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
260 panic!("cardano_transactions_prover_max_hashes_allowed_by_request is not implemented.");
261 }
262
263 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
265 panic!(
266 "cardano_transactions_block_streamer_max_roll_forwards_per_poll is not implemented."
267 );
268 }
269
270 fn enable_metrics_server(&self) -> bool {
272 panic!("enable_metrics_server is not implemented.");
273 }
274
275 fn metrics_server_ip(&self) -> String {
277 panic!("metrics_server_ip is not implemented.");
278 }
279
280 fn metrics_server_port(&self) -> u16 {
282 panic!("metrics_server_port is not implemented.");
283 }
284
285 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
287 panic!("persist_usage_report_interval_in_seconds is not implemented.");
288 }
289
290 fn leader_aggregator_endpoint(&self) -> Option<String> {
296 panic!("leader_aggregator_endpoint is not implemented.");
297 }
298
299 fn custom_origin_tag_white_list(&self) -> Option<String> {
302 panic!("custom_origin_tag_white_list is not implemented.");
303 }
304
305 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
307 panic!("get_server_url is not implemented.");
308 }
309
310 fn get_network(&self) -> StdResult<CardanoNetwork> {
312 CardanoNetwork::from_code(self.network(), self.network_magic())
313 .with_context(|| "Invalid network configuration")
314 }
315
316 #[cfg(feature = "future_dmq")]
318 fn get_dmq_network(&self) -> StdResult<DmqNetwork> {
319 DmqNetwork::from_code(self.network(), self.dmq_network_magic())
320 .with_context(|| "Invalid DMQ network configuration")
321 }
322
323 fn get_sqlite_dir(&self) -> PathBuf {
325 let store_dir = &self.data_stores_directory();
326
327 if !store_dir.exists() {
328 std::fs::create_dir_all(store_dir).unwrap();
329 }
330
331 self.data_stores_directory()
332 }
333
334 fn get_snapshot_dir(&self) -> StdResult<PathBuf> {
336 if !&self.snapshot_directory().exists() {
337 std::fs::create_dir_all(self.snapshot_directory())?;
338 }
339
340 Ok(self.snapshot_directory())
341 }
342
343 fn safe_epoch_retention_limit(&self) -> Option<u64> {
345 self.store_retention_limit()
346 .map(|limit| if limit > 3 { limit as u64 } else { 3 })
347 }
348
349 fn compute_allowed_signed_entity_types_discriminants(
351 &self,
352 ) -> StdResult<BTreeSet<SignedEntityTypeDiscriminants>> {
353 let allowed_discriminants = self
354 .signed_entity_types()
355 .as_ref()
356 .map(SignedEntityTypeDiscriminants::parse_list)
357 .transpose()
358 .with_context(|| "Invalid 'signed_entity_types' configuration")?
359 .unwrap_or_default();
360 let allowed_discriminants =
361 SignedEntityConfig::append_allowed_signed_entity_types_discriminants(
362 allowed_discriminants,
363 );
364
365 Ok(allowed_discriminants)
366 }
367
368 fn allow_http_serve_directory(&self) -> bool {
370 match self.snapshot_uploader_type() {
371 SnapshotUploaderType::Local => true,
372 SnapshotUploaderType::Gcp => false,
373 }
374 }
375
376 fn get_epoch_settings_configuration(&self) -> AggregatorEpochSettings {
378 AggregatorEpochSettings {
379 protocol_parameters: self.protocol_parameters(),
380 cardano_transactions_signing_config: self.cardano_transactions_signing_config(),
381 }
382 }
383
384 fn is_follower_aggregator(&self) -> bool {
386 self.leader_aggregator_endpoint().is_some()
387 }
388
389 fn compute_origin_tag_white_list(&self) -> HashSet<String> {
391 let mut white_list = HashSet::from([
392 "EXPLORER".to_string(),
393 "BENCHMARK".to_string(),
394 "CI".to_string(),
395 "NA".to_string(),
396 ]);
397 if let Some(custom_tags) = &self.custom_origin_tag_white_list() {
398 white_list.extend(custom_tags.split(',').map(|tag| tag.trim().to_string()));
399 }
400
401 white_list
402 }
403
404 fn aggregate_signature_type(&self) -> AggregateSignatureType {
406 panic!("get_aggregate_signature_type is not implemented.");
407 }
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize, Documenter)]
412pub struct ServeCommandConfiguration {
413 pub environment: ExecutionEnvironment,
415
416 #[example = "`cardano-cli`"]
418 pub cardano_cli_path: PathBuf,
419
420 #[example = "`/ipc/node.socket`"]
422 pub cardano_node_socket_path: PathBuf,
423
424 #[example = "`/ipc/dmq.socket`"]
426 pub dmq_node_socket_path: Option<PathBuf>,
427
428 pub cardano_node_version: String,
434
435 #[example = "`mainnet` or `preprod` or `devnet`"]
437 pub network: String,
438
439 #[example = "`1097911063` or `42`"]
443 pub network_magic: Option<u64>,
444
445 #[example = "`1097911063` or `42`"]
449 pub dmq_network_magic: Option<u64>,
450
451 pub chain_observer_type: ChainObserverType,
453
454 #[example = "`{ k: 5, m: 100, phi_f: 0.65 }`"]
456 pub protocol_parameters: ProtocolParameters,
457
458 #[example = "`gcp` or `local`"]
460 pub snapshot_uploader_type: SnapshotUploaderType,
461
462 pub snapshot_bucket_name: Option<String>,
464
465 pub snapshot_use_cdn_domain: bool,
467
468 pub server_ip: String,
470
471 pub server_port: u16,
473
474 pub public_server_url: Option<String>,
476
477 #[example = "`60000`"]
479 pub run_interval: u64,
480
481 pub db_directory: PathBuf,
483
484 pub snapshot_directory: PathBuf,
486
487 #[example = "`./mithril-aggregator/stores`"]
489 pub data_stores_directory: PathBuf,
490
491 pub genesis_verification_key: HexEncodedGenesisVerificationKey,
493
494 pub reset_digests_cache: bool,
496
497 pub disable_digests_cache: bool,
499
500 pub store_retention_limit: Option<usize>,
505
506 pub era_reader_adapter_type: EraReaderAdapterType,
508
509 pub era_reader_adapter_params: Option<String>,
511
512 #[example = "\
518 - secret-key:<br/>`{ \"type\": \"secret-key\", \"secret_key\": \"136372c3138312c3138382c3130352c3233312c3135\" }`<br/>\
519 - Gcp kms:<br/>`{ \"type\": \"gcp-kms\", \"resource_name\": \"projects/project_name/locations/_location_name/keyRings/key_ring_name/cryptoKeys/key_name/cryptoKeyVersions/key_version\" }`\
520 "]
521 #[serde(deserialize_with = "serde_deserialization::string_or_struct")]
522 pub ancillary_files_signer_config: AncillaryFilesSignerConfig,
523
524 #[example = "`CardanoImmutableFilesFull,CardanoStakeDistribution,CardanoDatabase,CardanoTransactions`"]
529 pub signed_entity_types: Option<String>,
530
531 #[example = "`gzip` or `zstandard`"]
533 pub snapshot_compression_algorithm: CompressionAlgorithm,
534
535 #[example = "`{ level: 9, number_of_workers: 4 }`"]
538 pub zstandard_parameters: Option<ZstandardCompressionParameters>,
539
540 pub cexplorer_pools_url: Option<String>,
542
543 pub signer_importer_run_interval: u64,
545
546 pub allow_unparsable_block: bool,
550
551 pub cardano_transactions_prover_cache_pool_size: usize,
553
554 pub cardano_transactions_database_connection_pool_size: usize,
556
557 #[example = "`{ security_parameter: 3000, step: 120 }`"]
559 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
560
561 pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
563
564 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
566
567 pub enable_metrics_server: bool,
569
570 pub metrics_server_ip: String,
572
573 pub metrics_server_port: u16,
575
576 pub persist_usage_report_interval_in_seconds: u64,
578
579 pub leader_aggregator_endpoint: Option<String>,
585
586 pub custom_origin_tag_white_list: Option<String>,
589
590 pub aggregate_signature_type: AggregateSignatureType,
592}
593
594#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
596#[serde(rename_all = "lowercase")]
597pub enum SnapshotUploaderType {
598 Gcp,
600 Local,
602}
603
604#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
606pub struct ZstandardCompressionParameters {
607 pub level: i32,
609
610 pub number_of_workers: u32,
612}
613
614impl Default for ZstandardCompressionParameters {
615 fn default() -> Self {
616 Self {
617 level: 9,
618 number_of_workers: 4,
619 }
620 }
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
627#[serde(rename_all = "kebab-case", tag = "type")]
628pub enum AncillaryFilesSignerConfig {
629 SecretKey {
631 secret_key: HexEncodedKey,
633 },
634 GcpKms {
636 resource_name: GcpCryptoKeyVersionResourceName,
638 #[serde(default = "default_gcp_kms_credentials_json_env_var")]
640 credentials_json_env_var: String,
641 },
642}
643
644fn default_gcp_kms_credentials_json_env_var() -> String {
645 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
646}
647
648impl FromStr for AncillaryFilesSignerConfig {
649 type Err = serde_json::Error;
650
651 fn from_str(s: &str) -> Result<Self, Self::Err> {
652 serde_json::from_str(s)
653 }
654}
655
656impl ServeCommandConfiguration {
657 pub fn new_sample(tmp_path: PathBuf) -> Self {
659 let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
660 .create_verifier()
661 .to_verification_key();
662 let ancillary_files_signer_secret_key =
663 ManifestSigner::create_deterministic_signer().secret_key();
664
665 Self {
666 environment: ExecutionEnvironment::Test,
667 cardano_cli_path: PathBuf::new(),
668 cardano_node_socket_path: PathBuf::new(),
669 dmq_node_socket_path: None,
670 cardano_node_version: "0.0.1".to_string(),
671 network: "devnet".to_string(),
672 network_magic: Some(42),
673 dmq_network_magic: Some(3141592),
674 chain_observer_type: ChainObserverType::Fake,
675 protocol_parameters: ProtocolParameters {
676 k: 5,
677 m: 100,
678 phi_f: 0.95,
679 },
680 snapshot_uploader_type: SnapshotUploaderType::Local,
681 snapshot_bucket_name: None,
682 snapshot_use_cdn_domain: false,
683 server_ip: "0.0.0.0".to_string(),
684 server_port: 8000,
685 public_server_url: None,
686 run_interval: 5000,
687 db_directory: PathBuf::new(),
688 snapshot_directory: tmp_path,
695 data_stores_directory: PathBuf::from(":memory:"),
696 genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
697 reset_digests_cache: false,
698 disable_digests_cache: false,
699 store_retention_limit: None,
700 era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
701 era_reader_adapter_params: None,
702 ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
703 secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
704 },
705 signed_entity_types: None,
706 snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
707 zstandard_parameters: Some(ZstandardCompressionParameters::default()),
708 cexplorer_pools_url: None,
709 signer_importer_run_interval: 1,
710 allow_unparsable_block: false,
711 cardano_transactions_prover_cache_pool_size: 3,
712 cardano_transactions_database_connection_pool_size: 5,
713 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
714 security_parameter: BlockNumber(120),
715 step: BlockNumber(15),
716 },
717 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
718 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
719 enable_metrics_server: true,
720 metrics_server_ip: "0.0.0.0".to_string(),
721 metrics_server_port: 9090,
722 persist_usage_report_interval_in_seconds: 10,
723 leader_aggregator_endpoint: None,
724 custom_origin_tag_white_list: None,
725 aggregate_signature_type: AggregateSignatureType::Concatenation,
726 }
727 }
728
729 pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
731 SanitizedUrlWithTrailingSlash::parse(&format!(
732 "http://{}:{}/{SERVER_BASE_PATH}/",
733 self.server_ip, self.server_port
734 ))
735 }
736}
737
738impl ConfigurationSource for ServeCommandConfiguration {
739 fn environment(&self) -> ExecutionEnvironment {
740 self.environment.clone()
741 }
742
743 fn cardano_cli_path(&self) -> PathBuf {
744 self.cardano_cli_path.clone()
745 }
746
747 fn cardano_node_socket_path(&self) -> PathBuf {
748 self.cardano_node_socket_path.clone()
749 }
750
751 fn dmq_node_socket_path(&self) -> Option<PathBuf> {
752 self.dmq_node_socket_path.clone()
753 }
754
755 fn cardano_node_version(&self) -> String {
756 self.cardano_node_version.clone()
757 }
758
759 fn network(&self) -> String {
760 self.network.clone()
761 }
762
763 fn network_magic(&self) -> Option<u64> {
764 self.network_magic
765 }
766
767 #[cfg(feature = "future_dmq")]
768 fn dmq_network_magic(&self) -> Option<u64> {
769 self.dmq_network_magic
770 }
771
772 fn chain_observer_type(&self) -> ChainObserverType {
773 self.chain_observer_type.clone()
774 }
775
776 fn protocol_parameters(&self) -> ProtocolParameters {
777 self.protocol_parameters.clone()
778 }
779
780 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
781 self.snapshot_uploader_type
782 }
783
784 fn snapshot_bucket_name(&self) -> Option<String> {
785 self.snapshot_bucket_name.clone()
786 }
787
788 fn snapshot_use_cdn_domain(&self) -> bool {
789 self.snapshot_use_cdn_domain
790 }
791
792 fn server_ip(&self) -> String {
793 self.server_ip.clone()
794 }
795
796 fn server_port(&self) -> u16 {
797 self.server_port
798 }
799
800 fn public_server_url(&self) -> Option<String> {
801 self.public_server_url.clone()
802 }
803
804 fn run_interval(&self) -> u64 {
805 self.run_interval
806 }
807
808 fn db_directory(&self) -> PathBuf {
809 self.db_directory.clone()
810 }
811
812 fn snapshot_directory(&self) -> PathBuf {
813 self.snapshot_directory.clone()
814 }
815
816 fn data_stores_directory(&self) -> PathBuf {
817 self.data_stores_directory.clone()
818 }
819
820 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
821 self.genesis_verification_key.clone()
822 }
823
824 fn reset_digests_cache(&self) -> bool {
825 self.reset_digests_cache
826 }
827
828 fn disable_digests_cache(&self) -> bool {
829 self.disable_digests_cache
830 }
831
832 fn store_retention_limit(&self) -> Option<usize> {
833 self.store_retention_limit
834 }
835
836 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
837 self.era_reader_adapter_type.clone()
838 }
839
840 fn era_reader_adapter_params(&self) -> Option<String> {
841 self.era_reader_adapter_params.clone()
842 }
843
844 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
845 self.ancillary_files_signer_config.clone()
846 }
847
848 fn signed_entity_types(&self) -> Option<String> {
849 self.signed_entity_types.clone()
850 }
851
852 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
853 self.snapshot_compression_algorithm
854 }
855
856 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
857 self.zstandard_parameters
858 }
859
860 fn cexplorer_pools_url(&self) -> Option<String> {
861 self.cexplorer_pools_url.clone()
862 }
863
864 fn signer_importer_run_interval(&self) -> u64 {
865 self.signer_importer_run_interval
866 }
867
868 fn allow_unparsable_block(&self) -> bool {
869 self.allow_unparsable_block
870 }
871
872 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
873 self.cardano_transactions_prover_cache_pool_size
874 }
875
876 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
877 self.cardano_transactions_database_connection_pool_size
878 }
879
880 fn cardano_transactions_signing_config(&self) -> CardanoTransactionsSigningConfig {
881 self.cardano_transactions_signing_config.clone()
882 }
883
884 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
885 self.cardano_transactions_prover_max_hashes_allowed_by_request
886 }
887
888 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
889 self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
890 }
891
892 fn enable_metrics_server(&self) -> bool {
893 self.enable_metrics_server
894 }
895
896 fn metrics_server_ip(&self) -> String {
897 self.metrics_server_ip.clone()
898 }
899
900 fn metrics_server_port(&self) -> u16 {
901 self.metrics_server_port
902 }
903
904 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
905 self.persist_usage_report_interval_in_seconds
906 }
907
908 fn leader_aggregator_endpoint(&self) -> Option<String> {
909 self.leader_aggregator_endpoint.clone()
910 }
911
912 fn custom_origin_tag_white_list(&self) -> Option<String> {
913 self.custom_origin_tag_white_list.clone()
914 }
915
916 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
917 match &self.public_server_url {
918 Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
919 None => self.get_local_server_url(),
920 }
921 }
922
923 fn aggregate_signature_type(&self) -> AggregateSignatureType {
924 self.aggregate_signature_type
925 }
926}
927
928#[derive(Debug, Clone, DocumenterDefault)]
930pub struct DefaultConfiguration {
931 pub environment: ExecutionEnvironment,
933
934 pub server_ip: String,
936
937 pub server_port: String,
939
940 pub db_directory: String,
942
943 pub snapshot_directory: String,
945
946 pub snapshot_uploader_type: String,
948
949 pub era_reader_adapter_type: String,
951
952 pub chain_observer_type: String,
954
955 pub reset_digests_cache: String,
957
958 pub disable_digests_cache: String,
960
961 pub snapshot_compression_algorithm: String,
963
964 pub snapshot_use_cdn_domain: String,
966
967 pub signer_importer_run_interval: u64,
969
970 pub allow_unparsable_block: String,
974
975 pub cardano_transactions_prover_cache_pool_size: u32,
977
978 pub cardano_transactions_database_connection_pool_size: u32,
980
981 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
983
984 pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
986
987 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
989
990 pub enable_metrics_server: String,
992
993 pub metrics_server_ip: String,
995
996 pub metrics_server_port: u16,
998
999 pub persist_usage_report_interval_in_seconds: u64,
1001
1002 pub aggregate_signature_type: String,
1004}
1005
1006impl Default for DefaultConfiguration {
1007 fn default() -> Self {
1008 Self {
1009 environment: ExecutionEnvironment::Production,
1010 server_ip: "0.0.0.0".to_string(),
1011 server_port: "8080".to_string(),
1012 db_directory: "/db".to_string(),
1013 snapshot_directory: ".".to_string(),
1014 snapshot_uploader_type: "gcp".to_string(),
1015 era_reader_adapter_type: "bootstrap".to_string(),
1016 chain_observer_type: "pallas".to_string(),
1017 reset_digests_cache: "false".to_string(),
1018 disable_digests_cache: "false".to_string(),
1019 snapshot_compression_algorithm: "zstandard".to_string(),
1020 snapshot_use_cdn_domain: "false".to_string(),
1021 signer_importer_run_interval: 720,
1022 allow_unparsable_block: "false".to_string(),
1023 cardano_transactions_prover_cache_pool_size: 10,
1024 cardano_transactions_database_connection_pool_size: 10,
1025 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
1026 security_parameter: BlockNumber(3000),
1027 step: BlockNumber(120),
1028 },
1029 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
1030 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
1031 enable_metrics_server: "false".to_string(),
1032 metrics_server_ip: "0.0.0.0".to_string(),
1033 metrics_server_port: 9090,
1034 persist_usage_report_interval_in_seconds: 10,
1035 aggregate_signature_type: "Concatenation".to_string(),
1036 }
1037 }
1038}
1039
1040impl DefaultConfiguration {
1041 fn namespace() -> String {
1042 "default configuration".to_string()
1043 }
1044}
1045
1046impl From<ExecutionEnvironment> for ValueKind {
1047 fn from(value: ExecutionEnvironment) -> Self {
1048 match value {
1049 ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
1050 ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
1051 }
1052 }
1053}
1054
1055impl Source for DefaultConfiguration {
1056 fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
1057 Box::new(self.clone())
1058 }
1059
1060 fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1061 let mut result = Map::new();
1062
1063 let namespace = DefaultConfiguration::namespace();
1064
1065 let myself = self.clone();
1066 register_config_value!(result, &namespace, myself.environment);
1067 register_config_value!(result, &namespace, myself.server_ip);
1068 register_config_value!(result, &namespace, myself.server_port);
1069 register_config_value!(result, &namespace, myself.db_directory);
1070 register_config_value!(result, &namespace, myself.snapshot_directory);
1071 register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1072 register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1073 register_config_value!(result, &namespace, myself.reset_digests_cache);
1074 register_config_value!(result, &namespace, myself.disable_digests_cache);
1075 register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1076 register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1077 register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1078 register_config_value!(result, &namespace, myself.allow_unparsable_block);
1079 register_config_value!(
1080 result,
1081 &namespace,
1082 myself.cardano_transactions_prover_cache_pool_size
1083 );
1084 register_config_value!(
1085 result,
1086 &namespace,
1087 myself.cardano_transactions_database_connection_pool_size
1088 );
1089 register_config_value!(
1090 result,
1091 &namespace,
1092 myself.cardano_transactions_prover_max_hashes_allowed_by_request
1093 );
1094 register_config_value!(
1095 result,
1096 &namespace,
1097 myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1098 );
1099 register_config_value!(result, &namespace, myself.enable_metrics_server);
1100 register_config_value!(result, &namespace, myself.metrics_server_ip);
1101 register_config_value!(result, &namespace, myself.metrics_server_port);
1102 register_config_value!(
1103 result,
1104 &namespace,
1105 myself.persist_usage_report_interval_in_seconds
1106 );
1107 register_config_value!(
1108 result,
1109 &namespace,
1110 myself.cardano_transactions_signing_config,
1111 |v: CardanoTransactionsSigningConfig| HashMap::from([
1112 (
1113 "security_parameter".to_string(),
1114 ValueKind::from(*v.security_parameter,),
1115 ),
1116 ("step".to_string(), ValueKind::from(*v.step),)
1117 ])
1118 );
1119 register_config_value!(result, &namespace, myself.aggregate_signature_type);
1120 Ok(result)
1121 }
1122}
1123
1124#[cfg(test)]
1125mod test {
1126 use mithril_common::temp_dir;
1127
1128 use super::*;
1129
1130 #[test]
1131 fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1132 for limit in 4..=10u64 {
1133 let configuration = ServeCommandConfiguration {
1134 store_retention_limit: Some(limit as usize),
1135 ..ServeCommandConfiguration::new_sample(temp_dir!())
1136 };
1137 assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1138 }
1139 }
1140
1141 #[test]
1142 fn safe_epoch_retention_limit_wont_change_a_none_value() {
1143 let configuration = ServeCommandConfiguration {
1144 store_retention_limit: None,
1145 ..ServeCommandConfiguration::new_sample(temp_dir!())
1146 };
1147 assert_eq!(configuration.safe_epoch_retention_limit(), None);
1148 }
1149
1150 #[test]
1151 fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1152 for limit in 0..=3 {
1153 let configuration = ServeCommandConfiguration {
1154 store_retention_limit: Some(limit),
1155 ..ServeCommandConfiguration::new_sample(temp_dir!())
1156 };
1157 assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1158 }
1159 }
1160
1161 #[test]
1162 fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1163 #[derive(Debug, Deserialize)]
1164 struct TargetConfig {
1165 cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1166 }
1167
1168 let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1169 let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1170
1171 assert_eq!(
1172 target.cardano_transactions_signing_config,
1173 DefaultConfiguration::default().cardano_transactions_signing_config
1174 );
1175 }
1176
1177 #[test]
1178 fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1179 let config = ServeCommandConfiguration {
1180 signed_entity_types: None,
1181 ..ServeCommandConfiguration::new_sample(temp_dir!())
1182 };
1183
1184 assert_eq!(
1185 config.compute_allowed_signed_entity_types_discriminants().unwrap(),
1186 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1187 );
1188 }
1189
1190 #[test]
1191 fn allow_http_serve_directory() {
1192 let config = ServeCommandConfiguration {
1193 snapshot_uploader_type: SnapshotUploaderType::Local,
1194 ..ServeCommandConfiguration::new_sample(temp_dir!())
1195 };
1196
1197 assert!(config.allow_http_serve_directory());
1198
1199 let config = ServeCommandConfiguration {
1200 snapshot_uploader_type: SnapshotUploaderType::Gcp,
1201 ..ServeCommandConfiguration::new_sample(temp_dir!())
1202 };
1203
1204 assert!(!config.allow_http_serve_directory());
1205 }
1206
1207 #[test]
1208 fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1209 let config = ServeCommandConfiguration {
1210 server_ip: "1.2.3.4".to_string(),
1211 server_port: 5678,
1212 public_server_url: None,
1213 ..ServeCommandConfiguration::new_sample(temp_dir!())
1214 };
1215
1216 assert_eq!(
1217 config.get_server_url().unwrap().as_str(),
1218 &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1219 );
1220 }
1221
1222 #[test]
1223 fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1224 let config = ServeCommandConfiguration {
1225 server_ip: "1.2.3.4".to_string(),
1226 server_port: 5678,
1227 public_server_url: Some("https://example.com".to_string()),
1228 ..ServeCommandConfiguration::new_sample(temp_dir!())
1229 };
1230
1231 assert_eq!(
1232 config.get_server_url().unwrap().as_str(),
1233 "https://example.com/"
1234 );
1235 }
1236
1237 #[test]
1238 fn joining_to_local_server_url_keep_base_path() {
1239 let config = ServeCommandConfiguration {
1240 server_ip: "1.2.3.4".to_string(),
1241 server_port: 6789,
1242 public_server_url: None,
1243 ..ServeCommandConfiguration::new_sample(temp_dir!())
1244 };
1245
1246 let joined_url = config.get_local_server_url().unwrap().join("some/path").unwrap();
1247 assert!(
1248 joined_url.as_str().contains(SERVER_BASE_PATH),
1249 "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1250 );
1251 }
1252
1253 #[test]
1254 fn joining_to_public_server_url_without_trailing_slash() {
1255 let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1256 let config = ServeCommandConfiguration {
1257 public_server_url: Some(format!(
1258 "https://example.com/{subpath_without_trailing_slash}"
1259 )),
1260 ..ServeCommandConfiguration::new_sample(temp_dir!())
1261 };
1262
1263 let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1264 assert!(
1265 joined_url.as_str().contains(subpath_without_trailing_slash),
1266 "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1267 );
1268 }
1269
1270 #[test]
1271 fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1272 let config = ServeCommandConfiguration {
1273 leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1274 ..ServeCommandConfiguration::new_sample(temp_dir!())
1275 };
1276
1277 assert!(config.is_follower_aggregator());
1278 }
1279
1280 #[test]
1281 fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1282 let config = ServeCommandConfiguration {
1283 leader_aggregator_endpoint: None,
1284 ..ServeCommandConfiguration::new_sample(temp_dir!())
1285 };
1286
1287 assert!(!config.is_follower_aggregator());
1288 }
1289
1290 #[test]
1291 fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value()
1292 {
1293 let serialized_json = r#"{
1294 "type": "secret-key",
1295 "secret_key": "whatever"
1296 }"#;
1297
1298 let deserialized: AncillaryFilesSignerConfig =
1299 serde_json::from_str(serialized_json).unwrap();
1300 assert_eq!(
1301 deserialized,
1302 AncillaryFilesSignerConfig::SecretKey {
1303 secret_key: "whatever".to_string()
1304 }
1305 );
1306 }
1307
1308 #[test]
1309 fn deserializing_ancillary_signing_gcp_kms_configuration() {
1310 let serialized_json = r#"{
1311 "type": "gcp-kms",
1312 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1313 "credentials_json_env_var": "CUSTOM_ENV_VAR"
1314 }"#;
1315
1316 let deserialized: AncillaryFilesSignerConfig =
1317 serde_json::from_str(serialized_json).unwrap();
1318 assert_eq!(
1319 deserialized,
1320 AncillaryFilesSignerConfig::GcpKms {
1321 resource_name: GcpCryptoKeyVersionResourceName {
1322 project: "123456789".to_string(),
1323 location: "global".to_string(),
1324 key_ring: "my-keyring".to_string(),
1325 key_name: "my-key".to_string(),
1326 version: "1".to_string(),
1327 },
1328 credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1329 }
1330 );
1331 }
1332
1333 #[test]
1334 fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default()
1335 {
1336 let serialized_json = r#"{
1337 "type": "gcp-kms",
1338 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1339 }"#;
1340
1341 let deserialized: AncillaryFilesSignerConfig =
1342 serde_json::from_str(serialized_json).unwrap();
1343 if let AncillaryFilesSignerConfig::GcpKms {
1344 credentials_json_env_var,
1345 ..
1346 } = deserialized
1347 {
1348 assert_eq!(
1349 credentials_json_env_var,
1350 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1351 );
1352 } else {
1353 panic!("Expected GcpKms variant but got {deserialized:?}");
1354 }
1355 }
1356
1357 mod origin_tag {
1358 use super::*;
1359
1360 #[test]
1361 fn default_origin_tag_white_list_is_not_empty() {
1362 let config = ServeCommandConfiguration {
1363 custom_origin_tag_white_list: None,
1364 ..ServeCommandConfiguration::new_sample(temp_dir!())
1365 };
1366 assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1367 }
1368
1369 #[test]
1370 fn custom_origin_tag_are_added_to_default_white_list() {
1371 let config = ServeCommandConfiguration {
1372 custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1373 ..ServeCommandConfiguration::new_sample(temp_dir!())
1374 };
1375
1376 let default_white_list = ServeCommandConfiguration {
1377 custom_origin_tag_white_list: None,
1378 ..ServeCommandConfiguration::new_sample(temp_dir!())
1379 }
1380 .compute_origin_tag_white_list();
1381
1382 let mut expected_white_list = default_white_list.clone();
1383 assert!(expected_white_list.insert("TAG_A".to_string()));
1384 assert!(expected_white_list.insert("TAG_B".to_string()));
1385 assert!(expected_white_list.insert("TAG_C".to_string()));
1386
1387 assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1388 }
1389 }
1390}