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;
28use 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 old_version = previous,
334 new_version = received_version,
335 "protocol version updated from network metadata"
336 );
337 }
338 }
339
340 pub(crate) async fn parse_proof_with_metadata_and_proof<R, O: FromProof<R> + MockResponse>(
366 &self,
367 request: O::Request,
368 response: O::Response,
369 ) -> Result<(Option<O>, ResponseMetadata, Proof), Error>
370 where
371 O::Request: Mockable + TransportRequest,
372 {
373 let provider = self
374 .context_provider()
375 .ok_or(drive_proof_verifier::Error::ContextProviderNotSet)?;
376 let method_name = request.method_name();
377
378 let (object, metadata, proof) = match self.inner {
379 SdkInstance::Dapi { .. } => O::maybe_from_proof_with_metadata(
380 request,
381 response,
382 self.network,
383 self.version(),
384 &provider,
385 ),
386 #[cfg(feature = "mocks")]
387 SdkInstance::Mock { ref mock, .. } => {
388 let guard = mock.lock().await;
389 guard.parse_proof_with_metadata(request, response)
390 }
391 }?;
392
393 self.verify_response_metadata(method_name, &metadata)
394 .inspect_err(|err| {
395 tracing::warn!(%err,method=method_name,"received response with stale metadata; try another server");
396 })?;
397
398 Ok((object, metadata, proof))
399 }
400
401 pub fn context_provider(&self) -> Option<impl ContextProvider> {
403 let provider_guard = self.context_provider.load();
404 let provider = provider_guard.as_ref().map(Arc::clone);
405
406 provider
407 }
408
409 #[cfg(feature = "mocks")]
420 pub fn mock(&mut self) -> MutexGuard<'_, MockDashPlatformSdk> {
421 if let Sdk {
422 inner: SdkInstance::Mock { ref mock, .. },
423 ..
424 } = self
425 {
426 mock.try_lock()
427 .expect("mock sdk is in use by another thread and cannot be reconfigured")
428 } else {
429 panic!("not a mock")
430 }
431 }
432
433 pub async fn get_identity_nonce(
438 &self,
439 identity_id: Identifier,
440 bump_first: bool,
441 settings: Option<PutSettings>,
442 ) -> Result<IdentityNonce, Error> {
443 let settings = settings.unwrap_or_default();
444 let nonce = self
445 .nonce_cache
446 .get_identity_nonce(self, identity_id, bump_first, &settings)
447 .await?;
448
449 tracing::trace!(
450 identity_id = %identity_id,
451 bump_first,
452 nonce,
453 "Fetched identity nonce"
454 );
455
456 Ok(nonce)
457 }
458
459 pub async fn get_identity_contract_nonce(
464 &self,
465 identity_id: Identifier,
466 contract_id: Identifier,
467 bump_first: bool,
468 settings: Option<PutSettings>,
469 ) -> Result<IdentityNonce, Error> {
470 let settings = settings.unwrap_or_default();
471 self.nonce_cache
472 .get_identity_contract_nonce(self, identity_id, contract_id, bump_first, &settings)
473 .await
474 }
475
476 pub async fn refresh_identity_nonce(&self, identity_id: &Identifier) {
480 self.nonce_cache.refresh(identity_id).await;
481 }
482
483 pub fn version<'v>(&self) -> &'v PlatformVersion {
489 let v = self.protocol_version.load(Ordering::Relaxed);
490 PlatformVersion::get(v).unwrap_or_else(|_| PlatformVersion::latest())
491 }
492
493 pub fn protocol_version_number(&self) -> u32 {
495 self.protocol_version.load(Ordering::Relaxed)
496 }
497
498 pub fn prove(&self) -> bool {
501 self.proofs
502 }
503
504 pub fn set_context_provider<C: ContextProvider + 'static>(&self, context_provider: C) {
512 self.context_provider
513 .swap(Some(Arc::new(Box::new(context_provider))));
514 }
515
516 pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
518 self.cancel_token.cancelled()
519 }
520
521 pub fn shutdown(&self) {
523 self.cancel_token.cancel();
524 }
525
526 pub fn address_list(&self) -> &AddressList {
528 match &self.inner {
529 SdkInstance::Dapi { dapi, .. } => dapi.address_list(),
530 #[cfg(feature = "mocks")]
531 SdkInstance::Mock { address_list, .. } => address_list,
532 }
533 }
534}
535
536pub(crate) fn verify_metadata_time(
544 metadata: &ResponseMetadata,
545 now_ms: u64,
546 tolerance_ms: u64,
547) -> Result<(), Error> {
548 let metadata_time = metadata.time_ms;
549
550 if now_ms.abs_diff(metadata_time) > tolerance_ms {
552 return Err(StaleNodeError::Time {
553 expected_timestamp_ms: now_ms,
554 received_timestamp_ms: metadata_time,
555 tolerance_ms,
556 }
557 .into());
558 }
559
560 tracing::trace!(
561 expected_time = now_ms,
562 received_time = metadata_time,
563 tolerance_ms,
564 "received response with valid time"
565 );
566 Ok(())
567}
568
569fn verify_metadata_height(
572 metadata: &ResponseMetadata,
573 tolerance: u64,
574 last_seen_height: Arc<atomic::AtomicU64>,
575) -> Result<(), Error> {
576 let mut expected_height = last_seen_height.load(Ordering::Relaxed);
577 let received_height = metadata.height;
578
579 if received_height == expected_height {
581 tracing::trace!(
582 expected_height,
583 received_height,
584 tolerance,
585 "received message has the same height as previously seen"
586 );
587 return Ok(());
588 }
589
590 if expected_height > tolerance && received_height < expected_height - tolerance {
592 return Err(StaleNodeError::Height {
593 expected_height,
594 received_height,
595 tolerance_blocks: tolerance,
596 }
597 .into());
598 }
599
600 tracing::trace!(
602 expected_height = expected_height,
603 received_height = received_height,
604 tolerance,
605 "received message with new height"
606 );
607 while let Err(stored_height) = last_seen_height.compare_exchange(
608 expected_height,
609 received_height,
610 Ordering::SeqCst,
611 Ordering::Relaxed,
612 ) {
613 if stored_height >= metadata.height {
615 break;
616 }
617 expected_height = stored_height;
618 }
619
620 Ok(())
621}
622
623#[async_trait::async_trait]
624impl DapiRequestExecutor for Sdk {
625 async fn execute<R: TransportRequest>(
626 &self,
627 request: R,
628 settings: RequestSettings,
629 ) -> ExecutionResult<R::Response, DapiClientError> {
630 match self.inner {
631 SdkInstance::Dapi { ref dapi, .. } => dapi.execute(request, settings).await,
632 #[cfg(feature = "mocks")]
633 SdkInstance::Mock { ref dapi, .. } => {
634 let dapi_guard = dapi.lock().await;
635 dapi_guard.execute(request, settings).await
636 }
637 }
638 }
639}
640
641pub struct SdkBuilder {
654 addresses: Option<AddressList>,
658 settings: Option<RequestSettings>,
659
660 network: Network,
661
662 core_ip: String,
663 core_port: u16,
664 core_user: String,
665 core_password: Zeroizing<String>,
666
667 proofs: bool,
669
670 version: &'static PlatformVersion,
672
673 version_explicit: bool,
676
677 #[cfg(feature = "mocks")]
679 data_contract_cache_size: NonZeroUsize,
680
681 #[cfg(feature = "mocks")]
683 token_config_cache_size: NonZeroUsize,
684
685 #[cfg(feature = "mocks")]
687 quorum_public_keys_cache_size: NonZeroUsize,
688
689 context_provider: Option<Box<dyn ContextProvider>>,
691
692 metadata_height_tolerance: Option<u64>,
697
698 metadata_time_tolerance_ms: Option<u64>,
702
703 #[cfg(feature = "mocks")]
705 dump_dir: Option<PathBuf>,
706
707 pub(crate) cancel_token: CancellationToken,
709
710 #[cfg(not(target_arch = "wasm32"))]
712 ca_certificate: Option<Certificate>,
713}
714
715impl Default for SdkBuilder {
716 fn default() -> Self {
718 Self {
719 addresses: None,
720 settings: None,
721 network: Network::Mainnet,
722 core_ip: "".to_string(),
723 core_port: 0,
724 core_password: "".to_string().into(),
725 core_user: "".to_string(),
726
727 proofs: true,
728 metadata_height_tolerance: Some(1),
729 metadata_time_tolerance_ms: None,
730
731 #[cfg(feature = "mocks")]
732 data_contract_cache_size: NonZeroUsize::new(DEFAULT_CONTRACT_CACHE_SIZE)
733 .expect("data contract cache size must be positive"),
734
735 #[cfg(feature = "mocks")]
736 token_config_cache_size: NonZeroUsize::new(DEFAULT_TOKEN_CONFIG_CACHE_SIZE)
737 .expect("token config cache size must be positive"),
738
739 #[cfg(feature = "mocks")]
740 quorum_public_keys_cache_size: NonZeroUsize::new(DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE)
741 .expect("quorum public keys cache size must be positive"),
742
743 context_provider: None,
744
745 cancel_token: CancellationToken::new(),
746
747 version: PlatformVersion::latest(),
748 version_explicit: false,
749 #[cfg(not(target_arch = "wasm32"))]
750 ca_certificate: None,
751
752 #[cfg(feature = "mocks")]
753 dump_dir: None,
754 }
755 }
756}
757
758impl SdkBuilder {
759 pub fn with_proofs(mut self, proofs: bool) -> Self {
764 self.proofs = proofs;
765 self
766 }
767 pub fn new(addresses: AddressList) -> Self {
769 Self {
770 addresses: Some(addresses),
771 ..Default::default()
772 }
773 }
774
775 pub fn with_address_list(mut self, addresses: AddressList) -> Self {
777 self.addresses = Some(addresses);
778 self
779 }
780
781 pub fn new_mock() -> Self {
783 Self::default()
784 }
785
786 pub fn new_testnet() -> Self {
792 let address_list = default_address_list_for_network(Network::Testnet);
793
794 Self::new(address_list).with_network(Network::Testnet)
795 }
796
797 pub fn new_mainnet() -> Self {
810 let address_list = default_address_list_for_network(Network::Mainnet);
811
812 Self::new(address_list).with_network(Network::Mainnet)
813 }
814
815 pub fn with_network(mut self, network: Network) -> Self {
819 self.network = network;
820 self
821 }
822
823 #[cfg(not(target_arch = "wasm32"))]
833 pub fn with_ca_certificate(mut self, pem_certificate: Certificate) -> Self {
834 self.ca_certificate = Some(pem_certificate);
835 self
836 }
837
838 #[cfg(not(target_arch = "wasm32"))]
843 pub fn with_ca_certificate_file(
844 self,
845 certificate_file_path: impl AsRef<Path>,
846 ) -> std::io::Result<Self> {
847 let pem = std::fs::read(certificate_file_path)?;
848 let cert = Certificate::from_pem(pem);
849
850 Ok(self.with_ca_certificate(cert))
851 }
852
853 pub fn with_settings(mut self, settings: RequestSettings) -> Self {
861 self.settings = Some(settings);
862 self
863 }
864
865 pub fn with_version(mut self, version: &'static PlatformVersion) -> Self {
871 self.version = version;
872 self.version_explicit = true;
873 self
874 }
875
876 pub fn with_context_provider<C: ContextProvider + 'static>(
883 mut self,
884 context_provider: C,
885 ) -> Self {
886 self.context_provider = Some(Box::new(context_provider));
887
888 self
889 }
890
891 pub fn with_cancellation_token(mut self, cancel_token: CancellationToken) -> Self {
895 self.cancel_token = cancel_token;
896 self
897 }
898
899 pub fn with_core(mut self, ip: &str, port: u16, user: &str, password: &str) -> Self {
907 self.core_ip = ip.to_string();
908 self.core_port = port;
909 self.core_user = user.to_string();
910 self.core_password = Zeroizing::from(password.to_string());
911
912 self
913 }
914
915 pub fn with_height_tolerance(mut self, tolerance: Option<u64>) -> Self {
927 self.metadata_height_tolerance = tolerance;
928 self
929 }
930
931 pub fn with_time_tolerance(mut self, tolerance_ms: Option<u64>) -> Self {
946 self.metadata_time_tolerance_ms = tolerance_ms;
947 self
948 }
949
950 #[cfg(feature = "mocks")]
963 pub fn with_dump_dir(mut self, dump_dir: &Path) -> Self {
964 self.dump_dir = Some(dump_dir.to_path_buf());
965 self
966 }
967
968 pub fn build(self) -> Result<Sdk, Error> {
976 let dapi_client_settings = match self.settings {
977 Some(settings) => DEFAULT_REQUEST_SETTINGS.override_by(settings),
978 None => DEFAULT_REQUEST_SETTINGS,
979 };
980
981 let sdk= match self.addresses {
982 Some(addresses) => {
984 #[allow(unused_mut)] let mut dapi = DapiClient::new(addresses, dapi_client_settings);
986 #[cfg(not(target_arch = "wasm32"))]
987 if let Some(pem) = self.ca_certificate {
988 dapi = dapi.with_ca_certificate(pem);
989 }
990
991 #[cfg(feature = "mocks")]
992 let dapi = dapi.dump_dir(self.dump_dir.clone());
993
994 #[allow(unused_mut)] let mut sdk= Sdk{
996 network: self.network,
997 dapi_client_settings,
998 inner:SdkInstance::Dapi { dapi },
999 proofs:self.proofs,
1000 context_provider: ArcSwapOption::new( self.context_provider.map(Arc::new)),
1001 cancel_token: self.cancel_token,
1002 nonce_cache: Default::default(),
1003 protocol_version: Arc::new(atomic::AtomicU32::new(
1007 if self.version_explicit { self.version.protocol_version } else { 0 },
1008 )),
1009 auto_detect_protocol_version: !self.version_explicit,
1010 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
1012 metadata_height_tolerance: self.metadata_height_tolerance,
1013 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
1014 #[cfg(feature = "mocks")]
1015 dump_dir: self.dump_dir,
1016 };
1017 if sdk.context_provider.load().is_none() {
1019 #[cfg(feature = "mocks")]
1020 if !self.core_ip.is_empty() {
1021 tracing::warn!(
1022 "ContextProvider not set, falling back to a mock one; use SdkBuilder::with_context_provider() to set it up");
1023 let mut context_provider = GrpcContextProvider::new(None,
1024 &self.core_ip, self.core_port, &self.core_user, &self.core_password,
1025 self.data_contract_cache_size, self.token_config_cache_size, self.quorum_public_keys_cache_size)?;
1026 #[cfg(feature = "mocks")]
1027 if sdk.dump_dir.is_some() {
1028 context_provider.set_dump_dir(sdk.dump_dir.clone());
1029 }
1030 let context_provider= Arc::new(context_provider);
1033 sdk.context_provider.swap(Some(Arc::new(Box::new(context_provider.clone()))));
1034 context_provider.set_sdk(Some(sdk.clone()));
1035 } else{
1036 return Err(Error::Config(concat!(
1037 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1038 "or configure Core access with SdkBuilder::with_core() to use mock context provider")
1039 .to_string()));
1040 }
1041 #[cfg(not(feature = "mocks"))]
1042 return Err(Error::Config(concat!(
1043 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1044 "or enable `mocks` feature to use mock context provider")
1045 .to_string()));
1046 };
1047
1048 sdk
1049 },
1050 #[cfg(feature = "mocks")]
1051 None => {
1053 let dapi =Arc::new(Mutex::new( MockDapiClient::new()));
1054 let context_provider = self.context_provider.unwrap_or_else(||{
1056 let mut cp=MockContextProvider::new();
1057 if let Some(ref dump_dir) = self.dump_dir {
1058 cp.quorum_keys_dir(Some(dump_dir.clone()));
1059 }
1060 Box::new(cp)
1061 }
1062 );
1063 let mock_sdk = MockDashPlatformSdk::new(self.version, Arc::clone(&dapi));
1064 let mock_sdk = Arc::new(Mutex::new(mock_sdk));
1065 let sdk= Sdk {
1066 network: self.network,
1067 dapi_client_settings,
1068 inner:SdkInstance::Mock {
1069 mock:mock_sdk.clone(),
1070 dapi,
1071 address_list: AddressList::new(),
1072 },
1073 dump_dir: self.dump_dir.clone(),
1074 proofs:self.proofs,
1075 nonce_cache: Default::default(),
1076 protocol_version: Arc::new(atomic::AtomicU32::new(
1077 if self.version_explicit { self.version.protocol_version } else { 0 },
1078 )),
1079 auto_detect_protocol_version: !self.version_explicit,
1080 context_provider: ArcSwapOption::new(Some(Arc::new(context_provider))),
1081 cancel_token: self.cancel_token,
1082 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
1083 metadata_height_tolerance: self.metadata_height_tolerance,
1084 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
1085 };
1086 let mut guard = mock_sdk.try_lock().expect("mock sdk is in use by another thread and cannot be reconfigured");
1087 guard.set_sdk(sdk.clone());
1088 if let Some(ref dump_dir) = self.dump_dir {
1089 guard.load_expectations_sync(dump_dir)?;
1090 };
1091
1092 sdk
1093 },
1094 #[cfg(not(feature = "mocks"))]
1095 None => return Err(Error::Config("Mock mode is not available. Please enable `mocks` feature or provide address list.".to_string())),
1096 };
1097
1098 Ok(sdk)
1099 }
1100}
1101
1102pub fn prettify_proof(proof: &Proof) -> String {
1103 let config = bincode::config::standard()
1104 .with_big_endian()
1105 .with_no_limit();
1106 let grovedb_proof: Result<GroveDBProof, DecodeError> =
1107 bincode::decode_from_slice(&proof.grovedb_proof, config).map(|(a, _)| a);
1108
1109 let grovedb_proof_string = match grovedb_proof {
1110 Ok(proof) => format!("{}", proof),
1111 Err(_) => "Invalid GroveDBProof".to_string(),
1112 };
1113 format!(
1114 "Proof {{
1115 grovedb_proof: {},
1116 quorum_hash: 0x{},
1117 signature: 0x{},
1118 round: {},
1119 block_id_hash: 0x{},
1120 quorum_type: {},
1121 }}",
1122 grovedb_proof_string,
1123 hex::encode(&proof.quorum_hash),
1124 hex::encode(&proof.signature),
1125 proof.round,
1126 hex::encode(&proof.block_id_hash),
1127 proof.quorum_type,
1128 )
1129}
1130
1131#[cfg(test)]
1132mod test {
1133 use std::sync::Arc;
1134
1135 use dapi_grpc::platform::v0::{GetIdentityRequest, ResponseMetadata};
1136 use rs_dapi_client::transport::TransportRequest;
1137 use test_case::test_matrix;
1138
1139 use crate::SdkBuilder;
1140
1141 use super::Network;
1142
1143 const MAINNET_PLATFORM_HTTP_PORT: u16 = 443;
1145 const TESTNET_PLATFORM_HTTP_PORT: u16 = 1443;
1147
1148 #[test]
1149 fn new_testnet_sources_bootstrap_from_seeds() {
1150 let builder = SdkBuilder::new_testnet();
1151 let address_list = builder
1152 .addresses
1153 .as_ref()
1154 .expect("testnet builder should configure default addresses");
1155
1156 assert_eq!(builder.network, Network::Testnet);
1157 assert!(
1158 !address_list.is_empty(),
1159 "testnet must have at least one bootstrap address"
1160 );
1161 for address in address_list.get_live_addresses() {
1162 assert_eq!(
1163 address.uri().port_u16(),
1164 Some(TESTNET_PLATFORM_HTTP_PORT),
1165 "testnet bootstrap address must use the platform HTTP port",
1166 );
1167 }
1168 }
1169
1170 #[test]
1171 fn new_mainnet_sources_bootstrap_from_seeds() {
1172 let builder = SdkBuilder::new_mainnet();
1173 let address_list = builder
1174 .addresses
1175 .as_ref()
1176 .expect("mainnet builder should configure default addresses");
1177
1178 assert_eq!(builder.network, Network::Mainnet);
1179 assert!(
1180 !address_list.is_empty(),
1181 "mainnet must have at least one bootstrap address"
1182 );
1183 for address in address_list.get_live_addresses() {
1184 assert_eq!(
1185 address.uri().port_u16(),
1186 Some(MAINNET_PLATFORM_HTTP_PORT),
1187 "mainnet bootstrap address must use the platform HTTP port",
1188 );
1189 }
1190 }
1191
1192 #[test]
1196 fn bootstrap_counts_reasonable() {
1197 let mainnet = SdkBuilder::new_mainnet()
1198 .addresses
1199 .expect("mainnet builder should configure default addresses");
1200 let testnet = SdkBuilder::new_testnet()
1201 .addresses
1202 .expect("testnet builder should configure default addresses");
1203 assert!(
1204 mainnet.len() >= 10,
1205 "expected >=10 mainnet bootstrap addresses, got {}",
1206 mainnet.len()
1207 );
1208 assert!(
1209 testnet.len() >= 10,
1210 "expected >=10 testnet bootstrap addresses, got {}",
1211 testnet.len()
1212 );
1213 }
1214
1215 #[test_matrix(97..102, 100, 2, false; "valid height")]
1216 #[test_case(103, 100, 2, true; "invalid height")]
1217 fn test_verify_metadata_height(
1218 expected_height: u64,
1219 received_height: u64,
1220 tolerance: u64,
1221 expect_err: bool,
1222 ) {
1223 let metadata = ResponseMetadata {
1224 height: received_height,
1225 ..Default::default()
1226 };
1227
1228 let last_seen_height = Arc::new(std::sync::atomic::AtomicU64::new(expected_height));
1229
1230 let result =
1231 super::verify_metadata_height(&metadata, tolerance, Arc::clone(&last_seen_height));
1232
1233 assert_eq!(result.is_err(), expect_err);
1234 if result.is_ok() {
1235 assert_eq!(
1236 last_seen_height.load(std::sync::atomic::Ordering::Relaxed),
1237 received_height,
1238 "previous height should be updated"
1239 );
1240 }
1241 }
1242
1243 #[test]
1244 fn cloned_sdk_verify_metadata_height() {
1245 let sdk1 = SdkBuilder::new_mock()
1246 .build()
1247 .expect("mock Sdk should be created");
1248
1249 let metadata = ResponseMetadata {
1251 height: 1,
1252 ..Default::default()
1253 };
1254
1255 let request = GetIdentityRequest::default();
1257 sdk1.verify_response_metadata(request.method_name(), &metadata)
1258 .expect("metadata should be valid");
1259
1260 assert_eq!(
1261 sdk1.metadata_last_seen_height
1262 .load(std::sync::atomic::Ordering::Relaxed),
1263 metadata.height,
1264 "initial height"
1265 );
1266
1267 let sdk2 = sdk1.clone();
1269 let sdk3 = sdk1.clone();
1270
1271 let metadata = ResponseMetadata {
1273 height: 2,
1274 ..Default::default()
1275 };
1276 let request = GetIdentityRequest::default();
1278 sdk2.verify_response_metadata(request.method_name(), &metadata)
1279 .expect("metadata should be valid");
1280
1281 assert_eq!(
1282 sdk1.metadata_last_seen_height
1283 .load(std::sync::atomic::Ordering::Relaxed),
1284 metadata.height,
1285 "first sdk should see height from second sdk"
1286 );
1287 assert_eq!(
1288 sdk3.metadata_last_seen_height
1289 .load(std::sync::atomic::Ordering::Relaxed),
1290 metadata.height,
1291 "third sdk should see height from second sdk"
1292 );
1293
1294 let metadata = ResponseMetadata {
1296 height: 3,
1297 ..Default::default()
1298 };
1299 let request = GetIdentityRequest::default();
1301 sdk3.verify_response_metadata(request.method_name(), &metadata)
1302 .expect("metadata should be valid");
1303
1304 assert_eq!(
1305 sdk1.metadata_last_seen_height
1306 .load(std::sync::atomic::Ordering::Relaxed),
1307 metadata.height,
1308 "first sdk should see height from third sdk"
1309 );
1310
1311 assert_eq!(
1312 sdk2.metadata_last_seen_height
1313 .load(std::sync::atomic::Ordering::Relaxed),
1314 metadata.height,
1315 "second sdk should see height from third sdk"
1316 );
1317
1318 let metadata = ResponseMetadata {
1320 height: 1,
1321 ..Default::default()
1322 };
1323
1324 let request = GetIdentityRequest::default();
1325 sdk1.verify_response_metadata(request.method_name(), &metadata)
1326 .expect_err("metadata should be invalid");
1327 }
1328
1329 fn mock_sdk_with_auto_detect(starting_version: u32) -> super::Sdk {
1332 use std::sync::atomic::Ordering;
1333
1334 let sdk = SdkBuilder::new_mock()
1335 .build()
1336 .expect("mock Sdk should be created");
1337 sdk.protocol_version
1338 .store(starting_version, Ordering::Relaxed);
1339 sdk
1340 }
1341
1342 #[test]
1343 fn test_version_update_from_metadata() {
1344 let sdk = mock_sdk_with_auto_detect(1);
1345
1346 assert_eq!(sdk.protocol_version_number(), 1);
1347
1348 let metadata = ResponseMetadata {
1349 protocol_version: 2,
1350 height: 1,
1351 ..Default::default()
1352 };
1353
1354 sdk.verify_response_metadata("test", &metadata)
1355 .expect("metadata should be valid");
1356
1357 assert_eq!(sdk.protocol_version_number(), 2);
1358 assert_eq!(sdk.version().protocol_version, 2);
1359 }
1360
1361 #[test]
1362 fn test_unknown_version_ignored() {
1363 use dpp::version::PlatformVersion;
1364
1365 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1366 let original_version = sdk.protocol_version_number();
1367
1368 let metadata = ResponseMetadata {
1369 protocol_version: 999,
1370 height: 1,
1371 ..Default::default()
1372 };
1373
1374 sdk.verify_response_metadata("test", &metadata)
1375 .expect("metadata should be valid");
1376
1377 assert_eq!(sdk.protocol_version_number(), original_version);
1378 assert_eq!(sdk.version().protocol_version, original_version);
1379 }
1380
1381 #[test]
1382 fn test_version_shared_between_clones() {
1383 let sdk = mock_sdk_with_auto_detect(1);
1384
1385 let clone = sdk.clone();
1386
1387 let metadata = ResponseMetadata {
1388 protocol_version: 2,
1389 height: 1,
1390 ..Default::default()
1391 };
1392
1393 clone
1394 .verify_response_metadata("test", &metadata)
1395 .expect("metadata should be valid");
1396
1397 assert_eq!(
1398 sdk.protocol_version_number(),
1399 2,
1400 "original should see update from clone"
1401 );
1402 }
1403
1404 #[test]
1405 fn test_version_downgrade_ignored() {
1406 let sdk = mock_sdk_with_auto_detect(2);
1407
1408 assert_eq!(sdk.protocol_version_number(), 2);
1409
1410 let metadata = ResponseMetadata {
1411 protocol_version: 1,
1412 height: 1,
1413 ..Default::default()
1414 };
1415
1416 sdk.verify_response_metadata("test", &metadata)
1417 .expect("metadata should be valid");
1418
1419 assert_eq!(sdk.protocol_version_number(), 2);
1420 }
1421
1422 #[test]
1423 fn test_version_zero_ignored() {
1424 use dpp::version::PlatformVersion;
1425
1426 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1427 let original_version = sdk.protocol_version_number();
1428
1429 let metadata = ResponseMetadata {
1430 protocol_version: 0,
1431 height: 1,
1432 ..Default::default()
1433 };
1434
1435 sdk.verify_response_metadata("test", &metadata)
1436 .expect("metadata should be valid");
1437
1438 assert_eq!(sdk.protocol_version_number(), original_version);
1439 }
1440
1441 #[test]
1442 fn test_concurrent_updates_converge_to_highest() {
1443 use std::thread;
1444
1445 let sdk = mock_sdk_with_auto_detect(1);
1446
1447 assert_eq!(sdk.protocol_version_number(), 1);
1448
1449 let mut handles = Vec::new();
1450 for version in [2u32, 3, 2, 3, 2, 3] {
1452 let sdk_clone = sdk.clone();
1453 handles.push(thread::spawn(move || {
1454 let metadata = ResponseMetadata {
1455 protocol_version: version,
1456 height: 1,
1457 ..Default::default()
1458 };
1459 sdk_clone
1460 .verify_response_metadata("test", &metadata)
1461 .expect("metadata should be valid");
1462 }));
1463 }
1464
1465 for h in handles {
1466 h.join().expect("thread should not panic");
1467 }
1468
1469 assert_eq!(
1471 sdk.protocol_version_number(),
1472 3,
1473 "concurrent updates must converge to highest version"
1474 );
1475 }
1476
1477 #[test]
1481 fn test_explicit_version_disables_auto_detect() {
1482 use dpp::version::PlatformVersion;
1483
1484 let sdk = SdkBuilder::new_mock()
1486 .with_version(PlatformVersion::get(1).unwrap())
1487 .build()
1488 .expect("mock Sdk should be created");
1489
1490 assert_eq!(sdk.protocol_version_number(), 1);
1491 assert!(!sdk.auto_detect_protocol_version);
1492
1493 let metadata = ResponseMetadata {
1495 protocol_version: 2,
1496 height: 1,
1497 ..Default::default()
1498 };
1499
1500 sdk.verify_response_metadata("test", &metadata)
1501 .expect("metadata should be valid");
1502
1503 assert_eq!(
1504 sdk.protocol_version_number(),
1505 1,
1506 "pinned version must not be auto-updated"
1507 );
1508 }
1509
1510 #[test]
1511 fn test_default_sdk_detects_older_network_version() {
1512 use dpp::version::PlatformVersion;
1513
1514 let sdk = SdkBuilder::new_mock()
1516 .build()
1517 .expect("mock Sdk should be created");
1518
1519 assert_eq!(
1521 sdk.version().protocol_version,
1522 PlatformVersion::latest().protocol_version,
1523 "before first response, should fall back to latest"
1524 );
1525 assert_eq!(sdk.protocol_version_number(), 0, "should be uninitialized");
1526
1527 let metadata = ResponseMetadata {
1529 protocol_version: 1,
1530 height: 1,
1531 ..Default::default()
1532 };
1533
1534 sdk.verify_response_metadata("test", &metadata)
1535 .expect("metadata should be valid");
1536
1537 assert_eq!(
1538 sdk.protocol_version_number(),
1539 1,
1540 "default SDK must detect older network version"
1541 );
1542 assert_eq!(sdk.version().protocol_version, 1);
1543 }
1544
1545 #[test_matrix([90,91,100,109,110], 100, 10, false; "valid time")]
1546 #[test_matrix([0,89,111], 100, 10, true; "invalid time")]
1547 #[test_matrix([0,100], [0,100], 100, false; "zero time")]
1548 #[test_matrix([99,101], 100, 0, true; "zero tolerance")]
1549 fn test_verify_metadata_time(
1550 received_time: u64,
1551 now_time: u64,
1552 tolerance: u64,
1553 expect_err: bool,
1554 ) {
1555 let metadata = ResponseMetadata {
1556 time_ms: received_time,
1557 ..Default::default()
1558 };
1559
1560 let result = super::verify_metadata_time(&metadata, now_time, tolerance);
1561
1562 assert_eq!(result.is_err(), expect_err);
1563 }
1564}