Skip to main content

dash_sdk/mock/
sdk.rs

1//! Mocking mechanisms for Dash Platform SDK.
2//!
3//! See [MockDashPlatformSdk] for more details.
4use 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/// Mechanisms to mock Dash Platform SDK.
34///
35/// This object is returned by [Sdk::mock()](crate::Sdk::mock()) and is used to define mock expectations.
36///
37/// Use [MockDashPlatformSdk::expect_fetch_many()] to define expectations for [FetchMany] requests
38/// and [MockDashPlatformSdk::expect_fetch()] for [Fetch] requests.
39///
40/// ## Panics
41///
42/// Can panic on errors.
43#[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    /// Number of transport executions of this exact request, including failures.
52    pub async fn request_count<R: Mockable>(&self, request: &R) -> usize {
53        self.dapi.lock().await.request_count(request)
54    }
55
56    /// Returns true when requests should use proofs.
57    ///
58    /// ## Panics
59    ///
60    /// Panics when sdk is not set during initialization.
61    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    /// Create new mock SDK.
70    ///
71    /// ## Note
72    ///
73    /// You have to call [MockDashPlatformSdk::set_sdk()] to set sdk, otherwise Mock SDK will panic.
74    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    /// Returns the current `PlatformVersion` from the outer [`Sdk`]'s
87    /// auto-detect-aware atomic. Both request-encode (`sdk.query_settings()`)
88    /// and proof-decode (`parse_proof_with_metadata`) read through this
89    /// single source, so a mock ratchet from response metadata is visible
90    /// to both paths.
91    ///
92    /// ## Panics
93    ///
94    /// Panics when sdk is not set during initialization.
95    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    /// Load all expectations from files in a directory asynchronously.
104    ///
105    /// See [MockDashPlatformSdk::load_expectations_sync()] for more details.
106    #[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    /// Load all expectations from files in a directory.
115    ///
116    ///
117    /// By default, mock expectations are loaded when Sdk is built with [SdkBuilder::build()](crate::SdkBuilder::build()).
118    /// This function can be used to load expectations after the Sdk is created, or use alternative location.
119    /// Expectation files must be prefixed with [DapiClient::DUMP_FILE_PREFIX] and
120    /// have `.json` extension.
121    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    /// Expect a [Fetch] request and return provided object.
338    ///
339    /// This method is used to define mock expectations for [Fetch] requests.
340    ///
341    /// ## Generic Parameters
342    ///
343    /// - `O`: Type of the object that will be returned in response to the query. Must implement [Fetch] and [MockResponse].
344    /// - `Q`: Type of the query that will be sent to Platform. Must implement [Query].
345    ///
346    /// ## Arguments
347    ///
348    /// - `query`: Query that will be sent to Platform.
349    /// - `object`: Object that will be returned in response to `query`, or None if the object is expected to not exist.
350    ///
351    /// ## Returns
352    ///
353    /// * Reference to self on success, to allow chaining
354    /// * Error when expectation cannot be set or is already defined for this request
355    ///
356    /// ## Panics
357    ///
358    /// Can panic on errors.
359    ///
360    /// ## Example
361    ///
362    /// ```no_run
363    /// # let r = tokio::runtime::Runtime::new().unwrap();
364    /// #
365    /// # r.block_on(async {
366    ///     use dash_sdk::{Sdk, platform::{Identity, Fetch, dpp::identity::accessors::IdentityGettersV0}};
367    ///
368    ///     let mut api = Sdk::new_mock();
369    ///     // Define expected response
370    ///     let expected: Identity = Identity::random_identity(1, None, api.version())
371    ///         .expect("create expected identity");
372    ///     // Define query that will be sent
373    ///     let query = expected.id();
374    ///     // Expect that in response to `query`, `expected` will be returned
375    ///     api.mock().expect_fetch(query, Some(expected.clone())).await.unwrap();
376    ///
377    ///     // Fetch the identity
378    ///     let retrieved = dpp::prelude::Identity::fetch(&api, query)
379    ///         .await
380    ///         .unwrap()
381    ///         .expect("object should exist");
382    ///
383    ///     // Check that the identity is the same as expected
384    ///     assert_eq!(retrieved, expected);
385    /// # });
386    /// ```
387    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    /// Remove previously defined expectation for a [Fetch] request.
403    ///
404    /// Returns `true` if any expectation was removed.
405    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    /// Expect a [FetchMany] request and return provided object.
416    ///
417    /// This method is used to define mock expectations for [FetchMany] requests.
418    ///
419    /// ## Generic Parameters
420    ///
421    /// - `O`: Type of the object that will be returned in response to the query.
422    ///   Must implement [FetchMany].
423    /// - `Q`: Type of the query that will be sent to Platform. Must implement [Query].
424    /// - `R`: Collection type for the results. Must implement [MockResponse].
425    ///
426    /// ## Arguments
427    ///
428    /// - `query`: Query that will be sent to Platform.
429    /// - `objects`: Collection of objects that will be returned in response to `query`, or None if no objects are expected.
430    ///
431    /// ## Returns
432    ///
433    /// * Reference to self on success, to allow chaining
434    /// * Error when expectation cannot be set or is already defined for this request
435    ///
436    /// ## Panics
437    ///
438    /// Can panic on errors.
439    ///
440    /// ## Example
441    ///
442    /// Usage example is similar to
443    /// [MockDashPlatformSdk::expect_fetch()], but the expected
444    /// object must be a vector of objects.
445    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    /// Convert the user-facing query to its rich form (`R`), prepare the owned
478    /// value, then encode its wire form (`W`) using the SDK's current `QuerySettings`.
479    /// Return `(rich, wire)` for proof and DAPI mock expectation keys.
480    ///
481    /// ## Panics
482    ///
483    /// INTENTIONAL(SEC-001): test-harness fail-fast — encoder errors
484    /// for V1-only `DocumentQuery` features against a V0
485    /// `PlatformVersion` crash the test setup loudly rather than
486    /// silently propagate. Panics also if `set_sdk` was not called.
487    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    /// Save expectations for a request.
504    ///
505    /// `rich_request` is the user-facing query (what [`FromProof`] binds to) and seeds
506    /// the proof-mock cache key. `wire_request` is the proto that flows over the wire
507    /// and seeds the DAPI executor mock. For non-versioned operations both arguments
508    /// are the same value; for documents the rich form is [`DocumentQuery`] and the
509    /// wire is [`GetDocumentsRequest`].
510    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    /// Remove expectations for a request.
548    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    /// Wrapper around [FromProof] that uses mock expectations, falling back to [FromProof] if no expectation is found.
563    ///
564    /// The flag is `true` when an expectation answered, so the metadata was made up
565    /// rather than verified.
566    #[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        // O: FromProof<<O as FromProof<I>>::Request>,
578    {
579        let key = Key::new(&request);
580
581        let data = match self.from_proof_expectations.get(&key) {
582            // Report the latest protocol version so the proof path's ratchet
583            // (`maybe_update_protocol_version`) fires as it would against a real
584            // network; `default()` reports 0, which the ratchet ignores.
585            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    /// Return context provider implementation defined for upstream Sdk object.
621    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
630/// Load expectation from file and save it to `dapi_guard` mock Dapi client.
631///
632/// This function is used to load expectations from files in a directory.
633/// It is implemented without reference to the `MockDashPlatformSdk` object
634/// to make it easier to use in async context.
635fn 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}