dash_context_provider/
provider.rs

1use crate::error::ContextProviderError;
2use dpp::data_contract::TokenConfiguration;
3use dpp::prelude::{CoreBlockHeight, DataContract, Identifier};
4use dpp::version::PlatformVersion;
5use drive::{error::proof::ProofError, query::ContractLookupFn};
6use std::{ops::Deref, sync::Arc};
7
8#[cfg(feature = "mocks")]
9use {
10    dpp::data_contract::serialized_version::DataContractInSerializationFormat, hex::ToHex,
11    std::io::ErrorKind,
12};
13
14/// Interface between the Sdk and state of the application.
15///
16/// ContextProvider is called by the [FromProof](crate::FromProof) trait (and similar) to get information about
17/// the application and/or network state, including data contracts that might be cached by the application or
18/// quorum public keys.
19///
20/// Developers using the Dash Platform SDK should implement this trait to provide required information
21/// to the Sdk, especially implementation of [FromProof](crate::FromProof) trait.
22///
23/// A ContextProvider should be thread-safe and manage timeouts and other concurrency-related issues internally,
24/// as the [FromProof](crate::FromProof) implementations can block on ContextProvider calls.
25pub trait ContextProvider: Send + Sync {
26    /// Fetches the data contract for a specified data contract ID.
27    /// This method is used by [FromProof](crate::FromProof) implementations to fetch data contracts
28    /// referenced in proofs.
29    ///
30    /// # Arguments
31    ///
32    /// * `data_contract_id`: The ID of the data contract to fetch.
33    /// * `platform_version`: The platform version to use.
34    ///
35    /// # Returns
36    ///
37    /// * `Ok(Option<Arc<DataContract>>)`: On success, returns the data contract if it exists, or `None` if it does not.
38    ///   We use Arc to avoid copying the data contract.
39    /// * `Err(Error)`: On failure, returns an error indicating why the operation failed.
40    fn get_data_contract(
41        &self,
42        id: &Identifier,
43        platform_version: &PlatformVersion,
44    ) -> Result<Option<Arc<DataContract>>, ContextProviderError>;
45
46    /// Fetches the token configuration for a specified token ID.
47    /// This method is used by [FromProof](crate::FromProof) implementations to fetch token configurations
48    /// referenced in proofs.
49    ///
50    /// # Arguments
51    ///
52    /// * `token_id`: The ID of the token to fetch.
53    /// * `platform_version`: The platform version to use.
54    ///
55    /// # Returns
56    ///
57    /// * `Ok(Option<TokenConfiguration>)`: On success, returns the token configuration if it exists, or `None` if it does not.
58    ///   We use Arc to avoid copying the token configuration.
59    /// * `Err(Error)`: On failure, returns an error indicating why the operation failed.
60    fn get_token_configuration(
61        &self,
62        token_id: &Identifier,
63    ) -> Result<Option<TokenConfiguration>, ContextProviderError>;
64
65    /// Fetches the public key for a specified quorum.
66    ///
67    /// # Arguments
68    ///
69    /// * `quorum_type`: The type of the quorum.
70    /// * `quorum_hash`: The hash of the quorum. This is used to determine which quorum's public key to fetch.
71    /// * `core_chain_locked_height`: Core chain locked height for which the quorum must be valid
72    ///
73    /// # Returns
74    ///
75    /// * `Ok(Vec<u8>)`: On success, returns a byte vector representing the public key of the quorum.
76    /// * `Err(Error)`: On failure, returns an error indicating why the operation failed.
77    fn get_quorum_public_key(
78        &self,
79        quorum_type: u32,
80        quorum_hash: [u8; 32], // quorum hash is 32 bytes
81        core_chain_locked_height: u32,
82    ) -> Result<[u8; 48], ContextProviderError>; // public key is 48 bytes
83
84    /// Gets the platform activation height from core. Once this has happened this can be hardcoded.
85    ///
86    /// # Returns
87    ///
88    /// * `Ok(CoreBlockHeight)`: On success, returns the platform activation height as defined by mn_rr
89    /// * `Err(Error)`: On failure, returns an error indicating why the operation failed.
90    fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError>;
91}
92
93impl<C: AsRef<dyn ContextProvider> + Send + Sync> ContextProvider for C {
94    fn get_data_contract(
95        &self,
96        id: &Identifier,
97        platform_version: &PlatformVersion,
98    ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
99        self.as_ref().get_data_contract(id, platform_version)
100    }
101
102    fn get_token_configuration(
103        &self,
104        token_id: &Identifier,
105    ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
106        self.as_ref().get_token_configuration(token_id)
107    }
108
109    fn get_quorum_public_key(
110        &self,
111        quorum_type: u32,
112        quorum_hash: [u8; 32],
113        core_chain_locked_height: u32,
114    ) -> Result<[u8; 48], ContextProviderError> {
115        self.as_ref()
116            .get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height)
117    }
118
119    fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
120        self.as_ref().get_platform_activation_height()
121    }
122}
123
124impl<T: ContextProvider> ContextProvider for std::sync::Mutex<T>
125where
126    Self: Sync + Send,
127{
128    fn get_data_contract(
129        &self,
130        id: &Identifier,
131        platform_version: &PlatformVersion,
132    ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
133        let lock = self.lock().expect("lock poisoned");
134        lock.get_data_contract(id, platform_version)
135    }
136
137    fn get_token_configuration(
138        &self,
139        token_id: &Identifier,
140    ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
141        let lock = self.lock().expect("lock poisoned");
142        lock.get_token_configuration(token_id)
143    }
144
145    fn get_quorum_public_key(
146        &self,
147        quorum_type: u32,
148        quorum_hash: [u8; 32], // quorum hash is 32 bytes
149        core_chain_locked_height: u32,
150    ) -> Result<[u8; 48], ContextProviderError> {
151        let lock = self.lock().expect("lock poisoned");
152        lock.get_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height)
153    }
154
155    fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
156        let lock = self.lock().expect("lock poisoned");
157        lock.get_platform_activation_height()
158    }
159}
160
161/// A trait that provides a function that can be used to look up a [DataContract] by its [Identifier].
162///
163/// This trait is automatically implemented for any type that implements [ContextProvider].
164/// It is used internally by the Drive proof verification functions to look up data contracts.
165pub trait DataContractProvider: Send + Sync {
166    /// Returns [ContractLookupFn] function that can be used to look up a [DataContract] by its [Identifier].
167    fn as_contract_lookup_fn<'a>(
168        &'a self,
169        platform_version: &'a PlatformVersion,
170    ) -> Box<ContractLookupFn<'a>>;
171}
172impl<C: ContextProvider + ?Sized> DataContractProvider for C {
173    /// Returns function that uses [ContextProvider] to provide a [DataContract] to Drive proof verification functions
174    fn as_contract_lookup_fn<'a>(
175        &'a self,
176        platform_version: &'a PlatformVersion,
177    ) -> Box<ContractLookupFn<'a>> {
178        let f = |id: &Identifier| -> Result<Option<Arc<DataContract>>, drive::error::Error> {
179            self.get_data_contract(id, platform_version).map_err(|e| {
180                drive::error::Error::Proof(ProofError::ErrorRetrievingContract(e.to_string()))
181            })
182        };
183
184        Box::new(f)
185    }
186}
187
188/// Mock ContextProvider that can read quorum keys from files.
189///
190/// Use [dash_sdk::SdkBuilder::with_dump_dir()] to generate quorum keys files.
191#[cfg(feature = "mocks")]
192#[derive(Debug)]
193pub struct MockContextProvider {
194    quorum_keys_dir: Option<std::path::PathBuf>,
195}
196
197#[cfg(feature = "mocks")]
198impl MockContextProvider {
199    /// Create a new instance of [MockContextProvider].
200    ///
201    /// This instance can be used to read quorum keys from files.
202    /// You need to configure quorum keys dir using
203    /// [MockContextProvider::quorum_keys_dir()](MockContextProvider::quorum_keys_dir())
204    /// before using this instance.
205    ///
206    /// In future, we may add more methods to this struct to allow setting expectations.
207    pub fn new() -> Self {
208        Self {
209            quorum_keys_dir: None,
210        }
211    }
212
213    /// Set the directory where quorum keys are stored.
214    ///
215    /// This directory should contain quorum keys files generated using [dash_sdk::SdkBuilder::with_dump_dir()].
216    pub fn quorum_keys_dir(&mut self, quorum_keys_dir: Option<std::path::PathBuf>) {
217        self.quorum_keys_dir = quorum_keys_dir;
218    }
219}
220
221#[cfg(feature = "mocks")]
222impl Default for MockContextProvider {
223    fn default() -> Self {
224        Self::new()
225    }
226}
227
228#[cfg(feature = "mocks")]
229impl ContextProvider for MockContextProvider {
230    /// Mock implementation of [ContextProvider] that returns keys from files saved on disk.
231    ///
232    /// Use [dash_sdk::SdkBuilder::with_dump_dir()] to generate quorum keys files.
233    fn get_quorum_public_key(
234        &self,
235        quorum_type: u32,
236        quorum_hash: [u8; 32],
237        _core_chain_locked_height: u32,
238    ) -> Result<[u8; 48], ContextProviderError> {
239        let path = match &self.quorum_keys_dir {
240            Some(p) => p,
241            None => return Err(ContextProviderError::Config("dump dir not set".to_string())),
242        };
243
244        let file = path.join(format!(
245            "quorum_pubkey-{}-{}.json",
246            quorum_type,
247            quorum_hash.encode_hex::<String>()
248        ));
249
250        let f = match std::fs::File::open(&file) {
251            Ok(f) => f,
252            Err(e) => {
253                return Err(ContextProviderError::InvalidQuorum(format!(
254                    "cannot load quorum key file {}: {}",
255                    file.to_string_lossy(),
256                    e
257                )))
258            }
259        };
260
261        let data = std::io::read_to_string(f).expect("cannot read quorum key file");
262        let key: Vec<u8> = hex::decode(data).expect("cannot parse quorum key");
263
264        Ok(key.try_into().expect("quorum key format mismatch"))
265    }
266
267    fn get_data_contract(
268        &self,
269        data_contract_id: &Identifier,
270        platform_version: &PlatformVersion,
271    ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
272        let path = match &self.quorum_keys_dir {
273            Some(p) => p,
274            None => return Err(ContextProviderError::Config("dump dir not set".to_string())),
275        };
276
277        let file = path.join(format!(
278            "data_contract-{}.json",
279            data_contract_id.encode_hex::<String>()
280        ));
281
282        let f = match std::fs::File::open(&file) {
283            Ok(f) => f,
284            Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None),
285            Err(e) => {
286                return Err(ContextProviderError::DataContractFailure(format!(
287                    "cannot load data contract file {}: {}",
288                    file.to_string_lossy(),
289                    e
290                )))
291            }
292        };
293
294        let serialized_form: DataContractInSerializationFormat = serde_json::from_reader(f)
295            .map_err(|e| {
296                ContextProviderError::DataContractFailure(format!(
297                    "cannot deserialized data contract with id {}: {}",
298                    data_contract_id, e
299                ))
300            })?;
301        let dc = DataContract::try_from_platform_versioned(
302            serialized_form,
303            false,
304            &mut vec![],
305            platform_version,
306        )
307        .map_err(|e| {
308            ContextProviderError::DataContractFailure(format!(
309                "cannot use serialized version of data contract with id {}: {}",
310                data_contract_id, e
311            ))
312        })?;
313
314        Ok(Some(Arc::new(dc)))
315    }
316
317    fn get_token_configuration(
318        &self,
319        _token_id: &Identifier,
320    ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
321        // Token configuration files are never generated
322        Ok(None)
323    }
324
325    fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
326        Ok(1320) // This is the default activation height for a Regtest network
327    }
328}
329
330// the trait `std::convert::AsRef<(dyn drive_proof_verifier::ContextProvider + 'static)>`
331// is not implemented for `std::sync::Arc<mock::provider::GrpcContextProvider<'_>>`
332impl<'a, T: ContextProvider + 'a> AsRef<dyn ContextProvider + 'a> for Arc<T> {
333    fn as_ref(&self) -> &(dyn ContextProvider + 'a) {
334        self.deref()
335    }
336}