diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca81b89da7..137cab89ea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,14 +34,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Version 0.29.0] ### Added +- [#1676](https://github.com/FuelLabs/fuel-core/pull/1676): Added new CLI arguments: + - `graphql-max-depth` + - `graphql-max-complexity` + - `graphql-max-recursive-depth` - [#1889](https://github.com/FuelLabs/fuel-core/pull/1889): Add new `FuelGasPriceProvider` that receives the gas price algorithm from a `GasPriceService` ### Changed +- [#1676](https://github.com/FuelLabs/fuel-core/pull/1676): Changed default value for `api-request-timeout` to be `30s`. - [#1942](https://github.com/FuelLabs/fuel-core/pull/1942): Sequential relayer's commits. - [#1952](https://github.com/FuelLabs/fuel-core/pull/1952): Change tip sorting to ratio between tip and max gas sorting in txpool - [#1960](https://github.com/FuelLabs/fuel-core/pull/1960): Update fuel-vm to v0.53.0. - [#1964](https://github.com/FuelLabs/fuel-core/pull/1964): Add `creation_instant` as second sort key in tx pool +#### Breaking + +- [#1676](https://github.com/FuelLabs/fuel-core/pull/1676): Now, GraphQL API has complexity and depth limitations on the queries. The default complexity limit is `20000`. It is ~50 blocks per request with transaction IDs and ~2-5 full blocks. +- [#1676](https://github.com/FuelLabs/fuel-core/pull/1676): New `fuel-core-client` is incompatible with the old `fuel-core` because of two requested new fields. + ### Fixed - [#1962](https://github.com/FuelLabs/fuel-core/pull/1962): Fixes the error message for incorrect keypair's path. - [#1950](https://github.com/FuelLabs/fuel-core/pull/1950): Fix cursor `BlockHeight` encoding in `SortedTXCursor` diff --git a/Cargo.lock b/Cargo.lock index 771cc2084fd..1ddcd391ffb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,21 +337,21 @@ dependencies = [ [[package]] name = "async-graphql" -version = "4.0.16" +version = "7.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ed522678d412d77effe47b3c82314ac36952a35e6e852093dd48287c421f80" +checksum = "bf338d20ba5bab309f55ce8df95d65ee19446f7737f06f4a64593ab2c6b546ad" dependencies = [ "async-graphql-derive", "async-graphql-parser", "async-graphql-value", "async-stream", "async-trait", - "base64 0.13.1", + "base64 0.22.1", "bytes", "fnv", "futures-util", - "http", - "indexmap 1.9.3", + "http 1.1.0", + "indexmap 2.2.6", "mime", "multer", "num-traits", @@ -361,8 +361,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "static_assertions", - "tempfile", + "static_assertions_next", "thiserror", "tracing", "tracing-futures", @@ -370,25 +369,26 @@ dependencies = [ [[package]] name = "async-graphql-derive" -version = "4.0.16" +version = "7.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c121a894495d7d3fc3d4e15e0a9843e422e4d1d9e3c514d8062a1c94b35b005d" +checksum = "fc51fd6b7102acda72bc94e8ae1543844d5688ff394a6cf7c21f2a07fe2d64e4" dependencies = [ "Inflector", "async-graphql-parser", - "darling 0.14.4", - "proc-macro-crate 1.3.1", + "darling 0.20.9", + "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "strum 0.26.2", + "syn 2.0.67", "thiserror", ] [[package]] name = "async-graphql-parser" -version = "4.0.16" +version = "7.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b6c386f398145c6180206c1869c2279f5a3d45db5be4e0266148c6ac5c6ad68" +checksum = "75361eefd64e39f89bead4cb45fddbaf60ddb0e7b15fb7c852b6088bcd63071f" dependencies = [ "async-graphql-value", "pest", @@ -398,12 +398,12 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "4.0.16" +version = "7.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a941b499fead4a3fb5392cabf42446566d18c86313f69f2deab69560394d65f" +checksum = "c1f665d2d52b41c4ed1f01c43f3ef27a2fe0af2452ed5c8bc7ac9b1a8719afaa" dependencies = [ "bytes", - "indexmap 1.9.3", + "indexmap 2.2.6", "serde", "serde_json", ] @@ -636,7 +636,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" dependencies = [ - "http", + "http 0.2.12", "log", "url", ] @@ -680,7 +680,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", - "http", + "http 0.2.12", "http-body", "hyper", "itoa", @@ -709,7 +709,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", + "http 0.2.12", "http-body", "mime", "tower-layer", @@ -1806,16 +1806,6 @@ dependencies = [ "darling_macro 0.13.4", ] -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - [[package]] name = "darling" version = "0.20.9" @@ -1840,20 +1830,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", -] - [[package]] name = "darling_core" version = "0.20.9" @@ -1879,17 +1855,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.9" @@ -2548,7 +2513,7 @@ dependencies = [ "futures-timer", "futures-util", "hashers", - "http", + "http 0.2.12", "instant", "jsonwebtoken", "once_cell", @@ -3842,7 +3807,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap 2.2.6", "slab", "tokio", @@ -4082,6 +4047,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -4089,7 +4065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -4138,7 +4114,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -4158,7 +4134,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", + "http 0.2.12", "hyper", "log", "rustls 0.21.12", @@ -4279,7 +4255,7 @@ dependencies = [ "attohttpc", "bytes", "futures", - "http", + "http 0.2.12", "hyper", "log", "rand", @@ -5474,16 +5450,15 @@ dependencies = [ [[package]] name = "multer" -version = "2.1.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.1.0", "httparse", - "log", "memchr", "mime", "spin 0.9.8", @@ -5710,7 +5685,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", @@ -5830,7 +5805,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.67", @@ -5986,7 +5961,7 @@ version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", @@ -6479,16 +6454,6 @@ dependencies = [ "uint", ] -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -6957,7 +6922,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "hyper", "hyper-rustls", @@ -7332,7 +7297,7 @@ version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", @@ -7872,6 +7837,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_assertions_next" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" + [[package]] name = "string_cache" version = "0.8.7" @@ -8503,17 +8474,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.21.1" @@ -8564,7 +8524,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", + "http 0.2.12", "http-body", "http-range-header", "pin-project-lite", @@ -8701,7 +8661,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.12", "httparse", "log", "rand", diff --git a/bin/fuel-core/src/cli/run.rs b/bin/fuel-core/src/cli/run.rs index a16a20ebdda..306033bfe7d 100644 --- a/bin/fuel-core/src/cli/run.rs +++ b/bin/fuel-core/src/cli/run.rs @@ -5,6 +5,7 @@ use crate::{ default_db_path, run::{ consensus::PoATriggerArgs, + graphql::GraphQLArgs, tx_pool::TxPoolArgs, }, ShutdownListener, @@ -19,6 +20,7 @@ use fuel_core::{ CombinedDatabase, CombinedDatabaseConfig, }, + fuel_core_graphql_api::ServiceConfig as GraphQLConfig, producer::Config as ProducerConfig, service::{ config::Trigger, @@ -73,6 +75,7 @@ pub const CONSENSUS_KEY_ENV: &str = "CONSENSUS_KEY_SECRET"; mod p2p; mod consensus; +mod graphql; mod profiling; #[cfg(feature = "relayer")] mod relayer; @@ -81,12 +84,6 @@ mod tx_pool; /// Run the Fuel client node locally. #[derive(Debug, Clone, Parser)] pub struct Command { - #[clap(long = "ip", default_value = "127.0.0.1", value_parser, env)] - pub ip: net::IpAddr, - - #[clap(long = "port", default_value = "4000", env)] - pub port: u16, - /// Vanity name for node, used in telemetry #[clap(long = "service-name", default_value = "fuel-core", value_parser, env)] pub service_name: String, @@ -169,6 +166,10 @@ pub struct Command { #[clap(flatten)] pub tx_pool: TxPoolArgs, + /// The cli arguments supported by the GraphQL API service. + #[clap(flatten)] + pub graphql: GraphQLArgs, + #[cfg_attr(feature = "relayer", clap(flatten))] #[cfg(feature = "relayer")] pub relayer_args: relayer::RelayerArgs, @@ -198,14 +199,6 @@ pub struct Command { #[clap(long = "time-until-synced", default_value = "0s", env)] pub time_until_synced: humantime::Duration, - /// Time to wait after submitting a query before debug info will be logged about query. - #[clap(long = "query-log-threshold-time", default_value = "2s", env)] - pub query_log_threshold_time: humantime::Duration, - - /// Timeout before drop the request. - #[clap(long = "api-request-timeout", default_value = "30m", env)] - pub api_request_timeout: humantime::Duration, - /// The size of the memory pool in number of `MemoryInstance`s. #[clap(long = "memory-pool-size", default_value = "32", env)] pub memory_pool_size: usize, @@ -217,8 +210,6 @@ pub struct Command { impl Command { pub fn get_config(self) -> anyhow::Result { let Command { - ip, - port, service_name: name, max_database_cache_size, database_path, @@ -243,15 +234,14 @@ impl Command { max_da_lag, max_wait_time, tx_pool, + graphql, min_connected_reserved_peers, time_until_synced, - query_log_threshold_time, - api_request_timeout, memory_pool_size, profiling: _, } = self; - let addr = net::SocketAddr::new(ip, port); + let addr = net::SocketAddr::new(graphql.ip, graphql.port); let snapshot_reader = match snapshot.as_ref() { None => crate::cli::local_testnet_reader(), @@ -335,8 +325,14 @@ impl Command { ); let config = Config { - addr, - api_request_timeout: api_request_timeout.into(), + graphql_config: GraphQLConfig { + addr, + max_queries_depth: graphql.graphql_max_depth, + max_queries_complexity: graphql.graphql_max_complexity, + max_queries_recursive_depth: graphql.graphql_max_recursive_depth, + api_request_timeout: graphql.api_request_timeout.into(), + query_log_threshold_time: graphql.query_log_threshold_time.into(), + }, combined_db_config, snapshot_reader, debug, @@ -372,7 +368,6 @@ impl Command { relayer_consensus_config: verifier, min_connected_reserved_peers, time_until_synced: time_until_synced.into(), - query_log_threshold_time: query_log_threshold_time.into(), memory_pool_size, }; Ok(config) diff --git a/bin/fuel-core/src/cli/run/graphql.rs b/bin/fuel-core/src/cli/run/graphql.rs new file mode 100644 index 00000000000..e0e8b771789 --- /dev/null +++ b/bin/fuel-core/src/cli/run/graphql.rs @@ -0,0 +1,34 @@ +//! Clap configuration related to GraphQL service. + +use std::net; + +#[derive(Debug, Clone, clap::Args)] +pub struct GraphQLArgs { + /// The IP address to bind the GraphQL service to. + #[clap(long = "ip", default_value = "127.0.0.1", value_parser, env)] + pub ip: net::IpAddr, + + /// The port to bind the GraphQL service to. + #[clap(long = "port", default_value = "4000", env)] + pub port: u16, + + /// The max depth of GraphQL queries. + #[clap(long = "graphql-max-depth", default_value = "16", env)] + pub graphql_max_depth: usize, + + /// The max complexity of GraphQL queries. + #[clap(long = "graphql-max-complexity", default_value = "20000", env)] + pub graphql_max_complexity: usize, + + /// The max recursive depth of GraphQL queries. + #[clap(long = "graphql-max-recursive-depth", default_value = "16", env)] + pub graphql_max_recursive_depth: usize, + + /// Time to wait after submitting a query before debug info will be logged about query. + #[clap(long = "query-log-threshold-time", default_value = "2s", env)] + pub query_log_threshold_time: humantime::Duration, + + /// Timeout before drop the request. + #[clap(long = "api-request-timeout", default_value = "30s", env)] + pub api_request_timeout: humantime::Duration, +} diff --git a/bin/fuel-core/src/cli/run/tx_pool.rs b/bin/fuel-core/src/cli/run/tx_pool.rs index a06550bd597..844b10c333a 100644 --- a/bin/fuel-core/src/cli/run/tx_pool.rs +++ b/bin/fuel-core/src/cli/run/tx_pool.rs @@ -1,4 +1,4 @@ -//! Clap configuration related to consensus parameters +//! Clap configuration related to TxPool service. use fuel_core::txpool::types::ContractId; use fuel_core_types::{ diff --git a/ci_checks.sh b/ci_checks.sh index 90e9815c3c4..d7249a89e73 100755 --- a/ci_checks.sh +++ b/ci_checks.sh @@ -9,6 +9,8 @@ # - Rust `1.75.0` # - Nightly rust formatter # - `cargo install cargo-sort` +# - `cargo install cargo-make` +# - `cargo install cargo-insta` # - `npm install prettier prettier-plugin-toml` npx prettier --check "**/Cargo.toml" && diff --git a/crates/client/assets/schema.sdl b/crates/client/assets/schema.sdl index 0a2ad51ab21..e096eb04301 100644 --- a/crates/client/assets/schema.sdl +++ b/crates/client/assets/schema.sdl @@ -27,14 +27,14 @@ type BalanceConnection { An edge in a connection. """ type BalanceEdge { - """ - A cursor for use in pagination - """ - cursor: String! """ The item at the end of the edge """ node: Balance! + """ + A cursor for use in pagination + """ + cursor: String! } input BalanceFilterInput { @@ -50,6 +50,7 @@ type Block { height: U32! header: Header! consensus: Consensus! + transactionIds: [TransactionId!]! transactions: [Transaction!]! } @@ -72,14 +73,14 @@ type BlockConnection { An edge in a connection. """ type BlockEdge { - """ - A cursor for use in pagination - """ - cursor: String! """ The item at the end of the edge """ node: Block! + """ + A cursor for use in pagination + """ + cursor: String! } scalar BlockId @@ -147,14 +148,14 @@ type CoinConnection { An edge in a connection. """ type CoinEdge { - """ - A cursor for use in pagination - """ - cursor: String! """ The item at the end of the edge """ node: Coin! + """ + A cursor for use in pagination + """ + cursor: String! } input CoinFilterInput { @@ -235,14 +236,14 @@ type ContractBalanceConnection { An edge in a connection. """ type ContractBalanceEdge { - """ - A cursor for use in pagination - """ - cursor: String! """ The item at the end of the edge """ node: ContractBalance! + """ + A cursor for use in pagination + """ + cursor: String! } input ContractBalanceFilterInput { @@ -317,6 +318,7 @@ input ExcludeInput { type FailureStatus { transactionId: TransactionId! + blockHeight: U32! block: Block! time: Tai64Timestamp! reason: String! @@ -640,14 +642,14 @@ type MessageConnection { An edge in a connection. """ type MessageEdge { - """ - A cursor for use in pagination - """ - cursor: String! """ The item at the end of the edge """ node: Message! + """ + A cursor for use in pagination + """ + cursor: String! } type MessageProof { @@ -840,12 +842,35 @@ type Query { Read read a range of memory bytes. """ memory(id: ID!, start: U32!, size: U32!): String! - balance(owner: Address!, assetId: AssetId!): Balance! + balance( + """ + address of the owner + """ + owner: Address!, + """ + asset_id of the coin + """ + assetId: AssetId! + ): Balance! balances(filter: BalanceFilterInput!, first: Int, after: String, last: Int, before: String): BalanceConnection! - block(id: BlockId, height: U32): Block + block( + """ + ID of the block + """ + id: BlockId, + """ + Height of the block + """ + height: U32 + ): Block blocks(first: Int, after: String, last: Int, before: String): BlockConnection! chain: ChainInfo! - transaction(id: TransactionId!): Transaction + transaction( + """ + The ID of the transaction + """ + id: TransactionId! + ): Transaction transactions(first: Int, after: String, last: Int, before: String): TransactionConnection! transactionsByOwner(owner: Address!, first: Int, after: String, last: Int, before: String): TransactionConnection! """ @@ -863,7 +888,12 @@ type Query { """ Gets the coin by `utxo_id`. """ - coin(utxoId: UtxoId!): Coin + coin( + """ + The ID of the coin + """ + utxoId: UtxoId! + ): Coin """ Gets all unspent coins of some `owner` maybe filtered with by `asset_id` per page. """ @@ -880,18 +910,56 @@ type Query { the same as the length of `query_per_asset`. The ordering of assets and `query_per_asset` is the same. """ - coinsToSpend(owner: Address!, queryPerAsset: [SpendQueryElementInput!]!, excludedIds: ExcludeInput): [[CoinType!]!]! - contract(id: ContractId!): Contract + coinsToSpend( + """ + The `Address` of the coins owner. + """ + owner: Address!, + """ + The list of requested assets` coins with asset ids, `target` amount the user wants to reach, and the `max` number of coins in the selection. Several entries with the same asset id are not allowed. + """ + queryPerAsset: [SpendQueryElementInput!]!, + """ + The excluded coins from the selection. + """ + excludedIds: ExcludeInput + ): [[CoinType!]!]! + contract( + """ + ID of the Contract + """ + id: ContractId! + ): Contract contractBalance(contract: ContractId!, asset: AssetId!): ContractBalance! contractBalances(filter: ContractBalanceFilterInput!, first: Int, after: String, last: Int, before: String): ContractBalanceConnection! nodeInfo: NodeInfo! latestGasPrice: LatestGasPrice! - estimateGasPrice(blockHorizon: U32): EstimateGasPrice! - message(nonce: Nonce!): Message - messages(owner: Address, first: Int, after: String, last: Int, before: String): MessageConnection! + estimateGasPrice( + """ + Number of blocks into the future to estimate the gas price for + """ + blockHorizon: U32 + ): EstimateGasPrice! + message( + """ + The Nonce of the message + """ + nonce: Nonce! + ): Message + messages( + """ + address of the owner + """ + owner: Address, first: Int, after: String, last: Int, before: String + ): MessageConnection! messageProof(transactionId: TransactionId!, nonce: Nonce!, commitBlockId: BlockId, commitBlockHeight: U32): MessageProof messageStatus(nonce: Nonce!): MessageStatus! - relayedTransactionStatus(id: RelayedTransactionId!): RelayedTransactionStatus + relayedTransactionStatus( + """ + The id of the relayed tx + """ + id: RelayedTransactionId! + ): RelayedTransactionStatus } type Receipt { @@ -966,7 +1034,13 @@ type RunResult { } enum RunState { + """ + All breakpoints have been processed, and the program has terminated + """ COMPLETED + """ + Stopped on a breakpoint + """ BREAKPOINT } @@ -1027,7 +1101,12 @@ type Subscription { a status. If this occurs the stream can simply be restarted to return the latest status. """ - statusChange(id: TransactionId!): TransactionStatus! + statusChange( + """ + The ID of the transaction + """ + id: TransactionId! + ): TransactionStatus! """ Submits transaction to the `TxPool` and await either confirmation or failure. """ @@ -1036,6 +1115,7 @@ type Subscription { type SuccessStatus { transactionId: TransactionId! + blockHeight: U32! block: Block! time: Tai64Timestamp! programState: ProgramState @@ -1104,14 +1184,14 @@ type TransactionConnection { An edge in a connection. """ type TransactionEdge { - """ - A cursor for use in pagination - """ - cursor: String! """ The item at the end of the edge """ node: Transaction! + """ + A cursor for use in pagination + """ + cursor: String! } scalar TransactionId @@ -1150,6 +1230,8 @@ type VariableOutput { assetId: AssetId! } +directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT +directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT schema { query: Query mutation: Mutation diff --git a/crates/client/src/client/schema/block.rs b/crates/client/src/client/schema/block.rs index f00392321db..e159d44bc9a 100644 --- a/crates/client/src/client/schema/block.rs +++ b/crates/client/src/client/schema/block.rs @@ -1,3 +1,4 @@ +use super::Bytes32; use crate::client::schema::{ schema, BlockId, @@ -5,6 +6,7 @@ use crate::client::schema::{ PageInfo, Signature, Tai64Timestamp, + TransactionId, U16, U32, U64, @@ -14,11 +16,6 @@ use fuel_core_types::{ fuel_types::BlockHeight, }; -use super::{ - tx::TransactionIdFragment, - Bytes32, -}; - #[derive(cynic::QueryVariables, Debug)] pub struct BlockByIdArgs { pub id: Option, @@ -90,7 +87,7 @@ pub struct Block { pub id: BlockId, pub header: Header, pub consensus: Consensus, - pub transactions: Vec, + pub transaction_ids: Vec, } #[derive(cynic::QueryFragment, Clone, Debug)] diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap index 6cd3efe8c2e..7f8855343cd 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_height_query_gql_output.snap @@ -35,8 +35,6 @@ query($height: U32) { signature } } - transactions { - id - } + transactionIds } } diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap index fc24235694d..0f2c57130a8 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__block_by_id_query_gql_output.snap @@ -35,8 +35,6 @@ query($id: BlockId) { signature } } - transactions { - id - } + transactionIds } } diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap index f46f2ac29a7..3a0dd3d07f9 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__block__tests__blocks_connection_query_gql_output.snap @@ -38,9 +38,7 @@ query($after: String, $before: String, $first: Int, $last: Int) { signature } } - transactions { - id - } + transactionIds } } pageInfo { diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap index 0fa04e142af..6197468df8d 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__chain__tests__chain_gql_query_output.snap @@ -38,9 +38,7 @@ query { signature } } - transactions { - id - } + transactionIds } consensusParameters { version diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__opaque_transaction_by_id_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__opaque_transaction_by_id_query_gql_output.snap index beb790a1346..d5ee2473e91 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__opaque_transaction_by_id_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__opaque_transaction_by_id_query_gql_output.snap @@ -12,9 +12,7 @@ query($id: TransactionId!) { } ... on SuccessStatus { transactionId - block { - height - } + blockHeight time programState { returnType @@ -58,9 +56,7 @@ query($id: TransactionId!) { } ... on FailureStatus { transactionId - block { - height - } + blockHeight time reason programState { diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_by_owner_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_by_owner_gql_output.snap index 31147d57769..77a337e2683 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_by_owner_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_by_owner_gql_output.snap @@ -15,9 +15,7 @@ query($owner: Address!, $after: String, $before: String, $first: Int, $last: Int } ... on SuccessStatus { transactionId - block { - height - } + blockHeight time programState { returnType @@ -61,9 +59,7 @@ query($owner: Address!, $after: String, $before: String, $first: Int, $last: Int } ... on FailureStatus { transactionId - block { - height - } + blockHeight time reason programState { diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_connection_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_connection_query_gql_output.snap index 8a91bc1817a..ebb4b6f215f 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_connection_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transactions_connection_query_gql_output.snap @@ -15,9 +15,7 @@ query($after: String, $before: String, $first: Int, $last: Int) { } ... on SuccessStatus { transactionId - block { - height - } + blockHeight time programState { returnType @@ -61,9 +59,7 @@ query($after: String, $before: String, $first: Int, $last: Int) { } ... on FailureStatus { transactionId - block { - height - } + blockHeight time reason programState { diff --git a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap index 696b7fbbe6a..b4b4094c67e 100644 --- a/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap +++ b/crates/client/src/client/schema/snapshots/fuel_core_client__client__schema__tx__tests__transparent_transaction_by_id_query_gql_output.snap @@ -96,9 +96,7 @@ query($id: TransactionId!) { } ... on SuccessStatus { transactionId - block { - height - } + blockHeight time programState { returnType @@ -142,9 +140,7 @@ query($id: TransactionId!) { } ... on FailureStatus { transactionId - block { - height - } + blockHeight time reason programState { diff --git a/crates/client/src/client/schema/tx.rs b/crates/client/src/client/schema/tx.rs index d170a1dae2b..f1fbb5e67fa 100644 --- a/crates/client/src/client/schema/tx.rs +++ b/crates/client/src/client/schema/tx.rs @@ -1,4 +1,3 @@ -use super::block::BlockHeightFragment; use crate::client::{ schema::{ schema, @@ -10,6 +9,7 @@ use crate::client::{ PageInfo, Tai64Timestamp, TransactionId, + U32, U64, }, types::TransactionResponse, @@ -179,7 +179,7 @@ pub struct SubmittedStatus { #[cynic(schema_path = "./assets/schema.sdl")] pub struct SuccessStatus { pub transaction_id: TransactionId, - pub block: BlockHeightFragment, + pub block_height: U32, pub time: Tai64Timestamp, pub program_state: Option, pub receipts: Vec, @@ -191,7 +191,7 @@ pub struct SuccessStatus { #[cynic(schema_path = "./assets/schema.sdl")] pub struct FailureStatus { pub transaction_id: TransactionId, - pub block: BlockHeightFragment, + pub block_height: U32, pub time: Tai64Timestamp, pub reason: String, pub program_state: Option, diff --git a/crates/client/src/client/types.rs b/crates/client/src/client/types.rs index 03c02f55791..9c565395355 100644 --- a/crates/client/src/client/types.rs +++ b/crates/client/src/client/types.rs @@ -126,7 +126,7 @@ impl TryFrom for TransactionStatus { submitted_at: s.time.0, }, SchemaTxStatus::SuccessStatus(s) => TransactionStatus::Success { - block_height: s.block.height.into(), + block_height: s.block_height.into(), time: s.time.0, program_state: s.program_state.map(TryInto::try_into).transpose()?, receipts: s @@ -138,7 +138,7 @@ impl TryFrom for TransactionStatus { total_fee: s.total_fee.0, }, SchemaTxStatus::FailureStatus(s) => TransactionStatus::Failure { - block_height: s.block.height.into(), + block_height: s.block_height.into(), time: s.time.0, reason: s.reason, program_state: s.program_state.map(TryInto::try_into).transpose()?, diff --git a/crates/client/src/client/types/block.rs b/crates/client/src/client/types/block.rs index a953f3ffaa9..3f96d34cc6e 100644 --- a/crates/client/src/client/types/block.rs +++ b/crates/client/src/client/types/block.rs @@ -140,17 +140,19 @@ impl TryFrom for Block { fn try_from(value: schema::block::Block) -> Result { match value.version { BlockVersion::V1 => { + let block_producer = value.block_producer(); + let id = value.id.into(); + let header = value.header.try_into()?; + let consensus = value.consensus.into(); let transactions = value - .transactions - .iter() - .map(|tx| tx.id.clone()) + .transaction_ids + .into_iter() .map(Into::into) .collect::>(); - let block_producer = value.block_producer(); Ok(Self { - id: value.id.into(), - header: value.header.try_into()?, - consensus: value.consensus.into(), + id, + header, + consensus, transactions, block_producer, }) diff --git a/crates/fuel-core/Cargo.toml b/crates/fuel-core/Cargo.toml index eacae8a9c3a..dd262cc1caa 100644 --- a/crates/fuel-core/Cargo.toml +++ b/crates/fuel-core/Cargo.toml @@ -12,7 +12,8 @@ version = { workspace = true } [dependencies] anyhow = { workspace = true } -async-graphql = { version = "4.0", features = [ +async-graphql = { version = "7.0.6", features = [ + "playground", "tracing", ], default-features = false } async-trait = { workspace = true } diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index bd1b050664f..07cc364eaf5 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -2,7 +2,10 @@ use fuel_core_storage::{ Error as StorageError, IsNotFound, }; -use std::net::SocketAddr; +use std::{ + net::SocketAddr, + time::Duration, +}; pub mod api_service; pub mod database; @@ -13,13 +16,58 @@ pub(crate) mod view_extension; pub mod worker_service; #[derive(Clone, Debug)] -pub struct Config { +pub struct ServiceConfig { pub addr: SocketAddr, + pub max_queries_depth: usize, + pub max_queries_complexity: usize, + pub max_queries_recursive_depth: usize, + /// 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, +} + +pub struct Costs { + pub balance_query: usize, + pub coins_to_spend: usize, + pub get_peers: usize, + pub estimate_predicates: usize, + pub dry_run: usize, + pub submit: usize, + pub submit_and_await: usize, + pub status_change: usize, + pub raw_payload: usize, + pub storage_read: usize, + pub storage_iterator: usize, + pub bytecode_read: usize, +} + +pub const QUERY_COSTS: Costs = Costs { + // balance_query: 4000, + balance_query: 10001, + coins_to_spend: 10001, + // get_peers: 2000, + get_peers: 10001, + // estimate_predicates: 3000, + estimate_predicates: 10001, + dry_run: 3000, + // submit: 5000, + submit: 10001, + submit_and_await: 10001, + status_change: 10001, + raw_payload: 10, + storage_read: 10, + storage_iterator: 100, + bytecode_read: 2000, +}; + +#[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_depth: usize, + pub max_txpool_depth: usize, pub chain_name: String, } diff --git a/crates/fuel-core/src/graphql_api/api_service.rs b/crates/fuel-core/src/graphql_api/api_service.rs index 88eaf3da23a..23e066523ef 100644 --- a/crates/fuel-core/src/graphql_api/api_service.rs +++ b/crates/fuel-core/src/graphql_api/api_service.rs @@ -73,7 +73,6 @@ use std::{ TcpListener, }, pin::Pin, - time::Duration, }; use tokio_stream::StreamExt; use tower_http::{ @@ -186,8 +185,6 @@ pub fn new_service( gas_price_provider: GasPriceProvider, consensus_parameters_provider: ConsensusProvider, memory_pool: SharedMemoryPool, - log_threshold_ms: Duration, - request_timeout: Duration, ) -> anyhow::Result where OnChain: AtomicView + 'static, @@ -195,11 +192,18 @@ where OnChain::LatestView: OnChainDatabase, OffChain::LatestView: OffChainDatabase, { - let network_addr = config.addr; + let network_addr = config.config.addr; let combined_read_database = ReadDatabase::new(genesis_block_height, on_database, off_database); + let request_timeout = config.config.api_request_timeout; let schema = schema + .limit_complexity(config.config.max_queries_complexity) + .limit_depth(config.config.max_queries_depth) + .limit_recursive_depth(config.config.max_queries_recursive_depth) + .extension(MetricsExtension::new( + config.config.query_log_threshold_time, + )) .data(config) .data(combined_read_database) .data(txpool) @@ -210,7 +214,6 @@ where .data(consensus_parameters_provider) .data(memory_pool) .extension(async_graphql::extensions::Tracing) - .extension(MetricsExtension::new(log_threshold_ms)) .extension(ViewExtension::new()) .finish(); diff --git a/crates/fuel-core/src/schema/balance.rs b/crates/fuel-core/src/schema/balance.rs index 1984e8ede08..6f83a831449 100644 --- a/crates/fuel-core/src/schema/balance.rs +++ b/crates/fuel-core/src/schema/balance.rs @@ -1,5 +1,8 @@ use crate::{ - fuel_core_graphql_api::api_service::ConsensusProvider, + fuel_core_graphql_api::{ + api_service::ConsensusProvider, + QUERY_COSTS, + }, query::BalanceQueryData, schema::{ scalars::{ @@ -50,6 +53,7 @@ pub struct BalanceQuery; #[Object] impl BalanceQuery { + #[graphql(complexity = "QUERY_COSTS.balance_query")] async fn balance( &self, ctx: &Context<'_>, @@ -65,10 +69,9 @@ impl BalanceQuery { Ok(balance) } - // TODO: We can't paginate over `AssetId` because it is not unique. - // It should be replaced with `UtxoId`. - // This API should be migrated to the indexer for better support and + // TODO: This API should be migrated to the indexer for better support and // discontinued within fuel-core. + #[graphql(complexity = "QUERY_COSTS.balance_query")] async fn balances( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/block.rs b/crates/fuel-core/src/schema/block.rs index 85e39d7c61e..40b03cd068d 100644 --- a/crates/fuel-core/src/schema/block.rs +++ b/crates/fuel-core/src/schema/block.rs @@ -1,6 +1,7 @@ use super::scalars::{ Bytes32, Tai64Timestamp, + TransactionId, }; use crate::{ fuel_core_graphql_api::{ @@ -9,6 +10,7 @@ use crate::{ ports::OffChainDatabase, Config as GraphQLConfig, IntoApiResult, + QUERY_COSTS, }, query::{ BlockQueryData, @@ -116,12 +118,24 @@ impl Block { self.0.header().clone().into() } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn consensus(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; let height = self.0.header().height(); Ok(query.consensus(height)?.try_into()?) } + async fn transaction_ids(&self) -> Vec { + self.0 + .transactions() + .iter() + .map(|tx_id| (*tx_id).into()) + .collect() + } + + // Assume that in average we have 32 transactions per block. + #[graphql(complexity = "QUERY_COSTS.storage_iterator\ + + (QUERY_COSTS.storage_read + child_complexity) * 32")] async fn transactions( &self, ctx: &Context<'_>, @@ -232,6 +246,7 @@ pub struct BlockQuery; #[Object] impl BlockQuery { + #[graphql(complexity = "2 * QUERY_COSTS.storage_read + child_complexity")] async fn block( &self, ctx: &Context<'_>, @@ -260,6 +275,11 @@ impl BlockQuery { .into_api_result() } + #[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\ + }")] async fn blocks( &self, ctx: &Context<'_>, @@ -285,18 +305,24 @@ pub struct HeaderQuery; #[Object] impl HeaderQuery { + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn header( &self, ctx: &Context<'_>, #[graphql(desc = "ID of the block")] id: Option, #[graphql(desc = "Height of the block")] height: Option, ) -> async_graphql::Result> { - Ok(BlockQuery {} + Ok(BlockQuery .block(ctx, id, height) .await? .map(|b| b.0.header().clone().into())) } + #[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\ + }")] async fn headers( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/chain.rs b/crates/fuel-core/src/schema/chain.rs index e334392ef5c..cfc364f126d 100644 --- a/crates/fuel-core/src/schema/chain.rs +++ b/crates/fuel-core/src/schema/chain.rs @@ -1,5 +1,8 @@ use crate::{ - fuel_core_graphql_api::api_service::ConsensusProvider, + fuel_core_graphql_api::{ + api_service::ConsensusProvider, + QUERY_COSTS, + }, graphql_api::Config, query::{ BlockQueryData, @@ -118,6 +121,7 @@ impl ConsensusParameters { } } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn tx_params(&self, ctx: &Context<'_>) -> async_graphql::Result { let params = ctx .data_unchecked::() @@ -126,6 +130,7 @@ impl ConsensusParameters { Ok(TxParameters(params.tx_params().to_owned())) } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn predicate_params( &self, ctx: &Context<'_>, @@ -137,6 +142,7 @@ impl ConsensusParameters { Ok(PredicateParameters(params.predicate_params().to_owned())) } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn script_params( &self, ctx: &Context<'_>, @@ -148,6 +154,7 @@ impl ConsensusParameters { Ok(ScriptParameters(params.script_params().to_owned())) } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn contract_params( &self, ctx: &Context<'_>, @@ -159,6 +166,7 @@ impl ConsensusParameters { Ok(ContractParameters(params.contract_params().to_owned())) } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn fee_params( &self, ctx: &Context<'_>, @@ -170,6 +178,7 @@ impl ConsensusParameters { Ok(FeeParameters(params.fee_params().to_owned())) } + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn base_asset_id(&self, ctx: &Context<'_>) -> async_graphql::Result { let params = ctx .data_unchecked::() @@ -178,6 +187,7 @@ impl ConsensusParameters { Ok(AssetId(*params.base_asset_id())) } + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn block_gas_limit(&self, ctx: &Context<'_>) -> async_graphql::Result { let params = ctx .data_unchecked::() @@ -190,6 +200,7 @@ impl ConsensusParameters { (*self.0.chain_id()).into() } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn gas_costs(&self, ctx: &Context<'_>) -> async_graphql::Result { let params = ctx .data_unchecked::() @@ -198,6 +209,7 @@ impl ConsensusParameters { Ok(GasCosts(params.gas_costs().clone())) } + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn privileged_address( &self, ctx: &Context<'_>, @@ -808,11 +820,13 @@ impl HeavyOperation { #[Object] impl ChainInfo { + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn name(&self, ctx: &Context<'_>) -> async_graphql::Result { let config: &Config = ctx.data_unchecked(); Ok(config.chain_name.clone()) } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn latest_block(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; @@ -820,6 +834,7 @@ impl ChainInfo { Ok(latest_block) } + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn da_height(&self, ctx: &Context<'_>) -> U64 { let Ok(query) = ctx.read_view() else { return 0.into(); @@ -828,6 +843,7 @@ impl ChainInfo { query.da_height().unwrap_or_default().0.into() } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn consensus_parameters( &self, ctx: &Context<'_>, @@ -839,6 +855,7 @@ impl ChainInfo { Ok(ConsensusParameters(params)) } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn gas_costs(&self, ctx: &Context<'_>) -> async_graphql::Result { let params = ctx .data_unchecked::() diff --git a/crates/fuel-core/src/schema/coins.rs b/crates/fuel-core/src/schema/coins.rs index 62194fb486a..0d0abc97da2 100644 --- a/crates/fuel-core/src/schema/coins.rs +++ b/crates/fuel-core/src/schema/coins.rs @@ -3,7 +3,10 @@ use crate::{ random_improve, SpendQuery, }, - fuel_core_graphql_api::IntoApiResult, + fuel_core_graphql_api::{ + IntoApiResult, + QUERY_COSTS, + }, graphql_api::api_service::ConsensusProvider, query::{ asset_query::AssetSpendTarget, @@ -92,6 +95,7 @@ impl MessageCoin { self.0.amount.into() } + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn asset_id(&self, ctx: &Context<'_>) -> AssetId { let params = ctx .data_unchecked::() @@ -147,6 +151,7 @@ pub struct CoinQuery; #[async_graphql::Object] impl CoinQuery { /// Gets the coin by `utxo_id`. + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn coin( &self, ctx: &Context<'_>, @@ -157,6 +162,11 @@ impl CoinQuery { } /// Gets all unspent coins of some `owner` maybe filtered with by `asset_id` per page. + #[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\ + }")] async fn coins( &self, ctx: &Context<'_>, @@ -198,6 +208,7 @@ impl CoinQuery { /// The list of spendable coins per asset from the query. The length of the result is /// the same as the length of `query_per_asset`. The ordering of assets and `query_per_asset` /// is the same. + #[graphql(complexity = "QUERY_COSTS.coins_to_spend")] async fn coins_to_spend( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/contract.rs b/crates/fuel-core/src/schema/contract.rs index eb19fee9b6f..bdefa2a53c0 100644 --- a/crates/fuel-core/src/schema/contract.rs +++ b/crates/fuel-core/src/schema/contract.rs @@ -1,5 +1,8 @@ use crate::{ - fuel_core_graphql_api::IntoApiResult, + fuel_core_graphql_api::{ + IntoApiResult, + QUERY_COSTS, + }, query::ContractQueryData, schema::{ scalars::{ @@ -40,6 +43,7 @@ impl Contract { self.0.into() } + #[graphql(complexity = "QUERY_COSTS.bytecode_read")] async fn bytecode(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; query @@ -48,6 +52,7 @@ impl Contract { .map_err(Into::into) } + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn salt(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; query @@ -62,6 +67,7 @@ pub struct ContractQuery; #[Object] impl ContractQuery { + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn contract( &self, ctx: &Context<'_>, @@ -100,6 +106,7 @@ pub struct ContractBalanceQuery; #[Object] impl ContractBalanceQuery { + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn contract_balance( &self, ctx: &Context<'_>, @@ -124,6 +131,11 @@ impl ContractBalanceQuery { }) } + #[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\ + }")] async fn contract_balances( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/gas_price.rs b/crates/fuel-core/src/schema/gas_price.rs index 2bb666ab680..dbc56958448 100644 --- a/crates/fuel-core/src/schema/gas_price.rs +++ b/crates/fuel-core/src/schema/gas_price.rs @@ -3,7 +3,10 @@ use super::scalars::{ U64, }; use crate::{ - graphql_api::api_service::GasPriceProvider, + graphql_api::{ + api_service::GasPriceProvider, + QUERY_COSTS, + }, query::{ BlockQueryData, SimpleTransactionData, @@ -43,6 +46,7 @@ pub struct LatestGasPriceQuery {} #[Object] impl LatestGasPriceQuery { + #[graphql(complexity = "2 * QUERY_COSTS.storage_read")] async fn latest_gas_price( &self, ctx: &Context<'_>, @@ -80,6 +84,7 @@ pub struct EstimateGasPriceQuery {} #[Object] impl EstimateGasPriceQuery { + #[graphql(complexity = "2 * QUERY_COSTS.storage_read")] async fn estimate_gas_price( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/message.rs b/crates/fuel-core/src/schema/message.rs index 1e760e98e89..8ba18784bf8 100644 --- a/crates/fuel-core/src/schema/message.rs +++ b/crates/fuel-core/src/schema/message.rs @@ -11,7 +11,10 @@ use super::{ ReadViewProvider, }; use crate::{ - fuel_core_graphql_api::ports::OffChainDatabase, + fuel_core_graphql_api::{ + ports::OffChainDatabase, + QUERY_COSTS, + }, graphql_api::IntoApiResult, query::MessageQueryData, schema::scalars::{ @@ -65,6 +68,7 @@ pub struct MessageQuery {} #[Object] impl MessageQuery { + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn message( &self, ctx: &Context<'_>, @@ -75,6 +79,11 @@ impl MessageQuery { query.message(&nonce).into_api_result() } + #[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\ + }")] async fn messages( &self, ctx: &Context<'_>, @@ -116,6 +125,8 @@ impl MessageQuery { .await } + // 256 * QUERY_COSTS.storage_read because the depth of the Merkle tree in the worst case is 256 + #[graphql(complexity = "256 * QUERY_COSTS.storage_read + child_complexity")] async fn message_proof( &self, ctx: &Context<'_>, @@ -146,6 +157,7 @@ impl MessageQuery { .map(MessageProof)) } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn message_status( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/node_info.rs b/crates/fuel-core/src/schema/node_info.rs index 1d8472d476f..ef4a5965f72 100644 --- a/crates/fuel-core/src/schema/node_info.rs +++ b/crates/fuel-core/src/schema/node_info.rs @@ -2,7 +2,10 @@ use super::scalars::{ U32, U64, }; -use crate::fuel_core_graphql_api::Config as GraphQLConfig; +use crate::fuel_core_graphql_api::{ + Config as GraphQLConfig, + QUERY_COSTS, +}; use async_graphql::{ Context, Object, @@ -39,6 +42,7 @@ impl NodeInfo { self.node_version.to_owned() } + #[graphql(complexity = "QUERY_COSTS.get_peers + child_complexity")] async fn peers(&self, _ctx: &Context<'_>) -> async_graphql::Result> { #[cfg(feature = "p2p")] { @@ -62,6 +66,7 @@ pub struct NodeQuery {} #[Object] impl NodeQuery { + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn node_info(&self, ctx: &Context<'_>) -> async_graphql::Result { let config = ctx.data_unchecked::(); @@ -71,7 +76,7 @@ impl NodeQuery { utxo_validation: config.utxo_validation, vm_backtrace: config.vm_backtrace, max_tx: (config.max_tx as u64).into(), - max_depth: (config.max_depth as u64).into(), + max_depth: (config.max_txpool_depth as u64).into(), node_version: VERSION.to_owned(), }) } diff --git a/crates/fuel-core/src/schema/relayed_tx.rs b/crates/fuel-core/src/schema/relayed_tx.rs index b11f7b3bf84..1bc915df1bf 100644 --- a/crates/fuel-core/src/schema/relayed_tx.rs +++ b/crates/fuel-core/src/schema/relayed_tx.rs @@ -1,5 +1,8 @@ use crate::{ - fuel_core_graphql_api::ports::DatabaseRelayedTransactions, + fuel_core_graphql_api::{ + ports::DatabaseRelayedTransactions, + QUERY_COSTS, + }, schema::{ scalars::{ RelayedTransactionId, @@ -23,6 +26,7 @@ pub struct RelayedTransactionQuery {} #[Object] impl RelayedTransactionQuery { + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn relayed_transaction_status( &self, ctx: &Context<'_>, diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index f64e98a939c..31e32044a19 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -7,6 +7,7 @@ use crate::{ }, ports::OffChainDatabase, IntoApiResult, + QUERY_COSTS, }, query::{ transaction_status_change, @@ -87,6 +88,7 @@ pub struct TxQuery; #[Object] impl TxQuery { + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn transaction( &self, ctx: &Context<'_>, @@ -106,6 +108,11 @@ impl TxQuery { } } + #[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\ + }")] async fn transactions( &self, ctx: &Context<'_>, @@ -167,6 +174,11 @@ impl TxQuery { .await } + #[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\ + }")] async fn transactions_by_owner( &self, ctx: &Context<'_>, @@ -206,6 +218,7 @@ impl TxQuery { } /// Estimate the predicate gas for the provided transaction + #[graphql(complexity = "QUERY_COSTS.estimate_predicates + child_complexity")] async fn estimate_predicates( &self, ctx: &Context<'_>, @@ -247,6 +260,9 @@ pub struct TxMutation; #[Object] impl TxMutation { /// Execute a dry-run of multiple transactions using a fork of current state, no changes are committed. + #[graphql( + complexity = "QUERY_COSTS.dry_run * txs.len() + child_complexity * txs.len()" + )] async fn dry_run( &self, ctx: &Context<'_>, @@ -289,6 +305,7 @@ impl TxMutation { /// Submits transaction to the `TxPool`. /// /// Returns submitted transaction if the transaction is included in the `TxPool` without problems. + #[graphql(complexity = "QUERY_COSTS.submit + child_complexity")] async fn submit( &self, ctx: &Context<'_>, @@ -329,6 +346,7 @@ impl TxStatusSubscription { /// then the updates arrive. In such a case the stream will close without /// a status. If this occurs the stream can simply be restarted to return /// the latest status. + #[graphql(complexity = "QUERY_COSTS.status_change + child_complexity")] async fn status_change<'a>( &self, ctx: &'a Context<'a>, @@ -354,6 +372,7 @@ impl TxStatusSubscription { } /// Submits transaction to the `TxPool` and await either confirmation or failure. + #[graphql(complexity = "QUERY_COSTS.submit_and_await + child_complexity")] async fn submit_and_await<'a>( &self, ctx: &Context<'a>, diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index 680ff3bee20..52d3c1dcd37 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -11,6 +11,7 @@ use crate::{ }, database::ReadView, IntoApiResult, + QUERY_COSTS, }, query::{ SimpleBlockData, @@ -172,6 +173,11 @@ impl SuccessStatus { self.tx_id.into() } + async fn block_height(&self) -> U32 { + self.block_height.into() + } + + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; let block = query.block(&self.block_height)?; @@ -216,6 +222,11 @@ impl FailureStatus { self.tx_id.into() } + async fn block_height(&self) -> U32 { + self.block_height.into() + } + + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn block(&self, ctx: &Context<'_>) -> async_graphql::Result { let query = ctx.read_view()?; let block = query.block(&self.block_height)?; @@ -387,6 +398,7 @@ impl Transaction { TransactionId(self.1) } + #[graphql(complexity = "QUERY_COSTS.storage_read")] async fn input_asset_ids(&self, ctx: &Context<'_>) -> Option> { let params = ctx .data_unchecked::() @@ -628,6 +640,7 @@ impl Transaction { } } + #[graphql(complexity = "QUERY_COSTS.storage_read + child_complexity")] async fn status( &self, ctx: &Context<'_>, @@ -763,6 +776,7 @@ impl Transaction { } } + #[graphql(complexity = "QUERY_COSTS.raw_payload")] /// Return the transaction bytes using canonical encoding async fn raw_payload(&self) -> HexString { HexString(self.0.clone().to_bytes()) diff --git a/crates/fuel-core/src/service/config.rs b/crates/fuel-core/src/service/config.rs index f2c7e2778ce..1480fe7df59 100644 --- a/crates/fuel-core/src/service/config.rs +++ b/crates/fuel-core/src/service/config.rs @@ -7,10 +7,7 @@ use fuel_core_types::{ }, secrecy::Secret, }; -use std::{ - net::SocketAddr, - time::Duration, -}; +use std::time::Duration; use strum_macros::{ Display, EnumString, @@ -36,12 +33,14 @@ pub use fuel_core_consensus_module::RelayerConsensusConfig; pub use fuel_core_importer; pub use fuel_core_poa::Trigger; -use crate::combined_database::CombinedDatabaseConfig; +use crate::{ + combined_database::CombinedDatabaseConfig, + graphql_api::ServiceConfig as GraphQLConfig, +}; #[derive(Clone, Debug)] pub struct Config { - pub addr: SocketAddr, - pub api_request_timeout: Duration, + pub graphql_config: GraphQLConfig, pub combined_db_config: CombinedDatabaseConfig, pub snapshot_reader: SnapshotReader, /// When `true`: @@ -71,8 +70,6 @@ pub struct Config { pub min_connected_reserved_peers: usize, /// Time to wait after receiving the latest block before considered to be Synced. pub time_until_synced: Duration, - /// Time to wait after submitting a query before debug info will be logged about query. - pub query_log_threshold_time: Duration, /// The size of the memory pool in number of `MemoryInstance`s. pub memory_pool_size: usize, } @@ -124,8 +121,17 @@ impl Config { }; Self { - addr: SocketAddr::new(std::net::Ipv4Addr::new(127, 0, 0, 1).into(), 0), - api_request_timeout: Duration::from_secs(60), + graphql_config: GraphQLConfig { + addr: std::net::SocketAddr::new( + std::net::Ipv4Addr::new(127, 0, 0, 1).into(), + 0, + ), + max_queries_depth: 16, + max_queries_complexity: 20000, + max_queries_recursive_depth: 16, + query_log_threshold_time: Duration::from_secs(2), + api_request_timeout: Duration::from_secs(60), + }, combined_db_config, debug: true, utxo_validation, @@ -156,7 +162,6 @@ impl Config { relayer_consensus_config: Default::default(), min_connected_reserved_peers: 0, time_until_synced: Duration::ZERO, - query_log_threshold_time: Duration::from_secs(2), memory_pool_size: 4, } } diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 110ce09ae91..616b9115185 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -241,12 +241,12 @@ pub fn init_sub_services( ); let graphql_config = GraphQLConfig { - addr: config.addr, + config: config.graphql_config.clone(), utxo_validation: config.utxo_validation, debug: config.debug, vm_backtrace: config.vm.backtrace, max_tx: config.txpool.max_tx, - max_depth: config.txpool.max_depth, + max_txpool_depth: config.txpool.max_depth, chain_name, }; @@ -263,8 +263,6 @@ pub fn init_sub_services( Box::new(gas_price_provider), Box::new(consensus_parameters_provider), SharedMemoryPool::new(config.memory_pool_size), - config.query_log_threshold_time, - config.api_request_timeout, )?; let shared = SharedState { diff --git a/tests/tests/dos.rs b/tests/tests/dos.rs new file mode 100644 index 00000000000..b1dd17f2bd6 --- /dev/null +++ b/tests/tests/dos.rs @@ -0,0 +1,128 @@ +#![allow(warnings)] + +use fuel_core::service::{ + Config, + FuelService, +}; + +async fn send_query(url: &str, query: &str) -> String { + let client = reqwest::Client::new(); + let mut map = std::collections::HashMap::new(); + map.insert("query", query); + let response = client.post(url).json(&map).send().await.unwrap(); + + response.text().await.unwrap() +} + +#[tokio::test] +async fn complex_queries__recursion() { + let query = r#" + query { + chain { + latestBlock { + transactions { + status { + ... on SuccessStatus { + block { + transactions { + status { + ... on SuccessStatus { + block { + transactions { + status { + ... on SuccessStatus { + block { + transactions { + status { + ... on SuccessStatus { + block { + transactions { + status { + ... on SuccessStatus { + block { + transactions { + status { + ... on SuccessStatus { + block { + transactions { + id + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + "#; + + let mut config = Config::local_node(); + config.graphql_config.max_queries_complexity = usize::MAX; + let node = FuelService::new_node(config).await.unwrap(); + let url = format!("http://{}/v1/graphql", node.bound_address); + + let result = send_query(&url, query).await; + 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 + } + } + } + } + "#; + + let node = FuelService::new_node(Config::local_node()).await.unwrap(); + let url = format!("http://{}/v1/graphql", node.bound_address); + + let result = send_query(&url, query).await; + assert!(result.contains("transactions")); +} + +#[tokio::test] +async fn complex_queries__50_block__query_to_complex() { + let query = r#" + query { + blocks(first: 50) { + nodes { + transactions { + id + } + } + } + } + "#; + + let node = FuelService::new_node(Config::local_node()).await.unwrap(); + let url = format!("http://{}/v1/graphql", node.bound_address); + + let result = send_query(&url, query).await; + assert!(result.contains("Query is too complex.")); +} diff --git a/tests/tests/lib.rs b/tests/tests/lib.rs index 0fe426347f4..651827ccaf0 100644 --- a/tests/tests/lib.rs +++ b/tests/tests/lib.rs @@ -9,6 +9,7 @@ mod coins; mod contract; mod dap; mod debugger; +mod dos; mod fee_collection_contract; mod local_node; diff --git a/tests/tests/regenesis.rs b/tests/tests/regenesis.rs index fadb52e01a1..9480ad62bb3 100644 --- a/tests/tests/regenesis.rs +++ b/tests/tests/regenesis.rs @@ -16,6 +16,7 @@ use fuel_core_client::client::{ FuelClient, }; use fuel_core_types::{ + blockchain::header::LATEST_STATE_TRANSITION_VERSION, fuel_asm::{ op, GTFArgs, @@ -436,12 +437,20 @@ async fn test_regenesis_message_proofs_are_preserved() -> anyhow::Result<()> { // ------------------------- Start a node with the regenesis ------------------------- + // Regenesis increases the version of the executor by one. + // We want to use native execution to produce blocks, + // so we override the version of the native executor. + let latest_state_transition_version = LATEST_STATE_TRANSITION_VERSION + .saturating_add(1) + .to_string(); let core = FuelCoreDriver::spawn(&[ "--debug", "--poa-instant", "true", "--snapshot", snapshot_dir.path().to_str().unwrap(), + "--native-executor-version", + latest_state_transition_version.as_str(), ]) .await?; diff --git a/tests/tests/trigger_integration/instant.rs b/tests/tests/trigger_integration/instant.rs index 1c9716e3d0a..b56cc6e8d68 100644 --- a/tests/tests/trigger_integration/instant.rs +++ b/tests/tests/trigger_integration/instant.rs @@ -52,7 +52,7 @@ async fn poa_instant_trigger_is_produces_instantly() { let count = client .blocks(PaginationRequest { cursor: None, - results: 1024, + results: 20, direction: PageDirection::Forward, }) .await diff --git a/tests/tests/trigger_integration/interval.rs b/tests/tests/trigger_integration/interval.rs index ee8e5815c1c..1cfef77d1ee 100644 --- a/tests/tests/trigger_integration/interval.rs +++ b/tests/tests/trigger_integration/interval.rs @@ -35,6 +35,7 @@ async fn poa_interval_produces_empty_blocks_at_correct_rate() { let db = Database::default(); let mut config = Config::local_node(); + config.graphql_config.max_queries_complexity = 1_000_000; config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); config.block_production = Trigger::Interval { block_time: Duration::new(round_time_seconds, 0), @@ -97,6 +98,7 @@ async fn poa_interval_produces_nonempty_blocks_at_correct_rate() { let db = Database::default(); let mut config = Config::local_node(); + config.graphql_config.max_queries_complexity = 1_000_000; config.consensus_key = Some(Secret::new(SecretKey::random(&mut rng).into())); config.block_production = Trigger::Interval { block_time: Duration::new(round_time_seconds, 0), diff --git a/tests/tests/trigger_integration/never.rs b/tests/tests/trigger_integration/never.rs index 2adb927d6dc..03b5e36d579 100644 --- a/tests/tests/trigger_integration/never.rs +++ b/tests/tests/trigger_integration/never.rs @@ -46,7 +46,7 @@ async fn poa_never_trigger_doesnt_produce_blocks() { let resp = client .blocks(PaginationRequest { cursor: None, - results: 1024, + results: 20, direction: PageDirection::Forward, }) .await