diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index 8e5a3d660b2..3c32299ba76 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -130,6 +130,7 @@ impl DapiRequestExecutor for DapiClient { // Join settings of different sources to get final version of the settings for this execution: let applied_settings = self .settings + .clone() .override_by(R::SETTINGS_OVERRIDES) .override_by(settings) .finalize(); @@ -151,6 +152,7 @@ impl DapiRequestExecutor for DapiClient { // Setup DAPI request execution routine future. It's a closure that will be called // more once to build new future on each retry. let routine = move || { + let applied_settings = applied_settings.clone(); // Try to get an address to initialize transport on: let address_list = self diff --git a/packages/rs-dapi-client/src/request_settings.rs b/packages/rs-dapi-client/src/request_settings.rs index 7c900a78290..642c011fe3f 100644 --- a/packages/rs-dapi-client/src/request_settings.rs +++ b/packages/rs-dapi-client/src/request_settings.rs @@ -1,6 +1,7 @@ //! DAPI client request settings processing. -use std::time::Duration; +use dapi_grpc::tonic::transport::Certificate; +use std::{fs::read_to_string, path::Path, time::Duration}; /// Default low-level client timeout const DEFAULT_CONNECT_TIMEOUT: Option = None; @@ -15,7 +16,7 @@ const DEFAULT_BAN_FAILED_ADDRESS: bool = true; /// 2. [crate::DapiClient] settings; /// 3. [crate::DapiRequest]-specific settings; /// 4. settings for an exact request execution call. -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] pub struct RequestSettings { /// Timeout for establishing a connection. pub connect_timeout: Option, @@ -25,6 +26,8 @@ pub struct RequestSettings { pub retries: Option, /// Ban DAPI address if node not responded or responded with error. pub ban_failed_address: Option, + /// Certificate Authority certificate to use for verifying the server's certificate. + pub ca_certificate: Option, } impl RequestSettings { @@ -36,6 +39,7 @@ impl RequestSettings { timeout: None, retries: None, ban_failed_address: None, + ca_certificate: None, } } @@ -48,6 +52,7 @@ impl RequestSettings { timeout: rhs.timeout.or(self.timeout), retries: rhs.retries.or(self.retries), ban_failed_address: rhs.ban_failed_address.or(self.ban_failed_address), + ca_certificate: rhs.ca_certificate.or(self.ca_certificate), } } @@ -60,12 +65,22 @@ impl RequestSettings { ban_failed_address: self .ban_failed_address .unwrap_or(DEFAULT_BAN_FAILED_ADDRESS), + ca_certificate: self.ca_certificate, } } + + /// Load a certificate from a file and set it as a CA certificate. + pub fn with_ca_certificate(mut self, path: impl AsRef) -> std::io::Result { + let cert_bytes = read_to_string(path)?; + let cert = Certificate::from_pem(cert_bytes); + + self.ca_certificate = Some(cert); + Ok(self) + } } /// DAPI settings ready to use. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct AppliedRequestSettings { /// Timeout for establishing a connection. pub connect_timeout: Option, @@ -75,4 +90,6 @@ pub struct AppliedRequestSettings { pub retries: usize, /// Ban DAPI address if node not responded or responded with error. pub ban_failed_address: bool, + /// Certificate Authority certificate to use for verifying the server's certificate. + pub ca_certificate: Option, } diff --git a/packages/rs-dapi-client/src/transport/grpc.rs b/packages/rs-dapi-client/src/transport/grpc.rs index 43680130e8f..acf987d70fe 100644 --- a/packages/rs-dapi-client/src/transport/grpc.rs +++ b/packages/rs-dapi-client/src/transport/grpc.rs @@ -1,17 +1,17 @@ //! Listing of gRPC requests used in DAPI. -use std::time::Duration; - use super::{CanRetry, TransportClient, TransportRequest}; use crate::connection_pool::{ConnectionPool, PoolPrefix}; use crate::{request_settings::AppliedRequestSettings, RequestSettings}; use dapi_grpc::core::v0::core_client::CoreClient; use dapi_grpc::core::v0::{self as core_proto}; use dapi_grpc::platform::v0::{self as platform_proto, platform_client::PlatformClient}; +use dapi_grpc::tonic::transport::ClientTlsConfig; use dapi_grpc::tonic::transport::Uri; use dapi_grpc::tonic::Streaming; use dapi_grpc::tonic::{transport::Channel, IntoRequest}; use futures::{future::BoxFuture, FutureExt, TryFutureExt}; +use std::time::Duration; /// Platform Client using gRPC transport. pub type PlatformGrpcClient = PlatformClient; @@ -19,12 +19,22 @@ pub type PlatformGrpcClient = PlatformClient; pub type CoreGrpcClient = CoreClient; fn create_channel(uri: Uri, settings: Option<&AppliedRequestSettings>) -> Channel { + let host = uri.host().expect("Failed to get host from URI").to_string(); + let mut builder = Channel::builder(uri); if let Some(settings) = settings { if let Some(timeout) = settings.connect_timeout { builder = builder.connect_timeout(timeout); } + if let Some(cert) = settings.ca_certificate.as_ref() { + let tls_config = ClientTlsConfig::new() + .domain_name(host) + .ca_certificate(cert.clone()); + builder = builder + .tls_config(tls_config) + .expect("Failed to set TLS config"); + } } builder.connect_lazy() @@ -186,8 +196,11 @@ impl_transport_request_grpc!( platform_proto::WaitForStateTransitionResultResponse, PlatformGrpcClient, RequestSettings { - timeout: Some(Duration::from_secs(120)), - ..RequestSettings::default() + timeout: Some(Duration::from_secs(80)), + retries: Some(0), + ca_certificate: None, + ban_failed_address: None, + connect_timeout: None, }, wait_for_state_transition_result ); @@ -382,7 +395,10 @@ impl_transport_request_grpc!( CoreGrpcClient, RequestSettings { timeout: Some(STREAMING_TIMEOUT), - ..RequestSettings::default() + ca_certificate: None, + ban_failed_address: None, + connect_timeout: None, + retries: None, }, subscribe_to_transactions_with_proofs ); diff --git a/packages/rs-dapi-client/tests/mock_dapi_client.rs b/packages/rs-dapi-client/tests/mock_dapi_client.rs index f069c4e47a3..7b9dc0b44d2 100644 --- a/packages/rs-dapi-client/tests/mock_dapi_client.rs +++ b/packages/rs-dapi-client/tests/mock_dapi_client.rs @@ -27,7 +27,10 @@ async fn test_mock_get_identity_dapi_client() { let settings = RequestSettings::default(); - let result = dapi.execute(request.clone(), settings).await.unwrap(); + let result = dapi + .execute(request.clone(), settings.clone()) + .await + .unwrap(); let result2 = request.execute(&dapi, settings).await.unwrap(); diff --git a/packages/rs-sdk/src/platform/transition/purchase_document.rs b/packages/rs-sdk/src/platform/transition/purchase_document.rs index aa58b63b324..f99df28ec58 100644 --- a/packages/rs-sdk/src/platform/transition/purchase_document.rs +++ b/packages/rs-sdk/src/platform/transition/purchase_document.rs @@ -75,7 +75,7 @@ impl PurchaseDocument for Document { purchaser_id, document_type.data_contract_id(), true, - settings, + settings.clone(), ) .await?; diff --git a/packages/rs-sdk/src/platform/transition/put_contract.rs b/packages/rs-sdk/src/platform/transition/put_contract.rs index fb7e55b5bc0..a4b0f5058dd 100644 --- a/packages/rs-sdk/src/platform/transition/put_contract.rs +++ b/packages/rs-sdk/src/platform/transition/put_contract.rs @@ -59,7 +59,7 @@ impl PutContract for DataContract { settings: Option, ) -> Result { let new_identity_nonce = sdk - .get_identity_nonce(self.owner_id(), true, settings) + .get_identity_nonce(self.owner_id(), true, settings.clone()) .await?; let key_id = identity_public_key.id(); diff --git a/packages/rs-sdk/src/platform/transition/put_document.rs b/packages/rs-sdk/src/platform/transition/put_document.rs index 7c9fecac3a7..e2a8ea60113 100644 --- a/packages/rs-sdk/src/platform/transition/put_document.rs +++ b/packages/rs-sdk/src/platform/transition/put_document.rs @@ -70,7 +70,7 @@ impl PutDocument for Document { self.owner_id(), document_type.data_contract_id(), true, - settings, + settings.clone(), ) .await?; diff --git a/packages/rs-sdk/src/platform/transition/put_settings.rs b/packages/rs-sdk/src/platform/transition/put_settings.rs index 7ddaef7a687..806b5c4433b 100644 --- a/packages/rs-sdk/src/platform/transition/put_settings.rs +++ b/packages/rs-sdk/src/platform/transition/put_settings.rs @@ -2,7 +2,7 @@ use dpp::prelude::UserFeeIncrease; use rs_dapi_client::RequestSettings; /// The options when putting something to platform -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] pub struct PutSettings { pub request_settings: RequestSettings, pub identity_nonce_stale_time_s: Option, diff --git a/packages/rs-sdk/src/platform/transition/transfer_document.rs b/packages/rs-sdk/src/platform/transition/transfer_document.rs index 140a6e31663..7a5034b3ce9 100644 --- a/packages/rs-sdk/src/platform/transition/transfer_document.rs +++ b/packages/rs-sdk/src/platform/transition/transfer_document.rs @@ -71,7 +71,7 @@ impl TransferDocument for Document { self.owner_id(), document_type.data_contract_id(), true, - settings, + settings.clone(), ) .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 93da9aaf2b7..f0224d01a05 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 @@ -71,7 +71,7 @@ impl UpdatePriceOfDocument for Document { self.owner_id(), document_type.data_contract_id(), true, - settings, + settings.clone(), ) .await?; 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 1dbff8c3ead..d46364c4638 100644 --- a/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs +++ b/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs @@ -48,7 +48,9 @@ impl WithdrawFromIdentity for Identity { signer: S, settings: Option, ) -> Result { - let new_identity_nonce = sdk.get_identity_nonce(self.id(), true, settings).await?; + let new_identity_nonce = sdk + .get_identity_nonce(self.id(), true, settings.clone()) + .await?; let state_transition = IdentityCreditWithdrawalTransition::try_from_identity( self, None, diff --git a/packages/rs-sdk/tests/.env.example b/packages/rs-sdk/tests/.env.example index 1a9222032eb..aceef63d3b4 100644 --- a/packages/rs-sdk/tests/.env.example +++ b/packages/rs-sdk/tests/.env.example @@ -3,6 +3,7 @@ DASH_SDK_PLATFORM_HOST="127.0.0.1" DASH_SDK_PLATFORM_PORT=2443 DASH_SDK_PLATFORM_SSL=false +# DASH_SDK_PLATFORM_CA_CERT_PATH=/some/path/to/ca.pem # ProTxHash of masternode that has at least 1 vote casted for DPNS name `testname` DASH_SDK_MASTERNODE_OWNER_PRO_REG_TX_HASH="6ac88f64622d9bc0cb79ad0f69657aa9488b213157d20ae0ca371fa5f04fb222" diff --git a/packages/rs-sdk/tests/fetch/config.rs b/packages/rs-sdk/tests/fetch/config.rs index a928ce317d0..dc245de50d7 100644 --- a/packages/rs-sdk/tests/fetch/config.rs +++ b/packages/rs-sdk/tests/fetch/config.rs @@ -3,6 +3,7 @@ //! This module contains [Config] struct that can be used to configure dash-platform-sdk. //! It's mainly used for testing. +use dash_sdk::RequestSettings; use dpp::platform_value::string_encoding::Encoding; use dpp::{ dashcore::{hashes::Hash, ProTxHash}, @@ -48,6 +49,10 @@ pub struct Config { #[serde(default)] pub platform_ssl: bool, + /// When platform_ssl is true, use the PEM-encoded CA certificate from provided absolute path to verify the server + #[serde(default)] + pub platform_ca_cert_path: Option, + /// Directory where all generated test vectors will be saved. /// /// See [SdkBuilder::with_dump_dir()](crate::SdkBuilder::with_dump_dir()) for more details. @@ -172,16 +177,28 @@ impl Config { panic!("cannot use namespace with root dump dir"); } + let request_settings = self + .platform_ca_cert_path + .as_ref() + .map(|cert| { + RequestSettings::default() + .with_ca_certificate(cert) + .expect("failed to load CA certificate") + }) + .unwrap_or_default(); + // offline testing takes precedence over network testing #[cfg(all(feature = "network-testing", not(feature = "offline-testing")))] let sdk = { // Dump all traffic to disk - let builder = dash_sdk::SdkBuilder::new(self.address_list()).with_core( - &self.platform_host, - self.core_port, - &self.core_user, - &self.core_password, - ); + let builder = dash_sdk::SdkBuilder::new(self.address_list()) + .with_core( + &self.platform_host, + self.core_port, + &self.core_user, + &self.core_password, + ) + .with_settings(request_settings); #[cfg(feature = "generate-test-vectors")] let builder = { @@ -209,6 +226,7 @@ impl Config { #[cfg(feature = "offline-testing")] let sdk = { let mut mock_sdk = dash_sdk::SdkBuilder::new_mock() + .with_settings(request_settings) .build() .expect("initialize api");