Skip to content

Commit

Permalink
refactor(sdk)!: separate dash core client error (#2380)
Browse files Browse the repository at this point in the history
  • Loading branch information
lklimek authored Dec 9, 2024
1 parent 5c3f318 commit c3a8f80
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 58 deletions.
1 change: 0 additions & 1 deletion packages/rs-dpp/src/bls/native_bls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions packages/rs-drive-proof-verifier/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<drive::error::Error> for Error {
Expand Down
83 changes: 32 additions & 51 deletions packages/rs-sdk/src/core/dash_core_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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);
}
}
}
}
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -162,7 +143,7 @@ impl LowLevelDashCoreClient {
pub fn list_unspent(
&self,
minimum_sum_satoshi: Option<u64>,
) -> Result<Vec<dashcore_rpc::json::ListUnspentResultEntry>, ContextProviderError> {
) -> Result<Vec<dashcore_rpc::json::ListUnspentResultEntry>, DashCoreError> {
let options = json::ListUnspentQueryOptions {
minimum_sum_amount: minimum_sum_satoshi.map(Amount::from_sat),
..Default::default()
Expand All @@ -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<Amount, ContextProviderError> {
pub fn get_balance(&self) -> Result<Amount, DashCoreError> {
let core = self.core.lock().expect("Core lock poisoned");
retry!(core.get_balance(None, None))
}
Expand All @@ -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");

Expand All @@ -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 = <Vec<u8> 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<CoreBlockHeight, ContextProviderError> {
pub fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, DashCoreError> {
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.
Expand All @@ -232,7 +213,7 @@ impl LowLevelDashCoreClient {
&self,
height: Option<u32>,
protx_type: Option<ProTxListType>,
) -> Result<Vec<ProTxHash>, ContextProviderError> {
) -> Result<Vec<ProTxHash>, 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))?;
Expand Down
56 changes: 56 additions & 0 deletions packages/rs-sdk/src/core/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! 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),

/// Quorum is invalid.
#[error("Invalid quorum: {0}")]
InvalidQuorum(String),

/// Fork activation error - most likely the fork is not activated yet.
#[error("Fork activation: {0}")]
ActivationForkError(String),
}

impl From<DashCoreError> 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,
}
}
}
2 changes: 2 additions & 0 deletions packages/rs-sdk/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
17 changes: 15 additions & 2 deletions packages/rs-sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -125,9 +127,20 @@ where
}
}

impl From<dashcore_rpc::Error> 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,
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk/src/mock/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ impl ContextProvider for GrpcContextProvider {
}

fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
self.core.get_platform_activation_height()
Ok(self.core.get_platform_activation_height()?)
}
}

Expand Down
1 change: 0 additions & 1 deletion packages/rs-sdk/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk/tests/fetch/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion packages/rs-sdk/tests/fetch/evonode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit c3a8f80

Please sign in to comment.