Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(sdk)!: separate dash core client error #2380

Merged
merged 3 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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),
lklimek marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading