1use crate::core::LowLevelDashCoreClient;
4use crate::platform::Fetch;
5use crate::sync::block_on;
6use crate::{Error, Sdk};
7use arc_swap::ArcSwapAny;
8use dash_context_provider::{ContextProvider, ContextProviderError};
9use dpp::data_contract::TokenConfiguration;
10use dpp::prelude::{CoreBlockHeight, DataContract, Identifier};
11use dpp::version::PlatformVersion;
12use std::hash::Hash;
13use std::num::NonZeroUsize;
14use std::sync::Arc;
15
16pub struct GrpcContextProvider {
20 core: LowLevelDashCoreClient,
22 sdk: ArcSwapAny<Arc<Option<Sdk>>>,
29
30 pub data_contracts_cache: Cache<Identifier, DataContract>,
34
35 pub token_configurations_cache: Cache<Identifier, TokenConfiguration>,
39
40 pub quorum_public_keys_cache: Cache<([u8; 32], u32), [u8; 48]>,
46
47 #[cfg(feature = "mocks")]
51 pub dump_dir: Option<std::path::PathBuf>,
52}
53
54impl GrpcContextProvider {
55 #[allow(clippy::too_many_arguments)]
62 pub fn new(
63 sdk: Option<Sdk>,
64 core_ip: &str,
65 core_port: u16,
66 core_user: &str,
67 core_password: &str,
68
69 data_contracts_cache_size: NonZeroUsize,
70 token_config_cache_size: NonZeroUsize,
71 quorum_public_keys_cache_size: NonZeroUsize,
72 ) -> Result<Self, Error> {
73 let core_client =
74 LowLevelDashCoreClient::new(core_ip, core_port, core_user, core_password)?;
75 Ok(Self {
76 core: core_client,
77 sdk: ArcSwapAny::new(Arc::new(sdk)),
78 data_contracts_cache: Cache::new(data_contracts_cache_size),
79 token_configurations_cache: Cache::new(token_config_cache_size),
80 quorum_public_keys_cache: Cache::new(quorum_public_keys_cache_size),
81 #[cfg(feature = "mocks")]
82 dump_dir: None,
83 })
84 }
85
86 pub fn set_sdk(&self, sdk: Option<Sdk>) {
92 self.sdk.store(Arc::new(sdk));
93 }
94 #[cfg(feature = "mocks")]
98 pub fn set_dump_dir(&mut self, dump_dir: Option<std::path::PathBuf>) {
99 self.dump_dir = dump_dir;
100 }
101
102 #[cfg(feature = "mocks")]
110 fn dump_quorum_public_key(
111 &self,
112 quorum_type: u32,
113 quorum_hash: [u8; 32],
114 _core_chain_locked_height: u32,
115 public_key: &[u8],
116 ) {
117 use hex::ToHex;
118
119 let path = match &self.dump_dir {
120 Some(p) => p,
121 None => return,
122 };
123
124 let encoded = hex::encode(public_key);
125
126 let file = path.join(format!(
127 "quorum_pubkey-{}-{}.json",
128 quorum_type,
129 quorum_hash.encode_hex::<String>()
130 ));
131
132 if let Err(e) = std::fs::write(file, encoded) {
133 tracing::warn!("Unable to write dump file {:?}: {}", path, e);
134 }
135 }
136
137 #[cfg(feature = "mocks")]
145 fn dump_data_contract(&self, data_contract: &DataContract) {
146 use dpp::data_contract::accessors::v0::DataContractV0Getters;
147 use hex::ToHex;
148
149 let path = match &self.dump_dir {
150 Some(p) => p,
151 None => return,
152 };
153 let id = data_contract.id();
154
155 let file = path.join(format!("data_contract-{}.json", id.encode_hex::<String>()));
156
157 let encoded = serde_json::to_vec(data_contract).expect("serialize data contract");
158 if let Err(e) = std::fs::write(file, encoded) {
159 tracing::warn!("Unable to write dump file {:?}: {}", path, e);
160 }
161 }
162}
163
164impl ContextProvider for GrpcContextProvider {
165 fn get_quorum_public_key(
166 &self,
167 quorum_type: u32,
168 quorum_hash: [u8; 32], core_chain_locked_height: u32,
170 ) -> Result<[u8; 48], ContextProviderError> {
171 if let Some(key) = self
172 .quorum_public_keys_cache
173 .get(&(quorum_hash, quorum_type))
174 {
175 return Ok(*key);
176 };
177
178 let key = self.core.get_quorum_public_key(quorum_type, quorum_hash)?;
179
180 self.quorum_public_keys_cache
181 .put((quorum_hash, quorum_type), key);
182
183 #[cfg(feature = "mocks")]
184 self.dump_quorum_public_key(quorum_type, quorum_hash, core_chain_locked_height, &key);
185
186 Ok(key)
187 }
188
189 fn get_data_contract(
190 &self,
191 data_contract_id: &Identifier,
192 _platform_version: &PlatformVersion,
193 ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
194 if let Some(contract) = self.data_contracts_cache.get(data_contract_id) {
195 return Ok(Some(contract));
196 };
197 let sdk_guard = self.sdk.load();
198
199 let sdk = match sdk_guard.as_ref() {
200 Some(sdk) => sdk,
201 None => {
202 tracing::warn!("data contract cache miss and no sdk provided, skipping fetch");
203 return Ok(None);
204 }
205 };
206
207 let contract_id = *data_contract_id;
208
209 let sdk_cloned = sdk.clone();
210
211 let data_contract: Option<DataContract> =
212 block_on(async move { DataContract::fetch(&sdk_cloned, contract_id).await })?
213 .map_err(|e| ContextProviderError::DataContractFailure(e.to_string()))?;
214
215 if let Some(ref dc) = data_contract {
216 self.data_contracts_cache.put(*data_contract_id, dc.clone());
217 };
218
219 #[cfg(feature = "mocks")]
220 if let Some(ref dc) = data_contract {
221 self.dump_data_contract(dc);
222 }
223
224 Ok(data_contract.map(Arc::new))
225 }
226
227 fn get_token_configuration(
228 &self,
229 token_id: &Identifier,
230 ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
231 if let Some(config) = self.token_configurations_cache.get(token_id) {
232 Ok(Some((*config).clone()))
233 } else {
234 tracing::warn!("token config cache miss");
235 Ok(None)
236 }
237 }
238
239 fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
240 self.core.get_platform_activation_height()
241 }
242}
243
244pub struct Cache<K: Hash + Eq, V> {
248 inner: std::sync::RwLock<lru::LruCache<K, Arc<V>>>,
251}
252
253impl<K: Hash + Eq, V> Cache<K, V> {
254 pub fn new(capacity: NonZeroUsize) -> Self {
256 Self {
257 inner: std::sync::RwLock::new(lru::LruCache::new(capacity)),
259 }
260 }
261
262 pub fn get(&self, k: &K) -> Option<Arc<V>> {
264 let mut guard = self.inner.write().expect("cache lock poisoned");
265 guard.get(k).map(Arc::clone)
266 }
267
268 pub fn put(&self, k: K, v: V) {
270 let mut guard = self.inner.write().expect("cache lock poisoned");
271 guard.put(k, Arc::new(v));
272 }
273}