Skip to main content

drive_proof_verifier/
proof.rs

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