Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CLI arguments for configuring GraphQL query costs. #2335

4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Changed

- [2334](https://github.com/FuelLabs/fuel-core/pull/2334): Prepare the GraphQL service for the switching to `async` methods.

### Added
- [2335](https://github.com/FuelLabs/fuel-core/pull/2335): Added CLI arguments for configuring GraphQL query costs.

## [Version 0.39.0]

### Added
Expand Down
24 changes: 24 additions & 0 deletions bin/fuel-core/src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use fuel_core::{
},
fuel_core_graphql_api::{
worker_service::DaCompressionConfig,
Costs,
ServiceConfig as GraphQLConfig,
},
producer::Config as ProducerConfig,
Expand Down Expand Up @@ -498,6 +499,29 @@ impl Command {
request_body_bytes_limit: graphql.graphql_request_body_bytes_limit,
api_request_timeout: graphql.api_request_timeout.into(),
query_log_threshold_time: graphql.query_log_threshold_time.into(),
costs: Costs {
balance_query: graphql.costs.balance_query,
coins_to_spend: graphql.costs.coins_to_spend,
get_peers: graphql.costs.get_peers,
estimate_predicates: graphql.costs.estimate_predicates,
dry_run: graphql.costs.dry_run,
submit: graphql.costs.submit,
submit_and_await: graphql.costs.submit_and_await,
status_change: graphql.costs.status_change,
storage_read: graphql.costs.storage_read,
tx_get: graphql.costs.tx_get,
tx_status_read: graphql.costs.tx_status_read,
tx_raw_payload: graphql.costs.tx_raw_payload,
block_header: graphql.costs.block_header,
block_transactions: graphql.costs.block_transactions,
block_transactions_ids: graphql.costs.block_transactions_ids,
storage_iterator: graphql.costs.storage_iterator,
bytecode_read: graphql.costs.bytecode_read,
state_transition_bytecode_read: graphql
.costs
.state_transition_bytecode_read,
da_compressed_block_read: graphql.costs.da_compressed_block_read,
},
},
combined_db_config,
snapshot_reader,
Expand Down
160 changes: 160 additions & 0 deletions bin/fuel-core/src/cli/run/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use std::net;

use fuel_core::fuel_core_graphql_api::DEFAULT_QUERY_COSTS;

#[derive(Debug, Clone, clap::Args)]
pub struct GraphQLArgs {
/// The IP address to bind the GraphQL service to.
Expand Down Expand Up @@ -55,4 +57,162 @@ pub struct GraphQLArgs {
/// Timeout before drop the request.
#[clap(long = "api-request-timeout", default_value = "30s", env)]
pub api_request_timeout: humantime::Duration,

#[clap(flatten)]
pub costs: QueryCosts,
}

/// Costs for individual graphql queries.
#[derive(Debug, Clone, clap::Args)]
pub struct QueryCosts {
/// Query costs for getting balances.
#[clap(
long = "query-cost-balance-query",
default_value = DEFAULT_QUERY_COSTS.balance_query.to_string(),
env
)]
pub balance_query: usize,

/// Query costs for getting coins to spend.
#[clap(
long = "query-cost-coins-to-spend",
default_value = DEFAULT_QUERY_COSTS.coins_to_spend.to_string(),
env)]
pub coins_to_spend: usize,

/// Query costs for getting peers.
#[clap(
long = "query-cost-get-peers",
default_value = DEFAULT_QUERY_COSTS.get_peers.to_string(),
env
)]
pub get_peers: usize,

/// Query costs for estimating predicates.
#[clap(
long = "query-cost-estimate-predicates",
default_value = DEFAULT_QUERY_COSTS.estimate_predicates.to_string(),
env
)]
pub estimate_predicates: usize,

/// Query costs for dry running a set of transactions.
#[clap(
long = "query-cost-dry-run",
default_value = DEFAULT_QUERY_COSTS.dry_run.to_string(),
env
)]
pub dry_run: usize,

/// Query costs for submitting a transaction.
#[clap(
long = "query-cost-submit",
default_value = DEFAULT_QUERY_COSTS.submit.to_string(),
env
)]
pub submit: usize,

