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