From 431ec00c0749bef6f35466054dc647c94853ae5e Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:52:34 +0200 Subject: [PATCH 01/11] feat(sdk): allow setting CA cert to use when connecting to servers --- packages/rs-dapi-client/src/dapi_client.rs | 2 ++ .../rs-dapi-client/src/request_settings.rs | 23 ++++++++++++-- packages/rs-dapi-client/src/transport/grpc.rs | 26 ++++++++++++---- .../rs-dapi-client/tests/mock_dapi_client.rs | 5 +++- .../platform/transition/purchase_document.rs | 2 +- .../src/platform/transition/put_contract.rs | 2 +- .../src/platform/transition/put_document.rs | 2 +- .../src/platform/transition/put_settings.rs | 2 +- .../platform/transition/transfer_document.rs | 2 +- .../transition/update_price_of_document.rs | 2 +- .../transition/withdraw_from_identity.rs | 4 ++- packages/rs-sdk/tests/.env.example | 1 + packages/rs-sdk/tests/fetch/config.rs | 30 +++++++++++++++---- 13 files changed, 81 insertions(+), 22 deletions(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index 8e5a3d660b..3c32299ba7 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 7c900a7829..642c011fe3 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 43680130e8..acf987d70f 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 f069c4e47a..7b9dc0b44d 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 aa58b63b32..f99df28ec5 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 fb7e55b5bc..a4b0f5058d 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 7c9fecac3a..e2a8ea6011 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 7ddaef7a68..806b5c4433 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 140a6e3166..7a5034b3ce 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 93da9aaf2b..f0224d01a0 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 1dbff8c3ea..d46364c463 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 1a9222032e..aceef63d3b 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 a928ce317d..dc245de50d 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"); From bae2d8c4ca20c4513193502bd9928923ba449c66 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:37:51 +0200 Subject: [PATCH 02/11] chore: move ca cert from RequestSettings to DapiClient --- packages/rs-dapi-client/src/dapi_client.rs | 26 +++++++++++++-- .../rs-dapi-client/src/request_settings.rs | 26 ++++++--------- packages/rs-dapi-client/src/transport/grpc.rs | 9 +++-- .../rs-dapi-client/tests/mock_dapi_client.rs | 5 +-- .../platform/transition/purchase_document.rs | 2 +- .../src/platform/transition/put_contract.rs | 2 +- .../src/platform/transition/put_document.rs | 2 +- .../src/platform/transition/put_settings.rs | 2 +- .../platform/transition/transfer_document.rs | 2 +- .../transition/update_price_of_document.rs | 2 +- .../transition/withdraw_from_identity.rs | 4 +-- packages/rs-sdk/src/sdk.rs | 33 ++++++++++++++++++- packages/rs-sdk/tests/fetch/config.rs | 33 +++++++------------ 13 files changed, 89 insertions(+), 59 deletions(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index 3c32299ba7..26cb899603 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -3,6 +3,7 @@ use backon::{ExponentialBuilder, Retryable}; use dapi_grpc::mock::Mockable; use dapi_grpc::tonic::async_trait; +use dapi_grpc::tonic::transport::Certificate; use std::fmt::Debug; use std::sync::{Arc, RwLock}; use std::time::Duration; @@ -94,6 +95,8 @@ pub struct DapiClient { address_list: Arc>, settings: RequestSettings, pool: ConnectionPool, + /// Certificate Authority certificate to use for verifying the server's certificate. + pub ca_certificate: Option, #[cfg(feature = "dump")] pub(crate) dump_dir: Option, } @@ -110,8 +113,23 @@ impl DapiClient { pool: ConnectionPool::new(address_count), #[cfg(feature = "dump")] dump_dir: None, + ca_certificate: None, } } + + /// Set CA certificate to use when verifying the server's certificate. + /// + /// # Arguments + /// + /// * `pem_ca_cert` - CA certificate in PEM format. + /// + /// # Returns + /// [DapiClient] with CA certificate set. + pub fn with_ca_certificate(mut self, pem_ca_cert: &[u8]) -> Self { + self.ca_certificate = Some(Certificate::from_pem(pem_ca_cert)); + + self + } } #[async_trait] @@ -128,13 +146,17 @@ impl DapiRequestExecutor for DapiClient { ::Error: Mockable, { // Join settings of different sources to get final version of the settings for this execution: - let applied_settings = self + let mut applied_settings = self .settings - .clone() .override_by(R::SETTINGS_OVERRIDES) .override_by(settings) .finalize(); + // Setup CA certificate + if let Some(ca_certificate) = &self.ca_certificate { + applied_settings = applied_settings.with_ca_certificate(ca_certificate.clone()); + } + // Setup retry policy: let retry_settings = ExponentialBuilder::default() .with_max_times(applied_settings.retries) diff --git a/packages/rs-dapi-client/src/request_settings.rs b/packages/rs-dapi-client/src/request_settings.rs index 642c011fe3..b4cc0ee0d6 100644 --- a/packages/rs-dapi-client/src/request_settings.rs +++ b/packages/rs-dapi-client/src/request_settings.rs @@ -1,7 +1,7 @@ //! DAPI client request settings processing. use dapi_grpc::tonic::transport::Certificate; -use std::{fs::read_to_string, path::Path, time::Duration}; +use std::time::Duration; /// Default low-level client timeout const DEFAULT_CONNECT_TIMEOUT: Option = None; @@ -16,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, Default)] +#[derive(Debug, Clone, Copy, Default)] pub struct RequestSettings { /// Timeout for establishing a connection. pub connect_timeout: Option, @@ -26,8 +26,6 @@ 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 { @@ -39,7 +37,6 @@ impl RequestSettings { timeout: None, retries: None, ban_failed_address: None, - ca_certificate: None, } } @@ -52,7 +49,6 @@ 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), } } @@ -65,18 +61,9 @@ impl RequestSettings { ban_failed_address: self .ban_failed_address .unwrap_or(DEFAULT_BAN_FAILED_ADDRESS), - ca_certificate: self.ca_certificate, + ca_certificate: None, } } - - /// 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. @@ -93,3 +80,10 @@ pub struct AppliedRequestSettings { /// Certificate Authority certificate to use for verifying the server's certificate. pub ca_certificate: Option, } +impl AppliedRequestSettings { + /// Use provided CA certificate for verifying the server's certificate. + pub fn with_ca_certificate(mut self, ca_cert: Certificate) -> Self { + self.ca_certificate = Some(ca_cert); + self + } +} diff --git a/packages/rs-dapi-client/src/transport/grpc.rs b/packages/rs-dapi-client/src/transport/grpc.rs index acf987d70f..1aa802cc5c 100644 --- a/packages/rs-dapi-client/src/transport/grpc.rs +++ b/packages/rs-dapi-client/src/transport/grpc.rs @@ -6,8 +6,8 @@ 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::transport::{Certificate, ClientTlsConfig}; use dapi_grpc::tonic::Streaming; use dapi_grpc::tonic::{transport::Channel, IntoRequest}; use futures::{future::BoxFuture, FutureExt, TryFutureExt}; @@ -27,10 +27,11 @@ fn create_channel(uri: Uri, settings: Option<&AppliedRequestSettings>) -> Channe if let Some(timeout) = settings.connect_timeout { builder = builder.connect_timeout(timeout); } - if let Some(cert) = settings.ca_certificate.as_ref() { + if let Some(pem) = settings.ca_certificate.as_ref() { + let cert = Certificate::from_pem(pem); let tls_config = ClientTlsConfig::new() .domain_name(host) - .ca_certificate(cert.clone()); + .ca_certificate(cert); builder = builder .tls_config(tls_config) .expect("Failed to set TLS config"); @@ -198,7 +199,6 @@ impl_transport_request_grpc!( RequestSettings { timeout: Some(Duration::from_secs(80)), retries: Some(0), - ca_certificate: None, ban_failed_address: None, connect_timeout: None, }, @@ -395,7 +395,6 @@ impl_transport_request_grpc!( CoreGrpcClient, RequestSettings { timeout: Some(STREAMING_TIMEOUT), - ca_certificate: None, ban_failed_address: None, connect_timeout: None, retries: None, diff --git a/packages/rs-dapi-client/tests/mock_dapi_client.rs b/packages/rs-dapi-client/tests/mock_dapi_client.rs index 7b9dc0b44d..f069c4e47a 100644 --- a/packages/rs-dapi-client/tests/mock_dapi_client.rs +++ b/packages/rs-dapi-client/tests/mock_dapi_client.rs @@ -27,10 +27,7 @@ async fn test_mock_get_identity_dapi_client() { let settings = RequestSettings::default(); - let result = dapi - .execute(request.clone(), settings.clone()) - .await - .unwrap(); + let result = dapi.execute(request.clone(), settings).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 f99df28ec5..aa58b63b32 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.clone(), + 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 a4b0f5058d..fb7e55b5bc 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.clone()) + .get_identity_nonce(self.owner_id(), true, settings) .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 e2a8ea6011..7c9fecac3a 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.clone(), + settings, ) .await?; diff --git a/packages/rs-sdk/src/platform/transition/put_settings.rs b/packages/rs-sdk/src/platform/transition/put_settings.rs index 806b5c4433..7ddaef7a68 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, Default)] +#[derive(Debug, Clone, Copy, 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 7a5034b3ce..140a6e3166 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.clone(), + 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 f0224d01a0..93da9aaf2b 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.clone(), + settings, ) .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 d46364c463..1dbff8c3ea 100644 --- a/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs +++ b/packages/rs-sdk/src/platform/transition/withdraw_from_identity.rs @@ -48,9 +48,7 @@ impl WithdrawFromIdentity for Identity { signer: S, settings: Option, ) -> Result { - let new_identity_nonce = sdk - .get_identity_nonce(self.id(), true, settings.clone()) - .await?; + let new_identity_nonce = sdk.get_identity_nonce(self.id(), true, settings).await?; let state_transition = IdentityCreditWithdrawalTransition::try_from_identity( self, None, diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index 79f53edb36..b681242332 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -587,6 +587,9 @@ pub struct SdkBuilder { /// Cancellation token; once cancelled, all pending requests should be aborted. pub(crate) cancel_token: CancellationToken, + + /// CA certificate to use for TLS connections. + ca_certificate: Option>, } impl Default for SdkBuilder { @@ -616,6 +619,8 @@ impl Default for SdkBuilder { version: PlatformVersion::latest(), + ca_certificate: None, + #[cfg(feature = "mocks")] dump_dir: None, } @@ -665,6 +670,28 @@ impl SdkBuilder { self } + /// Configure CA certificate to use when verifying TLS connections. + /// + /// Used mainly for testing purposes and local networks. + /// + /// If not set, uses standard system CA certificates. + pub fn with_ca_certificate(mut self, pem_certificate: &[u8]) -> Self { + self.ca_certificate = Some(pem_certificate.to_vec()); + self + } + + /// Load CA certificate from file. + /// + /// This is a convenience method that reads the certificate from a file and sets it using + /// [SdkBuilder::with_ca_certificate()]. + pub fn with_ca_certificate_file( + self, + certificate_file_path: impl AsRef, + ) -> std::io::Result { + let pem = std::fs::read(certificate_file_path).expect("failed to read file"); + Ok(self.with_ca_certificate(&pem)) + } + /// Configure request settings. /// /// Tune request settings used to connect to the Dash Platform. @@ -757,7 +784,11 @@ impl SdkBuilder { let sdk= match self.addresses { // non-mock mode Some(addresses) => { - let dapi = DapiClient::new(addresses, self.settings); + let mut dapi = DapiClient::new(addresses, self.settings); + if let Some(pem) = self.ca_certificate { + dapi = dapi.with_ca_certificate(&pem); + } + #[cfg(feature = "mocks")] let dapi = dapi.dump_dir(self.dump_dir.clone()); diff --git a/packages/rs-sdk/tests/fetch/config.rs b/packages/rs-sdk/tests/fetch/config.rs index dc245de50d..de32dc6e16 100644 --- a/packages/rs-sdk/tests/fetch/config.rs +++ b/packages/rs-sdk/tests/fetch/config.rs @@ -3,7 +3,6 @@ //! 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}, @@ -177,29 +176,21 @@ 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, - ) - .with_settings(request_settings); - + let mut builder = dash_sdk::SdkBuilder::new(self.address_list()).with_core( + &self.platform_host, + self.core_port, + &self.core_user, + &self.core_password, + ); + if let Some(cert_file) = &self.platform_ca_cert_path { + builder = builder + .with_ca_certificate_file(cert_file) + .expect("load CA cert"); + } #[cfg(feature = "generate-test-vectors")] let builder = { // When we use namespaces, clean up the namespaced dump dir before starting @@ -226,7 +217,6 @@ 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"); @@ -253,7 +243,6 @@ impl Config { Encoding::Base58, ) .unwrap() - .into() } fn default_data_contract_id() -> Identifier { From 6450cdece91311bb58ca6e708d6d83f1d00b12b3 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:17:06 +0200 Subject: [PATCH 03/11] test(sdk): enable logs in test_data_contract_read_not_found --- packages/rs-sdk/tests/fetch/data_contract.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rs-sdk/tests/fetch/data_contract.rs b/packages/rs-sdk/tests/fetch/data_contract.rs index 14c987933b..a18946ff6f 100644 --- a/packages/rs-sdk/tests/fetch/data_contract.rs +++ b/packages/rs-sdk/tests/fetch/data_contract.rs @@ -8,6 +8,8 @@ use drive_proof_verifier::types::DataContractHistory; /// Given some dummy data contract ID, when I fetch data contract, I get None because it doesn't exist. #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn test_data_contract_read_not_found() { + super::common::setup_logs(); + pub const DATA_CONTRACT_ID_BYTES: [u8; 32] = [1; 32]; let id = Identifier::from_bytes(&DATA_CONTRACT_ID_BYTES).expect("parse identity id"); From d263efc3b3f755206b82ec24133e2eee027b2577 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:26:17 +0200 Subject: [PATCH 04/11] chore(sdk): fix package path --- packages/rs-sdk/src/sdk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index b681242332..95462361af 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -686,7 +686,7 @@ impl SdkBuilder { /// [SdkBuilder::with_ca_certificate()]. pub fn with_ca_certificate_file( self, - certificate_file_path: impl AsRef, + certificate_file_path: impl AsRef, ) -> std::io::Result { let pem = std::fs::read(certificate_file_path).expect("failed to read file"); Ok(self.with_ca_certificate(&pem)) From d15438eb34754fcbdbefe4a28df1ada4c1877fb2 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:30:13 +0100 Subject: [PATCH 05/11] fix(dapi-client): fix after build --- packages/rs-dapi-client/src/dapi_client.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index c116b1bf11..e7ba849355 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -157,6 +157,9 @@ impl DapiRequestExecutor for DapiClient { let retries_counter_arc = Arc::new(AtomicUsize::new(0)); let retries_counter_arc_ref = &retries_counter_arc; + // We need reference so that the closure is FnMut + let applied_settings = &applied_settings; + // 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 || { @@ -208,7 +211,7 @@ impl DapiRequestExecutor for DapiClient { let mut transport_client = R::Client::with_uri_and_settings( address.uri().clone(), - &applied_settings, + applied_settings, &pool, ) .map_err(|error| ExecutionError { @@ -218,7 +221,7 @@ impl DapiRequestExecutor for DapiClient { })?; let response = transport_request - .execute_transport(&mut transport_client, &applied_settings) + .execute_transport(&mut transport_client, applied_settings) .await .map_err(DapiClientError::Transport); From 6447dae2bdaf1d945c7330dbfa18fcc56443af04 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:40:11 +0100 Subject: [PATCH 06/11] chore(sdk): ca certificate passed as Certificate object to dapi-client, not bytes --- packages/rs-dapi-client/src/dapi_client.rs | 4 ++-- packages/rs-sdk/src/sdk.rs | 26 +++++++++++++++++----- packages/rs-sdk/tests/fetch/config.rs | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index e7ba849355..919e292c14 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -106,8 +106,8 @@ impl DapiClient { /// /// # Returns /// [DapiClient] with CA certificate set. - pub fn with_ca_certificate(mut self, pem_ca_cert: &[u8]) -> Self { - self.ca_certificate = Some(Certificate::from_pem(pem_ca_cert)); + pub fn with_ca_certificate(mut self, ca_cert: Certificate) -> Self { + self.ca_certificate = Some(ca_cert); self } diff --git a/packages/rs-sdk/src/sdk.rs b/packages/rs-sdk/src/sdk.rs index affaee16da..bf90e42ef6 100644 --- a/packages/rs-sdk/src/sdk.rs +++ b/packages/rs-sdk/src/sdk.rs @@ -10,6 +10,7 @@ use crate::platform::{Fetch, Identifier}; use arc_swap::{ArcSwapAny, ArcSwapOption}; use dapi_grpc::mock::Mockable; use dapi_grpc::platform::v0::{Proof, ResponseMetadata}; +use dapi_grpc::tonic::transport::Certificate; use dpp::bincode; use dpp::bincode::error::DecodeError; use dpp::dashcore::Network; @@ -750,7 +751,7 @@ pub struct SdkBuilder { pub(crate) cancel_token: CancellationToken, /// CA certificate to use for TLS connections. - ca_certificate: Option>, + ca_certificate: Option, } impl Default for SdkBuilder { @@ -838,8 +839,8 @@ impl SdkBuilder { /// Used mainly for testing purposes and local networks. /// /// If not set, uses standard system CA certificates. - pub fn with_ca_certificate(mut self, pem_certificate: &[u8]) -> Self { - self.ca_certificate = Some(pem_certificate.to_vec()); + pub fn with_ca_certificate(mut self, pem_certificate: Certificate) -> Self { + self.ca_certificate = Some(pem_certificate); self } @@ -851,8 +852,21 @@ impl SdkBuilder { self, certificate_file_path: impl AsRef, ) -> std::io::Result { - let pem = std::fs::read(certificate_file_path).expect("failed to read file"); - Ok(self.with_ca_certificate(&pem)) + let pem = std::fs::read(certificate_file_path)?; + + // parse the certificate and check if it's valid + let mut verified_pem = std::io::BufReader::new(pem.as_slice()); + rustls_pemfile::certs(&mut verified_pem) + .next() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "No valid certificates found in the file", + ) + })??; + + let cert = Certificate::from_pem(pem); + Ok(self.with_ca_certificate(cert)) } /// Configure request settings. @@ -984,7 +998,7 @@ impl SdkBuilder { Some(addresses) => { let mut dapi = DapiClient::new(addresses, self.settings); if let Some(pem) = self.ca_certificate { - dapi = dapi.with_ca_certificate(&pem); + dapi = dapi.with_ca_certificate(pem); } #[cfg(feature = "mocks")] diff --git a/packages/rs-sdk/tests/fetch/config.rs b/packages/rs-sdk/tests/fetch/config.rs index 7238c31165..4ed193c550 100644 --- a/packages/rs-sdk/tests/fetch/config.rs +++ b/packages/rs-sdk/tests/fetch/config.rs @@ -49,7 +49,7 @@ 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 + /// When platform_ssl is true, use the PEM-encoded CA certificate from provided absolute path to verify the server certificate. #[serde(default)] pub platform_ca_cert_path: Option, From c5738070f22906b8b9b197d4a3d61223816b8c07 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:45:21 +0100 Subject: [PATCH 07/11] chore: fix build --- Cargo.lock | 1 + packages/rs-sdk/Cargo.toml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6f58c0dcc6..dd71d51490 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1133,6 +1133,7 @@ dependencies = [ "http", "lru", "rs-dapi-client", + "rustls-pemfile", "sanitize-filename", "serde", "serde_json", diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index 3f5487e4a9..be0d466e97 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -19,6 +19,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ drive-proof-verifier = { path = "../rs-drive-proof-verifier" } dapi-grpc-macros = { path = "../rs-dapi-grpc-macros" } http = { version = "1.1" } +rustls-pemfile = { version = "2.0.0" } thiserror = "1.0.64" tokio = { version = "1.40", features = ["macros", "rt-multi-thread"] } tokio-util = { version = "0.7.12" } @@ -57,7 +58,7 @@ sanitize-filename = { version = "0.5.0" } test-case = { version = "3.3.1" } [features] -default = ["mocks", "offline-testing"] +default = ["mocks", "network-testing"] mocks = [ "dep:serde", From d5cbe95392e69239f5314dd5ac49499969deba89 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:55:57 +0100 Subject: [PATCH 08/11] revert: default features changed by mistake --- packages/rs-sdk/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index be0d466e97..53045ccf42 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -58,7 +58,7 @@ sanitize-filename = { version = "0.5.0" } test-case = { version = "3.3.1" } [features] -default = ["mocks", "network-testing"] +default = ["mocks", "offline-testing"] mocks = [ "dep:serde", From 34c4776998525bba94283f54af7ec81f267cec74 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:47:42 +0100 Subject: [PATCH 09/11] chore: change how we apply ca cert to applied settings --- packages/rs-dapi-client/src/request_settings.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/rs-dapi-client/src/request_settings.rs b/packages/rs-dapi-client/src/request_settings.rs index 266ce002dc..df89112322 100644 --- a/packages/rs-dapi-client/src/request_settings.rs +++ b/packages/rs-dapi-client/src/request_settings.rs @@ -86,8 +86,10 @@ pub struct AppliedRequestSettings { } impl AppliedRequestSettings { /// Use provided CA certificate for verifying the server's certificate. - pub fn with_ca_certificate(mut self, ca_cert: Certificate) -> Self { - self.ca_certificate = Some(ca_cert); + /// + /// If set to None, the system's default CA certificates will be used. + pub fn with_ca_certificate(mut self, ca_cert: Option) -> Self { + self.ca_certificate = ca_cert; self } } From 53d772e4278a52e4587423622ffad2b61f1b1e6c Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:54:24 +0100 Subject: [PATCH 10/11] chore: dapi client applied settings refactor --- packages/rs-dapi-client/src/dapi_client.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index 46212410ca..f8818db5c6 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -196,16 +196,12 @@ impl DapiRequestExecutor for DapiClient { TransportError: Mockable, { // Join settings of different sources to get final version of the settings for this execution: - let mut applied_settings = self + let applied_settings = self .settings .override_by(R::SETTINGS_OVERRIDES) .override_by(settings) - .finalize(); - - // Setup CA certificate - if let Some(ca_certificate) = &self.ca_certificate { - applied_settings = applied_settings.with_ca_certificate(ca_certificate.clone()); - } + .finalize() + .with_ca_certificate(self.ca_certificate.clone()); // Setup retry policy: let retry_settings = ConstantBuilder::default() From 26ee05324a9b22d5625155067d3e5e192b86583d Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:19:47 +0100 Subject: [PATCH 11/11] chore: rename applied_settings var as per pr comments --- packages/rs-dapi-client/src/dapi_client.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rs-dapi-client/src/dapi_client.rs b/packages/rs-dapi-client/src/dapi_client.rs index f8818db5c6..126d820e1c 100644 --- a/packages/rs-dapi-client/src/dapi_client.rs +++ b/packages/rs-dapi-client/src/dapi_client.rs @@ -218,7 +218,7 @@ impl DapiRequestExecutor for DapiClient { let retries_counter_arc_ref = &retries_counter_arc; // We need reference so that the closure is FnMut - let applied_settings = &applied_settings; + let applied_settings_ref = &applied_settings; // Setup DAPI request execution routine future. It's a closure that will be called // more once to build new future on each retry. @@ -234,7 +234,7 @@ impl DapiRequestExecutor for DapiClient { let _span = tracing::trace_span!( "execute request", address = ?address_result, - settings = ?applied_settings, + settings = ?applied_settings_ref, method = request.method_name(), ) .entered(); @@ -264,7 +264,7 @@ impl DapiRequestExecutor for DapiClient { let mut transport_client = R::Client::with_uri_and_settings( address.uri().clone(), - applied_settings, + applied_settings_ref, &pool, ) .map_err(|error| ExecutionError { @@ -274,7 +274,7 @@ impl DapiRequestExecutor for DapiClient { })?; let result = transport_request - .execute_transport(&mut transport_client, applied_settings) + .execute_transport(&mut transport_client, applied_settings_ref) .await .map_err(DapiClientError::Transport); @@ -303,7 +303,7 @@ impl DapiRequestExecutor for DapiClient { update_address_ban_status::( &self.address_list, &execution_result, - applied_settings, + applied_settings_ref, ); execution_result