/// Query costs for submitting and awaiting a transaction.
#[clap(
long = "query-cost-submit-and-await",
default_value = DEFAULT_QUERY_COSTS.submit_and_await.to_string(),
env
)]
pub submit_and_await: usize,

/// Query costs for the status change query.
#[clap(
long = "query-cost-status-change",
default_value = DEFAULT_QUERY_COSTS.status_change.to_string(),
env
)]
pub status_change: usize,

/// Query costs for reading from storage.
#[clap(
long = "query-cost-storage-read",
default_value = DEFAULT_QUERY_COSTS.storage_read.to_string(),
env
)]
pub storage_read: usize,

/// Query costs for getting a transaction.
#[clap(
long = "query-cost-tx-get",
default_value = DEFAULT_QUERY_COSTS.tx_get.to_string(),
env
)]
pub tx_get: usize,

/// Query costs for reading tx status.
#[clap(
long = "query-cost-tx-status-read",
default_value = DEFAULT_QUERY_COSTS.tx_status_read.to_string(),
env
)]
pub tx_status_read: usize,

/// Query costs for getting the raw tx payload.
#[clap(
long = "query-cost-tx-raw-payload",
default_value = DEFAULT_QUERY_COSTS.tx_raw_payload.to_string(),
env
)]
pub tx_raw_payload: usize,

/// Query costs for block header.
#[clap(
long = "query-cost-block-header",
default_value = DEFAULT_QUERY_COSTS.block_header.to_string(),
env
)]
pub block_header: usize,

/// Query costs for block transactions.
#[clap(
long = "query-cost-block-transactions",
default_value = DEFAULT_QUERY_COSTS.block_transactions.to_string(),
env
)]
pub block_transactions: usize,

/// Query costs for block transactions ids.
#[clap(
long = "query-cost-block-transactions-ids",
default_value = DEFAULT_QUERY_COSTS.block_transactions_ids.to_string(),
env
)]
pub block_transactions_ids: usize,

/// Query costs for iterating over storage entries.
#[clap(
long = "query-cost-storage-iterator",
default_value = DEFAULT_QUERY_COSTS.storage_iterator.to_string(),
env
)]
pub storage_iterator: usize,

/// Query costs for reading bytecode.
#[clap(
long = "query-cost-bytecode-read",
default_value = DEFAULT_QUERY_COSTS.bytecode_read.to_string(),
env
)]
pub bytecode_read: usize,

/// Query costs for reading state transition bytecode.
#[clap(
long = "query-cost-state-transition-bytecode-read",
default_value = DEFAULT_QUERY_COSTS.state_transition_bytecode_read.to_string(),
env
)]
pub state_transition_bytecode_read: usize,

/// Query costs for reading a DA compressed block.
#[clap(
long = "query-cost-da-compressed-block-read",
default_value = DEFAULT_QUERY_COSTS.da_compressed_block_read.to_string(),
env
)]
pub da_compressed_block_read: usize,
}
55 changes: 41 additions & 14 deletions crates/fuel-core/src/graphql_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use fuel_core_storage::{
};
use std::{
net::SocketAddr,
sync::OnceLock,
time::Duration,
};

Expand All @@ -17,6 +18,17 @@ pub(crate) mod validation_extension;
pub(crate) mod view_extension;
pub mod worker_service;

#[derive(Clone, Debug)]
pub struct Config {
pub config: ServiceConfig,
pub utxo_validation: bool,
pub debug: bool,
pub vm_backtrace: bool,
pub max_tx: usize,
pub max_txpool_dependency_chain_length: usize,
pub chain_name: String,
}

