1use crate::error::{Error, StaleNodeError};
4use crate::internal_cache::NonceCache;
5use crate::mock::MockResponse;
6#[cfg(feature = "mocks")]
7use crate::mock::{provider::GrpcContextProvider, MockDashPlatformSdk};
8use crate::platform::transition::put_settings::PutSettings;
9use crate::platform::Identifier;
10use arc_swap::ArcSwapOption;
11use dapi_grpc::mock::Mockable;
12use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
13#[cfg(not(target_arch = "wasm32"))]
14use dapi_grpc::tonic::transport::Certificate;
15use dash_context_provider::ContextProvider;
16#[cfg(feature = "mocks")]
17use dash_context_provider::MockContextProvider;
18use dpp::bincode;
19use dpp::bincode::error::DecodeError;
20use dpp::dashcore::Network;
21use dpp::prelude::IdentityNonce;
22use dpp::version::PlatformVersion;
23use drive::grovedb::operations::proof::GroveDBProof;
24use drive_proof_verifier::FromProof;
25pub use http::Uri;
26#[cfg(feature = "mocks")]
27use rs_dapi_client::mock::MockDapiClient;
28pub use rs_dapi_client::AddressList;
29pub use rs_dapi_client::RequestSettings;
30use rs_dapi_client::{
31 transport::TransportRequest, DapiClient, DapiClientError, DapiRequestExecutor, ExecutionResult,
32};
33use std::fmt::Debug;
34#[cfg(feature = "mocks")]
35use std::num::NonZeroUsize;
36use std::path::Path;
37#[cfg(feature = "mocks")]
38use std::path::PathBuf;
39use std::sync::atomic::Ordering;
40use std::sync::{atomic, Arc};
41#[cfg(feature = "mocks")]
42use tokio::sync::{Mutex, MutexGuard};
43use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
44use zeroize::Zeroizing;
45
46pub const DEFAULT_CONTRACT_CACHE_SIZE: usize = 100;
48pub const DEFAULT_TOKEN_CONFIG_CACHE_SIZE: usize = 100;
50pub const DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE: usize = 100;
52const ADDRESS_STATE_TIME_TOLERANCE_MS: u64 = 31 * 60 * 1000;
54
55const DEFAULT_REQUEST_SETTINGS: RequestSettings = RequestSettings {
59 retries: Some(3),
60 timeout: None,
61 ban_failed_address: None,
62 connect_timeout: None,
63 max_decoding_message_size: None,
64};
65
66pub struct Sdk {
92 pub network: Network,
94 inner: SdkInstance,
95 proofs: bool,
99
100 nonce_cache: Arc<NonceCache>,
102
103 context_provider: ArcSwapOption<Box<dyn ContextProvider>>,
109
110 protocol_version: Arc<atomic::AtomicU32>,
112
113 auto_detect_protocol_version: bool,
116
117 metadata_last_seen_height: Arc<atomic::AtomicU64>,
121
122 metadata_height_tolerance: Option<u64>,
126
127 metadata_time_tolerance_ms: Option<u64>,
131
132 pub(crate) cancel_token: CancellationToken,
134
135 pub(crate) dapi_client_settings: RequestSettings,
137
138 #[cfg(feature = "mocks")]
139 dump_dir: Option<PathBuf>,
140}
141impl Clone for Sdk {
142 fn clone(&self) -> Self {
143 Self {
144 network: self.network,
145 inner: self.inner.clone(),
146 proofs: self.proofs,
147 nonce_cache: Arc::clone(&self.nonce_cache),
148 context_provider: ArcSwapOption::new(self.context_provider.load_full()),
149 cancel_token: self.cancel_token.clone(),
150 protocol_version: Arc::clone(&self.protocol_version),
151 auto_detect_protocol_version: self.auto_detect_protocol_version,
152 metadata_last_seen_height: Arc::clone(&self.metadata_last_seen_height),
153 metadata_height_tolerance: self.metadata_height_tolerance,
154 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
155 dapi_client_settings: self.dapi_client_settings,
156 #[cfg(feature = "mocks")]
157 dump_dir: self.dump_dir.clone(),
158 }
159 }
160}
161
162impl Debug for Sdk {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 match &self.inner {
165 SdkInstance::Dapi { dapi, .. } => f
166 .debug_struct("Sdk")
167 .field("dapi", dapi)
168 .field("proofs", &self.proofs)
169 .finish(),
170 #[cfg(feature = "mocks")]
171 SdkInstance::Mock { mock, .. } => f
172 .debug_struct("Sdk")
173 .field("mock", mock)
174 .field("proofs", &self.proofs)
175 .finish(),
176 }
177 }
178}
179
180#[derive(Debug, Clone)]
185enum SdkInstance {
186 Dapi {
188 dapi: DapiClient,
190 },
191 #[cfg(feature = "mocks")]
193 Mock {
194 dapi: Arc<Mutex<MockDapiClient>>,
198 mock: Arc<Mutex<MockDashPlatformSdk>>,
200 address_list: AddressList,
201 },
202}
203
204impl Sdk {
205 pub fn new_mock() -> Self {
211 SdkBuilder::default()
212 .build()
213 .expect("mock should be created")
214 }
215
216 fn freshness_criteria(&self, method_name: &str) -> (Option<u64>, Option<u64>) {
221 match method_name {
222 "get_addresses_trunk_state"
223 | "get_addresses_branch_state"
224 | "get_nullifiers_trunk_state"
225 | "get_nullifiers_branch_state" => (
226 None,
227 self.metadata_time_tolerance_ms
228 .and(Some(ADDRESS_STATE_TIME_TOLERANCE_MS)),
229 ),
230 _ => (
231 self.metadata_height_tolerance,
232 self.metadata_time_tolerance_ms,
233 ),
234 }
235 }
236
237 pub fn verify_response_metadata(
239 &self,
240 method_name: &str,
241 metadata: &ResponseMetadata,
242 ) -> Result<(), Error> {
243 let (metadata_height_tolerance, metadata_time_tolerance_ms) =
244 self.freshness_criteria(method_name);
245 if let Some(height_tolerance) = metadata_height_tolerance {
246 verify_metadata_height(
247 metadata,
248 height_tolerance,
249 Arc::clone(&(self.metadata_last_seen_height)),
250 )?;
251 };
252 if let Some(time_tolerance) = metadata_time_tolerance_ms {
253 let now = chrono::Utc::now().timestamp_millis() as u64;
254 verify_metadata_time(metadata, now, time_tolerance)?;
255 };
256
257 self.maybe_update_protocol_version(metadata.protocol_version);
258
259 Ok(())
260 }
261
262 fn maybe_update_protocol_version(&self, received_version: u32) {
268 if !self.auto_detect_protocol_version {
269 return;
270 }
271
272 if received_version == 0 {
273 return;
274 }
275
276 let current = self.protocol_version.load(Ordering::Relaxed);
277
278 if received_version <= current {
279 return;
280 }
281
282 if PlatformVersion::get(received_version).is_err() {
284 tracing::warn!(
285 received_version,
286 current_version = current,
287 "received unknown protocol version from network; keeping current"
288 );
289 return;
290 }
291
292 let previous = self
293 .protocol_version
294 .fetch_max(received_version, Ordering::Relaxed);
295 if previous < received_version {
296 tracing::info!(
297 old_version = previous,
298 new_version = received_version,
299 "protocol version updated from network metadata"
300 );
301 }
302 }
303
304 pub(crate) async fn parse_proof_with_metadata_and_proof<R, O: FromProof<R> + MockResponse>(
330 &self,
331 request: O::Request,
332 response: O::Response,
333 ) -> Result<(Option<O>, ResponseMetadata, Proof), Error>
334 where
335 O::Request: Mockable + TransportRequest,
336 {
337 let provider = self
338 .context_provider()
339 .ok_or(drive_proof_verifier::Error::ContextProviderNotSet)?;
340 let method_name = request.method_name();
341
342 let (object, metadata, proof) = match self.inner {
343 SdkInstance::Dapi { .. } => O::maybe_from_proof_with_metadata(
344 request,
345 response,
346 self.network,
347 self.version(),
348 &provider,
349 ),
350 #[cfg(feature = "mocks")]
351 SdkInstance::Mock { ref mock, .. } => {
352 let guard = mock.lock().await;
353 guard.parse_proof_with_metadata(request, response)
354 }
355 }?;
356
357 self.verify_response_metadata(method_name, &metadata)
358 .inspect_err(|err| {
359 tracing::warn!(%err,method=method_name,"received response with stale metadata; try another server");
360 })?;
361
362 Ok((object, metadata, proof))
363 }
364
365 pub fn context_provider(&self) -> Option<impl ContextProvider> {
367 let provider_guard = self.context_provider.load();
368 let provider = provider_guard.as_ref().map(Arc::clone);
369
370 provider
371 }
372
373 #[cfg(feature = "mocks")]
384 pub fn mock(&mut self) -> MutexGuard<'_, MockDashPlatformSdk> {
385 if let Sdk {
386 inner: SdkInstance::Mock { ref mock, .. },
387 ..
388 } = self
389 {
390 mock.try_lock()
391 .expect("mock sdk is in use by another thread and cannot be reconfigured")
392 } else {
393 panic!("not a mock")
394 }
395 }
396
397 pub async fn get_identity_nonce(
402 &self,
403 identity_id: Identifier,
404 bump_first: bool,
405 settings: Option<PutSettings>,
406 ) -> Result<IdentityNonce, Error> {
407 let settings = settings.unwrap_or_default();
408 let nonce = self
409 .nonce_cache
410 .get_identity_nonce(self, identity_id, bump_first, &settings)
411 .await?;
412
413 tracing::trace!(
414 identity_id = %identity_id,
415 bump_first,
416 nonce,
417 "Fetched identity nonce"
418 );
419
420 Ok(nonce)
421 }
422
423 pub async fn get_identity_contract_nonce(
428 &self,
429 identity_id: Identifier,
430 contract_id: Identifier,
431 bump_first: bool,
432 settings: Option<PutSettings>,
433 ) -> Result<IdentityNonce, Error> {
434 let settings = settings.unwrap_or_default();
435 self.nonce_cache
436 .get_identity_contract_nonce(self, identity_id, contract_id, bump_first, &settings)
437 .await
438 }
439
440 pub async fn refresh_identity_nonce(&self, identity_id: &Identifier) {
444 self.nonce_cache.refresh(identity_id).await;
445 }
446
447 pub fn version<'v>(&self) -> &'v PlatformVersion {
453 let v = self.protocol_version.load(Ordering::Relaxed);
454 PlatformVersion::get(v).unwrap_or_else(|_| PlatformVersion::latest())
455 }
456
457 pub fn protocol_version_number(&self) -> u32 {
459 self.protocol_version.load(Ordering::Relaxed)
460 }
461
462 pub fn prove(&self) -> bool {
465 self.proofs
466 }
467
468 pub fn set_context_provider<C: ContextProvider + 'static>(&self, context_provider: C) {
476 self.context_provider
477 .swap(Some(Arc::new(Box::new(context_provider))));
478 }
479
480 pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
482 self.cancel_token.cancelled()
483 }
484
485 pub fn shutdown(&self) {
487 self.cancel_token.cancel();
488 }
489
490 pub fn address_list(&self) -> &AddressList {
492 match &self.inner {
493 SdkInstance::Dapi { dapi, .. } => dapi.address_list(),
494 #[cfg(feature = "mocks")]
495 SdkInstance::Mock { address_list, .. } => address_list,
496 }
497 }
498}
499
500pub(crate) fn verify_metadata_time(
508 metadata: &ResponseMetadata,
509 now_ms: u64,
510 tolerance_ms: u64,
511) -> Result<(), Error> {
512 let metadata_time = metadata.time_ms;
513
514 if now_ms.abs_diff(metadata_time) > tolerance_ms {
516 return Err(StaleNodeError::Time {
517 expected_timestamp_ms: now_ms,
518 received_timestamp_ms: metadata_time,
519 tolerance_ms,
520 }
521 .into());
522 }
523
524 tracing::trace!(
525 expected_time = now_ms,
526 received_time = metadata_time,
527 tolerance_ms,
528 "received response with valid time"
529 );
530 Ok(())
531}
532
533fn verify_metadata_height(
536 metadata: &ResponseMetadata,
537 tolerance: u64,
538 last_seen_height: Arc<atomic::AtomicU64>,
539) -> Result<(), Error> {
540 let mut expected_height = last_seen_height.load(Ordering::Relaxed);
541 let received_height = metadata.height;
542
543 if received_height == expected_height {
545 tracing::trace!(
546 expected_height,
547 received_height,
548 tolerance,
549 "received message has the same height as previously seen"
550 );
551 return Ok(());
552 }
553
554 if expected_height > tolerance && received_height < expected_height - tolerance {
556 return Err(StaleNodeError::Height {
557 expected_height,
558 received_height,
559 tolerance_blocks: tolerance,
560 }
561 .into());
562 }
563
564 tracing::trace!(
566 expected_height = expected_height,
567 received_height = received_height,
568 tolerance,
569 "received message with new height"
570 );
571 while let Err(stored_height) = last_seen_height.compare_exchange(
572 expected_height,
573 received_height,
574 Ordering::SeqCst,
575 Ordering::Relaxed,
576 ) {
577 if stored_height >= metadata.height {
579 break;
580 }
581 expected_height = stored_height;
582 }
583
584 Ok(())
585}
586
587#[async_trait::async_trait]
588impl DapiRequestExecutor for Sdk {
589 async fn execute<R: TransportRequest>(
590 &self,
591 request: R,
592 settings: RequestSettings,
593 ) -> ExecutionResult<R::Response, DapiClientError> {
594 match self.inner {
595 SdkInstance::Dapi { ref dapi, .. } => dapi.execute(request, settings).await,
596 #[cfg(feature = "mocks")]
597 SdkInstance::Mock { ref dapi, .. } => {
598 let dapi_guard = dapi.lock().await;
599 dapi_guard.execute(request, settings).await
600 }
601 }
602 }
603}
604
605pub struct SdkBuilder {
618 addresses: Option<AddressList>,
622 settings: Option<RequestSettings>,
623
624 network: Network,
625
626 core_ip: String,
627 core_port: u16,
628 core_user: String,
629 core_password: Zeroizing<String>,
630
631 proofs: bool,
633
634 version: &'static PlatformVersion,
636
637 version_explicit: bool,
640
641 #[cfg(feature = "mocks")]
643 data_contract_cache_size: NonZeroUsize,
644
645 #[cfg(feature = "mocks")]
647 token_config_cache_size: NonZeroUsize,
648
649 #[cfg(feature = "mocks")]
651 quorum_public_keys_cache_size: NonZeroUsize,
652
653 context_provider: Option<Box<dyn ContextProvider>>,
655
656 metadata_height_tolerance: Option<u64>,
661
662 metadata_time_tolerance_ms: Option<u64>,
666
667 #[cfg(feature = "mocks")]
669 dump_dir: Option<PathBuf>,
670
671 pub(crate) cancel_token: CancellationToken,
673
674 #[cfg(not(target_arch = "wasm32"))]
676 ca_certificate: Option<Certificate>,
677}
678
679impl Default for SdkBuilder {
680 fn default() -> Self {
682 Self {
683 addresses: None,
684 settings: None,
685 network: Network::Mainnet,
686 core_ip: "".to_string(),
687 core_port: 0,
688 core_password: "".to_string().into(),
689 core_user: "".to_string(),
690
691 proofs: true,
692 metadata_height_tolerance: Some(1),
693 metadata_time_tolerance_ms: None,
694
695 #[cfg(feature = "mocks")]
696 data_contract_cache_size: NonZeroUsize::new(DEFAULT_CONTRACT_CACHE_SIZE)
697 .expect("data contract cache size must be positive"),
698
699 #[cfg(feature = "mocks")]
700 token_config_cache_size: NonZeroUsize::new(DEFAULT_TOKEN_CONFIG_CACHE_SIZE)
701 .expect("token config cache size must be positive"),
702
703 #[cfg(feature = "mocks")]
704 quorum_public_keys_cache_size: NonZeroUsize::new(DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE)
705 .expect("quorum public keys cache size must be positive"),
706
707 context_provider: None,
708
709 cancel_token: CancellationToken::new(),
710
711 version: PlatformVersion::latest(),
712 version_explicit: false,
713 #[cfg(not(target_arch = "wasm32"))]
714 ca_certificate: None,
715
716 #[cfg(feature = "mocks")]
717 dump_dir: None,
718 }
719 }
720}
721
722impl SdkBuilder {
723 pub fn with_proofs(mut self, proofs: bool) -> Self {
728 self.proofs = proofs;
729 self
730 }
731 pub fn new(addresses: AddressList) -> Self {
733 Self {
734 addresses: Some(addresses),
735 ..Default::default()
736 }
737 }
738
739 pub fn with_address_list(mut self, addresses: AddressList) -> Self {
741 self.addresses = Some(addresses);
742 self
743 }
744
745 pub fn new_mock() -> Self {
747 Self::default()
748 }
749
750 pub fn new_testnet() -> Self {
756 unimplemented!(
757 "Testnet address list not implemented yet. Use new() and provide address list."
758 )
759 }
760
761 pub fn new_mainnet() -> Self {
774 unimplemented!(
775 "Mainnet address list not implemented yet. Use new() and provide address list."
776 )
777 }
778
779 pub fn with_network(mut self, network: Network) -> Self {
783 self.network = network;
784 self
785 }
786
787 #[cfg(not(target_arch = "wasm32"))]
797 pub fn with_ca_certificate(mut self, pem_certificate: Certificate) -> Self {
798 self.ca_certificate = Some(pem_certificate);
799 self
800 }
801
802 #[cfg(not(target_arch = "wasm32"))]
807 pub fn with_ca_certificate_file(
808 self,
809 certificate_file_path: impl AsRef<Path>,
810 ) -> std::io::Result<Self> {
811 let pem = std::fs::read(certificate_file_path)?;
812 let cert = Certificate::from_pem(pem);
813
814 Ok(self.with_ca_certificate(cert))
815 }
816
817 pub fn with_settings(mut self, settings: RequestSettings) -> Self {
825 self.settings = Some(settings);
826 self
827 }
828
829 pub fn with_version(mut self, version: &'static PlatformVersion) -> Self {
835 self.version = version;
836 self.version_explicit = true;
837 self
838 }
839
840 pub fn with_context_provider<C: ContextProvider + 'static>(
847 mut self,
848 context_provider: C,
849 ) -> Self {
850 self.context_provider = Some(Box::new(context_provider));
851
852 self
853 }
854
855 pub fn with_cancellation_token(mut self, cancel_token: CancellationToken) -> Self {
859 self.cancel_token = cancel_token;
860 self
861 }
862
863 pub fn with_core(mut self, ip: &str, port: u16, user: &str, password: &str) -> Self {
871 self.core_ip = ip.to_string();
872 self.core_port = port;
873 self.core_user = user.to_string();
874 self.core_password = Zeroizing::from(password.to_string());
875
876 self
877 }
878
879 pub fn with_height_tolerance(mut self, tolerance: Option<u64>) -> Self {
891 self.metadata_height_tolerance = tolerance;
892 self
893 }
894
895 pub fn with_time_tolerance(mut self, tolerance_ms: Option<u64>) -> Self {
910 self.metadata_time_tolerance_ms = tolerance_ms;
911 self
912 }
913
914 #[cfg(feature = "mocks")]
927 pub fn with_dump_dir(mut self, dump_dir: &Path) -> Self {
928 self.dump_dir = Some(dump_dir.to_path_buf());
929 self
930 }
931
932 pub fn build(self) -> Result<Sdk, Error> {
940 let dapi_client_settings = match self.settings {
941 Some(settings) => DEFAULT_REQUEST_SETTINGS.override_by(settings),
942 None => DEFAULT_REQUEST_SETTINGS,
943 };
944
945 let sdk= match self.addresses {
946 Some(addresses) => {
948 #[allow(unused_mut)] let mut dapi = DapiClient::new(addresses, dapi_client_settings);
950 #[cfg(not(target_arch = "wasm32"))]
951 if let Some(pem) = self.ca_certificate {
952 dapi = dapi.with_ca_certificate(pem);
953 }
954
955 #[cfg(feature = "mocks")]
956 let dapi = dapi.dump_dir(self.dump_dir.clone());
957
958 #[allow(unused_mut)] let mut sdk= Sdk{
960 network: self.network,
961 dapi_client_settings,
962 inner:SdkInstance::Dapi { dapi },
963 proofs:self.proofs,
964 context_provider: ArcSwapOption::new( self.context_provider.map(Arc::new)),
965 cancel_token: self.cancel_token,
966 nonce_cache: Default::default(),
967 protocol_version: Arc::new(atomic::AtomicU32::new(
971 if self.version_explicit { self.version.protocol_version } else { 0 },
972 )),
973 auto_detect_protocol_version: !self.version_explicit,
974 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
976 metadata_height_tolerance: self.metadata_height_tolerance,
977 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
978 #[cfg(feature = "mocks")]
979 dump_dir: self.dump_dir,
980 };
981 if sdk.context_provider.load().is_none() {
983 #[cfg(feature = "mocks")]
984 if !self.core_ip.is_empty() {
985 tracing::warn!(
986 "ContextProvider not set, falling back to a mock one; use SdkBuilder::with_context_provider() to set it up");
987 let mut context_provider = GrpcContextProvider::new(None,
988 &self.core_ip, self.core_port, &self.core_user, &self.core_password,
989 self.data_contract_cache_size, self.token_config_cache_size, self.quorum_public_keys_cache_size)?;
990 #[cfg(feature = "mocks")]
991 if sdk.dump_dir.is_some() {
992 context_provider.set_dump_dir(sdk.dump_dir.clone());
993 }
994 let context_provider= Arc::new(context_provider);
997 sdk.context_provider.swap(Some(Arc::new(Box::new(context_provider.clone()))));
998 context_provider.set_sdk(Some(sdk.clone()));
999 } else{
1000 return Err(Error::Config(concat!(
1001 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1002 "or configure Core access with SdkBuilder::with_core() to use mock context provider")
1003 .to_string()));
1004 }
1005 #[cfg(not(feature = "mocks"))]
1006 return Err(Error::Config(concat!(
1007 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1008 "or enable `mocks` feature to use mock context provider")
1009 .to_string()));
1010 };
1011
1012 sdk
1013 },
1014 #[cfg(feature = "mocks")]
1015 None => {
1017 let dapi =Arc::new(Mutex::new( MockDapiClient::new()));
1018 let context_provider = self.context_provider.unwrap_or_else(||{
1020 let mut cp=MockContextProvider::new();
1021 if let Some(ref dump_dir) = self.dump_dir {
1022 cp.quorum_keys_dir(Some(dump_dir.clone()));
1023 }
1024 Box::new(cp)
1025 }
1026 );
1027 let mock_sdk = MockDashPlatformSdk::new(self.version, Arc::clone(&dapi));
1028 let mock_sdk = Arc::new(Mutex::new(mock_sdk));
1029 let sdk= Sdk {
1030 network: self.network,
1031 dapi_client_settings,
1032 inner:SdkInstance::Mock {
1033 mock:mock_sdk.clone(),
1034 dapi,
1035 address_list: AddressList::new(),
1036 },
1037 dump_dir: self.dump_dir.clone(),
1038 proofs:self.proofs,
1039 nonce_cache: Default::default(),
1040 protocol_version: Arc::new(atomic::AtomicU32::new(
1041 if self.version_explicit { self.version.protocol_version } else { 0 },
1042 )),
1043 auto_detect_protocol_version: !self.version_explicit,
1044 context_provider: ArcSwapOption::new(Some(Arc::new(context_provider))),
1045 cancel_token: self.cancel_token,
1046 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
1047 metadata_height_tolerance: self.metadata_height_tolerance,
1048 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
1049 };
1050 let mut guard = mock_sdk.try_lock().expect("mock sdk is in use by another thread and cannot be reconfigured");
1051 guard.set_sdk(sdk.clone());
1052 if let Some(ref dump_dir) = self.dump_dir {
1053 guard.load_expectations_sync(dump_dir)?;
1054 };
1055
1056 sdk
1057 },
1058 #[cfg(not(feature = "mocks"))]
1059 None => return Err(Error::Config("Mock mode is not available. Please enable `mocks` feature or provide address list.".to_string())),
1060 };
1061
1062 Ok(sdk)
1063 }
1064}
1065
1066pub fn prettify_proof(proof: &Proof) -> String {
1067 let config = bincode::config::standard()
1068 .with_big_endian()
1069 .with_no_limit();
1070 let grovedb_proof: Result<GroveDBProof, DecodeError> =
1071 bincode::decode_from_slice(&proof.grovedb_proof, config).map(|(a, _)| a);
1072
1073 let grovedb_proof_string = match grovedb_proof {
1074 Ok(proof) => format!("{}", proof),
1075 Err(_) => "Invalid GroveDBProof".to_string(),
1076 };
1077 format!(
1078 "Proof {{
1079 grovedb_proof: {},
1080 quorum_hash: 0x{},
1081 signature: 0x{},
1082 round: {},
1083 block_id_hash: 0x{},
1084 quorum_type: {},
1085 }}",
1086 grovedb_proof_string,
1087 hex::encode(&proof.quorum_hash),
1088 hex::encode(&proof.signature),
1089 proof.round,
1090 hex::encode(&proof.block_id_hash),
1091 proof.quorum_type,
1092 )
1093}
1094
1095#[cfg(test)]
1096mod test {
1097 use std::sync::Arc;
1098
1099 use dapi_grpc::platform::v0::{GetIdentityRequest, ResponseMetadata};
1100 use rs_dapi_client::transport::TransportRequest;
1101 use test_case::test_matrix;
1102
1103 use crate::SdkBuilder;
1104
1105 #[test_matrix(97..102, 100, 2, false; "valid height")]
1106 #[test_case(103, 100, 2, true; "invalid height")]
1107 fn test_verify_metadata_height(
1108 expected_height: u64,
1109 received_height: u64,
1110 tolerance: u64,
1111 expect_err: bool,
1112 ) {
1113 let metadata = ResponseMetadata {
1114 height: received_height,
1115 ..Default::default()
1116 };
1117
1118 let last_seen_height = Arc::new(std::sync::atomic::AtomicU64::new(expected_height));
1119
1120 let result =
1121 super::verify_metadata_height(&metadata, tolerance, Arc::clone(&last_seen_height));
1122
1123 assert_eq!(result.is_err(), expect_err);
1124 if result.is_ok() {
1125 assert_eq!(
1126 last_seen_height.load(std::sync::atomic::Ordering::Relaxed),
1127 received_height,
1128 "previous height should be updated"
1129 );
1130 }
1131 }
1132
1133 #[test]
1134 fn cloned_sdk_verify_metadata_height() {
1135 let sdk1 = SdkBuilder::new_mock()
1136 .build()
1137 .expect("mock Sdk should be created");
1138
1139 let metadata = ResponseMetadata {
1141 height: 1,
1142 ..Default::default()
1143 };
1144
1145 let request = GetIdentityRequest::default();
1147 sdk1.verify_response_metadata(request.method_name(), &metadata)
1148 .expect("metadata should be valid");
1149
1150 assert_eq!(
1151 sdk1.metadata_last_seen_height
1152 .load(std::sync::atomic::Ordering::Relaxed),
1153 metadata.height,
1154 "initial height"
1155 );
1156
1157 let sdk2 = sdk1.clone();
1159 let sdk3 = sdk1.clone();
1160
1161 let metadata = ResponseMetadata {
1163 height: 2,
1164 ..Default::default()
1165 };
1166 let request = GetIdentityRequest::default();
1168 sdk2.verify_response_metadata(request.method_name(), &metadata)
1169 .expect("metadata should be valid");
1170
1171 assert_eq!(
1172 sdk1.metadata_last_seen_height
1173 .load(std::sync::atomic::Ordering::Relaxed),
1174 metadata.height,
1175 "first sdk should see height from second sdk"
1176 );
1177 assert_eq!(
1178 sdk3.metadata_last_seen_height
1179 .load(std::sync::atomic::Ordering::Relaxed),
1180 metadata.height,
1181 "third sdk should see height from second sdk"
1182 );
1183
1184 let metadata = ResponseMetadata {
1186 height: 3,
1187 ..Default::default()
1188 };
1189 let request = GetIdentityRequest::default();
1191 sdk3.verify_response_metadata(request.method_name(), &metadata)
1192 .expect("metadata should be valid");
1193
1194 assert_eq!(
1195 sdk1.metadata_last_seen_height
1196 .load(std::sync::atomic::Ordering::Relaxed),
1197 metadata.height,
1198 "first sdk should see height from third sdk"
1199 );
1200
1201 assert_eq!(
1202 sdk2.metadata_last_seen_height
1203 .load(std::sync::atomic::Ordering::Relaxed),
1204 metadata.height,
1205 "second sdk should see height from third sdk"
1206 );
1207
1208 let metadata = ResponseMetadata {
1210 height: 1,
1211 ..Default::default()
1212 };
1213
1214 let request = GetIdentityRequest::default();
1215 sdk1.verify_response_metadata(request.method_name(), &metadata)
1216 .expect_err("metadata should be invalid");
1217 }
1218
1219 fn mock_sdk_with_auto_detect(starting_version: u32) -> super::Sdk {
1222 use std::sync::atomic::Ordering;
1223
1224 let sdk = SdkBuilder::new_mock()
1225 .build()
1226 .expect("mock Sdk should be created");
1227 sdk.protocol_version
1228 .store(starting_version, Ordering::Relaxed);
1229 sdk
1230 }
1231
1232 #[test]
1233 fn test_version_update_from_metadata() {
1234 let sdk = mock_sdk_with_auto_detect(1);
1235
1236 assert_eq!(sdk.protocol_version_number(), 1);
1237
1238 let metadata = ResponseMetadata {
1239 protocol_version: 2,
1240 height: 1,
1241 ..Default::default()
1242 };
1243
1244 sdk.verify_response_metadata("test", &metadata)
1245 .expect("metadata should be valid");
1246
1247 assert_eq!(sdk.protocol_version_number(), 2);
1248 assert_eq!(sdk.version().protocol_version, 2);
1249 }
1250
1251 #[test]
1252 fn test_unknown_version_ignored() {
1253 use dpp::version::PlatformVersion;
1254
1255 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1256 let original_version = sdk.protocol_version_number();
1257
1258 let metadata = ResponseMetadata {
1259 protocol_version: 999,
1260 height: 1,
1261 ..Default::default()
1262 };
1263
1264 sdk.verify_response_metadata("test", &metadata)
1265 .expect("metadata should be valid");
1266
1267 assert_eq!(sdk.protocol_version_number(), original_version);
1268 assert_eq!(sdk.version().protocol_version, original_version);
1269 }
1270
1271 #[test]
1272 fn test_version_shared_between_clones() {
1273 let sdk = mock_sdk_with_auto_detect(1);
1274
1275 let clone = sdk.clone();
1276
1277 let metadata = ResponseMetadata {
1278 protocol_version: 2,
1279 height: 1,
1280 ..Default::default()
1281 };
1282
1283 clone
1284 .verify_response_metadata("test", &metadata)
1285 .expect("metadata should be valid");
1286
1287 assert_eq!(
1288 sdk.protocol_version_number(),
1289 2,
1290 "original should see update from clone"
1291 );
1292 }
1293
1294 #[test]
1295 fn test_version_downgrade_ignored() {
1296 let sdk = mock_sdk_with_auto_detect(2);
1297
1298 assert_eq!(sdk.protocol_version_number(), 2);
1299
1300 let metadata = ResponseMetadata {
1301 protocol_version: 1,
1302 height: 1,
1303 ..Default::default()
1304 };
1305
1306 sdk.verify_response_metadata("test", &metadata)
1307 .expect("metadata should be valid");
1308
1309 assert_eq!(sdk.protocol_version_number(), 2);
1310 }
1311
1312 #[test]
1313 fn test_version_zero_ignored() {
1314 use dpp::version::PlatformVersion;
1315
1316 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1317 let original_version = sdk.protocol_version_number();
1318
1319 let metadata = ResponseMetadata {
1320 protocol_version: 0,
1321 height: 1,
1322 ..Default::default()
1323 };
1324
1325 sdk.verify_response_metadata("test", &metadata)
1326 .expect("metadata should be valid");
1327
1328 assert_eq!(sdk.protocol_version_number(), original_version);
1329 }
1330
1331 #[test]
1332 fn test_concurrent_updates_converge_to_highest() {
1333 use std::thread;
1334
1335 let sdk = mock_sdk_with_auto_detect(1);
1336
1337 assert_eq!(sdk.protocol_version_number(), 1);
1338
1339 let mut handles = Vec::new();
1340 for version in [2u32, 3, 2, 3, 2, 3] {
1342 let sdk_clone = sdk.clone();
1343 handles.push(thread::spawn(move || {
1344 let metadata = ResponseMetadata {
1345 protocol_version: version,
1346 height: 1,
1347 ..Default::default()
1348 };
1349 sdk_clone
1350 .verify_response_metadata("test", &metadata)
1351 .expect("metadata should be valid");
1352 }));
1353 }
1354
1355 for h in handles {
1356 h.join().expect("thread should not panic");
1357 }
1358
1359 assert_eq!(
1361 sdk.protocol_version_number(),
1362 3,
1363 "concurrent updates must converge to highest version"
1364 );
1365 }
1366
1367 #[test]
1371 fn test_explicit_version_disables_auto_detect() {
1372 use dpp::version::PlatformVersion;
1373
1374 let sdk = SdkBuilder::new_mock()
1376 .with_version(PlatformVersion::get(1).unwrap())
1377 .build()
1378 .expect("mock Sdk should be created");
1379
1380 assert_eq!(sdk.protocol_version_number(), 1);
1381 assert!(!sdk.auto_detect_protocol_version);
1382
1383 let metadata = ResponseMetadata {
1385 protocol_version: 2,
1386 height: 1,
1387 ..Default::default()
1388 };
1389
1390 sdk.verify_response_metadata("test", &metadata)
1391 .expect("metadata should be valid");
1392
1393 assert_eq!(
1394 sdk.protocol_version_number(),
1395 1,
1396 "pinned version must not be auto-updated"
1397 );
1398 }
1399
1400 #[test]
1401 fn test_default_sdk_detects_older_network_version() {
1402 use dpp::version::PlatformVersion;
1403
1404 let sdk = SdkBuilder::new_mock()
1406 .build()
1407 .expect("mock Sdk should be created");
1408
1409 assert_eq!(
1411 sdk.version().protocol_version,
1412 PlatformVersion::latest().protocol_version,
1413 "before first response, should fall back to latest"
1414 );
1415 assert_eq!(sdk.protocol_version_number(), 0, "should be uninitialized");
1416
1417 let metadata = ResponseMetadata {
1419 protocol_version: 1,
1420 height: 1,
1421 ..Default::default()
1422 };
1423
1424 sdk.verify_response_metadata("test", &metadata)
1425 .expect("metadata should be valid");
1426
1427 assert_eq!(
1428 sdk.protocol_version_number(),
1429 1,
1430 "default SDK must detect older network version"
1431 );
1432 assert_eq!(sdk.version().protocol_version, 1);
1433 }
1434
1435 #[test_matrix([90,91,100,109,110], 100, 10, false; "valid time")]
1436 #[test_matrix([0,89,111], 100, 10, true; "invalid time")]
1437 #[test_matrix([0,100], [0,100], 100, false; "zero time")]
1438 #[test_matrix([99,101], 100, 0, true; "zero tolerance")]
1439 fn test_verify_metadata_time(
1440 received_time: u64,
1441 now_time: u64,
1442 tolerance: u64,
1443 expect_err: bool,
1444 ) {
1445 let metadata = ResponseMetadata {
1446 time_ms: received_time,
1447 ..Default::default()
1448 };
1449
1450 let result = super::verify_metadata_time(&metadata, now_time, tolerance);
1451
1452 assert_eq!(result.is_err(), expect_err);
1453 }
1454}