Skip to main content

avalanche_types/avalanchego/
config.rs

1//! AvalancheGo configuration.
2use 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/// Represents AvalancheGo configuration.
13/// All file paths must be valid on the remote machines.
14/// For example, you may configure cert paths on your local laptop
15/// but the actual Avalanche nodes run on the remote machines
16/// so the paths will be invalid.
17/// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/config>
18/// ref. <https://github.com/ava-labs/avalanchego/blob/v1.9.8/config/flags.go>
19/// ref. <https://github.com/ava-labs/avalanchego/blob/v1.9.8/config/keys.go>
20/// ref. <https://serde.rs/container-attrs.html>
21#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
22#[serde(rename_all = "kebab-case")]
23pub struct Config {
24    /// File path to persist all fields below.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub config_file: Option<String>,
27
28    /// Genesis file path.
29    /// MUST BE NON-EMPTY for custom network.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub genesis_file: Option<String>,
32
33    /// Network ID. Default to custom network ID.
34    /// Set it to 1 for mainnet.
35    /// e.g., "mainnet" is 1, "fuji" is 5, "local" is 12345.
36    /// "utils/constants/NetworkID" only accepts string for known networks.
37    /// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/utils/constants#pkg-constants>
38    /// ref. <https://pkg.go.dev/github.com/ava-labs/avalanchego/utils/constants#NetworkName>
39    #[serde(default)]
40    pub network_id: u32,
41
42    #[serde(default)]
43    pub db_type: String,
44    /// Database directory, must be a valid path in remote host machine.
45    #[serde(default)]
46    pub db_dir: String,
47    /// Chain data directory, must be a valid path in remote host machine.
48    #[serde(default)]
49    pub chain_data_dir: String,
50
51    /// Logging directory, must be a valid path in remote host machine.
52    #[serde(default)]
53    pub log_dir: String,
54
55    /// "avalanchego" logging level.
56    /// See "utils/logging/level.go".
57    /// e.g., "INFO", "FATAL", "DEBUG", "VERBO", etc..
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub log_level: Option<String>,
60    /// "avalanchego" logging format.
61    /// e.g., "json", etc..
62    #[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    /// HTTP port.
68    #[serde(default)]
69    pub http_port: u32,
70    /// HTTP host, which avalanchego defaults to 127.0.0.1.
71    /// Set it to 0.0.0.0 to expose the HTTP API to all incoming traffic.
72    #[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    /// MUST BE a valid path in remote host machine.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub http_tls_key_file: Option<String>,
79    /// MUST BE a valid path in remote host machine.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub http_tls_cert_file: Option<String>,
82    /// Public IP of this node for P2P communication.
83    /// If empty, try to discover with NAT.
84    #[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    /// Staking port.
90    #[serde(default)]
91    pub staking_port: u32,
92    /// MUST BE a valid path in remote host machine.
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub staking_tls_key_file: Option<String>,
95    /// MUST BE a valid path in remote host machine.
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub staking_tls_cert_file: Option<String>,
98    /// MUST BE a valid path in remote host machine.
99    /// Path to the BLS key.
100    #[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    /// The sample size k, snowball.Parameters.K.
109    /// If zero, use the default value set via avalanche node code.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub snow_sample_size: Option<u32>,
112    /// The quorum size α, snowball.Parameters.Alpha.
113    /// If zero, use the default value set via avalanche node code.
114    #[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    /// A list of whitelisted/tracked subnet IDs (comma-separated).
149    /// From avalanchego v1.9.7, it's renamed to "track-subnets".
150    /// ref. <https://github.com/ava-labs/avalanchego/blob/v1.9.8/config/keys.go>
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub track_subnets: Option<String>,
153
154    /// Plugin directory.
155    /// Default to "/data/avalanche-plugins".
156    #[serde(default)]
157    pub plugin_dir: String,
158    /// Subnet configuration directory (e.g., /data/avalanche-configs/subnets/C.json).
159    /// If a subnet id is 2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt,
160    /// the config file for this subnet is located at {subnet-config-dir}/2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt.json.
161    #[serde(default)]
162    pub subnet_config_dir: String,
163    /// Chain configuration directory (e.g., /data/avalanche-configs/chains/C/config.json).
164    /// If a Subnet's chain id is 2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt,
165    /// the config file for this chain is located at {chain-config-dir}/2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt/config.json.
166    #[serde(default)]
167    pub chain_config_dir: String,
168
169    /// A comma separated string of explicit nodeID and IPs
170    /// to contact for starting state sync. Useful for testing.
171    /// NOTE: Actual state data will be downloaded from nodes
172    /// specified in the C-Chain config, or the entire network
173    /// if no list specified there.
174    #[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    /// Continuous profile flags
180    #[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    /// ref. <https://github.com/ava-labs/avalanchego/pull/1322>
214    #[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
248/// Default "config-file" path on the remote linux machines.
249/// MUST BE a valid path in remote host machine.
250pub const DEFAULT_CONFIG_FILE_PATH: &str = "/data/avalanche-configs/config.json";
251/// Default "genesis" path on the remote linux machines.
252/// MUST BE a valid path in remote host machine.
253pub const DEFAULT_GENESIS_PATH: &str = "/data/avalanche-configs/genesis.json";
254/// Default "chain aliases" path on the remote linux machine.
255/// MUST BE a valid path in remote host machine.
256pub const DEFAULT_CHAIN_ALIASES_PATH: &str = "/data/avalanche-configs/chains/aliases.json";
257
258pub const DEFAULT_DB_TYPE: &str = "leveldb";
259/// Default "db-dir" directory path for remote linux machines.
260/// MUST BE matched with the attached physical storage volume path.
261/// MUST BE a valid path in remote host machine.
262/// ref. See "cfn-templates/avalanche-node/asg_amd64_ubuntu.yaml" "ASGLaunchTemplate"
263pub const DEFAULT_DB_DIR: &str = "/data/db";
264/// Default "chain-data-dir" directory path for remote linux machines.
265/// MUST BE matched with the attached physical storage volume path.
266/// MUST BE a valid path in remote host machine.
267pub const DEFAULT_CHAIN_DATA_DIR: &str = "/data/chainData";
268
269/// Default "log-dir" directory path for remote linux machines.
270/// MUST BE a valid path in remote host machine.
271/// ref. See "cfn-templates/avalanche-node/asg_amd64_ubuntu.yaml" "ASGLaunchTemplate"
272pub const DEFAULT_LOG_DIR: &str = "/var/log/avalanchego";
273pub const DEFAULT_LOG_LEVEL: &str = "INFO";
274pub const DEFAULT_LOG_FORMAT: &str = "json";
275
276/// Default HTTP port.
277/// NOTE: keep default value in sync with "avalanchego/config/flags.go".
278pub const DEFAULT_HTTP_PORT: u32 = 9650;
279/// Default HTTP host.
280/// Open listener to "0.0.0.0" to allow all incoming traffic.
281/// e.g., If set to default "127.0.0.1", the external client
282/// cannot access "/ext/metrics". Set different values to
283/// make this more restrictive.
284pub 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;
288/// Default staking port.
289/// NOTE: keep default value in sync with "avalanchego/config/flags.go".
290pub const DEFAULT_STAKING_PORT: u32 = 9651;
291/// MUST BE a valid path in remote host machine.
292pub const DEFAULT_STAKING_TLS_KEY_FILE: &str = "/data/staking.key";
293/// MUST BE a valid path in remote host machine.
294pub const DEFAULT_STAKING_TLS_CERT_FILE: &str = "/data/staking.crt";
295/// MUST BE a valid path in remote host machine.
296pub const DEFAULT_STAKING_SIGNER_KEY_FILE: &str = "/data/staking-signer.bls.key";
297
298/// Default snow sample size.
299/// NOTE: keep this in sync with "avalanchego/config/flags.go".
300pub const DEFAULT_SNOW_SAMPLE_SIZE: u32 = 20;
301/// Default snow quorum size.
302/// NOTE: keep this in sync with "avalanchego/config/flags.go".
303pub 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
315/// ref. <https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go>
316pub const DEFAULT_PLUGIN_DIR: &str = "/data/avalanche-plugins";
317
318/// If a subnet id is 2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt,
319/// the config file for this subnet is located at {subnet-config-dir}/2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt.json.
320/// ref. <https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go>
321/// ref. <https://docs.avax.network/subnets/customize-a-subnet#chain-configs>
322pub const DEFAULT_SUBNET_CONFIG_DIR: &str = "/data/avalanche-configs/subnets";
323
324/// If a Subnet's chain id is 2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt,
325/// the config file for this chain is located at {chain-config-dir}/2ebCneCbwthjQ1rYT41nhd7M76Hc6YmosMAQrTFhBq8qeqh6tt/config.json.
326/// ref. <https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go>
327/// ref. <https://docs.avax.network/subnets/customize-a-subnet#chain-configs>
328pub const DEFAULT_CHAIN_CONFIG_DIR: &str = "/data/avalanche-configs/chains";
329
330/// MUST BE a valid path in remote host machine.
331pub const DEFAULT_PROFILE_DIR: &str = "/var/log/avalanchego-profile/avalanche";
332
333/// ref. [DefaultInboundThrottlerAtLargeAllocSize](https://github.com/ava-labs/avalanchego/blob/master/utils/constants/networking.go#L88)
334pub const DEFAULT_THROTTLER_INBOUND_AT_LARGE_ALLOC_SIZE: u64 = 6 * units::MIB;
335/// ref. [DefaultInboundThrottlerVdrAllocSize](https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go)
336pub const DEFAULT_THROTTLER_INBOUND_VALIDATOR_ALLOC_SIZE: u64 = 32 * units::MIB;
337/// ref. [DefaultInboundThrottlerNodeMaxAtLargeBytes](https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go)
338pub const DEFAULT_THROTTLER_INBOUND_NODE_MAX_AT_LARGE_BYTES: u64 = 2 * units::MIB;
339
340/// ref. [DefaultOutboundThrottlerAtLargeAllocSize](https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go)
341pub const DEFAULT_THROTTLER_OUTBOUND_AT_LARGE_ALLOC_SIZE: u64 = 32 * units::MIB;
342/// ref. [DefaultOutboundThrottlerVdrAllocSize](https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go)
343pub const DEFAULT_THROTTLER_OUTBOUND_VALIDATOR_ALLOC_SIZE: u64 = 32 * units::MIB;
344/// ref. [DefaultOutboundThrottlerNodeMaxAtLargeBytes](https://github.com/ava-labs/avalanchego/blob/v1.9.11/config/flags.go)
345pub 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    /// The defaults do not match with the ones in avalanchego,
359    /// as this is for avalanche-ops based deployments.
360    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), // 10-second
445            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    /// The defaults do not match with the ones in avalanchego,
474    /// as this is for avalanche-ops based deployments.
475    pub fn default_fuji() -> Self {
476        let mut cfg = Self::default_main();
477        cfg.network_id = 5;
478        cfg
479    }
480
481    /// The defaults do not match with the ones in avalanchego,
482    /// as this is for avalanche-ops based deployments.
483    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    /// Returns true if the configuration is mainnet.
491    pub fn is_mainnet(&self) -> bool {
492        self.network_id == 1
493    }
494
495    /// Returns true if the configuration is a custom network
496    /// thus requires a custom genesis file.
497    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    /// Converts to string with JSON encoder.
528    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    /// Saves the current configuration to disk
534    /// and overwrites the file.
535    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    /// Validates the configuration.
586    pub fn validate(&self) -> io::Result<()> {
587        log::info!("validating the avalanchego configuration");
588
589        // mainnet does not need genesis file
590        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        // custom network requires genesis file
602        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        // custom network requires genesis file
613        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        // network ID must match with the one in genesis file
626        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        // staking
642        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        // state sync
668        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        // continuous profiles
682        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}