mithril_aggregator/
configuration.rs

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