Skip to main content

iota_config/
node.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    net::{IpAddr, Ipv4Addr, SocketAddr},
7    num::NonZeroUsize,
8    path::{Path, PathBuf},
9    sync::Arc,
10    time::Duration,
11};
12
13use anyhow::Result;
14use iota_keys::keypair_file::{read_authority_keypair_from_file, read_keypair_from_file};
15use iota_metrics::MetricGroups;
16use iota_multiaddr::Multiaddr;
17use iota_names::config::IotaNamesConfig;
18use iota_sdk_crypto::simple::SimpleKeypair;
19use iota_sdk_types::Address;
20use iota_types::{
21    committee::EpochId,
22    crypto::{
23        AccountPrivateKey, AuthorityKeyPair, AuthorityPublicKeyBytes, KeypairTraits,
24        NetworkKeyPair, get_key_pair_from_rng,
25    },
26    messages_checkpoint::CheckpointSequenceNumber,
27    supported_protocol_versions::{Chain, SupportedProtocolVersions},
28    traffic_control::PolicyConfig,
29};
30use once_cell::sync::OnceCell;
31use rand::{rand_core::UnwrapErr, rngs::SysRng};
32use serde::{Deserialize, Serialize};
33use starfish_config::Parameters as StarfishParameters;
34
35use crate::{
36    Config, certificate_deny_config::CertificateDenyConfig, genesis,
37    migration_tx_data::MigrationTxData, object_storage_config::ObjectStoreConfig, p2p::P2pConfig,
38    transaction_deny_config::TransactionDenyConfig, verifier_signing_config::VerifierSigningConfig,
39};
40
41/// Default gas price of 1000 Nanos
42pub const DEFAULT_VALIDATOR_GAS_PRICE: u64 = iota_types::transaction::DEFAULT_VALIDATOR_GAS_PRICE;
43
44/// Default commission rate of 2%
45pub const DEFAULT_COMMISSION_RATE: u64 = 200;
46
47/// Default budget in MiB for the in-memory full-checkpoint-contents cache.
48pub const DEFAULT_FULL_CHECKPOINT_CONTENTS_CACHE_SIZE_MB: usize = 1024;
49
50#[derive(Clone, Debug, Deserialize, Serialize)]
51#[serde(rename_all = "kebab-case")]
52pub struct NodeConfig {
53    /// The public key bytes corresponding to the private key that the validator
54    /// holds to sign transactions.
55    #[serde(default = "default_authority_key_pair")]
56    pub authority_key_pair: AuthorityKeyPairWithPath,
57    /// The public key bytes corresponding to the private key that the validator
58    /// holds to sign consensus blocks.
59    #[serde(default = "default_key_pair")]
60    pub protocol_key_pair: KeyPairWithPath,
61    #[serde(default = "default_key_pair")]
62    pub account_key_pair: KeyPairWithPath,
63    /// The public key bytes corresponding to the private key that the validator
64    /// uses to establish TLS connections.
65    #[serde(default = "default_key_pair")]
66    pub network_key_pair: KeyPairWithPath,
67    pub db_path: PathBuf,
68
69    /// The network address for gRPC communication.
70    ///
71    /// Can be overwritten with args `listen-address` parameters.
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    /// The address for Prometheus metrics.
78    #[serde(default = "default_metrics_address")]
79    pub metrics_address: SocketAddr,
80
81    /// The address for the admin interface that is
82    /// run in the metrics separate runtime and provides access to
83    /// admin node commands such as logging and tracing options.
84    #[serde(default = "default_admin_interface_address")]
85    pub admin_interface_address: SocketAddr,
86
87    /// Configuration struct for the consensus.
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub consensus_config: Option<ConsensusConfig>,
90
91    /// Flag to enable the JSON-RPC API. Default: `true`. When `false` a full
92    /// node serves only the `/health` endpoint on `json_rpc_address`.
93    #[serde(default = "default_enable_jsonrpc_api")]
94    pub enable_jsonrpc_api: bool,
95
96    /// Renamed to `enable_jsonrpc_api`; see [`NodeConfig::check_renamed_keys`]
97    /// for which configs that still set it are refused. Never set this.
98    /// Removal is tracked in <https://github.com/iotaledger/iota/issues/12885>.
99    #[serde(default, skip_serializing)]
100    pub enable_index_processing: Option<bool>,
101
102    /// Flag to enable gRPC load shedding: requests over a service's
103    /// concurrency limit are rejected immediately with `RESOURCE_EXHAUSTED`
104    /// instead of waiting for a slot.
105    #[serde(default)]
106    pub grpc_load_shed: Option<bool>,
107
108    /// Maximum number of concurrent in-flight requests per CPU core, applied
109    /// to each service of the validator gRPC server separately (`Validator`,
110    /// `ValidatorV2`, `ValidatorPeer`), so a flood of client transaction
111    /// submissions cannot crowd validator-peer RPCs out of admission slots.
112    /// The effective per-service limit is this value multiplied by the CPU
113    /// cores of the machine at server startup, so the same config file scales
114    /// with the hardware.
115    ///
116    /// Most request time is spent awaiting locks, I/O or consensus rather
117    /// than on-CPU, so the default ceiling is generous: it bounds total
118    /// in-flight work so a request flood cannot grow queues and memory
119    /// without limit, it does not throttle normal load. Operators wanting
120    /// hard load-shedding can lower it and set `grpc_load_shed`.
121    ///
122    /// A value of zero is rejected at config load: it would not disable the
123    /// limit, it would block every request.
124    #[serde(default = "default_grpc_concurrency_limit_per_core")]
125    pub grpc_concurrency_limit_per_core: NonZeroUsize,
126
127    /// Configuration struct for P2P.
128    #[serde(default)]
129    pub p2p_config: P2pConfig,
130
131    /// Contains genesis location that might be `InPlace`
132    /// for reading all genesis data to memory or `InFile`,
133    /// and `OnceCell` pointer to a genesis struct.
134    pub genesis: Genesis,
135
136    /// Contains the path where to find the migration blob.
137    pub migration_tx_data_path: Option<PathBuf>,
138
139    /// Configuration for pruning of the authority store, to define when
140    /// an old data is removed from the storage space.
141    #[serde(default = "default_authority_store_pruning_config")]
142    pub authority_store_pruning_config: AuthorityStorePruningConfig,
143
144    /// Size of the broadcast channel used for notifying other systems of end of
145    /// epoch.
146    ///
147    /// If unspecified, this will default to `128`.
148    #[serde(default = "default_end_of_epoch_broadcast_channel_capacity")]
149    pub end_of_epoch_broadcast_channel_capacity: usize,
150
151    /// Configuration for the checkpoint executor for limiting
152    /// the number of checkpoints to execute concurrently,
153    /// and to allow for checkpoint post-processing.
154    #[serde(default)]
155    pub checkpoint_executor_config: CheckpointExecutorConfig,
156
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub metrics: Option<MetricsConfig>,
159
160    /// In a `iota-node` binary, this is set to
161    /// SupportedProtocolVersions::SYSTEM_DEFAULT in iota-node/src/main.rs.
162    /// It is present in the config so that it can be changed by tests in
163    /// order to test protocol upgrades.
164    #[serde(skip)]
165    pub supported_protocol_versions: Option<SupportedProtocolVersions>,
166
167    /// Configuration for enabling/disabling expensive safety checks.
168    #[serde(default)]
169    pub expensive_safety_check_config: ExpensiveSafetyCheckConfig,
170
171    /// Configuration to specify rules for denying transactions
172    /// based on `objectsIDs`, `addresses`, or enable/disable many
173    /// features such as publishing new packages or using shared objects.
174    #[serde(default)]
175    pub transaction_deny_config: TransactionDenyConfig,
176
177    /// Config used to deny execution for certificate digests
178    /// know for crashing or hanging validator nodes.
179    ///
180    /// Should be used for a fast temporary fixes and
181    /// removed once the issue is fixed.
182    #[serde(default)]
183    pub certificate_deny_config: CertificateDenyConfig,
184
185    /// Used to determine how state debug information is dumped
186    /// when a node forks.
187    #[serde(default)]
188    pub state_debug_dump_config: StateDebugDumpConfig,
189
190    #[serde(default)]
191    pub checkpoint_archive_config: Option<CheckpointArchiveConfig>,
192
193    /// Determines if snapshot should be uploaded to the remote storage.
194    #[serde(default)]
195    pub state_snapshot_write_config: StateSnapshotConfig,
196
197    #[serde(default = "default_transaction_kv_store_config")]
198    pub transaction_kv_store_read_config: TransactionKeyValueStoreReadConfig,
199
200    // TODO: write config seem to be unused.
201    #[serde(skip_serializing_if = "Option::is_none")]
202    pub transaction_kv_store_write_config: Option<TransactionKeyValueStoreWriteConfig>,
203
204    /// Configuration for defining thresholds and settings
205    /// for managing system overload conditions in a node.
206    #[serde(default = "default_authority_overload_config")]
207    pub authority_overload_config: AuthorityOverloadConfig,
208
209    /// Specifies the ending epoch for a node for debugging purposes.
210    ///
211    ///  Ignored if set by config, can be configured only by cli arguments.
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub run_with_range: Option<RunWithRange>,
214
215    /// Traffic control policy. For killswitch use None.
216    ///
217    /// With the key absent the default denial-of-service protection policy
218    /// applies, an explicit `null` turns traffic control off, and a value
219    /// configures it.
220    #[serde(
221        skip_serializing_if = "is_default_traffic_controller_policy_config",
222        default = "default_traffic_controller_policy_config"
223    )]
224    pub policy_config: Option<PolicyConfig>,
225
226    #[serde(default)]
227    pub execution_cache_config: ExecutionCacheConfig,
228
229    /// Memory budget in MiB for the in-memory cache of full checkpoint
230    /// contents, which serves the checkpoint executor's bulk transaction
231    /// loads and checkpoint-contents requests from state-sync peers. When
232    /// the budget is exceeded, the oldest checkpoints are evicted first.
233    /// Set to 0 to disable the cache; consumers then fall back to
234    /// reconstructing contents from the transaction and effects stores.
235    ///
236    /// The budget is accounted in serialized (BCS) bytes; the resident
237    /// memory of a full cache is somewhat higher than the configured value
238    /// due to in-memory representation overhead.
239    #[serde(default = "default_full_checkpoint_contents_cache_size_mb")]
240    pub full_checkpoint_contents_cache_size_mb: usize,
241
242    #[serde(default = "bool_true")]
243    pub enable_validator_tx_finalizer: bool,
244
245    /// Enables the pre-consensus soft-locking mechanism used by the
246    /// certificate-less (pcool) transaction flow (default: enabled).
247    ///
248    /// When disabled, post-consensus validation alone resolves owned-object
249    /// conflicts. Has no effect unless the pcool flow is enabled.
250    #[serde(default = "bool_true")]
251    pub enable_soft_locking: bool,
252
253    #[serde(default)]
254    pub verifier_signing_config: VerifierSigningConfig,
255
256    /// If a value is set, it determines if writes to DB can stall, which can
257    /// halt the whole process. By default, write stall is enabled on
258    /// validators but not on fullnodes.
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub enable_db_write_stall: Option<bool>,
261
262    #[serde(default, skip_serializing_if = "Option::is_none")]
263    pub iota_names_config: Option<IotaNamesConfig>,
264
265    /// Flag to enable the gRPC API.
266    #[serde(default)]
267    pub enable_grpc_api: bool,
268    /// Configuration of the gRPC API, read when `enable_grpc_api` is set.
269    ///
270    /// With the key absent the default configuration applies, an explicit
271    /// `null` leaves the API unconfigured — a node with `enable-grpc-api` set
272    /// then fails to start — and a value configures it.
273    #[serde(
274        default = "default_grpc_api_config",
275        skip_serializing_if = "is_default_grpc_api_config"
276    )]
277    pub grpc_api_config: Option<GrpcApiConfig>,
278
279    /// Allow overriding the chain for testing purposes. For instance, it allows
280    /// you to create a test network that believes it is mainnet or testnet.
281    /// Attempting to override this value on production networks will result
282    /// in an error.
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub chain_override_for_testing: Option<Chain>,
285
286    /// Configuration for the validator client monitor that tracks
287    /// client-observed performance metrics for validators.
288    #[serde(default, skip_serializing_if = "Option::is_none")]
289    pub validator_client_monitor_config:
290        Option<crate::validator_client_monitor_config::ValidatorClientMonitorConfig>,
291}
292
293#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
294#[serde(rename_all = "kebab-case")]
295pub struct TlsConfig {
296    /// File path to a PEM formatted TLS certificate chain
297    cert: String,
298    /// File path to a PEM formatted TLS private key
299    key: String,
300}
301
302impl TlsConfig {
303    pub fn cert(&self) -> &str {
304        &self.cert
305    }
306
307    pub fn key(&self) -> &str {
308        &self.key
309    }
310}
311
312/// Configuration for the gRPC API service
313#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
314#[serde(rename_all = "kebab-case")]
315pub struct GrpcApiConfig {
316    /// The address to bind the gRPC server to
317    #[serde(default = "default_grpc_api_address")]
318    pub address: SocketAddr,
319
320    /// TLS configuration for the gRPC server.
321    ///
322    /// If not provided, the gRPC server will use plain TCP without TLS.
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub tls: Option<TlsConfig>,
325
326    /// Maximum message size for gRPC responses (in bytes)
327    #[serde(default = "default_grpc_api_max_message_size_bytes")]
328    pub max_message_size_bytes: u32,
329
330    /// Buffer size for broadcast channels used for streaming
331    #[serde(default = "default_grpc_api_broadcast_buffer_size")]
332    pub broadcast_buffer_size: u32,
333
334    /// Maximum number of concurrent subscribers to checkpoint streaming RPCs.
335    /// Once the cap is reached, additional subscribe requests are rejected
336    /// with `Unavailable` to protect the server from being overwhelmed by
337    /// unbounded streaming clients. Values below 1 are clamped to 1 at
338    /// server startup.
339    #[serde(default = "default_grpc_api_max_concurrent_stream_subscribers")]
340    pub max_concurrent_stream_subscribers: u32,
341
342    /// Maximum size for Move values when rendering to JSON
343    /// in bytes.
344    #[serde(default = "default_grpc_api_max_json_move_value_size")]
345    pub max_json_move_value_size: usize,
346
347    /// Maximum number of transactions allowed in a single ExecuteTransactions
348    /// batch request.
349    #[serde(default = "default_grpc_api_max_execute_transaction_batch_size")]
350    pub max_execute_transaction_batch_size: u32,
351
352    /// Maximum number of transactions allowed in a single SimulateTransactions
353    /// batch request.
354    #[serde(default = "default_grpc_api_max_simulate_transaction_batch_size")]
355    pub max_simulate_transaction_batch_size: u32,
356
357    /// Maximum number of objects allowed in a single GetObjects batch request.
358    #[serde(default = "default_grpc_api_max_get_objects_batch_size")]
359    pub max_get_objects_batch_size: u32,
360
361    /// Maximum number of transactions allowed in a single GetTransactions batch
362    /// request.
363    #[serde(default = "default_grpc_api_max_get_transactions_batch_size")]
364    pub max_get_transactions_batch_size: u32,
365
366    /// Maximum number of view function calls allowed in a single
367    /// ViewFunctionCalls batch request.
368    #[serde(default = "default_grpc_api_max_view_function_call_batch_size")]
369    pub max_view_function_call_batch_size: u32,
370
371    /// Maximum allowed timeout in milliseconds for waiting for checkpoint
372    /// inclusion in ExecuteTransactions requests. Client-specified timeouts
373    /// are clamped to this value.
374    #[serde(default = "default_grpc_api_max_checkpoint_inclusion_timeout_ms")]
375    pub max_checkpoint_inclusion_timeout_ms: u64,
376}
377
378fn default_grpc_api_address() -> SocketAddr {
379    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 50051)
380}
381
382fn default_grpc_api_broadcast_buffer_size() -> u32 {
383    100
384}
385
386fn default_grpc_api_max_concurrent_stream_subscribers() -> u32 {
387    1024
388}
389
390fn default_grpc_api_max_message_size_bytes() -> u32 {
391    128 * 1024 * 1024 // 128MB
392}
393
394fn default_grpc_api_max_json_move_value_size() -> usize {
395    1024 * 1024 // 1 MB
396}
397
398fn default_grpc_api_max_execute_transaction_batch_size() -> u32 {
399    20
400}
401
402fn default_grpc_api_max_simulate_transaction_batch_size() -> u32 {
403    20
404}
405
406fn default_grpc_api_max_get_objects_batch_size() -> u32 {
407    1000
408}
409
410fn default_grpc_api_max_get_transactions_batch_size() -> u32 {
411    1000
412}
413
414fn default_grpc_api_max_view_function_call_batch_size() -> u32 {
415    20
416}
417
418fn default_grpc_api_max_checkpoint_inclusion_timeout_ms() -> u64 {
419    60_000 // 60 seconds
420}
421
422impl Default for GrpcApiConfig {
423    fn default() -> Self {
424        Self {
425            address: default_grpc_api_address(),
426            tls: None,
427            max_message_size_bytes: default_grpc_api_max_message_size_bytes(),
428            broadcast_buffer_size: default_grpc_api_broadcast_buffer_size(),
429            max_concurrent_stream_subscribers: default_grpc_api_max_concurrent_stream_subscribers(),
430            max_json_move_value_size: default_grpc_api_max_json_move_value_size(),
431            max_execute_transaction_batch_size: default_grpc_api_max_execute_transaction_batch_size(
432            ),
433            max_simulate_transaction_batch_size:
434                default_grpc_api_max_simulate_transaction_batch_size(),
435            max_get_objects_batch_size: default_grpc_api_max_get_objects_batch_size(),
436            max_get_transactions_batch_size: default_grpc_api_max_get_transactions_batch_size(),
437            max_view_function_call_batch_size: default_grpc_api_max_view_function_call_batch_size(),
438            max_checkpoint_inclusion_timeout_ms:
439                default_grpc_api_max_checkpoint_inclusion_timeout_ms(),
440        }
441    }
442}
443
444impl GrpcApiConfig {
445    // The default maximum uncompressed size in bytes for a message, based on
446    // tonic's default.
447    const GRPC_TONIC_DEFAULT_MAX_RECV_MESSAGE_SIZE: u32 = 4 * 1024 * 1024; // 4MB
448    const GRPC_MIN_CLIENT_MAX_MESSAGE_SIZE_BYTES: u32 =
449        Self::GRPC_TONIC_DEFAULT_MAX_RECV_MESSAGE_SIZE;
450
451    pub fn tls_config(&self) -> Option<&TlsConfig> {
452        self.tls.as_ref()
453    }
454
455    pub fn max_message_size_bytes(&self) -> u32 {
456        // Ensure max message size is at least the minimum allowed
457        self.max_message_size_bytes
458            .max(Self::GRPC_MIN_CLIENT_MAX_MESSAGE_SIZE_BYTES)
459    }
460
461    /// Calculate the maximum size for a message that can be
462    /// sent to a client, taking into account the client's max message size
463    /// preference.
464    pub fn max_message_size_client_bytes(&self, client_max_message_size_bytes: Option<u32>) -> u32 {
465        client_max_message_size_bytes
466            // if the client did not specify a max message size, use the tonic default receive
467            // message size
468            .unwrap_or(Self::GRPC_TONIC_DEFAULT_MAX_RECV_MESSAGE_SIZE)
469            // clamp the value between the tonic default and the service max message size
470            .clamp(
471                Self::GRPC_MIN_CLIENT_MAX_MESSAGE_SIZE_BYTES,
472                self.max_message_size_bytes(),
473            )
474    }
475}
476
477#[derive(Clone, Debug, Default, Deserialize, Serialize)]
478#[serde(rename_all = "kebab-case")]
479pub struct ExecutionCacheConfig {
480    #[serde(default)]
481    pub writeback_cache: WritebackCacheConfig,
482}
483
484#[derive(Clone, Debug, Default, Deserialize, Serialize)]
485#[serde(rename_all = "kebab-case")]
486pub struct WritebackCacheConfig {
487    /// Maximum number of entries in each cache. (There are several
488    /// different caches).
489    #[serde(default, skip_serializing_if = "Option::is_none")]
490    pub max_cache_size: Option<u64>, // defaults to 100000
491
492    #[serde(default, skip_serializing_if = "Option::is_none")]
493    pub package_cache_size: Option<u64>, // defaults to 1000
494
495    #[serde(default, skip_serializing_if = "Option::is_none")]
496    pub object_cache_size: Option<u64>, // defaults to max_cache_size
497    #[serde(default, skip_serializing_if = "Option::is_none")]
498    pub marker_cache_size: Option<u64>, // defaults to object_cache_size
499    #[serde(default, skip_serializing_if = "Option::is_none")]
500    pub object_by_id_cache_size: Option<u64>, // defaults to object_cache_size
501
502    #[serde(default, skip_serializing_if = "Option::is_none")]
503    pub transaction_cache_size: Option<u64>, // defaults to max_cache_size
504    #[serde(default, skip_serializing_if = "Option::is_none")]
505    pub executed_effect_cache_size: Option<u64>, // defaults to transaction_cache_size
506    #[serde(default, skip_serializing_if = "Option::is_none")]
507    pub effect_cache_size: Option<u64>, // defaults to executed_effect_cache_size
508
509    #[serde(default, skip_serializing_if = "Option::is_none")]
510    pub events_cache_size: Option<u64>, // defaults to transaction_cache_size
511
512    #[serde(default, skip_serializing_if = "Option::is_none")]
513    pub transaction_objects_cache_size: Option<u64>, // defaults to 1000
514
515    /// Number of uncommitted transactions at which to pause consensus
516    /// handler.
517    #[serde(default, skip_serializing_if = "Option::is_none")]
518    pub backpressure_threshold: Option<u64>, // defaults to 100_000
519
520    /// Number of uncommitted transactions at which to refuse new
521    /// transaction submissions. Defaults to backpressure_threshold
522    /// if unset.
523    #[serde(default, skip_serializing_if = "Option::is_none")]
524    pub backpressure_threshold_for_rpc: Option<u64>, // defaults to backpressure_threshold
525
526    /// Percentage of `backpressure_threshold` at which graduated load shedding
527    /// based on writeback-cache pending transaction count begins. The
528    /// locally-calculated shedding percentage increases linearly from 0% at
529    /// `backpressure_threshold * backpressure_soft_limit_pct / 100` up to
530    /// 100% at the `backpressure_threshold` if the cache size continues to
531    /// increase. The calculated shedding percentage is broadcast to other
532    /// validators for a coordinated response. Defaults to 50.
533    #[serde(default, skip_serializing_if = "Option::is_none")]
534    pub backpressure_soft_limit_pct: Option<u32>,
535}
536
537impl WritebackCacheConfig {
538    pub fn max_cache_size(&self) -> u64 {
539        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_MAX")
540            .ok()
541            .and_then(|s| s.parse().ok())
542            .or(self.max_cache_size)
543            .unwrap_or(100000)
544    }
545
546    pub fn package_cache_size(&self) -> u64 {
547        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_PACKAGE")
548            .ok()
549            .and_then(|s| s.parse().ok())
550            .or(self.package_cache_size)
551            .unwrap_or(1000)
552    }
553
554    pub fn object_cache_size(&self) -> u64 {
555        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_OBJECT")
556            .ok()
557            .and_then(|s| s.parse().ok())
558            .or(self.object_cache_size)
559            .unwrap_or_else(|| self.max_cache_size())
560    }
561
562    pub fn marker_cache_size(&self) -> u64 {
563        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_MARKER")
564            .ok()
565            .and_then(|s| s.parse().ok())
566            .or(self.marker_cache_size)
567            .unwrap_or_else(|| self.object_cache_size())
568    }
569
570    pub fn object_by_id_cache_size(&self) -> u64 {
571        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_OBJECT_BY_ID")
572            .ok()
573            .and_then(|s| s.parse().ok())
574            .or(self.object_by_id_cache_size)
575            .unwrap_or_else(|| self.object_cache_size())
576    }
577
578    pub fn transaction_cache_size(&self) -> u64 {
579        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_TRANSACTION")
580            .ok()
581            .and_then(|s| s.parse().ok())
582            .or(self.transaction_cache_size)
583            .unwrap_or_else(|| self.max_cache_size())
584    }
585
586    pub fn executed_effect_cache_size(&self) -> u64 {
587        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_EXECUTED_EFFECT")
588            .ok()
589            .and_then(|s| s.parse().ok())
590            .or(self.executed_effect_cache_size)
591            .unwrap_or_else(|| self.transaction_cache_size())
592    }
593
594    pub fn effect_cache_size(&self) -> u64 {
595        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_EFFECT")
596            .ok()
597            .and_then(|s| s.parse().ok())
598            .or(self.effect_cache_size)
599            .unwrap_or_else(|| self.executed_effect_cache_size())
600    }
601
602    pub fn events_cache_size(&self) -> u64 {
603        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_EVENTS")
604            .ok()
605            .and_then(|s| s.parse().ok())
606            .or(self.events_cache_size)
607            .unwrap_or_else(|| self.transaction_cache_size())
608    }
609
610    pub fn transaction_objects_cache_size(&self) -> u64 {
611        std::env::var("IOTA_CACHE_WRITEBACK_SIZE_TRANSACTION_OBJECTS")
612            .ok()
613            .and_then(|s| s.parse().ok())
614            .or(self.transaction_objects_cache_size)
615            .unwrap_or(1000)
616    }
617
618    pub fn backpressure_threshold(&self) -> u64 {
619        std::env::var("IOTA_CACHE_WRITEBACK_BACKPRESSURE_THRESHOLD")
620            .ok()
621            .and_then(|s| s.parse().ok())
622            .or(self.backpressure_threshold)
623            .unwrap_or(100_000)
624    }
625
626    pub fn backpressure_threshold_for_rpc(&self) -> u64 {
627        std::env::var("IOTA_CACHE_WRITEBACK_BACKPRESSURE_THRESHOLD_FOR_RPC")
628            .ok()
629            .and_then(|s| s.parse().ok())
630            .or(self.backpressure_threshold_for_rpc)
631            .unwrap_or(self.backpressure_threshold())
632    }
633
634    pub fn backpressure_soft_limit_pct(&self) -> u32 {
635        std::env::var("IOTA_CACHE_WRITEBACK_BACKPRESSURE_SOFT_LIMIT_PCT")
636            .ok()
637            .and_then(|s| s.parse().ok())
638            .or(self.backpressure_soft_limit_pct)
639            .unwrap_or(50)
640            .min(100)
641    }
642}
643
644#[derive(Clone, Debug, Deserialize, Serialize)]
645#[serde(rename_all = "kebab-case")]
646pub struct TransactionKeyValueStoreReadConfig {
647    #[serde(default = "default_base_url")]
648    pub base_url: String,
649
650    #[serde(default = "default_cache_size")]
651    pub cache_size: u64,
652}
653
654impl Default for TransactionKeyValueStoreReadConfig {
655    fn default() -> Self {
656        Self {
657            base_url: default_base_url(),
658            cache_size: default_cache_size(),
659        }
660    }
661}
662
663fn default_base_url() -> String {
664    "".to_string()
665}
666
667fn default_cache_size() -> u64 {
668    100_000
669}
670
671fn default_transaction_kv_store_config() -> TransactionKeyValueStoreReadConfig {
672    TransactionKeyValueStoreReadConfig::default()
673}
674
675fn default_authority_store_pruning_config() -> AuthorityStorePruningConfig {
676    AuthorityStorePruningConfig::default()
677}
678
679pub fn default_enable_jsonrpc_api() -> bool {
680    true
681}
682
683fn default_grpc_address() -> Multiaddr {
684    "/ip4/0.0.0.0/tcp/8080".parse().unwrap()
685}
686fn default_authority_key_pair() -> AuthorityKeyPairWithPath {
687    AuthorityKeyPairWithPath::new(
688        get_key_pair_from_rng::<AuthorityKeyPair, _>(&mut UnwrapErr(SysRng)).1,
689    )
690}
691
692fn default_key_pair() -> KeyPairWithPath {
693    KeyPairWithPath::new(AccountPrivateKey::random().into())
694}
695
696fn default_metrics_address() -> SocketAddr {
697    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9184)
698}
699
700pub fn default_admin_interface_address() -> SocketAddr {
701    SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 1337)
702}
703
704pub fn default_json_rpc_address() -> SocketAddr {
705    SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9000)
706}
707
708pub fn default_grpc_api_config() -> Option<GrpcApiConfig> {
709    Some(GrpcApiConfig::default())
710}
711
712fn is_default_grpc_api_config(grpc_api_config: &Option<GrpcApiConfig>) -> bool {
713    serializes_like(grpc_api_config, &default_grpc_api_config())
714}
715
716/// Returns whether `value` and `default` serialize to the same YAML.
717///
718/// Meant for `skip_serializing_if` predicates, which cannot report an error: a
719/// value that fails to serialize is reported as unlike the default, so the
720/// field is kept and the failure surfaces when serializing the field itself.
721fn serializes_like<T: Serialize>(value: &T, default: &T) -> bool {
722    match (serde_yaml::to_string(value), serde_yaml::to_string(default)) {
723        (Ok(value), Ok(default)) => value == default,
724        _ => false,
725    }
726}
727
728pub fn default_grpc_concurrency_limit_per_core() -> NonZeroUsize {
729    NonZeroUsize::new(1000).unwrap()
730}
731
732pub fn default_end_of_epoch_broadcast_channel_capacity() -> usize {
733    128
734}
735
736pub fn default_full_checkpoint_contents_cache_size_mb() -> usize {
737    DEFAULT_FULL_CHECKPOINT_CONTENTS_CACHE_SIZE_MB
738}
739
740pub fn bool_true() -> bool {
741    true
742}
743
744impl Config for NodeConfig {}
745
746impl NodeConfig {
747    /// Fails if the config file still sets a key that has been renamed, unless
748    /// the new key already does what the old value asked for; that case only
749    /// logs a warning. Call this before doing any work.
750    ///
751    /// `enable-index-processing` is accepted on a validator, which never built
752    /// the JSON-RPC index whatever its value, and as `true` while
753    /// `enable-jsonrpc-api` is on. `false` on a full node is refused: it served
754    /// the JSON-RPC API without its index, which `enable-jsonrpc-api` cannot
755    /// express.
756    pub fn check_renamed_keys(&self) -> Result<()> {
757        let Some(enable_index_processing) = self.enable_index_processing else {
758            return Ok(());
759        };
760        if self.is_validator() || (enable_index_processing && self.enable_jsonrpc_api) {
761            tracing::warn!(
762                "`enable-index-processing` was renamed to `enable-jsonrpc-api` and has no \
763                 effect; remove it from the config"
764            );
765            return Ok(());
766        }
767        anyhow::bail!(
768            "`enable-index-processing` was renamed to `enable-jsonrpc-api` (default true), which \
769             turns the JSON-RPC API and its index on or off together; remove the old key and \
770             set `enable-jsonrpc-api` to the value you want."
771        );
772    }
773
774    pub fn authority_key_pair(&self) -> &AuthorityKeyPair {
775        self.authority_key_pair.authority_keypair()
776    }
777
778    pub fn protocol_key_pair(&self) -> NetworkKeyPair {
779        self.protocol_key_pair.network_keypair()
780    }
781
782    pub fn network_key_pair(&self) -> NetworkKeyPair {
783        self.network_key_pair.network_keypair()
784    }
785
786    pub fn authority_public_key(&self) -> AuthorityPublicKeyBytes {
787        self.authority_key_pair().public().into()
788    }
789
790    pub fn db_path(&self) -> PathBuf {
791        self.db_path.join("live")
792    }
793
794    pub fn snapshot_path(&self) -> PathBuf {
795        self.db_path.join("snapshot")
796    }
797
798    pub fn network_address(&self) -> &Multiaddr {
799        &self.network_address
800    }
801
802    pub fn consensus_config(&self) -> Option<&ConsensusConfig> {
803        self.consensus_config.as_ref()
804    }
805
806    /// Whether this node builds the RPC indexes: only a fullnode with the
807    /// JSON-RPC or gRPC API enabled does.
808    pub fn maintains_rpc_indexes(&self) -> bool {
809        self.consensus_config().is_none() && (self.enable_jsonrpc_api || self.enable_grpc_api)
810    }
811
812    pub fn genesis(&self) -> Result<&genesis::Genesis> {
813        self.genesis.genesis()
814    }
815
816    pub fn load_migration_tx_data(&self) -> Result<MigrationTxData> {
817        let Some(location) = &self.migration_tx_data_path else {
818            anyhow::bail!("no file location set");
819        };
820
821        // Load from file
822        let migration_tx_data = MigrationTxData::load(location)?;
823
824        // Validate migration content in order to avoid corrupted or malicious data
825        migration_tx_data.validate_from_genesis(self.genesis.genesis()?)?;
826        Ok(migration_tx_data)
827    }
828
829    pub fn iota_address(&self) -> Address {
830        self.account_key_pair
831            .keypair()
832            .public_key()
833            .derive_address()
834    }
835
836    pub fn checkpoint_archive_config(&self) -> Option<&CheckpointArchiveConfig> {
837        self.checkpoint_archive_config.as_ref()
838    }
839
840    /// A config selects the validator role exactly when it has a consensus
841    /// config.
842    pub fn is_validator(&self) -> bool {
843        self.consensus_config.is_some()
844    }
845
846    /// Validate the node config. The checks reject a config a node could
847    /// not start with. They also reject the known cases where a node would
848    /// start and ignore part of the config.
849    pub fn validate(&self) -> Result<()> {
850        // The settings a validator must not carry: it never starts the gRPC
851        // server, and snapshot publication is a fullnode role.
852        if self.is_validator() {
853            if self.enable_grpc_api {
854                anyhow::bail!(
855                    "`enable-grpc-api` is set, but validators do not expose the gRPC API; turn \
856                     it off, or move the API to a fullnode"
857                );
858            }
859            if self
860                .state_snapshot_write_config
861                .object_store_config
862                .is_some()
863            {
864                anyhow::bail!(
865                    "`state-snapshot-write-config.object-store-config` is set, but snapshot \
866                     upload is only supported on fullnodes; remove the setting or move the \
867                     upload to a fullnode"
868                );
869            }
870        }
871        // In a file, an absent key deserializes to the default config, and a
872        // config built in code carries it too. `None` here therefore means an
873        // explicit `null`.
874        if self.enable_grpc_api && self.grpc_api_config.is_none() {
875            anyhow::bail!(
876                "`enable-grpc-api` is set but `grpc-api-config` is `null`; give it a value, \
877                 remove the key to take the default config, or turn off `enable-grpc-api`"
878            );
879        }
880        // The uploader builds the store at startup, which needs a backend.
881        if self
882            .state_snapshot_write_config
883            .object_store_config
884            .as_ref()
885            .is_some_and(|store| store.object_store.is_none())
886        {
887            anyhow::bail!(
888                "`state-snapshot-write-config.object-store-config` has no `object-store`; \
889                 snapshot upload needs a storage backend"
890            );
891        }
892        // Right after an epoch boundary, peers still sync the end of the
893        // previous epoch, and a validator still decides whether a transaction
894        // re-proposed from it was already executed, from its checkpoints.
895        if self
896            .authority_store_pruning_config
897            .num_epochs_to_retain_for_checkpoints()
898            == Some(0)
899        {
900            anyhow::bail!(
901                "`num-epochs-to-retain-for-checkpoints` is 0, but the previous epoch's \
902                 checkpoints are still needed after an epoch boundary; set it to 1 or more"
903            );
904        }
905        Ok(())
906    }
907}
908
909#[derive(Debug, Clone, Deserialize, Serialize)]
910#[serde(rename_all = "kebab-case")]
911pub struct ConsensusConfig {
912    /// Base consensus DB path for all epochs.
913    pub db_path: PathBuf,
914
915    /// The number of epochs for which to retain the consensus DBs.
916    /// Setting it to 0 will make a consensus DB getting dropped
917    /// as soon as system is switched to a new epoch.
918    pub db_retention_epochs: Option<u64>,
919
920    /// Pruner will run on every epoch change but it will also check
921    /// periodically on every `db_pruner_period_secs` seconds to see
922    /// if there are any epoch DBs to remove.
923    pub db_pruner_period_secs: Option<u64>,
924
925    /// Hard limit on the number of pending transactions to submit to
926    /// consensus, including those in submission wait. Used as the upper
927    /// bound for graduated pre-consensus load shedding
928    /// (`graduated_load_shedding_soft_limit_pct`) in the certificate-less
929    /// (P-COOL) mode, and as the threshold for the binary
930    /// cutoff in `ConsensusAdapter::check_consensus_overload()` in both
931    /// certificate-less and certificate-based flows.
932    ///
933    /// Default to 20_000 inflight limit, assuming 20_000 txn tps * 1 sec
934    /// consensus latency.
935    pub max_pending_transactions: Option<usize>,
936
937    /// When defined caps the calculated submission position to the
938    /// max_submit_position.
939    ///
940    /// Even if the is elected to submit from a higher
941    /// position than this, it will "reset" to the max_submit_position.
942    pub max_submit_position: Option<usize>,
943
944    /// The submit delay step to consensus defined in milliseconds.
945    ///
946    /// When provided it will override the current back off logic otherwise the
947    /// default backoff logic will be applied based on consensus latency
948    /// estimates.
949    pub submit_delay_step_override_millis: Option<u64>,
950
951    /// Parameters for Starfish consensus
952    #[serde(skip_serializing_if = "Option::is_none", alias = "starfish_parameters")]
953    pub parameters: Option<StarfishParameters>,
954
955    /// Percentage of `max_pending_transactions` (hard limit) defining the soft
956    /// limit at which graduated pre-consensus load shedding begins. When
957    /// in-flight transactions are at or below the soft limit, no shedding
958    /// occurs; above it, the shedding rate scales linearly from 0% to 100% at
959    /// `max_pending_transactions`. Used in the certificate-less (P-COOL) mode.
960    #[serde(skip_serializing_if = "Option::is_none")]
961    pub graduated_load_shedding_soft_limit_pct: Option<u32>,
962}
963
964impl ConsensusConfig {
965    pub fn db_path(&self) -> &Path {
966        &self.db_path
967    }
968
969    /// Returns the hard limit on the number of pending transactions to submit
970    /// to consensus, including those in submission wait. Defaults to 20_000
971    /// inflight limit, assuming 20_000 txn tps * 1 sec consensus latency.
972    pub fn max_pending_transactions(&self) -> usize {
973        self.max_pending_transactions.unwrap_or(20_000)
974    }
975
976    /// Returns the percentage of `max_pending_transactions` (hard limit)
977    /// defining the soft limit at which graduated pre-consensus load
978    /// shedding begins. Defaults to 50%. Used in the certificate-less
979    /// (P-COOL) mode.
980    pub fn graduated_load_shedding_soft_limit_pct(&self) -> u32 {
981        self.graduated_load_shedding_soft_limit_pct
982            .unwrap_or(50)
983            .min(100)
984    }
985
986    pub fn submit_delay_step_override(&self) -> Option<Duration> {
987        self.submit_delay_step_override_millis
988            .map(Duration::from_millis)
989    }
990
991    pub fn db_retention_epochs(&self) -> u64 {
992        self.db_retention_epochs.unwrap_or(0)
993    }
994
995    pub fn db_pruner_period(&self) -> Duration {
996        // Default to 1 hour
997        self.db_pruner_period_secs
998            .map(Duration::from_secs)
999            .unwrap_or(Duration::from_secs(3_600))
1000    }
1001}
1002
1003#[derive(Clone, Debug, Deserialize, Serialize)]
1004#[serde(rename_all = "kebab-case")]
1005pub struct CheckpointExecutorConfig {
1006    /// Upper bound on the number of checkpoints that can be concurrently
1007    /// executed.
1008    ///
1009    /// If unspecified, this will default to `200`
1010    #[serde(default = "default_checkpoint_execution_max_concurrency")]
1011    pub checkpoint_execution_max_concurrency: usize,
1012
1013    /// Number of seconds to wait for effects of a batch of transactions
1014    /// before logging a warning. Note that we will continue to retry
1015    /// indefinitely.
1016    ///
1017    /// If unspecified, this will default to `10`.
1018    #[serde(default = "default_local_execution_timeout_sec")]
1019    pub local_execution_timeout_sec: u64,
1020
1021    /// Optional directory used for data ingestion pipeline.
1022    ///
1023    /// When specified, each executed checkpoint will be saved in a local
1024    /// directory for post-processing
1025    #[serde(default, skip_serializing_if = "Option::is_none")]
1026    pub data_ingestion_dir: Option<PathBuf>,
1027}
1028
1029#[derive(Clone, Debug, Default, Deserialize, Serialize)]
1030#[serde(rename_all = "kebab-case")]
1031pub struct ExpensiveSafetyCheckConfig {
1032    /// If enabled, at epoch boundary, we will check that the storage
1033    /// fund balance is always identical to the sum of the storage
1034    /// rebate of all live objects, and that the total IOTA in the network
1035    /// remains the same.
1036    #[serde(default)]
1037    enable_epoch_iota_conservation_check: bool,
1038
1039    /// If enabled, we will check that the total IOTA in all input objects of a
1040    /// tx (both the Move part and the storage rebate) matches the total IOTA
1041    /// in all output objects of the tx + gas fees.
1042    #[serde(default)]
1043    enable_deep_per_tx_iota_conservation_check: bool,
1044
1045    /// Disable epoch IOTA conservation check even when we are running in debug
1046    /// mode.
1047    #[serde(default)]
1048    force_disable_epoch_iota_conservation_check: bool,
1049
1050    /// If enabled, at epoch boundary, we will check that the accumulated
1051    /// live object state matches the end of epoch root state digest.
1052    #[serde(default)]
1053    enable_state_consistency_check: bool,
1054
1055    /// Disable state consistency check even when we are running in debug mode.
1056    #[serde(default)]
1057    force_disable_state_consistency_check: bool,
1058}
1059
1060impl ExpensiveSafetyCheckConfig {
1061    pub fn new_enable_all() -> Self {
1062        Self {
1063            enable_epoch_iota_conservation_check: true,
1064            enable_deep_per_tx_iota_conservation_check: true,
1065            force_disable_epoch_iota_conservation_check: false,
1066            enable_state_consistency_check: true,
1067            force_disable_state_consistency_check: false,
1068        }
1069    }
1070
1071    pub fn new_disable_all() -> Self {
1072        Self {
1073            enable_epoch_iota_conservation_check: false,
1074            enable_deep_per_tx_iota_conservation_check: false,
1075            force_disable_epoch_iota_conservation_check: true,
1076            enable_state_consistency_check: false,
1077            force_disable_state_consistency_check: true,
1078        }
1079    }
1080
1081    pub fn force_disable_epoch_iota_conservation_check(&mut self) {
1082        self.force_disable_epoch_iota_conservation_check = true;
1083    }
1084
1085    pub fn enable_epoch_iota_conservation_check(&self) -> bool {
1086        (self.enable_epoch_iota_conservation_check || cfg!(debug_assertions))
1087            && !self.force_disable_epoch_iota_conservation_check
1088    }
1089
1090    pub fn force_disable_state_consistency_check(&mut self) {
1091        self.force_disable_state_consistency_check = true;
1092    }
1093
1094    pub fn enable_state_consistency_check(&self) -> bool {
1095        (self.enable_state_consistency_check || cfg!(debug_assertions))
1096            && !self.force_disable_state_consistency_check
1097    }
1098
1099    pub fn enable_deep_per_tx_iota_conservation_check(&self) -> bool {
1100        self.enable_deep_per_tx_iota_conservation_check || cfg!(debug_assertions)
1101    }
1102}
1103
1104fn default_checkpoint_execution_max_concurrency() -> usize {
1105    4
1106}
1107
1108fn default_local_execution_timeout_sec() -> u64 {
1109    30
1110}
1111
1112impl Default for CheckpointExecutorConfig {
1113    fn default() -> Self {
1114        Self {
1115            checkpoint_execution_max_concurrency: default_checkpoint_execution_max_concurrency(),
1116            local_execution_timeout_sec: default_local_execution_timeout_sec(),
1117            data_ingestion_dir: None,
1118        }
1119    }
1120}
1121
1122#[derive(Debug, Clone, Deserialize, Serialize)]
1123#[serde(rename_all = "kebab-case")]
1124pub struct AuthorityStorePruningConfig {
1125    /// number of the latest epoch dbs to retain
1126    #[serde(default = "default_num_latest_epoch_dbs_to_retain")]
1127    pub num_latest_epoch_dbs_to_retain: usize,
1128    /// Number of historic epochs of object versions to keep, on top of the
1129    /// epoch the node is in. Read through [`Self::num_epochs_to_retain`].
1130    ///   0        — keep the epoch the node is in only (default).
1131    ///   N        — keep the epoch the node is in plus the N epochs before it.
1132    ///   u64::MAX — keep every epoch's bucket; object pruning is off.
1133    #[serde(default)]
1134    pub num_epochs_to_retain: u64,
1135    /// enables periodic background compaction for old SST files whose last
1136    /// modified time is older than `periodic_compaction_threshold_days`
1137    /// days. That ensures that all sst files eventually go through the
1138    /// compaction process
1139    ///
1140    /// With the key absent files older than a day are compacted, an explicit
1141    /// `null` turns periodic compaction off, and a value sets the threshold in
1142    /// days.
1143    #[serde(
1144        default = "default_periodic_compaction_threshold_days",
1145        skip_serializing_if = "is_default_periodic_compaction_threshold_days"
1146    )]
1147    pub periodic_compaction_threshold_days: Option<usize>,
1148    /// Number of historic epochs of transactions, effects, events, and
1149    /// checkpoint data to keep, on top of the epoch the node is in. Controls
1150    /// transaction pruning. Read through
1151    /// [`Self::num_epochs_to_retain_for_checkpoints`].
1152    ///   0        — refused by [`NodeConfig::validate`].
1153    ///   N        — keep the epoch the node is in plus the N epochs before it.
1154    ///   u64::MAX — keep every epoch's bucket; transaction pruning is off.
1155    ///   None     — [`DEFAULT_NUM_EPOCHS_TO_RETAIN_FOR_CHECKPOINTS`].
1156    #[serde(skip_serializing_if = "Option::is_none")]
1157    pub num_epochs_to_retain_for_checkpoints: Option<u64>,
1158    /// Number of historic epochs of RPC index history (the transaction and
1159    /// event lookups behind `iotax_queryTransactionBlocks`, `iotax_queryEvents`
1160    /// and their gRPC equivalents) to keep, on top of the epoch the node is
1161    /// in. Index tables for live objects are not affected. Read through
1162    /// [`Self::num_epochs_to_retain_for_indexes`].
1163    ///   0        — keep the epoch the node is in only.
1164    ///   N        — keep the epoch the node is in plus the N epochs before it.
1165    ///   u64::MAX — keep every epoch's bucket; index pruning is off.
1166    ///   None     — follow `num_epochs_to_retain_for_checkpoints`.
1167    ///
1168    /// Must not exceed `num_epochs_to_retain_for_checkpoints`; the node
1169    /// refuses to start otherwise.
1170    #[serde(skip_serializing_if = "Option::is_none")]
1171    pub num_epochs_to_retain_for_indexes: Option<u64>,
1172}
1173
1174/// The historic epochs of transactions, effects, events and checkpoint data
1175/// kept when `num-epochs-to-retain-for-checkpoints` is not set: about a month
1176/// at the networks' one-day epochs.
1177pub const DEFAULT_NUM_EPOCHS_TO_RETAIN_FOR_CHECKPOINTS: u64 = 30;
1178
1179fn default_num_latest_epoch_dbs_to_retain() -> usize {
1180    3
1181}
1182
1183fn default_periodic_compaction_threshold_days() -> Option<usize> {
1184    Some(1)
1185}
1186
1187fn is_default_periodic_compaction_threshold_days(days: &Option<usize>) -> bool {
1188    *days == default_periodic_compaction_threshold_days()
1189}
1190
1191impl Default for AuthorityStorePruningConfig {
1192    fn default() -> Self {
1193        Self {
1194            num_latest_epoch_dbs_to_retain: default_num_latest_epoch_dbs_to_retain(),
1195            num_epochs_to_retain: 0,
1196            periodic_compaction_threshold_days: default_periodic_compaction_threshold_days(),
1197            num_epochs_to_retain_for_checkpoints: if cfg!(msim) { Some(2) } else { None },
1198            num_epochs_to_retain_for_indexes: None,
1199        }
1200    }
1201}
1202
1203impl AuthorityStorePruningConfig {
1204    pub fn set_num_epochs_to_retain(&mut self, num_epochs_to_retain: u64) {
1205        self.num_epochs_to_retain = num_epochs_to_retain;
1206    }
1207
1208    pub fn set_num_epochs_to_retain_for_checkpoints(&mut self, num_epochs_to_retain: Option<u64>) {
1209        self.num_epochs_to_retain_for_checkpoints = num_epochs_to_retain;
1210    }
1211
1212    /// Historic epochs of object versions to keep on top of the epoch the
1213    /// node is in, `None` when object pruning is off.
1214    pub fn num_epochs_to_retain(&self) -> Option<u64> {
1215        (self.num_epochs_to_retain != u64::MAX).then_some(self.num_epochs_to_retain)
1216    }
1217
1218    /// Historic epochs of transactions, effects, events and checkpoint data
1219    /// to keep on top of the epoch the node is in, `None` when transaction
1220    /// pruning is off. Unset is
1221    /// [`DEFAULT_NUM_EPOCHS_TO_RETAIN_FOR_CHECKPOINTS`].
1222    pub fn num_epochs_to_retain_for_checkpoints(&self) -> Option<u64> {
1223        match self.num_epochs_to_retain_for_checkpoints {
1224            Some(u64::MAX) => None,
1225            Some(n) => Some(n),
1226            None => Some(DEFAULT_NUM_EPOCHS_TO_RETAIN_FOR_CHECKPOINTS),
1227        }
1228    }
1229
1230    /// Historic epochs of RPC index history to keep on top of the epoch the
1231    /// node is in, `None` when index pruning is off. Unset follows the
1232    /// transaction retention.
1233    pub fn num_epochs_to_retain_for_indexes(&self) -> Option<u64> {
1234        match self.num_epochs_to_retain_for_indexes {
1235            Some(n) if n == u64::MAX => None,
1236            Some(n) => Some(n),
1237            None => self.num_epochs_to_retain_for_checkpoints(),
1238        }
1239    }
1240
1241    /// Fails if the index retention exceeds the transaction retention, which
1242    /// would let `iotax_queryTransactionBlocks` return digests the node has
1243    /// already pruned.
1244    pub fn check_index_retention_within_ledger(&self) -> Result<()> {
1245        let Some(ledger) = self.num_epochs_to_retain_for_checkpoints() else {
1246            // An unbounded ledger outlives every index retention.
1247            return Ok(());
1248        };
1249        let index = self.num_epochs_to_retain_for_indexes();
1250        if index.is_none_or(|index| index > ledger) {
1251            let index = index.map_or("unlimited".to_owned(), |index| index.to_string());
1252            anyhow::bail!(
1253                "num-epochs-to-retain-for-indexes ({index}) must not exceed \
1254                 num-epochs-to-retain-for-checkpoints ({ledger}): a longer index retention \
1255                 lets iotax_queryTransactionBlocks return a transaction digest whose ledger \
1256                 data has already been pruned. Lower num-epochs-to-retain-for-indexes or \
1257                 raise num-epochs-to-retain-for-checkpoints."
1258            );
1259        }
1260        Ok(())
1261    }
1262}
1263
1264#[derive(Debug, Clone, Deserialize, Serialize)]
1265#[serde(rename_all = "kebab-case")]
1266pub struct MetricsConfig {
1267    #[serde(skip_serializing_if = "Option::is_none")]
1268    pub push_interval_seconds: Option<u64>,
1269    #[serde(skip_serializing_if = "Option::is_none")]
1270    pub push_url: Option<String>,
1271    #[serde(skip_serializing_if = "Option::is_none")]
1272    pub groups: Option<MetricGroups>,
1273}
1274
1275fn default_checkpoint_archive_download_concurrency() -> NonZeroUsize {
1276    NonZeroUsize::new(10).unwrap()
1277}
1278
1279fn default_checkpoint_archive_verify_concurrency() -> NonZeroUsize {
1280    std::thread::available_parallelism().unwrap_or(NonZeroUsize::new(4).unwrap())
1281}
1282
1283/// Configuration for backfilling checkpoint contents from the
1284/// checkpoint archive when peers no longer serve the required range.
1285#[derive(Debug, Clone, Deserialize, Serialize)]
1286#[serde(rename_all = "kebab-case")]
1287pub struct CheckpointArchiveConfig {
1288    /// URL of the checkpoint archive to backfill from.
1289    pub url: String,
1290    /// Non-zero number of checkpoints to download in parallel.
1291    #[serde(default = "default_checkpoint_archive_download_concurrency")]
1292    pub download_concurrency: NonZeroUsize,
1293    /// Non-zero number of downloaded checkpoints to verify in parallel.
1294    /// Defaults to the number of CPU cores.
1295    #[serde(default = "default_checkpoint_archive_verify_concurrency")]
1296    pub verify_concurrency: NonZeroUsize,
1297}
1298
1299/// Configuration for the per-epoch state-snapshot publisher.
1300///
1301/// **Operator note (V2 snapshot publishing).** A node configured to publish
1302/// V2 snapshots must have a perpetual store containing no pre-V2
1303/// (`StoreObjectV1`) rows. This means a snapshot-publishing node must have
1304/// either synced from genesis under V2 or been restored from a V2 snapshot.
1305/// There is no on-disk backfill: a fresh sync is the only supported path.
1306#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1307#[serde(rename_all = "kebab-case")]
1308pub struct StateSnapshotConfig {
1309    #[serde(skip_serializing_if = "Option::is_none")]
1310    pub object_store_config: Option<ObjectStoreConfig>,
1311    pub concurrency: usize,
1312}
1313
1314#[derive(Default, Debug, Clone, Deserialize, Serialize)]
1315#[serde(rename_all = "kebab-case")]
1316pub struct TransactionKeyValueStoreWriteConfig {
1317    pub aws_access_key_id: String,
1318    pub aws_secret_access_key: String,
1319    pub aws_region: String,
1320    pub table_name: String,
1321    pub bucket_name: String,
1322    pub concurrency: usize,
1323}
1324
1325/// Configuration for the threshold(s) at which we consider the system
1326/// to be overloaded. When one of the threshold is passed, the node may
1327/// stop processing new transactions and/or certificates until the congestion
1328/// resolves.
1329#[derive(Clone, Debug, Deserialize, Serialize)]
1330#[serde(rename_all = "kebab-case")]
1331pub struct AuthorityOverloadConfig {
1332    /// Maximum time a transaction can wait in the transaction manager execution
1333    /// queue before it triggers an overload detection on the object it depends
1334    /// on.
1335    #[serde(default = "default_max_txn_age_in_queue")]
1336    pub max_txn_age_in_queue: Duration,
1337
1338    /// The interval of checking overload signal.
1339    #[serde(default = "default_overload_monitor_interval")]
1340    pub overload_monitor_interval: Duration,
1341
1342    /// The execution queueing latency when entering load shedding mode.
1343    #[serde(default = "default_execution_queue_latency_soft_limit")]
1344    pub execution_queue_latency_soft_limit: Duration,
1345
1346    /// The execution queueing latency when entering aggressive load shedding
1347    /// mode.
1348    #[serde(default = "default_execution_queue_latency_hard_limit")]
1349    pub execution_queue_latency_hard_limit: Duration,
1350
1351    /// The maximum percentage of transactions to shed in load shedding mode.
1352    #[serde(default = "default_max_load_shedding_percentage")]
1353    pub max_load_shedding_percentage: u32,
1354
1355    /// When in aggressive load shedding mode, the minimum percentage of
1356    /// transactions to shed.
1357    #[serde(default = "default_min_load_shedding_percentage_above_hard_limit")]
1358    pub min_load_shedding_percentage_above_hard_limit: u32,
1359
1360    /// If transaction ready rate is below this rate, we consider the validator
1361    /// is well under used, and will not enter load shedding mode.
1362    #[serde(default = "default_safe_transaction_ready_rate")]
1363    pub safe_transaction_ready_rate: u32,
1364
1365    /// When set to true, transaction signing may be rejected when the validator
1366    /// is overloaded.
1367    #[serde(default = "default_check_system_overload_at_signing")]
1368    pub check_system_overload_at_signing: bool,
1369
1370    /// When set to true, transaction execution may be rejected when the
1371    /// validator is overloaded.
1372    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1373    pub check_system_overload_at_execution: bool,
1374
1375    /// Reject a transaction if transaction manager queue length is above this
1376    /// threshold. 100_000 = 10k TPS * 5s resident time in transaction
1377    /// manager (pending + executing) * 2.
1378    #[serde(default = "default_max_transaction_manager_queue_length")]
1379    pub max_transaction_manager_queue_length: usize,
1380
1381    /// Reject a transaction if the number of pending transactions depending on
1382    /// the object is above the threshold.
1383    #[serde(default = "default_max_transaction_manager_per_object_queue_length")]
1384    pub max_transaction_manager_per_object_queue_length: usize,
1385
1386    /// Percentage of `max_transaction_manager_queue_length` at which graduated
1387    /// load shedding begins in the certificate-less (P-COOL) mode. Read
1388    /// via the same-named accessor, which clamps the value to <=100.
1389    #[serde(default = "default_max_transaction_manager_queue_length_soft_limit_pct")]
1390    pub max_transaction_manager_queue_length_soft_limit_pct: u32,
1391}
1392
1393impl AuthorityOverloadConfig {
1394    /// Returns the soft-limit percentage, clamped to <=100 to guard against
1395    /// out-of-range operator-supplied values.
1396    pub fn max_transaction_manager_queue_length_soft_limit_pct(&self) -> u32 {
1397        self.max_transaction_manager_queue_length_soft_limit_pct
1398            .min(100)
1399    }
1400}
1401
1402fn default_max_txn_age_in_queue() -> Duration {
1403    Duration::from_millis(500)
1404}
1405
1406fn default_overload_monitor_interval() -> Duration {
1407    Duration::from_secs(10)
1408}
1409
1410fn default_execution_queue_latency_soft_limit() -> Duration {
1411    Duration::from_secs(1)
1412}
1413
1414fn default_execution_queue_latency_hard_limit() -> Duration {
1415    Duration::from_secs(10)
1416}
1417
1418fn default_max_load_shedding_percentage() -> u32 {
1419    95
1420}
1421
1422fn default_min_load_shedding_percentage_above_hard_limit() -> u32 {
1423    50
1424}
1425
1426fn default_safe_transaction_ready_rate() -> u32 {
1427    100
1428}
1429
1430fn default_check_system_overload_at_signing() -> bool {
1431    true
1432}
1433
1434fn default_max_transaction_manager_queue_length() -> usize {
1435    100_000
1436}
1437
1438fn default_max_transaction_manager_queue_length_soft_limit_pct() -> u32 {
1439    50
1440}
1441
1442fn default_max_transaction_manager_per_object_queue_length() -> usize {
1443    20
1444}
1445
1446impl Default for AuthorityOverloadConfig {
1447    fn default() -> Self {
1448        Self {
1449            max_txn_age_in_queue: default_max_txn_age_in_queue(),
1450            overload_monitor_interval: default_overload_monitor_interval(),
1451            execution_queue_latency_soft_limit: default_execution_queue_latency_soft_limit(),
1452            execution_queue_latency_hard_limit: default_execution_queue_latency_hard_limit(),
1453            max_load_shedding_percentage: default_max_load_shedding_percentage(),
1454            min_load_shedding_percentage_above_hard_limit:
1455                default_min_load_shedding_percentage_above_hard_limit(),
1456            safe_transaction_ready_rate: default_safe_transaction_ready_rate(),
1457            check_system_overload_at_signing: true,
1458            check_system_overload_at_execution: false,
1459            max_transaction_manager_queue_length: default_max_transaction_manager_queue_length(),
1460            max_transaction_manager_queue_length_soft_limit_pct:
1461                default_max_transaction_manager_queue_length_soft_limit_pct(),
1462            max_transaction_manager_per_object_queue_length:
1463                default_max_transaction_manager_per_object_queue_length(),
1464        }
1465    }
1466}
1467
1468fn default_authority_overload_config() -> AuthorityOverloadConfig {
1469    AuthorityOverloadConfig::default()
1470}
1471
1472fn default_traffic_controller_policy_config() -> Option<PolicyConfig> {
1473    Some(PolicyConfig::default_dos_protection_policy())
1474}
1475
1476fn is_default_traffic_controller_policy_config(policy_config: &Option<PolicyConfig>) -> bool {
1477    serializes_like(policy_config, &default_traffic_controller_policy_config())
1478}
1479
1480#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1481pub struct Genesis {
1482    #[serde(flatten)]
1483    location: Option<GenesisLocation>,
1484
1485    #[serde(skip)]
1486    genesis: once_cell::sync::OnceCell<genesis::Genesis>,
1487}
1488
1489impl Genesis {
1490    pub fn new(genesis: genesis::Genesis) -> Self {
1491        Self {
1492            location: Some(GenesisLocation::InPlace {
1493                genesis: Box::new(genesis),
1494            }),
1495            genesis: Default::default(),
1496        }
1497    }
1498
1499    pub fn new_from_file<P: Into<PathBuf>>(path: P) -> Self {
1500        Self {
1501            location: Some(GenesisLocation::File {
1502                genesis_file_location: path.into(),
1503            }),
1504            genesis: Default::default(),
1505        }
1506    }
1507
1508    pub fn new_empty() -> Self {
1509        Self {
1510            location: None,
1511            genesis: Default::default(),
1512        }
1513    }
1514
1515    pub fn genesis(&self) -> Result<&genesis::Genesis> {
1516        match &self.location {
1517            Some(GenesisLocation::InPlace { genesis }) => Ok(genesis),
1518            Some(GenesisLocation::File {
1519                genesis_file_location,
1520            }) => self
1521                .genesis
1522                .get_or_try_init(|| genesis::Genesis::load(genesis_file_location)),
1523            None => anyhow::bail!("no genesis location set"),
1524        }
1525    }
1526}
1527
1528#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1529#[serde(untagged)]
1530enum GenesisLocation {
1531    InPlace {
1532        genesis: Box<genesis::Genesis>,
1533    },
1534    File {
1535        #[serde(rename = "genesis-file-location")]
1536        genesis_file_location: PathBuf,
1537    },
1538}
1539
1540/// Wrapper struct for SimpleKeypair that can be deserialized from a file path.
1541/// Used by network, worker, and account keypair.
1542#[derive(Clone, Debug, Deserialize, Serialize)]
1543pub struct KeyPairWithPath {
1544    #[serde(flatten)]
1545    location: KeyPairLocation,
1546
1547    #[serde(skip)]
1548    keypair: OnceCell<Arc<SimpleKeypair>>,
1549}
1550
1551impl PartialEq for KeyPairWithPath {
1552    fn eq(&self, other: &Self) -> bool {
1553        self.location == other.location
1554    }
1555}
1556
1557impl Eq for KeyPairWithPath {}
1558
1559#[derive(Debug, Clone, Deserialize, Serialize)]
1560#[serde(untagged)]
1561enum KeyPairLocation {
1562    InPlace {
1563        #[serde(with = "bech32_formatted_keypair")]
1564        value: Arc<SimpleKeypair>,
1565    },
1566    File {
1567        path: PathBuf,
1568    },
1569}
1570
1571impl PartialEq for KeyPairLocation {
1572    fn eq(&self, other: &Self) -> bool {
1573        match (self, other) {
1574            (Self::InPlace { value: a }, Self::InPlace { value: b }) => {
1575                a.to_bytes() == b.to_bytes()
1576            }
1577            (Self::File { path: a }, Self::File { path: b }) => a == b,
1578            _ => false,
1579        }
1580    }
1581}
1582
1583impl Eq for KeyPairLocation {}
1584
1585impl KeyPairWithPath {
1586    pub fn new(kp: SimpleKeypair) -> Self {
1587        let cell: OnceCell<Arc<SimpleKeypair>> = OnceCell::new();
1588        let arc_kp = Arc::new(kp);
1589        // OK to unwrap panic because authority should not start without all keypairs
1590        // loaded.
1591        cell.set(arc_kp.clone()).expect("failed to set keypair");
1592        Self {
1593            location: KeyPairLocation::InPlace { value: arc_kp },
1594            keypair: cell,
1595        }
1596    }
1597
1598    pub fn new_from_path(path: PathBuf) -> Self {
1599        let cell: OnceCell<Arc<SimpleKeypair>> = OnceCell::new();
1600        // OK to unwrap panic because authority should not start without all keypairs
1601        // loaded.
1602        cell.set(Arc::new(read_keypair_from_file(&path).unwrap_or_else(
1603            |e| panic!("invalid keypair file at path {path:?}: {e}"),
1604        )))
1605        .expect("failed to set keypair");
1606        Self {
1607            location: KeyPairLocation::File { path },
1608            keypair: cell,
1609        }
1610    }
1611
1612    pub fn keypair(&self) -> &SimpleKeypair {
1613        self.keypair
1614            .get_or_init(|| match &self.location {
1615                KeyPairLocation::InPlace { value } => value.clone(),
1616                KeyPairLocation::File { path } => {
1617                    // OK to unwrap panic because authority should not start without all keypairs
1618                    // loaded.
1619                    Arc::new(
1620                        read_keypair_from_file(path).unwrap_or_else(|e| {
1621                            panic!("invalid keypair file at path {path:?}: {e}")
1622                        }),
1623                    )
1624                }
1625            })
1626            .as_ref()
1627    }
1628
1629    /// The stored keypair as an ed25519 network keypair, for the network
1630    /// stacks that consume that type directly. Panics if the stored keypair
1631    /// is not ed25519.
1632    pub fn network_keypair(&self) -> NetworkKeyPair {
1633        self.keypair()
1634            .as_opt_ed25519()
1635            .cloned()
1636            .expect("only Ed25519 network keys are allowed")
1637    }
1638}
1639
1640/// Wrapper struct for AuthorityKeyPair that can be deserialized from a file
1641/// path.
1642#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
1643pub struct AuthorityKeyPairWithPath {
1644    #[serde(flatten)]
1645    location: AuthorityKeyPairLocation,
1646
1647    #[serde(skip)]
1648    keypair: OnceCell<Arc<AuthorityKeyPair>>,
1649}
1650
1651#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)]
1652#[serde(untagged)]
1653enum AuthorityKeyPairLocation {
1654    InPlace { value: Arc<AuthorityKeyPair> },
1655    File { path: PathBuf },
1656}
1657
1658impl AuthorityKeyPairWithPath {
1659    pub fn new(kp: AuthorityKeyPair) -> Self {
1660        let cell: OnceCell<Arc<AuthorityKeyPair>> = OnceCell::new();
1661        let arc_kp = Arc::new(kp);
1662        // OK to unwrap panic because authority should not start without all keypairs
1663        // loaded.
1664        cell.set(arc_kp.clone())
1665            .expect("failed to set authority keypair");
1666        Self {
1667            location: AuthorityKeyPairLocation::InPlace { value: arc_kp },
1668            keypair: cell,
1669        }
1670    }
1671
1672    pub fn new_from_path(path: PathBuf) -> Self {
1673        let cell: OnceCell<Arc<AuthorityKeyPair>> = OnceCell::new();
1674        // OK to unwrap panic because authority should not start without all keypairs
1675        // loaded.
1676        cell.set(Arc::new(
1677            read_authority_keypair_from_file(&path)
1678                .unwrap_or_else(|_| panic!("invalid authority keypair file at path {path:?}")),
1679        ))
1680        .expect("failed to set authority keypair");
1681        Self {
1682            location: AuthorityKeyPairLocation::File { path },
1683            keypair: cell,
1684        }
1685    }
1686
1687    pub fn authority_keypair(&self) -> &AuthorityKeyPair {
1688        self.keypair
1689            .get_or_init(|| match &self.location {
1690                AuthorityKeyPairLocation::InPlace { value } => value.clone(),
1691                AuthorityKeyPairLocation::File { path } => {
1692                    // OK to unwrap panic because authority should not start without all keypairs
1693                    // loaded.
1694                    Arc::new(
1695                        read_authority_keypair_from_file(path)
1696                            .unwrap_or_else(|_| panic!("invalid authority keypair file {path:?}")),
1697                    )
1698                }
1699            })
1700            .as_ref()
1701    }
1702}
1703
1704/// Configurations which determine how we dump state debug info.
1705/// Debug info is dumped when a node forks.
1706#[derive(Clone, Debug, Deserialize, Serialize, Default)]
1707#[serde(rename_all = "kebab-case")]
1708pub struct StateDebugDumpConfig {
1709    #[serde(skip_serializing_if = "Option::is_none")]
1710    pub dump_file_directory: Option<PathBuf>,
1711}
1712
1713#[cfg(test)]
1714mod tests {
1715    use std::path::PathBuf;
1716
1717    use fastcrypto::traits::KeyPair;
1718    use iota_keys::keypair_file::{write_authority_keypair_to_file, write_keypair_to_file};
1719    use iota_sdk_crypto::simple::SimpleKeypair;
1720    use iota_types::{
1721        crypto::{AuthorityKeyPair, NetworkKeyPair, get_key_pair_from_rng},
1722        traffic_control::PolicyConfig,
1723    };
1724    use rand::{SeedableRng, rngs::StdRng};
1725    use serde::Serialize;
1726    use serde_yaml::Value;
1727
1728    use super::{
1729        AuthorityStorePruningConfig, DEFAULT_NUM_EPOCHS_TO_RETAIN_FOR_CHECKPOINTS, Genesis,
1730        GrpcApiConfig, ObjectStoreConfig, default_grpc_api_config,
1731        default_periodic_compaction_threshold_days, default_traffic_controller_policy_config,
1732    };
1733    use crate::{ConsensusConfig, NodeConfig, object_storage_config::ObjectStoreType};
1734
1735    const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1736
1737    const POLICY_CONFIG: &[&str] = &["policy-config"];
1738    const GRPC_API_CONFIG: &[&str] = &["grpc-api-config"];
1739    const COMPACTION_THRESHOLD: &[&str] = &[
1740        "authority-store-pruning-config",
1741        "periodic-compaction-threshold-days",
1742    ];
1743
1744    fn template_config() -> NodeConfig {
1745        serde_yaml::from_str(TEMPLATE).unwrap()
1746    }
1747
1748    fn consensus_config() -> super::ConsensusConfig {
1749        serde_yaml::from_str("db-path: /opt/iota/consensus-db").unwrap()
1750    }
1751
1752    fn object_store_config() -> ObjectStoreConfig {
1753        ObjectStoreConfig {
1754            object_store: Some(ObjectStoreType::File),
1755            directory: Some(PathBuf::from("/opt/iota/snapshots")),
1756            ..Default::default()
1757        }
1758    }
1759
1760    fn round_trip(config: &NodeConfig) -> NodeConfig {
1761        serde_yaml::from_str(&serde_yaml::to_string(config).unwrap()).unwrap()
1762    }
1763
1764    fn as_yaml<T: Serialize>(value: &T) -> String {
1765        serde_yaml::to_string(value).unwrap()
1766    }
1767
1768    /// Reads `path` out of a serialized value, returning `None` when the last
1769    /// key is absent.
1770    fn written_at(value: &Value, path: &[&str]) -> Option<Value> {
1771        let (last, parents) = path.split_last().unwrap();
1772        let mut current = value;
1773        for name in parents {
1774            current = current
1775                .as_mapping()
1776                .unwrap()
1777                .get(&Value::String((*name).to_owned()))
1778                .unwrap();
1779        }
1780        current
1781            .as_mapping()
1782            .unwrap()
1783            .get(&Value::String((*last).to_owned()))
1784            .cloned()
1785    }
1786
1787    #[test]
1788    fn a_retention_below_two_is_no_longer_rounded_up() {
1789        let config = AuthorityStorePruningConfig {
1790            num_epochs_to_retain_for_checkpoints: Some(1),
1791            ..Default::default()
1792        };
1793        assert_eq!(config.num_epochs_to_retain_for_checkpoints(), Some(1));
1794    }
1795
1796    #[test]
1797    fn index_retention_within_ledger_retention_is_accepted() {
1798        let config = AuthorityStorePruningConfig {
1799            num_epochs_to_retain_for_checkpoints: Some(2),
1800            num_epochs_to_retain_for_indexes: Some(2),
1801            ..Default::default()
1802        };
1803        assert!(config.check_index_retention_within_ledger().is_ok());
1804    }
1805
1806    #[test]
1807    fn index_retention_beyond_ledger_retention_is_refused() {
1808        let config = AuthorityStorePruningConfig {
1809            num_epochs_to_retain_for_checkpoints: Some(2),
1810            num_epochs_to_retain_for_indexes: Some(3),
1811            ..Default::default()
1812        };
1813        let err = config
1814            .check_index_retention_within_ledger()
1815            .unwrap_err()
1816            .to_string();
1817        assert!(err.contains("num-epochs-to-retain-for-indexes"), "{err}");
1818        assert!(
1819            err.contains("num-epochs-to-retain-for-checkpoints"),
1820            "{err}"
1821        );
1822        assert!(err.contains('3'), "{err}");
1823        assert!(err.contains('2'), "{err}");
1824    }
1825
1826    #[test]
1827    fn the_narrowest_ledger_retention_refuses_a_longer_index_retention() {
1828        let config = AuthorityStorePruningConfig {
1829            num_epochs_to_retain_for_checkpoints: Some(1),
1830            num_epochs_to_retain_for_indexes: Some(2),
1831            ..Default::default()
1832        };
1833        assert!(config.check_index_retention_within_ledger().is_err());
1834    }
1835
1836    #[test]
1837    fn equal_narrowest_retentions_are_accepted() {
1838        let config = AuthorityStorePruningConfig {
1839            num_epochs_to_retain_for_checkpoints: Some(1),
1840            num_epochs_to_retain_for_indexes: Some(1),
1841            ..Default::default()
1842        };
1843        assert!(config.check_index_retention_within_ledger().is_ok());
1844    }
1845
1846    #[test]
1847    fn unbounded_ledger_retention_accepts_any_index_retention() {
1848        let config = AuthorityStorePruningConfig {
1849            num_epochs_to_retain_for_checkpoints: Some(u64::MAX),
1850            num_epochs_to_retain_for_indexes: Some(1000),
1851            ..Default::default()
1852        };
1853        assert!(config.check_index_retention_within_ledger().is_ok());
1854    }
1855
1856    #[test]
1857    fn unset_ledger_retention_keeps_the_default_and_the_index_follows_it() {
1858        let config = AuthorityStorePruningConfig {
1859            num_epochs_to_retain_for_checkpoints: None,
1860            num_epochs_to_retain_for_indexes: None,
1861            ..Default::default()
1862        };
1863        assert_eq!(
1864            config.num_epochs_to_retain_for_checkpoints(),
1865            Some(DEFAULT_NUM_EPOCHS_TO_RETAIN_FOR_CHECKPOINTS)
1866        );
1867        assert_eq!(
1868            config.num_epochs_to_retain_for_indexes(),
1869            Some(DEFAULT_NUM_EPOCHS_TO_RETAIN_FOR_CHECKPOINTS)
1870        );
1871        assert!(config.check_index_retention_within_ledger().is_ok());
1872    }
1873
1874    #[test]
1875    fn unset_index_retention_follows_the_ledger() {
1876        let config = AuthorityStorePruningConfig {
1877            num_epochs_to_retain_for_checkpoints: Some(3),
1878            num_epochs_to_retain_for_indexes: None,
1879            ..Default::default()
1880        };
1881        assert_eq!(config.num_epochs_to_retain_for_indexes(), Some(3));
1882        assert!(config.check_index_retention_within_ledger().is_ok());
1883    }
1884
1885    #[test]
1886    fn index_pruning_turned_off_against_a_bounded_ledger_is_refused() {
1887        // `u64::MAX` is index pruning off, which outlives any bounded ledger.
1888        let config = AuthorityStorePruningConfig {
1889            num_epochs_to_retain_for_checkpoints: Some(2),
1890            num_epochs_to_retain_for_indexes: Some(u64::MAX),
1891            ..Default::default()
1892        };
1893        let err = config
1894            .check_index_retention_within_ledger()
1895            .unwrap_err()
1896            .to_string();
1897        assert!(err.contains("unlimited"), "{err}");
1898    }
1899
1900    #[test]
1901    fn max_retention_means_pruning_is_off_for_every_knob() {
1902        let config = AuthorityStorePruningConfig {
1903            num_epochs_to_retain: u64::MAX,
1904            num_epochs_to_retain_for_checkpoints: Some(u64::MAX),
1905            num_epochs_to_retain_for_indexes: Some(u64::MAX),
1906            ..Default::default()
1907        };
1908        assert_eq!(config.num_epochs_to_retain(), None);
1909        assert_eq!(config.num_epochs_to_retain_for_checkpoints(), None);
1910        assert_eq!(config.num_epochs_to_retain_for_indexes(), None);
1911    }
1912
1913    #[test]
1914    fn a_fullnode_serving_an_api_builds_the_indexes() {
1915        const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1916
1917        let config: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1918        assert!(config.maintains_rpc_indexes());
1919    }
1920
1921    #[test]
1922    fn a_fullnode_with_both_apis_off_builds_no_indexes() {
1923        const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1924
1925        let mut config: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1926        config.enable_jsonrpc_api = false;
1927        config.enable_grpc_api = false;
1928        assert!(!config.maintains_rpc_indexes());
1929    }
1930
1931    #[test]
1932    fn a_validator_builds_no_indexes_even_with_an_api_enabled() {
1933        const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1934
1935        let mut config: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1936        config.enable_jsonrpc_api = true;
1937        config.consensus_config = Some(ConsensusConfig {
1938            db_path: PathBuf::from("/tmp/consensus"),
1939            db_retention_epochs: None,
1940            db_pruner_period_secs: None,
1941            max_pending_transactions: None,
1942            max_submit_position: None,
1943            submit_delay_step_override_millis: None,
1944            parameters: None,
1945            graduated_load_shedding_soft_limit_pct: None,
1946        });
1947        assert!(!config.maintains_rpc_indexes());
1948    }
1949
1950    #[test]
1951    fn serialize_genesis_from_file() {
1952        let g = Genesis::new_from_file("path/to/file");
1953
1954        let s = serde_yaml::to_string(&g).unwrap();
1955        assert_eq!("---\ngenesis-file-location: path/to/file\n", s);
1956        let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
1957        assert_eq!(g, loaded_genesis);
1958    }
1959
1960    #[test]
1961    fn fullnode_template() {
1962        const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
1963
1964        let _template: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
1965    }
1966
1967    #[test]
1968    fn validate_requires_a_grpc_config_when_the_api_is_enabled() {
1969        // An absent key gives the default config, so only an explicit `null`
1970        // reaches this rule.
1971        let mut config = template_config();
1972        config.grpc_api_config = None;
1973        config.validate().unwrap();
1974
1975        config.enable_grpc_api = true;
1976        let err = config.validate().unwrap_err().to_string();
1977        assert!(err.contains("`grpc-api-config` is `null`"), "{err}");
1978
1979        config.grpc_api_config = Some(GrpcApiConfig::default());
1980        config.validate().unwrap();
1981
1982        // Validators do not expose the gRPC API, so the config check does
1983        // not apply to them and the API itself is rejected instead.
1984        config.grpc_api_config = None;
1985        config.consensus_config = Some(consensus_config());
1986        let err = config.validate().unwrap_err().to_string();
1987        assert!(err.contains("validators do not expose"), "{err}");
1988    }
1989
1990    #[test]
1991    fn validate_rejects_the_grpc_api_on_a_validator() {
1992        let mut config = template_config();
1993        config.consensus_config = Some(consensus_config());
1994        config.validate().unwrap();
1995
1996        config.enable_grpc_api = true;
1997        let err = config.validate().unwrap_err().to_string();
1998        assert!(err.contains("validators do not expose"), "{err}");
1999
2000        // The same config is valid for a fullnode.
2001        config.consensus_config = None;
2002        config.validate().unwrap();
2003    }
2004
2005    #[test]
2006    fn validate_rejects_snapshot_upload_on_a_validator() {
2007        let mut config = template_config();
2008        config.state_snapshot_write_config.object_store_config = Some(object_store_config());
2009        config.validate().unwrap();
2010
2011        config.consensus_config = Some(consensus_config());
2012        let err = config.validate().unwrap_err().to_string();
2013        assert!(err.contains("snapshot upload"), "{err}");
2014    }
2015
2016    #[test]
2017    fn validate_rejects_a_snapshot_store_without_a_backend() {
2018        let mut config = template_config();
2019        config.state_snapshot_write_config.object_store_config = Some(ObjectStoreConfig::default());
2020
2021        let err = config.validate().unwrap_err().to_string();
2022        assert!(err.contains("storage backend"), "{err}");
2023    }
2024
2025    #[test]
2026    fn validate_rejects_a_zero_checkpoint_retention() {
2027        let mut config = template_config();
2028        config
2029            .authority_store_pruning_config
2030            .set_num_epochs_to_retain_for_checkpoints(Some(0));
2031        let err = config.validate().unwrap_err().to_string();
2032        assert!(
2033            err.contains("num-epochs-to-retain-for-checkpoints"),
2034            "{err}"
2035        );
2036
2037        config
2038            .authority_store_pruning_config
2039            .set_num_epochs_to_retain_for_checkpoints(Some(1));
2040        config.validate().unwrap();
2041    }
2042
2043    #[test]
2044    fn enable_soft_locking_defaults_to_enabled() {
2045        // The template omits `enable-soft-locking`, so this exercises the serde
2046        // default and pins the documented "default: enabled" contract.
2047        const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
2048
2049        let config: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
2050        assert!(config.enable_soft_locking);
2051    }
2052
2053    /// The old key is refused where `enable-jsonrpc-api` cannot be assumed to
2054    /// do what its value asked for, and accepted elsewhere, so that the configs
2055    /// earlier releases wrote out keep starting.
2056    #[test]
2057    fn renamed_enable_index_processing_key() {
2058        const TEMPLATE: &str = include_str!("../data/fullnode-template.yaml");
2059        let with_old_key = |value: bool| {
2060            let mut template: serde_yaml::Value = serde_yaml::from_str(TEMPLATE).unwrap();
2061            template
2062                .as_mapping_mut()
2063                .unwrap()
2064                .insert("enable-index-processing".into(), value.into());
2065            serde_yaml::from_value::<NodeConfig>(template).unwrap()
2066        };
2067
2068        // `false` on a full node served the JSON-RPC API without its index.
2069        let mut config = with_old_key(false);
2070        assert_eq!(config.enable_index_processing, Some(false));
2071        assert!(config.enable_jsonrpc_api);
2072        let err = config.check_renamed_keys().unwrap_err().to_string();
2073        assert!(err.contains("enable-index-processing"), "{err}");
2074        assert!(err.contains("enable-jsonrpc-api"), "{err}");
2075
2076        // A validator never built the index, whatever the value.
2077        config.consensus_config = Some(consensus_config());
2078        assert!(config.check_renamed_keys().is_ok());
2079
2080        // `true` asks for what `enable-jsonrpc-api` does by default.
2081        let mut config = with_old_key(true);
2082        assert!(config.check_renamed_keys().is_ok());
2083        config.enable_jsonrpc_api = false;
2084        assert!(config.check_renamed_keys().is_err());
2085
2086        // Never serialized, so rewriting the config cannot bring the key back.
2087        let serialized = serde_yaml::to_string(&config).unwrap();
2088        assert!(
2089            !serialized.contains("enable-index-processing"),
2090            "{serialized}"
2091        );
2092
2093        let config: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
2094        assert!(config.check_renamed_keys().is_ok());
2095    }
2096
2097    #[test]
2098    fn load_key_pairs_to_node_config() {
2099        let authority_key_pair: AuthorityKeyPair =
2100            get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
2101        let protocol_key_pair: NetworkKeyPair =
2102            get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
2103        let network_key_pair: NetworkKeyPair =
2104            get_key_pair_from_rng(&mut StdRng::from_seed([0; 32])).1;
2105
2106        write_authority_keypair_to_file(&authority_key_pair, PathBuf::from("authority.key"))
2107            .unwrap();
2108        write_keypair_to_file(
2109            &SimpleKeypair::from(protocol_key_pair.clone()),
2110            PathBuf::from("protocol.key"),
2111        )
2112        .unwrap();
2113        write_keypair_to_file(
2114            &SimpleKeypair::from(network_key_pair.clone()),
2115            PathBuf::from("network.key"),
2116        )
2117        .unwrap();
2118
2119        const TEMPLATE: &str = include_str!("../data/fullnode-template-with-path.yaml");
2120        let template: NodeConfig = serde_yaml::from_str(TEMPLATE).unwrap();
2121        assert_eq!(
2122            template.authority_key_pair().public(),
2123            authority_key_pair.public()
2124        );
2125        assert_eq!(
2126            template.network_key_pair().public_key(),
2127            network_key_pair.public_key()
2128        );
2129        assert_eq!(
2130            template.protocol_key_pair().public_key(),
2131            protocol_key_pair.public_key()
2132        );
2133    }
2134
2135    #[test]
2136    fn a_policy_config_survives_a_round_trip_in_all_three_states() {
2137        let mut config = template_config();
2138
2139        config.policy_config = None;
2140        assert!(round_trip(&config).policy_config.is_none());
2141
2142        config.policy_config = default_traffic_controller_policy_config();
2143        assert_eq!(
2144            as_yaml(&round_trip(&config).policy_config),
2145            as_yaml(&default_traffic_controller_policy_config())
2146        );
2147
2148        let configured = PolicyConfig {
2149            dry_run: !PolicyConfig::default_dos_protection_policy().dry_run,
2150            ..PolicyConfig::default_dos_protection_policy()
2151        };
2152        config.policy_config = Some(configured.clone());
2153        assert_eq!(
2154            as_yaml(&round_trip(&config).policy_config),
2155            as_yaml(&Some(configured))
2156        );
2157    }
2158
2159    #[test]
2160    fn a_grpc_api_config_survives_a_round_trip_in_all_three_states() {
2161        let mut config = template_config();
2162
2163        config.grpc_api_config = None;
2164        assert!(round_trip(&config).grpc_api_config.is_none());
2165
2166        config.grpc_api_config = default_grpc_api_config();
2167        assert_eq!(
2168            as_yaml(&round_trip(&config).grpc_api_config),
2169            as_yaml(&default_grpc_api_config())
2170        );
2171
2172        let configured = GrpcApiConfig {
2173            max_message_size_bytes: 1234,
2174            ..GrpcApiConfig::default()
2175        };
2176        config.grpc_api_config = Some(configured.clone());
2177        assert_eq!(
2178            as_yaml(&round_trip(&config).grpc_api_config),
2179            as_yaml(&Some(configured))
2180        );
2181    }
2182
2183    #[test]
2184    fn the_default_pruning_config_agrees_with_the_serde_default() {
2185        assert_eq!(
2186            super::AuthorityStorePruningConfig::default().periodic_compaction_threshold_days,
2187            default_periodic_compaction_threshold_days()
2188        );
2189    }
2190
2191    #[test]
2192    fn a_compaction_threshold_survives_a_round_trip_in_all_three_states() {
2193        let mut config = template_config();
2194
2195        for state in [None, default_periodic_compaction_threshold_days(), Some(7)] {
2196            config
2197                .authority_store_pruning_config
2198                .periodic_compaction_threshold_days = state;
2199            assert_eq!(
2200                round_trip(&config)
2201                    .authority_store_pruning_config
2202                    .periodic_compaction_threshold_days,
2203                state
2204            );
2205        }
2206    }
2207
2208    #[test]
2209    fn a_default_value_is_omitted_and_a_disabled_one_is_written_as_null() {
2210        let mut config = template_config();
2211        config.policy_config = default_traffic_controller_policy_config();
2212        config.grpc_api_config = default_grpc_api_config();
2213        config
2214            .authority_store_pruning_config
2215            .periodic_compaction_threshold_days = default_periodic_compaction_threshold_days();
2216
2217        let written = serde_yaml::to_value(&config).unwrap();
2218        assert_eq!(written_at(&written, POLICY_CONFIG), None);
2219        assert_eq!(written_at(&written, GRPC_API_CONFIG), None);
2220        assert_eq!(written_at(&written, COMPACTION_THRESHOLD), None);
2221
2222        config.policy_config = None;
2223        config.grpc_api_config = None;
2224        config
2225            .authority_store_pruning_config
2226            .periodic_compaction_threshold_days = None;
2227
2228        let written = serde_yaml::to_value(&config).unwrap();
2229        assert_eq!(written_at(&written, POLICY_CONFIG), Some(Value::Null));
2230        assert_eq!(written_at(&written, GRPC_API_CONFIG), Some(Value::Null));
2231        assert_eq!(
2232            written_at(&written, COMPACTION_THRESHOLD),
2233            Some(Value::Null)
2234        );
2235    }
2236}
2237
2238// RunWithRange is used to specify the ending epoch/checkpoint to process.
2239// this is intended for use with disaster recovery debugging and verification
2240// workflows, never in normal operations
2241#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
2242pub enum RunWithRange {
2243    Epoch(EpochId),
2244    Checkpoint(CheckpointSequenceNumber),
2245}
2246
2247impl RunWithRange {
2248    // is epoch_id > RunWithRange::Epoch
2249    pub fn is_epoch_gt(&self, epoch_id: EpochId) -> bool {
2250        matches!(self, RunWithRange::Epoch(e) if epoch_id > *e)
2251    }
2252
2253    pub fn matches_checkpoint(&self, seq_num: CheckpointSequenceNumber) -> bool {
2254        matches!(self, RunWithRange::Checkpoint(seq) if *seq == seq_num)
2255    }
2256
2257    pub fn into_checkpoint_bound(self) -> Option<CheckpointSequenceNumber> {
2258        match self {
2259            RunWithRange::Epoch(_) => None,
2260            RunWithRange::Checkpoint(seq) => Some(seq),
2261        }
2262    }
2263}
2264
2265/// A serde helper module used with #[serde(with = "...")] to change the
2266/// de/serialization format of an `SimpleKeypair` to Bech32 when written to or
2267/// read from a node config.
2268mod bech32_formatted_keypair {
2269    use std::ops::Deref;
2270
2271    use fastcrypto::encoding::{Base64, Encoding};
2272    use iota_sdk_crypto::{ToFromBech32, simple::SimpleKeypair};
2273    use serde::{Deserialize, Deserializer, Serializer};
2274
2275    pub fn serialize<S, T>(kp: &T, serializer: S) -> Result<S::Ok, S::Error>
2276    where
2277        S: Serializer,
2278        T: Deref<Target = SimpleKeypair>,
2279    {
2280        use serde::ser::Error;
2281
2282        // Serialize the keypair to a Bech32 string
2283        let s = kp.to_bech32().map_err(Error::custom)?;
2284
2285        serializer.serialize_str(&s)
2286    }
2287
2288    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
2289    where
2290        D: Deserializer<'de>,
2291        T: From<SimpleKeypair>,
2292    {
2293        use serde::de::Error;
2294
2295        let s = String::deserialize(deserializer)?;
2296
2297        // Try to deserialize the keypair from a Bech32 formatted string
2298        SimpleKeypair::from_bech32(&s)
2299            .map_err(Error::custom)
2300            .or_else(|_: D::Error| {
2301                // For backwards compatibility try Base64 if Bech32 failed
2302                let bytes = Base64::decode(&s).map_err(Error::custom)?;
2303                SimpleKeypair::from_bytes(&bytes).map_err(Error::custom)
2304            })
2305            .map(Into::into)
2306    }
2307}