Skip to main content

drive_abci/
config.rs

1use crate::logging::LogConfigs;
2use crate::utils::from_str_or_number;
3use crate::{abci::config::AbciConfig, error::Error};
4use bincode::{Decode, Encode};
5use dpp::dashcore::Network;
6use dpp::dashcore_rpc::json::QuorumType;
7use dpp::util::deserializer::ProtocolVersion;
8use dpp::version::INITIAL_PROTOCOL_VERSION;
9use drive::config::DriveConfig;
10use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize};
11use std::path::PathBuf;
12
13/// Configuration for Dash Core RPC client used in consensus logic
14#[derive(Clone, Debug, Serialize, Deserialize, Default)]
15pub struct ConsensusCoreRpcConfig {
16    /// Core RPC client hostname or IP address
17    #[serde(rename = "core_consensus_json_rpc_host")]
18    pub host: String,
19
20    /// Core RPC client port number
21    #[serde(
22        rename = "core_consensus_json_rpc_port",
23        deserialize_with = "from_str_or_number"
24    )]
25    pub port: u16,
26
27    /// Core RPC client username
28    #[serde(rename = "core_consensus_json_rpc_username")]
29    pub username: String,
30
31    /// Core RPC client password
32    #[serde(rename = "core_consensus_json_rpc_password")]
33    pub password: String,
34}
35
36impl ConsensusCoreRpcConfig {
37    /// Return core address in the `host:port` format.
38    pub fn url(&self) -> String {
39        format!("{}:{}", self.host, self.port)
40    }
41}
42
43/// Configuration for Dash Core RPC client used in check tx
44#[derive(Clone, Debug, Serialize, Deserialize, Default)]
45pub struct CheckTxCoreRpcConfig {
46    /// Core RPC client hostname or IP address
47    #[serde(rename = "core_check_tx_json_rpc_host")]
48    pub host: String,
49
50    /// Core RPC client port number
51    #[serde(
52        rename = "core_check_tx_json_rpc_port",
53        deserialize_with = "from_str_or_number"
54    )]
55    pub port: u16,
56
57    /// Core RPC client username
58    #[serde(rename = "core_check_tx_json_rpc_username")]
59    pub username: String,
60
61    /// Core RPC client password
62    #[serde(rename = "core_check_tx_json_rpc_password")]
63    pub password: String,
64}
65
66impl CheckTxCoreRpcConfig {
67    /// Return core address in the `host:port` format.
68    pub fn url(&self) -> String {
69        format!("{}:{}", self.host, self.port)
70    }
71}
72
73/// Configuration for Dash Core related things
74#[derive(Clone, Debug, Serialize, Deserialize)]
75#[serde(default)]
76#[derive(Default)]
77pub struct CoreConfig {
78    /// Core RPC config for consensus
79    #[serde(flatten)]
80    pub consensus_rpc: ConsensusCoreRpcConfig,
81    /// Core RPC config for check tx
82    #[serde(flatten)]
83    pub check_tx_rpc: CheckTxCoreRpcConfig,
84}
85
86/// Configuration of the execution part of Dash Platform.
87#[derive(Clone, Debug, Serialize, Deserialize)]
88// NOTE: in renames, we use lower_snake_case, because uppercase does not work; see
89// https://github.com/softprops/envy/issues/61 and https://github.com/softprops/envy/pull/69
90pub struct ExecutionConfig {
91    /// Should we use document triggers? Useful to set as `false` for tests
92    #[serde(default = "ExecutionConfig::default_use_document_triggers")]
93    pub use_document_triggers: bool,
94
95    /// Should we verify sum trees? Useful to set as `false` for tests
96    #[serde(default = "ExecutionConfig::default_verify_sum_trees")]
97    pub verify_sum_trees: bool,
98
99    /// Should we verify sum trees? Useful to set as `false` for tests
100    #[serde(default = "ExecutionConfig::default_verify_token_sum_trees")]
101    pub verify_token_sum_trees: bool,
102
103    /// How long in seconds should an epoch last
104    /// It might last a lot longer if the chain is halted
105    #[serde(
106        default = "ExecutionConfig::default_epoch_time_length_s",
107        deserialize_with = "from_str_or_number"
108    )]
109    pub epoch_time_length_s: u64,
110}
111
112/// Configuration of Dash Platform.
113///
114/// All fields in this struct can be configured using environment variables.
115/// These variables can also be defined in `.env` file in the current directory
116/// or its parents. You can also provide path to the .env file as a command-line argument.
117///
118/// Environment variables should be renamed to `SCREAMING_SNAKE_CASE`.
119/// For example, to define [`verify_sum_trees`], you should set VERIFY_SUM_TREES
120/// environment variable:
121///
122/// ``
123/// export VERIFY_SUM_TREES=true
124/// ``
125///
126/// [`verify_sum_trees`]: PlatformConfig::verify_sum_trees
127#[derive(Clone, Debug, Serialize)]
128// NOTE: in renames, we use lower_snake_case, because uppercase does not work; see
129// https://github.com/softprops/envy/issues/61 and https://github.com/softprops/envy/pull/69
130pub struct PlatformConfig {
131    /// The network type
132    pub network: Network,
133    /// Drive configuration
134    #[serde(flatten)]
135    pub drive: DriveConfig,
136
137    /// Dash Core config
138    #[serde(flatten)]
139    pub core: CoreConfig,
140
141    /// ABCI Application Server config
142    #[serde(flatten)]
143    pub abci: AbciConfig,
144
145    /// Address to listen for Prometheus connection.
146    ///
147    /// Optional.
148    ///
149    /// /// Address should be an URL with scheme `http://`, for example:
150    /// - `http://127.0.0.1:29090`
151    ///
152    /// Port number defaults to [crate::metrics::DEFAULT_PROMETHEUS_PORT].
153    pub prometheus_bind_address: Option<String>,
154
155    /// Address to listen for gRPC connection.
156    pub grpc_bind_address: String,
157
158    /// Execution config
159    #[serde(flatten)]
160    pub execution: ExecutionConfig,
161
162    /// The default quorum type
163    #[serde(flatten)]
164    pub validator_set: ValidatorSetConfig,
165
166    /// Chain lock configuration
167    #[serde(flatten)]
168    pub chain_lock: ChainLockConfig,
169
170    /// Instant lock configuration
171    #[serde(flatten)]
172    pub instant_lock: InstantLockConfig,
173
174    // todo: this should probably be coming from Tenderdash config. It's a test only param
175    /// Approximately how often are blocks produced
176    pub block_spacing_ms: u64,
177
178    /// Path to data storage
179    pub db_path: PathBuf,
180
181    /// Path to store rejected / invalid items (like transactions).
182    /// Used mainly for debugging.
183    ///
184    /// If not set, rejected and invalid items will not be stored.
185    pub rejections_path: Option<PathBuf>,
186
187    #[cfg(feature = "testing-config")]
188    /// This should be None, except in the case of Testing platform
189    #[serde(skip)]
190    pub testing_configs: PlatformTestConfig,
191
192    /// Enable tokio console (console feature must be enabled)
193    pub tokio_console_enabled: bool,
194
195    // TODO: Use from_str_to_socket_address
196    /// Tokio console address to connect to
197    pub tokio_console_address: String,
198
199    /// Number of seconds to store task information if there is no clients connected
200    pub tokio_console_retention_secs: u64,
201}
202
203// Define an intermediate struct that mirrors PlatformConfig
204#[derive(Deserialize)]
205struct PlatformConfigIntermediate {
206    /// The network type
207    #[serde(
208        default = "PlatformConfig::default_network",
209        deserialize_with = "from_str_to_network_with_aliases"
210    )]
211    pub network: Network,
212    /// Drive configuration
213    #[serde(flatten)]
214    pub drive: DriveConfig,
215    // Include all other fields
216    #[serde(flatten)]
217    pub core: CoreConfig,
218    #[serde(flatten)]
219    pub abci: AbciConfig,
220    pub prometheus_bind_address: Option<String>,
221    pub grpc_bind_address: String,
222    #[serde(flatten)]
223    pub execution: ExecutionConfig,
224    #[serde(flatten)]
225    pub validator_set: ValidatorSetConfig,
226    #[serde(flatten)]
227    pub chain_lock: ChainLockConfig,
228    #[serde(flatten)]
229    pub instant_lock: InstantLockConfig,
230    pub block_spacing_ms: u64,
231    #[serde(default = "PlatformConfig::default_initial_protocol_version")]
232    // TODO: Is not using
233    #[allow(dead_code)]
234    pub initial_protocol_version: ProtocolVersion,
235    pub db_path: PathBuf,
236    #[serde(default)]
237    pub rejections_path: Option<PathBuf>,
238    #[cfg(feature = "testing-config")]
239    #[serde(skip)]
240    pub testing_configs: PlatformTestConfig,
241    pub tokio_console_enabled: bool,
242    #[serde(default = "PlatformConfig::default_tokio_console_address")]
243    pub tokio_console_address: String,
244    #[serde(default = "PlatformConfig::default_tokio_console_retention_secs")]
245    pub tokio_console_retention_secs: u64,
246}
247
248impl<'de> Deserialize<'de> for PlatformConfig {
249    fn deserialize<D>(deserializer: D) -> Result<PlatformConfig, D::Error>
250    where
251        D: Deserializer<'de>,
252    {
253        // First, deserialize into an intermediate struct
254        let mut config = PlatformConfigIntermediate::deserialize(deserializer)?;
255
256        // Set drive.network = network
257        config.drive.network = config.network;
258
259        // Convert the intermediate struct into your actual PlatformConfig
260        Ok(PlatformConfig {
261            network: config.network,
262            drive: config.drive,
263            // Copy other fields
264            core: config.core,
265            abci: config.abci,
266            prometheus_bind_address: config.prometheus_bind_address,
267            grpc_bind_address: config.grpc_bind_address,
268            execution: config.execution,
269            validator_set: config.validator_set,
270            chain_lock: config.chain_lock,
271            instant_lock: config.instant_lock,
272            block_spacing_ms: config.block_spacing_ms,
273            db_path: config.db_path,
274            rejections_path: config.rejections_path,
275            #[cfg(feature = "testing-config")]
276            testing_configs: config.testing_configs,
277            tokio_console_enabled: config.tokio_console_enabled,
278            tokio_console_address: config.tokio_console_address,
279            tokio_console_retention_secs: config.tokio_console_retention_secs,
280        })
281    }
282}
283
284fn from_str_to_network_with_aliases<'de, D>(deserializer: D) -> Result<Network, D::Error>
285where
286    D: serde::Deserializer<'de>,
287{
288    let network_name = String::deserialize(deserializer)?;
289
290    match network_name.to_lowercase().as_str() {
291        "dash" | "mainnet" | "main" => Ok(Network::Mainnet),
292        "local" | "regtest" => Ok(Network::Regtest),
293        "testnet" | "test" => Ok(Network::Testnet),
294        "devnet" | "dev" => Ok(Network::Devnet),
295        _ => Err(serde::de::Error::custom(format!(
296            "can't parse network name: unknown network '{network_name}'"
297        ))),
298    }
299}
300
301/// A config suitable for a quorum configuration
302pub trait QuorumLikeConfig: Sized {
303    /// Quorum type
304    fn quorum_type(&self) -> QuorumType;
305
306    /// Quorum size
307    fn quorum_size(&self) -> u16;
308
309    /// Quorum DKG interval
310    fn quorum_window(&self) -> u32;
311
312    /// Quorum active signers count
313    fn quorum_active_signers(&self) -> u16;
314
315    /// Quorum rotation (dip24) or classic
316    fn quorum_rotation(&self) -> bool;
317}
318
319/// Chain Lock quorum configuration
320#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
321pub struct ValidatorSetConfig {
322    /// The quorum type used for verifying chain locks
323    #[serde(
324        rename = "validator_set_quorum_type",
325        serialize_with = "serialize_quorum_type",
326        deserialize_with = "deserialize_quorum_type"
327    )]
328    pub quorum_type: QuorumType,
329
330    /// The quorum size
331    #[serde(
332        rename = "validator_set_quorum_size",
333        deserialize_with = "from_str_or_number"
334    )]
335    pub quorum_size: u16,
336
337    /// The quorum window (DKG interval)
338    /// On Mainnet Chain Locks are signed using 400_60: One quorum in every 288 blocks and activeQuorumCount is 4.
339    /// On Testnet Chain Locks are signed using 50_60: One quorum in every 24 blocks and activeQuorumCount is 24.
340    #[serde(
341        rename = "validator_set_quorum_window",
342        deserialize_with = "from_str_or_number"
343    )]
344    pub quorum_window: u32,
345
346    /// The number of active signers
347    #[serde(
348        rename = "validator_set_quorum_active_signers",
349        deserialize_with = "from_str_or_number"
350    )]
351    pub quorum_active_signers: u16,
352
353    /// Whether the quorum is rotated DIP24 or classic
354    #[serde(
355        rename = "validator_set_quorum_rotation",
356        deserialize_with = "from_str_or_number"
357    )]
358    pub quorum_rotation: bool,
359}
360
361impl Default for ValidatorSetConfig {
362    fn default() -> Self {
363        // Mainnet
364        Self::default_100_67()
365    }
366}
367
368impl ValidatorSetConfig {
369    /// Creates a default config for LLMQ 100 67
370    pub fn default_100_67() -> Self {
371        Self {
372            quorum_type: QuorumType::Llmq100_67,
373            quorum_size: 100,
374            quorum_window: 24,
375            quorum_active_signers: 24,
376            quorum_rotation: false,
377        }
378    }
379}
380
381impl QuorumLikeConfig for ValidatorSetConfig {
382    fn quorum_type(&self) -> QuorumType {
383        self.quorum_type
384    }
385
386    fn quorum_size(&self) -> u16 {
387        self.quorum_size
388    }
389
390    fn quorum_window(&self) -> u32 {
391        self.quorum_window
392    }
393
394    fn quorum_active_signers(&self) -> u16 {
395        self.quorum_active_signers
396    }
397
398    fn quorum_rotation(&self) -> bool {
399        self.quorum_rotation
400    }
401}
402
403/// Chain Lock quorum configuration
404#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
405pub struct ChainLockConfig {
406    /// The quorum type used for verifying chain locks
407    #[serde(
408        rename = "chain_lock_quorum_type",
409        serialize_with = "serialize_quorum_type",
410        deserialize_with = "deserialize_quorum_type"
411    )]
412    pub quorum_type: QuorumType,
413
414    /// The quorum size
415    #[serde(
416        rename = "chain_lock_quorum_size",
417        deserialize_with = "from_str_or_number"
418    )]
419    pub quorum_size: u16,
420
421    /// The quorum window (DKG interval)
422    /// On Mainnet Chain Locks are signed using 400_60: One quorum in every 288 blocks and activeQuorumCount is 4.
423    /// On Testnet Chain Locks are signed using 50_60: One quorum in every 24 blocks and activeQuorumCount is 24.
424    #[serde(
425        rename = "chain_lock_quorum_window",
426        deserialize_with = "from_str_or_number"
427    )]
428    pub quorum_window: u32,
429
430    /// The number of active signers
431    #[serde(
432        rename = "chain_lock_quorum_active_signers",
433        deserialize_with = "from_str_or_number"
434    )]
435    pub quorum_active_signers: u16,
436
437    /// Whether the quorum is rotated DIP24 or classic
438    #[serde(
439        rename = "chain_lock_quorum_rotation",
440        deserialize_with = "from_str_or_number"
441    )]
442    pub quorum_rotation: bool,
443}
444
445impl Default for ChainLockConfig {
446    fn default() -> Self {
447        // Mainnet
448        Self {
449            quorum_type: QuorumType::Llmq400_60,
450            quorum_size: 400,
451            quorum_window: 24 * 12,
452            quorum_active_signers: 4,
453            quorum_rotation: false,
454        }
455    }
456}
457
458impl QuorumLikeConfig for ChainLockConfig {
459    fn quorum_type(&self) -> QuorumType {
460        self.quorum_type
461    }
462
463    fn quorum_size(&self) -> u16 {
464        self.quorum_size
465    }
466
467    fn quorum_window(&self) -> u32 {
468        self.quorum_window
469    }
470
471    fn quorum_active_signers(&self) -> u16 {
472        self.quorum_active_signers
473    }
474
475    fn quorum_rotation(&self) -> bool {
476        self.quorum_rotation
477    }
478}
479
480impl ChainLockConfig {
481    /// Creates a default config for LLMQ 100 67
482    pub fn default_100_67() -> Self {
483        Self {
484            quorum_type: QuorumType::Llmq100_67,
485            quorum_size: 100,
486            quorum_window: 24,
487            quorum_active_signers: 24,
488            quorum_rotation: false,
489        }
490    }
491}
492
493/// Chain Lock quorum configuration
494#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
495pub struct InstantLockConfig {
496    /// The quorum type used for verifying chain locks
497    #[serde(
498        rename = "instant_lock_quorum_type",
499        serialize_with = "serialize_quorum_type",
500        deserialize_with = "deserialize_quorum_type"
501    )]
502    pub quorum_type: QuorumType,
503
504    /// The quorum size
505    #[serde(
506        rename = "instant_lock_quorum_size",
507        deserialize_with = "from_str_or_number"
508    )]
509    pub quorum_size: u16,
510
511    /// The quorum window (DKG interval)
512    /// On Mainnet Chain Locks are signed using 400_60: One quorum in every 288 blocks and activeQuorumCount is 4.
513    /// On Testnet Chain Locks are signed using 50_60: One quorum in every 24 blocks and activeQuorumCount is 24.
514    #[serde(
515        rename = "instant_lock_quorum_window",
516        deserialize_with = "from_str_or_number"
517    )]
518    pub quorum_window: u32,
519
520    /// The number of active signers
521    #[serde(
522        rename = "instant_lock_quorum_active_signers",
523        deserialize_with = "from_str_or_number"
524    )]
525    pub quorum_active_signers: u16,
526
527    /// Whether the quorum is rotated DIP24 or classic
528    #[serde(
529        rename = "instant_lock_quorum_rotation",
530        deserialize_with = "from_str_or_number"
531    )]
532    pub quorum_rotation: bool,
533}
534
535impl Default for InstantLockConfig {
536    fn default() -> Self {
537        // Mainnet
538        Self {
539            quorum_type: QuorumType::Llmq60_75,
540            quorum_active_signers: 32,
541            quorum_size: 60,
542            quorum_window: 24 * 12,
543            quorum_rotation: true,
544        }
545    }
546}
547
548impl InstantLockConfig {
549    /// Creates a default config for LLMQ 100 67
550    pub fn default_100_67() -> Self {
551        Self {
552            quorum_type: QuorumType::Llmq100_67,
553            quorum_size: 100,
554            quorum_window: 24,
555            quorum_active_signers: 24,
556            quorum_rotation: false,
557        }
558    }
559}
560
561impl QuorumLikeConfig for InstantLockConfig {
562    fn quorum_type(&self) -> QuorumType {
563        self.quorum_type
564    }
565
566    fn quorum_size(&self) -> u16 {
567        self.quorum_size
568    }
569
570    fn quorum_window(&self) -> u32 {
571        self.quorum_window
572    }
573
574    fn quorum_active_signers(&self) -> u16 {
575        self.quorum_active_signers
576    }
577
578    fn quorum_rotation(&self) -> bool {
579        self.quorum_rotation
580    }
581}
582
583fn serialize_quorum_type<S>(quorum_type: &QuorumType, serializer: S) -> Result<S::Ok, S::Error>
584where
585    S: serde::Serializer,
586{
587    serializer.serialize_str(quorum_type.to_string().as_str())
588}
589
590fn deserialize_quorum_type<'de, D>(deserializer: D) -> Result<QuorumType, D::Error>
591where
592    D: serde::Deserializer<'de>,
593{
594    let quorum_type_name = String::deserialize(deserializer)?;
595
596    let quorum_type = if let Ok(t) = quorum_type_name.trim().parse::<u32>() {
597        QuorumType::from(t)
598    } else {
599        QuorumType::from(quorum_type_name.as_str())
600    };
601
602    if quorum_type == QuorumType::UNKNOWN {
603        return Err(serde::de::Error::custom(format!(
604            "unsupported QUORUM_TYPE: {}",
605            quorum_type_name
606        )));
607    };
608
609    Ok(quorum_type)
610}
611
612impl ExecutionConfig {
613    fn default_verify_sum_trees() -> bool {
614        true
615    }
616
617    fn default_verify_token_sum_trees() -> bool {
618        true
619    }
620
621    fn default_use_document_triggers() -> bool {
622        true
623    }
624
625    fn default_epoch_time_length_s() -> u64 {
626        788400
627    }
628}
629
630impl PlatformConfig {
631    fn default_initial_protocol_version() -> ProtocolVersion {
632        INITIAL_PROTOCOL_VERSION
633    }
634
635    fn default_network() -> Network {
636        Network::Mainnet
637    }
638
639    fn default_tokio_console_address() -> String {
640        String::from("127.0.0.1:6669")
641    }
642
643    fn default_tokio_console_retention_secs() -> u64 {
644        60 * 3
645    }
646}
647
648/// create new object using values from environment variables
649pub trait FromEnv {
650    /// create new object using values from environment variables
651    fn from_env() -> Result<Self, Error>
652    where
653        Self: Sized + DeserializeOwned,
654    {
655        envy::from_env::<Self>().map_err(Error::from)
656    }
657}
658
659impl FromEnv for PlatformConfig {
660    fn from_env() -> Result<Self, Error>
661    where
662        Self: Sized + DeserializeOwned,
663    {
664        let mut me = envy::from_env::<Self>().map_err(Error::from)?;
665        me.abci.log = LogConfigs::from_env()?;
666
667        Ok(me)
668    }
669}
670
671impl Default for ExecutionConfig {
672    fn default() -> Self {
673        Self {
674            use_document_triggers: ExecutionConfig::default_use_document_triggers(),
675            verify_sum_trees: ExecutionConfig::default_verify_sum_trees(),
676            verify_token_sum_trees: ExecutionConfig::default_verify_token_sum_trees(),
677            epoch_time_length_s: ExecutionConfig::default_epoch_time_length_s(),
678        }
679    }
680}
681
682impl Default for PlatformConfig {
683    fn default() -> Self {
684        Self::default_mainnet()
685    }
686}
687
688/// The platform config
689impl PlatformConfig {
690    /// The default depending on the network
691    pub fn default_for_network(network: Network) -> Self {
692        match network {
693            Network::Mainnet => Self::default_mainnet(),
694            Network::Testnet => Self::default_testnet(),
695            Network::Devnet => Self::default_devnet(),
696            Network::Regtest => Self::default_local(),
697        }
698    }
699
700    /// The default local config
701    pub fn default_local() -> Self {
702        Self {
703            network: Network::Regtest,
704            validator_set: ValidatorSetConfig {
705                quorum_type: QuorumType::LlmqTestPlatform,
706                quorum_size: 3,
707                quorum_window: 24,
708                quorum_active_signers: 2,
709                quorum_rotation: false,
710            },
711            chain_lock: ChainLockConfig {
712                quorum_type: QuorumType::LlmqTest,
713                quorum_active_signers: 2,
714                quorum_size: 3,
715                quorum_window: 24,
716                quorum_rotation: false,
717            },
718            instant_lock: InstantLockConfig {
719                quorum_type: QuorumType::LlmqTest,
720                quorum_active_signers: 2,
721                quorum_size: 3,
722                quorum_window: 24,
723                quorum_rotation: false,
724            },
725            block_spacing_ms: 5000,
726            drive: Default::default(),
727            abci: Default::default(),
728            core: Default::default(),
729            execution: Default::default(),
730            db_path: PathBuf::from("/var/lib/dash-platform/data"),
731            rejections_path: Some(PathBuf::from("/var/log/dash/rejected")),
732            #[cfg(feature = "testing-config")]
733            testing_configs: PlatformTestConfig::default(),
734            tokio_console_enabled: false,
735            tokio_console_address: PlatformConfig::default_tokio_console_address(),
736            tokio_console_retention_secs: PlatformConfig::default_tokio_console_retention_secs(),
737            prometheus_bind_address: None,
738            grpc_bind_address: "127.0.0.1:26670".to_string(),
739        }
740    }
741
742    /// The default devnet config
743    pub fn default_devnet() -> Self {
744        Self {
745            network: Network::Regtest,
746            validator_set: ValidatorSetConfig {
747                quorum_type: QuorumType::LlmqDevnetPlatform,
748                quorum_size: 12,
749                quorum_window: 24,
750                quorum_active_signers: 8,
751                quorum_rotation: false,
752            },
753            chain_lock: ChainLockConfig {
754                quorum_type: QuorumType::LlmqDevnetPlatform,
755                quorum_size: 12,
756                quorum_window: 24,
757                quorum_active_signers: 8,
758                quorum_rotation: false,
759            },
760            instant_lock: InstantLockConfig {
761                quorum_type: QuorumType::LlmqDevnetDip0024,
762                quorum_active_signers: 4,
763                quorum_size: 8,
764                quorum_window: 48,
765                quorum_rotation: true,
766            },
767            block_spacing_ms: 5000,
768            drive: Default::default(),
769            abci: Default::default(),
770            core: Default::default(),
771            execution: Default::default(),
772            db_path: PathBuf::from("/var/lib/dash-platform/data"),
773            rejections_path: Some(PathBuf::from("/var/log/dash/rejected")),
774            #[cfg(feature = "testing-config")]
775            testing_configs: PlatformTestConfig::default(),
776            tokio_console_enabled: false,
777            tokio_console_address: PlatformConfig::default_tokio_console_address(),
778            tokio_console_retention_secs: PlatformConfig::default_tokio_console_retention_secs(),
779            prometheus_bind_address: None,
780            grpc_bind_address: "127.0.0.1:26670".to_string(),
781        }
782    }
783
784    /// The default testnet config
785    pub fn default_testnet() -> Self {
786        Self {
787            network: Network::Testnet,
788            validator_set: ValidatorSetConfig {
789                quorum_type: QuorumType::Llmq25_67,
790                quorum_size: 25,
791                quorum_window: 24,
792                quorum_active_signers: 24,
793                quorum_rotation: false,
794            },
795            chain_lock: ChainLockConfig {
796                quorum_type: QuorumType::Llmq50_60,
797                quorum_active_signers: 24,
798                quorum_size: 50,
799                quorum_window: 24,
800                quorum_rotation: false,
801            },
802            instant_lock: InstantLockConfig {
803                quorum_type: QuorumType::Llmq60_75,
804                quorum_active_signers: 32,
805                quorum_size: 60,
806                quorum_window: 24 * 12,
807                quorum_rotation: true,
808            },
809            block_spacing_ms: 5000,
810            drive: DriveConfig::default_testnet(),
811            abci: Default::default(),
812            core: Default::default(),
813            execution: Default::default(),
814            db_path: PathBuf::from("/var/lib/dash-platform/data"),
815            rejections_path: Some(PathBuf::from("/var/log/dash/rejected")),
816            #[cfg(feature = "testing-config")]
817            testing_configs: PlatformTestConfig::default(),
818            prometheus_bind_address: None,
819            grpc_bind_address: "127.0.0.1:26670".to_string(),
820            tokio_console_enabled: false,
821            tokio_console_address: PlatformConfig::default_tokio_console_address(),
822            tokio_console_retention_secs: PlatformConfig::default_tokio_console_retention_secs(),
823        }
824    }
825
826    /// The default mainnet config
827    pub fn default_mainnet() -> Self {
828        Self {
829            network: Network::Mainnet,
830            validator_set: ValidatorSetConfig {
831                quorum_type: QuorumType::Llmq100_67,
832                quorum_size: 100,
833                quorum_window: 24,
834                quorum_active_signers: 24,
835                quorum_rotation: false,
836            },
837            chain_lock: ChainLockConfig {
838                quorum_type: QuorumType::Llmq400_60,
839                quorum_active_signers: 4,
840                quorum_size: 400,
841                quorum_window: 24 * 12,
842                quorum_rotation: false,
843            },
844            instant_lock: InstantLockConfig {
845                quorum_type: QuorumType::Llmq60_75,
846                quorum_active_signers: 32,
847                quorum_size: 60,
848                quorum_window: 24 * 12,
849                quorum_rotation: true,
850            },
851            block_spacing_ms: 5000,
852            drive: Default::default(),
853            abci: Default::default(),
854            core: Default::default(),
855            execution: Default::default(),
856            db_path: PathBuf::from("/var/lib/dash-platform/data"),
857            rejections_path: Some(PathBuf::from("/var/log/dash/rejected")),
858            #[cfg(feature = "testing-config")]
859            testing_configs: PlatformTestConfig::default(),
860            prometheus_bind_address: None,
861            grpc_bind_address: "127.0.0.1:26670".to_string(),
862            tokio_console_enabled: false,
863            tokio_console_address: PlatformConfig::default_tokio_console_address(),
864            tokio_console_retention_secs: PlatformConfig::default_tokio_console_retention_secs(),
865        }
866    }
867}
868
869#[cfg(feature = "testing-config")]
870/// Configs that should only happen during testing
871#[derive(Clone, Debug)]
872pub struct PlatformTestConfig {
873    /// Block signing
874    pub block_signing: bool,
875    /// Storing of platform state
876    pub store_platform_state: bool,
877    /// Block signature verification
878    pub block_commit_signature_verification: bool,
879    /// Disable instant lock signature verification
880    pub disable_instant_lock_signature_verification: bool,
881    /// Disable temporarily disabled contested documents validation
882    pub disable_contested_documents_is_allowed_validation: bool,
883    /// Disable checkpoint creation during tests
884    pub disable_checkpoints: bool,
885}
886
887#[cfg(feature = "testing-config")]
888impl PlatformTestConfig {
889    /// Much faster config for tests
890    pub fn default_minimal_verifications() -> Self {
891        Self {
892            block_signing: false,
893            store_platform_state: false,
894            block_commit_signature_verification: false,
895            disable_instant_lock_signature_verification: true,
896            disable_contested_documents_is_allowed_validation: true,
897            disable_checkpoints: true,
898        }
899    }
900}
901
902#[cfg(feature = "testing-config")]
903impl Default for PlatformTestConfig {
904    fn default() -> Self {
905        Self {
906            block_signing: true,
907            store_platform_state: true,
908            block_commit_signature_verification: true,
909            disable_instant_lock_signature_verification: false,
910            disable_contested_documents_is_allowed_validation: true,
911            disable_checkpoints: true,
912        }
913    }
914}
915
916#[cfg(test)]
917mod tests {
918    use super::FromEnv;
919    use crate::config::{
920        ChainLockConfig, ConsensusCoreRpcConfig, CoreConfig, InstantLockConfig, PlatformConfig,
921        QuorumLikeConfig, ValidatorSetConfig,
922    };
923    use crate::logging::LogDestination;
924    use dpp::dashcore::Network;
925    use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType;
926    use std::env;
927
928    #[test]
929    fn test_config_from_env() {
930        // ABCI log configs are parsed manually, so they deserve separate handling
931        // Note that STDOUT is also defined in .env.example, but env var should overwrite it.
932        let vectors = &[
933            ("STDOUT", "pretty"),
934            ("UPPERCASE", "json"),
935            ("lowercase", "pretty"),
936            ("miXedC4s3", "full"),
937            ("123", "compact"),
938        ];
939        for vector in vectors {
940            env::set_var(format!("ABCI_LOG_{}_DESTINATION", vector.0), "bytes");
941            env::set_var(format!("ABCI_LOG_{}_FORMAT", vector.0), vector.1);
942        }
943
944        let envfile = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".env.local");
945
946        dotenvy::from_path(envfile.as_path()).expect("cannot load .env file");
947        assert_eq!("/tmp/db", env::var("DB_PATH").unwrap());
948        assert_eq!("/tmp/rejected", env::var("REJECTIONS_PATH").unwrap());
949
950        let config = super::PlatformConfig::from_env().expect("expected config from env");
951        assert!(config.execution.verify_sum_trees);
952        assert_ne!(config.validator_set.quorum_type, QuorumType::UNKNOWN);
953        for id in vectors {
954            matches!(config.abci.log[id.0].destination, LogDestination::Bytes);
955        }
956    }
957
958    #[test]
959    #[ignore]
960    //todo: re-enable
961    fn test_config_from_testnet_propagates_network() {
962        // ABCI log configs are parsed manually, so they deserve separate handling
963        // Note that STDOUT is also defined in .env.example, but env var should overwrite it.
964
965        let envfile = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".env.testnet");
966
967        dotenvy::from_path(envfile.as_path()).expect("cannot load .env file");
968
969        let config = super::PlatformConfig::from_env().expect("expected config from env");
970        assert!(config.execution.verify_sum_trees);
971        assert_eq!(config.validator_set.quorum_type, QuorumType::Llmq25_67);
972        assert_eq!(config.network, config.drive.network);
973        assert_eq!(config.network, Network::Testnet);
974    }
975
976    // --- `from_str_to_network_with_aliases`: valid aliases and error path ---
977
978    /// Ensures every network alias accepted by the deserializer maps to the
979    /// expected `Network`, case-insensitively. This exercises each match arm in
980    /// `from_str_to_network_with_aliases`, which is otherwise only called at
981    /// most once per integration-test setup.
982    #[test]
983    fn network_aliases_deserialize_all_variants_case_insensitively() {
984        let cases = &[
985            ("\"dash\"", Network::Mainnet),
986            ("\"DASH\"", Network::Mainnet),
987            ("\"MainNet\"", Network::Mainnet),
988            ("\"main\"", Network::Mainnet),
989            ("\"local\"", Network::Regtest),
990            ("\"regtest\"", Network::Regtest),
991            ("\"testnet\"", Network::Testnet),
992            ("\"TEST\"", Network::Testnet),
993            ("\"devnet\"", Network::Devnet),
994            ("\"Dev\"", Network::Devnet),
995        ];
996
997        for (input, expected) in cases {
998            let mut de = serde_json::Deserializer::from_str(input);
999            let network = super::from_str_to_network_with_aliases(&mut de)
1000                .expect("alias should parse successfully");
1001            assert_eq!(
1002                &network, expected,
1003                "input {} should map to {:?}",
1004                input, expected
1005            );
1006        }
1007    }
1008
1009    /// Verifies that an unknown network alias produces a clear deserializer error.
1010    /// This covers the final arm of `from_str_to_network_with_aliases`.
1011    #[test]
1012    fn network_alias_unknown_value_returns_custom_error() {
1013        let mut de = serde_json::Deserializer::from_str("\"nonsense_network\"");
1014        let err = super::from_str_to_network_with_aliases(&mut de)
1015            .expect_err("unknown network should fail");
1016        let msg = err.to_string();
1017        assert!(
1018            msg.contains("unknown network") && msg.contains("nonsense_network"),
1019            "error message should name the offending value, got: {}",
1020            msg
1021        );
1022    }
1023
1024    // --- `deserialize_quorum_type`: numeric/string/UNKNOWN error path ---
1025
1026    /// Covers the string-name branch of `deserialize_quorum_type`.
1027    #[test]
1028    fn deserialize_quorum_type_accepts_string_names() {
1029        // All numeric fields use `from_str_or_number`, which requires a JSON
1030        // string (even for numbers).
1031        let json = r#"{
1032            "validator_set_quorum_type": "llmq_25_67",
1033            "validator_set_quorum_size": "25",
1034            "validator_set_quorum_window": "24",
1035            "validator_set_quorum_active_signers": "24",
1036            "validator_set_quorum_rotation": "false"
1037        }"#;
1038        let cfg: ValidatorSetConfig =
1039            serde_json::from_str(json).expect("quorum type string should deserialize");
1040        assert_eq!(cfg.quorum_type, QuorumType::Llmq25_67);
1041    }
1042
1043    /// Covers the numeric (u32) branch of `deserialize_quorum_type` (a number
1044    /// serialized as a JSON string still goes through the `parse::<u32>()` path).
1045    #[test]
1046    fn deserialize_quorum_type_accepts_numeric_string() {
1047        let json = r#"{
1048            "validator_set_quorum_type": "6",
1049            "validator_set_quorum_size": "25",
1050            "validator_set_quorum_window": "24",
1051            "validator_set_quorum_active_signers": "24",
1052            "validator_set_quorum_rotation": "false"
1053        }"#;
1054        let cfg: ValidatorSetConfig =
1055            serde_json::from_str(json).expect("numeric quorum type should deserialize");
1056        // QuorumType::from(6) should be a known variant; we only assert it is not UNKNOWN.
1057        assert_ne!(cfg.quorum_type, QuorumType::UNKNOWN);
1058    }
1059
1060    /// Covers the `UNKNOWN` rejection branch of `deserialize_quorum_type`.
1061    #[test]
1062    fn deserialize_quorum_type_rejects_unknown_names() {
1063        let json = r#"{
1064            "validator_set_quorum_type": "this_is_not_a_quorum",
1065            "validator_set_quorum_size": "25",
1066            "validator_set_quorum_window": "24",
1067            "validator_set_quorum_active_signers": "24",
1068            "validator_set_quorum_rotation": "false"
1069        }"#;
1070        let err = serde_json::from_str::<ValidatorSetConfig>(json)
1071            .expect_err("unknown quorum type name should fail");
1072        let msg = err.to_string();
1073        assert!(
1074            msg.contains("unsupported") && msg.contains("QUORUM_TYPE"),
1075            "expected unsupported QUORUM_TYPE error, got: {}",
1076            msg
1077        );
1078    }
1079
1080    // --- Core RPC URL composition and defaults ---
1081
1082    /// Exercises `ConsensusCoreRpcConfig::url` formatting.
1083    #[test]
1084    fn consensus_core_rpc_url_composes_host_and_port() {
1085        let cfg = ConsensusCoreRpcConfig {
1086            host: "node.example".to_string(),
1087            port: 9998,
1088            username: "u".to_string(),
1089            password: "p".to_string(),
1090        };
1091        assert_eq!(cfg.url(), "node.example:9998");
1092    }
1093
1094    /// Exercises `CheckTxCoreRpcConfig::url` formatting.
1095    #[test]
1096    fn check_tx_core_rpc_url_composes_host_and_port() {
1097        let cfg = super::CheckTxCoreRpcConfig {
1098            host: "127.0.0.1".to_string(),
1099            port: 1,
1100            username: String::new(),
1101            password: String::new(),
1102        };
1103        assert_eq!(cfg.url(), "127.0.0.1:1");
1104    }
1105
1106    /// `CoreConfig::default()` should be empty strings and zero port (covers
1107    /// the auto-derived `Default` with both flattened members).
1108    #[test]
1109    fn core_config_default_is_empty() {
1110        let cfg = CoreConfig::default();
1111        assert_eq!(cfg.consensus_rpc.host, "");
1112        assert_eq!(cfg.consensus_rpc.port, 0);
1113        assert_eq!(cfg.check_tx_rpc.host, "");
1114        assert_eq!(cfg.check_tx_rpc.port, 0);
1115    }
1116
1117    // --- `default_for_network` dispatch, including the catch-all ---
1118
1119    /// Exercises the `Network::Mainnet`, `Network::Testnet`, `Network::Devnet`,
1120    /// `Network::Regtest` arms of `default_for_network`.
1121    #[test]
1122    fn default_for_network_dispatches_all_known_networks() {
1123        let mainnet = PlatformConfig::default_for_network(Network::Mainnet);
1124        assert_eq!(mainnet.network, Network::Mainnet);
1125        assert_eq!(mainnet.validator_set.quorum_type, QuorumType::Llmq100_67);
1126
1127        let testnet = PlatformConfig::default_for_network(Network::Testnet);
1128        assert_eq!(testnet.network, Network::Testnet);
1129        assert_eq!(testnet.validator_set.quorum_type, QuorumType::Llmq25_67);
1130
1131        let devnet = PlatformConfig::default_for_network(Network::Devnet);
1132        // default_devnet currently sets network to Regtest (see impl).
1133        assert_eq!(devnet.network, Network::Regtest);
1134
1135        let regtest = PlatformConfig::default_for_network(Network::Regtest);
1136        assert_eq!(regtest.network, Network::Regtest);
1137    }
1138
1139    // --- `ValidatorSetConfig::default_100_67` and QuorumLikeConfig getters ---
1140
1141    /// Exercises `default_100_67` + every getter in `QuorumLikeConfig` for
1142    /// `ValidatorSetConfig` - these getters are pure accessors that would
1143    /// otherwise be uncovered outside the actual consensus path.
1144    #[test]
1145    fn validator_set_default_100_67_accessors() {
1146        let cfg = ValidatorSetConfig::default_100_67();
1147        assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67);
1148        assert_eq!(cfg.quorum_size(), 100);
1149        assert_eq!(cfg.quorum_window(), 24);
1150        assert_eq!(cfg.quorum_active_signers(), 24);
1151        assert!(!cfg.quorum_rotation());
1152    }
1153
1154    /// Same but for `ChainLockConfig::default_100_67`.
1155    #[test]
1156    fn chain_lock_default_100_67_accessors() {
1157        let cfg = ChainLockConfig::default_100_67();
1158        assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67);
1159        assert_eq!(cfg.quorum_size(), 100);
1160        assert_eq!(cfg.quorum_window(), 24);
1161        assert_eq!(cfg.quorum_active_signers(), 24);
1162        assert!(!cfg.quorum_rotation());
1163    }
1164
1165    /// `ChainLockConfig::default()` is Mainnet LLMQ400_60 - cover all getters.
1166    #[test]
1167    fn chain_lock_default_is_llmq_400_60() {
1168        let cfg = ChainLockConfig::default();
1169        assert_eq!(cfg.quorum_type(), QuorumType::Llmq400_60);
1170        assert_eq!(cfg.quorum_size(), 400);
1171        assert_eq!(cfg.quorum_window(), 24 * 12);
1172        assert_eq!(cfg.quorum_active_signers(), 4);
1173        assert!(!cfg.quorum_rotation());
1174    }
1175
1176    /// `InstantLockConfig::default()` is DIP24 rotated LLMQ60_75.
1177    #[test]
1178    fn instant_lock_default_is_llmq_60_75_rotated() {
1179        let cfg = InstantLockConfig::default();
1180        assert_eq!(cfg.quorum_type(), QuorumType::Llmq60_75);
1181        assert_eq!(cfg.quorum_size(), 60);
1182        assert_eq!(cfg.quorum_window(), 24 * 12);
1183        assert_eq!(cfg.quorum_active_signers(), 32);
1184        assert!(cfg.quorum_rotation());
1185    }
1186
1187    /// `InstantLockConfig::default_100_67` is the classic LLMQ variant.
1188    #[test]
1189    fn instant_lock_default_100_67_accessors() {
1190        let cfg = InstantLockConfig::default_100_67();
1191        assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67);
1192        assert_eq!(cfg.quorum_size(), 100);
1193        assert_eq!(cfg.quorum_window(), 24);
1194        assert_eq!(cfg.quorum_active_signers(), 24);
1195        assert!(!cfg.quorum_rotation());
1196    }
1197
1198    /// Exercises `PlatformConfig::default()` which delegates to `default_mainnet`.
1199    #[test]
1200    fn platform_config_default_is_mainnet() {
1201        let cfg = PlatformConfig::default();
1202        assert_eq!(cfg.network, Network::Mainnet);
1203        assert_eq!(cfg.validator_set.quorum_type, QuorumType::Llmq100_67);
1204        assert_eq!(cfg.chain_lock.quorum_type, QuorumType::Llmq400_60);
1205        assert_eq!(cfg.instant_lock.quorum_type, QuorumType::Llmq60_75);
1206        assert_eq!(cfg.block_spacing_ms, 5000);
1207    }
1208
1209    // --- `ExecutionConfig` default values ---
1210
1211    /// Exercises all four default-provider functions on `ExecutionConfig`.
1212    #[test]
1213    fn execution_config_defaults() {
1214        let cfg = super::ExecutionConfig::default();
1215        assert!(cfg.verify_sum_trees);
1216        assert!(cfg.verify_token_sum_trees);
1217        assert!(cfg.use_document_triggers);
1218        assert_eq!(cfg.epoch_time_length_s, 788400);
1219    }
1220
1221    // --- `serialize_quorum_type` round-trip via Serialize ---
1222
1223    /// Covers the `serialize_quorum_type` codepath (symmetric pair of the
1224    /// deserialize tests above).
1225    #[test]
1226    fn validator_set_config_serializes_quorum_type_as_string() {
1227        let cfg = ValidatorSetConfig::default_100_67();
1228        let json = serde_json::to_string(&cfg).expect("valid serialize");
1229        // The serialized representation must use the textual form.
1230        assert!(
1231            json.contains("llmq_100_67") || json.contains("Llmq100_67"),
1232            "expected textual quorum type in JSON, got: {}",
1233            json
1234        );
1235    }
1236}