Skip to main content

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