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