sui_config/
node.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3use crate::Config;
4use crate::certificate_deny_config::CertificateDenyConfig;
5use crate::genesis;
6use crate::object_storage_config::ObjectStoreConfig;
7use crate::p2p::P2pConfig;
8use crate::transaction_deny_config::TransactionDenyConfig;
9use crate::validator_client_monitor_config::ValidatorClientMonitorConfig;
10use crate::verifier_signing_config::VerifierSigningConfig;
11use anyhow::Result;
12use consensus_config::Parameters as ConsensusParameters;
13use mysten_common::fatal;
14use nonzero_ext::nonzero;
15use once_cell::sync::OnceCell;
16use rand::rngs::OsRng;
17use serde::{Deserialize, Serialize};
18use serde_with::serde_as;
19use std::collections::{BTreeMap, BTreeSet};
20use std::net::SocketAddr;
21use std::num::{NonZeroU32, NonZeroUsize};
22use std::path::{Path, PathBuf};
23use std::sync::Arc;
24use std::time::Duration;
25use sui_keys::keypair_file::{read_authority_keypair_from_file, read_keypair_from_file};
26use sui_types::base_types::{ObjectID, SuiAddress};
27use sui_types::committee::EpochId;
28use sui_types::crypto::AuthorityPublicKeyBytes;
29use sui_types::crypto::KeypairTraits;
30use sui_types::crypto::NetworkKeyPair;
31use sui_types::crypto::SuiKeyPair;
32use sui_types::messages_checkpoint::CheckpointSequenceNumber;
33use sui_types::node_role::{FullNodeSyncMode, NodeRole};
34use sui_types::supported_protocol_versions::{Chain, SupportedProtocolVersions};
35use sui_types::traffic_control::{PolicyConfig, RemoteFirewallConfig};
36
37use sui_types::crypto::{AccountKeyPair, AuthorityKeyPair, get_key_pair_from_rng};
38use sui_types::multiaddr::Multiaddr;
39use tracing::info;
40
41// Default max number of concurrent requests served
42pub const DEFAULT_GRPC_CONCURRENCY_LIMIT: usize = 20000000000;
43
44/// Default gas price of 100 Mist
45pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = sui_types::transaction::DEFAULT_VALIDATOR_GAS_PRICE;
46
47/// Default commission rate of 2%
48pub const DEFAULT_COMMISSION_RATE: u64 = 200;
49
50/// The type of funds withdraw scheduler to use.
51#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
52pub enum FundsWithdrawSchedulerType {
53    Naive,
54    #[default]
55    Eager,
56}
57
58#[serde_as]
59#[derive(Clone, Debug, Deserialize, Serialize)]
60#[serde(rename_all = "kebab-case")]
61pub struct NodeConfig {
62    #[serde(default = "default_authority_key_pair")]
63    pub protocol_key_pair: AuthorityKeyPairWithPath,
64    #[serde(default = "default_key_pair")]
65    pub worker_key_pair: KeyPairWithPath,
66    #[serde(default = "default_key_pair")]
67    pub account_key_pair: KeyPairWithPath,
68    #[serde(default = "default_key_pair")]
69    pub network_key_pair: KeyPairWithPath,
70
71    pub db_path: PathBuf,
72    #[serde(default = "default_grpc_address")]
73    pub network_address: Multiaddr,
74    #[serde(default = "default_json_rpc_address")]
75    pub json_rpc_address: SocketAddr,
76
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub rpc: Option<crate::RpcConfig>,
79
80    #[serde(default = "default_metrics_address")]
81    pub metrics_address: SocketAddr,
82    #[serde(default = "default_admin_interface_port")]
83    pub admin_interface_port: u16,
84
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub consensus_config: Option<ConsensusConfig>,
87
88    /// The sync mode for full nodes.
89    /// When `None` is provided and this is a full node then the default is used which is `StateSyncOnly`.
90    /// For validator nodes this is expected to be `None`
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub fullnode_sync_mode: Option<FullNodeSyncMode>,
93
94    #[serde(default = "default_enable_index_processing")]
95    pub enable_index_processing: bool,
96
97    /// When true, post-processing (JSON-RPC indexing and event emission) runs
98    /// synchronously on the execution path instead of being spawned to a
99    /// background thread. This is the legacy behavior and can be used as a
100    /// rollback mechanism or for testing.
101    #[serde(default)]
102    pub sync_post_process_one_tx: bool,
103
104    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
105    pub remove_deprecated_tables: bool,
106
107    #[serde(default)]
108    /// Determines the jsonrpc server type as either:
109    /// - 'websocket' for a websocket based service (deprecated)
110    /// - 'http' for an http based service
111    /// - 'both' for both a websocket and http based service (deprecated)
112    pub jsonrpc_server_type: Option<ServerType>,
113
114    #[serde(default)]
115    pub grpc_load_shed: Option<bool>,
116
117    #[serde(default = "default_concurrency_limit")]
118    pub grpc_concurrency_limit: Option<usize>,
119
120    #[serde(default)]
121    pub p2p_config: P2pConfig,
122
123    pub genesis: Genesis,
124
125    #[serde(default = "default_authority_store_pruning_config")]
126    pub authority_store_pruning_config: AuthorityStorePruningConfig,
127
128    /// Size of the broadcast channel used for notifying other systems of end of epoch.
129    ///
130    /// If unspecified, this will default to `128`.
131    #[serde(default = "default_end_of_epoch_broadcast_channel_capacity")]
132    pub end_of_epoch_broadcast_channel_capacity: usize,
133
134    #[serde(default)]
135    pub checkpoint_executor_config: CheckpointExecutorConfig,
136
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub metrics: Option<MetricsConfig>,
139
140    /// In a `sui-node` binary, this is set to SupportedProtocolVersions::SYSTEM_DEFAULT
141    /// in sui-node/src/main.rs. It is present in the config so that it can be changed by tests in
142    /// order to test protocol upgrades.
143    #[serde(skip)]
144    pub supported_protocol_versions: Option<SupportedProtocolVersions>,
145
146    #[serde(default)]
147    pub db_checkpoint_config: DBCheckpointConfig,
148
149    #[serde(default)]
150    pub expensive_safety_check_config: ExpensiveSafetyCheckConfig,
151
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub name_service_package_address: Option<SuiAddress>,
154
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub name_service_registry_id: Option<ObjectID>,
157
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub name_service_reverse_registry_id: Option<ObjectID>,
160
161    #[serde(default)]
162    pub transaction_deny_config: TransactionDenyConfig,
163
164    /// Whether dev-inspect transaction execution is disabled on this node.
165    #[serde(default)]
166    pub dev_inspect_disabled: bool,
167
168    #[serde(default)]
169    pub certificate_deny_config: CertificateDenyConfig,
170
171    #[serde(default)]
172    pub state_debug_dump_config: StateDebugDumpConfig,
173
174    #[serde(default)]
175    pub state_archive_read_config: Vec<StateArchiveConfig>,
176
177    #[serde(default)]
178    pub state_snapshot_write_config: StateSnapshotConfig,
179
180    #[serde(default)]
181    pub indexer_max_subscriptions: Option<usize>,
182
183    #[serde(default = "default_transaction_kv_store_config")]
184    pub transaction_kv_store_read_config: TransactionKeyValueStoreReadConfig,
185
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub transaction_kv_store_write_config: Option<TransactionKeyValueStoreWriteConfig>,
188
189    #[serde(default = "default_jwk_fetch_interval_seconds")]
190    pub jwk_fetch_interval_seconds: u64,
191
192    #[serde(default = "default_zklogin_oauth_providers")]
193    pub zklogin_oauth_providers: BTreeMap<Chain, BTreeSet<String>>,
194
195    #[serde(default = "default_authority_overload_config")]
196    pub authority_overload_config: AuthorityOverloadConfig,
197
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub run_with_range: Option<RunWithRange>,
200
201    // For killswitch use None
202    #[serde(
203        skip_serializing_if = "Option::is_none",
204        default = "default_traffic_controller_policy_config"
205    )]
206    pub policy_config: Option<PolicyConfig>,
207
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub firewall_config: Option<RemoteFirewallConfig>,
210
211    #[serde(default)]
212    pub execution_cache: ExecutionCacheConfig,
213
214    // step 1 in removing the old state accumulator
215    #[serde(skip)]
216    #[serde(default = "bool_true")]
217    pub state_accumulator_v2: bool,
218
219    /// The type of funds withdraw scheduler to use.
220    /// Default is Eager. Not exposed to file configuration.
221    #[serde(skip)]
222    #[serde(default)]
223    pub funds_withdraw_scheduler_type: FundsWithdrawSchedulerType,
224
225    #[serde(default = "bool_true")]
226    pub enable_soft_bundle: bool,
227
228    #[serde(default)]
229    pub verifier_signing_config: VerifierSigningConfig,
230
231    /// If a value is set, it determines if writes to DB can stall, which can halt the whole process.
232    /// By default, write stall is enabled on validators but not on fullnodes.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub enable_db_write_stall: Option<bool>,
235
236    /// If set, determines whether database writes are synced to disk (fsync).
237    /// Provides stronger durability at the cost of write performance.
238    /// Falls back to SUI_DB_SYNC_TO_DISK env var if not set. Default: disabled.
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub enable_db_sync_to_disk: Option<bool>,
241
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub execution_time_observer_config: Option<ExecutionTimeObserverConfig>,
244
245    /// Allow overriding the chain for testing purposes. For instance, it allows you to
246    /// create a test network that believes it is mainnet or testnet. Attempting to
247    /// override this value on production networks will result in an error.
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub chain_override_for_testing: Option<Chain>,
250
251    /// Configuration for validator client monitoring from the client perspective.
252    /// When enabled, tracks client-observed performance metrics for validators.
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub validator_client_monitor_config: Option<ValidatorClientMonitorConfig>,
255
256    /// Fork recovery configuration for handling validator equivocation after forks
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub fork_recovery: Option<ForkRecoveryConfig>,
259
260    /// Configuration for the transaction driver.
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub transaction_driver_config: Option<TransactionDriverConfig>,
263
264    /// Configuration for congestion tracker binary logging.
265    /// When set, enables per-commit binary logs of congestion tracker state.
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub congestion_log: Option<CongestionLogConfig>,
268}
269
270#[derive(Clone, Debug, Deserialize, Serialize)]
271#[serde(rename_all = "kebab-case")]
272pub struct TransactionDriverConfig {
273    /// The list of validators that are allowed to submit MFP transactions to (via the transaction driver).
274    /// Each entry is a validator display name.
275    #[serde(default, skip_serializing_if = "Vec::is_empty")]
276    pub allowed_submission_validators: Vec<String>,
277
278    /// The list of validators that are blocked from submitting block transactions to (via the transaction driver).
279    /// Each entry is a validator display name.
280    #[serde(default, skip_serializing_if = "Vec::is_empty")]
281    pub blocked_submission_validators: Vec<String>,
282
283    /// Enable early transaction validation before submission to consensus.
284    /// This checks for non-retriable errors (like old object versions) and rejects
285    /// transactions early to provide fast feedback to clients.
286    /// Note: Currently used in TransactionOrchestrator, but may be moved to TransactionDriver in future.
287    #[serde(default = "bool_true")]
288    pub enable_early_validation: bool,
289}
290
291impl Default for TransactionDriverConfig {
292    fn default() -> Self {
293        Self {
294            allowed_submission_validators: vec![],
295            blocked_submission_validators: vec![],
296            enable_early_validation: true,
297        }
298    }
299}
300
301#[derive(Clone, Debug, Deserialize, Serialize)]
302#[serde(rename_all = "kebab-case")]
303pub struct CongestionLogConfig {
304    pub path: PathBuf,
305    #[serde(default = "default_congestion_log_max_file_size")]
306    pub max_file_size: u64,
307    #[serde(default = "default_congestion_log_max_files")]
308    pub max_files: u32,
309}
310
311fn default_congestion_log_max_file_size() -> u64 {
312    100 * 1024 * 1024 // 100MB
313}
314
315fn default_congestion_log_max_files() -> u32 {
316    10
317}
318
319#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
320#[serde(rename_all = "kebab-case")]
321pub enum ForkCrashBehavior {
322    #[serde(rename = "await-fork-recovery")]
323    #[default]
324    AwaitForkRecovery,
325    /// Return an error instead of blocking forever. This is primarily for testing.
326    #[serde(rename = "return-error")]
327    ReturnError,
328}
329
330#[derive(Clone, Debug, Default, Deserialize, Serialize)]
331#[serde(rename_all = "kebab-case")]
332pub struct ForkRecoveryConfig {
333    /// Map of transaction digest to effects digest overrides
334    /// Used to repoint transactions to correct effects after a fork
335    #[serde(default)]
336    pub transaction_overrides: BTreeMap<String, String>,
337
338    /// Map of checkpoint sequence number to checkpoint digest overrides
339    /// On node start, if we have a locally computed checkpoint with a
340    /// digest mismatch with this table, we will clear any associated local state.
341    #[serde(default)]
342    pub checkpoint_overrides: BTreeMap<u64, String>,
343
344    /// Behavior when a fork is detected after recovery attempts
345    #[serde(default)]
346    pub fork_crash_behavior: ForkCrashBehavior,
347}
348
349#[derive(Clone, Debug, Default, Deserialize, Serialize)]
350#[serde(rename_all = "kebab-case")]
351pub struct ExecutionTimeObserverConfig {
352    /// Size of the channel used for buffering local execution time observations.
353    ///
354    /// If unspecified, this will default to `1_024`.
355    pub observation_channel_capacity: Option<NonZeroUsize>,
356
357    /// Size of the LRU cache used for storing local execution time observations.
358    ///
359    /// If unspecified, this will default to `10_000`.
360    pub observation_cache_size: Option<NonZeroUsize>,
361
362    /// Size of the channel used for buffering object debt updates from consensus handler.
363    ///
364    /// If unspecified, this will default to `128`.
365    pub object_debt_channel_capacity: Option<NonZeroUsize>,
366
367    /// Size of the LRU cache used for tracking object utilization.
368    ///
369    /// If unspecified, this will default to `50_000`.
370    pub object_utilization_cache_size: Option<NonZeroUsize>,
371
372    /// If true, the execution time observer will report per-object utilization metrics
373    /// with full object IDs. When set, the metric can have a high cardinality, so this
374    /// should not be used except in controlled tests where there are a small number of
375    /// objects.
376    ///
377    /// If false, object utilization is reported using hash(object_id) % 32 as the key,
378    /// which still allows observation of utilization when there are small numbers of
379    /// over-utilized objects.
380    ///
381    /// If unspecified, this will default to `false`.
382    pub report_object_utilization_metric_with_full_id: Option<bool>,
383
384    /// Unless target object utilization is exceeded by at least this amount, no observation
385    /// will be shared with consensus.
386    ///
387    /// If unspecified, this will default to `500` milliseconds.
388    pub observation_sharing_object_utilization_threshold: Option<Duration>,
389
390    /// Unless the current local observation differs from the last one we shared by at least this
391    /// percentage, no observation will be shared with consensus.
392    ///
393    /// If unspecified, this will default to `0.1`.
394    pub observation_sharing_diff_threshold: Option<f64>,
395
396    /// Minimum interval between sharing multiple observations of the same key.
397    ///
398    /// If unspecified, this will default to `5` seconds.
399    pub observation_sharing_min_interval: Option<Duration>,
400
401    /// Global per-second rate limit for sharing observations. This is a safety valve and
402    /// should not trigger during normal operation.
403    ///
404    /// If unspecified, this will default to `10` observations per second.
405    pub observation_sharing_rate_limit: Option<NonZeroU32>,
406
407    /// Global burst limit for sharing observations.
408    ///
409    /// If unspecified, this will default to `100` observations.
410    pub observation_sharing_burst_limit: Option<NonZeroU32>,
411
412    /// Whether to use gas price weighting in execution time estimates.
413    /// When enabled, samples with higher gas prices have more influence on the
414    /// execution time estimates, providing protection against volume-based
415    /// manipulation attacks.
416    ///
417    /// If unspecified, this will default to `false`.
418    pub enable_gas_price_weighting: Option<bool>,
419
420    /// Size of the weighted moving average window for execution time observations.
421    /// This determines how many recent observations are kept in the weighted moving average
422    /// calculation for each execution time observation key.
423    /// Note that this is independent of the window size for the simple moving average.
424    ///
425    /// If unspecified, this will default to `20`.
426    pub weighted_moving_average_window_size: Option<usize>,
427
428    /// Whether to inject synthetic execution time for testing in simtest.
429    /// When enabled, synthetic timings will be generated for execution time observations
430    /// to enable deterministic testing of congestion control features.
431    ///
432    /// If unspecified, this will default to `false`.
433    #[cfg(msim)]
434    pub inject_synthetic_execution_time: Option<bool>,
435}
436
437impl ExecutionTimeObserverConfig {
438    pub fn observation_channel_capacity(&self) -> NonZeroUsize {
439        self.observation_channel_capacity
440            .unwrap_or(nonzero!(1_024usize))
441    }
442
443    pub fn observation_cache_size(&self) -> NonZeroUsize {
444        self.observation_cache_size.unwrap_or(nonzero!(10_000usize))
445    }
446
447    pub fn object_debt_channel_capacity(&self) -> NonZeroUsize {
448        self.object_debt_channel_capacity
449            .unwrap_or(nonzero!(128usize))
450    }
451
452    pub fn object_utilization_cache_size(&self) -> NonZeroUsize {
453        self.object_utilization_cache_size
454            .unwrap_or(nonzero!(50_000usize))
455    }
456
457    pub fn report_object_utilization_metric_with_full_id(&self) -> bool {
458        self.report_object_utilization_metric_with_full_id
459            .unwrap_or(false)
460    }
461
462    pub fn observation_sharing_object_utilization_threshold(&self) -> Duration {
463        self.observation_sharing_object_utilization_threshold
464            .unwrap_or(Duration::from_millis(500))
465    }
466
467    pub fn observation_sharing_diff_threshold(&self) -> f64 {
468        self.observation_sharing_diff_threshold.unwrap_or(0.1)
469    }
470
471    pub fn observation_sharing_min_interval(&self) -> Duration {
472        self.observation_sharing_min_interval
473            .unwrap_or(Duration::from_secs(5))
474    }
475
476    pub fn observation_sharing_rate_limit(&self) -> NonZeroU32 {
477        self.observation_sharing_rate_limit
478            .unwrap_or(nonzero!(10u32))
479    }
480
481    pub fn observation_sharing_burst_limit(&self) -> NonZeroU32 {
482        self.observation_sharing_burst_limit
483            .unwrap_or(nonzero!(100u32))
484    }
485
486    pub fn enable_gas_price_weighting(&self) -> bool {
487        self.enable_gas_price_weighting.unwrap_or(false)
488    }
489
490    pub fn weighted_moving_average_window_size(&self) -> usize {
491        self.weighted_moving_average_window_size.unwrap_or(20)
492    }
493
494    #[cfg(msim)]
495    pub fn inject_synthetic_execution_time(&self) -> bool {
496        self.inject_synthetic_execution_time.unwrap_or(false)
497    }
498}
499
500#[allow(clippy::large_enum_variant)]
501#[derive(Clone, Debug, Deserialize, Serialize)]
502#[serde(rename_all = "kebab-case")]
503pub enum ExecutionCacheConfig {
504    PassthroughCache,
505    WritebackCache {
506        /// Maximum number of entries in each cache. (There are several different caches).
507        /// If None, the default of 10000 is used.
508        max_cache_size: Option<u64>,
509
510        package_cache_size: Option<u64>, // defaults to 1000
511
512        object_cache_size: Option<u64>, // defaults to max_cache_size
513        marker_cache_size: Option<u64>, // defaults to object_cache_size
514        object_by_id_cache_size: Option<u64>, // defaults to object_cache_size
515
516        transaction_cache_size: Option<u64>, // defaults to max_cache_size
517        executed_effect_cache_size: Option<u64>, // defaults to transaction_cache_size
518        effect_cache_size: Option<u64>,      // defaults to executed_effect_cache_size
519
520        events_cache_size: Option<u64>, // defaults to transaction_cache_size
521
522        transaction_objects_cache_size: Option<u64>, // defaults to 1000
523
524        /// Number of uncommitted transactions at which to pause consensus handler.
525        backpressure_threshold: Option<u64>,
526
527        /// Number of uncommitted transactions at which to refuse new transaction
528        /// submissions. Defaults to backpressure_threshold if unset.
529        backpressure_threshold_for_rpc: Option<u64>,
530    },
531}
532
533impl Default for ExecutionCacheConfig {
534    fn default() -> Self {
535        ExecutionCacheConfig::WritebackCache {
536            max_cache_size: None,
537            backpressure_threshold: None,
538            backpressure_threshold_for_rpc: None,
539            package_cache_size: None,
540            object_cache_size: None,
541            marker_cache_size: None,
542            object_by_id_cache_size: None,
543            transaction_cache_size: None,
544            executed_effect_cache_size: None,
545            effect_cache_size: None,
546            events_cache_size: None,
547            transaction_objects_cache_size: None,
548        }
549    }
550}
551
552impl ExecutionCacheConfig {
553    pub fn max_cache_size(&self) -> u64 {
554        std::env::var("SUI_MAX_CACHE_SIZE")
555            .ok()
556            .and_then(|s| s.parse().ok())
557            .unwrap_or_else(|| match self {
558                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
559                ExecutionCacheConfig::WritebackCache { max_cache_size, .. } => {
560                    max_cache_size.unwrap_or(100000)
561                }
562            })
563    }
564
565    pub fn package_cache_size(&self) -> u64 {
566        std::env::var("SUI_PACKAGE_CACHE_SIZE")
567            .ok()
568            .and_then(|s| s.parse().ok())
569            .unwrap_or_else(|| match self {
570                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
571                ExecutionCacheConfig::WritebackCache {
572                    package_cache_size, ..
573                } => package_cache_size.unwrap_or(1000),
574            })
575    }
576
577    pub fn object_cache_size(&self) -> u64 {
578        std::env::var("SUI_OBJECT_CACHE_SIZE")
579            .ok()
580            .and_then(|s| s.parse().ok())
581            .unwrap_or_else(|| match self {
582                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
583                ExecutionCacheConfig::WritebackCache {
584                    object_cache_size, ..
585                } => object_cache_size.unwrap_or(self.max_cache_size()),
586            })
587    }
588
589    pub fn marker_cache_size(&self) -> u64 {
590        std::env::var("SUI_MARKER_CACHE_SIZE")
591            .ok()
592            .and_then(|s| s.parse().ok())
593            .unwrap_or_else(|| match self {
594                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
595                ExecutionCacheConfig::WritebackCache {
596                    marker_cache_size, ..
597                } => marker_cache_size.unwrap_or(self.object_cache_size()),
598            })
599    }
600
601    pub fn object_by_id_cache_size(&self) -> u64 {
602        std::env::var("SUI_OBJECT_BY_ID_CACHE_SIZE")
603            .ok()
604            .and_then(|s| s.parse().ok())
605            .unwrap_or_else(|| match self {
606                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
607                ExecutionCacheConfig::WritebackCache {
608                    object_by_id_cache_size,
609                    ..
610                } => object_by_id_cache_size.unwrap_or(self.object_cache_size()),
611            })
612    }
613
614    pub fn transaction_cache_size(&self) -> u64 {
615        std::env::var("SUI_TRANSACTION_CACHE_SIZE")
616            .ok()
617            .and_then(|s| s.parse().ok())
618            .unwrap_or_else(|| match self {
619                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
620                ExecutionCacheConfig::WritebackCache {
621                    transaction_cache_size,
622                    ..
623                } => transaction_cache_size.unwrap_or(self.max_cache_size()),
624            })
625    }
626
627    pub fn executed_effect_cache_size(&self) -> u64 {
628        std::env::var("SUI_EXECUTED_EFFECT_CACHE_SIZE")
629            .ok()
630            .and_then(|s| s.parse().ok())
631            .unwrap_or_else(|| match self {
632                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
633                ExecutionCacheConfig::WritebackCache {
634                    executed_effect_cache_size,
635                    ..
636                } => executed_effect_cache_size.unwrap_or(self.transaction_cache_size()),
637            })
638    }
639
640    pub fn effect_cache_size(&self) -> u64 {
641        std::env::var("SUI_EFFECT_CACHE_SIZE")
642            .ok()
643            .and_then(|s| s.parse().ok())
644            .unwrap_or_else(|| match self {
645                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
646                ExecutionCacheConfig::WritebackCache {
647                    effect_cache_size, ..
648                } => effect_cache_size.unwrap_or(self.executed_effect_cache_size()),
649            })
650    }
651
652    pub fn events_cache_size(&self) -> u64 {
653        std::env::var("SUI_EVENTS_CACHE_SIZE")
654            .ok()
655            .and_then(|s| s.parse().ok())
656            .unwrap_or_else(|| match self {
657                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
658                ExecutionCacheConfig::WritebackCache {
659                    events_cache_size, ..
660                } => events_cache_size.unwrap_or(self.transaction_cache_size()),
661            })
662    }
663
664    pub fn transaction_objects_cache_size(&self) -> u64 {
665        std::env::var("SUI_TRANSACTION_OBJECTS_CACHE_SIZE")
666            .ok()
667            .and_then(|s| s.parse().ok())
668            .unwrap_or_else(|| match self {
669                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
670                ExecutionCacheConfig::WritebackCache {
671                    transaction_objects_cache_size,
672                    ..
673                } => transaction_objects_cache_size.unwrap_or(1000),
674            })
675    }
676
677    pub fn backpressure_threshold(&self) -> u64 {
678        std::env::var("SUI_BACKPRESSURE_THRESHOLD")
679            .ok()
680            .and_then(|s| s.parse().ok())
681            .unwrap_or_else(|| match self {
682                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
683                ExecutionCacheConfig::WritebackCache {
684                    backpressure_threshold,
685                    ..
686                } => backpressure_threshold.unwrap_or(100_000),
687            })
688    }
689
690    pub fn backpressure_threshold_for_rpc(&self) -> u64 {
691        std::env::var("SUI_BACKPRESSURE_THRESHOLD_FOR_RPC")
692            .ok()
693            .and_then(|s| s.parse().ok())
694            .unwrap_or_else(|| match self {
695                ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
696                ExecutionCacheConfig::WritebackCache {
697                    backpressure_threshold_for_rpc,
698                    ..
699                } => backpressure_threshold_for_rpc.unwrap_or(self.backpressure_threshold()),
700            })
701    }
702}
703
704#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
705#[serde(rename_all = "lowercase")]
706pub enum ServerType {
707    WebSocket,
708    Http,
709    Both,
710}
711
712#[derive(Clone, Debug, Deserialize, Serialize)]
713#[serde(rename_all = "kebab-case")]
714pub struct TransactionKeyValueStoreReadConfig {
715    #[serde(default = "default_base_url")]
716    pub base_url: String,
717
718    #[serde(default = "default_cache_size")]
719    pub cache_size: u64,
720}
721
722impl Default for TransactionKeyValueStoreReadConfig {
723    fn default() -> Self {
724        Self {
725            base_url: default_base_url(),
726            cache_size: default_cache_size(),
727        }
728    }
729}
730
731fn default_base_url() -> String {
732    "https://transactions.sui.io/".to_string()
733}
734
735fn default_cache_size() -> u64 {
736    100_000
737}
738
739fn default_jwk_fetch_interval_seconds() -> u64 {
740    3600
741}
742
743pub fn default_zklogin_oauth_providers() -> BTreeMap<Chain, BTreeSet<String>> {
744    let mut map = BTreeMap::new();
745
746    // providers that are available on devnet only.
747    let experimental_providers = BTreeSet::from([
748        "Google".to_string(),
749        "Facebook".to_string(),
750        "Twitch".to_string(),
751        "Kakao".to_string(),
752        "Apple".to_string(),
753        "Slack".to_string(),
754        "TestIssuer".to_string(),
755        "Microsoft".to_string(),
756        "KarrierOne".to_string(),
757        "Credenza3".to_string(),
758        "Playtron".to_string(),
759        "Threedos".to_string(),
760        "Onefc".to_string(),
761        "FanTV".to_string(),
762        "Arden".to_string(), // Arden partner
763        "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es".to_string(), // Trace, external partner
764        "EveFrontier".to_string(),
765        "TestEveFrontier".to_string(),
766        "AwsTenant-region:ap-southeast-1-tenant_id:ap-southeast-1_2QQPyQXDz".to_string(), // Decot, external partner
767        "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_Bpct2JyBg".to_string(), // test Gamma Prime, external partner
768        "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_4HdQTpt3E".to_string(), // Gamma Prime, external partner
769    ]);
770
771    // providers that are available for mainnet and testnet.
772    let providers = BTreeSet::from([
773        "Google".to_string(),
774        "Facebook".to_string(),
775        "Twitch".to_string(),
776        "Apple".to_string(),
777        "KarrierOne".to_string(),
778        "Credenza3".to_string(),
779        "Playtron".to_string(),
780        "Onefc".to_string(),
781        "Threedos".to_string(),
782        "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es".to_string(), // Trace, external partner
783        "Arden".to_string(),
784        "FanTV".to_string(),
785        "EveFrontier".to_string(),
786        "TestEveFrontier".to_string(),
787        "AwsTenant-region:ap-southeast-1-tenant_id:ap-southeast-1_2QQPyQXDz".to_string(), // Decot, external partner
788        "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_Bpct2JyBg".to_string(), // test Gamma Prime, external partner
789        "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_4HdQTpt3E".to_string(), // Gamma Prime, external partner
790    ]);
791    map.insert(Chain::Mainnet, providers.clone());
792    map.insert(Chain::Testnet, providers);
793    map.insert(Chain::Unknown, experimental_providers);
794    map
795}
796
797fn default_transaction_kv_store_config() -> TransactionKeyValueStoreReadConfig {
798    TransactionKeyValueStoreReadConfig::default()
799}
800
801fn default_authority_store_pruning_config() -> AuthorityStorePruningConfig {
802    AuthorityStorePruningConfig::default()
803}
804
805pub fn default_enable_index_processing() -> bool {
806    true
807}
808
809fn default_grpc_address() -> Multiaddr {
810    "/ip4/0.0.0.0/tcp/8080".parse().unwrap()
811}
812fn default_authority_key_pair() -> AuthorityKeyPairWithPath {
813    AuthorityKeyPairWithPath::new(get_key_pair_from_rng::<AuthorityKeyPair, _>(&mut OsRng).1)
814}
815
816fn default_key_pair() -> KeyPairWithPath {
817    KeyPairWithPath::new(
818        get_key_pair_from_rng::<AccountKeyPair, _>(&mut OsRng)
819            .1
820            .into(),
821    )
822}
823
824fn default_metrics_address() -> SocketAddr {
825    use std::net::{IpAddr, Ipv4Addr};
826    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9184)
827}
828
829pub fn default_admin_interface_port() -> u16 {
830    1337
831}
832
833pub fn default_json_rpc_address() -> SocketAddr {
834    use std::net::{IpAddr, Ipv4Addr};
835    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9000)
836}
837
838pub fn default_concurrency_limit() -> Option<usize> {
839    Some(DEFAULT_GRPC_CONCURRENCY_LIMIT)
840}
841
842pub fn default_end_of_epoch_broadcast_channel_capacity() -> usize {
843    128
844}
845
846pub fn bool_true() -> bool {
847    true
848}
849
850fn is_true(value: &bool) -> bool {
851    *value
852}
853
854impl Config for NodeConfig {}
855
856impl NodeConfig {
857    pub fn protocol_key_pair(&self) -> &AuthorityKeyPair {
858        self.protocol_key_pair.authority_keypair()
859    }
860
861    pub fn worker_key_pair(&self) -> &NetworkKeyPair {
862        match self.worker_key_pair.keypair() {
863            SuiKeyPair::Ed25519(kp) => kp,
864            other => panic!(
865                "Invalid keypair type: {:?}, only Ed25519 is allowed for worker key",
866                other
867            ),
868        }
869    }
870
871    pub fn network_key_pair(&self) -> &NetworkKeyPair {
872        match self.network_key_pair.keypair() {
873            SuiKeyPair::Ed25519(kp) => kp,
874            other => panic!(
875                "Invalid keypair type: {:?}, only Ed25519 is allowed for network key",
876                other
877            ),
878        }
879    }
880
881    pub fn protocol_public_key(&self) -> AuthorityPublicKeyBytes {
882        self.protocol_key_pair().public().into()
883    }
884
885    pub fn db_path(&self) -> PathBuf {
886        self.db_path.join("live")
887    }
888
889    pub fn db_checkpoint_path(&self) -> PathBuf {
890        self.db_path.join("db_checkpoints")
891    }
892
893    pub fn db_store_path(&self) -> PathBuf {
894        self.db_path().join("store")
895    }
896
897    pub fn archive_path(&self) -> PathBuf {
898        self.db_path.join("archive")
899    }
900
901    pub fn snapshot_path(&self) -> PathBuf {
902        self.db_path.join("snapshot")
903    }
904
905    pub fn network_address(&self) -> &Multiaddr {
906        &self.network_address
907    }
908
909    pub fn consensus_config(&self) -> Option<&ConsensusConfig> {
910        self.consensus_config.as_ref()
911    }
912
913    /// Returns the node role as declared by configuration. This is the
914    /// *intended* role used for one-time startup decisions (e.g. whether to
915    /// create RPC servers or index stores). The authoritative per-epoch role
916    /// lives on `AuthorityPerEpochStore::node_role()`.
917    pub fn intended_node_role(&self) -> NodeRole {
918        let has_consensus_config = self.consensus_config.is_some();
919
920        match (self.fullnode_sync_mode, has_consensus_config) {
921            (Some(FullNodeSyncMode::ConsensusObserver), _) => {
922                assert!(
923                    self.has_observer_config_peers(),
924                    "Observer peers must be configured when sync mode is ConsensusObserver"
925                );
926                NodeRole::FullNode(FullNodeSyncMode::ConsensusObserver)
927            }
928            (Some(FullNodeSyncMode::StateSyncOnly), true) => {
929                panic!("Consensus config should not be set for a StateSyncOnly full node");
930            }
931            (Some(FullNodeSyncMode::StateSyncOnly), false) => {
932                NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly)
933            }
934            (None, false) => NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly),
935            (None, true) => NodeRole::Validator,
936        }
937    }
938
939    pub fn has_observer_config_peers(&self) -> bool {
940        self.consensus_config
941            .as_ref()
942            .and_then(|c| c.parameters.as_ref())
943            .map(|p| !p.observer.peers.is_empty())
944            .unwrap_or(false)
945    }
946
947    pub fn genesis(&self) -> Result<&genesis::Genesis> {
948        self.genesis.genesis()
949    }
950
951    pub fn sui_address(&self) -> SuiAddress {
952        (&self.account_key_pair.keypair().public()).into()
953    }
954
955    pub fn archive_reader_config(&self) -> Option<ArchiveReaderConfig> {
956        self.state_archive_read_config
957            .first()
958            .map(|config| ArchiveReaderConfig {
959                ingestion_url: config.ingestion_url.clone(),
960                remote_store_options: config.remote_store_options.clone(),
961                download_concurrency: NonZeroUsize::new(config.concurrency)
962                    .unwrap_or(NonZeroUsize::new(5).unwrap()),
963                remote_store_config: ObjectStoreConfig::default(),
964            })
965    }
966
967    pub fn jsonrpc_server_type(&self) -> ServerType {
968        self.jsonrpc_server_type.unwrap_or(ServerType::Http)
969    }
970
971    pub fn rpc(&self) -> Option<&crate::RpcConfig> {
972        self.rpc.as_ref()
973    }
974}
975
976#[derive(Debug, Clone, Deserialize, Serialize)]
977pub enum ConsensusProtocol {
978    #[serde(rename = "narwhal")]
979    Narwhal,
980    #[serde(rename = "mysticeti")]
981    Mysticeti,
982}
983
984#[derive(Debug, Clone, Deserialize, Serialize)]
985#[serde(rename_all = "kebab-case")]
986pub struct ConsensusConfig {
987    // Base consensus DB path for all epochs.
988    pub db_path: PathBuf,
989
990    // The number of epochs for which to retain the consensus DBs. Setting it to 0 will make a consensus DB getting
991    // dropped as soon as system is switched to a new epoch.
992    pub db_retention_epochs: Option<u64>,
993
994    // Pruner will run on every epoch change but it will also check periodically on every `db_pruner_period_secs`
995    // seconds to see if there are any epoch DBs to remove.
996    pub db_pruner_period_secs: Option<u64>,
997
998    /// Maximum number of pending transactions to submit to consensus, including those
999    /// in submission wait.
1000    /// Default to 20_000 inflight limit, assuming 20_000 txn tps * 1 sec consensus latency.
1001    pub max_pending_transactions: Option<usize>,
1002
1003    pub parameters: Option<ConsensusParameters>,
1004
1005    /// Override for the consensus network listen address.
1006    /// When set, Mysticeti binds to this address instead of deriving from the committee.
1007    /// Address override is advertised via the discovery protocol.
1008    #[serde(skip_serializing_if = "Option::is_none")]
1009    pub listen_address: Option<Multiaddr>,
1010
1011    /// External consensus address that should be advertised via the discovery protocol,
1012    /// if it is different from `listen_address` above.
1013    ///
1014    /// When neither this nor `listen_address` is set, peers use the on-chain committee address.
1015    #[serde(skip_serializing_if = "Option::is_none")]
1016    pub external_address: Option<Multiaddr>,
1017}
1018
1019impl ConsensusConfig {
1020    pub fn db_path(&self) -> &Path {
1021        &self.db_path
1022    }
1023
1024    pub fn max_pending_transactions(&self) -> usize {
1025        self.max_pending_transactions.unwrap_or(20_000)
1026    }
1027
1028    pub fn db_retention_epochs(&self) -> u64 {
1029        self.db_retention_epochs.unwrap_or(0)
1030    }
1031
1032    pub fn db_pruner_period(&self) -> Duration {
1033        // Default to 1 hour
1034        self.db_pruner_period_secs
1035            .map(Duration::from_secs)
1036            .unwrap_or(Duration::from_secs(3_600))
1037    }
1038}
1039
1040#[derive(Clone, Debug, Deserialize, Serialize)]
1041#[serde(rename_all = "kebab-case")]
1042pub struct CheckpointExecutorConfig {
1043    /// Upper bound on the number of checkpoints that can be concurrently executed
1044    ///
1045    /// If unspecified, this will default to `200`
1046    #[serde(default = "default_checkpoint_execution_max_concurrency")]
1047    pub checkpoint_execution_max_concurrency: usize,
1048
1049    /// Number of seconds to wait for effects of a batch of transactions
1050    /// before logging a warning. Note that we will continue to retry
1051    /// indefinitely
1052    ///
1053    /// If unspecified, this will default to `10`.
1054    #[serde(default = "default_local_execution_timeout_sec")]
1055    pub local_execution_timeout_sec: u64,
1056
1057    /// Optional directory used for data ingestion pipeline
1058    /// When specified, each executed checkpoint will be saved in a local directory for post processing
1059    #[serde(default, skip_serializing_if = "Option::is_none")]
1060    pub data_ingestion_dir: Option<PathBuf>,
1061}
1062
1063#[derive(Clone, Debug, Default, Deserialize, Serialize)]
1064#[serde(rename_all = "kebab-case")]
1065pub struct ExpensiveSafetyCheckConfig {
1066    /// If enabled, at epoch boundary, we will check that the storage
1067    /// fund balance is always identical to the sum of the storage
1068    /// rebate of all live objects, and that the total SUI in the network remains
1069    /// the same.
1070    #[serde(default)]
1071    enable_epoch_sui_conservation_check: bool,
1072
1073    /// If enabled, we will check that the total SUI in all input objects of a tx
1074    /// (both the Move part and the storage rebate) matches the total SUI in all
1075    /// output objects of the tx + gas fees
1076    #[serde(default)]
1077    enable_deep_per_tx_sui_conservation_check: bool,
1078
1079    /// Disable epoch SUI conservation check even when we are running in debug mode.
1080    #[serde(default)]
1081    force_disable_epoch_sui_conservation_check: bool,
1082
1083    /// If enabled, at epoch boundary, we will check that the accumulated
1084    /// live object state matches the end of epoch root state digest.
1085    #[serde(default)]
1086    enable_state_consistency_check: bool,
1087
1088    /// Disable state consistency check even when we are running in debug mode.
1089    #[serde(default)]
1090    force_disable_state_consistency_check: bool,
1091
1092    #[serde(default)]
1093    enable_secondary_index_checks: bool,
1094    // TODO: Add more expensive checks here
1095}
1096
1097impl ExpensiveSafetyCheckConfig {
1098    pub fn new_enable_all() -> Self {
1099        Self {
1100            enable_epoch_sui_conservation_check: true,
1101            enable_deep_per_tx_sui_conservation_check: true,
1102            force_disable_epoch_sui_conservation_check: false,
1103            enable_state_consistency_check: true,
1104            force_disable_state_consistency_check: false,
1105            enable_secondary_index_checks: false, // Disable by default for now
1106        }
1107    }
1108
1109    pub fn new_enable_all_with_secondary_index_checks() -> Self {
1110        Self {
1111            enable_secondary_index_checks: true,
1112            ..Self::new_enable_all()
1113        }
1114    }
1115
1116    pub fn new_disable_all() -> Self {
1117        Self {
1118            enable_epoch_sui_conservation_check: false,
1119            enable_deep_per_tx_sui_conservation_check: false,
1120            force_disable_epoch_sui_conservation_check: true,
1121            enable_state_consistency_check: false,
1122            force_disable_state_consistency_check: true,
1123            enable_secondary_index_checks: false,
1124        }
1125    }
1126
1127    pub fn force_disable_epoch_sui_conservation_check(&mut self) {
1128        self.force_disable_epoch_sui_conservation_check = true;
1129    }
1130
1131    pub fn enable_epoch_sui_conservation_check(&self) -> bool {
1132        (self.enable_epoch_sui_conservation_check || cfg!(debug_assertions))
1133            && !self.force_disable_epoch_sui_conservation_check
1134    }
1135
1136    pub fn force_disable_state_consistency_check(&mut self) {
1137        self.force_disable_state_consistency_check = true;
1138    }
1139
1140    pub fn enable_state_consistency_check(&self) -> bool {
1141        (self.enable_state_consistency_check || cfg!(debug_assertions))
1142            && !self.force_disable_state_consistency_check
1143    }
1144
1145    pub fn enable_deep_per_tx_sui_conservation_check(&self) -> bool {
1146        self.enable_deep_per_tx_sui_conservation_check || cfg!(debug_assertions)
1147    }
1148
1149    pub fn enable_secondary_index_checks(&self) -> bool {
1150        self.enable_secondary_index_checks
1151    }
1152}
1153
1154fn default_checkpoint_execution_max_concurrency() -> usize {
1155    4
1156}
1157
1158fn default_local_execution_timeout_sec() -> u64 {
1159    30
1160}
1161
1162impl Default for CheckpointExecutorConfig {
1163    fn default() -> Self {
1164        Self {
1165            checkpoint_execution_max_concurrency: default_checkpoint_execution_max_concurrency(),
1166            local_execution_timeout_sec: default_local_execution_timeout_sec(),
1167            data_ingestion_dir: None,
1168        }
1169    }
1170}
1171
1172#[derive(Debug, Clone, Deserialize, Serialize)]
1173#[serde(rename_all = "kebab-case")]
1174pub struct AuthorityStorePruningConfig {
1175    /// number of the latest epoch dbs to retain
1176    #[serde(default = "default_num_latest_epoch_dbs_to_retain")]
1177    pub num_latest_epoch_dbs_to_retain: usize,
1178    /// time interval used by the pruner to determine whether there are any epoch DBs to remove
1179    #[serde(default = "default_epoch_db_pruning_period_secs")]
1180    pub epoch_db_pruning_period_secs: u64,
1181    /// number of epochs to keep the latest version of objects for.
1182    /// Note that a zero value corresponds to an aggressive pruner.
1183    /// This mode is experimental and needs to be used with caution.
1184    /// Use `u64::MAX` to disable the pruner for the objects.
1185    #[serde(default)]
1186    pub num_epochs_to_retain: u64,
1187    /// pruner's runtime interval used for aggressive mode
1188    #[serde(skip_serializing_if = "Option::is_none")]
1189    pub pruning_run_delay_seconds: Option<u64>,
1190    /// maximum number of checkpoints in the pruning batch. Can be adjusted to increase performance
1191    #[serde(default = "default_max_checkpoints_in_batch")]
1192    pub max_checkpoints_in_batch: usize,
1193    /// maximum number of transaction in the pruning batch
1194    #[serde(default = "default_max_transactions_in_batch")]
1195    pub max_transactions_in_batch: usize,
1196    /// enables periodic background compaction for old SST files whose last modified time is
1197    /// older than `periodic_compaction_threshold_days` days.
1198    /// That ensures that all sst files eventually go through the compaction process
1199    #[serde(
1200        default = "default_periodic_compaction_threshold_days",
1201        skip_serializing_if = "Option::is_none"
1202    )]
1203    pub periodic_compaction_threshold_days: Option<usize>,
1204    /// number of epochs to keep the latest version of transactions and effects for
1205    #[serde(skip_serializing_if = "Option::is_none")]
1206    pub num_epochs_to_retain_for_checkpoints: Option<u64>,
1207    /// disables object tombstone pruning. We don't serialize it if it is the default value, false.
1208    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1209    pub killswitch_tombstone_pruning: bool,
1210    #[serde(default = "default_smoothing", skip_serializing_if = "is_true")]
1211    pub smooth: bool,
1212    #[serde(skip_serializing_if = "Option::is_none")]
1213    pub num_epochs_to_retain_for_indexes: Option<u64>,
1214}
1215
1216fn default_num_latest_epoch_dbs_to_retain() -> usize {
1217    3
1218}
1219
1220fn default_epoch_db_pruning_period_secs() -> u64 {
1221    3600
1222}
1223
1224fn default_max_transactions_in_batch() -> usize {
1225    1000
1226}
1227
1228fn default_max_checkpoints_in_batch() -> usize {
1229    10
1230}
1231
1232fn default_smoothing() -> bool {
1233    cfg!(not(test))
1234}
1235
1236fn default_periodic_compaction_threshold_days() -> Option<usize> {
1237    Some(1)
1238}
1239
1240impl Default for AuthorityStorePruningConfig {
1241    fn default() -> Self {
1242        Self {
1243            num_latest_epoch_dbs_to_retain: default_num_latest_epoch_dbs_to_retain(),
1244            epoch_db_pruning_period_secs: default_epoch_db_pruning_period_secs(),
1245            num_epochs_to_retain: 0,
1246            pruning_run_delay_seconds: if cfg!(msim) { Some(2) } else { None },
1247            max_checkpoints_in_batch: default_max_checkpoints_in_batch(),
1248            max_transactions_in_batch: default_max_transactions_in_batch(),
1249            periodic_compaction_threshold_days: None,
1250            num_epochs_to_retain_for_checkpoints: if cfg!(msim) { Some(2) } else { None },
1251            killswitch_tombstone_pruning: false,
1252            smooth: true,
1253            num_epochs_to_retain_for_indexes: None,
1254        }
1255    }
1256}
1257
1258impl AuthorityStorePruningConfig {
1259    pub fn set_num_epochs_to_retain(&mut self, num_epochs_to_retain: u64) {
1260        self.num_epochs_to_retain = num_epochs_to_retain;
1261    }
1262
1263    pub fn set_num_epochs_to_retain_for_checkpoints(&mut self, num_epochs_to_retain: Option<u64>) {
1264        self.num_epochs_to_retain_for_checkpoints = num_epochs_to_retain;
1265    }
1266
1267    pub fn num_epochs_to_retain_for_checkpoints(&self) -> Option<u64> {
1268        self.num_epochs_to_retain_for_checkpoints
1269            // if n less than 2, coerce to 2 and log
1270            .map(|n| {
1271                if n < 2 {
1272                    info!("num_epochs_to_retain_for_checkpoints must be at least 2, rounding up from {}", n);
1273                    2
1274                } else {
1275                    n
1276                }
1277            })
1278    }
1279
1280    pub fn set_killswitch_tombstone_pruning(&mut self, killswitch_tombstone_pruning: bool) {
1281        self.killswitch_tombstone_pruning = killswitch_tombstone_pruning;
1282    }
1283}
1284
1285#[derive(Debug, Clone, Deserialize, Serialize)]
1286#[serde(rename_all = "kebab-case")]
1287pub struct MetricsConfig {
1288    #[serde(skip_serializing_if = "Option::is_none")]
1289    pub push_interval_seconds: Option<u64>,
1290    #[serde(skip_serializing_if = "Option::is_none")]
1291    pub push_url: Option<String>,
1292}
1293
1294#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1295#[serde(rename_all = "kebab-case")]
1296pub struct DBCheckpointConfig {
1297    #[serde(default)]
1298    pub perform_db_checkpoints_at_epoch_end: bool,
1299    #[serde(skip_serializing_if = "Option::is_none")]
1300    pub checkpoint_path: Option<PathBuf>,
1301    #[serde(skip_serializing_if = "Option::is_none")]
1302    pub object_store_config: Option<ObjectStoreConfig>,
1303    #[serde(skip_serializing_if = "Option::is_none")]
1304    pub perform_index_db_checkpoints_at_epoch_end: Option<bool>,
1305    #[serde(skip_serializing_if = "Option::is_none")]
1306    pub prune_and_compact_before_upload: Option<bool>,
1307}
1308
1309#[derive(Debug, Clone)]
1310pub struct ArchiveReaderConfig {
1311    pub remote_store_config: ObjectStoreConfig,
1312    pub download_concurrency: NonZeroUsize,
1313    pub ingestion_url: Option<String>,
1314    pub remote_store_options: Vec<(String, String)>,
1315}
1316
1317#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1318#[serde(rename_all = "kebab-case")]
1319pub struct StateArchiveConfig {
1320    #[serde(skip_serializing_if = "Option::is_none")]
1321    pub object_store_config: Option<ObjectStoreConfig>,
1322    pub concurrency: usize,
1323    #[serde(skip_serializing_if = "Option::is_none")]
1324    pub ingestion_url: Option<String>,
1325    #[serde(
1326        skip_serializing_if = "Vec::is_empty",
1327        default,
1328        deserialize_with = "deserialize_remote_store_options"
1329    )]
1330    pub remote_store_options: Vec<(String, String)>,
1331}
1332
1333#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1334#[serde(rename_all = "kebab-case")]
1335pub struct StateSnapshotConfig {
1336    #[serde(skip_serializing_if = "Option::is_none")]
1337    pub object_store_config: Option<ObjectStoreConfig>,
1338    pub concurrency: usize,
1339    /// Archive snapshots every N epochs. If set to 0, archival is disabled.
1340    /// Archived snapshots are copied to `archive/epoch_<N>/` in the same bucket
1341    /// and are intended to be kept indefinitely.
1342    #[serde(default)]
1343    pub archive_interval_epochs: u64,
1344}
1345
1346#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1347#[serde(rename_all = "kebab-case")]
1348pub struct TransactionKeyValueStoreWriteConfig {
1349    pub aws_access_key_id: String,
1350    pub aws_secret_access_key: String,
1351    pub aws_region: String,
1352    pub table_name: String,
1353    pub bucket_name: String,
1354    pub concurrency: usize,
1355}
1356
1357/// Configuration for the threshold(s) at which we consider the system
1358/// to be overloaded. When one of the threshold is passed, the node may
1359/// stop processing new transactions and/or certificates until the congestion
1360/// resolves.
1361#[derive(Clone, Debug, Deserialize, Serialize)]
1362#[serde(rename_all = "kebab-case")]
1363pub struct AuthorityOverloadConfig {
1364    #[serde(default = "default_max_txn_age_in_queue")]
1365    pub max_txn_age_in_queue: Duration,
1366
1367    // The interval of checking overload signal.
1368    #[serde(default = "default_overload_monitor_interval")]
1369    pub overload_monitor_interval: Duration,
1370
1371    // The execution queueing latency when entering load shedding mode.
1372    #[serde(default = "default_execution_queue_latency_soft_limit")]
1373    pub execution_queue_latency_soft_limit: Duration,
1374
1375    // The execution queueing latency when entering aggressive load shedding mode.
1376    #[serde(default = "default_execution_queue_latency_hard_limit")]
1377    pub execution_queue_latency_hard_limit: Duration,
1378
1379    // The maximum percentage of transactions to shed in load shedding mode.
1380    #[serde(default = "default_max_load_shedding_percentage")]
1381    pub max_load_shedding_percentage: u32,
1382
1383    // When in aggressive load shedding mode, the minimum percentage of
1384    // transactions to shed.
1385    #[serde(default = "default_min_load_shedding_percentage_above_hard_limit")]
1386    pub min_load_shedding_percentage_above_hard_limit: u32,
1387
1388    // If transaction ready rate is below this rate, we consider the validator
1389    // is well under used, and will not enter load shedding mode.
1390    #[serde(default = "default_safe_transaction_ready_rate")]
1391    pub safe_transaction_ready_rate: u32,
1392
1393    // When set to true, transaction signing may be rejected when the validator
1394    // is overloaded.
1395    #[serde(default = "default_check_system_overload_at_signing")]
1396    pub check_system_overload_at_signing: bool,
1397
1398    // When set to true, transaction execution may be rejected when the validator
1399    // is overloaded.
1400    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1401    pub check_system_overload_at_execution: bool,
1402
1403    // Reject a transaction if transaction manager queue length is above this threshold.
1404    // 100_000 = 10k TPS * 5s resident time in transaction manager (pending + executing) * 2.
1405    #[serde(default = "default_max_transaction_manager_queue_length")]
1406    pub max_transaction_manager_queue_length: usize,
1407
1408    // Reject a transaction if the number of pending transactions depending on the object
1409    // is above the threshold.
1410    #[serde(default = "default_max_transaction_manager_per_object_queue_length")]
1411    pub max_transaction_manager_per_object_queue_length: usize,
1412
1413    // Fraction of max_pending_transactions that determines the admission queue
1414    // capacity. During congestion, the queue evicts the lowest gas price entries
1415    // to make room for higher ones. Capacity = max_pending_transactions * fraction.
1416    #[serde(default = "default_admission_queue_capacity_fraction")]
1417    pub admission_queue_capacity_fraction: f64,
1418
1419    // Fraction of max_pending_transactions below which the admission queue is
1420    // bypassed (transactions are submitted directly to consensus). Above this
1421    // threshold, transactions go through the priority queue.
1422    #[serde(default = "default_admission_queue_bypass_fraction")]
1423    pub admission_queue_bypass_fraction: f64,
1424
1425    // Enables use of a gas-price-based priority queue for load shedding of
1426    // transactions at admission time. If false, when consensus is saturated, transactions
1427    // are rejected with TooManyTransactionsPendingConsensus.
1428    #[serde(default = "default_admission_queue_enabled")]
1429    pub admission_queue_enabled: bool,
1430
1431    // Failover timeout for the admission queue. If the queue has not made forward
1432    // progress (draining an entry or observing an empty queue) within this window,
1433    // it is presumed stuck and new transactions bypass it (using the same saturation
1434    // reject behavior as when the queue is disabled) until progress resumes.
1435    #[serde(default = "default_admission_queue_failover_timeout")]
1436    pub admission_queue_failover_timeout: Duration,
1437}
1438
1439fn default_max_txn_age_in_queue() -> Duration {
1440    Duration::from_millis(1000)
1441}
1442
1443fn default_overload_monitor_interval() -> Duration {
1444    Duration::from_secs(10)
1445}
1446
1447fn default_execution_queue_latency_soft_limit() -> Duration {
1448    Duration::from_secs(1)
1449}
1450
1451fn default_execution_queue_latency_hard_limit() -> Duration {
1452    Duration::from_secs(10)
1453}
1454
1455fn default_max_load_shedding_percentage() -> u32 {
1456    95
1457}
1458
1459fn default_min_load_shedding_percentage_above_hard_limit() -> u32 {
1460    50
1461}
1462
1463fn default_safe_transaction_ready_rate() -> u32 {
1464    100
1465}
1466
1467fn default_check_system_overload_at_signing() -> bool {
1468    true
1469}
1470
1471fn default_max_transaction_manager_queue_length() -> usize {
1472    100_000
1473}
1474
1475fn default_max_transaction_manager_per_object_queue_length() -> usize {
1476    2000
1477}
1478
1479fn default_admission_queue_capacity_fraction() -> f64 {
1480    0.5
1481}
1482
1483fn default_admission_queue_bypass_fraction() -> f64 {
1484    0.9
1485}
1486
1487fn default_admission_queue_enabled() -> bool {
1488    false
1489}
1490
1491fn default_admission_queue_failover_timeout() -> Duration {
1492    Duration::from_secs(30)
1493}
1494
1495impl Default for AuthorityOverloadConfig {
1496    fn default() -> Self {
1497        Self {
1498            max_txn_age_in_queue: default_max_txn_age_in_queue(),
1499            overload_monitor_interval: default_overload_monitor_interval(),
1500            execution_queue_latency_soft_limit: default_execution_queue_latency_soft_limit(),
1501            execution_queue_latency_hard_limit: default_execution_queue_latency_hard_limit(),
1502            max_load_shedding_percentage: default_max_load_shedding_percentage(),
1503            min_load_shedding_percentage_above_hard_limit:
1504                default_min_load_shedding_percentage_above_hard_limit(),
1505            safe_transaction_ready_rate: default_safe_transaction_ready_rate(),
1506            check_system_overload_at_signing: true,
1507            check_system_overload_at_execution: false,
1508            max_transaction_manager_queue_length: default_max_transaction_manager_queue_length(),
1509            max_transaction_manager_per_object_queue_length:
1510                default_max_transaction_manager_per_object_queue_length(),
1511            admission_queue_capacity_fraction: default_admission_queue_capacity_fraction(),
1512            admission_queue_bypass_fraction: default_admission_queue_bypass_fraction(),
1513            admission_queue_enabled: default_admission_queue_enabled(),
1514            admission_queue_failover_timeout: default_admission_queue_failover_timeout(),
1515        }
1516    }
1517}
1518
1519fn default_authority_overload_config() -> AuthorityOverloadConfig {
1520    AuthorityOverloadConfig::default()
1521}
1522
1523fn default_traffic_controller_policy_config() -> Option<PolicyConfig> {
1524    Some(PolicyConfig::default_dos_protection_policy())
1525}
1526
1527#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1528pub struct Genesis {
1529    #[serde(flatten)]
1530    location: GenesisLocation,
1531
1532    #[serde(skip)]
1533    genesis: once_cell::sync::OnceCell<genesis::Genesis>,
1534}
1535
1536impl Genesis {
1537    pub fn new(genesis: genesis::Genesis) -> Self {
1538        Self {
1539            location: GenesisLocation::InPlace { genesis },
1540            genesis: Default::default(),
1541        }
1542    }
1543
1544    pub fn new_from_file<P: Into<PathBuf>>(path: P) -> Self {
1545        Self {
1546            location: GenesisLocation::File {
1547                genesis_file_location: path.into(),
1548            },
1549            genesis: Default::default(),
1550        }
1551    }
1552
1553    pub fn genesis(&self) -> Result<&genesis::Genesis> {
1554        match &self.location {
1555            GenesisLocation::InPlace { genesis } => Ok(genesis),
1556            GenesisLocation::File {
1557                genesis_file_location,
1558            } => self
1559                .genesis
1560                .get_or_try_init(|| genesis::Genesis::load(genesis_file_location)),
1561        }
1562    }
1563}
1564
1565#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1566#[serde(untagged)]
1567#[allow(clippy::large_enum_variant)]
1568enum GenesisLocation {
1569    InPlace {
1570        genesis: genesis::Genesis,
1571    },
1572    File {
1573        #[serde(rename = "genesis-file-location")]
1574        genesis_file_location: PathBuf,
1575    },
1576}
1577
1578/// Wrapper struct for SuiKeyPair that can be deserialized from a file path. Used by network, worker, and account keypair.
1579#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1580pub struct KeyPairWithPath {
1581    #[serde(flatten)]
1582    location: KeyPairLocation,
1583
1584    #[serde(skip)]
1585    keypair: OnceCell<Arc<SuiKeyPair>>,
1586}
1587
1588#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1589#[serde_as]
1590#[serde(untagged)]
1591enum KeyPairLocation {
1592    InPlace {
1593        #[serde_as(as = "Arc<KeyPairBase64>")]
1594        value: Arc<SuiKeyPair>,
1595    },
1596    File {
1597        #[serde(rename = "path")]
1598        path: PathBuf,
1599    },
1600}
1601
1602impl KeyPairWithPath {
1603    pub fn new(kp: SuiKeyPair) -> Self {
1604        let cell: OnceCell<Arc<SuiKeyPair>> = OnceCell::new();
1605        let arc_kp = Arc::new(kp);
1606        // OK to unwrap panic because authority should not start without all keypairs loaded.
1607        cell.set(arc_kp.clone()).expect("Failed to set keypair");
1608        Self {
1609            location: KeyPairLocation::InPlace { value: arc_kp },
1610            keypair: cell,
1611        }
1612    }
1613
1614    pub fn new_from_path(path: PathBuf) -> Self {
1615        let cell: OnceCell<Arc<SuiKeyPair>> = OnceCell::new();
1616        // OK to unwrap panic because authority should not start without all keypairs loaded.
1617        cell.set(Arc::new(read_keypair_from_file(&path).unwrap_or_else(
1618            |e| panic!("Invalid keypair file at path {:?}: {e}", &path),
1619        )))
1620        .expect("Failed to set keypair");
1621        Self {
1622            location: KeyPairLocation::File { path },
1623            keypair: cell,
1624        }
1625    }
1626
1627    pub fn keypair(&self) -> &SuiKeyPair {
1628        self.keypair
1629            .get_or_init(|| match &self.location {
1630                KeyPairLocation::InPlace { value } => value.clone(),
1631                KeyPairLocation::File { path } => {
1632                    // OK to unwrap panic because authority should not start without all keypairs loaded.
1633                    Arc::new(
1634                        read_keypair_from_file(path).unwrap_or_else(|e| {
1635                            panic!("Invalid keypair file at path {:?}: {e}", path)
1636                        }),
1637                    )
1638                }
1639            })
1640            .as_ref()
1641    }
1642}
1643
1644/// Wrapper struct for AuthorityKeyPair that can be deserialized from a file path.
1645#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1646pub struct AuthorityKeyPairWithPath {
1647    #[serde(flatten)]
1648    location: AuthorityKeyPairLocation,
1649
1650    #[serde(skip)]
1651    keypair: OnceCell<Arc<AuthorityKeyPair>>,
1652}
1653
1654#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1655#[serde_as]
1656#[serde(untagged)]
1657enum AuthorityKeyPairLocation {
1658    InPlace { value: Arc<AuthorityKeyPair> },
1659    File { path: PathBuf },
1660}
1661
1662impl AuthorityKeyPairWithPath {
1663    pub fn new(kp: AuthorityKeyPair) -> Self {
1664        let cell: OnceCell<Arc<AuthorityKeyPair>> = OnceCell::new();
1665        let arc_kp = Arc::new(kp);
1666        // OK to unwrap panic because authority should not start without all keypairs loaded.
1667        cell.set(arc_kp.clone())
1668            .expect("Failed to set authority keypair");
1669        Self {
1670            location: AuthorityKeyPairLocation::InPlace { value: arc_kp },
1671            keypair: cell,
1672        }
1673    }
1674
1675    pub fn new_from_path(path: PathBuf) -> Self {
1676        let cell: OnceCell<Arc<AuthorityKeyPair>> = OnceCell::new();
1677        // OK to unwrap panic because authority should not start without all keypairs loaded.
1678        cell.set(Arc::new(
1679            read_authority_keypair_from_file(&path)
1680                .unwrap_or_else(|_| panic!("Invalid authority keypair file at path {:?}", &path)),
1681        ))
1682        .expect("Failed to set authority keypair");
1683        Self {
1684            location: AuthorityKeyPairLocation::File { path },
1685            keypair: cell,
1686        }
1687    }
1688
1689    pub fn authority_keypair(&self) -> &AuthorityKeyPair {
1690        self.keypair
1691            .get_or_init(|| match &self.location {
1692                AuthorityKeyPairLocation::InPlace { value } => value.clone(),
1693                AuthorityKeyPairLocation::File { path } => {
1694                    // OK to unwrap panic because authority should not start without all keypairs loaded.
1695                    Arc::new(
1696                        read_authority_keypair_from_file(path).unwrap_or_else(|_| {
1697                            panic!("Invalid authority keypair file {:?}", &path)
1698                        }),
1699                    )
1700                }
1701            })
1702            .as_ref()
1703    }
1704}
1705
1706/// Configurations which determine how we dump state debug info.
1707/// Debug info is dumped when a node forks.
1708#[derive(Clone, Debug, Deserialize, Serialize, Default)]
1709#[serde(rename_all = "kebab-case")]
1710pub struct StateDebugDumpConfig {
1711    #[serde(skip_serializing_if = "Option::is_none")]
1712    pub dump_file_directory: Option<PathBuf>,
1713}
1714
1715fn read_credential_from_path_or_literal(value: &str) -> Result<String, std::io::Error> {
1716    let path = Path::new(value);
1717    if path.exists() && path.is_file() {
1718        std::fs::read_to_string(path).map(|content| content.trim().to_string())
1719    } else {
1720        Ok(value.to_string())
1721    }
1722}
1723
1724// Custom deserializer for remote store options that supports file paths or literal values
1725fn deserialize_remote_store_options<'de, D>(
1726    deserializer: D,
1727) -> Result<Vec<(String, String)>, D::Error>
1728where
1729    D: serde::Deserializer<'de>,
1730{
1731    use serde::de::Error;
1732
1733    let raw_options: Vec<(String, String)> = Vec::deserialize(deserializer)?;
1734    let mut processed_options = Vec::new();
1735
1736    for (key, value) in raw_options {
1737        // GCS service_account keys expect a file path, not the file content
1738        // All other keys (AWS credentials, service_account_key) should read file content
1739        let is_service_account_path = matches!(
1740            key.as_str(),
1741            "google_service_account"
1742                | "service_account"
1743                | "google_service_account_path"
1744                | "service_account_path"
1745        );
1746
1747        let processed_value = if is_service_account_path {
1748            value
1749        } else {
1750            match read_credential_from_path_or_literal(&value) {
1751                Ok(processed) => processed,
1752                Err(e) => {
1753                    return Err(D::Error::custom(format!(
1754                        "Failed to read credential for key '{}': {}",
1755                        key, e
1756                    )));
1757                }
1758            }
1759        };
1760
1761        processed_options.push((key, processed_value));
1762    }
1763
1764    Ok(processed_options)
1765}
1766
1767#[cfg(test)]
1768mod tests {
1769    use std::path::PathBuf;
1770
1771    use fastcrypto::traits::KeyPair;
1772    use rand::{SeedableRng, rngs::StdRng};
1773    use sui_keys::keypair_file::{write_authority_keypair_to_file, write_keypair_to_file};
1774    use sui_types::crypto::{AuthorityKeyPair, NetworkKeyPair, SuiKeyPair, get_key_pair_from_rng};
1775
1776    use super::{Genesis, StateArchiveConfig};
1777    use crate::NodeConfig;
1778
1779    #[test]
1780    fn serialize_genesis_from_file() {
1781        let g = Genesis::new_from_file("path/to/file");
1782
1783        let s = serde_yaml::to_string(&g).unwrap();
1784        assert_eq!("---\ngenesis-file-location: path/to/file\n", s);
1785        let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
1786        assert_eq!(g, loaded_genesis);
1787    }
1788
1789    #[test]
1790    fn fullnode_template() {
1791        const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1792
1793        let _template: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1794    }
1795
1796    /// Tests that a legacy validator config (captured on 12/06/2024) can be parsed.
1797    #[test]
1798    fn legacy_validator_config() {
1799        const FILE: &str = include_str!("../data/sui-node-legacy.yaml");
1800
1801        let _template: NodeConfig = serde_yaml::from_str(FILE).unwrap();
1802    }
1803
1804    #[test]
1805    fn load_key_pairs_to_node_config() {
1806        let protocol_key_pair: AuthorityKeyPair =
1807            get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
1808        let worker_key_pair: NetworkKeyPair =
1809            get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
1810        let network_key_pair: NetworkKeyPair =
1811            get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
1812
1813        write_authority_keypair_to_file(&protocol_key_pair, PathBuf::from("protocol.key")).unwrap();
1814        write_keypair_to_file(
1815            &SuiKeyPair::Ed25519(worker_key_pair.copy()),
1816            PathBuf::from("worker.key"),
1817        )
1818        .unwrap();
1819        write_keypair_to_file(
1820            &SuiKeyPair::Ed25519(network_key_pair.copy()),
1821            PathBuf::from("network.key"),
1822        )
1823        .unwrap();
1824
1825        const TEMPLATE: &str = include_str!("../data/fullnode-template-with-path.yaml");
1826        let template: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1827        assert_eq!(
1828            template.protocol_key_pair().public(),
1829            protocol_key_pair.public()
1830        );
1831        assert_eq!(
1832            template.network_key_pair().public(),
1833            network_key_pair.public()
1834        );
1835        assert_eq!(
1836            template.worker_key_pair().public(),
1837            worker_key_pair.public()
1838        );
1839    }
1840
1841    #[test]
1842    fn test_remote_store_options_file_path_support() {
1843        // Create temporary credential files
1844        let temp_dir = std::env::temp_dir();
1845        let access_key_file = temp_dir.join("test_access_key");
1846        let secret_key_file = temp_dir.join("test_secret_key");
1847
1848        std::fs::write(&access_key_file, "test_access_key_value").unwrap();
1849        std::fs::write(&secret_key_file, "test_secret_key_value\n").unwrap();
1850
1851        let yaml_config = format!(
1852            r#"
1853object-store-config: null
1854concurrency: 5
1855ingestion-url: "https://example.com"
1856remote-store-options:
1857  - ["aws_access_key_id", "{}"]
1858  - ["aws_secret_access_key", "{}"]
1859  - ["literal_key", "literal_value"]
1860"#,
1861            access_key_file.to_string_lossy(),
1862            secret_key_file.to_string_lossy()
1863        );
1864
1865        let config: StateArchiveConfig = serde_yaml::from_str(&yaml_config).unwrap();
1866
1867        // Verify that file paths were resolved and literal values preserved
1868        assert_eq!(config.remote_store_options.len(), 3);
1869
1870        let access_key_option = config
1871            .remote_store_options
1872            .iter()
1873            .find(|(key, _)| key == "aws_access_key_id")
1874            .unwrap();
1875        assert_eq!(access_key_option.1, "test_access_key_value");
1876
1877        let secret_key_option = config
1878            .remote_store_options
1879            .iter()
1880            .find(|(key, _)| key == "aws_secret_access_key")
1881            .unwrap();
1882        assert_eq!(secret_key_option.1, "test_secret_key_value");
1883
1884        let literal_option = config
1885            .remote_store_options
1886            .iter()
1887            .find(|(key, _)| key == "literal_key")
1888            .unwrap();
1889        assert_eq!(literal_option.1, "literal_value");
1890
1891        // Clean up
1892        std::fs::remove_file(&access_key_file).ok();
1893        std::fs::remove_file(&secret_key_file).ok();
1894    }
1895
1896    #[test]
1897    fn test_remote_store_options_literal_values_only() {
1898        let yaml_config = r#"
1899object-store-config: null
1900concurrency: 5
1901ingestion-url: "https://example.com"
1902remote-store-options:
1903  - ["aws_access_key_id", "literal_access_key"]
1904  - ["aws_secret_access_key", "literal_secret_key"]
1905"#;
1906
1907        let config: StateArchiveConfig = serde_yaml::from_str(yaml_config).unwrap();
1908
1909        assert_eq!(config.remote_store_options.len(), 2);
1910        assert_eq!(config.remote_store_options[0].1, "literal_access_key");
1911        assert_eq!(config.remote_store_options[1].1, "literal_secret_key");
1912    }
1913
1914    #[test]
1915    fn test_remote_store_options_gcs_service_account_path_preserved() {
1916        let temp_dir = std::env::temp_dir();
1917        let service_account_file = temp_dir.join("test_service_account.json");
1918        let aws_key_file = temp_dir.join("test_aws_key");
1919
1920        std::fs::write(&service_account_file, r#"{"type": "service_account"}"#).unwrap();
1921        std::fs::write(&aws_key_file, "aws_key_value").unwrap();
1922
1923        let yaml_config = format!(
1924            r#"
1925object-store-config: null
1926concurrency: 5
1927ingestion-url: "gs://my-bucket"
1928remote-store-options:
1929  - ["service_account", "{}"]
1930  - ["google_service_account_path", "{}"]
1931  - ["aws_access_key_id", "{}"]
1932"#,
1933            service_account_file.to_string_lossy(),
1934            service_account_file.to_string_lossy(),
1935            aws_key_file.to_string_lossy()
1936        );
1937
1938        let config: StateArchiveConfig = serde_yaml::from_str(&yaml_config).unwrap();
1939
1940        assert_eq!(config.remote_store_options.len(), 3);
1941
1942        // service_account should preserve the file path, not read the content
1943        let service_account_option = config
1944            .remote_store_options
1945            .iter()
1946            .find(|(key, _)| key == "service_account")
1947            .unwrap();
1948        assert_eq!(
1949            service_account_option.1,
1950            service_account_file.to_string_lossy()
1951        );
1952
1953        // google_service_account_path should also preserve the file path
1954        let gcs_path_option = config
1955            .remote_store_options
1956            .iter()
1957            .find(|(key, _)| key == "google_service_account_path")
1958            .unwrap();
1959        assert_eq!(gcs_path_option.1, service_account_file.to_string_lossy());
1960
1961        // AWS key should read the file content
1962        let aws_option = config
1963            .remote_store_options
1964            .iter()
1965            .find(|(key, _)| key == "aws_access_key_id")
1966            .unwrap();
1967        assert_eq!(aws_option.1, "aws_key_value");
1968
1969        // Clean up
1970        std::fs::remove_file(&service_account_file).ok();
1971        std::fs::remove_file(&aws_key_file).ok();
1972    }
1973
1974    mod intended_node_role_tests {
1975        use super::*;
1976        use crate::ConsensusConfig;
1977        use consensus_config::Parameters as ConsensusParameters;
1978        use fastcrypto::ed25519::Ed25519KeyPair;
1979        use sui_types::node_role::{FullNodeSyncMode, NodeRole};
1980
1981        fn fullnode_template_config() -> NodeConfig {
1982            const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1983            serde_yaml::from_str(TEMPLATE).unwrap()
1984        }
1985
1986        fn minimal_consensus_config() -> ConsensusConfig {
1987            ConsensusConfig {
1988                db_path: PathBuf::from("/tmp/consensus"),
1989                db_retention_epochs: None,
1990                db_pruner_period_secs: None,
1991                max_pending_transactions: None,
1992                parameters: Default::default(),
1993                listen_address: None,
1994                external_address: None,
1995            }
1996        }
1997
1998        fn consensus_config_with_observer_peers() -> ConsensusConfig {
1999            let mut config = minimal_consensus_config();
2000            let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32]));
2001            let peer = consensus_config::PeerRecord {
2002                public_key: consensus_config::NetworkPublicKey::new(kp.public().clone()),
2003                address: "/ip4/127.0.0.1/udp/8080".parse().unwrap(),
2004            };
2005            let mut params = ConsensusParameters::default();
2006            params.observer.peers = vec![peer];
2007            config.parameters = Some(params);
2008            config
2009        }
2010
2011        #[test]
2012        fn validator_with_consensus_config() {
2013            let mut config = fullnode_template_config();
2014            config.consensus_config = Some(minimal_consensus_config());
2015            config.fullnode_sync_mode = None;
2016
2017            assert_eq!(config.intended_node_role(), NodeRole::Validator);
2018        }
2019
2020        #[test]
2021        fn fullnode_explicit_state_sync() {
2022            let mut config = fullnode_template_config();
2023            config.consensus_config = None;
2024            config.fullnode_sync_mode = Some(FullNodeSyncMode::StateSyncOnly);
2025
2026            assert_eq!(
2027                config.intended_node_role(),
2028                NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly)
2029            );
2030        }
2031
2032        #[test]
2033        fn fullnode_implicit_state_sync() {
2034            let mut config = fullnode_template_config();
2035            config.consensus_config = None;
2036            config.fullnode_sync_mode = None;
2037
2038            assert_eq!(
2039                config.intended_node_role(),
2040                NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly)
2041            );
2042        }
2043
2044        #[test]
2045        fn fullnode_consensus_observer() {
2046            let mut config = fullnode_template_config();
2047            config.consensus_config = Some(consensus_config_with_observer_peers());
2048            config.fullnode_sync_mode = Some(FullNodeSyncMode::ConsensusObserver);
2049
2050            assert_eq!(
2051                config.intended_node_role(),
2052                NodeRole::FullNode(FullNodeSyncMode::ConsensusObserver)
2053            );
2054        }
2055
2056        #[test]
2057        #[should_panic(
2058            expected = "Consensus config should not be set for a StateSyncOnly full node"
2059        )]
2060        fn state_sync_with_consensus_config_panics() {
2061            let mut config = fullnode_template_config();
2062            config.consensus_config = Some(minimal_consensus_config());
2063            config.fullnode_sync_mode = Some(FullNodeSyncMode::StateSyncOnly);
2064
2065            config.intended_node_role();
2066        }
2067
2068        #[test]
2069        #[should_panic(expected = "Observer peers must be configured")]
2070        fn observer_without_peers_panics() {
2071            let mut config = fullnode_template_config();
2072            config.consensus_config = Some(minimal_consensus_config());
2073            config.fullnode_sync_mode = Some(FullNodeSyncMode::ConsensusObserver);
2074
2075            config.intended_node_role();
2076        }
2077    }
2078}
2079
2080// RunWithRange is used to specify the ending epoch/checkpoint to process.
2081// this is intended for use with disaster recovery debugging and verification workflows, never in normal operations
2082#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
2083pub enum RunWithRange {
2084    Epoch(EpochId),
2085    Checkpoint(CheckpointSequenceNumber),
2086}
2087
2088impl RunWithRange {
2089    // is epoch_id > RunWithRange::Epoch
2090    pub fn is_epoch_gt(&self, epoch_id: EpochId) -> bool {
2091        matches!(self, RunWithRange::Epoch(e) if epoch_id > *e)
2092    }
2093
2094    pub fn matches_checkpoint(&self, seq_num: CheckpointSequenceNumber) -> bool {
2095        matches!(self, RunWithRange::Checkpoint(seq) if *seq == seq_num)
2096    }
2097
2098    pub fn into_checkpoint_bound(self) -> Option<CheckpointSequenceNumber> {
2099        match self {
2100            RunWithRange::Epoch(_) => None,
2101            RunWithRange::Checkpoint(seq) => Some(seq),
2102        }
2103    }
2104}