diff --git a/packages/fuels-accounts/Cargo.toml b/packages/fuels-accounts/Cargo.toml index 3918be22a..ed37c9a8d 100644 --- a/packages/fuels-accounts/Cargo.toml +++ b/packages/fuels-accounts/Cargo.toml @@ -25,7 +25,6 @@ semver = { workspace = true } tai64 = { workspace = true, features = ["serde"] } thiserror = { workspace = true, default-features = false } tokio = { workspace = true, features = ["full"], optional = true } -tracing = { workspace = true } zeroize = { workspace = true, features = ["derive"] } [dev-dependencies] diff --git a/packages/fuels-accounts/src/provider.rs b/packages/fuels-accounts/src/provider.rs index 50ef9d809..c5f046331 100644 --- a/packages/fuels-accounts/src/provider.rs +++ b/packages/fuels-accounts/src/provider.rs @@ -31,7 +31,7 @@ use fuels_core::{ chain_info::ChainInfo, coin::Coin, coin_type::CoinType, - errors::{error, Result}, + errors::Result, message::Message, message_proof::MessageProof, node_info::NodeInfo, @@ -42,7 +42,7 @@ use fuels_core::{ }, }; pub use retry_util::{Backoff, RetryConfig}; -use supported_versions::{check_fuel_core_version_compatibility, VersionCompatibility}; + use tai64::Tai64; #[cfg(feature = "coin-cache")] use tokio::sync::Mutex; @@ -131,11 +131,8 @@ impl Provider { /// Connects to an existing node at the given address. pub async fn connect(url: impl AsRef) -> Result { - let client = RetryableClient::new(&url, Default::default())?; + let client = RetryableClient::connect(&url, Default::default()).await?; let consensus_parameters = client.chain_info().await?.consensus_parameters; - let node_info = client.node_info().await?.into(); - - Self::ensure_client_version_is_supported(&node_info)?; Ok(Self { client, @@ -243,38 +240,6 @@ impl Provider { self.consensus_parameters.base_asset_id() } - fn ensure_client_version_is_supported(node_info: &NodeInfo) -> Result<()> { - let node_version = node_info - .node_version - .parse::() - .map_err(|e| error!(Provider, "could not parse Fuel client version: {}", e))?; - - let VersionCompatibility { - supported_version, - is_major_supported, - is_minor_supported, - is_patch_supported, - } = check_fuel_core_version_compatibility(node_version.clone()); - - if !is_major_supported || !is_minor_supported { - return Err(error!( - Provider, - "unsupported Fuel client version. \\ - Current version: {}, supported version: {}", - node_version, - supported_version - )); - } else if !is_patch_supported { - tracing::warn!( - fuel_client_version = %node_version, - supported_version = %supported_version, - "the patch versions of the client and SDK differ", - ); - }; - - Ok(()) - } - pub fn chain_id(&self) -> ChainId { self.consensus_parameters.chain_id() } diff --git a/packages/fuels-accounts/src/provider/retryable_client.rs b/packages/fuels-accounts/src/provider/retryable_client.rs index 3a1da44fc..2ac18b8cf 100644 --- a/packages/fuels-accounts/src/provider/retryable_client.rs +++ b/packages/fuels-accounts/src/provider/retryable_client.rs @@ -17,10 +17,12 @@ use fuels_core::types::errors::{error, Error, Result}; use crate::provider::{retry_util, RetryConfig}; +use super::supported_versions::{self, VersionCompatibility}; + #[derive(Debug, thiserror::Error)] pub(crate) enum RequestError { - #[error(transparent)] - IO(#[from] io::Error), + #[error("io error: {0}")] + IO(String), } type RequestResult = std::result::Result; @@ -36,20 +38,49 @@ pub(crate) struct RetryableClient { client: FuelClient, url: String, retry_config: RetryConfig, + prepend_warning: Option, } impl RetryableClient { - pub(crate) fn new(url: impl AsRef, retry_config: RetryConfig) -> Result { + pub(crate) async fn connect(url: impl AsRef, retry_config: RetryConfig) -> Result { let url = url.as_ref().to_string(); let client = FuelClient::new(&url).map_err(|e| error!(Provider, "{e}"))?; + let node_info = client.node_info().await?; + let warning = Self::version_compatibility_warning(&node_info)?; + Ok(Self { client, retry_config, url, + prepend_warning: warning, }) } + fn version_compatibility_warning(node_info: &NodeInfo) -> Result> { + let node_version = node_info + .node_version + .parse::() + .map_err(|e| error!(Provider, "could not parse Fuel client version: {}", e))?; + + let VersionCompatibility { + supported_version, + is_major_supported, + is_minor_supported, + .. + } = supported_versions::compare_node_compatibility(node_version.clone()); + + let msg = if !is_major_supported || !is_minor_supported { + Some(format!( + "warning: the fuel node version to which this provider is connected has a semver incompatible version from the one the SDK was developed against. Connected node version: {node_version}, supported version: {supported_version}", + )) + } else { + None + }; + + Ok(msg) + } + pub(crate) fn url(&self) -> &str { &self.url } @@ -58,57 +89,64 @@ impl RetryableClient { self.retry_config = retry_config; } - async fn our_retry(&self, action: impl Fn() -> Fut) -> RequestResult + async fn wrap(&self, action: impl Fn() -> Fut) -> RequestResult where Fut: Future>, { - Ok(retry_util::retry(action, &self.retry_config, |result| result.is_err()).await?) + retry_util::retry(action, &self.retry_config, |result| result.is_err()) + .await + .map_err(|e| { + let msg = if let Some(warning) = &self.prepend_warning { + format!("{warning}. {e}") + } else { + e.to_string() + }; + RequestError::IO(msg) + }) } // DELEGATION START pub async fn health(&self) -> RequestResult { - self.our_retry(|| self.client.health()).await + self.wrap(|| self.client.health()).await } pub async fn transaction(&self, id: &TxId) -> RequestResult> { - self.our_retry(|| self.client.transaction(id)).await + self.wrap(|| self.client.transaction(id)).await } pub(crate) async fn chain_info(&self) -> RequestResult { - self.our_retry(|| self.client.chain_info()).await + self.wrap(|| self.client.chain_info()).await } pub async fn await_transaction_commit(&self, id: &TxId) -> RequestResult { - self.our_retry(|| self.client.await_transaction_commit(id)) - .await + self.wrap(|| self.client.await_transaction_commit(id)).await } pub async fn submit_and_await_commit( &self, tx: &Transaction, ) -> RequestResult { - self.our_retry(|| self.client.submit_and_await_commit(tx)) - .await + self.wrap(|| self.client.submit_and_await_commit(tx)).await } pub async fn submit(&self, tx: &Transaction) -> RequestResult { - self.our_retry(|| self.client.submit(tx)).await + self.wrap(|| self.client.submit(tx)).await } pub async fn transaction_status(&self, id: &TxId) -> RequestResult { - self.our_retry(|| self.client.transaction_status(id)).await + self.wrap(|| self.client.transaction_status(id)).await } pub async fn node_info(&self) -> RequestResult { - self.our_retry(|| self.client.node_info()).await + self.wrap(|| self.client.node_info()).await } pub async fn latest_gas_price(&self) -> RequestResult { - self.our_retry(|| self.client.latest_gas_price()).await + self.wrap(|| self.client.latest_gas_price()).await } pub async fn estimate_gas_price(&self, block_horizon: u32) -> RequestResult { - self.our_retry(|| self.client.estimate_gas_price(block_horizon)) + self.wrap(|| self.client.estimate_gas_price(block_horizon)) .await .map(Into::into) } @@ -117,7 +155,7 @@ impl RetryableClient { &self, tx: &[Transaction], ) -> RequestResult> { - self.our_retry(|| self.client.dry_run(tx)).await + self.wrap(|| self.client.dry_run(tx)).await } pub async fn dry_run_opt( @@ -125,7 +163,7 @@ impl RetryableClient { tx: &[Transaction], utxo_validation: Option, ) -> RequestResult> { - self.our_retry(|| self.client.dry_run_opt(tx, utxo_validation)) + self.wrap(|| self.client.dry_run_opt(tx, utxo_validation)) .await } @@ -135,7 +173,7 @@ impl RetryableClient { asset_id: Option<&AssetId>, request: PaginationRequest, ) -> RequestResult> { - self.our_retry(move || self.client.coins(owner, asset_id, request.clone())) + self.wrap(move || self.client.coins(owner, asset_id, request.clone())) .await } @@ -145,7 +183,7 @@ impl RetryableClient { spend_query: Vec<(AssetId, u64, Option)>, excluded_ids: Option<(Vec, Vec)>, ) -> RequestResult>> { - self.our_retry(move || { + self.wrap(move || { self.client .coins_to_spend(owner, spend_query.clone(), excluded_ids.clone()) }) @@ -153,8 +191,7 @@ impl RetryableClient { } pub async fn balance(&self, owner: &Address, asset_id: Option<&AssetId>) -> RequestResult { - self.our_retry(|| self.client.balance(owner, asset_id)) - .await + self.wrap(|| self.client.balance(owner, asset_id)).await } pub async fn contract_balance( @@ -162,8 +199,7 @@ impl RetryableClient { id: &ContractId, asset: Option<&AssetId>, ) -> RequestResult { - self.our_retry(|| self.client.contract_balance(id, asset)) - .await + self.wrap(|| self.client.contract_balance(id, asset)).await } pub async fn contract_balances( @@ -171,7 +207,7 @@ impl RetryableClient { contract: &ContractId, request: PaginationRequest, ) -> RequestResult> { - self.our_retry(|| self.client.contract_balances(contract, request.clone())) + self.wrap(|| self.client.contract_balances(contract, request.clone())) .await } @@ -180,7 +216,7 @@ impl RetryableClient { owner: &Address, request: PaginationRequest, ) -> RequestResult> { - self.our_retry(|| self.client.balances(owner, request.clone())) + self.wrap(|| self.client.balances(owner, request.clone())) .await } @@ -188,7 +224,7 @@ impl RetryableClient { &self, request: PaginationRequest, ) -> RequestResult> { - self.our_retry(|| self.client.transactions(request.clone())) + self.wrap(|| self.client.transactions(request.clone())) .await } @@ -197,7 +233,7 @@ impl RetryableClient { owner: &Address, request: PaginationRequest, ) -> RequestResult> { - self.our_retry(|| self.client.transactions_by_owner(owner, request.clone())) + self.wrap(|| self.client.transactions_by_owner(owner, request.clone())) .await } @@ -206,7 +242,7 @@ impl RetryableClient { blocks_to_produce: u32, start_timestamp: Option, ) -> RequestResult { - self.our_retry(|| { + self.wrap(|| { self.client .produce_blocks(blocks_to_produce, start_timestamp) }) @@ -214,18 +250,18 @@ impl RetryableClient { } pub async fn block(&self, id: &BlockId) -> RequestResult> { - self.our_retry(|| self.client.block(id)).await + self.wrap(|| self.client.block(id)).await } pub async fn block_by_height(&self, height: BlockHeight) -> RequestResult> { - self.our_retry(|| self.client.block_by_height(height)).await + self.wrap(|| self.client.block_by_height(height)).await } pub async fn blocks( &self, request: PaginationRequest, ) -> RequestResult> { - self.our_retry(|| self.client.blocks(request.clone())).await + self.wrap(|| self.client.blocks(request.clone())).await } pub async fn messages( @@ -233,7 +269,7 @@ impl RetryableClient { owner: Option<&Address>, request: PaginationRequest, ) -> RequestResult> { - self.our_retry(|| self.client.messages(owner, request.clone())) + self.wrap(|| self.client.messages(owner, request.clone())) .await } @@ -245,7 +281,7 @@ impl RetryableClient { commit_block_id: Option<&BlockId>, commit_block_height: Option, ) -> RequestResult> { - self.our_retry(|| { + self.wrap(|| { self.client .message_proof(transaction_id, nonce, commit_block_id, commit_block_height) }) diff --git a/packages/fuels-accounts/src/provider/supported_versions.rs b/packages/fuels-accounts/src/provider/supported_versions.rs index eae993bb2..0cc09b892 100644 --- a/packages/fuels-accounts/src/provider/supported_versions.rs +++ b/packages/fuels-accounts/src/provider/supported_versions.rs @@ -12,9 +12,7 @@ pub(crate) struct VersionCompatibility { pub(crate) is_patch_supported: bool, } -pub(crate) fn check_fuel_core_version_compatibility( - network_version: Version, -) -> VersionCompatibility { +pub(crate) fn compare_node_compatibility(network_version: Version) -> VersionCompatibility { let supported_version = get_supported_fuel_core_version(); check_version_compatibility(network_version, supported_version) } diff --git a/packages/fuels/tests/providers.rs b/packages/fuels/tests/providers.rs index 9e280904b..6a9dede8a 100644 --- a/packages/fuels/tests/providers.rs +++ b/packages/fuels/tests/providers.rs @@ -511,7 +511,7 @@ async fn test_gas_errors() -> Result<()> { .await .expect_err("should error"); - let expected = "provider: Response errors; Validity(InsufficientFeeAmount"; + let expected = "provider: io error: Response errors; Validity(InsufficientFeeAmount"; assert!(response.to_string().contains(expected)); Ok(())