sui_config/
node.rs

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