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