diff --git a/.changes/changed/2715.md b/.changes/changed/2715.md new file mode 100644 index 00000000000..2a051222370 --- /dev/null +++ b/.changes/changed/2715.md @@ -0,0 +1 @@ +Each GraphQL response contains `current_consensus_parameters_version` and `current_stf_version` in the `extensions` section. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index dc6449092eb..6ab27f63337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9782,6 +9782,7 @@ dependencies = [ "itertools 0.12.1", "rand", "reqwest 0.12.12", + "serde_json", "tempfile", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index add03be9b44..4c411ff116f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,8 @@ codegen-units = 1 lto = "fat" # The difference in performance for "fat" and "thin" is small, # but "thin" LTO is much faster to compile. -# If you play with benchamrks or flamegraph, it is better to use "thin" -# To speedup iterations between compialtion. +# If you play with benchmarks or flamegraphs, it is better to use "thin" +# To speedup iterations between compilation. #lto = "thin" panic = "unwind" diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 0ee323d176d..e36cb7b3599 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -52,6 +52,10 @@ use cynic::{ QueryBuilder, }; use fuel_core_types::{ + blockchain::header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, fuel_asm::{ Instruction, Word, @@ -210,6 +214,28 @@ impl Clone for ConsistencyPolicy { } } +#[derive(Debug, Default)] +struct ChainStateInfo { + current_stf_version: Arc>>, + current_consensus_parameters_version: Arc>>, +} + +impl Clone for ChainStateInfo { + fn clone(&self) -> Self { + Self { + current_stf_version: Arc::new(Mutex::new( + self.current_stf_version.lock().ok().and_then(|v| *v), + )), + current_consensus_parameters_version: Arc::new(Mutex::new( + self.current_consensus_parameters_version + .lock() + .ok() + .and_then(|v| *v), + )), + } + } +} + #[derive(Debug, Clone)] pub struct FuelClient { client: reqwest::Client, @@ -217,6 +243,7 @@ pub struct FuelClient { cookie: std::sync::Arc, url: reqwest::Url, require_height: ConsistencyPolicy, + chain_state_info: ChainStateInfo, } impl FromStr for FuelClient { @@ -247,6 +274,7 @@ impl FromStr for FuelClient { require_height: ConsistencyPolicy::Auto { height: Arc::new(Mutex::new(None)), }, + chain_state_info: Default::default(), }) } @@ -259,6 +287,7 @@ impl FromStr for FuelClient { require_height: ConsistencyPolicy::Auto { height: Arc::new(Mutex::new(None)), }, + chain_state_info: Default::default(), }) } } @@ -322,6 +351,51 @@ impl FuelClient { } } + fn update_chain_state_info(&self, response: &FuelGraphQlResponse) { + if let Some(current_sft_version) = response + .extensions + .as_ref() + .and_then(|e| e.current_stf_version) + { + if let Ok(mut c) = self.chain_state_info.current_stf_version.lock() { + *c = Some(current_sft_version); + } + } + + if let Some(current_consensus_parameters_version) = response + .extensions + .as_ref() + .and_then(|e| e.current_consensus_parameters_version) + { + if let Ok(mut c) = self + .chain_state_info + .current_consensus_parameters_version + .lock() + { + *c = Some(current_consensus_parameters_version); + } + } + + let inner_required_height = match &self.require_height { + ConsistencyPolicy::Auto { height } => Some(height.clone()), + ConsistencyPolicy::Manual { .. } => None, + }; + + if let Some(inner_required_height) = inner_required_height { + if let Some(current_fuel_block_height) = response + .extensions + .as_ref() + .and_then(|e| e.current_fuel_block_height) + { + let mut lock = inner_required_height.lock().expect("Mutex poisoned"); + + if current_fuel_block_height >= lock.unwrap_or_default() { + *lock = Some(current_fuel_block_height); + } + } + } + } + /// Send the GraphQL query to the client. pub async fn query( &self, @@ -340,34 +414,14 @@ impl FuelClient { .await .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let inner_required_height = match &self.require_height { - ConsistencyPolicy::Auto { height } => Some(height.clone()), - _ => None, - }; - - Self::decode_response(response, inner_required_height) + self.decode_response(response) } - fn decode_response( - response: FuelGraphQlResponse, - inner_required_height: Option>>>, - ) -> io::Result + fn decode_response(&self, response: FuelGraphQlResponse) -> io::Result where R: serde::de::DeserializeOwned + 'static, { - if let Some(inner_required_height) = inner_required_height { - if let Some(current_fuel_block_height) = response - .extensions - .as_ref() - .and_then(|e| e.current_fuel_block_height) - { - let mut lock = inner_required_height.lock().expect("Mutex poisoned"); - - if current_fuel_block_height >= lock.unwrap_or_default() { - *lock = Some(current_fuel_block_height); - } - } - } + self.update_chain_state_info(&response); if let Some(failed) = response .extensions @@ -398,7 +452,7 @@ impl FuelClient { async fn subscribe( &self, q: StreamingOperation, - ) -> io::Result>> + ) -> io::Result> + '_> where Vars: serde::Serialize, ResponseData: serde::de::DeserializeOwned + 'static, @@ -471,17 +525,11 @@ impl FuelClient { let mut last = None; - let inner_required_height = match &self.require_height { - ConsistencyPolicy::Auto { height } => Some(height.clone()), - _ => None, - }; - let stream = es::Client::stream(&client) - .zip(futures::stream::repeat(inner_required_height)) - .take_while(|(result, _)| { + .take_while(|result| { futures::future::ready(!matches!(result, Err(es::Error::Eof))) }) - .filter_map(move |(result, inner_required_height)| { + .filter_map(move |result| { tracing::debug!("Got result: {result:?}"); let r = match result { Ok(es::SSE::Event(es::Event { data, .. })) => { @@ -489,7 +537,7 @@ impl FuelClient { &data, ) { Ok(resp) => { - match Self::decode_response(resp, inner_required_height) { + match self.decode_response(resp) { Ok(resp) => { match last.replace(data) { // Remove duplicates @@ -527,6 +575,24 @@ impl FuelClient { Ok(stream) } + pub fn latest_stf_version(&self) -> Option { + self.chain_state_info + .current_stf_version + .lock() + .ok() + .and_then(|value| *value) + } + + pub fn latest_consensus_parameters_version( + &self, + ) -> Option { + self.chain_state_info + .current_consensus_parameters_version + .lock() + .ok() + .and_then(|value| *value) + } + pub async fn health(&self) -> io::Result { let query = schema::Health::build(()); self.query(query).await.map(|r| r.health) @@ -764,10 +830,10 @@ impl FuelClient { /// Compared to the `submit_and_await_commit`, the stream also contains /// `SubmittedStatus` as an intermediate state. #[cfg(feature = "subscriptions")] - pub async fn submit_and_await_status( - &self, - tx: &Transaction, - ) -> io::Result>> { + pub async fn submit_and_await_status<'a>( + &'a self, + tx: &'a Transaction, + ) -> io::Result> + 'a> { use cynic::SubscriptionBuilder; let tx = tx.clone().to_bytes(); let s = schema::tx::SubmitAndAwaitStatusSubscription::build(TxArg { @@ -926,10 +992,10 @@ impl FuelClient { #[tracing::instrument(skip(self), level = "debug")] #[cfg(feature = "subscriptions")] /// Subscribe to the status of a transaction - pub async fn subscribe_transaction_status( - &self, - id: &TxId, - ) -> io::Result>> { + pub async fn subscribe_transaction_status<'a>( + &'a self, + id: &'a TxId, + ) -> io::Result> + 'a> { use cynic::SubscriptionBuilder; let tx_id: TransactionId = (*id).into(); let s = schema::tx::StatusChangeSubscription::build(TxIdArgs { id: tx_id }); diff --git a/crates/client/src/reqwest_ext.rs b/crates/client/src/reqwest_ext.rs index c0ca68111bf..7f8e15fe25b 100644 --- a/crates/client/src/reqwest_ext.rs +++ b/crates/client/src/reqwest_ext.rs @@ -3,7 +3,13 @@ use cynic::{ GraphQlResponse, Operation, }; -use fuel_core_types::fuel_types::BlockHeight; +use fuel_core_types::{ + blockchain::header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, + fuel_types::BlockHeight, +}; use std::{ future::Future, marker::PhantomData, @@ -20,6 +26,8 @@ pub struct ExtensionsResponse { pub required_fuel_block_height: Option, pub current_fuel_block_height: Option, pub fuel_block_height_precondition_failed: Option, + pub current_stf_version: Option, + pub current_consensus_parameters_version: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/fuel-core/src/graphql_api.rs b/crates/fuel-core/src/graphql_api.rs index c543654d518..107a1575ce0 100644 --- a/crates/fuel-core/src/graphql_api.rs +++ b/crates/fuel-core/src/graphql_api.rs @@ -12,12 +12,10 @@ pub mod api_service; pub(crate) mod block_height_subscription; pub mod da_compression; pub mod database; +pub(crate) mod extensions; pub(crate) mod indexation; -pub(crate) mod metrics_extension; pub mod ports; -pub(crate) mod required_fuel_block_height_extension; pub mod storage; -pub(crate) mod validation_extension; pub mod worker_service; #[derive(Clone, Debug)] diff --git a/crates/fuel-core/src/graphql_api/api_service.rs b/crates/fuel-core/src/graphql_api/api_service.rs index db350120b03..fed85ec2053 100644 --- a/crates/fuel-core/src/graphql_api/api_service.rs +++ b/crates/fuel-core/src/graphql_api/api_service.rs @@ -1,6 +1,5 @@ use crate::{ fuel_core_graphql_api::{ - metrics_extension::MetricsExtension, ports::{ BlockProducerPort, ConsensusModulePort, @@ -11,12 +10,16 @@ use crate::{ P2pPort, TxPoolPort, }, - validation_extension::ValidationExtension, Config, }, graphql_api::{ self, - required_fuel_block_height_extension::RequiredFuelBlockHeightExtension, + extensions::{ + chain_state_info::ChainStateInfoExtension, + metrics::MetricsExtension, + required_fuel_block_height::RequiredFuelBlockHeightExtension, + validation::ValidationExtension, + }, }, schema::{ CoreSchema, @@ -294,8 +297,9 @@ where .extension(RequiredFuelBlockHeightExtension::new( required_fuel_block_height_tolerance, required_fuel_block_height_timeout, - block_height_subscriber, + block_height_subscriber.clone(), )) + .extension(ChainStateInfoExtension::new(block_height_subscriber)) .finish(); let graphql_endpoint = "/v1/graphql"; diff --git a/crates/fuel-core/src/graphql_api/block_height_subscription.rs b/crates/fuel-core/src/graphql_api/block_height_subscription.rs index cc4ef9bd291..cc277c96cff 100644 --- a/crates/fuel-core/src/graphql_api/block_height_subscription.rs +++ b/crates/fuel-core/src/graphql_api/block_height_subscription.rs @@ -33,7 +33,7 @@ impl Handler { // get all sending endpoint corresponding to subscribers that are waiting for a block height // that is at most `block_height`. let to_notify = inner_map.tx_handles.split_off(&Reverse(block_height)); - inner_map.latest_seen_block_height = block_height; + inner_map.current_block_height = block_height; to_notify.into_values().flatten() }; @@ -59,7 +59,7 @@ impl Subscriber { let future = { let mut inner_map = self.inner.write(); - if inner_map.latest_seen_block_height >= block_height { + if inner_map.current_block_height >= block_height { return Ok(()); } @@ -77,22 +77,22 @@ impl Subscriber { }) } - pub fn latest_seen_block_height(&self) -> BlockHeight { - self.inner.read().latest_seen_block_height + pub fn current_block_height(&self) -> BlockHeight { + self.inner.read().current_block_height } } #[derive(Debug, Default)] struct HandlersMapInner { tx_handles: BTreeMap, Vec>>, - latest_seen_block_height: BlockHeight, + current_block_height: BlockHeight, } impl HandlersMapInner { - fn new(latest_seen_block_height: BlockHeight) -> Self { + fn new(current_block_height: BlockHeight) -> Self { Self { tx_handles: BTreeMap::new(), - latest_seen_block_height, + current_block_height, } } } diff --git a/crates/fuel-core/src/graphql_api/extensions.rs b/crates/fuel-core/src/graphql_api/extensions.rs new file mode 100644 index 00000000000..71278dbb491 --- /dev/null +++ b/crates/fuel-core/src/graphql_api/extensions.rs @@ -0,0 +1,4 @@ +pub(crate) mod chain_state_info; +pub(crate) mod metrics; +pub(crate) mod required_fuel_block_height; +pub(crate) mod validation; diff --git a/crates/fuel-core/src/graphql_api/extensions/chain_state_info.rs b/crates/fuel-core/src/graphql_api/extensions/chain_state_info.rs new file mode 100644 index 00000000000..767f298b20b --- /dev/null +++ b/crates/fuel-core/src/graphql_api/extensions/chain_state_info.rs @@ -0,0 +1,77 @@ +use std::sync::Arc; + +use async_graphql::{ + extensions::{ + Extension, + ExtensionContext, + ExtensionFactory, + NextExecute, + }, + Response, + Value, +}; + +use crate::graphql_api::{ + api_service::ConsensusProvider, + block_height_subscription, +}; + +const CURRENT_STF_VERSION: &str = "current_stf_version"; +const CURRENT_CONSENSUS_PARAMETERS_VERSION: &str = "current_consensus_parameters_version"; +const CURRENT_FUEL_BLOCK_HEIGHT: &str = "current_fuel_block_height"; + +#[derive(Debug)] +pub(crate) struct ChainStateInfoExtension { + block_height_subscriber: block_height_subscription::Subscriber, +} + +impl ChainStateInfoExtension { + pub fn new(block_height_subscriber: block_height_subscription::Subscriber) -> Self { + Self { + block_height_subscriber, + } + } +} + +impl ExtensionFactory for ChainStateInfoExtension { + fn create(&self) -> Arc { + Arc::new(ChainStateInfoExtension::new( + self.block_height_subscriber.clone(), + )) + } +} + +#[async_trait::async_trait] +impl Extension for ChainStateInfoExtension { + async fn execute( + &self, + ctx: &ExtensionContext<'_>, + operation_name: Option<&str>, + next: NextExecute<'_>, + ) -> Response { + let mut response = next.run(ctx, operation_name).await; + + let consensus_parameters_provider = ctx.data_unchecked::(); + let current_consensus_parameters_version = + consensus_parameters_provider.current_consensus_parameters_version(); + response.extensions.insert( + CURRENT_CONSENSUS_PARAMETERS_VERSION.to_string(), + Value::Number(current_consensus_parameters_version.into()), + ); + + let current_stf_version = consensus_parameters_provider.current_stf_version(); + response.extensions.insert( + CURRENT_STF_VERSION.to_string(), + Value::Number(current_stf_version.into()), + ); + + let current_block_height = self.block_height_subscriber.current_block_height(); + let current_block_height: u32 = *current_block_height; + response.extensions.insert( + CURRENT_FUEL_BLOCK_HEIGHT.to_string(), + Value::Number(current_block_height.into()), + ); + + response + } +} diff --git a/crates/fuel-core/src/graphql_api/metrics_extension.rs b/crates/fuel-core/src/graphql_api/extensions/metrics.rs similarity index 100% rename from crates/fuel-core/src/graphql_api/metrics_extension.rs rename to crates/fuel-core/src/graphql_api/extensions/metrics.rs diff --git a/crates/fuel-core/src/graphql_api/required_fuel_block_height_extension.rs b/crates/fuel-core/src/graphql_api/extensions/required_fuel_block_height.rs similarity index 92% rename from crates/fuel-core/src/graphql_api/required_fuel_block_height_extension.rs rename to crates/fuel-core/src/graphql_api/extensions/required_fuel_block_height.rs index fcfb44631b7..ee37a8670e4 100644 --- a/crates/fuel-core/src/graphql_api/required_fuel_block_height_extension.rs +++ b/crates/fuel-core/src/graphql_api/extensions/required_fuel_block_height.rs @@ -1,5 +1,3 @@ -use super::block_height_subscription; - use async_graphql::{ extensions::{ Extension, @@ -24,8 +22,9 @@ use std::sync::{ use tokio::time::Duration; +use crate::graphql_api::block_height_subscription; + const REQUIRED_FUEL_BLOCK_HEIGHT: &str = "required_fuel_block_height"; -const CURRENT_FUEL_BLOCK_HEIGHT: &str = "current_fuel_block_height"; const FUEL_BLOCK_HEIGHT_PRECONDITION_FAILED: &str = "fuel_block_height_precondition_failed"; /// The extension that implements the logic for checking whether @@ -152,7 +151,7 @@ impl Extension for RequiredFuelBlockHeightInner { ) -> Response { if let Some(required_block_height) = self.required_height.get() { let current_block_height = - self.block_height_subscriber.latest_seen_block_height(); + self.block_height_subscriber.current_block_height(); match BlockHeightComparison::from_block_heights( required_block_height, @@ -200,14 +199,6 @@ impl Extension for RequiredFuelBlockHeightInner { let mut response = next.run(ctx, operation_name).await; - let current_block_height = - self.block_height_subscriber.latest_seen_block_height(); - // Dereference to display the value in decimal base. - let current_block_height: u32 = *current_block_height; - response.extensions.insert( - CURRENT_FUEL_BLOCK_HEIGHT.to_string(), - Value::Number(current_block_height.into()), - ); if self.required_height.get().is_some() { response.extensions.insert( FUEL_BLOCK_HEIGHT_PRECONDITION_FAILED.to_string(), @@ -243,10 +234,6 @@ fn error_response( }), )]); - response.extensions.insert( - CURRENT_FUEL_BLOCK_HEIGHT.to_string(), - Value::Number((**current_block_height).into()), - ); response.extensions.insert( FUEL_BLOCK_HEIGHT_PRECONDITION_FAILED.to_string(), Value::Boolean(true), diff --git a/crates/fuel-core/src/graphql_api/validation_extension.rs b/crates/fuel-core/src/graphql_api/extensions/validation.rs similarity index 91% rename from crates/fuel-core/src/graphql_api/validation_extension.rs rename to crates/fuel-core/src/graphql_api/extensions/validation.rs index bbc6572b9d6..eab17e24c64 100644 --- a/crates/fuel-core/src/graphql_api/validation_extension.rs +++ b/crates/fuel-core/src/graphql_api/extensions/validation.rs @@ -1,11 +1,3 @@ -use crate::{ - fuel_core_graphql_api::validation_extension::visitor::{ - visit, - RuleError, - VisitorContext, - }, - graphql_api::validation_extension::recursion_finder::RecursionFinder, -}; use async_graphql::{ extensions::{ Extension, @@ -20,10 +12,16 @@ use async_graphql::{ ValidationResult, Variables, }; +use recursion_finder::RecursionFinder; use std::sync::{ Arc, Mutex, }; +use visitor::{ + visit, + RuleError, + VisitorContext, +}; mod recursion_finder; mod visitor; diff --git a/crates/fuel-core/src/graphql_api/validation_extension/recursion_finder.rs b/crates/fuel-core/src/graphql_api/extensions/validation/recursion_finder.rs similarity index 96% rename from crates/fuel-core/src/graphql_api/validation_extension/recursion_finder.rs rename to crates/fuel-core/src/graphql_api/extensions/validation/recursion_finder.rs index 003eefdf821..fb47dad2518 100644 --- a/crates/fuel-core/src/graphql_api/validation_extension/recursion_finder.rs +++ b/crates/fuel-core/src/graphql_api/extensions/validation/recursion_finder.rs @@ -1,7 +1,3 @@ -use crate::fuel_core_graphql_api::validation_extension::visitor::{ - Visitor, - VisitorContext, -}; use async_graphql::{ parser::types::Field, Positioned, @@ -11,6 +7,11 @@ use std::collections::{ HashMap, }; +use super::visitor::{ + Visitor, + VisitorContext, +}; + pub(super) struct RecursionFinder<'a> { visited: HashMap<&'a str, usize>, recursion_limit: usize, @@ -51,6 +52,7 @@ impl<'a> Visitor<'a> for RecursionFinder<'a> { } } } + fn exit_field(&mut self, ctx: &mut VisitorContext<'a>, _: &'a Positioned) { let ty = ctx.type_stack.last(); diff --git a/crates/fuel-core/src/graphql_api/validation_extension/visitor.rs b/crates/fuel-core/src/graphql_api/extensions/validation/visitor.rs similarity index 100% rename from crates/fuel-core/src/graphql_api/validation_extension/visitor.rs rename to crates/fuel-core/src/graphql_api/extensions/validation/visitor.rs diff --git a/crates/fuel-core/src/graphql_api/ports.rs b/crates/fuel-core/src/graphql_api/ports.rs index 2cd42b0d1f1..40f276953e1 100644 --- a/crates/fuel-core/src/graphql_api/ports.rs +++ b/crates/fuel-core/src/graphql_api/ports.rs @@ -32,7 +32,10 @@ use fuel_core_types::{ blockchain::{ block::CompressedBlock, consensus::Consensus, - header::ConsensusParametersVersion, + header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, primitives::{ BlockId, DaBlockHeight, @@ -459,11 +462,18 @@ pub mod worker { } pub trait ConsensusProvider: Send + Sync { - /// Returns latest consensus parameters. - fn latest_consensus_params(&self) -> Arc; + /// Returns current consensus parameters. + fn current_consensus_params(&self) -> Arc; + + /// Returns current consensus parameters version. + fn current_consensus_parameters_version(&self) -> ConsensusParametersVersion; + /// Returns consensus parameters at a specific version. fn consensus_params_at_version( &self, version: &ConsensusParametersVersion, ) -> anyhow::Result>; + + /// Returns the current state transition bytecode version. + fn current_stf_version(&self) -> StateTransitionBytecodeVersion; } diff --git a/crates/fuel-core/src/graphql_api/worker_service.rs b/crates/fuel-core/src/graphql_api/worker_service.rs index d490ed9861b..22b61a90a8b 100644 --- a/crates/fuel-core/src/graphql_api/worker_service.rs +++ b/crates/fuel-core/src/graphql_api/worker_service.rs @@ -111,6 +111,16 @@ use std::{ #[cfg(test)] mod tests; +pub(crate) struct Context<'a, TxPool, BlockImporter, OnChain, OffChain> { + pub(crate) tx_pool: TxPool, + pub(crate) block_importer: BlockImporter, + pub(crate) on_chain_database: OnChain, + pub(crate) off_chain_database: OffChain, + pub(crate) da_compression_config: DaCompressionConfig, + pub(crate) continue_on_error: bool, + pub(crate) consensus_parameters: &'a ConsensusParameters, +} + #[derive(Debug, Clone)] pub enum DaCompressionConfig { Disabled, @@ -728,14 +738,9 @@ where } } -pub fn new_service( - tx_pool: TxPool, - block_importer: BlockImporter, - on_chain_database: OnChain, - off_chain_database: OffChain, - da_compression_config: DaCompressionConfig, - continue_on_error: bool, - consensus_parameters: &ConsensusParameters, +#[allow(clippy::type_complexity)] +pub(crate) fn new_service( + context: Context, ) -> anyhow::Result>> where TxPool: ports::worker::TxPool, @@ -743,6 +748,16 @@ where OffChain: ports::worker::OffChainDatabase, BlockImporter: ports::worker::BlockImporter, { + let Context { + tx_pool, + block_importer, + on_chain_database, + off_chain_database, + da_compression_config, + continue_on_error, + consensus_parameters, + } = context; + let off_chain_block_height = off_chain_database.latest_height()?.unwrap_or_default(); let service = ServiceRunner::new(InitializeTask { diff --git a/crates/fuel-core/src/schema/balance.rs b/crates/fuel-core/src/schema/balance.rs index 673e5cebb2e..1c312d160eb 100644 --- a/crates/fuel-core/src/schema/balance.rs +++ b/crates/fuel-core/src/schema/balance.rs @@ -71,7 +71,7 @@ impl BalanceQuery { let query = ctx.read_view()?; let base_asset_id = *ctx .data_unchecked::() - .latest_consensus_params() + .current_consensus_params() .base_asset_id(); let balance = query .balance(owner.0, asset_id.0, base_asset_id) @@ -124,7 +124,7 @@ impl BalanceQuery { } let base_asset_id = *ctx .data_unchecked::() - .latest_consensus_params() + .current_consensus_params() .base_asset_id(); let owner = filter.owner.into(); crate::schema::query_pagination(after, before, first, last, |start, direction| { diff --git a/crates/fuel-core/src/schema/chain.rs b/crates/fuel-core/src/schema/chain.rs index 830f281c69f..113c1f09368 100644 --- a/crates/fuel-core/src/schema/chain.rs +++ b/crates/fuel-core/src/schema/chain.rs @@ -814,7 +814,7 @@ impl ChainInfo { ) -> async_graphql::Result { let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); Ok(ConsensusParameters(params)) } @@ -823,7 +823,7 @@ impl ChainInfo { async fn gas_costs(&self, ctx: &Context<'_>) -> async_graphql::Result { let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); Ok(GasCosts(params.gas_costs().clone())) } diff --git a/crates/fuel-core/src/schema/coins.rs b/crates/fuel-core/src/schema/coins.rs index 9f0a3bea1a0..eb2eab41140 100644 --- a/crates/fuel-core/src/schema/coins.rs +++ b/crates/fuel-core/src/schema/coins.rs @@ -117,7 +117,7 @@ impl MessageCoin { async fn asset_id(&self, ctx: &Context<'_>) -> AssetId { let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let base_asset_id = *params.base_asset_id(); base_asset_id.into() @@ -257,7 +257,7 @@ impl CoinQuery { ) -> async_graphql::Result>> { let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let max_input = params.tx_params().max_inputs(); let excluded_id_count = excluded_ids.as_ref().map_or(0, |exclude| { diff --git a/crates/fuel-core/src/schema/dap.rs b/crates/fuel-core/src/schema/dap.rs index 0bbfb0be37b..6b98b1abd0a 100644 --- a/crates/fuel-core/src/schema/dap.rs +++ b/crates/fuel-core/src/schema/dap.rs @@ -326,7 +326,7 @@ impl DapMutation { let db = ctx.data_unchecked::(); let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let id = ctx.data_unchecked::() @@ -359,7 +359,7 @@ impl DapMutation { let db = ctx.data_unchecked::(); let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); ctx.data_unchecked::() .lock() @@ -450,7 +450,7 @@ impl DapMutation { let mut locked = ctx.data_unchecked::().lock().await; let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let vm = locked .vm diff --git a/crates/fuel-core/src/schema/tx.rs b/crates/fuel-core/src/schema/tx.rs index 86780eeb736..83b45b711f8 100644 --- a/crates/fuel-core/src/schema/tx.rs +++ b/crates/fuel-core/src/schema/tx.rs @@ -216,7 +216,7 @@ impl TxQuery { let query = ctx.read_view()?; let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let owner = fuel_types::Address::from(owner); crate::schema::query_pagination( @@ -254,7 +254,7 @@ impl TxQuery { let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let memory_pool = ctx.data_unchecked::(); let memory = memory_pool.get_memory().await; @@ -331,7 +331,7 @@ impl TxMutation { let block_producer = ctx.data_unchecked::(); let consensus_params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let block_gas_limit = consensus_params.block_gas_limit(); if block_height.is_some() && !config.historical_execution { @@ -384,7 +384,7 @@ impl TxMutation { let txpool = ctx.data_unchecked::(); let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let tx = FuelTx::from_bytes(&tx.0)?; txpool @@ -476,7 +476,7 @@ async fn submit_and_await_status<'a>( let txpool = ctx.data_unchecked::(); let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let tx = FuelTx::from_bytes(&tx.0)?; let tx_id = tx.id(¶ms.chain_id()); let subscription = txpool.tx_update_subscribe(tx_id)?; diff --git a/crates/fuel-core/src/schema/tx/types.rs b/crates/fuel-core/src/schema/tx/types.rs index 572d5070898..61222c0a671 100644 --- a/crates/fuel-core/src/schema/tx/types.rs +++ b/crates/fuel-core/src/schema/tx/types.rs @@ -416,7 +416,7 @@ impl Transaction { async fn input_asset_ids(&self, ctx: &Context<'_>) -> Option> { let params = ctx .data_unchecked::() - .latest_consensus_params(); + .current_consensus_params(); let base_asset_id = params.base_asset_id(); match &self.0 { fuel_tx::Transaction::Script(tx) => Some( diff --git a/crates/fuel-core/src/service/adapters/consensus_parameters_provider.rs b/crates/fuel-core/src/service/adapters/consensus_parameters_provider.rs index 1f30ec7ebb2..7d34f5bc30e 100644 --- a/crates/fuel-core/src/service/adapters/consensus_parameters_provider.rs +++ b/crates/fuel-core/src/service/adapters/consensus_parameters_provider.rs @@ -8,7 +8,7 @@ use fuel_core_services::{ RunnableService, RunnableTask, ServiceRunner, - SharedMutex, + SharedRwLock, StateWatcher, TaskNextAction, }; @@ -21,7 +21,10 @@ use fuel_core_storage::{ }; use fuel_core_txpool::ports::BlockImporter; use fuel_core_types::{ - blockchain::header::ConsensusParametersVersion, + blockchain::header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, fuel_tx::ConsensusParameters, services::block_importer::SharedImportResult, }; @@ -35,9 +38,10 @@ use std::{ #[derive(Clone, Debug)] pub struct SharedState { pub(crate) latest_consensus_parameters_version: - SharedMutex, + SharedRwLock, pub(crate) consensus_parameters: - SharedMutex>>, + SharedRwLock>>, + pub(crate) latest_stf_version: SharedRwLock, pub(crate) database: Database, } @@ -56,9 +60,13 @@ impl Debug for Task { impl SharedState { fn new(database: Database) -> Self { + // We set versions in the `into_task` function during start of the service. let genesis_version = 0; + let latest_stf_version = 0; + Self { - latest_consensus_parameters_version: SharedMutex::new(genesis_version), + latest_consensus_parameters_version: SharedRwLock::new(genesis_version), + latest_stf_version: SharedRwLock::new(latest_stf_version), consensus_parameters: Default::default(), database, } @@ -77,7 +85,7 @@ impl SharedState { let consensus_parameters = Arc::new(consensus_parameters); self.consensus_parameters - .lock() + .write() .insert(version, consensus_parameters.clone()); Ok(consensus_parameters) } @@ -87,7 +95,7 @@ impl SharedState { version: &ConsensusParametersVersion, ) -> StorageResult> { { - let consensus_parameters = self.consensus_parameters.lock(); + let consensus_parameters = self.consensus_parameters.read(); if let Some(parameters) = consensus_parameters.get(version) { return Ok(parameters.clone()); } @@ -96,18 +104,34 @@ impl SharedState { self.cache_consensus_parameters(*version) } + fn cache_consensus_parameters_version(&self, version: ConsensusParametersVersion) { + *self.latest_consensus_parameters_version.write() = version; + } + pub fn latest_consensus_parameters(&self) -> Arc { self.latest_consensus_parameters_with_version().1 } + pub fn latest_consensus_parameters_version(&self) -> ConsensusParametersVersion { + *self.latest_consensus_parameters_version.read() + } + pub fn latest_consensus_parameters_with_version( &self, ) -> (ConsensusParametersVersion, Arc) { - let version = *self.latest_consensus_parameters_version.lock(); + let version = self.latest_consensus_parameters_version(); let params = self.get_consensus_parameters(&version) .expect("The latest consensus parameters always are available unless this function was called before regenesis."); (version, params) } + + fn cache_stf_version(&self, version: StateTransitionBytecodeVersion) { + *self.latest_stf_version.write() = version; + } + + pub fn latest_stf_version(&self) -> StateTransitionBytecodeVersion { + *self.latest_stf_version.read() + } } impl RunnableTask for Task { @@ -120,16 +144,16 @@ impl RunnableTask for Task { } Some(event) = self.blocks_events.next() => { - let new_version = event + let header = event .sealed_block .entity - .header() - .consensus_parameters_version(); + .header(); - if new_version > *self.shared_state.latest_consensus_parameters_version.lock() { - match self.shared_state.cache_consensus_parameters(new_version) { + let new_consensus_parameters_version = header.consensus_parameters_version(); + if new_consensus_parameters_version > self.shared_state.latest_consensus_parameters_version() { + match self.shared_state.cache_consensus_parameters(new_consensus_parameters_version) { Ok(_) => { - *self.shared_state.latest_consensus_parameters_version.lock() = new_version; + self.shared_state.cache_consensus_parameters_version(new_consensus_parameters_version); } Err(err) => { tracing::error!("Failed to cache consensus parameters: {:?}", err); @@ -137,6 +161,12 @@ impl RunnableTask for Task { } } } + + let new_stf_version = header.state_transition_bytecode_version(); + if new_stf_version > self.shared_state.latest_stf_version() { + self.shared_state.cache_stf_version(new_stf_version); + } + TaskNextAction::Continue } } @@ -169,10 +199,12 @@ impl RunnableService for Task { .database .latest_view()? .latest_consensus_parameters_version()?; + self.shared_state + .cache_consensus_parameters_version(latest_consensus_parameters_version); self.shared_state .cache_consensus_parameters(latest_consensus_parameters_version)?; - *self.shared_state.latest_consensus_parameters_version.lock() = - latest_consensus_parameters_version; + self.shared_state + .cache_stf_version(latest_consensus_parameters_version); Ok(self) } diff --git a/crates/fuel-core/src/service/adapters/gas_price_adapters/tests.rs b/crates/fuel-core/src/service/adapters/gas_price_adapters/tests.rs index f19aff2c143..2158b0859ac 100644 --- a/crates/fuel-core/src/service/adapters/gas_price_adapters/tests.rs +++ b/crates/fuel-core/src/service/adapters/gas_price_adapters/tests.rs @@ -4,7 +4,7 @@ use crate::service::adapters::{ consensus_parameters_provider, gas_price_adapters::GasPriceSettings, }; -use fuel_core_services::SharedMutex; +use fuel_core_services::SharedRwLock; use std::{ collections::HashMap, sync::Arc, @@ -15,14 +15,17 @@ fn settings__can_retrieve_settings() { // given let param_version = fuel_core_types::blockchain::header::ConsensusParametersVersion::default(); + let stf_version = + fuel_core_types::blockchain::header::StateTransitionBytecodeVersion::default(); let params = fuel_core_types::fuel_tx::consensus_parameters::ConsensusParametersV1::default(); let mut hash_map = HashMap::new(); hash_map.insert(param_version, Arc::new(params.clone().into())); let shared_state = consensus_parameters_provider::SharedState { - latest_consensus_parameters_version: SharedMutex::new(param_version), - consensus_parameters: SharedMutex::new(hash_map), + latest_consensus_parameters_version: SharedRwLock::new(param_version), + consensus_parameters: SharedRwLock::new(hash_map), database: Default::default(), + latest_stf_version: SharedRwLock::new(stf_version), }; let consensus_parameters_provider = crate::service::adapters::ConsensusParametersProvider::new(shared_state); diff --git a/crates/fuel-core/src/service/adapters/graphql_api.rs b/crates/fuel-core/src/service/adapters/graphql_api.rs index 064dbffb477..cb59150abf5 100644 --- a/crates/fuel-core/src/service/adapters/graphql_api.rs +++ b/crates/fuel-core/src/service/adapters/graphql_api.rs @@ -35,7 +35,10 @@ use fuel_core_txpool::{ TxStatusMessage, }; use fuel_core_types::{ - blockchain::header::ConsensusParametersVersion, + blockchain::header::{ + ConsensusParametersVersion, + StateTransitionBytecodeVersion, + }, entities::relayer::message::MerkleProof, fuel_tx::{ Bytes32, @@ -198,16 +201,24 @@ impl GasPriceEstimate for StaticGasPrice { } impl ConsensusProvider for ConsensusParametersProvider { - fn latest_consensus_params(&self) -> Arc { + fn current_consensus_params(&self) -> Arc { self.shared_state.latest_consensus_parameters() } + fn current_consensus_parameters_version(&self) -> ConsensusParametersVersion { + self.shared_state.latest_consensus_parameters_version() + } + fn consensus_params_at_version( &self, version: &ConsensusParametersVersion, ) -> anyhow::Result> { Ok(self.shared_state.get_consensus_parameters(version)?) } + + fn current_stf_version(&self) -> StateTransitionBytecodeVersion { + self.shared_state.latest_stf_version() + } } #[derive(Clone)] diff --git a/crates/fuel-core/src/service/sub_services.rs b/crates/fuel-core/src/service/sub_services.rs index 6630ebfff30..682e6f7d5a6 100644 --- a/crates/fuel-core/src/service/sub_services.rs +++ b/crates/fuel-core/src/service/sub_services.rs @@ -13,8 +13,11 @@ use crate::relayer::Config as RelayerConfig; use crate::{ combined_database::CombinedDatabase, database::Database, - fuel_core_graphql_api, - fuel_core_graphql_api::Config as GraphQLConfig, + fuel_core_graphql_api::{ + self, + Config as GraphQLConfig, + }, + graphql_api::worker_service, schema::build_schema, service::{ adapters::{ @@ -328,15 +331,17 @@ pub fn init_sub_services( let graphql_block_importer = GraphQLBlockImporter::new(importer_adapter.clone(), import_result_provider); - let graphql_worker = fuel_core_graphql_api::worker_service::new_service( - tx_pool_adapter.clone(), - graphql_block_importer, - database.on_chain().clone(), - database.off_chain().clone(), - config.da_compression.clone(), - config.continue_on_error, - &chain_config.consensus_parameters, - )?; + let graphql_worker_context = worker_service::Context { + tx_pool: tx_pool_adapter.clone(), + block_importer: graphql_block_importer, + on_chain_database: database.on_chain().clone(), + off_chain_database: database.off_chain().clone(), + da_compression_config: config.da_compression.clone(), + continue_on_error: config.continue_on_error, + consensus_parameters: &chain_config.consensus_parameters, + }; + let graphql_worker = + fuel_core_graphql_api::worker_service::new_service(graphql_worker_context)?; let graphql_block_height_subscription_handle = graphql_worker.shared.clone(); diff --git a/crates/services/src/lib.rs b/crates/services/src/lib.rs index 70672784fc0..486f89f681d 100644 --- a/crates/services/src/lib.rs +++ b/crates/services/src/lib.rs @@ -96,6 +96,7 @@ use std::fmt::Display; pub use sync::{ Shared, SharedMutex, + SharedRwLock, }; #[cfg(feature = "sync-processor")] pub use sync_processor::SyncProcessor; diff --git a/crates/services/src/sync.rs b/crates/services/src/sync.rs index 7d869b67203..357caa85452 100644 --- a/crates/services/src/sync.rs +++ b/crates/services/src/sync.rs @@ -41,3 +41,34 @@ impl From for SharedMutex { Self::new(t) } } + +/// A RwLock that can safely be in async contexts and avoids deadlocks. +#[derive(Default, Debug)] +pub struct SharedRwLock(Shared>); + +impl Clone for SharedRwLock { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Deref for SharedRwLock { + type Target = Shared>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl SharedRwLock { + /// Creates a new `SharedRwLock` with the given value. + pub fn new(t: T) -> Self { + Self(Shared::new(parking_lot::RwLock::new(t))) + } +} + +impl From for SharedRwLock { + fn from(t: T) -> Self { + Self::new(t) + } +} diff --git a/tests/test-helpers/Cargo.toml b/tests/test-helpers/Cargo.toml index a9ce35236c1..26cc26a88e0 100644 --- a/tests/test-helpers/Cargo.toml +++ b/tests/test-helpers/Cargo.toml @@ -34,5 +34,6 @@ futures = { workspace = true } itertools = { workspace = true } rand = { workspace = true } reqwest = { workspace = true } +serde_json = { workspace = true } tokio = { workspace = true } tempfile = { workspace = true } diff --git a/tests/test-helpers/src/counter_contract.rs b/tests/test-helpers/src/counter_contract.rs index 7a93c2ec812..a1ff4a499c3 100644 --- a/tests/test-helpers/src/counter_contract.rs +++ b/tests/test-helpers/src/counter_contract.rs @@ -77,7 +77,8 @@ pub async fn deploy( let contract_id = CreateMetadata::compute(&tx).unwrap().contract_id; - let mut status_stream = client.submit_and_await_status(&tx.into()).await.unwrap(); + let tx = tx.into(); + let mut status_stream = client.submit_and_await_status(&tx).await.unwrap(); let intermediate_status = status_stream.next().await.unwrap().unwrap(); assert!(matches!( intermediate_status, diff --git a/tests/test-helpers/src/lib.rs b/tests/test-helpers/src/lib.rs index fe1f0a702fd..f4b1b8dacd7 100644 --- a/tests/test-helpers/src/lib.rs +++ b/tests/test-helpers/src/lib.rs @@ -9,9 +9,14 @@ use fuel_core_types::{ }, fuel_crypto::SecretKey, fuel_tx::{ + policies::Policies, + AssetId, + Input, Output, Transaction, TransactionBuilder, + Upload, + UploadSubsection, }, }; use rand::{ @@ -25,6 +30,43 @@ pub mod builder; pub mod counter_contract; pub mod fuel_core_driver; +pub fn predicate() -> Vec { + vec![op::ret(1)].into_iter().collect::>() +} + +pub fn valid_input(rng: &mut StdRng, amount: u64) -> Input { + let owner = Input::predicate_owner(predicate()); + Input::coin_predicate( + rng.gen(), + owner, + amount, + AssetId::BASE, + Default::default(), + Default::default(), + predicate(), + vec![], + ) +} + +pub fn transactions_from_subsections( + rng: &mut StdRng, + subsections: Vec, + amount: u64, +) -> Vec { + subsections + .into_iter() + .map(|subsection| { + Transaction::upload_from_subsection( + subsection, + Policies::new().with_max_fee(amount), + vec![valid_input(rng, amount)], + vec![], + vec![], + ) + }) + .collect() +} + pub async fn send_graph_ql_query(url: &str, query: &str) -> String { let client = reqwest::Client::new(); let mut map = std::collections::HashMap::new(); diff --git a/tests/tests/graphql_extensions.rs b/tests/tests/graphql_extensions.rs new file mode 100644 index 00000000000..eebb6488762 --- /dev/null +++ b/tests/tests/graphql_extensions.rs @@ -0,0 +1,243 @@ +use fuel_core::service::Config; +use fuel_core_bin::FuelService; +use fuel_core_client::client::{ + types::TransactionStatus, + FuelClient, +}; +use fuel_core_types::fuel_tx::{ + policies::Policies, + Address, + AssetId, + Bytes32, + GasCosts, + Input, + Transaction, + UpgradePurpose, + Upload, + UploadSubsection, +}; +use fuel_core_upgradable_executor::WASM_BYTECODE; +use itertools::Itertools; +use rand::{ + rngs::StdRng, + Rng, +}; +use serde_json::Value; +use test_helpers::{ + builder::{ + TestContext, + TestSetupBuilder, + }, + predicate, + send_graph_ql_query, + transactions_from_subsections, +}; + +#[tokio::test] +async fn extension_fields_are_present() { + const QUERY: &str = r#" + query { + nodeInfo { + nodeVersion + } + } + "#; + + const REQUIRED_FIELDS: [&str; 3] = [ + "current_stf_version", + "current_fuel_block_height", + "current_consensus_parameters_version", + ]; + + // Given + let node = FuelService::new_node(Config::local_node()).await.unwrap(); + let url = format!("http://{}/v1/graphql", node.bound_address); + + // When + let response = send_graph_ql_query(&url, QUERY).await; + + // Then + let json_value: Value = + serde_json::from_str(&response).expect("should be valid json"); + let extensions = json_value + .get("extensions") + .expect("should have extensions"); + for field in REQUIRED_FIELDS.iter() { + let is_field_present = extensions.get(field).is_some(); + assert!(is_field_present) + } +} + +async fn upgrade_consensus_parameters( + rng: &mut StdRng, + client: &FuelClient, + privileged_address: &Address, +) { + const AMOUNT: u64 = 1_000; + + let mut new_consensus_parameters = + client.chain_info().await.unwrap().consensus_parameters; + new_consensus_parameters.set_gas_costs(GasCosts::free()); + + let upgrade = Transaction::upgrade_consensus_parameters( + &new_consensus_parameters, + Policies::new().with_max_fee(AMOUNT), + vec![Input::coin_predicate( + rng.gen(), + *privileged_address, + AMOUNT, + AssetId::BASE, + Default::default(), + Default::default(), + predicate(), + vec![], + )], + vec![], + vec![], + ) + .unwrap(); + + let mut tx = upgrade.into(); + client.estimate_predicates(&mut tx).await.unwrap(); + client.submit_and_await_commit(&tx).await.unwrap(); + client + .produce_blocks(1, None) + .await + .expect("should produce block"); +} + +#[tokio::test] +async fn graphql_extensions_should_provide_new_consensus_parameters_version_after_upgrade( +) { + let mut test_builder = TestSetupBuilder::new(2322); + let privileged_address = Input::predicate_owner(predicate()); + test_builder.utxo_validation = false; + test_builder.privileged_address = privileged_address; + let TestContext { + client, + srv: _srv, + mut rng, + .. + } = test_builder.finalize().await; + client + .produce_blocks(1, None) + .await + .expect("should produce block"); + + // Given + let pre_upgrade_version = client + .latest_consensus_parameters_version() + .expect("should have consensus parameters version"); + + // When + upgrade_consensus_parameters(&mut rng, &client, &privileged_address).await; + + // Then + let post_upgrade_version = client + .latest_consensus_parameters_version() + .expect("should have consensus parameters version"); + + assert_eq!(post_upgrade_version, pre_upgrade_version + 1); +} + +fn prepare_upload_transactions(rng: &mut StdRng, amount: u64) -> (Bytes32, Vec) { + const SUBSECTION_SIZE: usize = 64 * 1024; + + let subsections = + UploadSubsection::split_bytecode(WASM_BYTECODE, SUBSECTION_SIZE).unwrap(); + let root = subsections[0].root; + + let transactions = transactions_from_subsections(rng, subsections, amount); + (root, transactions) +} + +async fn upgrade_stf( + rng: &mut StdRng, + client: &FuelClient, + privileged_address: &Address, + transactions: impl Iterator, + amount: u64, + root: Bytes32, +) { + for upload in transactions { + let mut tx = upload.into(); + client + .estimate_predicates(&mut tx) + .await + .expect("Should estimate transaction"); + let result = client.submit_and_await_commit(&tx).await; + let result = result.expect("We should be able to upload the bytecode subsection"); + assert!(matches!(result, TransactionStatus::Success { .. })) + } + + let upgrade = Transaction::upgrade( + UpgradePurpose::StateTransition { root }, + Policies::new().with_max_fee(amount), + vec![Input::coin_predicate( + rng.gen(), + *privileged_address, + amount, + AssetId::BASE, + Default::default(), + Default::default(), + predicate(), + vec![], + )], + vec![], + vec![], + ); + let mut tx = upgrade.into(); + client.estimate_predicates(&mut tx).await.unwrap(); + let result = client.submit_and_await_commit(&tx).await; + let result = result.expect("We should be able to upgrade to the uploaded bytecode"); + assert!(matches!(result, TransactionStatus::Success { .. })); + client + .produce_blocks(1, None) + .await + .expect("should produce block"); +} + +#[tokio::test] +async fn graphql_extensions_should_provide_new_stf_version_after_upgrade() { + const AMOUNT: u64 = 1_000; + + let mut test_builder = TestSetupBuilder::new(2322); + let privileged_address = Input::predicate_owner(predicate()); + test_builder.utxo_validation = false; + test_builder.privileged_address = privileged_address; + let TestContext { + client, + srv: _srv, + mut rng, + .. + } = test_builder.finalize().await; + client + .produce_blocks(1, None) + .await + .expect("should produce block"); + + // Given + let (root, transactions) = prepare_upload_transactions(&mut rng, AMOUNT); + test_builder.config_coin_inputs_from_transactions(&transactions.iter().collect_vec()); + + let pre_upgrade_version = client + .latest_stf_version() + .expect("should have stf version"); + + // When + upgrade_stf( + &mut rng, + &client, + &privileged_address, + transactions.into_iter(), + AMOUNT, + root, + ) + .await; + + // Then + let post_upgrade_version = client + .latest_stf_version() + .expect("should have stf version"); + assert_eq!(post_upgrade_version, pre_upgrade_version + 1); +} diff --git a/tests/tests/lib.rs b/tests/tests/lib.rs index 3b6a515bb93..fb4868305f2 100644 --- a/tests/tests/lib.rs +++ b/tests/tests/lib.rs @@ -31,6 +31,8 @@ mod fee_collection_contract; #[cfg(not(feature = "only-p2p"))] mod gas_price; #[cfg(not(feature = "only-p2p"))] +mod graphql_extensions; +#[cfg(not(feature = "only-p2p"))] mod health; #[cfg(not(feature = "only-p2p"))] mod helpers; diff --git a/tests/tests/tx/upgrade.rs b/tests/tests/tx/upgrade.rs index 0bea3b41f11..5faeeaf5219 100644 --- a/tests/tests/tx/upgrade.rs +++ b/tests/tests/tx/upgrade.rs @@ -1,6 +1,5 @@ use fuel_core_client::client::types::TransactionStatus; use fuel_core_types::{ - fuel_asm::op, fuel_tx::{ policies::Policies, AssetId, @@ -10,7 +9,6 @@ use fuel_core_types::{ Receipt, Transaction, UpgradePurpose, - Upload, UploadSubsection, }, fuel_vm::UploadedBytecode, @@ -21,50 +19,18 @@ use rand::{ rngs::StdRng, Rng, }; -use test_helpers::builder::{ - TestContext, - TestSetupBuilder, +use test_helpers::{ + builder::{ + TestContext, + TestSetupBuilder, + }, + predicate, + transactions_from_subsections, + valid_input, }; const SUBSECTION_SIZE: usize = 64 * 1024; -fn predicate() -> Vec { - vec![op::ret(1)].into_iter().collect::>() -} - -fn valid_input(rng: &mut StdRng, amount: u64) -> Input { - let owner = Input::predicate_owner(predicate()); - Input::coin_predicate( - rng.gen(), - owner, - amount, - AssetId::BASE, - Default::default(), - Default::default(), - predicate(), - vec![], - ) -} - -fn transactions_from_subsections( - rng: &mut StdRng, - subsections: Vec, - amount: u64, -) -> Vec { - subsections - .into_iter() - .map(|subsection| { - Transaction::upload_from_subsection( - subsection, - Policies::new().with_max_fee(amount), - vec![valid_input(rng, amount)], - vec![], - vec![], - ) - }) - .collect_vec() -} - #[tokio::test] async fn can_upload_current_state_transition_function() { let amount = 1_000;