diff --git a/packages/rs-dpp/src/bls/native_bls.rs b/packages/rs-dpp/src/bls/native_bls.rs index 492f5635117..3e1341bf9ab 100644 --- a/packages/rs-dpp/src/bls/native_bls.rs +++ b/packages/rs-dpp/src/bls/native_bls.rs @@ -2,7 +2,6 @@ use crate::bls_signatures::{ Bls12381G2Impl, Pairing, PublicKey, SecretKey, Signature, SignatureSchemes, }; use crate::{BlsModule, ProtocolError, PublicKeyValidationError}; -use std::array::TryFromSliceError; #[derive(Default)] pub struct NativeBlsModule; diff --git a/packages/rs-drive-proof-verifier/src/error.rs b/packages/rs-drive-proof-verifier/src/error.rs index 3fb5825a8cf..3da25aacc7c 100644 --- a/packages/rs-drive-proof-verifier/src/error.rs +++ b/packages/rs-drive-proof-verifier/src/error.rs @@ -119,6 +119,10 @@ pub enum ContextProviderError { /// Async error, eg. when tokio runtime fails #[error("async error: {0}")] AsyncError(String), + + /// Dash Core error + #[error("Dash Core error: {0}")] + DashCoreError(String), } impl From for Error { diff --git a/packages/rs-sdk/src/core/dash_core_client.rs b/packages/rs-sdk/src/core/dash_core_client.rs index 11c1749cff2..73216f294af 100644 --- a/packages/rs-sdk/src/core/dash_core_client.rs +++ b/packages/rs-sdk/src/core/dash_core_client.rs @@ -12,11 +12,12 @@ use dashcore_rpc::{ }; use dpp::dashcore::ProTxHash; use dpp::prelude::CoreBlockHeight; -use drive_proof_verifier::error::ContextProviderError; use std::time::Duration; use std::{fmt::Debug, sync::Mutex}; use zeroize::Zeroizing; +use super::DashCoreError; + /// Core RPC client that can be used to retrieve quorum keys from core. /// /// TODO: This is a temporary implementation, effective until we integrate SPV. @@ -28,13 +29,6 @@ pub struct LowLevelDashCoreClient { core_port: u16, } -/// Client still warming up -pub const CORE_RPC_ERROR_IN_WARMUP: i32 = -28; -/// Dash is not connected -pub const CORE_RPC_CLIENT_NOT_CONNECTED: i32 = -9; -/// Still downloading initial blocks -pub const CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD: i32 = -10; - macro_rules! retry { ($action:expr) => {{ /// Maximum number of retry attempts @@ -60,30 +54,18 @@ macro_rules! retry { break; } Err(e) => { - match e { - dashcore_rpc::Error::JsonRpc( - // Retry on transport connection error - dashcore_rpc::jsonrpc::error::Error::Transport(_) - | dashcore_rpc::jsonrpc::error::Error::Rpc( - // Retry on Core RPC "not ready" errors - dashcore_rpc::jsonrpc::error::RpcError { - code: - CORE_RPC_ERROR_IN_WARMUP - | CORE_RPC_CLIENT_NOT_CONNECTED - | CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD, - .. - }, - ), - ) => { - if i == MAX_RETRIES - 1 { - final_result = - Some(Err(ContextProviderError::Generic(e.to_string()))); - } - let delay = fibonacci(i + 2) * FIB_MULTIPLIER; - std::thread::sleep(Duration::from_millis(delay * BASE_TIME_MS)); + use rs_dapi_client::CanRetry; + + let err: DashCoreError = e.into(); + if err.can_retry() { + if i == MAX_RETRIES - 1 { + final_result = Some(Err(err)); } - _ => return Err(ContextProviderError::Generic(e.to_string())), - }; + let delay = fibonacci(i + 2) * FIB_MULTIPLIER; + std::thread::sleep(Duration::from_millis(delay * BASE_TIME_MS)); + } else { + return Err(err); + } } } } @@ -133,8 +115,7 @@ impl LowLevelDashCoreClient { let core = Client::new( &addr, Auth::UserPass(core_user.to_string(), core_password.to_string()), - ) - .map_err(Error::CoreClientError)?; + )?; Ok(Self { core: Mutex::new(core), @@ -162,7 +143,7 @@ impl LowLevelDashCoreClient { pub fn list_unspent( &self, minimum_sum_satoshi: Option, - ) -> Result, ContextProviderError> { + ) -> Result, DashCoreError> { let options = json::ListUnspentQueryOptions { minimum_sum_amount: minimum_sum_satoshi.map(Amount::from_sat), ..Default::default() @@ -176,7 +157,7 @@ impl LowLevelDashCoreClient { /// Return address to which change of transaction can be sent. #[allow(dead_code)] #[deprecated(note = "This function is marked as unused.")] - pub fn get_balance(&self) -> Result { + pub fn get_balance(&self) -> Result { let core = self.core.lock().expect("Core lock poisoned"); retry!(core.get_balance(None, None)) } @@ -186,9 +167,9 @@ impl LowLevelDashCoreClient { &self, quorum_type: u32, quorum_hash: [u8; 32], - ) -> Result<[u8; 48], ContextProviderError> { + ) -> Result<[u8; 48], DashCoreError> { let quorum_hash = QuorumHash::from_slice(&quorum_hash) - .map_err(|e| ContextProviderError::InvalidQuorum(e.to_string()))?; + .map_err(|e| DashCoreError::InvalidQuorum(format!("invalid quorum hash: {}", e)))?; let core = self.core.lock().expect("Core lock poisoned"); @@ -199,29 +180,29 @@ impl LowLevelDashCoreClient { // Extract the quorum public key and attempt to convert it let key = quorum_info.quorum_public_key; let pubkey = as TryInto<[u8; 48]>>::try_into(key).map_err(|_| { - ContextProviderError::InvalidQuorum( - "quorum public key is not 48 bytes long".to_string(), - ) + DashCoreError::InvalidQuorum("quorum public key is not 48 bytes long".to_string()) })?; Ok(pubkey) } /// Retrieve platform activation height from core. - pub fn get_platform_activation_height(&self) -> Result { + pub fn get_platform_activation_height(&self) -> Result { let core = self.core.lock().expect("Core lock poisoned"); let blockchain_info = retry!(core.get_blockchain_info())?; - let fork_info = blockchain_info.softforks.get("mn_rr").ok_or( - ContextProviderError::ActivationForkError("no fork info for mn_rr".to_string()), - )?; - - fork_info - .height - .ok_or(ContextProviderError::ActivationForkError( - "unknown fork height".to_string(), - )) + let fork_info = + blockchain_info + .softforks + .get("mn_rr") + .ok_or(DashCoreError::ActivationForkError( + "no fork info for mn_rr".to_string(), + ))?; + + fork_info.height.ok_or(DashCoreError::ActivationForkError( + "unknown fork height".to_string(), + )) } /// Require list of validators from Core. @@ -232,7 +213,7 @@ impl LowLevelDashCoreClient { &self, height: Option, protx_type: Option, - ) -> Result, ContextProviderError> { + ) -> Result, DashCoreError> { let core = self.core.lock().expect("Core lock poisoned"); let pro_tx_list = retry!(core.get_protx_list(protx_type.clone(), Some(false), height))?; diff --git a/packages/rs-sdk/src/core/error.rs b/packages/rs-sdk/src/core/error.rs new file mode 100644 index 00000000000..99bc2456b4d --- /dev/null +++ b/packages/rs-sdk/src/core/error.rs @@ -0,0 +1,55 @@ +//! Errors that can occur in the Dash Core. + +use drive_proof_verifier::error::ContextProviderError; +use rs_dapi_client::CanRetry; + +/// Dash Core still warming up +pub const CORE_RPC_ERROR_IN_WARMUP: i32 = -28; +/// Dash Core Client is not connected +pub const CORE_RPC_CLIENT_NOT_CONNECTED: i32 = -9; +/// Dash Core still downloading initial blocks +pub const CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD: i32 = -10; + +#[derive(Debug, thiserror::Error)] +/// Errors that can occur when communicating with the Dash Core. +pub enum DashCoreError { + /// Error from Dash Core. + #[error("Dash Core RPC error: {0}")] + Rpc(#[from] dashcore_rpc::Error), + /// Invalid format of the hash. + #[error("Invalid data format: {0}")] + InvalidQuorum(String), + + /// Fork not activated yet + #[error("Fork not activated yet: {0}")] + ActivationForkError(String), +} + +impl From for ContextProviderError { + fn from(error: DashCoreError) -> Self { + match error { + DashCoreError::Rpc(e) => Self::DashCoreError(e.to_string()), + DashCoreError::InvalidQuorum(e) => Self::InvalidQuorum(e), + DashCoreError::ActivationForkError(e) => Self::ActivationForkError(e), + } + } +} + +impl CanRetry for DashCoreError { + fn can_retry(&self) -> bool { + use dashcore_rpc::jsonrpc::error::Error as JsonRpcError; + use dashcore_rpc::Error as RpcError; + match self { + DashCoreError::Rpc(RpcError::JsonRpc(JsonRpcError::Transport(..))) => true, + DashCoreError::Rpc(RpcError::JsonRpc(JsonRpcError::Rpc(e))) => { + matches!( + e.code, + CORE_RPC_ERROR_IN_WARMUP + | CORE_RPC_CLIENT_NOT_CONNECTED + | CORE_RPC_CLIENT_IN_INITIAL_DOWNLOAD, + ) + } + _ => false, + } + } +} diff --git a/packages/rs-sdk/src/core/mod.rs b/packages/rs-sdk/src/core/mod.rs index f642f3b26f6..d5c4be9cd5b 100644 --- a/packages/rs-sdk/src/core/mod.rs +++ b/packages/rs-sdk/src/core/mod.rs @@ -6,3 +6,5 @@ mod dash_core_client; mod transaction; #[cfg(feature = "mocks")] pub use dash_core_client::LowLevelDashCoreClient; +mod error; +pub use error::DashCoreError; diff --git a/packages/rs-sdk/src/error.rs b/packages/rs-sdk/src/error.rs index 23def69d1a9..75e114a8008 100644 --- a/packages/rs-sdk/src/error.rs +++ b/packages/rs-sdk/src/error.rs @@ -10,6 +10,8 @@ use rs_dapi_client::{CanRetry, DapiClientError, ExecutionError}; use std::fmt::Debug; use std::time::Duration; +use crate::core::DashCoreError; + /// Error type for the SDK // TODO: Propagate server address and retry information so that the user can retrieve it #[derive(Debug, thiserror::Error)] @@ -44,7 +46,7 @@ pub enum Error { MerkleBlockError(#[from] dpp::dashcore::merkle_tree::MerkleBlockError), /// Core client error, for example, connection error #[error("Core client error: {0}")] - CoreClientError(#[from] dashcore_rpc::Error), + CoreClientError(#[from] DashCoreError), /// Dependency not found, for example data contract for a document not found #[error("Required {0} not found: {1}")] MissingDependency(String, String), @@ -125,9 +127,20 @@ where } } +impl From for Error { + fn from(value: dashcore_rpc::Error) -> Self { + Self::CoreClientError(value.into()) + } +} + impl CanRetry for Error { fn can_retry(&self) -> bool { - matches!(self, Error::StaleNode(..) | Error::TimeoutReached(_, _)) + match self { + Error::StaleNode(..) => true, + Error::TimeoutReached(..) => true, + Error::CoreClientError(e) => e.can_retry(), + _ => false, + } } } diff --git a/packages/rs-sdk/src/mock/provider.rs b/packages/rs-sdk/src/mock/provider.rs index 879c4137ebe..ef0db5be923 100644 --- a/packages/rs-sdk/src/mock/provider.rs +++ b/packages/rs-sdk/src/mock/provider.rs @@ -215,7 +215,7 @@ impl ContextProvider for GrpcContextProvider { } fn get_platform_activation_height(&self) -> Result { - self.core.get_platform_activation_height() + Ok(self.core.get_platform_activation_height()?) } } diff --git a/packages/rs-sdk/src/sync.rs b/packages/rs-sdk/src/sync.rs index 5f5d2666699..3b267f5a531 100644 --- a/packages/rs-sdk/src/sync.rs +++ b/packages/rs-sdk/src/sync.rs @@ -244,7 +244,6 @@ where mod test { use super::*; use derive_more::Display; - use http::Uri; use rs_dapi_client::ExecutionError; use std::{ future::Future, diff --git a/packages/rs-sdk/tests/fetch/config.rs b/packages/rs-sdk/tests/fetch/config.rs index f55484f5ce3..5fc403560d2 100644 --- a/packages/rs-sdk/tests/fetch/config.rs +++ b/packages/rs-sdk/tests/fetch/config.rs @@ -10,7 +10,7 @@ use dpp::{ }; use rs_dapi_client::{Address, AddressList}; use serde::Deserialize; -use std::{path::PathBuf, str::FromStr}; +use std::path::PathBuf; use zeroize::Zeroizing; /// Existing document ID diff --git a/packages/rs-sdk/tests/fetch/evonode.rs b/packages/rs-sdk/tests/fetch/evonode.rs index b2521ba8648..9a01eabb9d3 100644 --- a/packages/rs-sdk/tests/fetch/evonode.rs +++ b/packages/rs-sdk/tests/fetch/evonode.rs @@ -4,7 +4,6 @@ use super::{common::setup_logs, config::Config}; use dash_sdk::platform::{types::evonode::EvoNode, FetchUnproved}; use dpp::dashcore::{hashes::Hash, ProTxHash}; use drive_proof_verifier::types::EvoNodeStatus; -use http::Uri; use rs_dapi_client::Address; use std::time::Duration; /// Given some existing evonode URIs, WHEN we connect to them, THEN we get status.