Skip to main content

fuel_core/service/
config.rs

1use crate::{
2    combined_database::CombinedDatabaseConfig,
3    graphql_api::ServiceConfig as GraphQLConfig,
4};
5use clap::ValueEnum;
6use fuel_core_chain_config::SnapshotReader;
7pub use fuel_core_consensus_module::RelayerConsensusConfig;
8pub use fuel_core_importer;
9pub use fuel_core_poa::Trigger;
10use fuel_core_tx_status_manager::config::Config as TxStatusManagerConfig;
11use fuel_core_txpool::config::Config as TxPoolConfig;
12use fuel_core_types::{
13    blockchain::header::StateTransitionBytecodeVersion,
14    fuel_types::{
15        AssetId,
16        ChainId,
17    },
18    signer::SignMode,
19};
20use std::{
21    num::{
22        NonZeroU32,
23        NonZeroU64,
24    },
25    path::PathBuf,
26    time::Duration,
27};
28use strum_macros::{
29    Display,
30    EnumString,
31    VariantNames,
32};
33
34#[cfg(feature = "parallel-executor")]
35use std::num::NonZeroUsize;
36
37#[cfg(feature = "relayer")]
38use fuel_core_relayer::Config as RelayerConfig;
39
40#[cfg(feature = "p2p")]
41use fuel_core_p2p::config::{
42    Config as P2PConfig,
43    NotInitialized,
44};
45
46#[cfg(feature = "test-helpers")]
47#[cfg(feature = "rpc")]
48use fuel_core_block_aggregator_api::service::StorageMethod;
49#[cfg(feature = "test-helpers")]
50use fuel_core_chain_config::{
51    ChainConfig,
52    StateConfig,
53};
54#[cfg(feature = "test-helpers")]
55#[cfg(feature = "rpc")]
56use fuel_core_types::fuel_types::BlockHeight;
57#[cfg(feature = "test-helpers")]
58use std::net::{
59    SocketAddr,
60    TcpListener,
61};
62
63#[derive(Clone, Debug)]
64pub struct RedisLeaderLockConfig {
65    pub redis_urls: Vec<String>,
66    pub lease_key: String,
67    pub lease_ttl: Duration,
68    pub node_timeout: Duration,
69    pub retry_delay: Duration,
70    pub max_retry_delay_offset: Duration,
71    pub max_attempts: u32,
72    pub stream_max_len: u32,
73    pub quorum_disruption_budget: u32,
74}
75
76#[derive(Clone, Debug)]
77pub struct Config {
78    pub graphql_config: GraphQLConfig,
79    pub combined_db_config: CombinedDatabaseConfig,
80    pub snapshot_reader: SnapshotReader,
81    pub continue_on_error: bool,
82    /// When `true`:
83    /// - Enables manual block production.
84    /// - Enables debugger endpoint.
85    /// - Allows setting `utxo_validation` to `false`.
86    pub debug: bool,
87    /// When `true`:
88    /// - Enables dry run in the past.
89    /// - Enables storage read replay for historical blocks.
90    /// - Enables querying historical contract state and balances.
91    pub historical_execution: bool,
92    pub expensive_subscriptions: bool,
93    pub utxo_validation: bool,
94    pub allow_syscall: bool,
95    pub native_executor_version: Option<StateTransitionBytecodeVersion>,
96    #[cfg(feature = "parallel-executor")]
97    pub executor_number_of_cores: NonZeroUsize,
98    pub block_production: Trigger,
99    pub leader_lock: Option<RedisLeaderLockConfig>,
100    pub predefined_blocks_path: Option<PathBuf>,
101    pub txpool: TxPoolConfig,
102    pub tx_status_manager: TxStatusManagerConfig,
103    pub block_producer: fuel_core_producer::Config,
104    pub gas_price_config: GasPriceConfig,
105    #[cfg(feature = "rpc")]
106    pub rpc_config: Option<fuel_core_block_aggregator_api::service::Config>,
107    pub da_compression: DaCompressionMode,
108    pub block_importer: fuel_core_importer::Config,
109    #[cfg(feature = "relayer")]
110    pub relayer: Option<RelayerConfig>,
111    #[cfg(feature = "p2p")]
112    pub p2p: Option<P2PConfig<NotInitialized>>,
113    #[cfg(feature = "p2p")]
114    pub sync: fuel_core_sync::Config,
115    #[cfg(feature = "p2p")]
116    pub pre_confirmation_signature_service:
117        fuel_core_poa::pre_confirmation_signature_service::config::Config,
118    #[cfg(feature = "shared-sequencer")]
119    pub shared_sequencer: fuel_core_shared_sequencer::Config,
120    pub consensus_signer: SignMode,
121    pub name: String,
122    pub relayer_consensus_config: fuel_core_consensus_module::RelayerConsensusConfig,
123    /// The number of reserved peers to connect to before starting to sync.
124    pub min_connected_reserved_peers: usize,
125    /// Time to wait after receiving the latest block before considered to be Synced.
126    pub time_until_synced: Duration,
127    /// The timeout after which block production is considered failed.
128    pub production_timeout: Duration,
129    /// The size of the memory pool in number of `MemoryInstance`s.
130    pub memory_pool_size: usize,
131}
132
133#[cfg(feature = "test-helpers")]
134pub fn free_local_addr() -> SocketAddr {
135    let listener = TcpListener::bind("[::1]:0").unwrap();
136    listener.local_addr().unwrap() // OS picks a free port
137}
138
139impl Config {
140    #[cfg(feature = "test-helpers")]
141    pub fn local_node() -> Self {
142        Self::local_node_with_state_config(StateConfig::local_testnet())
143    }
144
145    #[cfg(feature = "test-helpers")]
146    #[cfg(feature = "rpc")]
147    pub fn local_node_with_rpc() -> Self {
148        let mut config = Self::local_node_with_state_config(StateConfig::local_testnet());
149        let rpc_config = fuel_core_block_aggregator_api::service::Config {
150            addr: free_local_addr(),
151            sync_from: Some(BlockHeight::new(0)),
152            storage_method: StorageMethod::Local,
153            api_buffer_size: 100,
154        };
155        config.rpc_config = Some(rpc_config);
156        config
157    }
158
159    #[cfg(feature = "test-helpers")]
160    #[cfg(feature = "rpc")]
161    pub fn local_node_with_rpc_and_storage_method(storage_method: StorageMethod) -> Self {
162        let mut config = Self::local_node_with_state_config(StateConfig::local_testnet());
163        let rpc_config = fuel_core_block_aggregator_api::service::Config {
164            addr: free_local_addr(),
165            sync_from: Some(BlockHeight::new(0)),
166            storage_method,
167            api_buffer_size: 100,
168        };
169        config.rpc_config = Some(rpc_config);
170        config
171    }
172
173    #[cfg(feature = "test-helpers")]
174    pub fn local_node_with_state_config(state_config: StateConfig) -> Self {
175        Self::local_node_with_configs(ChainConfig::local_testnet(), state_config)
176    }
177
178    #[cfg(feature = "test-helpers")]
179    pub fn local_node_with_configs(
180        chain_config: ChainConfig,
181        state_config: StateConfig,
182    ) -> Self {
183        Self::local_node_with_reader(SnapshotReader::new_in_memory(
184            chain_config,
185            state_config,
186        ))
187    }
188
189    #[cfg(feature = "test-helpers")]
190    pub fn local_node_with_reader(snapshot_reader: SnapshotReader) -> Self {
191        let block_importer = fuel_core_importer::Config::new(false);
192        let latest_block = snapshot_reader.last_block_config();
193        // In tests, we always want to use the native executor as a default configuration.
194        let native_executor_version = latest_block
195            .map(|last_block| last_block.state_transition_version.saturating_add(1))
196            .unwrap_or(
197                fuel_core_types::blockchain::header::LATEST_STATE_TRANSITION_VERSION,
198            );
199
200        let utxo_validation = false;
201
202        let combined_db_config = CombinedDatabaseConfig {
203            #[cfg(feature = "rocksdb")]
204            database_config: crate::state::rocks_db::DatabaseConfig::config_for_tests(),
205            database_path: Default::default(),
206            #[cfg(feature = "rocksdb")]
207            database_type: DbType::RocksDb,
208            #[cfg(not(feature = "rocksdb"))]
209            database_type: DbType::InMemory,
210            #[cfg(feature = "rocksdb")]
211            state_rewind_policy:
212                crate::state::historical_rocksdb::StateRewindPolicy::RewindFullRange,
213        };
214
215        #[cfg(feature = "p2p")]
216        let network_name = snapshot_reader.chain_config().chain_name.clone();
217        let gas_price_config = GasPriceConfig::local_node();
218
219        const MAX_TXS_TTL: Duration = Duration::from_secs(60 * 100000000);
220
221        #[cfg(feature = "rpc")]
222        let rpc_config = None;
223
224        Self {
225            graphql_config: GraphQLConfig {
226                addr: std::net::SocketAddr::new(
227                    std::net::Ipv4Addr::new(127, 0, 0, 1).into(),
228                    0,
229                ),
230                number_of_threads: 0,
231                database_batch_size: 100,
232                block_subscriptions_queue: 1000,
233                max_queries_depth: 16,
234                max_queries_complexity: 80000,
235                max_queries_recursive_depth: 16,
236                max_queries_resolver_recursive_depth: 1,
237                max_queries_directives: 10,
238                max_concurrent_queries: 1024,
239                request_body_bytes_limit: 16 * 1024 * 1024,
240                query_log_threshold_time: Duration::from_secs(2),
241                api_request_timeout: Duration::from_secs(60),
242                assemble_tx_dry_run_limit: 3,
243                assemble_tx_estimate_predicates_limit: 5,
244                costs: Default::default(),
245                required_fuel_block_height_tolerance: 10,
246                required_fuel_block_height_timeout: Duration::from_secs(30),
247            },
248            combined_db_config,
249            continue_on_error: false,
250            debug: true,
251            historical_execution: true,
252            allow_syscall: true,
253            expensive_subscriptions: true,
254            utxo_validation,
255            native_executor_version: Some(native_executor_version),
256            #[cfg(feature = "parallel-executor")]
257            executor_number_of_cores: NonZeroUsize::new(1).expect("1 is not zero"),
258            snapshot_reader,
259            block_production: Trigger::Instant,
260            leader_lock: None,
261            predefined_blocks_path: None,
262            txpool: TxPoolConfig {
263                utxo_validation,
264                max_txs_ttl: MAX_TXS_TTL,
265                ..Default::default()
266            },
267            tx_status_manager: TxStatusManagerConfig {
268                subscription_ttl: MAX_TXS_TTL,
269                ..Default::default()
270            },
271            block_producer: fuel_core_producer::Config {
272                ..Default::default()
273            },
274            da_compression: DaCompressionMode::Disabled,
275            gas_price_config,
276            block_importer,
277            #[cfg(feature = "relayer")]
278            relayer: None,
279            #[cfg(feature = "p2p")]
280            p2p: Some(P2PConfig::<NotInitialized>::default(network_name.as_str())),
281            #[cfg(feature = "p2p")]
282            sync: fuel_core_sync::Config::default(),
283            #[cfg(feature = "p2p")]
284            pre_confirmation_signature_service:
285                fuel_core_poa::pre_confirmation_signature_service::config::Config::default(
286                ),
287            #[cfg(feature = "shared-sequencer")]
288            shared_sequencer: fuel_core_shared_sequencer::Config::local_node(),
289            consensus_signer: SignMode::Key(fuel_core_types::secrecy::Secret::new(
290                fuel_core_chain_config::default_consensus_dev_key().into(),
291            )),
292            name: String::default(),
293            relayer_consensus_config: Default::default(),
294            min_connected_reserved_peers: 0,
295            time_until_synced: Duration::ZERO,
296            production_timeout: Duration::from_secs(20),
297            memory_pool_size: 4,
298            #[cfg(feature = "rpc")]
299            rpc_config,
300        }
301    }
302
303    // TODO: Rework our configs system to avoid nesting of the same configs.
304    pub fn make_config_consistent(mut self) -> Config {
305        if !self.debug && !self.utxo_validation {
306            tracing::warn!(
307                "The `utxo_validation` should be `true` with disabled `debug`"
308            );
309            self.utxo_validation = true;
310        }
311
312        if self.txpool.utxo_validation != self.utxo_validation {
313            tracing::warn!("The `utxo_validation` of `TxPool` was inconsistent");
314            self.txpool.utxo_validation = self.utxo_validation;
315        }
316
317        self
318    }
319
320    pub fn base_asset_id(&self) -> AssetId {
321        *self
322            .snapshot_reader
323            .chain_config()
324            .consensus_parameters
325            .base_asset_id()
326    }
327
328    pub fn chain_id(&self) -> ChainId {
329        self.snapshot_reader
330            .chain_config()
331            .consensus_parameters
332            .chain_id()
333    }
334}
335
336impl From<&Config> for fuel_core_poa::Config {
337    fn from(config: &Config) -> Self {
338        fuel_core_poa::Config {
339            trigger: config.block_production,
340            signer: config.consensus_signer.clone(),
341            metrics: false,
342            min_connected_reserved_peers: config.min_connected_reserved_peers,
343            time_until_synced: config.time_until_synced,
344            production_timeout: config.production_timeout,
345            chain_id: config
346                .snapshot_reader
347                .chain_config()
348                .consensus_parameters
349                .chain_id(),
350        }
351    }
352}
353
354#[cfg(feature = "p2p")]
355impl From<&Config> for fuel_core_poa::pre_confirmation_signature_service::config::Config {
356    fn from(value: &Config) -> Self {
357        fuel_core_poa::pre_confirmation_signature_service::config::Config {
358            echo_delegation_interval: value
359                .pre_confirmation_signature_service
360                .echo_delegation_interval,
361            key_expiration_interval: value
362                .pre_confirmation_signature_service
363                .key_expiration_interval,
364            key_rotation_interval: value
365                .pre_confirmation_signature_service
366                .key_rotation_interval,
367        }
368    }
369}
370
371#[derive(
372    Clone, Copy, Debug, Display, Eq, PartialEq, EnumString, VariantNames, ValueEnum,
373)]
374#[strum(serialize_all = "kebab_case")]
375pub enum DbType {
376    InMemory,
377    RocksDb,
378}
379
380#[derive(Clone, Debug)]
381pub struct GasPriceConfig {
382    pub starting_exec_gas_price: u64,
383    pub exec_gas_price_change_percent: u16,
384    pub min_exec_gas_price: u64,
385    pub exec_gas_price_threshold_percent: u8,
386    pub da_committer_url: Option<url::Url>,
387    pub da_poll_interval: Option<Duration>,
388    pub da_gas_price_factor: NonZeroU64,
389    pub starting_recorded_height: Option<u32>,
390    pub min_da_gas_price: u64,
391    pub max_da_gas_price: u64,
392    pub max_da_gas_price_change_percent: u16,
393    pub da_gas_price_p_component: i64,
394    pub da_gas_price_d_component: i64,
395    pub gas_price_metrics: bool,
396    pub activity_normal_range_size: u16,
397    pub activity_capped_range_size: u16,
398    pub activity_decrease_range_size: u16,
399    pub block_activity_threshold: u8,
400}
401
402impl GasPriceConfig {
403    #[cfg(feature = "test-helpers")]
404    pub fn local_node() -> GasPriceConfig {
405        let starting_gas_price = 0;
406        let gas_price_change_percent = 0;
407        let min_gas_price = 0;
408        let gas_price_threshold_percent = 50;
409        let gas_price_metrics = false;
410
411        GasPriceConfig {
412            starting_exec_gas_price: starting_gas_price,
413            exec_gas_price_change_percent: gas_price_change_percent,
414            min_exec_gas_price: min_gas_price,
415            exec_gas_price_threshold_percent: gas_price_threshold_percent,
416            da_gas_price_factor: NonZeroU64::new(100).expect("100 is not zero"),
417            starting_recorded_height: None,
418            min_da_gas_price: 0,
419            max_da_gas_price: 1,
420            max_da_gas_price_change_percent: 0,
421            da_gas_price_p_component: 0,
422            da_gas_price_d_component: 0,
423            gas_price_metrics,
424            activity_normal_range_size: 0,
425            activity_capped_range_size: 0,
426            activity_decrease_range_size: 0,
427            da_committer_url: None,
428            block_activity_threshold: 0,
429            da_poll_interval: Some(Duration::from_secs(1)),
430        }
431    }
432}
433
434#[derive(Debug, Clone)]
435pub struct DaCompressionConfig {
436    pub retention_duration: Duration,
437    pub metrics: bool,
438    pub starting_height: Option<NonZeroU32>,
439}
440
441#[derive(Debug, Clone)]
442pub enum DaCompressionMode {
443    Disabled,
444    Enabled(DaCompressionConfig),
445}