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, CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig,
14    CompressionAlgorithm, ConfigSecret, HexEncodedGenesisVerificationKey, HexEncodedKey,
15    ProtocolParameters, SignedEntityConfig, 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 blocks and transactions prover cache pool size
242    fn cardano_blocks_transactions_prover_cache_pool_size(&self) -> usize {
243        panic!("cardano_blocks_transactions_prover_cache_pool_size is not implemented.");
244    }
245
246    /// Cardano and transactions database connection pool size
247    fn cardano_blocks_transactions_database_connection_pool_size(&self) -> usize {
248        panic!("cardano_blocks_transactions_database_connection_pool_size is not implemented.");
249    }
250
251    /// Cardano transactions prover cache pool size
252    fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
253        panic!("cardano_transactions_prover_cache_pool_size is not implemented.");
254    }
255
256    /// Cardano transactions database connection pool size
257    fn cardano_transactions_database_connection_pool_size(&self) -> usize {
258        panic!("cardano_transactions_database_connection_pool_size is not implemented.");
259    }
260
261    /// Cardano transactions signing configuration
262    fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
263        panic!("cardano_transactions_signing_config is not implemented.");
264    }
265
266    /// Cardano blocks and transactions signing configuration
267    fn cardano_blocks_transactions_signing_config(
268        &self,
269    ) -> Option<CardanoBlocksTransactionsSigningConfig> {
270        panic!("cardano_blocks_transactions_signing_config is not implemented.");
271    }
272
273    /// Blocks offset, from the tip of the chain, to exclude during the cardano transactions preload
274    fn preload_security_parameter(&self) -> BlockNumber {
275        panic!("preload_security_parameter is not implemented.");
276    }
277
278    /// Maximum number of hashes allowed by request to the Cardano prover.
279    ///
280    /// This unified limit is applied to both transaction-hash and block-hash proving routes.
281    fn cardano_prover_max_hashes_allowed_by_request(&self) -> usize {
282        panic!("cardano_prover_max_hashes_allowed_by_request is not implemented.");
283    }
284
285    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
286    fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
287        panic!(
288            "cardano_transactions_block_streamer_max_roll_forwards_per_poll is not implemented."
289        );
290    }
291
292    /// Minimum duration between two consecutive block streamer polls in milliseconds.
293    fn cardano_transactions_block_streamer_throttling_interval(&self) -> Option<u64> {
294        panic!("cardano_transactions_block_streamer_throttling_interval is not implemented.");
295    }
296
297    /// Enable metrics server (Prometheus endpoint on /metrics).
298    fn enable_metrics_server(&self) -> bool {
299        panic!("enable_metrics_server is not implemented.");
300    }
301
302    /// Metrics HTTP Server IP.
303    fn metrics_server_ip(&self) -> String {
304        panic!("metrics_server_ip is not implemented.");
305    }
306
307    /// Metrics HTTP Server listening port.
308    fn metrics_server_port(&self) -> u16 {
309        panic!("metrics_server_port is not implemented.");
310    }
311
312    /// Time interval at which usage metrics are persisted in event database (in seconds).
313    fn persist_usage_report_interval_in_seconds(&self) -> u64 {
314        panic!("persist_usage_report_interval_in_seconds is not implemented.");
315    }
316
317    /// Leader aggregator endpoint
318    ///
319    /// This is the endpoint of the aggregator that will be used to fetch the latest epoch settings
320    /// and store the signer registrations when the aggregator is running in a follower mode.
321    /// If this is not set, the aggregator will run in a leader mode.
322    fn leader_aggregator_endpoint(&self) -> Option<String> {
323        panic!("leader_aggregator_endpoint is not implemented.");
324    }
325
326    /// Custom origin tag of client request added to the whitelist (comma
327    /// separated list).
328    fn custom_origin_tag_white_list(&self) -> Option<String> {
329        panic!("custom_origin_tag_white_list is not implemented.");
330    }
331
332    /// Get the server URL.
333    fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
334        panic!("get_server_url is not implemented.");
335    }
336
337    /// Get a representation of the Cardano network.
338    fn get_network(&self) -> StdResult<CardanoNetwork> {
339        CardanoNetwork::from_code(self.network(), self.network_magic())
340            .with_context(|| "Invalid network configuration")
341    }
342
343    /// Get a representation of the DMQ network.
344    fn get_dmq_network(&self) -> StdResult<DmqNetwork> {
345        DmqNetwork::from_code(self.network(), self.dmq_network_magic())
346            .with_context(|| "Invalid DMQ network configuration")
347    }
348
349    /// Get the directory of the SQLite stores.
350    fn get_sqlite_dir(&self) -> PathBuf {
351        let store_dir = &self.data_stores_directory();
352
353        if !store_dir.exists() {
354            std::fs::create_dir_all(store_dir).unwrap();
355        }
356
357        self.data_stores_directory()
358    }
359
360    /// Get the snapshots directory.
361    fn get_snapshot_dir(&self) -> StdResult<PathBuf> {
362        if !&self.snapshot_directory().exists() {
363            std::fs::create_dir_all(self.snapshot_directory())?;
364        }
365
366        Ok(self.snapshot_directory())
367    }
368
369    /// Get the safe epoch retention limit.
370    fn safe_epoch_retention_limit(&self) -> Option<u64> {
371        self.store_retention_limit()
372            .map(|limit| if limit > 3 { limit as u64 } else { 3 })
373    }
374
375    /// Compute the list of signed entity discriminants that are allowed to be processed.
376    fn compute_allowed_signed_entity_types_discriminants(
377        &self,
378    ) -> StdResult<BTreeSet<SignedEntityTypeDiscriminants>> {
379        let allowed_discriminants = self
380            .signed_entity_types()
381            .as_ref()
382            .map(SignedEntityTypeDiscriminants::parse_list)
383            .transpose()
384            .with_context(|| "Invalid 'signed_entity_types' configuration")?
385            .unwrap_or_default();
386        let allowed_discriminants =
387            SignedEntityConfig::append_allowed_signed_entity_types_discriminants(
388                allowed_discriminants,
389            );
390
391        Ok(allowed_discriminants)
392    }
393
394    /// Check if the HTTP server can serve static directories.
395    fn allow_http_serve_directory(&self) -> bool {
396        match self.snapshot_uploader_type() {
397            SnapshotUploaderType::Local => true,
398            SnapshotUploaderType::Gcp => false,
399        }
400    }
401
402    /// `leader aggregator only` Infer the [AggregatorEpochSettings] from the configuration.
403    fn get_leader_aggregator_epoch_settings_configuration(
404        &self,
405    ) -> StdResult<AggregatorEpochSettings> {
406        let allowed_discriminants = self.compute_allowed_signed_entity_types_discriminants()?;
407
408        let cardano_transactions_signing_config = if allowed_discriminants
409            .contains(&SignedEntityTypeDiscriminants::CardanoTransactions)
410        {
411            let cardano_transactions_signing_config =
412                self.cardano_transactions_signing_config().with_context(
413                    || "Configuration `cardano_transactions_signing_config` is mandatory for a Leader Aggregator when `CardanoTransactions` is enabled in `signed_entity_types`"
414                )?;
415            Some(cardano_transactions_signing_config)
416        } else {
417            None
418        };
419
420        let cardano_blocks_transactions_signing_config = if allowed_discriminants
421            .contains(&SignedEntityTypeDiscriminants::CardanoBlocksTransactions)
422        {
423            let cardano_blocks_transactions_signing_config =
424                self.cardano_blocks_transactions_signing_config().with_context(
425                    || "Configuration `cardano_blocks_transactions_signing_config` is mandatory for a Leader Aggregator when `CardanoBlocksTransactions` is enabled in `signed_entity_types`"
426                )?;
427            Some(cardano_blocks_transactions_signing_config)
428        } else {
429            None
430        };
431
432        Ok(AggregatorEpochSettings {
433            protocol_parameters: self.protocol_parameters().with_context(
434                || "Configuration `protocol_parameters` is mandatory for a Leader Aggregator",
435            )?,
436            cardano_transactions_signing_config,
437            cardano_blocks_transactions_signing_config,
438        })
439    }
440
441    /// Check if the aggregator is running in follower mode.
442    fn is_follower_aggregator(&self) -> bool {
443        self.leader_aggregator_endpoint().is_some()
444    }
445
446    /// White list for origin client request.
447    fn compute_origin_tag_white_list(&self) -> HashSet<String> {
448        let mut white_list = HashSet::from([
449            "EXPLORER".to_string(),
450            "BENCHMARK".to_string(),
451            "CI".to_string(),
452            "NA".to_string(),
453        ]);
454        if let Some(custom_tags) = &self.custom_origin_tag_white_list() {
455            white_list.extend(custom_tags.split(',').map(|tag| tag.trim().to_string()));
456        }
457
458        white_list
459    }
460
461    /// Aggregate signature type
462    fn aggregate_signature_type(&self) -> AggregateSignatureType {
463        panic!("get_aggregate_signature_type is not implemented.");
464    }
465
466    /// Signature processor wait delay on error in milliseconds
467    fn signature_processor_wait_delay_on_error_ms(&self) -> u64 {
468        panic!("signature_processor_wait_delay_on_error_ms is not implemented.");
469    }
470}
471
472/// Serve command configuration
473#[derive(Debug, Clone, Deserialize, Documenter)]
474pub struct ServeCommandConfiguration {
475    /// What kind of runtime environment the configuration is meant to.
476    pub environment: ExecutionEnvironment,
477
478    /// Cardano CLI tool path
479    #[example = "`cardano-cli`"]
480    pub cardano_cli_path: PathBuf,
481
482    /// Path of the socket opened by the Cardano node
483    #[example = "`/ipc/node.socket`"]
484    pub cardano_node_socket_path: PathBuf,
485
486    /// Path of the socket opened by the DMQ node
487    #[example = "`/ipc/dmq.socket`"]
488    pub dmq_node_socket_path: Option<PathBuf>,
489
490    /// Cardano node version.
491    ///
492    /// **NOTE**: This cannot be verified for now (see [this
493    /// issue](https://github.com/input-output-hk/cardano-cli/issues/224)). This
494    /// is why it has to be manually given to the Aggregator
495    pub cardano_node_version: String,
496
497    /// Cardano network
498    #[example = "`mainnet` or `preprod` or `devnet`"]
499    pub network: String,
500
501    /// Cardano network magic number
502    ///
503    /// useful for TestNet & DevNet
504    #[example = "`1097911063` or `42`"]
505    pub network_magic: Option<u64>,
506
507    /// Dmq network magic number
508    ///
509    /// useful for TestNet & DevNet
510    #[example = "`1097911063` or `42`"]
511    pub dmq_network_magic: Option<u64>,
512
513    /// Cardano chain observer type
514    pub chain_observer_type: ChainObserverType,
515
516    /// Protocol parameters
517    #[example = "`{ k: 5, m: 100, phi_f: 0.65 }`"]
518    pub protocol_parameters: Option<ProtocolParameters>,
519
520    /// Type of snapshot uploader to use
521    #[example = "`gcp` or `local`"]
522    pub snapshot_uploader_type: SnapshotUploaderType,
523
524    /// Bucket name where the snapshots are stored if snapshot_uploader_type is Gcp
525    pub snapshot_bucket_name: Option<String>,
526
527    /// Use CDN domain to construct snapshot urls if snapshot_uploader_type is Gcp
528    pub snapshot_use_cdn_domain: bool,
529
530    /// Server listening IP
531    pub server_ip: String,
532
533    /// Server listening port
534    pub server_port: u16,
535
536    /// Server URL that can be accessed from the outside
537    pub public_server_url: Option<String>,
538
539    /// Run Interval is the interval between two runtime cycles in ms
540    #[example = "`60000`"]
541    pub run_interval: u64,
542
543    /// Directory of the Cardano node store.
544    pub db_directory: PathBuf,
545
546    /// Directory to store snapshot
547    pub snapshot_directory: PathBuf,
548
549    /// Directory to store aggregator databases
550    #[example = "`./mithril-aggregator/stores`"]
551    pub data_stores_directory: PathBuf,
552
553    /// Genesis verification key
554    pub genesis_verification_key: HexEncodedGenesisVerificationKey,
555
556    /// Should the immutable cache be reset or not
557    pub reset_digests_cache: bool,
558
559    /// Use the digest caching strategy
560    pub disable_digests_cache: bool,
561
562    /// Max number of records in stores.
563    /// When new records are added, oldest records are automatically deleted so
564    /// there can always be at max the number of records specified by this
565    /// setting.
566    pub store_retention_limit: Option<usize>,
567
568    /// Era reader adapter type
569    pub era_reader_adapter_type: EraReaderAdapterType,
570
571    /// Era reader adapter parameters
572    pub era_reader_adapter_params: Option<String>,
573
574    /// Configuration of the ancillary files signer
575    ///
576    /// Can either be a secret key or a key stored in a Google Cloud Platform KMS account.
577    ///
578    /// **IMPORTANT**: The cryptographic scheme used is ED25519
579    #[example = "\
580    - secret-key:<br/>`{ \"type\": \"secret-key\", \"secret_key\": \"136372c3138312c3138382c3130352c3233312c3135\" }`<br/>\
581    - Gcp kms:<br/>`{ \"type\": \"gcp-kms\", \"resource_name\": \"projects/project_name/locations/_location_name/keyRings/key_ring_name/cryptoKeys/key_name/cryptoKeyVersions/key_version\" }`\
582    "]
583    #[serde(deserialize_with = "serde_deserialization::string_or_struct")]
584    pub ancillary_files_signer_config: AncillaryFilesSignerConfig,
585
586    /// Signed entity types parameters (discriminants names in an ordered, case-sensitive, comma
587    /// separated list).
588    ///
589    /// The value `MithrilStakeDistribution` is prepended is automatically to the list.
590    #[example = "`CardanoImmutableFilesFull,CardanoStakeDistribution,CardanoDatabase,CardanoTransactions`"]
591    pub signed_entity_types: Option<String>,
592
593    /// Compression algorithm used for the snapshot archive artifacts.
594    #[example = "`gzip` or `zstandard`"]
595    pub snapshot_compression_algorithm: CompressionAlgorithm,
596
597    /// Specific parameters when [snapshot_compression_algorithm][Self::snapshot_compression_algorithm]
598    /// is set to [zstandard][CompressionAlgorithm::Zstandard].
599    #[example = "`{ level: 9, number_of_workers: 4 }`"]
600    pub zstandard_parameters: Option<ZstandardCompressionParameters>,
601
602    /// Optional parameters to connect to the Blockfrost API. Used to fetch the ticker and name of
603    /// the registered stake pools.
604    ///
605    /// `base_url` (optional) allows you to override the default URL, which is otherwise automatically determined from the project ID.
606    #[example = "\
607    `{ \"project_id\": \"preprodWuV1ICdtOWfZYfdcxpZ0tsS1N9rVZomQ\" }`<br/>\
608    or `{ \"project_id\": \"preprodWuV1ICdtOWfZYfdcxpZ0tsS1N9rVZomQ\", \"base_url\": \"https://your-custom-blockfrost-server.io/api/v0/\" }`\
609    "]
610    #[serde(
611        default,
612        deserialize_with = "serde_deserialization::string_or_struct_optional"
613    )]
614    pub blockfrost_parameters: Option<BlockfrostParameters>,
615
616    /// Time interval at which the pools names and ticker in blockfrost will be imported (in minutes).
617    pub signer_importer_run_interval: u64,
618
619    /// If set no error is returned in case of unparsable block and an error log is written instead.
620    ///
621    /// Will be ignored on (pre)production networks.
622    pub allow_unparsable_block: bool,
623
624    /// Cardano blocks and transactions prover cache pool size
625    pub cardano_blocks_transactions_prover_cache_pool_size: usize,
626
627    /// Cardano blocks and transactions database connection pool size
628    pub cardano_blocks_transactions_database_connection_pool_size: usize,
629
630    /// Cardano transactions prover cache pool size
631    pub cardano_transactions_prover_cache_pool_size: usize,
632
633    /// Cardano transactions database connection pool size
634    pub cardano_transactions_database_connection_pool_size: usize,
635
636    /// Cardano transactions signing configuration
637    #[example = "`{ security_parameter: 3000, step: 120 }`"]
638    pub cardano_transactions_signing_config: Option<CardanoTransactionsSigningConfig>,
639
640    /// Cardano blocks and transactions signing configuration
641    #[example = "`{ security_parameter: 3000, step: 120 }`"]
642    pub cardano_blocks_transactions_signing_config: Option<CardanoBlocksTransactionsSigningConfig>,
643
644    /// Blocks offset, from the tip of the chain, to exclude during the Cardano transactions preload,
645    /// default to 2160.
646    #[example = "`2160`"]
647    pub preload_security_parameter: BlockNumber,
648
649    /// Maximum number of hashes allowed by request to the Cardano prover.
650    ///
651    /// This unified limit is applied to both transaction-hash and block-hash proving routes.
652    pub cardano_prover_max_hashes_allowed_by_request: usize,
653
654    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
655    pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: usize,
656
657    /// Minimum duration between two consecutive block streamer polls in milliseconds `[default: 400]`.
658    ///
659    /// Set this value to `0` to disable throttling.
660    pub cardano_transactions_block_streamer_throttling_interval: Option<u64>,
661
662    /// Enable metrics server (Prometheus endpoint on /metrics).
663    pub enable_metrics_server: bool,
664
665    /// Metrics HTTP Server IP.
666    pub metrics_server_ip: String,
667
668    /// Metrics HTTP Server listening port.
669    pub metrics_server_port: u16,
670
671    /// Time interval at which usage metrics are persisted in event database (in seconds).
672    pub persist_usage_report_interval_in_seconds: u64,
673
674    // Leader aggregator endpoint
675    ///
676    /// This is the endpoint of the aggregator that will be used to fetch the latest epoch settings
677    /// and store the signer registrations when the aggregator is running in a follower mode.
678    /// If this is not set, the aggregator will run in a leader mode.
679    pub leader_aggregator_endpoint: Option<String>,
680
681    /// Custom origin tag of client request added to the whitelist (comma
682    /// separated list).
683    pub custom_origin_tag_white_list: Option<String>,
684
685    /// Aggregate signature type used to create certificates
686    pub aggregate_signature_type: AggregateSignatureType,
687
688    /// Delay to wait between two signature processing attempts after an error
689    pub signature_processor_wait_delay_on_error_ms: u64,
690}
691
692/// Uploader needed to copy the snapshot once computed.
693#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
694#[serde(rename_all = "lowercase")]
695pub enum SnapshotUploaderType {
696    /// Uploader to GCP storage.
697    Gcp,
698    /// Uploader to local storage.
699    Local,
700}
701
702/// [Zstandard][CompressionAlgorithm::Zstandard] specific parameters
703#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
704pub struct ZstandardCompressionParameters {
705    /// Level of compression, default to 9.
706    pub level: i32,
707
708    /// Number of workers when compressing, 0 will disable multithreading, default to 4.
709    pub number_of_workers: u32,
710}
711
712impl Default for ZstandardCompressionParameters {
713    fn default() -> Self {
714        Self {
715            level: 9,
716            number_of_workers: 4,
717        }
718    }
719}
720
721/// Configuration to connect to the Blockfrost API.
722///
723/// Currently only used to fetch the ticker and name for registered pools.
724#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
725pub struct BlockfrostParameters {
726    /// Project ID of the Blockfrost project.
727    pub project_id: ConfigSecret<String>,
728
729    /// Optional base URL for Blockfrost API, if not provided, the default URL will be determined
730    /// automatically from the project ID.
731    pub base_url: Option<String>,
732}
733
734impl FromStr for BlockfrostParameters {
735    type Err = serde_json::Error;
736
737    fn from_str(s: &str) -> Result<Self, Self::Err> {
738        serde_json::from_str(s)
739    }
740}
741
742/// Configuration of the ancillary files signer
743///
744/// **IMPORTANT**: The cryptographic scheme used is ED25519
745#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
746#[serde(rename_all = "kebab-case", tag = "type")]
747pub enum AncillaryFilesSignerConfig {
748    /// Sign with a secret key
749    SecretKey {
750        /// Hex encoded secret key
751        secret_key: HexEncodedKey,
752    },
753    /// Sign with a key stored in a Google Cloud Platform KMS account
754    GcpKms {
755        /// GCP KMS resource name
756        resource_name: GcpCryptoKeyVersionResourceName,
757        /// Environment variable containing the credentials JSON, if not set `GOOGLE_APPLICATION_CREDENTIALS_JSON` will be used
758        #[serde(default = "default_gcp_kms_credentials_json_env_var")]
759        credentials_json_env_var: String,
760    },
761}
762
763fn default_gcp_kms_credentials_json_env_var() -> String {
764    DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR.to_string()
765}
766
767impl FromStr for AncillaryFilesSignerConfig {
768    type Err = serde_json::Error;
769
770    fn from_str(s: &str) -> Result<Self, Self::Err> {
771        serde_json::from_str(s)
772    }
773}
774
775impl ServeCommandConfiguration {
776    /// Create a sample configuration mainly for tests
777    pub fn new_sample(tmp_path: PathBuf) -> Self {
778        let genesis_verification_key = ProtocolGenesisSigner::create_deterministic_signer()
779            .create_verifier()
780            .to_verification_key();
781        let ancillary_files_signer_secret_key =
782            ManifestSigner::create_deterministic_signer().secret_key();
783
784        Self {
785            environment: ExecutionEnvironment::Test,
786            cardano_cli_path: PathBuf::new(),
787            cardano_node_socket_path: PathBuf::new(),
788            dmq_node_socket_path: None,
789            cardano_node_version: "0.0.1".to_string(),
790            network: "devnet".to_string(),
791            network_magic: Some(42),
792            dmq_network_magic: Some(3141592),
793            chain_observer_type: ChainObserverType::Fake,
794            protocol_parameters: Some(ProtocolParameters {
795                k: 5,
796                m: 100,
797                phi_f: 0.95,
798            }),
799            snapshot_uploader_type: SnapshotUploaderType::Local,
800            snapshot_bucket_name: None,
801            snapshot_use_cdn_domain: false,
802            server_ip: "0.0.0.0".to_string(),
803            server_port: 8000,
804            public_server_url: None,
805            run_interval: 5000,
806            db_directory: PathBuf::new(),
807            // Note: this is a band-aid solution to avoid IO operations in the `mithril-aggregator`
808            // crate directory.
809            // Know issue:
810            // - There may be collision of the `snapshot_directory` between tests. Tests that
811            // depend on the `snapshot_directory` should specify their own,
812            // and they can use the `temp_dir` macro for that.
813            snapshot_directory: tmp_path,
814            data_stores_directory: PathBuf::from(":memory:"),
815            genesis_verification_key: genesis_verification_key.to_json_hex().unwrap(),
816            reset_digests_cache: false,
817            disable_digests_cache: false,
818            store_retention_limit: None,
819            era_reader_adapter_type: EraReaderAdapterType::Bootstrap,
820            era_reader_adapter_params: None,
821            ancillary_files_signer_config: AncillaryFilesSignerConfig::SecretKey {
822                secret_key: ancillary_files_signer_secret_key.to_json_hex().unwrap(),
823            },
824            signed_entity_types: None,
825            snapshot_compression_algorithm: CompressionAlgorithm::Zstandard,
826            zstandard_parameters: Some(ZstandardCompressionParameters::default()),
827            blockfrost_parameters: None,
828            signer_importer_run_interval: 1,
829            allow_unparsable_block: false,
830            cardano_blocks_transactions_prover_cache_pool_size: 3,
831            cardano_blocks_transactions_database_connection_pool_size: 5,
832            cardano_transactions_prover_cache_pool_size: 3,
833            cardano_transactions_database_connection_pool_size: 5,
834            cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
835                security_parameter: BlockNumber(120),
836                step: BlockNumber(15),
837            }),
838            cardano_blocks_transactions_signing_config: Some(
839                CardanoBlocksTransactionsSigningConfig {
840                    security_parameter: BlockNumber(120),
841                    step: BlockNumber(15),
842                },
843            ),
844            preload_security_parameter: BlockNumber(30),
845            cardano_prover_max_hashes_allowed_by_request: 100,
846            cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000,
847            cardano_transactions_block_streamer_throttling_interval: None,
848            enable_metrics_server: true,
849            metrics_server_ip: "0.0.0.0".to_string(),
850            metrics_server_port: 9090,
851            persist_usage_report_interval_in_seconds: 10,
852            leader_aggregator_endpoint: None,
853            custom_origin_tag_white_list: None,
854            aggregate_signature_type: AggregateSignatureType::Concatenation,
855            signature_processor_wait_delay_on_error_ms: 5000,
856        }
857    }
858
859    /// Build the local server URL from configuration.
860    pub fn get_local_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
861        SanitizedUrlWithTrailingSlash::parse(&format!(
862            "http://{}:{}/{SERVER_BASE_PATH}/",
863            self.server_ip, self.server_port
864        ))
865    }
866}
867
868impl ConfigurationSource for ServeCommandConfiguration {
869    fn environment(&self) -> ExecutionEnvironment {
870        self.environment.clone()
871    }
872
873    fn cardano_cli_path(&self) -> PathBuf {
874        self.cardano_cli_path.clone()
875    }
876
877    fn cardano_node_socket_path(&self) -> PathBuf {
878        self.cardano_node_socket_path.clone()
879    }
880
881    fn dmq_node_socket_path(&self) -> Option<PathBuf> {
882        self.dmq_node_socket_path.clone()
883    }
884
885    fn cardano_node_version(&self) -> String {
886        self.cardano_node_version.clone()
887    }
888
889    fn network(&self) -> String {
890        self.network.clone()
891    }
892
893    fn network_magic(&self) -> Option<u64> {
894        self.network_magic
895    }
896
897    fn dmq_network_magic(&self) -> Option<u64> {
898        self.dmq_network_magic
899    }
900
901    fn chain_observer_type(&self) -> ChainObserverType {
902        self.chain_observer_type.clone()
903    }
904
905    fn protocol_parameters(&self) -> Option<ProtocolParameters> {
906        self.protocol_parameters.clone()
907    }
908
909    fn snapshot_uploader_type(&self) -> SnapshotUploaderType {
910        self.snapshot_uploader_type
911    }
912
913    fn snapshot_bucket_name(&self) -> Option<String> {
914        self.snapshot_bucket_name.clone()
915    }
916
917    fn snapshot_use_cdn_domain(&self) -> bool {
918        self.snapshot_use_cdn_domain
919    }
920
921    fn server_ip(&self) -> String {
922        self.server_ip.clone()
923    }
924
925    fn server_port(&self) -> u16 {
926        self.server_port
927    }
928
929    fn public_server_url(&self) -> Option<String> {
930        self.public_server_url.clone()
931    }
932
933    fn run_interval(&self) -> u64 {
934        self.run_interval
935    }
936
937    fn db_directory(&self) -> PathBuf {
938        self.db_directory.clone()
939    }
940
941    fn snapshot_directory(&self) -> PathBuf {
942        self.snapshot_directory.clone()
943    }
944
945    fn data_stores_directory(&self) -> PathBuf {
946        self.data_stores_directory.clone()
947    }
948
949    fn genesis_verification_key(&self) -> HexEncodedGenesisVerificationKey {
950        self.genesis_verification_key.clone()
951    }
952
953    fn reset_digests_cache(&self) -> bool {
954        self.reset_digests_cache
955    }
956
957    fn disable_digests_cache(&self) -> bool {
958        self.disable_digests_cache
959    }
960
961    fn store_retention_limit(&self) -> Option<usize> {
962        self.store_retention_limit
963    }
964
965    fn era_reader_adapter_type(&self) -> EraReaderAdapterType {
966        self.era_reader_adapter_type.clone()
967    }
968
969    fn era_reader_adapter_params(&self) -> Option<String> {
970        self.era_reader_adapter_params.clone()
971    }
972
973    fn ancillary_files_signer_config(&self) -> AncillaryFilesSignerConfig {
974        self.ancillary_files_signer_config.clone()
975    }
976
977    fn signed_entity_types(&self) -> Option<String> {
978        self.signed_entity_types.clone()
979    }
980
981    fn snapshot_compression_algorithm(&self) -> CompressionAlgorithm {
982        self.snapshot_compression_algorithm
983    }
984
985    fn zstandard_parameters(&self) -> Option<ZstandardCompressionParameters> {
986        self.zstandard_parameters
987    }
988
989    fn blockfrost_parameters(&self) -> Option<BlockfrostParameters> {
990        self.blockfrost_parameters.clone()
991    }
992
993    fn signer_importer_run_interval(&self) -> u64 {
994        self.signer_importer_run_interval
995    }
996
997    fn allow_unparsable_block(&self) -> bool {
998        self.allow_unparsable_block
999    }
1000
1001    fn cardano_blocks_transactions_prover_cache_pool_size(&self) -> usize {
1002        self.cardano_blocks_transactions_prover_cache_pool_size
1003    }
1004
1005    fn cardano_blocks_transactions_database_connection_pool_size(&self) -> usize {
1006        self.cardano_blocks_transactions_database_connection_pool_size
1007    }
1008
1009    fn cardano_transactions_prover_cache_pool_size(&self) -> usize {
1010        self.cardano_transactions_prover_cache_pool_size
1011    }
1012
1013    fn cardano_transactions_database_connection_pool_size(&self) -> usize {
1014        self.cardano_transactions_database_connection_pool_size
1015    }
1016
1017    fn cardano_transactions_signing_config(&self) -> Option<CardanoTransactionsSigningConfig> {
1018        self.cardano_transactions_signing_config.clone()
1019    }
1020
1021    fn cardano_blocks_transactions_signing_config(
1022        &self,
1023    ) -> Option<CardanoBlocksTransactionsSigningConfig> {
1024        self.cardano_blocks_transactions_signing_config.clone()
1025    }
1026
1027    fn preload_security_parameter(&self) -> BlockNumber {
1028        self.preload_security_parameter
1029    }
1030
1031    fn cardano_prover_max_hashes_allowed_by_request(&self) -> usize {
1032        self.cardano_prover_max_hashes_allowed_by_request
1033    }
1034
1035    fn cardano_transactions_block_streamer_max_roll_forwards_per_poll(&self) -> usize {
1036        self.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1037    }
1038
1039    fn cardano_transactions_block_streamer_throttling_interval(&self) -> Option<u64> {
1040        self.cardano_transactions_block_streamer_throttling_interval
1041    }
1042
1043    fn enable_metrics_server(&self) -> bool {
1044        self.enable_metrics_server
1045    }
1046
1047    fn metrics_server_ip(&self) -> String {
1048        self.metrics_server_ip.clone()
1049    }
1050
1051    fn metrics_server_port(&self) -> u16 {
1052        self.metrics_server_port
1053    }
1054
1055    fn persist_usage_report_interval_in_seconds(&self) -> u64 {
1056        self.persist_usage_report_interval_in_seconds
1057    }
1058
1059    fn leader_aggregator_endpoint(&self) -> Option<String> {
1060        self.leader_aggregator_endpoint.clone()
1061    }
1062
1063    fn custom_origin_tag_white_list(&self) -> Option<String> {
1064        self.custom_origin_tag_white_list.clone()
1065    }
1066
1067    fn get_server_url(&self) -> StdResult<SanitizedUrlWithTrailingSlash> {
1068        match &self.public_server_url {
1069            Some(url) => SanitizedUrlWithTrailingSlash::parse(url),
1070            None => self.get_local_server_url(),
1071        }
1072    }
1073
1074    fn aggregate_signature_type(&self) -> AggregateSignatureType {
1075        self.aggregate_signature_type
1076    }
1077
1078    fn signature_processor_wait_delay_on_error_ms(&self) -> u64 {
1079        self.signature_processor_wait_delay_on_error_ms
1080    }
1081}
1082
1083/// Default configuration with all the default values for configurations.
1084#[derive(Debug, Clone, DocumenterDefault)]
1085pub struct DefaultConfiguration {
1086    /// Execution environment
1087    pub environment: ExecutionEnvironment,
1088
1089    /// Server listening IP
1090    pub server_ip: String,
1091
1092    /// Server listening port
1093    pub server_port: String,
1094
1095    /// Directory of the Cardano node database
1096    pub db_directory: String,
1097
1098    /// Directory to store snapshot
1099    pub snapshot_directory: String,
1100
1101    /// Type of snapshot uploader to use
1102    pub snapshot_uploader_type: String,
1103
1104    /// Era reader adapter type
1105    pub era_reader_adapter_type: String,
1106
1107    /// Chain observer type
1108    pub chain_observer_type: String,
1109
1110    /// ImmutableDigesterCacheProvider default setting
1111    pub reset_digests_cache: String,
1112
1113    /// ImmutableDigesterCacheProvider default setting
1114    pub disable_digests_cache: String,
1115
1116    /// Snapshot compression algorithm default setting
1117    pub snapshot_compression_algorithm: String,
1118
1119    /// Use CDN domain to construct snapshot urls default setting (if snapshot_uploader_type is Gcp)
1120    pub snapshot_use_cdn_domain: String,
1121
1122    /// Signer importer run interval default setting
1123    pub signer_importer_run_interval: u64,
1124
1125    /// If set no error is returned in case of unparsable block and an error log is written instead.
1126    ///
1127    /// Will be ignored on (pre)production networks.
1128    pub allow_unparsable_block: String,
1129
1130    /// Cardano blocks and transactions prover cache pool size
1131    pub cardano_blocks_transactions_prover_cache_pool_size: u32,
1132
1133    /// Cardano blocks and transactions database connection pool size
1134    pub cardano_blocks_transactions_database_connection_pool_size: u32,
1135
1136    /// Cardano transactions prover cache pool size
1137    pub cardano_transactions_prover_cache_pool_size: u32,
1138
1139    /// Cardano transactions database connection pool size
1140    pub cardano_transactions_database_connection_pool_size: u32,
1141
1142    /// Cardano transactions signing configuration
1143    pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1144
1145    /// Cardano blocks and transactions signing configuration
1146    pub cardano_blocks_transactions_signing_config: CardanoBlocksTransactionsSigningConfig,
1147
1148    /// Blocks offset, from the tip of the chain, to exclude during the Cardano transactions preload
1149    pub preload_security_parameter: u64,
1150
1151    /// Maximum number of hashes allowed by request to the Cardano prover.
1152    ///
1153    /// This unified limit is applied to both transaction-hash and block-hash proving routes.
1154    pub cardano_prover_max_hashes_allowed_by_request: u32,
1155
1156    /// The maximum number of roll forwards during a poll of the block streamer when importing transactions.
1157    pub cardano_transactions_block_streamer_max_roll_forwards_per_poll: u32,
1158
1159    /// Minimum duration between two consecutive block streamer polls in milliseconds.
1160    pub cardano_transactions_block_streamer_throttling_interval: u64,
1161
1162    /// Enable metrics server (Prometheus endpoint on /metrics).
1163    pub enable_metrics_server: String,
1164
1165    /// Metrics HTTP server IP.
1166    pub metrics_server_ip: String,
1167
1168    /// Metrics HTTP server listening port.
1169    pub metrics_server_port: u16,
1170
1171    /// Time interval at which metrics are persisted in event database (in seconds).
1172    pub persist_usage_report_interval_in_seconds: u64,
1173
1174    /// Aggregate signature type used to create certificates
1175    pub aggregate_signature_type: String,
1176
1177    /// Delay to wait between two signature processing attempts after an error
1178    pub signature_processor_wait_delay_on_error_ms: u64,
1179}
1180
1181impl Default for DefaultConfiguration {
1182    fn default() -> Self {
1183        Self {
1184            environment: ExecutionEnvironment::Production,
1185            server_ip: "0.0.0.0".to_string(),
1186            server_port: "8080".to_string(),
1187            db_directory: "/db".to_string(),
1188            snapshot_directory: ".".to_string(),
1189            snapshot_uploader_type: "gcp".to_string(),
1190            era_reader_adapter_type: "bootstrap".to_string(),
1191            chain_observer_type: "pallas".to_string(),
1192            reset_digests_cache: "false".to_string(),
1193            disable_digests_cache: "false".to_string(),
1194            snapshot_compression_algorithm: "zstandard".to_string(),
1195            snapshot_use_cdn_domain: "false".to_string(),
1196            signer_importer_run_interval: 720,
1197            allow_unparsable_block: "false".to_string(),
1198            cardano_blocks_transactions_prover_cache_pool_size: 10,
1199            cardano_blocks_transactions_database_connection_pool_size: 10,
1200            cardano_transactions_prover_cache_pool_size: 10,
1201            cardano_transactions_database_connection_pool_size: 10,
1202            cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
1203                security_parameter: BlockNumber(3000),
1204                step: BlockNumber(120),
1205            },
1206            cardano_blocks_transactions_signing_config: CardanoBlocksTransactionsSigningConfig {
1207                security_parameter: BlockNumber(3000),
1208                step: BlockNumber(120),
1209            },
1210            preload_security_parameter: 2160,
1211            cardano_prover_max_hashes_allowed_by_request: 100,
1212            cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000,
1213            cardano_transactions_block_streamer_throttling_interval: 50,
1214            enable_metrics_server: "false".to_string(),
1215            metrics_server_ip: "0.0.0.0".to_string(),
1216            metrics_server_port: 9090,
1217            persist_usage_report_interval_in_seconds: 10,
1218            aggregate_signature_type: "Concatenation".to_string(),
1219            signature_processor_wait_delay_on_error_ms: 1000,
1220        }
1221    }
1222}
1223
1224impl DefaultConfiguration {
1225    fn namespace() -> String {
1226        "default configuration".to_string()
1227    }
1228}
1229
1230impl From<ExecutionEnvironment> for ValueKind {
1231    fn from(value: ExecutionEnvironment) -> Self {
1232        match value {
1233            ExecutionEnvironment::Production => ValueKind::String("Production".to_string()),
1234            ExecutionEnvironment::Test => ValueKind::String("Test".to_string()),
1235        }
1236    }
1237}
1238
1239impl Source for DefaultConfiguration {
1240    fn clone_into_box(&self) -> Box<dyn Source + Send + Sync> {
1241        Box::new(self.clone())
1242    }
1243
1244    fn collect(&self) -> Result<Map<String, Value>, ConfigError> {
1245        let mut result = Map::new();
1246
1247        let namespace = DefaultConfiguration::namespace();
1248
1249        let myself = self.clone();
1250        register_config_value!(result, &namespace, myself.environment);
1251        register_config_value!(result, &namespace, myself.server_ip);
1252        register_config_value!(result, &namespace, myself.server_port);
1253        register_config_value!(result, &namespace, myself.db_directory);
1254        register_config_value!(result, &namespace, myself.snapshot_directory);
1255        register_config_value!(result, &namespace, myself.snapshot_uploader_type);
1256        register_config_value!(result, &namespace, myself.era_reader_adapter_type);
1257        register_config_value!(result, &namespace, myself.reset_digests_cache);
1258        register_config_value!(result, &namespace, myself.disable_digests_cache);
1259        register_config_value!(result, &namespace, myself.snapshot_compression_algorithm);
1260        register_config_value!(result, &namespace, myself.snapshot_use_cdn_domain);
1261        register_config_value!(result, &namespace, myself.signer_importer_run_interval);
1262        register_config_value!(result, &namespace, myself.allow_unparsable_block);
1263        register_config_value!(
1264            result,
1265            &namespace,
1266            myself.cardano_blocks_transactions_prover_cache_pool_size
1267        );
1268        register_config_value!(
1269            result,
1270            &namespace,
1271            myself.cardano_blocks_transactions_database_connection_pool_size
1272        );
1273        register_config_value!(
1274            result,
1275            &namespace,
1276            myself.cardano_transactions_prover_cache_pool_size
1277        );
1278        register_config_value!(
1279            result,
1280            &namespace,
1281            myself.cardano_transactions_database_connection_pool_size
1282        );
1283        register_config_value!(
1284            result,
1285            &namespace,
1286            myself.cardano_prover_max_hashes_allowed_by_request
1287        );
1288        register_config_value!(
1289            result,
1290            &namespace,
1291            myself.cardano_transactions_block_streamer_max_roll_forwards_per_poll
1292        );
1293        register_config_value!(
1294            result,
1295            &namespace,
1296            myself.cardano_transactions_block_streamer_throttling_interval
1297        );
1298        register_config_value!(result, &namespace, myself.enable_metrics_server);
1299        register_config_value!(result, &namespace, myself.metrics_server_ip);
1300        register_config_value!(result, &namespace, myself.metrics_server_port);
1301        register_config_value!(
1302            result,
1303            &namespace,
1304            myself.persist_usage_report_interval_in_seconds
1305        );
1306        register_config_value!(result, &namespace, myself.preload_security_parameter);
1307        register_config_value!(
1308            result,
1309            &namespace,
1310            myself.cardano_transactions_signing_config,
1311            |v: CardanoTransactionsSigningConfig| HashMap::from([
1312                (
1313                    "security_parameter".to_string(),
1314                    ValueKind::from(*v.security_parameter),
1315                ),
1316                ("step".to_string(), ValueKind::from(*v.step),)
1317            ])
1318        );
1319        register_config_value!(
1320            result,
1321            &namespace,
1322            myself.cardano_blocks_transactions_signing_config,
1323            |v: CardanoBlocksTransactionsSigningConfig| HashMap::from([
1324                (
1325                    "security_parameter".to_string(),
1326                    ValueKind::from(*v.security_parameter),
1327                ),
1328                ("step".to_string(), ValueKind::from(*v.step),)
1329            ])
1330        );
1331        register_config_value!(result, &namespace, myself.aggregate_signature_type);
1332        register_config_value!(
1333            result,
1334            &namespace,
1335            myself.signature_processor_wait_delay_on_error_ms
1336        );
1337        Ok(result)
1338    }
1339}
1340
1341#[cfg(test)]
1342mod test {
1343    use mithril_common::temp_dir;
1344    use mithril_common::test::double::fake_data;
1345
1346    use super::*;
1347
1348    #[test]
1349    fn safe_epoch_retention_limit_wont_change_a_value_higher_than_three() {
1350        for limit in 4..=10u64 {
1351            let configuration = ServeCommandConfiguration {
1352                store_retention_limit: Some(limit as usize),
1353                ..ServeCommandConfiguration::new_sample(temp_dir!())
1354            };
1355            assert_eq!(configuration.safe_epoch_retention_limit(), Some(limit));
1356        }
1357    }
1358
1359    #[test]
1360    fn safe_epoch_retention_limit_wont_change_a_none_value() {
1361        let configuration = ServeCommandConfiguration {
1362            store_retention_limit: None,
1363            ..ServeCommandConfiguration::new_sample(temp_dir!())
1364        };
1365        assert_eq!(configuration.safe_epoch_retention_limit(), None);
1366    }
1367
1368    #[test]
1369    fn safe_epoch_retention_limit_wont_yield_a_value_lower_than_three() {
1370        for limit in 0..=3 {
1371            let configuration = ServeCommandConfiguration {
1372                store_retention_limit: Some(limit),
1373                ..ServeCommandConfiguration::new_sample(temp_dir!())
1374            };
1375            assert_eq!(configuration.safe_epoch_retention_limit(), Some(3));
1376        }
1377    }
1378
1379    #[test]
1380    fn can_build_config_with_ctx_signing_config_from_default_configuration() {
1381        #[derive(Debug, Deserialize)]
1382        struct TargetConfig {
1383            cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
1384        }
1385
1386        let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1387        let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1388
1389        assert_eq!(
1390            target.cardano_transactions_signing_config,
1391            DefaultConfiguration::default().cardano_transactions_signing_config
1392        );
1393    }
1394
1395    #[test]
1396    fn can_build_config_with_cardano_blocks_tx_signing_config_from_default_configuration() {
1397        #[derive(Debug, Deserialize)]
1398        struct TargetConfig {
1399            cardano_blocks_transactions_signing_config: CardanoBlocksTransactionsSigningConfig,
1400        }
1401
1402        let config_builder = config::Config::builder().add_source(DefaultConfiguration::default());
1403        let target: TargetConfig = config_builder.build().unwrap().try_deserialize().unwrap();
1404
1405        assert_eq!(
1406            target.cardano_blocks_transactions_signing_config,
1407            DefaultConfiguration::default().cardano_blocks_transactions_signing_config
1408        );
1409    }
1410
1411    #[test]
1412    fn compute_allowed_signed_entity_types_discriminants_append_default_discriminants() {
1413        let config = ServeCommandConfiguration {
1414            signed_entity_types: None,
1415            ..ServeCommandConfiguration::new_sample(temp_dir!())
1416        };
1417
1418        assert_eq!(
1419            config.compute_allowed_signed_entity_types_discriminants().unwrap(),
1420            BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS)
1421        );
1422    }
1423
1424    #[test]
1425    fn allow_http_serve_directory() {
1426        let config = ServeCommandConfiguration {
1427            snapshot_uploader_type: SnapshotUploaderType::Local,
1428            ..ServeCommandConfiguration::new_sample(temp_dir!())
1429        };
1430
1431        assert!(config.allow_http_serve_directory());
1432
1433        let config = ServeCommandConfiguration {
1434            snapshot_uploader_type: SnapshotUploaderType::Gcp,
1435            ..ServeCommandConfiguration::new_sample(temp_dir!())
1436        };
1437
1438        assert!(!config.allow_http_serve_directory());
1439    }
1440
1441    #[test]
1442    fn get_server_url_return_local_url_with_server_base_path_if_public_url_is_not_set() {
1443        let config = ServeCommandConfiguration {
1444            server_ip: "1.2.3.4".to_string(),
1445            server_port: 5678,
1446            public_server_url: None,
1447            ..ServeCommandConfiguration::new_sample(temp_dir!())
1448        };
1449
1450        assert_eq!(
1451            config.get_server_url().unwrap().as_str(),
1452            &format!("http://1.2.3.4:5678/{SERVER_BASE_PATH}/")
1453        );
1454    }
1455
1456    #[test]
1457    fn get_server_url_return_sanitized_public_url_if_it_is_set() {
1458        let config = ServeCommandConfiguration {
1459            server_ip: "1.2.3.4".to_string(),
1460            server_port: 5678,
1461            public_server_url: Some("https://example.com".to_string()),
1462            ..ServeCommandConfiguration::new_sample(temp_dir!())
1463        };
1464
1465        assert_eq!(
1466            config.get_server_url().unwrap().as_str(),
1467            "https://example.com/"
1468        );
1469    }
1470
1471    #[test]
1472    fn joining_to_local_server_url_keep_base_path() {
1473        let config = ServeCommandConfiguration {
1474            server_ip: "1.2.3.4".to_string(),
1475            server_port: 6789,
1476            public_server_url: None,
1477            ..ServeCommandConfiguration::new_sample(temp_dir!())
1478        };
1479
1480        let joined_url = config.get_local_server_url().unwrap().join("some/path").unwrap();
1481        assert!(
1482            joined_url.as_str().contains(SERVER_BASE_PATH),
1483            "Joined URL `{joined_url}`, does not contain base path `{SERVER_BASE_PATH}`"
1484        );
1485    }
1486
1487    #[test]
1488    fn joining_to_public_server_url_without_trailing_slash() {
1489        let subpath_without_trailing_slash = "subpath_without_trailing_slash";
1490        let config = ServeCommandConfiguration {
1491            public_server_url: Some(format!(
1492                "https://example.com/{subpath_without_trailing_slash}"
1493            )),
1494            ..ServeCommandConfiguration::new_sample(temp_dir!())
1495        };
1496
1497        let joined_url = config.get_server_url().unwrap().join("some/path").unwrap();
1498        assert!(
1499            joined_url.as_str().contains(subpath_without_trailing_slash),
1500            "Joined URL `{joined_url}`, does not contain subpath `{subpath_without_trailing_slash}`"
1501        );
1502    }
1503
1504    #[test]
1505    fn is_follower_aggregator_returns_true_when_in_follower_mode() {
1506        let config = ServeCommandConfiguration {
1507            leader_aggregator_endpoint: Some("some_endpoint".to_string()),
1508            ..ServeCommandConfiguration::new_sample(temp_dir!())
1509        };
1510
1511        assert!(config.is_follower_aggregator());
1512    }
1513
1514    #[test]
1515    fn is_follower_aggregator_returns_false_when_in_leader_mode() {
1516        let config = ServeCommandConfiguration {
1517            leader_aggregator_endpoint: None,
1518            ..ServeCommandConfiguration::new_sample(temp_dir!())
1519        };
1520
1521        assert!(!config.is_follower_aggregator());
1522    }
1523
1524    #[test]
1525    fn deserializing_blockfrost_parameters() {
1526        let deserialized_without_base_url: BlockfrostParameters =
1527            serde_json::from_str(r#"{ "project_id": "preprodWuV1ICdtOWfZYf" }"#).unwrap();
1528        assert_eq!(
1529            deserialized_without_base_url,
1530            BlockfrostParameters {
1531                project_id: ConfigSecret::new("preprodWuV1ICdtOWfZYf".to_string()),
1532                base_url: None,
1533            }
1534        );
1535
1536        let deserialized_with_base_url: BlockfrostParameters = serde_json::from_str(
1537            r#"{ "project_id": "preprodWuV1ICdtOWfZYf", "base_url": "https://test.foo.bar" }"#,
1538        )
1539        .unwrap();
1540        assert_eq!(
1541            deserialized_with_base_url,
1542            BlockfrostParameters {
1543                project_id: ConfigSecret::new("preprodWuV1ICdtOWfZYf".to_string()),
1544                base_url: Some("https://test.foo.bar".to_string()),
1545            }
1546        );
1547    }
1548
1549    mod get_leader_aggregator_epoch_settings_configuration {
1550        use super::*;
1551
1552        #[test]
1553        fn succeed_when_cardano_transactions_is_disabled_and_cardano_transactions_signing_config_is_not_set()
1554         {
1555            let epoch_settings = ServeCommandConfiguration {
1556                signed_entity_types: None,
1557                cardano_transactions_signing_config: None,
1558                protocol_parameters: Some(ProtocolParameters::new(1, 2, 3.1)),
1559                ..ServeCommandConfiguration::new_sample(temp_dir!())
1560            }
1561            .get_leader_aggregator_epoch_settings_configuration()
1562            .unwrap();
1563
1564            assert_eq!(
1565                AggregatorEpochSettings {
1566                    protocol_parameters: ProtocolParameters::new(1, 2, 3.1),
1567                    cardano_transactions_signing_config: None,
1568                    cardano_blocks_transactions_signing_config: None,
1569                },
1570                epoch_settings
1571            );
1572        }
1573
1574        #[test]
1575        fn succeed_when_cardano_transactions_is_enabled_and_cardano_transactions_signing_config_is_set()
1576         {
1577            let epoch_settings = ServeCommandConfiguration {
1578                signed_entity_types: Some(
1579                    SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1580                ),
1581                cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1582                    security_parameter: BlockNumber(10),
1583                    step: BlockNumber(30),
1584                }),
1585                protocol_parameters: Some(ProtocolParameters::new(2, 3, 4.1)),
1586                ..ServeCommandConfiguration::new_sample(temp_dir!())
1587            }
1588            .get_leader_aggregator_epoch_settings_configuration()
1589            .unwrap();
1590
1591            assert_eq!(
1592                AggregatorEpochSettings {
1593                    protocol_parameters: ProtocolParameters::new(2, 3, 4.1),
1594                    cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
1595                        security_parameter: BlockNumber(10),
1596                        step: BlockNumber(30),
1597                    }),
1598                    cardano_blocks_transactions_signing_config: None,
1599                },
1600                epoch_settings
1601            );
1602        }
1603
1604        #[test]
1605        fn fails_when_cardano_transactions_is_enabled_without_associated_config() {
1606            let error = ServeCommandConfiguration {
1607                cardano_transactions_signing_config: None,
1608                signed_entity_types: Some(
1609                    SignedEntityTypeDiscriminants::CardanoTransactions.to_string(),
1610                ),
1611                protocol_parameters: Some(fake_data::protocol_parameters()),
1612                ..ServeCommandConfiguration::new_sample(temp_dir!())
1613            }
1614            .get_leader_aggregator_epoch_settings_configuration()
1615            .unwrap_err();
1616
1617            assert!(
1618                error
1619                    .to_string()
1620                    .contains("Configuration `cardano_transactions_signing_config` is mandatory")
1621            );
1622        }
1623
1624        #[test]
1625        fn succeed_when_cardano_blocks_transactions_is_disabled_and_cardano_blocks_transactions_signing_config_is_not_set()
1626         {
1627            let epoch_settings = ServeCommandConfiguration {
1628                signed_entity_types: None,
1629                cardano_blocks_transactions_signing_config: None,
1630                protocol_parameters: Some(ProtocolParameters::new(1, 2, 3.1)),
1631                ..ServeCommandConfiguration::new_sample(temp_dir!())
1632            }
1633            .get_leader_aggregator_epoch_settings_configuration()
1634            .unwrap();
1635
1636            assert_eq!(
1637                AggregatorEpochSettings {
1638                    protocol_parameters: ProtocolParameters::new(1, 2, 3.1),
1639                    cardano_transactions_signing_config: None,
1640                    cardano_blocks_transactions_signing_config: None,
1641                },
1642                epoch_settings
1643            );
1644        }
1645
1646        #[test]
1647        fn succeed_when_cardano_blocks_transactions_is_enabled_and_cardano_blocks_transactions_signing_config_is_set()
1648         {
1649            let epoch_settings = ServeCommandConfiguration {
1650                signed_entity_types: Some(
1651                    SignedEntityTypeDiscriminants::CardanoBlocksTransactions.to_string(),
1652                ),
1653                cardano_blocks_transactions_signing_config: Some(
1654                    CardanoBlocksTransactionsSigningConfig {
1655                        security_parameter: BlockNumber(10),
1656                        step: BlockNumber(30),
1657                    },
1658                ),
1659                protocol_parameters: Some(ProtocolParameters::new(2, 3, 4.1)),
1660                ..ServeCommandConfiguration::new_sample(temp_dir!())
1661            }
1662            .get_leader_aggregator_epoch_settings_configuration()
1663            .unwrap();
1664
1665            assert_eq!(
1666                AggregatorEpochSettings {
1667                    protocol_parameters: ProtocolParameters::new(2, 3, 4.1),
1668                    cardano_transactions_signing_config: None,
1669                    cardano_blocks_transactions_signing_config: Some(
1670                        CardanoBlocksTransactionsSigningConfig {
1671                            security_parameter: BlockNumber(10),
1672                            step: BlockNumber(30),
1673                        }
1674                    ),
1675                },
1676                epoch_settings
1677            );
1678        }
1679
1680        #[test]
1681        fn fails_when_cardano_blocks_transactions_is_enabled_without_associated_config() {
1682            let error = ServeCommandConfiguration {
1683                cardano_blocks_transactions_signing_config: None,
1684                signed_entity_types: Some(
1685                    SignedEntityTypeDiscriminants::CardanoBlocksTransactions.to_string(),
1686                ),
1687                protocol_parameters: Some(fake_data::protocol_parameters()),
1688                ..ServeCommandConfiguration::new_sample(temp_dir!())
1689            }
1690            .get_leader_aggregator_epoch_settings_configuration()
1691            .unwrap_err();
1692
1693            assert!(error.to_string().contains(
1694                "Configuration `cardano_blocks_transactions_signing_config` is mandatory"
1695            ));
1696        }
1697    }
1698
1699    #[test]
1700    fn serialized_ancillary_files_signer_config_use_snake_case_for_keys_and_kebab_case_for_type_value()
1701     {
1702        let serialized_json = r#"{
1703            "type": "secret-key",
1704            "secret_key": "whatever"
1705        }"#;
1706
1707        let deserialized: AncillaryFilesSignerConfig =
1708            serde_json::from_str(serialized_json).unwrap();
1709        assert_eq!(
1710            deserialized,
1711            AncillaryFilesSignerConfig::SecretKey {
1712                secret_key: "whatever".to_string()
1713            }
1714        );
1715    }
1716
1717    #[test]
1718    fn deserializing_ancillary_signing_gcp_kms_configuration() {
1719        let serialized_json = r#"{
1720            "type": "gcp-kms",
1721            "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1",
1722            "credentials_json_env_var": "CUSTOM_ENV_VAR"
1723        }"#;
1724
1725        let deserialized: AncillaryFilesSignerConfig =
1726            serde_json::from_str(serialized_json).unwrap();
1727        assert_eq!(
1728            deserialized,
1729            AncillaryFilesSignerConfig::GcpKms {
1730                resource_name: GcpCryptoKeyVersionResourceName {
1731                    project: "123456789".to_string(),
1732                    location: "global".to_string(),
1733                    key_ring: "my-keyring".to_string(),
1734                    key_name: "my-key".to_string(),
1735                    version: "1".to_string(),
1736                },
1737                credentials_json_env_var: "CUSTOM_ENV_VAR".to_string()
1738            }
1739        );
1740    }
1741
1742    #[test]
1743    fn deserializing_ancillary_signing_gcp_kms_configuration_without_credentials_json_env_var_fallback_to_default()
1744     {
1745        let serialized_json = r#"{
1746            "type": "gcp-kms",
1747            "resource_name": "projects/123456789/locations/global/keyRings/my-keyring/cryptoKeys/my-key/cryptoKeyVersions/1"
1748        }"#;
1749
1750        let deserialized: AncillaryFilesSignerConfig =
1751            serde_json::from_str(serialized_json).unwrap();
1752        if let AncillaryFilesSignerConfig::GcpKms {
1753            credentials_json_env_var,
1754            ..
1755        } = deserialized
1756        {
1757            assert_eq!(
1758                credentials_json_env_var,
1759                DEFAULT_GCP_CREDENTIALS_JSON_ENV_VAR
1760            );
1761        } else {
1762            panic!("Expected GcpKms variant but got {deserialized:?}");
1763        }
1764    }
1765
1766    mod origin_tag {
1767        use super::*;
1768
1769        #[test]
1770        fn default_origin_tag_white_list_is_not_empty() {
1771            let config = ServeCommandConfiguration {
1772                custom_origin_tag_white_list: None,
1773                ..ServeCommandConfiguration::new_sample(temp_dir!())
1774            };
1775            assert_ne!(config.compute_origin_tag_white_list().len(), 0,);
1776        }
1777
1778        #[test]
1779        fn custom_origin_tag_are_added_to_default_white_list() {
1780            let config = ServeCommandConfiguration {
1781                custom_origin_tag_white_list: Some("TAG_A,TAG_B , TAG_C".to_string()),
1782                ..ServeCommandConfiguration::new_sample(temp_dir!())
1783            };
1784
1785            let default_white_list = ServeCommandConfiguration {
1786                custom_origin_tag_white_list: None,
1787                ..ServeCommandConfiguration::new_sample(temp_dir!())
1788            }
1789            .compute_origin_tag_white_list();
1790
1791            let mut expected_white_list = default_white_list.clone();
1792            assert!(expected_white_list.insert("TAG_A".to_string()));
1793            assert!(expected_white_list.insert("TAG_B".to_string()));
1794            assert!(expected_white_list.insert("TAG_C".to_string()));
1795
1796            assert_eq!(expected_white_list, config.compute_origin_tag_white_list());
1797        }
1798    }
1799}