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#[derive(Clone, Debug, Serialize, Deserialize, Default)]
15pub struct ConsensusCoreRpcConfig {
16 #[serde(rename = "core_consensus_json_rpc_host")]
18 pub host: String,
19
20 #[serde(
22 rename = "core_consensus_json_rpc_port",
23 deserialize_with = "from_str_or_number"
24 )]
25 pub port: u16,
26
27 #[serde(rename = "core_consensus_json_rpc_username")]
29 pub username: String,
30
31 #[serde(rename = "core_consensus_json_rpc_password")]
33 pub password: String,
34}
35
36impl ConsensusCoreRpcConfig {
37 pub fn url(&self) -> String {
39 format!("{}:{}", self.host, self.port)
40 }
41}
42
43#[derive(Clone, Debug, Serialize, Deserialize, Default)]
45pub struct CheckTxCoreRpcConfig {
46 #[serde(rename = "core_check_tx_json_rpc_host")]
48 pub host: String,
49
50 #[serde(
52 rename = "core_check_tx_json_rpc_port",
53 deserialize_with = "from_str_or_number"
54 )]
55 pub port: u16,
56
57 #[serde(rename = "core_check_tx_json_rpc_username")]
59 pub username: String,
60
61 #[serde(rename = "core_check_tx_json_rpc_password")]
63 pub password: String,
64}
65
66impl CheckTxCoreRpcConfig {
67 pub fn url(&self) -> String {
69 format!("{}:{}", self.host, self.port)
70 }
71}
72
73#[derive(Clone, Debug, Serialize, Deserialize)]
75#[serde(default)]
76#[derive(Default)]
77pub struct CoreConfig {
78 #[serde(flatten)]
80 pub consensus_rpc: ConsensusCoreRpcConfig,
81 #[serde(flatten)]
83 pub check_tx_rpc: CheckTxCoreRpcConfig,
84}
85
86#[derive(Clone, Debug, Serialize, Deserialize)]
88pub struct ExecutionConfig {
91 #[serde(default = "ExecutionConfig::default_use_document_triggers")]
93 pub use_document_triggers: bool,
94
95 #[serde(default = "ExecutionConfig::default_verify_sum_trees")]
97 pub verify_sum_trees: bool,
98
99 #[serde(default = "ExecutionConfig::default_verify_token_sum_trees")]
101 pub verify_token_sum_trees: bool,
102
103 #[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#[derive(Clone, Debug, Serialize)]
128pub struct PlatformConfig {
131 pub network: Network,
133 #[serde(flatten)]
135 pub drive: DriveConfig,
136
137 #[serde(flatten)]
139 pub core: CoreConfig,
140
141 #[serde(flatten)]
143 pub abci: AbciConfig,
144
145 pub prometheus_bind_address: Option<String>,
154
155 pub grpc_bind_address: String,
157
158 #[serde(flatten)]
160 pub execution: ExecutionConfig,
161
162 #[serde(flatten)]
164 pub validator_set: ValidatorSetConfig,
165
166 #[serde(flatten)]
168 pub chain_lock: ChainLockConfig,
169
170 #[serde(flatten)]
172 pub instant_lock: InstantLockConfig,
173
174 pub block_spacing_ms: u64,
177
178 pub db_path: PathBuf,
180
181 pub rejections_path: Option<PathBuf>,
186
187 #[cfg(feature = "testing-config")]
188 #[serde(skip)]
190 pub testing_configs: PlatformTestConfig,
191
192 pub tokio_console_enabled: bool,
194
195 pub tokio_console_address: String,
198
199 pub tokio_console_retention_secs: u64,
201}
202
203#[derive(Deserialize)]
205struct PlatformConfigIntermediate {
206 #[serde(
208 default = "PlatformConfig::default_network",
209 deserialize_with = "from_str_to_network_with_aliases"
210 )]
211 pub network: Network,
212 #[serde(flatten)]
214 pub drive: DriveConfig,
215 #[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 #[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 let mut config = PlatformConfigIntermediate::deserialize(deserializer)?;
255
256 config.drive.network = config.network;
258
259 Ok(PlatformConfig {
261 network: config.network,
262 drive: config.drive,
263 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
301pub trait QuorumLikeConfig: Sized {
303 fn quorum_type(&self) -> QuorumType;
305
306 fn quorum_size(&self) -> u16;
308
309 fn quorum_window(&self) -> u32;
311
312 fn quorum_active_signers(&self) -> u16;
314
315 fn quorum_rotation(&self) -> bool;
317}
318
319#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
321pub struct ValidatorSetConfig {
322 #[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 #[serde(
332 rename = "validator_set_quorum_size",
333 deserialize_with = "from_str_or_number"
334 )]
335 pub quorum_size: u16,
336
337 #[serde(
341 rename = "validator_set_quorum_window",
342 deserialize_with = "from_str_or_number"
343 )]
344 pub quorum_window: u32,
345
346 #[serde(
348 rename = "validator_set_quorum_active_signers",
349 deserialize_with = "from_str_or_number"
350 )]
351 pub quorum_active_signers: u16,
352
353 #[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 Self::default_100_67()
365 }
366}
367
368impl ValidatorSetConfig {
369 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#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
405pub struct ChainLockConfig {
406 #[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 #[serde(
416 rename = "chain_lock_quorum_size",
417 deserialize_with = "from_str_or_number"
418 )]
419 pub quorum_size: u16,
420
421 #[serde(
425 rename = "chain_lock_quorum_window",
426 deserialize_with = "from_str_or_number"
427 )]
428 pub quorum_window: u32,
429
430 #[serde(
432 rename = "chain_lock_quorum_active_signers",
433 deserialize_with = "from_str_or_number"
434 )]
435 pub quorum_active_signers: u16,
436
437 #[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 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 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#[derive(Clone, Debug, Serialize, Deserialize, Encode, Decode)]
495pub struct InstantLockConfig {
496 #[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 #[serde(
506 rename = "instant_lock_quorum_size",
507 deserialize_with = "from_str_or_number"
508 )]
509 pub quorum_size: u16,
510
511 #[serde(
515 rename = "instant_lock_quorum_window",
516 deserialize_with = "from_str_or_number"
517 )]
518 pub quorum_window: u32,
519
520 #[serde(
522 rename = "instant_lock_quorum_active_signers",
523 deserialize_with = "from_str_or_number"
524 )]
525 pub quorum_active_signers: u16,
526
527 #[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 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 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
648pub trait FromEnv {
650 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
688impl PlatformConfig {
690 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 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 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 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 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#[derive(Clone, Debug)]
872pub struct PlatformTestConfig {
873 pub block_signing: bool,
875 pub store_platform_state: bool,
877 pub block_commit_signature_verification: bool,
879 pub disable_instant_lock_signature_verification: bool,
881 pub disable_contested_documents_is_allowed_validation: bool,
883 pub disable_checkpoints: bool,
885}
886
887#[cfg(feature = "testing-config")]
888impl PlatformTestConfig {
889 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 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 fn test_config_from_testnet_propagates_network() {
962 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 #[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 #[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 #[test]
1028 fn deserialize_quorum_type_accepts_string_names() {
1029 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 #[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 assert_ne!(cfg.quorum_type, QuorumType::UNKNOWN);
1058 }
1059
1060 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 assert!(
1231 json.contains("llmq_100_67") || json.contains("Llmq100_67"),
1232 "expected textual quorum type in JSON, got: {}",
1233 json
1234 );
1235 }
1236}