fuel_core/
graphql_api.rs

1use async_graphql::Context;
2use fuel_core_storage::{
3    Error as StorageError,
4    IsNotFound,
5};
6use std::{
7    net::SocketAddr,
8    sync::OnceLock,
9    time::Duration,
10};
11
12pub mod api_service;
13pub(crate) mod block_height_subscription;
14pub mod database;
15pub(crate) mod extensions;
16pub(crate) mod indexation;
17pub mod ports;
18pub mod storage;
19pub mod worker_service;
20
21#[derive(Clone, Debug)]
22pub struct Config {
23    pub config: ServiceConfig,
24    pub utxo_validation: bool,
25    pub debug: bool,
26    pub allow_syscall: bool,
27    pub historical_execution: bool,
28    pub expensive_subscriptions: bool,
29    pub max_tx: usize,
30    pub max_gas: u64,
31    pub max_size: usize,
32    pub max_txpool_dependency_chain_length: usize,
33    pub chain_name: String,
34}
35
36#[derive(Clone, Debug)]
37pub struct ServiceConfig {
38    pub addr: SocketAddr,
39    pub number_of_threads: usize,
40    pub database_batch_size: usize,
41    pub block_subscriptions_queue: usize,
42    pub max_queries_depth: usize,
43    pub max_queries_complexity: usize,
44    pub max_queries_recursive_depth: usize,
45    pub max_queries_resolver_recursive_depth: usize,
46    pub max_queries_directives: usize,
47    pub max_concurrent_queries: usize,
48    pub request_body_bytes_limit: usize,
49    /// Number of blocks that the node can be lagging behind the required fuel block height
50    /// before it will be considered out of sync.
51    pub required_fuel_block_height_tolerance: u32,
52    /// The time to wait before dropping the request if the node is lagging behind the required
53    /// fuel block height.
54    pub required_fuel_block_height_timeout: Duration,
55    /// Time to wait after submitting a query before debug info will be logged about query.
56    pub query_log_threshold_time: Duration,
57    pub api_request_timeout: Duration,
58    pub assemble_tx_dry_run_limit: usize,
59    pub assemble_tx_estimate_predicates_limit: usize,
60    /// Configurable cost parameters to limit graphql queries complexity
61    pub costs: Costs,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
65pub struct Costs {
66    pub balance_query: usize,
67    pub coins_to_spend: usize,
68    pub get_peers: usize,
69    pub estimate_predicates: usize,
70    pub assemble_tx: usize,
71    pub dry_run: usize,
72    pub storage_read_replay: usize,
73    pub submit: usize,
74    pub submit_and_await: usize,
75    pub status_change: usize,
76    pub storage_read: usize,
77    pub tx_get: usize,
78    pub tx_status_read: usize,
79    pub tx_raw_payload: usize,
80    pub block_header: usize,
81    pub block_transactions: usize,
82    pub block_transactions_ids: usize,
83    pub storage_iterator: usize,
84    pub bytecode_read: usize,
85    pub state_transition_bytecode_read: usize,
86    pub da_compressed_block_read: usize,
87}
88
89#[cfg(feature = "test-helpers")]
90impl Default for Costs {
91    fn default() -> Self {
92        DEFAULT_QUERY_COSTS
93    }
94}
95
96const BALANCES_QUERY_COST_WITH_INDEXATION: usize = 0;
97const BALANCES_QUERY_COST_WITHOUT_INDEXATION: usize = 40001;
98
99pub const DEFAULT_QUERY_COSTS: Costs = Costs {
100    balance_query: BALANCES_QUERY_COST_WITH_INDEXATION,
101    coins_to_spend: 40001,
102    get_peers: 40001,
103    estimate_predicates: 40001,
104    dry_run: 12000,
105    assemble_tx: 76_000,
106    storage_read_replay: 40001,
107    submit: 40001,
108    submit_and_await: 40001,
109    status_change: 40001,
110    storage_read: 40,
111    tx_get: 200,
112    tx_status_read: 200,
113    tx_raw_payload: 600,
114    block_header: 600,
115    block_transactions: 6000,
116    block_transactions_ids: 200,
117    storage_iterator: 100,
118    bytecode_read: 8000,
119    state_transition_bytecode_read: 76_000,
120    da_compressed_block_read: 4000,
121};
122
123pub fn query_costs() -> &'static Costs {
124    QUERY_COSTS.get().unwrap_or(&DEFAULT_QUERY_COSTS)
125}
126
127pub static QUERY_COSTS: OnceLock<Costs> = OnceLock::new();
128
129#[cfg(feature = "test-helpers")]
130fn default_query_costs(balances_indexation_enabled: bool) -> Costs {
131    let mut cost = DEFAULT_QUERY_COSTS;
132
133    if !balances_indexation_enabled {
134        cost.balance_query = BALANCES_QUERY_COST_WITHOUT_INDEXATION;
135    }
136
137    cost
138}
139
140fn initialize_query_costs(
141    costs: Costs,
142    _balances_indexation_enabled: bool,
143) -> anyhow::Result<()> {
144    #[cfg(feature = "test-helpers")]
145    if costs != default_query_costs(_balances_indexation_enabled) {
146        // We don't support setting these values in test contexts, because
147        // it can lead to unexpected behavior if multiple tests try to
148        // initialize different values.
149        anyhow::bail!("cannot initialize queries with non-default costs in tests")
150    }
151
152    QUERY_COSTS.get_or_init(|| costs);
153
154    Ok(())
155}
156
157pub trait IntoApiResult<T> {
158    fn into_api_result<NewT, E>(self) -> Result<Option<NewT>, E>
159    where
160        NewT: From<T>,
161        E: From<StorageError>;
162}
163
164impl<T> IntoApiResult<T> for Result<T, StorageError> {
165    fn into_api_result<NewT, E>(self) -> Result<Option<NewT>, E>
166    where
167        NewT: From<T>,
168        E: From<StorageError>,
169    {
170        if self.is_not_found() {
171            Ok(None)
172        } else {
173            Ok(Some(self?.into()))
174        }
175    }
176}
177
178pub fn require_historical_execution(ctx: &Context<'_>) -> async_graphql::Result<()> {
179    let config = ctx.data_unchecked::<Config>();
180
181    if config.historical_execution {
182        Ok(())
183    } else {
184        Err(async_graphql::Error::new(
185            "`--historical-execution` is required for this operation",
186        ))
187    }
188}
189
190pub fn require_expensive_subscriptions(ctx: &Context<'_>) -> async_graphql::Result<()> {
191    let config = ctx.data_unchecked::<Config>();
192
193    if config.expensive_subscriptions {
194        Ok(())
195    } else {
196        Err(async_graphql::Error::new(
197            "`--expensive-subscriptions` is required for this operation",
198        ))
199    }
200}