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::Address;
29pub use rs_dapi_client::AddressList;
30pub use rs_dapi_client::RequestSettings;
31use rs_dapi_client::{
32 transport::TransportRequest, DapiClient, DapiClientError, DapiRequestExecutor, ExecutionResult,
33};
34use std::fmt::Debug;
35#[cfg(feature = "mocks")]
36use std::num::NonZeroUsize;
37use std::path::Path;
38#[cfg(feature = "mocks")]
39use std::path::PathBuf;
40use std::sync::atomic::Ordering;
41use std::sync::{atomic, Arc};
42#[cfg(feature = "mocks")]
43use tokio::sync::{Mutex, MutexGuard};
44use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
45use zeroize::Zeroizing;
46
47pub const DEFAULT_CONTRACT_CACHE_SIZE: usize = 100;
49pub const DEFAULT_TOKEN_CONFIG_CACHE_SIZE: usize = 100;
51pub const DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE: usize = 100;
53const ADDRESS_STATE_TIME_TOLERANCE_MS: u64 = 31 * 60 * 1000;
55
56const DEFAULT_REQUEST_SETTINGS: RequestSettings = RequestSettings {
60 retries: Some(3),
61 timeout: None,
62 ban_failed_address: None,
63 connect_timeout: None,
64 max_decoding_message_size: None,
65};
66
67fn default_address_list_for_network(network: Network) -> AddressList {
84 if !matches!(network, Network::Mainnet | Network::Testnet) {
85 panic!("default address list is only available for mainnet and testnet");
86 }
87 let mut list = AddressList::new();
88 for seed in dash_network_seeds::evo_seeds(network) {
89 let Some(port) = seed.platform_http_port else {
90 continue;
91 };
92 let url = format!("https://{}:{}", seed.address.ip(), port);
93 if let Ok(uri) = url.parse::<Uri>() {
94 if let Ok(address) = Address::try_from(uri) {
95 list.add(address);
96 }
97 }
98 }
99 list
100}
101
102pub struct Sdk {
128 pub network: Network,
130 inner: SdkInstance,
131 proofs: bool,
135
136 nonce_cache: Arc<NonceCache>,
138
139 context_provider: ArcSwapOption<Box<dyn ContextProvider>>,
145
146 protocol_version: Arc<atomic::AtomicU32>,
148
149 auto_detect_protocol_version: bool,
152
153 metadata_last_seen_height: Arc<atomic::AtomicU64>,
157
158 metadata_height_tolerance: Option<u64>,
162
163 metadata_time_tolerance_ms: Option<u64>,
167
168 pub(crate) cancel_token: CancellationToken,
170
171 pub(crate) dapi_client_settings: RequestSettings,
173
174 #[cfg(feature = "mocks")]
175 dump_dir: Option<PathBuf>,
176}
177impl Clone for Sdk {
178 fn clone(&self) -> Self {
179 Self {
180 network: self.network,
181 inner: self.inner.clone(),
182 proofs: self.proofs,
183 nonce_cache: Arc::clone(&self.nonce_cache),
184 context_provider: ArcSwapOption::new(self.context_provider.load_full()),
185 cancel_token: self.cancel_token.clone(),
186 protocol_version: Arc::clone(&self.protocol_version),
187 auto_detect_protocol_version: self.auto_detect_protocol_version,
188 metadata_last_seen_height: Arc::clone(&self.metadata_last_seen_height),
189 metadata_height_tolerance: self.metadata_height_tolerance,
190 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
191 dapi_client_settings: self.dapi_client_settings,
192 #[cfg(feature = "mocks")]
193 dump_dir: self.dump_dir.clone(),
194 }
195 }
196}
197
198impl Debug for Sdk {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 match &self.inner {
201 SdkInstance::Dapi { dapi, .. } => f
202 .debug_struct("Sdk")
203 .field("dapi", dapi)
204 .field("proofs", &self.proofs)
205 .finish(),
206 #[cfg(feature = "mocks")]
207 SdkInstance::Mock { mock, .. } => f
208 .debug_struct("Sdk")
209 .field("mock", mock)
210 .field("proofs", &self.proofs)
211 .finish(),
212 }
213 }
214}
215
216#[derive(Debug, Clone)]
221enum SdkInstance {
222 Dapi {
224 dapi: DapiClient,
226 },
227 #[cfg(feature = "mocks")]
229 Mock {
230 dapi: Arc<Mutex<MockDapiClient>>,
234 mock: Arc<Mutex<MockDashPlatformSdk>>,
236 address_list: AddressList,
237 },
238}
239
240impl Sdk {
241 pub fn new_mock() -> Self {
247 SdkBuilder::default()
248 .build()
249 .expect("mock should be created")
250 }
251
252 fn freshness_criteria(&self, method_name: &str) -> (Option<u64>, Option<u64>) {
257 match method_name {
258 "get_addresses_trunk_state"
259 | "get_addresses_branch_state"
260 | "get_nullifiers_trunk_state"
261 | "get_nullifiers_branch_state" => (
262 None,
263 self.metadata_time_tolerance_ms
264 .and(Some(ADDRESS_STATE_TIME_TOLERANCE_MS)),
265 ),
266 _ => (
267 self.metadata_height_tolerance,
268 self.metadata_time_tolerance_ms,
269 ),
270 }
271 }
272
273 pub fn verify_response_metadata(
275 &self,
276 method_name: &str,
277 metadata: &ResponseMetadata,
278 ) -> Result<(), Error> {
279 let (metadata_height_tolerance, metadata_time_tolerance_ms) =
280 self.freshness_criteria(method_name);
281 if let Some(height_tolerance) = metadata_height_tolerance {
282 verify_metadata_height(
283 metadata,
284 height_tolerance,
285 Arc::clone(&(self.metadata_last_seen_height)),
286 )?;
287 };
288 if let Some(time_tolerance) = metadata_time_tolerance_ms {
289 let now = chrono::Utc::now().timestamp_millis() as u64;
290 verify_metadata_time(metadata, now, time_tolerance)?;
291 };
292
293 self.maybe_update_protocol_version(metadata.protocol_version);
294
295 Ok(())
296 }
297
298 fn maybe_update_protocol_version(&self, received_version: u32) {
304 if !self.auto_detect_protocol_version {
305 return;
306 }
307
308 if received_version == 0 {
309 return;
310 }
311
312 let current = self.protocol_version.load(Ordering::Relaxed);
313
314 if received_version <= current {
315 return;
316 }
317
318 if PlatformVersion::get(received_version).is_err() {
320 tracing::warn!(
321 received_version,
322 current_version = current,
323 "received unknown protocol version from network; keeping current"
324 );
325 return;
326 }
327
328 let previous = self
329 .protocol_version
330 .fetch_max(received_version, Ordering::Relaxed);
331 if previous < received_version {
332 tracing::info!(
333 target: "dash_sdk::protocol_version",
334 from = previous,
335 to = received_version,
336 "ratcheting protocol version upward"
337 );
338 }
339 }
340
341 pub(crate) async fn parse_proof_with_metadata_and_proof<R, O: FromProof<R> + MockResponse>(
367 &self,
368 request: O::Request,
369 response: O::Response,
370 method_name: &'static str,
371 ) -> Result<(Option<O>, ResponseMetadata, Proof), Error>
372 where
373 O::Request: Mockable,
374 {
375 let provider = self
376 .context_provider()
377 .ok_or(drive_proof_verifier::Error::ContextProviderNotSet)?;
378
379 let (object, metadata, proof) = match self.inner {
380 SdkInstance::Dapi { .. } => O::maybe_from_proof_with_metadata(
381 request,
382 response,
383 self.network,
384 self.version(),
385 &provider,
386 ),
387 #[cfg(feature = "mocks")]
388 SdkInstance::Mock { ref mock, .. } => {
389 let guard = mock.lock().await;
390 guard.parse_proof_with_metadata(request, response)
391 }
392 }?;
393
394 self.verify_response_metadata(method_name, &metadata)
395 .inspect_err(|err| {
396 tracing::warn!(%err,method=method_name,"received response with stale metadata; try another server");
397 })?;
398
399 Ok((object, metadata, proof))
400 }
401
402 pub fn context_provider(&self) -> Option<impl ContextProvider> {
404 let provider_guard = self.context_provider.load();
405 let provider = provider_guard.as_ref().map(Arc::clone);
406
407 provider
408 }
409
410 #[cfg(feature = "mocks")]
421 pub fn mock(&mut self) -> MutexGuard<'_, MockDashPlatformSdk> {
422 if let Sdk {
423 inner: SdkInstance::Mock { ref mock, .. },
424 ..
425 } = self
426 {
427 mock.try_lock()
428 .expect("mock sdk is in use by another thread and cannot be reconfigured")
429 } else {
430 panic!("not a mock")
431 }
432 }
433
434 pub async fn get_identity_nonce(
439 &self,
440 identity_id: Identifier,
441 bump_first: bool,
442 settings: Option<PutSettings>,
443 ) -> Result<IdentityNonce, Error> {
444 let settings = settings.unwrap_or_default();
445 let nonce = self
446 .nonce_cache
447 .get_identity_nonce(self, identity_id, bump_first, &settings)
448 .await?;
449
450 tracing::trace!(
451 identity_id = %identity_id,
452 bump_first,
453 nonce,
454 "Fetched identity nonce"
455 );
456
457 Ok(nonce)
458 }
459
460 pub async fn get_identity_contract_nonce(
465 &self,
466 identity_id: Identifier,
467 contract_id: Identifier,
468 bump_first: bool,
469 settings: Option<PutSettings>,
470 ) -> Result<IdentityNonce, Error> {
471 let settings = settings.unwrap_or_default();
472 self.nonce_cache
473 .get_identity_contract_nonce(self, identity_id, contract_id, bump_first, &settings)
474 .await
475 }
476
477 pub async fn refresh_identity_nonce(&self, identity_id: &Identifier) {
481 self.nonce_cache.refresh(identity_id).await;
482 }
483
484 pub fn version<'v>(&self) -> &'v PlatformVersion {
490 let v = self.protocol_version.load(Ordering::Relaxed);
491 PlatformVersion::get(v).unwrap_or_else(|_| PlatformVersion::latest())
492 }
493
494 pub fn protocol_version_number(&self) -> u32 {
496 self.protocol_version.load(Ordering::Relaxed)
497 }
498
499 pub fn prove(&self) -> bool {
502 self.proofs
503 }
504
505 pub fn query_settings(&self) -> crate::platform::QuerySettings<'_> {
512 crate::platform::QuerySettings {
513 request_settings: &self.dapi_client_settings,
514 protocol_version: self.version(),
515 prove: self.prove(),
516 }
517 }
518
519 pub fn set_context_provider<C: ContextProvider + 'static>(&self, context_provider: C) {
527 self.context_provider
528 .swap(Some(Arc::new(Box::new(context_provider))));
529 }
530
531 pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
533 self.cancel_token.cancelled()
534 }
535
536 pub fn shutdown(&self) {
538 self.cancel_token.cancel();
539 }
540
541 pub fn address_list(&self) -> &AddressList {
543 match &self.inner {
544 SdkInstance::Dapi { dapi, .. } => dapi.address_list(),
545 #[cfg(feature = "mocks")]
546 SdkInstance::Mock { address_list, .. } => address_list,
547 }
548 }
549}
550
551pub(crate) fn verify_metadata_time(
559 metadata: &ResponseMetadata,
560 now_ms: u64,
561 tolerance_ms: u64,
562) -> Result<(), Error> {
563 let metadata_time = metadata.time_ms;
564
565 if now_ms.abs_diff(metadata_time) > tolerance_ms {
567 return Err(StaleNodeError::Time {
568 expected_timestamp_ms: now_ms,
569 received_timestamp_ms: metadata_time,
570 tolerance_ms,
571 }
572 .into());
573 }
574
575 tracing::trace!(
576 expected_time = now_ms,
577 received_time = metadata_time,
578 tolerance_ms,
579 "received response with valid time"
580 );
581 Ok(())
582}
583
584fn verify_metadata_height(
587 metadata: &ResponseMetadata,
588 tolerance: u64,
589 last_seen_height: Arc<atomic::AtomicU64>,
590) -> Result<(), Error> {
591 let mut expected_height = last_seen_height.load(Ordering::Relaxed);
592 let received_height = metadata.height;
593
594 if received_height == expected_height {
596 tracing::trace!(
597 expected_height,
598 received_height,
599 tolerance,
600 "received message has the same height as previously seen"
601 );
602 return Ok(());
603 }
604
605 if expected_height > tolerance && received_height < expected_height - tolerance {
607 return Err(StaleNodeError::Height {
608 expected_height,
609 received_height,
610 tolerance_blocks: tolerance,
611 }
612 .into());
613 }
614
615 tracing::trace!(
617 expected_height = expected_height,
618 received_height = received_height,
619 tolerance,
620 "received message with new height"
621 );
622 while let Err(stored_height) = last_seen_height.compare_exchange(
623 expected_height,
624 received_height,
625 Ordering::SeqCst,
626 Ordering::Relaxed,
627 ) {
628 if stored_height >= metadata.height {
630 break;
631 }
632 expected_height = stored_height;
633 }
634
635 Ok(())
636}
637
638#[async_trait::async_trait]
639impl DapiRequestExecutor for Sdk {
640 async fn execute<R: TransportRequest>(
641 &self,
642 request: R,
643 settings: RequestSettings,
644 ) -> ExecutionResult<R::Response, DapiClientError> {
645 match self.inner {
646 SdkInstance::Dapi { ref dapi, .. } => dapi.execute(request, settings).await,
647 #[cfg(feature = "mocks")]
648 SdkInstance::Mock { ref dapi, .. } => {
649 let dapi_guard = dapi.lock().await;
650 dapi_guard.execute(request, settings).await
651 }
652 }
653 }
654}
655
656pub struct SdkBuilder {
669 addresses: Option<AddressList>,
673 settings: Option<RequestSettings>,
674
675 network: Network,
676
677 core_ip: String,
678 core_port: u16,
679 core_user: String,
680 core_password: Zeroizing<String>,
681
682 proofs: bool,
684
685 version: &'static PlatformVersion,
687
688 version_explicit: bool,
691
692 #[cfg(feature = "mocks")]
694 data_contract_cache_size: NonZeroUsize,
695
696 #[cfg(feature = "mocks")]
698 token_config_cache_size: NonZeroUsize,
699
700 #[cfg(feature = "mocks")]
702 quorum_public_keys_cache_size: NonZeroUsize,
703
704 context_provider: Option<Box<dyn ContextProvider>>,
706
707 metadata_height_tolerance: Option<u64>,
712
713 metadata_time_tolerance_ms: Option<u64>,
717
718 #[cfg(feature = "mocks")]
720 dump_dir: Option<PathBuf>,
721
722 pub(crate) cancel_token: CancellationToken,
724
725 #[cfg(not(target_arch = "wasm32"))]
727 ca_certificate: Option<Certificate>,
728}
729
730impl Default for SdkBuilder {
731 fn default() -> Self {
733 Self {
734 addresses: None,
735 settings: None,
736 network: Network::Mainnet,
737 core_ip: "".to_string(),
738 core_port: 0,
739 core_password: "".to_string().into(),
740 core_user: "".to_string(),
741
742 proofs: true,
743 metadata_height_tolerance: Some(1),
744 metadata_time_tolerance_ms: None,
745
746 #[cfg(feature = "mocks")]
747 data_contract_cache_size: NonZeroUsize::new(DEFAULT_CONTRACT_CACHE_SIZE)
748 .expect("data contract cache size must be positive"),
749
750 #[cfg(feature = "mocks")]
751 token_config_cache_size: NonZeroUsize::new(DEFAULT_TOKEN_CONFIG_CACHE_SIZE)
752 .expect("token config cache size must be positive"),
753
754 #[cfg(feature = "mocks")]
755 quorum_public_keys_cache_size: NonZeroUsize::new(DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE)
756 .expect("quorum public keys cache size must be positive"),
757
758 context_provider: None,
759
760 cancel_token: CancellationToken::new(),
761
762 version: PlatformVersion::latest(),
763 version_explicit: false,
764 #[cfg(not(target_arch = "wasm32"))]
765 ca_certificate: None,
766
767 #[cfg(feature = "mocks")]
768 dump_dir: None,
769 }
770 }
771}
772
773impl SdkBuilder {
774 pub fn with_proofs(mut self, proofs: bool) -> Self {
779 self.proofs = proofs;
780 self
781 }
782 pub fn new(addresses: AddressList) -> Self {
784 Self {
785 addresses: Some(addresses),
786 ..Default::default()
787 }
788 }
789
790 pub fn with_address_list(mut self, addresses: AddressList) -> Self {
792 self.addresses = Some(addresses);
793 self
794 }
795
796 pub fn new_mock() -> Self {
798 Self::default()
799 }
800
801 pub fn new_testnet() -> Self {
807 let address_list = default_address_list_for_network(Network::Testnet);
808
809 Self::new(address_list).with_network(Network::Testnet)
810 }
811
812 pub fn new_mainnet() -> Self {
825 let address_list = default_address_list_for_network(Network::Mainnet);
826
827 Self::new(address_list).with_network(Network::Mainnet)
828 }
829
830 pub fn with_network(mut self, network: Network) -> Self {
834 self.network = network;
835 self
836 }
837
838 #[cfg(not(target_arch = "wasm32"))]
848 pub fn with_ca_certificate(mut self, pem_certificate: Certificate) -> Self {
849 self.ca_certificate = Some(pem_certificate);
850 self
851 }
852
853 #[cfg(not(target_arch = "wasm32"))]
858 pub fn with_ca_certificate_file(
859 self,
860 certificate_file_path: impl AsRef<Path>,
861 ) -> std::io::Result<Self> {
862 let pem = std::fs::read(certificate_file_path)?;
863 let cert = Certificate::from_pem(pem);
864
865 Ok(self.with_ca_certificate(cert))
866 }
867
868 pub fn with_settings(mut self, settings: RequestSettings) -> Self {
876 self.settings = Some(settings);
877 self
878 }
879
880 pub fn with_version(mut self, version: &'static PlatformVersion) -> Self {
886 self.version = version;
887 self.version_explicit = true;
888 self
889 }
890
891 pub fn with_initial_version(mut self, version: &'static PlatformVersion) -> Self {
917 self.version = version;
918 self.version_explicit = false;
919 self
920 }
921
922 pub fn with_context_provider<C: ContextProvider + 'static>(
929 mut self,
930 context_provider: C,
931 ) -> Self {
932 self.context_provider = Some(Box::new(context_provider));
933
934 self
935 }
936
937 pub fn with_cancellation_token(mut self, cancel_token: CancellationToken) -> Self {
941 self.cancel_token = cancel_token;
942 self
943 }
944
945 pub fn with_core(mut self, ip: &str, port: u16, user: &str, password: &str) -> Self {
953 self.core_ip = ip.to_string();
954 self.core_port = port;
955 self.core_user = user.to_string();
956 self.core_password = Zeroizing::from(password.to_string());
957
958 self
959 }
960
961 pub fn with_height_tolerance(mut self, tolerance: Option<u64>) -> Self {
973 self.metadata_height_tolerance = tolerance;
974 self
975 }
976
977 pub fn with_time_tolerance(mut self, tolerance_ms: Option<u64>) -> Self {
992 self.metadata_time_tolerance_ms = tolerance_ms;
993 self
994 }
995
996 #[cfg(feature = "mocks")]
1009 pub fn with_dump_dir(mut self, dump_dir: &Path) -> Self {
1010 self.dump_dir = Some(dump_dir.to_path_buf());
1011 self
1012 }
1013
1014 pub fn build(self) -> Result<Sdk, Error> {
1022 let dapi_client_settings = match self.settings {
1023 Some(settings) => DEFAULT_REQUEST_SETTINGS.override_by(settings),
1024 None => DEFAULT_REQUEST_SETTINGS,
1025 };
1026
1027 let sdk= match self.addresses {
1028 Some(addresses) => {
1030 #[allow(unused_mut)] let mut dapi = DapiClient::new(addresses, dapi_client_settings);
1032 #[cfg(not(target_arch = "wasm32"))]
1033 if let Some(pem) = self.ca_certificate {
1034 dapi = dapi.with_ca_certificate(pem);
1035 }
1036
1037 #[cfg(feature = "mocks")]
1038 let dapi = dapi.dump_dir(self.dump_dir.clone());
1039
1040 #[allow(unused_mut)] let mut sdk= Sdk{
1042 network: self.network,
1043 dapi_client_settings,
1044 inner:SdkInstance::Dapi { dapi },
1045 proofs:self.proofs,
1046 context_provider: ArcSwapOption::new( self.context_provider.map(Arc::new)),
1047 cancel_token: self.cancel_token,
1048 nonce_cache: Default::default(),
1049 protocol_version: Arc::new(atomic::AtomicU32::new(
1052 self.version.protocol_version,
1053 )),
1054 auto_detect_protocol_version: !self.version_explicit,
1055 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
1057 metadata_height_tolerance: self.metadata_height_tolerance,
1058 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
1059 #[cfg(feature = "mocks")]
1060 dump_dir: self.dump_dir,
1061 };
1062 if sdk.context_provider.load().is_none() {
1064 #[cfg(feature = "mocks")]
1065 if !self.core_ip.is_empty() {
1066 tracing::warn!(
1067 "ContextProvider not set, falling back to a mock one; use SdkBuilder::with_context_provider() to set it up");
1068 let mut context_provider = GrpcContextProvider::new(None,
1069 &self.core_ip, self.core_port, &self.core_user, &self.core_password,
1070 self.data_contract_cache_size, self.token_config_cache_size, self.quorum_public_keys_cache_size)?;
1071 #[cfg(feature = "mocks")]
1072 if sdk.dump_dir.is_some() {
1073 context_provider.set_dump_dir(sdk.dump_dir.clone());
1074 }
1075 let context_provider= Arc::new(context_provider);
1078 sdk.context_provider.swap(Some(Arc::new(Box::new(context_provider.clone()))));
1079 context_provider.set_sdk(Some(sdk.clone()));
1080 } else{
1081 return Err(Error::Config(concat!(
1082 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1083 "or configure Core access with SdkBuilder::with_core() to use mock context provider")
1084 .to_string()));
1085 }
1086 #[cfg(not(feature = "mocks"))]
1087 return Err(Error::Config(concat!(
1088 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1089 "or enable `mocks` feature to use mock context provider")
1090 .to_string()));
1091 };
1092
1093 sdk
1094 },
1095 #[cfg(feature = "mocks")]
1096 None => {
1098 let dapi =Arc::new(Mutex::new( MockDapiClient::new()));
1099 let context_provider = self.context_provider.unwrap_or_else(||{
1101 let mut cp=MockContextProvider::new();
1102 if let Some(ref dump_dir) = self.dump_dir {
1103 cp.quorum_keys_dir(Some(dump_dir.clone()));
1104 }
1105 Box::new(cp)
1106 }
1107 );
1108 let mock_sdk = MockDashPlatformSdk::new(Arc::clone(&dapi));
1109 let mock_sdk = Arc::new(Mutex::new(mock_sdk));
1110 let sdk= Sdk {
1111 network: self.network,
1112 dapi_client_settings,
1113 inner:SdkInstance::Mock {
1114 mock:mock_sdk.clone(),
1115 dapi,
1116 address_list: AddressList::new(),
1117 },
1118 dump_dir: self.dump_dir.clone(),
1119 proofs:self.proofs,
1120 nonce_cache: Default::default(),
1121 protocol_version: Arc::new(atomic::AtomicU32::new(
1122 self.version.protocol_version,
1123 )),
1124 auto_detect_protocol_version: !self.version_explicit,
1125 context_provider: ArcSwapOption::new(Some(Arc::new(context_provider))),
1126 cancel_token: self.cancel_token,
1127 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
1128 metadata_height_tolerance: self.metadata_height_tolerance,
1129 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
1130 };
1131 let mut guard = mock_sdk.try_lock().expect("mock sdk is in use by another thread and cannot be reconfigured");
1132 guard.set_sdk(sdk.clone());
1133 if let Some(ref dump_dir) = self.dump_dir {
1134 guard.load_expectations_sync(dump_dir)?;
1135 };
1136
1137 sdk
1138 },
1139 #[cfg(not(feature = "mocks"))]
1140 None => return Err(Error::Config("Mock mode is not available. Please enable `mocks` feature or provide address list.".to_string())),
1141 };
1142
1143 Ok(sdk)
1144 }
1145}
1146
1147pub fn prettify_proof(proof: &Proof) -> String {
1148 let config = bincode::config::standard()
1149 .with_big_endian()
1150 .with_no_limit();
1151 let grovedb_proof: Result<GroveDBProof, DecodeError> =
1152 bincode::decode_from_slice(&proof.grovedb_proof, config).map(|(a, _)| a);
1153
1154 let grovedb_proof_string = match grovedb_proof {
1155 Ok(proof) => format!("{}", proof),
1156 Err(_) => "Invalid GroveDBProof".to_string(),
1157 };
1158 format!(
1159 "Proof {{
1160 grovedb_proof: {},
1161 quorum_hash: 0x{},
1162 signature: 0x{},
1163 round: {},
1164 block_id_hash: 0x{},
1165 quorum_type: {},
1166 }}",
1167 grovedb_proof_string,
1168 hex::encode(&proof.quorum_hash),
1169 hex::encode(&proof.signature),
1170 proof.round,
1171 hex::encode(&proof.block_id_hash),
1172 proof.quorum_type,
1173 )
1174}
1175
1176#[cfg(test)]
1177mod test {
1178 use std::sync::Arc;
1179
1180 use dapi_grpc::platform::v0::{GetIdentityRequest, ResponseMetadata};
1181 use rs_dapi_client::transport::TransportRequest;
1182 use test_case::test_matrix;
1183
1184 use crate::SdkBuilder;
1185
1186 use super::Network;
1187
1188 const MAINNET_PLATFORM_HTTP_PORT: u16 = 443;
1190 const TESTNET_PLATFORM_HTTP_PORT: u16 = 1443;
1192
1193 #[test]
1194 fn new_testnet_sources_bootstrap_from_seeds() {
1195 let builder = SdkBuilder::new_testnet();
1196 let address_list = builder
1197 .addresses
1198 .as_ref()
1199 .expect("testnet builder should configure default addresses");
1200
1201 assert_eq!(builder.network, Network::Testnet);
1202 assert!(
1203 !address_list.is_empty(),
1204 "testnet must have at least one bootstrap address"
1205 );
1206 for address in address_list.get_live_addresses() {
1207 assert_eq!(
1208 address.uri().port_u16(),
1209 Some(TESTNET_PLATFORM_HTTP_PORT),
1210 "testnet bootstrap address must use the platform HTTP port",
1211 );
1212 }
1213 }
1214
1215 #[test]
1216 fn new_mainnet_sources_bootstrap_from_seeds() {
1217 let builder = SdkBuilder::new_mainnet();
1218 let address_list = builder
1219 .addresses
1220 .as_ref()
1221 .expect("mainnet builder should configure default addresses");
1222
1223 assert_eq!(builder.network, Network::Mainnet);
1224 assert!(
1225 !address_list.is_empty(),
1226 "mainnet must have at least one bootstrap address"
1227 );
1228 for address in address_list.get_live_addresses() {
1229 assert_eq!(
1230 address.uri().port_u16(),
1231 Some(MAINNET_PLATFORM_HTTP_PORT),
1232 "mainnet bootstrap address must use the platform HTTP port",
1233 );
1234 }
1235 }
1236
1237 #[test]
1241 fn bootstrap_counts_reasonable() {
1242 let mainnet = SdkBuilder::new_mainnet()
1243 .addresses
1244 .expect("mainnet builder should configure default addresses");
1245 let testnet = SdkBuilder::new_testnet()
1246 .addresses
1247 .expect("testnet builder should configure default addresses");
1248 assert!(
1249 mainnet.len() >= 10,
1250 "expected >=10 mainnet bootstrap addresses, got {}",
1251 mainnet.len()
1252 );
1253 assert!(
1254 testnet.len() >= 10,
1255 "expected >=10 testnet bootstrap addresses, got {}",
1256 testnet.len()
1257 );
1258 }
1259
1260 #[test_matrix(97..102, 100, 2, false; "valid height")]
1261 #[test_case(103, 100, 2, true; "invalid height")]
1262 fn test_verify_metadata_height(
1263 expected_height: u64,
1264 received_height: u64,
1265 tolerance: u64,
1266 expect_err: bool,
1267 ) {
1268 let metadata = ResponseMetadata {
1269 height: received_height,
1270 ..Default::default()
1271 };
1272
1273 let last_seen_height = Arc::new(std::sync::atomic::AtomicU64::new(expected_height));
1274
1275 let result =
1276 super::verify_metadata_height(&metadata, tolerance, Arc::clone(&last_seen_height));
1277
1278 assert_eq!(result.is_err(), expect_err);
1279 if result.is_ok() {
1280 assert_eq!(
1281 last_seen_height.load(std::sync::atomic::Ordering::Relaxed),
1282 received_height,
1283 "previous height should be updated"
1284 );
1285 }
1286 }
1287
1288 #[test]
1289 fn cloned_sdk_verify_metadata_height() {
1290 let sdk1 = SdkBuilder::new_mock()
1291 .build()
1292 .expect("mock Sdk should be created");
1293
1294 let metadata = ResponseMetadata {
1296 height: 1,
1297 ..Default::default()
1298 };
1299
1300 let request = GetIdentityRequest::default();
1302 sdk1.verify_response_metadata(request.method_name(), &metadata)
1303 .expect("metadata should be valid");
1304
1305 assert_eq!(
1306 sdk1.metadata_last_seen_height
1307 .load(std::sync::atomic::Ordering::Relaxed),
1308 metadata.height,
1309 "initial height"
1310 );
1311
1312 let sdk2 = sdk1.clone();
1314 let sdk3 = sdk1.clone();
1315
1316 let metadata = ResponseMetadata {
1318 height: 2,
1319 ..Default::default()
1320 };
1321 let request = GetIdentityRequest::default();
1323 sdk2.verify_response_metadata(request.method_name(), &metadata)
1324 .expect("metadata should be valid");
1325
1326 assert_eq!(
1327 sdk1.metadata_last_seen_height
1328 .load(std::sync::atomic::Ordering::Relaxed),
1329 metadata.height,
1330 "first sdk should see height from second sdk"
1331 );
1332 assert_eq!(
1333 sdk3.metadata_last_seen_height
1334 .load(std::sync::atomic::Ordering::Relaxed),
1335 metadata.height,
1336 "third sdk should see height from second sdk"
1337 );
1338
1339 let metadata = ResponseMetadata {
1341 height: 3,
1342 ..Default::default()
1343 };
1344 let request = GetIdentityRequest::default();
1346 sdk3.verify_response_metadata(request.method_name(), &metadata)
1347 .expect("metadata should be valid");
1348
1349 assert_eq!(
1350 sdk1.metadata_last_seen_height
1351 .load(std::sync::atomic::Ordering::Relaxed),
1352 metadata.height,
1353 "first sdk should see height from third sdk"
1354 );
1355
1356 assert_eq!(
1357 sdk2.metadata_last_seen_height
1358 .load(std::sync::atomic::Ordering::Relaxed),
1359 metadata.height,
1360 "second sdk should see height from third sdk"
1361 );
1362
1363 let metadata = ResponseMetadata {
1365 height: 1,
1366 ..Default::default()
1367 };
1368
1369 let request = GetIdentityRequest::default();
1370 sdk1.verify_response_metadata(request.method_name(), &metadata)
1371 .expect_err("metadata should be invalid");
1372 }
1373
1374 fn mock_sdk_with_auto_detect(starting_version: u32) -> super::Sdk {
1377 use std::sync::atomic::Ordering;
1378
1379 let sdk = SdkBuilder::new_mock()
1380 .build()
1381 .expect("mock Sdk should be created");
1382 sdk.protocol_version
1383 .store(starting_version, Ordering::Relaxed);
1384 sdk
1385 }
1386
1387 #[test]
1388 fn test_version_update_from_metadata() {
1389 let sdk = mock_sdk_with_auto_detect(1);
1390
1391 assert_eq!(sdk.protocol_version_number(), 1);
1392
1393 let metadata = ResponseMetadata {
1394 protocol_version: 2,
1395 height: 1,
1396 ..Default::default()
1397 };
1398
1399 sdk.verify_response_metadata("test", &metadata)
1400 .expect("metadata should be valid");
1401
1402 assert_eq!(sdk.protocol_version_number(), 2);
1403 assert_eq!(sdk.version().protocol_version, 2);
1404 }
1405
1406 #[test]
1407 fn test_unknown_version_ignored() {
1408 use dpp::version::PlatformVersion;
1409
1410 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1411 let original_version = sdk.protocol_version_number();
1412
1413 let metadata = ResponseMetadata {
1414 protocol_version: 999,
1415 height: 1,
1416 ..Default::default()
1417 };
1418
1419 sdk.verify_response_metadata("test", &metadata)
1420 .expect("metadata should be valid");
1421
1422 assert_eq!(sdk.protocol_version_number(), original_version);
1423 assert_eq!(sdk.version().protocol_version, original_version);
1424 }
1425
1426 #[test]
1427 fn test_version_shared_between_clones() {
1428 let sdk = mock_sdk_with_auto_detect(1);
1429
1430 let clone = sdk.clone();
1431
1432 let metadata = ResponseMetadata {
1433 protocol_version: 2,
1434 height: 1,
1435 ..Default::default()
1436 };
1437
1438 clone
1439 .verify_response_metadata("test", &metadata)
1440 .expect("metadata should be valid");
1441
1442 assert_eq!(
1443 sdk.protocol_version_number(),
1444 2,
1445 "original should see update from clone"
1446 );
1447 }
1448
1449 #[test]
1450 fn test_version_downgrade_ignored() {
1451 let sdk = mock_sdk_with_auto_detect(2);
1452
1453 assert_eq!(sdk.protocol_version_number(), 2);
1454
1455 let metadata = ResponseMetadata {
1456 protocol_version: 1,
1457 height: 1,
1458 ..Default::default()
1459 };
1460
1461 sdk.verify_response_metadata("test", &metadata)
1462 .expect("metadata should be valid");
1463
1464 assert_eq!(sdk.protocol_version_number(), 2);
1465 }
1466
1467 #[test]
1468 fn test_version_zero_ignored() {
1469 use dpp::version::PlatformVersion;
1470
1471 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1472 let original_version = sdk.protocol_version_number();
1473
1474 let metadata = ResponseMetadata {
1475 protocol_version: 0,
1476 height: 1,
1477 ..Default::default()
1478 };
1479
1480 sdk.verify_response_metadata("test", &metadata)
1481 .expect("metadata should be valid");
1482
1483 assert_eq!(sdk.protocol_version_number(), original_version);
1484 }
1485
1486 #[test]
1487 fn test_concurrent_updates_converge_to_highest() {
1488 use std::thread;
1489
1490 let sdk = mock_sdk_with_auto_detect(1);
1491
1492 assert_eq!(sdk.protocol_version_number(), 1);
1493
1494 let mut handles = Vec::new();
1495 for version in [2u32, 3, 2, 3, 2, 3] {
1497 let sdk_clone = sdk.clone();
1498 handles.push(thread::spawn(move || {
1499 let metadata = ResponseMetadata {
1500 protocol_version: version,
1501 height: 1,
1502 ..Default::default()
1503 };
1504 sdk_clone
1505 .verify_response_metadata("test", &metadata)
1506 .expect("metadata should be valid");
1507 }));
1508 }
1509
1510 for h in handles {
1511 h.join().expect("thread should not panic");
1512 }
1513
1514 assert_eq!(
1516 sdk.protocol_version_number(),
1517 3,
1518 "concurrent updates must converge to highest version"
1519 );
1520 }
1521
1522 #[test]
1526 fn test_explicit_version_disables_auto_detect() {
1527 use dpp::version::PlatformVersion;
1528
1529 let sdk = SdkBuilder::new_mock()
1531 .with_version(PlatformVersion::get(1).unwrap())
1532 .build()
1533 .expect("mock Sdk should be created");
1534
1535 assert_eq!(sdk.protocol_version_number(), 1);
1536 assert!(!sdk.auto_detect_protocol_version);
1537
1538 let metadata = ResponseMetadata {
1540 protocol_version: 2,
1541 height: 1,
1542 ..Default::default()
1543 };
1544
1545 sdk.verify_response_metadata("test", &metadata)
1546 .expect("metadata should be valid");
1547
1548 assert_eq!(
1549 sdk.protocol_version_number(),
1550 1,
1551 "pinned version must not be auto-updated"
1552 );
1553 }
1554
1555 #[test]
1556 fn test_with_initial_version_seeds_to_older_network_version() {
1557 use dpp::version::PlatformVersion;
1558
1559 let initial = PlatformVersion::get(1).expect("PV 1 exists");
1563 let sdk = SdkBuilder::new_mock()
1564 .with_initial_version(initial)
1565 .build()
1566 .expect("mock Sdk should be created");
1567
1568 assert_eq!(
1569 sdk.protocol_version_number(),
1570 1,
1571 "with_initial_version must seed the atomic without pinning"
1572 );
1573 assert_eq!(sdk.version().protocol_version, 1);
1574
1575 let metadata = ResponseMetadata {
1577 protocol_version: 1,
1578 height: 1,
1579 ..Default::default()
1580 };
1581 sdk.verify_response_metadata("test", &metadata)
1582 .expect("metadata should be valid");
1583 assert_eq!(sdk.protocol_version_number(), 1);
1584 }
1585
1586 #[test]
1587 fn test_with_initial_version_after_with_version_restores_auto_detect() {
1588 use dpp::version::PlatformVersion;
1589
1590 let v_latest = PlatformVersion::latest();
1594 let v_old = PlatformVersion::get(1).expect("PV 1 exists");
1595
1596 let sdk = SdkBuilder::new_mock()
1597 .with_version(v_latest)
1598 .with_initial_version(v_old)
1599 .build()
1600 .expect("mock Sdk should be created");
1601
1602 assert_eq!(
1603 sdk.protocol_version_number(),
1604 v_old.protocol_version,
1605 "with_initial_version must overwrite the prior with_version seed"
1606 );
1607 assert!(
1608 sdk.auto_detect_protocol_version,
1609 "with_initial_version must restore auto-detect after with_version disabled it"
1610 );
1611
1612 let metadata = ResponseMetadata {
1614 protocol_version: v_latest.protocol_version,
1615 height: 1,
1616 ..Default::default()
1617 };
1618 sdk.verify_response_metadata("test", &metadata)
1619 .expect("metadata should be valid");
1620 assert_eq!(sdk.protocol_version_number(), v_latest.protocol_version);
1621 }
1622
1623 #[test]
1624 fn test_mock_version_follows_outer_sdk_atomic() {
1625 use dpp::version::PlatformVersion;
1626
1627 let v_old = PlatformVersion::get(1).expect("PV 1 exists");
1632 let v_new = PlatformVersion::latest();
1633
1634 let mut sdk = SdkBuilder::new_mock()
1635 .with_initial_version(v_old)
1636 .build()
1637 .expect("mock Sdk should be created");
1638
1639 assert_eq!(sdk.version().protocol_version, v_old.protocol_version);
1640 {
1641 let mock = sdk.mock();
1642 assert_eq!(
1643 mock.version().protocol_version,
1644 v_old.protocol_version,
1645 "mock version must mirror outer SDK before ratchet"
1646 );
1647 }
1648
1649 let metadata = ResponseMetadata {
1650 protocol_version: v_new.protocol_version,
1651 height: 1,
1652 ..Default::default()
1653 };
1654 sdk.verify_response_metadata("test", &metadata)
1655 .expect("metadata should be valid");
1656
1657 assert_eq!(sdk.version().protocol_version, v_new.protocol_version);
1658 let mock = sdk.mock();
1659 assert_eq!(
1660 mock.version().protocol_version,
1661 v_new.protocol_version,
1662 "mock version must follow outer ratchet (CMT-001 regression)"
1663 );
1664 }
1665
1666 #[test_matrix([90,91,100,109,110], 100, 10, false; "valid time")]
1667 #[test_matrix([0,89,111], 100, 10, true; "invalid time")]
1668 #[test_matrix([0,100], [0,100], 100, false; "zero time")]
1669 #[test_matrix([99,101], 100, 0, true; "zero tolerance")]
1670 fn test_verify_metadata_time(
1671 received_time: u64,
1672 now_time: u64,
1673 tolerance: u64,
1674 expect_err: bool,
1675 ) {
1676 let metadata = ResponseMetadata {
1677 time_ms: received_time,
1678 ..Default::default()
1679 };
1680
1681 let result = super::verify_metadata_time(&metadata, now_time, tolerance);
1682
1683 assert_eq!(result.is_err(), expect_err);
1684 }
1685}