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