Skip to content

Commit

Permalink
Reject queries that are recursive during the resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
xgreenx committed Oct 10, 2024
1 parent 78a3750 commit 238cb36
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 19 deletions.
2 changes: 2 additions & 0 deletions bin/fuel-core/src/cli/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,8 @@ impl Command {
max_queries_depth: graphql.graphql_max_depth,
max_queries_complexity: graphql.graphql_max_complexity,
max_queries_recursive_depth: graphql.graphql_max_recursive_depth,
max_queries_resolver_recursive_depth: graphql
.max_queries_resolver_recursive_depth,
max_queries_directives: graphql.max_queries_directives,
max_concurrent_queries: graphql.graphql_max_concurrent_queries,
request_body_bytes_limit: graphql.graphql_request_body_bytes_limit,
Expand Down
8 changes: 8 additions & 0 deletions bin/fuel-core/src/cli/run/graphql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ pub struct GraphQLArgs {
#[clap(long = "graphql-max-recursive-depth", default_value = "16", env)]
pub graphql_max_recursive_depth: usize,

/// The max resolver recursive depth of GraphQL queries.
#[clap(
long = "graphql-max-resolver-recursive-depth",
default_value = "1",
env
)]
pub max_queries_resolver_recursive_depth: usize,

/// The max number of directives in the query.
#[clap(long = "graphql-max-directives", default_value = "10", env)]
pub max_queries_directives: usize,
Expand Down
28 changes: 28 additions & 0 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::client::{
},
upgrades::StateTransitionBytecode,
RelayedTransactionStatus,
StatusWithTransaction,
},
};
use anyhow::Context;
Expand Down Expand Up @@ -549,6 +550,33 @@ impl FuelClient {
Ok(status)
}

/// Similar to [`submit_and_await_commit`], but the status also contains transaction.
#[cfg(feature = "subscriptions")]
pub async fn submit_and_await_commit_with_tx(
&self,
tx: &Transaction,
) -> io::Result<StatusWithTransaction> {
use cynic::SubscriptionBuilder;
let tx = tx.clone().to_bytes();
let s = schema::tx::SubmitAndAwaitSubscriptionWithTransaction::build(TxArg {
tx: HexString(Bytes(tx)),
});

let mut stream = self.subscribe(s).await?.map(
|r: io::Result<schema::tx::SubmitAndAwaitSubscriptionWithTransaction>| {
let status: StatusWithTransaction = r?.submit_and_await.try_into()?;
Result::<_, io::Error>::Ok(status)
},
);

let status = stream.next().await.ok_or(io::Error::new(
io::ErrorKind::Other,
"Failed to get status from the submission",
))??;

Ok(status)
}

/// Submits the transaction to the `TxPool` and returns a stream of events.
/// Compared to the `submit_and_await_commit`, the stream also contains
/// `SubmittedStatus` as an intermediate state.
Expand Down
51 changes: 49 additions & 2 deletions crates/client/src/client/schema/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,21 @@ pub enum TransactionStatus {
Unknown,
}

#[allow(clippy::enum_variant_names)]
#[derive(cynic::InlineFragments, Clone, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "TransactionStatus"
)]
pub enum StatusWithTransaction {
SubmittedStatus(SubmittedStatus),
SuccessStatus(SuccessStatusWithTransaction),
SqueezedOutStatus(SqueezedOutStatus),
FailureStatus(FailureStatusWithTransaction),
#[cynic(fallback)]
Unknown,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct SubmittedStatus {
Expand All @@ -202,7 +217,17 @@ pub struct SubmittedStatus {
#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct SuccessStatus {
#[cfg(feature = "test-helpers")]
pub block_height: U32,
pub time: Tai64Timestamp,
pub program_state: Option<ProgramState>,
pub receipts: Vec<Receipt>,
pub total_gas: U64,
pub total_fee: U64,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl", graphql_type = "SuccessStatus")]
pub struct SuccessStatusWithTransaction {
pub transaction: OpaqueTransaction,
pub block_height: U32,
pub time: Tai64Timestamp,
Expand All @@ -215,7 +240,18 @@ pub struct SuccessStatus {
#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl")]
pub struct FailureStatus {
#[cfg(feature = "test-helpers")]
pub block_height: U32,
pub time: Tai64Timestamp,
pub reason: String,
pub program_state: Option<ProgramState>,
pub receipts: Vec<Receipt>,
pub total_gas: U64,
pub total_fee: U64,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(schema_path = "./assets/schema.sdl", graphql_type = "FailureStatus")]
pub struct FailureStatusWithTransaction {
pub transaction: OpaqueTransaction,
pub block_height: U32,
pub time: Tai64Timestamp,
Expand Down Expand Up @@ -432,6 +468,17 @@ pub struct SubmitAndAwaitSubscription {
pub submit_and_await: TransactionStatus,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
graphql_type = "Subscription",
variables = "TxArg"
)]
pub struct SubmitAndAwaitSubscriptionWithTransaction {
#[arguments(tx: $tx)]
pub submit_and_await: StatusWithTransaction,
}

#[derive(cynic::QueryFragment, Clone, Debug)]
#[cynic(
schema_path = "./assets/schema.sdl",
Expand Down
88 changes: 80 additions & 8 deletions crates/client/src/client/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use crate::client::schema::{
relayed_tx::RelayedTransactionStatus as SchemaRelayedTransactionStatus,
tx::{
OpaqueTransactionWithStatus,
StatusWithTransaction as SchemaStatusWithTx,
TransactionStatus as SchemaTxStatus,
},
ConversionError,
Expand Down Expand Up @@ -94,14 +95,17 @@ pub struct TransactionResponse {
pub status: TransactionStatus,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct StatusWithTransactionResponse {
pub status: StatusWithTransaction,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TransactionStatus {
Submitted {
submitted_at: Tai64,
},
Success {
#[cfg(feature = "test-helpers")]
transaction: Transaction,
block_height: BlockHeight,
time: Tai64,
program_state: Option<ProgramState>,
Expand All @@ -113,8 +117,6 @@ pub enum TransactionStatus {
reason: String,
},
Failure {
#[cfg(feature = "test-helpers")]
transaction: Transaction,
block_height: BlockHeight,
time: Tai64,
reason: String,
Expand All @@ -134,8 +136,6 @@ impl TryFrom<SchemaTxStatus> for TransactionStatus {
submitted_at: s.time.0,
},
SchemaTxStatus::SuccessStatus(s) => TransactionStatus::Success {
#[cfg(feature = "test-helpers")]
transaction: s.transaction.try_into()?,
block_height: s.block_height.into(),
time: s.time.0,
program_state: s.program_state.map(TryInto::try_into).transpose()?,
Expand All @@ -148,8 +148,6 @@ impl TryFrom<SchemaTxStatus> for TransactionStatus {
total_fee: s.total_fee.0,
},
SchemaTxStatus::FailureStatus(s) => TransactionStatus::Failure {
#[cfg(feature = "test-helpers")]
transaction: s.transaction.try_into()?,
block_height: s.block_height.into(),
time: s.time.0,
reason: s.reason,
Expand All @@ -172,6 +170,80 @@ impl TryFrom<SchemaTxStatus> for TransactionStatus {
}
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum StatusWithTransaction {
Submitted {
submitted_at: Tai64,
},
Success {
transaction: Transaction,
block_height: BlockHeight,
time: Tai64,
program_state: Option<ProgramState>,
receipts: Vec<Receipt>,
total_gas: u64,
total_fee: u64,
},
SqueezedOut {
reason: String,
},
Failure {
transaction: Transaction,
block_height: BlockHeight,
time: Tai64,
reason: String,
program_state: Option<ProgramState>,
receipts: Vec<Receipt>,
total_gas: u64,
total_fee: u64,
},
}

impl TryFrom<SchemaStatusWithTx> for StatusWithTransaction {
type Error = ConversionError;

fn try_from(status: SchemaStatusWithTx) -> Result<Self, Self::Error> {
Ok(match status {
SchemaStatusWithTx::SubmittedStatus(s) => StatusWithTransaction::Submitted {
submitted_at: s.time.0,
},
SchemaStatusWithTx::SuccessStatus(s) => StatusWithTransaction::Success {
transaction: s.transaction.try_into()?,
block_height: s.block_height.into(),
time: s.time.0,
program_state: s.program_state.map(TryInto::try_into).transpose()?,
receipts: s
.receipts
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
total_gas: s.total_gas.0,
total_fee: s.total_fee.0,
},
SchemaStatusWithTx::FailureStatus(s) => StatusWithTransaction::Failure {
transaction: s.transaction.try_into()?,
block_height: s.block_height.into(),
time: s.time.0,
reason: s.reason,
program_state: s.program_state.map(TryInto::try_into).transpose()?,
receipts: s
.receipts
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, _>>()?,
total_gas: s.total_gas.0,
total_fee: s.total_fee.0,
},
SchemaStatusWithTx::SqueezedOutStatus(s) => {
StatusWithTransaction::SqueezedOut { reason: s.reason }
}
SchemaStatusWithTx::Unknown => {
return Err(Self::Error::UnknownVariant("SchemaTxStatus"))
}
})
}
}

impl TryFrom<OpaqueTransactionWithStatus> for TransactionResponse {
type Error = ConversionError;

Expand Down
1 change: 1 addition & 0 deletions crates/fuel-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ hyper = { workspace = true }
indicatif = { workspace = true, default-features = true }
itertools = { workspace = true }
num_cpus = { version = "1.16.0", optional = true }
parking_lot = { workspace = true }
paste = { workspace = true }
postcard = { workspace = true, optional = true }
rand = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/fuel-core/src/graphql_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod database;
pub(crate) mod metrics_extension;
pub mod ports;
pub mod storage;
pub(crate) mod validation_extension;
pub(crate) mod view_extension;
pub mod worker_service;

Expand All @@ -22,6 +23,7 @@ pub struct ServiceConfig {
pub max_queries_depth: usize,
pub max_queries_complexity: usize,
pub max_queries_recursive_depth: usize,
pub max_queries_resolver_recursive_depth: usize,
pub max_queries_directives: usize,
pub max_concurrent_queries: usize,
pub request_body_bytes_limit: usize,
Expand Down
6 changes: 6 additions & 0 deletions crates/fuel-core/src/graphql_api/api_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
P2pPort,
TxPoolPort,
},
validation_extension::ValidationExtension,
view_extension::ViewExtension,
Config,
},
Expand Down Expand Up @@ -225,6 +226,8 @@ where
let request_timeout = config.config.api_request_timeout;
let concurrency_limit = config.config.max_concurrent_queries;
let body_limit = config.config.request_body_bytes_limit;
let max_queries_resolver_recursive_depth =
config.config.max_queries_resolver_recursive_depth;

let schema = schema
.limit_complexity(config.config.max_queries_complexity)
Expand All @@ -243,6 +246,9 @@ where
.data(gas_price_provider)
.data(consensus_parameters_provider)
.data(memory_pool)
.extension(ValidationExtension::new(
max_queries_resolver_recursive_depth,
))
.extension(async_graphql::extensions::Tracing)
.extension(ViewExtension::new())
.finish();
Expand Down
Loading

0 comments on commit 238cb36

Please sign in to comment.