drive_proof_verifier/
proof.rs

1pub mod groups;
2pub mod identity_token_balance;
3pub mod token_contract_info;
4pub mod token_direct_purchase;
5pub mod token_info;
6pub mod token_perpetual_distribution_last_claim;
7pub mod token_pre_programmed_distributions;
8pub mod token_status;
9pub mod token_total_supply;
10
11use crate::from_request::TryFromRequest;
12use crate::verify::verify_tenderdash_proof;
13use crate::{types::*, ContextProvider, DataContractProvider, Error};
14use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start;
15use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
16use dapi_grpc::platform::v0::get_path_elements_request::GetPathElementsRequestV0;
17use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_request::{
18    self, GetProtocolVersionUpgradeVoteStatusRequestV0,
19};
20use dapi_grpc::platform::v0::security_level_map::KeyKindRequestType as GrpcKeyKind;
21use dapi_grpc::platform::v0::{
22    get_address_info_request, get_addresses_infos_request,
23    get_contested_resource_identity_votes_request, get_data_contract_history_request, get_data_contract_request, get_data_contracts_request, get_epochs_info_request, get_evonodes_proposed_epoch_blocks_by_ids_request, get_evonodes_proposed_epoch_blocks_by_range_request, get_finalized_epoch_infos_request, get_identities_balances_request, get_identities_contract_keys_request, get_identity_balance_and_revision_request, get_identity_balance_request, get_identity_by_non_unique_public_key_hash_request,
24    get_identity_by_public_key_hash_request, get_identity_contract_nonce_request, get_identity_keys_request, get_identity_nonce_request, get_identity_request, get_path_elements_request, get_prefunded_specialized_balance_request, GetContestedResourceVotersForIdentityRequest, GetContestedResourceVotersForIdentityResponse, GetPathElementsRequest, GetPathElementsResponse, GetProtocolVersionUpgradeStateRequest, GetProtocolVersionUpgradeStateResponse, GetProtocolVersionUpgradeVoteStatusRequest, GetProtocolVersionUpgradeVoteStatusResponse, Proof, ResponseMetadata
25};
26use dapi_grpc::platform::{
27    v0::{self as platform, key_request_type, KeyRequestType as GrpcKeyType},
28    VersionedGrpcResponse,
29};
30use dpp::address_funds::PlatformAddress;
31use dpp::block::block_info::BlockInfo;
32use dpp::block::epoch::EpochIndex;
33use dpp::block::extended_epoch_info::ExtendedEpochInfo;
34use dpp::core_subsidy::NetworkCoreSubsidy;
35use dpp::dashcore::hashes::Hash;
36use dpp::dashcore::{Network, ProTxHash};
37use dpp::document::{Document, DocumentV0Getters};
38use dpp::fee::Credits;
39use dpp::identity::identities_contract_keys::IdentitiesContractKeys;
40use dpp::identity::Purpose;
41use dpp::platform_value::{self};
42use dpp::prelude::{AddressNonce, DataContract, Identifier, Identity};
43use dpp::serialization::PlatformDeserializable;
44use dpp::state_transition::proof_result::StateTransitionProofResult;
45use dpp::state_transition::StateTransition;
46use dpp::version::PlatformVersion;
47use dpp::voting::votes::Vote;
48use drive::drive::identity::identity_and_non_unique_public_key_hash_double_proof::IdentityAndNonUniquePublicKeyHashDoubleProof;
49use drive::drive::identity::key::fetch::{
50    IdentityKeysRequest, KeyKindRequestType, KeyRequestType, PurposeU8, SecurityLevelU8,
51};
52use drive::drive::Drive;
53use drive::error::proof::ProofError;
54use drive::grovedb::Error as GroveError;
55use drive::grovedb::GroveTrunkQueryResult;
56use drive::query::contested_resource_votes_given_by_identity_query::ContestedResourceVotesGivenByIdentityQuery;
57use drive::query::proposer_block_count_query::ProposerQueryType;
58use drive::query::vote_poll_contestant_votes_query::ContestedDocumentVotePollVotesDriveQuery;
59use drive::query::vote_poll_vote_state_query::ContestedDocumentVotePollDriveQuery;
60use drive::query::vote_polls_by_document_type_query::VotePollsByDocumentTypeQuery;
61use drive::query::{DriveDocumentQuery, VotePollsByEndDateDriveQuery};
62use indexmap::IndexMap;
63use std::array::TryFromSliceError;
64use std::collections::BTreeMap;
65use std::num::TryFromIntError;
66use crate::error::MapGroveDbError;
67
68/// Parse and verify the received proof and retrieve the requested object, if any.
69///
70/// Use [`FromProof::maybe_from_proof()`] or [`FromProof::from_proof()`] to parse and verify proofs received
71/// from the Dash Platform (including verification of grovedb-generated proofs and cryptographic proofs generated
72/// by Tenderdash).
73///
74/// gRPC responses, received from the Dash Platform in response to requests containing `prove: true`, contain
75/// GroveDB proof structure (including encapsulated objects) and metadata required to verify cryptographic proof
76/// generated by the Tenderdash. This trait provides methods that parse and verify the proof and retrieve the requested
77/// object (or information that the object does not exist) in one step.
78///
79/// This trait is implemented by several objects defined in [Dash Platform Protocol](dpp), like [Identity],
80/// [DataContract], [Documents], etc. It is also implemented by several helper objects from [types] module.
81pub trait FromProof<Req> {
82    /// Request type for which this trait is implemented.
83    type Request;
84    /// Response type for which this trait is implemented.
85    type Response;
86
87    /// Parse and verify the received proof and retrieve the requested object, if any.
88    ///
89    /// # Arguments
90    ///
91    /// * `request`: The request sent to the server.
92    /// * `response`: The response received from the server.
93    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
94    /// * `platform_version`: The platform version that should be used.
95    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
96    ///
97    /// # Returns
98    ///
99    /// * `Ok(Some(object, metadata))` when the requested object was found in the proof.
100    /// * `Ok(None)` when the requested object was not found in the proof; this can be interpreted as proof of non-existence.
101    ///   For collections, returns Ok(None) if none of the requested objects were found.
102    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
103    fn maybe_from_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
104        request: I,
105        response: O,
106        network: Network,
107        platform_version: &PlatformVersion,
108        provider: &'a dyn ContextProvider,
109    ) -> Result<Option<Self>, Error>
110    where
111        Self: Sized + 'a,
112    {
113        Self::maybe_from_proof_with_metadata(request, response, network, platform_version, provider)
114            .map(|maybe_result| maybe_result.0)
115    }
116
117    /// Parse and verify the received proof and retrieve the requested object, if any.
118    ///
119    /// # Arguments
120    ///
121    /// * `request`: The request sent to the server.
122    /// * `response`: The response received from the server.
123    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
124    /// * `platform_version`: The platform version that should be used.
125    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
126    ///
127    /// # Returns
128    ///
129    /// * `Ok(Some((object, metadata)))` when the requested object was found in the proof.
130    /// * `Ok(None)` when the requested object was not found in the proof; this can be interpreted as proof of non-existence.
131    ///   For collections, returns Ok(None) if none of the requested objects were found.
132    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
133    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
134        request: I,
135        response: O,
136        network: Network,
137        platform_version: &PlatformVersion,
138        provider: &'a dyn ContextProvider,
139    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
140    where
141        Self: Sized + 'a;
142
143    /// Retrieve the requested object from the proof.
144    ///
145    /// Runs full verification of the proof and retrieves enclosed objects.
146    ///
147    /// This method uses [`FromProof::maybe_from_proof()`] internally and throws an error
148    /// if the requested object does not exist in the proof.
149    ///
150    /// # Arguments
151    ///
152    /// * `request`: The request sent to the server.
153    /// * `response`: The response received from the server.
154    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
155    /// * `platform_version`: The platform version that should be used.
156    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
157    ///
158    /// # Returns
159    ///
160    /// * `Ok(object)` when the requested object was found in the proof.
161    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
162    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
163    fn from_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
164        request: I,
165        response: O,
166        network: Network,
167        platform_version: &PlatformVersion,
168        provider: &'a dyn ContextProvider,
169    ) -> Result<Self, Error>
170    where
171        Self: Sized + 'a,
172    {
173        Self::maybe_from_proof(request, response, network, platform_version, provider)?
174            .ok_or(Error::NotFound)
175    }
176
177    /// Retrieve the requested object from the proof with metadata.
178    ///
179    /// Runs full verification of the proof and retrieves enclosed objects.
180    ///
181    /// This method uses [`FromProof::maybe_from_proof_with_metadata()`] internally and throws an error
182    /// if the requested object does not exist in the proof.
183    ///
184    /// # Arguments
185    ///
186    /// * `request`: The request sent to the server.
187    /// * `response`: The response received from the server.
188    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
189    /// * `platform_version`: The platform version that should be used.
190    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
191    ///
192    /// # Returns
193    ///
194    /// * `Ok(Some(object, metadata))` when the requested object was found in the proof.
195    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
196    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
197    fn from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
198        request: I,
199        response: O,
200        network: Network,
201        platform_version: &PlatformVersion,
202        provider: &'a dyn ContextProvider,
203    ) -> Result<(Self, ResponseMetadata), Error>
204    where
205        Self: Sized + 'a,
206    {
207        let (main_item, response_metadata, _) = Self::maybe_from_proof_with_metadata(
208            request,
209            response,
210            network,
211            platform_version,
212            provider,
213        )?;
214        Ok((main_item.ok_or(Error::NotFound)?, response_metadata))
215    }
216
217    /// Retrieve the requested object from the proof with metadata.
218    ///
219    /// Runs full verification of the proof and retrieves enclosed objects.
220    ///
221    /// This method uses [`FromProof::maybe_from_proof_with_metadata()`] internally and throws an error
222    /// if the requested object does not exist in the proof.
223    ///
224    /// # Arguments
225    ///
226    /// * `request`: The request sent to the server.
227    /// * `response`: The response received from the server.
228    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
229    /// * `platform_version`: The platform version that should be used.
230    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
231    ///
232    /// # Returns
233    ///
234    /// * `Ok(Some(object, metadata, proof))` when the requested object was found in the proof.
235    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
236    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
237    fn from_proof_with_metadata_and_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
238        request: I,
239        response: O,
240        network: Network,
241        platform_version: &PlatformVersion,
242        provider: &'a dyn ContextProvider,
243    ) -> Result<(Self, ResponseMetadata, Proof), Error>
244    where
245        Self: Sized + 'a,
246    {
247        let (main_item, response_metadata, proof) = Self::maybe_from_proof_with_metadata(
248            request,
249            response,
250            network,
251            platform_version,
252            provider,
253        )?;
254        Ok((main_item.ok_or(Error::NotFound)?, response_metadata, proof))
255    }
256}
257
258impl FromProof<platform::GetIdentityRequest> for Identity {
259    type Request = platform::GetIdentityRequest;
260    type Response = platform::GetIdentityResponse;
261
262    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
263        request: I,
264        response: O,
265        _network: Network,
266        platform_version: &PlatformVersion,
267        provider: &'a dyn ContextProvider,
268    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
269    where
270        Identity: Sized + 'a,
271    {
272        let request: platform::GetIdentityRequest = request.into();
273        let response: Self::Response = response.into();
274
275        // Parse response to read proof and metadata
276        let proof = response.proof().or(Err(Error::NoProofInResult))?;
277        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
278
279        let id = match request.version.ok_or(Error::EmptyVersion)? {
280            get_identity_request::Version::V0(v0) => {
281                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
282                    error: e.to_string(),
283                })?
284            }
285        };
286
287        // Extract content from proof and verify Drive/GroveDB proofs
288        let (root_hash, maybe_identity) = Drive::verify_full_identity_by_identity_id(
289            &proof.grovedb_proof,
290            false,
291            id.into_buffer(),
292            platform_version,
293        )
294        .map_drive_error(proof, mtd)?;
295
296        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
297
298        Ok((maybe_identity, mtd.clone(), proof.clone()))
299    }
300}
301
302// TODO: figure out how to deal with mock::automock
303impl FromProof<platform::GetIdentityByPublicKeyHashRequest> for Identity {
304    type Request = platform::GetIdentityByPublicKeyHashRequest;
305    type Response = platform::GetIdentityByPublicKeyHashResponse;
306
307    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
308        request: I,
309        response: O,
310        _network: Network,
311        platform_version: &PlatformVersion,
312        provider: &'a dyn ContextProvider,
313    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
314    where
315        Identity: 'a,
316    {
317        let request = request.into();
318        let response = response.into();
319        // Parse response to read proof and metadata
320        let proof = response.proof().or(Err(Error::NoProofInResult))?;
321
322        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
323
324        let public_key_hash = match request.version.ok_or(Error::EmptyVersion)? {
325            get_identity_by_public_key_hash_request::Version::V0(v0) => {
326                let public_key_hash: [u8; 20] =
327                    v0.public_key_hash
328                        .try_into()
329                        .map_err(|_| Error::DriveError {
330                            error: "Invalid public key hash length".to_string(),
331                        })?;
332                public_key_hash
333            }
334        };
335
336        // Extract content from proof and verify Drive/GroveDB proofs
337        let (root_hash, maybe_identity) = Drive::verify_full_identity_by_unique_public_key_hash(
338            &proof.grovedb_proof,
339            public_key_hash,
340            platform_version,
341        )
342        .map_drive_error(proof, mtd)?;
343
344        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
345
346        Ok((maybe_identity, mtd.clone(), proof.clone()))
347    }
348}
349
350impl FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest> for Identity {
351    type Request = platform::GetIdentityByNonUniquePublicKeyHashRequest;
352    type Response = platform::GetIdentityByNonUniquePublicKeyHashResponse;
353    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
354        request: I,
355        response: O,
356        _network: Network,
357        platform_version: &PlatformVersion,
358        provider: &'a dyn ContextProvider,
359    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
360    where
361        Self: Sized + 'a,
362    {
363        let request = request.into();
364        let response = response.into();
365        // Parse response to read proof and metadata
366        // note that proof in this case is different
367        // let proof = response.proof().or(Err(Error::NoProofInResult))?;
368        use platform::get_identity_by_non_unique_public_key_hash_response::{
369            get_identity_by_non_unique_public_key_hash_response_v0::Result as V0Result, Version::V0,
370        };
371
372        let (proved_response, mtd) = match response.version {
373            Some(V0(v0)) => {
374                let proof = if let V0Result::Proof(p) = v0.result.ok_or(Error::NoProofInResult)? {
375                    p
376                } else {
377                    return Err(Error::NoProofInResult);
378                };
379
380                (proof, v0.metadata.ok_or(Error::EmptyResponseMetadata)?)
381            }
382            _ => return Err(Error::EmptyResponseMetadata),
383        };
384
385        // let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
386
387        let (public_key_hash, after_identity) = match request.version.ok_or(Error::EmptyVersion)? {
388            get_identity_by_non_unique_public_key_hash_request::Version::V0(v0) => {
389                let public_key_hash =
390                    v0.public_key_hash
391                        .try_into()
392                        .map_err(|_| Error::RequestError {
393                            error: "Invalid public key hash length".to_string(),
394                        })?;
395
396                let after = v0
397                    .start_after
398                    .map(|a| {
399                        a.try_into().map_err(|_| Error::RequestError {
400                            error: "Invalid start_after length".to_string(),
401                        })
402                    })
403                    .transpose()?;
404                (public_key_hash, after)
405            }
406        };
407
408        // we need to convert some data to handle non-default proof structure for this response
409        let proof = proved_response
410            .grovedb_identity_public_key_hash_proof
411            .ok_or(Error::NoProofInResult)?;
412
413        let proof_tuple = IdentityAndNonUniquePublicKeyHashDoubleProof {
414            identity_proof: proved_response.identity_proof_bytes,
415            identity_id_public_key_hash_proof: proof.grovedb_proof.clone(),
416        };
417
418        // Extract content from proof and verify Drive/GroveDB proofs
419        let (root_hash, maybe_identity) =
420            Drive::verify_full_identity_by_non_unique_public_key_hash(
421                &proof_tuple,
422                public_key_hash,
423                after_identity,
424                platform_version,
425            )
426            .map_err(|e| match e {
427                drive::error::Error::GroveDB(e) => {
428                    // If InvalidProof error is returned, extract the path query from it
429                    let maybe_query = match e.as_ref() {
430                        GroveError::InvalidProof(path_query, ..) => Some(path_query.clone()),
431                        _ => None,
432                    };
433
434                    Error::GroveDBError {
435                        proof_bytes: proof.grovedb_proof.clone(),
436                        path_query: maybe_query,
437                        height: mtd.height,
438                        time_ms: mtd.time_ms,
439                        error: e.to_string(),
440                    }
441                }
442                _ => e.into(),
443            })?;
444
445        verify_tenderdash_proof(&proof, &mtd, &root_hash, provider)?;
446
447        Ok((maybe_identity, mtd.clone(), proof))
448    }
449}
450
451impl FromProof<platform::GetIdentityKeysRequest> for IdentityPublicKeys {
452    type Request = platform::GetIdentityKeysRequest;
453    type Response = platform::GetIdentityKeysResponse;
454
455    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
456        request: I,
457        response: O,
458        _network: Network,
459        platform_version: &PlatformVersion,
460        provider: &'a dyn ContextProvider,
461    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
462    where
463        IdentityPublicKeys: 'a,
464    {
465        let request: Self::Request = request.into();
466        let response: Self::Response = response.into();
467
468        // Parse response to read proof and metadata
469        let proof = response.proof().or(Err(Error::NoProofInResult))?;
470
471        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
472
473        let (request_type, identity_id, limit, offset) =
474            match request.version.ok_or(Error::EmptyVersion)? {
475                get_identity_keys_request::Version::V0(v0) => {
476                    let request_type = v0.request_type;
477                    let identity_id = Identifier::from_bytes(&v0.identity_id)
478                        .map_err(|e| Error::ProtocolError {
479                            error: e.to_string(),
480                        })?
481                        .into_buffer();
482                    let limit = v0.limit.map(try_u32_to_u16).transpose()?;
483                    let offset = v0.offset.map(try_u32_to_u16).transpose()?;
484                    (request_type, identity_id, limit, offset)
485                }
486            };
487
488        let request_type = parse_key_request_type(&request_type)?;
489
490        let key_request = IdentityKeysRequest {
491            identity_id,
492            request_type,
493            limit,
494            offset,
495        };
496
497        tracing::debug!(?identity_id, "checking proof of identity keys");
498
499        // Extract content from proof and verify Drive/GroveDB proofs
500        let (root_hash, maybe_identity) = Drive::verify_identity_keys_by_identity_id(
501            &proof.grovedb_proof,
502            key_request,
503            false,
504            false,
505            false,
506            platform_version,
507        )
508        .map_drive_error(proof, mtd)?;
509
510        let maybe_keys: Option<IdentityPublicKeys> = if let Some(identity) = maybe_identity {
511            if identity.loaded_public_keys.is_empty() {
512                None
513            } else {
514                let mut keys = identity
515                    .loaded_public_keys
516                    .into_iter()
517                    .map(|(k, v)| (k, Some(v.clone())))
518                    .collect::<IdentityPublicKeys>();
519
520                let mut not_found = identity
521                    .not_found_public_keys
522                    .into_iter()
523                    .map(|k| (k, None))
524                    .collect::<IdentityPublicKeys>();
525
526                keys.append(&mut not_found);
527
528                Some(keys)
529            }
530        } else {
531            None
532        };
533
534        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
535
536        Ok((maybe_keys, mtd.clone(), proof.clone()))
537    }
538}
539
540fn parse_key_request_type(request: &Option<GrpcKeyType>) -> Result<KeyRequestType, Error> {
541    let key_request_type = request
542        .to_owned()
543        .ok_or(Error::RequestError {
544            error: "missing key request type".to_string(),
545        })?
546        .request
547        .ok_or(Error::RequestError {
548            error: "empty request field in key request type".to_string(),
549        })?;
550
551    let request_type = match key_request_type {
552        key_request_type::Request::AllKeys(_) => KeyRequestType::AllKeys,
553        key_request_type::Request::SpecificKeys(specific_keys) => {
554            KeyRequestType::SpecificKeys(specific_keys.key_ids)
555        }
556        key_request_type::Request::SearchKey(search_key) => {
557            let purpose = search_key
558                .purpose_map
559                .iter()
560                .map(|(k, v)| {
561                     let v = v.security_level_map
562                            .iter()
563                            .map(|(level, &kind)| {
564                                let kt = match GrpcKeyKind::try_from(kind) {
565                                    Ok(GrpcKeyKind::CurrentKeyOfKindRequest) => {
566                                        Ok(KeyKindRequestType::CurrentKeyOfKindRequest)
567                                    }
568                                    Ok(GrpcKeyKind::AllKeysOfKindRequest) => {
569                                        Ok(KeyKindRequestType::AllKeysOfKindRequest)
570                                    }
571                                    _ => Err(Error::RequestError {
572                                        error: format!("missing requested key type: {}", kind),
573                                    }),
574                                };
575                                match kt  {
576                                    Err(e) => Err(e),
577                                    Ok(d) => Ok((*level as u8, d))
578                                }
579                            })
580                            .collect::<Result<BTreeMap<SecurityLevelU8,KeyKindRequestType>,Error>>();
581
582                            match v {
583                                Err(e) =>Err(e),
584                                Ok(d) => Ok((*k as u8,d)),
585                            }
586                })
587                .collect::<Result<BTreeMap<PurposeU8, BTreeMap<SecurityLevelU8, KeyKindRequestType>>,Error>>()?;
588
589            KeyRequestType::SearchKey(purpose)
590        }
591    };
592
593    Ok(request_type)
594}
595
596impl FromProof<platform::GetIdentityNonceRequest> for IdentityNonceFetcher {
597    type Request = platform::GetIdentityNonceRequest;
598    type Response = platform::GetIdentityNonceResponse;
599
600    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
601        request: I,
602        response: O,
603        _network: Network,
604        platform_version: &PlatformVersion,
605        provider: &'a dyn ContextProvider,
606    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
607    where
608        IdentityNonceFetcher: 'a,
609    {
610        let request: Self::Request = request.into();
611        let response: Self::Response = response.into();
612
613        // Parse response to read proof and metadata
614        let proof = response.proof().or(Err(Error::NoProofInResult))?;
615
616        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
617
618        let identity_id =
619            match request.version.ok_or(Error::EmptyVersion)? {
620                get_identity_nonce_request::Version::V0(v0) => Ok::<Identifier, Error>(
621                    Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::ProtocolError {
622                        error: e.to_string(),
623                    })?,
624                ),
625            }?;
626
627        // Extract content from proof and verify Drive/GroveDB proofs
628        let (root_hash, maybe_nonce) = Drive::verify_identity_nonce(
629            &proof.grovedb_proof,
630            identity_id.into_buffer(),
631            false,
632            platform_version,
633        )
634        .map_drive_error(proof, mtd)?;
635
636        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
637
638        Ok((
639            maybe_nonce.map(IdentityNonceFetcher),
640            mtd.clone(),
641            proof.clone(),
642        ))
643    }
644}
645
646impl FromProof<platform::GetIdentityContractNonceRequest> for IdentityContractNonceFetcher {
647    type Request = platform::GetIdentityContractNonceRequest;
648    type Response = platform::GetIdentityContractNonceResponse;
649
650    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
651        request: I,
652        response: O,
653        _network: Network,
654        platform_version: &PlatformVersion,
655        provider: &'a dyn ContextProvider,
656    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
657    where
658        IdentityContractNonceFetcher: 'a,
659    {
660        let request: Self::Request = request.into();
661        let response: Self::Response = response.into();
662
663        // Parse response to read proof and metadata
664        let proof = response.proof().or(Err(Error::NoProofInResult))?;
665
666        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
667
668        let (identity_id, contract_id) = match request.version.ok_or(Error::EmptyVersion)? {
669            get_identity_contract_nonce_request::Version::V0(v0) => {
670                Ok::<(Identifier, Identifier), Error>((
671                    Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::ProtocolError {
672                        error: e.to_string(),
673                    })?,
674                    Identifier::from_bytes(&v0.contract_id).map_err(|e| Error::ProtocolError {
675                        error: e.to_string(),
676                    })?,
677                ))
678            }
679        }?;
680
681        // Extract content from proof and verify Drive/GroveDB proofs
682        let (root_hash, maybe_identity) = Drive::verify_identity_contract_nonce(
683            &proof.grovedb_proof,
684            identity_id.into_buffer(),
685            contract_id.into_buffer(),
686            false,
687            platform_version,
688        )
689        .map_drive_error(proof, mtd)?;
690
691        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
692
693        Ok((
694            maybe_identity.map(IdentityContractNonceFetcher),
695            mtd.clone(),
696            proof.clone(),
697        ))
698    }
699}
700
701impl FromProof<platform::GetIdentityBalanceRequest> for IdentityBalance {
702    type Request = platform::GetIdentityBalanceRequest;
703    type Response = platform::GetIdentityBalanceResponse;
704
705    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
706        request: I,
707        response: O,
708        _network: Network,
709        platform_version: &PlatformVersion,
710        provider: &'a dyn ContextProvider,
711    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
712    where
713        IdentityBalance: 'a,
714    {
715        let request: Self::Request = request.into();
716        let response: Self::Response = response.into();
717
718        // Parse response to read proof and metadata
719        let proof = response.proof().or(Err(Error::NoProofInResult))?;
720
721        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
722
723        let id = match request.version.ok_or(Error::EmptyVersion)? {
724            get_identity_balance_request::Version::V0(v0) => Identifier::from_bytes(&v0.id)
725                .map_err(|e| Error::ProtocolError {
726                    error: e.to_string(),
727                }),
728        }?;
729
730        // Extract content from proof and verify Drive/GroveDB proofs
731        let (root_hash, maybe_identity) = Drive::verify_identity_balance_for_identity_id(
732            &proof.grovedb_proof,
733            id.into_buffer(),
734            false,
735            platform_version,
736        )
737        .map_drive_error(proof, mtd)?;
738
739        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
740
741        Ok((maybe_identity, mtd.clone(), proof.clone()))
742    }
743}
744
745impl FromProof<platform::GetIdentitiesBalancesRequest> for IdentityBalances {
746    type Request = platform::GetIdentitiesBalancesRequest;
747    type Response = platform::GetIdentitiesBalancesResponse;
748
749    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
750        request: I,
751        response: O,
752        _network: Network,
753        platform_version: &PlatformVersion,
754        provider: &'a dyn ContextProvider,
755    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
756    where
757        IdentityBalances: 'a,
758    {
759        let request: Self::Request = request.into();
760        let response: Self::Response = response.into();
761        // Parse response to read proof and metadata
762        let proof = response.proof().or(Err(Error::NoProofInResult))?;
763
764        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
765
766        let identities_ids = match request.version.ok_or(Error::EmptyVersion)? {
767            get_identities_balances_request::Version::V0(v0) => v0.ids,
768        };
769
770        let identity_ids = identities_ids
771            .into_iter()
772            .map(|identity_bytes| {
773                Identifier::from_bytes(&identity_bytes)
774                    .map(|identifier| identifier.into_buffer())
775                    .map_err(|e| Error::RequestError {
776                        error: format!("identities must be all 32 bytes {}", e),
777                    })
778            })
779            .collect::<Result<Vec<[u8; 32]>, Error>>()?;
780        let (root_hash, balances) = Drive::verify_identity_balances_for_identity_ids(
781            &proof.grovedb_proof,
782            false,
783            &identity_ids,
784            platform_version,
785        )
786        .map_drive_error(proof, mtd)?;
787
788        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
789
790        Ok((Some(balances), mtd.clone(), proof.clone()))
791    }
792}
793
794impl FromProof<platform::GetIdentityBalanceAndRevisionRequest> for IdentityBalanceAndRevision {
795    type Request = platform::GetIdentityBalanceAndRevisionRequest;
796    type Response = platform::GetIdentityBalanceAndRevisionResponse;
797
798    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
799        request: I,
800        response: O,
801        _network: Network,
802        platform_version: &PlatformVersion,
803        provider: &'a dyn ContextProvider,
804    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
805    where
806        IdentityBalanceAndRevision: 'a,
807    {
808        let request: Self::Request = request.into();
809        let response: Self::Response = response.into();
810
811        // Parse response to read proof and metadata
812        let proof = response.proof().or(Err(Error::NoProofInResult))?;
813
814        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
815
816        let id = match request.version.ok_or(Error::EmptyVersion)? {
817            get_identity_balance_and_revision_request::Version::V0(v0) => {
818                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
819                    error: e.to_string(),
820                })
821            }
822        }?;
823
824        // Extract content from proof and verify Drive/GroveDB proofs
825        let (root_hash, maybe_identity) =
826            Drive::verify_identity_balance_and_revision_for_identity_id(
827                &proof.grovedb_proof,
828                id.into_buffer(),
829                false,
830                platform_version,
831            )
832            .map_drive_error(proof, mtd)?;
833
834        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
835
836        Ok((maybe_identity, mtd.clone(), proof.clone()))
837    }
838}
839
840impl FromProof<platform::GetAddressInfoRequest> for AddressInfo {
841    type Request = platform::GetAddressInfoRequest;
842    type Response = platform::GetAddressInfoResponse;
843
844    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
845        request: I,
846        response: O,
847        _network: Network,
848        platform_version: &PlatformVersion,
849        provider: &'a dyn ContextProvider,
850    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
851    where
852        AddressInfo: 'a,
853    {
854        let request: Self::Request = request.into();
855        let response: Self::Response = response.into();
856
857        let proof = response.proof().or(Err(Error::NoProofInResult))?;
858        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
859
860        let address = match request.version.ok_or(Error::EmptyVersion)? {
861            get_address_info_request::Version::V0(v0) => PlatformAddress::from_bytes(&v0.address)
862                .map_err(|e| Error::RequestError {
863                error: format!("invalid address: {}", e),
864            })?,
865        };
866
867        let (root_hash, maybe_info) =
868            Drive::verify_address_info(&proof.grovedb_proof, &address, false, platform_version)
869                .map_drive_error(proof, mtd)?;
870
871        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
872
873        let info = maybe_info.map(|(nonce, balance)| AddressInfo {
874            address,
875            nonce,
876            balance,
877        });
878
879        Ok((info, mtd.clone(), proof.clone()))
880    }
881}
882
883impl FromProof<platform::GetAddressesInfosRequest> for AddressInfos {
884    type Request = platform::GetAddressesInfosRequest;
885    type Response = platform::GetAddressesInfosResponse;
886
887    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
888        request: I,
889        response: O,
890        _network: Network,
891        platform_version: &PlatformVersion,
892        provider: &'a dyn ContextProvider,
893    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
894    where
895        AddressInfos: 'a,
896    {
897        let request: Self::Request = request.into();
898        let response: Self::Response = response.into();
899
900        let proof = response.proof().or(Err(Error::NoProofInResult))?;
901        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
902
903        let addresses_bytes = match request.version.ok_or(Error::EmptyVersion)? {
904            get_addresses_infos_request::Version::V0(v0) => v0.addresses,
905        };
906
907        let addresses: Vec<PlatformAddress> = addresses_bytes
908            .into_iter()
909            .map(|bytes| {
910                PlatformAddress::from_bytes(&bytes).map_err(|e| Error::RequestError {
911                    error: format!("invalid address: {}", e),
912                })
913            })
914            .collect::<Result<_, _>>()?;
915
916        let (root_hash, entries) = Drive::verify_addresses_infos::<
917            _,
918            Vec<(PlatformAddress, Option<(AddressNonce, Credits)>)>,
919        >(
920            &proof.grovedb_proof,
921            addresses.iter(),
922            false,
923            platform_version,
924        )
925        .map_drive_error(proof, mtd)?;
926
927        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
928
929        let infos = entries
930            .into_iter()
931            .map(|(address, maybe_info)| {
932                let info = maybe_info.map(|(nonce, balance)| AddressInfo {
933                    address,
934                    nonce,
935                    balance,
936                });
937                (address, info)
938            })
939            .collect::<AddressInfos>();
940
941        Ok((Some(infos), mtd.clone(), proof.clone()))
942    }
943}
944
945impl FromProof<platform::GetRecentAddressBalanceChangesRequest> for RecentAddressBalanceChanges {
946    type Request = platform::GetRecentAddressBalanceChangesRequest;
947    type Response = platform::GetRecentAddressBalanceChangesResponse;
948
949    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
950        request: I,
951        response: O,
952        _network: Network,
953        platform_version: &PlatformVersion,
954        provider: &'a dyn ContextProvider,
955    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
956    where
957        RecentAddressBalanceChanges: 'a,
958    {
959        use dapi_grpc::platform::v0::get_recent_address_balance_changes_request;
960
961        let request: Self::Request = request.into();
962        let response: Self::Response = response.into();
963
964        let proof = response.proof().or(Err(Error::NoProofInResult))?;
965        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
966
967        let (start_height, start_height_exclusive) =
968            match request.version.ok_or(Error::EmptyVersion)? {
969                get_recent_address_balance_changes_request::Version::V0(v0) => {
970                    (v0.start_height, v0.start_height_exclusive)
971                }
972            };
973
974        let limit = Some(100u16); // Same limit as in query handler
975
976        let (root_hash, verified_changes) = if start_height_exclusive {
977            Drive::verify_recent_address_balance_changes_after(
978                &proof.grovedb_proof,
979                start_height,
980                limit,
981                false,
982                platform_version,
983            )
984            .map_drive_error(proof, mtd)?
985        } else {
986            Drive::verify_recent_address_balance_changes(
987                &proof.grovedb_proof,
988                start_height,
989                limit,
990                false,
991                platform_version,
992            )
993            .map_drive_error(proof, mtd)?
994        };
995
996        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
997
998        let result = RecentAddressBalanceChanges(
999            verified_changes
1000                .into_iter()
1001                .map(|(block_height, changes)| BlockAddressBalanceChanges {
1002                    block_height,
1003                    changes,
1004                })
1005                .collect(),
1006        );
1007
1008        Ok((Some(result), mtd.clone(), proof.clone()))
1009    }
1010}
1011
1012impl FromProof<platform::GetRecentCompactedAddressBalanceChangesRequest>
1013    for RecentCompactedAddressBalanceChanges
1014{
1015    type Request = platform::GetRecentCompactedAddressBalanceChangesRequest;
1016    type Response = platform::GetRecentCompactedAddressBalanceChangesResponse;
1017
1018    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1019        request: I,
1020        response: O,
1021        _network: Network,
1022        platform_version: &PlatformVersion,
1023        provider: &'a dyn ContextProvider,
1024    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1025    where
1026        RecentCompactedAddressBalanceChanges: 'a,
1027    {
1028        use dapi_grpc::platform::v0::get_recent_compacted_address_balance_changes_request;
1029
1030        let request: Self::Request = request.into();
1031        let response: Self::Response = response.into();
1032
1033        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1034        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1035
1036        let start_block_height = match request.version.ok_or(Error::EmptyVersion)? {
1037            get_recent_compacted_address_balance_changes_request::Version::V0(v0) => {
1038                v0.start_block_height
1039            }
1040        };
1041
1042        // Ensure it is the same limit as in query handler; see
1043        // packages/rs-drive-abci/src/query/address_funds/recent_compacted_address_balance_changes/v0/mod.rs
1044        let limit = Some(25u16);
1045
1046        let (root_hash, verified_changes) = Drive::verify_compacted_address_balance_changes(
1047            &proof.grovedb_proof,
1048            start_block_height,
1049            limit,
1050            platform_version,
1051        )
1052        .map_drive_error(proof, mtd)?;
1053
1054        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1055
1056        let result = RecentCompactedAddressBalanceChanges(
1057            verified_changes
1058                .into_iter()
1059                .map(|(start_block_height, end_block_height, changes)| {
1060                    CompactedBlockAddressBalanceChanges {
1061                        start_block_height,
1062                        end_block_height,
1063                        changes,
1064                    }
1065                })
1066                .collect(),
1067        );
1068
1069        Ok((Some(result), mtd.clone(), proof.clone()))
1070    }
1071}
1072
1073impl FromProof<platform::GetAddressesTrunkStateRequest> for GroveTrunkQueryResult {
1074    type Request = platform::GetAddressesTrunkStateRequest;
1075    type Response = platform::GetAddressesTrunkStateResponse;
1076
1077    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1078        _request: I,
1079        response: O,
1080        _network: Network,
1081        platform_version: &PlatformVersion,
1082        provider: &'a dyn ContextProvider,
1083    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1084    where
1085        GroveTrunkQueryResult: 'a,
1086    {
1087        let response: Self::Response = response.into();
1088
1089        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1090        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1091
1092        let (root_hash, trunk_result) =
1093            Drive::verify_address_funds_trunk_query(&proof.grovedb_proof, platform_version)
1094                .map_drive_error(proof, mtd)?;
1095
1096        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1097
1098        Ok((Some(trunk_result), mtd.clone(), proof.clone()))
1099    }
1100}
1101
1102impl FromProof<platform::GetAddressesTrunkStateRequest> for PlatformAddressTrunkState {
1103    type Request = platform::GetAddressesTrunkStateRequest;
1104    type Response = platform::GetAddressesTrunkStateResponse;
1105
1106    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1107        request: I,
1108        response: O,
1109        network: Network,
1110        platform_version: &PlatformVersion,
1111        provider: &'a dyn ContextProvider,
1112    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1113    where
1114        PlatformAddressTrunkState: 'a,
1115    {
1116        let (result, metadata, proof) = <GroveTrunkQueryResult as FromProof<
1117            platform::GetAddressesTrunkStateRequest,
1118        >>::maybe_from_proof_with_metadata(
1119            request, response, network, platform_version, provider
1120        )?;
1121
1122        Ok((result.map(PlatformAddressTrunkState), metadata, proof))
1123    }
1124}
1125
1126impl FromProof<platform::GetNullifiersTrunkStateRequest> for GroveTrunkQueryResult {
1127    type Request = platform::GetNullifiersTrunkStateRequest;
1128    type Response = platform::GetNullifiersTrunkStateResponse;
1129
1130    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1131        request: I,
1132        response: O,
1133        _network: Network,
1134        platform_version: &PlatformVersion,
1135        provider: &'a dyn ContextProvider,
1136    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1137    where
1138        GroveTrunkQueryResult: 'a,
1139    {
1140        let request: Self::Request = request.into();
1141        let response: Self::Response = response.into();
1142
1143        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1144        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1145
1146        // Extract pool_type and pool_identifier from request
1147        let (pool_type, pool_identifier) = match &request.version {
1148            Some(platform::get_nullifiers_trunk_state_request::Version::V0(v0)) => {
1149                let pool_id = if v0.pool_identifier.is_empty() {
1150                    None
1151                } else {
1152                    Some(v0.pool_identifier.as_slice())
1153                };
1154                (v0.pool_type, pool_id)
1155            }
1156            None => return Err(Error::EmptyVersion),
1157        };
1158
1159        let (root_hash, trunk_result) = Drive::verify_nullifiers_trunk_query(
1160            &proof.grovedb_proof,
1161            pool_type,
1162            pool_identifier,
1163            platform_version,
1164        )
1165        .map_drive_error(proof, mtd)?;
1166
1167        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1168
1169        Ok((Some(trunk_result), mtd.clone(), proof.clone()))
1170    }
1171}
1172
1173impl FromProof<platform::GetNullifiersTrunkStateRequest> for NullifiersTrunkState {
1174    type Request = platform::GetNullifiersTrunkStateRequest;
1175    type Response = platform::GetNullifiersTrunkStateResponse;
1176
1177    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1178        request: I,
1179        response: O,
1180        network: Network,
1181        platform_version: &PlatformVersion,
1182        provider: &'a dyn ContextProvider,
1183    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1184    where
1185        NullifiersTrunkState: 'a,
1186    {
1187        let (result, metadata, proof) = <GroveTrunkQueryResult as FromProof<
1188            platform::GetNullifiersTrunkStateRequest,
1189        >>::maybe_from_proof_with_metadata(
1190            request, response, network, platform_version, provider
1191        )?;
1192
1193        Ok((result.map(NullifiersTrunkState), metadata, proof))
1194    }
1195}
1196
1197impl FromProof<platform::GetDataContractRequest> for DataContract {
1198    type Request = platform::GetDataContractRequest;
1199    type Response = platform::GetDataContractResponse;
1200
1201    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1202        request: I,
1203        response: O,
1204        _network: Network,
1205        platform_version: &PlatformVersion,
1206        provider: &'a dyn ContextProvider,
1207    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1208    where
1209        DataContract: 'a,
1210    {
1211        let request: Self::Request = request.into();
1212        let response: Self::Response = response.into();
1213
1214        // Parse response to read proof and metadata
1215        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1216
1217        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1218
1219        let id = match request.version.ok_or(Error::EmptyVersion)? {
1220            get_data_contract_request::Version::V0(v0) => {
1221                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1222                    error: e.to_string(),
1223                })
1224            }
1225        }?;
1226
1227        // Extract content from proof and verify Drive/GroveDB proofs
1228        let (root_hash, maybe_contract) = Drive::verify_contract(
1229            &proof.grovedb_proof,
1230            None,
1231            false,
1232            false,
1233            id.into_buffer(),
1234            platform_version,
1235        )
1236        .map_drive_error(proof, mtd)?;
1237
1238        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1239
1240        Ok((maybe_contract, mtd.clone(), proof.clone()))
1241    }
1242}
1243
1244impl FromProof<platform::GetDataContractRequest> for (DataContract, Vec<u8>) {
1245    type Request = platform::GetDataContractRequest;
1246    type Response = platform::GetDataContractResponse;
1247
1248    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1249        request: I,
1250        response: O,
1251        _network: Network,
1252        platform_version: &PlatformVersion,
1253        provider: &'a dyn ContextProvider,
1254    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1255    where
1256        DataContract: 'a,
1257    {
1258        let request: Self::Request = request.into();
1259        let response: Self::Response = response.into();
1260
1261        // Parse response to read proof and metadata
1262        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1263
1264        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1265
1266        let id = match request.version.ok_or(Error::EmptyVersion)? {
1267            get_data_contract_request::Version::V0(v0) => {
1268                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1269                    error: e.to_string(),
1270                })
1271            }
1272        }?;
1273
1274        // Extract content from proof and verify Drive/GroveDB proofs
1275        let (root_hash, maybe_contract) = Drive::verify_contract_return_serialization(
1276            &proof.grovedb_proof,
1277            None,
1278            false,
1279            false,
1280            id.into_buffer(),
1281            platform_version,
1282        )
1283        .map_drive_error(proof, mtd)?;
1284
1285        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1286
1287        Ok((maybe_contract, mtd.clone(), proof.clone()))
1288    }
1289}
1290
1291impl FromProof<platform::GetDataContractsRequest> for DataContracts {
1292    type Request = platform::GetDataContractsRequest;
1293    type Response = platform::GetDataContractsResponse;
1294
1295    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1296        request: I,
1297        response: O,
1298        _network: Network,
1299        platform_version: &PlatformVersion,
1300        provider: &'a dyn ContextProvider,
1301    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1302    where
1303        DataContracts: 'a,
1304    {
1305        let request: Self::Request = request.into();
1306        let response: Self::Response = response.into();
1307
1308        // Parse response to read proof and metadata
1309        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1310
1311        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1312
1313        let ids = match request.version.ok_or(Error::EmptyVersion)? {
1314            get_data_contracts_request::Version::V0(v0) => v0.ids,
1315        };
1316
1317        let ids = ids
1318            .iter()
1319            .map(|id| {
1320                id.clone().try_into().map_err(|_e| Error::RequestError {
1321                    error: format!("wrong id size: expected: {}, got: {}", 32, id.len()),
1322                })
1323            })
1324            .collect::<Result<Vec<[u8; 32]>, Error>>()?;
1325
1326        // Extract content from proof and verify Drive/GroveDB proofs
1327        let (root_hash, contracts) = Drive::verify_contracts(
1328            &proof.grovedb_proof,
1329            false,
1330            ids.as_slice(),
1331            platform_version,
1332        )
1333        .map_drive_error(proof, mtd)?;
1334
1335        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1336        let contracts = contracts
1337            .into_iter()
1338            .map(|(k, v)| {
1339                Identifier::from_bytes(&k).map(|id| (id, v)).map_err(|e| {
1340                    Error::ResultEncodingError {
1341                        error: e.to_string(),
1342                    }
1343                })
1344            })
1345            .collect::<Result<DataContracts, Error>>()?;
1346
1347        let maybe_contracts = if contracts.is_empty() {
1348            None
1349        } else {
1350            Some(contracts)
1351        };
1352
1353        Ok((maybe_contracts, mtd.clone(), proof.clone()))
1354    }
1355}
1356
1357impl FromProof<platform::GetDataContractHistoryRequest> for DataContractHistory {
1358    type Request = platform::GetDataContractHistoryRequest;
1359    type Response = platform::GetDataContractHistoryResponse;
1360
1361    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1362        request: I,
1363        response: O,
1364        _network: Network,
1365        platform_version: &PlatformVersion,
1366        provider: &'a dyn ContextProvider,
1367    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1368    where
1369        Self: Sized + 'a,
1370    {
1371        let request: Self::Request = request.into();
1372        let response: Self::Response = response.into();
1373
1374        // Parse response to read proof and metadata
1375        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1376
1377        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1378
1379        let (id, limit, offset, start_at_ms) = match request.version.ok_or(Error::EmptyVersion)? {
1380            get_data_contract_history_request::Version::V0(v0) => {
1381                let id = Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1382                    error: e.to_string(),
1383                })?;
1384                let limit = u32_to_u16_opt(v0.limit.unwrap_or_default())?;
1385                let offset = u32_to_u16_opt(v0.offset.unwrap_or_default())?;
1386                let start_at_ms = v0.start_at_ms;
1387                (id, limit, offset, start_at_ms)
1388            }
1389        };
1390
1391        // Extract content from proof and verify Drive/GroveDB proofs
1392        let (root_hash, maybe_history) = Drive::verify_contract_history(
1393            &proof.grovedb_proof,
1394            id.into_buffer(),
1395            start_at_ms,
1396            limit,
1397            offset,
1398            platform_version,
1399        )
1400        .map_drive_error(proof, mtd)?;
1401
1402        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1403
1404        Ok((
1405            maybe_history.map(IndexMap::from_iter),
1406            mtd.clone(),
1407            proof.clone(),
1408        ))
1409    }
1410}
1411
1412impl FromProof<platform::BroadcastStateTransitionRequest> for StateTransitionProofResult {
1413    type Request = platform::BroadcastStateTransitionRequest;
1414    type Response = platform::WaitForStateTransitionResultResponse;
1415
1416    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1417        request: I,
1418        response: O,
1419        _network: Network,
1420        platform_version: &PlatformVersion,
1421        provider: &'a dyn ContextProvider,
1422    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1423    where
1424        Self: Sized + 'a,
1425    {
1426        let request: Self::Request = request.into();
1427        let response: Self::Response = response.into();
1428
1429        // Parse response to read proof and metadata
1430        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1431
1432        let state_transition = StateTransition::deserialize_from_bytes(&request.state_transition)
1433            .map_err(|e| Error::ProtocolError {
1434            error: e.to_string(),
1435        })?;
1436
1437        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1438
1439        let epoch_u16 = try_u32_to_u16(mtd.epoch).map_err(|_| {
1440            Into::<Error>::into(drive::error::Error::Proof(ProofError::InvalidMetadata(
1441                format!(
1442                    "platform returned an epoch {} that was higher than maximum of a 16 bit integer",
1443                    mtd.epoch
1444                ),
1445            )))
1446        })?;
1447
1448        let block_info = BlockInfo {
1449            time_ms: mtd.time_ms,
1450            height: mtd.height,
1451            core_height: mtd.core_chain_locked_height,
1452            epoch: epoch_u16.try_into()?,
1453        };
1454
1455        let contracts_provider_fn = provider.as_contract_lookup_fn(platform_version);
1456
1457        let (root_hash, result) = Drive::verify_state_transition_was_executed_with_proof(
1458            &state_transition,
1459            &block_info,
1460            &proof.grovedb_proof,
1461            &contracts_provider_fn,
1462            platform_version,
1463        )
1464        .map_drive_error(proof, mtd)?;
1465
1466        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1467
1468        Ok((Some(result), mtd.clone(), proof.clone()))
1469    }
1470}
1471
1472impl FromProof<platform::GetEpochsInfoRequest> for ExtendedEpochInfo {
1473    type Request = platform::GetEpochsInfoRequest;
1474    type Response = platform::GetEpochsInfoResponse;
1475
1476    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1477        request: I,
1478        response: O,
1479        network: Network,
1480        platform_version: &PlatformVersion,
1481        provider: &'a dyn ContextProvider,
1482    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1483    where
1484        Self: Sized + 'a,
1485    {
1486        let epochs = ExtendedEpochInfos::maybe_from_proof_with_metadata(
1487            request,
1488            response,
1489            network,
1490            platform_version,
1491            provider,
1492        )?;
1493
1494        if let Some(e) = epochs.0 {
1495            if e.len() != 1 {
1496                return Err(Error::RequestError {
1497                    error: format!("expected 1 epoch, got {}", e.len()),
1498                });
1499            }
1500            let epoch = e.into_iter().next().and_then(|v| v.1);
1501            Ok((epoch, epochs.1, epochs.2))
1502        } else {
1503            Ok((None, epochs.1, epochs.2))
1504        }
1505    }
1506}
1507
1508impl FromProof<platform::GetEpochsInfoRequest> for ExtendedEpochInfos {
1509    type Request = platform::GetEpochsInfoRequest;
1510    type Response = platform::GetEpochsInfoResponse;
1511
1512    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1513        request: I,
1514        response: O,
1515        _network: Network,
1516        platform_version: &PlatformVersion,
1517        provider: &'a dyn ContextProvider,
1518    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1519    where
1520        Self: Sized + 'a,
1521    {
1522        let request: Self::Request = request.into();
1523        let response: Self::Response = response.into();
1524        // Parse response to read proof and metadata
1525        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1526
1527        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1528
1529        let (start_epoch, count, ascending) = match request.version.ok_or(Error::EmptyVersion)? {
1530            get_epochs_info_request::Version::V0(v0) => (v0.start_epoch, v0.count, v0.ascending),
1531        };
1532
1533        let current_epoch: EpochIndex = try_u32_to_u16(mtd.epoch)?;
1534        let start_epoch: Option<EpochIndex> = if let Some(epoch) = start_epoch {
1535            Some(try_u32_to_u16(epoch)?)
1536        } else {
1537            None
1538        };
1539        let count = try_u32_to_u16(count)?;
1540
1541        let (root_hash, epoch_info) = Drive::verify_epoch_infos(
1542            &proof.grovedb_proof,
1543            current_epoch,
1544            start_epoch,
1545            count,
1546            ascending,
1547            platform_version,
1548        )
1549        .map_drive_error(proof, mtd)?;
1550
1551        let epoch_info = epoch_info
1552            .into_iter()
1553            .map(|v| {
1554                #[allow(clippy::infallible_destructuring_match)]
1555                let info = match &v {
1556                    ExtendedEpochInfo::V0(i) => i,
1557                };
1558
1559                (info.index, Some(v))
1560            })
1561            .collect::<ExtendedEpochInfos>();
1562
1563        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1564
1565        Ok((epoch_info.into_option(), mtd.clone(), proof.clone()))
1566    }
1567}
1568
1569impl FromProof<platform::GetFinalizedEpochInfosRequest> for FinalizedEpochInfos {
1570    type Request = platform::GetFinalizedEpochInfosRequest;
1571    type Response = platform::GetFinalizedEpochInfosResponse;
1572
1573    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1574        request: I,
1575        response: O,
1576        _network: Network,
1577        platform_version: &PlatformVersion,
1578        provider: &'a dyn ContextProvider,
1579    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1580    where
1581        Self: Sized + 'a,
1582    {
1583        let request: Self::Request = request.into();
1584        let response: Self::Response = response.into();
1585        // Parse response to read proof and metadata
1586        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1587
1588        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1589
1590        let (
1591            start_epoch_index,
1592            start_epoch_index_included,
1593            end_epoch_index,
1594            end_epoch_index_included,
1595        ) = match request.version.ok_or(Error::EmptyVersion)? {
1596            get_finalized_epoch_infos_request::Version::V0(v0) => (
1597                v0.start_epoch_index,
1598                v0.start_epoch_index_included,
1599                v0.end_epoch_index,
1600                v0.end_epoch_index_included,
1601            ),
1602        };
1603
1604        let start_epoch_index: EpochIndex = try_u32_to_u16(start_epoch_index)?;
1605        let end_epoch_index: EpochIndex = try_u32_to_u16(end_epoch_index)?;
1606
1607        let (root_hash, epoch_info) = Drive::verify_finalized_epoch_infos(
1608            &proof.grovedb_proof,
1609            start_epoch_index,
1610            start_epoch_index_included,
1611            end_epoch_index,
1612            end_epoch_index_included,
1613            platform_version,
1614        )
1615        .map_drive_error(proof, mtd)?;
1616
1617        let epoch_info = epoch_info
1618            .into_iter()
1619            .map(|(epoch_index, finalized_epoch_info)| (epoch_index, Some(finalized_epoch_info)))
1620            .collect::<FinalizedEpochInfos>();
1621
1622        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1623
1624        Ok((epoch_info.into_option(), mtd.clone(), proof.clone()))
1625    }
1626}
1627
1628fn try_u32_to_u16(i: u32) -> Result<u16, Error> {
1629    i.try_into()
1630        .map_err(|e: TryFromIntError| Error::RequestError {
1631            error: e.to_string(),
1632        })
1633}
1634
1635impl FromProof<GetProtocolVersionUpgradeStateRequest> for ProtocolVersionUpgrades {
1636    type Request = GetProtocolVersionUpgradeStateRequest;
1637    type Response = GetProtocolVersionUpgradeStateResponse;
1638
1639    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1640        _request: I,
1641        response: O,
1642        _network: Network,
1643        platform_version: &PlatformVersion,
1644        provider: &'a dyn ContextProvider,
1645    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1646    where
1647        Self: Sized + 'a,
1648    {
1649        let response: Self::Response = response.into();
1650        // Parse response to read proof and metadata
1651        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1652        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1653
1654        let (root_hash, objects) =
1655            Drive::verify_upgrade_state(&proof.grovedb_proof, platform_version)
1656                .map_drive_error(proof, mtd)?;
1657
1658        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1659
1660        // Convert objects to a map of Option values
1661        let response: Self = objects.into_iter().map(|(k, v)| (k, Some(v))).collect();
1662
1663        Ok((response.into_option(), mtd.clone(), proof.clone()))
1664    }
1665}
1666
1667impl FromProof<GetProtocolVersionUpgradeVoteStatusRequest> for MasternodeProtocolVotes {
1668    type Request = GetProtocolVersionUpgradeVoteStatusRequest;
1669    type Response = GetProtocolVersionUpgradeVoteStatusResponse;
1670
1671    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1672        request: I,
1673        response: O,
1674        _network: Network,
1675        platform_version: &PlatformVersion,
1676        provider: &'a dyn ContextProvider,
1677    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1678    where
1679        Self: Sized + 'a,
1680    {
1681        let request = request.into();
1682        let response: Self::Response = response.into();
1683        // Parse response to read proof and metadata
1684        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1685        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1686
1687        let request_v0: GetProtocolVersionUpgradeVoteStatusRequestV0 = match request.version {
1688            Some(get_protocol_version_upgrade_vote_status_request::Version::V0(v0)) => v0,
1689            None => return Err(Error::EmptyVersion),
1690        };
1691
1692        let start_pro_tx_hash: Option<[u8; 32]> =
1693            if request_v0.start_pro_tx_hash.is_empty() {
1694                None
1695            } else {
1696                Some(request_v0.start_pro_tx_hash[..].try_into().map_err(
1697                    |e: TryFromSliceError| Error::RequestError {
1698                        error: e.to_string(),
1699                    },
1700                )?)
1701            };
1702
1703        let (root_hash, objects) = Drive::verify_upgrade_vote_status(
1704            &proof.grovedb_proof,
1705            start_pro_tx_hash,
1706            try_u32_to_u16(request_v0.count)?,
1707            platform_version,
1708        )
1709        .map_drive_error(proof, mtd)?;
1710
1711        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1712
1713        if objects.is_empty() {
1714            return Ok((None, mtd.clone(), proof.clone()));
1715        }
1716        let votes: MasternodeProtocolVotes = objects
1717            .into_iter()
1718            .map(|(key, value)| {
1719                ProTxHash::from_slice(&key)
1720                    .map(|pro_tx_hash| {
1721                        (
1722                            pro_tx_hash,
1723                            Some(MasternodeProtocolVote {
1724                                pro_tx_hash,
1725                                voted_version: value,
1726                            }),
1727                        )
1728                    })
1729                    .map_err(|e| Error::ResultEncodingError {
1730                        error: e.to_string(),
1731                    })
1732            })
1733            .collect::<Result<MasternodeProtocolVotes, Error>>()?;
1734
1735        Ok((votes.into_option(), mtd.clone(), proof.clone()))
1736    }
1737}
1738
1739impl FromProof<GetPathElementsRequest> for Elements {
1740    type Request = GetPathElementsRequest;
1741    type Response = GetPathElementsResponse;
1742
1743    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1744        request: I,
1745        response: O,
1746        _network: Network,
1747        platform_version: &PlatformVersion,
1748        provider: &'a dyn ContextProvider,
1749    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1750    where
1751        Self: Sized + 'a,
1752    {
1753        let request = request.into();
1754        let response: Self::Response = response.into();
1755        // Parse response to read proof and metadata
1756        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1757        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1758
1759        let request_v0: GetPathElementsRequestV0 = match request.version {
1760            Some(get_path_elements_request::Version::V0(v0)) => v0,
1761            None => return Err(Error::EmptyVersion),
1762        };
1763
1764        let path = request_v0.path;
1765        let keys = request_v0.keys;
1766
1767        let (root_hash, objects) =
1768            Drive::verify_elements(&proof.grovedb_proof, path, keys, platform_version)?;
1769        let elements: Elements = Elements::from_iter(objects);
1770
1771        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1772
1773        Ok((elements.into_option(), mtd.clone(), proof.clone()))
1774    }
1775}
1776
1777impl<'dq, Q> FromProof<Q> for Documents
1778where
1779    Q: TryInto<DriveDocumentQuery<'dq>> + Clone + 'dq,
1780    Q::Error: std::fmt::Display,
1781{
1782    type Request = Q;
1783    type Response = platform::GetDocumentsResponse;
1784
1785    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1786        request: I,
1787        response: O,
1788        _network: Network,
1789        platform_version: &PlatformVersion,
1790        provider: &'a dyn ContextProvider,
1791    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1792    where
1793        Self: 'a,
1794    {
1795        let request: Self::Request = request.into();
1796        let response: Self::Response = response.into();
1797
1798        let request: DriveDocumentQuery<'dq> =
1799            request
1800                .clone()
1801                .try_into()
1802                .map_err(|e: Q::Error| Error::RequestError {
1803                    error: e.to_string(),
1804                })?;
1805
1806        // Parse response to read proof and metadata
1807        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1808
1809        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1810
1811        let (root_hash, documents) = request
1812            .verify_proof(&proof.grovedb_proof, platform_version)
1813            .map_drive_error(proof, mtd)?;
1814
1815        let documents = documents
1816            .into_iter()
1817            .map(|d| (d.id(), Some(d)))
1818            .collect::<Documents>();
1819
1820        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1821
1822        Ok((documents.into_option(), mtd.clone(), proof.clone()))
1823    }
1824}
1825
1826impl FromProof<platform::GetIdentitiesContractKeysRequest> for IdentitiesContractKeys {
1827    type Request = platform::GetIdentitiesContractKeysRequest;
1828    type Response = platform::GetIdentitiesContractKeysResponse;
1829
1830    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1831        request: I,
1832        response: O,
1833        _network: Network,
1834        platform_version: &PlatformVersion,
1835        provider: &'a dyn ContextProvider,
1836    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1837    where
1838        Self: 'a,
1839    {
1840        let request: Self::Request = request.into();
1841        let response: Self::Response = response.into();
1842
1843        // Parse response to read proof and metadata
1844        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1845
1846        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1847
1848        let (identities_ids, contract_id, document_type_name, purposes) =
1849            match request.version.ok_or(Error::EmptyVersion)? {
1850                get_identities_contract_keys_request::Version::V0(v0) => {
1851                    let GetIdentitiesContractKeysRequestV0 {
1852                        identities_ids,
1853                        contract_id,
1854                        document_type_name,
1855                        purposes,
1856                        ..
1857                    } = v0;
1858                    let identifiers = identities_ids
1859                        .into_iter()
1860                        .map(|identity_id_vec| {
1861                            let identifier = Identifier::from_vec(identity_id_vec)?;
1862                            Ok(identifier.to_buffer())
1863                        })
1864                        .collect::<Result<Vec<[u8; 32]>, platform_value::Error>>()
1865                        .map_err(|e| Error::ProtocolError {
1866                            error: e.to_string(),
1867                        })?;
1868                    let contract_id = Identifier::from_vec(contract_id)
1869                        .map_err(|e| Error::ProtocolError {
1870                            error: e.to_string(),
1871                        })?
1872                        .into_buffer();
1873                    let purposes = purposes
1874                        .into_iter()
1875                        .map(|purpose| {
1876                            Purpose::try_from(purpose).map_err(|e| Error::ProtocolError {
1877                                error: e.to_string(),
1878                            })
1879                        })
1880                        .collect::<Result<Vec<Purpose>, Error>>()?;
1881                    (identifiers, contract_id, document_type_name, purposes)
1882                }
1883            };
1884
1885        // Extract content from proof and verify Drive/GroveDB proofs
1886        let (root_hash, identities_contract_keys) = Drive::verify_identities_contract_keys(
1887            &proof.grovedb_proof,
1888            identities_ids.as_slice(),
1889            &contract_id,
1890            document_type_name,
1891            purposes,
1892            false,
1893            platform_version,
1894        )
1895        .map_drive_error(proof, mtd)?;
1896
1897        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1898
1899        if identities_contract_keys.is_empty() {
1900            return Ok((None, mtd.clone(), proof.clone()));
1901        }
1902
1903        Ok((Some(identities_contract_keys), mtd.clone(), proof.clone()))
1904    }
1905}
1906
1907impl FromProof<platform::GetContestedResourcesRequest> for ContestedResources {
1908    type Request = platform::GetContestedResourcesRequest;
1909    type Response = platform::GetContestedResourcesResponse;
1910
1911    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1912        request: I,
1913        response: O,
1914        _network: Network,
1915        platform_version: &PlatformVersion,
1916        provider: &'a dyn ContextProvider,
1917    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1918    where
1919        Self: Sized + 'a,
1920    {
1921        let request: Self::Request = request.into();
1922        let response: Self::Response = response.into();
1923
1924        // Decode request to get drive query
1925        let drive_query = VotePollsByDocumentTypeQuery::try_from_request(request)?;
1926        let resolved_request = drive_query.resolve_with_known_contracts_provider(
1927            &provider.as_contract_lookup_fn(platform_version),
1928        )?;
1929
1930        // Parse response to read proof and metadata
1931        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1932        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1933
1934        let (root_hash, items) = resolved_request
1935            .verify_contests_proof(&proof.grovedb_proof, platform_version)
1936            .map_drive_error(proof, mtd)?;
1937
1938        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1939
1940        let resources: ContestedResources = items.into_iter().map(ContestedResource).collect();
1941
1942        Ok((resources.into_option(), mtd.clone(), proof.clone()))
1943    }
1944}
1945
1946// rpc getContestedResourceVoteState(GetContestedResourceVoteStateRequest) returns (GetContestedResourceVoteStateResponse);
1947impl FromProof<platform::GetContestedResourceVoteStateRequest> for Contenders {
1948    type Request = platform::GetContestedResourceVoteStateRequest;
1949    type Response = platform::GetContestedResourceVoteStateResponse;
1950
1951    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1952        request: I,
1953        response: O,
1954        _network: Network,
1955        platform_version: &PlatformVersion,
1956        provider: &'a dyn ContextProvider,
1957    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1958    where
1959        Self: 'a,
1960    {
1961        let request: Self::Request = request.into();
1962        let response: Self::Response = response.into();
1963
1964        // Decode request to get drive query
1965        let drive_query = ContestedDocumentVotePollDriveQuery::try_from_request(request)?;
1966
1967        // Resolve request to get verify_*_proof
1968        let contracts_provider = provider.as_contract_lookup_fn(platform_version);
1969        let resolved_request =
1970            drive_query.resolve_with_known_contracts_provider(&contracts_provider)?;
1971
1972        // Parse response to read proof and metadata
1973        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1974        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1975
1976        let (root_hash, contested_resource_vote_state) = resolved_request
1977            .verify_vote_poll_vote_state_proof(&proof.grovedb_proof, platform_version)
1978            .map_drive_error(proof, mtd)?;
1979
1980        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1981
1982        let contenders = contested_resource_vote_state
1983            .contenders
1984            .into_iter()
1985            .map(|v| (v.identity_id(), v))
1986            .collect();
1987
1988        let response = Contenders {
1989            winner: contested_resource_vote_state.winner,
1990            contenders,
1991            abstain_vote_tally: contested_resource_vote_state.abstaining_vote_tally,
1992            lock_vote_tally: contested_resource_vote_state.locked_vote_tally,
1993        };
1994        Ok((response.into_option(), mtd.clone(), proof.clone()))
1995    }
1996}
1997
1998impl FromProof<GetContestedResourceVotersForIdentityRequest> for Voters {
1999    type Request = GetContestedResourceVotersForIdentityRequest;
2000    type Response = GetContestedResourceVotersForIdentityResponse;
2001
2002    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2003        request: I,
2004        response: O,
2005        _network: Network,
2006        platform_version: &PlatformVersion,
2007        provider: &'a dyn ContextProvider,
2008    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2009    where
2010        Self: Sized + 'a,
2011    {
2012        let request: Self::Request = request.into();
2013        let response: Self::Response = response.into();
2014
2015        // Decode request to get drive query
2016        let drive_query = ContestedDocumentVotePollVotesDriveQuery::try_from_request(request)?;
2017
2018        // Parse request to get resolved contract that implements verify_*_proof
2019        let contracts_provider = provider.as_contract_lookup_fn(platform_version);
2020
2021        let resolved_request =
2022            drive_query.resolve_with_known_contracts_provider(&contracts_provider)?;
2023
2024        // Parse response to read proof and metadata
2025        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2026        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2027
2028        let (root_hash, voters) = resolved_request
2029            .verify_vote_poll_votes_proof(&proof.grovedb_proof, platform_version)
2030            .map_drive_error(proof, mtd)?;
2031
2032        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2033
2034        if voters.is_empty() {
2035            return Ok((None, mtd.clone(), proof.clone()));
2036        }
2037        let result: Voters = voters.into_iter().map(Voter::from).collect();
2038
2039        Ok((result.into_option(), mtd.clone(), proof.clone()))
2040    }
2041}
2042
2043impl FromProof<platform::GetContestedResourceIdentityVotesRequest> for ResourceVotesByIdentity {
2044    type Request = platform::GetContestedResourceIdentityVotesRequest;
2045    type Response = platform::GetContestedResourceIdentityVotesResponse;
2046
2047    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2048        request: I,
2049        response: O,
2050        _network: Network,
2051        platform_version: &PlatformVersion,
2052        provider: &'a dyn ContextProvider,
2053    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2054    where
2055        Self: Sized + 'a,
2056    {
2057        let request: Self::Request = request.into();
2058        let response: Self::Response = response.into();
2059
2060        // Decode request to get drive query
2061        let drive_query = ContestedResourceVotesGivenByIdentityQuery::try_from_request(request)?;
2062
2063        // Parse response to read proof and metadata
2064        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2065        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2066
2067        let contract_provider_fn = provider.as_contract_lookup_fn(platform_version);
2068        let (root_hash, voters) = drive_query
2069            .verify_identity_votes_given_proof::<Vec<_>>(
2070                &proof.grovedb_proof,
2071                &contract_provider_fn,
2072                platform_version,
2073            )
2074            .map_drive_error(proof, mtd)?;
2075
2076        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2077
2078        let response: ResourceVotesByIdentity = voters
2079            .into_iter()
2080            .map(|(id, vote)| (id, Some(vote)))
2081            .collect();
2082
2083        Ok((response.into_option(), mtd.clone(), proof.clone()))
2084    }
2085}
2086
2087impl FromProof<platform::GetVotePollsByEndDateRequest> for VotePollsGroupedByTimestamp {
2088    type Request = platform::GetVotePollsByEndDateRequest;
2089    type Response = platform::GetVotePollsByEndDateResponse;
2090
2091    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2092        request: I,
2093        response: O,
2094        _network: Network,
2095        platform_version: &PlatformVersion,
2096        provider: &'a dyn ContextProvider,
2097    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2098    where
2099        Self: Sized + 'a,
2100    {
2101        let request: Self::Request = request.into();
2102        let response: Self::Response = response.into();
2103
2104        // Decode request to get drive query
2105        let drive_query = VotePollsByEndDateDriveQuery::try_from_request(request)?;
2106
2107        // Parse response to read proof and metadata
2108        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2109        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2110
2111        let (root_hash, vote_polls) = drive_query
2112            .verify_vote_polls_by_end_date_proof::<Vec<(_, _)>>(
2113                &proof.grovedb_proof,
2114                platform_version,
2115            )
2116            .map_drive_error(proof, mtd)?;
2117
2118        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2119
2120        let response = VotePollsGroupedByTimestamp(vote_polls).sorted(drive_query.order_ascending);
2121
2122        Ok((response.into_option(), mtd.clone(), proof.clone()))
2123    }
2124}
2125
2126impl FromProof<platform::GetPrefundedSpecializedBalanceRequest> for PrefundedSpecializedBalance {
2127    type Request = platform::GetPrefundedSpecializedBalanceRequest;
2128    type Response = platform::GetPrefundedSpecializedBalanceResponse;
2129
2130    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2131        request: I,
2132        response: O,
2133        _network: Network,
2134        platform_version: &PlatformVersion,
2135        provider: &'a dyn ContextProvider,
2136    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2137    where
2138        Self: Sized + 'a,
2139    {
2140        let request: Self::Request = request.into();
2141        let response: Self::Response = response.into();
2142
2143        let balance_id = match request.version.ok_or(Error::EmptyVersion)? {
2144            get_prefunded_specialized_balance_request::Version::V0(v0) => {
2145                Identifier::from_vec(v0.id).map_err(|e| Error::RequestError {
2146                    error: e.to_string(),
2147                })?
2148            }
2149        };
2150
2151        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2152
2153        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2154
2155        let (root_hash, balance) = Drive::verify_specialized_balance(
2156            &proof.grovedb_proof,
2157            balance_id.into_buffer(),
2158            false,
2159            platform_version,
2160        )
2161        .map_drive_error(proof, mtd)?;
2162
2163        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2164
2165        Ok((balance.map(|v| v.into()), mtd.clone(), proof.clone()))
2166    }
2167}
2168
2169impl FromProof<platform::GetContestedResourceIdentityVotesRequest> for Vote {
2170    type Request = platform::GetContestedResourceIdentityVotesRequest;
2171    type Response = platform::GetContestedResourceIdentityVotesResponse;
2172
2173    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2174        request: I,
2175        response: O,
2176        network: Network,
2177        platform_version: &PlatformVersion,
2178        provider: &'a dyn ContextProvider,
2179    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2180    where
2181        Self: Sized + 'a,
2182    {
2183        let request = request.into();
2184        let id_in_request = match request.version.as_ref().ok_or(Error::EmptyVersion)? {
2185            get_contested_resource_identity_votes_request::Version::V0(v0) => {
2186                Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::RequestError {
2187                    error: e.to_string(),
2188                })?
2189            }
2190        };
2191
2192        let (maybe_votes, mtd, proof) = ResourceVotesByIdentity::maybe_from_proof_with_metadata(
2193            request,
2194            response,
2195            network,
2196            platform_version,
2197            provider,
2198        )?;
2199
2200        let (id, vote) = match maybe_votes {
2201            Some(v) if v.len() > 1 => {
2202                return Err(Error::ResponseDecodeError {
2203                    error: format!("expected 1 vote, got {}", v.len()),
2204                })
2205            }
2206            Some(v) if v.is_empty() => return Ok((None, mtd, proof)),
2207            Some(v) => v
2208                .into_iter()
2209                .next()
2210                .expect("is_empty() must detect empty map"),
2211            None => return Ok((None, mtd, proof)),
2212        };
2213
2214        if id != id_in_request {
2215            return Err(Error::ResponseDecodeError {
2216                error: format!(
2217                    "expected vote for identity {}, got vote for identity {}",
2218                    id_in_request, id
2219                ),
2220            });
2221        }
2222
2223        Ok((vote.map(Vote::ResourceVote), mtd, proof))
2224    }
2225}
2226
2227impl FromProof<platform::GetTotalCreditsInPlatformRequest> for TotalCreditsInPlatform {
2228    type Request = platform::GetTotalCreditsInPlatformRequest;
2229    type Response = platform::GetTotalCreditsInPlatformResponse;
2230
2231    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2232        _request: I,
2233        response: O,
2234        network: Network,
2235        platform_version: &PlatformVersion,
2236        provider: &'a dyn ContextProvider,
2237    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2238    where
2239        Self: Sized + 'a,
2240    {
2241        let response: Self::Response = response.into();
2242        // Parse response to read proof and metadata
2243        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2244        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2245
2246        let core_subsidy_halving_interval = network.core_subsidy_halving_interval();
2247
2248        let (root_hash, credits) = Drive::verify_total_credits_in_system(
2249            &proof.grovedb_proof,
2250            core_subsidy_halving_interval,
2251            || {
2252                provider.get_platform_activation_height().map_err(|e| {
2253                    drive::error::Error::Proof(ProofError::MissingContextRequirement(e.to_string()))
2254                })
2255            },
2256            mtd.core_chain_locked_height,
2257            platform_version,
2258        )
2259        .map_drive_error(proof, mtd)?;
2260
2261        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2262
2263        Ok((
2264            Some(TotalCreditsInPlatform(credits)),
2265            mtd.clone(),
2266            proof.clone(),
2267        ))
2268    }
2269}
2270impl FromProof<platform::GetEvonodesProposedEpochBlocksByIdsRequest> for ProposerBlockCounts {
2271    type Request = platform::GetEvonodesProposedEpochBlocksByIdsRequest;
2272    type Response = platform::GetEvonodesProposedEpochBlocksResponse;
2273
2274    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2275        request: I,
2276        response: O,
2277        _network: Network,
2278        platform_version: &PlatformVersion,
2279        provider: &'a dyn ContextProvider,
2280    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2281    where
2282        Self: Sized + 'a,
2283    {
2284        let request: Self::Request = request.into();
2285        let response: Self::Response = response.into();
2286        // Parse response to read proof and metadata
2287        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2288        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2289
2290        let (ids, epoch) = match request.version.ok_or(Error::EmptyVersion)? {
2291            get_evonodes_proposed_epoch_blocks_by_ids_request::Version::V0(v0) => {
2292                (v0.ids, v0.epoch)
2293            }
2294        };
2295
2296        let epoch_index = match epoch {
2297            Some(index) => try_u32_to_u16(index)?,
2298            None => try_u32_to_u16(mtd.epoch)?,
2299        };
2300
2301        let (root_hash, proposer_block_counts) = Drive::verify_epoch_proposers(
2302            &proof.grovedb_proof,
2303            epoch_index,
2304            ProposerQueryType::ByIds(ids),
2305            platform_version,
2306        )
2307        .map_drive_error(proof, mtd)?;
2308
2309        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2310
2311        Ok((
2312            Some(ProposerBlockCounts(proposer_block_counts)),
2313            mtd.clone(),
2314            proof.clone(),
2315        ))
2316    }
2317}
2318
2319impl FromProof<platform::GetEvonodesProposedEpochBlocksByRangeRequest> for ProposerBlockCounts {
2320    type Request = platform::GetEvonodesProposedEpochBlocksByRangeRequest;
2321    type Response = platform::GetEvonodesProposedEpochBlocksResponse;
2322
2323    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2324        request: I,
2325        response: O,
2326        _network: Network,
2327        platform_version: &PlatformVersion,
2328        provider: &'a dyn ContextProvider,
2329    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2330    where
2331        Self: Sized + 'a,
2332    {
2333        let request: Self::Request = request.into();
2334        let response: Self::Response = response.into();
2335        // Parse response to read proof and metadata
2336        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2337        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2338
2339        let (epoch, limit, start) = match request.version.ok_or(Error::EmptyVersion)? {
2340            get_evonodes_proposed_epoch_blocks_by_range_request::Version::V0(v0) => {
2341                (v0.epoch, v0.limit, v0.start)
2342            }
2343        };
2344
2345        let formatted_start = match start {
2346            None => None,
2347            Some(Start::StartAfter(after)) => {
2348                let id: [u8; 32] = after.try_into().map_err(|_| Error::DriveError {
2349                    error: "Invalid public key hash length".to_string(),
2350                })?;
2351                Some((id, false))
2352            }
2353            Some(Start::StartAt(at)) => {
2354                let id: [u8; 32] = at.try_into().map_err(|_| Error::DriveError {
2355                    error: "Invalid public key hash length".to_string(),
2356                })?;
2357                Some((id, true))
2358            }
2359        };
2360
2361        let epoch_index = match epoch {
2362            Some(index) => try_u32_to_u16(index)?,
2363            None => try_u32_to_u16(mtd.epoch)?,
2364        };
2365        let checked_limit = limit.map(try_u32_to_u16).transpose()?;
2366
2367        let (root_hash, proposer_block_counts) = Drive::verify_epoch_proposers(
2368            &proof.grovedb_proof,
2369            epoch_index,
2370            ProposerQueryType::ByRange(checked_limit, formatted_start),
2371            platform_version,
2372        )
2373        .map_drive_error(proof, mtd)?;
2374
2375        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2376
2377        Ok((
2378            Some(ProposerBlockCounts(proposer_block_counts)),
2379            mtd.clone(),
2380            proof.clone(),
2381        ))
2382    }
2383}
2384
2385/// Convert u32, if 0 return None, otherwise return Some(u16).
2386/// Errors when value is out of range.
2387fn u32_to_u16_opt(i: u32) -> Result<Option<u16>, Error> {
2388    let i: Option<u16> = if i != 0 {
2389        let i: u16 = i
2390            .try_into()
2391            .map_err(|e: TryFromIntError| Error::RequestError {
2392                error: format!("value {} out of range: {}", i, e),
2393            })?;
2394        Some(i)
2395    } else {
2396        None
2397    };
2398
2399    Ok(i)
2400}
2401
2402// --- Shielded Pool Query Proof Verification ---
2403
2404impl FromProof<platform::GetShieldedPoolStateRequest> for ShieldedPoolState {
2405    type Request = platform::GetShieldedPoolStateRequest;
2406    type Response = platform::GetShieldedPoolStateResponse;
2407
2408    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2409        _request: I,
2410        response: O,
2411        _network: Network,
2412        platform_version: &PlatformVersion,
2413        provider: &'a dyn ContextProvider,
2414    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2415    where
2416        Self: Sized + 'a,
2417    {
2418        let response: Self::Response = response.into();
2419        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2420        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2421
2422        let (root_hash, maybe_balance) =
2423            Drive::verify_shielded_pool_state(&proof.grovedb_proof, false, platform_version)
2424                .map_drive_error(proof, mtd)?;
2425
2426        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2427
2428        Ok((
2429            maybe_balance.map(ShieldedPoolState),
2430            mtd.clone(),
2431            proof.clone(),
2432        ))
2433    }
2434}
2435
2436impl FromProof<platform::GetShieldedAnchorsRequest> for ShieldedAnchors {
2437    type Request = platform::GetShieldedAnchorsRequest;
2438    type Response = platform::GetShieldedAnchorsResponse;
2439
2440    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2441        _request: I,
2442        response: O,
2443        _network: Network,
2444        platform_version: &PlatformVersion,
2445        provider: &'a dyn ContextProvider,
2446    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2447    where
2448        Self: Sized + 'a,
2449    {
2450        let response: Self::Response = response.into();
2451        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2452        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2453
2454        let (root_hash, anchors) =
2455            Drive::verify_shielded_anchors(&proof.grovedb_proof, false, platform_version)
2456                .map_drive_error(proof, mtd)?;
2457
2458        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2459
2460        let result = if anchors.is_empty() {
2461            None
2462        } else {
2463            Some(ShieldedAnchors(anchors))
2464        };
2465
2466        Ok((result, mtd.clone(), proof.clone()))
2467    }
2468}
2469
2470impl FromProof<platform::GetMostRecentShieldedAnchorRequest> for MostRecentShieldedAnchor {
2471    type Request = platform::GetMostRecentShieldedAnchorRequest;
2472    type Response = platform::GetMostRecentShieldedAnchorResponse;
2473
2474    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2475        _request: I,
2476        response: O,
2477        _network: Network,
2478        platform_version: &PlatformVersion,
2479        provider: &'a dyn ContextProvider,
2480    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2481    where
2482        Self: Sized + 'a,
2483    {
2484        let response: Self::Response = response.into();
2485        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2486        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2487
2488        let (root_hash, maybe_anchor) = Drive::verify_most_recent_shielded_anchor(
2489            &proof.grovedb_proof,
2490            false,
2491            platform_version,
2492        )
2493        .map_drive_error(proof, mtd)?;
2494
2495        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2496
2497        Ok((
2498            maybe_anchor.map(MostRecentShieldedAnchor),
2499            mtd.clone(),
2500            proof.clone(),
2501        ))
2502    }
2503}
2504
2505impl FromProof<platform::GetShieldedEncryptedNotesRequest> for ShieldedEncryptedNotes {
2506    type Request = platform::GetShieldedEncryptedNotesRequest;
2507    type Response = platform::GetShieldedEncryptedNotesResponse;
2508
2509    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2510        request: I,
2511        response: O,
2512        _network: Network,
2513        platform_version: &PlatformVersion,
2514        provider: &'a dyn ContextProvider,
2515    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2516    where
2517        Self: Sized + 'a,
2518    {
2519        use dapi_grpc::platform::v0::get_shielded_encrypted_notes_request;
2520
2521        let request: Self::Request = request.into();
2522        let response: Self::Response = response.into();
2523        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2524        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2525
2526        let (start_index, count) = match request.version.ok_or(Error::EmptyVersion)? {
2527            get_shielded_encrypted_notes_request::Version::V0(v0) => (v0.start_index, v0.count),
2528        };
2529
2530        let max_elements = platform_version
2531            .drive_abci
2532            .query
2533            .shielded_queries
2534            .max_encrypted_notes_per_query as u32;
2535
2536        let (root_hash, notes) = Drive::verify_shielded_encrypted_notes(
2537            &proof.grovedb_proof,
2538            start_index,
2539            count,
2540            max_elements,
2541            false,
2542            platform_version,
2543        )
2544        .map_drive_error(proof, mtd)?;
2545
2546        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2547
2548        let result = if notes.is_empty() {
2549            None
2550        } else {
2551            Some(ShieldedEncryptedNotes(
2552                notes
2553                    .into_iter()
2554                    .map(|(cmx, nullifier, encrypted_note)| ShieldedEncryptedNote {
2555                        cmx,
2556                        nullifier,
2557                        encrypted_note,
2558                    })
2559                    .collect(),
2560            ))
2561        };
2562
2563        Ok((result, mtd.clone(), proof.clone()))
2564    }
2565}
2566
2567impl FromProof<platform::GetShieldedNullifiersRequest> for ShieldedNullifierStatuses {
2568    type Request = platform::GetShieldedNullifiersRequest;
2569    type Response = platform::GetShieldedNullifiersResponse;
2570
2571    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2572        request: I,
2573        response: O,
2574        _network: Network,
2575        platform_version: &PlatformVersion,
2576        provider: &'a dyn ContextProvider,
2577    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2578    where
2579        Self: Sized + 'a,
2580    {
2581        use dapi_grpc::platform::v0::get_shielded_nullifiers_request;
2582
2583        let request: Self::Request = request.into();
2584        let response: Self::Response = response.into();
2585        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2586        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2587
2588        let nullifiers = match request.version.ok_or(Error::EmptyVersion)? {
2589            get_shielded_nullifiers_request::Version::V0(v0) => v0.nullifiers,
2590        };
2591
2592        let (root_hash, statuses) = Drive::verify_shielded_nullifiers(
2593            &proof.grovedb_proof,
2594            &nullifiers,
2595            false,
2596            platform_version,
2597        )
2598        .map_drive_error(proof, mtd)?;
2599
2600        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2601
2602        let result = if statuses.is_empty() {
2603            None
2604        } else {
2605            Some(ShieldedNullifierStatuses(
2606                statuses
2607                    .into_iter()
2608                    .map(|(nullifier, is_spent)| {
2609                        let nullifier: [u8; 32] =
2610                            nullifier
2611                                .try_into()
2612                                .map_err(|_| Error::ResultEncodingError {
2613                                    error: "nullifier from Drive proof is not 32 bytes".to_string(),
2614                                })?;
2615                        Ok(ShieldedNullifierStatus {
2616                            nullifier,
2617                            is_spent,
2618                        })
2619                    })
2620                    .collect::<Result<Vec<_>, Error>>()?,
2621            ))
2622        };
2623
2624        Ok((result, mtd.clone(), proof.clone()))
2625    }
2626}
2627
2628impl FromProof<platform::GetRecentNullifierChangesRequest> for RecentNullifierChanges {
2629    type Request = platform::GetRecentNullifierChangesRequest;
2630    type Response = platform::GetRecentNullifierChangesResponse;
2631
2632    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2633        request: I,
2634        response: O,
2635        _network: Network,
2636        platform_version: &PlatformVersion,
2637        provider: &'a dyn ContextProvider,
2638    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2639    where
2640        RecentNullifierChanges: 'a,
2641    {
2642        use dapi_grpc::platform::v0::get_recent_nullifier_changes_request;
2643
2644        let request: Self::Request = request.into();
2645        let response: Self::Response = response.into();
2646
2647        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2648        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2649
2650        let start_height = match request.version.ok_or(Error::EmptyVersion)? {
2651            get_recent_nullifier_changes_request::Version::V0(v0) => v0.start_height,
2652        };
2653
2654        let limit = Some(100u16); // Same limit as in query handler
2655
2656        let (root_hash, verified_changes) = Drive::verify_recent_nullifier_changes(
2657            &proof.grovedb_proof,
2658            start_height,
2659            limit,
2660            false,
2661            platform_version,
2662        )
2663        .map_drive_error(proof, mtd)?;
2664
2665        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2666
2667        let result = RecentNullifierChanges(
2668            verified_changes
2669                .into_iter()
2670                .map(|change| BlockNullifierChanges {
2671                    block_height: change.block_height,
2672                    nullifiers: change.nullifiers.into_inner(),
2673                })
2674                .collect(),
2675        );
2676
2677        Ok((Some(result), mtd.clone(), proof.clone()))
2678    }
2679}
2680
2681impl FromProof<platform::GetRecentCompactedNullifierChangesRequest>
2682    for RecentCompactedNullifierChanges
2683{
2684    type Request = platform::GetRecentCompactedNullifierChangesRequest;
2685    type Response = platform::GetRecentCompactedNullifierChangesResponse;
2686
2687    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2688        request: I,
2689        response: O,
2690        _network: Network,
2691        platform_version: &PlatformVersion,
2692        provider: &'a dyn ContextProvider,
2693    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2694    where
2695        RecentCompactedNullifierChanges: 'a,
2696    {
2697        use dapi_grpc::platform::v0::get_recent_compacted_nullifier_changes_request;
2698
2699        let request: Self::Request = request.into();
2700        let response: Self::Response = response.into();
2701
2702        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2703        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2704
2705        let start_block_height = match request.version.ok_or(Error::EmptyVersion)? {
2706            get_recent_compacted_nullifier_changes_request::Version::V0(v0) => {
2707                v0.start_block_height
2708            }
2709        };
2710
2711        let limit = Some(25u16); // Same limit as in query handler
2712
2713        let (root_hash, verified_changes) = Drive::verify_compacted_nullifier_changes(
2714            &proof.grovedb_proof,
2715            start_block_height,
2716            limit,
2717            platform_version,
2718        )
2719        .map_drive_error(proof, mtd)?;
2720
2721        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2722
2723        let result = RecentCompactedNullifierChanges(
2724            verified_changes
2725                .into_iter()
2726                .map(|change| CompactedBlockNullifierChanges {
2727                    start_block_height: change.start_block,
2728                    end_block_height: change.end_block,
2729                    nullifiers: change.nullifiers.into_inner(),
2730                })
2731                .collect(),
2732        );
2733
2734        Ok((Some(result), mtd.clone(), proof.clone()))
2735    }
2736}
2737
2738/// Determine number of non-None elements
2739pub trait Length {
2740    /// Return number of non-None elements in the data structure
2741    fn count_some(&self) -> usize;
2742    /// Return number of all elements in the data structure, including None
2743    fn count(&self) -> usize;
2744}
2745
2746impl<T: Length> Length for Option<T> {
2747    fn count_some(&self) -> usize {
2748        match self {
2749            None => 0,
2750            Some(i) => i.count_some(),
2751        }
2752    }
2753    fn count(&self) -> usize {
2754        match self {
2755            None => 0,
2756            Some(i) => i.count(),
2757        }
2758    }
2759}
2760
2761impl<T> Length for Vec<Option<T>> {
2762    fn count_some(&self) -> usize {
2763        self.iter().filter(|v| v.is_some()).count()
2764    }
2765
2766    fn count(&self) -> usize {
2767        self.len()
2768    }
2769}
2770
2771impl<K, T> Length for Vec<(K, Option<T>)> {
2772    fn count_some(&self) -> usize {
2773        self.iter().filter(|(_, v)| v.is_some()).count()
2774    }
2775
2776    fn count(&self) -> usize {
2777        self.len()
2778    }
2779}
2780
2781impl<K, T> Length for BTreeMap<K, Option<T>> {
2782    fn count_some(&self) -> usize {
2783        self.values().filter(|v| v.is_some()).count()
2784    }
2785
2786    fn count(&self) -> usize {
2787        self.len()
2788    }
2789}
2790
2791impl<K, T> Length for IndexMap<K, Option<T>> {
2792    fn count_some(&self) -> usize {
2793        self.values().filter(|v| v.is_some()).count()
2794    }
2795
2796    fn count(&self) -> usize {
2797        self.len()
2798    }
2799}
2800
2801/// Implement Length trait for a type
2802///
2803/// # Arguments
2804///
2805/// * `$object`: The type for which to implement Length trait
2806/// * `$len`: A closure that returns the length of the object; if omitted, defaults to 1
2807macro_rules! define_length {
2808    ($object:ty,$some:expr,$counter:expr) => {
2809        impl Length for $object {
2810            fn count_some(&self) -> usize {
2811                #[allow(clippy::redundant_closure_call)]
2812                $some(self)
2813            }
2814
2815            fn count(&self) -> usize {
2816                #[allow(clippy::redundant_closure_call)]
2817                $counter(self)
2818            }
2819        }
2820    };
2821    ($object:ty,$some:expr) => {
2822        define_length!($object, $some, $some);
2823    };
2824    ($object:ty) => {
2825        define_length!($object, |_| 1, |_| 1);
2826    };
2827}
2828
2829define_length!(DataContract);
2830define_length!(DataContractHistory, |d: &DataContractHistory| d.len());
2831define_length!(Document);
2832define_length!(Identity);
2833define_length!(IdentityBalance);
2834define_length!(IdentityBalanceAndRevision);
2835define_length!(
2836    IdentitiesContractKeys,
2837    |x: &IdentitiesContractKeys| x.values().map(|v| v.count_some()).sum(),
2838    |x: &IdentitiesContractKeys| x.len()
2839);
2840define_length!(ContestedResources, |x: &ContestedResources| x.0.len());
2841define_length!(Contenders, |x: &Contenders| x.contenders.len());
2842define_length!(Voters, |x: &Voters| x.0.len());
2843define_length!(
2844    VotePollsGroupedByTimestamp,
2845    |x: &VotePollsGroupedByTimestamp| x.0.iter().map(|v| v.1.len()).sum(),
2846    |x: &VotePollsGroupedByTimestamp| x.0.len()
2847);
2848
2849/// Convert a type into an Option
2850trait IntoOption
2851where
2852    Self: Sized,
2853{
2854    /// For zero-length data structures, return None, otherwise return Some(self).
2855    ///
2856    /// In case of a zero-length data structure, the function returns None.
2857    /// Otherwise, it returns Some(self), even it all values are None. This is to ensure that proof of absence
2858    /// preserves the keys that are not present in the data structure.
2859    fn into_option(self) -> Option<Self>;
2860}
2861
2862impl<L: Length> IntoOption for L {
2863    fn into_option(self) -> Option<Self>
2864    where
2865        Self: Sized,
2866    {
2867        if self.count() == 0 {
2868            None
2869        } else {
2870            Some(self)
2871        }
2872    }
2873}
2874
2875#[cfg(test)]
2876mod tests {
2877    use super::*;
2878
2879    #[test]
2880    fn try_u32_to_u16_succeeds_for_valid_values() {
2881        assert_eq!(try_u32_to_u16(0).unwrap(), 0u16);
2882        assert_eq!(try_u32_to_u16(1).unwrap(), 1u16);
2883        assert_eq!(try_u32_to_u16(42).unwrap(), 42u16);
2884        assert_eq!(try_u32_to_u16(u16::MAX as u32).unwrap(), u16::MAX);
2885    }
2886
2887    #[test]
2888    fn try_u32_to_u16_errors_on_overflow() {
2889        // This is the exact attack vector: epoch 65536 would silently truncate
2890        // to 0 with `as u16`, allowing a malicious node to serve a proof for
2891        // epoch 0 while claiming the metadata epoch is 65536.
2892        let result = try_u32_to_u16(65536);
2893        assert!(
2894            result.is_err(),
2895            "epoch 65536 must not silently truncate to 0"
2896        );
2897
2898        let result = try_u32_to_u16(u32::MAX);
2899        assert!(result.is_err(), "epoch u32::MAX must not silently truncate");
2900
2901        let result = try_u32_to_u16(100_000);
2902        assert!(result.is_err(), "epoch 100000 must not silently truncate");
2903    }
2904
2905    #[test]
2906    fn u32_to_u16_opt_succeeds_for_valid_values() {
2907        assert_eq!(u32_to_u16_opt(0).unwrap(), None);
2908        assert_eq!(u32_to_u16_opt(1).unwrap(), Some(1u16));
2909        assert_eq!(u32_to_u16_opt(u16::MAX as u32).unwrap(), Some(u16::MAX));
2910    }
2911
2912    #[test]
2913    fn u32_to_u16_opt_errors_on_overflow() {
2914        let result = u32_to_u16_opt(65536);
2915        assert!(
2916            result.is_err(),
2917            "value 65536 must not silently truncate to 0"
2918        );
2919
2920        let result = u32_to_u16_opt(u32::MAX);
2921        assert!(result.is_err(), "value u32::MAX must not silently truncate");
2922    }
2923}