drive_proof_verifier/proof/
identity_token_balance.rs1use crate::error::MapGroveDbError;
2use crate::types::identity_token_balance::{IdentitiesTokenBalances, IdentityTokenBalances};
3use crate::verify::verify_tenderdash_proof;
4use crate::{ContextProvider, Error, FromProof};
5use dapi_grpc::platform::v0::{
6 get_identities_token_balances_request, get_identity_token_balances_request,
7 GetIdentitiesTokenBalancesRequest, GetIdentitiesTokenBalancesResponse,
8 GetIdentityTokenBalancesRequest, GetIdentityTokenBalancesResponse, Proof, ResponseMetadata,
9};
10use dapi_grpc::platform::VersionedGrpcResponse;
11use dpp::dashcore::Network;
12use dpp::version::PlatformVersion;
13use drive::drive::Drive;
14
15impl FromProof<GetIdentityTokenBalancesRequest> for IdentityTokenBalances {
16 type Request = GetIdentityTokenBalancesRequest;
17 type Response = GetIdentityTokenBalancesResponse;
18
19 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
20 request: I,
21 response: O,
22 _network: Network,
23 platform_version: &PlatformVersion,
24 provider: &'a dyn ContextProvider,
25 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
26 where
27 Self: Sized + 'a,
28 {
29 let request: Self::Request = request.into();
30 let response: Self::Response = response.into();
31
32 let (token_ids, identity_id) = match request.version.ok_or(Error::EmptyVersion)? {
33 get_identity_token_balances_request::Version::V0(v0) => {
34 let identity_id =
35 <[u8; 32]>::try_from(v0.identity_id).map_err(|_| Error::RequestError {
36 error: "can't convert identity_id to [u8; 32]".to_string(),
37 })?;
38
39 let token_ids = v0
40 .token_ids
41 .into_iter()
42 .map(<[u8; 32]>::try_from)
43 .collect::<Result<Vec<_>, _>>()
44 .map_err(|_| Error::RequestError {
45 error: "can't convert token_id to [u8; 32]".to_string(),
46 })?;
47
48 (token_ids, identity_id)
49 }
50 };
51
52 let metadata = response
53 .metadata()
54 .or(Err(Error::EmptyResponseMetadata))?
55 .clone();
56
57 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
58
59 let (root_hash, result) = Drive::verify_token_balances_for_identity_id(
60 &proof.grovedb_proof,
61 &token_ids,
62 identity_id,
63 false,
64 platform_version,
65 )
66 .map_drive_error(&proof, &metadata)?;
67
68 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
69
70 Ok((Some(result), metadata, proof))
71 }
72}
73
74impl FromProof<GetIdentitiesTokenBalancesRequest> for IdentitiesTokenBalances {
75 type Request = GetIdentitiesTokenBalancesRequest;
76 type Response = GetIdentitiesTokenBalancesResponse;
77
78 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
79 request: I,
80 response: O,
81 _network: Network,
82 platform_version: &PlatformVersion,
83 provider: &'a dyn ContextProvider,
84 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
85 where
86 Self: Sized + 'a,
87 {
88 let request: Self::Request = request.into();
89 let response: Self::Response = response.into();
90
91 let (token_id, identity_ids) = match request.version.ok_or(Error::EmptyVersion)? {
92 get_identities_token_balances_request::Version::V0(v0) => {
93 let token_id = <[u8; 32]>::try_from(v0.token_id.as_slice()).map_err(|error| {
94 Error::RequestError {
95 error: error.to_string(),
96 }
97 })?;
98
99 let identity_ids = v0
100 .identity_ids
101 .into_iter()
102 .map(<[u8; 32]>::try_from)
103 .collect::<Result<Vec<_>, _>>()
104 .map_err(|_| Error::RequestError {
105 error: "can't convert identity_id to [u8; 32]".to_string(),
106 })?;
107
108 (token_id, identity_ids)
109 }
110 };
111
112 let metadata = response
113 .metadata()
114 .or(Err(Error::EmptyResponseMetadata))?
115 .clone();
116
117 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
118
119 let (root_hash, result) = Drive::verify_token_balances_for_identity_ids(
120 &proof.grovedb_proof,
121 token_id,
122 &identity_ids,
123 false,
124 platform_version,
125 )
126 .map_drive_error(&proof, &metadata)?;
127
128 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
129
130 Ok((Some(result), metadata, proof))
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use dapi_grpc::platform::v0::get_identities_token_balances_request::{
138 GetIdentitiesTokenBalancesRequestV0, Version as IdsReqVersion,
139 };
140 use dapi_grpc::platform::v0::get_identity_token_balances_request::{
141 GetIdentityTokenBalancesRequestV0, Version as IdReqVersion,
142 };
143 use dash_context_provider::ContextProviderError;
144 use dpp::data_contract::TokenConfiguration;
145 use dpp::prelude::{CoreBlockHeight, DataContract, Identifier};
146 use std::sync::Arc;
147
148 struct UnreachableProvider;
149
150 impl ContextProvider for UnreachableProvider {
151 fn get_data_contract(
152 &self,
153 _id: &Identifier,
154 _pv: &PlatformVersion,
155 ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
156 panic!("should not be called")
157 }
158 fn get_token_configuration(
159 &self,
160 _id: &Identifier,
161 ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
162 panic!("should not be called")
163 }
164 fn get_quorum_public_key(
165 &self,
166 _qt: u32,
167 _qh: [u8; 32],
168 _h: u32,
169 ) -> Result<[u8; 48], ContextProviderError> {
170 panic!("should not be called")
171 }
172 fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
173 panic!("should not be called")
174 }
175 }
176
177 fn pv() -> &'static PlatformVersion {
178 PlatformVersion::latest()
179 }
180
181 #[test]
184 fn identity_token_balances_empty_version_on_request_missing() {
185 let request = GetIdentityTokenBalancesRequest { version: None };
186 let response = GetIdentityTokenBalancesResponse::default();
187 let err = <IdentityTokenBalances as FromProof<_>>::maybe_from_proof(
188 request,
189 response,
190 Network::Testnet,
191 pv(),
192 &UnreachableProvider,
193 )
194 .unwrap_err();
195 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
196 }
197
198 #[test]
199 fn identity_token_balances_request_error_on_bad_identity_id() {
200 let request = GetIdentityTokenBalancesRequest {
201 version: Some(IdReqVersion::V0(GetIdentityTokenBalancesRequestV0 {
202 identity_id: vec![0u8; 10], token_ids: vec![vec![0u8; 32]],
204 prove: true,
205 })),
206 };
207 let response = GetIdentityTokenBalancesResponse::default();
208 let err = <IdentityTokenBalances as FromProof<_>>::maybe_from_proof(
209 request,
210 response,
211 Network::Testnet,
212 pv(),
213 &UnreachableProvider,
214 )
215 .unwrap_err();
216 match err {
217 Error::RequestError { error } => assert!(error.contains("identity_id"), "got: {error}"),
218 other => panic!("expected RequestError, got: {other:?}"),
219 }
220 }
221
222 #[test]
223 fn identity_token_balances_request_error_on_bad_token_id() {
224 let request = GetIdentityTokenBalancesRequest {
225 version: Some(IdReqVersion::V0(GetIdentityTokenBalancesRequestV0 {
226 identity_id: vec![0u8; 32],
227 token_ids: vec![vec![0u8; 32], vec![1u8; 5]], prove: true,
229 })),
230 };
231 let response = GetIdentityTokenBalancesResponse::default();
232 let err = <IdentityTokenBalances as FromProof<_>>::maybe_from_proof(
233 request,
234 response,
235 Network::Testnet,
236 pv(),
237 &UnreachableProvider,
238 )
239 .unwrap_err();
240 match err {
241 Error::RequestError { error } => assert!(error.contains("token_id"), "got: {error}"),
242 other => panic!("expected RequestError, got: {other:?}"),
243 }
244 }
245
246 #[test]
249 fn identities_token_balances_empty_version_on_request_missing() {
250 let request = GetIdentitiesTokenBalancesRequest { version: None };
251 let response = GetIdentitiesTokenBalancesResponse::default();
252 let err = <IdentitiesTokenBalances as FromProof<_>>::maybe_from_proof(
253 request,
254 response,
255 Network::Testnet,
256 pv(),
257 &UnreachableProvider,
258 )
259 .unwrap_err();
260 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
261 }
262
263 #[test]
264 fn identities_token_balances_request_error_on_bad_token_id() {
265 let request = GetIdentitiesTokenBalancesRequest {
266 version: Some(IdsReqVersion::V0(GetIdentitiesTokenBalancesRequestV0 {
267 token_id: vec![0u8; 7], identity_ids: vec![vec![1u8; 32]],
269 prove: true,
270 })),
271 };
272 let response = GetIdentitiesTokenBalancesResponse::default();
273 let err = <IdentitiesTokenBalances as FromProof<_>>::maybe_from_proof(
274 request,
275 response,
276 Network::Testnet,
277 pv(),
278 &UnreachableProvider,
279 )
280 .unwrap_err();
281 assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
282 }
283
284 #[test]
285 fn identities_token_balances_request_error_on_bad_identity_id() {
286 let request = GetIdentitiesTokenBalancesRequest {
287 version: Some(IdsReqVersion::V0(GetIdentitiesTokenBalancesRequestV0 {
288 token_id: vec![0u8; 32],
289 identity_ids: vec![vec![1u8; 32], vec![2u8; 33]], prove: true,
291 })),
292 };
293 let response = GetIdentitiesTokenBalancesResponse::default();
294 let err = <IdentitiesTokenBalances as FromProof<_>>::maybe_from_proof(
295 request,
296 response,
297 Network::Testnet,
298 pv(),
299 &UnreachableProvider,
300 )
301 .unwrap_err();
302 match err {
303 Error::RequestError { error } => assert!(error.contains("identity_id"), "got: {error}"),
304 other => panic!("expected RequestError, got: {other:?}"),
305 }
306 }
307}