#[derive(Clone, Debug)]
pub struct ServiceConfig {
pub addr: SocketAddr,
Expand All @@ -30,8 +42,11 @@ pub struct ServiceConfig {
/// Time to wait after submitting a query before debug info will be logged about query.
pub query_log_threshold_time: Duration,
pub api_request_timeout: Duration,
/// Configurable cost parameters to limit graphql queries complexity
pub costs: Costs,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Costs {
pub balance_query: usize,
pub coins_to_spend: usize,
Expand All @@ -54,16 +69,19 @@ pub struct Costs {
pub da_compressed_block_read: usize,
}

pub const QUERY_COSTS: Costs = Costs {
// balance_query: 4000,
#[cfg(feature = "test-helpers")]
impl Default for Costs {
netrome marked this conversation as resolved.
Show resolved Hide resolved
fn default() -> Self {
DEFAULT_QUERY_COSTS
}
}

pub const DEFAULT_QUERY_COSTS: Costs = Costs {
balance_query: 40001,
coins_to_spend: 40001,
// get_peers: 2000,
get_peers: 40001,
// estimate_predicates: 3000,
estimate_predicates: 40001,
dry_run: 12000,
// submit: 5000,
submit: 40001,
submit_and_await: 40001,
status_change: 40001,
Expand All @@ -80,15 +98,24 @@ pub const QUERY_COSTS: Costs = Costs {
da_compressed_block_read: 4000,
};

#[derive(Clone, Debug)]
pub struct Config {
pub config: ServiceConfig,
pub utxo_validation: bool,
pub debug: bool,
pub vm_backtrace: bool,
pub max_tx: usize,
pub max_txpool_dependency_chain_length: usize,
pub chain_name: String,
pub fn query_costs() -> &'static Costs {
QUERY_COSTS.get().unwrap_or(&DEFAULT_QUERY_COSTS)
}

pub static QUERY_COSTS: OnceLock<Costs> = OnceLock::new();

fn initialize_query_costs(costs: Costs) -> anyhow::Result<()> {
#[cfg(feature = "test-helpers")]
if costs != DEFAULT_QUERY_COSTS {
// We don't support setting these values in test contexts, because
// it can lead to unexpected behavior if multiple tests try to
// initialize different values.
anyhow::bail!("cannot initialize queries with non-default costs in tests")
}

QUERY_COSTS.get_or_init(|| costs);

Ok(())
}

pub trait IntoApiResult<T> {
Expand Down
3 changes: 3 additions & 0 deletions crates/fuel-core/src/graphql_api/api_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
view_extension::ViewExtension,
Config,
},
graphql_api,
schema::{
CoreSchema,
CoreSchemaBuilder,
Expand Down Expand Up @@ -220,6 +221,8 @@ where
OnChain::LatestView: OnChainDatabase,
OffChain::LatestView: OffChainDatabase,
{
graphql_api::initialize_query_costs(config.config.costs.clone())?;
netrome marked this conversation as resolved.
Show resolved Hide resolved

let network_addr = config.config.addr;
let combined_read_database =
ReadDatabase::new(genesis_block_height, on_database, off_database);
Expand Down
6 changes: 3 additions & 3 deletions crates/fuel-core/src/schema/balance.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
fuel_core_graphql_api::{
api_service::ConsensusProvider,
QUERY_COSTS,
query_costs,
},
schema::{
scalars::{
Expand Down Expand Up @@ -52,7 +52,7 @@ pub struct BalanceQuery;

#[Object]
impl BalanceQuery {
#[graphql(complexity = "QUERY_COSTS.balance_query")]
#[graphql(complexity = "query_costs().balance_query")]
async fn balance(
&self,
ctx: &Context<'_>,
Expand All @@ -70,7 +70,7 @@ impl BalanceQuery {

// TODO: This API should be migrated to the indexer for better support and
// discontinued within fuel-core.
#[graphql(complexity = "QUERY_COSTS.balance_query")]
#[graphql(complexity = "query_costs().balance_query")]
async fn balances(
&self,
ctx: &Context<'_>,
Expand Down
6 changes: 3 additions & 3 deletions crates/fuel-core/src/schema/blob.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
fuel_core_graphql_api::QUERY_COSTS,
fuel_core_graphql_api::query_costs,
graphql_api::IntoApiResult,
schema::{
scalars::{
Expand Down Expand Up @@ -27,7 +27,7 @@ impl Blob {
self.0.into()
}

#[graphql(complexity = "QUERY_COSTS.bytecode_read")]
#[graphql(complexity = "query_costs().bytecode_read")]
async fn bytecode(&self, ctx: &Context<'_>) -> async_graphql::Result<HexString> {
let query = ctx.read_view()?;
query
Expand All @@ -48,7 +48,7 @@ pub struct BlobQuery;

#[Object]
impl BlobQuery {
#[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")]
#[graphql(complexity = "query_costs().storage_read + child_complexity")]
async fn blob(
&self,
ctx: &Context<'_>,
Expand Down
Loading
Loading