1use 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
41pub const DEFAULT_GRPC_CONCURRENCY_LIMIT: usize = 20000000000;
43
44pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = sui_types::transaction::DEFAULT_VALIDATOR_GAS_PRICE;
46
47pub const DEFAULT_COMMISSION_RATE: u64 = 200;
49
50#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
52pub enum FundsWithdrawSchedulerType {
53 Naive,
54 #[default]
55 Eager,
56}
57
58#[serde_as]
59#[derive(Clone, Debug, Deserialize, Serialize)]
60#[serde(rename_all = "kebab-case")]
61pub struct NodeConfig {
62 #[serde(default = "default_authority_key_pair")]
63 pub protocol_key_pair: AuthorityKeyPairWithPath,
64 #[serde(default = "default_key_pair")]
65 pub worker_key_pair: KeyPairWithPath,
66 #[serde(default = "default_key_pair")]
67 pub account_key_pair: KeyPairWithPath,
68 #[serde(default = "default_key_pair")]
69 pub network_key_pair: KeyPairWithPath,
70
71 pub db_path: PathBuf,
72 #[serde(default = "default_grpc_address")]
73 pub network_address: Multiaddr,
74 #[serde(default = "default_json_rpc_address")]
75 pub json_rpc_address: SocketAddr,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub rpc: Option<crate::RpcConfig>,
79
80 #[serde(default = "default_metrics_address")]
81 pub metrics_address: SocketAddr,
82 #[serde(default = "default_admin_interface_port")]
83 pub admin_interface_port: u16,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub consensus_config: Option<ConsensusConfig>,
87
88 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub fullnode_sync_mode: Option<FullNodeSyncMode>,
93
94 #[serde(default = "default_enable_index_processing")]
95 pub enable_index_processing: bool,
96
97 #[serde(default)]
102 pub sync_post_process_one_tx: bool,
103
104 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
105 pub remove_deprecated_tables: bool,
106
107 #[serde(default)]
108 pub jsonrpc_server_type: Option<ServerType>,
113
114 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
122 pub disable_json_rpc: bool,
123
124 #[serde(default)]
125 pub grpc_load_shed: Option<bool>,
126
127 #[serde(default = "default_concurrency_limit")]
128 pub grpc_concurrency_limit: Option<usize>,
129
130 #[serde(default)]
131 pub p2p_config: P2pConfig,
132
133 pub genesis: Genesis,
134
135 #[serde(default = "default_authority_store_pruning_config")]
136 pub authority_store_pruning_config: AuthorityStorePruningConfig,
137
138 #[serde(default = "default_end_of_epoch_broadcast_channel_capacity")]
142 pub end_of_epoch_broadcast_channel_capacity: usize,
143
144 #[serde(default)]
145 pub checkpoint_executor_config: CheckpointExecutorConfig,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub metrics: Option<MetricsConfig>,
149
150 #[serde(skip)]
154 pub supported_protocol_versions: Option<SupportedProtocolVersions>,
155
156 #[serde(default)]
157 pub db_checkpoint_config: DBCheckpointConfig,
158
159 #[serde(default)]
160 pub expensive_safety_check_config: ExpensiveSafetyCheckConfig,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub name_service_package_address: Option<SuiAddress>,
164
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub name_service_registry_id: Option<ObjectID>,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub name_service_reverse_registry_id: Option<ObjectID>,
170
171 #[serde(default)]
172 pub transaction_deny_config: TransactionDenyConfig,
173
174 #[serde(default)]
178 pub peer_deny_sync_config: PeerDenySyncConfig,
179
180 #[serde(default)]
182 pub dev_inspect_disabled: bool,
183
184 #[serde(default)]
185 pub certificate_deny_config: CertificateDenyConfig,
186
187 #[serde(default)]
188 pub state_debug_dump_config: StateDebugDumpConfig,
189
190 #[serde(default)]
191 pub state_archive_read_config: Vec<StateArchiveConfig>,
192
193 #[serde(default)]
194 pub state_snapshot_write_config: StateSnapshotConfig,
195
196 #[serde(default)]
197 pub indexer_max_subscriptions: Option<usize>,
198
199 #[serde(default = "default_transaction_kv_store_config")]
200 pub transaction_kv_store_read_config: TransactionKeyValueStoreReadConfig,
201
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub transaction_kv_store_write_config: Option<TransactionKeyValueStoreWriteConfig>,
204
205 #[serde(default = "default_jwk_fetch_interval_seconds")]
206 pub jwk_fetch_interval_seconds: u64,
207
208 #[serde(default = "default_zklogin_oauth_providers")]
209 pub zklogin_oauth_providers: BTreeMap<Chain, BTreeSet<String>>,
210
211 #[serde(default = "default_authority_overload_config")]
212 pub authority_overload_config: AuthorityOverloadConfig,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
215 pub run_with_range: Option<RunWithRange>,
216
217 #[serde(
219 skip_serializing_if = "Option::is_none",
220 default = "default_traffic_controller_policy_config"
221 )]
222 pub policy_config: Option<PolicyConfig>,
223
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub firewall_config: Option<RemoteFirewallConfig>,
226
227 #[serde(default)]
228 pub execution_cache: ExecutionCacheConfig,
229
230 #[serde(skip)]
232 #[serde(default = "bool_true")]
233 pub state_accumulator_v2: bool,
234
235 #[serde(skip)]
238 #[serde(default)]
239 pub funds_withdraw_scheduler_type: FundsWithdrawSchedulerType,
240
241 #[serde(default = "bool_true")]
242 pub enable_soft_bundle: bool,
243
244 #[serde(default)]
245 pub verifier_signing_config: VerifierSigningConfig,
246
247 #[serde(skip_serializing_if = "Option::is_none")]
250 pub enable_db_write_stall: Option<bool>,
251
252 #[serde(skip_serializing_if = "Option::is_none")]
256 pub enable_db_sync_to_disk: Option<bool>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
259 pub execution_time_observer_config: Option<ExecutionTimeObserverConfig>,
260
261 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub recent_submission_dedup_window_ms: Option<u64>,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
270 pub chain_override_for_testing: Option<Chain>,
271
272 #[serde(skip_serializing_if = "Option::is_none")]
275 pub validator_client_monitor_config: Option<ValidatorClientMonitorConfig>,
276
277 #[serde(skip_serializing_if = "Option::is_none")]
279 pub fork_recovery: Option<ForkRecoveryConfig>,
280
281 #[serde(skip_serializing_if = "Option::is_none")]
283 pub transaction_driver_config: Option<TransactionDriverConfig>,
284
285 #[serde(skip_serializing_if = "Option::is_none")]
288 pub congestion_log: Option<CongestionLogConfig>,
289
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub address_prober: Option<AddressProberConfig>,
293}
294
295#[derive(Clone, Debug, Deserialize, Serialize)]
296#[serde(rename_all = "kebab-case")]
297pub struct TransactionDriverConfig {
298 #[serde(default, skip_serializing_if = "Vec::is_empty")]
301 pub allowed_submission_validators: Vec<String>,
302
303 #[serde(default, skip_serializing_if = "Vec::is_empty")]
306 pub blocked_submission_validators: Vec<String>,
307
308 #[serde(default = "bool_true")]
313 pub enable_early_validation: bool,
314}
315
316impl Default for TransactionDriverConfig {
317 fn default() -> Self {
318 Self {
319 allowed_submission_validators: vec![],
320 blocked_submission_validators: vec![],
321 enable_early_validation: true,
322 }
323 }
324}
325
326#[derive(Clone, Debug, Deserialize, Serialize)]
327#[serde(rename_all = "kebab-case")]
328pub struct CongestionLogConfig {
329 pub path: PathBuf,
330 #[serde(default = "default_congestion_log_max_file_size")]
331 pub max_file_size: u64,
332 #[serde(default = "default_congestion_log_max_files")]
333 pub max_files: u32,
334}
335
336fn default_congestion_log_max_file_size() -> u64 {
337 100 * 1024 * 1024 }
339
340fn default_congestion_log_max_files() -> u32 {
341 10
342}
343
344#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
345#[serde(rename_all = "kebab-case")]
346pub enum ForkCrashBehavior {
347 #[serde(rename = "recover-once-per-version")]
356 #[default]
357 RecoverOncePerVersion,
358
359 #[serde(rename = "await-fork-recovery")]
362 AwaitForkRecovery,
363
364 #[serde(rename = "return-error")]
366 ReturnError,
367}
368
369#[derive(Clone, Debug, Default, Deserialize, Serialize)]
370#[serde(rename_all = "kebab-case")]
371pub struct ForkRecoveryConfig {
372 #[serde(default)]
375 pub transaction_overrides: BTreeMap<String, String>,
376
377 #[serde(default)]
381 pub checkpoint_overrides: BTreeMap<u64, String>,
382
383 #[serde(default)]
385 pub fork_crash_behavior: ForkCrashBehavior,
386}
387
388#[derive(Clone, Debug, Default, Deserialize, Serialize)]
392#[serde(rename_all = "kebab-case")]
393pub struct AddressProberConfig {
394 pub enabled: Option<bool>,
398
399 pub good_interval: Option<Duration>,
403
404 pub failed_interval: Option<Duration>,
409
410 pub failure_threshold: Option<u32>,
415
416 pub concurrency: Option<usize>,
420
421 pub consensus_probe_timeout: Option<Duration>,
425}
426
427impl AddressProberConfig {
428 pub fn enabled(&self) -> bool {
429 self.enabled.unwrap_or(true)
430 }
431
432 pub fn good_interval(&self) -> Duration {
433 self.good_interval.unwrap_or(Duration::from_secs(60 * 60))
434 }
435
436 pub fn failed_interval(&self) -> Duration {
437 self.failed_interval.unwrap_or(Duration::from_secs(60))
438 }
439
440 pub fn failure_threshold(&self) -> u32 {
441 self.failure_threshold.unwrap_or(3)
442 }
443
444 pub fn concurrency(&self) -> usize {
445 self.concurrency.unwrap_or(16)
446 }
447
448 pub fn consensus_probe_timeout(&self) -> Duration {
449 self.consensus_probe_timeout
450 .unwrap_or(Duration::from_secs(10))
451 }
452
453 pub fn validate(&self) -> anyhow::Result<()> {
454 anyhow::ensure!(
455 self.failed_interval() <= self.good_interval(),
456 "address prober failed_interval ({:?}) must be <= good_interval ({:?})",
457 self.failed_interval(),
458 self.good_interval(),
459 );
460 Ok(())
461 }
462}
463
464#[derive(Clone, Debug, Default, Deserialize, Serialize)]
465#[serde(rename_all = "kebab-case")]
466pub struct ExecutionTimeObserverConfig {
467 pub observation_channel_capacity: Option<NonZeroUsize>,
471
472 pub observation_cache_size: Option<NonZeroUsize>,
476
477 pub object_debt_channel_capacity: Option<NonZeroUsize>,
481
482 pub object_utilization_cache_size: Option<NonZeroUsize>,
486
487 pub report_object_utilization_metric_with_full_id: Option<bool>,
498
499 pub observation_sharing_object_utilization_threshold: Option<Duration>,
504
505 pub observation_sharing_diff_threshold: Option<f64>,
510
511 pub observation_sharing_min_interval: Option<Duration>,
515
516 pub observation_sharing_rate_limit: Option<NonZeroU32>,
521
522 pub observation_sharing_burst_limit: Option<NonZeroU32>,
526
527 pub enable_gas_price_weighting: Option<bool>,
534
535 pub weighted_moving_average_window_size: Option<usize>,
542
543 #[cfg(msim)]
549 pub inject_synthetic_execution_time: Option<bool>,
550}
551
552impl ExecutionTimeObserverConfig {
553 pub fn observation_channel_capacity(&self) -> NonZeroUsize {
554 self.observation_channel_capacity
555 .unwrap_or(nonzero!(1_024usize))
556 }
557
558 pub fn observation_cache_size(&self) -> NonZeroUsize {
559 self.observation_cache_size.unwrap_or(nonzero!(10_000usize))
560 }
561
562 pub fn object_debt_channel_capacity(&self) -> NonZeroUsize {
563 self.object_debt_channel_capacity
564 .unwrap_or(nonzero!(128usize))
565 }
566
567 pub fn object_utilization_cache_size(&self) -> NonZeroUsize {
568 self.object_utilization_cache_size
569 .unwrap_or(nonzero!(50_000usize))
570 }
571
572 pub fn report_object_utilization_metric_with_full_id(&self) -> bool {
573 self.report_object_utilization_metric_with_full_id
574 .unwrap_or(false)
575 }
576
577 pub fn observation_sharing_object_utilization_threshold(&self) -> Duration {
578 self.observation_sharing_object_utilization_threshold
579 .unwrap_or(Duration::from_millis(500))
580 }
581
582 pub fn observation_sharing_diff_threshold(&self) -> f64 {
583 self.observation_sharing_diff_threshold.unwrap_or(0.1)
584 }
585
586 pub fn observation_sharing_min_interval(&self) -> Duration {
587 self.observation_sharing_min_interval
588 .unwrap_or(Duration::from_secs(5))
589 }
590
591 pub fn observation_sharing_rate_limit(&self) -> NonZeroU32 {
592 self.observation_sharing_rate_limit
593 .unwrap_or(nonzero!(10u32))
594 }
595
596 pub fn observation_sharing_burst_limit(&self) -> NonZeroU32 {
597 self.observation_sharing_burst_limit
598 .unwrap_or(nonzero!(100u32))
599 }
600
601 pub fn enable_gas_price_weighting(&self) -> bool {
602 self.enable_gas_price_weighting.unwrap_or(false)
603 }
604
605 pub fn weighted_moving_average_window_size(&self) -> usize {
606 self.weighted_moving_average_window_size.unwrap_or(20)
607 }
608
609 #[cfg(msim)]
610 pub fn inject_synthetic_execution_time(&self) -> bool {
611 self.inject_synthetic_execution_time.unwrap_or(false)
612 }
613}
614
615#[allow(clippy::large_enum_variant)]
616#[derive(Clone, Debug, Deserialize, Serialize)]
617#[serde(rename_all = "kebab-case")]
618pub enum ExecutionCacheConfig {
619 PassthroughCache,
620 WritebackCache {
621 max_cache_size: Option<u64>,
624
625 package_cache_size: Option<u64>, object_cache_size: Option<u64>, marker_cache_size: Option<u64>, object_by_id_cache_size: Option<u64>, transaction_cache_size: Option<u64>, executed_effect_cache_size: Option<u64>, effect_cache_size: Option<u64>, events_cache_size: Option<u64>, transaction_objects_cache_size: Option<u64>, backpressure_threshold: Option<u64>,
641
642 backpressure_threshold_for_rpc: Option<u64>,
645 },
646}
647
648impl Default for ExecutionCacheConfig {
649 fn default() -> Self {
650 ExecutionCacheConfig::WritebackCache {
651 max_cache_size: None,
652 backpressure_threshold: None,
653 backpressure_threshold_for_rpc: None,
654 package_cache_size: None,
655 object_cache_size: None,
656 marker_cache_size: None,
657 object_by_id_cache_size: None,
658 transaction_cache_size: None,
659 executed_effect_cache_size: None,
660 effect_cache_size: None,
661 events_cache_size: None,
662 transaction_objects_cache_size: None,
663 }
664 }
665}
666
667impl ExecutionCacheConfig {
668 pub fn max_cache_size(&self) -> u64 {
669 std::env::var("SUI_MAX_CACHE_SIZE")
670 .ok()
671 .and_then(|s| s.parse().ok())
672 .unwrap_or_else(|| match self {
673 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
674 ExecutionCacheConfig::WritebackCache { max_cache_size, .. } => {
675 max_cache_size.unwrap_or(100000)
676 }
677 })
678 }
679
680 pub fn package_cache_size(&self) -> u64 {
681 std::env::var("SUI_PACKAGE_CACHE_SIZE")
682 .ok()
683 .and_then(|s| s.parse().ok())
684 .unwrap_or_else(|| match self {
685 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
686 ExecutionCacheConfig::WritebackCache {
687 package_cache_size, ..
688 } => package_cache_size.unwrap_or(1000),
689 })
690 }
691
692 pub fn object_cache_size(&self) -> u64 {
693 std::env::var("SUI_OBJECT_CACHE_SIZE")
694 .ok()
695 .and_then(|s| s.parse().ok())
696 .unwrap_or_else(|| match self {
697 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
698 ExecutionCacheConfig::WritebackCache {
699 object_cache_size, ..
700 } => object_cache_size.unwrap_or(self.max_cache_size()),
701 })
702 }
703
704 pub fn marker_cache_size(&self) -> u64 {
705 std::env::var("SUI_MARKER_CACHE_SIZE")
706 .ok()
707 .and_then(|s| s.parse().ok())
708 .unwrap_or_else(|| match self {
709 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
710 ExecutionCacheConfig::WritebackCache {
711 marker_cache_size, ..
712 } => marker_cache_size.unwrap_or(self.object_cache_size()),
713 })
714 }
715
716 pub fn object_by_id_cache_size(&self) -> u64 {
717 std::env::var("SUI_OBJECT_BY_ID_CACHE_SIZE")
718 .ok()
719 .and_then(|s| s.parse().ok())
720 .unwrap_or_else(|| match self {
721 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
722 ExecutionCacheConfig::WritebackCache {
723 object_by_id_cache_size,
724 ..
725 } => object_by_id_cache_size.unwrap_or(self.object_cache_size()),
726 })
727 }
728
729 pub fn transaction_cache_size(&self) -> u64 {
730 std::env::var("SUI_TRANSACTION_CACHE_SIZE")
731 .ok()
732 .and_then(|s| s.parse().ok())
733 .unwrap_or_else(|| match self {
734 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
735 ExecutionCacheConfig::WritebackCache {
736 transaction_cache_size,
737 ..
738 } => transaction_cache_size.unwrap_or(self.max_cache_size()),
739 })
740 }
741
742 pub fn executed_effect_cache_size(&self) -> u64 {
743 std::env::var("SUI_EXECUTED_EFFECT_CACHE_SIZE")
744 .ok()
745 .and_then(|s| s.parse().ok())
746 .unwrap_or_else(|| match self {
747 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
748 ExecutionCacheConfig::WritebackCache {
749 executed_effect_cache_size,
750 ..
751 } => executed_effect_cache_size.unwrap_or(self.transaction_cache_size()),
752 })
753 }
754
755 pub fn effect_cache_size(&self) -> u64 {
756 std::env::var("SUI_EFFECT_CACHE_SIZE")
757 .ok()
758 .and_then(|s| s.parse().ok())
759 .unwrap_or_else(|| match self {
760 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
761 ExecutionCacheConfig::WritebackCache {
762 effect_cache_size, ..
763 } => effect_cache_size.unwrap_or(self.executed_effect_cache_size()),
764 })
765 }
766
767 pub fn events_cache_size(&self) -> u64 {
768 std::env::var("SUI_EVENTS_CACHE_SIZE")
769 .ok()
770 .and_then(|s| s.parse().ok())
771 .unwrap_or_else(|| match self {
772 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
773 ExecutionCacheConfig::WritebackCache {
774 events_cache_size, ..
775 } => events_cache_size.unwrap_or(self.transaction_cache_size()),
776 })
777 }
778
779 pub fn transaction_objects_cache_size(&self) -> u64 {
780 std::env::var("SUI_TRANSACTION_OBJECTS_CACHE_SIZE")
781 .ok()
782 .and_then(|s| s.parse().ok())
783 .unwrap_or_else(|| match self {
784 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
785 ExecutionCacheConfig::WritebackCache {
786 transaction_objects_cache_size,
787 ..
788 } => transaction_objects_cache_size.unwrap_or(1000),
789 })
790 }
791
792 pub fn backpressure_threshold(&self) -> u64 {
793 std::env::var("SUI_BACKPRESSURE_THRESHOLD")
794 .ok()
795 .and_then(|s| s.parse().ok())
796 .unwrap_or_else(|| match self {
797 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
798 ExecutionCacheConfig::WritebackCache {
799 backpressure_threshold,
800 ..
801 } => backpressure_threshold.unwrap_or(100_000),
802 })
803 }
804
805 pub fn backpressure_threshold_for_rpc(&self) -> u64 {
806 std::env::var("SUI_BACKPRESSURE_THRESHOLD_FOR_RPC")
807 .ok()
808 .and_then(|s| s.parse().ok())
809 .unwrap_or_else(|| match self {
810 ExecutionCacheConfig::PassthroughCache => fatal!("invalid cache config"),
811 ExecutionCacheConfig::WritebackCache {
812 backpressure_threshold_for_rpc,
813 ..
814 } => backpressure_threshold_for_rpc.unwrap_or(self.backpressure_threshold()),
815 })
816 }
817}
818
819#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
820#[serde(rename_all = "lowercase")]
821pub enum ServerType {
822 WebSocket,
823 Http,
824 Both,
825}
826
827#[derive(Clone, Debug, Deserialize, Serialize)]
828#[serde(rename_all = "kebab-case")]
829pub struct TransactionKeyValueStoreReadConfig {
830 #[serde(default = "default_base_url")]
831 pub base_url: String,
832
833 #[serde(default = "default_cache_size")]
834 pub cache_size: u64,
835}
836
837impl Default for TransactionKeyValueStoreReadConfig {
838 fn default() -> Self {
839 Self {
840 base_url: default_base_url(),
841 cache_size: default_cache_size(),
842 }
843 }
844}
845
846fn default_base_url() -> String {
847 "https://transactions.sui.io/".to_string()
848}
849
850fn default_cache_size() -> u64 {
851 100_000
852}
853
854fn default_jwk_fetch_interval_seconds() -> u64 {
855 3600
856}
857
858pub fn default_zklogin_oauth_providers() -> BTreeMap<Chain, BTreeSet<String>> {
859 let mut map = BTreeMap::new();
860
861 let experimental_providers = BTreeSet::from([
863 "Google".to_string(),
864 "Facebook".to_string(),
865 "Twitch".to_string(),
866 "Kakao".to_string(),
867 "Apple".to_string(),
868 "Slack".to_string(),
869 "TestIssuer".to_string(),
870 "TestIssuerKey8192".to_string(),
871 "Microsoft".to_string(),
872 "KarrierOne".to_string(),
873 "Credenza3".to_string(),
874 "Playtron".to_string(),
875 "Threedos".to_string(),
876 "Onefc".to_string(),
877 "FanTV".to_string(),
878 "Arden".to_string(), "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es".to_string(), "EveFrontier".to_string(),
881 "TestEveFrontier".to_string(),
882 "AwsTenant-region:ap-southeast-1-tenant_id:ap-southeast-1_2QQPyQXDz".to_string(), "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_Bpct2JyBg".to_string(), "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_4HdQTpt3E".to_string(), ]);
886
887 let providers = BTreeSet::from([
889 "Google".to_string(),
890 "Facebook".to_string(),
891 "Twitch".to_string(),
892 "Apple".to_string(),
893 "KarrierOne".to_string(),
894 "Credenza3".to_string(),
895 "Playtron".to_string(),
896 "Onefc".to_string(),
897 "Threedos".to_string(),
898 "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es".to_string(), "Arden".to_string(),
900 "FanTV".to_string(),
901 "EveFrontier".to_string(),
902 "TestEveFrontier".to_string(),
903 "AwsTenant-region:ap-southeast-1-tenant_id:ap-southeast-1_2QQPyQXDz".to_string(), "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_Bpct2JyBg".to_string(), "AwsTenant-region:eu-north-1-tenant_id:eu-north-1_4HdQTpt3E".to_string(), ]);
907 map.insert(Chain::Mainnet, providers.clone());
908 map.insert(Chain::Testnet, providers);
909 map.insert(Chain::Unknown, experimental_providers);
910 map
911}
912
913fn default_transaction_kv_store_config() -> TransactionKeyValueStoreReadConfig {
914 TransactionKeyValueStoreReadConfig::default()
915}
916
917fn default_authority_store_pruning_config() -> AuthorityStorePruningConfig {
918 AuthorityStorePruningConfig::default()
919}
920
921pub fn default_enable_index_processing() -> bool {
922 true
923}
924
925fn default_grpc_address() -> Multiaddr {
926 "/ip4/0.0.0.0/tcp/8080".parse().unwrap()
927}
928fn default_authority_key_pair() -> AuthorityKeyPairWithPath {
929 AuthorityKeyPairWithPath::new(get_key_pair_from_rng::<AuthorityKeyPair, _>(&mut OsRng).1)
930}
931
932fn default_key_pair() -> KeyPairWithPath {
933 KeyPairWithPath::new(
934 get_key_pair_from_rng::<AccountKeyPair, _>(&mut OsRng)
935 .1
936 .into(),
937 )
938}
939
940fn default_metrics_address() -> SocketAddr {
941 use std::net::{IpAddr, Ipv4Addr};
942 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9184)
943}
944
945pub fn default_admin_interface_port() -> u16 {
946 1337
947}
948
949pub fn default_json_rpc_address() -> SocketAddr {
950 use std::net::{IpAddr, Ipv4Addr};
951 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9000)
952}
953
954pub fn default_concurrency_limit() -> Option<usize> {
955 Some(DEFAULT_GRPC_CONCURRENCY_LIMIT)
956}
957
958pub fn default_end_of_epoch_broadcast_channel_capacity() -> usize {
959 128
960}
961
962pub fn bool_true() -> bool {
963 true
964}
965
966fn is_true(value: &bool) -> bool {
967 *value
968}
969
970impl Config for NodeConfig {}
971
972impl NodeConfig {
973 pub fn protocol_key_pair(&self) -> &AuthorityKeyPair {
974 self.protocol_key_pair.authority_keypair()
975 }
976
977 pub fn recent_submission_dedup_window(&self) -> Duration {
980 Duration::from_millis(self.recent_submission_dedup_window_ms.unwrap_or(1000))
981 }
982
983 pub fn worker_key_pair(&self) -> &NetworkKeyPair {
984 match self.worker_key_pair.keypair() {
985 SuiKeyPair::Ed25519(kp) => kp,
986 other => panic!(
987 "Invalid keypair type: {:?}, only Ed25519 is allowed for worker key",
988 other
989 ),
990 }
991 }
992
993 pub fn network_key_pair(&self) -> &NetworkKeyPair {
994 match self.network_key_pair.keypair() {
995 SuiKeyPair::Ed25519(kp) => kp,
996 other => panic!(
997 "Invalid keypair type: {:?}, only Ed25519 is allowed for network key",
998 other
999 ),
1000 }
1001 }
1002
1003 pub fn protocol_public_key(&self) -> AuthorityPublicKeyBytes {
1004 self.protocol_key_pair().public().into()
1005 }
1006
1007 pub fn db_path(&self) -> PathBuf {
1008 self.db_path.join("live")
1009 }
1010
1011 pub fn db_checkpoint_path(&self) -> PathBuf {
1012 self.db_path.join("db_checkpoints")
1013 }
1014
1015 pub fn db_store_path(&self) -> PathBuf {
1016 self.db_path().join("store")
1017 }
1018
1019 pub fn archive_path(&self) -> PathBuf {
1020 self.db_path.join("archive")
1021 }
1022
1023 pub fn snapshot_path(&self) -> PathBuf {
1024 self.db_path.join("snapshot")
1025 }
1026
1027 pub fn network_address(&self) -> &Multiaddr {
1028 &self.network_address
1029 }
1030
1031 pub fn consensus_config(&self) -> Option<&ConsensusConfig> {
1032 self.consensus_config.as_ref()
1033 }
1034
1035 pub fn intended_node_role(&self) -> NodeRole {
1040 let has_consensus_config = self.consensus_config.is_some();
1041
1042 match (self.fullnode_sync_mode, has_consensus_config) {
1043 (Some(FullNodeSyncMode::ConsensusObserver), _) => {
1044 assert!(
1045 self.has_observer_config_peers(),
1046 "Observer peers must be configured when sync mode is ConsensusObserver"
1047 );
1048 NodeRole::FullNode(FullNodeSyncMode::ConsensusObserver)
1049 }
1050 (Some(FullNodeSyncMode::StateSyncOnly), true) => {
1051 panic!("Consensus config should not be set for a StateSyncOnly full node");
1052 }
1053 (Some(FullNodeSyncMode::StateSyncOnly), false) => {
1054 NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly)
1055 }
1056 (None, false) => NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly),
1057 (None, true) => NodeRole::Validator,
1058 }
1059 }
1060
1061 pub fn has_observer_config_peers(&self) -> bool {
1062 self.consensus_config
1063 .as_ref()
1064 .and_then(|c| c.parameters.as_ref())
1065 .map(|p| !p.observer.peers.is_empty())
1066 .unwrap_or(false)
1067 }
1068
1069 pub fn genesis(&self) -> Result<&genesis::Genesis> {
1070 self.genesis.genesis()
1071 }
1072
1073 pub fn sui_address(&self) -> SuiAddress {
1074 (&self.account_key_pair.keypair().public()).into()
1075 }
1076
1077 pub fn archive_reader_config(&self) -> Option<ArchiveReaderConfig> {
1078 self.state_archive_read_config
1079 .first()
1080 .map(|config| ArchiveReaderConfig {
1081 ingestion_url: config.ingestion_url.clone(),
1082 remote_store_options: config.remote_store_options.clone(),
1083 remote_store_headers: config.remote_store_headers.clone(),
1084 download_concurrency: NonZeroUsize::new(config.concurrency)
1085 .unwrap_or(NonZeroUsize::new(5).unwrap()),
1086 remote_store_config: ObjectStoreConfig::default(),
1087 })
1088 }
1089
1090 pub fn jsonrpc_server_type(&self) -> ServerType {
1091 self.jsonrpc_server_type.unwrap_or(ServerType::Http)
1092 }
1093
1094 pub fn json_rpc_enabled(&self) -> bool {
1098 !self.disable_json_rpc
1099 }
1100
1101 pub fn rpc(&self) -> Option<&crate::RpcConfig> {
1102 self.rpc.as_ref()
1103 }
1104}
1105
1106#[derive(Debug, Clone, Deserialize, Serialize)]
1107pub enum ConsensusProtocol {
1108 #[serde(rename = "narwhal")]
1109 Narwhal,
1110 #[serde(rename = "mysticeti")]
1111 Mysticeti,
1112}
1113
1114#[derive(Debug, Clone, Deserialize, Serialize)]
1115#[serde(rename_all = "kebab-case")]
1116pub struct ConsensusConfig {
1117 pub db_path: PathBuf,
1119
1120 pub db_retention_epochs: Option<u64>,
1123
1124 pub db_pruner_period_secs: Option<u64>,
1127
1128 pub max_pending_transactions: Option<usize>,
1132
1133 pub parameters: Option<ConsensusParameters>,
1134
1135 #[serde(skip_serializing_if = "Option::is_none")]
1139 pub listen_address: Option<Multiaddr>,
1140
1141 #[serde(skip_serializing_if = "Option::is_none")]
1146 pub external_address: Option<Multiaddr>,
1147}
1148
1149impl ConsensusConfig {
1150 pub fn db_path(&self) -> &Path {
1151 &self.db_path
1152 }
1153
1154 pub fn max_pending_transactions(&self) -> usize {
1155 self.max_pending_transactions.unwrap_or(20_000)
1156 }
1157
1158 pub fn db_retention_epochs(&self) -> u64 {
1159 self.db_retention_epochs.unwrap_or(0)
1160 }
1161
1162 pub fn db_pruner_period(&self) -> Duration {
1163 self.db_pruner_period_secs
1165 .map(Duration::from_secs)
1166 .unwrap_or(Duration::from_secs(3_600))
1167 }
1168}
1169
1170#[derive(Clone, Debug, Deserialize, Serialize)]
1171#[serde(rename_all = "kebab-case")]
1172pub struct CheckpointExecutorConfig {
1173 #[serde(default = "default_checkpoint_execution_max_concurrency")]
1177 pub checkpoint_execution_max_concurrency: usize,
1178
1179 #[serde(default = "default_local_execution_timeout_sec")]
1185 pub local_execution_timeout_sec: u64,
1186
1187 #[serde(default, skip_serializing_if = "Option::is_none")]
1190 pub data_ingestion_dir: Option<PathBuf>,
1191}
1192
1193#[derive(Clone, Debug, Default, Deserialize, Serialize)]
1194#[serde(rename_all = "kebab-case")]
1195pub struct ExpensiveSafetyCheckConfig {
1196 #[serde(default)]
1201 enable_epoch_sui_conservation_check: bool,
1202
1203 #[serde(default)]
1207 enable_deep_per_tx_sui_conservation_check: bool,
1208
1209 #[serde(default)]
1211 force_disable_epoch_sui_conservation_check: bool,
1212
1213 #[serde(default)]
1216 enable_state_consistency_check: bool,
1217
1218 #[serde(default)]
1220 force_disable_state_consistency_check: bool,
1221
1222 #[serde(default)]
1223 enable_secondary_index_checks: bool,
1224 }
1226
1227impl ExpensiveSafetyCheckConfig {
1228 pub fn new_enable_all() -> Self {
1229 Self {
1230 enable_epoch_sui_conservation_check: true,
1231 enable_deep_per_tx_sui_conservation_check: true,
1232 force_disable_epoch_sui_conservation_check: false,
1233 enable_state_consistency_check: true,
1234 force_disable_state_consistency_check: false,
1235 enable_secondary_index_checks: false, }
1237 }
1238
1239 pub fn new_enable_all_with_secondary_index_checks() -> Self {
1240 Self {
1241 enable_secondary_index_checks: true,
1242 ..Self::new_enable_all()
1243 }
1244 }
1245
1246 pub fn new_disable_all() -> Self {
1247 Self {
1248 enable_epoch_sui_conservation_check: false,
1249 enable_deep_per_tx_sui_conservation_check: false,
1250 force_disable_epoch_sui_conservation_check: true,
1251 enable_state_consistency_check: false,
1252 force_disable_state_consistency_check: true,
1253 enable_secondary_index_checks: false,
1254 }
1255 }
1256
1257 pub fn force_disable_epoch_sui_conservation_check(&mut self) {
1258 self.force_disable_epoch_sui_conservation_check = true;
1259 }
1260
1261 pub fn enable_epoch_sui_conservation_check(&self) -> bool {
1262 (self.enable_epoch_sui_conservation_check || cfg!(debug_assertions))
1263 && !self.force_disable_epoch_sui_conservation_check
1264 }
1265
1266 pub fn force_disable_state_consistency_check(&mut self) {
1267 self.force_disable_state_consistency_check = true;
1268 }
1269
1270 pub fn enable_state_consistency_check(&self) -> bool {
1271 (self.enable_state_consistency_check || cfg!(debug_assertions))
1272 && !self.force_disable_state_consistency_check
1273 }
1274
1275 pub fn enable_deep_per_tx_sui_conservation_check(&self) -> bool {
1276 self.enable_deep_per_tx_sui_conservation_check || cfg!(debug_assertions)
1277 }
1278
1279 pub fn enable_secondary_index_checks(&self) -> bool {
1280 self.enable_secondary_index_checks
1281 }
1282}
1283
1284fn default_checkpoint_execution_max_concurrency() -> usize {
1285 4
1286}
1287
1288fn default_local_execution_timeout_sec() -> u64 {
1289 30
1290}
1291
1292impl Default for CheckpointExecutorConfig {
1293 fn default() -> Self {
1294 Self {
1295 checkpoint_execution_max_concurrency: default_checkpoint_execution_max_concurrency(),
1296 local_execution_timeout_sec: default_local_execution_timeout_sec(),
1297 data_ingestion_dir: None,
1298 }
1299 }
1300}
1301
1302#[derive(Debug, Clone, Deserialize, Serialize)]
1303#[serde(rename_all = "kebab-case")]
1304pub struct AuthorityStorePruningConfig {
1305 #[serde(default = "default_num_latest_epoch_dbs_to_retain")]
1307 pub num_latest_epoch_dbs_to_retain: usize,
1308 #[serde(default = "default_epoch_db_pruning_period_secs")]
1310 pub epoch_db_pruning_period_secs: u64,
1311 #[serde(default)]
1316 pub num_epochs_to_retain: u64,
1317 #[serde(skip_serializing_if = "Option::is_none")]
1319 pub pruning_run_delay_seconds: Option<u64>,
1320 #[serde(default = "default_max_checkpoints_in_batch")]
1322 pub max_checkpoints_in_batch: usize,
1323 #[serde(default = "default_max_transactions_in_batch")]
1325 pub max_transactions_in_batch: usize,
1326 #[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 #[serde(default, skip_serializing_if = "Option::is_none")]
1344 pub rpc_store_bitmap_periodic_compaction_days: Option<u64>,
1345 #[serde(skip_serializing_if = "Option::is_none")]
1347 pub num_epochs_to_retain_for_checkpoints: Option<u64>,
1348 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1350 pub killswitch_tombstone_pruning: bool,
1351 #[serde(default = "default_smoothing", skip_serializing_if = "is_true")]
1352 pub smooth: bool,
1353 #[serde(skip_serializing_if = "Option::is_none")]
1354 pub num_epochs_to_retain_for_indexes: Option<u64>,
1355}
1356
1357fn default_num_latest_epoch_dbs_to_retain() -> usize {
1358 3
1359}
1360
1361fn default_epoch_db_pruning_period_secs() -> u64 {
1362 3600
1363}
1364
1365fn default_max_transactions_in_batch() -> usize {
1366 1000
1367}
1368
1369fn default_max_checkpoints_in_batch() -> usize {
1370 10
1371}
1372
1373fn default_smoothing() -> bool {
1374 cfg!(not(test))
1375}
1376
1377fn default_periodic_compaction_threshold_days() -> Option<usize> {
1378 Some(1)
1379}
1380
1381impl Default for AuthorityStorePruningConfig {
1382 fn default() -> Self {
1383 Self {
1384 num_latest_epoch_dbs_to_retain: default_num_latest_epoch_dbs_to_retain(),
1385 epoch_db_pruning_period_secs: default_epoch_db_pruning_period_secs(),
1386 num_epochs_to_retain: 0,
1387 pruning_run_delay_seconds: if cfg!(msim) { Some(2) } else { None },
1388 max_checkpoints_in_batch: default_max_checkpoints_in_batch(),
1389 max_transactions_in_batch: default_max_transactions_in_batch(),
1390 periodic_compaction_threshold_days: None,
1391 rpc_store_bitmap_periodic_compaction_days: None,
1392 num_epochs_to_retain_for_checkpoints: if cfg!(msim) { Some(2) } else { None },
1393 killswitch_tombstone_pruning: false,
1394 smooth: true,
1395 num_epochs_to_retain_for_indexes: None,
1396 }
1397 }
1398}
1399
1400impl AuthorityStorePruningConfig {
1401 pub fn set_num_epochs_to_retain(&mut self, num_epochs_to_retain: u64) {
1402 self.num_epochs_to_retain = num_epochs_to_retain;
1403 }
1404
1405 pub fn set_num_epochs_to_retain_for_checkpoints(&mut self, num_epochs_to_retain: Option<u64>) {
1406 self.num_epochs_to_retain_for_checkpoints = num_epochs_to_retain;
1407 }
1408
1409 pub fn num_epochs_to_retain_for_checkpoints(&self) -> Option<u64> {
1410 self.num_epochs_to_retain_for_checkpoints
1411 .map(|n| {
1413 if n < 2 {
1414 info!("num_epochs_to_retain_for_checkpoints must be at least 2, rounding up from {}", n);
1415 2
1416 } else {
1417 n
1418 }
1419 })
1420 }
1421
1422 pub fn set_killswitch_tombstone_pruning(&mut self, killswitch_tombstone_pruning: bool) {
1423 self.killswitch_tombstone_pruning = killswitch_tombstone_pruning;
1424 }
1425}
1426
1427#[derive(Debug, Clone, Deserialize, Serialize)]
1428#[serde(rename_all = "kebab-case")]
1429pub struct MetricsConfig {
1430 #[serde(skip_serializing_if = "Option::is_none")]
1431 pub push_interval_seconds: Option<u64>,
1432 #[serde(skip_serializing_if = "Option::is_none")]
1433 pub push_url: Option<String>,
1434}
1435
1436#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1437#[serde(rename_all = "kebab-case")]
1438pub struct DBCheckpointConfig {
1439 #[serde(default)]
1440 pub perform_db_checkpoints_at_epoch_end: bool,
1441 #[serde(skip_serializing_if = "Option::is_none")]
1442 pub checkpoint_path: Option<PathBuf>,
1443 #[serde(skip_serializing_if = "Option::is_none")]
1444 pub object_store_config: Option<ObjectStoreConfig>,
1445 #[serde(skip_serializing_if = "Option::is_none")]
1446 pub perform_index_db_checkpoints_at_epoch_end: Option<bool>,
1447 #[serde(skip_serializing_if = "Option::is_none")]
1448 pub prune_and_compact_before_upload: Option<bool>,
1449}
1450
1451#[derive(Debug, Clone)]
1452pub struct ArchiveReaderConfig {
1453 pub remote_store_config: ObjectStoreConfig,
1454 pub download_concurrency: NonZeroUsize,
1455 pub ingestion_url: Option<String>,
1456 pub remote_store_options: Vec<(String, String)>,
1457 pub remote_store_headers: Vec<(String, String)>,
1458}
1459
1460#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1461#[serde(rename_all = "kebab-case")]
1462pub struct StateArchiveConfig {
1463 #[serde(skip_serializing_if = "Option::is_none")]
1464 pub object_store_config: Option<ObjectStoreConfig>,
1465 pub concurrency: usize,
1466 #[serde(skip_serializing_if = "Option::is_none")]
1467 pub ingestion_url: Option<String>,
1468 #[serde(
1469 skip_serializing_if = "Vec::is_empty",
1470 default,
1471 deserialize_with = "deserialize_remote_store_options"
1472 )]
1473 pub remote_store_options: Vec<(String, String)>,
1474 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1478 pub remote_store_headers: Vec<(String, String)>,
1479}
1480
1481#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1482#[serde(rename_all = "kebab-case")]
1483pub struct StateSnapshotConfig {
1484 #[serde(skip_serializing_if = "Option::is_none")]
1485 pub object_store_config: Option<ObjectStoreConfig>,
1486 pub concurrency: usize,
1487 #[serde(default)]
1491 pub archive_interval_epochs: u64,
1492}
1493
1494#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1495#[serde(rename_all = "kebab-case")]
1496pub struct TransactionKeyValueStoreWriteConfig {
1497 pub aws_access_key_id: String,
1498 pub aws_secret_access_key: String,
1499 pub aws_region: String,
1500 pub table_name: String,
1501 pub bucket_name: String,
1502 pub concurrency: usize,
1503}
1504
1505#[derive(Clone, Debug, Deserialize, Serialize)]
1510#[serde(rename_all = "kebab-case")]
1511pub struct AuthorityOverloadConfig {
1512 #[serde(default = "default_max_txn_age_in_queue")]
1513 pub max_txn_age_in_queue: Duration,
1514
1515 #[serde(default = "default_overload_monitor_interval")]
1517 pub overload_monitor_interval: Duration,
1518
1519 #[serde(default = "default_execution_queue_latency_soft_limit")]
1521 pub execution_queue_latency_soft_limit: Duration,
1522
1523 #[serde(default = "default_execution_queue_latency_hard_limit")]
1525 pub execution_queue_latency_hard_limit: Duration,
1526
1527 #[serde(default = "default_max_load_shedding_percentage")]
1529 pub max_load_shedding_percentage: u32,
1530
1531 #[serde(default = "default_min_load_shedding_percentage_above_hard_limit")]
1534 pub min_load_shedding_percentage_above_hard_limit: u32,
1535
1536 #[serde(default = "default_safe_transaction_ready_rate")]
1539 pub safe_transaction_ready_rate: u32,
1540
1541 #[serde(default = "default_check_system_overload_at_signing")]
1544 pub check_system_overload_at_signing: bool,
1545
1546 #[serde(default = "default_max_transaction_manager_queue_length")]
1549 pub max_transaction_manager_queue_length: usize,
1550
1551 #[serde(default = "default_max_transaction_manager_per_object_queue_length")]
1554 pub max_transaction_manager_per_object_queue_length: usize,
1555
1556 #[serde(default = "default_admission_queue_capacity_fraction")]
1560 pub admission_queue_capacity_fraction: f64,
1561
1562 #[serde(default = "default_admission_queue_enabled")]
1566 pub admission_queue_enabled: bool,
1567
1568 #[serde(default = "default_admission_queue_failover_timeout")]
1573 pub admission_queue_failover_timeout: Duration,
1574}
1575
1576fn default_max_txn_age_in_queue() -> Duration {
1577 Duration::from_millis(1000)
1578}
1579
1580fn default_overload_monitor_interval() -> Duration {
1581 Duration::from_secs(10)
1582}
1583
1584fn default_execution_queue_latency_soft_limit() -> Duration {
1585 Duration::from_secs(1)
1586}
1587
1588fn default_execution_queue_latency_hard_limit() -> Duration {
1589 Duration::from_secs(10)
1590}
1591
1592fn default_max_load_shedding_percentage() -> u32 {
1593 95
1594}
1595
1596fn default_min_load_shedding_percentage_above_hard_limit() -> u32 {
1597 50
1598}
1599
1600fn default_safe_transaction_ready_rate() -> u32 {
1601 100
1602}
1603
1604fn default_check_system_overload_at_signing() -> bool {
1605 true
1606}
1607
1608fn default_max_transaction_manager_queue_length() -> usize {
1609 100_000
1610}
1611
1612fn default_max_transaction_manager_per_object_queue_length() -> usize {
1613 2000
1614}
1615
1616fn default_admission_queue_capacity_fraction() -> f64 {
1617 0.5
1618}
1619
1620fn default_admission_queue_enabled() -> bool {
1621 true
1622}
1623
1624fn default_admission_queue_failover_timeout() -> Duration {
1625 Duration::from_secs(30)
1626}
1627
1628impl Default for AuthorityOverloadConfig {
1629 fn default() -> Self {
1630 Self {
1631 max_txn_age_in_queue: default_max_txn_age_in_queue(),
1632 overload_monitor_interval: default_overload_monitor_interval(),
1633 execution_queue_latency_soft_limit: default_execution_queue_latency_soft_limit(),
1634 execution_queue_latency_hard_limit: default_execution_queue_latency_hard_limit(),
1635 max_load_shedding_percentage: default_max_load_shedding_percentage(),
1636 min_load_shedding_percentage_above_hard_limit:
1637 default_min_load_shedding_percentage_above_hard_limit(),
1638 safe_transaction_ready_rate: default_safe_transaction_ready_rate(),
1639 check_system_overload_at_signing: true,
1640 max_transaction_manager_queue_length: default_max_transaction_manager_queue_length(),
1641 max_transaction_manager_per_object_queue_length:
1642 default_max_transaction_manager_per_object_queue_length(),
1643 admission_queue_capacity_fraction: default_admission_queue_capacity_fraction(),
1644 admission_queue_enabled: default_admission_queue_enabled(),
1645 admission_queue_failover_timeout: default_admission_queue_failover_timeout(),
1646 }
1647 }
1648}
1649
1650fn default_authority_overload_config() -> AuthorityOverloadConfig {
1651 AuthorityOverloadConfig::default()
1652}
1653
1654fn default_traffic_controller_policy_config() -> Option<PolicyConfig> {
1655 Some(PolicyConfig::default_dos_protection_policy())
1656}
1657
1658#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1659pub struct Genesis {
1660 #[serde(flatten)]
1661 location: GenesisLocation,
1662
1663 #[serde(skip)]
1664 genesis: once_cell::sync::OnceCell<genesis::Genesis>,
1665}
1666
1667impl Genesis {
1668 pub fn new(genesis: genesis::Genesis) -> Self {
1669 Self {
1670 location: GenesisLocation::InPlace { genesis },
1671 genesis: Default::default(),
1672 }
1673 }
1674
1675 pub fn new_from_file<P: Into<PathBuf>>(path: P) -> Self {
1676 Self {
1677 location: GenesisLocation::File {
1678 genesis_file_location: path.into(),
1679 },
1680 genesis: Default::default(),
1681 }
1682 }
1683
1684 pub fn genesis(&self) -> Result<&genesis::Genesis> {
1685 match &self.location {
1686 GenesisLocation::InPlace { genesis } => Ok(genesis),
1687 GenesisLocation::File {
1688 genesis_file_location,
1689 } => self
1690 .genesis
1691 .get_or_try_init(|| genesis::Genesis::load(genesis_file_location)),
1692 }
1693 }
1694}
1695
1696#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1697#[serde(untagged)]
1698#[allow(clippy::large_enum_variant)]
1699enum GenesisLocation {
1700 InPlace {
1701 genesis: genesis::Genesis,
1702 },
1703 File {
1704 #[serde(rename = "genesis-file-location")]
1705 genesis_file_location: PathBuf,
1706 },
1707}
1708
1709#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1711pub struct KeyPairWithPath {
1712 #[serde(flatten)]
1713 location: KeyPairLocation,
1714
1715 #[serde(skip)]
1716 keypair: OnceCell<Arc<SuiKeyPair>>,
1717}
1718
1719#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1720#[serde_as]
1721#[serde(untagged)]
1722enum KeyPairLocation {
1723 InPlace {
1724 #[serde_as(as = "Arc<KeyPairBase64>")]
1725 value: Arc<SuiKeyPair>,
1726 },
1727 File {
1728 #[serde(rename = "path")]
1729 path: PathBuf,
1730 },
1731}
1732
1733impl KeyPairWithPath {
1734 pub fn new(kp: SuiKeyPair) -> Self {
1735 let cell: OnceCell<Arc<SuiKeyPair>> = OnceCell::new();
1736 let arc_kp = Arc::new(kp);
1737 cell.set(arc_kp.clone()).expect("Failed to set keypair");
1739 Self {
1740 location: KeyPairLocation::InPlace { value: arc_kp },
1741 keypair: cell,
1742 }
1743 }
1744
1745 pub fn new_from_path(path: PathBuf) -> Self {
1746 let cell: OnceCell<Arc<SuiKeyPair>> = OnceCell::new();
1747 cell.set(Arc::new(read_keypair_from_file(&path).unwrap_or_else(
1749 |e| panic!("Invalid keypair file at path {:?}: {e}", &path),
1750 )))
1751 .expect("Failed to set keypair");
1752 Self {
1753 location: KeyPairLocation::File { path },
1754 keypair: cell,
1755 }
1756 }
1757
1758 pub fn keypair(&self) -> &SuiKeyPair {
1759 self.keypair
1760 .get_or_init(|| match &self.location {
1761 KeyPairLocation::InPlace { value } => value.clone(),
1762 KeyPairLocation::File { path } => {
1763 Arc::new(
1765 read_keypair_from_file(path).unwrap_or_else(|e| {
1766 panic!("Invalid keypair file at path {:?}: {e}", path)
1767 }),
1768 )
1769 }
1770 })
1771 .as_ref()
1772 }
1773}
1774
1775#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1777pub struct AuthorityKeyPairWithPath {
1778 #[serde(flatten)]
1779 location: AuthorityKeyPairLocation,
1780
1781 #[serde(skip)]
1782 keypair: OnceCell<Arc<AuthorityKeyPair>>,
1783}
1784
1785#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1786#[serde_as]
1787#[serde(untagged)]
1788enum AuthorityKeyPairLocation {
1789 InPlace { value: Arc<AuthorityKeyPair> },
1790 File { path: PathBuf },
1791}
1792
1793impl AuthorityKeyPairWithPath {
1794 pub fn new(kp: AuthorityKeyPair) -> Self {
1795 let cell: OnceCell<Arc<AuthorityKeyPair>> = OnceCell::new();
1796 let arc_kp = Arc::new(kp);
1797 cell.set(arc_kp.clone())
1799 .expect("Failed to set authority keypair");
1800 Self {
1801 location: AuthorityKeyPairLocation::InPlace { value: arc_kp },
1802 keypair: cell,
1803 }
1804 }
1805
1806 pub fn new_from_path(path: PathBuf) -> Self {
1807 let cell: OnceCell<Arc<AuthorityKeyPair>> = OnceCell::new();
1808 cell.set(Arc::new(
1810 read_authority_keypair_from_file(&path)
1811 .unwrap_or_else(|_| panic!("Invalid authority keypair file at path {:?}", &path)),
1812 ))
1813 .expect("Failed to set authority keypair");
1814 Self {
1815 location: AuthorityKeyPairLocation::File { path },
1816 keypair: cell,
1817 }
1818 }
1819
1820 pub fn authority_keypair(&self) -> &AuthorityKeyPair {
1821 self.keypair
1822 .get_or_init(|| match &self.location {
1823 AuthorityKeyPairLocation::InPlace { value } => value.clone(),
1824 AuthorityKeyPairLocation::File { path } => {
1825 Arc::new(
1827 read_authority_keypair_from_file(path).unwrap_or_else(|_| {
1828 panic!("Invalid authority keypair file {:?}", &path)
1829 }),
1830 )
1831 }
1832 })
1833 .as_ref()
1834 }
1835}
1836
1837#[derive(Clone, Debug, Deserialize, Serialize, Default)]
1840#[serde(rename_all = "kebab-case")]
1841pub struct StateDebugDumpConfig {
1842 #[serde(skip_serializing_if = "Option::is_none")]
1843 pub dump_file_directory: Option<PathBuf>,
1844}
1845
1846fn read_credential_from_path_or_literal(value: &str) -> Result<String, std::io::Error> {
1847 let path = Path::new(value);
1848 if path.exists() && path.is_file() {
1849 std::fs::read_to_string(path).map(|content| content.trim().to_string())
1850 } else {
1851 Ok(value.to_string())
1852 }
1853}
1854
1855fn deserialize_remote_store_options<'de, D>(
1857 deserializer: D,
1858) -> Result<Vec<(String, String)>, D::Error>
1859where
1860 D: serde::Deserializer<'de>,
1861{
1862 use serde::de::Error;
1863
1864 let raw_options: Vec<(String, String)> = Vec::deserialize(deserializer)?;
1865 let mut processed_options = Vec::new();
1866
1867 for (key, value) in raw_options {
1868 let is_service_account_path = matches!(
1871 key.as_str(),
1872 "google_service_account"
1873 | "service_account"
1874 | "google_service_account_path"
1875 | "service_account_path"
1876 );
1877
1878 let processed_value = if is_service_account_path {
1879 value
1880 } else {
1881 match read_credential_from_path_or_literal(&value) {
1882 Ok(processed) => processed,
1883 Err(e) => {
1884 return Err(D::Error::custom(format!(
1885 "Failed to read credential for key '{}': {}",
1886 key, e
1887 )));
1888 }
1889 }
1890 };
1891
1892 processed_options.push((key, processed_value));
1893 }
1894
1895 Ok(processed_options)
1896}
1897
1898#[cfg(test)]
1899mod tests {
1900 use std::path::PathBuf;
1901
1902 use fastcrypto::traits::KeyPair;
1903 use rand::{SeedableRng, rngs::StdRng};
1904 use sui_keys::keypair_file::{write_authority_keypair_to_file, write_keypair_to_file};
1905 use sui_types::crypto::{AuthorityKeyPair, NetworkKeyPair, SuiKeyPair, get_key_pair_from_rng};
1906
1907 use super::{AuthorityStorePruningConfig, Genesis, StateArchiveConfig};
1908 use crate::NodeConfig;
1909
1910 #[test]
1911 fn serialize_genesis_from_file() {
1912 let g = Genesis::new_from_file("path/to/file");
1913
1914 let s = serde_yaml::to_string(&g).unwrap();
1915 assert_eq!("---\ngenesis-file-location: path/to/file\n", s);
1916 let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
1917 assert_eq!(g, loaded_genesis);
1918 }
1919
1920 #[test]
1921 fn fullnode_template() {
1922 const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1923
1924 let _template: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1925 }
1926
1927 #[test]
1929 fn legacy_validator_config() {
1930 const FILE: &str = include_str!("../data/sui-node-legacy.yaml");
1931
1932 let template: NodeConfig = serde_yaml::from_str(FILE).unwrap();
1933 assert_eq!(
1934 template
1935 .authority_store_pruning_config
1936 .rpc_store_bitmap_periodic_compaction_days,
1937 None
1938 );
1939 }
1940
1941 #[test]
1942 fn rpc_store_bitmap_periodic_compaction_days_override_deserializes() {
1943 assert_eq!(
1944 AuthorityStorePruningConfig::default().rpc_store_bitmap_periodic_compaction_days,
1945 None
1946 );
1947
1948 let omitted: AuthorityStorePruningConfig = serde_yaml::from_str("{}").unwrap();
1949 assert_eq!(omitted.rpc_store_bitmap_periodic_compaction_days, None);
1950
1951 let disabled: AuthorityStorePruningConfig =
1952 serde_yaml::from_str("rpc-store-bitmap-periodic-compaction-days: 0").unwrap();
1953 assert_eq!(disabled.rpc_store_bitmap_periodic_compaction_days, Some(0));
1954
1955 let configured: AuthorityStorePruningConfig =
1956 serde_yaml::from_str("rpc-store-bitmap-periodic-compaction-days: 17").unwrap();
1957 let serialized = serde_yaml::to_string(&configured).unwrap();
1958 let round_tripped: AuthorityStorePruningConfig = serde_yaml::from_str(&serialized).unwrap();
1959 assert_eq!(
1960 round_tripped.rpc_store_bitmap_periodic_compaction_days,
1961 Some(17)
1962 );
1963 }
1964
1965 #[test]
1966 fn load_key_pairs_to_node_config() {
1967 let protocol_key_pair: AuthorityKeyPair =
1968 get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
1969 let worker_key_pair: NetworkKeyPair =
1970 get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
1971 let network_key_pair: NetworkKeyPair =
1972 get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
1973
1974 write_authority_keypair_to_file(&protocol_key_pair, PathBuf::from("protocol.key")).unwrap();
1975 write_keypair_to_file(
1976 &SuiKeyPair::Ed25519(worker_key_pair.copy()),
1977 PathBuf::from("worker.key"),
1978 )
1979 .unwrap();
1980 write_keypair_to_file(
1981 &SuiKeyPair::Ed25519(network_key_pair.copy()),
1982 PathBuf::from("network.key"),
1983 )
1984 .unwrap();
1985
1986 const TEMPLATE: &str = include_str!("../data/fullnode-template-with-path.yaml");
1987 let template: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1988 assert_eq!(
1989 template.protocol_key_pair().public(),
1990 protocol_key_pair.public()
1991 );
1992 assert_eq!(
1993 template.network_key_pair().public(),
1994 network_key_pair.public()
1995 );
1996 assert_eq!(
1997 template.worker_key_pair().public(),
1998 worker_key_pair.public()
1999 );
2000 }
2001
2002 #[test]
2003 fn test_remote_store_options_file_path_support() {
2004 let temp_dir = std::env::temp_dir();
2006 let access_key_file = temp_dir.join("test_access_key");
2007 let secret_key_file = temp_dir.join("test_secret_key");
2008
2009 std::fs::write(&access_key_file, "test_access_key_value").unwrap();
2010 std::fs::write(&secret_key_file, "test_secret_key_value\n").unwrap();
2011
2012 let yaml_config = format!(
2013 r#"
2014object-store-config: null
2015concurrency: 5
2016ingestion-url: "https://example.com"
2017remote-store-options:
2018 - ["aws_access_key_id", "{}"]
2019 - ["aws_secret_access_key", "{}"]
2020 - ["literal_key", "literal_value"]
2021"#,
2022 access_key_file.to_string_lossy(),
2023 secret_key_file.to_string_lossy()
2024 );
2025
2026 let config: StateArchiveConfig = serde_yaml::from_str(&yaml_config).unwrap();
2027
2028 assert_eq!(config.remote_store_options.len(), 3);
2030
2031 let access_key_option = config
2032 .remote_store_options
2033 .iter()
2034 .find(|(key, _)| key == "aws_access_key_id")
2035 .unwrap();
2036 assert_eq!(access_key_option.1, "test_access_key_value");
2037
2038 let secret_key_option = config
2039 .remote_store_options
2040 .iter()
2041 .find(|(key, _)| key == "aws_secret_access_key")
2042 .unwrap();
2043 assert_eq!(secret_key_option.1, "test_secret_key_value");
2044
2045 let literal_option = config
2046 .remote_store_options
2047 .iter()
2048 .find(|(key, _)| key == "literal_key")
2049 .unwrap();
2050 assert_eq!(literal_option.1, "literal_value");
2051
2052 std::fs::remove_file(&access_key_file).ok();
2054 std::fs::remove_file(&secret_key_file).ok();
2055 }
2056
2057 #[test]
2058 fn test_remote_store_options_literal_values_only() {
2059 let yaml_config = r#"
2060object-store-config: null
2061concurrency: 5
2062ingestion-url: "https://example.com"
2063remote-store-options:
2064 - ["aws_access_key_id", "literal_access_key"]
2065 - ["aws_secret_access_key", "literal_secret_key"]
2066"#;
2067
2068 let config: StateArchiveConfig = serde_yaml::from_str(yaml_config).unwrap();
2069
2070 assert_eq!(config.remote_store_options.len(), 2);
2071 assert_eq!(config.remote_store_options[0].1, "literal_access_key");
2072 assert_eq!(config.remote_store_options[1].1, "literal_secret_key");
2073 }
2074
2075 #[test]
2076 fn test_remote_store_options_gcs_service_account_path_preserved() {
2077 let temp_dir = std::env::temp_dir();
2078 let service_account_file = temp_dir.join("test_service_account.json");
2079 let aws_key_file = temp_dir.join("test_aws_key");
2080
2081 std::fs::write(&service_account_file, r#"{"type": "service_account"}"#).unwrap();
2082 std::fs::write(&aws_key_file, "aws_key_value").unwrap();
2083
2084 let yaml_config = format!(
2085 r#"
2086object-store-config: null
2087concurrency: 5
2088ingestion-url: "gs://my-bucket"
2089remote-store-options:
2090 - ["service_account", "{}"]
2091 - ["google_service_account_path", "{}"]
2092 - ["aws_access_key_id", "{}"]
2093"#,
2094 service_account_file.to_string_lossy(),
2095 service_account_file.to_string_lossy(),
2096 aws_key_file.to_string_lossy()
2097 );
2098
2099 let config: StateArchiveConfig = serde_yaml::from_str(&yaml_config).unwrap();
2100
2101 assert_eq!(config.remote_store_options.len(), 3);
2102
2103 let service_account_option = config
2105 .remote_store_options
2106 .iter()
2107 .find(|(key, _)| key == "service_account")
2108 .unwrap();
2109 assert_eq!(
2110 service_account_option.1,
2111 service_account_file.to_string_lossy()
2112 );
2113
2114 let gcs_path_option = config
2116 .remote_store_options
2117 .iter()
2118 .find(|(key, _)| key == "google_service_account_path")
2119 .unwrap();
2120 assert_eq!(gcs_path_option.1, service_account_file.to_string_lossy());
2121
2122 let aws_option = config
2124 .remote_store_options
2125 .iter()
2126 .find(|(key, _)| key == "aws_access_key_id")
2127 .unwrap();
2128 assert_eq!(aws_option.1, "aws_key_value");
2129
2130 std::fs::remove_file(&service_account_file).ok();
2132 std::fs::remove_file(&aws_key_file).ok();
2133 }
2134
2135 mod intended_node_role_tests {
2136 use super::*;
2137 use crate::ConsensusConfig;
2138 use consensus_config::Parameters as ConsensusParameters;
2139 use fastcrypto::ed25519::Ed25519KeyPair;
2140 use sui_types::node_role::{FullNodeSyncMode, NodeRole};
2141
2142 fn fullnode_template_config() -> NodeConfig {
2143 const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
2144 serde_yaml::from_str(TEMPLATE).unwrap()
2145 }
2146
2147 fn minimal_consensus_config() -> ConsensusConfig {
2148 ConsensusConfig {
2149 db_path: PathBuf::from("/tmp/consensus"),
2150 db_retention_epochs: None,
2151 db_pruner_period_secs: None,
2152 max_pending_transactions: None,
2153 parameters: Default::default(),
2154 listen_address: None,
2155 external_address: None,
2156 }
2157 }
2158
2159 fn consensus_config_with_observer_peers() -> ConsensusConfig {
2160 let mut config = minimal_consensus_config();
2161 let kp = Ed25519KeyPair::generate(&mut StdRng::from_seed([0; 32]));
2162 let peer = consensus_config::PeerRecord {
2163 public_key: consensus_config::NetworkPublicKey::new(kp.public().clone()),
2164 address: "/ip4/127.0.0.1/udp/8080".parse().unwrap(),
2165 };
2166 let mut params = ConsensusParameters::default();
2167 params.observer.peers = vec![peer];
2168 config.parameters = Some(params);
2169 config
2170 }
2171
2172 #[test]
2173 fn validator_with_consensus_config() {
2174 let mut config = fullnode_template_config();
2175 config.consensus_config = Some(minimal_consensus_config());
2176 config.fullnode_sync_mode = None;
2177
2178 assert_eq!(config.intended_node_role(), NodeRole::Validator);
2179 }
2180
2181 #[test]
2182 fn fullnode_explicit_state_sync() {
2183 let mut config = fullnode_template_config();
2184 config.consensus_config = None;
2185 config.fullnode_sync_mode = Some(FullNodeSyncMode::StateSyncOnly);
2186
2187 assert_eq!(
2188 config.intended_node_role(),
2189 NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly)
2190 );
2191 }
2192
2193 #[test]
2194 fn fullnode_implicit_state_sync() {
2195 let mut config = fullnode_template_config();
2196 config.consensus_config = None;
2197 config.fullnode_sync_mode = None;
2198
2199 assert_eq!(
2200 config.intended_node_role(),
2201 NodeRole::FullNode(FullNodeSyncMode::StateSyncOnly)
2202 );
2203 }
2204
2205 #[test]
2206 fn fullnode_consensus_observer() {
2207 let mut config = fullnode_template_config();
2208 config.consensus_config = Some(consensus_config_with_observer_peers());
2209 config.fullnode_sync_mode = Some(FullNodeSyncMode::ConsensusObserver);
2210
2211 assert_eq!(
2212 config.intended_node_role(),
2213 NodeRole::FullNode(FullNodeSyncMode::ConsensusObserver)
2214 );
2215 }
2216
2217 #[test]
2218 #[should_panic(
2219 expected = "Consensus config should not be set for a StateSyncOnly full node"
2220 )]
2221 fn state_sync_with_consensus_config_panics() {
2222 let mut config = fullnode_template_config();
2223 config.consensus_config = Some(minimal_consensus_config());
2224 config.fullnode_sync_mode = Some(FullNodeSyncMode::StateSyncOnly);
2225
2226 config.intended_node_role();
2227 }
2228
2229 #[test]
2230 #[should_panic(expected = "Observer peers must be configured")]
2231 fn observer_without_peers_panics() {
2232 let mut config = fullnode_template_config();
2233 config.consensus_config = Some(minimal_consensus_config());
2234 config.fullnode_sync_mode = Some(FullNodeSyncMode::ConsensusObserver);
2235
2236 config.intended_node_role();
2237 }
2238 }
2239}
2240
2241#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
2244pub enum RunWithRange {
2245 Epoch(EpochId),
2246 Checkpoint(CheckpointSequenceNumber),
2247}
2248
2249impl RunWithRange {
2250 pub fn is_epoch_gt(&self, epoch_id: EpochId) -> bool {
2252 matches!(self, RunWithRange::Epoch(e) if epoch_id > *e)
2253 }
2254
2255 pub fn matches_checkpoint(&self, seq_num: CheckpointSequenceNumber) -> bool {
2256 matches!(self, RunWithRange::Checkpoint(seq) if *seq == seq_num)
2257 }
2258
2259 pub fn into_checkpoint_bound(self) -> Option<CheckpointSequenceNumber> {
2260 match self {
2261 RunWithRange::Epoch(_) => None,
2262 RunWithRange::Checkpoint(seq) => Some(seq),
2263 }
2264 }
2265}