mithril_aggregator/
configuration.rs

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/// Different kinds of execution environments
30#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
31pub enum ExecutionEnvironment {
32    /// Test environment, maximum logging, memory stores etc.
33    Test,
34
35    /// Production environment, minimum logging, maximum performances,
36    /// persistent stores etc.
37    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
54/// This trait defines the configuration interface for the aggregator.
55///
56/// By default, each function panics if not overridden, forcing concrete configuration
57/// implementations to explicitly provide the necessary values.
58pub trait ConfigurationSource {
59    /// What kind of runtime environment the configuration is meant to.
60    fn environment(&self) -> ExecutionEnvironment;
61
62    /// Cardano CLI tool path
63    fn cardano_cli_path(&self) -> PathBuf {
64        panic!("cardano_cli_path is not implemented.");
65    }
66
67    /// Path of the socket opened by the Cardano node
68    fn cardano_node_socket_path(&self) -> PathBuf {
69        panic!("cardano_node_socket_path is not implemented.");
70    }
71
72    /// Path of the socket opened by the DMQ node
73    fn dmq_node_socket_path(&self) -> Option<PathBuf> {
74        panic!("dmq_node_socket_path is not implemented.");
75    }
76
77    /// Cardano node version.
78    ///
79    /// **NOTE**: This cannot be verified for now (see [this
80    /// issue](https://github.com/input-output-hk/cardano-cli/issues/224)). This
81    /// is why it has to be manually given to the Aggregator
82    fn cardano_node_version(&self) -> String {
83        panic!("cardano_node_version is not implemented.");
84    }
85
86    /// Cardano network
87    fn network(&self) -> String {
88        panic!("network is not implemented.");
89    }
90
91    /// Cardano Network Magic number
92    ///
93    /// useful for TestNet & DevNet
94    fn network_magic(&self) -> Option<u64> {
95        panic!("network_magic is not implemented.");
96    }
97
98    /// DMQ Network Magic number
99    ///
100    /// useful for TestNet & DevNet
101    #[cfg(feature = "future_dmq")]
102    fn dmq_network_magic(&self) -> Option<u64> {
103        panic!("dmq_network_magic is not implemented.");
104    }
105
106    /// Cardano chain observer type
107    fn chain_observer_type(&self) -> ChainObserverType {
108        panic!("chain_observer_type is not implemented.");
109    }
110
111    /// Protocol parameters
112    fn protocol_parameters(&self) -> Option<ProtocolParameters> {
113        panic!("protocol_parameters is not implemented.");
114    }
115
116    /// Type of snapshot uploader to use
117    fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
118        panic!("snapshot_uploader_type is not implemented.");
119    }
120
121    /// Bucket name where the snapshots are stored if snapshot_uploader_type is Gcp
122    fn snapshot_bucket_name(&self) -> Option<String> {
123        panic!("snapshot_bucket_name is not implemented.");
124    }
125
126    /// Use CDN domain to construct snapshot urls if snapshot_uploader_type is Gcp
127    fn snapshot_use_cdn_domain(&self) -> bool {
128        panic!("snapshot_use_cdn_domain is not implemented.");
129    }
130
131    /// Server listening IP
132    fn server_ip(&self) -> String {
133        panic!("server_ip is not implemented.");
134    }
135
136    /// Server listening port
137    fn server_port(&self) -> u16 {
138        panic!("server_port is not implemented.");
139    }
140
141    /// Server URL that can be accessed from the outside
142    fn public_server_url(&self) -> Option<String> {
143        panic!("public_server_url is not implemented.");
144    }
145
146    /// Run Interval is the interval between two runtime cycles in ms
147    fn run_interval(&self) -> u64 {
148        panic!("run_interval is not implemented.");
149    }
150
151    /// Directory of the Cardano node store.
152    fn db_directory(&self) -> PathBuf {
153        panic!("db_directory is not implemented.");
154    }
155
156    /// Directory to store snapshot
157    fn snapshot_directory(&self) -> PathBuf {
158        panic!("snapshot_directory is not implemented.");
159    }
160
161    /// Directory to store aggregator databases
162    fn data_stores_directory(&self) -> PathBuf {
163        panic!("data_stores_directory is not implemented.");
164    }
165
166    /// Genesis verification key
167    fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
168        panic!("genesis_verification_key is not implemented.");
169    }
170
171    /// Should the immutable cache be reset or not
172    fn reset_digests_cache(&self) -> bool {
173        panic!("reset_digests_cache is not implemented.");
174    }
175
176    /// Use the digest caching strategy
177    fn disable_digests_cache(&self) -> bool {
178        panic!("disable_digests_cache is not implemented.");
179    }
180
181    /// Max number of records in stores.
182    /// When new records are added, oldest records are automatically deleted so
183    /// there can always be at max the number of records specified by this
184    /// setting.
185    fn store_retention_limit(&self) -> Option<usize> {
186        panic!("store_retention_limit is not implemented.");
187    }
188
189    /// Era reader adapter type
190    fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
191        panic!("era_reader_adapter_type is not implemented.");
192    }
193
194    /// Era reader adapter parameters
195    fn era_reader_adapter_params(&self) -> Option<String> {
196        panic!("era_reader_adapter_params is not implemented.");
197    }
198
199    /// Configuration of the ancillary files signer
200    ///
201    /// **IMPORTANT**: The cryptographic scheme used is ED25519
202    fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
203        panic!("ancillary_files_signer_config is not implemented.");
204    }
205
206    /// Signed entity types parameters (discriminants names in an ordered, case-sensitive, comma
207    /// separated list).
208    ///
209    /// The values `MithrilStakeDistribution` and `CardanoImmutableFilesFull` are prepended
210    /// automatically to the list.
211    fn signed_entity_types(&self) -> Option<String> {
212        panic!("signed_entity_types is not implemented.");
213    }
214
215    /// Compression algorithm used for the snapshot archive artifacts.
216    fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
217        panic!("snapshot_compression_algorithm is not implemented.");
218    }
219
220    /// Specific parameters when [CompressionAlgorithm] is set to
221    /// [zstandard][CompressionAlgorithm::Zstandard].
222    fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
223        panic!("zstandard_parameters is not implemented.");
224    }
225
226    /// Url to CExplorer list of pools to import as signer in the database.
227    fn cexplorer_pools_url(&self) -> Option<String> {
228        panic!("cexplorer_pools_url is not implemented.");
229    }
230
231    /// Time interval at which the signers in `cexplorer_pools_url` will be imported (in minutes).
232    fn signer_importer_run_interval(&self) -> u64 {
233        panic!("signer_importer_run_interval is not implemented.");
234    }
235
236    /// If set no error is returned in case of unparsable block and an error log is written instead.
237    ///
238    /// Will be ignored on (pre)production networks.
239    fn allow_unparsable_block(&self) -> bool {
240        panic!("allow_unparsable_block is not implemented.");
241    }
242
243    /// Cardano transactions prover cache pool size
244    fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
245        panic!("cardano_transactions_prover_cache_pool_size is not implemented.");
246    }
247
248    /// Cardano transactions database connection pool size
249    fn cardano_transactions_database_connection_pool_size(&self) -> usize {
250        panic!("cardano_transactions_database_connection_pool_size is not implemented.");
251    }
252
253    /// Cardano transactions signing configuration
254    fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
255        panic!("cardano_transactions_signing_config is not implemented.");
256    }
257
258    /// Blocks offset, from the tip of the chain, to exclude during the cardano transactions preload
259    fn preload_security_parameter(&self) -> BlockNumber {
260        panic!("preload_security_parameter is not implemented.");
261    }
262
263    /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions
264    fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
265        panic!("cardano_transactions_prover_max_hashes_allowed_by_request is not implemented.");
266    }
267
268    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
269    fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
270        panic!(
271            "cardano_transactions_block_streamer_max_roll_forwards_per_poll is not implemented."
272        );
273    }
274
275    /// Enable metrics server (Prometheus endpoint on /metrics).
276    fn enable_metrics_server(&self) -> bool {
277        panic!("enable_metrics_server is not implemented.");
278    }
279
280    /// Metrics HTTP Server IP.
281    fn metrics_server_ip(&self) -> String {
282        panic!("metrics_server_ip is not implemented.");
283    }
284
285    /// Metrics HTTP Server listening port.
286    fn metrics_server_port(&self) -> u16 {
287        panic!("metrics_server_port is not implemented.");
288    }
289
290    /// Time interval at which usage metrics are persisted in event database (in seconds).
291    fn persist_usage_report_interval_in_seconds(&self) -> u64 {
292        panic!("persist_usage_report_interval_in_seconds is not implemented.");
293    }
294
295    /// Leader aggregator endpoint
296    ///
297    /// This is the endpoint of the aggregator that will be used to fetch the latest epoch settings
298    /// and store the signer registrations when the aggregator is running in a follower mode.
299    /// If this is not set, the aggregator will run in a leader mode.
300    fn leader_aggregator_endpoint(&self) -> Option<String> {
301        panic!("leader_aggregator_endpoint is not implemented.");
302    }
303
304    /// Custom origin tag of client request added to the whitelist (comma
305    /// separated list).
306    fn custom_origin_tag_white_list(&self) -> Option<String> {
307        panic!("custom_origin_tag_white_list is not implemented.");
308    }
309
310    /// Get the server URL.
311    fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
312        panic!("get_server_url is not implemented.");
313    }
314
315    /// Get a representation of the Cardano network.
316    fn get_network(&self) -> StdResult<CardanoNetwork> {
317        CardanoNetwork::from_code(self.network(), self.network_magic())
318            .with_context(|| "Invalid network configuration")
319    }
320
321    /// Get a representation of the DMQ network.
322    #[cfg(feature = "future_dmq")]
323    fn get_dmq_network(&self) -> StdResult<DmqNetwork> {
324        DmqNetwork::from_code(self.network(), self.dmq_network_magic())
325            .with_context(|| "Invalid DMQ network configuration")
326    }
327
328    /// Get the directory of the SQLite stores.
329    fn get_sqlite_dir(&self) -> PathBuf {
330        let store_dir = &self.data_stores_directory();
331
332        if !store_dir.exists() {
333            std::fs::create_dir_all(store_dir).unwrap();
334        }
335
336        self.data_stores_directory()
337    }
338
339    /// Get the snapshots directory.
340    fn get_snapshot_dir(&self) -> StdResult<PathBuf> {
341        if !&self.snapshot_directory().exists() {
342            std::fs::create_dir_all(self.snapshot_directory())?;
343        }
344
345        Ok(self.snapshot_directory())
346    }
347
348    /// Get the safe epoch retention limit.
349    fn safe_epoch_retention_limit(&self) -> Option<u64> {
350        self.store_retention_limit()
351            .map(|limit| if limit > 3 { limit as u64 } else { 3 })
352    }
353
354    /// Compute the list of signed entity discriminants that are allowed to be processed.
355    fn compute_allowed_signed_entity_types_discriminants(
356        &self,
357    ) -> StdResult<BTreeSet<SignedEntityTypeDiscriminants>> {
358        let allowed_discriminants = self
359            .signed_entity_types()
360            .as_ref()
361            .map(SignedEntityTypeDiscriminants::parse_list)
362            .transpose()
363            .with_context(|| "Invalid 'signed_entity_types' configuration")?
364            .unwrap_or_default();
365        let allowed_discriminants =
366            SignedEntityConfig::append_allowed_signed_entity_types_discriminants(
367                allowed_discriminants,
368            );
369
370        Ok(allowed_discriminants)
371    }
372
373    /// Check if the HTTP server can serve static directories.
374    fn allow_http_serve_directory(&self) -> bool {
375        match self.snapshot_uploader_type() {
376            SnapshotUploaderType::Local => true,
377            SnapshotUploaderType::Gcp => false,
378        }
379    }
380
381    /// `leader aggregator only` Infer the [AggregatorEpochSettings] from the configuration.
382    fn get_leader_aggregator_epoch_settings_configuration(
383        &self,
384    ) -> StdResult<AggregatorEpochSettings> {
385        let allowed_discriminants = self.compute_allowed_signed_entity_types_discriminants()?;
386
387        let cardano_transactions_signing_config = if allowed_discriminants
388            .contains(&SignedEntityTypeDiscriminants::CardanoTransactions)
389        {
390            let cardano_transactions_signing_config =
391                self.cardano_transactions_signing_config().with_context(
392                    || "Configuration `cardano_transactions_signing_config` is mandatory for a Leader Aggregator when `CardanoTransactions` is enabled in `signed_entity_types`"
393                )?;
394            Some(cardano_transactions_signing_config)
395        } else {
396            None
397        };
398
399        Ok(AggregatorEpochSettings {
400            protocol_parameters: self.protocol_parameters().with_context(
401                || "Configuration `protocol_parameters` is mandatory for a Leader Aggregator",
402            )?,
403            cardano_transactions_signing_config,
404        })
405    }
406
407    /// Check if the aggregator is running in follower mode.
408    fn is_follower_aggregator(&self) -> bool {
409        self.leader_aggregator_endpoint().is_some()
410    }
411
412    /// White list for origin client request.
413    fn compute_origin_tag_white_list(&self) -> HashSet<String> {
414        let mut white_list = HashSet::from([
415            "EXPLORER".to_string(),
416            "BENCHMARK".to_string(),
417            "CI".to_string(),
418            "NA".to_string(),
419        ]);
420        if let Some(custom_tags) = &self.custom_origin_tag_white_list() {
421            white_list.extend(custom_tags.split(',').map(|tag| tag.trim().to_string()));
422        }
423
424        white_list
425    }
426
427    /// Aggregate signature type
428    fn aggregate_signature_type(&self) -> AggregateSignatureType {
429        panic!("get_aggregate_signature_type is not implemented.");
430    }
431}
432
433/// Serve command configuration
434#[derive(Debug, Clone, Serialize, Deserialize, Documenter)]
435pub struct ServeCommandConfiguration {
436    /// What kind of runtime environment the configuration is meant to.
437    pub environment: ExecutionEnvironment,
438
439    /// Cardano CLI tool path
440    #[example = "`cardano-cli`"]
441    pub cardano_cli_path: PathBuf,
442
443    /// Path of the socket opened by the Cardano node
444    #[example = "`/ipc/node.socket`"]
445    pub cardano_node_socket_path: PathBuf,
446
447    /// Path of the socket opened by the DMQ node
448    #[example = "`/ipc/dmq.socket`"]
449    pub dmq_node_socket_path: Option<PathBuf>,
450
451    /// Cardano node version.
452    ///
453    /// **NOTE**: This cannot be verified for now (see [this
454    /// issue](https://github.com/input-output-hk/cardano-cli/issues/224)). This
455    /// is why it has to be manually given to the Aggregator
456    pub cardano_node_version: String,
457
458    /// Cardano network
459    #[example = "`mainnet` or `preprod` or `devnet`"]
460    pub network: String,
461
462    /// Cardano network magic number
463    ///
464    /// useful for TestNet & DevNet
465    #[example = "`1097911063` or `42`"]
466    pub network_magic: Option<u64>,
467
468    /// Dmq network magic number
469    ///
470    /// useful for TestNet & DevNet
471    #[example = "`1097911063` or `42`"]
472    pub dmq_network_magic: Option<u64>,
473
474    /// Cardano chain observer type
475    pub chain_observer_type: ChainObserverType,
476
477    /// Protocol parameters
478    #[example = "`{ k: 5, m: 100, phi_f: 0.65 }`"]
479    pub protocol_parameters: Option<ProtocolParameters>,
480
481    /// Type of snapshot uploader to use
482    #[example = "`gcp` or `local`"]
483    pub snapshot_uploader_type: SnapshotUploaderType,
484
485    /// Bucket name where the snapshots are stored if snapshot_uploader_type is Gcp
486    pub snapshot_bucket_name: Option<String>,
487
488    /// Use CDN domain to construct snapshot urls if snapshot_uploader_type is Gcp
489    pub snapshot_use_cdn_domain: bool,
490
491    /// Server listening IP
492    pub server_ip: String,
493
494    /// Server listening port
495    pub server_port: u16,
496
497    /// Server URL that can be accessed from the outside
498    pub public_server_url: Option<String>,
499
500    /// Run Interval is the interval between two runtime cycles in ms
501    #[example = "`60000`"]
502    pub run_interval: u64,
503
504    /// Directory of the Cardano node store.
505    pub db_directory: PathBuf,
506
507    /// Directory to store snapshot
508    pub snapshot_directory: PathBuf,
509
510    /// Directory to store aggregator databases
511    #[example = "`./mithril-aggregator/stores`"]
512    pub data_stores_directory: PathBuf,
513
514    /// Genesis verification key
515    pub genesis_verification_key: HexEncodedGenesisVerificationKey,
516
517    /// Should the immutable cache be reset or not
518    pub reset_digests_cache: bool,
519
520    /// Use the digest caching strategy
521    pub disable_digests_cache: bool,
522
523    /// Max number of records in stores.
524    /// When new records are added, oldest records are automatically deleted so
525    /// there can always be at max the number of records specified by this
526    /// setting.
527    pub store_retention_limit: Option<usize>,
528
529    /// Era reader adapter type
530    pub era_reader_adapter_type: EraReaderAdapterType,
531
532    /// Era reader adapter parameters
533    pub era_reader_adapter_params: Option<String>,
534
535    /// Configuration of the ancillary files signer
536    ///
537    /// Can either be a secret key or a key stored in a Google Cloud Platform KMS account.
538    ///
539    /// **IMPORTANT**: The cryptographic scheme used is ED25519
540    #[example = "\
541    - secret-key:<br/>`{ \"type\": \"secret-key\", \"secret_key\": \"136372c3138312c3138382c3130352c3233312c3135\" }`<br/>\
542    - Gcp kms:<br/>`{ \"type\": \"gcp-kms\", \"resource_name\": \"projects/project_name/locations/_location_name/keyRings/key_ring_name/cryptoKeys/key_name/cryptoKeyVersions/key_version\" }`\
543    "]
544    #[serde(deserialize_with = "serde_deserialization::string_or_struct")]
545    pub ancillary_files_signer_config: AncillaryFilesSignerConfig,
546
547    /// Signed entity types parameters (discriminants names in an ordered, case-sensitive, comma
548    /// separated list).
549    ///
550    /// The value `MithrilStakeDistribution` is prepended is automatically to the list.
551    #[example = "`CardanoImmutableFilesFull,CardanoStakeDistribution,CardanoDatabase,CardanoTransactions`"]
552    pub signed_entity_types: Option<String>,
553
554    /// Compression algorithm used for the snapshot archive artifacts.
555    #[example = "`gzip` or `zstandard`"]
556    pub snapshot_compression_algorithm: CompressionAlgorithm,
557
558    /// Specific parameters when [snapshot_compression_algorithm][Self::snapshot_compression_algorithm]
559    /// is set to [zstandard][CompressionAlgorithm::Zstandard].
560    #[example = "`{ level: 9, number_of_workers: 4 }`"]
561    pub zstandard_parameters: Option<ZstandardCompressionParameters>,
562
563    /// Url to CExplorer list of pools to import as signer in the database.
564    pub cexplorer_pools_url: Option<String>,
565
566    /// Time interval at which the signers in [Self::cexplorer_pools_url] will be imported (in minutes).
567    pub signer_importer_run_interval: u64,
568
569    /// If set no error is returned in case of unparsable block and an error log is written instead.
570    ///
571    /// Will be ignored on (pre)production networks.
572    pub allow_unparsable_block: bool,
573
574    /// Cardano transactions prover cache pool size
575    pub cardano_transactions_prover_cache_pool_size: usize,
576
577    /// Cardano transactions database connection pool size
578    pub cardano_transactions_database_connection_pool_size: usize,
579
580    /// Cardano transactions signing configuration
581    #[example = "`{ security_parameter: 3000, step: 120 }`"]
582    pub cardano_transactions_signing_config: Option<CardanoTransactionsSigningConfig>,
583
584    /// Blocks offset, from the tip of the chain, to exclude during the Cardano transactions preload,
585    /// default to 2160.
586    #[example = "`2160`"]
587    pub preload_security_parameter: BlockNumber,
588
589    /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions
590    pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
591
592    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
593    pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
594
595    /// Enable metrics server (Prometheus endpoint on /metrics).
596    pub enable_metrics_server: bool,
597
598    /// Metrics HTTP Server IP.
599    pub metrics_server_ip: String,
600
601    /// Metrics HTTP Server listening port.
602    pub metrics_server_port: u16,
603
604    /// Time interval at which usage metrics are persisted in event database (in seconds).
605    pub persist_usage_report_interval_in_seconds: u64,
606
607    // Leader aggregator endpoint
608    ///
609    /// This is the endpoint of the aggregator that will be used to fetch the latest epoch settings
610    /// and store the signer registrations when the aggregator is running in a follower mode.
611    /// If this is not set, the aggregator will run in a leader mode.
612    pub leader_aggregator_endpoint: Option<String>,
613
614    /// Custom origin tag of client request added to the whitelist (comma
615    /// separated list).
616    pub custom_origin_tag_white_list: Option<String>,
617
618    /// Aggregate signature type used to create certificates
619    pub aggregate_signature_type: AggregateSignatureType,
620}
621
622/// Uploader needed to copy the snapshot once computed.
623#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
624#[serde(rename_all = "lowercase")]
625pub enum SnapshotUploaderType {
626    /// Uploader to GCP storage.
627    Gcp,
628    /// Uploader to local storage.
629    Local,
630}
631
632/// [Zstandard][CompressionAlgorithm::Zstandard] specific parameters
633#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
634pub struct ZstandardCompressionParameters {
635    /// Level of compression, default to 9.
636    pub level: i32,
637
638    /// Number of workers when compressing, 0 will disable multithreading, default to 4.
639    pub number_of_workers: u32,
640}
641
642impl Default for ZstandardCompressionParameters {
643    fn default() -> Self {
644        Self {
645            level: 9,
646            number_of_workers: 4,
647        }
648    }
649}
650
651/// Configuration of the ancillary files signer
652///
653/// **IMPORTANT**: The cryptographic scheme used is ED25519
654#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
655#[serde(rename_all = "kebab-case", tag = "type")]
656pub enum AncillaryFilesSignerConfig {
657    /// Sign with a secret key
658    SecretKey {
659        /// Hex encoded secret key
660        secret_key: HexEncodedKey,
661    },
662    /// Sign with a key stored in a Google Cloud Platform KMS account
663    GcpKms {
664        /// GCP KMS resource name
665        resource_name: GcpCryptoKeyVersionResourceName,
666        /// Environment variable containing the credentials JSON, if not set `GOOGLE_APPLICATION_CREDENTIALS_JSON` will be used
667        #[serde(default = "default_gcp_kms_credentials_json_env_var")]
668        credentials_json_env_var: String,
669    },
670}
671
672fn default_gcp_kms_credentials_json_env_var() -> String {
673    DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
674}
675
676impl FromStr for AncillaryFilesSignerConfig {
677    type Err = serde_json::Error;
678
679    fn from_str(s: &str) -> Result<Self, Self::Err> {
680        serde_json::from_str(s)
681    }
682}
683
684impl ServeCommandConfiguration {
685    /// Create a sample configuration mainly for tests
686    pub fn new_sample(tmp_path: PathBuf) -> Self {
687        let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
688            .create_verifier()
689            .to_verification_key();
690        let ancillary_files_signer_secret_key =
691            ManifestSigner::create_deterministic_signer().secret_key();
692
693        Self {
694            environment: ExecutionEnvironment::Test,
695            cardano_cli_path: PathBuf::new(),
696            cardano_node_socket_path: PathBuf::new(),
697            dmq_node_socket_path: None,
698            cardano_node_version: "0.0.1".to_string(),
699            network: "devnet".to_string(),
700            network_magic: Some(42),
701            dmq_network_magic: Some(3141592),
702            chain_observer_type: ChainObserverType::Fake,
703            protocol_parameters: Some(ProtocolParameters {
704                k: 5,
705                m: 100,
706                phi_f: 0.95,
707            }),
708            snapshot_uploader_type: SnapshotUploaderType::Local,
709            snapshot_bucket_name: None,
710            snapshot_use_cdn_domain: false,
711            server_ip: "0.0.0.0".to_string(),
712            server_port: 8000,
713            public_server_url: None,
714            run_interval: 5000,
715            db_directory: PathBuf::new(),
716            // Note: this is a band-aid solution to avoid IO operations in the `mithril-aggregator`
717            // crate directory.
718            // Know issue:
719            // - There may be collision of the `snapshot_directory` between tests. Tests that
720            // depend on the `snapshot_directory` should specify their own,
721            // and they can use the `temp_dir` macro for that.
722            snapshot_directory: tmp_path,
723            data_stores_directory: PathBuf::from(":memory:"),
724            genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
725            reset_digests_cache: false,
726            disable_digests_cache: false,
727            store_retention_limit: None,
728            era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
729            era_reader_adapter_params: None,
730            ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
731                secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
732            },
733            signed_entity_types: None,
734            snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
735            zstandard_parameters: Some(ZstandardCompressionParameters::default()),
736            cexplorer_pools_url: None,
737            signer_importer_run_interval: 1,
738            allow_unparsable_block: false,
739            cardano_transactions_prover_cache_pool_size: 3,
740            cardano_transactions_database_connection_pool_size: 5,
741            cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
742                security_parameter: BlockNumber(120),
743                step: BlockNumber(15),
744            }),
745            preload_security_parameter: BlockNumber(30),
746            cardano_transactions_prover_max_hashes_allowed_by_request: 100,
747            cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
748            enable_metrics_server: true,
749            metrics_server_ip: "0.0.0.0".to_string(),
750            metrics_server_port: 9090,
751            persist_usage_report_interval_in_seconds: 10,
752            leader_aggregator_endpoint: None,
753            custom_origin_tag_white_list: None,
754            aggregate_signature_type: AggregateSignatureType::Concatenation,
755        }
756    }
757
758    /// Build the local server URL from configuration.
759    pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
760        SanitizedUrlWithTrailingSlash::parse(&format!(
761            "http://{}:{}/{SERVER_BASE_PATH}/",
762            self.server_ip, self.server_port
763        ))
764    }
765}
766
767impl ConfigurationSource for ServeCommandConfiguration {
768    fn environment(&self) -> ExecutionEnvironment {
769        self.environment.clone()
770    }
771
772    fn cardano_cli_path(&self) -> PathBuf {
773        self.cardano_cli_path.clone()
774    }
775
776    fn cardano_node_socket_path(&self) -> PathBuf {
777        self.cardano_node_socket_path.clone()
778    }
779
780    fn dmq_node_socket_path(&self) -> Option<PathBuf> {
781        self.dmq_node_socket_path.clone()
782    }
783
784    fn cardano_node_version(&self) -> String {
785        self.cardano_node_version.clone()
786    }
787
788    fn network(&self) -> String {
789        self.network.clone()
790    }
791
792    fn network_magic(&self) -> Option<u64> {
793        self.network_magic
794    }
795
796    #[cfg(feature = "future_dmq")]
797    fn dmq_network_magic(&self) -> Option<u64> {
798        self.dmq_network_magic
799    }
800
801    fn chain_observer_type(&self) -> ChainObserverType {
802        self.chain_observer_type.clone()
803    }
804
805    fn protocol_parameters(&self) -> Option<ProtocolParameters> {
806        self.protocol_parameters.clone()
807    }
808
809    fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
810        self.snapshot_uploader_type
811    }
812
813    fn snapshot_bucket_name(&self) -> Option<String> {
814        self.snapshot_bucket_name.clone()
815    }
816
817    fn snapshot_use_cdn_domain(&self) -> bool {
818        self.snapshot_use_cdn_domain
819    }
820
821    fn server_ip(&self) -> String {
822        self.server_ip.clone()
823    }
824
825    fn server_port(&self) -> u16 {
826        self.server_port
827    }
828
829    fn public_server_url(&self) -> Option<String> {
830        self.public_server_url.clone()
831    }
832
833    fn run_interval(&self) -> u64 {
834        self.run_interval
835    }
836
837    fn db_directory(&self) -> PathBuf {
838        self.db_directory.clone()
839    }
840
841    fn snapshot_directory(&self) -> PathBuf {
842        self.snapshot_directory.clone()
843    }
844
845    fn data_stores_directory(&self) -> PathBuf {
846        self.data_stores_directory.clone()
847    }
848
849    fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
850        self.genesis_verification_key.clone()
851    }
852
853    fn reset_digests_cache(&self) -> bool {
854        self.reset_digests_cache
855    }
856
857    fn disable_digests_cache(&self) -> bool {
858        self.disable_digests_cache
859    }
860
861    fn store_retention_limit(&self) -> Option<usize> {
862        self.store_retention_limit
863    }
864
865    fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
866        self.era_reader_adapter_type.clone()
867    }
868
869    fn era_reader_adapter_params(&self) -> Option<String> {
870        self.era_reader_adapter_params.clone()
871    }
872
873    fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
874        self.ancillary_files_signer_config.clone()
875    }
876
877    fn signed_entity_types(&self) -> Option<String> {
878        self.signed_entity_types.clone()
879    }
880
881    fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
882        self.snapshot_compression_algorithm
883    }
884
885    fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
886        self.zstandard_parameters
887    }
888
889    fn cexplorer_pools_url(&self) -> Option<String> {
890        self.cexplorer_pools_url.clone()
891    }
892
893    fn signer_importer_run_interval(&self) -> u64 {
894        self.signer_importer_run_interval
895    }
896
897    fn allow_unparsable_block(&self) -> bool {
898        self.allow_unparsable_block
899    }
900
901    fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
902        self.cardano_transactions_prover_cache_pool_size
903    }
904
905    fn cardano_transactions_database_connection_pool_size(&self) -> usize {
906        self.cardano_transactions_database_connection_pool_size
907    }
908
909    fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
910        self.cardano_transactions_signing_config.clone()
911    }
912
913    fn preload_security_parameter(&self) -> BlockNumber {
914        self.preload_security_parameter
915    }
916
917    fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
918        self.cardano_transactions_prover_max_hashes_allowed_by_request
919    }
920
921    fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
922        self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
923    }
924
925    fn enable_metrics_server(&self) -> bool {
926        self.enable_metrics_server
927    }
928
929    fn metrics_server_ip(&self) -> String {
930        self.metrics_server_ip.clone()
931    }
932
933    fn metrics_server_port(&self) -> u16 {
934        self.metrics_server_port
935    }
936
937    fn persist_usage_report_interval_in_seconds(&self) -> u64 {
938        self.persist_usage_report_interval_in_seconds
939    }
940
941    fn leader_aggregator_endpoint(&self) -> Option<String> {
942        self.leader_aggregator_endpoint.clone()
943    }
944
945    fn custom_origin_tag_white_list(&self) -> Option<String> {
946        self.custom_origin_tag_white_list.clone()
947    }
948
949    fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
950        match &self.public_server_url {
951            Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
952            None => self.get_local_server_url(),
953        }
954    }
955
956    fn aggregate_signature_type(&self) -> AggregateSignatureType {
957        self.aggregate_signature_type
958    }
959}
960
961/// Default configuration with all the default values for configurations.
962#[derive(Debug, Clone, DocumenterDefault)]
963pub struct DefaultConfiguration {
964    /// Execution environment
965    pub environment: ExecutionEnvironment,
966
967    /// Server listening IP
968    pub server_ip: String,
969
970    /// Server listening port
971    pub server_port: String,
972
973    /// Directory of the Cardano node database
974    pub db_directory: String,
975
976    /// Directory to store snapshot
977    pub snapshot_directory: String,
978
979    /// Type of snapshot uploader to use
980    pub snapshot_uploader_type: String,
981
982    /// Era reader adapter type
983    pub era_reader_adapter_type: String,
984
985    /// Chain observer type
986    pub chain_observer_type: String,
987
988    /// ImmutableDigesterCacheProvider default setting
989    pub reset_digests_cache: String,
990
991    /// ImmutableDigesterCacheProvider default setting
992    pub disable_digests_cache: String,
993
994    /// Snapshot compression algorithm default setting
995    pub snapshot_compression_algorithm: String,
996
997    /// Use CDN domain to construct snapshot urls default setting (if snapshot_uploader_type is Gcp)
998    pub snapshot_use_cdn_domain: String,
999
1000    /// Signer importer run interval default setting
1001    pub signer_importer_run_interval: u64,
1002
1003    /// If set no error is returned in case of unparsable block and an error log is written instead.
1004    ///
1005    /// Will be ignored on (pre)production networks.
1006    pub allow_unparsable_block: String,
1007
1008    /// Cardano transactions prover cache pool size
1009    pub cardano_transactions_prover_cache_pool_size: u32,
1010
1011    /// Cardano transactions database connection pool size
1012    pub cardano_transactions_database_connection_pool_size: u32,
1013
1014    /// Cardano transactions signing configuration
1015    pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1016
1017    /// Blocks offset, from the tip of the chain, to exclude during the Cardano transactions preload
1018    pub preload_security_parameter: u64,
1019
1020    /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions
1021    pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
1022
1023    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
1024    pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
1025
1026    /// Enable metrics server (Prometheus endpoint on /metrics).
1027    pub enable_metrics_server: String,
1028
1029    /// Metrics HTTP server IP.
1030    pub metrics_server_ip: String,
1031
1032    /// Metrics HTTP server listening port.
1033    pub metrics_server_port: u16,
1034
1035    /// Time interval at which metrics are persisted in event database (in seconds).
1036    pub persist_usage_report_interval_in_seconds: u64,
1037
1038    /// Aggregate signature type used to create certificates
1039    pub aggregate_signature_type: String,
1040}
1041
1042impl Default for DefaultConfiguration {
1043    fn default() -> Self {
1044        Self {
1045            environment: ExecutionEnvironment::Production,
1046            server_ip: "0.0.0.0".to_string(),
1047            server_port: "8080".to_string(),
1048            db_directory: "/db".to_string(),
1049            snapshot_directory: ".".to_string(),
1050            snapshot_uploader_type: "gcp".to_string(),
1051            era_reader_adapter_type: "bootstrap".to_string(),
1052            chain_observer_type: "pallas".to_string(),
1053            reset_digests_cache: "false".to_string(),
1054            disable_digests_cache: "false".to_string(),
1055            snapshot_compression_algorithm: "zstandard".to_string(),
1056            snapshot_use_cdn_domain: "false".to_string(),
1057            signer_importer_run_interval: 720,
1058            allow_unparsable_block: "false".to_string(),
1059            cardano_transactions_prover_cache_pool_size: 10,
1060            cardano_transactions_database_connection_pool_size: 10,
1061            cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
1062                security_parameter: BlockNumber(3000),
1063                step: BlockNumber(120),
1064            },
1065            preload_security_parameter: 2160,
1066            cardano_transactions_prover_max_hashes_allowed_by_request: 100,
1067            cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
1068            enable_metrics_server: "false".to_string(),
1069            metrics_server_ip: "0.0.0.0".to_string(),
1070            metrics_server_port: 9090,
1071            persist_usage_report_interval_in_seconds: 10,
1072            aggregate_signature_type: "Concatenation".to_string(),
1073        }
1074    }
1075}
1076
1077impl DefaultConfiguration {
1078    fn namespace() -> String {
1079        "default configuration".to_string()
1080    }
1081}
1082
1083impl From<ExecutionEnvironment> for ValueKind {
1084    fn from(value: ExecutionEnvironment) -> Self {
1085        match value {
1086            ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
1087            ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
1088        }
1089    }
1090}
1091
1092impl Source for DefaultConfiguration {
1093    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
1094        Box::new(self.clone())
1095    }
1096
1097    fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1098        let mut result = Map::new();
1099
1100        let namespace = DefaultConfiguration::namespace();
1101
1102        let myself = self.clone();
1103        register_config_value!(result, &namespace, myself.environment);
1104        register_config_value!(result, &namespace, myself.server_ip);
1105        register_config_value!(result, &namespace, myself.server_port);
1106        register_config_value!(result, &namespace, myself.db_directory);
1107        register_config_value!(result, &namespace, myself.snapshot_directory);
1108        register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1109        register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1110        register_config_value!(result, &namespace, myself.reset_digests_cache);
1111        register_config_value!(result, &namespace, myself.disable_digests_cache);
1112        register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1113        register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1114        register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1115        register_config_value!(result, &namespace, myself.allow_unparsable_block);
1116        register_config_value!(
1117            result,
1118            &namespace,
1119            myself.cardano_transactions_prover_cache_pool_size
1120        );
1121        register_config_value!(
1122            result,
1123            &namespace,
1124            myself.cardano_transactions_database_connection_pool_size
1125        );
1126        register_config_value!(
1127            result,
1128            &namespace,
1129            myself.cardano_transactions_prover_max_hashes_allowed_by_request
1130        );
1131        register_config_value!(
1132            result,
1133            &namespace,
1134            myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1135        );
1136        register_config_value!(result, &namespace, myself.enable_metrics_server);
1137        register_config_value!(result, &namespace, myself.metrics_server_ip);
1138        register_config_value!(result, &namespace, myself.metrics_server_port);
1139        register_config_value!(
1140            result,
1141            &namespace,
1142            myself.persist_usage_report_interval_in_seconds
1143        );
1144        register_config_value!(result, &namespace, myself.preload_security_parameter);
1145        register_config_value!(
1146            result,
1147            &namespace,
1148            myself.cardano_transactions_signing_config,
1149            |v: CardanoTransactionsSigningConfig| HashMap::from([
1150                (
1151                    "security_parameter".to_string(),
1152                    ValueKind::from(*v.security_parameter),
1153                ),
1154                ("step".to_string(), ValueKind::from(*v.step),)
1155            ])
1156        );
1157        register_config_value!(result, &namespace, myself.aggregate_signature_type);
1158        Ok(result)
1159    }
1160}
1161
1162#[cfg(test)]
1163mod test {
1164    use mithril_common::temp_dir;
1165    use mithril_common::test::double::fake_data;
1166
1167    use super::*;
1168
1169    #[test]
1170    fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1171        for limit in 4..=10u64 {
1172            let configuration = ServeCommandConfiguration {
1173                store_retention_limit: Some(limit as usize),
1174                ..ServeCommandConfiguration::new_sample(temp_dir!())
1175            };
1176            assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1177        }
1178    }
1179
1180    #[test]
1181    fn safe_epoch_retention_limit_wont_change_a_none_value() {
1182        let configuration = ServeCommandConfiguration {
1183            store_retention_limit: None,
1184            ..ServeCommandConfiguration::new_sample(temp_dir!())
1185        };
1186        assert_eq!(configuration.safe_epoch_retention_limit(), None);
1187    }
1188
1189    #[test]
1190    fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1191        for limit in 0..=3 {
1192            let configuration = ServeCommandConfiguration {
1193                store_retention_limit: Some(limit),
1194                ..ServeCommandConfiguration::new_sample(temp_dir!())
1195            };
1196            assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1197        }
1198    }
1199
1200    #[test]
1201    fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1202        #[derive(Debug, Deserialize)]
1203        struct TargetConfig {
1204            cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1205        }
1206
1207        let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1208        let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1209
1210        assert_eq!(
1211            target.cardano_transactions_signing_config,
1212            DefaultConfiguration::default().cardano_transactions_signing_config
1213        );
1214    }
1215
1216    #[test]
1217    fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1218        let config = ServeCommandConfiguration {
1219            signed_entity_types: None,
1220            ..ServeCommandConfiguration::new_sample(temp_dir!())
1221        };
1222
1223        assert_eq!(
1224            config.compute_allowed_signed_entity_types_discriminants().unwrap(),
1225            BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1226        );
1227    }
1228
1229    #[test]
1230    fn allow_http_serve_directory() {
1231        let config = ServeCommandConfiguration {
1232            snapshot_uploader_type: SnapshotUploaderType::Local,
1233            ..ServeCommandConfiguration::new_sample(temp_dir!())
1234        };
1235
1236        assert!(config.allow_http_serve_directory());
1237
1238        let config = ServeCommandConfiguration {
1239            snapshot_uploader_type: SnapshotUploaderType::Gcp,
1240            ..ServeCommandConfiguration::new_sample(temp_dir!())
1241        };
1242
1243        assert!(!config.allow_http_serve_directory());
1244    }
1245
1246    #[test]
1247    fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1248        let config = ServeCommandConfiguration {
1249            server_ip: "1.2.3.4".to_string(),
1250            server_port: 5678,
1251            public_server_url: None,
1252            ..ServeCommandConfiguration::new_sample(temp_dir!())
1253        };
1254
1255        assert_eq!(
1256            config.get_server_url().unwrap().as_str(),
1257            &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1258        );
1259    }
1260
1261    #[test]
1262    fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1263        let config = ServeCommandConfiguration {
1264            server_ip: "1.2.3.4".to_string(),
1265            server_port: 5678,
1266            public_server_url: Some("https://example.com".to_string()),
1267            ..ServeCommandConfiguration::new_sample(temp_dir!())
1268        };
1269
1270        assert_eq!(
1271            config.get_server_url().unwrap().as_str(),
1272            "https://example.com/"
1273        );
1274    }
1275
1276    #[test]
1277    fn joining_to_local_server_url_keep_base_path() {
1278        let config = ServeCommandConfiguration {
1279            server_ip: "1.2.3.4".to_string(),
1280            server_port: 6789,
1281            public_server_url: None,
1282            ..ServeCommandConfiguration::new_sample(temp_dir!())
1283        };
1284
1285        let joined_url = config.get_local_server_url().unwrap().join("some/path").unwrap();
1286        assert!(
1287            joined_url.as_str().contains(SERVER_BASE_PATH),
1288            "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1289        );
1290    }
1291
1292    #[test]
1293    fn joining_to_public_server_url_without_trailing_slash() {
1294        let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1295        let config = ServeCommandConfiguration {
1296            public_server_url: Some(format!(
1297                "https://example.com/{subpath_without_trailing_slash}"
1298            )),
1299            ..ServeCommandConfiguration::new_sample(temp_dir!())
1300        };
1301
1302        let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1303        assert!(
1304            joined_url.as_str().contains(subpath_without_trailing_slash),
1305            "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1306        );
1307    }
1308
1309    #[test]
1310    fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1311        let config = ServeCommandConfiguration {
1312            leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1313            ..ServeCommandConfiguration::new_sample(temp_dir!())
1314        };
1315
1316        assert!(config.is_follower_aggregator());
1317    }
1318
1319    #[test]
1320    fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1321        let config = ServeCommandConfiguration {
1322            leader_aggregator_endpoint: None,
1323            ..ServeCommandConfiguration::new_sample(temp_dir!())
1324        };
1325
1326        assert!(!config.is_follower_aggregator());
1327    }
1328
1329    mod get_leader_aggregator_epoch_settings_configuration {
1330        use super::*;
1331
1332        #[test]
1333        fn succeed_when_cardano_transactions_is_disabled_and_cardano_transactions_signing_config_is_not_set()
1334         {
1335            let epoch_settings = ServeCommandConfiguration {
1336                signed_entity_types: None,
1337                cardano_transactions_signing_config: None,
1338                protocol_parameters: Some(ProtocolParameters::new(1, 2, 3.1)),
1339                ..ServeCommandConfiguration::new_sample(temp_dir!())
1340            }
1341            .get_leader_aggregator_epoch_settings_configuration()
1342            .unwrap();
1343
1344            assert_eq!(
1345                AggregatorEpochSettings {
1346                    protocol_parameters: ProtocolParameters::new(1, 2, 3.1),
1347                    cardano_transactions_signing_config: None
1348                },
1349                epoch_settings
1350            );
1351        }
1352
1353        #[test]
1354        fn succeed_when_cardano_transactions_is_enabled_and_cardano_transactions_signing_config_is_set()
1355         {
1356            let epoch_settings = ServeCommandConfiguration {
1357                signed_entity_types: Some(
1358                    SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1359                ),
1360                cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1361                    security_parameter: BlockNumber(10),
1362                    step: BlockNumber(30),
1363                }),
1364                protocol_parameters: Some(ProtocolParameters::new(2, 3, 4.1)),
1365                ..ServeCommandConfiguration::new_sample(temp_dir!())
1366            }
1367            .get_leader_aggregator_epoch_settings_configuration()
1368            .unwrap();
1369
1370            assert_eq!(
1371                AggregatorEpochSettings {
1372                    protocol_parameters: ProtocolParameters::new(2, 3, 4.1),
1373                    cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1374                        security_parameter: BlockNumber(10),
1375                        step: BlockNumber(30),
1376                    },)
1377                },
1378                epoch_settings
1379            );
1380        }
1381
1382        #[test]
1383        fn fails_when_cardano_transactions_is_enabled_without_associated_config() {
1384            let error = ServeCommandConfiguration {
1385                cardano_transactions_signing_config: None,
1386                signed_entity_types: Some(
1387                    SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1388                ),
1389                protocol_parameters: Some(fake_data::protocol_parameters()),
1390                ..ServeCommandConfiguration::new_sample(temp_dir!())
1391            }
1392            .get_leader_aggregator_epoch_settings_configuration()
1393            .unwrap_err();
1394
1395            assert!(
1396                error
1397                    .to_string()
1398                    .contains("Configuration `cardano_transactions_signing_config` is mandatory")
1399            );
1400        }
1401    }
1402
1403    #[test]
1404    fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value()
1405     {
1406        let serialized_json = r#"{
1407            "type": "secret-key",
1408            "secret_key": "whatever"
1409        }"#;
1410
1411        let deserialized: AncillaryFilesSignerConfig =
1412            serde_json::from_str(serialized_json).unwrap();
1413        assert_eq!(
1414            deserialized,
1415            AncillaryFilesSignerConfig::SecretKey {
1416                secret_key: "whatever".to_string()
1417            }
1418        );
1419    }
1420
1421    #[test]
1422    fn deserializing_ancillary_signing_gcp_kms_configuration() {
1423        let serialized_json = r#"{
1424            "type": "gcp-kms",
1425            "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1426            "credentials_json_env_var": "CUSTOM_ENV_VAR"
1427        }"#;
1428
1429        let deserialized: AncillaryFilesSignerConfig =
1430            serde_json::from_str(serialized_json).unwrap();
1431        assert_eq!(
1432            deserialized,
1433            AncillaryFilesSignerConfig::GcpKms {
1434                resource_name: GcpCryptoKeyVersionResourceName {
1435                    project: "123456789".to_string(),
1436                    location: "global".to_string(),
1437                    key_ring: "my-keyring".to_string(),
1438                    key_name: "my-key".to_string(),
1439                    version: "1".to_string(),
1440                },
1441                credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1442            }
1443        );
1444    }
1445
1446    #[test]
1447    fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default()
1448     {
1449        let serialized_json = r#"{
1450            "type": "gcp-kms",
1451            "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1452        }"#;
1453
1454        let deserialized: AncillaryFilesSignerConfig =
1455            serde_json::from_str(serialized_json).unwrap();
1456        if let AncillaryFilesSignerConfig::GcpKms {
1457            credentials_json_env_var,
1458            ..
1459        } = deserialized
1460        {
1461            assert_eq!(
1462                credentials_json_env_var,
1463                DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1464            );
1465        } else {
1466            panic!("Expected GcpKms variant but got {deserialized:?}");
1467        }
1468    }
1469
1470    mod origin_tag {
1471        use super::*;
1472
1473        #[test]
1474        fn default_origin_tag_white_list_is_not_empty() {
1475            let config = ServeCommandConfiguration {
1476                custom_origin_tag_white_list: None,
1477                ..ServeCommandConfiguration::new_sample(temp_dir!())
1478            };
1479            assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1480        }
1481
1482        #[test]
1483        fn custom_origin_tag_are_added_to_default_white_list() {
1484            let config = ServeCommandConfiguration {
1485                custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1486                ..ServeCommandConfiguration::new_sample(temp_dir!())
1487            };
1488
1489            let default_white_list = ServeCommandConfiguration {
1490                custom_origin_tag_white_list: None,
1491                ..ServeCommandConfiguration::new_sample(temp_dir!())
1492            }
1493            .compute_origin_tag_white_list();
1494
1495            let mut expected_white_list = default_white_list.clone();
1496            assert!(expected_white_list.insert("TAG_A".to_string()));
1497            assert!(expected_white_list.insert("TAG_B".to_string()));
1498            assert!(expected_white_list.insert("TAG_C".to_string()));
1499
1500            assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1501        }
1502    }
1503}