Skip to main content

iota_config/
node.rs

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