1use super::MockResponse;
5use crate::sdk::{verifier_version, ProofResponseMetadata};
6use crate::{
7 platform::{
8 types::{evonode::EvoNode, identity::IdentityRequest},
9 Fetch, FetchMany, Query,
10 },
11 sync::block_on,
12 Error, Sdk,
13};
14use arc_swap::ArcSwapOption;
15use dapi_grpc::platform::v0::{Proof, ResponseMetadata};
16use dapi_grpc::{
17 mock::Mockable,
18 platform::v0::{self as proto},
19};
20use dash_context_provider::{ContextProvider, ContextProviderError};
21use dpp::dashcore::Network;
22use dpp::version::PlatformVersion;
23use drive_proof_verifier::FromProof;
24use rs_dapi_client::mock::MockError;
25use rs_dapi_client::{
26 mock::{Key, MockDapiClient},
27 transport::TransportRequest,
28 DapiClient, DumpData, ExecutionResponse,
29};
30use std::{collections::BTreeMap, convert::identity, path::PathBuf, sync::Arc};
31use tokio::sync::{Mutex, OwnedMutexGuard};
32
33#[derive(Debug)]
44pub struct MockDashPlatformSdk {
45 from_proof_expectations: BTreeMap<Key, Vec<u8>>,
46 dapi: Arc<Mutex<MockDapiClient>>,
47 sdk: ArcSwapOption<Sdk>,
48}
49
50impl MockDashPlatformSdk {
51 pub async fn request_count<R: Mockable>(&self, request: &R) -> usize {
53 self.dapi.lock().await.request_count(request)
54 }
55
56 pub fn prove(&self) -> bool {
62 if let Some(sdk) = self.sdk.load().as_ref() {
63 sdk.prove()
64 } else {
65 panic!("sdk must be set when creating mock ")
66 }
67 }
68
69 pub(crate) fn new(dapi: Arc<Mutex<MockDapiClient>>) -> Self {
75 Self {
76 from_proof_expectations: Default::default(),
77 dapi,
78 sdk: ArcSwapOption::new(None),
79 }
80 }
81
82 pub(crate) fn set_sdk(&mut self, sdk: Sdk) {
83 self.sdk.store(Some(Arc::new(sdk)));
84 }
85
86 pub(crate) fn version<'v>(&self) -> &'v PlatformVersion {
96 if let Some(sdk) = self.sdk.load().as_ref() {
97 sdk.version()
98 } else {
99 panic!("sdk must be set when creating mock ")
100 }
101 }
102
103 #[deprecated(since = "1.4.0", note = "use load_expectations_sync")]
107 pub async fn load_expectations<P: AsRef<std::path::Path> + Send + 'static>(
108 &mut self,
109 dir: P,
110 ) -> Result<&mut Self, Error> {
111 self.load_expectations_sync(dir)
112 }
113
114 pub fn load_expectations_sync<P: AsRef<std::path::Path>>(
122 &mut self,
123 dir: P,
124 ) -> Result<&mut Self, Error> {
125 let prefix = DapiClient::DUMP_FILE_PREFIX;
126
127 let entries = dir.as_ref().read_dir().map_err(|e| {
128 Error::Config(format!(
129 "cannot load mock expectations from {}: {}",
130 dir.as_ref().display(),
131 e
132 ))
133 })?;
134
135 let files: Vec<PathBuf> = entries
136 .into_iter()
137 .filter_map(|x| x.ok())
138 .filter(|f| {
139 f.file_type().is_ok_and(|t| t.is_file())
140 && f.file_name().to_string_lossy().starts_with(prefix)
141 && f.file_name().to_string_lossy().ends_with(".json")
142 })
143 .map(|f| f.path())
144 .collect();
145
146 let mut dapi = block_on(self.dapi.clone().lock_owned())?;
147
148 for filename in &files {
149 let basename = filename.file_name().unwrap().to_str().unwrap();
150 let request_type = basename.split('_').nth(1).unwrap_or_default();
151
152 match request_type {
153 "GetDocumentsRequest" => {
154 load_expectation::<proto::GetDocumentsRequest>(&mut dapi, filename)?
155 }
156 "GetEpochsInfoRequest" => {
157 load_expectation::<proto::GetEpochsInfoRequest>(&mut dapi, filename)?
158 }
159 "GetDataContractRequest" => {
160 load_expectation::<proto::GetDataContractRequest>(&mut dapi, filename)?
161 }
162 "GetDataContractsRequest" => {
163 load_expectation::<proto::GetDataContractsRequest>(&mut dapi, filename)?
164 }
165 "GetDataContractsByRangeRequest" => {
166 load_expectation::<proto::GetDataContractsByRangeRequest>(&mut dapi, filename)?
167 }
168 "GetDataContractsLatestVersionsRequest" => load_expectation::<
169 proto::GetDataContractsLatestVersionsRequest,
170 >(&mut dapi, filename)?,
171 "GetDataContractHistoryRequest" => {
172 load_expectation::<proto::GetDataContractHistoryRequest>(&mut dapi, filename)?
173 }
174 "GetContractGroupInfoRequest" => {
175 load_expectation::<proto::GetContractGroupInfoRequest>(&mut dapi, filename)?
176 }
177 "GetContractGroupMembersRequest" => {
178 load_expectation::<proto::GetContractGroupMembersRequest>(&mut dapi, filename)?
179 }
180 "GetContractGroupsForContractRequest" => load_expectation::<
181 proto::GetContractGroupsForContractRequest,
182 >(&mut dapi, filename)?,
183 "GetContractModerationStatusRequest" => load_expectation::<
184 proto::GetContractModerationStatusRequest,
185 >(&mut dapi, filename)?,
186 "GetContractModerationEntriesRequest" => load_expectation::<
187 proto::GetContractModerationEntriesRequest,
188 >(&mut dapi, filename)?,
189 "GetContractDocumentRemovalsRequest" => load_expectation::<
190 proto::GetContractDocumentRemovalsRequest,
191 >(&mut dapi, filename)?,
192 "GetContractTeamActionsRequest" => {
193 load_expectation::<proto::GetContractTeamActionsRequest>(&mut dapi, filename)?
194 }
195 "GetContractTeamActionSignersRequest" => load_expectation::<
196 proto::GetContractTeamActionSignersRequest,
197 >(&mut dapi, filename)?,
198 "GetContractModerationActionCountsRequest" => {
199 load_expectation::<proto::GetContractModerationActionCountsRequest>(
200 &mut dapi, filename,
201 )?
202 }
203 "GetContractFeePotsRequest" => {
204 load_expectation::<proto::GetContractFeePotsRequest>(&mut dapi, filename)?
205 }
206 "GetDocumentHistoryRequest" => {
207 load_expectation::<proto::GetDocumentHistoryRequest>(&mut dapi, filename)?
208 }
209 "IdentityRequest" => load_expectation::<IdentityRequest>(&mut dapi, filename)?,
210 "GetIdentityRequest" => {
211 load_expectation::<proto::GetIdentityRequest>(&mut dapi, filename)?
212 }
213
214 "GetIdentityBalanceRequest" => {
215 load_expectation::<proto::GetIdentityBalanceRequest>(&mut dapi, filename)?
216 }
217 "GetIdentityContractNonceRequest" => {
218 load_expectation::<proto::GetIdentityContractNonceRequest>(&mut dapi, filename)?
219 }
220 "GetIdentityBalanceAndRevisionRequest" => load_expectation::<
221 proto::GetIdentityBalanceAndRevisionRequest,
222 >(&mut dapi, filename)?,
223 "GetAddressInfoRequest" => {
224 load_expectation::<proto::GetAddressInfoRequest>(&mut dapi, filename)?
225 }
226 "GetAddressesInfosRequest" => {
227 load_expectation::<proto::GetAddressesInfosRequest>(&mut dapi, filename)?
228 }
229 "GetIdentityKeysRequest" => {
230 load_expectation::<proto::GetIdentityKeysRequest>(&mut dapi, filename)?
231 }
232 "GetProtocolVersionUpgradeStateRequest" => load_expectation::<
233 proto::GetProtocolVersionUpgradeStateRequest,
234 >(&mut dapi, filename)?,
235 "GetProtocolVersionUpgradeVoteStatusRequest" => {
236 load_expectation::<proto::GetProtocolVersionUpgradeVoteStatusRequest>(
237 &mut dapi, filename,
238 )?
239 }
240 "GetContestedResourcesRequest" => {
241 load_expectation::<proto::GetContestedResourcesRequest>(&mut dapi, filename)?
242 }
243 "GetContestedResourceVoteStateRequest" => load_expectation::<
244 proto::GetContestedResourceVoteStateRequest,
245 >(&mut dapi, filename)?,
246 "GetContestedResourceVotersForIdentityRequest" => {
247 load_expectation::<proto::GetContestedResourceVotersForIdentityRequest>(
248 &mut dapi, filename,
249 )?
250 }
251 "GetContestedResourceIdentityVotesRequest" => {
252 load_expectation::<proto::GetContestedResourceIdentityVotesRequest>(
253 &mut dapi, filename,
254 )?
255 }
256 "GetVotePollsByEndDateRequest" => {
257 load_expectation::<proto::GetVotePollsByEndDateRequest>(&mut dapi, filename)?
258 }
259 "GetPrefundedSpecializedBalanceRequest" => load_expectation::<
260 proto::GetPrefundedSpecializedBalanceRequest,
261 >(&mut dapi, filename)?,
262 "GetPathElementsRequest" => {
263 load_expectation::<proto::GetPathElementsRequest>(&mut dapi, filename)?
264 }
265 "GetTotalCreditsInPlatformRequest" => load_expectation::<
266 proto::GetTotalCreditsInPlatformRequest,
267 >(&mut dapi, filename)?,
268 "GetIdentityKeysRemainingBudgetsRequest" => load_expectation::<
269 proto::GetIdentityKeysRemainingBudgetsRequest,
270 >(&mut dapi, filename)?,
271 "GetIdentityTokenBalancesRequest" => {
272 load_expectation::<proto::GetIdentityTokenBalancesRequest>(&mut dapi, filename)?
273 }
274 "GetIdentitiesTokenBalancesRequest" => load_expectation::<
275 proto::GetIdentitiesTokenBalancesRequest,
276 >(&mut dapi, filename)?,
277 "GetIdentityTokenInfosRequest" => {
278 load_expectation::<proto::GetIdentityTokenInfosRequest>(&mut dapi, filename)?
279 }
280 "GetIdentitiesTokenInfosRequest" => {
281 load_expectation::<proto::GetIdentitiesTokenInfosRequest>(&mut dapi, filename)?
282 }
283 "GetTokenStatusesRequest" => {
284 load_expectation::<proto::GetTokenStatusesRequest>(&mut dapi, filename)?
285 }
286 "GetTokenTotalSupplyRequest" => {
287 load_expectation::<proto::GetTokenTotalSupplyRequest>(&mut dapi, filename)?
288 }
289 "GetGroupInfoRequest" => {
290 load_expectation::<proto::GetGroupInfoRequest>(&mut dapi, filename)?
291 }
292 "GetGroupInfosRequest" => {
293 load_expectation::<proto::GetGroupInfosRequest>(&mut dapi, filename)?
294 }
295 "GetGroupActionsRequest" => {
296 load_expectation::<proto::GetGroupActionsRequest>(&mut dapi, filename)?
297 }
298 "GetGroupActionSignersRequest" => {
299 load_expectation::<proto::GetGroupActionSignersRequest>(&mut dapi, filename)?
300 }
301 "BroadcastStateTransitionRequest" => {
302 load_expectation::<proto::BroadcastStateTransitionRequest>(&mut dapi, filename)?
303 }
304 "WaitForStateTransitionResultRequest" => load_expectation::<
305 proto::WaitForStateTransitionResultRequest,
306 >(&mut dapi, filename)?,
307 "EvoNode" => load_expectation::<EvoNode>(&mut dapi, filename)?,
308 "GetTokenDirectPurchasePricesRequest" => load_expectation::<
309 proto::GetTokenDirectPurchasePricesRequest,
310 >(&mut dapi, filename)?,
311 "GetTokenPerpetualDistributionLastClaimRequest" => {
312 load_expectation::<proto::GetTokenPerpetualDistributionLastClaimRequest>(
313 &mut dapi, filename,
314 )?
315 }
316 "GetTokenPreProgrammedDistributionsRequest" => {
317 load_expectation::<proto::GetTokenPreProgrammedDistributionsRequest>(
318 &mut dapi, filename,
319 )?
320 }
321 "GetAddressesTrunkStateRequest" => {
322 load_expectation::<proto::GetAddressesTrunkStateRequest>(&mut dapi, filename)?
323 }
324 _ => {
325 return Err(Error::Config(format!(
326 "unknown request type {} in {}, missing match arm in load_expectations?",
327 request_type,
328 filename.display()
329 )))
330 }
331 };
332 }
333
334 Ok(self)
335 }
336
337 pub async fn expect_fetch<O: Fetch + MockResponse, Q: Query<<O as Fetch>::Query>>(
388 &mut self,
389 query: Q,
390 object: Option<O>,
391 ) -> Result<&mut Self, Error>
392 where
393 <<O as Fetch>::Request as TransportRequest>::Response: Default,
394 {
395 let (rich, wire) = self
396 .encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query, identity);
397 self.expect(&rich, wire, object).await?;
398
399 Ok(self)
400 }
401
402 pub async fn remove_fetch_expectation<O, Q>(&mut self, query: Q) -> bool
406 where
407 O: Fetch,
408 Q: Query<<O as Fetch>::Query>,
409 {
410 let (rich, wire) = self
411 .encode_rich_to_wire::<Q, <O as Fetch>::Query, <O as Fetch>::Request>(query, identity);
412 self.remove(&rich, wire).await
413 }
414
415 pub async fn expect_fetch_many<
446 K: Ord,
447 O: FetchMany<K, R>,
448 Q: Query<<O as FetchMany<K, R>>::Query>,
449 R,
450 >(
451 &mut self,
452 query: Q,
453 objects: Option<R>,
454 ) -> Result<&mut Self, Error>
455 where
456 R: FromIterator<(K, Option<O>)>
457 + MockResponse
458 + FromProof<
459 <O as FetchMany<K, R>>::Query,
460 Request = <O as FetchMany<K, R>>::Query,
461 Response = <<O as FetchMany<K, R>>::Request as TransportRequest>::Response,
462 > + Sync
463 + Send
464 + Default,
465 <<O as FetchMany<K, R>>::Request as TransportRequest>::Response: Default,
466 {
467 let (rich, wire) = self
468 .encode_rich_to_wire::<Q, <O as FetchMany<K, R>>::Query, <O as FetchMany<K, R>>::Request>(
469 query,
470 <O as FetchMany<K, R>>::prepare_query,
471 );
472 self.expect(&rich, wire, objects).await?;
473
474 Ok(self)
475 }
476
477 fn encode_rich_to_wire<Q, R, W>(&self, query: Q, prepare: impl FnOnce(R) -> R) -> (R, W)
488 where
489 Q: Query<R>,
490 R: Query<W> + Mockable,
491 W: TransportRequest,
492 {
493 let sdk_guard = self.sdk.load();
494 let sdk = sdk_guard
495 .as_ref()
496 .expect("sdk must be set when creating mock");
497 let settings = sdk.query_settings();
498 let rich: R = prepare(query.query(&settings).expect("query must be correct"));
499 let wire: W = rich.query(&settings).expect("wire encoding must succeed");
500 (rich, wire)
501 }
502
503 async fn expect<R: Mockable + std::fmt::Debug, W: TransportRequest, O: MockResponse>(
511 &mut self,
512 rich_request: &R,
513 wire_request: W,
514 returned_object: Option<O>,
515 ) -> Result<(), Error>
516 where
517 W::Response: Default,
518 {
519 let key = Key::new(rich_request);
520
521 if self.from_proof_expectations.contains_key(&key) {
522 return Err(MockError::MockExpectationConflict(format!(
523 "proof expectation key {} already defined for {} request: {:?}",
524 key,
525 std::any::type_name::<R>(),
526 rich_request
527 ))
528 .into());
529 }
530
531 self.from_proof_expectations
532 .insert(key, returned_object.mock_serialize(self));
533
534 let mut dapi_guard = self.dapi.lock().await;
535 dapi_guard.expect(
536 &wire_request,
537 &Ok(ExecutionResponse {
538 inner: Default::default(),
539 retries: 0,
540 address: "http://127.0.0.1".parse().expect("failed to parse address"),
541 }),
542 )?;
543
544 Ok(())
545 }
546
547 async fn remove<R: Mockable, W: TransportRequest>(
549 &mut self,
550 rich_request: &R,
551 wire_request: W,
552 ) -> bool {
553 let key = Key::new(rich_request);
554 let removed_from_proof = self.from_proof_expectations.remove(&key).is_some();
555
556 let mut dapi_guard = self.dapi.lock().await;
557 let removed_from_dapi = dapi_guard.remove(&wire_request);
558
559 removed_from_proof || removed_from_dapi
560 }
561
562 #[allow(clippy::type_complexity)]
567 pub(crate) fn parse_proof_with_metadata<I, O: FromProof<I>>(
568 &self,
569 request: O::Request,
570 response: O::Response,
571 platform_version: &'static PlatformVersion,
572 ) -> Result<((Option<O>, ResponseMetadata, Proof), bool), drive_proof_verifier::Error>
573 where
574 O::Request: Mockable,
575 O::Response: ProofResponseMetadata,
576 Option<O>: MockResponse,
577 {
579 let key = Key::new(&request);
580
581 let data = match self.from_proof_expectations.get(&key) {
582 Some(d) => (
586 (
587 Option::<O>::mock_deserialize(self, d),
588 ResponseMetadata {
589 protocol_version: dpp::version::LATEST_VERSION,
590 ..Default::default()
591 },
592 Proof::default(),
593 ),
594 true,
595 ),
596 None => {
597 let platform_version = verifier_version(
598 platform_version,
599 response.response_metadata()?.protocol_version,
600 );
601 let provider = self.context_provider()
602 .ok_or(ContextProviderError::InvalidQuorum(
603 "expectation not found and quorum info provider not initialized with sdk.mock().quorum_info_dir()".to_string()
604 ))?;
605 (
606 O::maybe_from_proof_with_metadata(
607 request,
608 response,
609 Network::Regtest,
610 platform_version,
611 &provider,
612 )?,
613 false,
614 )
615 }
616 };
617
618 Ok(data)
619 }
620 fn context_provider(&self) -> Option<impl ContextProvider> {
622 if let Some(sdk) = self.sdk.load_full() {
623 sdk.clone().context_provider()
624 } else {
625 None
626 }
627 }
628}
629
630fn load_expectation<T: TransportRequest>(
636 dapi_guard: &mut OwnedMutexGuard<MockDapiClient>,
637 path: &PathBuf,
638) -> Result<(), Error> {
639 let data = DumpData::<T>::load(path)
640 .map_err(|e| {
641 Error::Config(format!(
642 "cannot load mock expectations from {}: {}",
643 path.display(),
644 e
645 ))
646 })?
647 .deserialize();
648 dapi_guard.expect(&data.0, &data.1)?;
649 Ok(())
650}