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;
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, ConfigSecret,
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, 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 blockfrost_parameters(&self) -> Option<BlockfrostParameters> {
226 panic!("blockfrost_parameters 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, 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 #[example = "\
570 `{ \"project_id\": \"preprodWuV1ICdtOWfZYfdcxpZ0tsS1N9rVZomQ\" }`<br/>\
571 or `{ \"project_id\": \"preprodWuV1ICdtOWfZYfdcxpZ0tsS1N9rVZomQ\", \"base_url\": \"https://your-custom-blockfrost-server.io/api/v0/\" }`\
572 "]
573 #[serde(
574 default,
575 deserialize_with = "serde_deserialization::string_or_struct_optional"
576 )]
577 pub blockfrost_parameters: Option<BlockfrostParameters>,
578
579 pub signer_importer_run_interval: u64,
581
582 pub allow_unparsable_block: bool,
586
587 pub cardano_transactions_prover_cache_pool_size: usize,
589
590 pub cardano_transactions_database_connection_pool_size: usize,
592
593 #[example = "`{ security_parameter: 3000, step: 120 }`"]
595 pub cardano_transactions_signing_config: Option<CardanoTransactionsSigningConfig>,
596
597 #[example = "`2160`"]
600 pub preload_security_parameter: BlockNumber,
601
602 pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
604
605 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
607
608 pub enable_metrics_server: bool,
610
611 pub metrics_server_ip: String,
613
614 pub metrics_server_port: u16,
616
617 pub persist_usage_report_interval_in_seconds: u64,
619
620 pub leader_aggregator_endpoint: Option<String>,
626
627 pub custom_origin_tag_white_list: Option<String>,
630
631 pub aggregate_signature_type: AggregateSignatureType,
633
634 pub signature_processor_wait_delay_on_error_ms: u64,
636}
637
638#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
640#[serde(rename_all = "lowercase")]
641pub enum SnapshotUploaderType {
642 Gcp,
644 Local,
646}
647
648#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
650pub struct ZstandardCompressionParameters {
651 pub level: i32,
653
654 pub number_of_workers: u32,
656}
657
658impl Default for ZstandardCompressionParameters {
659 fn default() -> Self {
660 Self {
661 level: 9,
662 number_of_workers: 4,
663 }
664 }
665}
666
667#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
671pub struct BlockfrostParameters {
672 pub project_id: ConfigSecret<String>,
674
675 pub base_url: Option<String>,
678}
679
680impl FromStr for BlockfrostParameters {
681 type Err = serde_json::Error;
682
683 fn from_str(s: &str) -> Result<Self, Self::Err> {
684 serde_json::from_str(s)
685 }
686}
687
688#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
692#[serde(rename_all = "kebab-case", tag = "type")]
693pub enum AncillaryFilesSignerConfig {
694 SecretKey {
696 secret_key: HexEncodedKey,
698 },
699 GcpKms {
701 resource_name: GcpCryptoKeyVersionResourceName,
703 #[serde(default = "default_gcp_kms_credentials_json_env_var")]
705 credentials_json_env_var: String,
706 },
707}
708
709fn default_gcp_kms_credentials_json_env_var() -> String {
710 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
711}
712
713impl FromStr for AncillaryFilesSignerConfig {
714 type Err = serde_json::Error;
715
716 fn from_str(s: &str) -> Result<Self, Self::Err> {
717 serde_json::from_str(s)
718 }
719}
720
721impl ServeCommandConfiguration {
722 pub fn new_sample(tmp_path: PathBuf) -> Self {
724 let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
725 .create_verifier()
726 .to_verification_key();
727 let ancillary_files_signer_secret_key =
728 ManifestSigner::create_deterministic_signer().secret_key();
729
730 Self {
731 environment: ExecutionEnvironment::Test,
732 cardano_cli_path: PathBuf::new(),
733 cardano_node_socket_path: PathBuf::new(),
734 dmq_node_socket_path: None,
735 cardano_node_version: "0.0.1".to_string(),
736 network: "devnet".to_string(),
737 network_magic: Some(42),
738 dmq_network_magic: Some(3141592),
739 chain_observer_type: ChainObserverType::Fake,
740 protocol_parameters: Some(ProtocolParameters {
741 k: 5,
742 m: 100,
743 phi_f: 0.95,
744 }),
745 snapshot_uploader_type: SnapshotUploaderType::Local,
746 snapshot_bucket_name: None,
747 snapshot_use_cdn_domain: false,
748 server_ip: "0.0.0.0".to_string(),
749 server_port: 8000,
750 public_server_url: None,
751 run_interval: 5000,
752 db_directory: PathBuf::new(),
753 snapshot_directory: tmp_path,
760 data_stores_directory: PathBuf::from(":memory:"),
761 genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
762 reset_digests_cache: false,
763 disable_digests_cache: false,
764 store_retention_limit: None,
765 era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
766 era_reader_adapter_params: None,
767 ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
768 secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
769 },
770 signed_entity_types: None,
771 snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
772 zstandard_parameters: Some(ZstandardCompressionParameters::default()),
773 blockfrost_parameters: None,
774 signer_importer_run_interval: 1,
775 allow_unparsable_block: false,
776 cardano_transactions_prover_cache_pool_size: 3,
777 cardano_transactions_database_connection_pool_size: 5,
778 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
779 security_parameter: BlockNumber(120),
780 step: BlockNumber(15),
781 }),
782 preload_security_parameter: BlockNumber(30),
783 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
784 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
785 enable_metrics_server: true,
786 metrics_server_ip: "0.0.0.0".to_string(),
787 metrics_server_port: 9090,
788 persist_usage_report_interval_in_seconds: 10,
789 leader_aggregator_endpoint: None,
790 custom_origin_tag_white_list: None,
791 aggregate_signature_type: AggregateSignatureType::Concatenation,
792 signature_processor_wait_delay_on_error_ms: 5000,
793 }
794 }
795
796 pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
798 SanitizedUrlWithTrailingSlash::parse(&format!(
799 "http://{}:{}/{SERVER_BASE_PATH}/",
800 self.server_ip, self.server_port
801 ))
802 }
803}
804
805impl ConfigurationSource for ServeCommandConfiguration {
806 fn environment(&self) -> ExecutionEnvironment {
807 self.environment.clone()
808 }
809
810 fn cardano_cli_path(&self) -> PathBuf {
811 self.cardano_cli_path.clone()
812 }
813
814 fn cardano_node_socket_path(&self) -> PathBuf {
815 self.cardano_node_socket_path.clone()
816 }
817
818 fn dmq_node_socket_path(&self) -> Option<PathBuf> {
819 self.dmq_node_socket_path.clone()
820 }
821
822 fn cardano_node_version(&self) -> String {
823 self.cardano_node_version.clone()
824 }
825
826 fn network(&self) -> String {
827 self.network.clone()
828 }
829
830 fn network_magic(&self) -> Option<u64> {
831 self.network_magic
832 }
833
834 fn dmq_network_magic(&self) -> Option<u64> {
835 self.dmq_network_magic
836 }
837
838 fn chain_observer_type(&self) -> ChainObserverType {
839 self.chain_observer_type.clone()
840 }
841
842 fn protocol_parameters(&self) -> Option<ProtocolParameters> {
843 self.protocol_parameters.clone()
844 }
845
846 fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
847 self.snapshot_uploader_type
848 }
849
850 fn snapshot_bucket_name(&self) -> Option<String> {
851 self.snapshot_bucket_name.clone()
852 }
853
854 fn snapshot_use_cdn_domain(&self) -> bool {
855 self.snapshot_use_cdn_domain
856 }
857
858 fn server_ip(&self) -> String {
859 self.server_ip.clone()
860 }
861
862 fn server_port(&self) -> u16 {
863 self.server_port
864 }
865
866 fn public_server_url(&self) -> Option<String> {
867 self.public_server_url.clone()
868 }
869
870 fn run_interval(&self) -> u64 {
871 self.run_interval
872 }
873
874 fn db_directory(&self) -> PathBuf {
875 self.db_directory.clone()
876 }
877
878 fn snapshot_directory(&self) -> PathBuf {
879 self.snapshot_directory.clone()
880 }
881
882 fn data_stores_directory(&self) -> PathBuf {
883 self.data_stores_directory.clone()
884 }
885
886 fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
887 self.genesis_verification_key.clone()
888 }
889
890 fn reset_digests_cache(&self) -> bool {
891 self.reset_digests_cache
892 }
893
894 fn disable_digests_cache(&self) -> bool {
895 self.disable_digests_cache
896 }
897
898 fn store_retention_limit(&self) -> Option<usize> {
899 self.store_retention_limit
900 }
901
902 fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
903 self.era_reader_adapter_type.clone()
904 }
905
906 fn era_reader_adapter_params(&self) -> Option<String> {
907 self.era_reader_adapter_params.clone()
908 }
909
910 fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
911 self.ancillary_files_signer_config.clone()
912 }
913
914 fn signed_entity_types(&self) -> Option<String> {
915 self.signed_entity_types.clone()
916 }
917
918 fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
919 self.snapshot_compression_algorithm
920 }
921
922 fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
923 self.zstandard_parameters
924 }
925
926 fn blockfrost_parameters(&self) -> Option<BlockfrostParameters> {
927 self.blockfrost_parameters.clone()
928 }
929
930 fn signer_importer_run_interval(&self) -> u64 {
931 self.signer_importer_run_interval
932 }
933
934 fn allow_unparsable_block(&self) -> bool {
935 self.allow_unparsable_block
936 }
937
938 fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
939 self.cardano_transactions_prover_cache_pool_size
940 }
941
942 fn cardano_transactions_database_connection_pool_size(&self) -> usize {
943 self.cardano_transactions_database_connection_pool_size
944 }
945
946 fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
947 self.cardano_transactions_signing_config.clone()
948 }
949
950 fn preload_security_parameter(&self) -> BlockNumber {
951 self.preload_security_parameter
952 }
953
954 fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
955 self.cardano_transactions_prover_max_hashes_allowed_by_request
956 }
957
958 fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
959 self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
960 }
961
962 fn enable_metrics_server(&self) -> bool {
963 self.enable_metrics_server
964 }
965
966 fn metrics_server_ip(&self) -> String {
967 self.metrics_server_ip.clone()
968 }
969
970 fn metrics_server_port(&self) -> u16 {
971 self.metrics_server_port
972 }
973
974 fn persist_usage_report_interval_in_seconds(&self) -> u64 {
975 self.persist_usage_report_interval_in_seconds
976 }
977
978 fn leader_aggregator_endpoint(&self) -> Option<String> {
979 self.leader_aggregator_endpoint.clone()
980 }
981
982 fn custom_origin_tag_white_list(&self) -> Option<String> {
983 self.custom_origin_tag_white_list.clone()
984 }
985
986 fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
987 match &self.public_server_url {
988 Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
989 None => self.get_local_server_url(),
990 }
991 }
992
993 fn aggregate_signature_type(&self) -> AggregateSignatureType {
994 self.aggregate_signature_type
995 }
996
997 fn signature_processor_wait_delay_on_error_ms(&self) -> u64 {
998 self.signature_processor_wait_delay_on_error_ms
999 }
1000}
1001
1002#[derive(Debug, Clone, DocumenterDefault)]
1004pub struct DefaultConfiguration {
1005 pub environment: ExecutionEnvironment,
1007
1008 pub server_ip: String,
1010
1011 pub server_port: String,
1013
1014 pub db_directory: String,
1016
1017 pub snapshot_directory: String,
1019
1020 pub snapshot_uploader_type: String,
1022
1023 pub era_reader_adapter_type: String,
1025
1026 pub chain_observer_type: String,
1028
1029 pub reset_digests_cache: String,
1031
1032 pub disable_digests_cache: String,
1034
1035 pub snapshot_compression_algorithm: String,
1037
1038 pub snapshot_use_cdn_domain: String,
1040
1041 pub signer_importer_run_interval: u64,
1043
1044 pub allow_unparsable_block: String,
1048
1049 pub cardano_transactions_prover_cache_pool_size: u32,
1051
1052 pub cardano_transactions_database_connection_pool_size: u32,
1054
1055 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1057
1058 pub preload_security_parameter: u64,
1060
1061 pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
1063
1064 pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
1066
1067 pub enable_metrics_server: String,
1069
1070 pub metrics_server_ip: String,
1072
1073 pub metrics_server_port: u16,
1075
1076 pub persist_usage_report_interval_in_seconds: u64,
1078
1079 pub aggregate_signature_type: String,
1081
1082 pub signature_processor_wait_delay_on_error_ms: u64,
1084}
1085
1086impl Default for DefaultConfiguration {
1087 fn default() -> Self {
1088 Self {
1089 environment: ExecutionEnvironment::Production,
1090 server_ip: "0.0.0.0".to_string(),
1091 server_port: "8080".to_string(),
1092 db_directory: "/db".to_string(),
1093 snapshot_directory: ".".to_string(),
1094 snapshot_uploader_type: "gcp".to_string(),
1095 era_reader_adapter_type: "bootstrap".to_string(),
1096 chain_observer_type: "pallas".to_string(),
1097 reset_digests_cache: "false".to_string(),
1098 disable_digests_cache: "false".to_string(),
1099 snapshot_compression_algorithm: "zstandard".to_string(),
1100 snapshot_use_cdn_domain: "false".to_string(),
1101 signer_importer_run_interval: 720,
1102 allow_unparsable_block: "false".to_string(),
1103 cardano_transactions_prover_cache_pool_size: 10,
1104 cardano_transactions_database_connection_pool_size: 10,
1105 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
1106 security_parameter: BlockNumber(3000),
1107 step: BlockNumber(120),
1108 },
1109 preload_security_parameter: 2160,
1110 cardano_transactions_prover_max_hashes_allowed_by_request: 100,
1111 cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
1112 enable_metrics_server: "false".to_string(),
1113 metrics_server_ip: "0.0.0.0".to_string(),
1114 metrics_server_port: 9090,
1115 persist_usage_report_interval_in_seconds: 10,
1116 aggregate_signature_type: "Concatenation".to_string(),
1117 signature_processor_wait_delay_on_error_ms: 1000,
1118 }
1119 }
1120}
1121
1122impl DefaultConfiguration {
1123 fn namespace() -> String {
1124 "default configuration".to_string()
1125 }
1126}
1127
1128impl From<ExecutionEnvironment> for ValueKind {
1129 fn from(value: ExecutionEnvironment) -> Self {
1130 match value {
1131 ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
1132 ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
1133 }
1134 }
1135}
1136
1137impl Source for DefaultConfiguration {
1138 fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
1139 Box::new(self.clone())
1140 }
1141
1142 fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1143 let mut result = Map::new();
1144
1145 let namespace = DefaultConfiguration::namespace();
1146
1147 let myself = self.clone();
1148 register_config_value!(result, &namespace, myself.environment);
1149 register_config_value!(result, &namespace, myself.server_ip);
1150 register_config_value!(result, &namespace, myself.server_port);
1151 register_config_value!(result, &namespace, myself.db_directory);
1152 register_config_value!(result, &namespace, myself.snapshot_directory);
1153 register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1154 register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1155 register_config_value!(result, &namespace, myself.reset_digests_cache);
1156 register_config_value!(result, &namespace, myself.disable_digests_cache);
1157 register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1158 register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1159 register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1160 register_config_value!(result, &namespace, myself.allow_unparsable_block);
1161 register_config_value!(
1162 result,
1163 &namespace,
1164 myself.cardano_transactions_prover_cache_pool_size
1165 );
1166 register_config_value!(
1167 result,
1168 &namespace,
1169 myself.cardano_transactions_database_connection_pool_size
1170 );
1171 register_config_value!(
1172 result,
1173 &namespace,
1174 myself.cardano_transactions_prover_max_hashes_allowed_by_request
1175 );
1176 register_config_value!(
1177 result,
1178 &namespace,
1179 myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1180 );
1181 register_config_value!(result, &namespace, myself.enable_metrics_server);
1182 register_config_value!(result, &namespace, myself.metrics_server_ip);
1183 register_config_value!(result, &namespace, myself.metrics_server_port);
1184 register_config_value!(
1185 result,
1186 &namespace,
1187 myself.persist_usage_report_interval_in_seconds
1188 );
1189 register_config_value!(result, &namespace, myself.preload_security_parameter);
1190 register_config_value!(
1191 result,
1192 &namespace,
1193 myself.cardano_transactions_signing_config,
1194 |v: CardanoTransactionsSigningConfig| HashMap::from([
1195 (
1196 "security_parameter".to_string(),
1197 ValueKind::from(*v.security_parameter),
1198 ),
1199 ("step".to_string(), ValueKind::from(*v.step),)
1200 ])
1201 );
1202 register_config_value!(result, &namespace, myself.aggregate_signature_type);
1203 register_config_value!(
1204 result,
1205 &namespace,
1206 myself.signature_processor_wait_delay_on_error_ms
1207 );
1208 Ok(result)
1209 }
1210}
1211
1212#[cfg(test)]
1213mod test {
1214 use mithril_common::temp_dir;
1215 use mithril_common::test::double::fake_data;
1216
1217 use super::*;
1218
1219 #[test]
1220 fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1221 for limit in 4..=10u64 {
1222 let configuration = ServeCommandConfiguration {
1223 store_retention_limit: Some(limit as usize),
1224 ..ServeCommandConfiguration::new_sample(temp_dir!())
1225 };
1226 assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1227 }
1228 }
1229
1230 #[test]
1231 fn safe_epoch_retention_limit_wont_change_a_none_value() {
1232 let configuration = ServeCommandConfiguration {
1233 store_retention_limit: None,
1234 ..ServeCommandConfiguration::new_sample(temp_dir!())
1235 };
1236 assert_eq!(configuration.safe_epoch_retention_limit(), None);
1237 }
1238
1239 #[test]
1240 fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1241 for limit in 0..=3 {
1242 let configuration = ServeCommandConfiguration {
1243 store_retention_limit: Some(limit),
1244 ..ServeCommandConfiguration::new_sample(temp_dir!())
1245 };
1246 assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1247 }
1248 }
1249
1250 #[test]
1251 fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1252 #[derive(Debug, Deserialize)]
1253 struct TargetConfig {
1254 cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1255 }
1256
1257 let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1258 let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1259
1260 assert_eq!(
1261 target.cardano_transactions_signing_config,
1262 DefaultConfiguration::default().cardano_transactions_signing_config
1263 );
1264 }
1265
1266 #[test]
1267 fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1268 let config = ServeCommandConfiguration {
1269 signed_entity_types: None,
1270 ..ServeCommandConfiguration::new_sample(temp_dir!())
1271 };
1272
1273 assert_eq!(
1274 config.compute_allowed_signed_entity_types_discriminants().unwrap(),
1275 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1276 );
1277 }
1278
1279 #[test]
1280 fn allow_http_serve_directory() {
1281 let config = ServeCommandConfiguration {
1282 snapshot_uploader_type: SnapshotUploaderType::Local,
1283 ..ServeCommandConfiguration::new_sample(temp_dir!())
1284 };
1285
1286 assert!(config.allow_http_serve_directory());
1287
1288 let config = ServeCommandConfiguration {
1289 snapshot_uploader_type: SnapshotUploaderType::Gcp,
1290 ..ServeCommandConfiguration::new_sample(temp_dir!())
1291 };
1292
1293 assert!(!config.allow_http_serve_directory());
1294 }
1295
1296 #[test]
1297 fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1298 let config = ServeCommandConfiguration {
1299 server_ip: "1.2.3.4".to_string(),
1300 server_port: 5678,
1301 public_server_url: None,
1302 ..ServeCommandConfiguration::new_sample(temp_dir!())
1303 };
1304
1305 assert_eq!(
1306 config.get_server_url().unwrap().as_str(),
1307 &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1308 );
1309 }
1310
1311 #[test]
1312 fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1313 let config = ServeCommandConfiguration {
1314 server_ip: "1.2.3.4".to_string(),
1315 server_port: 5678,
1316 public_server_url: Some("https://example.com".to_string()),
1317 ..ServeCommandConfiguration::new_sample(temp_dir!())
1318 };
1319
1320 assert_eq!(
1321 config.get_server_url().unwrap().as_str(),
1322 "https://example.com/"
1323 );
1324 }
1325
1326 #[test]
1327 fn joining_to_local_server_url_keep_base_path() {
1328 let config = ServeCommandConfiguration {
1329 server_ip: "1.2.3.4".to_string(),
1330 server_port: 6789,
1331 public_server_url: None,
1332 ..ServeCommandConfiguration::new_sample(temp_dir!())
1333 };
1334
1335 let joined_url = config.get_local_server_url().unwrap().join("some/path").unwrap();
1336 assert!(
1337 joined_url.as_str().contains(SERVER_BASE_PATH),
1338 "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1339 );
1340 }
1341
1342 #[test]
1343 fn joining_to_public_server_url_without_trailing_slash() {
1344 let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1345 let config = ServeCommandConfiguration {
1346 public_server_url: Some(format!(
1347 "https://example.com/{subpath_without_trailing_slash}"
1348 )),
1349 ..ServeCommandConfiguration::new_sample(temp_dir!())
1350 };
1351
1352 let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1353 assert!(
1354 joined_url.as_str().contains(subpath_without_trailing_slash),
1355 "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1356 );
1357 }
1358
1359 #[test]
1360 fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1361 let config = ServeCommandConfiguration {
1362 leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1363 ..ServeCommandConfiguration::new_sample(temp_dir!())
1364 };
1365
1366 assert!(config.is_follower_aggregator());
1367 }
1368
1369 #[test]
1370 fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1371 let config = ServeCommandConfiguration {
1372 leader_aggregator_endpoint: None,
1373 ..ServeCommandConfiguration::new_sample(temp_dir!())
1374 };
1375
1376 assert!(!config.is_follower_aggregator());
1377 }
1378
1379 #[test]
1380 fn deserializing_blockfrost_parameters() {
1381 let deserialized_without_base_url: BlockfrostParameters =
1382 serde_json::from_str(r#"{ "project_id": "preprodWuV1ICdtOWfZYf" }"#).unwrap();
1383 assert_eq!(
1384 deserialized_without_base_url,
1385 BlockfrostParameters {
1386 project_id: ConfigSecret::new("preprodWuV1ICdtOWfZYf".to_string()),
1387 base_url: None,
1388 }
1389 );
1390
1391 let deserialized_with_base_url: BlockfrostParameters = serde_json::from_str(
1392 r#"{ "project_id": "preprodWuV1ICdtOWfZYf", "base_url": "https://test.foo.bar" }"#,
1393 )
1394 .unwrap();
1395 assert_eq!(
1396 deserialized_with_base_url,
1397 BlockfrostParameters {
1398 project_id: ConfigSecret::new("preprodWuV1ICdtOWfZYf".to_string()),
1399 base_url: Some("https://test.foo.bar".to_string()),
1400 }
1401 );
1402 }
1403
1404 mod get_leader_aggregator_epoch_settings_configuration {
1405 use super::*;
1406
1407 #[test]
1408 fn succeed_when_cardano_transactions_is_disabled_and_cardano_transactions_signing_config_is_not_set()
1409 {
1410 let epoch_settings = ServeCommandConfiguration {
1411 signed_entity_types: None,
1412 cardano_transactions_signing_config: None,
1413 protocol_parameters: Some(ProtocolParameters::new(1, 2, 3.1)),
1414 ..ServeCommandConfiguration::new_sample(temp_dir!())
1415 }
1416 .get_leader_aggregator_epoch_settings_configuration()
1417 .unwrap();
1418
1419 assert_eq!(
1420 AggregatorEpochSettings {
1421 protocol_parameters: ProtocolParameters::new(1, 2, 3.1),
1422 cardano_transactions_signing_config: None
1423 },
1424 epoch_settings
1425 );
1426 }
1427
1428 #[test]
1429 fn succeed_when_cardano_transactions_is_enabled_and_cardano_transactions_signing_config_is_set()
1430 {
1431 let epoch_settings = ServeCommandConfiguration {
1432 signed_entity_types: Some(
1433 SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1434 ),
1435 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1436 security_parameter: BlockNumber(10),
1437 step: BlockNumber(30),
1438 }),
1439 protocol_parameters: Some(ProtocolParameters::new(2, 3, 4.1)),
1440 ..ServeCommandConfiguration::new_sample(temp_dir!())
1441 }
1442 .get_leader_aggregator_epoch_settings_configuration()
1443 .unwrap();
1444
1445 assert_eq!(
1446 AggregatorEpochSettings {
1447 protocol_parameters: ProtocolParameters::new(2, 3, 4.1),
1448 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1449 security_parameter: BlockNumber(10),
1450 step: BlockNumber(30),
1451 },)
1452 },
1453 epoch_settings
1454 );
1455 }
1456
1457 #[test]
1458 fn fails_when_cardano_transactions_is_enabled_without_associated_config() {
1459 let error = ServeCommandConfiguration {
1460 cardano_transactions_signing_config: None,
1461 signed_entity_types: Some(
1462 SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1463 ),
1464 protocol_parameters: Some(fake_data::protocol_parameters()),
1465 ..ServeCommandConfiguration::new_sample(temp_dir!())
1466 }
1467 .get_leader_aggregator_epoch_settings_configuration()
1468 .unwrap_err();
1469
1470 assert!(
1471 error
1472 .to_string()
1473 .contains("Configuration `cardano_transactions_signing_config` is mandatory")
1474 );
1475 }
1476 }
1477
1478 #[test]
1479 fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value()
1480 {
1481 let serialized_json = r#"{
1482 "type": "secret-key",
1483 "secret_key": "whatever"
1484 }"#;
1485
1486 let deserialized: AncillaryFilesSignerConfig =
1487 serde_json::from_str(serialized_json).unwrap();
1488 assert_eq!(
1489 deserialized,
1490 AncillaryFilesSignerConfig::SecretKey {
1491 secret_key: "whatever".to_string()
1492 }
1493 );
1494 }
1495
1496 #[test]
1497 fn deserializing_ancillary_signing_gcp_kms_configuration() {
1498 let serialized_json = r#"{
1499 "type": "gcp-kms",
1500 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1501 "credentials_json_env_var": "CUSTOM_ENV_VAR"
1502 }"#;
1503
1504 let deserialized: AncillaryFilesSignerConfig =
1505 serde_json::from_str(serialized_json).unwrap();
1506 assert_eq!(
1507 deserialized,
1508 AncillaryFilesSignerConfig::GcpKms {
1509 resource_name: GcpCryptoKeyVersionResourceName {
1510 project: "123456789".to_string(),
1511 location: "global".to_string(),
1512 key_ring: "my-keyring".to_string(),
1513 key_name: "my-key".to_string(),
1514 version: "1".to_string(),
1515 },
1516 credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1517 }
1518 );
1519 }
1520
1521 #[test]
1522 fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default()
1523 {
1524 let serialized_json = r#"{
1525 "type": "gcp-kms",
1526 "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1527 }"#;
1528
1529 let deserialized: AncillaryFilesSignerConfig =
1530 serde_json::from_str(serialized_json).unwrap();
1531 if let AncillaryFilesSignerConfig::GcpKms {
1532 credentials_json_env_var,
1533 ..
1534 } = deserialized
1535 {
1536 assert_eq!(
1537 credentials_json_env_var,
1538 DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1539 );
1540 } else {
1541 panic!("Expected GcpKms variant but got {deserialized:?}");
1542 }
1543 }
1544
1545 mod origin_tag {
1546 use super::*;
1547
1548 #[test]
1549 fn default_origin_tag_white_list_is_not_empty() {
1550 let config = ServeCommandConfiguration {
1551 custom_origin_tag_white_list: None,
1552 ..ServeCommandConfiguration::new_sample(temp_dir!())
1553 };
1554 assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1555 }
1556
1557 #[test]
1558 fn custom_origin_tag_are_added_to_default_white_list() {
1559 let config = ServeCommandConfiguration {
1560 custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1561 ..ServeCommandConfiguration::new_sample(temp_dir!())
1562 };
1563
1564 let default_white_list = ServeCommandConfiguration {
1565 custom_origin_tag_white_list: None,
1566 ..ServeCommandConfiguration::new_sample(temp_dir!())
1567 }
1568 .compute_origin_tag_white_list();
1569
1570 let mut expected_white_list = default_white_list.clone();
1571 assert!(expected_white_list.insert("TAG_A".to_string()));
1572 assert!(expected_white_list.insert("TAG_B".to_string()));
1573 assert!(expected_white_list.insert("TAG_C".to_string()));
1574
1575 assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1576 }
1577 }
1578}