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