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::fetch_current_no_parameters::FetchCurrent;
9use crate::platform::transition::put_settings::PutSettings;
10use crate::platform::Identifier;
11use arc_swap::ArcSwapOption;
12use dapi_grpc::mock::Mockable;
13use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
14#[cfg(not(target_arch = "wasm32"))]
15use dapi_grpc::tonic::transport::Certificate;
16use dash_context_provider::ContextProvider;
17#[cfg(feature = "mocks")]
18use dash_context_provider::MockContextProvider;
19use dpp::bincode;
20use dpp::bincode::error::DecodeError;
21use dpp::block::extended_epoch_info::ExtendedEpochInfo;
22use dpp::dashcore::Network;
23use dpp::prelude::IdentityNonce;
24use dpp::version::PlatformVersion;
25use drive::grovedb::operations::proof::GroveDBProof;
26use drive_proof_verifier::FromProof;
27pub use http::Uri;
28#[cfg(feature = "mocks")]
29use rs_dapi_client::mock::MockDapiClient;
30pub use rs_dapi_client::Address;
31pub use rs_dapi_client::AddressBanInfo;
32pub use rs_dapi_client::AddressList;
33pub use rs_dapi_client::RequestSettings;
34use rs_dapi_client::{
35 transport::TransportRequest, DapiClient, DapiClientError, DapiRequestExecutor, ExecutionResult,
36};
37use std::fmt::Debug;
38#[cfg(feature = "mocks")]
39use std::num::NonZeroUsize;
40use std::path::Path;
41#[cfg(feature = "mocks")]
42use std::path::PathBuf;
43use std::sync::atomic::Ordering;
44use std::sync::{atomic, Arc};
45#[cfg(feature = "mocks")]
46use tokio::sync::{Mutex, MutexGuard};
47use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
48use zeroize::Zeroizing;
49
50pub const DEFAULT_CONTRACT_CACHE_SIZE: usize = 100;
52pub const DEFAULT_TOKEN_CONFIG_CACHE_SIZE: usize = 100;
54pub const DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE: usize = 100;
56const fn min_protocol_version(network: Network) -> u32 {
64 match network {
65 Network::Mainnet => dpp::version::v11::PROTOCOL_VERSION_11,
66 Network::Testnet => dpp::version::v12::PROTOCOL_VERSION_12,
67 Network::Devnet => dpp::version::v12::PROTOCOL_VERSION_12,
68 Network::Regtest => dpp::version::v12::PROTOCOL_VERSION_12,
69 }
70}
71
72const ADDRESS_STATE_TIME_TOLERANCE_MS: u64 = 31 * 60 * 1000;
74
75const DEFAULT_REQUEST_SETTINGS: RequestSettings = RequestSettings {
79 retries: Some(3),
80 timeout: None,
81 ban_failed_address: None,
82 connect_timeout: None,
83 max_decoding_message_size: None,
84};
85
86fn default_address_list_for_network(network: Network) -> AddressList {
103 if !matches!(network, Network::Mainnet | Network::Testnet) {
104 panic!("default address list is only available for mainnet and testnet");
105 }
106 let mut list = AddressList::new();
107 for seed in dash_network_seeds::evo_seeds(network) {
108 let Some(port) = seed.platform_http_port else {
109 continue;
110 };
111 let url = format!("https://{}:{}", seed.address.ip(), port);
112 if let Ok(uri) = url.parse::<Uri>() {
113 if let Ok(address) = Address::try_from(uri) {
114 list.add(address);
115 }
116 }
117 }
118 list
119}
120
121pub struct Sdk {
147 pub network: Network,
149 inner: SdkInstance,
150 proofs: bool,
154
155 nonce_cache: Arc<NonceCache>,
157
158 context_provider: ArcSwapOption<Box<dyn ContextProvider>>,
164
165 protocol_version: Arc<atomic::AtomicU32>,
167
168 version_pinned: bool,
172
173 metadata_last_seen_height: Arc<atomic::AtomicU64>,
177
178 metadata_height_tolerance: Option<u64>,
182
183 metadata_time_tolerance_ms: Option<u64>,
187
188 pub(crate) cancel_token: CancellationToken,
190
191 pub(crate) dapi_client_settings: RequestSettings,
193
194 #[cfg(feature = "mocks")]
195 dump_dir: Option<PathBuf>,
196}
197impl Clone for Sdk {
198 fn clone(&self) -> Self {
199 Self {
200 network: self.network,
201 inner: self.inner.clone(),
202 proofs: self.proofs,
203 nonce_cache: Arc::clone(&self.nonce_cache),
204 context_provider: ArcSwapOption::new(self.context_provider.load_full()),
205 cancel_token: self.cancel_token.clone(),
206 protocol_version: Arc::clone(&self.protocol_version),
207 version_pinned: self.version_pinned,
208 metadata_last_seen_height: Arc::clone(&self.metadata_last_seen_height),
209 metadata_height_tolerance: self.metadata_height_tolerance,
210 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
211 dapi_client_settings: self.dapi_client_settings,
212 #[cfg(feature = "mocks")]
213 dump_dir: self.dump_dir.clone(),
214 }
215 }
216}
217
218impl Debug for Sdk {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 match &self.inner {
221 SdkInstance::Dapi { dapi, .. } => f
222 .debug_struct("Sdk")
223 .field("dapi", dapi)
224 .field("proofs", &self.proofs)
225 .finish(),
226 #[cfg(feature = "mocks")]
227 SdkInstance::Mock { mock, .. } => f
228 .debug_struct("Sdk")
229 .field("mock", mock)
230 .field("proofs", &self.proofs)
231 .finish(),
232 }
233 }
234}
235
236#[derive(Debug, Clone)]
241enum SdkInstance {
242 Dapi {
244 dapi: DapiClient,
246 },
247 #[cfg(feature = "mocks")]
249 Mock {
250 dapi: Arc<Mutex<MockDapiClient>>,
254 mock: Arc<Mutex<MockDashPlatformSdk>>,
256 address_list: AddressList,
257 },
258}
259
260impl Sdk {
261 pub fn new_mock() -> Self {
267 SdkBuilder::default()
268 .build()
269 .expect("mock should be created")
270 }
271
272 fn freshness_criteria(&self, method_name: &str) -> (Option<u64>, Option<u64>) {
277 match method_name {
278 "get_addresses_trunk_state" | "get_addresses_branch_state" => (
279 None,
280 self.metadata_time_tolerance_ms
281 .and(Some(ADDRESS_STATE_TIME_TOLERANCE_MS)),
282 ),
283 _ => (
284 self.metadata_height_tolerance,
285 self.metadata_time_tolerance_ms,
286 ),
287 }
288 }
289
290 pub fn verify_response_metadata(
292 &self,
293 method_name: &str,
294 metadata: &ResponseMetadata,
295 ) -> Result<(), Error> {
296 let (metadata_height_tolerance, metadata_time_tolerance_ms) =
297 self.freshness_criteria(method_name);
298 if let Some(height_tolerance) = metadata_height_tolerance {
299 verify_metadata_height(
300 metadata,
301 height_tolerance,
302 Arc::clone(&(self.metadata_last_seen_height)),
303 )?;
304 };
305 if let Some(time_tolerance) = metadata_time_tolerance_ms {
306 let now = chrono::Utc::now().timestamp_millis() as u64;
307 verify_metadata_time(metadata, now, time_tolerance)?;
308 };
309
310 self.maybe_update_protocol_version(metadata.protocol_version);
311
312 Ok(())
313 }
314
315 fn maybe_update_protocol_version(&self, received_version: u32) {
321 if self.version_pinned {
322 return;
323 }
324
325 if received_version == 0 {
326 return;
327 }
328
329 let current = self.protocol_version.load(Ordering::Relaxed);
330
331 if received_version <= current {
332 return;
333 }
334
335 if PlatformVersion::get(received_version).is_err() {
337 tracing::warn!(
338 received_version,
339 current_version = current,
340 "received unknown protocol version from network; keeping current"
341 );
342 return;
343 }
344
345 let previous = self
346 .protocol_version
347 .fetch_max(received_version, Ordering::Relaxed);
348 if previous < received_version {
349 tracing::info!(
350 target: "dash_sdk::protocol_version",
351 from = previous,
352 to = received_version,
353 "ratcheting protocol version upward"
354 );
355 }
356 }
357
358 pub async fn refresh_protocol_version(&self) -> Result<u32, Error> {
381 if !self.prove() {
382 return Ok(self.protocol_version_number());
383 }
384 if !self.version_pinned {
385 if let Err(error) = ExtendedEpochInfo::fetch_current(self).await {
386 tracing::warn!(
387 target: "dash_sdk::protocol_version",
388 %error,
389 "proven protocol-version refresh failed; keeping current version \
390 (never falling back to an unverified one)"
391 );
392 }
393 }
394 Ok(self.protocol_version_number())
395 }
396
397 pub(crate) async fn parse_proof_with_metadata_and_proof<R, O: FromProof<R> + MockResponse>(
422 &self,
423 request: O::Request,
424 response: O::Response,
425 method_name: &'static str,
426 ) -> Result<(Option<O>, ResponseMetadata, Proof), Error>
427 where
428 O::Request: Mockable,
429 {
430 let provider = self
431 .context_provider()
432 .ok_or(drive_proof_verifier::Error::ContextProviderNotSet)?;
433
434 let (object, metadata, proof) = match self.inner {
435 SdkInstance::Dapi { .. } => O::maybe_from_proof_with_metadata(
436 request,
437 response,
438 self.network,
439 self.version(),
440 &provider,
441 ),
442 #[cfg(feature = "mocks")]
443 SdkInstance::Mock { ref mock, .. } => {
444 let guard = mock.lock().await;
445 guard.parse_proof_with_metadata(request, response)
446 }
447 }?;
448
449 self.verify_response_metadata(method_name, &metadata)
454 .inspect_err(|err| {
455 tracing::warn!(%err,method=method_name,"received response with stale metadata; try another server");
456 })?;
457
458 Ok((object, metadata, proof))
459 }
460
461 pub fn context_provider(&self) -> Option<impl ContextProvider> {
463 let provider_guard = self.context_provider.load();
464 let provider = provider_guard.as_ref().map(Arc::clone);
465
466 provider
467 }
468
469 #[cfg(feature = "mocks")]
480 pub fn mock(&mut self) -> MutexGuard<'_, MockDashPlatformSdk> {
481 if let Sdk {
482 inner: SdkInstance::Mock { ref mock, .. },
483 ..
484 } = self
485 {
486 mock.try_lock()
487 .expect("mock sdk is in use by another thread and cannot be reconfigured")
488 } else {
489 panic!("not a mock")
490 }
491 }
492
493 pub async fn get_identity_nonce(
498 &self,
499 identity_id: Identifier,
500 bump_first: bool,
501 settings: Option<PutSettings>,
502 ) -> Result<IdentityNonce, Error> {
503 let settings = settings.unwrap_or_default();
504 let nonce = self
505 .nonce_cache
506 .get_identity_nonce(self, identity_id, bump_first, &settings)
507 .await?;
508
509 tracing::trace!(
510 identity_id = %identity_id,
511 bump_first,
512 nonce,
513 "Fetched identity nonce"
514 );
515
516 Ok(nonce)
517 }
518
519 pub async fn get_identity_contract_nonce(
524 &self,
525 identity_id: Identifier,
526 contract_id: Identifier,
527 bump_first: bool,
528 settings: Option<PutSettings>,
529 ) -> Result<IdentityNonce, Error> {
530 let settings = settings.unwrap_or_default();
531 self.nonce_cache
532 .get_identity_contract_nonce(self, identity_id, contract_id, bump_first, &settings)
533 .await
534 }
535
536 pub async fn refresh_identity_nonce(&self, identity_id: &Identifier) {
540 self.nonce_cache.refresh(identity_id).await;
541 }
542
543 pub fn version<'v>(&self) -> &'v PlatformVersion {
551 let v = self.protocol_version.load(Ordering::Relaxed);
552 PlatformVersion::get(v).unwrap_or_else(|_| PlatformVersion::latest())
553 }
554
555 pub fn protocol_version_number(&self) -> u32 {
557 self.protocol_version.load(Ordering::Relaxed)
558 }
559
560 pub fn prove(&self) -> bool {
563 self.proofs
564 }
565
566 pub fn query_settings(&self) -> crate::platform::QuerySettings<'_> {
573 crate::platform::QuerySettings {
574 request_settings: &self.dapi_client_settings,
575 protocol_version: self.version(),
576 prove: self.prove(),
577 }
578 }
579
580 pub fn set_context_provider<C: ContextProvider + 'static>(&self, context_provider: C) {
588 self.context_provider
589 .swap(Some(Arc::new(Box::new(context_provider))));
590 }
591
592 pub fn cancelled(&self) -> WaitForCancellationFuture<'_> {
594 self.cancel_token.cancelled()
595 }
596
597 pub fn shutdown(&self) {
599 self.cancel_token.cancel();
600 }
601
602 pub fn address_list(&self) -> &AddressList {
604 match &self.inner {
605 SdkInstance::Dapi { dapi, .. } => dapi.address_list(),
606 #[cfg(feature = "mocks")]
607 SdkInstance::Mock { address_list, .. } => address_list,
608 }
609 }
610
611 pub fn address_ban_info(&self) -> Vec<AddressBanInfo> {
618 self.address_list().ban_info()
619 }
620}
621
622pub(crate) fn verify_metadata_time(
630 metadata: &ResponseMetadata,
631 now_ms: u64,
632 tolerance_ms: u64,
633) -> Result<(), Error> {
634 let metadata_time = metadata.time_ms;
635
636 if now_ms.abs_diff(metadata_time) > tolerance_ms {
638 return Err(StaleNodeError::Time {
639 expected_timestamp_ms: now_ms,
640 received_timestamp_ms: metadata_time,
641 tolerance_ms,
642 }
643 .into());
644 }
645
646 tracing::trace!(
647 expected_time = now_ms,
648 received_time = metadata_time,
649 tolerance_ms,
650 "received response with valid time"
651 );
652 Ok(())
653}
654
655fn verify_metadata_height(
658 metadata: &ResponseMetadata,
659 tolerance: u64,
660 last_seen_height: Arc<atomic::AtomicU64>,
661) -> Result<(), Error> {
662 let mut expected_height = last_seen_height.load(Ordering::Relaxed);
663 let received_height = metadata.height;
664
665 if received_height == expected_height {
667 tracing::trace!(
668 expected_height,
669 received_height,
670 tolerance,
671 "received message has the same height as previously seen"
672 );
673 return Ok(());
674 }
675
676 if expected_height > tolerance && received_height < expected_height - tolerance {
678 return Err(StaleNodeError::Height {
679 expected_height,
680 received_height,
681 tolerance_blocks: tolerance,
682 }
683 .into());
684 }
685
686 tracing::trace!(
688 expected_height = expected_height,
689 received_height = received_height,
690 tolerance,
691 "received message with new height"
692 );
693 while let Err(stored_height) = last_seen_height.compare_exchange(
694 expected_height,
695 received_height,
696 Ordering::SeqCst,
697 Ordering::Relaxed,
698 ) {
699 if stored_height >= metadata.height {
701 break;
702 }
703 expected_height = stored_height;
704 }
705
706 Ok(())
707}
708
709#[async_trait::async_trait]
710impl DapiRequestExecutor for Sdk {
711 async fn execute<R: TransportRequest>(
712 &self,
713 request: R,
714 settings: RequestSettings,
715 ) -> ExecutionResult<R::Response, DapiClientError> {
716 match self.inner {
717 SdkInstance::Dapi { ref dapi, .. } => dapi.execute(request, settings).await,
718 #[cfg(feature = "mocks")]
719 SdkInstance::Mock { ref dapi, .. } => {
720 let dapi_guard = dapi.lock().await;
721 dapi_guard.execute(request, settings).await
722 }
723 }
724 }
725}
726
727pub struct SdkBuilder {
740 addresses: Option<AddressList>,
744 settings: Option<RequestSettings>,
745
746 network: Network,
747
748 core_ip: String,
749 core_port: u16,
750 core_user: String,
751 core_password: Zeroizing<String>,
752
753 proofs: bool,
755
756 version: Option<&'static PlatformVersion>,
759
760 version_pinned: bool,
764
765 #[cfg(feature = "mocks")]
767 data_contract_cache_size: NonZeroUsize,
768
769 #[cfg(feature = "mocks")]
771 token_config_cache_size: NonZeroUsize,
772
773 #[cfg(feature = "mocks")]
775 quorum_public_keys_cache_size: NonZeroUsize,
776
777 context_provider: Option<Box<dyn ContextProvider>>,
779
780 metadata_height_tolerance: Option<u64>,
785
786 metadata_time_tolerance_ms: Option<u64>,
790
791 #[cfg(feature = "mocks")]
793 dump_dir: Option<PathBuf>,
794
795 pub(crate) cancel_token: CancellationToken,
797
798 #[cfg(not(target_arch = "wasm32"))]
800 ca_certificate: Option<Certificate>,
801}
802
803impl Default for SdkBuilder {
804 fn default() -> Self {
806 Self {
807 addresses: None,
808 settings: None,
809 network: Network::Mainnet,
810 core_ip: "".to_string(),
811 core_port: 0,
812 core_password: "".to_string().into(),
813 core_user: "".to_string(),
814
815 proofs: true,
816 metadata_height_tolerance: Some(1),
817 metadata_time_tolerance_ms: None,
818
819 #[cfg(feature = "mocks")]
820 data_contract_cache_size: NonZeroUsize::new(DEFAULT_CONTRACT_CACHE_SIZE)
821 .expect("data contract cache size must be positive"),
822
823 #[cfg(feature = "mocks")]
824 token_config_cache_size: NonZeroUsize::new(DEFAULT_TOKEN_CONFIG_CACHE_SIZE)
825 .expect("token config cache size must be positive"),
826
827 #[cfg(feature = "mocks")]
828 quorum_public_keys_cache_size: NonZeroUsize::new(DEFAULT_QUORUM_PUBLIC_KEYS_CACHE_SIZE)
829 .expect("quorum public keys cache size must be positive"),
830
831 context_provider: None,
832
833 cancel_token: CancellationToken::new(),
834
835 version: None,
839 version_pinned: false,
840 #[cfg(not(target_arch = "wasm32"))]
841 ca_certificate: None,
842
843 #[cfg(feature = "mocks")]
844 dump_dir: None,
845 }
846 }
847}
848
849impl SdkBuilder {
850 pub fn with_proofs(mut self, proofs: bool) -> Self {
855 self.proofs = proofs;
856 self
857 }
858 pub fn new(addresses: AddressList) -> Self {
860 Self {
861 addresses: Some(addresses),
862 ..Default::default()
863 }
864 }
865
866 pub fn with_address_list(mut self, addresses: AddressList) -> Self {
868 self.addresses = Some(addresses);
869 self
870 }
871
872 pub fn new_mock() -> Self {
874 Self::default()
875 }
876
877 pub fn new_testnet() -> Self {
883 let address_list = default_address_list_for_network(Network::Testnet);
884
885 Self::new(address_list).with_network(Network::Testnet)
886 }
887
888 pub fn new_mainnet() -> Self {
901 let address_list = default_address_list_for_network(Network::Mainnet);
902
903 Self::new(address_list).with_network(Network::Mainnet)
904 }
905
906 pub fn with_network(mut self, network: Network) -> Self {
910 self.network = network;
911 self
912 }
913
914 #[cfg(not(target_arch = "wasm32"))]
924 pub fn with_ca_certificate(mut self, pem_certificate: Certificate) -> Self {
925 self.ca_certificate = Some(pem_certificate);
926 self
927 }
928
929 #[cfg(not(target_arch = "wasm32"))]
934 pub fn with_ca_certificate_file(
935 self,
936 certificate_file_path: impl AsRef<Path>,
937 ) -> std::io::Result<Self> {
938 let pem = std::fs::read(certificate_file_path)?;
939 let cert = Certificate::from_pem(pem);
940
941 Ok(self.with_ca_certificate(cert))
942 }
943
944 pub fn with_settings(mut self, settings: RequestSettings) -> Self {
952 self.settings = Some(settings);
953 self
954 }
955
956 pub fn with_version(mut self, version: &'static PlatformVersion) -> Self {
967 self.version = Some(version);
968 self.version_pinned = true;
969 self
970 }
971
972 pub fn with_initial_version(mut self, version: &'static PlatformVersion) -> Self {
988 self.version = Some(version);
989 self.version_pinned = false;
990 self
991 }
992
993 pub fn with_context_provider<C: ContextProvider + 'static>(
1000 mut self,
1001 context_provider: C,
1002 ) -> Self {
1003 self.context_provider = Some(Box::new(context_provider));
1004
1005 self
1006 }
1007
1008 pub fn with_cancellation_token(mut self, cancel_token: CancellationToken) -> Self {
1012 self.cancel_token = cancel_token;
1013 self
1014 }
1015
1016 pub fn with_core(mut self, ip: &str, port: u16, user: &str, password: &str) -> Self {
1024 self.core_ip = ip.to_string();
1025 self.core_port = port;
1026 self.core_user = user.to_string();
1027 self.core_password = Zeroizing::from(password.to_string());
1028
1029 self
1030 }
1031
1032 pub fn with_height_tolerance(mut self, tolerance: Option<u64>) -> Self {
1044 self.metadata_height_tolerance = tolerance;
1045 self
1046 }
1047
1048 pub fn with_time_tolerance(mut self, tolerance_ms: Option<u64>) -> Self {
1063 self.metadata_time_tolerance_ms = tolerance_ms;
1064 self
1065 }
1066
1067 #[cfg(feature = "mocks")]
1080 pub fn with_dump_dir(mut self, dump_dir: &Path) -> Self {
1081 self.dump_dir = Some(dump_dir.to_path_buf());
1082 self
1083 }
1084
1085 pub fn build(self) -> Result<Sdk, Error> {
1093 let dapi_client_settings = match self.settings {
1094 Some(settings) => DEFAULT_REQUEST_SETTINGS.override_by(settings),
1095 None => DEFAULT_REQUEST_SETTINGS,
1096 };
1097
1098 let initial_version = self.version.unwrap_or_else(|| {
1099 PlatformVersion::get(min_protocol_version(self.network))
1100 .expect("min_protocol_version for a network must be a valid version")
1101 });
1102
1103 let sdk= match self.addresses {
1104 Some(addresses) => {
1106 #[allow(unused_mut)] let mut dapi = DapiClient::new(addresses, dapi_client_settings);
1108 #[cfg(not(target_arch = "wasm32"))]
1109 if let Some(pem) = self.ca_certificate {
1110 dapi = dapi.with_ca_certificate(pem);
1111 }
1112
1113 #[cfg(feature = "mocks")]
1114 let dapi = dapi.dump_dir(self.dump_dir.clone());
1115
1116 #[allow(unused_mut)] let mut sdk= Sdk{
1118 network: self.network,
1119 dapi_client_settings,
1120 inner:SdkInstance::Dapi { dapi },
1121 proofs:self.proofs,
1122 context_provider: ArcSwapOption::new( self.context_provider.map(Arc::new)),
1123 cancel_token: self.cancel_token,
1124 nonce_cache: Default::default(),
1125 protocol_version: Arc::new(atomic::AtomicU32::new(initial_version.protocol_version)),
1128 version_pinned: self.version_pinned,
1129 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
1131 metadata_height_tolerance: self.metadata_height_tolerance,
1132 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
1133 #[cfg(feature = "mocks")]
1134 dump_dir: self.dump_dir,
1135 };
1136 if sdk.context_provider.load().is_none() {
1138 #[cfg(feature = "mocks")]
1139 if !self.core_ip.is_empty() {
1140 tracing::warn!(
1141 "ContextProvider not set, falling back to a mock one; use SdkBuilder::with_context_provider() to set it up");
1142 let mut context_provider = GrpcContextProvider::new(None,
1143 &self.core_ip, self.core_port, &self.core_user, &self.core_password,
1144 self.data_contract_cache_size, self.token_config_cache_size, self.quorum_public_keys_cache_size)?;
1145 #[cfg(feature = "mocks")]
1146 if sdk.dump_dir.is_some() {
1147 context_provider.set_dump_dir(sdk.dump_dir.clone());
1148 }
1149 let context_provider= Arc::new(context_provider);
1152 sdk.context_provider.swap(Some(Arc::new(Box::new(context_provider.clone()))));
1153 context_provider.set_sdk(Some(sdk.clone()));
1154 } else{
1155 return Err(Error::Config(concat!(
1156 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1157 "or configure Core access with SdkBuilder::with_core() to use mock context provider")
1158 .to_string()));
1159 }
1160 #[cfg(not(feature = "mocks"))]
1161 return Err(Error::Config(concat!(
1162 "context provider is not set, configure it with SdkBuilder::with_context_provider() ",
1163 "or enable `mocks` feature to use mock context provider")
1164 .to_string()));
1165 };
1166
1167 sdk
1168 },
1169 #[cfg(feature = "mocks")]
1170 None => {
1172 let dapi =Arc::new(Mutex::new( MockDapiClient::new()));
1173 let context_provider = self.context_provider.unwrap_or_else(||{
1175 let mut cp=MockContextProvider::new();
1176 if let Some(ref dump_dir) = self.dump_dir {
1177 cp.quorum_keys_dir(Some(dump_dir.clone()));
1178 }
1179 Box::new(cp)
1180 }
1181 );
1182 let mock_sdk = MockDashPlatformSdk::new(Arc::clone(&dapi));
1183 let mock_sdk = Arc::new(Mutex::new(mock_sdk));
1184 let sdk= Sdk {
1185 network: self.network,
1186 dapi_client_settings,
1187 inner:SdkInstance::Mock {
1188 mock:mock_sdk.clone(),
1189 dapi,
1190 address_list: AddressList::new(),
1191 },
1192 dump_dir: self.dump_dir.clone(),
1193 proofs:self.proofs,
1194 nonce_cache: Default::default(),
1195 protocol_version: Arc::new(atomic::AtomicU32::new(initial_version.protocol_version)),
1196 version_pinned: self.version_pinned,
1197 context_provider: ArcSwapOption::new(Some(Arc::new(context_provider))),
1198 cancel_token: self.cancel_token,
1199 metadata_last_seen_height: Arc::new(atomic::AtomicU64::new(0)),
1200 metadata_height_tolerance: self.metadata_height_tolerance,
1201 metadata_time_tolerance_ms: self.metadata_time_tolerance_ms,
1202 };
1203 let mut guard = mock_sdk.try_lock().expect("mock sdk is in use by another thread and cannot be reconfigured");
1204 guard.set_sdk(sdk.clone());
1205 if let Some(ref dump_dir) = self.dump_dir {
1206 guard.load_expectations_sync(dump_dir)?;
1207 };
1208
1209 sdk
1210 },
1211 #[cfg(not(feature = "mocks"))]
1212 None => return Err(Error::Config("Mock mode is not available. Please enable `mocks` feature or provide address list.".to_string())),
1213 };
1214
1215 Ok(sdk)
1216 }
1217}
1218
1219pub fn prettify_proof(proof: &Proof) -> String {
1220 let config = bincode::config::standard()
1221 .with_big_endian()
1222 .with_no_limit();
1223 let grovedb_proof: Result<GroveDBProof, DecodeError> =
1224 bincode::decode_from_slice(&proof.grovedb_proof, config).map(|(a, _)| a);
1225
1226 let grovedb_proof_string = match grovedb_proof {
1227 Ok(proof) => format!("{}", proof),
1228 Err(_) => "Invalid GroveDBProof".to_string(),
1229 };
1230 format!(
1231 "Proof {{
1232 grovedb_proof: {},
1233 quorum_hash: 0x{},
1234 signature: 0x{},
1235 round: {},
1236 block_id_hash: 0x{},
1237 quorum_type: {},
1238 }}",
1239 grovedb_proof_string,
1240 hex::encode(&proof.quorum_hash),
1241 hex::encode(&proof.signature),
1242 proof.round,
1243 hex::encode(&proof.block_id_hash),
1244 proof.quorum_type,
1245 )
1246}
1247
1248#[cfg(test)]
1249mod test {
1250 use std::sync::Arc;
1251
1252 use dapi_grpc::platform::v0::{GetIdentityRequest, ResponseMetadata};
1253 use rs_dapi_client::transport::TransportRequest;
1254 use test_case::test_matrix;
1255
1256 use crate::SdkBuilder;
1257
1258 use super::{min_protocol_version, Network};
1259
1260 const MAINNET_PLATFORM_HTTP_PORT: u16 = 443;
1262 const TESTNET_PLATFORM_HTTP_PORT: u16 = 1443;
1264
1265 #[test]
1266 fn new_testnet_sources_bootstrap_from_seeds() {
1267 let builder = SdkBuilder::new_testnet();
1268 let address_list = builder
1269 .addresses
1270 .as_ref()
1271 .expect("testnet builder should configure default addresses");
1272
1273 assert_eq!(builder.network, Network::Testnet);
1274 assert!(
1275 !address_list.is_empty(),
1276 "testnet must have at least one bootstrap address"
1277 );
1278 for address in address_list.get_live_addresses() {
1279 assert_eq!(
1280 address.uri().port_u16(),
1281 Some(TESTNET_PLATFORM_HTTP_PORT),
1282 "testnet bootstrap address must use the platform HTTP port",
1283 );
1284 }
1285 }
1286
1287 #[test]
1288 fn new_mainnet_sources_bootstrap_from_seeds() {
1289 let builder = SdkBuilder::new_mainnet();
1290 let address_list = builder
1291 .addresses
1292 .as_ref()
1293 .expect("mainnet builder should configure default addresses");
1294
1295 assert_eq!(builder.network, Network::Mainnet);
1296 assert!(
1297 !address_list.is_empty(),
1298 "mainnet must have at least one bootstrap address"
1299 );
1300 for address in address_list.get_live_addresses() {
1301 assert_eq!(
1302 address.uri().port_u16(),
1303 Some(MAINNET_PLATFORM_HTTP_PORT),
1304 "mainnet bootstrap address must use the platform HTTP port",
1305 );
1306 }
1307 }
1308
1309 #[test]
1313 fn bootstrap_counts_reasonable() {
1314 let mainnet = SdkBuilder::new_mainnet()
1315 .addresses
1316 .expect("mainnet builder should configure default addresses");
1317 let testnet = SdkBuilder::new_testnet()
1318 .addresses
1319 .expect("testnet builder should configure default addresses");
1320 assert!(
1321 mainnet.len() >= 10,
1322 "expected >=10 mainnet bootstrap addresses, got {}",
1323 mainnet.len()
1324 );
1325 assert!(
1326 testnet.len() >= 10,
1327 "expected >=10 testnet bootstrap addresses, got {}",
1328 testnet.len()
1329 );
1330 }
1331
1332 #[test_matrix(97..102, 100, 2, false; "valid height")]
1333 #[test_case(103, 100, 2, true; "invalid height")]
1334 fn test_verify_metadata_height(
1335 expected_height: u64,
1336 received_height: u64,
1337 tolerance: u64,
1338 expect_err: bool,
1339 ) {
1340 let metadata = ResponseMetadata {
1341 height: received_height,
1342 ..Default::default()
1343 };
1344
1345 let last_seen_height = Arc::new(std::sync::atomic::AtomicU64::new(expected_height));
1346
1347 let result =
1348 super::verify_metadata_height(&metadata, tolerance, Arc::clone(&last_seen_height));
1349
1350 assert_eq!(result.is_err(), expect_err);
1351 if result.is_ok() {
1352 assert_eq!(
1353 last_seen_height.load(std::sync::atomic::Ordering::Relaxed),
1354 received_height,
1355 "previous height should be updated"
1356 );
1357 }
1358 }
1359
1360 #[test]
1361 fn cloned_sdk_verify_metadata_height() {
1362 let sdk1 = SdkBuilder::new_mock()
1363 .build()
1364 .expect("mock Sdk should be created");
1365
1366 let metadata = ResponseMetadata {
1368 height: 1,
1369 ..Default::default()
1370 };
1371
1372 let request = GetIdentityRequest::default();
1374 sdk1.verify_response_metadata(request.method_name(), &metadata)
1375 .expect("metadata should be valid");
1376
1377 assert_eq!(
1378 sdk1.metadata_last_seen_height
1379 .load(std::sync::atomic::Ordering::Relaxed),
1380 metadata.height,
1381 "initial height"
1382 );
1383
1384 let sdk2 = sdk1.clone();
1386 let sdk3 = sdk1.clone();
1387
1388 let metadata = ResponseMetadata {
1390 height: 2,
1391 ..Default::default()
1392 };
1393 let request = GetIdentityRequest::default();
1395 sdk2.verify_response_metadata(request.method_name(), &metadata)
1396 .expect("metadata should be valid");
1397
1398 assert_eq!(
1399 sdk1.metadata_last_seen_height
1400 .load(std::sync::atomic::Ordering::Relaxed),
1401 metadata.height,
1402 "first sdk should see height from second sdk"
1403 );
1404 assert_eq!(
1405 sdk3.metadata_last_seen_height
1406 .load(std::sync::atomic::Ordering::Relaxed),
1407 metadata.height,
1408 "third sdk should see height from second sdk"
1409 );
1410
1411 let metadata = ResponseMetadata {
1413 height: 3,
1414 ..Default::default()
1415 };
1416 let request = GetIdentityRequest::default();
1418 sdk3.verify_response_metadata(request.method_name(), &metadata)
1419 .expect("metadata should be valid");
1420
1421 assert_eq!(
1422 sdk1.metadata_last_seen_height
1423 .load(std::sync::atomic::Ordering::Relaxed),
1424 metadata.height,
1425 "first sdk should see height from third sdk"
1426 );
1427
1428 assert_eq!(
1429 sdk2.metadata_last_seen_height
1430 .load(std::sync::atomic::Ordering::Relaxed),
1431 metadata.height,
1432 "second sdk should see height from third sdk"
1433 );
1434
1435 let metadata = ResponseMetadata {
1437 height: 1,
1438 ..Default::default()
1439 };
1440
1441 let request = GetIdentityRequest::default();
1442 sdk1.verify_response_metadata(request.method_name(), &metadata)
1443 .expect_err("metadata should be invalid");
1444 }
1445
1446 fn mock_sdk_with_auto_detect(starting_version: u32) -> super::Sdk {
1449 use std::sync::atomic::Ordering;
1450
1451 let sdk = SdkBuilder::new_mock()
1452 .build()
1453 .expect("mock Sdk should be created");
1454 sdk.protocol_version
1455 .store(starting_version, Ordering::Relaxed);
1456 sdk
1457 }
1458
1459 #[test]
1460 fn test_version_update_from_metadata() {
1461 let sdk = mock_sdk_with_auto_detect(1);
1462
1463 assert_eq!(sdk.protocol_version_number(), 1);
1464
1465 let metadata = ResponseMetadata {
1466 protocol_version: 2,
1467 height: 1,
1468 ..Default::default()
1469 };
1470
1471 sdk.verify_response_metadata("test", &metadata)
1472 .expect("metadata should be valid");
1473
1474 assert_eq!(sdk.protocol_version_number(), 2);
1475 assert_eq!(sdk.version().protocol_version, 2);
1476 }
1477
1478 #[test]
1479 fn test_unknown_version_ignored() {
1480 use dpp::version::PlatformVersion;
1481
1482 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1483 let original_version = sdk.protocol_version_number();
1484
1485 let metadata = ResponseMetadata {
1486 protocol_version: 999,
1487 height: 1,
1488 ..Default::default()
1489 };
1490
1491 sdk.verify_response_metadata("test", &metadata)
1492 .expect("metadata should be valid");
1493
1494 assert_eq!(sdk.protocol_version_number(), original_version);
1495 assert_eq!(sdk.version().protocol_version, original_version);
1496 }
1497
1498 #[test]
1499 fn test_version_shared_between_clones() {
1500 let sdk = mock_sdk_with_auto_detect(1);
1501
1502 let clone = sdk.clone();
1503
1504 let metadata = ResponseMetadata {
1505 protocol_version: 2,
1506 height: 1,
1507 ..Default::default()
1508 };
1509
1510 clone
1511 .verify_response_metadata("test", &metadata)
1512 .expect("metadata should be valid");
1513
1514 assert_eq!(
1515 sdk.protocol_version_number(),
1516 2,
1517 "original should see update from clone"
1518 );
1519 }
1520
1521 #[test]
1522 fn test_version_downgrade_ignored() {
1523 let sdk = mock_sdk_with_auto_detect(2);
1524
1525 assert_eq!(sdk.protocol_version_number(), 2);
1526
1527 let metadata = ResponseMetadata {
1528 protocol_version: 1,
1529 height: 1,
1530 ..Default::default()
1531 };
1532
1533 sdk.verify_response_metadata("test", &metadata)
1534 .expect("metadata should be valid");
1535
1536 assert_eq!(sdk.protocol_version_number(), 2);
1537 }
1538
1539 #[test]
1540 fn test_version_zero_ignored() {
1541 use dpp::version::PlatformVersion;
1542
1543 let sdk = mock_sdk_with_auto_detect(PlatformVersion::latest().protocol_version);
1544 let original_version = sdk.protocol_version_number();
1545
1546 let metadata = ResponseMetadata {
1547 protocol_version: 0,
1548 height: 1,
1549 ..Default::default()
1550 };
1551
1552 sdk.verify_response_metadata("test", &metadata)
1553 .expect("metadata should be valid");
1554
1555 assert_eq!(sdk.protocol_version_number(), original_version);
1556 }
1557
1558 #[test]
1559 fn test_concurrent_updates_converge_to_highest() {
1560 use std::thread;
1561
1562 let sdk = mock_sdk_with_auto_detect(1);
1563
1564 assert_eq!(sdk.protocol_version_number(), 1);
1565
1566 let mut handles = Vec::new();
1567 for version in [2u32, 3, 2, 3, 2, 3] {
1569 let sdk_clone = sdk.clone();
1570 handles.push(thread::spawn(move || {
1571 let metadata = ResponseMetadata {
1572 protocol_version: version,
1573 height: 1,
1574 ..Default::default()
1575 };
1576 sdk_clone
1577 .verify_response_metadata("test", &metadata)
1578 .expect("metadata should be valid");
1579 }));
1580 }
1581
1582 for h in handles {
1583 h.join().expect("thread should not panic");
1584 }
1585
1586 assert_eq!(
1588 sdk.protocol_version_number(),
1589 3,
1590 "concurrent updates must converge to highest version"
1591 );
1592 }
1593
1594 #[test]
1598 fn test_explicit_version_disables_auto_detect() {
1599 use dpp::version::PlatformVersion;
1600
1601 let pinned = PlatformVersion::get(min_protocol_version(Network::Mainnet))
1604 .expect("mainnet-floor PV exists");
1605 let sdk = SdkBuilder::new_mock()
1606 .with_version(pinned)
1607 .build()
1608 .expect("mock Sdk should be created");
1609
1610 assert_eq!(sdk.protocol_version_number(), pinned.protocol_version);
1611 assert!(sdk.version_pinned);
1612
1613 let metadata = ResponseMetadata {
1615 protocol_version: dpp::version::v12::PROTOCOL_VERSION_12,
1616 height: 1,
1617 ..Default::default()
1618 };
1619
1620 sdk.verify_response_metadata("test", &metadata)
1621 .expect("metadata should be valid");
1622
1623 assert_eq!(
1624 sdk.protocol_version_number(),
1625 pinned.protocol_version,
1626 "pinned version must not be auto-updated"
1627 );
1628 }
1629
1630 #[test]
1631 fn test_with_initial_version_seeds_to_older_network_version() {
1632 use dpp::version::PlatformVersion;
1633
1634 let floor = min_protocol_version(Network::Mainnet);
1638 let initial = PlatformVersion::get(floor).expect("mainnet-floor PV exists");
1639 let sdk = SdkBuilder::new_mock()
1640 .with_initial_version(initial)
1641 .build()
1642 .expect("mock Sdk should be created");
1643
1644 assert_eq!(
1645 sdk.protocol_version_number(),
1646 floor,
1647 "with_initial_version must seed the atomic without pinning"
1648 );
1649 assert_eq!(sdk.version().protocol_version, floor);
1650 assert!(
1651 !sdk.version_pinned,
1652 "with_initial_version must keep auto-detect enabled"
1653 );
1654
1655 let metadata = ResponseMetadata {
1657 protocol_version: floor,
1658 height: 1,
1659 ..Default::default()
1660 };
1661 sdk.verify_response_metadata("test", &metadata)
1662 .expect("metadata should be valid");
1663 assert_eq!(sdk.protocol_version_number(), floor);
1664
1665 let newer = dpp::version::v12::PROTOCOL_VERSION_12;
1667 assert!(newer > floor, "ratchet target must exceed the floor");
1668 let metadata = ResponseMetadata {
1669 protocol_version: newer,
1670 height: 2,
1671 ..Default::default()
1672 };
1673 sdk.verify_response_metadata("test", &metadata)
1674 .expect("metadata should be valid");
1675 assert_eq!(sdk.protocol_version_number(), newer);
1676 }
1677
1678 #[test]
1679 fn test_with_initial_version_after_with_version_restores_auto_detect() {
1680 use dpp::version::PlatformVersion;
1681
1682 let v_latest = PlatformVersion::latest();
1689 let v_old = PlatformVersion::get(min_protocol_version(Network::Mainnet))
1690 .expect("mainnet-floor PV exists");
1691 assert!(
1692 v_old.protocol_version < v_latest.protocol_version,
1693 "v_old must be below latest so the later ratchet is observable"
1694 );
1695
1696 let sdk = SdkBuilder::new_mock()
1697 .with_version(v_latest)
1698 .with_initial_version(v_old)
1699 .build()
1700 .expect("mock Sdk should be created");
1701
1702 assert_eq!(
1703 sdk.protocol_version_number(),
1704 v_old.protocol_version,
1705 "with_initial_version must overwrite the prior with_version seed"
1706 );
1707 assert!(
1708 !sdk.version_pinned,
1709 "with_initial_version must restore auto-detect after with_version disabled it"
1710 );
1711
1712 let metadata = ResponseMetadata {
1714 protocol_version: v_latest.protocol_version,
1715 height: 1,
1716 ..Default::default()
1717 };
1718 sdk.verify_response_metadata("test", &metadata)
1719 .expect("metadata should be valid");
1720 assert_eq!(sdk.protocol_version_number(), v_latest.protocol_version);
1721 }
1722
1723 #[test]
1724 fn test_mock_version_follows_outer_sdk_atomic() {
1725 use dpp::version::PlatformVersion;
1726
1727 let v_old = PlatformVersion::get(min_protocol_version(Network::Mainnet))
1733 .expect("mainnet-floor PV exists");
1734 let v_new = PlatformVersion::latest();
1735 assert!(
1736 v_old.protocol_version < v_new.protocol_version,
1737 "v_old must be below latest so the ratchet is observable"
1738 );
1739
1740 let mut sdk = SdkBuilder::new_mock()
1741 .with_initial_version(v_old)
1742 .build()
1743 .expect("mock Sdk should be created");
1744
1745 assert_eq!(sdk.version().protocol_version, v_old.protocol_version);
1746 {
1747 let mock = sdk.mock();
1748 assert_eq!(
1749 mock.version().protocol_version,
1750 v_old.protocol_version,
1751 "mock version must mirror outer SDK before ratchet"
1752 );
1753 }
1754
1755 let metadata = ResponseMetadata {
1756 protocol_version: v_new.protocol_version,
1757 height: 1,
1758 ..Default::default()
1759 };
1760 sdk.verify_response_metadata("test", &metadata)
1761 .expect("metadata should be valid");
1762
1763 assert_eq!(sdk.version().protocol_version, v_new.protocol_version);
1764 let mock = sdk.mock();
1765 assert_eq!(
1766 mock.version().protocol_version,
1767 v_new.protocol_version,
1768 "mock version must follow outer ratchet"
1769 );
1770 }
1771
1772 #[test]
1773 fn test_default_builder_seeds_initial_protocol_version_floor() {
1774 let sdk = SdkBuilder::new_mock()
1777 .build()
1778 .expect("mock Sdk should be created");
1779
1780 let expected = min_protocol_version(Network::Mainnet);
1781 assert_eq!(
1782 sdk.protocol_version_number(),
1783 expected,
1784 "unpinned mainnet SDK must boot at the mainnet floor, not latest()"
1785 );
1786 assert_eq!(sdk.version().protocol_version, expected);
1787 assert!(
1788 !sdk.version_pinned,
1789 "default SDK must keep auto-detect enabled"
1790 );
1791 }
1792
1793 #[test]
1794 fn test_default_floor_ratchets_up_but_never_down() {
1795 let sdk = SdkBuilder::new_mock()
1796 .build()
1797 .expect("mock Sdk should be created");
1798 let floor = min_protocol_version(Network::Mainnet);
1800 assert_eq!(sdk.protocol_version_number(), floor);
1801
1802 let target = dpp::version::v12::PROTOCOL_VERSION_12;
1805 assert!(
1806 target > floor,
1807 "ratchet test target must exceed the floor; bump it if the floor reaches v12"
1808 );
1809 sdk.maybe_update_protocol_version(target);
1810 assert_eq!(
1811 sdk.protocol_version_number(),
1812 target,
1813 "auto-detect must ratchet upward from the floor"
1814 );
1815
1816 sdk.maybe_update_protocol_version(floor - 1);
1818 assert_eq!(
1819 sdk.protocol_version_number(),
1820 target,
1821 "ratchet must never downgrade below the highest observed version"
1822 );
1823 }
1824
1825 #[test]
1836 fn test_ratchet_rejects_unknown_and_non_upward_versions() {
1837 let sdk = SdkBuilder::new_mock()
1838 .build()
1839 .expect("mock Sdk should be created");
1840 let floor = min_protocol_version(Network::Mainnet);
1842 assert_eq!(sdk.protocol_version_number(), floor);
1843
1844 sdk.maybe_update_protocol_version(dpp::version::LATEST_VERSION + 1);
1846 assert_eq!(
1847 sdk.protocol_version_number(),
1848 floor,
1849 "unknown protocol version must not move the stored version"
1850 );
1851
1852 sdk.maybe_update_protocol_version(0);
1854 assert_eq!(
1855 sdk.protocol_version_number(),
1856 floor,
1857 "zero protocol version must be ignored"
1858 );
1859
1860 sdk.maybe_update_protocol_version(floor);
1862 assert_eq!(sdk.protocol_version_number(), floor);
1863
1864 sdk.maybe_update_protocol_version(floor - 1);
1866 assert_eq!(
1867 sdk.protocol_version_number(),
1868 floor,
1869 "lower known version must not downgrade the stored version"
1870 );
1871 }
1872
1873 #[test]
1876 fn test_explicit_pin_below_floor_is_preserved() {
1877 use dpp::version::PlatformVersion;
1878
1879 let floor = min_protocol_version(Network::Mainnet);
1880 let below = floor - 1;
1881 let pinned = PlatformVersion::get(below).expect("sub-floor PV exists");
1882 let sdk = SdkBuilder::new_mock()
1883 .with_version(pinned)
1884 .build()
1885 .expect("mock Sdk should be created");
1886
1887 assert_eq!(
1888 sdk.protocol_version_number(),
1889 below,
1890 "a pin below the floor must be preserved"
1891 );
1892 assert!(sdk.version_pinned);
1894 }
1895
1896 #[test]
1904 fn test_testnet_default_builder_boots_at_per_network_floor() {
1905 let sdk = SdkBuilder::new_mock()
1906 .with_network(Network::Testnet)
1907 .build()
1908 .expect("mock Sdk should be created");
1909
1910 assert_eq!(
1911 sdk.protocol_version_number(),
1912 min_protocol_version(Network::Testnet),
1913 "testnet seeds directly at its per-network floor"
1914 );
1915 assert!(!sdk.version_pinned);
1916 }
1917
1918 #[test_matrix([90,91,100,109,110], 100, 10, false; "valid time")]
1919 #[test_matrix([0,89,111], 100, 10, true; "invalid time")]
1920 #[test_matrix([0,100], [0,100], 100, false; "zero time")]
1921 #[test_matrix([99,101], 100, 0, true; "zero tolerance")]
1922 fn test_verify_metadata_time(
1923 received_time: u64,
1924 now_time: u64,
1925 tolerance: u64,
1926 expect_err: bool,
1927 ) {
1928 let metadata = ResponseMetadata {
1929 time_ms: received_time,
1930 ..Default::default()
1931 };
1932
1933 let result = super::verify_metadata_time(&metadata, now_time, tolerance);
1934
1935 assert_eq!(result.is_err(), expect_err);
1936 }
1937
1938 async fn expect_epoch_refresh(sdk: &mut super::Sdk) {
1948 use crate::platform::types::epoch::EpochQuery;
1949 use crate::platform::LimitQuery;
1950 use dpp::block::extended_epoch_info::{v0::ExtendedEpochInfoV0, ExtendedEpochInfo};
1951
1952 let query = LimitQuery {
1954 query: EpochQuery {
1955 start: None,
1956 ascending: false,
1957 },
1958 limit: Some(1),
1959 start_info: None,
1960 };
1961
1962 let epoch = ExtendedEpochInfo::from(ExtendedEpochInfoV0 {
1963 index: 0,
1964 first_block_time: 0,
1965 first_block_height: 0,
1966 first_core_block_height: 0,
1967 fee_multiplier_permille: 0,
1968 protocol_version: dpp::version::LATEST_VERSION,
1969 });
1970
1971 sdk.mock()
1972 .expect_fetch::<ExtendedEpochInfo, _>(query, Some(epoch))
1973 .await
1974 .expect("register epoch refresh expectation");
1975 }
1976
1977 #[tokio::test]
1983 async fn test_refresh_ratchets_up_via_proven_query() {
1984 let mut sdk = mock_sdk_with_auto_detect(super::min_protocol_version(Network::Mainnet));
1985 assert_eq!(
1986 sdk.protocol_version_number(),
1987 super::min_protocol_version(Network::Mainnet)
1988 );
1989
1990 expect_epoch_refresh(&mut sdk).await;
1991
1992 let resulting = sdk
1993 .refresh_protocol_version()
1994 .await
1995 .expect("refresh should succeed");
1996
1997 assert_eq!(
1998 resulting,
1999 dpp::version::LATEST_VERSION,
2000 "returned version must reflect the ratchet to the network's latest"
2001 );
2002 assert_eq!(sdk.protocol_version_number(), dpp::version::LATEST_VERSION);
2003 assert_eq!(sdk.version().protocol_version, dpp::version::LATEST_VERSION);
2004 }
2005
2006 #[tokio::test]
2011 async fn test_refresh_leaves_pinned_sdk_unchanged() {
2012 use dpp::version::PlatformVersion;
2013
2014 let pinned = PlatformVersion::get(min_protocol_version(Network::Mainnet))
2016 .expect("mainnet-floor PV exists");
2017 let sdk = SdkBuilder::new_mock()
2018 .with_version(pinned)
2019 .build()
2020 .expect("mock Sdk should be created");
2021 assert_eq!(sdk.protocol_version_number(), pinned.protocol_version);
2022 assert!(sdk.version_pinned);
2023
2024 let resulting = sdk
2027 .refresh_protocol_version()
2028 .await
2029 .expect("pinned refresh is a no-op and must not error");
2030
2031 assert_eq!(
2032 resulting, pinned.protocol_version,
2033 "pinned version must not move"
2034 );
2035 assert_eq!(sdk.protocol_version_number(), pinned.protocol_version);
2036 }
2037
2038 #[tokio::test]
2043 async fn test_refresh_query_unavailable_keeps_current_version() {
2044 let starting = min_protocol_version(Network::Mainnet);
2045 let sdk = mock_sdk_with_auto_detect(starting);
2046 assert_eq!(sdk.protocol_version_number(), starting);
2047
2048 let resulting = sdk
2049 .refresh_protocol_version()
2050 .await
2051 .expect("refresh is best-effort and must not error when the query fails");
2052
2053 assert_eq!(
2054 resulting, starting,
2055 "a failed refresh must leave the stored version untouched (no fallback)"
2056 );
2057 assert_eq!(sdk.protocol_version_number(), starting);
2058 }
2059}