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