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;
8
9use mithril_cardano_node_chain::chain_observer::ChainObserverType;
10use mithril_cli_helper::{register_config_value, serde_deserialization};
11use mithril_common::crypto_helper::{ManifestSigner, ProtocolGenesisSigner};
12use mithril_common::entities::{
13    BlockNumber, CardanoTransactionsSigningConfig, CompressionAlgorithm, ConfigSecret,
14    HexEncodedGenesisVerificationKey, HexEncodedKey, ProtocolParameters, SignedEntityConfig,
15    SignedEntityTypeDiscriminants,
16};
17use mithril_common::{AggregateSignatureType, CardanoNetwork, StdResult};
18use mithril_dmq::DmqNetwork;
19use mithril_doc::{Documenter, DocumenterDefault, StructDoc};
20use mithril_era::adapters::EraReaderAdapterType;
21
22use crate::entities::AggregatorEpochSettings;
23use crate::http_server::SERVER_BASE_PATH;
24use crate::services::ancillary_signer::GcpCryptoKeyVersionResourceName;
25use crate::tools::DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR;
26use crate::tools::url_sanitizer::SanitizedUrlWithTrailingSlash;
27
28/// Different kinds of execution environments
29#[derive(Debug, 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    /// Blockfrost api configuration
225    fn blockfrost_parameters(&self) -> Option<BlockfrostParameters> {
226        panic!("blockfrost_parameters is not implemented.");
227    }
228
229    /// Time interval at which the pools names and ticker in blockfrost 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, 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    /// Optional parameters to connect to the Blockfrost API. Used to fetch the ticker and name of
566    /// the registered stake pools.
567    ///
568    /// `base_url` (optional) allows you to override the default URL, which is otherwise automatically determined from the project ID.
569    #[example = "\
570    `{ \"project_id\": \"preprodWuV1ICdtOWfZYfdcxpZ0tsS1N9rVZomQ\" }`<br/>\
571    or `{ \"project_id\": \"preprodWuV1ICdtOWfZYfdcxpZ0tsS1N9rVZomQ\", \"base_url\": \"https://your-custom-blockfrost-server.io/api/v0/\" }`\
572    "]
573    #[serde(
574        default,
575        deserialize_with = "serde_deserialization::string_or_struct_optional"
576    )]
577    pub blockfrost_parameters: Option<BlockfrostParameters>,
578
579    /// Time interval at which the pools names and ticker in blockfrost will be imported (in minutes).
580    pub signer_importer_run_interval: u64,
581
582    /// If set no error is returned in case of unparsable block and an error log is written instead.
583    ///
584    /// Will be ignored on (pre)production networks.
585    pub allow_unparsable_block: bool,
586
587    /// Cardano transactions prover cache pool size
588    pub cardano_transactions_prover_cache_pool_size: usize,
589
590    /// Cardano transactions database connection pool size
591    pub cardano_transactions_database_connection_pool_size: usize,
592
593    /// Cardano transactions signing configuration
594    #[example = "`{ security_parameter: 3000, step: 120 }`"]
595    pub cardano_transactions_signing_config: Option<CardanoTransactionsSigningConfig>,
596
597    /// Blocks offset, from the tip of the chain, to exclude during the Cardano transactions preload,
598    /// default to 2160.
599    #[example = "`2160`"]
600    pub preload_security_parameter: BlockNumber,
601
602    /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions
603    pub cardano_transactions_prover_max_hashes_allowed_by_request: usize,
604
605    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
606    pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
607
608    /// Enable metrics server (Prometheus endpoint on /metrics).
609    pub enable_metrics_server: bool,
610
611    /// Metrics HTTP Server IP.
612    pub metrics_server_ip: String,
613
614    /// Metrics HTTP Server listening port.
615    pub metrics_server_port: u16,
616
617    /// Time interval at which usage metrics are persisted in event database (in seconds).
618    pub persist_usage_report_interval_in_seconds: u64,
619
620    // Leader aggregator endpoint
621    ///
622    /// This is the endpoint of the aggregator that will be used to fetch the latest epoch settings
623    /// and store the signer registrations when the aggregator is running in a follower mode.
624    /// If this is not set, the aggregator will run in a leader mode.
625    pub leader_aggregator_endpoint: Option<String>,
626
627    /// Custom origin tag of client request added to the whitelist (comma
628    /// separated list).
629    pub custom_origin_tag_white_list: Option<String>,
630
631    /// Aggregate signature type used to create certificates
632    pub aggregate_signature_type: AggregateSignatureType,
633
634    /// Delay to wait between two signature processing attempts after an error
635    pub signature_processor_wait_delay_on_error_ms: u64,
636}
637
638/// Uploader needed to copy the snapshot once computed.
639#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
640#[serde(rename_all = "lowercase")]
641pub enum SnapshotUploaderType {
642    /// Uploader to GCP storage.
643    Gcp,
644    /// Uploader to local storage.
645    Local,
646}
647
648/// [Zstandard][CompressionAlgorithm::Zstandard] specific parameters
649#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
650pub struct ZstandardCompressionParameters {
651    /// Level of compression, default to 9.
652    pub level: i32,
653
654    /// Number of workers when compressing, 0 will disable multithreading, default to 4.
655    pub number_of_workers: u32,
656}
657
658impl Default for ZstandardCompressionParameters {
659    fn default() -> Self {
660        Self {
661            level: 9,
662            number_of_workers: 4,
663        }
664    }
665}
666
667/// Configuration to connect to the Blockfrost API.
668///
669/// Currently only used to fetch the ticker and name for registered pools.
670#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
671pub struct BlockfrostParameters {
672    /// Project ID of the Blockfrost project.
673    pub project_id: ConfigSecret<String>,
674
675    /// Optional base URL for Blockfrost API, if not provided, the default URL will be determined
676    /// automatically from the project ID.
677    pub base_url: Option<String>,
678}
679
680impl FromStr for BlockfrostParameters {
681    type Err = serde_json::Error;
682
683    fn from_str(s: &str) -> Result<Self, Self::Err> {
684        serde_json::from_str(s)
685    }
686}
687
688/// Configuration of the ancillary files signer
689///
690/// **IMPORTANT**: The cryptographic scheme used is ED25519
691#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
692#[serde(rename_all = "kebab-case", tag = "type")]
693pub enum AncillaryFilesSignerConfig {
694    /// Sign with a secret key
695    SecretKey {
696        /// Hex encoded secret key
697        secret_key: HexEncodedKey,
698    },
699    /// Sign with a key stored in a Google Cloud Platform KMS account
700    GcpKms {
701        /// GCP KMS resource name
702        resource_name: GcpCryptoKeyVersionResourceName,
703        /// Environment variable containing the credentials JSON, if not set `GOOGLE_APPLICATION_CREDENTIALS_JSON` will be used
704        #[serde(default = "default_gcp_kms_credentials_json_env_var")]
705        credentials_json_env_var: String,
706    },
707}
708
709fn default_gcp_kms_credentials_json_env_var() -> String {
710    DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
711}
712
713impl FromStr for AncillaryFilesSignerConfig {
714    type Err = serde_json::Error;
715
716    fn from_str(s: &str) -> Result<Self, Self::Err> {
717        serde_json::from_str(s)
718    }
719}
720
721impl ServeCommandConfiguration {
722    /// Create a sample configuration mainly for tests
723    pub fn new_sample(tmp_path: PathBuf) -> Self {
724        let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
725            .create_verifier()
726            .to_verification_key();
727        let ancillary_files_signer_secret_key =
728            ManifestSigner::create_deterministic_signer().secret_key();
729
730        Self {
731            environment: ExecutionEnvironment::Test,
732            cardano_cli_path: PathBuf::new(),
733            cardano_node_socket_path: PathBuf::new(),
734            dmq_node_socket_path: None,
735            cardano_node_version: "0.0.1".to_string(),
736            network: "devnet".to_string(),
737            network_magic: Some(42),
738            dmq_network_magic: Some(3141592),
739            chain_observer_type: ChainObserverType::Fake,
740            protocol_parameters: Some(ProtocolParameters {
741                k: 5,
742                m: 100,
743                phi_f: 0.95,
744            }),
745            snapshot_uploader_type: SnapshotUploaderType::Local,
746            snapshot_bucket_name: None,
747            snapshot_use_cdn_domain: false,
748            server_ip: "0.0.0.0".to_string(),
749            server_port: 8000,
750            public_server_url: None,
751            run_interval: 5000,
752            db_directory: PathBuf::new(),
753            // Note: this is a band-aid solution to avoid IO operations in the `mithril-aggregator`
754            // crate directory.
755            // Know issue:
756            // - There may be collision of the `snapshot_directory` between tests. Tests that
757            // depend on the `snapshot_directory` should specify their own,
758            // and they can use the `temp_dir` macro for that.
759            snapshot_directory: tmp_path,
760            data_stores_directory: PathBuf::from(":memory:"),
761            genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
762            reset_digests_cache: false,
763            disable_digests_cache: false,
764            store_retention_limit: None,
765            era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
766            era_reader_adapter_params: None,
767            ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
768                secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
769            },
770            signed_entity_types: None,
771            snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
772            zstandard_parameters: Some(ZstandardCompressionParameters::default()),
773            blockfrost_parameters: None,
774            signer_importer_run_interval: 1,
775            allow_unparsable_block: false,
776            cardano_transactions_prover_cache_pool_size: 3,
777            cardano_transactions_database_connection_pool_size: 5,
778            cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
779                security_parameter: BlockNumber(120),
780                step: BlockNumber(15),
781            }),
782            preload_security_parameter: BlockNumber(30),
783            cardano_transactions_prover_max_hashes_allowed_by_request: 100,
784            cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
785            enable_metrics_server: true,
786            metrics_server_ip: "0.0.0.0".to_string(),
787            metrics_server_port: 9090,
788            persist_usage_report_interval_in_seconds: 10,
789            leader_aggregator_endpoint: None,
790            custom_origin_tag_white_list: None,
791            aggregate_signature_type: AggregateSignatureType::Concatenation,
792            signature_processor_wait_delay_on_error_ms: 5000,
793        }
794    }
795
796    /// Build the local server URL from configuration.
797    pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
798        SanitizedUrlWithTrailingSlash::parse(&format!(
799            "http://{}:{}/{SERVER_BASE_PATH}/",
800            self.server_ip, self.server_port
801        ))
802    }
803}
804
805impl ConfigurationSource for ServeCommandConfiguration {
806    fn environment(&self) -> ExecutionEnvironment {
807        self.environment.clone()
808    }
809
810    fn cardano_cli_path(&self) -> PathBuf {
811        self.cardano_cli_path.clone()
812    }
813
814    fn cardano_node_socket_path(&self) -> PathBuf {
815        self.cardano_node_socket_path.clone()
816    }
817
818    fn dmq_node_socket_path(&self) -> Option<PathBuf> {
819        self.dmq_node_socket_path.clone()
820    }
821
822    fn cardano_node_version(&self) -> String {
823        self.cardano_node_version.clone()
824    }
825
826    fn network(&self) -> String {
827        self.network.clone()
828    }
829
830    fn network_magic(&self) -> Option<u64> {
831        self.network_magic
832    }
833
834    fn dmq_network_magic(&self) -> Option<u64> {
835        self.dmq_network_magic
836    }
837
838    fn chain_observer_type(&self) -> ChainObserverType {
839        self.chain_observer_type.clone()
840    }
841
842    fn protocol_parameters(&self) -> Option<ProtocolParameters> {
843        self.protocol_parameters.clone()
844    }
845
846    fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
847        self.snapshot_uploader_type
848    }
849
850    fn snapshot_bucket_name(&self) -> Option<String> {
851        self.snapshot_bucket_name.clone()
852    }
853
854    fn snapshot_use_cdn_domain(&self) -> bool {
855        self.snapshot_use_cdn_domain
856    }
857
858    fn server_ip(&self) -> String {
859        self.server_ip.clone()
860    }
861
862    fn server_port(&self) -> u16 {
863        self.server_port
864    }
865
866    fn public_server_url(&self) -> Option<String> {
867        self.public_server_url.clone()
868    }
869
870    fn run_interval(&self) -> u64 {
871        self.run_interval
872    }
873
874    fn db_directory(&self) -> PathBuf {
875        self.db_directory.clone()
876    }
877
878    fn snapshot_directory(&self) -> PathBuf {
879        self.snapshot_directory.clone()
880    }
881
882    fn data_stores_directory(&self) -> PathBuf {
883        self.data_stores_directory.clone()
884    }
885
886    fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
887        self.genesis_verification_key.clone()
888    }
889
890    fn reset_digests_cache(&self) -> bool {
891        self.reset_digests_cache
892    }
893
894    fn disable_digests_cache(&self) -> bool {
895        self.disable_digests_cache
896    }
897
898    fn store_retention_limit(&self) -> Option<usize> {
899        self.store_retention_limit
900    }
901
902    fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
903        self.era_reader_adapter_type.clone()
904    }
905
906    fn era_reader_adapter_params(&self) -> Option<String> {
907        self.era_reader_adapter_params.clone()
908    }
909
910    fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
911        self.ancillary_files_signer_config.clone()
912    }
913
914    fn signed_entity_types(&self) -> Option<String> {
915        self.signed_entity_types.clone()
916    }
917
918    fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
919        self.snapshot_compression_algorithm
920    }
921
922    fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
923        self.zstandard_parameters
924    }
925
926    fn blockfrost_parameters(&self) -> Option<BlockfrostParameters> {
927        self.blockfrost_parameters.clone()
928    }
929
930    fn signer_importer_run_interval(&self) -> u64 {
931        self.signer_importer_run_interval
932    }
933
934    fn allow_unparsable_block(&self) -> bool {
935        self.allow_unparsable_block
936    }
937
938    fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
939        self.cardano_transactions_prover_cache_pool_size
940    }
941
942    fn cardano_transactions_database_connection_pool_size(&self) -> usize {
943        self.cardano_transactions_database_connection_pool_size
944    }
945
946    fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
947        self.cardano_transactions_signing_config.clone()
948    }
949
950    fn preload_security_parameter(&self) -> BlockNumber {
951        self.preload_security_parameter
952    }
953
954    fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize {
955        self.cardano_transactions_prover_max_hashes_allowed_by_request
956    }
957
958    fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
959        self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
960    }
961
962    fn enable_metrics_server(&self) -> bool {
963        self.enable_metrics_server
964    }
965
966    fn metrics_server_ip(&self) -> String {
967        self.metrics_server_ip.clone()
968    }
969
970    fn metrics_server_port(&self) -> u16 {
971        self.metrics_server_port
972    }
973
974    fn persist_usage_report_interval_in_seconds(&self) -> u64 {
975        self.persist_usage_report_interval_in_seconds
976    }
977
978    fn leader_aggregator_endpoint(&self) -> Option<String> {
979        self.leader_aggregator_endpoint.clone()
980    }
981
982    fn custom_origin_tag_white_list(&self) -> Option<String> {
983        self.custom_origin_tag_white_list.clone()
984    }
985
986    fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
987        match &self.public_server_url {
988            Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
989            None => self.get_local_server_url(),
990        }
991    }
992
993    fn aggregate_signature_type(&self) -> AggregateSignatureType {
994        self.aggregate_signature_type
995    }
996
997    fn signature_processor_wait_delay_on_error_ms(&self) -> u64 {
998        self.signature_processor_wait_delay_on_error_ms
999    }
1000}
1001
1002/// Default configuration with all the default values for configurations.
1003#[derive(Debug, Clone, DocumenterDefault)]
1004pub struct DefaultConfiguration {
1005    /// Execution environment
1006    pub environment: ExecutionEnvironment,
1007
1008    /// Server listening IP
1009    pub server_ip: String,
1010
1011    /// Server listening port
1012    pub server_port: String,
1013
1014    /// Directory of the Cardano node database
1015    pub db_directory: String,
1016
1017    /// Directory to store snapshot
1018    pub snapshot_directory: String,
1019
1020    /// Type of snapshot uploader to use
1021    pub snapshot_uploader_type: String,
1022
1023    /// Era reader adapter type
1024    pub era_reader_adapter_type: String,
1025
1026    /// Chain observer type
1027    pub chain_observer_type: String,
1028
1029    /// ImmutableDigesterCacheProvider default setting
1030    pub reset_digests_cache: String,
1031
1032    /// ImmutableDigesterCacheProvider default setting
1033    pub disable_digests_cache: String,
1034
1035    /// Snapshot compression algorithm default setting
1036    pub snapshot_compression_algorithm: String,
1037
1038    /// Use CDN domain to construct snapshot urls default setting (if snapshot_uploader_type is Gcp)
1039    pub snapshot_use_cdn_domain: String,
1040
1041    /// Signer importer run interval default setting
1042    pub signer_importer_run_interval: u64,
1043
1044    /// If set no error is returned in case of unparsable block and an error log is written instead.
1045    ///
1046    /// Will be ignored on (pre)production networks.
1047    pub allow_unparsable_block: String,
1048
1049    /// Cardano transactions prover cache pool size
1050    pub cardano_transactions_prover_cache_pool_size: u32,
1051
1052    /// Cardano transactions database connection pool size
1053    pub cardano_transactions_database_connection_pool_size: u32,
1054
1055    /// Cardano transactions signing configuration
1056    pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1057
1058    /// Blocks offset, from the tip of the chain, to exclude during the Cardano transactions preload
1059    pub preload_security_parameter: u64,
1060
1061    /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions
1062    pub cardano_transactions_prover_max_hashes_allowed_by_request: u32,
1063
1064    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
1065    pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
1066
1067    /// Enable metrics server (Prometheus endpoint on /metrics).
1068    pub enable_metrics_server: String,
1069
1070    /// Metrics HTTP server IP.
1071    pub metrics_server_ip: String,
1072
1073    /// Metrics HTTP server listening port.
1074    pub metrics_server_port: u16,
1075
1076    /// Time interval at which metrics are persisted in event database (in seconds).
1077    pub persist_usage_report_interval_in_seconds: u64,
1078
1079    /// Aggregate signature type used to create certificates
1080    pub aggregate_signature_type: String,
1081
1082    /// Delay to wait between two signature processing attempts after an error
1083    pub signature_processor_wait_delay_on_error_ms: u64,
1084}
1085
1086impl Default for DefaultConfiguration {
1087    fn default() -> Self {
1088        Self {
1089            environment: ExecutionEnvironment::Production,
1090            server_ip: "0.0.0.0".to_string(),
1091            server_port: "8080".to_string(),
1092            db_directory: "/db".to_string(),
1093            snapshot_directory: ".".to_string(),
1094            snapshot_uploader_type: "gcp".to_string(),
1095            era_reader_adapter_type: "bootstrap".to_string(),
1096            chain_observer_type: "pallas".to_string(),
1097            reset_digests_cache: "false".to_string(),
1098            disable_digests_cache: "false".to_string(),
1099            snapshot_compression_algorithm: "zstandard".to_string(),
1100            snapshot_use_cdn_domain: "false".to_string(),
1101            signer_importer_run_interval: 720,
1102            allow_unparsable_block: "false".to_string(),
1103            cardano_transactions_prover_cache_pool_size: 10,
1104            cardano_transactions_database_connection_pool_size: 10,
1105            cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
1106                security_parameter: BlockNumber(3000),
1107                step: BlockNumber(120),
1108            },
1109            preload_security_parameter: 2160,
1110            cardano_transactions_prover_max_hashes_allowed_by_request: 100,
1111            cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
1112            enable_metrics_server: "false".to_string(),
1113            metrics_server_ip: "0.0.0.0".to_string(),
1114            metrics_server_port: 9090,
1115            persist_usage_report_interval_in_seconds: 10,
1116            aggregate_signature_type: "Concatenation".to_string(),
1117            signature_processor_wait_delay_on_error_ms: 1000,
1118        }
1119    }
1120}
1121
1122impl DefaultConfiguration {
1123    fn namespace() -> String {
1124        "default configuration".to_string()
1125    }
1126}
1127
1128impl From<ExecutionEnvironment> for ValueKind {
1129    fn from(value: ExecutionEnvironment) -> Self {
1130        match value {
1131            ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
1132            ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
1133        }
1134    }
1135}
1136
1137impl Source for DefaultConfiguration {
1138    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
1139        Box::new(self.clone())
1140    }
1141
1142    fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1143        let mut result = Map::new();
1144
1145        let namespace = DefaultConfiguration::namespace();
1146
1147        let myself = self.clone();
1148        register_config_value!(result, &namespace, myself.environment);
1149        register_config_value!(result, &namespace, myself.server_ip);
1150        register_config_value!(result, &namespace, myself.server_port);
1151        register_config_value!(result, &namespace, myself.db_directory);
1152        register_config_value!(result, &namespace, myself.snapshot_directory);
1153        register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1154        register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1155        register_config_value!(result, &namespace, myself.reset_digests_cache);
1156        register_config_value!(result, &namespace, myself.disable_digests_cache);
1157        register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1158        register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1159        register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1160        register_config_value!(result, &namespace, myself.allow_unparsable_block);
1161        register_config_value!(
1162            result,
1163            &namespace,
1164            myself.cardano_transactions_prover_cache_pool_size
1165        );
1166        register_config_value!(
1167            result,
1168            &namespace,
1169            myself.cardano_transactions_database_connection_pool_size
1170        );
1171        register_config_value!(
1172            result,
1173            &namespace,
1174            myself.cardano_transactions_prover_max_hashes_allowed_by_request
1175        );
1176        register_config_value!(
1177            result,
1178            &namespace,
1179            myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1180        );
1181        register_config_value!(result, &namespace, myself.enable_metrics_server);
1182        register_config_value!(result, &namespace, myself.metrics_server_ip);
1183        register_config_value!(result, &namespace, myself.metrics_server_port);
1184        register_config_value!(
1185            result,
1186            &namespace,
1187            myself.persist_usage_report_interval_in_seconds
1188        );
1189        register_config_value!(result, &namespace, myself.preload_security_parameter);
1190        register_config_value!(
1191            result,
1192            &namespace,
1193            myself.cardano_transactions_signing_config,
1194            |v: CardanoTransactionsSigningConfig| HashMap::from([
1195                (
1196                    "security_parameter".to_string(),
1197                    ValueKind::from(*v.security_parameter),
1198                ),
1199                ("step".to_string(), ValueKind::from(*v.step),)
1200            ])
1201        );
1202        register_config_value!(result, &namespace, myself.aggregate_signature_type);
1203        register_config_value!(
1204            result,
1205            &namespace,
1206            myself.signature_processor_wait_delay_on_error_ms
1207        );
1208        Ok(result)
1209    }
1210}
1211
1212#[cfg(test)]
1213mod test {
1214    use mithril_common::temp_dir;
1215    use mithril_common::test::double::fake_data;
1216
1217    use super::*;
1218
1219    #[test]
1220    fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1221        for limit in 4..=10u64 {
1222            let configuration = ServeCommandConfiguration {
1223                store_retention_limit: Some(limit as usize),
1224                ..ServeCommandConfiguration::new_sample(temp_dir!())
1225            };
1226            assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1227        }
1228    }
1229
1230    #[test]
1231    fn safe_epoch_retention_limit_wont_change_a_none_value() {
1232        let configuration = ServeCommandConfiguration {
1233            store_retention_limit: None,
1234            ..ServeCommandConfiguration::new_sample(temp_dir!())
1235        };
1236        assert_eq!(configuration.safe_epoch_retention_limit(), None);
1237    }
1238
1239    #[test]
1240    fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1241        for limit in 0..=3 {
1242            let configuration = ServeCommandConfiguration {
1243                store_retention_limit: Some(limit),
1244                ..ServeCommandConfiguration::new_sample(temp_dir!())
1245            };
1246            assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1247        }
1248    }
1249
1250    #[test]
1251    fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1252        #[derive(Debug, Deserialize)]
1253        struct TargetConfig {
1254            cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1255        }
1256
1257        let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1258        let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1259
1260        assert_eq!(
1261            target.cardano_transactions_signing_config,
1262            DefaultConfiguration::default().cardano_transactions_signing_config
1263        );
1264    }
1265
1266    #[test]
1267    fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1268        let config = ServeCommandConfiguration {
1269            signed_entity_types: None,
1270            ..ServeCommandConfiguration::new_sample(temp_dir!())
1271        };
1272
1273        assert_eq!(
1274            config.compute_allowed_signed_entity_types_discriminants().unwrap(),
1275            BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1276        );
1277    }
1278
1279    #[test]
1280    fn allow_http_serve_directory() {
1281        let config = ServeCommandConfiguration {
1282            snapshot_uploader_type: SnapshotUploaderType::Local,
1283            ..ServeCommandConfiguration::new_sample(temp_dir!())
1284        };
1285
1286        assert!(config.allow_http_serve_directory());
1287
1288        let config = ServeCommandConfiguration {
1289            snapshot_uploader_type: SnapshotUploaderType::Gcp,
1290            ..ServeCommandConfiguration::new_sample(temp_dir!())
1291        };
1292
1293        assert!(!config.allow_http_serve_directory());
1294    }
1295
1296    #[test]
1297    fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1298        let config = ServeCommandConfiguration {
1299            server_ip: "1.2.3.4".to_string(),
1300            server_port: 5678,
1301            public_server_url: None,
1302            ..ServeCommandConfiguration::new_sample(temp_dir!())
1303        };
1304
1305        assert_eq!(
1306            config.get_server_url().unwrap().as_str(),
1307            &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1308        );
1309    }
1310
1311    #[test]
1312    fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1313        let config = ServeCommandConfiguration {
1314            server_ip: "1.2.3.4".to_string(),
1315            server_port: 5678,
1316            public_server_url: Some("https://example.com".to_string()),
1317            ..ServeCommandConfiguration::new_sample(temp_dir!())
1318        };
1319
1320        assert_eq!(
1321            config.get_server_url().unwrap().as_str(),
1322            "https://example.com/"
1323        );
1324    }
1325
1326    #[test]
1327    fn joining_to_local_server_url_keep_base_path() {
1328        let config = ServeCommandConfiguration {
1329            server_ip: "1.2.3.4".to_string(),
1330            server_port: 6789,
1331            public_server_url: None,
1332            ..ServeCommandConfiguration::new_sample(temp_dir!())
1333        };
1334
1335        let joined_url = config.get_local_server_url().unwrap().join("some/path").unwrap();
1336        assert!(
1337            joined_url.as_str().contains(SERVER_BASE_PATH),
1338            "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1339        );
1340    }
1341
1342    #[test]
1343    fn joining_to_public_server_url_without_trailing_slash() {
1344        let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1345        let config = ServeCommandConfiguration {
1346            public_server_url: Some(format!(
1347                "https://example.com/{subpath_without_trailing_slash}"
1348            )),
1349            ..ServeCommandConfiguration::new_sample(temp_dir!())
1350        };
1351
1352        let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1353        assert!(
1354            joined_url.as_str().contains(subpath_without_trailing_slash),
1355            "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1356        );
1357    }
1358
1359    #[test]
1360    fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1361        let config = ServeCommandConfiguration {
1362            leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1363            ..ServeCommandConfiguration::new_sample(temp_dir!())
1364        };
1365
1366        assert!(config.is_follower_aggregator());
1367    }
1368
1369    #[test]
1370    fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1371        let config = ServeCommandConfiguration {
1372            leader_aggregator_endpoint: None,
1373            ..ServeCommandConfiguration::new_sample(temp_dir!())
1374        };
1375
1376        assert!(!config.is_follower_aggregator());
1377    }
1378
1379    #[test]
1380    fn deserializing_blockfrost_parameters() {
1381        let deserialized_without_base_url: BlockfrostParameters =
1382            serde_json::from_str(r#"{ "project_id": "preprodWuV1ICdtOWfZYf" }"#).unwrap();
1383        assert_eq!(
1384            deserialized_without_base_url,
1385            BlockfrostParameters {
1386                project_id: ConfigSecret::new("preprodWuV1ICdtOWfZYf".to_string()),
1387                base_url: None,
1388            }
1389        );
1390
1391        let deserialized_with_base_url: BlockfrostParameters = serde_json::from_str(
1392            r#"{ "project_id": "preprodWuV1ICdtOWfZYf", "base_url": "https://test.foo.bar" }"#,
1393        )
1394        .unwrap();
1395        assert_eq!(
1396            deserialized_with_base_url,
1397            BlockfrostParameters {
1398                project_id: ConfigSecret::new("preprodWuV1ICdtOWfZYf".to_string()),
1399                base_url: Some("https://test.foo.bar".to_string()),
1400            }
1401        );
1402    }
1403
1404    mod get_leader_aggregator_epoch_settings_configuration {
1405        use super::*;
1406
1407        #[test]
1408        fn succeed_when_cardano_transactions_is_disabled_and_cardano_transactions_signing_config_is_not_set()
1409         {
1410            let epoch_settings = ServeCommandConfiguration {
1411                signed_entity_types: None,
1412                cardano_transactions_signing_config: None,
1413                protocol_parameters: Some(ProtocolParameters::new(1, 2, 3.1)),
1414                ..ServeCommandConfiguration::new_sample(temp_dir!())
1415            }
1416            .get_leader_aggregator_epoch_settings_configuration()
1417            .unwrap();
1418
1419            assert_eq!(
1420                AggregatorEpochSettings {
1421                    protocol_parameters: ProtocolParameters::new(1, 2, 3.1),
1422                    cardano_transactions_signing_config: None
1423                },
1424                epoch_settings
1425            );
1426        }
1427
1428        #[test]
1429        fn succeed_when_cardano_transactions_is_enabled_and_cardano_transactions_signing_config_is_set()
1430         {
1431            let epoch_settings = ServeCommandConfiguration {
1432                signed_entity_types: Some(
1433                    SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1434                ),
1435                cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1436                    security_parameter: BlockNumber(10),
1437                    step: BlockNumber(30),
1438                }),
1439                protocol_parameters: Some(ProtocolParameters::new(2, 3, 4.1)),
1440                ..ServeCommandConfiguration::new_sample(temp_dir!())
1441            }
1442            .get_leader_aggregator_epoch_settings_configuration()
1443            .unwrap();
1444
1445            assert_eq!(
1446                AggregatorEpochSettings {
1447                    protocol_parameters: ProtocolParameters::new(2, 3, 4.1),
1448                    cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1449                        security_parameter: BlockNumber(10),
1450                        step: BlockNumber(30),
1451                    },)
1452                },
1453                epoch_settings
1454            );
1455        }
1456
1457        #[test]
1458        fn fails_when_cardano_transactions_is_enabled_without_associated_config() {
1459            let error = ServeCommandConfiguration {
1460                cardano_transactions_signing_config: None,
1461                signed_entity_types: Some(
1462                    SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1463                ),
1464                protocol_parameters: Some(fake_data::protocol_parameters()),
1465                ..ServeCommandConfiguration::new_sample(temp_dir!())
1466            }
1467            .get_leader_aggregator_epoch_settings_configuration()
1468            .unwrap_err();
1469
1470            assert!(
1471                error
1472                    .to_string()
1473                    .contains("Configuration `cardano_transactions_signing_config` is mandatory")
1474            );
1475        }
1476    }
1477
1478    #[test]
1479    fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value()
1480     {
1481        let serialized_json = r#"{
1482            "type": "secret-key",
1483            "secret_key": "whatever"
1484        }"#;
1485
1486        let deserialized: AncillaryFilesSignerConfig =
1487            serde_json::from_str(serialized_json).unwrap();
1488        assert_eq!(
1489            deserialized,
1490            AncillaryFilesSignerConfig::SecretKey {
1491                secret_key: "whatever".to_string()
1492            }
1493        );
1494    }
1495
1496    #[test]
1497    fn deserializing_ancillary_signing_gcp_kms_configuration() {
1498        let serialized_json = r#"{
1499            "type": "gcp-kms",
1500            "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1501            "credentials_json_env_var": "CUSTOM_ENV_VAR"
1502        }"#;
1503
1504        let deserialized: AncillaryFilesSignerConfig =
1505            serde_json::from_str(serialized_json).unwrap();
1506        assert_eq!(
1507            deserialized,
1508            AncillaryFilesSignerConfig::GcpKms {
1509                resource_name: GcpCryptoKeyVersionResourceName {
1510                    project: "123456789".to_string(),
1511                    location: "global".to_string(),
1512                    key_ring: "my-keyring".to_string(),
1513                    key_name: "my-key".to_string(),
1514                    version: "1".to_string(),
1515                },
1516                credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1517            }
1518        );
1519    }
1520
1521    #[test]
1522    fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default()
1523     {
1524        let serialized_json = r#"{
1525            "type": "gcp-kms",
1526            "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1527        }"#;
1528
1529        let deserialized: AncillaryFilesSignerConfig =
1530            serde_json::from_str(serialized_json).unwrap();
1531        if let AncillaryFilesSignerConfig::GcpKms {
1532            credentials_json_env_var,
1533            ..
1534        } = deserialized
1535        {
1536            assert_eq!(
1537                credentials_json_env_var,
1538                DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1539            );
1540        } else {
1541            panic!("Expected GcpKms variant but got {deserialized:?}");
1542        }
1543    }
1544
1545    mod origin_tag {
1546        use super::*;
1547
1548        #[test]
1549        fn default_origin_tag_white_list_is_not_empty() {
1550            let config = ServeCommandConfiguration {
1551                custom_origin_tag_white_list: None,
1552                ..ServeCommandConfiguration::new_sample(temp_dir!())
1553            };
1554            assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1555        }
1556
1557        #[test]
1558        fn custom_origin_tag_are_added_to_default_white_list() {
1559            let config = ServeCommandConfiguration {
1560                custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1561                ..ServeCommandConfiguration::new_sample(temp_dir!())
1562            };
1563
1564            let default_white_list = ServeCommandConfiguration {
1565                custom_origin_tag_white_list: None,
1566                ..ServeCommandConfiguration::new_sample(temp_dir!())
1567            }
1568            .compute_origin_tag_white_list();
1569
1570            let mut expected_white_list = default_white_list.clone();
1571            assert!(expected_white_list.insert("TAG_A".to_string()));
1572            assert!(expected_white_list.insert("TAG_B".to_string()));
1573            assert!(expected_white_list.insert("TAG_C".to_string()));
1574
1575            assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1576        }
1577    }
1578}