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, PlatformVersionCurrentVersion};
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 metadata_last_seen_height: Arc<atomic::AtomicU64>,
114
115 metadata_height_tolerance: Option<u64>,
119
120 metadata_time_tolerance_ms: Option<u64>,
124
125 pub(crate) cancel_token: CancellationToken,
127
128 pub(crate) dapi_client_settings: RequestSettings,
130
131 #[cfg(feature = "mocks")]
132 dump_dir: Option<PathBuf>,
133}
134impl Clone for Sdk {
135 fn clone(&self) -> Self {
136 Self {
137 network: self.network,
138 inner: self.inner.clone(),
139 proofs: self.proofs,
140 nonce_cache: Arc::clone(&self.nonce_cache),
141 context_provider: ArcSwapOption::new(self.context_provider.load_full()),
142 cancel_token: self.cancel_token.clone(),
143 metadata_last_seen_height: Arc::clone(&self.metadata_last_seen_height),
144 metadata_height_tolerance: self.metadata_height_tolerance,
145 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
146 dapi_client_settings: self.dapi_client_settings,
147 #[cfg(feature = "mocks")]
148 dump_dir: self.dump_dir.clone(),
149 }
150 }
151}
152
153impl Debug for Sdk {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match &self.inner {
156 SdkInstance::Dapi { dapi, .. } => f
157 .debug_struct("Sdk")
158 .field("dapi", dapi)
159 .field("proofs", &self.proofs)
160 .finish(),
161 #[cfg(feature = "mocks")]
162 SdkInstance::Mock { mock, .. } => f
163 .debug_struct("Sdk")
164 .field("mock", mock)
165 .field("proofs", &self.proofs)
166 .finish(),
167 }
168 }
169}
170
171#[derive(Debug, Clone)]
176enum SdkInstance {
177 Dapi {
179 dapi: DapiClient,
181
182 version: &'static PlatformVersion,
184 },
185 #[cfg(feature = "mocks")]
187 Mock {
188 dapi: Arc<Mutex<MockDapiClient>>,
192 mock: Arc<Mutex<MockDashPlatformSdk>>,
194 address_list: AddressList,
195 version: &'static PlatformVersion,
197 },
198}
199
200impl Sdk {
201 pub fn new_mock() -> Self {
207 SdkBuilder::default()
208 .build()
209 .expect("mock should be created")
210 }
211
212 fn freshness_criteria(&self, method_name: &str) -> (Option<u64>, Option<u64>) {
217 match method_name {
218 "get_addresses_trunk_state"
219 | "get_addresses_branch_state"
220 | "get_nullifiers_trunk_state"
221 | "get_nullifiers_branch_state" => (
222 None,
223 self.metadata_time_tolerance_ms
224 .and(Some(ADDRESS_STATE_TIME_TOLERANCE_MS)),
225 ),
226 _ => (
227 self.metadata_height_tolerance,
228 self.metadata_time_tolerance_ms,
229 ),
230 }
231 }
232
233 pub fn verify_response_metadata(
235 &self,
236 method_name: &str,
237 metadata: &ResponseMetadata,
238 ) -> Result<(), Error> {
239 let (metadata_height_tolerance, metadata_time_tolerance_ms) =
240 self.freshness_criteria(method_name);
241 if let Some(height_tolerance) = metadata_height_tolerance {
242 verify_metadata_height(
243 metadata,
244 height_tolerance,
245 Arc::clone(&(self.metadata_last_seen_height)),
246 )?;
247 };
248 if let Some(time_tolerance) = metadata_time_tolerance_ms {
249 let now = chrono::Utc::now().timestamp_millis() as u64;
250 verify_metadata_time(metadata, now, time_tolerance)?;
251 };
252
253 Ok(())
254 }
255
256 pub(crate) async fn parse_proof_with_metadata_and_proof<R, O: FromProof<R> + MockResponse>(
266 &self,
267 request: O::Request,
268 response: O::Response,
269 ) -> Result<(Option<O>, ResponseMetadata, Proof), Error>
270 where
271 O::Request: Mockable + TransportRequest,
272 {
273 let provider = self
274 .context_provider()
275 .ok_or(drive_proof_verifier::Error::ContextProviderNotSet)?;
276 let method_name = request.method_name();
277
278 let (object, metadata, proof) = match self.inner {
279 SdkInstance::Dapi { .. } => O::maybe_from_proof_with_metadata(
280 request,
281 response,
282 self.network,
283 self.version(),
284 &provider,
285 ),
286 #[cfg(feature = "mocks")]
287 SdkInstance::Mock { ref mock, .. } => {
288 let guard = mock.lock().await;
289 guard.parse_proof_with_metadata(request, response)
290 }
291 }?;
292
293 self.verify_response_metadata(method_name, &metadata)
294 .inspect_err(|err| {
295 tracing::warn!(%err,method=method_name,"received response with stale metadata; try another server");
296 })?;
297
298 Ok((object, metadata, proof))
299 }
300
301 pub fn context_provider(&self) -> Option<impl ContextProvider> {
303 let provider_guard = self.context_provider.load();
304 let provider = provider_guard.as_ref().map(Arc::clone);
305
306 provider
307 }
308
309 #[cfg(feature = "mocks")]
320 pub fn mock(&mut self) -> MutexGuard<'_, MockDashPlatformSdk> {
321 if let Sdk {
322 inner: SdkInstance::Mock { ref mock, .. },
323 ..
324 } = self
325 {
326 mock.try_lock()
327 .expect("mock sdk is in use by another thread and cannot be reconfigured")
328 } else {
329 panic!("not a mock")
330 }
331 }
332
333 pub async fn get_identity_nonce(
338 &self,
339 identity_id: Identifier,
340 bump_first: bool,
341 settings: Option<PutSettings>,
342 ) -> Result<IdentityNonce, Error> {
343 let settings = settings.unwrap_or_default();
344 let nonce = self
345 .nonce_cache
346 .get_identity_nonce(self, identity_id, bump_first, &settings)
347 .await?;
348
349 tracing::trace!(
350 identity_id = %identity_id,
351 bump_first,
352 nonce,
353 "Fetched identity nonce"
354 );
355
356 Ok(nonce)
357 }
358
359 pub async fn get_identity_contract_nonce(
364 &self,
365 identity_id: Identifier,
366 contract_id: Identifier,
367 bump_first: bool,
368 settings: Option<PutSettings>,
369 ) -> Result<IdentityNonce, Error> {
370 let settings = settings.unwrap_or_default();
371 self.nonce_cache
372 .get_identity_contract_nonce(self, identity_id, contract_id, bump_first, &settings)
373 .await
374 }
375
376 pub async fn refresh_identity_nonce(&self, identity_id: &Identifier) {
380 self.nonce_cache.refresh(identity_id).await;
381 }
382
383 pub fn version<'v>(&self) -> &'v PlatformVersion {
390 match &self.inner {
391 SdkInstance::Dapi { version, .. } => version,
392 #[cfg(feature = "mocks")]
393 SdkInstance::Mock { version, .. } => version,
394 }
395 }
396
397 pub fn prove(&self) -> bool {
400 self.proofs
401 }
402
403 pub fn set_context_provider<C: ContextProvider + 'static>(&self, context_provider: C) {
411 self.context_provider
412 .swap(Some(Arc::new(Box::new(context_provider))));
413 }
414
415 pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
417 self.cancel_token.cancelled()
418 }
419
420 pub fn shutdown(&self) {
422 self.cancel_token.cancel();
423 }
424
425 pub fn address_list(&self) -> &AddressList {
427 match &self.inner {
428 SdkInstance::Dapi { dapi, .. } => dapi.address_list(),
429 #[cfg(feature = "mocks")]
430 SdkInstance::Mock { address_list, .. } => address_list,
431 }
432 }
433}
434
435pub(crate) fn verify_metadata_time(
443 metadata: &ResponseMetadata,
444 now_ms: u64,
445 tolerance_ms: u64,
446) -> Result<(), Error> {
447 let metadata_time = metadata.time_ms;
448
449 if now_ms.abs_diff(metadata_time) > tolerance_ms {
451 return Err(StaleNodeError::Time {
452 expected_timestamp_ms: now_ms,
453 received_timestamp_ms: metadata_time,
454 tolerance_ms,
455 }
456 .into());
457 }
458
459 tracing::trace!(
460 expected_time = now_ms,
461 received_time = metadata_time,
462 tolerance_ms,
463 "received response with valid time"
464 );
465 Ok(())
466}
467
468fn verify_metadata_height(
471 metadata: &ResponseMetadata,
472 tolerance: u64,
473 last_seen_height: Arc<atomic::AtomicU64>,
474) -> Result<(), Error> {
475 let mut expected_height = last_seen_height.load(Ordering::Relaxed);
476 let received_height = metadata.height;
477
478 if received_height == expected_height {
480 tracing::trace!(
481 expected_height,
482 received_height,
483 tolerance,
484 "received message has the same height as previously seen"
485 );
486 return Ok(());
487 }
488
489 if expected_height > tolerance && received_height < expected_height - tolerance {
491 return Err(StaleNodeError::Height {
492 expected_height,
493 received_height,
494 tolerance_blocks: tolerance,
495 }
496 .into());
497 }
498
499 tracing::trace!(
501 expected_height = expected_height,
502 received_height = received_height,
503 tolerance,
504 "received message with new height"
505 );
506 while let Err(stored_height) = last_seen_height.compare_exchange(
507 expected_height,
508 received_height,
509 Ordering::SeqCst,
510 Ordering::Relaxed,
511 ) {
512 if stored_height >= metadata.height {
514 break;
515 }
516 expected_height = stored_height;
517 }
518
519 Ok(())
520}
521
522#[async_trait::async_trait]
523impl DapiRequestExecutor for Sdk {
524 async fn execute<R: TransportRequest>(
525 &self,
526 request: R,
527 settings: RequestSettings,
528 ) -> ExecutionResult<R::Response, DapiClientError> {
529 match self.inner {
530 SdkInstance::Dapi { ref dapi, .. } => dapi.execute(request, settings).await,
531 #[cfg(feature = "mocks")]
532 SdkInstance::Mock { ref dapi, .. } => {
533 let dapi_guard = dapi.lock().await;
534 dapi_guard.execute(request, settings).await
535 }
536 }
537 }
538}
539
540pub struct SdkBuilder {
553 addresses: Option<AddressList>,
557 settings: Option<RequestSettings>,
558
559 network: Network,
560
561 core_ip: String,
562 core_port: u16,
563 core_user: String,
564 core_password: Zeroizing<String>,
565
566 proofs: bool,
568
569 version: &'static PlatformVersion,
571
572 #[cfg(feature = "mocks")]
574 data_contract_cache_size: NonZeroUsize,
575
576 #[cfg(feature = "mocks")]
578 token_config_cache_size: NonZeroUsize,
579
580 #[cfg(feature = "mocks")]
582 quorum_public_keys_cache_size: NonZeroUsize,
583
584 context_provider: Option<Box<dyn ContextProvider>>,
586
587 metadata_height_tolerance: Option<u64>,
592
593 metadata_time_tolerance_ms: Option<u64>,
597
598 #[cfg(feature = "mocks")]
600 dump_dir: Option<PathBuf>,
601
602 pub(crate) cancel_token: CancellationToken,
604
605 #[cfg(not(target_arch = "wasm32"))]
607 ca_certificate: Option<Certificate>,
608}
609
610impl Default for SdkBuilder {
611 fn default() -> Self {
613 Self {
614 addresses: None,
615 settings: None,
616 network: Network::Mainnet,
617 core_ip: "".to_string(),
618 core_port: 0,
619 core_password: "".to_string().into(),
620 core_user: "".to_string(),
621
622 proofs: true,
623 metadata_height_tolerance: Some(1),
624 metadata_time_tolerance_ms: None,
625
626 #[cfg(feature = "mocks")]
627 data_contract_cache_size: NonZeroUsize::new(DEFAULT_CONTRACT_CACHE_SIZE)
628 .expect("data contract cache size must be positive"),
629
630 #[cfg(feature = "mocks")]
631 token_config_cache_size: NonZeroUsize::new(DEFAULT_TOKEN_CONFIG_CACHE_SIZE)
632 .expect("token config cache size must be positive"),
633
634 #[cfg(feature = "mocks")]
635 quorum_public_keys_cache_size: NonZeroUsize::new(DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE)
636 .expect("quorum public keys cache size must be positive"),
637
638 context_provider: None,
639
640 cancel_token: CancellationToken::new(),
641
642 version: PlatformVersion::latest(),
643 #[cfg(not(target_arch = "wasm32"))]
644 ca_certificate: None,
645
646 #[cfg(feature = "mocks")]
647 dump_dir: None,
648 }
649 }
650}
651
652impl SdkBuilder {
653 pub fn with_proofs(mut self, proofs: bool) -> Self {
658 self.proofs = proofs;
659 self
660 }
661 pub fn new(addresses: AddressList) -> Self {
663 Self {
664 addresses: Some(addresses),
665 ..Default::default()
666 }
667 }
668
669 pub fn with_address_list(mut self, addresses: AddressList) -> Self {
671 self.addresses = Some(addresses);
672 self
673 }
674
675 pub fn new_mock() -> Self {
677 Self::default()
678 }
679
680 pub fn new_testnet() -> Self {
686 unimplemented!(
687 "Testnet address list not implemented yet. Use new() and provide address list."
688 )
689 }
690
691 pub fn new_mainnet() -> Self {
704 unimplemented!(
705 "Mainnet address list not implemented yet. Use new() and provide address list."
706 )
707 }
708
709 pub fn with_network(mut self, network: Network) -> Self {
713 self.network = network;
714 self
715 }
716
717 #[cfg(not(target_arch = "wasm32"))]
727 pub fn with_ca_certificate(mut self, pem_certificate: Certificate) -> Self {
728 self.ca_certificate = Some(pem_certificate);
729 self
730 }
731
732 #[cfg(not(target_arch = "wasm32"))]
737 pub fn with_ca_certificate_file(
738 self,
739 certificate_file_path: impl AsRef<Path>,
740 ) -> std::io::Result<Self> {
741 let pem = std::fs::read(certificate_file_path)?;
742 let cert = Certificate::from_pem(pem);
743
744 Ok(self.with_ca_certificate(cert))
745 }
746
747 pub fn with_settings(mut self, settings: RequestSettings) -> Self {
755 self.settings = Some(settings);
756 self
757 }
758
759 pub fn with_version(mut self, version: &'static PlatformVersion) -> Self {
765 self.version = version;
766 self
767 }
768
769 pub fn with_context_provider<C: ContextProvider + 'static>(
776 mut self,
777 context_provider: C,
778 ) -> Self {
779 self.context_provider = Some(Box::new(context_provider));
780
781 self
782 }
783
784 pub fn with_cancellation_token(mut self, cancel_token: CancellationToken) -> Self {
788 self.cancel_token = cancel_token;
789 self
790 }
791
792 pub fn with_core(mut self, ip: &str, port: u16, user: &str, password: &str) -> Self {
800 self.core_ip = ip.to_string();
801 self.core_port = port;
802 self.core_user = user.to_string();
803 self.core_password = Zeroizing::from(password.to_string());
804
805 self
806 }
807
808 pub fn with_height_tolerance(mut self, tolerance: Option<u64>) -> Self {
820 self.metadata_height_tolerance = tolerance;
821 self
822 }
823
824 pub fn with_time_tolerance(mut self, tolerance_ms: Option<u64>) -> Self {
839 self.metadata_time_tolerance_ms = tolerance_ms;
840 self
841 }
842
843 #[cfg(feature = "mocks")]
856 pub fn with_dump_dir(mut self, dump_dir: &Path) -> Self {
857 self.dump_dir = Some(dump_dir.to_path_buf());
858 self
859 }
860
861 pub fn build(self) -> Result<Sdk, Error> {
869 PlatformVersion::set_current(self.version);
870
871 let dapi_client_settings = match self.settings {
872 Some(settings) => DEFAULT_REQUEST_SETTINGS.override_by(settings),
873 None => DEFAULT_REQUEST_SETTINGS,
874 };
875
876 let sdk= match self.addresses {
877 Some(addresses) => {
879 #[allow(unused_mut)] let mut dapi = DapiClient::new(addresses, dapi_client_settings);
881 #[cfg(not(target_arch = "wasm32"))]
882 if let Some(pem) = self.ca_certificate {
883 dapi = dapi.with_ca_certificate(pem);
884 }
885
886 #[cfg(feature = "mocks")]
887 let dapi = dapi.dump_dir(self.dump_dir.clone());
888
889 #[allow(unused_mut)] let mut sdk= Sdk{
891 network: self.network,
892 dapi_client_settings,
893 inner:SdkInstance::Dapi { dapi, version:self.version },
894 proofs:self.proofs,
895 context_provider: ArcSwapOption::new( self.context_provider.map(Arc::new)),
896 cancel_token: self.cancel_token,
897 nonce_cache: Default::default(),
898 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
900 metadata_height_tolerance: self.metadata_height_tolerance,
901 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
902 #[cfg(feature = "mocks")]
903 dump_dir: self.dump_dir,
904 };
905 if sdk.context_provider.load().is_none() {
907 #[cfg(feature = "mocks")]
908 if !self.core_ip.is_empty() {
909 tracing::warn!(
910 "ContextProvider not set, falling back to a mock one; use SdkBuilder::with_context_provider() to set it up");
911 let mut context_provider = GrpcContextProvider::new(None,
912 &self.core_ip, self.core_port, &self.core_user, &self.core_password,
913 self.data_contract_cache_size, self.token_config_cache_size, self.quorum_public_keys_cache_size)?;
914 #[cfg(feature = "mocks")]
915 if sdk.dump_dir.is_some() {
916 context_provider.set_dump_dir(sdk.dump_dir.clone());
917 }
918 let context_provider= Arc::new(context_provider);
921 sdk.context_provider.swap(Some(Arc::new(Box::new(context_provider.clone()))));
922 context_provider.set_sdk(Some(sdk.clone()));
923 } else{
924 return Err(Error::Config(concat!(
925 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
926 "or configure Core access with SdkBuilder::with_core() to use mock context provider")
927 .to_string()));
928 }
929 #[cfg(not(feature = "mocks"))]
930 return Err(Error::Config(concat!(
931 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
932 "or enable `mocks` feature to use mock context provider")
933 .to_string()));
934 };
935
936 sdk
937 },
938 #[cfg(feature = "mocks")]
939 None => {
941 let dapi =Arc::new(Mutex::new( MockDapiClient::new()));
942 let context_provider = self.context_provider.unwrap_or_else(||{
944 let mut cp=MockContextProvider::new();
945 if let Some(ref dump_dir) = self.dump_dir {
946 cp.quorum_keys_dir(Some(dump_dir.clone()));
947 }
948 Box::new(cp)
949 }
950 );
951 let mock_sdk = MockDashPlatformSdk::new(self.version, Arc::clone(&dapi));
952 let mock_sdk = Arc::new(Mutex::new(mock_sdk));
953 let sdk= Sdk {
954 network: self.network,
955 dapi_client_settings,
956 inner:SdkInstance::Mock {
957 mock:mock_sdk.clone(),
958 dapi,
959 address_list: AddressList::new(),
960 version: self.version,
961 },
962 dump_dir: self.dump_dir.clone(),
963 proofs:self.proofs,
964 nonce_cache: Default::default(),
965 context_provider: ArcSwapOption::new(Some(Arc::new(context_provider))),
966 cancel_token: self.cancel_token,
967 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
968 metadata_height_tolerance: self.metadata_height_tolerance,
969 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
970 };
971 let mut guard = mock_sdk.try_lock().expect("mock sdk is in use by another thread and cannot be reconfigured");
972 guard.set_sdk(sdk.clone());
973 if let Some(ref dump_dir) = self.dump_dir {
974 guard.load_expectations_sync(dump_dir)?;
975 };
976
977 sdk
978 },
979 #[cfg(not(feature = "mocks"))]
980 None => return Err(Error::Config("Mock mode is not available. Please enable `mocks` feature or provide address list.".to_string())),
981 };
982
983 Ok(sdk)
984 }
985}
986
987pub fn prettify_proof(proof: &Proof) -> String {
988 let config = bincode::config::standard()
989 .with_big_endian()
990 .with_no_limit();
991 let grovedb_proof: Result<GroveDBProof, DecodeError> =
992 bincode::decode_from_slice(&proof.grovedb_proof, config).map(|(a, _)| a);
993
994 let grovedb_proof_string = match grovedb_proof {
995 Ok(proof) => format!("{}", proof),
996 Err(_) => "Invalid GroveDBProof".to_string(),
997 };
998 format!(
999 "Proof {{
1000 grovedb_proof: {},
1001 quorum_hash: 0x{},
1002 signature: 0x{},
1003 round: {},
1004 block_id_hash: 0x{},
1005 quorum_type: {},
1006 }}",
1007 grovedb_proof_string,
1008 hex::encode(&proof.quorum_hash),
1009 hex::encode(&proof.signature),
1010 proof.round,
1011 hex::encode(&proof.block_id_hash),
1012 proof.quorum_type,
1013 )
1014}
1015
1016#[cfg(test)]
1017mod test {
1018 use std::sync::Arc;
1019
1020 use dapi_grpc::platform::v0::{GetIdentityRequest, ResponseMetadata};
1021 use rs_dapi_client::transport::TransportRequest;
1022 use test_case::test_matrix;
1023
1024 use crate::SdkBuilder;
1025
1026 #[test_matrix(97..102, 100, 2, false; "valid height")]
1027 #[test_case(103, 100, 2, true; "invalid height")]
1028 fn test_verify_metadata_height(
1029 expected_height: u64,
1030 received_height: u64,
1031 tolerance: u64,
1032 expect_err: bool,
1033 ) {
1034 let metadata = ResponseMetadata {
1035 height: received_height,
1036 ..Default::default()
1037 };
1038
1039 let last_seen_height = Arc::new(std::sync::atomic::AtomicU64::new(expected_height));
1040
1041 let result =
1042 super::verify_metadata_height(&metadata, tolerance, Arc::clone(&last_seen_height));
1043
1044 assert_eq!(result.is_err(), expect_err);
1045 if result.is_ok() {
1046 assert_eq!(
1047 last_seen_height.load(std::sync::atomic::Ordering::Relaxed),
1048 received_height,
1049 "previous height should be updated"
1050 );
1051 }
1052 }
1053
1054 #[test]
1055 fn cloned_sdk_verify_metadata_height() {
1056 let sdk1 = SdkBuilder::new_mock()
1057 .build()
1058 .expect("mock Sdk should be created");
1059
1060 let metadata = ResponseMetadata {
1062 height: 1,
1063 ..Default::default()
1064 };
1065
1066 let request = GetIdentityRequest::default();
1068 sdk1.verify_response_metadata(request.method_name(), &metadata)
1069 .expect("metadata should be valid");
1070
1071 assert_eq!(
1072 sdk1.metadata_last_seen_height
1073 .load(std::sync::atomic::Ordering::Relaxed),
1074 metadata.height,
1075 "initial height"
1076 );
1077
1078 let sdk2 = sdk1.clone();
1080 let sdk3 = sdk1.clone();
1081
1082 let metadata = ResponseMetadata {
1084 height: 2,
1085 ..Default::default()
1086 };
1087 let request = GetIdentityRequest::default();
1089 sdk2.verify_response_metadata(request.method_name(), &metadata)
1090 .expect("metadata should be valid");
1091
1092 assert_eq!(
1093 sdk1.metadata_last_seen_height
1094 .load(std::sync::atomic::Ordering::Relaxed),
1095 metadata.height,
1096 "first sdk should see height from second sdk"
1097 );
1098 assert_eq!(
1099 sdk3.metadata_last_seen_height
1100 .load(std::sync::atomic::Ordering::Relaxed),
1101 metadata.height,
1102 "third sdk should see height from second sdk"
1103 );
1104
1105 let metadata = ResponseMetadata {
1107 height: 3,
1108 ..Default::default()
1109 };
1110 let request = GetIdentityRequest::default();
1112 sdk3.verify_response_metadata(request.method_name(), &metadata)
1113 .expect("metadata should be valid");
1114
1115 assert_eq!(
1116 sdk1.metadata_last_seen_height
1117 .load(std::sync::atomic::Ordering::Relaxed),
1118 metadata.height,
1119 "first sdk should see height from third sdk"
1120 );
1121
1122 assert_eq!(
1123 sdk2.metadata_last_seen_height
1124 .load(std::sync::atomic::Ordering::Relaxed),
1125 metadata.height,
1126 "second sdk should see height from third sdk"
1127 );
1128
1129 let metadata = ResponseMetadata {
1131 height: 1,
1132 ..Default::default()
1133 };
1134
1135 let request = GetIdentityRequest::default();
1136 sdk1.verify_response_metadata(request.method_name(), &metadata)
1137 .expect_err("metadata should be invalid");
1138 }
1139
1140 #[test_matrix([90,91,100,109,110], 100, 10, false; "valid time")]
1141 #[test_matrix([0,89,111], 100, 10, true; "invalid time")]
1142 #[test_matrix([0,100], [0,100], 100, false; "zero time")]
1143 #[test_matrix([99,101], 100, 0, true; "zero tolerance")]
1144 fn test_verify_metadata_time(
1145 received_time: u64,
1146 now_time: u64,
1147 tolerance: u64,
1148 expect_err: bool,
1149 ) {
1150 let metadata = ResponseMetadata {
1151 time_ms: received_time,
1152 ..Default::default()
1153 };
1154
1155 let result = super::verify_metadata_time(&metadata, now_time, tolerance);
1156
1157 assert_eq!(result.is_err(), expect_err);
1158 }
1159}