Skip to main content

drive_proof_verifier/
proof.rs

1pub mod document_count;
2pub mod document_split_count;
3pub mod groups;
4pub mod identity_token_balance;
5pub mod token_contract_info;
6pub mod token_direct_purchase;
7pub mod token_info;
8pub mod token_perpetual_distribution_last_claim;
9pub mod token_pre_programmed_distributions;
10pub mod token_status;
11pub mod token_total_supply;
12
13use crate::from_request::TryFromRequest;
14use crate::verify::verify_tenderdash_proof;
15use crate::{types::*, ContextProvider, DataContractProvider, Error};
16use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start;
17use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
18use dapi_grpc::platform::v0::get_path_elements_request::GetPathElementsRequestV0;
19use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_request::{
20    self, GetProtocolVersionUpgradeVoteStatusRequestV0,
21};
22use dapi_grpc::platform::v0::security_level_map::KeyKindRequestType as GrpcKeyKind;
23use dapi_grpc::platform::v0::{
24    get_address_info_request, get_addresses_infos_request,
25    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,
26    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
27};
28use dapi_grpc::platform::{
29    v0::{self as platform, key_request_type, KeyRequestType as GrpcKeyType},
30    VersionedGrpcResponse,
31};
32use dpp::address_funds::PlatformAddress;
33use dpp::block::block_info::BlockInfo;
34use dpp::block::epoch::EpochIndex;
35use dpp::block::extended_epoch_info::ExtendedEpochInfo;
36use dpp::core_subsidy::NetworkCoreSubsidy;
37use dpp::dashcore::hashes::Hash;
38use dpp::dashcore::{Network, ProTxHash};
39use dpp::document::{Document, DocumentV0Getters};
40use dpp::fee::Credits;
41use dpp::identity::identities_contract_keys::IdentitiesContractKeys;
42use dpp::identity::Purpose;
43use dpp::platform_value::{self};
44use dpp::prelude::{AddressNonce, DataContract, Identifier, Identity};
45use dpp::serialization::PlatformDeserializable;
46use dpp::state_transition::proof_result::StateTransitionProofResult;
47use dpp::state_transition::StateTransition;
48use dpp::version::PlatformVersion;
49use dpp::voting::votes::Vote;
50use drive::drive::identity::identity_and_non_unique_public_key_hash_double_proof::IdentityAndNonUniquePublicKeyHashDoubleProof;
51use drive::drive::identity::key::fetch::{
52    IdentityKeysRequest, KeyKindRequestType, KeyRequestType, PurposeU8, SecurityLevelU8,
53};
54use drive::drive::Drive;
55use drive::error::proof::ProofError;
56use drive::grovedb::Error as GroveError;
57use drive::grovedb::GroveTrunkQueryResult;
58use drive::query::contested_resource_votes_given_by_identity_query::ContestedResourceVotesGivenByIdentityQuery;
59use drive::query::proposer_block_count_query::ProposerQueryType;
60use drive::query::vote_poll_contestant_votes_query::ContestedDocumentVotePollVotesDriveQuery;
61use drive::query::vote_poll_vote_state_query::ContestedDocumentVotePollDriveQuery;
62use drive::query::vote_polls_by_document_type_query::VotePollsByDocumentTypeQuery;
63use drive::query::{DriveDocumentQuery, VotePollsByEndDateDriveQuery};
64use indexmap::IndexMap;
65use std::array::TryFromSliceError;
66use std::collections::BTreeMap;
67use std::num::TryFromIntError;
68use crate::error::MapGroveDbError;
69
70/// Parse and verify the received proof and retrieve the requested object, if any.
71///
72/// Use [`FromProof::maybe_from_proof()`] or [`FromProof::from_proof()`] to parse and verify proofs received
73/// from the Dash Platform (including verification of grovedb-generated proofs and cryptographic proofs generated
74/// by Tenderdash).
75///
76/// gRPC responses, received from the Dash Platform in response to requests containing `prove: true`, contain
77/// GroveDB proof structure (including encapsulated objects) and metadata required to verify cryptographic proof
78/// generated by the Tenderdash. This trait provides methods that parse and verify the proof and retrieve the requested
79/// object (or information that the object does not exist) in one step.
80///
81/// This trait is implemented by several objects defined in [Dash Platform Protocol](dpp), like [Identity],
82/// [DataContract], [Documents], etc. It is also implemented by several helper objects from [types] module.
83pub trait FromProof<Req> {
84    /// Request type for which this trait is implemented.
85    type Request;
86    /// Response type for which this trait is implemented.
87    type Response;
88
89    /// Parse and verify the received proof and retrieve the requested object, if any.
90    ///
91    /// # Arguments
92    ///
93    /// * `request`: The request sent to the server.
94    /// * `response`: The response received from the server.
95    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
96    /// * `platform_version`: The platform version that should be used.
97    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
98    ///
99    /// # Returns
100    ///
101    /// * `Ok(Some(object, metadata))` when the requested object was found in the proof.
102    /// * `Ok(None)` when the requested object was not found in the proof; this can be interpreted as proof of non-existence.
103    ///   For collections, returns Ok(None) if none of the requested objects were found.
104    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
105    fn maybe_from_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
106        request: I,
107        response: O,
108        network: Network,
109        platform_version: &PlatformVersion,
110        provider: &'a dyn ContextProvider,
111    ) -> Result<Option<Self>, Error>
112    where
113        Self: Sized + 'a,
114    {
115        Self::maybe_from_proof_with_metadata(request, response, network, platform_version, provider)
116            .map(|maybe_result| maybe_result.0)
117    }
118
119    /// Parse and verify the received proof and retrieve the requested object, if any.
120    ///
121    /// # Arguments
122    ///
123    /// * `request`: The request sent to the server.
124    /// * `response`: The response received from the server.
125    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
126    /// * `platform_version`: The platform version that should be used.
127    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
128    ///
129    /// # Returns
130    ///
131    /// * `Ok(Some((object, metadata)))` when the requested object was found in the proof.
132    /// * `Ok(None)` when the requested object was not found in the proof; this can be interpreted as proof of non-existence.
133    ///   For collections, returns Ok(None) if none of the requested objects were found.
134    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
135    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
136        request: I,
137        response: O,
138        network: Network,
139        platform_version: &PlatformVersion,
140        provider: &'a dyn ContextProvider,
141    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
142    where
143        Self: Sized + 'a;
144
145    /// Retrieve the requested object from the proof.
146    ///
147    /// Runs full verification of the proof and retrieves enclosed objects.
148    ///
149    /// This method uses [`FromProof::maybe_from_proof()`] internally and throws an error
150    /// if the requested object does not exist in the proof.
151    ///
152    /// # Arguments
153    ///
154    /// * `request`: The request sent to the server.
155    /// * `response`: The response received from the server.
156    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
157    /// * `platform_version`: The platform version that should be used.
158    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
159    ///
160    /// # Returns
161    ///
162    /// * `Ok(object)` when the requested object was found in the proof.
163    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
164    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
165    fn from_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
166        request: I,
167        response: O,
168        network: Network,
169        platform_version: &PlatformVersion,
170        provider: &'a dyn ContextProvider,
171    ) -> Result<Self, Error>
172    where
173        Self: Sized + 'a,
174    {
175        Self::maybe_from_proof(request, response, network, platform_version, provider)?
176            .ok_or(Error::NotFound)
177    }
178
179    /// Retrieve the requested object from the proof with metadata.
180    ///
181    /// Runs full verification of the proof and retrieves enclosed objects.
182    ///
183    /// This method uses [`FromProof::maybe_from_proof_with_metadata()`] internally and throws an error
184    /// if the requested object does not exist in the proof.
185    ///
186    /// # Arguments
187    ///
188    /// * `request`: The request sent to the server.
189    /// * `response`: The response received from the server.
190    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
191    /// * `platform_version`: The platform version that should be used.
192    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
193    ///
194    /// # Returns
195    ///
196    /// * `Ok(Some(object, metadata))` when the requested object was found in the proof.
197    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
198    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
199    fn from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
200        request: I,
201        response: O,
202        network: Network,
203        platform_version: &PlatformVersion,
204        provider: &'a dyn ContextProvider,
205    ) -> Result<(Self, ResponseMetadata), Error>
206    where
207        Self: Sized + 'a,
208    {
209        let (main_item, response_metadata, _) = Self::maybe_from_proof_with_metadata(
210            request,
211            response,
212            network,
213            platform_version,
214            provider,
215        )?;
216        Ok((main_item.ok_or(Error::NotFound)?, response_metadata))
217    }
218
219    /// Retrieve the requested object from the proof with metadata.
220    ///
221    /// Runs full verification of the proof and retrieves enclosed objects.
222    ///
223    /// This method uses [`FromProof::maybe_from_proof_with_metadata()`] internally and throws an error
224    /// if the requested object does not exist in the proof.
225    ///
226    /// # Arguments
227    ///
228    /// * `request`: The request sent to the server.
229    /// * `response`: The response received from the server.
230    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
231    /// * `platform_version`: The platform version that should be used.
232    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
233    ///
234    /// # Returns
235    ///
236    /// * `Ok(Some(object, metadata, proof))` when the requested object was found in the proof.
237    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
238    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
239    fn from_proof_with_metadata_and_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
240        request: I,
241        response: O,
242        network: Network,
243        platform_version: &PlatformVersion,
244        provider: &'a dyn ContextProvider,
245    ) -> Result<(Self, ResponseMetadata, Proof), Error>
246    where
247        Self: Sized + 'a,
248    {
249        let (main_item, response_metadata, proof) = Self::maybe_from_proof_with_metadata(
250            request,
251            response,
252            network,
253            platform_version,
254            provider,
255        )?;
256        Ok((main_item.ok_or(Error::NotFound)?, response_metadata, proof))
257    }
258}
259
260impl FromProof<platform::GetIdentityRequest> for Identity {
261    type Request = platform::GetIdentityRequest;
262    type Response = platform::GetIdentityResponse;
263
264    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
265        request: I,
266        response: O,
267        _network: Network,
268        platform_version: &PlatformVersion,
269        provider: &'a dyn ContextProvider,
270    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
271    where
272        Identity: Sized + 'a,
273    {
274        let request: platform::GetIdentityRequest = request.into();
275        let response: Self::Response = response.into();
276
277        // Parse response to read proof and metadata
278        let proof = response.proof().or(Err(Error::NoProofInResult))?;
279        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
280
281        let id = match request.version.ok_or(Error::EmptyVersion)? {
282            get_identity_request::Version::V0(v0) => {
283                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
284                    error: e.to_string(),
285                })?
286            }
287        };
288
289        // Extract content from proof and verify Drive/GroveDB proofs
290        let (root_hash, maybe_identity) = Drive::verify_full_identity_by_identity_id(
291            &proof.grovedb_proof,
292            false,
293            id.into_buffer(),
294            platform_version,
295        )
296        .map_drive_error(proof, mtd)?;
297
298        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
299
300        Ok((maybe_identity, mtd.clone(), proof.clone()))
301    }
302}
303
304// TODO: figure out how to deal with mock::automock
305impl FromProof<platform::GetIdentityByPublicKeyHashRequest> for Identity {
306    type Request = platform::GetIdentityByPublicKeyHashRequest;
307    type Response = platform::GetIdentityByPublicKeyHashResponse;
308
309    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
310        request: I,
311        response: O,
312        _network: Network,
313        platform_version: &PlatformVersion,
314        provider: &'a dyn ContextProvider,
315    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
316    where
317        Identity: 'a,
318    {
319        let request = request.into();
320        let response = response.into();
321        // Parse response to read proof and metadata
322        let proof = response.proof().or(Err(Error::NoProofInResult))?;
323
324        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
325
326        let public_key_hash = match request.version.ok_or(Error::EmptyVersion)? {
327            get_identity_by_public_key_hash_request::Version::V0(v0) => {
328                let public_key_hash: [u8; 20] =
329                    v0.public_key_hash
330                        .try_into()
331                        .map_err(|_| Error::DriveError {
332                            error: "Invalid public key hash length".to_string(),
333                        })?;
334                public_key_hash
335            }
336        };
337
338        // Extract content from proof and verify Drive/GroveDB proofs
339        let (root_hash, maybe_identity) = Drive::verify_full_identity_by_unique_public_key_hash(
340            &proof.grovedb_proof,
341            public_key_hash,
342            platform_version,
343        )
344        .map_drive_error(proof, mtd)?;
345
346        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
347
348        Ok((maybe_identity, mtd.clone(), proof.clone()))
349    }
350}
351
352impl FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest> for Identity {
353    type Request = platform::GetIdentityByNonUniquePublicKeyHashRequest;
354    type Response = platform::GetIdentityByNonUniquePublicKeyHashResponse;
355    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
356        request: I,
357        response: O,
358        _network: Network,
359        platform_version: &PlatformVersion,
360        provider: &'a dyn ContextProvider,
361    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
362    where
363        Self: Sized + 'a,
364    {
365        let request = request.into();
366        let response = response.into();
367        // Parse response to read proof and metadata
368        // note that proof in this case is different
369        // let proof = response.proof().or(Err(Error::NoProofInResult))?;
370        use platform::get_identity_by_non_unique_public_key_hash_response::{
371            get_identity_by_non_unique_public_key_hash_response_v0::Result as V0Result, Version::V0,
372        };
373
374        let (proved_response, mtd) = match response.version {
375            Some(V0(v0)) => {
376                let proof = if let V0Result::Proof(p) = v0.result.ok_or(Error::NoProofInResult)? {
377                    p
378                } else {
379                    return Err(Error::NoProofInResult);
380                };
381
382                (proof, v0.metadata.ok_or(Error::EmptyResponseMetadata)?)
383            }
384            _ => return Err(Error::EmptyResponseMetadata),
385        };
386
387        // let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
388
389        let (public_key_hash, after_identity) = match request.version.ok_or(Error::EmptyVersion)? {
390            get_identity_by_non_unique_public_key_hash_request::Version::V0(v0) => {
391                let public_key_hash =
392                    v0.public_key_hash
393                        .try_into()
394                        .map_err(|_| Error::RequestError {
395                            error: "Invalid public key hash length".to_string(),
396                        })?;
397
398                let after = v0
399                    .start_after
400                    .map(|a| {
401                        a.try_into().map_err(|_| Error::RequestError {
402                            error: "Invalid start_after length".to_string(),
403                        })
404                    })
405                    .transpose()?;
406                (public_key_hash, after)
407            }
408        };
409
410        // we need to convert some data to handle non-default proof structure for this response
411        let proof = proved_response
412            .grovedb_identity_public_key_hash_proof
413            .ok_or(Error::NoProofInResult)?;
414
415        let proof_tuple = IdentityAndNonUniquePublicKeyHashDoubleProof {
416            identity_proof: proved_response.identity_proof_bytes,
417            identity_id_public_key_hash_proof: proof.grovedb_proof.clone(),
418        };
419
420        // Extract content from proof and verify Drive/GroveDB proofs
421        let (root_hash, maybe_identity) =
422            Drive::verify_full_identity_by_non_unique_public_key_hash(
423                &proof_tuple,
424                public_key_hash,
425                after_identity,
426                platform_version,
427            )
428            .map_err(|e| match e {
429                drive::error::Error::GroveDB(e) => {
430                    // If InvalidProof error is returned, extract the path query from it
431                    let maybe_query = match e.as_ref() {
432                        GroveError::InvalidProof(path_query, ..) => Some(path_query.clone()),
433                        _ => None,
434                    };
435
436                    Error::GroveDBError {
437                        proof_bytes: proof.grovedb_proof.clone(),
438                        path_query: maybe_query,
439                        height: mtd.height,
440                        time_ms: mtd.time_ms,
441                        error: e.to_string(),
442                    }
443                }
444                _ => e.into(),
445            })?;
446
447        verify_tenderdash_proof(&proof, &mtd, &root_hash, provider)?;
448
449        Ok((maybe_identity, mtd.clone(), proof))
450    }
451}
452
453impl FromProof<platform::GetIdentityKeysRequest> for IdentityPublicKeys {
454    type Request = platform::GetIdentityKeysRequest;
455    type Response = platform::GetIdentityKeysResponse;
456
457    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
458        request: I,
459        response: O,
460        _network: Network,
461        platform_version: &PlatformVersion,
462        provider: &'a dyn ContextProvider,
463    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
464    where
465        IdentityPublicKeys: 'a,
466    {
467        let request: Self::Request = request.into();
468        let response: Self::Response = response.into();
469
470        // Parse response to read proof and metadata
471        let proof = response.proof().or(Err(Error::NoProofInResult))?;
472
473        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
474
475        let (request_type, identity_id, limit, offset) =
476            match request.version.ok_or(Error::EmptyVersion)? {
477                get_identity_keys_request::Version::V0(v0) => {
478                    let request_type = v0.request_type;
479                    let identity_id = Identifier::from_bytes(&v0.identity_id)
480                        .map_err(|e| Error::ProtocolError {
481                            error: e.to_string(),
482                        })?
483                        .into_buffer();
484                    let limit = v0.limit.map(try_u32_to_u16).transpose()?;
485                    let offset = v0.offset.map(try_u32_to_u16).transpose()?;
486                    (request_type, identity_id, limit, offset)
487                }
488            };
489
490        let request_type = parse_key_request_type(&request_type)?;
491
492        let key_request = IdentityKeysRequest {
493            identity_id,
494            request_type,
495            limit,
496            offset,
497        };
498
499        tracing::debug!(?identity_id, "checking proof of identity keys");
500
501        // Extract content from proof and verify Drive/GroveDB proofs
502        let (root_hash, maybe_identity) = Drive::verify_identity_keys_by_identity_id(
503            &proof.grovedb_proof,
504            key_request,
505            false,
506            false,
507            false,
508            platform_version,
509        )
510        .map_drive_error(proof, mtd)?;
511
512        let maybe_keys: Option<IdentityPublicKeys> = if let Some(identity) = maybe_identity {
513            if identity.loaded_public_keys.is_empty() {
514                None
515            } else {
516                let mut keys = identity
517                    .loaded_public_keys
518                    .into_iter()
519                    .map(|(k, v)| (k, Some(v.clone())))
520                    .collect::<IdentityPublicKeys>();
521
522                let mut not_found = identity
523                    .not_found_public_keys
524                    .into_iter()
525                    .map(|k| (k, None))
526                    .collect::<IdentityPublicKeys>();
527
528                keys.append(&mut not_found);
529
530                Some(keys)
531            }
532        } else {
533            None
534        };
535
536        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
537
538        Ok((maybe_keys, mtd.clone(), proof.clone()))
539    }
540}
541
542fn parse_key_request_type(request: &Option<GrpcKeyType>) -> Result<KeyRequestType, Error> {
543    let key_request_type = request
544        .to_owned()
545        .ok_or(Error::RequestError {
546            error: "missing key request type".to_string(),
547        })?
548        .request
549        .ok_or(Error::RequestError {
550            error: "empty request field in key request type".to_string(),
551        })?;
552
553    let request_type = match key_request_type {
554        key_request_type::Request::AllKeys(_) => KeyRequestType::AllKeys,
555        key_request_type::Request::SpecificKeys(specific_keys) => {
556            KeyRequestType::SpecificKeys(specific_keys.key_ids)
557        }
558        key_request_type::Request::SearchKey(search_key) => {
559            let purpose = search_key
560                .purpose_map
561                .iter()
562                .map(|(k, v)| {
563                     let v = v.security_level_map
564                            .iter()
565                            .map(|(level, &kind)| {
566                                let kt = match GrpcKeyKind::try_from(kind) {
567                                    Ok(GrpcKeyKind::CurrentKeyOfKindRequest) => {
568                                        Ok(KeyKindRequestType::CurrentKeyOfKindRequest)
569                                    }
570                                    Ok(GrpcKeyKind::AllKeysOfKindRequest) => {
571                                        Ok(KeyKindRequestType::AllKeysOfKindRequest)
572                                    }
573                                    _ => Err(Error::RequestError {
574                                        error: format!("missing requested key type: {}", kind),
575                                    }),
576                                };
577                                match kt  {
578                                    Err(e) => Err(e),
579                                    Ok(d) => Ok((*level as u8, d))
580                                }
581                            })
582                            .collect::<Result<BTreeMap<SecurityLevelU8,KeyKindRequestType>,Error>>();
583
584                            match v {
585                                Err(e) =>Err(e),
586                                Ok(d) => Ok((*k as u8,d)),
587                            }
588                })
589                .collect::<Result<BTreeMap<PurposeU8, BTreeMap<SecurityLevelU8, KeyKindRequestType>>,Error>>()?;
590
591            KeyRequestType::SearchKey(purpose)
592        }
593    };
594
595    Ok(request_type)
596}
597
598impl FromProof<platform::GetIdentityNonceRequest> for IdentityNonceFetcher {
599    type Request = platform::GetIdentityNonceRequest;
600    type Response = platform::GetIdentityNonceResponse;
601
602    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
603        request: I,
604        response: O,
605        _network: Network,
606        platform_version: &PlatformVersion,
607        provider: &'a dyn ContextProvider,
608    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
609    where
610        IdentityNonceFetcher: 'a,
611    {
612        let request: Self::Request = request.into();
613        let response: Self::Response = response.into();
614
615        // Parse response to read proof and metadata
616        let proof = response.proof().or(Err(Error::NoProofInResult))?;
617
618        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
619
620        let identity_id =
621            match request.version.ok_or(Error::EmptyVersion)? {
622                get_identity_nonce_request::Version::V0(v0) => Ok::<Identifier, Error>(
623                    Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::ProtocolError {
624                        error: e.to_string(),
625                    })?,
626                ),
627            }?;
628
629        // Extract content from proof and verify Drive/GroveDB proofs
630        let (root_hash, maybe_nonce) = Drive::verify_identity_nonce(
631            &proof.grovedb_proof,
632            identity_id.into_buffer(),
633            false,
634            platform_version,
635        )
636        .map_drive_error(proof, mtd)?;
637
638        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
639
640        Ok((
641            maybe_nonce.map(IdentityNonceFetcher),
642            mtd.clone(),
643            proof.clone(),
644        ))
645    }
646}
647
648impl FromProof<platform::GetIdentityContractNonceRequest> for IdentityContractNonceFetcher {
649    type Request = platform::GetIdentityContractNonceRequest;
650    type Response = platform::GetIdentityContractNonceResponse;
651
652    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
653        request: I,
654        response: O,
655        _network: Network,
656        platform_version: &PlatformVersion,
657        provider: &'a dyn ContextProvider,
658    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
659    where
660        IdentityContractNonceFetcher: 'a,
661    {
662        let request: Self::Request = request.into();
663        let response: Self::Response = response.into();
664
665        // Parse response to read proof and metadata
666        let proof = response.proof().or(Err(Error::NoProofInResult))?;
667
668        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
669
670        let (identity_id, contract_id) = match request.version.ok_or(Error::EmptyVersion)? {
671            get_identity_contract_nonce_request::Version::V0(v0) => {
672                Ok::<(Identifier, Identifier), Error>((
673                    Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::ProtocolError {
674                        error: e.to_string(),
675                    })?,
676                    Identifier::from_bytes(&v0.contract_id).map_err(|e| Error::ProtocolError {
677                        error: e.to_string(),
678                    })?,
679                ))
680            }
681        }?;
682
683        // Extract content from proof and verify Drive/GroveDB proofs
684        let (root_hash, maybe_identity) = Drive::verify_identity_contract_nonce(
685            &proof.grovedb_proof,
686            identity_id.into_buffer(),
687            contract_id.into_buffer(),
688            false,
689            platform_version,
690        )
691        .map_drive_error(proof, mtd)?;
692
693        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
694
695        Ok((
696            maybe_identity.map(IdentityContractNonceFetcher),
697            mtd.clone(),
698            proof.clone(),
699        ))
700    }
701}
702
703impl FromProof<platform::GetIdentityBalanceRequest> for IdentityBalance {
704    type Request = platform::GetIdentityBalanceRequest;
705    type Response = platform::GetIdentityBalanceResponse;
706
707    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
708        request: I,
709        response: O,
710        _network: Network,
711        platform_version: &PlatformVersion,
712        provider: &'a dyn ContextProvider,
713    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
714    where
715        IdentityBalance: 'a,
716    {
717        let request: Self::Request = request.into();
718        let response: Self::Response = response.into();
719
720        // Parse response to read proof and metadata
721        let proof = response.proof().or(Err(Error::NoProofInResult))?;
722
723        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
724
725        let id = match request.version.ok_or(Error::EmptyVersion)? {
726            get_identity_balance_request::Version::V0(v0) => Identifier::from_bytes(&v0.id)
727                .map_err(|e| Error::ProtocolError {
728                    error: e.to_string(),
729                }),
730        }?;
731
732        // Extract content from proof and verify Drive/GroveDB proofs
733        let (root_hash, maybe_identity) = Drive::verify_identity_balance_for_identity_id(
734            &proof.grovedb_proof,
735            id.into_buffer(),
736            false,
737            platform_version,
738        )
739        .map_drive_error(proof, mtd)?;
740
741        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
742
743        Ok((maybe_identity, mtd.clone(), proof.clone()))
744    }
745}
746
747impl FromProof<platform::GetIdentitiesBalancesRequest> for IdentityBalances {
748    type Request = platform::GetIdentitiesBalancesRequest;
749    type Response = platform::GetIdentitiesBalancesResponse;
750
751    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
752        request: I,
753        response: O,
754        _network: Network,
755        platform_version: &PlatformVersion,
756        provider: &'a dyn ContextProvider,
757    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
758    where
759        IdentityBalances: 'a,
760    {
761        let request: Self::Request = request.into();
762        let response: Self::Response = response.into();
763        // Parse response to read proof and metadata
764        let proof = response.proof().or(Err(Error::NoProofInResult))?;
765
766        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
767
768        let identities_ids = match request.version.ok_or(Error::EmptyVersion)? {
769            get_identities_balances_request::Version::V0(v0) => v0.ids,
770        };
771
772        let identity_ids = identities_ids
773            .into_iter()
774            .map(|identity_bytes| {
775                Identifier::from_bytes(&identity_bytes)
776                    .map(|identifier| identifier.into_buffer())
777                    .map_err(|e| Error::RequestError {
778                        error: format!("identities must be all 32 bytes {}", e),
779                    })
780            })
781            .collect::<Result<Vec<[u8; 32]>, Error>>()?;
782        let (root_hash, balances) = Drive::verify_identity_balances_for_identity_ids(
783            &proof.grovedb_proof,
784            false,
785            &identity_ids,
786            platform_version,
787        )
788        .map_drive_error(proof, mtd)?;
789
790        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
791
792        Ok((Some(balances), mtd.clone(), proof.clone()))
793    }
794}
795
796impl FromProof<platform::GetIdentityBalanceAndRevisionRequest> for IdentityBalanceAndRevision {
797    type Request = platform::GetIdentityBalanceAndRevisionRequest;
798    type Response = platform::GetIdentityBalanceAndRevisionResponse;
799
800    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
801        request: I,
802        response: O,
803        _network: Network,
804        platform_version: &PlatformVersion,
805        provider: &'a dyn ContextProvider,
806    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
807    where
808        IdentityBalanceAndRevision: 'a,
809    {
810        let request: Self::Request = request.into();
811        let response: Self::Response = response.into();
812
813        // Parse response to read proof and metadata
814        let proof = response.proof().or(Err(Error::NoProofInResult))?;
815
816        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
817
818        let id = match request.version.ok_or(Error::EmptyVersion)? {
819            get_identity_balance_and_revision_request::Version::V0(v0) => {
820                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
821                    error: e.to_string(),
822                })
823            }
824        }?;
825
826        // Extract content from proof and verify Drive/GroveDB proofs
827        let (root_hash, maybe_identity) =
828            Drive::verify_identity_balance_and_revision_for_identity_id(
829                &proof.grovedb_proof,
830                id.into_buffer(),
831                false,
832                platform_version,
833            )
834            .map_drive_error(proof, mtd)?;
835
836        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
837
838        Ok((maybe_identity, mtd.clone(), proof.clone()))
839    }
840}
841
842impl FromProof<platform::GetAddressInfoRequest> for AddressInfo {
843    type Request = platform::GetAddressInfoRequest;
844    type Response = platform::GetAddressInfoResponse;
845
846    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
847        request: I,
848        response: O,
849        _network: Network,
850        platform_version: &PlatformVersion,
851        provider: &'a dyn ContextProvider,
852    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
853    where
854        AddressInfo: 'a,
855    {
856        let request: Self::Request = request.into();
857        let response: Self::Response = response.into();
858
859        let proof = response.proof().or(Err(Error::NoProofInResult))?;
860        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
861
862        let address = match request.version.ok_or(Error::EmptyVersion)? {
863            get_address_info_request::Version::V0(v0) => PlatformAddress::from_bytes(&v0.address)
864                .map_err(|e| Error::RequestError {
865                error: format!("invalid address: {}", e),
866            })?,
867        };
868
869        let (root_hash, maybe_info) =
870            Drive::verify_address_info(&proof.grovedb_proof, &address, false, platform_version)
871                .map_drive_error(proof, mtd)?;
872
873        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
874
875        let info = maybe_info.map(|(nonce, balance)| AddressInfo {
876            address,
877            nonce,
878            balance,
879        });
880
881        Ok((info, mtd.clone(), proof.clone()))
882    }
883}
884
885impl FromProof<platform::GetAddressesInfosRequest> for AddressInfos {
886    type Request = platform::GetAddressesInfosRequest;
887    type Response = platform::GetAddressesInfosResponse;
888
889    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
890        request: I,
891        response: O,
892        _network: Network,
893        platform_version: &PlatformVersion,
894        provider: &'a dyn ContextProvider,
895    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
896    where
897        AddressInfos: 'a,
898    {
899        let request: Self::Request = request.into();
900        let response: Self::Response = response.into();
901
902        let proof = response.proof().or(Err(Error::NoProofInResult))?;
903        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
904
905        let addresses_bytes = match request.version.ok_or(Error::EmptyVersion)? {
906            get_addresses_infos_request::Version::V0(v0) => v0.addresses,
907        };
908
909        let addresses: Vec<PlatformAddress> = addresses_bytes
910            .into_iter()
911            .map(|bytes| {
912                PlatformAddress::from_bytes(&bytes).map_err(|e| Error::RequestError {
913                    error: format!("invalid address: {}", e),
914                })
915            })
916            .collect::<Result<_, _>>()?;
917
918        let (root_hash, entries) = Drive::verify_addresses_infos::<
919            _,
920            Vec<(PlatformAddress, Option<(AddressNonce, Credits)>)>,
921        >(
922            &proof.grovedb_proof,
923            addresses.iter(),
924            false,
925            platform_version,
926        )
927        .map_drive_error(proof, mtd)?;
928
929        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
930
931        let infos = entries
932            .into_iter()
933            .map(|(address, maybe_info)| {
934                let info = maybe_info.map(|(nonce, balance)| AddressInfo {
935                    address,
936                    nonce,
937                    balance,
938                });
939                (address, info)
940            })
941            .collect::<AddressInfos>();
942
943        Ok((Some(infos), mtd.clone(), proof.clone()))
944    }
945}
946
947impl FromProof<platform::GetRecentAddressBalanceChangesRequest> for RecentAddressBalanceChanges {
948    type Request = platform::GetRecentAddressBalanceChangesRequest;
949    type Response = platform::GetRecentAddressBalanceChangesResponse;
950
951    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
952        request: I,
953        response: O,
954        _network: Network,
955        platform_version: &PlatformVersion,
956        provider: &'a dyn ContextProvider,
957    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
958    where
959        RecentAddressBalanceChanges: 'a,
960    {
961        use dapi_grpc::platform::v0::get_recent_address_balance_changes_request;
962
963        let request: Self::Request = request.into();
964        let response: Self::Response = response.into();
965
966        let proof = response.proof().or(Err(Error::NoProofInResult))?;
967        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
968
969        let (start_height, start_height_exclusive) =
970            match request.version.ok_or(Error::EmptyVersion)? {
971                get_recent_address_balance_changes_request::Version::V0(v0) => {
972                    (v0.start_height, v0.start_height_exclusive)
973                }
974            };
975
976        let limit = Some(100u16); // Same limit as in query handler
977
978        let (root_hash, verified_changes) = if start_height_exclusive {
979            Drive::verify_recent_address_balance_changes_after(
980                &proof.grovedb_proof,
981                start_height,
982                limit,
983                false,
984                platform_version,
985            )
986            .map_drive_error(proof, mtd)?
987        } else {
988            Drive::verify_recent_address_balance_changes(
989                &proof.grovedb_proof,
990                start_height,
991                limit,
992                false,
993                platform_version,
994            )
995            .map_drive_error(proof, mtd)?
996        };
997
998        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
999
1000        let result = RecentAddressBalanceChanges(
1001            verified_changes
1002                .into_iter()
1003                .map(|(block_height, changes)| BlockAddressBalanceChanges {
1004                    block_height,
1005                    changes,
1006                })
1007                .collect(),
1008        );
1009
1010        Ok((Some(result), mtd.clone(), proof.clone()))
1011    }
1012}
1013
1014impl FromProof<platform::GetRecentCompactedAddressBalanceChangesRequest>
1015    for RecentCompactedAddressBalanceChanges
1016{
1017    type Request = platform::GetRecentCompactedAddressBalanceChangesRequest;
1018    type Response = platform::GetRecentCompactedAddressBalanceChangesResponse;
1019
1020    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1021        request: I,
1022        response: O,
1023        _network: Network,
1024        platform_version: &PlatformVersion,
1025        provider: &'a dyn ContextProvider,
1026    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1027    where
1028        RecentCompactedAddressBalanceChanges: 'a,
1029    {
1030        use dapi_grpc::platform::v0::get_recent_compacted_address_balance_changes_request;
1031
1032        let request: Self::Request = request.into();
1033        let response: Self::Response = response.into();
1034
1035        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1036        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1037
1038        let start_block_height = match request.version.ok_or(Error::EmptyVersion)? {
1039            get_recent_compacted_address_balance_changes_request::Version::V0(v0) => {
1040                v0.start_block_height
1041            }
1042        };
1043
1044        // Ensure it is the same limit as in query handler; see
1045        // packages/rs-drive-abci/src/query/address_funds/recent_compacted_address_balance_changes/v0/mod.rs
1046        let limit = Some(25u16);
1047
1048        let (root_hash, verified_changes) = Drive::verify_compacted_address_balance_changes(
1049            &proof.grovedb_proof,
1050            start_block_height,
1051            limit,
1052            platform_version,
1053        )
1054        .map_drive_error(proof, mtd)?;
1055
1056        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1057
1058        let result = RecentCompactedAddressBalanceChanges(
1059            verified_changes
1060                .into_iter()
1061                .map(|(start_block_height, end_block_height, changes)| {
1062                    CompactedBlockAddressBalanceChanges {
1063                        start_block_height,
1064                        end_block_height,
1065                        changes,
1066                    }
1067                })
1068                .collect(),
1069        );
1070
1071        Ok((Some(result), mtd.clone(), proof.clone()))
1072    }
1073}
1074
1075impl FromProof<platform::GetAddressesTrunkStateRequest> for GroveTrunkQueryResult {
1076    type Request = platform::GetAddressesTrunkStateRequest;
1077    type Response = platform::GetAddressesTrunkStateResponse;
1078
1079    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1080        _request: I,
1081        response: O,
1082        _network: Network,
1083        platform_version: &PlatformVersion,
1084        provider: &'a dyn ContextProvider,
1085    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1086    where
1087        GroveTrunkQueryResult: 'a,
1088    {
1089        let response: Self::Response = response.into();
1090
1091        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1092        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1093
1094        let (root_hash, trunk_result) =
1095            Drive::verify_address_funds_trunk_query(&proof.grovedb_proof, platform_version)
1096                .map_drive_error(proof, mtd)?;
1097
1098        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1099
1100        Ok((Some(trunk_result), mtd.clone(), proof.clone()))
1101    }
1102}
1103
1104impl FromProof<platform::GetAddressesTrunkStateRequest> for PlatformAddressTrunkState {
1105    type Request = platform::GetAddressesTrunkStateRequest;
1106    type Response = platform::GetAddressesTrunkStateResponse;
1107
1108    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1109        request: I,
1110        response: O,
1111        network: Network,
1112        platform_version: &PlatformVersion,
1113        provider: &'a dyn ContextProvider,
1114    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1115    where
1116        PlatformAddressTrunkState: 'a,
1117    {
1118        let (result, metadata, proof) = <GroveTrunkQueryResult as FromProof<
1119            platform::GetAddressesTrunkStateRequest,
1120        >>::maybe_from_proof_with_metadata(
1121            request, response, network, platform_version, provider
1122        )?;
1123
1124        Ok((result.map(PlatformAddressTrunkState), metadata, proof))
1125    }
1126}
1127
1128impl FromProof<platform::GetNullifiersTrunkStateRequest> for GroveTrunkQueryResult {
1129    type Request = platform::GetNullifiersTrunkStateRequest;
1130    type Response = platform::GetNullifiersTrunkStateResponse;
1131
1132    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1133        request: I,
1134        response: O,
1135        _network: Network,
1136        platform_version: &PlatformVersion,
1137        provider: &'a dyn ContextProvider,
1138    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1139    where
1140        GroveTrunkQueryResult: 'a,
1141    {
1142        let request: Self::Request = request.into();
1143        let response: Self::Response = response.into();
1144
1145        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1146        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1147
1148        // Extract pool_type and pool_identifier from request
1149        let (pool_type, pool_identifier) = match &request.version {
1150            Some(platform::get_nullifiers_trunk_state_request::Version::V0(v0)) => {
1151                let pool_id = if v0.pool_identifier.is_empty() {
1152                    None
1153                } else {
1154                    Some(v0.pool_identifier.as_slice())
1155                };
1156                (v0.pool_type, pool_id)
1157            }
1158            None => return Err(Error::EmptyVersion),
1159        };
1160
1161        let (root_hash, trunk_result) = Drive::verify_nullifiers_trunk_query(
1162            &proof.grovedb_proof,
1163            pool_type,
1164            pool_identifier,
1165            platform_version,
1166        )
1167        .map_drive_error(proof, mtd)?;
1168
1169        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1170
1171        Ok((Some(trunk_result), mtd.clone(), proof.clone()))
1172    }
1173}
1174
1175impl FromProof<platform::GetNullifiersTrunkStateRequest> for NullifiersTrunkState {
1176    type Request = platform::GetNullifiersTrunkStateRequest;
1177    type Response = platform::GetNullifiersTrunkStateResponse;
1178
1179    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1180        request: I,
1181        response: O,
1182        network: Network,
1183        platform_version: &PlatformVersion,
1184        provider: &'a dyn ContextProvider,
1185    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1186    where
1187        NullifiersTrunkState: 'a,
1188    {
1189        let (result, metadata, proof) = <GroveTrunkQueryResult as FromProof<
1190            platform::GetNullifiersTrunkStateRequest,
1191        >>::maybe_from_proof_with_metadata(
1192            request, response, network, platform_version, provider
1193        )?;
1194
1195        Ok((result.map(NullifiersTrunkState), metadata, proof))
1196    }
1197}
1198
1199impl FromProof<platform::GetDataContractRequest> for DataContract {
1200    type Request = platform::GetDataContractRequest;
1201    type Response = platform::GetDataContractResponse;
1202
1203    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1204        request: I,
1205        response: O,
1206        _network: Network,
1207        platform_version: &PlatformVersion,
1208        provider: &'a dyn ContextProvider,
1209    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1210    where
1211        DataContract: 'a,
1212    {
1213        let request: Self::Request = request.into();
1214        let response: Self::Response = response.into();
1215
1216        // Parse response to read proof and metadata
1217        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1218
1219        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1220
1221        let id = match request.version.ok_or(Error::EmptyVersion)? {
1222            get_data_contract_request::Version::V0(v0) => {
1223                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1224                    error: e.to_string(),
1225                })
1226            }
1227        }?;
1228
1229        // Extract content from proof and verify Drive/GroveDB proofs
1230        let (root_hash, maybe_contract) = Drive::verify_contract(
1231            &proof.grovedb_proof,
1232            None,
1233            false,
1234            false,
1235            id.into_buffer(),
1236            platform_version,
1237        )
1238        .map_drive_error(proof, mtd)?;
1239
1240        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1241
1242        Ok((maybe_contract, mtd.clone(), proof.clone()))
1243    }
1244}
1245
1246impl FromProof<platform::GetDataContractRequest> for (DataContract, Vec<u8>) {
1247    type Request = platform::GetDataContractRequest;
1248    type Response = platform::GetDataContractResponse;
1249
1250    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1251        request: I,
1252        response: O,
1253        _network: Network,
1254        platform_version: &PlatformVersion,
1255        provider: &'a dyn ContextProvider,
1256    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1257    where
1258        DataContract: 'a,
1259    {
1260        let request: Self::Request = request.into();
1261        let response: Self::Response = response.into();
1262
1263        // Parse response to read proof and metadata
1264        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1265
1266        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1267
1268        let id = match request.version.ok_or(Error::EmptyVersion)? {
1269            get_data_contract_request::Version::V0(v0) => {
1270                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1271                    error: e.to_string(),
1272                })
1273            }
1274        }?;
1275
1276        // Extract content from proof and verify Drive/GroveDB proofs
1277        let (root_hash, maybe_contract) = Drive::verify_contract_return_serialization(
1278            &proof.grovedb_proof,
1279            None,
1280            false,
1281            false,
1282            id.into_buffer(),
1283            platform_version,
1284        )
1285        .map_drive_error(proof, mtd)?;
1286
1287        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1288
1289        Ok((maybe_contract, mtd.clone(), proof.clone()))
1290    }
1291}
1292
1293impl FromProof<platform::GetDataContractsRequest> for DataContracts {
1294    type Request = platform::GetDataContractsRequest;
1295    type Response = platform::GetDataContractsResponse;
1296
1297    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1298        request: I,
1299        response: O,
1300        _network: Network,
1301        platform_version: &PlatformVersion,
1302        provider: &'a dyn ContextProvider,
1303    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1304    where
1305        DataContracts: 'a,
1306    {
1307        let request: Self::Request = request.into();
1308        let response: Self::Response = response.into();
1309
1310        // Parse response to read proof and metadata
1311        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1312
1313        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1314
1315        let ids = match request.version.ok_or(Error::EmptyVersion)? {
1316            get_data_contracts_request::Version::V0(v0) => v0.ids,
1317        };
1318
1319        let ids = ids
1320            .iter()
1321            .map(|id| {
1322                id.clone().try_into().map_err(|_e| Error::RequestError {
1323                    error: format!("wrong id size: expected: {}, got: {}", 32, id.len()),
1324                })
1325            })
1326            .collect::<Result<Vec<[u8; 32]>, Error>>()?;
1327
1328        // Extract content from proof and verify Drive/GroveDB proofs
1329        let (root_hash, contracts) = Drive::verify_contracts(
1330            &proof.grovedb_proof,
1331            false,
1332            ids.as_slice(),
1333            platform_version,
1334        )
1335        .map_drive_error(proof, mtd)?;
1336
1337        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1338        let contracts = contracts
1339            .into_iter()
1340            .map(|(k, v)| {
1341                Identifier::from_bytes(&k).map(|id| (id, v)).map_err(|e| {
1342                    Error::ResultEncodingError {
1343                        error: e.to_string(),
1344                    }
1345                })
1346            })
1347            .collect::<Result<DataContracts, Error>>()?;
1348
1349        let maybe_contracts = if contracts.is_empty() {
1350            None
1351        } else {
1352            Some(contracts)
1353        };
1354
1355        Ok((maybe_contracts, mtd.clone(), proof.clone()))
1356    }
1357}
1358
1359impl FromProof<platform::GetDataContractHistoryRequest> for DataContractHistory {
1360    type Request = platform::GetDataContractHistoryRequest;
1361    type Response = platform::GetDataContractHistoryResponse;
1362
1363    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1364        request: I,
1365        response: O,
1366        _network: Network,
1367        platform_version: &PlatformVersion,
1368        provider: &'a dyn ContextProvider,
1369    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1370    where
1371        Self: Sized + 'a,
1372    {
1373        let request: Self::Request = request.into();
1374        let response: Self::Response = response.into();
1375
1376        // Parse response to read proof and metadata
1377        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1378
1379        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1380
1381        let (id, limit, offset, start_at_ms) = match request.version.ok_or(Error::EmptyVersion)? {
1382            get_data_contract_history_request::Version::V0(v0) => {
1383                let id = Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1384                    error: e.to_string(),
1385                })?;
1386                let limit = u32_to_u16_opt(v0.limit.unwrap_or_default())?;
1387                let offset = u32_to_u16_opt(v0.offset.unwrap_or_default())?;
1388                let start_at_ms = v0.start_at_ms;
1389                (id, limit, offset, start_at_ms)
1390            }
1391        };
1392
1393        // Extract content from proof and verify Drive/GroveDB proofs
1394        let (root_hash, maybe_history) = Drive::verify_contract_history(
1395            &proof.grovedb_proof,
1396            id.into_buffer(),
1397            start_at_ms,
1398            limit,
1399            offset,
1400            platform_version,
1401        )
1402        .map_drive_error(proof, mtd)?;
1403
1404        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1405
1406        Ok((
1407            maybe_history.map(IndexMap::from_iter),
1408            mtd.clone(),
1409            proof.clone(),
1410        ))
1411    }
1412}
1413
1414impl FromProof<platform::BroadcastStateTransitionRequest> for StateTransitionProofResult {
1415    type Request = platform::BroadcastStateTransitionRequest;
1416    type Response = platform::WaitForStateTransitionResultResponse;
1417
1418    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1419        request: I,
1420        response: O,
1421        _network: Network,
1422        platform_version: &PlatformVersion,
1423        provider: &'a dyn ContextProvider,
1424    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1425    where
1426        Self: Sized + 'a,
1427    {
1428        let request: Self::Request = request.into();
1429        let response: Self::Response = response.into();
1430
1431        // Parse response to read proof and metadata
1432        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1433
1434        let state_transition = StateTransition::deserialize_from_bytes(&request.state_transition)
1435            .map_err(|e| Error::ProtocolError {
1436            error: e.to_string(),
1437        })?;
1438
1439        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1440
1441        let epoch_u16 = try_u32_to_u16(mtd.epoch).map_err(|_| {
1442            Into::<Error>::into(drive::error::Error::Proof(ProofError::InvalidMetadata(
1443                format!(
1444                    "platform returned an epoch {} that was higher than maximum of a 16 bit integer",
1445                    mtd.epoch
1446                ),
1447            )))
1448        })?;
1449
1450        let block_info = BlockInfo {
1451            time_ms: mtd.time_ms,
1452            height: mtd.height,
1453            core_height: mtd.core_chain_locked_height,
1454            epoch: epoch_u16.try_into()?,
1455        };
1456
1457        let contracts_provider_fn = provider.as_contract_lookup_fn(platform_version);
1458
1459        let (root_hash, result) = Drive::verify_state_transition_was_executed_with_proof(
1460            &state_transition,
1461            &block_info,
1462            &proof.grovedb_proof,
1463            &contracts_provider_fn,
1464            platform_version,
1465        )
1466        .map_drive_error(proof, mtd)?;
1467
1468        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1469
1470        Ok((Some(result), mtd.clone(), proof.clone()))
1471    }
1472}
1473
1474impl FromProof<platform::GetEpochsInfoRequest> for ExtendedEpochInfo {
1475    type Request = platform::GetEpochsInfoRequest;
1476    type Response = platform::GetEpochsInfoResponse;
1477
1478    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1479        request: I,
1480        response: O,
1481        network: Network,
1482        platform_version: &PlatformVersion,
1483        provider: &'a dyn ContextProvider,
1484    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1485    where
1486        Self: Sized + 'a,
1487    {
1488        let epochs = ExtendedEpochInfos::maybe_from_proof_with_metadata(
1489            request,
1490            response,
1491            network,
1492            platform_version,
1493            provider,
1494        )?;
1495
1496        if let Some(e) = epochs.0 {
1497            if e.len() != 1 {
1498                return Err(Error::RequestError {
1499                    error: format!("expected 1 epoch, got {}", e.len()),
1500                });
1501            }
1502            let epoch = e.into_iter().next().and_then(|v| v.1);
1503            Ok((epoch, epochs.1, epochs.2))
1504        } else {
1505            Ok((None, epochs.1, epochs.2))
1506        }
1507    }
1508}
1509
1510impl FromProof<platform::GetEpochsInfoRequest> for ExtendedEpochInfos {
1511    type Request = platform::GetEpochsInfoRequest;
1512    type Response = platform::GetEpochsInfoResponse;
1513
1514    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1515        request: I,
1516        response: O,
1517        _network: Network,
1518        platform_version: &PlatformVersion,
1519        provider: &'a dyn ContextProvider,
1520    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1521    where
1522        Self: Sized + 'a,
1523    {
1524        let request: Self::Request = request.into();
1525        let response: Self::Response = response.into();
1526        // Parse response to read proof and metadata
1527        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1528
1529        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1530
1531        let (start_epoch, count, ascending) = match request.version.ok_or(Error::EmptyVersion)? {
1532            get_epochs_info_request::Version::V0(v0) => (v0.start_epoch, v0.count, v0.ascending),
1533        };
1534
1535        let current_epoch: EpochIndex = try_u32_to_u16(mtd.epoch)?;
1536        let start_epoch: Option<EpochIndex> = if let Some(epoch) = start_epoch {
1537            Some(try_u32_to_u16(epoch)?)
1538        } else {
1539            None
1540        };
1541        let count = try_u32_to_u16(count)?;
1542
1543        let (root_hash, epoch_info) = Drive::verify_epoch_infos(
1544            &proof.grovedb_proof,
1545            current_epoch,
1546            start_epoch,
1547            count,
1548            ascending,
1549            platform_version,
1550        )
1551        .map_drive_error(proof, mtd)?;
1552
1553        let epoch_info = epoch_info
1554            .into_iter()
1555            .map(|v| {
1556                #[allow(clippy::infallible_destructuring_match)]
1557                let info = match &v {
1558                    ExtendedEpochInfo::V0(i) => i,
1559                };
1560
1561                (info.index, Some(v))
1562            })
1563            .collect::<ExtendedEpochInfos>();
1564
1565        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1566
1567        Ok((epoch_info.into_option(), mtd.clone(), proof.clone()))
1568    }
1569}
1570
1571impl FromProof<platform::GetFinalizedEpochInfosRequest> for FinalizedEpochInfos {
1572    type Request = platform::GetFinalizedEpochInfosRequest;
1573    type Response = platform::GetFinalizedEpochInfosResponse;
1574
1575    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1576        request: I,
1577        response: O,
1578        _network: Network,
1579        platform_version: &PlatformVersion,
1580        provider: &'a dyn ContextProvider,
1581    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1582    where
1583        Self: Sized + 'a,
1584    {
1585        let request: Self::Request = request.into();
1586        let response: Self::Response = response.into();
1587        // Parse response to read proof and metadata
1588        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1589
1590        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1591
1592        let (
1593            start_epoch_index,
1594            start_epoch_index_included,
1595            end_epoch_index,
1596            end_epoch_index_included,
1597        ) = match request.version.ok_or(Error::EmptyVersion)? {
1598            get_finalized_epoch_infos_request::Version::V0(v0) => (
1599                v0.start_epoch_index,
1600                v0.start_epoch_index_included,
1601                v0.end_epoch_index,
1602                v0.end_epoch_index_included,
1603            ),
1604        };
1605
1606        let start_epoch_index: EpochIndex = try_u32_to_u16(start_epoch_index)?;
1607        let end_epoch_index: EpochIndex = try_u32_to_u16(end_epoch_index)?;
1608
1609        let (root_hash, epoch_info) = Drive::verify_finalized_epoch_infos(
1610            &proof.grovedb_proof,
1611            start_epoch_index,
1612            start_epoch_index_included,
1613            end_epoch_index,
1614            end_epoch_index_included,
1615            platform_version,
1616        )
1617        .map_drive_error(proof, mtd)?;
1618
1619        let epoch_info = epoch_info
1620            .into_iter()
1621            .map(|(epoch_index, finalized_epoch_info)| (epoch_index, Some(finalized_epoch_info)))
1622            .collect::<FinalizedEpochInfos>();
1623
1624        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1625
1626        Ok((epoch_info.into_option(), mtd.clone(), proof.clone()))
1627    }
1628}
1629
1630fn try_u32_to_u16(i: u32) -> Result<u16, Error> {
1631    i.try_into()
1632        .map_err(|e: TryFromIntError| Error::RequestError {
1633            error: e.to_string(),
1634        })
1635}
1636
1637impl FromProof<GetProtocolVersionUpgradeStateRequest> for ProtocolVersionUpgrades {
1638    type Request = GetProtocolVersionUpgradeStateRequest;
1639    type Response = GetProtocolVersionUpgradeStateResponse;
1640
1641    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1642        _request: I,
1643        response: O,
1644        _network: Network,
1645        platform_version: &PlatformVersion,
1646        provider: &'a dyn ContextProvider,
1647    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1648    where
1649        Self: Sized + 'a,
1650    {
1651        let response: Self::Response = response.into();
1652        // Parse response to read proof and metadata
1653        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1654        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1655
1656        let (root_hash, objects) =
1657            Drive::verify_upgrade_state(&proof.grovedb_proof, platform_version)
1658                .map_drive_error(proof, mtd)?;
1659
1660        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1661
1662        // Convert objects to a map of Option values
1663        let response: Self = objects.into_iter().map(|(k, v)| (k, Some(v))).collect();
1664
1665        Ok((response.into_option(), mtd.clone(), proof.clone()))
1666    }
1667}
1668
1669impl FromProof<GetProtocolVersionUpgradeVoteStatusRequest> for MasternodeProtocolVotes {
1670    type Request = GetProtocolVersionUpgradeVoteStatusRequest;
1671    type Response = GetProtocolVersionUpgradeVoteStatusResponse;
1672
1673    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1674        request: I,
1675        response: O,
1676        _network: Network,
1677        platform_version: &PlatformVersion,
1678        provider: &'a dyn ContextProvider,
1679    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1680    where
1681        Self: Sized + 'a,
1682    {
1683        let request = request.into();
1684        let response: Self::Response = response.into();
1685        // Parse response to read proof and metadata
1686        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1687        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1688
1689        let request_v0: GetProtocolVersionUpgradeVoteStatusRequestV0 = match request.version {
1690            Some(get_protocol_version_upgrade_vote_status_request::Version::V0(v0)) => v0,
1691            None => return Err(Error::EmptyVersion),
1692        };
1693
1694        let start_pro_tx_hash: Option<[u8; 32]> =
1695            if request_v0.start_pro_tx_hash.is_empty() {
1696                None
1697            } else {
1698                Some(request_v0.start_pro_tx_hash[..].try_into().map_err(
1699                    |e: TryFromSliceError| Error::RequestError {
1700                        error: e.to_string(),
1701                    },
1702                )?)
1703            };
1704
1705        let (root_hash, objects) = Drive::verify_upgrade_vote_status(
1706            &proof.grovedb_proof,
1707            start_pro_tx_hash,
1708            try_u32_to_u16(request_v0.count)?,
1709            platform_version,
1710        )
1711        .map_drive_error(proof, mtd)?;
1712
1713        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1714
1715        if objects.is_empty() {
1716            return Ok((None, mtd.clone(), proof.clone()));
1717        }
1718        let votes: MasternodeProtocolVotes = objects
1719            .into_iter()
1720            .map(|(key, value)| {
1721                ProTxHash::from_slice(&key)
1722                    .map(|pro_tx_hash| {
1723                        (
1724                            pro_tx_hash,
1725                            Some(MasternodeProtocolVote {
1726                                pro_tx_hash,
1727                                voted_version: value,
1728                            }),
1729                        )
1730                    })
1731                    .map_err(|e| Error::ResultEncodingError {
1732                        error: e.to_string(),
1733                    })
1734            })
1735            .collect::<Result<MasternodeProtocolVotes, Error>>()?;
1736
1737        Ok((votes.into_option(), mtd.clone(), proof.clone()))
1738    }
1739}
1740
1741impl FromProof<GetPathElementsRequest> for Elements {
1742    type Request = GetPathElementsRequest;
1743    type Response = GetPathElementsResponse;
1744
1745    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1746        request: I,
1747        response: O,
1748        _network: Network,
1749        platform_version: &PlatformVersion,
1750        provider: &'a dyn ContextProvider,
1751    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1752    where
1753        Self: Sized + 'a,
1754    {
1755        let request = request.into();
1756        let response: Self::Response = response.into();
1757        // Parse response to read proof and metadata
1758        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1759        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1760
1761        let request_v0: GetPathElementsRequestV0 = match request.version {
1762            Some(get_path_elements_request::Version::V0(v0)) => v0,
1763            None => return Err(Error::EmptyVersion),
1764        };
1765
1766        let path = request_v0.path;
1767        let keys = request_v0.keys;
1768
1769        let (root_hash, objects) =
1770            Drive::verify_elements(&proof.grovedb_proof, path, keys, platform_version)?;
1771        let elements: Elements = Elements::from_iter(objects);
1772
1773        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1774
1775        Ok((elements.into_option(), mtd.clone(), proof.clone()))
1776    }
1777}
1778
1779impl<'dq, Q> FromProof<Q> for Documents
1780where
1781    Q: TryInto<DriveDocumentQuery<'dq>> + Clone + 'dq,
1782    Q::Error: std::fmt::Display,
1783{
1784    type Request = Q;
1785    type Response = platform::GetDocumentsResponse;
1786
1787    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1788        request: I,
1789        response: O,
1790        _network: Network,
1791        platform_version: &PlatformVersion,
1792        provider: &'a dyn ContextProvider,
1793    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1794    where
1795        Self: 'a,
1796    {
1797        let request: Self::Request = request.into();
1798        let response: Self::Response = response.into();
1799
1800        let request: DriveDocumentQuery<'dq> =
1801            request
1802                .clone()
1803                .try_into()
1804                .map_err(|e: Q::Error| Error::RequestError {
1805                    error: e.to_string(),
1806                })?;
1807
1808        // Parse response to read proof and metadata
1809        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1810
1811        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1812
1813        let (root_hash, documents) = request
1814            .verify_proof(&proof.grovedb_proof, platform_version)
1815            .map_drive_error(proof, mtd)?;
1816
1817        let documents = documents
1818            .into_iter()
1819            .map(|d| (d.id(), Some(d)))
1820            .collect::<Documents>();
1821
1822        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1823
1824        Ok((documents.into_option(), mtd.clone(), proof.clone()))
1825    }
1826}
1827
1828impl FromProof<platform::GetIdentitiesContractKeysRequest> for IdentitiesContractKeys {
1829    type Request = platform::GetIdentitiesContractKeysRequest;
1830    type Response = platform::GetIdentitiesContractKeysResponse;
1831
1832    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1833        request: I,
1834        response: O,
1835        _network: Network,
1836        platform_version: &PlatformVersion,
1837        provider: &'a dyn ContextProvider,
1838    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1839    where
1840        Self: 'a,
1841    {
1842        let request: Self::Request = request.into();
1843        let response: Self::Response = response.into();
1844
1845        // Parse response to read proof and metadata
1846        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1847
1848        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1849
1850        let (identities_ids, contract_id, document_type_name, purposes) =
1851            match request.version.ok_or(Error::EmptyVersion)? {
1852                get_identities_contract_keys_request::Version::V0(v0) => {
1853                    let GetIdentitiesContractKeysRequestV0 {
1854                        identities_ids,
1855                        contract_id,
1856                        document_type_name,
1857                        purposes,
1858                        ..
1859                    } = v0;
1860                    let identifiers = identities_ids
1861                        .into_iter()
1862                        .map(|identity_id_vec| {
1863                            let identifier = Identifier::from_vec(identity_id_vec)?;
1864                            Ok(identifier.to_buffer())
1865                        })
1866                        .collect::<Result<Vec<[u8; 32]>, platform_value::Error>>()
1867                        .map_err(|e| Error::ProtocolError {
1868                            error: e.to_string(),
1869                        })?;
1870                    let contract_id = Identifier::from_vec(contract_id)
1871                        .map_err(|e| Error::ProtocolError {
1872                            error: e.to_string(),
1873                        })?
1874                        .into_buffer();
1875                    let purposes = purposes
1876                        .into_iter()
1877                        .map(|purpose| {
1878                            Purpose::try_from(purpose).map_err(|e| Error::ProtocolError {
1879                                error: e.to_string(),
1880                            })
1881                        })
1882                        .collect::<Result<Vec<Purpose>, Error>>()?;
1883                    (identifiers, contract_id, document_type_name, purposes)
1884                }
1885            };
1886
1887        // Extract content from proof and verify Drive/GroveDB proofs
1888        let (root_hash, identities_contract_keys) = Drive::verify_identities_contract_keys(
1889            &proof.grovedb_proof,
1890            identities_ids.as_slice(),
1891            &contract_id,
1892            document_type_name,
1893            purposes,
1894            false,
1895            platform_version,
1896        )
1897        .map_drive_error(proof, mtd)?;
1898
1899        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1900
1901        if identities_contract_keys.is_empty() {
1902            return Ok((None, mtd.clone(), proof.clone()));
1903        }
1904
1905        Ok((Some(identities_contract_keys), mtd.clone(), proof.clone()))
1906    }
1907}
1908
1909impl FromProof<platform::GetContestedResourcesRequest> for ContestedResources {
1910    type Request = platform::GetContestedResourcesRequest;
1911    type Response = platform::GetContestedResourcesResponse;
1912
1913    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1914        request: I,
1915        response: O,
1916        _network: Network,
1917        platform_version: &PlatformVersion,
1918        provider: &'a dyn ContextProvider,
1919    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1920    where
1921        Self: Sized + 'a,
1922    {
1923        let request: Self::Request = request.into();
1924        let response: Self::Response = response.into();
1925
1926        // Decode request to get drive query
1927        let drive_query = VotePollsByDocumentTypeQuery::try_from_request(request)?;
1928        let resolved_request = drive_query.resolve_with_known_contracts_provider(
1929            &provider.as_contract_lookup_fn(platform_version),
1930        )?;
1931
1932        // Parse response to read proof and metadata
1933        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1934        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1935
1936        let (root_hash, items) = resolved_request
1937            .verify_contests_proof(&proof.grovedb_proof, platform_version)
1938            .map_drive_error(proof, mtd)?;
1939
1940        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1941
1942        let resources: ContestedResources = items.into_iter().map(ContestedResource).collect();
1943
1944        Ok((resources.into_option(), mtd.clone(), proof.clone()))
1945    }
1946}
1947
1948// rpc getContestedResourceVoteState(GetContestedResourceVoteStateRequest) returns (GetContestedResourceVoteStateResponse);
1949impl FromProof<platform::GetContestedResourceVoteStateRequest> for Contenders {
1950    type Request = platform::GetContestedResourceVoteStateRequest;
1951    type Response = platform::GetContestedResourceVoteStateResponse;
1952
1953    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1954        request: I,
1955        response: O,
1956        _network: Network,
1957        platform_version: &PlatformVersion,
1958        provider: &'a dyn ContextProvider,
1959    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1960    where
1961        Self: 'a,
1962    {
1963        let request: Self::Request = request.into();
1964        let response: Self::Response = response.into();
1965
1966        // Decode request to get drive query
1967        let drive_query = ContestedDocumentVotePollDriveQuery::try_from_request(request)?;
1968
1969        // Resolve request to get verify_*_proof
1970        let contracts_provider = provider.as_contract_lookup_fn(platform_version);
1971        let resolved_request =
1972            drive_query.resolve_with_known_contracts_provider(&contracts_provider)?;
1973
1974        // Parse response to read proof and metadata
1975        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1976        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1977
1978        let (root_hash, contested_resource_vote_state) = resolved_request
1979            .verify_vote_poll_vote_state_proof(&proof.grovedb_proof, platform_version)
1980            .map_drive_error(proof, mtd)?;
1981
1982        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1983
1984        let contenders = contested_resource_vote_state
1985            .contenders
1986            .into_iter()
1987            .map(|v| (v.identity_id(), v))
1988            .collect();
1989
1990        let response = Contenders {
1991            winner: contested_resource_vote_state.winner,
1992            contenders,
1993            abstain_vote_tally: contested_resource_vote_state.abstaining_vote_tally,
1994            lock_vote_tally: contested_resource_vote_state.locked_vote_tally,
1995        };
1996        Ok((response.into_option(), mtd.clone(), proof.clone()))
1997    }
1998}
1999
2000impl FromProof<GetContestedResourceVotersForIdentityRequest> for Voters {
2001    type Request = GetContestedResourceVotersForIdentityRequest;
2002    type Response = GetContestedResourceVotersForIdentityResponse;
2003
2004    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2005        request: I,
2006        response: O,
2007        _network: Network,
2008        platform_version: &PlatformVersion,
2009        provider: &'a dyn ContextProvider,
2010    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2011    where
2012        Self: Sized + 'a,
2013    {
2014        let request: Self::Request = request.into();
2015        let response: Self::Response = response.into();
2016
2017        // Decode request to get drive query
2018        let drive_query = ContestedDocumentVotePollVotesDriveQuery::try_from_request(request)?;
2019
2020        // Parse request to get resolved contract that implements verify_*_proof
2021        let contracts_provider = provider.as_contract_lookup_fn(platform_version);
2022
2023        let resolved_request =
2024            drive_query.resolve_with_known_contracts_provider(&contracts_provider)?;
2025
2026        // Parse response to read proof and metadata
2027        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2028        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2029
2030        let (root_hash, voters) = resolved_request
2031            .verify_vote_poll_votes_proof(&proof.grovedb_proof, platform_version)
2032            .map_drive_error(proof, mtd)?;
2033
2034        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2035
2036        if voters.is_empty() {
2037            return Ok((None, mtd.clone(), proof.clone()));
2038        }
2039        let result: Voters = voters.into_iter().map(Voter::from).collect();
2040
2041        Ok((result.into_option(), mtd.clone(), proof.clone()))
2042    }
2043}
2044
2045impl FromProof<platform::GetContestedResourceIdentityVotesRequest> for ResourceVotesByIdentity {
2046    type Request = platform::GetContestedResourceIdentityVotesRequest;
2047    type Response = platform::GetContestedResourceIdentityVotesResponse;
2048
2049    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2050        request: I,
2051        response: O,
2052        _network: Network,
2053        platform_version: &PlatformVersion,
2054        provider: &'a dyn ContextProvider,
2055    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2056    where
2057        Self: Sized + 'a,
2058    {
2059        let request: Self::Request = request.into();
2060        let response: Self::Response = response.into();
2061
2062        // Decode request to get drive query
2063        let drive_query = ContestedResourceVotesGivenByIdentityQuery::try_from_request(request)?;
2064
2065        // Parse response to read proof and metadata
2066        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2067        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2068
2069        let contract_provider_fn = provider.as_contract_lookup_fn(platform_version);
2070        let (root_hash, voters) = drive_query
2071            .verify_identity_votes_given_proof::<Vec<_>>(
2072                &proof.grovedb_proof,
2073                &contract_provider_fn,
2074                platform_version,
2075            )
2076            .map_drive_error(proof, mtd)?;
2077
2078        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2079
2080        let response: ResourceVotesByIdentity = voters
2081            .into_iter()
2082            .map(|(id, vote)| (id, Some(vote)))
2083            .collect();
2084
2085        Ok((response.into_option(), mtd.clone(), proof.clone()))
2086    }
2087}
2088
2089impl FromProof<platform::GetVotePollsByEndDateRequest> for VotePollsGroupedByTimestamp {
2090    type Request = platform::GetVotePollsByEndDateRequest;
2091    type Response = platform::GetVotePollsByEndDateResponse;
2092
2093    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2094        request: I,
2095        response: O,
2096        _network: Network,
2097        platform_version: &PlatformVersion,
2098        provider: &'a dyn ContextProvider,
2099    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2100    where
2101        Self: Sized + 'a,
2102    {
2103        let request: Self::Request = request.into();
2104        let response: Self::Response = response.into();
2105
2106        // Decode request to get drive query
2107        let drive_query = VotePollsByEndDateDriveQuery::try_from_request(request)?;
2108
2109        // Parse response to read proof and metadata
2110        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2111        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2112
2113        let (root_hash, vote_polls) = drive_query
2114            .verify_vote_polls_by_end_date_proof::<Vec<(_, _)>>(
2115                &proof.grovedb_proof,
2116                platform_version,
2117            )
2118            .map_drive_error(proof, mtd)?;
2119
2120        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2121
2122        let response = VotePollsGroupedByTimestamp(vote_polls).sorted(drive_query.order_ascending);
2123
2124        Ok((response.into_option(), mtd.clone(), proof.clone()))
2125    }
2126}
2127
2128impl FromProof<platform::GetPrefundedSpecializedBalanceRequest> for PrefundedSpecializedBalance {
2129    type Request = platform::GetPrefundedSpecializedBalanceRequest;
2130    type Response = platform::GetPrefundedSpecializedBalanceResponse;
2131
2132    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2133        request: I,
2134        response: O,
2135        _network: Network,
2136        platform_version: &PlatformVersion,
2137        provider: &'a dyn ContextProvider,
2138    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2139    where
2140        Self: Sized + 'a,
2141    {
2142        let request: Self::Request = request.into();
2143        let response: Self::Response = response.into();
2144
2145        let balance_id = match request.version.ok_or(Error::EmptyVersion)? {
2146            get_prefunded_specialized_balance_request::Version::V0(v0) => {
2147                Identifier::from_vec(v0.id).map_err(|e| Error::RequestError {
2148                    error: e.to_string(),
2149                })?
2150            }
2151        };
2152
2153        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2154
2155        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2156
2157        let (root_hash, balance) = Drive::verify_specialized_balance(
2158            &proof.grovedb_proof,
2159            balance_id.into_buffer(),
2160            false,
2161            platform_version,
2162        )
2163        .map_drive_error(proof, mtd)?;
2164
2165        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2166
2167        Ok((balance.map(|v| v.into()), mtd.clone(), proof.clone()))
2168    }
2169}
2170
2171impl FromProof<platform::GetContestedResourceIdentityVotesRequest> for Vote {
2172    type Request = platform::GetContestedResourceIdentityVotesRequest;
2173    type Response = platform::GetContestedResourceIdentityVotesResponse;
2174
2175    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2176        request: I,
2177        response: O,
2178        network: Network,
2179        platform_version: &PlatformVersion,
2180        provider: &'a dyn ContextProvider,
2181    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2182    where
2183        Self: Sized + 'a,
2184    {
2185        let request = request.into();
2186        let id_in_request = match request.version.as_ref().ok_or(Error::EmptyVersion)? {
2187            get_contested_resource_identity_votes_request::Version::V0(v0) => {
2188                Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::RequestError {
2189                    error: e.to_string(),
2190                })?
2191            }
2192        };
2193
2194        let (maybe_votes, mtd, proof) = ResourceVotesByIdentity::maybe_from_proof_with_metadata(
2195            request,
2196            response,
2197            network,
2198            platform_version,
2199            provider,
2200        )?;
2201
2202        let (id, vote) = match maybe_votes {
2203            Some(v) if v.len() > 1 => {
2204                return Err(Error::ResponseDecodeError {
2205                    error: format!("expected 1 vote, got {}", v.len()),
2206                })
2207            }
2208            Some(v) if v.is_empty() => return Ok((None, mtd, proof)),
2209            Some(v) => v
2210                .into_iter()
2211                .next()
2212                .expect("is_empty() must detect empty map"),
2213            None => return Ok((None, mtd, proof)),
2214        };
2215
2216        if id != id_in_request {
2217            return Err(Error::ResponseDecodeError {
2218                error: format!(
2219                    "expected vote for identity {}, got vote for identity {}",
2220                    id_in_request, id
2221                ),
2222            });
2223        }
2224
2225        Ok((vote.map(Vote::ResourceVote), mtd, proof))
2226    }
2227}
2228
2229impl FromProof<platform::GetTotalCreditsInPlatformRequest> for TotalCreditsInPlatform {
2230    type Request = platform::GetTotalCreditsInPlatformRequest;
2231    type Response = platform::GetTotalCreditsInPlatformResponse;
2232
2233    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2234        _request: I,
2235        response: O,
2236        network: Network,
2237        platform_version: &PlatformVersion,
2238        provider: &'a dyn ContextProvider,
2239    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2240    where
2241        Self: Sized + 'a,
2242    {
2243        let response: Self::Response = response.into();
2244        // Parse response to read proof and metadata
2245        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2246        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2247
2248        let core_subsidy_halving_interval = network.core_subsidy_halving_interval();
2249
2250        let (root_hash, credits) = Drive::verify_total_credits_in_system(
2251            &proof.grovedb_proof,
2252            core_subsidy_halving_interval,
2253            || {
2254                provider.get_platform_activation_height().map_err(|e| {
2255                    drive::error::Error::Proof(ProofError::MissingContextRequirement(e.to_string()))
2256                })
2257            },
2258            mtd.core_chain_locked_height,
2259            platform_version,
2260        )
2261        .map_drive_error(proof, mtd)?;
2262
2263        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2264
2265        Ok((
2266            Some(TotalCreditsInPlatform(credits)),
2267            mtd.clone(),
2268            proof.clone(),
2269        ))
2270    }
2271}
2272impl FromProof<platform::GetEvonodesProposedEpochBlocksByIdsRequest> for ProposerBlockCounts {
2273    type Request = platform::GetEvonodesProposedEpochBlocksByIdsRequest;
2274    type Response = platform::GetEvonodesProposedEpochBlocksResponse;
2275
2276    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2277        request: I,
2278        response: O,
2279        _network: Network,
2280        platform_version: &PlatformVersion,
2281        provider: &'a dyn ContextProvider,
2282    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2283    where
2284        Self: Sized + 'a,
2285    {
2286        let request: Self::Request = request.into();
2287        let response: Self::Response = response.into();
2288        // Parse response to read proof and metadata
2289        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2290        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2291
2292        let (ids, epoch) = match request.version.ok_or(Error::EmptyVersion)? {
2293            get_evonodes_proposed_epoch_blocks_by_ids_request::Version::V0(v0) => {
2294                (v0.ids, v0.epoch)
2295            }
2296        };
2297
2298        let epoch_index = match epoch {
2299            Some(index) => try_u32_to_u16(index)?,
2300            None => try_u32_to_u16(mtd.epoch)?,
2301        };
2302
2303        let (root_hash, proposer_block_counts) = Drive::verify_epoch_proposers(
2304            &proof.grovedb_proof,
2305            epoch_index,
2306            ProposerQueryType::ByIds(ids),
2307            platform_version,
2308        )
2309        .map_drive_error(proof, mtd)?;
2310
2311        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2312
2313        Ok((
2314            Some(ProposerBlockCounts(proposer_block_counts)),
2315            mtd.clone(),
2316            proof.clone(),
2317        ))
2318    }
2319}
2320
2321impl FromProof<platform::GetEvonodesProposedEpochBlocksByRangeRequest> for ProposerBlockCounts {
2322    type Request = platform::GetEvonodesProposedEpochBlocksByRangeRequest;
2323    type Response = platform::GetEvonodesProposedEpochBlocksResponse;
2324
2325    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2326        request: I,
2327        response: O,
2328        _network: Network,
2329        platform_version: &PlatformVersion,
2330        provider: &'a dyn ContextProvider,
2331    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2332    where
2333        Self: Sized + 'a,
2334    {
2335        let request: Self::Request = request.into();
2336        let response: Self::Response = response.into();
2337        // Parse response to read proof and metadata
2338        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2339        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2340
2341        let (epoch, limit, start) = match request.version.ok_or(Error::EmptyVersion)? {
2342            get_evonodes_proposed_epoch_blocks_by_range_request::Version::V0(v0) => {
2343                (v0.epoch, v0.limit, v0.start)
2344            }
2345        };
2346
2347        let formatted_start = match start {
2348            None => None,
2349            Some(Start::StartAfter(after)) => {
2350                let id: [u8; 32] = after.try_into().map_err(|_| Error::DriveError {
2351                    error: "Invalid public key hash length".to_string(),
2352                })?;
2353                Some((id, false))
2354            }
2355            Some(Start::StartAt(at)) => {
2356                let id: [u8; 32] = at.try_into().map_err(|_| Error::DriveError {
2357                    error: "Invalid public key hash length".to_string(),
2358                })?;
2359                Some((id, true))
2360            }
2361        };
2362
2363        let epoch_index = match epoch {
2364            Some(index) => try_u32_to_u16(index)?,
2365            None => try_u32_to_u16(mtd.epoch)?,
2366        };
2367        let checked_limit = limit.map(try_u32_to_u16).transpose()?;
2368
2369        let (root_hash, proposer_block_counts) = Drive::verify_epoch_proposers(
2370            &proof.grovedb_proof,
2371            epoch_index,
2372            ProposerQueryType::ByRange(checked_limit, formatted_start),
2373            platform_version,
2374        )
2375        .map_drive_error(proof, mtd)?;
2376
2377        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2378
2379        Ok((
2380            Some(ProposerBlockCounts(proposer_block_counts)),
2381            mtd.clone(),
2382            proof.clone(),
2383        ))
2384    }
2385}
2386
2387/// Convert u32, if 0 return None, otherwise return Some(u16).
2388/// Errors when value is out of range.
2389fn u32_to_u16_opt(i: u32) -> Result<Option<u16>, Error> {
2390    let i: Option<u16> = if i != 0 {
2391        let i: u16 = i
2392            .try_into()
2393            .map_err(|e: TryFromIntError| Error::RequestError {
2394                error: format!("value {} out of range: {}", i, e),
2395            })?;
2396        Some(i)
2397    } else {
2398        None
2399    };
2400
2401    Ok(i)
2402}
2403
2404// --- Shielded Pool Query Proof Verification ---
2405
2406impl FromProof<platform::GetShieldedPoolStateRequest> for ShieldedPoolState {
2407    type Request = platform::GetShieldedPoolStateRequest;
2408    type Response = platform::GetShieldedPoolStateResponse;
2409
2410    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2411        _request: I,
2412        response: O,
2413        _network: Network,
2414        platform_version: &PlatformVersion,
2415        provider: &'a dyn ContextProvider,
2416    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2417    where
2418        Self: Sized + 'a,
2419    {
2420        let response: Self::Response = response.into();
2421        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2422        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2423
2424        let (root_hash, maybe_balance) =
2425            Drive::verify_shielded_pool_state(&proof.grovedb_proof, false, platform_version)
2426                .map_drive_error(proof, mtd)?;
2427
2428        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2429
2430        Ok((
2431            maybe_balance.map(ShieldedPoolState),
2432            mtd.clone(),
2433            proof.clone(),
2434        ))
2435    }
2436}
2437
2438impl FromProof<platform::GetShieldedAnchorsRequest> for ShieldedAnchors {
2439    type Request = platform::GetShieldedAnchorsRequest;
2440    type Response = platform::GetShieldedAnchorsResponse;
2441
2442    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2443        _request: I,
2444        response: O,
2445        _network: Network,
2446        platform_version: &PlatformVersion,
2447        provider: &'a dyn ContextProvider,
2448    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2449    where
2450        Self: Sized + 'a,
2451    {
2452        let response: Self::Response = response.into();
2453        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2454        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2455
2456        let (root_hash, anchors) =
2457            Drive::verify_shielded_anchors(&proof.grovedb_proof, false, platform_version)
2458                .map_drive_error(proof, mtd)?;
2459
2460        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2461
2462        let result = if anchors.is_empty() {
2463            None
2464        } else {
2465            Some(ShieldedAnchors(anchors))
2466        };
2467
2468        Ok((result, mtd.clone(), proof.clone()))
2469    }
2470}
2471
2472impl FromProof<platform::GetMostRecentShieldedAnchorRequest> for MostRecentShieldedAnchor {
2473    type Request = platform::GetMostRecentShieldedAnchorRequest;
2474    type Response = platform::GetMostRecentShieldedAnchorResponse;
2475
2476    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2477        _request: I,
2478        response: O,
2479        _network: Network,
2480        platform_version: &PlatformVersion,
2481        provider: &'a dyn ContextProvider,
2482    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2483    where
2484        Self: Sized + 'a,
2485    {
2486        let response: Self::Response = response.into();
2487        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2488        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2489
2490        let (root_hash, maybe_anchor) = Drive::verify_most_recent_shielded_anchor(
2491            &proof.grovedb_proof,
2492            false,
2493            platform_version,
2494        )
2495        .map_drive_error(proof, mtd)?;
2496
2497        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2498
2499        Ok((
2500            maybe_anchor.map(MostRecentShieldedAnchor),
2501            mtd.clone(),
2502            proof.clone(),
2503        ))
2504    }
2505}
2506
2507impl FromProof<platform::GetShieldedEncryptedNotesRequest> for ShieldedEncryptedNotes {
2508    type Request = platform::GetShieldedEncryptedNotesRequest;
2509    type Response = platform::GetShieldedEncryptedNotesResponse;
2510
2511    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2512        request: I,
2513        response: O,
2514        _network: Network,
2515        platform_version: &PlatformVersion,
2516        provider: &'a dyn ContextProvider,
2517    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2518    where
2519        Self: Sized + 'a,
2520    {
2521        use dapi_grpc::platform::v0::get_shielded_encrypted_notes_request;
2522
2523        let request: Self::Request = request.into();
2524        let response: Self::Response = response.into();
2525        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2526        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2527
2528        let (start_index, count) = match request.version.ok_or(Error::EmptyVersion)? {
2529            get_shielded_encrypted_notes_request::Version::V0(v0) => (v0.start_index, v0.count),
2530        };
2531
2532        let max_elements = platform_version
2533            .drive_abci
2534            .query
2535            .shielded_queries
2536            .max_encrypted_notes_per_query as u32;
2537
2538        let (root_hash, notes) = Drive::verify_shielded_encrypted_notes(
2539            &proof.grovedb_proof,
2540            start_index,
2541            count,
2542            max_elements,
2543            false,
2544            platform_version,
2545        )
2546        .map_drive_error(proof, mtd)?;
2547
2548        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2549
2550        let result = if notes.is_empty() {
2551            None
2552        } else {
2553            Some(ShieldedEncryptedNotes(
2554                notes
2555                    .into_iter()
2556                    .map(|(cmx, nullifier, encrypted_note)| ShieldedEncryptedNote {
2557                        cmx,
2558                        nullifier,
2559                        encrypted_note,
2560                    })
2561                    .collect(),
2562            ))
2563        };
2564
2565        Ok((result, mtd.clone(), proof.clone()))
2566    }
2567}
2568
2569impl FromProof<platform::GetShieldedNullifiersRequest> for ShieldedNullifierStatuses {
2570    type Request = platform::GetShieldedNullifiersRequest;
2571    type Response = platform::GetShieldedNullifiersResponse;
2572
2573    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2574        request: I,
2575        response: O,
2576        _network: Network,
2577        platform_version: &PlatformVersion,
2578        provider: &'a dyn ContextProvider,
2579    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2580    where
2581        Self: Sized + 'a,
2582    {
2583        use dapi_grpc::platform::v0::get_shielded_nullifiers_request;
2584
2585        let request: Self::Request = request.into();
2586        let response: Self::Response = response.into();
2587        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2588        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2589
2590        let nullifiers = match request.version.ok_or(Error::EmptyVersion)? {
2591            get_shielded_nullifiers_request::Version::V0(v0) => v0.nullifiers,
2592        };
2593
2594        let (root_hash, statuses) = Drive::verify_shielded_nullifiers(
2595            &proof.grovedb_proof,
2596            &nullifiers,
2597            false,
2598            platform_version,
2599        )
2600        .map_drive_error(proof, mtd)?;
2601
2602        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2603
2604        let result = if statuses.is_empty() {
2605            None
2606        } else {
2607            Some(ShieldedNullifierStatuses(
2608                statuses
2609                    .into_iter()
2610                    .map(|(nullifier, is_spent)| {
2611                        let nullifier: [u8; 32] =
2612                            nullifier
2613                                .try_into()
2614                                .map_err(|_| Error::ResultEncodingError {
2615                                    error: "nullifier from Drive proof is not 32 bytes".to_string(),
2616                                })?;
2617                        Ok(ShieldedNullifierStatus {
2618                            nullifier,
2619                            is_spent,
2620                        })
2621                    })
2622                    .collect::<Result<Vec<_>, Error>>()?,
2623            ))
2624        };
2625
2626        Ok((result, mtd.clone(), proof.clone()))
2627    }
2628}
2629
2630impl FromProof<platform::GetRecentNullifierChangesRequest> for RecentNullifierChanges {
2631    type Request = platform::GetRecentNullifierChangesRequest;
2632    type Response = platform::GetRecentNullifierChangesResponse;
2633
2634    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2635        request: I,
2636        response: O,
2637        _network: Network,
2638        platform_version: &PlatformVersion,
2639        provider: &'a dyn ContextProvider,
2640    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2641    where
2642        RecentNullifierChanges: 'a,
2643    {
2644        use dapi_grpc::platform::v0::get_recent_nullifier_changes_request;
2645
2646        let request: Self::Request = request.into();
2647        let response: Self::Response = response.into();
2648
2649        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2650        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2651
2652        let start_height = match request.version.ok_or(Error::EmptyVersion)? {
2653            get_recent_nullifier_changes_request::Version::V0(v0) => v0.start_height,
2654        };
2655
2656        let limit = Some(100u16); // Same limit as in query handler
2657
2658        let (root_hash, verified_changes) = Drive::verify_recent_nullifier_changes(
2659            &proof.grovedb_proof,
2660            start_height,
2661            limit,
2662            false,
2663            platform_version,
2664        )
2665        .map_drive_error(proof, mtd)?;
2666
2667        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2668
2669        let result = RecentNullifierChanges(
2670            verified_changes
2671                .into_iter()
2672                .map(|change| BlockNullifierChanges {
2673                    block_height: change.block_height,
2674                    nullifiers: change.nullifiers.into_inner(),
2675                })
2676                .collect(),
2677        );
2678
2679        Ok((Some(result), mtd.clone(), proof.clone()))
2680    }
2681}
2682
2683impl FromProof<platform::GetRecentCompactedNullifierChangesRequest>
2684    for RecentCompactedNullifierChanges
2685{
2686    type Request = platform::GetRecentCompactedNullifierChangesRequest;
2687    type Response = platform::GetRecentCompactedNullifierChangesResponse;
2688
2689    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2690        request: I,
2691        response: O,
2692        _network: Network,
2693        platform_version: &PlatformVersion,
2694        provider: &'a dyn ContextProvider,
2695    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2696    where
2697        RecentCompactedNullifierChanges: 'a,
2698    {
2699        use dapi_grpc::platform::v0::get_recent_compacted_nullifier_changes_request;
2700
2701        let request: Self::Request = request.into();
2702        let response: Self::Response = response.into();
2703
2704        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2705        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2706
2707        let start_block_height = match request.version.ok_or(Error::EmptyVersion)? {
2708            get_recent_compacted_nullifier_changes_request::Version::V0(v0) => {
2709                v0.start_block_height
2710            }
2711        };
2712
2713        let limit = Some(25u16); // Same limit as in query handler
2714
2715        let (root_hash, verified_changes) = Drive::verify_compacted_nullifier_changes(
2716            &proof.grovedb_proof,
2717            start_block_height,
2718            limit,
2719            platform_version,
2720        )
2721        .map_drive_error(proof, mtd)?;
2722
2723        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2724
2725        let result = RecentCompactedNullifierChanges(
2726            verified_changes
2727                .into_iter()
2728                .map(|change| CompactedBlockNullifierChanges {
2729                    start_block_height: change.start_block,
2730                    end_block_height: change.end_block,
2731                    nullifiers: change.nullifiers.into_inner(),
2732                })
2733                .collect(),
2734        );
2735
2736        Ok((Some(result), mtd.clone(), proof.clone()))
2737    }
2738}
2739
2740/// Determine number of non-None elements
2741pub trait Length {
2742    /// Return number of non-None elements in the data structure
2743    fn count_some(&self) -> usize;
2744    /// Return number of all elements in the data structure, including None
2745    fn count(&self) -> usize;
2746}
2747
2748impl<T: Length> Length for Option<T> {
2749    fn count_some(&self) -> usize {
2750        match self {
2751            None => 0,
2752            Some(i) => i.count_some(),
2753        }
2754    }
2755    fn count(&self) -> usize {
2756        match self {
2757            None => 0,
2758            Some(i) => i.count(),
2759        }
2760    }
2761}
2762
2763impl<T> Length for Vec<Option<T>> {
2764    fn count_some(&self) -> usize {
2765        self.iter().filter(|v| v.is_some()).count()
2766    }
2767
2768    fn count(&self) -> usize {
2769        self.len()
2770    }
2771}
2772
2773impl<K, T> Length for Vec<(K, Option<T>)> {
2774    fn count_some(&self) -> usize {
2775        self.iter().filter(|(_, v)| v.is_some()).count()
2776    }
2777
2778    fn count(&self) -> usize {
2779        self.len()
2780    }
2781}
2782
2783impl<K, T> Length for BTreeMap<K, Option<T>> {
2784    fn count_some(&self) -> usize {
2785        self.values().filter(|v| v.is_some()).count()
2786    }
2787
2788    fn count(&self) -> usize {
2789        self.len()
2790    }
2791}
2792
2793impl<K, T> Length for IndexMap<K, Option<T>> {
2794    fn count_some(&self) -> usize {
2795        self.values().filter(|v| v.is_some()).count()
2796    }
2797
2798    fn count(&self) -> usize {
2799        self.len()
2800    }
2801}
2802
2803/// Implement Length trait for a type
2804///
2805/// # Arguments
2806///
2807/// * `$object`: The type for which to implement Length trait
2808/// * `$len`: A closure that returns the length of the object; if omitted, defaults to 1
2809macro_rules! define_length {
2810    ($object:ty,$some:expr,$counter:expr) => {
2811        impl Length for $object {
2812            fn count_some(&self) -> usize {
2813                #[allow(clippy::redundant_closure_call)]
2814                $some(self)
2815            }
2816
2817            fn count(&self) -> usize {
2818                #[allow(clippy::redundant_closure_call)]
2819                $counter(self)
2820            }
2821        }
2822    };
2823    ($object:ty,$some:expr) => {
2824        define_length!($object, $some, $some);
2825    };
2826    ($object:ty) => {
2827        define_length!($object, |_| 1, |_| 1);
2828    };
2829}
2830
2831define_length!(DataContract);
2832define_length!(DataContractHistory, |d: &DataContractHistory| d.len());
2833define_length!(Document);
2834define_length!(Identity);
2835define_length!(IdentityBalance);
2836define_length!(IdentityBalanceAndRevision);
2837define_length!(
2838    IdentitiesContractKeys,
2839    |x: &IdentitiesContractKeys| x.values().map(|v| v.count_some()).sum(),
2840    |x: &IdentitiesContractKeys| x.len()
2841);
2842define_length!(ContestedResources, |x: &ContestedResources| x.0.len());
2843define_length!(Contenders, |x: &Contenders| x.contenders.len());
2844define_length!(Voters, |x: &Voters| x.0.len());
2845define_length!(
2846    VotePollsGroupedByTimestamp,
2847    |x: &VotePollsGroupedByTimestamp| x.0.iter().map(|v| v.1.len()).sum(),
2848    |x: &VotePollsGroupedByTimestamp| x.0.len()
2849);
2850
2851/// Convert a type into an Option
2852trait IntoOption
2853where
2854    Self: Sized,
2855{
2856    /// For zero-length data structures, return None, otherwise return Some(self).
2857    ///
2858    /// In case of a zero-length data structure, the function returns None.
2859    /// Otherwise, it returns Some(self), even it all values are None. This is to ensure that proof of absence
2860    /// preserves the keys that are not present in the data structure.
2861    fn into_option(self) -> Option<Self>;
2862}
2863
2864impl<L: Length> IntoOption for L {
2865    fn into_option(self) -> Option<Self>
2866    where
2867        Self: Sized,
2868    {
2869        if self.count() == 0 {
2870            None
2871        } else {
2872            Some(self)
2873        }
2874    }
2875}
2876
2877#[cfg(test)]
2878mod tests {
2879    use super::*;
2880
2881    #[test]
2882    fn try_u32_to_u16_succeeds_for_valid_values() {
2883        assert_eq!(try_u32_to_u16(0).unwrap(), 0u16);
2884        assert_eq!(try_u32_to_u16(1).unwrap(), 1u16);
2885        assert_eq!(try_u32_to_u16(42).unwrap(), 42u16);
2886        assert_eq!(try_u32_to_u16(u16::MAX as u32).unwrap(), u16::MAX);
2887    }
2888
2889    #[test]
2890    fn try_u32_to_u16_errors_on_overflow() {
2891        // This is the exact attack vector: epoch 65536 would silently truncate
2892        // to 0 with `as u16`, allowing a malicious node to serve a proof for
2893        // epoch 0 while claiming the metadata epoch is 65536.
2894        let result = try_u32_to_u16(65536);
2895        assert!(
2896            result.is_err(),
2897            "epoch 65536 must not silently truncate to 0"
2898        );
2899
2900        let result = try_u32_to_u16(u32::MAX);
2901        assert!(result.is_err(), "epoch u32::MAX must not silently truncate");
2902
2903        let result = try_u32_to_u16(100_000);
2904        assert!(result.is_err(), "epoch 100000 must not silently truncate");
2905    }
2906
2907    #[test]
2908    fn u32_to_u16_opt_succeeds_for_valid_values() {
2909        assert_eq!(u32_to_u16_opt(0).unwrap(), None);
2910        assert_eq!(u32_to_u16_opt(1).unwrap(), Some(1u16));
2911        assert_eq!(u32_to_u16_opt(u16::MAX as u32).unwrap(), Some(u16::MAX));
2912    }
2913
2914    #[test]
2915    fn u32_to_u16_opt_errors_on_overflow() {
2916        let result = u32_to_u16_opt(65536);
2917        assert!(
2918            result.is_err(),
2919            "value 65536 must not silently truncate to 0"
2920        );
2921
2922        let result = u32_to_u16_opt(u32::MAX);
2923        assert!(result.is_err(), "value u32::MAX must not silently truncate");
2924    }
2925
2926    // ---------------------------------------------------------------------
2927    // Length / IntoOption trait tests
2928    // ---------------------------------------------------------------------
2929
2930    #[test]
2931    fn length_vec_option_counts_some_and_total() {
2932        let v: Vec<Option<u32>> = vec![Some(1), None, Some(2), None, Some(3)];
2933        assert_eq!(v.count(), 5);
2934        assert_eq!(v.count_some(), 3);
2935
2936        let empty: Vec<Option<u32>> = vec![];
2937        assert_eq!(empty.count(), 0);
2938        assert_eq!(empty.count_some(), 0);
2939    }
2940
2941    #[test]
2942    fn length_option_of_length_delegates() {
2943        let inner: Vec<Option<u32>> = vec![Some(1), None];
2944        let some_inner: Option<Vec<Option<u32>>> = Some(inner);
2945        assert_eq!(some_inner.count(), 2);
2946        assert_eq!(some_inner.count_some(), 1);
2947
2948        let none_inner: Option<Vec<Option<u32>>> = None;
2949        assert_eq!(none_inner.count(), 0);
2950        assert_eq!(none_inner.count_some(), 0);
2951    }
2952
2953    #[test]
2954    fn length_vec_of_key_option_pair() {
2955        let v: Vec<(u8, Option<u32>)> = vec![(1, Some(10)), (2, None), (3, Some(30)), (4, None)];
2956        assert_eq!(v.count(), 4);
2957        assert_eq!(v.count_some(), 2);
2958    }
2959
2960    #[test]
2961    fn length_btreemap_of_option() {
2962        let mut m: BTreeMap<u8, Option<u32>> = BTreeMap::new();
2963        m.insert(1, Some(10));
2964        m.insert(2, None);
2965        m.insert(3, Some(30));
2966        assert_eq!(m.count(), 3);
2967        assert_eq!(m.count_some(), 2);
2968    }
2969
2970    #[test]
2971    fn length_indexmap_of_option() {
2972        let mut m: IndexMap<u8, Option<u32>> = IndexMap::new();
2973        m.insert(1, Some(10));
2974        m.insert(2, None);
2975        m.insert(3, Some(30));
2976        m.insert(4, None);
2977        assert_eq!(m.count(), 4);
2978        assert_eq!(m.count_some(), 2);
2979    }
2980
2981    #[test]
2982    fn into_option_returns_none_for_empty_and_some_for_nonempty() {
2983        // Empty collection -> None
2984        let empty: Vec<Option<u32>> = vec![];
2985        assert!(empty.into_option().is_none());
2986
2987        // Non-empty, even if all are None -> Some(self)
2988        let all_none: Vec<Option<u32>> = vec![None, None];
2989        let wrapped = all_none.into_option();
2990        assert!(wrapped.is_some());
2991        assert_eq!(wrapped.unwrap().len(), 2);
2992
2993        // Non-empty with some values -> Some(self)
2994        let mixed: Vec<Option<u32>> = vec![Some(1), None];
2995        assert!(mixed.into_option().is_some());
2996    }
2997
2998    #[test]
2999    fn into_option_for_indexmap() {
3000        let empty: IndexMap<u8, Option<u32>> = IndexMap::new();
3001        assert!(empty.into_option().is_none());
3002
3003        let mut m: IndexMap<u8, Option<u32>> = IndexMap::new();
3004        m.insert(1, None); // only None value, but count() > 0
3005        let wrapped = m.into_option();
3006        assert!(
3007            wrapped.is_some(),
3008            "IntoOption must preserve maps that carry absence markers"
3009        );
3010    }
3011
3012    // ---------------------------------------------------------------------
3013    // parse_key_request_type tests
3014    // ---------------------------------------------------------------------
3015
3016    #[test]
3017    fn parse_key_request_type_missing_outer_request() {
3018        let err = parse_key_request_type(&None)
3019            .err()
3020            .expect("None input must error");
3021        match err {
3022            Error::RequestError { error } => {
3023                assert!(
3024                    error.contains("missing key request type"),
3025                    "unexpected error message: {error}"
3026                );
3027            }
3028            other => panic!("expected RequestError, got: {other:?}"),
3029        }
3030    }
3031
3032    #[test]
3033    fn parse_key_request_type_missing_inner_request_field() {
3034        // Outer Some, inner `request` is None -> second `ok_or` triggers.
3035        let outer = Some(GrpcKeyType { request: None });
3036        let err = parse_key_request_type(&outer)
3037            .err()
3038            .expect("missing request must error");
3039        match err {
3040            Error::RequestError { error } => {
3041                assert!(
3042                    error.contains("empty request field"),
3043                    "unexpected error message: {error}"
3044                );
3045            }
3046            other => panic!("expected RequestError, got: {other:?}"),
3047        }
3048    }
3049
3050    #[test]
3051    fn parse_key_request_type_all_keys_variant() {
3052        use dapi_grpc::platform::v0::AllKeys;
3053        let outer = Some(GrpcKeyType {
3054            request: Some(key_request_type::Request::AllKeys(AllKeys {})),
3055        });
3056        let parsed = parse_key_request_type(&outer).unwrap();
3057        assert!(matches!(parsed, KeyRequestType::AllKeys));
3058    }
3059
3060    #[test]
3061    fn parse_key_request_type_specific_keys_variant() {
3062        use dapi_grpc::platform::v0::SpecificKeys;
3063        let outer = Some(GrpcKeyType {
3064            request: Some(key_request_type::Request::SpecificKeys(SpecificKeys {
3065                key_ids: vec![1, 2, 3],
3066            })),
3067        });
3068        let parsed = parse_key_request_type(&outer).unwrap();
3069        match parsed {
3070            KeyRequestType::SpecificKeys(ids) => assert_eq!(ids, vec![1, 2, 3]),
3071            _ => panic!("expected SpecificKeys variant"),
3072        }
3073    }
3074
3075    #[test]
3076    fn parse_key_request_type_search_key_rejects_invalid_kind() {
3077        use dapi_grpc::platform::v0::{SearchKey, SecurityLevelMap};
3078        let mut sec_map: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
3079        // 99 is not a valid GrpcKeyKind, must produce RequestError
3080        sec_map.insert(0, 99);
3081
3082        let mut purpose_map = std::collections::HashMap::new();
3083        purpose_map.insert(
3084            0u32,
3085            SecurityLevelMap {
3086                security_level_map: sec_map,
3087            },
3088        );
3089
3090        let outer = Some(GrpcKeyType {
3091            request: Some(key_request_type::Request::SearchKey(SearchKey {
3092                purpose_map,
3093            })),
3094        });
3095
3096        let err = parse_key_request_type(&outer)
3097            .err()
3098            .expect("bad key kind must error");
3099        match err {
3100            Error::RequestError { error } => assert!(
3101                error.contains("missing requested key type"),
3102                "unexpected error: {error}"
3103            ),
3104            other => panic!("expected RequestError for bad key kind, got: {other:?}"),
3105        }
3106    }
3107
3108    #[test]
3109    fn parse_key_request_type_search_key_accepts_valid_kinds() {
3110        use dapi_grpc::platform::v0::{SearchKey, SecurityLevelMap};
3111        let mut sec_map: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
3112        sec_map.insert(0, GrpcKeyKind::CurrentKeyOfKindRequest as i32);
3113        sec_map.insert(1, GrpcKeyKind::AllKeysOfKindRequest as i32);
3114
3115        let mut purpose_map = std::collections::HashMap::new();
3116        purpose_map.insert(
3117            0u32,
3118            SecurityLevelMap {
3119                security_level_map: sec_map,
3120            },
3121        );
3122
3123        let outer = Some(GrpcKeyType {
3124            request: Some(key_request_type::Request::SearchKey(SearchKey {
3125                purpose_map,
3126            })),
3127        });
3128
3129        let parsed = parse_key_request_type(&outer).unwrap();
3130        match parsed {
3131            KeyRequestType::SearchKey(purposes) => {
3132                let inner = purposes.get(&0u8).expect("purpose 0 parsed");
3133                assert_eq!(inner.len(), 2);
3134                assert!(matches!(
3135                    inner.get(&0u8),
3136                    Some(KeyKindRequestType::CurrentKeyOfKindRequest)
3137                ));
3138                assert!(matches!(
3139                    inner.get(&1u8),
3140                    Some(KeyKindRequestType::AllKeysOfKindRequest)
3141                ));
3142            }
3143            _ => panic!("expected SearchKey variant"),
3144        }
3145    }
3146
3147    // ---------------------------------------------------------------------
3148    // FromProof error-path tests
3149    //
3150    // These tests verify that response/request decoding errors fire
3151    // *before* any cryptographic proof verification is attempted, so we
3152    // don't need a real quorum or GroveDB proof to exercise them.
3153    // ---------------------------------------------------------------------
3154
3155    /// A ContextProvider that must never be called during these tests —
3156    /// if it is, the test has reached the cryptographic-verification stage
3157    /// incorrectly, which is itself a meaningful failure.
3158    struct UnreachableContextProvider;
3159
3160    impl dash_context_provider::ContextProvider for UnreachableContextProvider {
3161        fn get_data_contract(
3162            &self,
3163            _id: &dpp::prelude::Identifier,
3164            _platform_version: &PlatformVersion,
3165        ) -> Result<Option<std::sync::Arc<DataContract>>, dash_context_provider::ContextProviderError>
3166        {
3167            panic!("context provider should not be called on decode-error test")
3168        }
3169
3170        fn get_token_configuration(
3171            &self,
3172            _token_id: &dpp::prelude::Identifier,
3173        ) -> Result<
3174            Option<dpp::data_contract::TokenConfiguration>,
3175            dash_context_provider::ContextProviderError,
3176        > {
3177            panic!("context provider should not be called on decode-error test")
3178        }
3179
3180        fn get_quorum_public_key(
3181            &self,
3182            _quorum_type: u32,
3183            _quorum_hash: [u8; 32],
3184            _core_chain_locked_height: u32,
3185        ) -> Result<[u8; 48], dash_context_provider::ContextProviderError> {
3186            panic!("context provider should not be called on decode-error test")
3187        }
3188
3189        fn get_platform_activation_height(
3190            &self,
3191        ) -> Result<dpp::prelude::CoreBlockHeight, dash_context_provider::ContextProviderError>
3192        {
3193            panic!("context provider should not be called on decode-error test")
3194        }
3195    }
3196
3197    fn unreachable_provider() -> UnreachableContextProvider {
3198        UnreachableContextProvider
3199    }
3200
3201    fn default_platform_version() -> &'static PlatformVersion {
3202        PlatformVersion::latest()
3203    }
3204
3205    /// Build a fully-populated `GetIdentityResponse` shell so that
3206    /// `response.proof()` and `response.metadata()` both succeed. The
3207    /// enclosed proof is empty, so any real verification would fail — but
3208    /// these tests stop before that point.
3209    fn identity_response_with_proof_and_metadata() -> platform::GetIdentityResponse {
3210        use platform::get_identity_response::{
3211            get_identity_response_v0::Result as V0Result, GetIdentityResponseV0, Version,
3212        };
3213        platform::GetIdentityResponse {
3214            version: Some(Version::V0(GetIdentityResponseV0 {
3215                result: Some(V0Result::Proof(Proof::default())),
3216                metadata: Some(ResponseMetadata::default()),
3217            })),
3218        }
3219    }
3220
3221    #[test]
3222    fn identity_from_proof_no_proof_when_response_empty() {
3223        // Default response has `version: None` -> response.proof() errors
3224        // -> mapped to NoProofInResult.
3225        let request = platform::GetIdentityRequest::default();
3226        let response = platform::GetIdentityResponse::default();
3227
3228        let provider = unreachable_provider();
3229        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3230            request,
3231            response,
3232            Network::Testnet,
3233            default_platform_version(),
3234            &provider,
3235        )
3236        .unwrap_err();
3237
3238        assert!(
3239            matches!(err, Error::NoProofInResult),
3240            "expected NoProofInResult, got: {err:?}"
3241        );
3242    }
3243
3244    #[test]
3245    fn identity_from_proof_empty_metadata_when_metadata_missing() {
3246        use platform::get_identity_response::{
3247            get_identity_response_v0::Result as V0Result, GetIdentityResponseV0, Version,
3248        };
3249        // Response has a Proof but no metadata -> EmptyResponseMetadata.
3250        let response = platform::GetIdentityResponse {
3251            version: Some(Version::V0(GetIdentityResponseV0 {
3252                result: Some(V0Result::Proof(Proof::default())),
3253                metadata: None,
3254            })),
3255        };
3256        let request = platform::GetIdentityRequest::default();
3257        let provider = unreachable_provider();
3258        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3259            request,
3260            response,
3261            Network::Testnet,
3262            default_platform_version(),
3263            &provider,
3264        )
3265        .unwrap_err();
3266        assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
3267    }
3268
3269    #[test]
3270    fn identity_from_proof_empty_version_when_request_has_no_version() {
3271        // Valid response, but request.version is None -> EmptyVersion.
3272        let response = identity_response_with_proof_and_metadata();
3273        let request = platform::GetIdentityRequest { version: None };
3274        let provider = unreachable_provider();
3275        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3276            request,
3277            response,
3278            Network::Testnet,
3279            default_platform_version(),
3280            &provider,
3281        )
3282        .unwrap_err();
3283        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
3284    }
3285
3286    #[test]
3287    fn identity_from_proof_protocol_error_on_bad_id_length() {
3288        use dapi_grpc::platform::v0::get_identity_request::GetIdentityRequestV0;
3289        // id must be 32 bytes; anything else fails Identifier::from_bytes.
3290        let request: platform::GetIdentityRequest = GetIdentityRequestV0 {
3291            id: vec![0u8; 8],
3292            prove: true,
3293        }
3294        .into();
3295        let response = identity_response_with_proof_and_metadata();
3296        let provider = unreachable_provider();
3297        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3298            request,
3299            response,
3300            Network::Testnet,
3301            default_platform_version(),
3302            &provider,
3303        )
3304        .unwrap_err();
3305        assert!(
3306            matches!(err, Error::ProtocolError { .. }),
3307            "expected ProtocolError on bad id length, got: {err:?}"
3308        );
3309    }
3310
3311    /// A minimal `FromProof` impl whose `maybe_from_proof_with_metadata`
3312    /// returns `Ok((None, ..))`, isolating the `from_proof` wrapper's
3313    /// `None -> Error::NotFound` mapping from the decode/verify pipeline.
3314    #[derive(Debug)]
3315    struct MissingFromProof;
3316
3317    impl FromProof<()> for MissingFromProof {
3318        type Request = ();
3319        type Response = ();
3320
3321        fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
3322            _request: I,
3323            _response: O,
3324            _network: Network,
3325            _platform_version: &PlatformVersion,
3326            _provider: &'a dyn ContextProvider,
3327        ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
3328        where
3329            Self: Sized + 'a,
3330        {
3331            Ok((None, ResponseMetadata::default(), Proof::default()))
3332        }
3333    }
3334
3335    #[test]
3336    fn from_proof_maps_none_to_not_found() {
3337        // `from_proof` (vs `maybe_from_proof`) is expected to map `Ok(None)`
3338        // to `Error::NotFound`. Verify that wrapper behavior directly rather
3339        // than conflating it with decode-error propagation.
3340        let provider = unreachable_provider();
3341        let err = <MissingFromProof as FromProof<()>>::from_proof(
3342            (),
3343            (),
3344            Network::Testnet,
3345            default_platform_version(),
3346            &provider,
3347        )
3348        .unwrap_err();
3349        assert!(
3350            matches!(err, Error::NotFound),
3351            "expected NotFound when maybe_from_proof returns None, got: {err:?}"
3352        );
3353    }
3354
3355    #[test]
3356    fn identity_by_public_key_hash_invalid_length_yields_drive_error() {
3357        use dapi_grpc::platform::v0::get_identity_by_public_key_hash_request::GetIdentityByPublicKeyHashRequestV0;
3358
3359        // public_key_hash must be exactly 20 bytes; 10 bytes fails.
3360        let request: platform::GetIdentityByPublicKeyHashRequest =
3361            GetIdentityByPublicKeyHashRequestV0 {
3362                public_key_hash: vec![0u8; 10],
3363                prove: true,
3364            }
3365            .into();
3366
3367        // Build a response that succeeds on proof/metadata lookups.
3368        use platform::get_identity_by_public_key_hash_response::{
3369            get_identity_by_public_key_hash_response_v0::Result as V0Result,
3370            GetIdentityByPublicKeyHashResponseV0, Version,
3371        };
3372        let response = platform::GetIdentityByPublicKeyHashResponse {
3373            version: Some(Version::V0(GetIdentityByPublicKeyHashResponseV0 {
3374                result: Some(V0Result::Proof(Proof::default())),
3375                metadata: Some(ResponseMetadata::default()),
3376            })),
3377        };
3378
3379        let provider = unreachable_provider();
3380        let err =
3381            <Identity as FromProof<platform::GetIdentityByPublicKeyHashRequest>>::maybe_from_proof(
3382                request,
3383                response,
3384                Network::Testnet,
3385                default_platform_version(),
3386                &provider,
3387            )
3388            .unwrap_err();
3389
3390        match err {
3391            Error::DriveError { error } => {
3392                assert!(
3393                    error.contains("Invalid public key hash length"),
3394                    "unexpected error body: {error}"
3395                );
3396            }
3397            other => panic!("expected DriveError, got: {other:?}"),
3398        }
3399    }
3400
3401    #[test]
3402    fn identity_by_non_unique_public_key_hash_rejects_bad_key_hash_length() {
3403        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3404        use platform::get_identity_by_non_unique_public_key_hash_response::{
3405            get_identity_by_non_unique_public_key_hash_response_v0::Result as V0Result,
3406            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3407        };
3408
3409        // Build a response with a proved result so we get past the response shape check
3410        // and hit the request validation.
3411        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3412            version: Some(Version::V0(
3413                GetIdentityByNonUniquePublicKeyHashResponseV0 {
3414                    result: Some(V0Result::Proof(
3415                        dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_response::get_identity_by_non_unique_public_key_hash_response_v0::IdentityProvedResponse {
3416                            identity_proof_bytes: None,
3417                            grovedb_identity_public_key_hash_proof: Some(Proof::default()),
3418                        },
3419                    )),
3420                    metadata: Some(ResponseMetadata::default()),
3421                },
3422            )),
3423        };
3424
3425        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3426            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3427                public_key_hash: vec![0u8; 3], // must be 20 bytes
3428                start_after: None,
3429                prove: true,
3430            }
3431            .into();
3432
3433        let provider = unreachable_provider();
3434        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3435            request,
3436            response,
3437            Network::Testnet,
3438            default_platform_version(),
3439            &provider,
3440        )
3441        .unwrap_err();
3442
3443        match err {
3444            Error::RequestError { error } => {
3445                assert!(
3446                    error.contains("Invalid public key hash length"),
3447                    "got: {error}"
3448                );
3449            }
3450            other => panic!("expected RequestError, got: {other:?}"),
3451        }
3452    }
3453
3454    #[test]
3455    fn identity_by_non_unique_public_key_hash_rejects_bad_start_after_length() {
3456        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3457        use platform::get_identity_by_non_unique_public_key_hash_response::{
3458            get_identity_by_non_unique_public_key_hash_response_v0::Result as V0Result,
3459            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3460        };
3461
3462        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3463            version: Some(Version::V0(
3464                GetIdentityByNonUniquePublicKeyHashResponseV0 {
3465                    result: Some(V0Result::Proof(
3466                        dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_response::get_identity_by_non_unique_public_key_hash_response_v0::IdentityProvedResponse {
3467                            identity_proof_bytes: None,
3468                            grovedb_identity_public_key_hash_proof: Some(Proof::default()),
3469                        },
3470                    )),
3471                    metadata: Some(ResponseMetadata::default()),
3472                },
3473            )),
3474        };
3475
3476        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3477            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3478                public_key_hash: vec![0u8; 20],   // good
3479                start_after: Some(vec![0u8; 10]), // wrong length; must be 32
3480                prove: true,
3481            }
3482            .into();
3483
3484        let provider = unreachable_provider();
3485        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3486            request,
3487            response,
3488            Network::Testnet,
3489            default_platform_version(),
3490            &provider,
3491        )
3492        .unwrap_err();
3493
3494        match err {
3495            Error::RequestError { error } => {
3496                assert!(error.contains("Invalid start_after length"), "got: {error}");
3497            }
3498            other => panic!("expected RequestError for start_after, got: {other:?}"),
3499        }
3500    }
3501
3502    #[test]
3503    fn identity_by_non_unique_response_with_no_result_yields_no_proof() {
3504        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3505        use platform::get_identity_by_non_unique_public_key_hash_response::{
3506            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3507        };
3508
3509        // v0 with result=None -> NoProofInResult on the `.ok_or` branch.
3510        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3511            version: Some(Version::V0(GetIdentityByNonUniquePublicKeyHashResponseV0 {
3512                result: None,
3513                metadata: None,
3514            })),
3515        };
3516        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3517            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3518                public_key_hash: vec![0u8; 20],
3519                start_after: None,
3520                prove: true,
3521            }
3522            .into();
3523        let provider = unreachable_provider();
3524        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3525            request,
3526            response,
3527            Network::Testnet,
3528            default_platform_version(),
3529            &provider,
3530        )
3531        .unwrap_err();
3532        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
3533    }
3534
3535    #[test]
3536    fn identity_by_non_unique_response_with_no_version_yields_empty_metadata() {
3537        // response.version = None hits the `_ => EmptyResponseMetadata` arm.
3538        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3539        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse { version: None };
3540        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3541            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3542                public_key_hash: vec![0u8; 20],
3543                start_after: None,
3544                prove: true,
3545            }
3546            .into();
3547        let provider = unreachable_provider();
3548        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3549            request,
3550            response,
3551            Network::Testnet,
3552            default_platform_version(),
3553            &provider,
3554        )
3555        .unwrap_err();
3556        assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
3557    }
3558
3559    #[test]
3560    fn identities_balances_rejects_non_32_byte_id() {
3561        use dapi_grpc::platform::v0::get_identities_balances_request::GetIdentitiesBalancesRequestV0;
3562        use platform::get_identities_balances_response::{
3563            get_identities_balances_response_v0::Result as V0Result,
3564            GetIdentitiesBalancesResponseV0, Version,
3565        };
3566
3567        let response = platform::GetIdentitiesBalancesResponse {
3568            version: Some(Version::V0(GetIdentitiesBalancesResponseV0 {
3569                result: Some(V0Result::Proof(Proof::default())),
3570                metadata: Some(ResponseMetadata::default()),
3571            })),
3572        };
3573
3574        let request: platform::GetIdentitiesBalancesRequest = GetIdentitiesBalancesRequestV0 {
3575            ids: vec![vec![0u8; 10]], // wrong length
3576            prove: true,
3577        }
3578        .into();
3579
3580        let provider = unreachable_provider();
3581        let err =
3582            <IdentityBalances as FromProof<platform::GetIdentitiesBalancesRequest>>::maybe_from_proof(
3583                request,
3584                response,
3585                Network::Testnet,
3586                default_platform_version(),
3587                &provider,
3588            )
3589            .unwrap_err();
3590        match err {
3591            Error::RequestError { error } => {
3592                assert!(error.contains("all 32 bytes"), "got: {error}");
3593            }
3594            other => panic!("expected RequestError, got: {other:?}"),
3595        }
3596    }
3597
3598    #[test]
3599    fn data_contracts_rejects_wrong_size_id() {
3600        use dapi_grpc::platform::v0::get_data_contracts_request::GetDataContractsRequestV0;
3601        use platform::get_data_contracts_response::{
3602            get_data_contracts_response_v0::Result as V0Result, GetDataContractsResponseV0, Version,
3603        };
3604
3605        let response = platform::GetDataContractsResponse {
3606            version: Some(Version::V0(GetDataContractsResponseV0 {
3607                result: Some(V0Result::Proof(Proof::default())),
3608                metadata: Some(ResponseMetadata::default()),
3609            })),
3610        };
3611        let request: platform::GetDataContractsRequest = GetDataContractsRequestV0 {
3612            ids: vec![vec![0u8; 20]], // must be 32 bytes
3613            prove: true,
3614        }
3615        .into();
3616        let provider = unreachable_provider();
3617        let err =
3618            <DataContracts as FromProof<platform::GetDataContractsRequest>>::maybe_from_proof(
3619                request,
3620                response,
3621                Network::Testnet,
3622                default_platform_version(),
3623                &provider,
3624            )
3625            .unwrap_err();
3626        match err {
3627            Error::RequestError { error } => {
3628                assert!(error.contains("wrong id size"), "got: {error}");
3629            }
3630            other => panic!("expected RequestError, got: {other:?}"),
3631        }
3632    }
3633
3634    #[test]
3635    fn upgrade_vote_status_rejects_bad_start_pro_tx_hash_length() {
3636        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_request::GetProtocolVersionUpgradeVoteStatusRequestV0;
3637        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_response::{
3638            get_protocol_version_upgrade_vote_status_response_v0::Result as V0Result,
3639            GetProtocolVersionUpgradeVoteStatusResponseV0, Version,
3640        };
3641
3642        let response = GetProtocolVersionUpgradeVoteStatusResponse {
3643            version: Some(Version::V0(GetProtocolVersionUpgradeVoteStatusResponseV0 {
3644                result: Some(V0Result::Proof(Proof::default())),
3645                metadata: Some(ResponseMetadata::default()),
3646            })),
3647        };
3648        // start_pro_tx_hash must be 32 bytes if non-empty.
3649        let request: GetProtocolVersionUpgradeVoteStatusRequest =
3650            GetProtocolVersionUpgradeVoteStatusRequestV0 {
3651                start_pro_tx_hash: vec![0u8; 5],
3652                count: 10,
3653                prove: true,
3654            }
3655            .into();
3656        let provider = unreachable_provider();
3657        let err = <MasternodeProtocolVotes as FromProof<
3658            GetProtocolVersionUpgradeVoteStatusRequest,
3659        >>::maybe_from_proof(
3660            request,
3661            response,
3662            Network::Testnet,
3663            default_platform_version(),
3664            &provider,
3665        )
3666        .unwrap_err();
3667        match err {
3668            Error::RequestError { .. } => {}
3669            other => panic!("expected RequestError for bad pro_tx_hash length, got: {other:?}"),
3670        }
3671    }
3672
3673    #[test]
3674    fn upgrade_vote_status_empty_version_on_request_none() {
3675        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_response::{
3676            get_protocol_version_upgrade_vote_status_response_v0::Result as V0Result,
3677            GetProtocolVersionUpgradeVoteStatusResponseV0, Version,
3678        };
3679        let response = GetProtocolVersionUpgradeVoteStatusResponse {
3680            version: Some(Version::V0(GetProtocolVersionUpgradeVoteStatusResponseV0 {
3681                result: Some(V0Result::Proof(Proof::default())),
3682                metadata: Some(ResponseMetadata::default()),
3683            })),
3684        };
3685        let request = GetProtocolVersionUpgradeVoteStatusRequest { version: None };
3686        let provider = unreachable_provider();
3687        let err = <MasternodeProtocolVotes as FromProof<
3688            GetProtocolVersionUpgradeVoteStatusRequest,
3689        >>::maybe_from_proof(
3690            request,
3691            response,
3692            Network::Testnet,
3693            default_platform_version(),
3694            &provider,
3695        )
3696        .unwrap_err();
3697        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
3698    }
3699
3700    #[test]
3701    fn path_elements_no_proof_without_response() {
3702        let request = GetPathElementsRequest::default();
3703        let response = GetPathElementsResponse::default();
3704        let provider = unreachable_provider();
3705        let err = <Elements as FromProof<GetPathElementsRequest>>::maybe_from_proof(
3706            request,
3707            response,
3708            Network::Testnet,
3709            default_platform_version(),
3710            &provider,
3711        )
3712        .unwrap_err();
3713        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
3714    }
3715
3716    #[test]
3717    fn prefunded_balance_rejects_bad_id_length() {
3718        use dapi_grpc::platform::v0::get_prefunded_specialized_balance_request::GetPrefundedSpecializedBalanceRequestV0;
3719        use platform::get_prefunded_specialized_balance_response::{
3720            get_prefunded_specialized_balance_response_v0::Result as V0Result,
3721            GetPrefundedSpecializedBalanceResponseV0, Version,
3722        };
3723        let response = platform::GetPrefundedSpecializedBalanceResponse {
3724            version: Some(Version::V0(GetPrefundedSpecializedBalanceResponseV0 {
3725                result: Some(V0Result::Proof(Proof::default())),
3726                metadata: Some(ResponseMetadata::default()),
3727            })),
3728        };
3729        let request: platform::GetPrefundedSpecializedBalanceRequest =
3730            GetPrefundedSpecializedBalanceRequestV0 {
3731                id: vec![0u8; 3], // must be 32
3732                prove: true,
3733            }
3734            .into();
3735        let provider = unreachable_provider();
3736        let err = <PrefundedSpecializedBalance as FromProof<
3737            platform::GetPrefundedSpecializedBalanceRequest,
3738        >>::maybe_from_proof(
3739            request,
3740            response,
3741            Network::Testnet,
3742            default_platform_version(),
3743            &provider,
3744        )
3745        .unwrap_err();
3746        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
3747    }
3748
3749    #[test]
3750    fn epochs_info_rejects_overflowing_start_epoch() {
3751        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
3752        use platform::get_epochs_info_response::{
3753            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
3754        };
3755        let response = platform::GetEpochsInfoResponse {
3756            version: Some(Version::V0(GetEpochsInfoResponseV0 {
3757                result: Some(V0Result::Proof(Proof::default())),
3758                metadata: Some(ResponseMetadata {
3759                    epoch: 10,
3760                    ..Default::default()
3761                }),
3762            })),
3763        };
3764        // start_epoch > u16::MAX triggers try_u32_to_u16 error.
3765        let request = platform::GetEpochsInfoRequest {
3766            version: Some(platform::get_epochs_info_request::Version::V0(
3767                GetEpochsInfoRequestV0 {
3768                    start_epoch: Some(100_000),
3769                    count: 1,
3770                    ascending: true,
3771                    prove: true,
3772                },
3773            )),
3774        };
3775        let provider = unreachable_provider();
3776        let err =
3777            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
3778                request,
3779                response,
3780                Network::Testnet,
3781                default_platform_version(),
3782                &provider,
3783            )
3784            .unwrap_err();
3785        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
3786    }
3787
3788    #[test]
3789    fn broadcast_state_transition_rejects_garbage_payload() {
3790        // Cannot deserialize random bytes into a StateTransition, so we hit
3791        // the ProtocolError branch before any proof work happens.
3792        let request = platform::BroadcastStateTransitionRequest {
3793            state_transition: vec![0xFFu8; 16], // nonsense bytes
3794        };
3795        // Response structure only needs to have a valid proof field since
3796        // deserialize happens after proof extraction.
3797        use platform::wait_for_state_transition_result_response::{
3798            wait_for_state_transition_result_response_v0::Result as V0Result, Version,
3799            WaitForStateTransitionResultResponseV0,
3800        };
3801        let response = platform::WaitForStateTransitionResultResponse {
3802            version: Some(Version::V0(WaitForStateTransitionResultResponseV0 {
3803                result: Some(V0Result::Proof(Proof::default())),
3804                metadata: Some(ResponseMetadata::default()),
3805            })),
3806        };
3807        let provider = unreachable_provider();
3808        let err = <StateTransitionProofResult as FromProof<
3809            platform::BroadcastStateTransitionRequest,
3810        >>::maybe_from_proof(
3811            request,
3812            response,
3813            Network::Testnet,
3814            default_platform_version(),
3815            &provider,
3816        )
3817        .unwrap_err();
3818        assert!(
3819            matches!(err, Error::ProtocolError { .. }),
3820            "expected ProtocolError from StateTransition decode, got: {err:?}"
3821        );
3822    }
3823
3824    // ---------------------------------------------------------------------
3825    // Additional coverage: numeric helpers
3826    // ---------------------------------------------------------------------
3827
3828    #[test]
3829    fn u32_to_u16_opt_zero_maps_to_none() {
3830        // zero -> None (not Some(0)): guards against accidentally treating
3831        // "unset" as "limit 0".
3832        let parsed = u32_to_u16_opt(0).unwrap();
3833        assert!(parsed.is_none(), "value 0 must decode to None");
3834    }
3835
3836    #[test]
3837    fn u32_to_u16_opt_at_boundary() {
3838        let parsed = u32_to_u16_opt(u16::MAX as u32).unwrap();
3839        assert_eq!(parsed, Some(u16::MAX));
3840    }
3841
3842    #[test]
3843    fn u32_to_u16_opt_error_just_above_boundary() {
3844        // u16::MAX + 1 is the minimal out-of-range value.
3845        let err = u32_to_u16_opt((u16::MAX as u32) + 1).unwrap_err();
3846        match err {
3847            Error::RequestError { error } => {
3848                assert!(error.contains("out of range"), "got: {error}");
3849            }
3850            other => panic!("expected RequestError, got: {other:?}"),
3851        }
3852    }
3853
3854    #[test]
3855    fn try_u32_to_u16_at_boundary_plus_one() {
3856        // u16::MAX is ok; u16::MAX+1 is not.
3857        assert!(try_u32_to_u16(u16::MAX as u32).is_ok());
3858        assert!(try_u32_to_u16((u16::MAX as u32) + 1).is_err());
3859    }
3860
3861    // ---------------------------------------------------------------------
3862    // Additional coverage: Length / IntoOption edge cases
3863    // ---------------------------------------------------------------------
3864
3865    #[test]
3866    fn length_option_of_length_none_counts_zero() {
3867        let none_opt: Option<Vec<Option<u32>>> = None;
3868        assert_eq!(none_opt.count(), 0);
3869        assert_eq!(none_opt.count_some(), 0);
3870    }
3871
3872    #[test]
3873    fn length_vec_of_key_option_pair_only_none_values() {
3874        let v: Vec<(u8, Option<u32>)> = vec![(1, None), (2, None)];
3875        assert_eq!(v.count(), 2);
3876        assert_eq!(
3877            v.count_some(),
3878            0,
3879            "count_some must only count entries whose value is Some"
3880        );
3881    }
3882
3883    #[test]
3884    fn length_btreemap_only_none_values() {
3885        let mut m: BTreeMap<u8, Option<u32>> = BTreeMap::new();
3886        m.insert(1, None);
3887        m.insert(2, None);
3888        assert_eq!(m.count(), 2);
3889        assert_eq!(m.count_some(), 0);
3890    }
3891
3892    #[test]
3893    fn into_option_for_vec_of_key_option_pair_empty_and_nonempty() {
3894        let empty: Vec<(u8, Option<u32>)> = vec![];
3895        assert!(
3896            empty.into_option().is_none(),
3897            "empty vec must decode to None"
3898        );
3899
3900        let single: Vec<(u8, Option<u32>)> = vec![(1, None)];
3901        assert!(
3902            single.into_option().is_some(),
3903            "non-empty vec with only None values must still be Some"
3904        );
3905    }
3906
3907    #[test]
3908    fn into_option_for_btreemap_empty_and_nonempty() {
3909        let empty: BTreeMap<u8, Option<u32>> = BTreeMap::new();
3910        assert!(empty.into_option().is_none());
3911
3912        let mut m: BTreeMap<u8, Option<u32>> = BTreeMap::new();
3913        m.insert(1, None);
3914        assert!(m.into_option().is_some());
3915    }
3916
3917    // ---------------------------------------------------------------------
3918    // Additional coverage: parse_key_request_type
3919    // ---------------------------------------------------------------------
3920
3921    #[test]
3922    fn parse_key_request_type_specific_keys_empty_ids() {
3923        // Exercises the SpecificKeys branch when the id list is empty.
3924        use dapi_grpc::platform::v0::SpecificKeys;
3925        let outer = Some(GrpcKeyType {
3926            request: Some(key_request_type::Request::SpecificKeys(SpecificKeys {
3927                key_ids: vec![],
3928            })),
3929        });
3930        let parsed = parse_key_request_type(&outer).unwrap();
3931        match parsed {
3932            KeyRequestType::SpecificKeys(ids) => {
3933                assert!(ids.is_empty(), "empty ids must round-trip as empty");
3934            }
3935            _ => panic!("expected SpecificKeys variant"),
3936        }
3937    }
3938
3939    #[test]
3940    fn parse_key_request_type_search_key_empty_purpose_map() {
3941        // Exercises the SearchKey branch when the purpose map is empty.
3942        use dapi_grpc::platform::v0::SearchKey;
3943        let outer = Some(GrpcKeyType {
3944            request: Some(key_request_type::Request::SearchKey(SearchKey {
3945                purpose_map: std::collections::HashMap::new(),
3946            })),
3947        });
3948        let parsed = parse_key_request_type(&outer).unwrap();
3949        match parsed {
3950            KeyRequestType::SearchKey(m) => {
3951                assert!(m.is_empty(), "empty map must round-trip as empty");
3952            }
3953            _ => panic!("expected SearchKey variant"),
3954        }
3955    }
3956
3957    #[test]
3958    fn parse_key_request_type_search_key_negative_kind_rejected() {
3959        // Negative i32 values are not valid GrpcKeyKind values -> RequestError.
3960        use dapi_grpc::platform::v0::{SearchKey, SecurityLevelMap};
3961        let mut sec_map: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
3962        sec_map.insert(0, -1);
3963        let mut purpose_map = std::collections::HashMap::new();
3964        purpose_map.insert(
3965            0u32,
3966            SecurityLevelMap {
3967                security_level_map: sec_map,
3968            },
3969        );
3970        let outer = Some(GrpcKeyType {
3971            request: Some(key_request_type::Request::SearchKey(SearchKey {
3972                purpose_map,
3973            })),
3974        });
3975        // KeyRequestType does not implement Debug, so use `.err().expect(...)`
3976        // rather than `.unwrap_err()`.
3977        let err = parse_key_request_type(&outer)
3978            .err()
3979            .expect("negative kind must error");
3980        match err {
3981            Error::RequestError { error } => {
3982                assert!(error.contains("missing requested key type"), "got: {error}");
3983            }
3984            other => panic!("expected RequestError, got: {other:?}"),
3985        }
3986    }
3987
3988    // ---------------------------------------------------------------------
3989    // Additional coverage: FromProof wrapper methods
3990    // ---------------------------------------------------------------------
3991
3992    /// FromProof impl that always succeeds with a Some value; used for
3993    /// exercising the `from_proof*` wrappers' happy paths.
3994    #[derive(Debug, PartialEq)]
3995    struct PresentFromProof(u32);
3996
3997    impl FromProof<()> for PresentFromProof {
3998        type Request = ();
3999        type Response = ();
4000
4001        fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
4002            _request: I,
4003            _response: O,
4004            _network: Network,
4005            _platform_version: &PlatformVersion,
4006            _provider: &'a dyn ContextProvider,
4007        ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
4008        where
4009            Self: Sized + 'a,
4010        {
4011            Ok((
4012                Some(PresentFromProof(7)),
4013                ResponseMetadata {
4014                    height: 123,
4015                    ..Default::default()
4016                },
4017                Proof::default(),
4018            ))
4019        }
4020    }
4021
4022    #[test]
4023    fn from_proof_with_metadata_returns_value_and_metadata() {
4024        // Ensures the `from_proof_with_metadata` wrapper unwraps Some correctly.
4025        let provider = unreachable_provider();
4026        let (value, mtd) = <PresentFromProof as FromProof<()>>::from_proof_with_metadata(
4027            (),
4028            (),
4029            Network::Testnet,
4030            default_platform_version(),
4031            &provider,
4032        )
4033        .unwrap();
4034        assert_eq!(value, PresentFromProof(7));
4035        assert_eq!(mtd.height, 123);
4036    }
4037
4038    #[test]
4039    fn from_proof_with_metadata_and_proof_returns_all_three() {
4040        let provider = unreachable_provider();
4041        let (value, mtd, _proof) =
4042            <PresentFromProof as FromProof<()>>::from_proof_with_metadata_and_proof(
4043                (),
4044                (),
4045                Network::Testnet,
4046                default_platform_version(),
4047                &provider,
4048            )
4049            .unwrap();
4050        assert_eq!(value, PresentFromProof(7));
4051        assert_eq!(mtd.height, 123);
4052    }
4053
4054    #[test]
4055    fn from_proof_on_missing_returns_not_found_via_wrapper() {
4056        // `from_proof` forwards to `maybe_from_proof` then maps None -> NotFound.
4057        let provider = unreachable_provider();
4058        let err = <MissingFromProof as FromProof<()>>::from_proof_with_metadata(
4059            (),
4060            (),
4061            Network::Testnet,
4062            default_platform_version(),
4063            &provider,
4064        )
4065        .unwrap_err();
4066        assert!(matches!(err, Error::NotFound), "got: {err:?}");
4067    }
4068
4069    #[test]
4070    fn from_proof_with_metadata_and_proof_missing_returns_not_found() {
4071        let provider = unreachable_provider();
4072        let err = <MissingFromProof as FromProof<()>>::from_proof_with_metadata_and_proof(
4073            (),
4074            (),
4075            Network::Testnet,
4076            default_platform_version(),
4077            &provider,
4078        )
4079        .unwrap_err();
4080        assert!(matches!(err, Error::NotFound), "got: {err:?}");
4081    }
4082
4083    #[test]
4084    fn maybe_from_proof_delegates_to_with_metadata_and_forwards_none() {
4085        // `maybe_from_proof` discards metadata/proof when forwarding the
4086        // underlying `(None, _, _)` shape.
4087        let provider = unreachable_provider();
4088        let result = <MissingFromProof as FromProof<()>>::maybe_from_proof(
4089            (),
4090            (),
4091            Network::Testnet,
4092            default_platform_version(),
4093            &provider,
4094        )
4095        .unwrap();
4096        assert!(result.is_none(), "MissingFromProof must bubble None");
4097    }
4098
4099    // ---------------------------------------------------------------------
4100    // Additional coverage: more FromProof impls' decode-error paths
4101    // ---------------------------------------------------------------------
4102
4103    fn default_metadata_with_epoch(epoch: u32) -> ResponseMetadata {
4104        ResponseMetadata {
4105            epoch,
4106            ..Default::default()
4107        }
4108    }
4109
4110    #[test]
4111    fn identity_keys_rejects_bad_identity_id_length() {
4112        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4113        use platform::get_identity_keys_response::{
4114            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4115        };
4116
4117        let response = platform::GetIdentityKeysResponse {
4118            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4119                result: Some(V0Result::Proof(Proof::default())),
4120                metadata: Some(ResponseMetadata::default()),
4121            })),
4122        };
4123        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4124            identity_id: vec![0u8; 5], // must be 32
4125            request_type: None,
4126            limit: None,
4127            offset: None,
4128            prove: true,
4129        }
4130        .into();
4131        let provider = unreachable_provider();
4132        let err =
4133            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4134                request,
4135                response,
4136                Network::Testnet,
4137                default_platform_version(),
4138                &provider,
4139            )
4140            .unwrap_err();
4141        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4142    }
4143
4144    #[test]
4145    fn identity_keys_rejects_overflowing_limit() {
4146        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4147        use platform::get_identity_keys_response::{
4148            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4149        };
4150
4151        let response = platform::GetIdentityKeysResponse {
4152            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4153                result: Some(V0Result::Proof(Proof::default())),
4154                metadata: Some(ResponseMetadata::default()),
4155            })),
4156        };
4157        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4158            identity_id: vec![0u8; 32], // valid
4159            request_type: None,
4160            limit: Some(100_000),
4161            offset: None,
4162            prove: true,
4163        }
4164        .into();
4165        let provider = unreachable_provider();
4166        let err =
4167            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4168                request,
4169                response,
4170                Network::Testnet,
4171                default_platform_version(),
4172                &provider,
4173            )
4174            .unwrap_err();
4175        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4176    }
4177
4178    #[test]
4179    fn identity_keys_rejects_overflowing_offset() {
4180        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4181        use platform::get_identity_keys_response::{
4182            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4183        };
4184
4185        let response = platform::GetIdentityKeysResponse {
4186            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4187                result: Some(V0Result::Proof(Proof::default())),
4188                metadata: Some(ResponseMetadata::default()),
4189            })),
4190        };
4191        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4192            identity_id: vec![0u8; 32],
4193            request_type: None,
4194            limit: None,
4195            offset: Some(100_000),
4196            prove: true,
4197        }
4198        .into();
4199        let provider = unreachable_provider();
4200        let err =
4201            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4202                request,
4203                response,
4204                Network::Testnet,
4205                default_platform_version(),
4206                &provider,
4207            )
4208            .unwrap_err();
4209        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4210    }
4211
4212    #[test]
4213    fn identity_keys_rejects_missing_key_request_type() {
4214        // limit/offset are valid and identity_id is valid, so execution
4215        // reaches parse_key_request_type which errors on None.
4216        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4217        use platform::get_identity_keys_response::{
4218            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4219        };
4220
4221        let response = platform::GetIdentityKeysResponse {
4222            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4223                result: Some(V0Result::Proof(Proof::default())),
4224                metadata: Some(ResponseMetadata::default()),
4225            })),
4226        };
4227        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4228            identity_id: vec![0u8; 32],
4229            request_type: None,
4230            limit: None,
4231            offset: None,
4232            prove: true,
4233        }
4234        .into();
4235        let provider = unreachable_provider();
4236        let err =
4237            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4238                request,
4239                response,
4240                Network::Testnet,
4241                default_platform_version(),
4242                &provider,
4243            )
4244            .unwrap_err();
4245        match err {
4246            Error::RequestError { error } => {
4247                assert!(error.contains("missing key request type"), "got: {error}");
4248            }
4249            other => panic!("expected RequestError, got: {other:?}"),
4250        }
4251    }
4252
4253    #[test]
4254    fn identity_keys_no_proof_when_response_empty() {
4255        let request = platform::GetIdentityKeysRequest::default();
4256        let response = platform::GetIdentityKeysResponse::default();
4257        let provider = unreachable_provider();
4258        let err =
4259            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4260                request,
4261                response,
4262                Network::Testnet,
4263                default_platform_version(),
4264                &provider,
4265            )
4266            .unwrap_err();
4267        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4268    }
4269
4270    #[test]
4271    fn identity_nonce_rejects_bad_identity_id_length() {
4272        use dapi_grpc::platform::v0::get_identity_nonce_request::GetIdentityNonceRequestV0;
4273        use platform::get_identity_nonce_response::{
4274            get_identity_nonce_response_v0::Result as V0Result, GetIdentityNonceResponseV0, Version,
4275        };
4276
4277        let response = platform::GetIdentityNonceResponse {
4278            version: Some(Version::V0(GetIdentityNonceResponseV0 {
4279                result: Some(V0Result::Proof(Proof::default())),
4280                metadata: Some(ResponseMetadata::default()),
4281            })),
4282        };
4283        let request: platform::GetIdentityNonceRequest = GetIdentityNonceRequestV0 {
4284            identity_id: vec![0u8; 1], // must be 32
4285            prove: true,
4286        }
4287        .into();
4288        let provider = unreachable_provider();
4289        let err = <IdentityNonceFetcher as FromProof<
4290            platform::GetIdentityNonceRequest,
4291        >>::maybe_from_proof(
4292            request,
4293            response,
4294            Network::Testnet,
4295            default_platform_version(),
4296            &provider,
4297        )
4298        .unwrap_err();
4299        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4300    }
4301
4302    #[test]
4303    fn identity_nonce_no_proof_when_response_empty() {
4304        let request = platform::GetIdentityNonceRequest::default();
4305        let response = platform::GetIdentityNonceResponse::default();
4306        let provider = unreachable_provider();
4307        let err = <IdentityNonceFetcher as FromProof<
4308            platform::GetIdentityNonceRequest,
4309        >>::maybe_from_proof(
4310            request,
4311            response,
4312            Network::Testnet,
4313            default_platform_version(),
4314            &provider,
4315        )
4316        .unwrap_err();
4317        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4318    }
4319
4320    #[test]
4321    fn identity_contract_nonce_rejects_bad_identity_id_length() {
4322        use dapi_grpc::platform::v0::get_identity_contract_nonce_request::GetIdentityContractNonceRequestV0;
4323        use platform::get_identity_contract_nonce_response::{
4324            get_identity_contract_nonce_response_v0::Result as V0Result,
4325            GetIdentityContractNonceResponseV0, Version,
4326        };
4327
4328        let response = platform::GetIdentityContractNonceResponse {
4329            version: Some(Version::V0(GetIdentityContractNonceResponseV0 {
4330                result: Some(V0Result::Proof(Proof::default())),
4331                metadata: Some(ResponseMetadata::default()),
4332            })),
4333        };
4334        let request: platform::GetIdentityContractNonceRequest =
4335            GetIdentityContractNonceRequestV0 {
4336                identity_id: vec![0u8; 10], // must be 32
4337                contract_id: vec![0u8; 32],
4338                prove: true,
4339            }
4340            .into();
4341        let provider = unreachable_provider();
4342        let err = <IdentityContractNonceFetcher as FromProof<
4343            platform::GetIdentityContractNonceRequest,
4344        >>::maybe_from_proof(
4345            request,
4346            response,
4347            Network::Testnet,
4348            default_platform_version(),
4349            &provider,
4350        )
4351        .unwrap_err();
4352        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4353    }
4354
4355    #[test]
4356    fn identity_contract_nonce_rejects_bad_contract_id_length() {
4357        use dapi_grpc::platform::v0::get_identity_contract_nonce_request::GetIdentityContractNonceRequestV0;
4358        use platform::get_identity_contract_nonce_response::{
4359            get_identity_contract_nonce_response_v0::Result as V0Result,
4360            GetIdentityContractNonceResponseV0, Version,
4361        };
4362
4363        let response = platform::GetIdentityContractNonceResponse {
4364            version: Some(Version::V0(GetIdentityContractNonceResponseV0 {
4365                result: Some(V0Result::Proof(Proof::default())),
4366                metadata: Some(ResponseMetadata::default()),
4367            })),
4368        };
4369        let request: platform::GetIdentityContractNonceRequest =
4370            GetIdentityContractNonceRequestV0 {
4371                identity_id: vec![0u8; 32],
4372                contract_id: vec![0u8; 10], // must be 32
4373                prove: true,
4374            }
4375            .into();
4376        let provider = unreachable_provider();
4377        let err = <IdentityContractNonceFetcher as FromProof<
4378            platform::GetIdentityContractNonceRequest,
4379        >>::maybe_from_proof(
4380            request,
4381            response,
4382            Network::Testnet,
4383            default_platform_version(),
4384            &provider,
4385        )
4386        .unwrap_err();
4387        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4388    }
4389
4390    #[test]
4391    fn identity_balance_rejects_bad_id_length() {
4392        use dapi_grpc::platform::v0::get_identity_balance_request::GetIdentityBalanceRequestV0;
4393        use platform::get_identity_balance_response::{
4394            get_identity_balance_response_v0::Result as V0Result, GetIdentityBalanceResponseV0,
4395            Version,
4396        };
4397
4398        let response = platform::GetIdentityBalanceResponse {
4399            version: Some(Version::V0(GetIdentityBalanceResponseV0 {
4400                result: Some(V0Result::Proof(Proof::default())),
4401                metadata: Some(ResponseMetadata::default()),
4402            })),
4403        };
4404        let request: platform::GetIdentityBalanceRequest = GetIdentityBalanceRequestV0 {
4405            id: vec![0u8; 5],
4406            prove: true,
4407        }
4408        .into();
4409        let provider = unreachable_provider();
4410        let err =
4411            <IdentityBalance as FromProof<platform::GetIdentityBalanceRequest>>::maybe_from_proof(
4412                request,
4413                response,
4414                Network::Testnet,
4415                default_platform_version(),
4416                &provider,
4417            )
4418            .unwrap_err();
4419        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4420    }
4421
4422    #[test]
4423    fn identity_balance_empty_version_on_request_version_none() {
4424        // response OK; request.version None -> EmptyVersion.
4425        use platform::get_identity_balance_response::{
4426            get_identity_balance_response_v0::Result as V0Result, GetIdentityBalanceResponseV0,
4427            Version,
4428        };
4429        let response = platform::GetIdentityBalanceResponse {
4430            version: Some(Version::V0(GetIdentityBalanceResponseV0 {
4431                result: Some(V0Result::Proof(Proof::default())),
4432                metadata: Some(ResponseMetadata::default()),
4433            })),
4434        };
4435        let request = platform::GetIdentityBalanceRequest { version: None };
4436        let provider = unreachable_provider();
4437        let err =
4438            <IdentityBalance as FromProof<platform::GetIdentityBalanceRequest>>::maybe_from_proof(
4439                request,
4440                response,
4441                Network::Testnet,
4442                default_platform_version(),
4443                &provider,
4444            )
4445            .unwrap_err();
4446        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4447    }
4448
4449    #[test]
4450    fn identity_balance_and_revision_rejects_bad_id_length() {
4451        use dapi_grpc::platform::v0::get_identity_balance_and_revision_request::GetIdentityBalanceAndRevisionRequestV0;
4452        use platform::get_identity_balance_and_revision_response::{
4453            get_identity_balance_and_revision_response_v0::Result as V0Result,
4454            GetIdentityBalanceAndRevisionResponseV0, Version,
4455        };
4456        let response = platform::GetIdentityBalanceAndRevisionResponse {
4457            version: Some(Version::V0(GetIdentityBalanceAndRevisionResponseV0 {
4458                result: Some(V0Result::Proof(Proof::default())),
4459                metadata: Some(ResponseMetadata::default()),
4460            })),
4461        };
4462        let request: platform::GetIdentityBalanceAndRevisionRequest =
4463            GetIdentityBalanceAndRevisionRequestV0 {
4464                id: vec![0u8; 5],
4465                prove: true,
4466            }
4467            .into();
4468        let provider = unreachable_provider();
4469        let err = <IdentityBalanceAndRevision as FromProof<
4470            platform::GetIdentityBalanceAndRevisionRequest,
4471        >>::maybe_from_proof(
4472            request,
4473            response,
4474            Network::Testnet,
4475            default_platform_version(),
4476            &provider,
4477        )
4478        .unwrap_err();
4479        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4480    }
4481
4482    #[test]
4483    fn identities_balances_empty_version_none() {
4484        use platform::get_identities_balances_response::{
4485            get_identities_balances_response_v0::Result as V0Result,
4486            GetIdentitiesBalancesResponseV0, Version,
4487        };
4488        let response = platform::GetIdentitiesBalancesResponse {
4489            version: Some(Version::V0(GetIdentitiesBalancesResponseV0 {
4490                result: Some(V0Result::Proof(Proof::default())),
4491                metadata: Some(ResponseMetadata::default()),
4492            })),
4493        };
4494        let request = platform::GetIdentitiesBalancesRequest { version: None };
4495        let provider = unreachable_provider();
4496        let err =
4497            <IdentityBalances as FromProof<platform::GetIdentitiesBalancesRequest>>::maybe_from_proof(
4498                request,
4499                response,
4500                Network::Testnet,
4501                default_platform_version(),
4502                &provider,
4503            )
4504            .unwrap_err();
4505        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4506    }
4507
4508    #[test]
4509    fn data_contract_rejects_bad_id_length() {
4510        use dapi_grpc::platform::v0::get_data_contract_request::GetDataContractRequestV0;
4511        use platform::get_data_contract_response::{
4512            get_data_contract_response_v0::Result as V0Result, GetDataContractResponseV0, Version,
4513        };
4514        let response = platform::GetDataContractResponse {
4515            version: Some(Version::V0(GetDataContractResponseV0 {
4516                result: Some(V0Result::Proof(Proof::default())),
4517                metadata: Some(ResponseMetadata::default()),
4518            })),
4519        };
4520        let request: platform::GetDataContractRequest = GetDataContractRequestV0 {
4521            id: vec![0u8; 5],
4522            prove: true,
4523        }
4524        .into();
4525        let provider = unreachable_provider();
4526        let err = <DataContract as FromProof<platform::GetDataContractRequest>>::maybe_from_proof(
4527            request,
4528            response,
4529            Network::Testnet,
4530            default_platform_version(),
4531            &provider,
4532        )
4533        .unwrap_err();
4534        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4535    }
4536
4537    #[test]
4538    fn data_contract_no_proof_when_response_empty() {
4539        let request = platform::GetDataContractRequest::default();
4540        let response = platform::GetDataContractResponse::default();
4541        let provider = unreachable_provider();
4542        let err = <DataContract as FromProof<platform::GetDataContractRequest>>::maybe_from_proof(
4543            request,
4544            response,
4545            Network::Testnet,
4546            default_platform_version(),
4547            &provider,
4548        )
4549        .unwrap_err();
4550        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4551    }
4552
4553    #[test]
4554    fn data_contract_with_serialization_rejects_bad_id_length() {
4555        // This hits the second `FromProof for (DataContract, Vec<u8>)` impl.
4556        use dapi_grpc::platform::v0::get_data_contract_request::GetDataContractRequestV0;
4557        use platform::get_data_contract_response::{
4558            get_data_contract_response_v0::Result as V0Result, GetDataContractResponseV0, Version,
4559        };
4560        let response = platform::GetDataContractResponse {
4561            version: Some(Version::V0(GetDataContractResponseV0 {
4562                result: Some(V0Result::Proof(Proof::default())),
4563                metadata: Some(ResponseMetadata::default()),
4564            })),
4565        };
4566        let request: platform::GetDataContractRequest = GetDataContractRequestV0 {
4567            id: vec![0u8; 5],
4568            prove: true,
4569        }
4570        .into();
4571        let provider = unreachable_provider();
4572        let err =
4573            <(DataContract, Vec<u8>) as FromProof<platform::GetDataContractRequest>>::maybe_from_proof(
4574                request,
4575                response,
4576                Network::Testnet,
4577                default_platform_version(),
4578                &provider,
4579            )
4580            .unwrap_err();
4581        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4582    }
4583
4584    #[test]
4585    fn data_contract_history_rejects_bad_id_length() {
4586        use dapi_grpc::platform::v0::get_data_contract_history_request::GetDataContractHistoryRequestV0;
4587        use platform::get_data_contract_history_response::{
4588            get_data_contract_history_response_v0::Result as V0Result,
4589            GetDataContractHistoryResponseV0, Version,
4590        };
4591        let response = platform::GetDataContractHistoryResponse {
4592            version: Some(Version::V0(GetDataContractHistoryResponseV0 {
4593                result: Some(V0Result::Proof(Proof::default())),
4594                metadata: Some(ResponseMetadata::default()),
4595            })),
4596        };
4597        let request: platform::GetDataContractHistoryRequest = GetDataContractHistoryRequestV0 {
4598            id: vec![0u8; 5],
4599            limit: None,
4600            offset: None,
4601            start_at_ms: 0,
4602            prove: true,
4603        }
4604        .into();
4605        let provider = unreachable_provider();
4606        let err = <DataContractHistory as FromProof<
4607            platform::GetDataContractHistoryRequest,
4608        >>::maybe_from_proof(
4609            request,
4610            response,
4611            Network::Testnet,
4612            default_platform_version(),
4613            &provider,
4614        )
4615        .unwrap_err();
4616        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4617    }
4618
4619    #[test]
4620    fn data_contract_history_rejects_overflowing_limit() {
4621        use dapi_grpc::platform::v0::get_data_contract_history_request::GetDataContractHistoryRequestV0;
4622        use platform::get_data_contract_history_response::{
4623            get_data_contract_history_response_v0::Result as V0Result,
4624            GetDataContractHistoryResponseV0, Version,
4625        };
4626        let response = platform::GetDataContractHistoryResponse {
4627            version: Some(Version::V0(GetDataContractHistoryResponseV0 {
4628                result: Some(V0Result::Proof(Proof::default())),
4629                metadata: Some(ResponseMetadata::default()),
4630            })),
4631        };
4632        let request: platform::GetDataContractHistoryRequest = GetDataContractHistoryRequestV0 {
4633            id: vec![0u8; 32],
4634            limit: Some(100_000),
4635            offset: None,
4636            start_at_ms: 0,
4637            prove: true,
4638        }
4639        .into();
4640        let provider = unreachable_provider();
4641        let err = <DataContractHistory as FromProof<
4642            platform::GetDataContractHistoryRequest,
4643        >>::maybe_from_proof(
4644            request,
4645            response,
4646            Network::Testnet,
4647            default_platform_version(),
4648            &provider,
4649        )
4650        .unwrap_err();
4651        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4652    }
4653
4654    #[test]
4655    fn address_info_rejects_bad_address_bytes() {
4656        // PlatformAddress::from_bytes fails for invalid lengths.
4657        use dapi_grpc::platform::v0::get_address_info_request::GetAddressInfoRequestV0;
4658        use platform::get_address_info_response::{
4659            get_address_info_response_v0::Result as V0Result, GetAddressInfoResponseV0, Version,
4660        };
4661        let response = platform::GetAddressInfoResponse {
4662            version: Some(Version::V0(GetAddressInfoResponseV0 {
4663                result: Some(V0Result::Proof(Proof::default())),
4664                metadata: Some(ResponseMetadata::default()),
4665            })),
4666        };
4667        let request: platform::GetAddressInfoRequest = GetAddressInfoRequestV0 {
4668            address: vec![0u8; 3], // not a valid address length
4669            prove: true,
4670        }
4671        .into();
4672        let provider = unreachable_provider();
4673        let err = <AddressInfo as FromProof<platform::GetAddressInfoRequest>>::maybe_from_proof(
4674            request,
4675            response,
4676            Network::Testnet,
4677            default_platform_version(),
4678            &provider,
4679        )
4680        .unwrap_err();
4681        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4682    }
4683
4684    #[test]
4685    fn addresses_infos_rejects_bad_address_bytes() {
4686        use dapi_grpc::platform::v0::get_addresses_infos_request::GetAddressesInfosRequestV0;
4687        use platform::get_addresses_infos_response::{
4688            get_addresses_infos_response_v0::Result as V0Result, GetAddressesInfosResponseV0,
4689            Version,
4690        };
4691        let response = platform::GetAddressesInfosResponse {
4692            version: Some(Version::V0(GetAddressesInfosResponseV0 {
4693                result: Some(V0Result::Proof(Proof::default())),
4694                metadata: Some(ResponseMetadata::default()),
4695            })),
4696        };
4697        let request: platform::GetAddressesInfosRequest = GetAddressesInfosRequestV0 {
4698            addresses: vec![vec![0u8; 3]],
4699            prove: true,
4700        }
4701        .into();
4702        let provider = unreachable_provider();
4703        let err =
4704            <AddressInfos as FromProof<platform::GetAddressesInfosRequest>>::maybe_from_proof(
4705                request,
4706                response,
4707                Network::Testnet,
4708                default_platform_version(),
4709                &provider,
4710            )
4711            .unwrap_err();
4712        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4713    }
4714
4715    #[test]
4716    fn addresses_trunk_state_grove_no_proof() {
4717        // GroveTrunkQueryResult impl ignores the request entirely, so the
4718        // first failure possible is NoProofInResult when the response is empty.
4719        let response = platform::GetAddressesTrunkStateResponse::default();
4720        let request = platform::GetAddressesTrunkStateRequest::default();
4721        let provider = unreachable_provider();
4722        let err = <GroveTrunkQueryResult as FromProof<
4723            platform::GetAddressesTrunkStateRequest,
4724        >>::maybe_from_proof(
4725            request,
4726            response,
4727            Network::Testnet,
4728            default_platform_version(),
4729            &provider,
4730        )
4731        .unwrap_err();
4732        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4733    }
4734
4735    #[test]
4736    fn platform_address_trunk_state_no_proof() {
4737        // PlatformAddressTrunkState wraps the GroveTrunkQueryResult impl; same error.
4738        let response = platform::GetAddressesTrunkStateResponse::default();
4739        let request = platform::GetAddressesTrunkStateRequest::default();
4740        let provider = unreachable_provider();
4741        let err = <PlatformAddressTrunkState as FromProof<
4742            platform::GetAddressesTrunkStateRequest,
4743        >>::maybe_from_proof(
4744            request,
4745            response,
4746            Network::Testnet,
4747            default_platform_version(),
4748            &provider,
4749        )
4750        .unwrap_err();
4751        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4752    }
4753
4754    #[test]
4755    fn nullifiers_trunk_state_empty_version_returns_empty_version_err() {
4756        // The explicit `None => EmptyVersion` arm.
4757        use platform::get_nullifiers_trunk_state_response::{
4758            GetNullifiersTrunkStateResponseV0, Version,
4759        };
4760        let response = platform::GetNullifiersTrunkStateResponse {
4761            version: Some(Version::V0(GetNullifiersTrunkStateResponseV0 {
4762                proof: Some(Proof::default()),
4763                metadata: Some(ResponseMetadata::default()),
4764            })),
4765        };
4766        let request = platform::GetNullifiersTrunkStateRequest { version: None };
4767        let provider = unreachable_provider();
4768        let err = <GroveTrunkQueryResult as FromProof<
4769            platform::GetNullifiersTrunkStateRequest,
4770        >>::maybe_from_proof(
4771            request,
4772            response,
4773            Network::Testnet,
4774            default_platform_version(),
4775            &provider,
4776        )
4777        .unwrap_err();
4778        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4779    }
4780
4781    #[test]
4782    fn nullifiers_trunk_state_no_proof_when_response_empty() {
4783        let response = platform::GetNullifiersTrunkStateResponse::default();
4784        let request = platform::GetNullifiersTrunkStateRequest::default();
4785        let provider = unreachable_provider();
4786        let err = <GroveTrunkQueryResult as FromProof<
4787            platform::GetNullifiersTrunkStateRequest,
4788        >>::maybe_from_proof(
4789            request,
4790            response,
4791            Network::Testnet,
4792            default_platform_version(),
4793            &provider,
4794        )
4795        .unwrap_err();
4796        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4797    }
4798
4799    #[test]
4800    fn epochs_info_empty_version_none() {
4801        use platform::get_epochs_info_response::{
4802            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
4803        };
4804        let response = platform::GetEpochsInfoResponse {
4805            version: Some(Version::V0(GetEpochsInfoResponseV0 {
4806                result: Some(V0Result::Proof(Proof::default())),
4807                metadata: Some(ResponseMetadata::default()),
4808            })),
4809        };
4810        let request = platform::GetEpochsInfoRequest { version: None };
4811        let provider = unreachable_provider();
4812        let err =
4813            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
4814                request,
4815                response,
4816                Network::Testnet,
4817                default_platform_version(),
4818                &provider,
4819            )
4820            .unwrap_err();
4821        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4822    }
4823
4824    #[test]
4825    fn epochs_info_rejects_overflowing_count() {
4826        // start_epoch valid, but count > u16::MAX -> try_u32_to_u16 errors.
4827        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
4828        use platform::get_epochs_info_response::{
4829            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
4830        };
4831        let response = platform::GetEpochsInfoResponse {
4832            version: Some(Version::V0(GetEpochsInfoResponseV0 {
4833                result: Some(V0Result::Proof(Proof::default())),
4834                metadata: Some(default_metadata_with_epoch(10)),
4835            })),
4836        };
4837        let request = platform::GetEpochsInfoRequest {
4838            version: Some(platform::get_epochs_info_request::Version::V0(
4839                GetEpochsInfoRequestV0 {
4840                    start_epoch: Some(0),
4841                    count: 100_000,
4842                    ascending: true,
4843                    prove: true,
4844                },
4845            )),
4846        };
4847        let provider = unreachable_provider();
4848        let err =
4849            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
4850                request,
4851                response,
4852                Network::Testnet,
4853                default_platform_version(),
4854                &provider,
4855            )
4856            .unwrap_err();
4857        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4858    }
4859
4860    #[test]
4861    fn epochs_info_rejects_overflowing_metadata_epoch() {
4862        // mtd.epoch > u16::MAX triggers `try_u32_to_u16(mtd.epoch)` error
4863        // *before* the start_epoch check.
4864        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
4865        use platform::get_epochs_info_response::{
4866            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
4867        };
4868        let response = platform::GetEpochsInfoResponse {
4869            version: Some(Version::V0(GetEpochsInfoResponseV0 {
4870                result: Some(V0Result::Proof(Proof::default())),
4871                metadata: Some(default_metadata_with_epoch(70_000)),
4872            })),
4873        };
4874        let request = platform::GetEpochsInfoRequest {
4875            version: Some(platform::get_epochs_info_request::Version::V0(
4876                GetEpochsInfoRequestV0 {
4877                    start_epoch: None,
4878                    count: 1,
4879                    ascending: true,
4880                    prove: true,
4881                },
4882            )),
4883        };
4884        let provider = unreachable_provider();
4885        let err =
4886            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
4887                request,
4888                response,
4889                Network::Testnet,
4890                default_platform_version(),
4891                &provider,
4892            )
4893            .unwrap_err();
4894        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4895    }
4896
4897    #[test]
4898    fn extended_epoch_info_single_bubbles_empty_version() {
4899        // Ensures the wrapper passes through error from inner impl.
4900        use platform::get_epochs_info_response::{
4901            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
4902        };
4903        let response = platform::GetEpochsInfoResponse {
4904            version: Some(Version::V0(GetEpochsInfoResponseV0 {
4905                result: Some(V0Result::Proof(Proof::default())),
4906                metadata: Some(ResponseMetadata::default()),
4907            })),
4908        };
4909        let request = platform::GetEpochsInfoRequest { version: None };
4910        let provider = unreachable_provider();
4911        let err =
4912            <ExtendedEpochInfo as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
4913                request,
4914                response,
4915                Network::Testnet,
4916                default_platform_version(),
4917                &provider,
4918            )
4919            .unwrap_err();
4920        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4921    }
4922
4923    #[test]
4924    fn finalized_epoch_infos_rejects_overflowing_start_index() {
4925        use dapi_grpc::platform::v0::get_finalized_epoch_infos_request::GetFinalizedEpochInfosRequestV0;
4926        use platform::get_finalized_epoch_infos_response::{
4927            get_finalized_epoch_infos_response_v0::Result as V0Result,
4928            GetFinalizedEpochInfosResponseV0, Version,
4929        };
4930        let response = platform::GetFinalizedEpochInfosResponse {
4931            version: Some(Version::V0(GetFinalizedEpochInfosResponseV0 {
4932                result: Some(V0Result::Proof(Proof::default())),
4933                metadata: Some(ResponseMetadata::default()),
4934            })),
4935        };
4936        let request: platform::GetFinalizedEpochInfosRequest = GetFinalizedEpochInfosRequestV0 {
4937            start_epoch_index: 100_000,
4938            start_epoch_index_included: true,
4939            end_epoch_index: 1,
4940            end_epoch_index_included: true,
4941            prove: true,
4942        }
4943        .into();
4944        let provider = unreachable_provider();
4945        let err = <FinalizedEpochInfos as FromProof<
4946            platform::GetFinalizedEpochInfosRequest,
4947        >>::maybe_from_proof(
4948            request,
4949            response,
4950            Network::Testnet,
4951            default_platform_version(),
4952            &provider,
4953        )
4954        .unwrap_err();
4955        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4956    }
4957
4958    #[test]
4959    fn finalized_epoch_infos_rejects_overflowing_end_index() {
4960        use dapi_grpc::platform::v0::get_finalized_epoch_infos_request::GetFinalizedEpochInfosRequestV0;
4961        use platform::get_finalized_epoch_infos_response::{
4962            get_finalized_epoch_infos_response_v0::Result as V0Result,
4963            GetFinalizedEpochInfosResponseV0, Version,
4964        };
4965        let response = platform::GetFinalizedEpochInfosResponse {
4966            version: Some(Version::V0(GetFinalizedEpochInfosResponseV0 {
4967                result: Some(V0Result::Proof(Proof::default())),
4968                metadata: Some(ResponseMetadata::default()),
4969            })),
4970        };
4971        let request: platform::GetFinalizedEpochInfosRequest = GetFinalizedEpochInfosRequestV0 {
4972            start_epoch_index: 1,
4973            start_epoch_index_included: true,
4974            end_epoch_index: 100_000,
4975            end_epoch_index_included: true,
4976            prove: true,
4977        }
4978        .into();
4979        let provider = unreachable_provider();
4980        let err = <FinalizedEpochInfos as FromProof<
4981            platform::GetFinalizedEpochInfosRequest,
4982        >>::maybe_from_proof(
4983            request,
4984            response,
4985            Network::Testnet,
4986            default_platform_version(),
4987            &provider,
4988        )
4989        .unwrap_err();
4990        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4991    }
4992
4993    #[test]
4994    fn upgrade_state_no_proof_when_response_empty() {
4995        // No request dependency, so the first error must come from the response.
4996        let response = GetProtocolVersionUpgradeStateResponse::default();
4997        let request = GetProtocolVersionUpgradeStateRequest::default();
4998        let provider = unreachable_provider();
4999        let err = <ProtocolVersionUpgrades as FromProof<
5000            GetProtocolVersionUpgradeStateRequest,
5001        >>::maybe_from_proof(
5002            request,
5003            response,
5004            Network::Testnet,
5005            default_platform_version(),
5006            &provider,
5007        )
5008        .unwrap_err();
5009        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5010    }
5011
5012    #[test]
5013    fn upgrade_vote_status_no_proof_when_response_empty() {
5014        let response = GetProtocolVersionUpgradeVoteStatusResponse::default();
5015        let request = GetProtocolVersionUpgradeVoteStatusRequest::default();
5016        let provider = unreachable_provider();
5017        let err = <MasternodeProtocolVotes as FromProof<
5018            GetProtocolVersionUpgradeVoteStatusRequest,
5019        >>::maybe_from_proof(
5020            request,
5021            response,
5022            Network::Testnet,
5023            default_platform_version(),
5024            &provider,
5025        )
5026        .unwrap_err();
5027        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5028    }
5029
5030    #[test]
5031    fn upgrade_vote_status_rejects_overflowing_count() {
5032        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_request::GetProtocolVersionUpgradeVoteStatusRequestV0;
5033        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_response::{
5034            get_protocol_version_upgrade_vote_status_response_v0::Result as V0Result,
5035            GetProtocolVersionUpgradeVoteStatusResponseV0, Version,
5036        };
5037
5038        let response = GetProtocolVersionUpgradeVoteStatusResponse {
5039            version: Some(Version::V0(GetProtocolVersionUpgradeVoteStatusResponseV0 {
5040                result: Some(V0Result::Proof(Proof::default())),
5041                metadata: Some(ResponseMetadata::default()),
5042            })),
5043        };
5044        // empty start_pro_tx_hash (None branch), but count overflow
5045        let request: GetProtocolVersionUpgradeVoteStatusRequest =
5046            GetProtocolVersionUpgradeVoteStatusRequestV0 {
5047                start_pro_tx_hash: vec![],
5048                count: 100_000,
5049                prove: true,
5050            }
5051            .into();
5052        let provider = unreachable_provider();
5053        let err = <MasternodeProtocolVotes as FromProof<
5054            GetProtocolVersionUpgradeVoteStatusRequest,
5055        >>::maybe_from_proof(
5056            request,
5057            response,
5058            Network::Testnet,
5059            default_platform_version(),
5060            &provider,
5061        )
5062        .unwrap_err();
5063        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5064    }
5065
5066    #[test]
5067    fn path_elements_empty_version_when_no_version() {
5068        // Response has full v0 shell, request has None -> EmptyVersion.
5069        use platform::get_path_elements_response::{
5070            get_path_elements_response_v0::Result as V0Result, GetPathElementsResponseV0, Version,
5071        };
5072        let response = GetPathElementsResponse {
5073            version: Some(Version::V0(GetPathElementsResponseV0 {
5074                result: Some(V0Result::Proof(Proof::default())),
5075                metadata: Some(ResponseMetadata::default()),
5076            })),
5077        };
5078        let request = GetPathElementsRequest { version: None };
5079        let provider = unreachable_provider();
5080        let err = <Elements as FromProof<GetPathElementsRequest>>::maybe_from_proof(
5081            request,
5082            response,
5083            Network::Testnet,
5084            default_platform_version(),
5085            &provider,
5086        )
5087        .unwrap_err();
5088        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5089    }
5090
5091    #[test]
5092    fn identities_contract_keys_rejects_bad_identity_id() {
5093        use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
5094        use platform::get_identities_contract_keys_response::{
5095            get_identities_contract_keys_response_v0::Result as V0Result,
5096            GetIdentitiesContractKeysResponseV0, Version,
5097        };
5098        let response = platform::GetIdentitiesContractKeysResponse {
5099            version: Some(Version::V0(GetIdentitiesContractKeysResponseV0 {
5100                result: Some(V0Result::Proof(Proof::default())),
5101                metadata: Some(ResponseMetadata::default()),
5102            })),
5103        };
5104        let request: platform::GetIdentitiesContractKeysRequest =
5105            GetIdentitiesContractKeysRequestV0 {
5106                identities_ids: vec![vec![0u8; 5]], // bad
5107                contract_id: vec![0u8; 32],
5108                document_type_name: None,
5109                purposes: vec![0],
5110                prove: true,
5111            }
5112            .into();
5113        let provider = unreachable_provider();
5114        let err = <IdentitiesContractKeys as FromProof<
5115            platform::GetIdentitiesContractKeysRequest,
5116        >>::maybe_from_proof(
5117            request,
5118            response,
5119            Network::Testnet,
5120            default_platform_version(),
5121            &provider,
5122        )
5123        .unwrap_err();
5124        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5125    }
5126
5127    #[test]
5128    fn identities_contract_keys_rejects_bad_contract_id() {
5129        use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
5130        use platform::get_identities_contract_keys_response::{
5131            get_identities_contract_keys_response_v0::Result as V0Result,
5132            GetIdentitiesContractKeysResponseV0, Version,
5133        };
5134        let response = platform::GetIdentitiesContractKeysResponse {
5135            version: Some(Version::V0(GetIdentitiesContractKeysResponseV0 {
5136                result: Some(V0Result::Proof(Proof::default())),
5137                metadata: Some(ResponseMetadata::default()),
5138            })),
5139        };
5140        let request: platform::GetIdentitiesContractKeysRequest =
5141            GetIdentitiesContractKeysRequestV0 {
5142                identities_ids: vec![vec![0u8; 32]],
5143                contract_id: vec![0u8; 5], // bad
5144                document_type_name: None,
5145                purposes: vec![0],
5146                prove: true,
5147            }
5148            .into();
5149        let provider = unreachable_provider();
5150        let err = <IdentitiesContractKeys as FromProof<
5151            platform::GetIdentitiesContractKeysRequest,
5152        >>::maybe_from_proof(
5153            request,
5154            response,
5155            Network::Testnet,
5156            default_platform_version(),
5157            &provider,
5158        )
5159        .unwrap_err();
5160        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5161    }
5162
5163    #[test]
5164    fn identities_contract_keys_rejects_bad_purpose() {
5165        use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
5166        use platform::get_identities_contract_keys_response::{
5167            get_identities_contract_keys_response_v0::Result as V0Result,
5168            GetIdentitiesContractKeysResponseV0, Version,
5169        };
5170        let response = platform::GetIdentitiesContractKeysResponse {
5171            version: Some(Version::V0(GetIdentitiesContractKeysResponseV0 {
5172                result: Some(V0Result::Proof(Proof::default())),
5173                metadata: Some(ResponseMetadata::default()),
5174            })),
5175        };
5176        // purpose 250 is not a valid Purpose value.
5177        let request: platform::GetIdentitiesContractKeysRequest =
5178            GetIdentitiesContractKeysRequestV0 {
5179                identities_ids: vec![vec![0u8; 32]],
5180                contract_id: vec![0u8; 32],
5181                document_type_name: None,
5182                purposes: vec![250],
5183                prove: true,
5184            }
5185            .into();
5186        let provider = unreachable_provider();
5187        let err = <IdentitiesContractKeys as FromProof<
5188            platform::GetIdentitiesContractKeysRequest,
5189        >>::maybe_from_proof(
5190            request,
5191            response,
5192            Network::Testnet,
5193            default_platform_version(),
5194            &provider,
5195        )
5196        .unwrap_err();
5197        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5198    }
5199
5200    #[test]
5201    fn prefunded_balance_empty_version_on_request_version_none() {
5202        // response has proof; request.version = None -> EmptyVersion
5203        use platform::get_prefunded_specialized_balance_response::{
5204            get_prefunded_specialized_balance_response_v0::Result as V0Result,
5205            GetPrefundedSpecializedBalanceResponseV0, Version,
5206        };
5207        let response = platform::GetPrefundedSpecializedBalanceResponse {
5208            version: Some(Version::V0(GetPrefundedSpecializedBalanceResponseV0 {
5209                result: Some(V0Result::Proof(Proof::default())),
5210                metadata: Some(ResponseMetadata::default()),
5211            })),
5212        };
5213        let request = platform::GetPrefundedSpecializedBalanceRequest { version: None };
5214        let provider = unreachable_provider();
5215        let err = <PrefundedSpecializedBalance as FromProof<
5216            platform::GetPrefundedSpecializedBalanceRequest,
5217        >>::maybe_from_proof(
5218            request,
5219            response,
5220            Network::Testnet,
5221            default_platform_version(),
5222            &provider,
5223        )
5224        .unwrap_err();
5225        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5226    }
5227
5228    #[test]
5229    fn evonodes_proposed_epoch_blocks_by_ids_empty_version() {
5230        use platform::get_evonodes_proposed_epoch_blocks_response::{
5231            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5232            GetEvonodesProposedEpochBlocksResponseV0, Version,
5233        };
5234        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5235            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5236                result: Some(V0Result::Proof(Proof::default())),
5237                metadata: Some(ResponseMetadata::default()),
5238            })),
5239        };
5240        let request = platform::GetEvonodesProposedEpochBlocksByIdsRequest { version: None };
5241        let provider = unreachable_provider();
5242        let err = <ProposerBlockCounts as FromProof<
5243            platform::GetEvonodesProposedEpochBlocksByIdsRequest,
5244        >>::maybe_from_proof(
5245            request,
5246            response,
5247            Network::Testnet,
5248            default_platform_version(),
5249            &provider,
5250        )
5251        .unwrap_err();
5252        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5253    }
5254
5255    #[test]
5256    fn evonodes_proposed_epoch_blocks_by_ids_rejects_overflowing_epoch() {
5257        // Request sets epoch > u16::MAX -> try_u32_to_u16 error.
5258        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_ids_request::GetEvonodesProposedEpochBlocksByIdsRequestV0;
5259        use platform::get_evonodes_proposed_epoch_blocks_response::{
5260            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5261            GetEvonodesProposedEpochBlocksResponseV0, Version,
5262        };
5263        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5264            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5265                result: Some(V0Result::Proof(Proof::default())),
5266                metadata: Some(default_metadata_with_epoch(1)),
5267            })),
5268        };
5269        let request: platform::GetEvonodesProposedEpochBlocksByIdsRequest =
5270            GetEvonodesProposedEpochBlocksByIdsRequestV0 {
5271                epoch: Some(100_000),
5272                ids: vec![],
5273                prove: true,
5274            }
5275            .into();
5276        let provider = unreachable_provider();
5277        let err = <ProposerBlockCounts as FromProof<
5278            platform::GetEvonodesProposedEpochBlocksByIdsRequest,
5279        >>::maybe_from_proof(
5280            request,
5281            response,
5282            Network::Testnet,
5283            default_platform_version(),
5284            &provider,
5285        )
5286        .unwrap_err();
5287        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5288    }
5289
5290    #[test]
5291    fn evonodes_proposed_epoch_blocks_by_ids_falls_back_to_metadata_epoch_overflow() {
5292        // epoch None -> fall back to mtd.epoch which is overflowing.
5293        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_ids_request::GetEvonodesProposedEpochBlocksByIdsRequestV0;
5294        use platform::get_evonodes_proposed_epoch_blocks_response::{
5295            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5296            GetEvonodesProposedEpochBlocksResponseV0, Version,
5297        };
5298        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5299            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5300                result: Some(V0Result::Proof(Proof::default())),
5301                metadata: Some(default_metadata_with_epoch(99_999)),
5302            })),
5303        };
5304        let request: platform::GetEvonodesProposedEpochBlocksByIdsRequest =
5305            GetEvonodesProposedEpochBlocksByIdsRequestV0 {
5306                epoch: None,
5307                ids: vec![],
5308                prove: true,
5309            }
5310            .into();
5311        let provider = unreachable_provider();
5312        let err = <ProposerBlockCounts as FromProof<
5313            platform::GetEvonodesProposedEpochBlocksByIdsRequest,
5314        >>::maybe_from_proof(
5315            request,
5316            response,
5317            Network::Testnet,
5318            default_platform_version(),
5319            &provider,
5320        )
5321        .unwrap_err();
5322        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5323    }
5324
5325    #[test]
5326    fn evonodes_proposed_epoch_blocks_by_range_empty_version() {
5327        use platform::get_evonodes_proposed_epoch_blocks_response::{
5328            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5329            GetEvonodesProposedEpochBlocksResponseV0, Version,
5330        };
5331        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5332            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5333                result: Some(V0Result::Proof(Proof::default())),
5334                metadata: Some(ResponseMetadata::default()),
5335            })),
5336        };
5337        let request = platform::GetEvonodesProposedEpochBlocksByRangeRequest { version: None };
5338        let provider = unreachable_provider();
5339        let err = <ProposerBlockCounts as FromProof<
5340            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5341        >>::maybe_from_proof(
5342            request,
5343            response,
5344            Network::Testnet,
5345            default_platform_version(),
5346            &provider,
5347        )
5348        .unwrap_err();
5349        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5350    }
5351
5352    #[test]
5353    fn evonodes_proposed_epoch_blocks_by_range_rejects_bad_start_after() {
5354        // Start::StartAfter with wrong length must fail.
5355        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::{
5356            get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start,
5357            GetEvonodesProposedEpochBlocksByRangeRequestV0,
5358        };
5359        use platform::get_evonodes_proposed_epoch_blocks_response::{
5360            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5361            GetEvonodesProposedEpochBlocksResponseV0, Version,
5362        };
5363        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5364            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5365                result: Some(V0Result::Proof(Proof::default())),
5366                metadata: Some(default_metadata_with_epoch(1)),
5367            })),
5368        };
5369        let request: platform::GetEvonodesProposedEpochBlocksByRangeRequest =
5370            GetEvonodesProposedEpochBlocksByRangeRequestV0 {
5371                epoch: Some(1),
5372                limit: None,
5373                start: Some(Start::StartAfter(vec![0u8; 5])),
5374                prove: true,
5375            }
5376            .into();
5377        let provider = unreachable_provider();
5378        let err = <ProposerBlockCounts as FromProof<
5379            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5380        >>::maybe_from_proof(
5381            request,
5382            response,
5383            Network::Testnet,
5384            default_platform_version(),
5385            &provider,
5386        )
5387        .unwrap_err();
5388        assert!(matches!(err, Error::DriveError { .. }), "got: {err:?}");
5389    }
5390
5391    #[test]
5392    fn evonodes_proposed_epoch_blocks_by_range_rejects_bad_start_at() {
5393        // Start::StartAt with wrong length must fail.
5394        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::{
5395            get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start,
5396            GetEvonodesProposedEpochBlocksByRangeRequestV0,
5397        };
5398        use platform::get_evonodes_proposed_epoch_blocks_response::{
5399            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5400            GetEvonodesProposedEpochBlocksResponseV0, Version,
5401        };
5402        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5403            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5404                result: Some(V0Result::Proof(Proof::default())),
5405                metadata: Some(default_metadata_with_epoch(1)),
5406            })),
5407        };
5408        let request: platform::GetEvonodesProposedEpochBlocksByRangeRequest =
5409            GetEvonodesProposedEpochBlocksByRangeRequestV0 {
5410                epoch: Some(1),
5411                limit: None,
5412                start: Some(Start::StartAt(vec![0u8; 4])),
5413                prove: true,
5414            }
5415            .into();
5416        let provider = unreachable_provider();
5417        let err = <ProposerBlockCounts as FromProof<
5418            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5419        >>::maybe_from_proof(
5420            request,
5421            response,
5422            Network::Testnet,
5423            default_platform_version(),
5424            &provider,
5425        )
5426        .unwrap_err();
5427        assert!(matches!(err, Error::DriveError { .. }), "got: {err:?}");
5428    }
5429
5430    #[test]
5431    fn evonodes_proposed_epoch_blocks_by_range_rejects_overflow_limit() {
5432        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::GetEvonodesProposedEpochBlocksByRangeRequestV0;
5433        use platform::get_evonodes_proposed_epoch_blocks_response::{
5434            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5435            GetEvonodesProposedEpochBlocksResponseV0, Version,
5436        };
5437        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5438            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5439                result: Some(V0Result::Proof(Proof::default())),
5440                metadata: Some(default_metadata_with_epoch(1)),
5441            })),
5442        };
5443        let request: platform::GetEvonodesProposedEpochBlocksByRangeRequest =
5444            GetEvonodesProposedEpochBlocksByRangeRequestV0 {
5445                epoch: Some(1),
5446                limit: Some(100_000),
5447                start: None,
5448                prove: true,
5449            }
5450            .into();
5451        let provider = unreachable_provider();
5452        let err = <ProposerBlockCounts as FromProof<
5453            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5454        >>::maybe_from_proof(
5455            request,
5456            response,
5457            Network::Testnet,
5458            default_platform_version(),
5459            &provider,
5460        )
5461        .unwrap_err();
5462        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5463    }
5464
5465    #[test]
5466    fn shielded_pool_state_no_proof_when_response_empty() {
5467        let response = platform::GetShieldedPoolStateResponse::default();
5468        let request = platform::GetShieldedPoolStateRequest::default();
5469        let provider = unreachable_provider();
5470        let err = <ShieldedPoolState as FromProof<
5471            platform::GetShieldedPoolStateRequest,
5472        >>::maybe_from_proof(
5473            request,
5474            response,
5475            Network::Testnet,
5476            default_platform_version(),
5477            &provider,
5478        )
5479        .unwrap_err();
5480        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5481    }
5482
5483    #[test]
5484    fn shielded_anchors_no_proof_when_response_empty() {
5485        let response = platform::GetShieldedAnchorsResponse::default();
5486        let request = platform::GetShieldedAnchorsRequest::default();
5487        let provider = unreachable_provider();
5488        let err =
5489            <ShieldedAnchors as FromProof<platform::GetShieldedAnchorsRequest>>::maybe_from_proof(
5490                request,
5491                response,
5492                Network::Testnet,
5493                default_platform_version(),
5494                &provider,
5495            )
5496            .unwrap_err();
5497        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5498    }
5499
5500    #[test]
5501    fn most_recent_shielded_anchor_no_proof_when_response_empty() {
5502        let response = platform::GetMostRecentShieldedAnchorResponse::default();
5503        let request = platform::GetMostRecentShieldedAnchorRequest::default();
5504        let provider = unreachable_provider();
5505        let err = <MostRecentShieldedAnchor as FromProof<
5506            platform::GetMostRecentShieldedAnchorRequest,
5507        >>::maybe_from_proof(
5508            request,
5509            response,
5510            Network::Testnet,
5511            default_platform_version(),
5512            &provider,
5513        )
5514        .unwrap_err();
5515        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5516    }
5517
5518    #[test]
5519    fn shielded_encrypted_notes_empty_version_on_request_version_none() {
5520        use platform::get_shielded_encrypted_notes_response::{
5521            get_shielded_encrypted_notes_response_v0::Result as V0Result,
5522            GetShieldedEncryptedNotesResponseV0, Version,
5523        };
5524        let response = platform::GetShieldedEncryptedNotesResponse {
5525            version: Some(Version::V0(GetShieldedEncryptedNotesResponseV0 {
5526                result: Some(V0Result::Proof(Proof::default())),
5527                metadata: Some(ResponseMetadata::default()),
5528            })),
5529        };
5530        let request = platform::GetShieldedEncryptedNotesRequest { version: None };
5531        let provider = unreachable_provider();
5532        let err = <ShieldedEncryptedNotes as FromProof<
5533            platform::GetShieldedEncryptedNotesRequest,
5534        >>::maybe_from_proof(
5535            request,
5536            response,
5537            Network::Testnet,
5538            default_platform_version(),
5539            &provider,
5540        )
5541        .unwrap_err();
5542        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5543    }
5544
5545    #[test]
5546    fn shielded_encrypted_notes_no_proof_when_response_empty() {
5547        let response = platform::GetShieldedEncryptedNotesResponse::default();
5548        let request = platform::GetShieldedEncryptedNotesRequest::default();
5549        let provider = unreachable_provider();
5550        let err = <ShieldedEncryptedNotes as FromProof<
5551            platform::GetShieldedEncryptedNotesRequest,
5552        >>::maybe_from_proof(
5553            request,
5554            response,
5555            Network::Testnet,
5556            default_platform_version(),
5557            &provider,
5558        )
5559        .unwrap_err();
5560        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5561    }
5562
5563    #[test]
5564    fn shielded_nullifiers_empty_version_on_request_version_none() {
5565        use platform::get_shielded_nullifiers_response::{
5566            get_shielded_nullifiers_response_v0::Result as V0Result,
5567            GetShieldedNullifiersResponseV0, Version,
5568        };
5569        let response = platform::GetShieldedNullifiersResponse {
5570            version: Some(Version::V0(GetShieldedNullifiersResponseV0 {
5571                result: Some(V0Result::Proof(Proof::default())),
5572                metadata: Some(ResponseMetadata::default()),
5573            })),
5574        };
5575        let request = platform::GetShieldedNullifiersRequest { version: None };
5576        let provider = unreachable_provider();
5577        let err = <ShieldedNullifierStatuses as FromProof<
5578            platform::GetShieldedNullifiersRequest,
5579        >>::maybe_from_proof(
5580            request,
5581            response,
5582            Network::Testnet,
5583            default_platform_version(),
5584            &provider,
5585        )
5586        .unwrap_err();
5587        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5588    }
5589
5590    #[test]
5591    fn recent_address_balance_changes_empty_version() {
5592        use platform::get_recent_address_balance_changes_response::{
5593            get_recent_address_balance_changes_response_v0::Result as V0Result,
5594            GetRecentAddressBalanceChangesResponseV0, Version,
5595        };
5596        let response = platform::GetRecentAddressBalanceChangesResponse {
5597            version: Some(Version::V0(GetRecentAddressBalanceChangesResponseV0 {
5598                result: Some(V0Result::Proof(Proof::default())),
5599                metadata: Some(ResponseMetadata::default()),
5600            })),
5601        };
5602        let request = platform::GetRecentAddressBalanceChangesRequest { version: None };
5603        let provider = unreachable_provider();
5604        let err = <RecentAddressBalanceChanges as FromProof<
5605            platform::GetRecentAddressBalanceChangesRequest,
5606        >>::maybe_from_proof(
5607            request,
5608            response,
5609            Network::Testnet,
5610            default_platform_version(),
5611            &provider,
5612        )
5613        .unwrap_err();
5614        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5615    }
5616
5617    #[test]
5618    fn recent_compacted_address_balance_changes_empty_version() {
5619        use platform::get_recent_compacted_address_balance_changes_response::{
5620            get_recent_compacted_address_balance_changes_response_v0::Result as V0Result,
5621            GetRecentCompactedAddressBalanceChangesResponseV0, Version,
5622        };
5623        let response = platform::GetRecentCompactedAddressBalanceChangesResponse {
5624            version: Some(Version::V0(
5625                GetRecentCompactedAddressBalanceChangesResponseV0 {
5626                    result: Some(V0Result::Proof(Proof::default())),
5627                    metadata: Some(ResponseMetadata::default()),
5628                },
5629            )),
5630        };
5631        let request = platform::GetRecentCompactedAddressBalanceChangesRequest { version: None };
5632        let provider = unreachable_provider();
5633        let err = <RecentCompactedAddressBalanceChanges as FromProof<
5634            platform::GetRecentCompactedAddressBalanceChangesRequest,
5635        >>::maybe_from_proof(
5636            request,
5637            response,
5638            Network::Testnet,
5639            default_platform_version(),
5640            &provider,
5641        )
5642        .unwrap_err();
5643        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5644    }
5645
5646    #[test]
5647    fn recent_nullifier_changes_empty_version() {
5648        use platform::get_recent_nullifier_changes_response::{
5649            get_recent_nullifier_changes_response_v0::Result as V0Result,
5650            GetRecentNullifierChangesResponseV0, Version,
5651        };
5652        let response = platform::GetRecentNullifierChangesResponse {
5653            version: Some(Version::V0(GetRecentNullifierChangesResponseV0 {
5654                result: Some(V0Result::Proof(Proof::default())),
5655                metadata: Some(ResponseMetadata::default()),
5656            })),
5657        };
5658        let request = platform::GetRecentNullifierChangesRequest { version: None };
5659        let provider = unreachable_provider();
5660        let err = <RecentNullifierChanges as FromProof<
5661            platform::GetRecentNullifierChangesRequest,
5662        >>::maybe_from_proof(
5663            request,
5664            response,
5665            Network::Testnet,
5666            default_platform_version(),
5667            &provider,
5668        )
5669        .unwrap_err();
5670        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5671    }
5672
5673    #[test]
5674    fn recent_compacted_nullifier_changes_empty_version() {
5675        use platform::get_recent_compacted_nullifier_changes_response::{
5676            get_recent_compacted_nullifier_changes_response_v0::Result as V0Result,
5677            GetRecentCompactedNullifierChangesResponseV0, Version,
5678        };
5679        let response = platform::GetRecentCompactedNullifierChangesResponse {
5680            version: Some(Version::V0(GetRecentCompactedNullifierChangesResponseV0 {
5681                result: Some(V0Result::Proof(Proof::default())),
5682                metadata: Some(ResponseMetadata::default()),
5683            })),
5684        };
5685        let request = platform::GetRecentCompactedNullifierChangesRequest { version: None };
5686        let provider = unreachable_provider();
5687        let err = <RecentCompactedNullifierChanges as FromProof<
5688            platform::GetRecentCompactedNullifierChangesRequest,
5689        >>::maybe_from_proof(
5690            request,
5691            response,
5692            Network::Testnet,
5693            default_platform_version(),
5694            &provider,
5695        )
5696        .unwrap_err();
5697        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5698    }
5699
5700    #[test]
5701    fn vote_identity_vote_rejects_bad_identity_id_length() {
5702        // The Vote impl validates id_in_request length before delegating.
5703        use dapi_grpc::platform::v0::get_contested_resource_identity_votes_request::GetContestedResourceIdentityVotesRequestV0;
5704        let request: platform::GetContestedResourceIdentityVotesRequest =
5705            GetContestedResourceIdentityVotesRequestV0 {
5706                identity_id: vec![0u8; 5], // bad
5707                limit: None,
5708                offset: None,
5709                order_ascending: true,
5710                start_at_vote_poll_id_info: None,
5711                prove: true,
5712            }
5713            .into();
5714        let response = platform::GetContestedResourceIdentityVotesResponse::default();
5715        let provider = unreachable_provider();
5716        let err = <Vote as FromProof<
5717            platform::GetContestedResourceIdentityVotesRequest,
5718        >>::maybe_from_proof(
5719            request,
5720            response,
5721            Network::Testnet,
5722            default_platform_version(),
5723            &provider,
5724        )
5725        .unwrap_err();
5726        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5727    }
5728
5729    #[test]
5730    fn vote_identity_vote_empty_version_on_request_none() {
5731        let request = platform::GetContestedResourceIdentityVotesRequest { version: None };
5732        let response = platform::GetContestedResourceIdentityVotesResponse::default();
5733        let provider = unreachable_provider();
5734        let err = <Vote as FromProof<
5735            platform::GetContestedResourceIdentityVotesRequest,
5736        >>::maybe_from_proof(
5737            request,
5738            response,
5739            Network::Testnet,
5740            default_platform_version(),
5741            &provider,
5742        )
5743        .unwrap_err();
5744        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5745    }
5746
5747    #[test]
5748    fn resource_votes_by_identity_empty_version_via_try_from_request() {
5749        // Delegates to ContestedResourceVotesGivenByIdentityQuery::try_from_request,
5750        // which returns Error::EmptyVersion when the request version is missing.
5751        let request = platform::GetContestedResourceIdentityVotesRequest { version: None };
5752        let response = platform::GetContestedResourceIdentityVotesResponse::default();
5753        let provider = unreachable_provider();
5754        let err = <ResourceVotesByIdentity as FromProof<
5755            platform::GetContestedResourceIdentityVotesRequest,
5756        >>::maybe_from_proof(
5757            request,
5758            response,
5759            Network::Testnet,
5760            default_platform_version(),
5761            &provider,
5762        )
5763        .unwrap_err();
5764        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5765    }
5766
5767    // ---------------------------------------------------------------------
5768    // IntoOption for more types
5769    // ---------------------------------------------------------------------
5770
5771    #[test]
5772    fn into_option_indexmap_empty_vs_with_entry_only_none() {
5773        use dpp::prelude::Identifier;
5774        let empty: RetrievedObjects<Identifier, u32> = RetrievedObjects::new();
5775        assert!(empty.into_option().is_none());
5776
5777        let mut map: RetrievedObjects<Identifier, u32> = RetrievedObjects::new();
5778        map.insert(Identifier::new([7u8; 32]), None);
5779        let mapped = map.into_option();
5780        assert!(
5781            mapped.is_some(),
5782            "absence markers must be preserved in into_option"
5783        );
5784        assert_eq!(mapped.unwrap().len(), 1);
5785    }
5786
5787    // ---------------------------------------------------------------------
5788    // Additional: extend existing malformed request tests to all StateTransitions
5789    // ---------------------------------------------------------------------
5790
5791    #[test]
5792    fn broadcast_state_transition_no_proof_when_response_empty() {
5793        let request = platform::BroadcastStateTransitionRequest {
5794            state_transition: vec![],
5795        };
5796        let response = platform::WaitForStateTransitionResultResponse::default();
5797        let provider = unreachable_provider();
5798        let err = <StateTransitionProofResult as FromProof<
5799            platform::BroadcastStateTransitionRequest,
5800        >>::maybe_from_proof(
5801            request,
5802            response,
5803            Network::Testnet,
5804            default_platform_version(),
5805            &provider,
5806        )
5807        .unwrap_err();
5808        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5809    }
5810
5811    #[test]
5812    fn broadcast_state_transition_protocol_error_fires_before_metadata_check() {
5813        // This test pins the ORDERING of validation in
5814        // `StateTransitionProofResult::maybe_from_proof` for broadcast
5815        // state transitions: proof extraction -> state_transition decode ->
5816        // metadata check. An invalid state_transition payload triggers
5817        // `ProtocolError` on decode BEFORE the missing-metadata branch is
5818        // reached, so even though `metadata: None` here, the assertion
5819        // targets `ProtocolError`, not `EmptyResponseMetadata`.
5820        //
5821        // (For the happy-path `EmptyResponseMetadata` branch, a valid
5822        // serialized state transition would be needed; that is covered
5823        // elsewhere. This test deliberately documents the decode-first
5824        // ordering.)
5825        use platform::wait_for_state_transition_result_response::{
5826            wait_for_state_transition_result_response_v0::Result as V0Result, Version,
5827            WaitForStateTransitionResultResponseV0,
5828        };
5829        let request = platform::BroadcastStateTransitionRequest {
5830            state_transition: vec![0xFFu8; 4],
5831        };
5832        let response = platform::WaitForStateTransitionResultResponse {
5833            version: Some(Version::V0(WaitForStateTransitionResultResponseV0 {
5834                result: Some(V0Result::Proof(Proof::default())),
5835                metadata: None, // missing — would trigger EmptyResponseMetadata if reached
5836            })),
5837        };
5838        let provider = unreachable_provider();
5839        let err = <StateTransitionProofResult as FromProof<
5840            platform::BroadcastStateTransitionRequest,
5841        >>::maybe_from_proof(
5842            request,
5843            response,
5844            Network::Testnet,
5845            default_platform_version(),
5846            &provider,
5847        )
5848        .unwrap_err();
5849        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5850    }
5851}