Skip to main content

sui_config/
node.rs

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