1use std::{
3 collections::BTreeSet,
4 fs::{self, File},
5 io::{self, Error, ErrorKind, Write},
6 path::Path,
7};
8
9use crate::{avalanchego::genesis, constants, units};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
22#[serde(rename_all = "kebab-case")]
23pub struct Config {
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub config_file: Option<String>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
31 pub genesis_file: Option<String>,
32
33 #[serde(default)]
40 pub network_id: u32,
41
42 #[serde(default)]
43 pub db_type: String,
44 #[serde(default)]
46 pub db_dir: String,
47 #[serde(default)]
49 pub chain_data_dir: String,
50
51 #[serde(default)]
53 pub log_dir: String,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
59 pub log_level: Option<String>,
60 #[serde(skip_serializing_if = "Option::is_none")]
63 pub log_format: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub log_display_level: Option<String>,
66
67 #[serde(default)]
69 pub http_port: u32,
70 #[serde(skip_serializing_if = "Option::is_none")]
73 pub http_host: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub http_tls_enabled: Option<bool>,
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub http_tls_key_file: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub http_tls_cert_file: Option<String>,
82 #[serde(skip_serializing_if = "Option::is_none")]
85 pub public_ip: Option<String>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub sybil_protection_enabled: Option<bool>,
89 #[serde(default)]
91 pub staking_port: u32,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub staking_tls_key_file: Option<String>,
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub staking_tls_cert_file: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
101 pub staking_signer_key_file: Option<String>,
102
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub bootstrap_ips: Option<String>,
105 #[serde(skip_serializing_if = "Option::is_none")]
106 pub bootstrap_ids: Option<String>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
111 pub snow_sample_size: Option<u32>,
112 #[serde(skip_serializing_if = "Option::is_none")]
115 pub snow_quorum_size: Option<u32>,
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub snow_concurrent_repolls: Option<u32>,
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub snow_max_time_processing: Option<String>,
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub snow_rogue_commit_threshold: Option<u32>,
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub snow_virtuous_commit_threshold: Option<u32>,
124
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub network_peer_list_gossip_frequency: Option<String>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 pub network_max_reconnect_delay: Option<String>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub index_enabled: Option<bool>,
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub index_allow_incomplete: Option<bool>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub api_admin_enabled: Option<bool>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub api_info_enabled: Option<bool>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub api_keystore_enabled: Option<bool>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub api_metrics_enabled: Option<bool>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub api_health_enabled: Option<bool>,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub api_ipcs_enabled: Option<bool>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
152 pub track_subnets: Option<String>,
153
154 #[serde(default)]
157 pub plugin_dir: String,
158 #[serde(default)]
162 pub subnet_config_dir: String,
163 #[serde(default)]
167 pub chain_config_dir: String,
168
169 #[serde(skip_serializing_if = "Option::is_none")]
175 pub state_sync_ids: Option<String>,
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub state_sync_ips: Option<String>,
178
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub profile_dir: Option<String>,
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub profile_continuous_enabled: Option<bool>,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub profile_continuous_freq: Option<String>,
186 #[serde(skip_serializing_if = "Option::is_none")]
187 pub profile_continuous_max_files: Option<u32>,
188
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub proposervm_use_current_height: Option<bool>,
191 #[serde(skip_serializing_if = "Option::is_none")]
192 pub throttler_inbound_node_max_processing_msgs: Option<u64>,
193 #[serde(skip_serializing_if = "Option::is_none")]
194 pub throttler_inbound_bandwidth_refill_rate: Option<u64>,
195 #[serde(skip_serializing_if = "Option::is_none")]
196 pub throttler_inbound_bandwidth_max_burst_size: Option<u64>,
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub throttler_inbound_cpu_validator_alloc: Option<u64>,
199 #[serde(skip_serializing_if = "Option::is_none")]
200 pub throttler_inbound_disk_validator_alloc: Option<u64>,
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub throttler_inbound_at_large_alloc_size: Option<u64>,
203 #[serde(skip_serializing_if = "Option::is_none")]
204 pub throttler_inbound_validator_alloc_size: Option<u64>,
205 #[serde(skip_serializing_if = "Option::is_none")]
206 pub throttler_inbound_node_max_at_large_bytes: Option<u64>,
207
208 #[serde(skip_serializing_if = "Option::is_none")]
209 pub snow_mixed_query_num_push_vdr: Option<u64>,
210
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub consensus_accepted_frontier_gossip_frequency: Option<i64>,
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub consensus_app_concurrency: Option<i64>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
218 pub consensus_on_accept_gossip_validator_size: Option<u64>,
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub consensus_on_accept_gossip_non_validator_size: Option<u64>,
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub consensus_on_accept_gossip_peer_size: Option<u64>,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub consensus_accepted_frontier_gossip_peer_size: Option<u64>,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
227 pub throttler_outbound_at_large_alloc_size: Option<u64>,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub throttler_outbound_validator_alloc_size: Option<u64>,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub throttler_outbound_node_max_at_large_bytes: Option<u64>,
232
233 #[serde(skip_serializing_if = "Option::is_none")]
234 pub network_minimum_timeout: Option<String>,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub network_require_validator_to_connect: Option<bool>,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub network_compression_type: Option<String>,
240
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub tracing_enabled: Option<bool>,
243
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub process_context_file: Option<String>,
246}
247
248pub const DEFAULT_CONFIG_FILE_PATH: &str = "/data/avalanche-configs/config.json";
251pub const DEFAULT_GENESIS_PATH: &str = "/data/avalanche-configs/genesis.json";
254pub const DEFAULT_CHAIN_ALIASES_PATH: &str = "/data/avalanche-configs/chains/aliases.json";
257
258pub const DEFAULT_DB_TYPE: &str = "leveldb";
259pub const DEFAULT_DB_DIR: &str = "/data/db";
264pub const DEFAULT_CHAIN_DATA_DIR: &str = "/data/chainData";
268
269pub const DEFAULT_LOG_DIR: &str = "/var/log/avalanchego";
273pub const DEFAULT_LOG_LEVEL: &str = "INFO";
274pub const DEFAULT_LOG_FORMAT: &str = "json";
275
276pub const DEFAULT_HTTP_PORT: u32 = 9650;
279pub const DEFAULT_HTTP_HOST: &str = "0.0.0.0";
285pub const DEFAULT_HTTP_TLS_ENABLED: bool = false;
286
287pub const DEFAULT_SYBIL_PROTECTION_ENABLED: bool = true;
288pub const DEFAULT_STAKING_PORT: u32 = 9651;
291pub const DEFAULT_STAKING_TLS_KEY_FILE: &str = "/data/staking.key";
293pub const DEFAULT_STAKING_TLS_CERT_FILE: &str = "/data/staking.crt";
295pub const DEFAULT_STAKING_SIGNER_KEY_FILE: &str = "/data/staking-signer.bls.key";
297
298pub const DEFAULT_SNOW_SAMPLE_SIZE: u32 = 20;
301pub const DEFAULT_SNOW_QUORUM_SIZE: u32 = 15;
304
305pub const DEFAULT_INDEX_ENABLED: bool = false;
306pub const DEFAULT_INDEX_ALLOW_INCOMPLETE: bool = false;
307
308pub const DEFAULT_API_ADMIN_ENABLED: bool = false;
309pub const DEFAULT_API_INFO_ENABLED: bool = true;
310pub const DEFAULT_API_KEYSTORE_ENABLED: bool = false;
311pub const DEFAULT_API_METRICS_ENABLED: bool = true;
312pub const DEFAULT_API_HEALTH_ENABLED: bool = true;
313pub const DEFAULT_API_IPCS_ENABLED: bool = false;
314
315pub const DEFAULT_PLUGIN_DIR: &str = "/data/avalanche-plugins";
317
318pub const DEFAULT_SUBNET_CONFIG_DIR: &str = "/data/avalanche-configs/subnets";
323
324pub const DEFAULT_CHAIN_CONFIG_DIR: &str = "/data/avalanche-configs/chains";
329
330pub const DEFAULT_PROFILE_DIR: &str = "/var/log/avalanchego-profile/avalanche";
332
333pub const DEFAULT_THROTTLER_INBOUND_AT_LARGE_ALLOC_SIZE: u64 = 6 * units::MIB;
335pub const DEFAULT_THROTTLER_INBOUND_VALIDATOR_ALLOC_SIZE: u64 = 32 * units::MIB;
337pub const DEFAULT_THROTTLER_INBOUND_NODE_MAX_AT_LARGE_BYTES: u64 = 2 * units::MIB;
339
340pub const DEFAULT_THROTTLER_OUTBOUND_AT_LARGE_ALLOC_SIZE: u64 = 32 * units::MIB;
342pub const DEFAULT_THROTTLER_OUTBOUND_VALIDATOR_ALLOC_SIZE: u64 = 32 * units::MIB;
344pub const DEFAULT_THROTTLER_OUTBOUND_NODE_MAX_AT_LARGE_BYTES: u64 = 2 * units::MIB;
346
347pub const DEFAULT_NETWORK_COMPRESSION_TYPE: &str = "zstd";
348
349pub const DEFAULT_PROCESS_CONTEXT_FILE: &str = "/data/process.json";
350
351impl Default for Config {
352 fn default() -> Self {
353 Self::default_main()
354 }
355}
356
357impl Config {
358 pub fn default_main() -> Self {
361 Self {
362 config_file: Some(String::from(DEFAULT_CONFIG_FILE_PATH)),
363 genesis_file: None,
364
365 network_id: 1,
366
367 db_type: String::from(DEFAULT_DB_TYPE),
368 db_dir: String::from(DEFAULT_DB_DIR),
369 chain_data_dir: String::from(DEFAULT_CHAIN_DATA_DIR),
370 log_dir: String::from(DEFAULT_LOG_DIR),
371 log_level: Some(String::from(DEFAULT_LOG_LEVEL)),
372 log_format: Some(String::from(DEFAULT_LOG_FORMAT)),
373 log_display_level: None,
374
375 http_port: DEFAULT_HTTP_PORT,
376 http_host: Some(String::from(DEFAULT_HTTP_HOST)),
377 http_tls_enabled: Some(DEFAULT_HTTP_TLS_ENABLED),
378 http_tls_key_file: None,
379 http_tls_cert_file: None,
380 public_ip: None,
381
382 sybil_protection_enabled: Some(DEFAULT_SYBIL_PROTECTION_ENABLED),
383 staking_port: DEFAULT_STAKING_PORT,
384 staking_tls_key_file: Some(String::from(DEFAULT_STAKING_TLS_KEY_FILE)),
385 staking_tls_cert_file: Some(String::from(DEFAULT_STAKING_TLS_CERT_FILE)),
386 staking_signer_key_file: Some(String::from(DEFAULT_STAKING_SIGNER_KEY_FILE)),
387
388 bootstrap_ips: None,
389 bootstrap_ids: None,
390
391 snow_sample_size: Some(DEFAULT_SNOW_SAMPLE_SIZE),
392 snow_quorum_size: Some(DEFAULT_SNOW_QUORUM_SIZE),
393 snow_concurrent_repolls: None,
394 snow_max_time_processing: None,
395 snow_rogue_commit_threshold: None,
396 snow_virtuous_commit_threshold: None,
397
398 network_peer_list_gossip_frequency: None,
399 network_max_reconnect_delay: None,
400
401 index_enabled: Some(DEFAULT_INDEX_ENABLED),
402 index_allow_incomplete: Some(DEFAULT_INDEX_ALLOW_INCOMPLETE),
403
404 api_admin_enabled: Some(DEFAULT_API_ADMIN_ENABLED),
405 api_info_enabled: Some(DEFAULT_API_INFO_ENABLED),
406 api_keystore_enabled: Some(DEFAULT_API_KEYSTORE_ENABLED),
407 api_metrics_enabled: Some(DEFAULT_API_METRICS_ENABLED),
408 api_health_enabled: Some(DEFAULT_API_HEALTH_ENABLED),
409 api_ipcs_enabled: Some(DEFAULT_API_IPCS_ENABLED),
410
411 track_subnets: None,
412
413 plugin_dir: String::from(DEFAULT_PLUGIN_DIR),
414 subnet_config_dir: String::from(DEFAULT_SUBNET_CONFIG_DIR),
415 chain_config_dir: String::from(DEFAULT_CHAIN_CONFIG_DIR),
416
417 state_sync_ids: None,
418 state_sync_ips: None,
419
420 profile_dir: Some(String::from(DEFAULT_PROFILE_DIR)),
421 profile_continuous_enabled: None,
422 profile_continuous_freq: None,
423 profile_continuous_max_files: None,
424
425 proposervm_use_current_height: Some(true),
426 throttler_inbound_node_max_processing_msgs: Some(100000),
427 throttler_inbound_bandwidth_refill_rate: Some(1073741824),
428 throttler_inbound_bandwidth_max_burst_size: Some(1073741824),
429 throttler_inbound_cpu_validator_alloc: Some(100000),
430 throttler_inbound_disk_validator_alloc: Some(10737418240000),
431
432 throttler_inbound_at_large_alloc_size: Some(
433 DEFAULT_THROTTLER_INBOUND_AT_LARGE_ALLOC_SIZE,
434 ),
435 throttler_inbound_validator_alloc_size: Some(
436 DEFAULT_THROTTLER_INBOUND_VALIDATOR_ALLOC_SIZE,
437 ),
438 throttler_inbound_node_max_at_large_bytes: Some(
439 DEFAULT_THROTTLER_INBOUND_NODE_MAX_AT_LARGE_BYTES,
440 ),
441
442 snow_mixed_query_num_push_vdr: Some(10),
443
444 consensus_accepted_frontier_gossip_frequency: Some(10000000000), consensus_app_concurrency: Some(2),
446
447 consensus_on_accept_gossip_validator_size: Some(0),
448 consensus_on_accept_gossip_non_validator_size: Some(0),
449 consensus_on_accept_gossip_peer_size: Some(10),
450 consensus_accepted_frontier_gossip_peer_size: Some(10),
451
452 throttler_outbound_at_large_alloc_size: Some(
453 DEFAULT_THROTTLER_OUTBOUND_AT_LARGE_ALLOC_SIZE,
454 ),
455 throttler_outbound_validator_alloc_size: Some(
456 DEFAULT_THROTTLER_OUTBOUND_VALIDATOR_ALLOC_SIZE,
457 ),
458 throttler_outbound_node_max_at_large_bytes: Some(
459 DEFAULT_THROTTLER_OUTBOUND_NODE_MAX_AT_LARGE_BYTES,
460 ),
461
462 network_minimum_timeout: None,
463 network_require_validator_to_connect: None,
464
465 network_compression_type: Some(DEFAULT_NETWORK_COMPRESSION_TYPE.to_string()),
466
467 tracing_enabled: None,
468
469 process_context_file: Some(DEFAULT_PROCESS_CONTEXT_FILE.to_string()),
470 }
471 }
472
473 pub fn default_fuji() -> Self {
476 let mut cfg = Self::default_main();
477 cfg.network_id = 5;
478 cfg
479 }
480
481 pub fn default_custom() -> Self {
484 let mut cfg = Self::default_main();
485 cfg.network_id = constants::DEFAULT_CUSTOM_NETWORK_ID;
486 cfg.genesis_file = Some(String::from(DEFAULT_GENESIS_PATH));
487 cfg
488 }
489
490 pub fn is_mainnet(&self) -> bool {
492 self.network_id == 1
493 }
494
495 pub fn is_custom_network(&self) -> bool {
498 !self.is_mainnet() && (self.network_id == 0 || self.network_id > 5)
499 }
500
501 pub fn add_track_subnets(&mut self, ids: Option<String>) {
502 let mut all_ids = BTreeSet::new();
503 if let Some(existing) = &self.track_subnets {
504 let ss: Vec<&str> = existing.split(',').collect();
505 for id in ss {
506 all_ids.insert(id.trim().to_string());
507 }
508 }
509
510 if let Some(new_ids) = &ids {
511 let ss: Vec<&str> = new_ids.split(',').collect();
512 for id in ss {
513 all_ids.insert(id.trim().to_string());
514 }
515 }
516
517 let mut ids = Vec::new();
518 for id in all_ids.iter() {
519 ids.push(id.trim().to_string());
520 }
521
522 if !ids.is_empty() {
523 self.track_subnets = Some(ids.join(","))
524 }
525 }
526
527 pub fn encode_json(&self) -> io::Result<String> {
529 serde_json::to_string(&self)
530 .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))
531 }
532
533 pub fn sync(&self, file_path: Option<String>) -> io::Result<()> {
536 if file_path.is_none() && self.config_file.is_none() {
537 return Err(Error::new(
538 ErrorKind::InvalidInput,
539 "empty config_file path",
540 ));
541 }
542 let p = file_path.unwrap_or_else(|| {
543 self.config_file
544 .clone()
545 .expect("unexpected None config_file")
546 });
547
548 log::info!("mkdir avalanchego configuration dir for '{}'", p);
549 let path = Path::new(&p);
550 if let Some(parent_dir) = path.parent() {
551 log::info!("creating parent dir '{}'", parent_dir.display());
552 fs::create_dir_all(parent_dir)?;
553 }
554
555 let d = serde_json::to_vec(self)
556 .map_err(|e| Error::new(ErrorKind::Other, format!("failed to serialize JSON {}", e)))?;
557
558 log::info!("syncing avalanchego Config to '{}'", p);
559 let mut f = File::create(p)?;
560 f.write_all(&d)?;
561
562 Ok(())
563 }
564
565 pub fn load(file_path: &str) -> io::Result<Self> {
566 log::info!("loading avalanchego config from {}", file_path);
567
568 if !Path::new(file_path).exists() {
569 return Err(Error::new(
570 ErrorKind::NotFound,
571 format!("file {} does not exists", file_path),
572 ));
573 }
574
575 let f = File::open(file_path).map_err(|e| {
576 Error::new(
577 ErrorKind::Other,
578 format!("failed to open {} ({})", file_path, e),
579 )
580 })?;
581 serde_json::from_reader(f)
582 .map_err(|e| Error::new(ErrorKind::InvalidInput, format!("invalid JSON: {}", e)))
583 }
584
585 pub fn validate(&self) -> io::Result<()> {
587 log::info!("validating the avalanchego configuration");
588
589 if !self.is_custom_network() && self.genesis_file.is_some() {
591 return Err(Error::new(
592 ErrorKind::InvalidInput,
593 format!(
594 "non-empty '--genesis={}' for network_id {}",
595 self.genesis_file.clone().expect("unexpected None genesis"),
596 self.network_id,
597 ),
598 ));
599 }
600
601 if self.is_custom_network() && self.genesis_file.is_none() {
603 return Err(Error::new(
604 ErrorKind::InvalidInput,
605 format!(
606 "non-empty '--network-id={}' but empty '--genesis'",
607 self.network_id
608 ),
609 ));
610 }
611
612 if self.genesis_file.is_some()
614 && !Path::new(&self.genesis_file.clone().expect("unexpected None genesis")).exists()
615 {
616 return Err(Error::new(
617 ErrorKind::InvalidInput,
618 format!(
619 "non-empty '--genesis={}' but genesis file does not exist",
620 self.genesis_file.clone().expect("unexpected None genesis")
621 ),
622 ));
623 }
624
625 if self.genesis_file.is_some() {
627 let genesis_file_path = self.genesis_file.clone().expect("unexpected None genesis");
628 let genesis_config =
629 genesis::Genesis::load(&genesis_file_path).expect("unexpected None genesis config");
630 if genesis_config.network_id.ne(&self.network_id) {
631 return Err(Error::new(
632 ErrorKind::InvalidInput,
633 format!(
634 "'genesis' network ID {} != avalanchego::Config.network_id {}",
635 genesis_config.network_id, self.network_id
636 ),
637 ));
638 }
639 }
640
641 if self.sybil_protection_enabled.is_some() && !self.sybil_protection_enabled.unwrap() {
643 return Err(Error::new(
644 ErrorKind::InvalidInput,
645 "'sybil_protection_enabled' must be true",
646 ));
647 }
648 if self.staking_tls_cert_file.is_none() {
649 return Err(Error::new(
650 ErrorKind::InvalidInput,
651 "'staking-tls-cert-file' not defined",
652 ));
653 }
654 if self.staking_tls_key_file.is_none() {
655 return Err(Error::new(
656 ErrorKind::InvalidInput,
657 "'staking-tls-key-file' not defined",
658 ));
659 }
660 if self.staking_signer_key_file.is_none() {
661 return Err(Error::new(
662 ErrorKind::InvalidInput,
663 "'staking-signer-key-file' not defined",
664 ));
665 }
666
667 if self.state_sync_ids.is_some() && self.state_sync_ips.is_none() {
669 return Err(Error::new(
670 ErrorKind::InvalidInput,
671 "non-empty 'state-sync-ids' but empty 'state-sync-ips'",
672 ));
673 }
674 if self.state_sync_ids.is_none() && self.state_sync_ips.is_some() {
675 return Err(Error::new(
676 ErrorKind::InvalidInput,
677 "non-empty 'state-sync-ips' but empty 'state-sync-ids'",
678 ));
679 }
680
681 if self.profile_continuous_enabled.is_some() && !self.profile_continuous_enabled.unwrap() {
683 return Err(Error::new(
684 ErrorKind::InvalidInput,
685 "'profile-continuous-enabled' must be true",
686 ));
687 }
688 if self.profile_continuous_freq.is_some() && self.profile_continuous_enabled.is_none() {
689 return Err(Error::new(
690 ErrorKind::InvalidInput,
691 "non-empty 'profile-continuous-freq' but empty 'profile-continuous-enabled'",
692 ));
693 }
694 if self.profile_continuous_max_files.is_some() && self.profile_continuous_enabled.is_none()
695 {
696 return Err(Error::new(
697 ErrorKind::InvalidInput,
698 "non-empty 'profile-continuous-max-files' but empty 'profile-continuous-enabled'",
699 ));
700 }
701
702 Ok(())
703 }
704}
705
706#[test]
707fn test_config() {
708 use std::fs;
709 let _ = env_logger::builder().is_test(true).try_init();
710
711 let mut config = Config::default_custom();
712 config.network_id = 1337;
713
714 let ret = config.encode_json();
715 assert!(ret.is_ok());
716 let s = ret.unwrap();
717 log::info!("config: {}", s);
718
719 let p = random_manager::tmp_path(10, Some(".yaml")).unwrap();
720 let ret = config.sync(Some(p.clone()));
721 assert!(ret.is_ok());
722
723 let config_loaded = Config::load(&p).unwrap();
724 assert_eq!(config, config_loaded);
725
726 config.add_track_subnets(Some("x,y,a,b,d,f".to_string()));
727 println!("{}", config.track_subnets.clone().unwrap());
728 assert_eq!(config.track_subnets.unwrap(), "a,b,d,f,x,y");
729
730 fs::remove_file(p).unwrap();
731}