From 89f4f29200f6cc370ab5a97a41a1959099051d16 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:50:04 +0100 Subject: [PATCH 01/22] feat: retry broadcast --- packages/rs-dapi-client/src/executor.rs | 65 ++++++++++ packages/rs-dapi-client/src/lib.rs | 1 + .../src/platform/transition/broadcast.rs | 116 ++++++++++++------ 3 files changed, 146 insertions(+), 36 deletions(-) diff --git a/packages/rs-dapi-client/src/executor.rs b/packages/rs-dapi-client/src/executor.rs index e1b5dca2f9..41510301c4 100644 --- a/packages/rs-dapi-client/src/executor.rs +++ b/packages/rs-dapi-client/src/executor.rs @@ -124,6 +124,18 @@ where /// Result of request execution pub type ExecutionResult = Result, ExecutionError>; +impl From> for ExecutionResult { + fn from(response: ExecutionResponse) -> Self { + ExecutionResult::::Ok(response) + } +} + +impl From> for ExecutionResult { + fn from(e: ExecutionError) -> Self { + ExecutionResult::::Err(e) + } +} + impl IntoInner> for ExecutionResult { fn into_inner(self) -> Result { match self { @@ -145,3 +157,56 @@ where } } } + +/// Convert Result to ExecutionResult<>, taking context from ExecutionResponse. +pub trait WrapWithExecutionResult: Sized { + /// Convert Result to ExecutionResult<>, taking context from ExecutionResponse. + /// + /// This function simplifies processing of results by wrapping them into ExecutionResult. + /// It is useful when you have execution result retrieved in previous step and you want to + /// add it to the result of the current step. + /// + /// ## Example + /// + /// ```rust + /// use rs_dapi_client::{ExecutionResponse, ExecutionResult, WrapWithExecutionResult}; + /// + /// let response: ExecutionResponse = ExecutionResponse { + /// inner: 42, + /// retries: 123, + /// address: "http://127.0.0.1".parse().expect("create mock address"), + /// }; + /// + /// let result: Result = Err("next error".to_string()); + /// let wrapped_result: ExecutionResult = result.wrap(&response); + /// + /// if let ExecutionResult::Err(error) = wrapped_result { + /// assert_eq!(error.inner, "next error"); + /// assert_eq!(error.retries, 123); + /// } else { + /// panic!("Expected error"); + /// } + /// ``` + fn wrap(self, result: &W) -> ExecutionResult; +} + +impl WrapWithExecutionResult> for Result +where + R: From, + RE: From, +{ + fn wrap(self, result: &ExecutionResponse) -> ExecutionResult { + match self { + Ok(r) => ExecutionResult::Ok(ExecutionResponse { + inner: r.into(), + retries: result.retries, + address: result.address.clone(), + }), + Err(e) => ExecutionResult::Err(ExecutionError { + inner: e.into(), + retries: result.retries, + address: Some(result.address.clone()), + }), + } + } +} diff --git a/packages/rs-dapi-client/src/lib.rs b/packages/rs-dapi-client/src/lib.rs index 2ce4a9da43..eb372927dd 100644 --- a/packages/rs-dapi-client/src/lib.rs +++ b/packages/rs-dapi-client/src/lib.rs @@ -22,6 +22,7 @@ pub use dapi_client::{DapiClient, DapiClientError}; pub use dump::DumpData; pub use executor::{ DapiRequestExecutor, ExecutionError, ExecutionResponse, ExecutionResult, InnerInto, IntoInner, + WrapWithExecutionResult, }; use futures::{future::BoxFuture, FutureExt}; pub use request_settings::RequestSettings; diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index 7e4c6488c1..1e91d63cef 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -1,14 +1,16 @@ use super::broadcast_request::BroadcastRequestForStateTransition; use crate::platform::block_info_from_metadata::block_info_from_metadata; +use crate::sync::retry; use crate::{Error, Sdk}; +use dapi_grpc::platform::v0::Proof; use dapi_grpc::platform::VersionedGrpcResponse; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; use drive::drive::Drive; use drive_proof_verifier::error::ContextProviderError; use drive_proof_verifier::DataContractProvider; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; - +use rs_dapi_client::WrapWithExecutionResult; +use rs_dapi_client::{DapiRequest, ExecutionError, InnerInto, IntoInner, RequestSettings}; #[async_trait::async_trait] pub trait BroadcastStateTransition { async fn broadcast(&self, sdk: &Sdk) -> Result<(), Error>; @@ -22,54 +24,96 @@ pub trait BroadcastStateTransition { #[async_trait::async_trait] impl BroadcastStateTransition for StateTransition { async fn broadcast(&self, sdk: &Sdk) -> Result<(), Error> { - let request = self.broadcast_request_for_state_transition()?; + let retry_settings = sdk.dapi_client_settings; - request - .execute(sdk, RequestSettings::default()) - .await // TODO: We need better way to handle execution errors - .into_inner()?; + // async fn retry_test_function(settings: RequestSettings) -> ExecutionResult<(), dash_sdk::Error> + let factory = |request_settings: RequestSettings| async move { + let request = + self.broadcast_request_for_state_transition() + .map_err(|e| ExecutionError { + inner: e, + address: None, + retries: 0, + })?; + request + .execute(sdk, request_settings) + .await + .map_err(|e| e.inner_into()) + }; // response is empty for a broadcast, result comes from the stream wait for state transition result - - Ok(()) + retry(retry_settings, factory) + .await + .into_inner() + .map(|_| ()) } async fn broadcast_and_wait( &self, sdk: &Sdk, - _time_out_ms: Option, + time_out_ms: Option, ) -> Result { - let request = self.broadcast_request_for_state_transition()?; - // TODO: Implement retry logic - request - .clone() - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; + self.broadcast(sdk).await?; - let request = self.wait_for_state_transition_result_request()?; + let retry_settings = sdk.dapi_client_settings; - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; + let factory = |request_settings: RequestSettings| async move { + let request = self + .wait_for_state_transition_result_request() + .map_err(|e| ExecutionError { + inner: e, + address: None, + retries: 0, + })?; + + let response = request + .execute(sdk, request_settings) + .await + .map_err(|e| e.inner_into())?; + + let grpc_response = &response.inner; + let metadata = grpc_response.metadata().wrap(&response)?.inner; + let block_info = block_info_from_metadata(metadata).wrap(&response)?.inner; + let proof: &Proof = (*grpc_response).proof().wrap(&response)?.inner; - let block_info = block_info_from_metadata(response.metadata()?)?; - let proof = response.proof_owned()?; - let context_provider = - sdk.context_provider() - .ok_or(Error::from(ContextProviderError::Config( + let context_provider = sdk.context_provider().ok_or(ExecutionError { + inner: Error::from(ContextProviderError::Config( "Context provider not initialized".to_string(), - )))?; + )), + address: Some(response.address.clone()), + retries: response.retries, + })?; + + let (_, result) = Drive::verify_state_transition_was_executed_with_proof( + self, + &block_info, + proof.grovedb_proof.as_slice(), + &context_provider.as_contract_lookup_fn(), + sdk.version(), + ) + .wrap(&response)? + .inner; + + Ok::<_, Error>(result).wrap(&response) + }; - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - self, - &block_info, - proof.grovedb_proof.as_slice(), - &context_provider.as_contract_lookup_fn(), - sdk.version(), - )?; + let future = retry(retry_settings, factory); + match time_out_ms { + Some(time_out_ms) => { + let timeout = tokio::time::Duration::from_millis(time_out_ms); + tokio::time::timeout(timeout, future) + .await + .map_err(|e| { + Error::TimeoutReached( + timeout, + format!("Timeout waiting for state transition result: {:?}", e), + ) + })? + .into_inner() + } + None => future.await.into_inner(), + } - Ok(result) + //Result } } From da8ec794b47324837f797db090fb51f811bf1d08 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:54:46 +0100 Subject: [PATCH 02/22] chore(sdk): remove unused file --- packages/rs-sdk/src/platform/transition.rs | 3 --- packages/rs-sdk/src/platform/transition/context.rs | 5 ----- 2 files changed, 8 deletions(-) delete mode 100644 packages/rs-sdk/src/platform/transition/context.rs diff --git a/packages/rs-sdk/src/platform/transition.rs b/packages/rs-sdk/src/platform/transition.rs index 6bd51a3b2e..4fde48c972 100644 --- a/packages/rs-sdk/src/platform/transition.rs +++ b/packages/rs-sdk/src/platform/transition.rs @@ -2,7 +2,6 @@ pub mod broadcast; pub(crate) mod broadcast_identity; pub mod broadcast_request; -pub(crate) mod context; pub mod purchase_document; pub mod put_contract; pub mod put_document; @@ -16,6 +15,4 @@ pub mod update_price_of_document; pub mod vote; pub mod withdraw_from_identity; -pub use context::*; - pub use txid::TxId; diff --git a/packages/rs-sdk/src/platform/transition/context.rs b/packages/rs-sdk/src/platform/transition/context.rs deleted file mode 100644 index c2d3f27e82..0000000000 --- a/packages/rs-sdk/src/platform/transition/context.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Not sure if we need it at all - -pub enum TransitionContext { - Todo, -} From 4618f5517d32aa59cb8a9df3187b309dd74bde5a Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:52:06 +0100 Subject: [PATCH 03/22] refactor(sdk): sepaate StateTransition::wait_for_response() --- .../src/platform/transition/broadcast.rs | 37 +++++++++++++--- .../platform/transition/purchase_document.rs | 38 +++-------------- .../src/platform/transition/put_contract.rs | 42 ++----------------- 3 files changed, 41 insertions(+), 76 deletions(-) diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index 1e91d63cef..b8818c3273 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -9,11 +9,19 @@ use dpp::state_transition::StateTransition; use drive::drive::Drive; use drive_proof_verifier::error::ContextProviderError; use drive_proof_verifier::DataContractProvider; +use futures::TryFutureExt; use rs_dapi_client::WrapWithExecutionResult; use rs_dapi_client::{DapiRequest, ExecutionError, InnerInto, IntoInner, RequestSettings}; +use tokio::time::timeout; + #[async_trait::async_trait] pub trait BroadcastStateTransition { async fn broadcast(&self, sdk: &Sdk) -> Result<(), Error>; + async fn wait_for_response( + &self, + sdk: &Sdk, + time_out_ms: Option, + ) -> Result; async fn broadcast_and_wait( &self, sdk: &Sdk, @@ -47,14 +55,11 @@ impl BroadcastStateTransition for StateTransition { .into_inner() .map(|_| ()) } - - async fn broadcast_and_wait( + async fn wait_for_response( &self, sdk: &Sdk, time_out_ms: Option, ) -> Result { - self.broadcast(sdk).await?; - let retry_settings = sdk.dapi_client_settings; let factory = |request_settings: RequestSettings| async move { @@ -113,7 +118,29 @@ impl BroadcastStateTransition for StateTransition { } None => future.await.into_inner(), } + } - //Result + async fn broadcast_and_wait( + &self, + sdk: &Sdk, + time_out_ms: Option, + ) -> Result { + let future = async { + self.broadcast(sdk).await?; + self.wait_for_response(sdk, time_out_ms).await + }; + + match time_out_ms { + Some(time_out_ms) => timeout(tokio::time::Duration::from_millis(time_out_ms), future) + .into_future() + .await + .map_err(|e| { + Error::TimeoutReached( + tokio::time::Duration::from_millis(time_out_ms), + format!("Timeout waiting for state transition result: {:?}", e), + ) + })?, + None => future.await, + } } } diff --git a/packages/rs-sdk/src/platform/transition/purchase_document.rs b/packages/rs-sdk/src/platform/transition/purchase_document.rs index 1ede5c247e..187f5db70a 100644 --- a/packages/rs-sdk/src/platform/transition/purchase_document.rs +++ b/packages/rs-sdk/src/platform/transition/purchase_document.rs @@ -1,11 +1,8 @@ -use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; use std::sync::Arc; use crate::{Error, Sdk}; -use crate::platform::block_info_from_metadata::block_info_from_metadata; use crate::platform::transition::put_settings::PutSettings; -use dapi_grpc::platform::VersionedGrpcResponse; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::DocumentType; use dpp::data_contract::DataContract; @@ -18,8 +15,8 @@ use dpp::state_transition::documents_batch_transition::methods::v0::DocumentsBat use dpp::state_transition::documents_batch_transition::DocumentsBatchTransition; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; -use drive::drive::Drive; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; + +use super::broadcast::BroadcastStateTransition; #[async_trait::async_trait] /// A trait for purchasing a document on Platform @@ -96,16 +93,8 @@ impl PurchaseDocument for Document { None, )?; - let request = transition.broadcast_request_for_state_transition()?; - - request - .clone() - .execute(sdk, settings.request_settings) - .await // TODO: We need better way to handle execution errors - .into_inner()?; - + transition.broadcast(sdk).await?; // response is empty for a broadcast, result comes from the stream wait for state transition result - Ok(transition) } @@ -113,26 +102,9 @@ impl PurchaseDocument for Document { &self, sdk: &Sdk, state_transition: StateTransition, - data_contract: Arc, + _data_contract: Arc, ) -> Result { - let request = state_transition.wait_for_state_transition_result_request()?; - // TODO: Implement retry logic - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - - let proof = response.proof_owned()?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &state_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &|_| Ok(Some(data_contract.clone())), - sdk.version(), - )?; + let result = state_transition.wait_for_response(sdk, None).await?; match result { StateTransitionProofResult::VerifiedDocuments(mut documents) => { diff --git a/packages/rs-sdk/src/platform/transition/put_contract.rs b/packages/rs-sdk/src/platform/transition/put_contract.rs index a8f07b0b31..d939b60580 100644 --- a/packages/rs-sdk/src/platform/transition/put_contract.rs +++ b/packages/rs-sdk/src/platform/transition/put_contract.rs @@ -1,11 +1,8 @@ -use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; use std::collections::BTreeMap; use crate::{Error, Sdk}; -use crate::platform::block_info_from_metadata::block_info_from_metadata; use crate::platform::transition::put_settings::PutSettings; -use dapi_grpc::platform::VersionedGrpcResponse; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::DataContract; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; @@ -15,10 +12,8 @@ use dpp::state_transition::data_contract_create_transition::methods::DataContrac use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; -use drive::drive::Drive; -use drive_proof_verifier::error::ContextProviderError; -use drive_proof_verifier::DataContractProvider; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; + +use super::broadcast::BroadcastStateTransition; #[async_trait::async_trait] /// A trait for putting a contract to platform @@ -81,14 +76,7 @@ impl PutContract for DataContract { None, )?; - let request = transition.broadcast_request_for_state_transition()?; - - request - .clone() - .execute(sdk, settings.unwrap_or_default().request_settings) - .await // TODO: We need better way to handle execution errors - .into_inner()?; - + transition.broadcast(sdk).await?; // response is empty for a broadcast, result comes from the stream wait for state transition result Ok(transition) @@ -99,29 +87,7 @@ impl PutContract for DataContract { sdk: &Sdk, state_transition: StateTransition, ) -> Result { - let request = state_transition.wait_for_state_transition_result_request()?; - - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - - let proof = response.proof_owned()?; - let context_provider = - sdk.context_provider() - .ok_or(Error::from(ContextProviderError::Config( - "Context provider not initialized".to_string(), - )))?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &state_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &context_provider.as_contract_lookup_fn(), - sdk.version(), - )?; + let result = state_transition.wait_for_response(sdk, None).await?; //todo verify From c10700c767fc9e0c4d2d98416563a9870876bf7a Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:14:42 +0100 Subject: [PATCH 04/22] refactor(sdk)!: pass settings to ST broadcast --- .../rs-dapi-client/src/request_settings.rs | 3 +- .../src/platform/transition/broadcast.rs | 22 ++++-- .../platform/transition/purchase_document.rs | 6 +- .../src/platform/transition/put_contract.rs | 6 +- .../src/platform/transition/put_document.rs | 70 +++++++------------ .../src/platform/transition/transfer.rs | 4 +- .../transition/withdraw_from_identity.rs | 4 +- 7 files changed, 56 insertions(+), 59 deletions(-) diff --git a/packages/rs-dapi-client/src/request_settings.rs b/packages/rs-dapi-client/src/request_settings.rs index 21a1f69b38..5c867f75e0 100644 --- a/packages/rs-dapi-client/src/request_settings.rs +++ b/packages/rs-dapi-client/src/request_settings.rs @@ -19,7 +19,8 @@ const DEFAULT_BAN_FAILED_ADDRESS: bool = true; pub struct RequestSettings { /// Timeout for establishing a connection. pub connect_timeout: Option, - /// Timeout for a request. + /// Timeout for single request (soft limit). + /// Note that the total maximum time of execution can exceed `(timeout + connect_timeout) * retries`. pub timeout: Option, /// Number of retries in case of failed requests. If max retries reached, the last error is returned. /// 1 means one request and one retry in case of error, etc. diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index b8818c3273..815dd56c9a 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -16,23 +16,28 @@ use tokio::time::timeout; #[async_trait::async_trait] pub trait BroadcastStateTransition { - async fn broadcast(&self, sdk: &Sdk) -> Result<(), Error>; + async fn broadcast(&self, sdk: &Sdk, settings: Option) -> Result<(), Error>; async fn wait_for_response( &self, sdk: &Sdk, + settings: Option, time_out_ms: Option, ) -> Result; async fn broadcast_and_wait( &self, sdk: &Sdk, + settings: Option, time_out_ms: Option, ) -> Result; } #[async_trait::async_trait] impl BroadcastStateTransition for StateTransition { - async fn broadcast(&self, sdk: &Sdk) -> Result<(), Error> { - let retry_settings = sdk.dapi_client_settings; + async fn broadcast(&self, sdk: &Sdk, settings: Option) -> Result<(), Error> { + let retry_settings = match settings { + Some(s) => sdk.dapi_client_settings.override_by(s), + None => sdk.dapi_client_settings, + }; // async fn retry_test_function(settings: RequestSettings) -> ExecutionResult<(), dash_sdk::Error> let factory = |request_settings: RequestSettings| async move { @@ -58,9 +63,13 @@ impl BroadcastStateTransition for StateTransition { async fn wait_for_response( &self, sdk: &Sdk, + settings: Option, time_out_ms: Option, ) -> Result { - let retry_settings = sdk.dapi_client_settings; + let retry_settings = match settings { + Some(s) => sdk.dapi_client_settings.override_by(s), + None => sdk.dapi_client_settings, + }; let factory = |request_settings: RequestSettings| async move { let request = self @@ -123,11 +132,12 @@ impl BroadcastStateTransition for StateTransition { async fn broadcast_and_wait( &self, sdk: &Sdk, + settings: Option, time_out_ms: Option, ) -> Result { let future = async { - self.broadcast(sdk).await?; - self.wait_for_response(sdk, time_out_ms).await + self.broadcast(sdk, settings).await?; + self.wait_for_response(sdk, settings, time_out_ms).await }; match time_out_ms { diff --git a/packages/rs-sdk/src/platform/transition/purchase_document.rs b/packages/rs-sdk/src/platform/transition/purchase_document.rs index 187f5db70a..92b86ad017 100644 --- a/packages/rs-sdk/src/platform/transition/purchase_document.rs +++ b/packages/rs-sdk/src/platform/transition/purchase_document.rs @@ -93,7 +93,9 @@ impl PurchaseDocument for Document { None, )?; - transition.broadcast(sdk).await?; + transition + .broadcast(sdk, Some(settings.request_settings)) + .await?; // response is empty for a broadcast, result comes from the stream wait for state transition result Ok(transition) } @@ -104,7 +106,7 @@ impl PurchaseDocument for Document { state_transition: StateTransition, _data_contract: Arc, ) -> Result { - let result = state_transition.wait_for_response(sdk, None).await?; + let result = state_transition.wait_for_response(sdk, None, None).await?; match result { StateTransitionProofResult::VerifiedDocuments(mut documents) => { diff --git a/packages/rs-sdk/src/platform/transition/put_contract.rs b/packages/rs-sdk/src/platform/transition/put_contract.rs index d939b60580..866d13253c 100644 --- a/packages/rs-sdk/src/platform/transition/put_contract.rs +++ b/packages/rs-sdk/src/platform/transition/put_contract.rs @@ -76,7 +76,9 @@ impl PutContract for DataContract { None, )?; - transition.broadcast(sdk).await?; + transition + .broadcast(sdk, settings.map(|s| s.request_settings)) + .await?; // response is empty for a broadcast, result comes from the stream wait for state transition result Ok(transition) @@ -87,7 +89,7 @@ impl PutContract for DataContract { sdk: &Sdk, state_transition: StateTransition, ) -> Result { - let result = state_transition.wait_for_response(sdk, None).await?; + let result = state_transition.wait_for_response(sdk, None, None).await?; //todo verify diff --git a/packages/rs-sdk/src/platform/transition/put_document.rs b/packages/rs-sdk/src/platform/transition/put_document.rs index 806e640d93..4bfdb1ffe6 100644 --- a/packages/rs-sdk/src/platform/transition/put_document.rs +++ b/packages/rs-sdk/src/platform/transition/put_document.rs @@ -1,11 +1,6 @@ -use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; -use std::sync::Arc; - -use crate::{Error, Sdk}; - -use crate::platform::block_info_from_metadata::block_info_from_metadata; +use super::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; -use dapi_grpc::platform::VersionedGrpcResponse; +use crate::{Error, Sdk}; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::DocumentType; use dpp::data_contract::DataContract; @@ -16,8 +11,7 @@ use dpp::state_transition::documents_batch_transition::methods::v0::DocumentsBat use dpp::state_transition::documents_batch_transition::DocumentsBatchTransition; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; -use drive::drive::Drive; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; +use std::sync::Arc; #[async_trait::async_trait] /// A trait for putting a document to platform @@ -90,16 +84,10 @@ impl PutDocument for Document { None, )?; - let request = transition.broadcast_request_for_state_transition()?; - - request - .clone() - .execute(sdk, settings.request_settings) - .await // TODO: We need better way to handle execution errors - .into_inner()?; - // response is empty for a broadcast, result comes from the stream wait for state transition result - + transition + .broadcast(sdk, Some(settings.request_settings)) + .await?; Ok(transition) } @@ -107,29 +95,10 @@ impl PutDocument for Document { &self, sdk: &Sdk, state_transition: StateTransition, - data_contract: Arc, + _data_contract: Arc, ) -> Result { - let request = state_transition.wait_for_state_transition_result_request()?; - // TODO: Implement retry logic - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - - let proof = response.proof_owned()?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &state_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &|_| Ok(Some(data_contract.clone())), - sdk.version(), - )?; - + let result = state_transition.wait_for_response(sdk, None, None).await?; //todo verify - match result { StateTransitionProofResult::VerifiedDocuments(mut documents) => { let document = documents @@ -152,7 +121,7 @@ impl PutDocument for Document { document_type: DocumentType, document_state_transition_entropy: [u8; 32], identity_public_key: IdentityPublicKey, - data_contract: Arc, + _data_contract: Arc, signer: &S, ) -> Result { let state_transition = self @@ -166,11 +135,20 @@ impl PutDocument for Document { ) .await?; - // TODO: Why do we need full type annotation? - let document = - >::wait_for_response(self, sdk, state_transition, data_contract) - .await?; - - Ok(document) + let result = state_transition.broadcast_and_wait(sdk, None, None).await?; + match result { + StateTransitionProofResult::VerifiedDocuments(mut documents) => { + let document = documents + .remove(self.id_ref()) + .ok_or(Error::InvalidProvedResponse( + "did not prove the sent document".to_string(), + ))? + .ok_or(Error::InvalidProvedResponse( + "expected there to actually be a document".to_string(), + ))?; + Ok(document) + } + _ => Err(Error::DapiClientError("proved a non document".to_string())), + } } } diff --git a/packages/rs-sdk/src/platform/transition/transfer.rs b/packages/rs-sdk/src/platform/transition/transfer.rs index bf330a1024..8a327e2012 100644 --- a/packages/rs-sdk/src/platform/transition/transfer.rs +++ b/packages/rs-sdk/src/platform/transition/transfer.rs @@ -53,7 +53,9 @@ impl TransferToIdentity for Identity { None, )?; - let result = state_transition.broadcast_and_wait(sdk, None).await?; + let result = state_transition + .broadcast_and_wait(sdk, settings.map(|s| s.request_settings), None) + .await?; match result { StateTransitionProofResult::VerifiedPartialIdentity(identity) => { diff --git a/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs b/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs index 1d72c86e07..8e73955cfd 100644 --- a/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs +++ b/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs @@ -62,7 +62,9 @@ impl WithdrawFromIdentity for Identity { None, )?; - let result = state_transition.broadcast_and_wait(sdk, None).await?; + let result = state_transition + .broadcast_and_wait(sdk, settings.map(|s| s.request_settings), None) + .await?; match result { StateTransitionProofResult::VerifiedPartialIdentity(identity) => { From 1ec37c17be5877c7517fd640a39dadc744cd9085 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:24:54 +0100 Subject: [PATCH 05/22] refactor(sdk): broadcast st --- packages/rs-dpp/Cargo.toml | 4 +- .../src/state_transition/proof_result.rs | 2 +- packages/rs-sdk/src/core/transaction.rs | 2 +- packages/rs-sdk/src/error.rs | 12 +- .../src/platform/transition/broadcast.rs | 106 ++++++++---------- 5 files changed, 62 insertions(+), 64 deletions(-) diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index ff377de748..b3880fa8bc 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -29,7 +29,7 @@ dashcore = { git = "https://github.com/dashpay/rust-dashcore", features = [ "signer", "serde", "bls", - "eddsa" + "eddsa", ], default-features = false, tag = "0.32.0" } env_logger = { version = "0.11" } getrandom = { version = "0.2", features = ["js"] } @@ -56,7 +56,7 @@ platform-version = { path = "../rs-platform-version" } platform-versioning = { path = "../rs-platform-versioning" } platform-serialization = { path = "../rs-platform-serialization" } platform-serialization-derive = { path = "../rs-platform-serialization-derive" } -derive_more = { version = "1.0", features = ["from", "display"] } +derive_more = { version = "1.0", features = ["from", "display", "try_into"] } nohash-hasher = "0.2.0" rust_decimal = "1.29.1" rust_decimal_macros = "1.29.1" diff --git a/packages/rs-dpp/src/state_transition/proof_result.rs b/packages/rs-dpp/src/state_transition/proof_result.rs index ebff592c8c..012326ac30 100644 --- a/packages/rs-dpp/src/state_transition/proof_result.rs +++ b/packages/rs-dpp/src/state_transition/proof_result.rs @@ -5,7 +5,7 @@ use crate::voting::votes::Vote; use platform_value::Identifier; use std::collections::BTreeMap; -#[derive(Debug)] +#[derive(Debug, strum::Display, derive_more::TryInto)] pub enum StateTransitionProofResult { VerifiedDataContract(DataContract), VerifiedIdentity(Identity), diff --git a/packages/rs-sdk/src/core/transaction.rs b/packages/rs-sdk/src/core/transaction.rs index a71a6f664c..39d196b57a 100644 --- a/packages/rs-sdk/src/core/transaction.rs +++ b/packages/rs-sdk/src/core/transaction.rs @@ -57,7 +57,7 @@ impl Sdk { self.execute(core_transactions_stream, RequestSettings::default()) .await .into_inner() - .map_err(|e| Error::DapiClientError(e.to_string())) + .map_err(|e| e.into()) } /// Waits for a response for the asset lock proof diff --git a/packages/rs-sdk/src/error.rs b/packages/rs-sdk/src/error.rs index 2d0ba29a2b..f067d3d5aa 100644 --- a/packages/rs-sdk/src/error.rs +++ b/packages/rs-sdk/src/error.rs @@ -1,4 +1,5 @@ //! Definitions of errors +use dapi_grpc::tonic::Code; use dpp::consensus::ConsensusError; use dpp::serialization::PlatformDeserializable; use dpp::version::PlatformVersionError; @@ -56,6 +57,10 @@ pub enum Error { /// SDK operation timeout reached error #[error("SDK operation timeout {} secs reached: {1}", .0.as_secs())] TimeoutReached(Duration, String), + + /// Object already exists + #[error("Object already exists: {0}")] + AlreadyExists(String), /// Generic error // TODO: Use domain specific errors instead of generic ones #[error("SDK error: {0}")] @@ -78,6 +83,7 @@ pub enum Error { impl From for Error { fn from(value: DapiClientError) -> Self { if let DapiClientError::Transport(TransportError::Grpc(status)) = &value { + // If we have some consensus error metadata, we deserialize it and return as ConsensusError if let Some(consensus_error_value) = status .metadata() .get_bin("dash-serialized-consensus-error-bin") @@ -90,9 +96,13 @@ impl From for Error { }) .unwrap_or_else(Self::Protocol); } + // Otherwise we parse the error code and act accordingly + if status.code() == Code::AlreadyExists { + return Self::AlreadyExists(status.message().to_string()); + } } - Self::DapiClientError(format!("{:?}", value)) + Self::DapiClientError(value.to_string()) } } diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index 815dd56c9a..000ee81a8a 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -1,41 +1,38 @@ use super::broadcast_request::BroadcastRequestForStateTransition; +use super::put_settings::PutSettings; use crate::platform::block_info_from_metadata::block_info_from_metadata; use crate::sync::retry; use crate::{Error, Sdk}; -use dapi_grpc::platform::v0::Proof; +use dapi_grpc::platform::v0::{Proof, WaitForStateTransitionResultResponse}; use dapi_grpc::platform::VersionedGrpcResponse; use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; use drive::drive::Drive; use drive_proof_verifier::error::ContextProviderError; use drive_proof_verifier::DataContractProvider; -use futures::TryFutureExt; use rs_dapi_client::WrapWithExecutionResult; use rs_dapi_client::{DapiRequest, ExecutionError, InnerInto, IntoInner, RequestSettings}; -use tokio::time::timeout; #[async_trait::async_trait] pub trait BroadcastStateTransition { - async fn broadcast(&self, sdk: &Sdk, settings: Option) -> Result<(), Error>; - async fn wait_for_response( + async fn broadcast(&self, sdk: &Sdk, settings: Option) -> Result<(), Error>; + async fn wait_for_response>( &self, sdk: &Sdk, - settings: Option, - time_out_ms: Option, - ) -> Result; - async fn broadcast_and_wait( + settings: Option, + ) -> Result; + async fn broadcast_and_wait>( &self, sdk: &Sdk, - settings: Option, - time_out_ms: Option, - ) -> Result; + settings: Option, + ) -> Result; } #[async_trait::async_trait] impl BroadcastStateTransition for StateTransition { - async fn broadcast(&self, sdk: &Sdk, settings: Option) -> Result<(), Error> { + async fn broadcast(&self, sdk: &Sdk, settings: Option) -> Result<(), Error> { let retry_settings = match settings { - Some(s) => sdk.dapi_client_settings.override_by(s), + Some(s) => sdk.dapi_client_settings.override_by(s.request_settings), None => sdk.dapi_client_settings, }; @@ -60,14 +57,13 @@ impl BroadcastStateTransition for StateTransition { .into_inner() .map(|_| ()) } - async fn wait_for_response( + async fn wait_for_response>( &self, sdk: &Sdk, - settings: Option, - time_out_ms: Option, - ) -> Result { + settings: Option, + ) -> Result { let retry_settings = match settings { - Some(s) => sdk.dapi_client_settings.override_by(s), + Some(s) => sdk.dapi_client_settings.override_by(s.request_settings), None => sdk.dapi_client_settings, }; @@ -80,12 +76,9 @@ impl BroadcastStateTransition for StateTransition { retries: 0, })?; - let response = request - .execute(sdk, request_settings) - .await - .map_err(|e| e.inner_into())?; + let response = request.execute(sdk, request_settings).await.inner_into()?; - let grpc_response = &response.inner; + let grpc_response: &WaitForStateTransitionResultResponse = &response.inner; let metadata = grpc_response.metadata().wrap(&response)?.inner; let block_info = block_info_from_metadata(metadata).wrap(&response)?.inner; let proof: &Proof = (*grpc_response).proof().wrap(&response)?.inner; @@ -108,49 +101,44 @@ impl BroadcastStateTransition for StateTransition { .wrap(&response)? .inner; - Ok::<_, Error>(result).wrap(&response) + let variant_name = result.to_string(); + T::try_from(result) + .map_err(|_| { + Error::InvalidProvedResponse(format!( + "invalid proved response: cannot convert from {} to {}", + variant_name, + std::any::type_name::(), + )) + }) + .wrap(&response) }; let future = retry(retry_settings, factory); - match time_out_ms { - Some(time_out_ms) => { - let timeout = tokio::time::Duration::from_millis(time_out_ms); - tokio::time::timeout(timeout, future) - .await - .map_err(|e| { - Error::TimeoutReached( - timeout, - format!("Timeout waiting for state transition result: {:?}", e), - ) - })? - .into_inner() - } + let wait_timeout = settings.and_then(|s| s.wait_timeout); + match wait_timeout { + Some(timeout) => tokio::time::timeout(timeout, future) + .await + .map_err(|e| { + Error::TimeoutReached( + timeout, + format!("Timeout waiting for result of {} (tx id: {}) affecting object {}: {:?}", + self.name(), + self.transaction_id().map(hex::encode).unwrap_or("UNKNOWN".to_string()), + self.unique_identifiers().join(","), + e), + ) + })? + .into_inner(), None => future.await.into_inner(), } } - async fn broadcast_and_wait( + async fn broadcast_and_wait>( &self, sdk: &Sdk, - settings: Option, - time_out_ms: Option, - ) -> Result { - let future = async { - self.broadcast(sdk, settings).await?; - self.wait_for_response(sdk, settings, time_out_ms).await - }; - - match time_out_ms { - Some(time_out_ms) => timeout(tokio::time::Duration::from_millis(time_out_ms), future) - .into_future() - .await - .map_err(|e| { - Error::TimeoutReached( - tokio::time::Duration::from_millis(time_out_ms), - format!("Timeout waiting for state transition result: {:?}", e), - ) - })?, - None => future.await, - } + settings: Option, + ) -> Result { + self.broadcast(sdk, settings).await?; + self.wait_for_response::(sdk, settings).await } } From a82fd36bba9a52f6088855ea6ada90cec20fe966 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:44:13 +0100 Subject: [PATCH 06/22] chore: fix build issues --- .../src/platform/transition/purchase_document.rs | 6 ++---- .../rs-sdk/src/platform/transition/put_contract.rs | 6 ++---- .../rs-sdk/src/platform/transition/put_document.rs | 8 +++----- .../rs-sdk/src/platform/transition/put_settings.rs | 10 ++++++++++ packages/rs-sdk/src/platform/transition/transfer.rs | 4 +--- .../src/platform/transition/withdraw_from_identity.rs | 4 +--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/rs-sdk/src/platform/transition/purchase_document.rs b/packages/rs-sdk/src/platform/transition/purchase_document.rs index 92b86ad017..1de4aeb43f 100644 --- a/packages/rs-sdk/src/platform/transition/purchase_document.rs +++ b/packages/rs-sdk/src/platform/transition/purchase_document.rs @@ -93,9 +93,7 @@ impl PurchaseDocument for Document { None, )?; - transition - .broadcast(sdk, Some(settings.request_settings)) - .await?; + transition.broadcast(sdk, Some(settings)).await?; // response is empty for a broadcast, result comes from the stream wait for state transition result Ok(transition) } @@ -106,7 +104,7 @@ impl PurchaseDocument for Document { state_transition: StateTransition, _data_contract: Arc, ) -> Result { - let result = state_transition.wait_for_response(sdk, None, None).await?; + let result = state_transition.wait_for_response(sdk, None).await?; match result { StateTransitionProofResult::VerifiedDocuments(mut documents) => { diff --git a/packages/rs-sdk/src/platform/transition/put_contract.rs b/packages/rs-sdk/src/platform/transition/put_contract.rs index 866d13253c..9fc0e956ee 100644 --- a/packages/rs-sdk/src/platform/transition/put_contract.rs +++ b/packages/rs-sdk/src/platform/transition/put_contract.rs @@ -76,9 +76,7 @@ impl PutContract for DataContract { None, )?; - transition - .broadcast(sdk, settings.map(|s| s.request_settings)) - .await?; + transition.broadcast(sdk, settings).await?; // response is empty for a broadcast, result comes from the stream wait for state transition result Ok(transition) @@ -89,7 +87,7 @@ impl PutContract for DataContract { sdk: &Sdk, state_transition: StateTransition, ) -> Result { - let result = state_transition.wait_for_response(sdk, None, None).await?; + let result = state_transition.wait_for_response(sdk, None).await?; //todo verify diff --git a/packages/rs-sdk/src/platform/transition/put_document.rs b/packages/rs-sdk/src/platform/transition/put_document.rs index 4bfdb1ffe6..6e8617f953 100644 --- a/packages/rs-sdk/src/platform/transition/put_document.rs +++ b/packages/rs-sdk/src/platform/transition/put_document.rs @@ -85,9 +85,7 @@ impl PutDocument for Document { )?; // response is empty for a broadcast, result comes from the stream wait for state transition result - transition - .broadcast(sdk, Some(settings.request_settings)) - .await?; + transition.broadcast(sdk, Some(settings)).await?; Ok(transition) } @@ -97,7 +95,7 @@ impl PutDocument for Document { state_transition: StateTransition, _data_contract: Arc, ) -> Result { - let result = state_transition.wait_for_response(sdk, None, None).await?; + let result = state_transition.wait_for_response(sdk, None).await?; //todo verify match result { StateTransitionProofResult::VerifiedDocuments(mut documents) => { @@ -135,7 +133,7 @@ impl PutDocument for Document { ) .await?; - let result = state_transition.broadcast_and_wait(sdk, None, None).await?; + let result = state_transition.broadcast_and_wait(sdk, None).await?; match result { StateTransitionProofResult::VerifiedDocuments(mut documents) => { let document = documents diff --git a/packages/rs-sdk/src/platform/transition/put_settings.rs b/packages/rs-sdk/src/platform/transition/put_settings.rs index 7ddaef7a68..541726c8be 100644 --- a/packages/rs-sdk/src/platform/transition/put_settings.rs +++ b/packages/rs-sdk/src/platform/transition/put_settings.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use dpp::prelude::UserFeeIncrease; use rs_dapi_client::RequestSettings; @@ -7,4 +9,12 @@ pub struct PutSettings { pub request_settings: RequestSettings, pub identity_nonce_stale_time_s: Option, pub user_fee_increase: Option, + /// The time to wait for the response of a state transition after it has been broadcast + pub wait_timeout: Option, +} + +impl From for RequestSettings { + fn from(settings: PutSettings) -> Self { + settings.request_settings + } } diff --git a/packages/rs-sdk/src/platform/transition/transfer.rs b/packages/rs-sdk/src/platform/transition/transfer.rs index 8a327e2012..18a01033d4 100644 --- a/packages/rs-sdk/src/platform/transition/transfer.rs +++ b/packages/rs-sdk/src/platform/transition/transfer.rs @@ -53,9 +53,7 @@ impl TransferToIdentity for Identity { None, )?; - let result = state_transition - .broadcast_and_wait(sdk, settings.map(|s| s.request_settings), None) - .await?; + let result = state_transition.broadcast_and_wait(sdk, settings).await?; match result { StateTransitionProofResult::VerifiedPartialIdentity(identity) => { diff --git a/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs b/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs index 8e73955cfd..4cd59e811b 100644 --- a/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs +++ b/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs @@ -62,9 +62,7 @@ impl WithdrawFromIdentity for Identity { None, )?; - let result = state_transition - .broadcast_and_wait(sdk, settings.map(|s| s.request_settings), None) - .await?; + let result = state_transition.broadcast_and_wait(sdk, settings).await?; match result { StateTransitionProofResult::VerifiedPartialIdentity(identity) => { From 9b663ea8284c3555b76632855f2677b3d4771754 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:54:40 +0100 Subject: [PATCH 07/22] doc(sdk): improve documentation a bit --- .../rs-dapi-client/src/request_settings.rs | 4 ++- packages/rs-dapi-client/src/transport/grpc.rs | 34 +++++++++++++++++-- packages/rs-sdk/src/error.rs | 7 ++-- .../src/platform/transition/broadcast.rs | 2 ++ .../src/platform/transition/put_settings.rs | 8 ++++- .../src/platform/transition/transfer.rs | 6 ++++ 6 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/rs-dapi-client/src/request_settings.rs b/packages/rs-dapi-client/src/request_settings.rs index 5c867f75e0..9ad08e8861 100644 --- a/packages/rs-dapi-client/src/request_settings.rs +++ b/packages/rs-dapi-client/src/request_settings.rs @@ -20,7 +20,9 @@ pub struct RequestSettings { /// Timeout for establishing a connection. pub connect_timeout: Option, /// Timeout for single request (soft limit). - /// Note that the total maximum time of execution can exceed `(timeout + connect_timeout) * retries`. + /// + /// Note that the total maximum time of execution can exceed `(timeout + connect_timeout) * retries` + /// as it accounts for internal processing time between retries. pub timeout: Option, /// Number of retries in case of failed requests. If max retries reached, the last error is returned. /// 1 means one request and one retry in case of error, etc. diff --git a/packages/rs-dapi-client/src/transport/grpc.rs b/packages/rs-dapi-client/src/transport/grpc.rs index fb1f08c842..853639ca77 100644 --- a/packages/rs-dapi-client/src/transport/grpc.rs +++ b/packages/rs-dapi-client/src/transport/grpc.rs @@ -132,8 +132,38 @@ impl CanRetry for dapi_grpc::tonic::Status { } } -/// A shortcut to link between gRPC request type, response type, client and its -/// method in order to represent it in a form of types and data. +/// Macro to implement the `TransportRequest` trait for a given request type, response type, client type, and settings. +/// +/// # Parameters +/// +/// - `$request:ty`: The request type for which the `TransportRequest` trait will be implemented. +/// - `$response:ty`: The response type returned by the transport request. +/// - `$client:ty`: The client type used to execute the transport request (eg. generated by `tonic` crate). +/// - `$settings:expr`: The settings to be used for the transport request; these settings will override client's +/// default settings, but can still be overriden by arguments to +/// the [`DapiRequestExecutor::execute`](crate::DapiRequestExecutor::execute) method. +/// - `$($method:tt)+`: The method of `$client` to be called to execute the request. +/// +/// # Example +/// +/// ```compile_fail +/// impl_transport_request_grpc!( +/// MyRequestType, +/// MyResponseType, +/// MyClientType, +/// my_settings, +/// my_method +/// ); +/// ``` +/// +/// This will generate an implementation of the `TransportRequest` trait for `MyRequestType` +/// that uses `MyClientType` to execute the `my_method` method, with the specified `my_settings`. +/// +/// The generated implementation will: +/// - Define the associated types `Client` and `Response`. +/// - Set the `SETTINGS_OVERRIDES` constant to the provided settings. +/// - Implement the `method_name` function to return the name of the method as a string. +/// - Implement the `execute_transport` function to execute the transport request using the provided client and settings. macro_rules! impl_transport_request_grpc { ($request:ty, $response:ty, $client:ty, $settings:expr, $($method:tt)+) => { impl TransportRequest for $request { diff --git a/packages/rs-sdk/src/error.rs b/packages/rs-sdk/src/error.rs index f067d3d5aa..23def69d1a 100644 --- a/packages/rs-sdk/src/error.rs +++ b/packages/rs-sdk/src/error.rs @@ -58,7 +58,7 @@ pub enum Error { #[error("SDK operation timeout {} secs reached: {1}", .0.as_secs())] TimeoutReached(Duration, String), - /// Object already exists + /// Returned when an attempt is made to create an object that already exists in the system #[error("Object already exists: {0}")] AlreadyExists(String), /// Generic error @@ -94,7 +94,10 @@ impl From for Error { .map(|consensus_error| { Self::Protocol(ProtocolError::ConsensusError(Box::new(consensus_error))) }) - .unwrap_or_else(Self::Protocol); + .unwrap_or_else(|e| { + tracing::debug!("Failed to deserialize consensus error: {}", e); + Self::Protocol(e) + }); } // Otherwise we parse the error code and act accordingly if status.code() == Code::AlreadyExists { diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index 000ee81a8a..4e73c161d5 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -67,6 +67,7 @@ impl BroadcastStateTransition for StateTransition { None => sdk.dapi_client_settings, }; + // prepare a factory that will generate closure which executes actual code let factory = |request_settings: RequestSettings| async move { let request = self .wait_for_state_transition_result_request() @@ -114,6 +115,7 @@ impl BroadcastStateTransition for StateTransition { }; let future = retry(retry_settings, factory); + // run the future with or without timeout, depending on the settings let wait_timeout = settings.and_then(|s| s.wait_timeout); match wait_timeout { Some(timeout) => tokio::time::timeout(timeout, future) diff --git a/packages/rs-sdk/src/platform/transition/put_settings.rs b/packages/rs-sdk/src/platform/transition/put_settings.rs index 541726c8be..2a9c0c85ce 100644 --- a/packages/rs-sdk/src/platform/transition/put_settings.rs +++ b/packages/rs-sdk/src/platform/transition/put_settings.rs @@ -9,7 +9,13 @@ pub struct PutSettings { pub request_settings: RequestSettings, pub identity_nonce_stale_time_s: Option, pub user_fee_increase: Option, - /// The time to wait for the response of a state transition after it has been broadcast + /// Soft limit of total time to wait for state transition to be executed (included in a block). + /// + /// This is an upper limit, and other settings may affect the actual wait time + /// (like DAPI timeouts, [RequestSettings::timeout], [RequestSettings::retries], etc.). + /// If you want to use `wait_timeout`, tune `retries` accordingly. + /// + /// It can be exceeded due to execution of non-cancellable parts of the Sdk. pub wait_timeout: Option, } diff --git a/packages/rs-sdk/src/platform/transition/transfer.rs b/packages/rs-sdk/src/platform/transition/transfer.rs index 18a01033d4..6d932c5abb 100644 --- a/packages/rs-sdk/src/platform/transition/transfer.rs +++ b/packages/rs-sdk/src/platform/transition/transfer.rs @@ -17,6 +17,12 @@ pub trait TransferToIdentity { /// /// If signing_transfer_key_to_use is not set, we will try to use one in the signer that is /// available for the transfer. + /// + /// This method will resolve once the state transition is executed. + /// + /// ## Returns + /// + /// Final balance of the identity after the transfer. async fn transfer_credits( &self, sdk: &Sdk, From 2d5318d98f15ecbfd111836fc480fb0ac6d087e7 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:13:05 +0100 Subject: [PATCH 08/22] chore: rename Wrap trait --- packages/rs-dapi-client/src/executor.rs | 34 ++++++++++++------- packages/rs-dapi-client/src/lib.rs | 2 +- .../src/platform/transition/broadcast.rs | 2 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/rs-dapi-client/src/executor.rs b/packages/rs-dapi-client/src/executor.rs index 41510301c4..716a22b5b3 100644 --- a/packages/rs-dapi-client/src/executor.rs +++ b/packages/rs-dapi-client/src/executor.rs @@ -158,29 +158,37 @@ where } } -/// Convert Result to ExecutionResult<>, taking context from ExecutionResponse. -pub trait WrapWithExecutionResult: Sized { - /// Convert Result to ExecutionResult<>, taking context from ExecutionResponse. +/// Convert Result to ExecutionResult, taking context from ExecutionResponse. +pub trait Wrap: Sized { + /// Convert self (eg. some [Result]) to [ExecutionResult], taking context information from `W` (eg. ExecutionResponse). /// /// This function simplifies processing of results by wrapping them into ExecutionResult. /// It is useful when you have execution result retrieved in previous step and you want to /// add it to the result of the current step. /// + /// Useful when chaining multiple commands and you want to keep track of retries and address. + /// /// ## Example /// /// ```rust - /// use rs_dapi_client::{ExecutionResponse, ExecutionResult, WrapWithExecutionResult}; + /// use rs_dapi_client::{ExecutionResponse, ExecutionResult, Wrap}; + /// + /// fn some_request() -> ExecutionResult { + /// Ok(ExecutionResponse { + /// inner: 42, + /// retries: 123, + /// address: "http://127.0.0.1".parse().expect("create mock address"), + /// }) + /// } /// - /// let response: ExecutionResponse = ExecutionResponse { - /// inner: 42, - /// retries: 123, - /// address: "http://127.0.0.1".parse().expect("create mock address"), - /// }; + /// fn next_step() -> Result { + /// Err("next error".to_string()) + /// } /// - /// let result: Result = Err("next error".to_string()); - /// let wrapped_result: ExecutionResult = result.wrap(&response); + /// let response = some_request().expect("request should succeed"); + /// let result: ExecutionResult = next_step().wrap(&response); /// - /// if let ExecutionResult::Err(error) = wrapped_result { + /// if let ExecutionResult::Err(error) = result { /// assert_eq!(error.inner, "next error"); /// assert_eq!(error.retries, 123); /// } else { @@ -190,7 +198,7 @@ pub trait WrapWithExecutionResult: Sized { fn wrap(self, result: &W) -> ExecutionResult; } -impl WrapWithExecutionResult> for Result +impl Wrap> for Result where R: From, RE: From, diff --git a/packages/rs-dapi-client/src/lib.rs b/packages/rs-dapi-client/src/lib.rs index eb372927dd..9f5f45e27a 100644 --- a/packages/rs-dapi-client/src/lib.rs +++ b/packages/rs-dapi-client/src/lib.rs @@ -22,7 +22,7 @@ pub use dapi_client::{DapiClient, DapiClientError}; pub use dump::DumpData; pub use executor::{ DapiRequestExecutor, ExecutionError, ExecutionResponse, ExecutionResult, InnerInto, IntoInner, - WrapWithExecutionResult, + Wrap, }; use futures::{future::BoxFuture, FutureExt}; pub use request_settings::RequestSettings; diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index 4e73c161d5..00f1795645 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -10,7 +10,7 @@ use dpp::state_transition::StateTransition; use drive::drive::Drive; use drive_proof_verifier::error::ContextProviderError; use drive_proof_verifier::DataContractProvider; -use rs_dapi_client::WrapWithExecutionResult; +use rs_dapi_client::Wrap; use rs_dapi_client::{DapiRequest, ExecutionError, InnerInto, IntoInner, RequestSettings}; #[async_trait::async_trait] From 83c81605d737dc7cb01ab0fab25d97f4d6d9d22a Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:19:19 +0100 Subject: [PATCH 09/22] test(sdk): fix tests to address new error msgs --- packages/rs-sdk/tests/fetch/contested_resource.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/rs-sdk/tests/fetch/contested_resource.rs b/packages/rs-sdk/tests/fetch/contested_resource.rs index 643396d495..c37d8da39c 100644 --- a/packages/rs-sdk/tests/fetch/contested_resource.rs +++ b/packages/rs-sdk/tests/fetch/contested_resource.rs @@ -232,11 +232,11 @@ async fn contested_resources_limit_PLAN_656() { /// None #[test_case::test_case(|_q| {}, Ok("ContestedResources([ContestedResource(Text(".into()); "unmodified base query is Ok")] #[test_case::test_case(|q| q.start_index_values = vec![Value::Text("".to_string())], Ok("".into()); "index value empty string is Ok")] -#[test_case::test_case(|q| q.document_type_name = "some random non-existing name".to_string(), Err(r#"code: InvalidArgument, message: "document type some random non-existing name not found"#); "non existing document type returns InvalidArgument")] -#[test_case::test_case(|q| q.index_name = "nx index".to_string(), Err(r#"code: InvalidArgument, message: "index with name nx index is not the contested index"#); "non existing index returns InvalidArgument")] -#[test_case::test_case(|q| q.index_name = "dashIdentityId".to_string(), Err(r#"code: InvalidArgument, message: "index with name dashIdentityId is not the contested index"#); "existing non-contested index returns InvalidArgument")] +#[test_case::test_case(|q| q.document_type_name = "some random non-existing name".to_string(), Err(r#"status: InvalidArgument, message: "document type some random non-existing name not found"#); "non existing document type returns InvalidArgument")] +#[test_case::test_case(|q| q.index_name = "nx index".to_string(), Err(r#"status: InvalidArgument, message: "index with name nx index is not the contested index"#); "non existing index returns InvalidArgument")] +#[test_case::test_case(|q| q.index_name = "dashIdentityId".to_string(), Err(r#"status: InvalidArgument, message: "index with name dashIdentityId is not the contested index"#); "existing non-contested index returns InvalidArgument")] // Disabled due to bug PLAN-653 -// #[test_case::test_case(|q| q.start_at_value = Some((Value::Array(vec![]), true)), Err(r#"code: InvalidArgument"#); "start_at_value wrong index type returns InvalidArgument PLAN-653")] +// #[test_case::test_case(|q| q.start_at_value = Some((Value::Array(vec![]), true)), Err(r#"status: InvalidArgument"#); "start_at_value wrong index type returns InvalidArgument PLAN-653")] #[test_case::test_case(|q| q.start_index_values = vec![], Ok(r#"ContestedResources([ContestedResource(Text("dash"))])"#.into()); "start_index_values empty vec returns top-level keys")] #[test_case::test_case(|q| q.start_index_values = vec![Value::Text("".to_string())], Ok(r#"ContestedResources([])"#.into()); "start_index_values empty string returns zero results")] #[test_case::test_case(|q| { @@ -276,8 +276,8 @@ async fn contested_resources_limit_PLAN_656() { q.end_index_values = vec![Value::Text("zzz non existing".to_string())] }, Ok("ContestedResources([])".into()); "Non-existing end_index_values returns error")] #[test_case::test_case(|q| q.end_index_values = vec![Value::Array(vec![0.into(), 1.into()])], Err("incorrect index values error: too many end index values were provided"); "wrong type of end_index_values should return InvalidArgument")] -#[test_case::test_case(|q| q.limit = Some(0), Err(r#"code: InvalidArgument"#); "limit 0 returns InvalidArgument")] -#[test_case::test_case(|q| q.limit = Some(u16::MAX), Err(r#"code: InvalidArgument"#); "limit u16::MAX returns InvalidArgument")] +#[test_case::test_case(|q| q.limit = Some(0), Err(r#"status: InvalidArgument"#); "limit 0 returns InvalidArgument")] +#[test_case::test_case(|q| q.limit = Some(u16::MAX), Err(r#"status: InvalidArgument"#); "limit u16::MAX returns InvalidArgument")] // Disabled due to bug PLAN-656 // #[test_case::test_case(|q| { // q.start_index_values = vec![Value::Text("dash".to_string())]; From 21e229afa08602de9fa7009a6ca3a41c2d671ea8 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:37:31 +0100 Subject: [PATCH 10/22] chore: add TODO --- packages/rs-sdk/src/platform/transition/put_settings.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/rs-sdk/src/platform/transition/put_settings.rs b/packages/rs-sdk/src/platform/transition/put_settings.rs index 2a9c0c85ce..02d60100b3 100644 --- a/packages/rs-sdk/src/platform/transition/put_settings.rs +++ b/packages/rs-sdk/src/platform/transition/put_settings.rs @@ -16,6 +16,7 @@ pub struct PutSettings { /// If you want to use `wait_timeout`, tune `retries` accordingly. /// /// It can be exceeded due to execution of non-cancellable parts of the Sdk. + // TODO: Simplify timeout logic when waiting for response in Sdk, as having 3 different timeouts is confusing. pub wait_timeout: Option, } From ebf61e031ddb0c980f0a0874d4ec632cfc5b477b Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:41:51 +0100 Subject: [PATCH 11/22] refactor(sdk)!: change state transitions api --- packages/rs-sdk/src/platform/transition.rs | 1 + .../platform/transition/purchase_document.rs | 58 +-------- .../src/platform/transition/put_contract.rs | 35 +---- .../src/platform/transition/put_document.rs | 58 +-------- .../src/platform/transition/put_identity.rs | 123 +++++------------- .../platform/transition/top_up_identity.rs | 58 ++------- .../src/platform/transition/transfer.rs | 16 +-- .../platform/transition/transfer_document.rs | 80 ++---------- .../transition/update_price_of_document.rs | 86 +----------- .../src/platform/transition/waitable.rs | 119 +++++++++++++++++ 10 files changed, 199 insertions(+), 435 deletions(-) create mode 100644 packages/rs-sdk/src/platform/transition/waitable.rs diff --git a/packages/rs-sdk/src/platform/transition.rs b/packages/rs-sdk/src/platform/transition.rs index 4fde48c972..c82a494d2d 100644 --- a/packages/rs-sdk/src/platform/transition.rs +++ b/packages/rs-sdk/src/platform/transition.rs @@ -13,6 +13,7 @@ pub mod transfer_document; mod txid; pub mod update_price_of_document; pub mod vote; +pub mod waitable; pub mod withdraw_from_identity; pub use txid::TxId; diff --git a/packages/rs-sdk/src/platform/transition/purchase_document.rs b/packages/rs-sdk/src/platform/transition/purchase_document.rs index 1de4aeb43f..b0613f2eca 100644 --- a/packages/rs-sdk/src/platform/transition/purchase_document.rs +++ b/packages/rs-sdk/src/platform/transition/purchase_document.rs @@ -1,23 +1,19 @@ -use std::sync::Arc; - use crate::{Error, Sdk}; +use super::broadcast::BroadcastStateTransition; +use super::waitable::Waitable; use crate::platform::transition::put_settings::PutSettings; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::DocumentType; -use dpp::data_contract::DataContract; -use dpp::document::{Document, DocumentV0Getters}; +use dpp::document::Document; use dpp::fee::Credits; use dpp::identity::signer::Signer; use dpp::identity::IdentityPublicKey; use dpp::prelude::Identifier; use dpp::state_transition::documents_batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; use dpp::state_transition::documents_batch_transition::DocumentsBatchTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; -use super::broadcast::BroadcastStateTransition; - #[async_trait::async_trait] /// A trait for purchasing a document on Platform pub trait PurchaseDocument { @@ -34,14 +30,6 @@ pub trait PurchaseDocument { settings: Option, ) -> Result; - /// Waits for the response of a state transition after it has been broadcast - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - data_contract: Arc, - ) -> Result; - /// Tries to purchase a document on platform and waits for the response async fn purchase_document_and_wait_for_response( &self, @@ -50,8 +38,8 @@ pub trait PurchaseDocument { document_type: DocumentType, purchaser_id: Identifier, identity_public_key: IdentityPublicKey, - data_contract: Arc, signer: &S, + settings: Option, ) -> Result; } @@ -98,30 +86,6 @@ impl PurchaseDocument for Document { Ok(transition) } - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - _data_contract: Arc, - ) -> Result { - let result = state_transition.wait_for_response(sdk, None).await?; - - match result { - StateTransitionProofResult::VerifiedDocuments(mut documents) => { - let document = documents - .remove(self.id_ref()) - .ok_or(Error::InvalidProvedResponse( - "did not prove the sent document".to_string(), - ))? - .ok_or(Error::InvalidProvedResponse( - "expected there to actually be a document".to_string(), - ))?; - Ok(document) - } - _ => Err(Error::DapiClientError("proved a non document".to_string())), - } - } - async fn purchase_document_and_wait_for_response( &self, price: Credits, @@ -129,8 +93,8 @@ impl PurchaseDocument for Document { document_type: DocumentType, purchaser_id: Identifier, identity_public_key: IdentityPublicKey, - data_contract: Arc, signer: &S, + settings: Option, ) -> Result { let state_transition = self .purchase_document( @@ -140,18 +104,10 @@ impl PurchaseDocument for Document { purchaser_id, identity_public_key, signer, - None, + settings, ) .await?; - let document = >::wait_for_response( - self, - sdk, - state_transition, - data_contract, - ) - .await?; - - Ok(document) + ::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/put_contract.rs b/packages/rs-sdk/src/platform/transition/put_contract.rs index 9fc0e956ee..9e206f9dd2 100644 --- a/packages/rs-sdk/src/platform/transition/put_contract.rs +++ b/packages/rs-sdk/src/platform/transition/put_contract.rs @@ -10,14 +10,14 @@ use dpp::identity::signer::Signer; use dpp::identity::{IdentityPublicKey, PartialIdentity}; use dpp::state_transition::data_contract_create_transition::methods::DataContractCreateTransitionMethodsV0; use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; use super::broadcast::BroadcastStateTransition; +use super::waitable::Waitable; #[async_trait::async_trait] /// A trait for putting a contract to platform -pub trait PutContract { +pub trait PutContract: Waitable { /// Puts a document on platform /// setting settings to `None` sets default connection behavior async fn put_to_platform( @@ -28,19 +28,13 @@ pub trait PutContract { settings: Option, ) -> Result; - /// Waits for the response of a state transition after it has been broadcast - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - ) -> Result; - /// Puts a contract on platform and waits for the confirmation proof async fn put_to_platform_and_wait_for_response( &self, sdk: &Sdk, identity_public_key: IdentityPublicKey, signer: &S, + settings: Option, ) -> Result; } @@ -82,34 +76,17 @@ impl PutContract for DataContract { Ok(transition) } - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - ) -> Result { - let result = state_transition.wait_for_response(sdk, None).await?; - - //todo verify - - match result { - StateTransitionProofResult::VerifiedDataContract(data_contract) => Ok(data_contract), - _ => Err(Error::DapiClientError("proved a non document".to_string())), - } - } - async fn put_to_platform_and_wait_for_response( &self, sdk: &Sdk, identity_public_key: IdentityPublicKey, signer: &S, + settings: Option, ) -> Result { let state_transition = self - .put_to_platform(sdk, identity_public_key, signer, None) + .put_to_platform(sdk, identity_public_key, signer, settings) .await?; - let data_contract = - >::wait_for_response(self, sdk, state_transition).await?; - - Ok(data_contract) + Self::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/put_document.rs b/packages/rs-sdk/src/platform/transition/put_document.rs index 6e8617f953..efceb7cadf 100644 --- a/packages/rs-sdk/src/platform/transition/put_document.rs +++ b/packages/rs-sdk/src/platform/transition/put_document.rs @@ -1,17 +1,15 @@ use super::broadcast::BroadcastStateTransition; +use super::waitable::Waitable; use crate::platform::transition::put_settings::PutSettings; use crate::{Error, Sdk}; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::DocumentType; -use dpp::data_contract::DataContract; use dpp::document::{Document, DocumentV0Getters}; use dpp::identity::signer::Signer; use dpp::identity::IdentityPublicKey; use dpp::state_transition::documents_batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; use dpp::state_transition::documents_batch_transition::DocumentsBatchTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; -use std::sync::Arc; #[async_trait::async_trait] /// A trait for putting a document to platform @@ -28,14 +26,6 @@ pub trait PutDocument { settings: Option, ) -> Result; - /// Waits for the response of a state transition after it has been broadcast - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - data_contract: Arc, - ) -> Result; - /// Puts an identity on platform and waits for the confirmation proof async fn put_to_platform_and_wait_for_response( &self, @@ -43,8 +33,8 @@ pub trait PutDocument { document_type: DocumentType, document_state_transition_entropy: [u8; 32], identity_public_key: IdentityPublicKey, - data_contract: Arc, signer: &S, + settings: Option, ) -> Result; } @@ -89,38 +79,14 @@ impl PutDocument for Document { Ok(transition) } - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - _data_contract: Arc, - ) -> Result { - let result = state_transition.wait_for_response(sdk, None).await?; - //todo verify - match result { - StateTransitionProofResult::VerifiedDocuments(mut documents) => { - let document = documents - .remove(self.id_ref()) - .ok_or(Error::InvalidProvedResponse( - "did not prove the sent document".to_string(), - ))? - .ok_or(Error::InvalidProvedResponse( - "expected there to actually be a document".to_string(), - ))?; - Ok(document) - } - _ => Err(Error::DapiClientError("proved a non document".to_string())), - } - } - async fn put_to_platform_and_wait_for_response( &self, sdk: &Sdk, document_type: DocumentType, document_state_transition_entropy: [u8; 32], identity_public_key: IdentityPublicKey, - _data_contract: Arc, signer: &S, + settings: Option, ) -> Result { let state_transition = self .put_to_platform( @@ -129,24 +95,10 @@ impl PutDocument for Document { document_state_transition_entropy, identity_public_key, signer, - None, + settings, ) .await?; - let result = state_transition.broadcast_and_wait(sdk, None).await?; - match result { - StateTransitionProofResult::VerifiedDocuments(mut documents) => { - let document = documents - .remove(self.id_ref()) - .ok_or(Error::InvalidProvedResponse( - "did not prove the sent document".to_string(), - ))? - .ok_or(Error::InvalidProvedResponse( - "expected there to actually be a document".to_string(), - ))?; - Ok(document) - } - _ => Err(Error::DapiClientError("proved a non document".to_string())), - } + ::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/put_identity.rs b/packages/rs-sdk/src/platform/transition/put_identity.rs index 30276a06a0..ce79b52d81 100644 --- a/packages/rs-sdk/src/platform/transition/put_identity.rs +++ b/packages/rs-sdk/src/platform/transition/put_identity.rs @@ -1,43 +1,39 @@ use crate::platform::transition::broadcast_identity::BroadcastRequestForNewIdentity; -use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; -use crate::platform::Fetch; use crate::{Error, Sdk}; -use dapi_grpc::platform::VersionedGrpcResponse; -use dapi_grpc::tonic::Code; +use super::broadcast::BroadcastStateTransition; +use super::put_settings::PutSettings; +use super::waitable::Waitable; use dpp::dashcore::PrivateKey; use dpp::identity::signer::Signer; use dpp::prelude::{AssetLockProof, Identity}; -use drive_proof_verifier::error::ContextProviderError; -use drive_proof_verifier::DataContractProvider; +use dpp::state_transition::StateTransition; -use crate::platform::block_info_from_metadata::block_info_from_metadata; -use dpp::state_transition::proof_result::StateTransitionProofResult; -use drive::drive::Drive; -use rs_dapi_client::transport::TransportError; -use rs_dapi_client::{DapiClientError, DapiRequest, IntoInner, RequestSettings}; - -#[async_trait::async_trait] /// A trait for putting an identity to platform -pub trait PutIdentity { - /// Puts an identity on platform +#[async_trait::async_trait] +pub trait PutIdentity: Waitable { + /// Puts an identity on platform. + /// + /// TODO: Discuss if it should not actually consume self, since it is no longer valid (eg. identity id is changed) async fn put_to_platform( &self, sdk: &Sdk, asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &PrivateKey, signer: &S, - ) -> Result<(), Error>; - /// Puts an identity on platform and waits for the confirmation proof + settings: Option, + ) -> Result; + + /// Puts an identity on platform and waits for the confirmation proof. async fn put_to_platform_and_wait_for_response( &self, sdk: &Sdk, asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &PrivateKey, signer: &S, - ) -> Result; + settings: Option, + ) -> Result; } - #[async_trait::async_trait] impl PutIdentity for Identity { async fn put_to_platform( @@ -46,23 +42,18 @@ impl PutIdentity for Identity { asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &PrivateKey, signer: &S, - ) -> Result<(), Error> { - let (_, request) = self.broadcast_request_for_new_identity( + settings: Option, + ) -> Result { + let (state_transition, _) = self.broadcast_request_for_new_identity( asset_lock_proof, asset_lock_proof_private_key, signer, sdk.version(), )?; - request - .clone() - .execute(sdk, RequestSettings::default()) - .await // TODO: We need better way to handle execution errors - .into_inner()?; - // response is empty for a broadcast, result comes from the stream wait for state transition result - - Ok(()) + state_transition.broadcast(sdk, settings).await?; + Ok(state_transition) } async fn put_to_platform_and_wait_for_response( @@ -71,68 +62,18 @@ impl PutIdentity for Identity { asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &PrivateKey, signer: &S, + settings: Option, ) -> Result { - let identity_id = asset_lock_proof.create_identifier()?; - let (state_transition, request) = self.broadcast_request_for_new_identity( - asset_lock_proof, - asset_lock_proof_private_key, - signer, - sdk.version(), - )?; - - let response_result = request - .clone() - .execute(sdk, RequestSettings::default()) - .await - .into_inner(); - - match response_result { - Ok(_) => {} - //todo make this more reliable - Err(DapiClientError::Transport(TransportError::Grpc(te))) - if te.code() == Code::AlreadyExists => - { - tracing::debug!( - ?identity_id, - "attempt to create identity that already exists" - ); - let identity = Identity::fetch(sdk, identity_id).await?; - return identity.ok_or(Error::DapiClientError( - "identity was proved to not exist but was said to exist".to_string(), - )); - } - Err(e) => return Err(e.into()), - } - - let request = state_transition.wait_for_state_transition_result_request()?; - // TODO: Implement retry logic - - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - let proof = response.proof_owned()?; - let context_provider = - sdk.context_provider() - .ok_or(Error::from(ContextProviderError::Config( - "Context provider not initialized".to_string(), - )))?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &state_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &context_provider.as_contract_lookup_fn(), - sdk.version(), - )?; - - //todo verify - - match result { - StateTransitionProofResult::VerifiedIdentity(identity) => Ok(identity), - _ => Err(Error::DapiClientError("proved a non identity".to_string())), - } + let state_transition = self + .put_to_platform( + sdk, + asset_lock_proof, + asset_lock_proof_private_key, + signer, + settings, + ) + .await?; + + Self::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/top_up_identity.rs b/packages/rs-sdk/src/platform/transition/top_up_identity.rs index c43d8a9f19..38bcb351e6 100644 --- a/packages/rs-sdk/src/platform/transition/top_up_identity.rs +++ b/packages/rs-sdk/src/platform/transition/top_up_identity.rs @@ -1,17 +1,11 @@ -use crate::platform::block_info_from_metadata::block_info_from_metadata; -use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; +use super::broadcast::BroadcastStateTransition; +use super::put_settings::PutSettings; use crate::{Error, Sdk}; -use dapi_grpc::platform::VersionedGrpcResponse; use dpp::dashcore::PrivateKey; -use dpp::identity::Identity; +use dpp::identity::{Identity, PartialIdentity}; use dpp::prelude::{AssetLockProof, UserFeeIncrease}; use dpp::state_transition::identity_topup_transition::methods::IdentityTopUpTransitionMethodsV0; use dpp::state_transition::identity_topup_transition::IdentityTopUpTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; -use drive::drive::Drive; -use drive_proof_verifier::error::ContextProviderError; -use drive_proof_verifier::DataContractProvider; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; #[async_trait::async_trait] pub trait TopUpIdentity { @@ -21,6 +15,7 @@ pub trait TopUpIdentity { asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &PrivateKey, user_fee_increase: Option, + settings: Option, ) -> Result; } @@ -32,6 +27,7 @@ impl TopUpIdentity for Identity { asset_lock_proof: AssetLockProof, asset_lock_proof_private_key: &PrivateKey, user_fee_increase: Option, + settings: Option, ) -> Result { let state_transition = IdentityTopUpTransition::try_from_identity( self, @@ -41,46 +37,10 @@ impl TopUpIdentity for Identity { sdk.version(), None, )?; + let identity: PartialIdentity = state_transition.broadcast_and_wait(sdk, settings).await?; - let request = state_transition.broadcast_request_for_state_transition()?; - - request - .clone() - .execute(sdk, RequestSettings::default()) - .await // TODO: We need better way to handle execution errors - .into_inner()?; - - let request = state_transition.wait_for_state_transition_result_request()?; - // TODO: Implement retry logic in wait for state transition result - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - - let proof = response.proof_owned()?; - let context_provider = - sdk.context_provider() - .ok_or(Error::from(ContextProviderError::Config( - "Context provider not initialized".to_string(), - )))?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &state_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &context_provider.as_contract_lookup_fn(), - sdk.version(), - )?; - - match result { - StateTransitionProofResult::VerifiedPartialIdentity(identity) => { - identity.balance.ok_or(Error::DapiClientError( - "expected an identity balance".to_string(), - )) - } - _ => Err(Error::DapiClientError("proved a non identity".to_string())), - } + identity.balance.ok_or(Error::DapiClientError( + "expected an identity balance".to_string(), + )) } } diff --git a/packages/rs-sdk/src/platform/transition/transfer.rs b/packages/rs-sdk/src/platform/transition/transfer.rs index 6d932c5abb..e8e19eafe8 100644 --- a/packages/rs-sdk/src/platform/transition/transfer.rs +++ b/packages/rs-sdk/src/platform/transition/transfer.rs @@ -5,10 +5,9 @@ use crate::platform::transition::broadcast::BroadcastStateTransition; use crate::platform::transition::put_settings::PutSettings; use crate::{Error, Sdk}; use dpp::identity::signer::Signer; -use dpp::identity::{Identity, IdentityPublicKey}; +use dpp::identity::{Identity, IdentityPublicKey, PartialIdentity}; use dpp::state_transition::identity_credit_transfer_transition::methods::IdentityCreditTransferTransitionMethodsV0; use dpp::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; #[async_trait::async_trait] pub trait TransferToIdentity { @@ -59,15 +58,10 @@ impl TransferToIdentity for Identity { None, )?; - let result = state_transition.broadcast_and_wait(sdk, settings).await?; + let identity: PartialIdentity = state_transition.broadcast_and_wait(sdk, settings).await?; - match result { - StateTransitionProofResult::VerifiedPartialIdentity(identity) => { - identity.balance.ok_or(Error::DapiClientError( - "expected an identity balance after transfer".to_string(), - )) - } - _ => Err(Error::DapiClientError("proved a non identity".to_string())), - } + identity.balance.ok_or(Error::DapiClientError( + "expected an identity balance after transfer".to_string(), + )) } } diff --git a/packages/rs-sdk/src/platform/transition/transfer_document.rs b/packages/rs-sdk/src/platform/transition/transfer_document.rs index a64c76cb95..2106141ae3 100644 --- a/packages/rs-sdk/src/platform/transition/transfer_document.rs +++ b/packages/rs-sdk/src/platform/transition/transfer_document.rs @@ -1,28 +1,21 @@ +use super::waitable::Waitable; use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; -use std::sync::Arc; - -use crate::{Error, Sdk}; - -use crate::platform::block_info_from_metadata::block_info_from_metadata; use crate::platform::transition::put_settings::PutSettings; use crate::platform::Identifier; -use dapi_grpc::platform::VersionedGrpcResponse; +use crate::{Error, Sdk}; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::DocumentType; -use dpp::data_contract::DataContract; use dpp::document::{Document, DocumentV0Getters}; use dpp::identity::signer::Signer; use dpp::identity::IdentityPublicKey; use dpp::state_transition::documents_batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; use dpp::state_transition::documents_batch_transition::DocumentsBatchTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; -use drive::drive::Drive; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; +use rs_dapi_client::{DapiRequest, IntoInner}; #[async_trait::async_trait] /// A trait for transferring a document on Platform -pub trait TransferDocument { +pub trait TransferDocument: Waitable { /// Transfers a document on platform /// Setting settings to `None` sets default connection behavior async fn transfer_document_to_identity( @@ -35,14 +28,6 @@ pub trait TransferDocument { settings: Option, ) -> Result; - /// Waits for the response of a state transition after it has been broadcast - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - data_contract: Arc, - ) -> Result; - /// Transfers a document on platform and waits for the response async fn transfer_document_to_identity_and_wait_for_response( &self, @@ -50,8 +35,8 @@ pub trait TransferDocument { sdk: &Sdk, document_type: DocumentType, identity_public_key: IdentityPublicKey, - data_contract: Arc, signer: &S, + settings: Option, ) -> Result; } @@ -104,55 +89,14 @@ impl TransferDocument for Document { Ok(transition) } - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - data_contract: Arc, - ) -> Result { - let request = state_transition.wait_for_state_transition_result_request()?; - - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - - let proof = response.proof_owned()?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &state_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &|_| Ok(Some(data_contract.clone())), - sdk.version(), - )?; - - match result { - StateTransitionProofResult::VerifiedDocuments(mut documents) => { - let document = documents - .remove(self.id_ref()) - .ok_or(Error::InvalidProvedResponse( - "did not prove the sent document".to_string(), - ))? - .ok_or(Error::InvalidProvedResponse( - "expected there to actually be a document".to_string(), - ))?; - Ok(document) - } - _ => Err(Error::DapiClientError("proved a non document".to_string())), - } - } - async fn transfer_document_to_identity_and_wait_for_response( &self, recipient_id: Identifier, sdk: &Sdk, document_type: DocumentType, identity_public_key: IdentityPublicKey, - data_contract: Arc, signer: &S, + settings: Option, ) -> Result { let state_transition = self .transfer_document_to_identity( @@ -161,18 +105,10 @@ impl TransferDocument for Document { document_type, identity_public_key, signer, - None, + settings, ) .await?; - let document = >::wait_for_response( - self, - sdk, - state_transition, - data_contract, - ) - .await?; - - Ok(document) + Self::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/update_price_of_document.rs b/packages/rs-sdk/src/platform/transition/update_price_of_document.rs index 0f331cde5d..99a5642bf9 100644 --- a/packages/rs-sdk/src/platform/transition/update_price_of_document.rs +++ b/packages/rs-sdk/src/platform/transition/update_price_of_document.rs @@ -1,28 +1,21 @@ -use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; -use std::sync::Arc; - use crate::{Error, Sdk}; -use crate::platform::block_info_from_metadata::block_info_from_metadata; +use super::broadcast::BroadcastStateTransition; +use super::waitable::Waitable; use crate::platform::transition::put_settings::PutSettings; -use dapi_grpc::platform::VersionedGrpcResponse; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::document_type::DocumentType; -use dpp::data_contract::DataContract; use dpp::document::{Document, DocumentV0Getters}; use dpp::fee::Credits; use dpp::identity::signer::Signer; use dpp::identity::IdentityPublicKey; use dpp::state_transition::documents_batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; use dpp::state_transition::documents_batch_transition::DocumentsBatchTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::state_transition::StateTransition; -use drive::drive::Drive; -use rs_dapi_client::{DapiRequest, IntoInner, RequestSettings}; #[async_trait::async_trait] /// A trait for updating the price of a document on Platform -pub trait UpdatePriceOfDocument { +pub trait UpdatePriceOfDocument: Waitable { /// Updates the price of a document on platform /// Setting settings to `None` sets default connection behavior async fn update_price_of_document( @@ -35,14 +28,6 @@ pub trait UpdatePriceOfDocument { settings: Option, ) -> Result; - /// Waits for the response of a state transition after it has been broadcast - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - data_contract: Arc, - ) -> Result; - /// Updates the price of a document on platform and waits for the response async fn update_price_of_document_and_wait_for_response( &self, @@ -50,8 +35,8 @@ pub trait UpdatePriceOfDocument { sdk: &Sdk, document_type: DocumentType, identity_public_key: IdentityPublicKey, - data_contract: Arc, signer: &S, + settings: Option, ) -> Result; } @@ -92,81 +77,24 @@ impl UpdatePriceOfDocument for Document { None, )?; - let request = transition.broadcast_request_for_state_transition()?; - - request - .clone() - .execute(sdk, settings.request_settings) - .await // TODO: We need better way to handle execution errors - .into_inner()?; - // response is empty for a broadcast, result comes from the stream wait for state transition result - + transition.broadcast(sdk, Some(settings)).await?; Ok(transition) } - async fn wait_for_response( - &self, - sdk: &Sdk, - state_transition: StateTransition, - data_contract: Arc, - ) -> Result { - let request = state_transition.wait_for_state_transition_result_request()?; - // TODO: Implement retry logic - let response = request - .execute(sdk, RequestSettings::default()) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - - let proof = response.proof_owned()?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &state_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &|_| Ok(Some(data_contract.clone())), - sdk.version(), - )?; - - match result { - StateTransitionProofResult::VerifiedDocuments(mut documents) => { - let document = documents - .remove(self.id_ref()) - .ok_or(Error::InvalidProvedResponse( - "did not prove the sent document".to_string(), - ))? - .ok_or(Error::InvalidProvedResponse( - "expected there to actually be a document".to_string(), - ))?; - Ok(document) - } - _ => Err(Error::DapiClientError("proved a non document".to_string())), - } - } - async fn update_price_of_document_and_wait_for_response( &self, price: Credits, sdk: &Sdk, document_type: DocumentType, identity_public_key: IdentityPublicKey, - data_contract: Arc, signer: &S, + settings: Option, ) -> Result { let state_transition = self .update_price_of_document(price, sdk, document_type, identity_public_key, signer, None) .await?; - let document = >::wait_for_response( - self, - sdk, - state_transition, - data_contract, - ) - .await?; - - Ok(document) + Self::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/waitable.rs b/packages/rs-sdk/src/platform/transition/waitable.rs new file mode 100644 index 0000000000..515ed499d5 --- /dev/null +++ b/packages/rs-sdk/src/platform/transition/waitable.rs @@ -0,0 +1,119 @@ +use std::collections::BTreeMap; + +use super::broadcast::BroadcastStateTransition; +use super::put_settings::PutSettings; +use crate::platform::Fetch; +use crate::Error; +use crate::Sdk; +use dpp::document::Document; +use dpp::prelude::{DataContract, Identifier, Identity}; +use dpp::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0; +use dpp::state_transition::StateTransition; +use dpp::state_transition::StateTransitionLike; +use dpp::ProtocolError; + +/// Waitable trait provides a wait to wait for a response of a state transition after it has been broadcast and +/// receive altered objects. +/// +/// This is simple conveniance trait wrapping the [`BroadcastStateTransition::wait_for_response`] method. +#[async_trait::async_trait] +pub trait Waitable: Sized { + async fn wait_for_response( + sdk: &Sdk, + state_transition: StateTransition, + settings: Option, + ) -> Result; +} +#[async_trait::async_trait] +impl Waitable for DataContract { + async fn wait_for_response( + sdk: &Sdk, + state_transition: StateTransition, + settings: Option, + ) -> Result { + state_transition.wait_for_response(sdk, settings).await + } +} + +#[async_trait::async_trait] +impl Waitable for Document { + async fn wait_for_response( + sdk: &Sdk, + state_transition: StateTransition, + settings: Option, + ) -> Result { + let doc_id = if let StateTransition::DocumentsBatch(transition) = &state_transition { + let ids = transition.modified_data_ids(); + if ids.len() != 1 { + return Err(Error::Protocol( + dpp::ProtocolError::InvalidStateTransitionType(format!( + "expected state transition with exactly one document, got {}", + ids.into_iter() + .map(|id| id + .to_string(dpp::platform_value::string_encoding::Encoding::Base58)) + .collect::>() + .join(", ") + )), + )); + } + ids[0] + } else { + return Err(Error::Protocol(ProtocolError::InvalidStateTransitionType( + format!( + "expected state transition to be a DocumentsBatchTransition, got {}", + state_transition.name() + ), + ))); + }; + + let mut documents: BTreeMap> = + state_transition.wait_for_response(sdk, settings).await?; + + let document: Document = documents + .remove(&doc_id) + .ok_or(Error::InvalidProvedResponse( + "did not prove the sent document".to_string(), + ))? + .ok_or(Error::InvalidProvedResponse( + "expected there to actually be a document".to_string(), + ))?; + + Ok(document) + } +} + +#[async_trait::async_trait] +impl Waitable for Identity { + async fn wait_for_response( + sdk: &Sdk, + state_transition: StateTransition, + settings: Option, + ) -> Result { + let result: Result = state_transition.wait_for_response(sdk, settings).await; + + match result { + Ok(identity) => Ok(identity), + // TODO: We need to refactor sdk Error to be able to retrieve gRPC error code and identify conflicts + Err(Error::AlreadyExists(_)) => { + let identity_id = if let StateTransition::IdentityCreate(st) = state_transition { + st.identity_id() + } else { + return Err(Error::Generic(format!( + "expected identity create state transition, got {:?}", + state_transition.name() + ))); + }; + + tracing::debug!( + ?identity_id, + "attempt to create identity that already exists" + ); + let identity = Identity::fetch(sdk, identity_id).await?; + identity.ok_or(Error::DapiClientError( + "identity was proved to not exist but was said to exist".to_string(), + )) + } + Err(e) => Err(e), + } + } +} From 654e56374560eaac676639a144390eb668c897f7 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:26:03 +0100 Subject: [PATCH 12/22] test(sdk): update tests --- .../rs-sdk/tests/fetch/contested_resource_vote_state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/rs-sdk/tests/fetch/contested_resource_vote_state.rs b/packages/rs-sdk/tests/fetch/contested_resource_vote_state.rs index 6c0bd2f7c4..dd21b55aa7 100644 --- a/packages/rs-sdk/tests/fetch/contested_resource_vote_state.rs +++ b/packages/rs-sdk/tests/fetch/contested_resource_vote_state.rs @@ -107,7 +107,7 @@ async fn contested_resource_vote_states_nx_contract() { if let dash_sdk::error::Error::DapiClientError(e) = result { assert!( e.contains( - "Transport(Grpc(Status { code: InvalidArgument, message: \"contract not found error" + "Transport(Grpc(Status { status: InvalidArgument, message: \"contract not found error" ), "we should get contract not found error, got: {:?}", e, @@ -280,9 +280,9 @@ type MutFn = fn(&mut ContestedDocumentVotePollDriveQuery); #[test_case(|q| q.limit = Some(u16::MAX), Err("limit 65535 out of bounds of [1, 100]"); "limit u16::MAX")] #[test_case(|q| q.start_at = Some(([0x11; 32], true)), Ok("Contenders { winner: None, contenders: {Identifier("); "start_at does not exist should return next contenders")] #[test_case(|q| q.start_at = Some(([0xff; 32], true)), Ok("Contenders { winner: None, contenders: {}, abstain_vote_tally: None, lock_vote_tally: None }"); "start_at 0xff;32 should return zero contenders")] -#[test_case(|q| q.vote_poll.document_type_name = "nx doctype".to_string(), Err(r#"code: InvalidArgument, message: "document type nx doctype not found"#); "non existing document type returns InvalidArgument")] -#[test_case(|q| q.vote_poll.index_name = "nx index".to_string(), Err(r#"code: InvalidArgument, message: "index with name nx index is not the contested index"#); "non existing index returns InvalidArgument")] -#[test_case(|q| q.vote_poll.index_name = "dashIdentityId".to_string(), Err(r#"code: InvalidArgument, message: "index with name dashIdentityId is not the contested index"#); "existing non-contested index returns InvalidArgument")] +#[test_case(|q| q.vote_poll.document_type_name = "nx doctype".to_string(), Err(r#"status: InvalidArgument, message: "document type nx doctype not found"#); "non existing document type returns InvalidArgument")] +#[test_case(|q| q.vote_poll.index_name = "nx index".to_string(), Err(r#"status: InvalidArgument, message: "index with name nx index is not the contested index"#); "non existing index returns InvalidArgument")] +#[test_case(|q| q.vote_poll.index_name = "dashIdentityId".to_string(), Err(r#"status: InvalidArgument, message: "index with name dashIdentityId is not the contested index"#); "existing non-contested index returns InvalidArgument")] #[test_case(|q| q.vote_poll.index_values = vec![], Err("query uses index parentNameAndLabel, this index has 2 properties, but the query provided 0 index values instead"); "index_values empty vec returns error")] #[test_case(|q| q.vote_poll.index_values = vec![Value::Text("".to_string())], Err("query uses index parentNameAndLabel, this index has 2 properties, but the query provided 1 index values instead"); "index_values empty string returns error")] #[test_case(|q| q.vote_poll.index_values = vec![Value::Text("dash".to_string())], Err("query uses index parentNameAndLabel, this index has 2 properties, but the query provided 1 index values instead"); "index_values with one value returns error")] From d5e03b38efe399807923fdc5b7d970e5f9310ff5 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:23:36 +0100 Subject: [PATCH 13/22] chore(sdk): retry 3 times by default --- packages/rs-sdk/src/sdk.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 3fd570e206..061230905c 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -50,6 +50,16 @@ pub const DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE: usize = 100; /// The default identity nonce stale time in seconds pub const DEFAULT_IDENTITY_NONCE_STALE_TIME_S: u64 = 1200; //20 mins +/// The default request settings for the SDK, used when the user does not provide any. +/// +/// Use [SdkBuilder::with_settings] to set custom settings. +const DEFAULT_REQUEST_SETTINGS: RequestSettings = RequestSettings { + retries: Some(3), + timeout: None, + ban_failed_address: None, + connect_timeout: None, +}; + /// a type to represent staleness in seconds pub type StalenessInSeconds = u64; @@ -705,7 +715,7 @@ pub struct SdkBuilder { /// /// If `None`, a mock client will be created. addresses: Option, - settings: RequestSettings, + settings: Option, network: Network, @@ -755,7 +765,7 @@ impl Default for SdkBuilder { fn default() -> Self { Self { addresses: None, - settings: RequestSettings::default(), + settings: None, network: Network::Dash, core_ip: "".to_string(), core_port: 0, @@ -836,7 +846,7 @@ impl SdkBuilder { /// /// See [`RequestSettings`] for more information. pub fn with_settings(mut self, settings: RequestSettings) -> Self { - self.settings = settings; + self.settings = Some(settings); self } @@ -952,17 +962,18 @@ impl SdkBuilder { pub fn build(self) -> Result { PlatformVersion::set_current(self.version); + let dapi_client_settings = self.settings.unwrap_or(DEFAULT_REQUEST_SETTINGS); let sdk= match self.addresses { // non-mock mode Some(addresses) => { - let dapi = DapiClient::new(addresses, self.settings); + let dapi = DapiClient::new(addresses,dapi_client_settings); #[cfg(feature = "mocks")] let dapi = dapi.dump_dir(self.dump_dir.clone()); #[allow(unused_mut)] // needs to be mutable for #[cfg(feature = "mocks")] let mut sdk= Sdk{ network: self.network, - dapi_client_settings: self.settings, + dapi_client_settings, inner:SdkInstance::Dapi { dapi, version:self.version }, proofs:self.proofs, context_provider: ArcSwapOption::new( self.context_provider.map(Arc::new)), @@ -1025,7 +1036,7 @@ impl SdkBuilder { let mock_sdk = Arc::new(Mutex::new(mock_sdk)); let sdk= Sdk { network: self.network, - dapi_client_settings: self.settings, + dapi_client_settings, inner:SdkInstance::Mock { mock:mock_sdk.clone(), dapi, From 89333d7a09e2d2fda22975d08227c9481690fa02 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:38:05 +0100 Subject: [PATCH 14/22] chore(dapi-client): add WARN log when banning address --- packages/rs-dapi-client/src/dapi_client.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index 579c62e015..09374d620b 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -227,7 +227,11 @@ impl DapiRequestExecutor for DapiClient { .address_list .write() .expect("can't get address list for write"); - + tracing::warn!( + ?address, + ?error, + "received server error, banning address" + ); address_list.ban_address(&address).map_err(|error| { ExecutionError { inner: DapiClientError::AddressList(error), From b1e3e6ca22ddce146b50e0a16aa1855476fdd4c8 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:32:32 +0100 Subject: [PATCH 15/22] chore(dapi-client): improve logging --- packages/rs-dapi-client/src/dapi_client.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index 09374d620b..e33ef5da2c 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -240,9 +240,18 @@ impl DapiRequestExecutor for DapiClient { address: Some(address.clone()), } })?; + } else { + tracing::debug!( + ?address, + ?error, + "received server error, we should ban the node but banning is disabled" + ); } } else { - tracing::trace!(?error, "received error"); + tracing::debug!( + ?error, + "server returned error, most likely the request is invalid" + ); } } }; From 79ed045790f242af4cafc5e4de4eb1df5ef7856c Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:39:52 +0100 Subject: [PATCH 16/22] chore: unify error msg --- packages/rs-dapi-client/src/dapi_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index e33ef5da2c..c5ef5e67a1 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -250,7 +250,7 @@ impl DapiRequestExecutor for DapiClient { } else { tracing::debug!( ?error, - "server returned error, most likely the request is invalid" + "received server error, most likely the request is invalid" ); } } From 146d83da58cdb9ff7773822ccf58fdbe19cad286 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:53:17 +0100 Subject: [PATCH 17/22] chore: rename some fn --- packages/rs-dapi-client/src/executor.rs | 8 ++++---- packages/rs-dapi-client/src/lib.rs | 2 +- .../src/platform/transition/broadcast.rs | 20 +++++++++++++------ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/rs-dapi-client/src/executor.rs b/packages/rs-dapi-client/src/executor.rs index 716a22b5b3..cbb999a3b0 100644 --- a/packages/rs-dapi-client/src/executor.rs +++ b/packages/rs-dapi-client/src/executor.rs @@ -159,7 +159,7 @@ where } /// Convert Result to ExecutionResult, taking context from ExecutionResponse. -pub trait Wrap: Sized { +pub trait WrapToExecutionResult: Sized { /// Convert self (eg. some [Result]) to [ExecutionResult], taking context information from `W` (eg. ExecutionResponse). /// /// This function simplifies processing of results by wrapping them into ExecutionResult. @@ -195,15 +195,15 @@ pub trait Wrap: Sized { /// panic!("Expected error"); /// } /// ``` - fn wrap(self, result: &W) -> ExecutionResult; + fn wrap_to_execution_result(self, result: &W) -> ExecutionResult; } -impl Wrap> for Result +impl WrapToExecutionResult> for Result where R: From, RE: From, { - fn wrap(self, result: &ExecutionResponse) -> ExecutionResult { + fn wrap_to_execution_result(self, result: &ExecutionResponse) -> ExecutionResult { match self { Ok(r) => ExecutionResult::Ok(ExecutionResponse { inner: r.into(), diff --git a/packages/rs-dapi-client/src/lib.rs b/packages/rs-dapi-client/src/lib.rs index 9f5f45e27a..f8c03f3956 100644 --- a/packages/rs-dapi-client/src/lib.rs +++ b/packages/rs-dapi-client/src/lib.rs @@ -22,7 +22,7 @@ pub use dapi_client::{DapiClient, DapiClientError}; pub use dump::DumpData; pub use executor::{ DapiRequestExecutor, ExecutionError, ExecutionResponse, ExecutionResult, InnerInto, IntoInner, - Wrap, + WrapToExecutionResult, }; use futures::{future::BoxFuture, FutureExt}; pub use request_settings::RequestSettings; diff --git a/packages/rs-sdk/src/platform/transition/broadcast.rs b/packages/rs-sdk/src/platform/transition/broadcast.rs index 00f1795645..f41a279b13 100644 --- a/packages/rs-sdk/src/platform/transition/broadcast.rs +++ b/packages/rs-sdk/src/platform/transition/broadcast.rs @@ -10,7 +10,7 @@ use dpp::state_transition::StateTransition; use drive::drive::Drive; use drive_proof_verifier::error::ContextProviderError; use drive_proof_verifier::DataContractProvider; -use rs_dapi_client::Wrap; +use rs_dapi_client::WrapToExecutionResult; use rs_dapi_client::{DapiRequest, ExecutionError, InnerInto, IntoInner, RequestSettings}; #[async_trait::async_trait] @@ -80,9 +80,17 @@ impl BroadcastStateTransition for StateTransition { let response = request.execute(sdk, request_settings).await.inner_into()?; let grpc_response: &WaitForStateTransitionResultResponse = &response.inner; - let metadata = grpc_response.metadata().wrap(&response)?.inner; - let block_info = block_info_from_metadata(metadata).wrap(&response)?.inner; - let proof: &Proof = (*grpc_response).proof().wrap(&response)?.inner; + let metadata = grpc_response + .metadata() + .wrap_to_execution_result(&response)? + .inner; + let block_info = block_info_from_metadata(metadata) + .wrap_to_execution_result(&response)? + .inner; + let proof: &Proof = (*grpc_response) + .proof() + .wrap_to_execution_result(&response)? + .inner; let context_provider = sdk.context_provider().ok_or(ExecutionError { inner: Error::from(ContextProviderError::Config( @@ -99,7 +107,7 @@ impl BroadcastStateTransition for StateTransition { &context_provider.as_contract_lookup_fn(), sdk.version(), ) - .wrap(&response)? + .wrap_to_execution_result(&response)? .inner; let variant_name = result.to_string(); @@ -111,7 +119,7 @@ impl BroadcastStateTransition for StateTransition { std::any::type_name::(), )) }) - .wrap(&response) + .wrap_to_execution_result(&response) }; let future = retry(retry_settings, factory); From 21af0e7e4b4885b7e43b526ff04643ab716e33b5 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:15:46 +0100 Subject: [PATCH 18/22] chore: fix tests --- packages/dapi-grpc/build.rs | 2 +- packages/rs-dapi-client/src/executor.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dapi-grpc/build.rs b/packages/dapi-grpc/build.rs index f70b685fbd..642b614ab9 100644 --- a/packages/dapi-grpc/build.rs +++ b/packages/dapi-grpc/build.rs @@ -289,7 +289,7 @@ impl MappingConfig { create_dir_all(&self.out_dir)?; self.builder - .compile(&[self.protobuf_file], &self.proto_includes) + .compile_protos(&[self.protobuf_file], &self.proto_includes) } } diff --git a/packages/rs-dapi-client/src/executor.rs b/packages/rs-dapi-client/src/executor.rs index cbb999a3b0..0afb8f5705 100644 --- a/packages/rs-dapi-client/src/executor.rs +++ b/packages/rs-dapi-client/src/executor.rs @@ -171,7 +171,7 @@ pub trait WrapToExecutionResult: Sized { /// ## Example /// /// ```rust - /// use rs_dapi_client::{ExecutionResponse, ExecutionResult, Wrap}; + /// use rs_dapi_client::{ExecutionResponse, ExecutionResult, WrapToExecutionResult}; /// /// fn some_request() -> ExecutionResult { /// Ok(ExecutionResponse { @@ -186,7 +186,7 @@ pub trait WrapToExecutionResult: Sized { /// } /// /// let response = some_request().expect("request should succeed"); - /// let result: ExecutionResult = next_step().wrap(&response); + /// let result: ExecutionResult = next_step().wrap_to_execution_result(&response); /// /// if let ExecutionResult::Err(error) = result { /// assert_eq!(error.inner, "next error"); From 4ab4565e36fd2f5dab4b4621841067ae0c5b7ee6 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:44:06 +0100 Subject: [PATCH 19/22] chore: rabbit feedback - crate error handling upgrade --- packages/rs-sdk/src/platform/transition/waitable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rs-sdk/src/platform/transition/waitable.rs b/packages/rs-sdk/src/platform/transition/waitable.rs index 515ed499d5..e75a62d86b 100644 --- a/packages/rs-sdk/src/platform/transition/waitable.rs +++ b/packages/rs-sdk/src/platform/transition/waitable.rs @@ -41,7 +41,7 @@ impl Waitable for Document { sdk: &Sdk, state_transition: StateTransition, settings: Option, - ) -> Result { + ) -> Result { let doc_id = if let StateTransition::DocumentsBatch(transition) = &state_transition { let ids = transition.modified_data_ids(); if ids.len() != 1 { @@ -88,7 +88,7 @@ impl Waitable for Identity { sdk: &Sdk, state_transition: StateTransition, settings: Option, - ) -> Result { + ) -> Result { let result: Result = state_transition.wait_for_response(sdk, settings).await; match result { From c8b6c78768edce4e92a975fb04cf98e3dc9462e0 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:07:25 +0100 Subject: [PATCH 20/22] chore: add Waitable supertrait to Put* traits --- packages/rs-sdk/src/platform/transition/purchase_document.rs | 4 ++-- packages/rs-sdk/src/platform/transition/put_document.rs | 4 ++-- packages/rs-sdk/src/platform/transition/top_up_identity.rs | 3 ++- packages/rs-sdk/src/platform/transition/transfer.rs | 4 +++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/rs-sdk/src/platform/transition/purchase_document.rs b/packages/rs-sdk/src/platform/transition/purchase_document.rs index fee8a0c594..530c1c6b83 100644 --- a/packages/rs-sdk/src/platform/transition/purchase_document.rs +++ b/packages/rs-sdk/src/platform/transition/purchase_document.rs @@ -15,7 +15,7 @@ use dpp::state_transition::StateTransition; #[async_trait::async_trait] /// A trait for purchasing a document on Platform -pub trait PurchaseDocument { +pub trait PurchaseDocument: Waitable { /// Tries to purchase a document on platform /// Setting settings to `None` sets default connection behavior async fn purchase_document( @@ -107,6 +107,6 @@ impl PurchaseDocument for Document { ) .await?; - ::wait_for_response(sdk, state_transition, settings).await + Self::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/put_document.rs b/packages/rs-sdk/src/platform/transition/put_document.rs index efceb7cadf..3ef5c5c864 100644 --- a/packages/rs-sdk/src/platform/transition/put_document.rs +++ b/packages/rs-sdk/src/platform/transition/put_document.rs @@ -13,7 +13,7 @@ use dpp::state_transition::StateTransition; #[async_trait::async_trait] /// A trait for putting a document to platform -pub trait PutDocument { +pub trait PutDocument: Waitable { /// Puts a document on platform /// setting settings to `None` sets default connection behavior async fn put_to_platform( @@ -99,6 +99,6 @@ impl PutDocument for Document { ) .await?; - ::wait_for_response(sdk, state_transition, settings).await + Self::wait_for_response(sdk, state_transition, settings).await } } diff --git a/packages/rs-sdk/src/platform/transition/top_up_identity.rs b/packages/rs-sdk/src/platform/transition/top_up_identity.rs index 38bcb351e6..10998b6ae7 100644 --- a/packages/rs-sdk/src/platform/transition/top_up_identity.rs +++ b/packages/rs-sdk/src/platform/transition/top_up_identity.rs @@ -1,5 +1,6 @@ use super::broadcast::BroadcastStateTransition; use super::put_settings::PutSettings; +use super::waitable::Waitable; use crate::{Error, Sdk}; use dpp::dashcore::PrivateKey; use dpp::identity::{Identity, PartialIdentity}; @@ -8,7 +9,7 @@ use dpp::state_transition::identity_topup_transition::methods::IdentityTopUpTran use dpp::state_transition::identity_topup_transition::IdentityTopUpTransition; #[async_trait::async_trait] -pub trait TopUpIdentity { +pub trait TopUpIdentity: Waitable { async fn top_up_identity( &self, sdk: &Sdk, diff --git a/packages/rs-sdk/src/platform/transition/transfer.rs b/packages/rs-sdk/src/platform/transition/transfer.rs index e8e19eafe8..7bd7ddd364 100644 --- a/packages/rs-sdk/src/platform/transition/transfer.rs +++ b/packages/rs-sdk/src/platform/transition/transfer.rs @@ -9,8 +9,10 @@ use dpp::identity::{Identity, IdentityPublicKey, PartialIdentity}; use dpp::state_transition::identity_credit_transfer_transition::methods::IdentityCreditTransferTransitionMethodsV0; use dpp::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition; +use super::waitable::Waitable; + #[async_trait::async_trait] -pub trait TransferToIdentity { +pub trait TransferToIdentity: Waitable { /// Function to transfer credits from an identity to another identity. Returns the final /// identity balance. /// From 9042fd732fbf77d5ee4b9da141e193b18d2da8ac Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:08:04 +0100 Subject: [PATCH 21/22] fix(sdk): build overrides default settings instead of replacing --- packages/rs-sdk/src/sdk.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 061230905c..148f8d9506 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -962,7 +962,11 @@ impl SdkBuilder { pub fn build(self) -> Result { PlatformVersion::set_current(self.version); - let dapi_client_settings = self.settings.unwrap_or(DEFAULT_REQUEST_SETTINGS); + let dapi_client_settings = match self.settings { + Some(settings) => DEFAULT_REQUEST_SETTINGS.override_by(settings), + None => DEFAULT_REQUEST_SETTINGS, + }; + let sdk= match self.addresses { // non-mock mode Some(addresses) => { From 8329002e1f682166ae5627f4554a1a96c4c8df4e Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:33:24 +0100 Subject: [PATCH 22/22] refactor(sdk): impl Waitable for Vote --- .../rs-sdk/src/platform/transition/vote.rs | 41 ++----------------- .../src/platform/transition/waitable.rs | 12 ++++++ 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/packages/rs-sdk/src/platform/transition/vote.rs b/packages/rs-sdk/src/platform/transition/vote.rs index 5666b8b42d..3734e892f2 100644 --- a/packages/rs-sdk/src/platform/transition/vote.rs +++ b/packages/rs-sdk/src/platform/transition/vote.rs @@ -1,10 +1,8 @@ -use crate::platform::block_info_from_metadata::block_info_from_metadata; use crate::platform::query::VoteQuery; use crate::platform::transition::broadcast_request::BroadcastRequestForStateTransition; use crate::platform::transition::put_settings::PutSettings; use crate::platform::Fetch; use crate::{Error, Sdk}; -use dapi_grpc::platform::VersionedGrpcResponse; use dpp::identifier::MasternodeIdentifiers; use dpp::identity::hash::IdentityPublicKeyHashMethodsV0; use dpp::identity::signer::Signer; @@ -12,16 +10,15 @@ use dpp::identity::IdentityPublicKey; use dpp::prelude::Identifier; use dpp::state_transition::masternode_vote_transition::methods::MasternodeVoteTransitionMethodsV0; use dpp::state_transition::masternode_vote_transition::MasternodeVoteTransition; -use dpp::state_transition::proof_result::StateTransitionProofResult; use dpp::voting::votes::resource_vote::accessors::v0::ResourceVoteGettersV0; use dpp::voting::votes::Vote; -use drive::drive::Drive; -use drive_proof_verifier::{error::ContextProviderError, DataContractProvider}; use rs_dapi_client::{DapiRequest, IntoInner}; +use super::waitable::Waitable; + #[async_trait::async_trait] /// A trait for putting a vote on platform -pub trait PutVote { +pub trait PutVote: Waitable { /// Puts an identity on platform async fn put_to_platform( &self, @@ -129,37 +126,7 @@ impl PutVote for Vote { } } } - - let request = masternode_vote_transition.wait_for_state_transition_result_request()?; - let response = request - .execute(sdk, settings.request_settings) - .await - .into_inner()?; - - let block_info = block_info_from_metadata(response.metadata()?)?; - let proof = response.proof_owned()?; - let context_provider = - sdk.context_provider() - .ok_or(Error::from(ContextProviderError::Config( - "Context provider not initialized".to_string(), - )))?; - - let (_, result) = Drive::verify_state_transition_was_executed_with_proof( - &masternode_vote_transition, - &block_info, - proof.grovedb_proof.as_slice(), - &context_provider.as_contract_lookup_fn(), - sdk.version(), - )?; - - //todo verify - - match result { - StateTransitionProofResult::VerifiedMasternodeVote(vote) => Ok(vote), - _ => Err(Error::DapiClientError( - "proved something that was not a vote".to_string(), - )), - } + Self::wait_for_response(sdk, masternode_vote_transition, Some(settings)).await } } diff --git a/packages/rs-sdk/src/platform/transition/waitable.rs b/packages/rs-sdk/src/platform/transition/waitable.rs index e75a62d86b..a63acb0949 100644 --- a/packages/rs-sdk/src/platform/transition/waitable.rs +++ b/packages/rs-sdk/src/platform/transition/waitable.rs @@ -10,6 +10,7 @@ use dpp::prelude::{DataContract, Identifier, Identity}; use dpp::state_transition::identity_create_transition::accessors::IdentityCreateTransitionAccessorsV0; use dpp::state_transition::StateTransition; use dpp::state_transition::StateTransitionLike; +use dpp::voting::votes::Vote; use dpp::ProtocolError; /// Waitable trait provides a wait to wait for a response of a state transition after it has been broadcast and @@ -117,3 +118,14 @@ impl Waitable for Identity { } } } + +#[async_trait::async_trait] +impl Waitable for Vote { + async fn wait_for_response( + sdk: &Sdk, + state_transition: StateTransition, + settings: Option, + ) -> Result { + state_transition.wait_for_response(sdk, settings).await + } +}