diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9a61b3498..25948d102d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [2204](https://github.com/FuelLabs/fuel-core/pull/2204): Added `dnsaddr` resolution for TLD without suffixes. ### Changed +- [2276](https://github.com/FuelLabs/fuel-core/pull/2276): Change complexity block query to allow fetching of 10 blocks #### Breaking - [2199](https://github.com/FuelLabs/fuel-core/pull/2199): Applying several breaking changes to the WASM interface from backlog: diff --git a/bin/fuel-core/src/cli/run/graphql.rs b/bin/fuel-core/src/cli/run/graphql.rs index 66ab6f06886..422c4e202a6 100644 --- a/bin/fuel-core/src/cli/run/graphql.rs +++ b/bin/fuel-core/src/cli/run/graphql.rs @@ -17,7 +17,7 @@ pub struct GraphQLArgs { pub graphql_max_depth: usize, /// The max complexity of GraphQL queries. - #[clap(long = "graphql-max-complexity", default_value = "20000", env)] + #[clap(long = "graphql-max-complexity", default_value = "80000", env)] pub graphql_max_complexity: usize, /// The max recursive depth of GraphQL queries. diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index c3fc9abb991..fefab94b0ef 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -38,27 +38,41 @@ pub struct Costs { pub status_change: usize, pub raw_payload: usize, pub storage_read: usize, + pub tx_get: usize, + pub tx_status_read: usize, + pub tx_raw_payload: usize, + pub block_header: usize, + pub block_transactions: usize, + pub block_transactions_ids: usize, pub storage_iterator: usize, pub bytecode_read: usize, + pub state_transition_bytecode_read: usize, } pub const QUERY_COSTS: Costs = Costs { // balance_query: 4000, - balance_query: 10001, - coins_to_spend: 10001, + balance_query: 40001, + coins_to_spend: 40001, // get_peers: 2000, - get_peers: 10001, + get_peers: 40001, // estimate_predicates: 3000, - estimate_predicates: 10001, - dry_run: 3000, + estimate_predicates: 40001, + dry_run: 12000, // submit: 5000, - submit: 10001, - submit_and_await: 10001, - status_change: 10001, + submit: 40001, + submit_and_await: 40001, + status_change: 40001, raw_payload: 10, storage_read: 10, + tx_get: 50, + tx_status_read: 50, + tx_raw_payload: 150, + block_header: 150, + block_transactions: 1500, + block_transactions_ids: 50, storage_iterator: 100, - bytecode_read: 2000, + bytecode_read: 8000, + state_transition_bytecode_read: 76_000, }; #[derive(Clone, Debug)] diff --git a/crates/fuel-core/src/schema/block.rs b/crates/fuel-core/src/schema/block.rs index ee9c3e78f8c..81a9bf3c0af 100644 --- a/crates/fuel-core/src/schema/block.rs +++ b/crates/fuel-core/src/schema/block.rs @@ -125,6 +125,7 @@ impl Block { Ok(query.consensus(height)?.try_into()?) } + #[graphql(complexity = "QUERY_COSTS.block_transactions_ids")] async fn transaction_ids(&self) -> Vec { self.0 .transactions() @@ -134,8 +135,7 @@ impl Block { } // Assume that in average we have 32 transactions per block. - #[graphql(complexity = "QUERY_COSTS.storage_iterator\ - + (QUERY_COSTS.storage_read + child_complexity) * 32")] + #[graphql(complexity = "QUERY_COSTS.block_transactions + child_complexity")] async fn transactions( &self, ctx: &Context<'_>, @@ -246,7 +246,7 @@ pub struct BlockQuery; #[Object] impl BlockQuery { - #[graphql(complexity = "2 * QUERY_COSTS.storage_read + child_complexity")] + #[graphql(complexity = "QUERY_COSTS.block_header + child_complexity")] async fn block( &self, ctx: &Context<'_>, @@ -276,9 +276,8 @@ impl BlockQuery { } #[graphql(complexity = "{\ - QUERY_COSTS.storage_iterator\ - + (QUERY_COSTS.storage_read + first.unwrap_or_default() as usize) * child_complexity \ - + (QUERY_COSTS.storage_read + last.unwrap_or_default() as usize) * child_complexity\ + (QUERY_COSTS.block_header + child_complexity) \ + * (first.unwrap_or_default() as usize + last.unwrap_or_default() as usize) \ }")] async fn blocks( &self, @@ -305,7 +304,7 @@ pub struct HeaderQuery; #[Object] impl HeaderQuery { - #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] + #[graphql(complexity = "QUERY_COSTS.block_header + child_complexity")] async fn header( &self, ctx: &Context<'_>, @@ -319,9 +318,8 @@ impl HeaderQuery { } #[graphql(complexity = "{\ - QUERY_COSTS.storage_iterator\ - + (QUERY_COSTS.storage_read + first.unwrap_or_default() as usize) * child_complexity \ - + (QUERY_COSTS.storage_read + last.unwrap_or_default() as usize) * child_complexity\ + (QUERY_COSTS.block_header + child_complexity) \ + * (first.unwrap_or_default() as usize + last.unwrap_or_default() as usize) \ }")] async fn headers( &self, @@ -374,13 +372,14 @@ impl BlockMutation { start_timestamp: Option, blocks_to_produce: U32, ) -> async_graphql::Result { - let consensus_module = ctx.data_unchecked::(); let config = ctx.data_unchecked::().clone(); if !config.debug { return Err(anyhow!("`debug` must be enabled to use this endpoint").into()) } + let consensus_module = ctx.data_unchecked::(); + let start_time = start_timestamp.map(|timestamp| timestamp.0); let blocks_to_produce: u32 = blocks_to_produce.into(); consensus_module diff --git a/crates/fuel-core/src/schema/gas_price.rs b/crates/fuel-core/src/schema/gas_price.rs index 577d68cec9e..f1ded5fc106 100644 --- a/crates/fuel-core/src/schema/gas_price.rs +++ b/crates/fuel-core/src/schema/gas_price.rs @@ -46,7 +46,7 @@ pub struct LatestGasPriceQuery {} #[Object] impl LatestGasPriceQuery { - #[graphql(complexity = "2 * QUERY_COSTS.storage_read")] + #[graphql(complexity = "QUERY_COSTS.block_header")] async fn latest_gas_price( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index ce860a97084..6785c1f79df 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -111,10 +111,10 @@ impl TxQuery { } } + // We assume that each block has 100 transactions. #[graphql(complexity = "{\ - QUERY_COSTS.storage_iterator\ - + (QUERY_COSTS.storage_read + first.unwrap_or_default() as usize) * child_complexity \ - + (QUERY_COSTS.storage_read + last.unwrap_or_default() as usize) * child_complexity\ + (QUERY_COSTS.tx_get + child_complexity) \ + * (first.unwrap_or_default() as usize + last.unwrap_or_default() as usize) }")] async fn transactions( &self, diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index 9496a5bda22..b7394fd1751 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -182,7 +182,7 @@ impl SuccessStatus { self.block_height.into() } - #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] + #[graphql(complexity = "QUERY_COSTS.block_header + child_complexity")] async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; let block = query.block(&self.block_height)?; @@ -238,7 +238,7 @@ impl FailureStatus { self.block_height.into() } - #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] + #[graphql(complexity = "QUERY_COSTS.block_header + child_complexity")] async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; let block = query.block(&self.block_height)?; @@ -693,7 +693,7 @@ impl Transaction { } } - #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] + #[graphql(complexity = "QUERY_COSTS.tx_status_read + child_complexity")] async fn status( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/upgrades.rs b/crates/fuel-core/src/schema/upgrades.rs index bda98240304..d9e62906a1e 100644 --- a/crates/fuel-core/src/schema/upgrades.rs +++ b/crates/fuel-core/src/schema/upgrades.rs @@ -74,6 +74,7 @@ impl StateTransitionBytecode { HexString(self.root.to_vec()) } + #[graphql(complexity = "QUERY_COSTS.state_transition_bytecode_read")] async fn bytecode( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index 092909929a1..83ae8002143 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -134,7 +134,7 @@ impl Config { 0, ), max_queries_depth: 16, - max_queries_complexity: 20000, + max_queries_complexity: 80000, max_queries_recursive_depth: 16, request_body_bytes_limit: 16 * 1024 * 1024, query_log_threshold_time: Duration::from_secs(2), diff --git a/tests/tests/dos.rs b/tests/tests/dos.rs index e038c70d297..1bfc527555c 100644 --- a/tests/tests/dos.rs +++ b/tests/tests/dos.rs @@ -5,6 +5,7 @@ use fuel_core::service::{ FuelService, ServiceTrait, }; +use fuel_core_types::blockchain::header::LATEST_STATE_TRANSITION_VERSION; use test_helpers::send_graph_ql_query; #[tokio::test] @@ -78,37 +79,222 @@ async fn complex_queries__recursion() { assert!(result.contains("The recursion depth of the query cannot be greater than")); } -#[tokio::test] -async fn complex_queries__10_blocks__works() { - let query = r#" - query { - blocks(first: 10) { - nodes { - transactions { - id +const FULL_BLOCK_QUERY: &str = r#" + query { + blocks(first: $NUMBER_OF_BLOCKS) { + edges { + cursor + node { + id + header { + id + daHeight + consensusParametersVersion + stateTransitionBytecodeVersion + transactionsCount + messageReceiptCount + transactionsRoot + messageOutboxRoot + eventInboxRoot + height + prevRoot + time + applicationHash + } + consensus { + ... on Genesis { + chainConfigHash + coinsRoot + contractsRoot + messagesRoot + transactionsRoot + } + ... on PoAConsensus { + signature + } + } + transactions { + rawPayload + status { + ... on SubmittedStatus { + time + } + ... on SuccessStatus { + transactionId + block { + height + } + time + programState { + returnType + data + } + receipts { + param1 + param2 + amount + assetId + gas + digest + id + is + pc + ptr + ra + rb + rc + rd + reason + receiptType + to + toAddress + val + len + result + gasUsed + data + sender + recipient + nonce + contractId + subId + } + totalGas + totalFee + } + ... on SqueezedOutStatus { + reason + } + ... on FailureStatus { + transactionId + blockHeight + time + reason + programState { + returnType + data + } + receipts { + param1 + param2 + amount + assetId + gas + digest + id + is + pc + ptr + ra + rb + rc + rd + reason + receiptType + to + toAddress + val + len + result + gasUsed + data + sender + recipient + nonce + contractId + subId + } + totalGas + totalFee + } } } } } - "#; + pageInfo { + endCursor + hasNextPage + hasPreviousPage + startCursor + } + } + } +"#; + +#[tokio::test] +async fn complex_queries__40_full_blocks__works() { + let query = FULL_BLOCK_QUERY.to_string(); + let query = query.replace("$NUMBER_OF_BLOCKS", "40"); let node = FuelService::new_node(Config::local_node()).await.unwrap(); let url = format!("http://{}/v1/graphql", node.bound_address); - let result = send_graph_ql_query(&url, query).await; + let result = send_graph_ql_query(&url, query.as_str()).await; assert!(result.contains("transactions")); } #[tokio::test] -async fn complex_queries__50_block__query_to_complex() { +async fn complex_queries__41_full_block__query_to_complex() { + let query = FULL_BLOCK_QUERY.to_string(); + let query = query.replace("$NUMBER_OF_BLOCKS", "41"); + + let node = FuelService::new_node(Config::local_node()).await.unwrap(); + let url = format!("http://{}/v1/graphql", node.bound_address); + + let result = send_graph_ql_query(&url, query.as_str()).await; + assert!(result.contains("Query is too complex.")); +} + +#[tokio::test] +async fn complex_queries__100_block_headers__works() { let query = r#" query { - blocks(first: 50) { - nodes { - transactions { + blocks(first: 100) { + edges { + cursor + node { id + header { + id + daHeight + consensusParametersVersion + stateTransitionBytecodeVersion + transactionsCount + messageReceiptCount + transactionsRoot + messageOutboxRoot + eventInboxRoot + height + prevRoot + time + applicationHash + } + consensus { + ... on Genesis { + chainConfigHash + coinsRoot + contractsRoot + messagesRoot + transactionsRoot + } + ... on PoAConsensus { + signature + } + } } } + pageInfo { + endCursor + hasNextPage + hasPreviousPage + startCursor + } + pageInfo { + endCursor + hasNextPage + hasPreviousPage + startCursor + } } } "#; @@ -117,7 +303,8 @@ async fn complex_queries__50_block__query_to_complex() { let url = format!("http://{}/v1/graphql", node.bound_address); let result = send_graph_ql_query(&url, query).await; - assert!(result.contains("Query is too complex.")); + dbg!(&result); + assert!(result.contains("transactions")); } #[tokio::test] @@ -140,3 +327,56 @@ async fn body_limit_prevents_from_huge_queries() { let result = response.unwrap(); assert_eq!(result.status(), 413); } + +#[tokio::test] +async fn complex_queries__1_state_transition_bytecode__works() { + let version = LATEST_STATE_TRANSITION_VERSION; + let query = r#" + query { + stateTransitionBytecodeByVersion(version: $VERSION) { + bytecode { + uploadedSubsectionsNumber + bytecode + completed + } + } + }"# + .to_string(); + let query = query.replace("$VERSION", version.to_string().as_str()); + + let node = FuelService::new_node(Config::local_node()).await.unwrap(); + let url = format!("http://{}/v1/graphql", node.bound_address); + + let result = send_graph_ql_query(&url, query.as_str()).await; + assert!(result.contains("bytecode"), "{}", result); +} + +#[tokio::test] +async fn complex_queries__2_state_transition_bytecode__query_to_complex() { + let version = LATEST_STATE_TRANSITION_VERSION; + let query = r#" + query { + stateTransitionBytecodeByVersion(version: $VERSION) { + bytecode { + uploadedSubsectionsNumber + bytecode + completed + } + } + stateTransitionBytecodeByVersion(version: $VERSION) { + bytecode { + uploadedSubsectionsNumber + bytecode + completed + } + } + }"# + .to_string(); + let query = query.replace("$VERSION", version.to_string().as_str()); + + let node = FuelService::new_node(Config::local_node()).await.unwrap(); + let url = format!("http://{}/v1/graphql", node.bound_address); + + let result = send_graph_ql_query(&url, query.as_str()).await; + assert!(result.contains("Query is too complex.")); +} diff --git a/tests/tests/regenesis.rs b/tests/tests/regenesis.rs index 94d67d241ff..3ba03d27366 100644 --- a/tests/tests/regenesis.rs +++ b/tests/tests/regenesis.rs @@ -66,6 +66,7 @@ async fn take_snapshot(db_dir: &TempDir, snapshot_dir: &TempDir) -> anyhow::Resu #[tokio::test(flavor = "multi_thread")] async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { + const BLOCKS_QUERY: i32 = 50; let mut rng = StdRng::seed_from_u64(1234); let core = @@ -77,7 +78,7 @@ async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { .client .blocks(PaginationRequest { cursor: None, - results: 100, + results: BLOCKS_QUERY, direction: PageDirection::Forward, }) .await @@ -112,7 +113,7 @@ async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { .client .blocks(PaginationRequest { cursor: None, - results: 100, + results: BLOCKS_QUERY, direction: PageDirection::Forward, }) .await @@ -150,7 +151,7 @@ async fn test_regenesis_old_blocks_are_preserved() -> anyhow::Result<()> { .client .blocks(PaginationRequest { cursor: None, - results: 100, + results: BLOCKS_QUERY, direction: PageDirection::Forward, }) .await