1use crate::config::config_fields::{
2 CARDANO_ACTIVE_SLOTS_COEFF, CARDANO_EPOCH_DURATION_MILLIS, CARDANO_FIRST_EPOCH_NUMBER,
3 CARDANO_FIRST_EPOCH_TIMESTAMP_MILLIS, CARDANO_FIRST_SLOT_NUMBER, CARDANO_SECURITY_PARAMETER,
4};
5use crate::io::IOContext;
6use clap::Parser;
7use config_fields::CARDANO_SLOT_DURATION_MILLIS;
8use serde::{Deserialize, Serialize, de::DeserializeOwned};
9use sidechain_domain::{MainchainKeyHash, UtxoId};
10use sp_core::offchain::{Duration, Timestamp};
11use std::fmt::{Display, Formatter, Write};
12use std::str::FromStr;
13use std::{marker::PhantomData, process::exit};
14
15pub(crate) struct ConfigFieldDefinition<'a, T> {
17 pub(crate) name: &'a str,
19 pub(crate) config_file: ConfigFile,
21 pub(crate) path: &'a [&'a str],
23 pub(crate) default: Option<&'a str>,
25 _marker: PhantomData<T>,
26}
27
28#[cfg(test)]
29impl<'a, T> ConfigFieldDefinition<'a, T> {
30 pub(crate) fn new(
31 name: &'a str,
32 config_file: ConfigFile,
33 path: &'a [&'a str],
34 default: Option<&'a str>,
35 ) -> Self {
36 Self { name, config_file, path, default, _marker: Default::default() }
37 }
38}
39
40impl<'a> ConfigFieldDefinition<'a, String> {
41 pub(crate) fn load_or_prompt_and_save<C: IOContext>(&self, context: &C) -> String {
44 if let Some(value) = self.load_from_file_and_print(context) {
45 value
46 } else {
47 let value = context.prompt(&format!("Enter the {}", self.name), self.default);
48 self.save_to_file(&value, context);
49 value
50 }
51 }
52
53 pub(crate) fn prompt_with_default_from_file_and_save<C: IOContext>(
55 &self,
56 context: &C,
57 ) -> String {
58 let value = context.prompt(
59 &format!("Enter the {}", self.name),
60 self.load_from_file(context).as_deref().or(self.default),
61 );
62 self.save_to_file(&value, context);
63 value
64 }
65}
66
67impl<'a, T> ConfigFieldDefinition<'a, T> {
68 pub(crate) fn prompt_with_default_from_file_parse_and_save<C: IOContext>(
69 &self,
70 context: &C,
71 ) -> Result<T, <T as FromStr>::Err>
72 where
73 T: DeserializeOwned + std::fmt::Display + FromStr + serde::Serialize,
74 {
75 let loaded_value = self.load_from_file(context).map(|v| v.to_string());
76 let default_value = loaded_value.as_deref().or_else(|| self.default);
77 let value = context.prompt(&format!("Enter the {}", self.name), default_value);
78 let parsed_value: T = value.parse()?;
79 self.save_to_file(&parsed_value, context);
80 Ok(parsed_value)
81 }
82
83 pub(crate) fn select_options_with_default_from_file_and_save<C: IOContext>(
84 &self,
85 prompt: &str,
86 context: &C,
87 ) -> Result<T, <T as FromStr>::Err>
88 where
89 T: DeserializeOwned
90 + std::fmt::Display
91 + FromStr
92 + serde::Serialize
93 + SelectOptions<Opt = T>
94 + Clone
95 + std::fmt::Debug
96 + std::panic::UnwindSafe
97 + std::panic::RefUnwindSafe,
98 {
99 let loaded_value = self.load_from_file(context);
100 let default_value_opt: Option<T> =
101 loaded_value.or_else(|| self.default.and_then(|s| s.parse().ok()));
102 let options: Vec<T> = default_value_opt.map_or_else(
103 || T::select_options(),
104 |default_value| T::select_options_with_default(&default_value),
105 );
106 let value_ix = context.prompt_multi_option(prompt, options.clone());
107 let value = options[value_ix].clone();
108 self.save_to_file(&value, context);
109 Ok(value)
110 }
111
112 pub(crate) fn load_from_file<C: IOContext>(&self, context: &C) -> Option<T>
114 where
115 T: DeserializeOwned,
116 {
117 self.load_file(context).and_then(|json| self.extract_from_json_object(&json))
118 }
119
120 fn load_from_file_and_print(&self, context: &impl IOContext) -> Option<T>
121 where
122 T: DeserializeOwned + std::fmt::Display,
123 {
124 let value = self.load_from_file(context)?;
125 context.eprint(&format!(
126 "🛠️ Loaded {} from config ({}): {value}",
127 self.name,
128 context.config_file_path(self.config_file)
129 ));
130 Some(value)
131 }
132
133 pub(crate) fn save_to_file<C: IOContext>(&self, value: &T, context: &C)
135 where
136 T: Serialize,
137 {
138 let mut json =
139 self.load_file(context).unwrap_or(serde_json::Value::Object(Default::default()));
140 let mut head = &mut json;
141 for &field in self.path {
142 head[field] = head
143 .get(field)
144 .cloned()
145 .filter(serde_json::Value::is_object)
146 .unwrap_or(serde_json::Value::Object(Default::default()));
147 head = &mut head[field];
148 }
149 *head = serde_json::to_value(value).unwrap();
150 context.write_file(
151 &context.config_file_path(self.config_file),
152 &serde_json::to_string_pretty(&json).unwrap(),
153 );
154 }
155
156 pub(crate) fn save_if_empty<C: IOContext>(&self, value: T, context: &C) -> T
157 where
158 T: DeserializeOwned + serde::Serialize,
159 {
160 if let Some(value) = self.load_from_file(context) {
161 value
162 } else {
163 self.save_to_file(&value, context);
164 value
165 }
166 }
167
168 pub(crate) fn extract_from_json_object(&self, json: &serde_json::Value) -> Option<T>
170 where
171 T: DeserializeOwned,
172 {
173 let mut json: Option<&serde_json::Value> = Some(json);
174 for &field in self.path {
175 if let Some(json_inner) = json {
176 json = json_inner.get(field)
177 } else {
178 return None;
179 }
180 }
181 json.and_then(|json| serde_json::from_value(json.clone()).ok())
182 }
183
184 pub(crate) fn load_file<C: IOContext>(&self, context: &C) -> Option<serde_json::Value> {
186 let path = context.config_file_path(self.config_file);
187 if !context.file_exists(&path) {
188 return None;
189 }
190
191 if let Some(file_content_string) = context.read_file(&path) {
192 if let Ok(value) = serde_json::from_str(&file_content_string) {
193 return Some(value);
194 }
195 }
196
197 context.eprint(&format!(
198 "Config file {} is broken. Delete it or fix manually and restart this wizard",
199 path
200 ));
201 exit(-1)
202 }
203}
204
205#[derive(Clone, Debug)]
206pub struct ServiceConfig {
207 pub protocol: NetworkProtocol,
208 pub hostname: String,
209 pub port: u16,
210 pub timeout_seconds: u64,
211}
212
213impl ServiceConfig {
214 pub(crate) fn url(&self) -> String {
215 format!("{}://{}:{}", self.protocol, self.hostname, self.port)
216 }
217}
218
219pub(crate) trait SelectOptions {
220 type Opt: PartialEq;
221 fn select_options() -> Vec<Self::Opt>;
222 fn select_options_with_default(default_value: &Self::Opt) -> Vec<Self::Opt> {
223 let mut options = Self::select_options();
224 options.sort_by_key(|option| if option != default_value { 1 } else { 0 });
225 options
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
230pub enum NetworkProtocol {
232 #[serde(rename = "http")]
233 Http,
235 #[serde(rename = "https")]
236 Https,
238}
239
240impl std::fmt::Display for NetworkProtocol {
241 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
242 let str = match self {
243 NetworkProtocol::Http => "http".to_string(),
244 NetworkProtocol::Https => "https".to_string(),
245 };
246 write!(f, "{}", str)
247 }
248}
249
250impl FromStr for NetworkProtocol {
251 type Err = String;
252
253 fn from_str(s: &str) -> Result<Self, Self::Err> {
254 match s.to_lowercase().as_str() {
255 "http" => Ok(NetworkProtocol::Http),
256 "https" => Ok(NetworkProtocol::Https),
257 other => Err(format!("Invalid security protocol {other}, please provide http or http")),
258 }
259 }
260}
261
262impl SelectOptions for NetworkProtocol {
263 type Opt = NetworkProtocol;
264 fn select_options() -> Vec<NetworkProtocol> {
265 vec![NetworkProtocol::Http, NetworkProtocol::Https]
266 }
267}
268
269#[derive(Deserialize)]
270#[allow(dead_code)]
271pub(crate) struct MainChainAddresses {
272 pub(crate) committee_candidates_address: String,
273 pub(crate) d_parameter_policy_id: String,
275 pub(crate) permissioned_candidates_policy_id: String,
277 pub(crate) bridge: NativeTokenConfig,
279}
280
281#[derive(Deserialize, PartialEq, Clone, Debug)]
282pub(crate) struct CardanoParameters {
283 pub(crate) security_parameter: u64,
284 pub(crate) active_slots_coeff: f64,
285 pub(crate) first_epoch_number: u32,
286 pub(crate) first_slot_number: u64,
287 pub(crate) epoch_duration_millis: u64,
288 pub(crate) first_epoch_timestamp_millis: u64,
289 #[serde(default = "default_slot_duration_millis")]
290 pub(crate) slot_duration_millis: u64,
291}
292
293fn default_slot_duration_millis() -> u64 {
294 1000
295}
296
297impl CardanoParameters {
298 pub(crate) fn save(&self, context: &impl IOContext) {
299 CARDANO_SECURITY_PARAMETER.save_to_file(&self.security_parameter, context);
300 CARDANO_ACTIVE_SLOTS_COEFF.save_to_file(&self.active_slots_coeff, context);
301 CARDANO_FIRST_EPOCH_NUMBER.save_to_file(&self.first_epoch_number, context);
302 CARDANO_FIRST_SLOT_NUMBER.save_to_file(&self.first_slot_number, context);
303 CARDANO_EPOCH_DURATION_MILLIS.save_to_file(&self.epoch_duration_millis, context);
304 CARDANO_FIRST_EPOCH_TIMESTAMP_MILLIS
305 .save_to_file(&self.first_epoch_timestamp_millis, context);
306 CARDANO_SLOT_DURATION_MILLIS.save_to_file(&self.slot_duration_millis, context);
307 }
308}
309
310impl From<CardanoParameters> for sidechain_domain::mainchain_epoch::MainchainEpochConfig {
311 fn from(value: CardanoParameters) -> Self {
312 Self {
313 first_epoch_timestamp_millis: Timestamp::from_unix_millis(
314 value.first_epoch_timestamp_millis,
315 ),
316 epoch_duration_millis: Duration::from_millis(value.epoch_duration_millis),
317 first_epoch_number: value.first_epoch_number,
318 first_slot_number: value.first_slot_number,
319 slot_duration_millis: Duration::from_millis(value.slot_duration_millis),
320 }
321 }
322}
323
324#[derive(Deserialize, Serialize, Parser, Clone, Debug)]
325pub(crate) struct SidechainParams {
326 #[arg(long)]
327 pub(crate) genesis_utxo: UtxoId,
328}
329
330impl Display for SidechainParams {
331 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
332 let json_string = serde_json::to_string_pretty(self).unwrap();
333 write!(f, "{}", &json_string)
334 }
335}
336
337#[derive(Deserialize, Serialize, Clone, Debug)]
338pub(crate) struct AssetConfig {
339 policy_id: String,
340 asset_name: String,
341}
342
343#[derive(Deserialize, Serialize, Clone, Debug)]
344pub(crate) struct NativeTokenConfig {
345 pub(crate) asset: AssetConfig,
346 pub(crate) illiquid_circulation_supply_validator_address: String,
347}
348
349#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
350pub(crate) struct GovernanceAuthoritiesKeyHashes(pub(crate) Vec<MainchainKeyHash>);
351
352impl FromStr for GovernanceAuthoritiesKeyHashes {
353 type Err = String;
354
355 fn from_str(s: &str) -> Result<Self, Self::Err> {
356 let mut key_hashes = vec![];
357 for x in s.split_whitespace() {
358 let trimmed = x.trim();
359 let key_hash = MainchainKeyHash::decode_hex(trimmed).map_err(|e| {
360 format!("'{}' is not a valid Cardano Key Hash, reason: {}", trimmed, e)
361 })?;
362 key_hashes.push(key_hash)
363 }
364 Ok(GovernanceAuthoritiesKeyHashes(key_hashes))
365 }
366}
367
368impl Display for GovernanceAuthoritiesKeyHashes {
369 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
370 let mut it = self.0.iter();
371 if let Some(key_hash) = it.next() {
373 f.write_str(&key_hash.to_hex_string())?;
374 for key_hash in it {
375 f.write_char(' ')?;
376 f.write_str(&key_hash.to_hex_string())?;
377 }
378 }
379 Ok(())
380 }
381}
382
383#[derive(Deserialize)]
384pub(crate) struct ChainConfig {
385 pub(crate) cardano: CardanoParameters,
386 pub(crate) chain_parameters: SidechainParams,
387 pub(crate) cardano_addresses: MainChainAddresses,
388}
389
390#[derive(Copy, Clone)]
391pub enum ConfigFile {
392 Chain,
393 Resources,
394}
395
396pub(crate) const KEYS_FILE_PATH: &str = "partner-chains-public-keys.json";
397pub(crate) const CHAIN_SPEC_PATH: &str = "chain-spec.json";
398
399pub(crate) fn load_chain_config(context: &impl IOContext) -> anyhow::Result<ChainConfig> {
400 let chain_config_file_path = context.config_file_path(ConfigFile::Chain);
401 if let Some(chain_config_file) = context.read_file(&chain_config_file_path) {
402 serde_json::from_str::<ChainConfig>(&chain_config_file)
403 .map_err(|err| anyhow::anyhow!(format!("⚠️ Chain config file {chain_config_file_path} is invalid: {err}. Run prepare-configuration wizard or fix errors manually.")))
404 } else {
405 Err(anyhow::anyhow!(format!(
406 "⚠️ Chain config file {chain_config_file_path} does not exists. Run prepare-configuration wizard first."
407 )))
408 }
409}
410
411pub(crate) mod config_fields {
412 use super::*;
413 use sidechain_domain::{AssetName, MainchainAddress, PolicyId, UtxoId};
414
415 pub(crate) const BRIDGE_TOKEN_POLICY: ConfigFieldDefinition<'static, PolicyId> =
416 ConfigFieldDefinition {
417 config_file: ConfigFile::Chain,
418 path: &["cardano_addresses", "bridge", "asset", "policy_id"],
419 name: "bridge token policy ID",
420 default: None,
421 _marker: PhantomData,
422 };
423
424 pub(crate) const BRIDGE_TOKEN_ASSET_NAME: ConfigFieldDefinition<'static, AssetName> =
425 ConfigFieldDefinition {
426 config_file: ConfigFile::Chain,
427 path: &["cardano_addresses", "bridge", "asset", "asset_name"],
428 name: "bridge token asset name in hex",
429 default: None,
430 _marker: PhantomData,
431 };
432
433 pub(crate) const ILLIQUID_SUPPLY_ADDRESS: ConfigFieldDefinition<'static, MainchainAddress> =
434 ConfigFieldDefinition {
435 config_file: ConfigFile::Chain,
436 path: &["cardano_addresses", "bridge", "illiquid_circulation_supply_validator_address"],
437 name: "bridge token illiquid circullation supply address",
438 default: None,
439 _marker: PhantomData,
440 };
441
442 pub(crate) const SUBSTRATE_NODE_DATA_BASE_PATH: ConfigFieldDefinition<'static, String> =
443 ConfigFieldDefinition {
444 config_file: ConfigFile::Resources,
445 path: &["substrate_node_base_path"],
446 name: "node base path",
447 default: Some("./data"),
448 _marker: PhantomData,
449 };
450
451 pub(crate) const CARDANO_PAYMENT_VERIFICATION_KEY_FILE: ConfigFieldDefinition<'static, String> =
452 ConfigFieldDefinition {
453 config_file: ConfigFile::Resources,
454 path: &["cardano_payment_verification_key_file"],
455 name: "path to the payment verification file",
456 default: Some("payment.vkey"),
457 _marker: PhantomData,
458 };
459
460 pub(crate) const CARDANO_PAYMENT_SIGNING_KEY_FILE: ConfigFieldDefinition<'static, String> =
461 ConfigFieldDefinition {
462 config_file: ConfigFile::Resources,
463 path: &["cardano_payment_signing_key_file"],
464 name: "path to the payment signing key file",
465 default: Some("payment.skey"),
466 _marker: PhantomData,
467 };
468
469 pub(crate) const CARDANO_COLD_VERIFICATION_KEY_FILE: ConfigFieldDefinition<'static, String> =
470 ConfigFieldDefinition {
471 config_file: ConfigFile::Resources,
472 path: &["cardano_cold_verification_key_file"],
473 name: "path to the cold verification key file",
474 default: Some("cold.vkey"),
475 _marker: PhantomData,
476 };
477
478 pub(crate) const GENESIS_UTXO: ConfigFieldDefinition<'static, UtxoId> = ConfigFieldDefinition {
479 config_file: ConfigFile::Chain,
480 path: &["chain_parameters", "genesis_utxo"],
481 name: "genesis utxo",
482 default: None,
483 _marker: PhantomData,
484 };
485
486 pub(crate) const BOOTNODES: ConfigFieldDefinition<'static, Vec<String>> =
487 ConfigFieldDefinition {
488 config_file: ConfigFile::Chain,
489 path: &["bootnodes"],
490 name: "bootnodes",
491 default: None,
492 _marker: PhantomData,
493 };
494
495 pub(crate) const INITIAL_PERMISSIONED_CANDIDATES: ConfigFieldDefinition<
496 'static,
497 Vec<crate::permissioned_candidates::PermissionedCandidateKeys>,
498 > = ConfigFieldDefinition {
499 config_file: ConfigFile::Chain,
500 path: &["initial_permissioned_candidates"],
501 name: "initial permissioned candidates",
502 default: None,
503 _marker: PhantomData,
504 };
505
506 pub(crate) const POSTGRES_CONNECTION_STRING: ConfigFieldDefinition<'static, String> =
507 ConfigFieldDefinition {
508 config_file: ConfigFile::Resources,
509 path: &["db_sync_postgres_connection_string"],
510 name: "DB-Sync Postgres connection string",
511 default: Some("postgresql://postgres-user:postgres-password@localhost:5432/cexplorer"),
512 _marker: PhantomData,
513 };
514
515 pub(crate) const CARDANO_DATA_SOURCE: ConfigFieldDefinition<'static, String> =
516 ConfigFieldDefinition {
517 config_file: ConfigFile::Resources,
518 path: &["cardano_data_source"],
519 name: "Cardano data source",
520 default: Some("db-sync"),
521 _marker: PhantomData,
522 };
523
524 pub(crate) const OGMIOS_PROTOCOL: ConfigFieldDefinition<'static, NetworkProtocol> =
525 ConfigFieldDefinition {
526 config_file: ConfigFile::Resources,
527 path: &["ogmios", "protocol"],
528 name: "Ogmios protocol (http/https)",
529 default: Some("http"),
530 _marker: PhantomData,
531 };
532
533 pub(crate) const OGMIOS_HOSTNAME: ConfigFieldDefinition<'static, String> =
534 ConfigFieldDefinition {
535 config_file: ConfigFile::Resources,
536 path: &["ogmios", "hostname"],
537 name: "Ogmios hostname",
538 default: Some("localhost"),
539 _marker: PhantomData,
540 };
541
542 pub(crate) const OGMIOS_PORT: ConfigFieldDefinition<'static, u16> = ConfigFieldDefinition {
543 config_file: ConfigFile::Resources,
544 path: &["ogmios", "port"],
545 name: "Ogmios port",
546 default: Some("1337"),
547 _marker: PhantomData,
548 };
549
550 pub(crate) const OGMIOS_REQUEST_TIMEOUT: ConfigFieldDefinition<'static, u64> =
551 ConfigFieldDefinition {
552 config_file: ConfigFile::Resources,
553 path: &["ogmios", "request_timeout"],
554 name: "Ogmios request timeout [seconds]",
555 default: Some("180"),
556 _marker: PhantomData,
557 };
558
559 pub(crate) const COMMITTEE_CANDIDATES_ADDRESS: ConfigFieldDefinition<
560 'static,
561 MainchainAddress,
562 > = ConfigFieldDefinition {
563 config_file: ConfigFile::Chain,
564 path: &["cardano_addresses", "committee_candidates_address"],
565 name: "Committee candidates address",
566 default: None,
567 _marker: PhantomData,
568 };
569
570 pub(crate) const D_PARAMETER_POLICY_ID: ConfigFieldDefinition<'static, PolicyId> =
571 ConfigFieldDefinition {
572 config_file: ConfigFile::Chain,
573 path: &["cardano_addresses", "d_parameter_policy_id"],
574 name: "D parameter policy id",
575 default: None,
576 _marker: PhantomData,
577 };
578
579 pub(crate) const PERMISSIONED_CANDIDATES_POLICY_ID: ConfigFieldDefinition<'static, PolicyId> =
580 ConfigFieldDefinition {
581 config_file: ConfigFile::Chain,
582 path: &["cardano_addresses", "permissioned_candidates_policy_id"],
583 name: "permissioned candidates policy id",
584 default: None,
585 _marker: PhantomData,
586 };
587
588 pub(crate) const GOVERNED_MAP_VALIDATOR_ADDRESS: ConfigFieldDefinition<
589 'static,
590 MainchainAddress,
591 > = ConfigFieldDefinition {
592 config_file: ConfigFile::Chain,
593 path: &["cardano_addresses", "governed_map", "validator_address"],
594 name: "Governed Map Validator Address",
595 default: None,
596 _marker: PhantomData,
597 };
598
599 pub(crate) const GOVERNED_MAP_POLICY_ID: ConfigFieldDefinition<'static, PolicyId> =
600 ConfigFieldDefinition {
601 config_file: ConfigFile::Chain,
602 path: &["cardano_addresses", "governed_map", "policy_id"],
603 name: "Governed Map Policy Id",
604 default: None,
605 _marker: PhantomData,
606 };
607
608 pub(crate) const CARDANO_SECURITY_PARAMETER: ConfigFieldDefinition<'static, u64> =
609 ConfigFieldDefinition {
610 config_file: ConfigFile::Chain,
611 path: &["cardano", "security_parameter"],
612 name: "cardano security parameter",
613 default: None,
614 _marker: PhantomData,
615 };
616
617 pub(crate) const CARDANO_ACTIVE_SLOTS_COEFF: ConfigFieldDefinition<'static, f64> =
618 ConfigFieldDefinition {
619 config_file: ConfigFile::Chain,
620 path: &["cardano", "active_slots_coeff"],
621 name: "cardano active slot coefficient",
622 default: None,
623 _marker: PhantomData,
624 };
625
626 pub(crate) const CARDANO_FIRST_EPOCH_NUMBER: ConfigFieldDefinition<'static, u32> =
627 ConfigFieldDefinition {
628 config_file: ConfigFile::Chain,
629 path: &["cardano", "first_epoch_number"],
630 name: "cardano first epoch number in shelley era",
631 default: None,
632 _marker: PhantomData,
633 };
634
635 pub(crate) const CARDANO_FIRST_SLOT_NUMBER: ConfigFieldDefinition<'static, u64> =
636 ConfigFieldDefinition {
637 config_file: ConfigFile::Chain,
638 path: &["cardano", "first_slot_number"],
639 name: "cardano first slot number in shelley era",
640 default: None,
641 _marker: PhantomData,
642 };
643
644 pub(crate) const CARDANO_EPOCH_DURATION_MILLIS: ConfigFieldDefinition<'static, u64> =
645 ConfigFieldDefinition {
646 config_file: ConfigFile::Chain,
647 path: &["cardano", "epoch_duration_millis"],
648 name: "cardano epoch duration in millis",
649 default: None,
650 _marker: PhantomData,
651 };
652
653 pub(crate) const CARDANO_FIRST_EPOCH_TIMESTAMP_MILLIS: ConfigFieldDefinition<'static, u64> =
654 ConfigFieldDefinition {
655 config_file: ConfigFile::Chain,
656 path: &["cardano", "first_epoch_timestamp_millis"],
657 name: "cardano first shelley epoch timestamp in millis",
658 default: None,
659 _marker: PhantomData,
660 };
661
662 pub(crate) const CARDANO_SLOT_DURATION_MILLIS: ConfigFieldDefinition<'static, u64> =
663 ConfigFieldDefinition {
664 config_file: ConfigFile::Chain,
665 path: &["cardano", "slot_duration_millis"],
666 name: "cardano slot duration in millis",
667 default: Some("1000"),
668 _marker: PhantomData,
669 };
670
671 pub(crate) const NODE_P2P_PORT: ConfigFieldDefinition<'static, u16> = ConfigFieldDefinition {
672 config_file: ConfigFile::Resources,
673 path: &["node_p2p_port"],
674 name: "substrate-node p2p protocol TCP port",
675 default: Some("30333"),
676 _marker: PhantomData,
677 };
678
679 pub(crate) const INITIAL_GOVERNANCE_AUTHORITIES: ConfigFieldDefinition<
680 'static,
681 GovernanceAuthoritiesKeyHashes,
682 > = ConfigFieldDefinition {
683 config_file: ConfigFile::Chain,
684 path: &["initial_governance", "authorities"],
685 name: "space separated keys hashes of the initial Multisig Governance Authorities",
686 default: Some("[]"),
687 _marker: PhantomData,
688 };
689
690 pub(crate) const INITIAL_GOVERNANCE_THRESHOLD: ConfigFieldDefinition<'static, u8> =
691 ConfigFieldDefinition {
692 config_file: ConfigFile::Chain,
693 path: &["initial_governance", "threshold"],
694 name: "Initial Multisig Governance Threshold",
695 default: Some("1"),
696 _marker: PhantomData,
697 };
698}