Skip to main content

drive_proof_verifier/
proof.rs

1/// Verified chained-document (provable semi-join) result: two grovedb
2/// proofs — the inner indexOnly page and the outer by-ids fetch derived
3/// from its proven values — bound to one quorum-signed root.
4pub mod chained_document;
5/// Verified composite-document result: a page plus the sub-queries
6/// derived from it (joins, lookups, counts, siblings), ONE merged
7/// grovedb proof bound to one quorum-signed root.
8pub mod composite_document;
9pub mod contract_groups;
10pub mod contract_moderation;
11pub mod data_contracts_by_range;
12pub mod data_contracts_latest_versions;
13/// Verified average result. Holds the `(count, sum)` pair recovered
14/// from a `CountSumTree` / PCPS proof; client divides to obtain the
15/// average. Lights up alongside grovedb PR 670's
16/// `AggregateCountAndSumOnRange` primitive.
17pub mod document_average;
18pub mod document_count;
19/// Verified having-range (`GROUP BY … HAVING <aggregate> <op> <value>
20/// LIMIT n`) result. One entry per matching group, in axis order, read
21/// as a value-bounded range of an indexed tree's per-axis secondary
22/// (grovedb PR 657); see the file's docs.
23pub mod document_having;
24/// Verified ranked (`GROUP BY … ORDER BY <aggregate> LIMIT n
25/// [OFFSET m]`) result. One entry per returned group, in ranking order,
26/// plus the attested rank the page starts at, read from an indexed
27/// tree's per-axis secondary (grovedb PR 657); see the file's docs.
28pub mod document_ranked;
29/// Per-entry verified average result. One `(in_key, key, count, sum)`
30/// tuple per matched group; client divides per-entry to obtain
31/// per-group averages.
32pub mod document_split_average;
33pub mod document_split_count;
34/// Per-entry verified sum result (sum-side analog of
35/// `document_split_count`). One `(in_key, key, sum)` triple per
36/// matched group. Lights up alongside grovedb PR 670.
37pub mod document_split_sum;
38/// Verified sum result (sum-side analog of `document_count`).
39/// Single-value aggregate sum recovered from a sum-tree proof.
40/// Lights up alongside grovedb PR 670; see the file's docs.
41pub mod document_sum;
42pub mod groups;
43pub mod identity_keys_remaining_budgets;
44pub mod identity_token_balance;
45pub mod token_contract_info;
46pub mod token_direct_purchase;
47pub mod token_info;
48pub mod token_perpetual_distribution_last_claim;
49pub mod token_pre_programmed_distributions;
50pub mod token_status;
51pub mod token_total_supply;
52
53use crate::from_request::TryFromRequest;
54use crate::verify::{
55    supported_grovedb_proof_bytes, require_supported_grovedb_proof_bytes, verify_tenderdash_proof, verify_tenderdash_signature,
56};
57use crate::{types::*, ContextProvider, DataContractProvider, Error};
58use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start;
59use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
60use dapi_grpc::platform::v0::get_path_elements_request::GetPathElementsRequestV0;
61use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_request::{
62    self, GetProtocolVersionUpgradeVoteStatusRequestV0,
63};
64use dapi_grpc::platform::v0::security_level_map::KeyKindRequestType as GrpcKeyKind;
65use dapi_grpc::platform::v0::{
66    get_address_info_request, get_addresses_infos_request,
67    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,
68    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
69};
70use dapi_grpc::platform::{
71    v0::{self as platform, key_request_type, KeyRequestType as GrpcKeyType},
72    VersionedGrpcResponse,
73};
74use dpp::address_funds::PlatformAddress;
75use dpp::block::block_info::BlockInfo;
76use dpp::block::epoch::EpochIndex;
77use dpp::block::extended_epoch_info::ExtendedEpochInfo;
78use dpp::core_subsidy::NetworkCoreSubsidy;
79use dpp::data_contract::accessors::v0::DataContractV0Getters;
80use dpp::dashcore::hashes::Hash;
81use dpp::dashcore::{Network, ProTxHash};
82use dpp::document::{Document, DocumentV0Getters};
83use dpp::fee::Credits;
84use dpp::identity::identities_contract_keys::IdentitiesContractKeys;
85use dpp::identity::Purpose;
86use dpp::platform_value::{self};
87use dpp::prelude::{AddressNonce, DataContract, Identifier, Identity};
88use dpp::serialization::PlatformDeserializableUntrusted;
89use dpp::state_transition::proof_result::StateTransitionProofOutcome;
90use dpp::state_transition::StateTransition;
91use dpp::version::PlatformVersion;
92use dpp::voting::votes::Vote;
93use drive::drive::identity::identity_and_non_unique_public_key_hash_double_proof::IdentityAndNonUniquePublicKeyHashDoubleProof;
94use drive::drive::identity::key::fetch::{
95    IdentityKeysRequest, KeyKindRequestType, KeyRequestType, PurposeU8, SecurityLevelU8,
96};
97use drive::drive::Drive;
98use drive::error::proof::ProofError;
99use drive::grovedb::Error as GroveError;
100use drive::grovedb::GroveTrunkQueryResult;
101use drive::query::contested_resource_votes_given_by_identity_query::ContestedResourceVotesGivenByIdentityQuery;
102use drive::query::proposer_block_count_query::ProposerQueryType;
103use drive::query::vote_poll_contestant_votes_query::ContestedDocumentVotePollVotesDriveQuery;
104use drive::query::vote_poll_vote_state_query::ContestedDocumentVotePollDriveQuery;
105use drive::query::vote_polls_by_document_type_query::VotePollsByDocumentTypeQuery;
106use drive::query::{DriveDocumentQuery, VotePollsByEndDateDriveQuery};
107use indexmap::IndexMap;
108use std::array::TryFromSliceError;
109use std::collections::BTreeMap;
110use std::num::TryFromIntError;
111use crate::error::MapGroveDbError;
112
113/// Parse and verify the received proof and retrieve the requested object, if any.
114///
115/// Use [`FromProof::maybe_from_proof()`] or [`FromProof::from_proof()`] to parse and verify proofs received
116/// from the Dash Platform (including verification of grovedb-generated proofs and cryptographic proofs generated
117/// by Tenderdash).
118///
119/// gRPC responses, received from the Dash Platform in response to requests containing `prove: true`, contain
120/// GroveDB proof structure (including encapsulated objects) and metadata required to verify cryptographic proof
121/// generated by the Tenderdash. This trait provides methods that parse and verify the proof and retrieve the requested
122/// object (or information that the object does not exist) in one step.
123///
124/// This trait is implemented by several objects defined in [Dash Platform Protocol](dpp), like [Identity],
125/// [DataContract], [Documents], etc. It is also implemented by several helper objects from [types] module.
126pub trait FromProof<Req> {
127    /// Request type for which this trait is implemented.
128    type Request;
129    /// Response type for which this trait is implemented.
130    type Response;
131
132    /// Parse and verify the received proof and retrieve the requested object, if any.
133    ///
134    /// # Arguments
135    ///
136    /// * `request`: The request sent to the server.
137    /// * `response`: The response received from the server.
138    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
139    /// * `platform_version`: The platform version that should be used.
140    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
141    ///
142    /// # Returns
143    ///
144    /// * `Ok(Some(object, metadata))` when the requested object was found in the proof.
145    /// * `Ok(None)` when the requested object was not found in the proof; this can be interpreted as proof of non-existence.
146    ///   For collections, returns Ok(None) if none of the requested objects were found.
147    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
148    fn maybe_from_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
149        request: I,
150        response: O,
151        network: Network,
152        platform_version: &PlatformVersion,
153        provider: &'a dyn ContextProvider,
154    ) -> Result<Option<Self>, Error>
155    where
156        Self: Sized + 'a,
157    {
158        Self::maybe_from_proof_with_metadata(request, response, network, platform_version, provider)
159            .map(|maybe_result| maybe_result.0)
160    }
161
162    /// Parse and verify the received proof and retrieve the requested object, if any.
163    ///
164    /// # Arguments
165    ///
166    /// * `request`: The request sent to the server.
167    /// * `response`: The response received from the server.
168    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
169    /// * `platform_version`: The platform version that should be used.
170    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
171    ///
172    /// # Returns
173    ///
174    /// * `Ok(Some((object, metadata)))` when the requested object was found in the proof.
175    /// * `Ok(None)` when the requested object was not found in the proof; this can be interpreted as proof of non-existence.
176    ///   For collections, returns Ok(None) if none of the requested objects were found.
177    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
178    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
179        request: I,
180        response: O,
181        network: Network,
182        platform_version: &PlatformVersion,
183        provider: &'a dyn ContextProvider,
184    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
185    where
186        Self: Sized + 'a;
187
188    /// Retrieve the requested object from the proof.
189    ///
190    /// Runs full verification of the proof and retrieves enclosed objects.
191    ///
192    /// This method uses [`FromProof::maybe_from_proof()`] internally and throws an error
193    /// if the requested object does not exist in the proof.
194    ///
195    /// # Arguments
196    ///
197    /// * `request`: The request sent to the server.
198    /// * `response`: The response received from the server.
199    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
200    /// * `platform_version`: The platform version that should be used.
201    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
202    ///
203    /// # Returns
204    ///
205    /// * `Ok(object)` when the requested object was found in the proof.
206    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
207    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
208    fn from_proof<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
209        request: I,
210        response: O,
211        network: Network,
212        platform_version: &PlatformVersion,
213        provider: &'a dyn ContextProvider,
214    ) -> Result<Self, Error>
215    where
216        Self: Sized + 'a,
217    {
218        Self::maybe_from_proof(request, response, network, platform_version, provider)?
219            .ok_or(Error::NotFound)
220    }
221
222    /// Retrieve the requested object from the proof with metadata.
223    ///
224    /// Runs full verification of the proof and retrieves enclosed objects.
225    ///
226    /// This method uses [`FromProof::maybe_from_proof_with_metadata()`] internally and throws an error
227    /// if the requested object does not exist in the proof.
228    ///
229    /// # Arguments
230    ///
231    /// * `request`: The request sent to the server.
232    /// * `response`: The response received from the server.
233    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
234    /// * `platform_version`: The platform version that should be used.
235    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
236    ///
237    /// # Returns
238    ///
239    /// * `Ok(Some(object, metadata))` when the requested object was found in the proof.
240    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
241    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
242    fn from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
243        request: I,
244        response: O,
245        network: Network,
246        platform_version: &PlatformVersion,
247        provider: &'a dyn ContextProvider,
248    ) -> Result<(Self, ResponseMetadata), Error>
249    where
250        Self: Sized + 'a,
251    {
252        let (main_item, response_metadata, _) = Self::maybe_from_proof_with_metadata(
253            request,
254            response,
255            network,
256            platform_version,
257            provider,
258        )?;
259        Ok((main_item.ok_or(Error::NotFound)?, response_metadata))
260    }
261
262    /// Retrieve the requested object from the proof with metadata.
263    ///
264    /// Runs full verification of the proof and retrieves enclosed objects.
265    ///
266    /// This method uses [`FromProof::maybe_from_proof_with_metadata()`] internally and throws an error
267    /// if the requested object does not exist in the proof.
268    ///
269    /// # Arguments
270    ///
271    /// * `request`: The request sent to the server.
272    /// * `response`: The response received from the server.
273    /// * `network`: The network we are using, Mainnet/Testnet/Devnet or Regtest
274    /// * `platform_version`: The platform version that should be used.
275    /// * `provider`: A callback implementing [ContextProvider] that provides quorum details required to verify the proof.
276    ///
277    /// # Returns
278    ///
279    /// * `Ok(Some(object, metadata, proof))` when the requested object was found in the proof.
280    /// * `Err(Error::DocumentMissingInProof)` when the requested object was not found in the proof.
281    /// * `Err(Error)` when either the provided data is invalid or proof validation failed.
282    fn from_proof_with_metadata_and_proof<'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<(Self, ResponseMetadata, Proof), Error>
289    where
290        Self: Sized + 'a,
291    {
292        let (main_item, response_metadata, proof) = Self::maybe_from_proof_with_metadata(
293            request,
294            response,
295            network,
296            platform_version,
297            provider,
298        )?;
299        Ok((main_item.ok_or(Error::NotFound)?, response_metadata, proof))
300    }
301}
302
303impl FromProof<platform::GetIdentityRequest> for Identity {
304    type Request = platform::GetIdentityRequest;
305    type Response = platform::GetIdentityResponse;
306
307    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
308        request: I,
309        response: O,
310        _network: Network,
311        platform_version: &PlatformVersion,
312        provider: &'a dyn ContextProvider,
313    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
314    where
315        Identity: Sized + 'a,
316    {
317        let request: platform::GetIdentityRequest = request.into();
318        let response: Self::Response = response.into();
319
320        // Parse response to read proof and metadata
321        let proof = response.proof().or(Err(Error::NoProofInResult))?;
322        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
323
324        let id = match request.version.ok_or(Error::EmptyVersion)? {
325            get_identity_request::Version::V0(v0) => {
326                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
327                    error: e.to_string(),
328                })?
329            }
330        };
331
332        // Extract content from proof and verify Drive/GroveDB proofs
333        let (root_hash, maybe_identity) = Drive::verify_full_identity_by_identity_id(
334            supported_grovedb_proof_bytes(proof)?,
335            false,
336            id.into_buffer(),
337            platform_version,
338        )
339        .map_drive_error(proof, mtd)?;
340
341        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
342
343        Ok((maybe_identity, mtd.clone(), proof.clone()))
344    }
345}
346
347// TODO: figure out how to deal with mock::automock
348impl FromProof<platform::GetIdentityByPublicKeyHashRequest> for Identity {
349    type Request = platform::GetIdentityByPublicKeyHashRequest;
350    type Response = platform::GetIdentityByPublicKeyHashResponse;
351
352    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
353        request: I,
354        response: O,
355        _network: Network,
356        platform_version: &PlatformVersion,
357        provider: &'a dyn ContextProvider,
358    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
359    where
360        Identity: 'a,
361    {
362        let request = request.into();
363        let response = response.into();
364        // Parse response to read proof and metadata
365        let proof = response.proof().or(Err(Error::NoProofInResult))?;
366
367        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
368
369        let public_key_hash = match request.version.ok_or(Error::EmptyVersion)? {
370            get_identity_by_public_key_hash_request::Version::V0(v0) => {
371                let public_key_hash: [u8; 20] =
372                    v0.public_key_hash
373                        .try_into()
374                        .map_err(|_| Error::DriveError {
375                            error: "Invalid public key hash length".to_string(),
376                        })?;
377                public_key_hash
378            }
379        };
380
381        // Extract content from proof and verify Drive/GroveDB proofs
382        let (root_hash, maybe_identity) = Drive::verify_full_identity_by_unique_public_key_hash(
383            supported_grovedb_proof_bytes(proof)?,
384            public_key_hash,
385            platform_version,
386        )
387        .map_drive_error(proof, mtd)?;
388
389        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
390
391        Ok((maybe_identity, mtd.clone(), proof.clone()))
392    }
393}
394
395impl FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest> for Identity {
396    type Request = platform::GetIdentityByNonUniquePublicKeyHashRequest;
397    type Response = platform::GetIdentityByNonUniquePublicKeyHashResponse;
398    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
399        request: I,
400        response: O,
401        _network: Network,
402        platform_version: &PlatformVersion,
403        provider: &'a dyn ContextProvider,
404    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
405    where
406        Self: Sized + 'a,
407    {
408        let request = request.into();
409        let response = response.into();
410        // Parse response to read proof and metadata
411        // note that proof in this case is different
412        // let proof = response.proof().or(Err(Error::NoProofInResult))?;
413        use platform::get_identity_by_non_unique_public_key_hash_response::{
414            get_identity_by_non_unique_public_key_hash_response_v0::Result as V0Result, Version::V0,
415        };
416
417        let (proved_response, mtd) = match response.version {
418            Some(V0(v0)) => {
419                let proof = if let V0Result::Proof(p) = v0.result.ok_or(Error::NoProofInResult)? {
420                    p
421                } else {
422                    return Err(Error::NoProofInResult);
423                };
424
425                (proof, v0.metadata.ok_or(Error::EmptyResponseMetadata)?)
426            }
427            _ => return Err(Error::EmptyResponseMetadata),
428        };
429
430        // let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
431
432        let (public_key_hash, after_identity) = match request.version.ok_or(Error::EmptyVersion)? {
433            get_identity_by_non_unique_public_key_hash_request::Version::V0(v0) => {
434                let public_key_hash =
435                    v0.public_key_hash
436                        .try_into()
437                        .map_err(|_| Error::RequestError {
438                            error: "Invalid public key hash length".to_string(),
439                        })?;
440
441                let after = v0
442                    .start_after
443                    .map(|a| {
444                        a.try_into().map_err(|_| Error::RequestError {
445                            error: "Invalid start_after length".to_string(),
446                        })
447                    })
448                    .transpose()?;
449                (public_key_hash, after)
450            }
451        };
452
453        // we need to convert some data to handle non-default proof structure for this response
454        let proof = proved_response
455            .grovedb_identity_public_key_hash_proof
456            .ok_or(Error::NoProofInResult)?;
457        require_supported_grovedb_proof_bytes(&proof.grovedb_proof)?;
458
459        if let Some(identity_proof) = proved_response.identity_proof_bytes.as_deref() {
460            require_supported_grovedb_proof_bytes(identity_proof)?;
461        }
462
463        let proof_tuple = IdentityAndNonUniquePublicKeyHashDoubleProof {
464            identity_proof: proved_response.identity_proof_bytes,
465            identity_id_public_key_hash_proof: proof.grovedb_proof.clone(),
466        };
467
468        // Extract content from proof and verify Drive/GroveDB proofs
469        let (root_hash, maybe_identity) =
470            Drive::verify_full_identity_by_non_unique_public_key_hash(
471                &proof_tuple,
472                public_key_hash,
473                after_identity,
474                platform_version,
475            )
476            .map_err(|e| match e {
477                drive::error::Error::GroveDB(e) => {
478                    // If InvalidProof error is returned, extract the path query from it
479                    let maybe_query = match e.as_ref() {
480                        GroveError::InvalidProof(path_query, ..) => Some(path_query.clone()),
481                        _ => None,
482                    };
483
484                    Error::GroveDBError {
485                        proof_bytes: proof.grovedb_proof.clone(),
486                        path_query: maybe_query,
487                        height: mtd.height,
488                        time_ms: mtd.time_ms,
489                        error: e.to_string(),
490                    }
491                }
492                _ => e.into(),
493            })?;
494
495        verify_tenderdash_proof(&proof, &mtd, &root_hash, provider)?;
496
497        Ok((maybe_identity, mtd.clone(), proof))
498    }
499}
500
501impl FromProof<platform::GetIdentityKeysRequest> for IdentityPublicKeys {
502    type Request = platform::GetIdentityKeysRequest;
503    type Response = platform::GetIdentityKeysResponse;
504
505    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
506        request: I,
507        response: O,
508        _network: Network,
509        platform_version: &PlatformVersion,
510        provider: &'a dyn ContextProvider,
511    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
512    where
513        IdentityPublicKeys: 'a,
514    {
515        let request: Self::Request = request.into();
516        let response: Self::Response = response.into();
517
518        // Parse response to read proof and metadata
519        let proof = response.proof().or(Err(Error::NoProofInResult))?;
520
521        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
522
523        let (request_type, identity_id, limit, offset) =
524            match request.version.ok_or(Error::EmptyVersion)? {
525                get_identity_keys_request::Version::V0(v0) => {
526                    let request_type = v0.request_type;
527                    let identity_id = Identifier::from_bytes(&v0.identity_id)
528                        .map_err(|e| Error::ProtocolError {
529                            error: e.to_string(),
530                        })?
531                        .into_buffer();
532                    let limit = v0.limit.map(try_u32_to_u16).transpose()?;
533                    let offset = v0.offset.map(try_u32_to_u16).transpose()?;
534                    (request_type, identity_id, limit, offset)
535                }
536            };
537
538        let request_type = parse_key_request_type(&request_type)?;
539
540        let key_request = IdentityKeysRequest {
541            identity_id,
542            request_type,
543            limit,
544            offset,
545        };
546
547        tracing::debug!(?identity_id, "checking proof of identity keys");
548
549        // Extract content from proof and verify Drive/GroveDB proofs
550        let (root_hash, maybe_identity) = Drive::verify_identity_keys_by_identity_id(
551            supported_grovedb_proof_bytes(proof)?,
552            key_request,
553            false,
554            false,
555            false,
556            platform_version,
557        )
558        .map_drive_error(proof, mtd)?;
559
560        let maybe_keys: Option<IdentityPublicKeys> = if let Some(identity) = maybe_identity {
561            if identity.loaded_public_keys.is_empty() {
562                None
563            } else {
564                let mut keys = identity
565                    .loaded_public_keys
566                    .into_iter()
567                    .map(|(k, v)| (k, Some(v.clone())))
568                    .collect::<IdentityPublicKeys>();
569
570                let mut not_found = identity
571                    .not_found_public_keys
572                    .into_iter()
573                    .map(|k| (k, None))
574                    .collect::<IdentityPublicKeys>();
575
576                keys.append(&mut not_found);
577
578                Some(keys)
579            }
580        } else {
581            None
582        };
583
584        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
585
586        Ok((maybe_keys, mtd.clone(), proof.clone()))
587    }
588}
589
590fn parse_key_request_type(request: &Option<GrpcKeyType>) -> Result<KeyRequestType, Error> {
591    let key_request_type = request
592        .to_owned()
593        .ok_or(Error::RequestError {
594            error: "missing key request type".to_string(),
595        })?
596        .request
597        .ok_or(Error::RequestError {
598            error: "empty request field in key request type".to_string(),
599        })?;
600
601    let request_type = match key_request_type {
602        key_request_type::Request::AllKeys(_) => KeyRequestType::AllKeys,
603        key_request_type::Request::SpecificKeys(specific_keys) => {
604            KeyRequestType::SpecificKeys(specific_keys.key_ids)
605        }
606        key_request_type::Request::SearchKey(search_key) => {
607            let purpose = search_key
608                .purpose_map
609                .iter()
610                .map(|(k, v)| {
611                     let v = v.security_level_map
612                            .iter()
613                            .map(|(level, &kind)| {
614                                let kt = match GrpcKeyKind::try_from(kind) {
615                                    Ok(GrpcKeyKind::CurrentKeyOfKindRequest) => {
616                                        Ok(KeyKindRequestType::CurrentKeyOfKindRequest)
617                                    }
618                                    Ok(GrpcKeyKind::AllKeysOfKindRequest) => {
619                                        Ok(KeyKindRequestType::AllKeysOfKindRequest)
620                                    }
621                                    _ => Err(Error::RequestError {
622                                        error: format!("missing requested key type: {}", kind),
623                                    }),
624                                };
625                                match kt  {
626                                    Err(e) => Err(e),
627                                    Ok(d) => Ok((*level as u8, d))
628                                }
629                            })
630                            .collect::<Result<BTreeMap<SecurityLevelU8,KeyKindRequestType>,Error>>();
631
632                            match v {
633                                Err(e) =>Err(e),
634                                Ok(d) => Ok((*k as u8,d)),
635                            }
636                })
637                .collect::<Result<BTreeMap<PurposeU8, BTreeMap<SecurityLevelU8, KeyKindRequestType>>,Error>>()?;
638
639            KeyRequestType::SearchKey(purpose)
640        }
641    };
642
643    Ok(request_type)
644}
645
646impl FromProof<platform::GetIdentityNonceRequest> for IdentityNonceFetcher {
647    type Request = platform::GetIdentityNonceRequest;
648    type Response = platform::GetIdentityNonceResponse;
649
650    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
651        request: I,
652        response: O,
653        _network: Network,
654        platform_version: &PlatformVersion,
655        provider: &'a dyn ContextProvider,
656    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
657    where
658        IdentityNonceFetcher: 'a,
659    {
660        let request: Self::Request = request.into();
661        let response: Self::Response = response.into();
662
663        // Parse response to read proof and metadata
664        let proof = response.proof().or(Err(Error::NoProofInResult))?;
665
666        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
667
668        let identity_id =
669            match request.version.ok_or(Error::EmptyVersion)? {
670                get_identity_nonce_request::Version::V0(v0) => Ok::<Identifier, Error>(
671                    Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::ProtocolError {
672                        error: e.to_string(),
673                    })?,
674                ),
675            }?;
676
677        // Extract content from proof and verify Drive/GroveDB proofs
678        let (root_hash, maybe_nonce) = Drive::verify_identity_nonce(
679            supported_grovedb_proof_bytes(proof)?,
680            identity_id.into_buffer(),
681            false,
682            platform_version,
683        )
684        .map_drive_error(proof, mtd)?;
685
686        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
687
688        Ok((
689            maybe_nonce.map(IdentityNonceFetcher),
690            mtd.clone(),
691            proof.clone(),
692        ))
693    }
694}
695
696impl FromProof<platform::GetIdentityContractNonceRequest> for IdentityContractNonceFetcher {
697    type Request = platform::GetIdentityContractNonceRequest;
698    type Response = platform::GetIdentityContractNonceResponse;
699
700    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
701        request: I,
702        response: O,
703        _network: Network,
704        platform_version: &PlatformVersion,
705        provider: &'a dyn ContextProvider,
706    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
707    where
708        IdentityContractNonceFetcher: 'a,
709    {
710        let request: Self::Request = request.into();
711        let response: Self::Response = response.into();
712
713        // Parse response to read proof and metadata
714        let proof = response.proof().or(Err(Error::NoProofInResult))?;
715
716        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
717
718        let (identity_id, contract_id) = match request.version.ok_or(Error::EmptyVersion)? {
719            get_identity_contract_nonce_request::Version::V0(v0) => {
720                Ok::<(Identifier, Identifier), Error>((
721                    Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::ProtocolError {
722                        error: e.to_string(),
723                    })?,
724                    Identifier::from_bytes(&v0.contract_id).map_err(|e| Error::ProtocolError {
725                        error: e.to_string(),
726                    })?,
727                ))
728            }
729        }?;
730
731        // Extract content from proof and verify Drive/GroveDB proofs
732        let (root_hash, maybe_identity) = Drive::verify_identity_contract_nonce(
733            supported_grovedb_proof_bytes(proof)?,
734            identity_id.into_buffer(),
735            contract_id.into_buffer(),
736            false,
737            platform_version,
738        )
739        .map_drive_error(proof, mtd)?;
740
741        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
742
743        Ok((
744            maybe_identity.map(IdentityContractNonceFetcher),
745            mtd.clone(),
746            proof.clone(),
747        ))
748    }
749}
750
751impl FromProof<platform::GetIdentityBalanceRequest> for IdentityBalance {
752    type Request = platform::GetIdentityBalanceRequest;
753    type Response = platform::GetIdentityBalanceResponse;
754
755    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
756        request: I,
757        response: O,
758        _network: Network,
759        platform_version: &PlatformVersion,
760        provider: &'a dyn ContextProvider,
761    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
762    where
763        IdentityBalance: 'a,
764    {
765        let request: Self::Request = request.into();
766        let response: Self::Response = response.into();
767
768        // Parse response to read proof and metadata
769        let proof = response.proof().or(Err(Error::NoProofInResult))?;
770
771        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
772
773        let id = match request.version.ok_or(Error::EmptyVersion)? {
774            get_identity_balance_request::Version::V0(v0) => Identifier::from_bytes(&v0.id)
775                .map_err(|e| Error::ProtocolError {
776                    error: e.to_string(),
777                }),
778        }?;
779
780        // Extract content from proof and verify Drive/GroveDB proofs
781        let (root_hash, maybe_identity) = Drive::verify_identity_balance_for_identity_id(
782            supported_grovedb_proof_bytes(proof)?,
783            id.into_buffer(),
784            false,
785            platform_version,
786        )
787        .map_drive_error(proof, mtd)?;
788
789        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
790
791        Ok((maybe_identity, mtd.clone(), proof.clone()))
792    }
793}
794
795impl FromProof<platform::GetIdentitiesBalancesRequest> for IdentityBalances {
796    type Request = platform::GetIdentitiesBalancesRequest;
797    type Response = platform::GetIdentitiesBalancesResponse;
798
799    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
800        request: I,
801        response: O,
802        _network: Network,
803        platform_version: &PlatformVersion,
804        provider: &'a dyn ContextProvider,
805    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
806    where
807        IdentityBalances: 'a,
808    {
809        let request: Self::Request = request.into();
810        let response: Self::Response = response.into();
811        // Parse response to read proof and metadata
812        let proof = response.proof().or(Err(Error::NoProofInResult))?;
813
814        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
815
816        let identities_ids = match request.version.ok_or(Error::EmptyVersion)? {
817            get_identities_balances_request::Version::V0(v0) => v0.ids,
818        };
819
820        let identity_ids = identities_ids
821            .into_iter()
822            .map(|identity_bytes| {
823                Identifier::from_bytes(&identity_bytes)
824                    .map(|identifier| identifier.into_buffer())
825                    .map_err(|e| Error::RequestError {
826                        error: format!("identities must be all 32 bytes {}", e),
827                    })
828            })
829            .collect::<Result<Vec<[u8; 32]>, Error>>()?;
830        let (root_hash, balances) = Drive::verify_identity_balances_for_identity_ids(
831            supported_grovedb_proof_bytes(proof)?,
832            false,
833            &identity_ids,
834            platform_version,
835        )
836        .map_drive_error(proof, mtd)?;
837
838        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
839
840        Ok((Some(balances), mtd.clone(), proof.clone()))
841    }
842}
843
844impl FromProof<platform::GetIdentityBalanceAndRevisionRequest> for IdentityBalanceAndRevision {
845    type Request = platform::GetIdentityBalanceAndRevisionRequest;
846    type Response = platform::GetIdentityBalanceAndRevisionResponse;
847
848    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
849        request: I,
850        response: O,
851        _network: Network,
852        platform_version: &PlatformVersion,
853        provider: &'a dyn ContextProvider,
854    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
855    where
856        IdentityBalanceAndRevision: 'a,
857    {
858        let request: Self::Request = request.into();
859        let response: Self::Response = response.into();
860
861        // Parse response to read proof and metadata
862        let proof = response.proof().or(Err(Error::NoProofInResult))?;
863
864        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
865
866        let id = match request.version.ok_or(Error::EmptyVersion)? {
867            get_identity_balance_and_revision_request::Version::V0(v0) => {
868                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
869                    error: e.to_string(),
870                })
871            }
872        }?;
873
874        // Extract content from proof and verify Drive/GroveDB proofs
875        let (root_hash, maybe_identity) =
876            Drive::verify_identity_balance_and_revision_for_identity_id(
877                supported_grovedb_proof_bytes(proof)?,
878                id.into_buffer(),
879                false,
880                platform_version,
881            )
882            .map_drive_error(proof, mtd)?;
883
884        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
885
886        Ok((maybe_identity, mtd.clone(), proof.clone()))
887    }
888}
889
890impl FromProof<platform::GetAddressInfoRequest> for AddressInfo {
891    type Request = platform::GetAddressInfoRequest;
892    type Response = platform::GetAddressInfoResponse;
893
894    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
895        request: I,
896        response: O,
897        _network: Network,
898        platform_version: &PlatformVersion,
899        provider: &'a dyn ContextProvider,
900    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
901    where
902        AddressInfo: 'a,
903    {
904        let request: Self::Request = request.into();
905        let response: Self::Response = response.into();
906
907        let proof = response.proof().or(Err(Error::NoProofInResult))?;
908        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
909
910        let address = match request.version.ok_or(Error::EmptyVersion)? {
911            get_address_info_request::Version::V0(v0) => PlatformAddress::from_bytes(&v0.address)
912                .map_err(|e| Error::RequestError {
913                error: format!("invalid address: {}", e),
914            })?,
915        };
916
917        let (root_hash, maybe_info) = Drive::verify_address_info(
918            supported_grovedb_proof_bytes(proof)?,
919            &address,
920            false,
921            platform_version,
922        )
923        .map_drive_error(proof, mtd)?;
924
925        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
926
927        let info = maybe_info.map(|(nonce, balance)| AddressInfo {
928            address,
929            nonce,
930            balance,
931        });
932
933        Ok((info, mtd.clone(), proof.clone()))
934    }
935}
936
937impl FromProof<platform::GetAddressesInfosRequest> for AddressInfos {
938    type Request = platform::GetAddressesInfosRequest;
939    type Response = platform::GetAddressesInfosResponse;
940
941    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
942        request: I,
943        response: O,
944        _network: Network,
945        platform_version: &PlatformVersion,
946        provider: &'a dyn ContextProvider,
947    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
948    where
949        AddressInfos: 'a,
950    {
951        let request: Self::Request = request.into();
952        let response: Self::Response = response.into();
953
954        let proof = response.proof().or(Err(Error::NoProofInResult))?;
955        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
956
957        let addresses_bytes = match request.version.ok_or(Error::EmptyVersion)? {
958            get_addresses_infos_request::Version::V0(v0) => v0.addresses,
959        };
960
961        let addresses: Vec<PlatformAddress> = addresses_bytes
962            .into_iter()
963            .map(|bytes| {
964                PlatformAddress::from_bytes(&bytes).map_err(|e| Error::RequestError {
965                    error: format!("invalid address: {}", e),
966                })
967            })
968            .collect::<Result<_, _>>()?;
969
970        let (root_hash, entries) = Drive::verify_addresses_infos::<
971            _,
972            Vec<(PlatformAddress, Option<(AddressNonce, Credits)>)>,
973        >(
974            supported_grovedb_proof_bytes(proof)?,
975            addresses.iter(),
976            false,
977            platform_version,
978        )
979        .map_drive_error(proof, mtd)?;
980
981        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
982
983        let infos = entries
984            .into_iter()
985            .map(|(address, maybe_info)| {
986                let info = maybe_info.map(|(nonce, balance)| AddressInfo {
987                    address,
988                    nonce,
989                    balance,
990                });
991                (address, info)
992            })
993            .collect::<AddressInfos>();
994
995        Ok((Some(infos), mtd.clone(), proof.clone()))
996    }
997}
998
999impl FromProof<platform::GetRecentAddressBalanceChangesRequest> for RecentAddressBalanceChanges {
1000    type Request = platform::GetRecentAddressBalanceChangesRequest;
1001    type Response = platform::GetRecentAddressBalanceChangesResponse;
1002
1003    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1004        request: I,
1005        response: O,
1006        _network: Network,
1007        platform_version: &PlatformVersion,
1008        provider: &'a dyn ContextProvider,
1009    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1010    where
1011        RecentAddressBalanceChanges: 'a,
1012    {
1013        use dapi_grpc::platform::v0::get_recent_address_balance_changes_request;
1014
1015        let request: Self::Request = request.into();
1016        let response: Self::Response = response.into();
1017
1018        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1019        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1020
1021        let (start_height, start_height_exclusive) =
1022            match request.version.ok_or(Error::EmptyVersion)? {
1023                get_recent_address_balance_changes_request::Version::V0(v0) => {
1024                    (v0.start_height, v0.start_height_exclusive)
1025                }
1026            };
1027
1028        let limit = Some(100u16); // Same limit as in query handler
1029
1030        let (root_hash, verified_changes) = if start_height_exclusive {
1031            Drive::verify_recent_address_balance_changes_after(
1032                supported_grovedb_proof_bytes(proof)?,
1033                start_height,
1034                limit,
1035                false,
1036                platform_version,
1037            )
1038            .map_drive_error(proof, mtd)?
1039        } else {
1040            Drive::verify_recent_address_balance_changes(
1041                supported_grovedb_proof_bytes(proof)?,
1042                start_height,
1043                limit,
1044                false,
1045                platform_version,
1046            )
1047            .map_drive_error(proof, mtd)?
1048        };
1049
1050        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1051
1052        let result = RecentAddressBalanceChanges(
1053            verified_changes
1054                .into_iter()
1055                .map(|(block_height, changes)| BlockAddressBalanceChanges {
1056                    block_height,
1057                    changes,
1058                })
1059                .collect(),
1060        );
1061
1062        Ok((Some(result), mtd.clone(), proof.clone()))
1063    }
1064}
1065
1066impl FromProof<platform::GetRecentCompactedAddressBalanceChangesRequest>
1067    for RecentCompactedAddressBalanceChanges
1068{
1069    type Request = platform::GetRecentCompactedAddressBalanceChangesRequest;
1070    type Response = platform::GetRecentCompactedAddressBalanceChangesResponse;
1071
1072    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1073        request: I,
1074        response: O,
1075        _network: Network,
1076        platform_version: &PlatformVersion,
1077        provider: &'a dyn ContextProvider,
1078    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1079    where
1080        RecentCompactedAddressBalanceChanges: 'a,
1081    {
1082        use dapi_grpc::platform::v0::get_recent_compacted_address_balance_changes_request;
1083
1084        let request: Self::Request = request.into();
1085        let response: Self::Response = response.into();
1086
1087        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1088        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1089
1090        let start_block_height = match request.version.ok_or(Error::EmptyVersion)? {
1091            get_recent_compacted_address_balance_changes_request::Version::V0(v0) => {
1092                v0.start_block_height
1093            }
1094        };
1095
1096        // Ensure it is the same limit as in query handler; see
1097        // packages/rs-drive-abci/src/query/address_funds/recent_compacted_address_balance_changes/v0/mod.rs
1098        let limit = Some(25u16);
1099
1100        if platform_version
1101            .drive
1102            .methods
1103            .verify
1104            .address_funds
1105            .verify_compacted_address_balance_changes
1106            == 0
1107        {
1108            require_supported_grovedb_proof_bytes(&proof.grovedb_proof)?;
1109        }
1110
1111        let (root_hash, verified_changes) = Drive::verify_compacted_address_balance_changes(
1112            &proof.grovedb_proof,
1113            start_block_height,
1114            limit,
1115            platform_version,
1116        )
1117        .map_drive_error(proof, mtd)?;
1118
1119        verify_tenderdash_signature(proof, mtd, &root_hash, provider)?;
1120
1121        let result = RecentCompactedAddressBalanceChanges(
1122            verified_changes
1123                .into_iter()
1124                .map(|(start_block_height, end_block_height, changes)| {
1125                    CompactedBlockAddressBalanceChanges {
1126                        start_block_height,
1127                        end_block_height,
1128                        changes,
1129                    }
1130                })
1131                .collect(),
1132        );
1133
1134        Ok((Some(result), mtd.clone(), proof.clone()))
1135    }
1136}
1137
1138impl FromProof<platform::GetAddressesTrunkStateRequest> for GroveTrunkQueryResult {
1139    type Request = platform::GetAddressesTrunkStateRequest;
1140    type Response = platform::GetAddressesTrunkStateResponse;
1141
1142    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1143        _request: I,
1144        response: O,
1145        _network: Network,
1146        platform_version: &PlatformVersion,
1147        provider: &'a dyn ContextProvider,
1148    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1149    where
1150        GroveTrunkQueryResult: 'a,
1151    {
1152        let response: Self::Response = response.into();
1153
1154        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1155        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1156
1157        let (root_hash, trunk_result) = Drive::verify_address_funds_trunk_query(
1158            supported_grovedb_proof_bytes(proof)?,
1159            platform_version,
1160        )
1161        .map_drive_error(proof, mtd)?;
1162
1163        verify_tenderdash_signature(proof, mtd, &root_hash, provider)?;
1164
1165        Ok((Some(trunk_result), mtd.clone(), proof.clone()))
1166    }
1167}
1168
1169impl FromProof<platform::GetAddressesTrunkStateRequest> for PlatformAddressTrunkState {
1170    type Request = platform::GetAddressesTrunkStateRequest;
1171    type Response = platform::GetAddressesTrunkStateResponse;
1172
1173    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1174        request: I,
1175        response: O,
1176        network: Network,
1177        platform_version: &PlatformVersion,
1178        provider: &'a dyn ContextProvider,
1179    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1180    where
1181        PlatformAddressTrunkState: 'a,
1182    {
1183        let (result, metadata, proof) = <GroveTrunkQueryResult as FromProof<
1184            platform::GetAddressesTrunkStateRequest,
1185        >>::maybe_from_proof_with_metadata(
1186            request, response, network, platform_version, provider
1187        )?;
1188
1189        Ok((result.map(PlatformAddressTrunkState), metadata, proof))
1190    }
1191}
1192
1193impl FromProof<platform::GetDataContractRequest> for DataContract {
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(
1225            supported_grovedb_proof_bytes(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::GetDataContractRequest> for (DataContract, Vec<u8>) {
1241    type Request = platform::GetDataContractRequest;
1242    type Response = platform::GetDataContractResponse;
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        DataContract: '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 id = match request.version.ok_or(Error::EmptyVersion)? {
1263            get_data_contract_request::Version::V0(v0) => {
1264                Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1265                    error: e.to_string(),
1266                })
1267            }
1268        }?;
1269
1270        // Extract content from proof and verify Drive/GroveDB proofs
1271        let (root_hash, maybe_contract) = Drive::verify_contract_return_serialization(
1272            supported_grovedb_proof_bytes(proof)?,
1273            None,
1274            false,
1275            false,
1276            id.into_buffer(),
1277            platform_version,
1278        )
1279        .map_drive_error(proof, mtd)?;
1280
1281        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1282
1283        Ok((maybe_contract, mtd.clone(), proof.clone()))
1284    }
1285}
1286
1287impl FromProof<platform::GetDataContractsRequest> for DataContracts {
1288    type Request = platform::GetDataContractsRequest;
1289    type Response = platform::GetDataContractsResponse;
1290
1291    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1292        request: I,
1293        response: O,
1294        _network: Network,
1295        platform_version: &PlatformVersion,
1296        provider: &'a dyn ContextProvider,
1297    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1298    where
1299        DataContracts: 'a,
1300    {
1301        let request: Self::Request = request.into();
1302        let response: Self::Response = response.into();
1303
1304        // Parse response to read proof and metadata
1305        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1306
1307        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1308
1309        let ids = match request.version.ok_or(Error::EmptyVersion)? {
1310            get_data_contracts_request::Version::V0(v0) => v0.ids,
1311        };
1312
1313        let ids = ids
1314            .iter()
1315            .map(|id| {
1316                id.clone().try_into().map_err(|_e| Error::RequestError {
1317                    error: format!("wrong id size: expected: {}, got: {}", 32, id.len()),
1318                })
1319            })
1320            .collect::<Result<Vec<[u8; 32]>, Error>>()?;
1321
1322        // Extract content from proof and verify Drive/GroveDB proofs
1323        let (root_hash, contracts) = Drive::verify_contracts(
1324            supported_grovedb_proof_bytes(proof)?,
1325            false,
1326            ids.as_slice(),
1327            platform_version,
1328        )
1329        .map_drive_error(proof, mtd)?;
1330
1331        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1332        let contracts = contracts
1333            .into_iter()
1334            .map(|(k, v)| {
1335                Identifier::from_bytes(&k).map(|id| (id, v)).map_err(|e| {
1336                    Error::ResultEncodingError {
1337                        error: e.to_string(),
1338                    }
1339                })
1340            })
1341            .collect::<Result<DataContracts, Error>>()?;
1342
1343        let maybe_contracts = if contracts.is_empty() {
1344            None
1345        } else {
1346            Some(contracts)
1347        };
1348
1349        Ok((maybe_contracts, mtd.clone(), proof.clone()))
1350    }
1351}
1352
1353impl FromProof<platform::GetDataContractHistoryRequest> for DataContractHistory {
1354    type Request = platform::GetDataContractHistoryRequest;
1355    type Response = platform::GetDataContractHistoryResponse;
1356
1357    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1358        request: I,
1359        response: O,
1360        _network: Network,
1361        platform_version: &PlatformVersion,
1362        provider: &'a dyn ContextProvider,
1363    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1364    where
1365        Self: Sized + 'a,
1366    {
1367        let request: Self::Request = request.into();
1368        let response: Self::Response = response.into();
1369
1370        // Parse response to read proof and metadata
1371        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1372
1373        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1374
1375        let (id, limit, offset, start_at_ms) = match request.version.ok_or(Error::EmptyVersion)? {
1376            get_data_contract_history_request::Version::V0(v0) => {
1377                let id = Identifier::from_bytes(&v0.id).map_err(|e| Error::ProtocolError {
1378                    error: e.to_string(),
1379                })?;
1380                let limit = u32_to_u16_opt(v0.limit.unwrap_or_default())?;
1381                let offset = u32_to_u16_opt(v0.offset.unwrap_or_default())?;
1382                let start_at_ms = v0.start_at_ms;
1383                (id, limit, offset, start_at_ms)
1384            }
1385        };
1386
1387        // Extract content from proof and verify Drive/GroveDB proofs
1388        let (root_hash, maybe_history) = Drive::verify_contract_history(
1389            supported_grovedb_proof_bytes(proof)?,
1390            id.into_buffer(),
1391            start_at_ms,
1392            limit,
1393            offset,
1394            platform_version,
1395        )
1396        .map_drive_error(proof, mtd)?;
1397
1398        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1399
1400        Ok((
1401            maybe_history.map(IndexMap::from_iter),
1402            mtd.clone(),
1403            proof.clone(),
1404        ))
1405    }
1406}
1407
1408impl FromProof<platform::GetDocumentHistoryRequest> for DocumentHistory {
1409    type Request = platform::GetDocumentHistoryRequest;
1410    type Response = platform::GetDocumentHistoryResponse;
1411
1412    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1413        request: I,
1414        response: O,
1415        _network: Network,
1416        platform_version: &PlatformVersion,
1417        provider: &'a dyn ContextProvider,
1418    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1419    where
1420        Self: Sized + 'a,
1421    {
1422        let request: Self::Request = request.into();
1423        let response: Self::Response = response.into();
1424
1425        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1426        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1427
1428        let (contract_id, document_type_name, document_id, limit, offset, start_at_ms) =
1429            match request.version.ok_or(Error::EmptyVersion)? {
1430                get_document_history_request::Version::V0(v0) => {
1431                    let contract_id =
1432                        Identifier::from_bytes(&v0.data_contract_id).map_err(|e| {
1433                            Error::ProtocolError {
1434                                error: e.to_string(),
1435                            }
1436                        })?;
1437                    let document_id = Identifier::from_bytes(&v0.document_id).map_err(|e| {
1438                        Error::ProtocolError {
1439                            error: e.to_string(),
1440                        }
1441                    })?;
1442                    let limit = u32_to_u16_opt(v0.limit.unwrap_or_default())?;
1443                    let offset = u32_to_u16_opt(v0.offset.unwrap_or_default())?;
1444                    (
1445                        contract_id,
1446                        v0.document_type_name,
1447                        document_id,
1448                        limit,
1449                        offset,
1450                        v0.start_at_ms,
1451                    )
1452                }
1453            };
1454
1455        let data_contract = provider
1456            .get_data_contract(&contract_id, platform_version)?
1457            .ok_or(Error::NotFound)?;
1458        let document_type = data_contract
1459            .document_type_for_name(&document_type_name)
1460            .map_err(|e| Error::ProtocolError {
1461                error: e.to_string(),
1462            })?;
1463
1464        let (root_hash, maybe_history) = Drive::verify_document_history(
1465            supported_grovedb_proof_bytes(proof)?,
1466            contract_id.into_buffer(),
1467            &document_type_name,
1468            document_type,
1469            document_id.into_buffer(),
1470            start_at_ms,
1471            limit,
1472            offset,
1473            platform_version,
1474        )
1475        .map_drive_error(proof, mtd)?;
1476
1477        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1478
1479        // Preserve the distinction between a verified-but-empty history page
1480        // (e.g. an offset/start_at_ms past the last revision) and an absent
1481        // result: DocumentHistory carries retrieved values, not proof-of-absence,
1482        // so a proven empty page is a legitimate `Some(empty)` rather than `None`.
1483        Ok((
1484            maybe_history.map(IndexMap::from_iter),
1485            mtd.clone(),
1486            proof.clone(),
1487        ))
1488    }
1489}
1490
1491impl FromProof<platform::BroadcastStateTransitionRequest> for StateTransitionProofOutcome {
1492    type Request = platform::BroadcastStateTransitionRequest;
1493    type Response = platform::WaitForStateTransitionResultResponse;
1494
1495    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1496        request: I,
1497        response: O,
1498        _network: Network,
1499        platform_version: &PlatformVersion,
1500        provider: &'a dyn ContextProvider,
1501    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1502    where
1503        Self: Sized + 'a,
1504    {
1505        let request: Self::Request = request.into();
1506        let response: Self::Response = response.into();
1507
1508        // Parse response to read proof and metadata
1509        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1510
1511        let state_transition = StateTransition::deserialize_from_bytes_untrusted(
1512            &request.state_transition,
1513        )
1514        .map_err(|e| Error::ProtocolError {
1515            error: e.to_string(),
1516        })?;
1517
1518        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1519
1520        let block_info = BlockInfo {
1521            time_ms: mtd.time_ms,
1522            height: mtd.height,
1523            core_height: mtd.core_chain_locked_height,
1524            // Response metadata is not part of the authenticated state ID.
1525            // Current proof consumers do not require an epoch, so do not
1526            // propagate an unsigned selector into the verification context.
1527            epoch: Default::default(),
1528        };
1529
1530        let contracts_provider_fn = provider.as_contract_lookup_fn(platform_version);
1531
1532        // Transition families whose proof cannot be bound to execution
1533        // (balance top-ups, credit transfers/withdrawals, address funds
1534        // movements, shields, no-history token operations) yield a verified
1535        // snapshot of the affected keys at the proof's block, tagged
1536        // `AffectedState` on the `StateTransitionProofOutcome`. The tag is
1537        // preserved here so SDK callers can enforce their required
1538        // guarantee: strict waits reject snapshots, snapshot waits treat
1539        // them as height-pinned state, never as execution evidence.
1540        let (root_hash, outcome) = Drive::verify_state_transition_was_executed_with_proof(
1541            &state_transition,
1542            &block_info,
1543            supported_grovedb_proof_bytes(proof)?,
1544            &contracts_provider_fn,
1545            platform_version,
1546        )
1547        .map_drive_error(proof, mtd)?;
1548
1549        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1550
1551        Ok((Some(outcome), mtd.clone(), proof.clone()))
1552    }
1553}
1554
1555impl FromProof<platform::GetEpochsInfoRequest> for ExtendedEpochInfo {
1556    type Request = platform::GetEpochsInfoRequest;
1557    type Response = platform::GetEpochsInfoResponse;
1558
1559    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1560        request: I,
1561        response: O,
1562        network: Network,
1563        platform_version: &PlatformVersion,
1564        provider: &'a dyn ContextProvider,
1565    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1566    where
1567        Self: Sized + 'a,
1568    {
1569        let epochs = ExtendedEpochInfos::maybe_from_proof_with_metadata(
1570            request,
1571            response,
1572            network,
1573            platform_version,
1574            provider,
1575        )?;
1576
1577        if let Some(e) = epochs.0 {
1578            if e.len() != 1 {
1579                return Err(Error::RequestError {
1580                    error: format!("expected 1 epoch, got {}", e.len()),
1581                });
1582            }
1583            let epoch = e.into_iter().next().and_then(|v| v.1);
1584            Ok((epoch, epochs.1, epochs.2))
1585        } else {
1586            Ok((None, epochs.1, epochs.2))
1587        }
1588    }
1589}
1590
1591impl FromProof<platform::GetEpochsInfoRequest> for ExtendedEpochInfos {
1592    type Request = platform::GetEpochsInfoRequest;
1593    type Response = platform::GetEpochsInfoResponse;
1594
1595    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1596        request: I,
1597        response: O,
1598        _network: Network,
1599        platform_version: &PlatformVersion,
1600        provider: &'a dyn ContextProvider,
1601    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1602    where
1603        Self: Sized + 'a,
1604    {
1605        let request: Self::Request = request.into();
1606        let response: Self::Response = response.into();
1607        // Parse response to read proof and metadata
1608        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1609
1610        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1611
1612        let (start_epoch, count, ascending) = match request.version.ok_or(Error::EmptyVersion)? {
1613            get_epochs_info_request::Version::V0(v0) => (v0.start_epoch, v0.count, v0.ascending),
1614        };
1615
1616        let start_epoch: Option<EpochIndex> = if let Some(epoch) = start_epoch {
1617            Some(try_u32_to_u16(epoch)?)
1618        } else {
1619            None
1620        };
1621        if start_epoch.is_none() && !ascending {
1622            return Err(Error::RequestError {
1623                error: "proved descending epoch queries require an explicit start epoch"
1624                    .to_string(),
1625            });
1626        }
1627        // This argument is consulted only for descending queries without an
1628        // explicit start, which are rejected above. Avoid unsigned metadata.
1629        let current_epoch: EpochIndex = 0;
1630        let count = try_u32_to_u16(count)?;
1631
1632        let (root_hash, epoch_info) = Drive::verify_epoch_infos(
1633            supported_grovedb_proof_bytes(proof)?,
1634            current_epoch,
1635            start_epoch,
1636            count,
1637            ascending,
1638            platform_version,
1639        )
1640        .map_drive_error(proof, mtd)?;
1641
1642        let epoch_info = epoch_info
1643            .into_iter()
1644            .map(|v| {
1645                #[allow(clippy::infallible_destructuring_match)]
1646                let info = match &v {
1647                    ExtendedEpochInfo::V0(i) => i,
1648                };
1649
1650                (info.index, Some(v))
1651            })
1652            .collect::<ExtendedEpochInfos>();
1653
1654        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1655
1656        Ok((epoch_info.into_option(), mtd.clone(), proof.clone()))
1657    }
1658}
1659
1660impl FromProof<platform::GetFinalizedEpochInfosRequest> for FinalizedEpochInfos {
1661    type Request = platform::GetFinalizedEpochInfosRequest;
1662    type Response = platform::GetFinalizedEpochInfosResponse;
1663
1664    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1665        request: I,
1666        response: O,
1667        _network: Network,
1668        platform_version: &PlatformVersion,
1669        provider: &'a dyn ContextProvider,
1670    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1671    where
1672        Self: Sized + 'a,
1673    {
1674        let request: Self::Request = request.into();
1675        let response: Self::Response = response.into();
1676        // Parse response to read proof and metadata
1677        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1678
1679        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1680
1681        let (
1682            start_epoch_index,
1683            start_epoch_index_included,
1684            end_epoch_index,
1685            end_epoch_index_included,
1686        ) = match request.version.ok_or(Error::EmptyVersion)? {
1687            get_finalized_epoch_infos_request::Version::V0(v0) => (
1688                v0.start_epoch_index,
1689                v0.start_epoch_index_included,
1690                v0.end_epoch_index,
1691                v0.end_epoch_index_included,
1692            ),
1693        };
1694
1695        let start_epoch_index: EpochIndex = try_u32_to_u16(start_epoch_index)?;
1696        let end_epoch_index: EpochIndex = try_u32_to_u16(end_epoch_index)?;
1697
1698        let (root_hash, epoch_info) = Drive::verify_finalized_epoch_infos(
1699            supported_grovedb_proof_bytes(proof)?,
1700            start_epoch_index,
1701            start_epoch_index_included,
1702            end_epoch_index,
1703            end_epoch_index_included,
1704            platform_version,
1705        )
1706        .map_drive_error(proof, mtd)?;
1707
1708        let epoch_info = epoch_info
1709            .into_iter()
1710            .map(|(epoch_index, finalized_epoch_info)| (epoch_index, Some(finalized_epoch_info)))
1711            .collect::<FinalizedEpochInfos>();
1712
1713        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1714
1715        Ok((epoch_info.into_option(), mtd.clone(), proof.clone()))
1716    }
1717}
1718
1719fn try_u32_to_u16(i: u32) -> Result<u16, Error> {
1720    i.try_into()
1721        .map_err(|e: TryFromIntError| Error::RequestError {
1722            error: e.to_string(),
1723        })
1724}
1725
1726impl FromProof<GetProtocolVersionUpgradeStateRequest> for ProtocolVersionUpgrades {
1727    type Request = GetProtocolVersionUpgradeStateRequest;
1728    type Response = GetProtocolVersionUpgradeStateResponse;
1729
1730    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1731        _request: I,
1732        response: O,
1733        _network: Network,
1734        platform_version: &PlatformVersion,
1735        provider: &'a dyn ContextProvider,
1736    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1737    where
1738        Self: Sized + 'a,
1739    {
1740        let response: Self::Response = response.into();
1741        // Parse response to read proof and metadata
1742        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1743        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1744
1745        let (root_hash, objects) =
1746            Drive::verify_upgrade_state(supported_grovedb_proof_bytes(proof)?, platform_version)
1747                .map_drive_error(proof, mtd)?;
1748
1749        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1750
1751        // Convert objects to a map of Option values
1752        let response: Self = objects.into_iter().map(|(k, v)| (k, Some(v))).collect();
1753
1754        Ok((response.into_option(), mtd.clone(), proof.clone()))
1755    }
1756}
1757
1758impl FromProof<GetProtocolVersionUpgradeVoteStatusRequest> for MasternodeProtocolVotes {
1759    type Request = GetProtocolVersionUpgradeVoteStatusRequest;
1760    type Response = GetProtocolVersionUpgradeVoteStatusResponse;
1761
1762    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1763        request: I,
1764        response: O,
1765        _network: Network,
1766        platform_version: &PlatformVersion,
1767        provider: &'a dyn ContextProvider,
1768    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1769    where
1770        Self: Sized + 'a,
1771    {
1772        let request = request.into();
1773        let response: Self::Response = response.into();
1774        // Parse response to read proof and metadata
1775        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1776        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1777
1778        let request_v0: GetProtocolVersionUpgradeVoteStatusRequestV0 = match request.version {
1779            Some(get_protocol_version_upgrade_vote_status_request::Version::V0(v0)) => v0,
1780            None => return Err(Error::EmptyVersion),
1781        };
1782
1783        let start_pro_tx_hash: Option<[u8; 32]> =
1784            if request_v0.start_pro_tx_hash.is_empty() {
1785                None
1786            } else {
1787                Some(request_v0.start_pro_tx_hash[..].try_into().map_err(
1788                    |e: TryFromSliceError| Error::RequestError {
1789                        error: e.to_string(),
1790                    },
1791                )?)
1792            };
1793        let count = try_u32_to_u16(request_v0.count)?;
1794
1795        let (root_hash, objects) = Drive::verify_upgrade_vote_status(
1796            supported_grovedb_proof_bytes(proof)?,
1797            start_pro_tx_hash,
1798            count,
1799            platform_version,
1800        )
1801        .map_drive_error(proof, mtd)?;
1802
1803        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1804
1805        if objects.is_empty() {
1806            return Ok((None, mtd.clone(), proof.clone()));
1807        }
1808        let votes: MasternodeProtocolVotes = objects
1809            .into_iter()
1810            .map(|(key, value)| {
1811                ProTxHash::from_slice(&key)
1812                    .map(|pro_tx_hash| {
1813                        (
1814                            pro_tx_hash,
1815                            Some(MasternodeProtocolVote {
1816                                pro_tx_hash,
1817                                voted_version: value,
1818                            }),
1819                        )
1820                    })
1821                    .map_err(|e| Error::ResultEncodingError {
1822                        error: e.to_string(),
1823                    })
1824            })
1825            .collect::<Result<MasternodeProtocolVotes, Error>>()?;
1826
1827        Ok((votes.into_option(), mtd.clone(), proof.clone()))
1828    }
1829}
1830
1831impl FromProof<GetPathElementsRequest> for Elements {
1832    type Request = GetPathElementsRequest;
1833    type Response = GetPathElementsResponse;
1834
1835    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1836        request: I,
1837        response: O,
1838        _network: Network,
1839        platform_version: &PlatformVersion,
1840        provider: &'a dyn ContextProvider,
1841    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1842    where
1843        Self: Sized + 'a,
1844    {
1845        let request = request.into();
1846        let response: Self::Response = response.into();
1847        // Parse response to read proof and metadata
1848        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1849        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1850
1851        let request_v0: GetPathElementsRequestV0 = match request.version {
1852            Some(get_path_elements_request::Version::V0(v0)) => v0,
1853            None => return Err(Error::EmptyVersion),
1854        };
1855
1856        let path = request_v0.path;
1857        let keys = request_v0.keys;
1858
1859        let (root_hash, objects) = Drive::verify_elements(
1860            supported_grovedb_proof_bytes(proof)?,
1861            path,
1862            keys,
1863            platform_version,
1864        )?;
1865        let elements: Elements = Elements::from_iter(objects);
1866
1867        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1868
1869        Ok((elements.into_option(), mtd.clone(), proof.clone()))
1870    }
1871}
1872
1873impl<'dq, Q> FromProof<Q> for Documents
1874where
1875    Q: TryInto<DriveDocumentQuery<'dq>> + Clone + 'dq,
1876    Q::Error: std::fmt::Display,
1877{
1878    type Request = Q;
1879    type Response = platform::GetDocumentsResponse;
1880
1881    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1882        request: I,
1883        response: O,
1884        _network: Network,
1885        platform_version: &PlatformVersion,
1886        provider: &'a dyn ContextProvider,
1887    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1888    where
1889        Self: 'a,
1890    {
1891        let request: Self::Request = request.into();
1892        let response: Self::Response = response.into();
1893
1894        let request: DriveDocumentQuery<'dq> =
1895            request
1896                .clone()
1897                .try_into()
1898                .map_err(|e: Q::Error| Error::RequestError {
1899                    error: e.to_string(),
1900                })?;
1901
1902        // Parse response to read proof and metadata
1903        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1904
1905        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1906
1907        let (root_hash, documents) = request
1908            .verify_proof(supported_grovedb_proof_bytes(proof)?, platform_version)
1909            .map_drive_error(proof, mtd)?;
1910
1911        let documents = documents
1912            .into_iter()
1913            .map(|d| (d.id(), Some(d)))
1914            .collect::<Documents>();
1915
1916        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1917
1918        Ok((documents.into_option(), mtd.clone(), proof.clone()))
1919    }
1920}
1921
1922impl FromProof<platform::GetIdentitiesContractKeysRequest> for IdentitiesContractKeys {
1923    type Request = platform::GetIdentitiesContractKeysRequest;
1924    type Response = platform::GetIdentitiesContractKeysResponse;
1925
1926    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
1927        request: I,
1928        response: O,
1929        _network: Network,
1930        platform_version: &PlatformVersion,
1931        provider: &'a dyn ContextProvider,
1932    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
1933    where
1934        Self: 'a,
1935    {
1936        let request: Self::Request = request.into();
1937        let response: Self::Response = response.into();
1938
1939        // Parse response to read proof and metadata
1940        let proof = response.proof().or(Err(Error::NoProofInResult))?;
1941
1942        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
1943
1944        let (identities_ids, contract_id, document_type_name, purposes) =
1945            match request.version.ok_or(Error::EmptyVersion)? {
1946                get_identities_contract_keys_request::Version::V0(v0) => {
1947                    let GetIdentitiesContractKeysRequestV0 {
1948                        identities_ids,
1949                        contract_id,
1950                        document_type_name,
1951                        purposes,
1952                        ..
1953                    } = v0;
1954                    let identifiers = identities_ids
1955                        .into_iter()
1956                        .map(|identity_id_vec| {
1957                            let identifier = Identifier::from_vec(identity_id_vec)?;
1958                            Ok(identifier.to_buffer())
1959                        })
1960                        .collect::<Result<Vec<[u8; 32]>, platform_value::Error>>()
1961                        .map_err(|e| Error::ProtocolError {
1962                            error: e.to_string(),
1963                        })?;
1964                    let contract_id = Identifier::from_vec(contract_id)
1965                        .map_err(|e| Error::ProtocolError {
1966                            error: e.to_string(),
1967                        })?
1968                        .into_buffer();
1969                    let purposes = purposes
1970                        .into_iter()
1971                        .map(|purpose| {
1972                            Purpose::try_from(purpose).map_err(|e| Error::ProtocolError {
1973                                error: e.to_string(),
1974                            })
1975                        })
1976                        .collect::<Result<Vec<Purpose>, Error>>()?;
1977                    (identifiers, contract_id, document_type_name, purposes)
1978                }
1979            };
1980
1981        // Extract content from proof and verify Drive/GroveDB proofs
1982        let (root_hash, identities_contract_keys) = Drive::verify_identities_contract_keys(
1983            supported_grovedb_proof_bytes(proof)?,
1984            identities_ids.as_slice(),
1985            &contract_id,
1986            document_type_name,
1987            purposes,
1988            false,
1989            platform_version,
1990        )
1991        .map_drive_error(proof, mtd)?;
1992
1993        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
1994
1995        if identities_contract_keys.is_empty() {
1996            return Ok((None, mtd.clone(), proof.clone()));
1997        }
1998
1999        Ok((Some(identities_contract_keys), mtd.clone(), proof.clone()))
2000    }
2001}
2002
2003impl FromProof<platform::GetContestedResourcesRequest> for ContestedResources {
2004    type Request = platform::GetContestedResourcesRequest;
2005    type Response = platform::GetContestedResourcesResponse;
2006
2007    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2008        request: I,
2009        response: O,
2010        _network: Network,
2011        platform_version: &PlatformVersion,
2012        provider: &'a dyn ContextProvider,
2013    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2014    where
2015        Self: Sized + 'a,
2016    {
2017        let request: Self::Request = request.into();
2018        let response: Self::Response = response.into();
2019
2020        // Decode request to get drive query
2021        let drive_query = VotePollsByDocumentTypeQuery::try_from_request(request)?;
2022        let resolved_request = drive_query.resolve_with_known_contracts_provider(
2023            &provider.as_contract_lookup_fn(platform_version),
2024        )?;
2025
2026        // Parse response to read proof and metadata
2027        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2028        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2029
2030        let (root_hash, items) = resolved_request
2031            .verify_contests_proof(supported_grovedb_proof_bytes(proof)?, platform_version)
2032            .map_drive_error(proof, mtd)?;
2033
2034        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2035
2036        let resources: ContestedResources = items.into_iter().map(ContestedResource).collect();
2037
2038        Ok((resources.into_option(), mtd.clone(), proof.clone()))
2039    }
2040}
2041
2042// rpc getContestedResourceVoteState(GetContestedResourceVoteStateRequest) returns (GetContestedResourceVoteStateResponse);
2043impl FromProof<platform::GetContestedResourceVoteStateRequest> for Contenders {
2044    type Request = platform::GetContestedResourceVoteStateRequest;
2045    type Response = platform::GetContestedResourceVoteStateResponse;
2046
2047    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2048        request: I,
2049        response: O,
2050        _network: Network,
2051        platform_version: &PlatformVersion,
2052        provider: &'a dyn ContextProvider,
2053    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2054    where
2055        Self: 'a,
2056    {
2057        let request: Self::Request = request.into();
2058        let response: Self::Response = response.into();
2059
2060        // Decode request to get drive query
2061        let drive_query = ContestedDocumentVotePollDriveQuery::try_from_request(request)?;
2062
2063        // Resolve request to get verify_*_proof
2064        let contracts_provider = provider.as_contract_lookup_fn(platform_version);
2065        let resolved_request =
2066            drive_query.resolve_with_known_contracts_provider(&contracts_provider)?;
2067
2068        // Parse response to read proof and metadata
2069        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2070        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2071
2072        let (root_hash, contested_resource_vote_state) = resolved_request
2073            .verify_vote_poll_vote_state_proof(
2074                supported_grovedb_proof_bytes(proof)?,
2075                platform_version,
2076            )
2077            .map_drive_error(proof, mtd)?;
2078
2079        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2080
2081        let contenders = contested_resource_vote_state
2082            .contenders
2083            .into_iter()
2084            .map(|v| (v.identity_id(), v))
2085            .collect();
2086
2087        let response = Contenders {
2088            winner: contested_resource_vote_state.winner,
2089            contenders,
2090            abstain_vote_tally: contested_resource_vote_state.abstaining_vote_tally,
2091            lock_vote_tally: contested_resource_vote_state.locked_vote_tally,
2092        };
2093        Ok((response.into_option(), mtd.clone(), proof.clone()))
2094    }
2095}
2096
2097impl FromProof<GetContestedResourceVotersForIdentityRequest> for Voters {
2098    type Request = GetContestedResourceVotersForIdentityRequest;
2099    type Response = GetContestedResourceVotersForIdentityResponse;
2100
2101    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2102        request: I,
2103        response: O,
2104        _network: Network,
2105        platform_version: &PlatformVersion,
2106        provider: &'a dyn ContextProvider,
2107    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2108    where
2109        Self: Sized + 'a,
2110    {
2111        let request: Self::Request = request.into();
2112        let response: Self::Response = response.into();
2113
2114        // Decode request to get drive query
2115        let drive_query = ContestedDocumentVotePollVotesDriveQuery::try_from_request(request)?;
2116
2117        // Parse request to get resolved contract that implements verify_*_proof
2118        let contracts_provider = provider.as_contract_lookup_fn(platform_version);
2119
2120        let resolved_request =
2121            drive_query.resolve_with_known_contracts_provider(&contracts_provider)?;
2122
2123        // Parse response to read proof and metadata
2124        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2125        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2126
2127        let (root_hash, voters) = resolved_request
2128            .verify_vote_poll_votes_proof(supported_grovedb_proof_bytes(proof)?, platform_version)
2129            .map_drive_error(proof, mtd)?;
2130
2131        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2132
2133        if voters.is_empty() {
2134            return Ok((None, mtd.clone(), proof.clone()));
2135        }
2136        let result: Voters = voters.into_iter().map(Voter::from).collect();
2137
2138        Ok((result.into_option(), mtd.clone(), proof.clone()))
2139    }
2140}
2141
2142impl FromProof<platform::GetContestedResourceIdentityVotesRequest> for ResourceVotesByIdentity {
2143    type Request = platform::GetContestedResourceIdentityVotesRequest;
2144    type Response = platform::GetContestedResourceIdentityVotesResponse;
2145
2146    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2147        request: I,
2148        response: O,
2149        _network: Network,
2150        platform_version: &PlatformVersion,
2151        provider: &'a dyn ContextProvider,
2152    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2153    where
2154        Self: Sized + 'a,
2155    {
2156        let request: Self::Request = request.into();
2157        let response: Self::Response = response.into();
2158
2159        // Decode request to get drive query
2160        let drive_query = ContestedResourceVotesGivenByIdentityQuery::try_from_request(request)?;
2161
2162        // Parse response to read proof and metadata
2163        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2164        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2165
2166        let contract_provider_fn = provider.as_contract_lookup_fn(platform_version);
2167        let (root_hash, voters) = drive_query
2168            .verify_identity_votes_given_proof::<Vec<_>>(
2169                supported_grovedb_proof_bytes(proof)?,
2170                &contract_provider_fn,
2171                platform_version,
2172            )
2173            .map_drive_error(proof, mtd)?;
2174
2175        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2176
2177        let response: ResourceVotesByIdentity = voters
2178            .into_iter()
2179            .map(|(id, vote)| (id, Some(vote)))
2180            .collect();
2181
2182        Ok((response.into_option(), mtd.clone(), proof.clone()))
2183    }
2184}
2185
2186impl FromProof<platform::GetVotePollsByEndDateRequest> for VotePollsGroupedByTimestamp {
2187    type Request = platform::GetVotePollsByEndDateRequest;
2188    type Response = platform::GetVotePollsByEndDateResponse;
2189
2190    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2191        request: I,
2192        response: O,
2193        _network: Network,
2194        platform_version: &PlatformVersion,
2195        provider: &'a dyn ContextProvider,
2196    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2197    where
2198        Self: Sized + 'a,
2199    {
2200        let request: Self::Request = request.into();
2201        let response: Self::Response = response.into();
2202
2203        // Decode request to get drive query
2204        let drive_query = VotePollsByEndDateDriveQuery::try_from_request(request)?;
2205
2206        // Parse response to read proof and metadata
2207        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2208        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2209
2210        let (root_hash, vote_polls) = drive_query
2211            .verify_vote_polls_by_end_date_proof::<Vec<(_, _)>>(
2212                supported_grovedb_proof_bytes(proof)?,
2213                platform_version,
2214            )
2215            .map_drive_error(proof, mtd)?;
2216
2217        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2218
2219        let response = VotePollsGroupedByTimestamp(vote_polls).sorted(drive_query.order_ascending);
2220
2221        Ok((response.into_option(), mtd.clone(), proof.clone()))
2222    }
2223}
2224
2225impl FromProof<platform::GetPrefundedSpecializedBalanceRequest> for PrefundedSpecializedBalance {
2226    type Request = platform::GetPrefundedSpecializedBalanceRequest;
2227    type Response = platform::GetPrefundedSpecializedBalanceResponse;
2228
2229    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2230        request: I,
2231        response: O,
2232        _network: Network,
2233        platform_version: &PlatformVersion,
2234        provider: &'a dyn ContextProvider,
2235    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2236    where
2237        Self: Sized + 'a,
2238    {
2239        let request: Self::Request = request.into();
2240        let response: Self::Response = response.into();
2241
2242        let balance_id = match request.version.ok_or(Error::EmptyVersion)? {
2243            get_prefunded_specialized_balance_request::Version::V0(v0) => {
2244                Identifier::from_vec(v0.id).map_err(|e| Error::RequestError {
2245                    error: e.to_string(),
2246                })?
2247            }
2248        };
2249
2250        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2251
2252        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2253
2254        let (root_hash, balance) = Drive::verify_specialized_balance(
2255            supported_grovedb_proof_bytes(proof)?,
2256            balance_id.into_buffer(),
2257            false,
2258            platform_version,
2259        )
2260        .map_drive_error(proof, mtd)?;
2261
2262        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2263
2264        Ok((balance.map(|v| v.into()), mtd.clone(), proof.clone()))
2265    }
2266}
2267
2268impl FromProof<platform::GetContestedResourceIdentityVotesRequest> for Vote {
2269    type Request = platform::GetContestedResourceIdentityVotesRequest;
2270    type Response = platform::GetContestedResourceIdentityVotesResponse;
2271
2272    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2273        request: I,
2274        response: O,
2275        network: Network,
2276        platform_version: &PlatformVersion,
2277        provider: &'a dyn ContextProvider,
2278    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2279    where
2280        Self: Sized + 'a,
2281    {
2282        let request = request.into();
2283        let id_in_request = match request.version.as_ref().ok_or(Error::EmptyVersion)? {
2284            get_contested_resource_identity_votes_request::Version::V0(v0) => {
2285                Identifier::from_bytes(&v0.identity_id).map_err(|e| Error::RequestError {
2286                    error: e.to_string(),
2287                })?
2288            }
2289        };
2290
2291        let (maybe_votes, mtd, proof) = ResourceVotesByIdentity::maybe_from_proof_with_metadata(
2292            request,
2293            response,
2294            network,
2295            platform_version,
2296            provider,
2297        )?;
2298
2299        let (id, vote) = match maybe_votes {
2300            Some(v) if v.len() > 1 => {
2301                return Err(Error::ResponseDecodeError {
2302                    error: format!("expected 1 vote, got {}", v.len()),
2303                })
2304            }
2305            Some(v) if v.is_empty() => return Ok((None, mtd, proof)),
2306            Some(v) => v
2307                .into_iter()
2308                .next()
2309                .expect("is_empty() must detect empty map"),
2310            None => return Ok((None, mtd, proof)),
2311        };
2312
2313        if id != id_in_request {
2314            return Err(Error::ResponseDecodeError {
2315                error: format!(
2316                    "expected vote for identity {}, got vote for identity {}",
2317                    id_in_request, id
2318                ),
2319            });
2320        }
2321
2322        Ok((vote.map(Vote::ResourceVote), mtd, proof))
2323    }
2324}
2325
2326impl FromProof<platform::GetTotalCreditsInPlatformRequest> for TotalCreditsInPlatform {
2327    type Request = platform::GetTotalCreditsInPlatformRequest;
2328    type Response = platform::GetTotalCreditsInPlatformResponse;
2329
2330    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2331        _request: I,
2332        response: O,
2333        network: Network,
2334        platform_version: &PlatformVersion,
2335        provider: &'a dyn ContextProvider,
2336    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2337    where
2338        Self: Sized + 'a,
2339    {
2340        let response: Self::Response = response.into();
2341        // Parse response to read proof and metadata
2342        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2343        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2344
2345        let core_subsidy_halving_interval = network.core_subsidy_halving_interval();
2346
2347        let (root_hash, credits) = Drive::verify_total_credits_in_system(
2348            supported_grovedb_proof_bytes(proof)?,
2349            core_subsidy_halving_interval,
2350            || {
2351                provider.get_platform_activation_height().map_err(|e| {
2352                    drive::error::Error::Proof(ProofError::MissingContextRequirement(e.to_string()))
2353                })
2354            },
2355            mtd.core_chain_locked_height,
2356            platform_version,
2357        )
2358        .map_drive_error(proof, mtd)?;
2359
2360        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2361
2362        Ok((
2363            Some(TotalCreditsInPlatform(credits)),
2364            mtd.clone(),
2365            proof.clone(),
2366        ))
2367    }
2368}
2369impl FromProof<platform::GetEvonodesProposedEpochBlocksByIdsRequest> for ProposerBlockCounts {
2370    type Request = platform::GetEvonodesProposedEpochBlocksByIdsRequest;
2371    type Response = platform::GetEvonodesProposedEpochBlocksResponse;
2372
2373    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2374        request: I,
2375        response: O,
2376        _network: Network,
2377        platform_version: &PlatformVersion,
2378        provider: &'a dyn ContextProvider,
2379    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2380    where
2381        Self: Sized + 'a,
2382    {
2383        let request: Self::Request = request.into();
2384        let response: Self::Response = response.into();
2385        // Parse response to read proof and metadata
2386        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2387        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2388
2389        let (ids, epoch) = match request.version.ok_or(Error::EmptyVersion)? {
2390            get_evonodes_proposed_epoch_blocks_by_ids_request::Version::V0(v0) => {
2391                (v0.ids, v0.epoch)
2392            }
2393        };
2394
2395        let epoch_index = match epoch {
2396            Some(index) => try_u32_to_u16(index)?,
2397            None => {
2398                return Err(Error::RequestError {
2399                    error: "proved proposer queries require an explicit epoch".to_string(),
2400                })
2401            }
2402        };
2403
2404        let (root_hash, proposer_block_counts) = Drive::verify_epoch_proposers(
2405            supported_grovedb_proof_bytes(proof)?,
2406            epoch_index,
2407            ProposerQueryType::ByIds(ids),
2408            platform_version,
2409        )
2410        .map_drive_error(proof, mtd)?;
2411
2412        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2413
2414        Ok((
2415            Some(ProposerBlockCounts(proposer_block_counts)),
2416            mtd.clone(),
2417            proof.clone(),
2418        ))
2419    }
2420}
2421
2422impl FromProof<platform::GetEvonodesProposedEpochBlocksByRangeRequest> for ProposerBlockCounts {
2423    type Request = platform::GetEvonodesProposedEpochBlocksByRangeRequest;
2424    type Response = platform::GetEvonodesProposedEpochBlocksResponse;
2425
2426    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2427        request: I,
2428        response: O,
2429        _network: Network,
2430        platform_version: &PlatformVersion,
2431        provider: &'a dyn ContextProvider,
2432    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2433    where
2434        Self: Sized + 'a,
2435    {
2436        let request: Self::Request = request.into();
2437        let response: Self::Response = response.into();
2438        // Parse response to read proof and metadata
2439        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2440        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2441
2442        let (epoch, limit, start) = match request.version.ok_or(Error::EmptyVersion)? {
2443            get_evonodes_proposed_epoch_blocks_by_range_request::Version::V0(v0) => {
2444                (v0.epoch, v0.limit, v0.start)
2445            }
2446        };
2447
2448        let formatted_start = match start {
2449            None => None,
2450            Some(Start::StartAfter(after)) => {
2451                let id: [u8; 32] = after.try_into().map_err(|_| Error::DriveError {
2452                    error: "Invalid public key hash length".to_string(),
2453                })?;
2454                Some((id, false))
2455            }
2456            Some(Start::StartAt(at)) => {
2457                let id: [u8; 32] = at.try_into().map_err(|_| Error::DriveError {
2458                    error: "Invalid public key hash length".to_string(),
2459                })?;
2460                Some((id, true))
2461            }
2462        };
2463
2464        let epoch_index = match epoch {
2465            Some(index) => try_u32_to_u16(index)?,
2466            None => {
2467                return Err(Error::RequestError {
2468                    error: "proved proposer queries require an explicit epoch".to_string(),
2469                })
2470            }
2471        };
2472        let checked_limit = limit.map(try_u32_to_u16).transpose()?;
2473
2474        let (root_hash, proposer_block_counts) = Drive::verify_epoch_proposers(
2475            supported_grovedb_proof_bytes(proof)?,
2476            epoch_index,
2477            ProposerQueryType::ByRange(checked_limit, formatted_start),
2478            platform_version,
2479        )
2480        .map_drive_error(proof, mtd)?;
2481
2482        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2483
2484        Ok((
2485            Some(ProposerBlockCounts(proposer_block_counts)),
2486            mtd.clone(),
2487            proof.clone(),
2488        ))
2489    }
2490}
2491
2492/// Convert u32, if 0 return None, otherwise return Some(u16).
2493/// Errors when value is out of range.
2494fn u32_to_u16_opt(i: u32) -> Result<Option<u16>, Error> {
2495    let i: Option<u16> = if i != 0 {
2496        let i: u16 = i
2497            .try_into()
2498            .map_err(|e: TryFromIntError| Error::RequestError {
2499                error: format!("value {} out of range: {}", i, e),
2500            })?;
2501        Some(i)
2502    } else {
2503        None
2504    };
2505
2506    Ok(i)
2507}
2508
2509// --- Shielded Pool Query Proof Verification ---
2510
2511/// The optional `token_id` every shielded pool request carries: `None` targets the credit
2512/// shielded pool, `Some` the pool of that token. The verifier must prove against the same
2513/// pool the request named, so a malformed id is a request error rather than a silent fallback.
2514fn shielded_pool_token_id(token_id: Option<Vec<u8>>) -> Result<Option<[u8; 32]>, Error> {
2515    token_id
2516        .map(|token_id| {
2517            token_id
2518                .try_into()
2519                .map_err(|token_id: Vec<u8>| Error::RequestError {
2520                    error: format!("token_id must be 32 bytes, got {}", token_id.len()),
2521                })
2522        })
2523        .transpose()
2524}
2525
2526impl FromProof<platform::GetShieldedPoolStateRequest> for ShieldedPoolState {
2527    type Request = platform::GetShieldedPoolStateRequest;
2528    type Response = platform::GetShieldedPoolStateResponse;
2529
2530    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2531        request: I,
2532        response: O,
2533        _network: Network,
2534        platform_version: &PlatformVersion,
2535        provider: &'a dyn ContextProvider,
2536    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2537    where
2538        Self: Sized + 'a,
2539    {
2540        use dapi_grpc::platform::v0::get_shielded_pool_state_request;
2541
2542        let request: Self::Request = request.into();
2543        let response: Self::Response = response.into();
2544        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2545        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2546
2547        let token_id = match request.version.ok_or(Error::EmptyVersion)? {
2548            get_shielded_pool_state_request::Version::V0(v0) => {
2549                shielded_pool_token_id(v0.token_id)?
2550            }
2551        };
2552
2553        let proof_bytes = supported_grovedb_proof_bytes(proof)?;
2554        let (root_hash, maybe_balance) = match token_id {
2555            None => Drive::verify_shielded_pool_state(proof_bytes, false, platform_version),
2556            Some(token_id) => Drive::verify_token_shielded_pool_state(
2557                proof_bytes,
2558                token_id,
2559                false,
2560                platform_version,
2561            ),
2562        }
2563        .map_drive_error(proof, mtd)?;
2564
2565        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2566
2567        Ok((
2568            maybe_balance.map(ShieldedPoolState),
2569            mtd.clone(),
2570            proof.clone(),
2571        ))
2572    }
2573}
2574
2575impl FromProof<platform::GetShieldedNotesCountRequest> for ShieldedNotesCount {
2576    type Request = platform::GetShieldedNotesCountRequest;
2577    type Response = platform::GetShieldedNotesCountResponse;
2578
2579    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2580        request: I,
2581        response: O,
2582        _network: Network,
2583        platform_version: &PlatformVersion,
2584        provider: &'a dyn ContextProvider,
2585    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2586    where
2587        Self: Sized + 'a,
2588    {
2589        use dapi_grpc::platform::v0::get_shielded_notes_count_request;
2590
2591        let request: Self::Request = request.into();
2592        let response: Self::Response = response.into();
2593        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2594        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2595
2596        let token_id = match request.version.ok_or(Error::EmptyVersion)? {
2597            get_shielded_notes_count_request::Version::V0(v0) => {
2598                shielded_pool_token_id(v0.token_id)?
2599            }
2600        };
2601
2602        // Mirrors `ShieldedPoolState` above; the only difference is the
2603        // proved element type — `verify_shielded_notes_count` decodes
2604        // `total_count` out of the `CommitmentTree` element rather than a
2605        // `SumItem` balance.
2606        let proof_bytes = supported_grovedb_proof_bytes(proof)?;
2607        let (root_hash, maybe_count) = match token_id {
2608            None => Drive::verify_shielded_notes_count(proof_bytes, false, platform_version),
2609            Some(token_id) => Drive::verify_token_shielded_pool_notes_count(
2610                proof_bytes,
2611                token_id,
2612                false,
2613                platform_version,
2614            ),
2615        }
2616        .map_drive_error(proof, mtd)?;
2617
2618        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2619
2620        Ok((
2621            maybe_count.map(ShieldedNotesCount),
2622            mtd.clone(),
2623            proof.clone(),
2624        ))
2625    }
2626}
2627
2628impl FromProof<platform::GetShieldedAnchorsRequest> for ShieldedAnchors {
2629    type Request = platform::GetShieldedAnchorsRequest;
2630    type Response = platform::GetShieldedAnchorsResponse;
2631
2632    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2633        request: I,
2634        response: O,
2635        _network: Network,
2636        platform_version: &PlatformVersion,
2637        provider: &'a dyn ContextProvider,
2638    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2639    where
2640        Self: Sized + 'a,
2641    {
2642        use dapi_grpc::platform::v0::get_shielded_anchors_request;
2643
2644        let request: Self::Request = request.into();
2645        let response: Self::Response = response.into();
2646        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2647        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2648
2649        let token_id = match request.version.ok_or(Error::EmptyVersion)? {
2650            get_shielded_anchors_request::Version::V0(v0) => shielded_pool_token_id(v0.token_id)?,
2651        };
2652
2653        let proof_bytes = supported_grovedb_proof_bytes(proof)?;
2654        let (root_hash, anchors) = match token_id {
2655            None => Drive::verify_shielded_anchors(proof_bytes, false, platform_version),
2656            Some(token_id) => Drive::verify_token_shielded_pool_anchors(
2657                proof_bytes,
2658                token_id,
2659                false,
2660                platform_version,
2661            ),
2662        }
2663        .map_drive_error(proof, mtd)?;
2664
2665        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2666
2667        let result = if anchors.is_empty() {
2668            None
2669        } else {
2670            Some(ShieldedAnchors(anchors))
2671        };
2672
2673        Ok((result, mtd.clone(), proof.clone()))
2674    }
2675}
2676
2677impl FromProof<platform::GetMostRecentShieldedAnchorRequest> for MostRecentShieldedAnchor {
2678    type Request = platform::GetMostRecentShieldedAnchorRequest;
2679    type Response = platform::GetMostRecentShieldedAnchorResponse;
2680
2681    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2682        request: I,
2683        response: O,
2684        _network: Network,
2685        platform_version: &PlatformVersion,
2686        provider: &'a dyn ContextProvider,
2687    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2688    where
2689        Self: Sized + 'a,
2690    {
2691        use dapi_grpc::platform::v0::get_most_recent_shielded_anchor_request;
2692
2693        let request: Self::Request = request.into();
2694        let response: Self::Response = response.into();
2695        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2696        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2697
2698        let token_id = match request.version.ok_or(Error::EmptyVersion)? {
2699            get_most_recent_shielded_anchor_request::Version::V0(v0) => {
2700                shielded_pool_token_id(v0.token_id)?
2701            }
2702        };
2703
2704        let proof_bytes = supported_grovedb_proof_bytes(proof)?;
2705        let (root_hash, maybe_anchor) = match token_id {
2706            None => Drive::verify_most_recent_shielded_anchor(proof_bytes, false, platform_version),
2707            Some(token_id) => Drive::verify_most_recent_token_shielded_pool_anchor(
2708                proof_bytes,
2709                token_id,
2710                false,
2711                platform_version,
2712            ),
2713        }
2714        .map_drive_error(proof, mtd)?;
2715
2716        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2717
2718        Ok((
2719            maybe_anchor.map(MostRecentShieldedAnchor),
2720            mtd.clone(),
2721            proof.clone(),
2722        ))
2723    }
2724}
2725
2726impl FromProof<platform::GetShieldedEncryptedNotesRequest> for ShieldedEncryptedNotes {
2727    type Request = platform::GetShieldedEncryptedNotesRequest;
2728    type Response = platform::GetShieldedEncryptedNotesResponse;
2729
2730    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2731        request: I,
2732        response: O,
2733        _network: Network,
2734        platform_version: &PlatformVersion,
2735        provider: &'a dyn ContextProvider,
2736    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2737    where
2738        Self: Sized + 'a,
2739    {
2740        use dapi_grpc::platform::v0::get_shielded_encrypted_notes_request;
2741
2742        let request: Self::Request = request.into();
2743        let response: Self::Response = response.into();
2744        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2745        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2746
2747        let (start_index, count, token_id) = match request.version.ok_or(Error::EmptyVersion)? {
2748            get_shielded_encrypted_notes_request::Version::V0(v0) => (
2749                v0.start_index,
2750                v0.count,
2751                shielded_pool_token_id(v0.token_id)?,
2752            ),
2753        };
2754
2755        let max_elements = platform_version
2756            .drive_abci
2757            .query
2758            .shielded_queries
2759            .max_query_chunks as u32
2760            * (1u32 << drive::drive::shielded::paths::SHIELDED_NOTES_CHUNK_POWER);
2761
2762        let proof_bytes = supported_grovedb_proof_bytes(proof)?;
2763        let (root_hash, notes, total_count) = match token_id {
2764            None => Drive::verify_shielded_encrypted_notes(
2765                proof_bytes,
2766                start_index,
2767                count,
2768                max_elements,
2769                false,
2770                platform_version,
2771            ),
2772            Some(token_id) => Drive::verify_token_shielded_pool_encrypted_notes(
2773                proof_bytes,
2774                token_id,
2775                start_index,
2776                count,
2777                max_elements,
2778                false,
2779                platform_version,
2780            ),
2781        }
2782        .map_drive_error(proof, mtd)?;
2783
2784        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2785
2786        // `total_count` (the on-chain total note count) is extracted from the
2787        // same proof, so it is available even when this chunk returned no
2788        // notes — return the result whenever the proof verified, carrying the
2789        // count for the sync progress-bar denominator. `None` would only be
2790        // appropriate if the proof itself were absent, which is handled above.
2791        let result = Some(ShieldedEncryptedNotes {
2792            notes: notes
2793                .into_iter()
2794                .map(|n| ShieldedEncryptedNote {
2795                    cmx: n.cmx.to_vec(),
2796                    nullifier: n.nullifier.to_vec(),
2797                    cv_net: n.cv_net.to_vec(),
2798                    encrypted_note: n.encrypted_note.to_vec(),
2799                })
2800                .collect(),
2801            total_count,
2802        });
2803
2804        Ok((result, mtd.clone(), proof.clone()))
2805    }
2806}
2807
2808impl FromProof<platform::GetShieldedNullifiersRequest> for ShieldedNullifierStatuses {
2809    type Request = platform::GetShieldedNullifiersRequest;
2810    type Response = platform::GetShieldedNullifiersResponse;
2811
2812    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
2813        request: I,
2814        response: O,
2815        _network: Network,
2816        platform_version: &PlatformVersion,
2817        provider: &'a dyn ContextProvider,
2818    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
2819    where
2820        Self: Sized + 'a,
2821    {
2822        use dapi_grpc::platform::v0::get_shielded_nullifiers_request;
2823
2824        let request: Self::Request = request.into();
2825        let response: Self::Response = response.into();
2826        let proof = response.proof().or(Err(Error::NoProofInResult))?;
2827        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
2828
2829        let (nullifiers, token_id) = match request.version.ok_or(Error::EmptyVersion)? {
2830            get_shielded_nullifiers_request::Version::V0(v0) => {
2831                (v0.nullifiers, shielded_pool_token_id(v0.token_id)?)
2832            }
2833        };
2834
2835        let proof_bytes = supported_grovedb_proof_bytes(proof)?;
2836        let (root_hash, statuses) = match token_id {
2837            None => {
2838                Drive::verify_shielded_nullifiers(proof_bytes, &nullifiers, false, platform_version)
2839            }
2840            Some(token_id) => Drive::verify_token_shielded_pool_nullifiers(
2841                proof_bytes,
2842                token_id,
2843                &nullifiers,
2844                false,
2845                platform_version,
2846            ),
2847        }
2848        .map_drive_error(proof, mtd)?;
2849
2850        verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
2851
2852        let result = if statuses.is_empty() {
2853            None
2854        } else {
2855            Some(ShieldedNullifierStatuses(
2856                statuses
2857                    .into_iter()
2858                    .map(|(nullifier, is_spent)| {
2859                        let nullifier: [u8; 32] =
2860                            nullifier
2861                                .try_into()
2862                                .map_err(|_| Error::ResultEncodingError {
2863                                    error: "nullifier from Drive proof is not 32 bytes".to_string(),
2864                                })?;
2865                        Ok(ShieldedNullifierStatus {
2866                            nullifier,
2867                            is_spent,
2868                        })
2869                    })
2870                    .collect::<Result<Vec<_>, Error>>()?,
2871            ))
2872        };
2873
2874        Ok((result, mtd.clone(), proof.clone()))
2875    }
2876}
2877
2878/// Determine number of non-None elements
2879pub trait Length {
2880    /// Return number of non-None elements in the data structure
2881    fn count_some(&self) -> usize;
2882    /// Return number of all elements in the data structure, including None
2883    fn count(&self) -> usize;
2884}
2885
2886impl<T: Length> Length for Option<T> {
2887    fn count_some(&self) -> usize {
2888        match self {
2889            None => 0,
2890            Some(i) => i.count_some(),
2891        }
2892    }
2893    fn count(&self) -> usize {
2894        match self {
2895            None => 0,
2896            Some(i) => i.count(),
2897        }
2898    }
2899}
2900
2901impl<T> Length for Vec<Option<T>> {
2902    fn count_some(&self) -> usize {
2903        self.iter().filter(|v| v.is_some()).count()
2904    }
2905
2906    fn count(&self) -> usize {
2907        self.len()
2908    }
2909}
2910
2911impl<K, T> Length for Vec<(K, Option<T>)> {
2912    fn count_some(&self) -> usize {
2913        self.iter().filter(|(_, v)| v.is_some()).count()
2914    }
2915
2916    fn count(&self) -> usize {
2917        self.len()
2918    }
2919}
2920
2921impl<K, T> Length for BTreeMap<K, Option<T>> {
2922    fn count_some(&self) -> usize {
2923        self.values().filter(|v| v.is_some()).count()
2924    }
2925
2926    fn count(&self) -> usize {
2927        self.len()
2928    }
2929}
2930
2931impl<K, T> Length for IndexMap<K, Option<T>> {
2932    fn count_some(&self) -> usize {
2933        self.values().filter(|v| v.is_some()).count()
2934    }
2935
2936    fn count(&self) -> usize {
2937        self.len()
2938    }
2939}
2940
2941/// Implement Length trait for a type
2942///
2943/// # Arguments
2944///
2945/// * `$object`: The type for which to implement Length trait
2946/// * `$len`: A closure that returns the length of the object; if omitted, defaults to 1
2947macro_rules! define_length {
2948    ($object:ty,$some:expr,$counter:expr) => {
2949        impl Length for $object {
2950            fn count_some(&self) -> usize {
2951                #[allow(clippy::redundant_closure_call)]
2952                $some(self)
2953            }
2954
2955            fn count(&self) -> usize {
2956                #[allow(clippy::redundant_closure_call)]
2957                $counter(self)
2958            }
2959        }
2960    };
2961    ($object:ty,$some:expr) => {
2962        define_length!($object, $some, $some);
2963    };
2964    ($object:ty) => {
2965        define_length!($object, |_| 1, |_| 1);
2966    };
2967}
2968
2969define_length!(DataContract);
2970define_length!(DataContractHistory, |d: &DataContractHistory| d.len());
2971define_length!(DocumentHistory, |d: &DocumentHistory| d.len());
2972define_length!(Document);
2973define_length!(Identity);
2974define_length!(IdentityBalance);
2975define_length!(IdentityBalanceAndRevision);
2976define_length!(
2977    IdentitiesContractKeys,
2978    |x: &IdentitiesContractKeys| x.values().map(|v| v.count_some()).sum(),
2979    |x: &IdentitiesContractKeys| x.len()
2980);
2981define_length!(ContestedResources, |x: &ContestedResources| x.0.len());
2982define_length!(Contenders, |x: &Contenders| x.contenders.len());
2983define_length!(Voters, |x: &Voters| x.0.len());
2984define_length!(
2985    VotePollsGroupedByTimestamp,
2986    |x: &VotePollsGroupedByTimestamp| x.0.iter().map(|v| v.1.len()).sum(),
2987    |x: &VotePollsGroupedByTimestamp| x.0.len()
2988);
2989
2990/// Convert a type into an Option
2991trait IntoOption
2992where
2993    Self: Sized,
2994{
2995    /// For zero-length data structures, return None, otherwise return Some(self).
2996    ///
2997    /// In case of a zero-length data structure, the function returns None.
2998    /// Otherwise, it returns Some(self), even it all values are None. This is to ensure that proof of absence
2999    /// preserves the keys that are not present in the data structure.
3000    fn into_option(self) -> Option<Self>;
3001}
3002
3003impl<L: Length> IntoOption for L {
3004    fn into_option(self) -> Option<Self>
3005    where
3006        Self: Sized,
3007    {
3008        if self.count() == 0 {
3009            None
3010        } else {
3011            Some(self)
3012        }
3013    }
3014}
3015
3016#[cfg(test)]
3017mod tests {
3018    use super::*;
3019
3020    #[test]
3021    fn try_u32_to_u16_succeeds_for_valid_values() {
3022        assert_eq!(try_u32_to_u16(0).unwrap(), 0u16);
3023        assert_eq!(try_u32_to_u16(1).unwrap(), 1u16);
3024        assert_eq!(try_u32_to_u16(42).unwrap(), 42u16);
3025        assert_eq!(try_u32_to_u16(u16::MAX as u32).unwrap(), u16::MAX);
3026    }
3027
3028    #[test]
3029    fn try_u32_to_u16_errors_on_overflow() {
3030        // This is the exact attack vector: epoch 65536 would silently truncate
3031        // to 0 with `as u16`, allowing a malicious node to serve a proof for
3032        // epoch 0 while claiming the metadata epoch is 65536.
3033        let result = try_u32_to_u16(65536);
3034        assert!(
3035            result.is_err(),
3036            "epoch 65536 must not silently truncate to 0"
3037        );
3038
3039        let result = try_u32_to_u16(u32::MAX);
3040        assert!(result.is_err(), "epoch u32::MAX must not silently truncate");
3041
3042        let result = try_u32_to_u16(100_000);
3043        assert!(result.is_err(), "epoch 100000 must not silently truncate");
3044    }
3045
3046    #[test]
3047    fn u32_to_u16_opt_succeeds_for_valid_values() {
3048        assert_eq!(u32_to_u16_opt(0).unwrap(), None);
3049        assert_eq!(u32_to_u16_opt(1).unwrap(), Some(1u16));
3050        assert_eq!(u32_to_u16_opt(u16::MAX as u32).unwrap(), Some(u16::MAX));
3051    }
3052
3053    #[test]
3054    fn u32_to_u16_opt_errors_on_overflow() {
3055        let result = u32_to_u16_opt(65536);
3056        assert!(
3057            result.is_err(),
3058            "value 65536 must not silently truncate to 0"
3059        );
3060
3061        let result = u32_to_u16_opt(u32::MAX);
3062        assert!(result.is_err(), "value u32::MAX must not silently truncate");
3063    }
3064
3065    // ---------------------------------------------------------------------
3066    // Length / IntoOption trait tests
3067    // ---------------------------------------------------------------------
3068
3069    #[test]
3070    fn length_vec_option_counts_some_and_total() {
3071        let v: Vec<Option<u32>> = vec![Some(1), None, Some(2), None, Some(3)];
3072        assert_eq!(v.count(), 5);
3073        assert_eq!(v.count_some(), 3);
3074
3075        let empty: Vec<Option<u32>> = vec![];
3076        assert_eq!(empty.count(), 0);
3077        assert_eq!(empty.count_some(), 0);
3078    }
3079
3080    #[test]
3081    fn length_option_of_length_delegates() {
3082        let inner: Vec<Option<u32>> = vec![Some(1), None];
3083        let some_inner: Option<Vec<Option<u32>>> = Some(inner);
3084        assert_eq!(some_inner.count(), 2);
3085        assert_eq!(some_inner.count_some(), 1);
3086
3087        let none_inner: Option<Vec<Option<u32>>> = None;
3088        assert_eq!(none_inner.count(), 0);
3089        assert_eq!(none_inner.count_some(), 0);
3090    }
3091
3092    #[test]
3093    fn length_vec_of_key_option_pair() {
3094        let v: Vec<(u8, Option<u32>)> = vec![(1, Some(10)), (2, None), (3, Some(30)), (4, None)];
3095        assert_eq!(v.count(), 4);
3096        assert_eq!(v.count_some(), 2);
3097    }
3098
3099    #[test]
3100    fn length_btreemap_of_option() {
3101        let mut m: BTreeMap<u8, Option<u32>> = BTreeMap::new();
3102        m.insert(1, Some(10));
3103        m.insert(2, None);
3104        m.insert(3, Some(30));
3105        assert_eq!(m.count(), 3);
3106        assert_eq!(m.count_some(), 2);
3107    }
3108
3109    #[test]
3110    fn length_indexmap_of_option() {
3111        let mut m: IndexMap<u8, Option<u32>> = IndexMap::new();
3112        m.insert(1, Some(10));
3113        m.insert(2, None);
3114        m.insert(3, Some(30));
3115        m.insert(4, None);
3116        assert_eq!(m.count(), 4);
3117        assert_eq!(m.count_some(), 2);
3118    }
3119
3120    #[test]
3121    fn into_option_returns_none_for_empty_and_some_for_nonempty() {
3122        // Empty collection -> None
3123        let empty: Vec<Option<u32>> = vec![];
3124        assert!(empty.into_option().is_none());
3125
3126        // Non-empty, even if all are None -> Some(self)
3127        let all_none: Vec<Option<u32>> = vec![None, None];
3128        let wrapped = all_none.into_option();
3129        assert!(wrapped.is_some());
3130        assert_eq!(wrapped.unwrap().len(), 2);
3131
3132        // Non-empty with some values -> Some(self)
3133        let mixed: Vec<Option<u32>> = vec![Some(1), None];
3134        assert!(mixed.into_option().is_some());
3135    }
3136
3137    #[test]
3138    fn into_option_for_indexmap() {
3139        let empty: IndexMap<u8, Option<u32>> = IndexMap::new();
3140        assert!(empty.into_option().is_none());
3141
3142        let mut m: IndexMap<u8, Option<u32>> = IndexMap::new();
3143        m.insert(1, None); // only None value, but count() > 0
3144        let wrapped = m.into_option();
3145        assert!(
3146            wrapped.is_some(),
3147            "IntoOption must preserve maps that carry absence markers"
3148        );
3149    }
3150
3151    // ---------------------------------------------------------------------
3152    // parse_key_request_type tests
3153    // ---------------------------------------------------------------------
3154
3155    #[test]
3156    fn parse_key_request_type_missing_outer_request() {
3157        let err = parse_key_request_type(&None)
3158            .err()
3159            .expect("None input must error");
3160        match err {
3161            Error::RequestError { error } => {
3162                assert!(
3163                    error.contains("missing key request type"),
3164                    "unexpected error message: {error}"
3165                );
3166            }
3167            other => panic!("expected RequestError, got: {other:?}"),
3168        }
3169    }
3170
3171    #[test]
3172    fn parse_key_request_type_missing_inner_request_field() {
3173        // Outer Some, inner `request` is None -> second `ok_or` triggers.
3174        let outer = Some(GrpcKeyType { request: None });
3175        let err = parse_key_request_type(&outer)
3176            .err()
3177            .expect("missing request must error");
3178        match err {
3179            Error::RequestError { error } => {
3180                assert!(
3181                    error.contains("empty request field"),
3182                    "unexpected error message: {error}"
3183                );
3184            }
3185            other => panic!("expected RequestError, got: {other:?}"),
3186        }
3187    }
3188
3189    #[test]
3190    fn parse_key_request_type_all_keys_variant() {
3191        use dapi_grpc::platform::v0::AllKeys;
3192        let outer = Some(GrpcKeyType {
3193            request: Some(key_request_type::Request::AllKeys(AllKeys {})),
3194        });
3195        let parsed = parse_key_request_type(&outer).unwrap();
3196        assert!(matches!(parsed, KeyRequestType::AllKeys));
3197    }
3198
3199    #[test]
3200    fn parse_key_request_type_specific_keys_variant() {
3201        use dapi_grpc::platform::v0::SpecificKeys;
3202        let outer = Some(GrpcKeyType {
3203            request: Some(key_request_type::Request::SpecificKeys(SpecificKeys {
3204                key_ids: vec![1, 2, 3],
3205            })),
3206        });
3207        let parsed = parse_key_request_type(&outer).unwrap();
3208        match parsed {
3209            KeyRequestType::SpecificKeys(ids) => assert_eq!(ids, vec![1, 2, 3]),
3210            _ => panic!("expected SpecificKeys variant"),
3211        }
3212    }
3213
3214    #[test]
3215    fn parse_key_request_type_search_key_rejects_invalid_kind() {
3216        use dapi_grpc::platform::v0::{SearchKey, SecurityLevelMap};
3217        let mut sec_map: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
3218        // 99 is not a valid GrpcKeyKind, must produce RequestError
3219        sec_map.insert(0, 99);
3220
3221        let mut purpose_map = std::collections::HashMap::new();
3222        purpose_map.insert(
3223            0u32,
3224            SecurityLevelMap {
3225                security_level_map: sec_map,
3226            },
3227        );
3228
3229        let outer = Some(GrpcKeyType {
3230            request: Some(key_request_type::Request::SearchKey(SearchKey {
3231                purpose_map,
3232            })),
3233        });
3234
3235        let err = parse_key_request_type(&outer)
3236            .err()
3237            .expect("bad key kind must error");
3238        match err {
3239            Error::RequestError { error } => assert!(
3240                error.contains("missing requested key type"),
3241                "unexpected error: {error}"
3242            ),
3243            other => panic!("expected RequestError for bad key kind, got: {other:?}"),
3244        }
3245    }
3246
3247    #[test]
3248    fn parse_key_request_type_search_key_accepts_valid_kinds() {
3249        use dapi_grpc::platform::v0::{SearchKey, SecurityLevelMap};
3250        let mut sec_map: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
3251        sec_map.insert(0, GrpcKeyKind::CurrentKeyOfKindRequest as i32);
3252        sec_map.insert(1, GrpcKeyKind::AllKeysOfKindRequest as i32);
3253
3254        let mut purpose_map = std::collections::HashMap::new();
3255        purpose_map.insert(
3256            0u32,
3257            SecurityLevelMap {
3258                security_level_map: sec_map,
3259            },
3260        );
3261
3262        let outer = Some(GrpcKeyType {
3263            request: Some(key_request_type::Request::SearchKey(SearchKey {
3264                purpose_map,
3265            })),
3266        });
3267
3268        let parsed = parse_key_request_type(&outer).unwrap();
3269        match parsed {
3270            KeyRequestType::SearchKey(purposes) => {
3271                let inner = purposes.get(&0u8).expect("purpose 0 parsed");
3272                assert_eq!(inner.len(), 2);
3273                assert!(matches!(
3274                    inner.get(&0u8),
3275                    Some(KeyKindRequestType::CurrentKeyOfKindRequest)
3276                ));
3277                assert!(matches!(
3278                    inner.get(&1u8),
3279                    Some(KeyKindRequestType::AllKeysOfKindRequest)
3280                ));
3281            }
3282            _ => panic!("expected SearchKey variant"),
3283        }
3284    }
3285
3286    // ---------------------------------------------------------------------
3287    // FromProof error-path tests
3288    //
3289    // These tests verify that response/request decoding errors fire
3290    // *before* any cryptographic proof verification is attempted, so we
3291    // don't need a real quorum or GroveDB proof to exercise them.
3292    // ---------------------------------------------------------------------
3293
3294    /// A ContextProvider that must never be called during these tests —
3295    /// if it is, the test has reached the cryptographic-verification stage
3296    /// incorrectly, which is itself a meaningful failure.
3297    struct UnreachableContextProvider;
3298
3299    impl dash_context_provider::ContextProvider for UnreachableContextProvider {
3300        fn get_data_contract(
3301            &self,
3302            _id: &dpp::prelude::Identifier,
3303            _platform_version: &PlatformVersion,
3304        ) -> Result<Option<std::sync::Arc<DataContract>>, dash_context_provider::ContextProviderError>
3305        {
3306            panic!("context provider should not be called on decode-error test")
3307        }
3308
3309        fn get_token_configuration(
3310            &self,
3311            _token_id: &dpp::prelude::Identifier,
3312        ) -> Result<
3313            Option<dpp::data_contract::TokenConfiguration>,
3314            dash_context_provider::ContextProviderError,
3315        > {
3316            panic!("context provider should not be called on decode-error test")
3317        }
3318
3319        fn get_quorum_public_key(
3320            &self,
3321            _quorum_type: u32,
3322            _quorum_hash: [u8; 32],
3323            _core_chain_locked_height: u32,
3324        ) -> Result<[u8; 48], dash_context_provider::ContextProviderError> {
3325            panic!("context provider should not be called on decode-error test")
3326        }
3327
3328        fn get_platform_activation_height(
3329            &self,
3330        ) -> Result<dpp::prelude::CoreBlockHeight, dash_context_provider::ContextProviderError>
3331        {
3332            panic!("context provider should not be called on decode-error test")
3333        }
3334    }
3335
3336    fn unreachable_provider() -> UnreachableContextProvider {
3337        UnreachableContextProvider
3338    }
3339
3340    fn default_platform_version() -> &'static PlatformVersion {
3341        PlatformVersion::latest()
3342    }
3343
3344    struct NoDataContractProvider;
3345
3346    impl dash_context_provider::ContextProvider for NoDataContractProvider {
3347        fn get_data_contract(
3348            &self,
3349            _id: &dpp::prelude::Identifier,
3350            _platform_version: &PlatformVersion,
3351        ) -> Result<Option<std::sync::Arc<DataContract>>, dash_context_provider::ContextProviderError>
3352        {
3353            Ok(None)
3354        }
3355
3356        fn get_token_configuration(
3357            &self,
3358            _token_id: &dpp::prelude::Identifier,
3359        ) -> Result<
3360            Option<dpp::data_contract::TokenConfiguration>,
3361            dash_context_provider::ContextProviderError,
3362        > {
3363            panic!("token configuration should not be requested")
3364        }
3365
3366        fn get_quorum_public_key(
3367            &self,
3368            _quorum_type: u32,
3369            _quorum_hash: [u8; 32],
3370            _core_chain_locked_height: u32,
3371        ) -> Result<[u8; 48], dash_context_provider::ContextProviderError> {
3372            panic!("quorum public key should not be requested")
3373        }
3374
3375        fn get_platform_activation_height(
3376            &self,
3377        ) -> Result<dpp::prelude::CoreBlockHeight, dash_context_provider::ContextProviderError>
3378        {
3379            panic!("platform activation height should not be requested")
3380        }
3381    }
3382
3383    struct StaticDataContractProvider {
3384        data_contract: std::sync::Arc<DataContract>,
3385    }
3386
3387    impl dash_context_provider::ContextProvider for StaticDataContractProvider {
3388        fn get_data_contract(
3389            &self,
3390            id: &dpp::prelude::Identifier,
3391            _platform_version: &PlatformVersion,
3392        ) -> Result<Option<std::sync::Arc<DataContract>>, dash_context_provider::ContextProviderError>
3393        {
3394            if self.data_contract.id() == *id {
3395                Ok(Some(self.data_contract.clone()))
3396            } else {
3397                Ok(None)
3398            }
3399        }
3400
3401        fn get_token_configuration(
3402            &self,
3403            _token_id: &dpp::prelude::Identifier,
3404        ) -> Result<
3405            Option<dpp::data_contract::TokenConfiguration>,
3406            dash_context_provider::ContextProviderError,
3407        > {
3408            panic!("token configuration should not be requested")
3409        }
3410
3411        fn get_quorum_public_key(
3412            &self,
3413            _quorum_type: u32,
3414            _quorum_hash: [u8; 32],
3415            _core_chain_locked_height: u32,
3416        ) -> Result<[u8; 48], dash_context_provider::ContextProviderError> {
3417            panic!("quorum public key should not be requested")
3418        }
3419
3420        fn get_platform_activation_height(
3421            &self,
3422        ) -> Result<dpp::prelude::CoreBlockHeight, dash_context_provider::ContextProviderError>
3423        {
3424            panic!("platform activation height should not be requested")
3425        }
3426    }
3427
3428    /// Build a fully-populated `GetIdentityResponse` shell so that
3429    /// `response.proof()` and `response.metadata()` both succeed. The
3430    /// enclosed proof is empty, so any real verification would fail — but
3431    /// these tests stop before that point.
3432    fn identity_response_with_proof_and_metadata() -> platform::GetIdentityResponse {
3433        use platform::get_identity_response::{
3434            get_identity_response_v0::Result as V0Result, GetIdentityResponseV0, Version,
3435        };
3436        platform::GetIdentityResponse {
3437            version: Some(Version::V0(GetIdentityResponseV0 {
3438                result: Some(V0Result::Proof(Proof::default())),
3439                metadata: Some(ResponseMetadata::default()),
3440            })),
3441        }
3442    }
3443
3444    fn document_history_response_with_proof_and_metadata() -> platform::GetDocumentHistoryResponse {
3445        use platform::get_document_history_response::{
3446            get_document_history_response_v0::Result as V0Result, GetDocumentHistoryResponseV0,
3447            Version,
3448        };
3449        platform::GetDocumentHistoryResponse {
3450            version: Some(Version::V0(GetDocumentHistoryResponseV0 {
3451                result: Some(V0Result::Proof(Proof::default())),
3452                metadata: Some(ResponseMetadata::default()),
3453            })),
3454        }
3455    }
3456
3457    fn document_history_request(
3458        data_contract_id: Vec<u8>,
3459        document_type_name: &str,
3460        document_id: Vec<u8>,
3461        limit: Option<u32>,
3462        offset: Option<u32>,
3463    ) -> platform::GetDocumentHistoryRequest {
3464        use dapi_grpc::platform::v0::get_document_history_request::GetDocumentHistoryRequestV0;
3465        GetDocumentHistoryRequestV0 {
3466            data_contract_id,
3467            document_type_name: document_type_name.to_string(),
3468            document_id,
3469            limit,
3470            offset,
3471            start_at_ms: 0,
3472            prove: true,
3473        }
3474        .into()
3475    }
3476
3477    fn document_history_error(
3478        request: platform::GetDocumentHistoryRequest,
3479        response: platform::GetDocumentHistoryResponse,
3480        provider: &dyn ContextProvider,
3481    ) -> Error {
3482        <DocumentHistory as FromProof<
3483            platform::GetDocumentHistoryRequest,
3484        >>::maybe_from_proof_with_metadata(
3485            request,
3486            response,
3487            Network::Testnet,
3488            default_platform_version(),
3489            provider,
3490        )
3491        .unwrap_err()
3492    }
3493
3494    #[test]
3495    fn identity_from_proof_no_proof_when_response_empty() {
3496        // Default response has `version: None` -> response.proof() errors
3497        // -> mapped to NoProofInResult.
3498        let request = platform::GetIdentityRequest::default();
3499        let response = platform::GetIdentityResponse::default();
3500
3501        let provider = unreachable_provider();
3502        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3503            request,
3504            response,
3505            Network::Testnet,
3506            default_platform_version(),
3507            &provider,
3508        )
3509        .unwrap_err();
3510
3511        assert!(
3512            matches!(err, Error::NoProofInResult),
3513            "expected NoProofInResult, got: {err:?}"
3514        );
3515    }
3516
3517    #[test]
3518    fn identity_from_proof_empty_metadata_when_metadata_missing() {
3519        use platform::get_identity_response::{
3520            get_identity_response_v0::Result as V0Result, GetIdentityResponseV0, Version,
3521        };
3522        // Response has a Proof but no metadata -> EmptyResponseMetadata.
3523        let response = platform::GetIdentityResponse {
3524            version: Some(Version::V0(GetIdentityResponseV0 {
3525                result: Some(V0Result::Proof(Proof::default())),
3526                metadata: None,
3527            })),
3528        };
3529        let request = platform::GetIdentityRequest::default();
3530        let provider = unreachable_provider();
3531        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3532            request,
3533            response,
3534            Network::Testnet,
3535            default_platform_version(),
3536            &provider,
3537        )
3538        .unwrap_err();
3539        assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
3540    }
3541
3542    #[test]
3543    fn identity_from_proof_empty_version_when_request_has_no_version() {
3544        // Valid response, but request.version is None -> EmptyVersion.
3545        let response = identity_response_with_proof_and_metadata();
3546        let request = platform::GetIdentityRequest { version: None };
3547        let provider = unreachable_provider();
3548        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3549            request,
3550            response,
3551            Network::Testnet,
3552            default_platform_version(),
3553            &provider,
3554        )
3555        .unwrap_err();
3556        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
3557    }
3558
3559    #[test]
3560    fn identity_from_proof_protocol_error_on_bad_id_length() {
3561        use dapi_grpc::platform::v0::get_identity_request::GetIdentityRequestV0;
3562        // id must be 32 bytes; anything else fails Identifier::from_bytes.
3563        let request: platform::GetIdentityRequest = GetIdentityRequestV0 {
3564            id: vec![0u8; 8],
3565            prove: true,
3566        }
3567        .into();
3568        let response = identity_response_with_proof_and_metadata();
3569        let provider = unreachable_provider();
3570        let err = <Identity as FromProof<platform::GetIdentityRequest>>::maybe_from_proof(
3571            request,
3572            response,
3573            Network::Testnet,
3574            default_platform_version(),
3575            &provider,
3576        )
3577        .unwrap_err();
3578        assert!(
3579            matches!(err, Error::ProtocolError { .. }),
3580            "expected ProtocolError on bad id length, got: {err:?}"
3581        );
3582    }
3583
3584    /// A minimal `FromProof` impl whose `maybe_from_proof_with_metadata`
3585    /// returns `Ok((None, ..))`, isolating the `from_proof` wrapper's
3586    /// `None -> Error::NotFound` mapping from the decode/verify pipeline.
3587    #[derive(Debug)]
3588    struct MissingFromProof;
3589
3590    impl FromProof<()> for MissingFromProof {
3591        type Request = ();
3592        type Response = ();
3593
3594        fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
3595            _request: I,
3596            _response: O,
3597            _network: Network,
3598            _platform_version: &PlatformVersion,
3599            _provider: &'a dyn ContextProvider,
3600        ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
3601        where
3602            Self: Sized + 'a,
3603        {
3604            Ok((None, ResponseMetadata::default(), Proof::default()))
3605        }
3606    }
3607
3608    #[test]
3609    fn from_proof_maps_none_to_not_found() {
3610        // `from_proof` (vs `maybe_from_proof`) is expected to map `Ok(None)`
3611        // to `Error::NotFound`. Verify that wrapper behavior directly rather
3612        // than conflating it with decode-error propagation.
3613        let provider = unreachable_provider();
3614        let err = <MissingFromProof as FromProof<()>>::from_proof(
3615            (),
3616            (),
3617            Network::Testnet,
3618            default_platform_version(),
3619            &provider,
3620        )
3621        .unwrap_err();
3622        assert!(
3623            matches!(err, Error::NotFound),
3624            "expected NotFound when maybe_from_proof returns None, got: {err:?}"
3625        );
3626    }
3627
3628    #[test]
3629    fn identity_by_public_key_hash_invalid_length_yields_drive_error() {
3630        use dapi_grpc::platform::v0::get_identity_by_public_key_hash_request::GetIdentityByPublicKeyHashRequestV0;
3631
3632        // public_key_hash must be exactly 20 bytes; 10 bytes fails.
3633        let request: platform::GetIdentityByPublicKeyHashRequest =
3634            GetIdentityByPublicKeyHashRequestV0 {
3635                public_key_hash: vec![0u8; 10],
3636                prove: true,
3637            }
3638            .into();
3639
3640        // Build a response that succeeds on proof/metadata lookups.
3641        use platform::get_identity_by_public_key_hash_response::{
3642            get_identity_by_public_key_hash_response_v0::Result as V0Result,
3643            GetIdentityByPublicKeyHashResponseV0, Version,
3644        };
3645        let response = platform::GetIdentityByPublicKeyHashResponse {
3646            version: Some(Version::V0(GetIdentityByPublicKeyHashResponseV0 {
3647                result: Some(V0Result::Proof(Proof::default())),
3648                metadata: Some(ResponseMetadata::default()),
3649            })),
3650        };
3651
3652        let provider = unreachable_provider();
3653        let err =
3654            <Identity as FromProof<platform::GetIdentityByPublicKeyHashRequest>>::maybe_from_proof(
3655                request,
3656                response,
3657                Network::Testnet,
3658                default_platform_version(),
3659                &provider,
3660            )
3661            .unwrap_err();
3662
3663        match err {
3664            Error::DriveError { error } => {
3665                assert!(
3666                    error.contains("Invalid public key hash length"),
3667                    "unexpected error body: {error}"
3668                );
3669            }
3670            other => panic!("expected DriveError, got: {other:?}"),
3671        }
3672    }
3673
3674    #[test]
3675    fn identity_by_non_unique_public_key_hash_rejects_bad_key_hash_length() {
3676        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3677        use platform::get_identity_by_non_unique_public_key_hash_response::{
3678            get_identity_by_non_unique_public_key_hash_response_v0::Result as V0Result,
3679            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3680        };
3681
3682        // Build a response with a proved result so we get past the response shape check
3683        // and hit the request validation.
3684        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3685            version: Some(Version::V0(
3686                GetIdentityByNonUniquePublicKeyHashResponseV0 {
3687                    result: Some(V0Result::Proof(
3688                        dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_response::get_identity_by_non_unique_public_key_hash_response_v0::IdentityProvedResponse {
3689                            identity_proof_bytes: None,
3690                            grovedb_identity_public_key_hash_proof: Some(Proof::default()),
3691                        },
3692                    )),
3693                    metadata: Some(ResponseMetadata::default()),
3694                },
3695            )),
3696        };
3697
3698        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3699            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3700                public_key_hash: vec![0u8; 3], // must be 20 bytes
3701                start_after: None,
3702                prove: true,
3703            }
3704            .into();
3705
3706        let provider = unreachable_provider();
3707        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3708            request,
3709            response,
3710            Network::Testnet,
3711            default_platform_version(),
3712            &provider,
3713        )
3714        .unwrap_err();
3715
3716        match err {
3717            Error::RequestError { error } => {
3718                assert!(
3719                    error.contains("Invalid public key hash length"),
3720                    "got: {error}"
3721                );
3722            }
3723            other => panic!("expected RequestError, got: {other:?}"),
3724        }
3725    }
3726
3727    #[test]
3728    fn identity_by_non_unique_public_key_hash_rejects_bad_start_after_length() {
3729        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3730        use platform::get_identity_by_non_unique_public_key_hash_response::{
3731            get_identity_by_non_unique_public_key_hash_response_v0::Result as V0Result,
3732            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3733        };
3734
3735        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3736            version: Some(Version::V0(
3737                GetIdentityByNonUniquePublicKeyHashResponseV0 {
3738                    result: Some(V0Result::Proof(
3739                        dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_response::get_identity_by_non_unique_public_key_hash_response_v0::IdentityProvedResponse {
3740                            identity_proof_bytes: None,
3741                            grovedb_identity_public_key_hash_proof: Some(Proof::default()),
3742                        },
3743                    )),
3744                    metadata: Some(ResponseMetadata::default()),
3745                },
3746            )),
3747        };
3748
3749        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3750            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3751                public_key_hash: vec![0u8; 20],   // good
3752                start_after: Some(vec![0u8; 10]), // wrong length; must be 32
3753                prove: true,
3754            }
3755            .into();
3756
3757        let provider = unreachable_provider();
3758        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3759            request,
3760            response,
3761            Network::Testnet,
3762            default_platform_version(),
3763            &provider,
3764        )
3765        .unwrap_err();
3766
3767        match err {
3768            Error::RequestError { error } => {
3769                assert!(error.contains("Invalid start_after length"), "got: {error}");
3770            }
3771            other => panic!("expected RequestError for start_after, got: {other:?}"),
3772        }
3773    }
3774
3775    #[test]
3776    fn identity_by_non_unique_public_key_hash_rejects_v0_inner_proof() {
3777        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3778        use platform::get_identity_by_non_unique_public_key_hash_response::{
3779            get_identity_by_non_unique_public_key_hash_response_v0::{
3780                IdentityProvedResponse, Result as V0Result,
3781            },
3782            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3783        };
3784
3785        let config = bincode::config::standard().with_big_endian();
3786        let v0_inner_proof =
3787            bincode::encode_to_vec(0u32, config).expect("V0 envelope discriminant should encode");
3788        let v1_outer_proof =
3789            bincode::encode_to_vec(1u32, config).expect("V1 envelope discriminant should encode");
3790        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3791            version: Some(Version::V0(GetIdentityByNonUniquePublicKeyHashResponseV0 {
3792                result: Some(V0Result::Proof(IdentityProvedResponse {
3793                    identity_proof_bytes: Some(v0_inner_proof),
3794                    grovedb_identity_public_key_hash_proof: Some(Proof {
3795                        grovedb_proof: v1_outer_proof,
3796                        ..Proof::default()
3797                    }),
3798                })),
3799                metadata: Some(ResponseMetadata::default()),
3800            })),
3801        };
3802        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3803            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3804                public_key_hash: vec![0u8; 20],
3805                start_after: None,
3806                prove: true,
3807            }
3808            .into();
3809
3810        let err = <Identity as FromProof<
3811            platform::GetIdentityByNonUniquePublicKeyHashRequest,
3812        >>::maybe_from_proof(
3813            request,
3814            response,
3815            Network::Testnet,
3816            default_platform_version(),
3817            &unreachable_provider(),
3818        )
3819        .unwrap_err();
3820
3821        assert!(matches!(
3822            err,
3823            Error::UnsupportedGroveDBProofVersion { version: 0, .. }
3824        ));
3825    }
3826
3827    #[test]
3828    fn identity_by_non_unique_public_key_hash_rejects_v0_outer_proof() {
3829        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3830        use platform::get_identity_by_non_unique_public_key_hash_response::{
3831            get_identity_by_non_unique_public_key_hash_response_v0::{
3832                IdentityProvedResponse, Result as V0Result,
3833            },
3834            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3835        };
3836
3837        let config = bincode::config::standard().with_big_endian();
3838        let v1_inner_proof =
3839            bincode::encode_to_vec(1u32, config).expect("V1 envelope discriminant should encode");
3840        let v0_outer_proof =
3841            bincode::encode_to_vec(0u32, config).expect("V0 envelope discriminant should encode");
3842        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3843            version: Some(Version::V0(GetIdentityByNonUniquePublicKeyHashResponseV0 {
3844                result: Some(V0Result::Proof(IdentityProvedResponse {
3845                    identity_proof_bytes: Some(v1_inner_proof),
3846                    grovedb_identity_public_key_hash_proof: Some(Proof {
3847                        grovedb_proof: v0_outer_proof,
3848                        ..Proof::default()
3849                    }),
3850                })),
3851                metadata: Some(ResponseMetadata::default()),
3852            })),
3853        };
3854        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3855            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3856                public_key_hash: vec![0u8; 20],
3857                start_after: None,
3858                prove: true,
3859            }
3860            .into();
3861
3862        let err = <Identity as FromProof<
3863            platform::GetIdentityByNonUniquePublicKeyHashRequest,
3864        >>::maybe_from_proof(
3865            request,
3866            response,
3867            Network::Testnet,
3868            default_platform_version(),
3869            &unreachable_provider(),
3870        )
3871        .unwrap_err();
3872
3873        assert!(matches!(
3874            err,
3875            Error::UnsupportedGroveDBProofVersion { version: 0, .. }
3876        ));
3877    }
3878
3879    #[test]
3880    fn identity_by_non_unique_response_with_no_result_yields_no_proof() {
3881        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3882        use platform::get_identity_by_non_unique_public_key_hash_response::{
3883            GetIdentityByNonUniquePublicKeyHashResponseV0, Version,
3884        };
3885
3886        // v0 with result=None -> NoProofInResult on the `.ok_or` branch.
3887        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse {
3888            version: Some(Version::V0(GetIdentityByNonUniquePublicKeyHashResponseV0 {
3889                result: None,
3890                metadata: None,
3891            })),
3892        };
3893        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3894            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3895                public_key_hash: vec![0u8; 20],
3896                start_after: None,
3897                prove: true,
3898            }
3899            .into();
3900        let provider = unreachable_provider();
3901        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3902            request,
3903            response,
3904            Network::Testnet,
3905            default_platform_version(),
3906            &provider,
3907        )
3908        .unwrap_err();
3909        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
3910    }
3911
3912    #[test]
3913    fn identity_by_non_unique_response_with_no_version_yields_empty_metadata() {
3914        // response.version = None hits the `_ => EmptyResponseMetadata` arm.
3915        use dapi_grpc::platform::v0::get_identity_by_non_unique_public_key_hash_request::GetIdentityByNonUniquePublicKeyHashRequestV0;
3916        let response = platform::GetIdentityByNonUniquePublicKeyHashResponse { version: None };
3917        let request: platform::GetIdentityByNonUniquePublicKeyHashRequest =
3918            GetIdentityByNonUniquePublicKeyHashRequestV0 {
3919                public_key_hash: vec![0u8; 20],
3920                start_after: None,
3921                prove: true,
3922            }
3923            .into();
3924        let provider = unreachable_provider();
3925        let err = <Identity as FromProof<platform::GetIdentityByNonUniquePublicKeyHashRequest>>::maybe_from_proof(
3926            request,
3927            response,
3928            Network::Testnet,
3929            default_platform_version(),
3930            &provider,
3931        )
3932        .unwrap_err();
3933        assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
3934    }
3935
3936    #[test]
3937    fn identities_balances_rejects_non_32_byte_id() {
3938        use dapi_grpc::platform::v0::get_identities_balances_request::GetIdentitiesBalancesRequestV0;
3939        use platform::get_identities_balances_response::{
3940            get_identities_balances_response_v0::Result as V0Result,
3941            GetIdentitiesBalancesResponseV0, Version,
3942        };
3943
3944        let response = platform::GetIdentitiesBalancesResponse {
3945            version: Some(Version::V0(GetIdentitiesBalancesResponseV0 {
3946                result: Some(V0Result::Proof(Proof::default())),
3947                metadata: Some(ResponseMetadata::default()),
3948            })),
3949        };
3950
3951        let request: platform::GetIdentitiesBalancesRequest = GetIdentitiesBalancesRequestV0 {
3952            ids: vec![vec![0u8; 10]], // wrong length
3953            prove: true,
3954        }
3955        .into();
3956
3957        let provider = unreachable_provider();
3958        let err =
3959            <IdentityBalances as FromProof<platform::GetIdentitiesBalancesRequest>>::maybe_from_proof(
3960                request,
3961                response,
3962                Network::Testnet,
3963                default_platform_version(),
3964                &provider,
3965            )
3966            .unwrap_err();
3967        match err {
3968            Error::RequestError { error } => {
3969                assert!(error.contains("all 32 bytes"), "got: {error}");
3970            }
3971            other => panic!("expected RequestError, got: {other:?}"),
3972        }
3973    }
3974
3975    #[test]
3976    fn data_contracts_rejects_wrong_size_id() {
3977        use dapi_grpc::platform::v0::get_data_contracts_request::GetDataContractsRequestV0;
3978        use platform::get_data_contracts_response::{
3979            get_data_contracts_response_v0::Result as V0Result, GetDataContractsResponseV0, Version,
3980        };
3981
3982        let response = platform::GetDataContractsResponse {
3983            version: Some(Version::V0(GetDataContractsResponseV0 {
3984                result: Some(V0Result::Proof(Proof::default())),
3985                metadata: Some(ResponseMetadata::default()),
3986            })),
3987        };
3988        let request: platform::GetDataContractsRequest = GetDataContractsRequestV0 {
3989            ids: vec![vec![0u8; 20]], // must be 32 bytes
3990            prove: true,
3991        }
3992        .into();
3993        let provider = unreachable_provider();
3994        let err =
3995            <DataContracts as FromProof<platform::GetDataContractsRequest>>::maybe_from_proof(
3996                request,
3997                response,
3998                Network::Testnet,
3999                default_platform_version(),
4000                &provider,
4001            )
4002            .unwrap_err();
4003        match err {
4004            Error::RequestError { error } => {
4005                assert!(error.contains("wrong id size"), "got: {error}");
4006            }
4007            other => panic!("expected RequestError, got: {other:?}"),
4008        }
4009    }
4010
4011    #[test]
4012    fn upgrade_vote_status_rejects_bad_start_pro_tx_hash_length() {
4013        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_request::GetProtocolVersionUpgradeVoteStatusRequestV0;
4014        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_response::{
4015            get_protocol_version_upgrade_vote_status_response_v0::Result as V0Result,
4016            GetProtocolVersionUpgradeVoteStatusResponseV0, Version,
4017        };
4018
4019        let response = GetProtocolVersionUpgradeVoteStatusResponse {
4020            version: Some(Version::V0(GetProtocolVersionUpgradeVoteStatusResponseV0 {
4021                result: Some(V0Result::Proof(Proof::default())),
4022                metadata: Some(ResponseMetadata::default()),
4023            })),
4024        };
4025        // start_pro_tx_hash must be 32 bytes if non-empty.
4026        let request: GetProtocolVersionUpgradeVoteStatusRequest =
4027            GetProtocolVersionUpgradeVoteStatusRequestV0 {
4028                start_pro_tx_hash: vec![0u8; 5],
4029                count: 10,
4030                prove: true,
4031            }
4032            .into();
4033        let provider = unreachable_provider();
4034        let err = <MasternodeProtocolVotes as FromProof<
4035            GetProtocolVersionUpgradeVoteStatusRequest,
4036        >>::maybe_from_proof(
4037            request,
4038            response,
4039            Network::Testnet,
4040            default_platform_version(),
4041            &provider,
4042        )
4043        .unwrap_err();
4044        match err {
4045            Error::RequestError { .. } => {}
4046            other => panic!("expected RequestError for bad pro_tx_hash length, got: {other:?}"),
4047        }
4048    }
4049
4050    #[test]
4051    fn upgrade_vote_status_empty_version_on_request_none() {
4052        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_response::{
4053            get_protocol_version_upgrade_vote_status_response_v0::Result as V0Result,
4054            GetProtocolVersionUpgradeVoteStatusResponseV0, Version,
4055        };
4056        let response = GetProtocolVersionUpgradeVoteStatusResponse {
4057            version: Some(Version::V0(GetProtocolVersionUpgradeVoteStatusResponseV0 {
4058                result: Some(V0Result::Proof(Proof::default())),
4059                metadata: Some(ResponseMetadata::default()),
4060            })),
4061        };
4062        let request = GetProtocolVersionUpgradeVoteStatusRequest { version: None };
4063        let provider = unreachable_provider();
4064        let err = <MasternodeProtocolVotes as FromProof<
4065            GetProtocolVersionUpgradeVoteStatusRequest,
4066        >>::maybe_from_proof(
4067            request,
4068            response,
4069            Network::Testnet,
4070            default_platform_version(),
4071            &provider,
4072        )
4073        .unwrap_err();
4074        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4075    }
4076
4077    #[test]
4078    fn path_elements_no_proof_without_response() {
4079        let request = GetPathElementsRequest::default();
4080        let response = GetPathElementsResponse::default();
4081        let provider = unreachable_provider();
4082        let err = <Elements as FromProof<GetPathElementsRequest>>::maybe_from_proof(
4083            request,
4084            response,
4085            Network::Testnet,
4086            default_platform_version(),
4087            &provider,
4088        )
4089        .unwrap_err();
4090        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4091    }
4092
4093    #[test]
4094    fn prefunded_balance_rejects_bad_id_length() {
4095        use dapi_grpc::platform::v0::get_prefunded_specialized_balance_request::GetPrefundedSpecializedBalanceRequestV0;
4096        use platform::get_prefunded_specialized_balance_response::{
4097            get_prefunded_specialized_balance_response_v0::Result as V0Result,
4098            GetPrefundedSpecializedBalanceResponseV0, Version,
4099        };
4100        let response = platform::GetPrefundedSpecializedBalanceResponse {
4101            version: Some(Version::V0(GetPrefundedSpecializedBalanceResponseV0 {
4102                result: Some(V0Result::Proof(Proof::default())),
4103                metadata: Some(ResponseMetadata::default()),
4104            })),
4105        };
4106        let request: platform::GetPrefundedSpecializedBalanceRequest =
4107            GetPrefundedSpecializedBalanceRequestV0 {
4108                id: vec![0u8; 3], // must be 32
4109                prove: true,
4110            }
4111            .into();
4112        let provider = unreachable_provider();
4113        let err = <PrefundedSpecializedBalance as FromProof<
4114            platform::GetPrefundedSpecializedBalanceRequest,
4115        >>::maybe_from_proof(
4116            request,
4117            response,
4118            Network::Testnet,
4119            default_platform_version(),
4120            &provider,
4121        )
4122        .unwrap_err();
4123        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4124    }
4125
4126    #[test]
4127    fn epochs_info_rejects_overflowing_start_epoch() {
4128        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
4129        use platform::get_epochs_info_response::{
4130            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
4131        };
4132        let response = platform::GetEpochsInfoResponse {
4133            version: Some(Version::V0(GetEpochsInfoResponseV0 {
4134                result: Some(V0Result::Proof(Proof::default())),
4135                metadata: Some(ResponseMetadata {
4136                    epoch: 10,
4137                    ..Default::default()
4138                }),
4139            })),
4140        };
4141        // start_epoch > u16::MAX triggers try_u32_to_u16 error.
4142        let request = platform::GetEpochsInfoRequest {
4143            version: Some(platform::get_epochs_info_request::Version::V0(
4144                GetEpochsInfoRequestV0 {
4145                    start_epoch: Some(100_000),
4146                    count: 1,
4147                    ascending: true,
4148                    prove: true,
4149                },
4150            )),
4151        };
4152        let provider = unreachable_provider();
4153        let err =
4154            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
4155                request,
4156                response,
4157                Network::Testnet,
4158                default_platform_version(),
4159                &provider,
4160            )
4161            .unwrap_err();
4162        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4163    }
4164
4165    #[test]
4166    fn broadcast_state_transition_rejects_garbage_payload() {
4167        // Cannot deserialize random bytes into a StateTransition, so we hit
4168        // the ProtocolError branch before any proof work happens.
4169        let request = platform::BroadcastStateTransitionRequest {
4170            state_transition: vec![0xFFu8; 16], // nonsense bytes
4171        };
4172        // Response structure only needs to have a valid proof field since
4173        // deserialize happens after proof extraction.
4174        use platform::wait_for_state_transition_result_response::{
4175            wait_for_state_transition_result_response_v0::Result as V0Result, Version,
4176            WaitForStateTransitionResultResponseV0,
4177        };
4178        let response = platform::WaitForStateTransitionResultResponse {
4179            version: Some(Version::V0(WaitForStateTransitionResultResponseV0 {
4180                result: Some(V0Result::Proof(Proof::default())),
4181                metadata: Some(ResponseMetadata::default()),
4182            })),
4183        };
4184        let provider = unreachable_provider();
4185        let err = <StateTransitionProofOutcome as FromProof<
4186            platform::BroadcastStateTransitionRequest,
4187        >>::maybe_from_proof(
4188            request,
4189            response,
4190            Network::Testnet,
4191            default_platform_version(),
4192            &provider,
4193        )
4194        .unwrap_err();
4195        assert!(
4196            matches!(err, Error::ProtocolError { .. }),
4197            "expected ProtocolError from StateTransition decode, got: {err:?}"
4198        );
4199    }
4200
4201    // ---------------------------------------------------------------------
4202    // Additional coverage: numeric helpers
4203    // ---------------------------------------------------------------------
4204
4205    #[test]
4206    fn u32_to_u16_opt_zero_maps_to_none() {
4207        // zero -> None (not Some(0)): guards against accidentally treating
4208        // "unset" as "limit 0".
4209        let parsed = u32_to_u16_opt(0).unwrap();
4210        assert!(parsed.is_none(), "value 0 must decode to None");
4211    }
4212
4213    #[test]
4214    fn u32_to_u16_opt_at_boundary() {
4215        let parsed = u32_to_u16_opt(u16::MAX as u32).unwrap();
4216        assert_eq!(parsed, Some(u16::MAX));
4217    }
4218
4219    #[test]
4220    fn u32_to_u16_opt_error_just_above_boundary() {
4221        // u16::MAX + 1 is the minimal out-of-range value.
4222        let err = u32_to_u16_opt((u16::MAX as u32) + 1).unwrap_err();
4223        match err {
4224            Error::RequestError { error } => {
4225                assert!(error.contains("out of range"), "got: {error}");
4226            }
4227            other => panic!("expected RequestError, got: {other:?}"),
4228        }
4229    }
4230
4231    #[test]
4232    fn try_u32_to_u16_at_boundary_plus_one() {
4233        // u16::MAX is ok; u16::MAX+1 is not.
4234        assert!(try_u32_to_u16(u16::MAX as u32).is_ok());
4235        assert!(try_u32_to_u16((u16::MAX as u32) + 1).is_err());
4236    }
4237
4238    // ---------------------------------------------------------------------
4239    // Additional coverage: Length / IntoOption edge cases
4240    // ---------------------------------------------------------------------
4241
4242    #[test]
4243    fn length_option_of_length_none_counts_zero() {
4244        let none_opt: Option<Vec<Option<u32>>> = None;
4245        assert_eq!(none_opt.count(), 0);
4246        assert_eq!(none_opt.count_some(), 0);
4247    }
4248
4249    #[test]
4250    fn length_vec_of_key_option_pair_only_none_values() {
4251        let v: Vec<(u8, Option<u32>)> = vec![(1, None), (2, None)];
4252        assert_eq!(v.count(), 2);
4253        assert_eq!(
4254            v.count_some(),
4255            0,
4256            "count_some must only count entries whose value is Some"
4257        );
4258    }
4259
4260    #[test]
4261    fn length_btreemap_only_none_values() {
4262        let mut m: BTreeMap<u8, Option<u32>> = BTreeMap::new();
4263        m.insert(1, None);
4264        m.insert(2, None);
4265        assert_eq!(m.count(), 2);
4266        assert_eq!(m.count_some(), 0);
4267    }
4268
4269    #[test]
4270    fn into_option_for_vec_of_key_option_pair_empty_and_nonempty() {
4271        let empty: Vec<(u8, Option<u32>)> = vec![];
4272        assert!(
4273            empty.into_option().is_none(),
4274            "empty vec must decode to None"
4275        );
4276
4277        let single: Vec<(u8, Option<u32>)> = vec![(1, None)];
4278        assert!(
4279            single.into_option().is_some(),
4280            "non-empty vec with only None values must still be Some"
4281        );
4282    }
4283
4284    #[test]
4285    fn into_option_for_btreemap_empty_and_nonempty() {
4286        let empty: BTreeMap<u8, Option<u32>> = BTreeMap::new();
4287        assert!(empty.into_option().is_none());
4288
4289        let mut m: BTreeMap<u8, Option<u32>> = BTreeMap::new();
4290        m.insert(1, None);
4291        assert!(m.into_option().is_some());
4292    }
4293
4294    // ---------------------------------------------------------------------
4295    // Additional coverage: parse_key_request_type
4296    // ---------------------------------------------------------------------
4297
4298    #[test]
4299    fn parse_key_request_type_specific_keys_empty_ids() {
4300        // Exercises the SpecificKeys branch when the id list is empty.
4301        use dapi_grpc::platform::v0::SpecificKeys;
4302        let outer = Some(GrpcKeyType {
4303            request: Some(key_request_type::Request::SpecificKeys(SpecificKeys {
4304                key_ids: vec![],
4305            })),
4306        });
4307        let parsed = parse_key_request_type(&outer).unwrap();
4308        match parsed {
4309            KeyRequestType::SpecificKeys(ids) => {
4310                assert!(ids.is_empty(), "empty ids must round-trip as empty");
4311            }
4312            _ => panic!("expected SpecificKeys variant"),
4313        }
4314    }
4315
4316    #[test]
4317    fn parse_key_request_type_search_key_empty_purpose_map() {
4318        // Exercises the SearchKey branch when the purpose map is empty.
4319        use dapi_grpc::platform::v0::SearchKey;
4320        let outer = Some(GrpcKeyType {
4321            request: Some(key_request_type::Request::SearchKey(SearchKey {
4322                purpose_map: std::collections::HashMap::new(),
4323            })),
4324        });
4325        let parsed = parse_key_request_type(&outer).unwrap();
4326        match parsed {
4327            KeyRequestType::SearchKey(m) => {
4328                assert!(m.is_empty(), "empty map must round-trip as empty");
4329            }
4330            _ => panic!("expected SearchKey variant"),
4331        }
4332    }
4333
4334    #[test]
4335    fn parse_key_request_type_search_key_negative_kind_rejected() {
4336        // Negative i32 values are not valid GrpcKeyKind values -> RequestError.
4337        use dapi_grpc::platform::v0::{SearchKey, SecurityLevelMap};
4338        let mut sec_map: std::collections::HashMap<u32, i32> = std::collections::HashMap::new();
4339        sec_map.insert(0, -1);
4340        let mut purpose_map = std::collections::HashMap::new();
4341        purpose_map.insert(
4342            0u32,
4343            SecurityLevelMap {
4344                security_level_map: sec_map,
4345            },
4346        );
4347        let outer = Some(GrpcKeyType {
4348            request: Some(key_request_type::Request::SearchKey(SearchKey {
4349                purpose_map,
4350            })),
4351        });
4352        // KeyRequestType does not implement Debug, so use `.err().expect(...)`
4353        // rather than `.unwrap_err()`.
4354        let err = parse_key_request_type(&outer)
4355            .err()
4356            .expect("negative kind must error");
4357        match err {
4358            Error::RequestError { error } => {
4359                assert!(error.contains("missing requested key type"), "got: {error}");
4360            }
4361            other => panic!("expected RequestError, got: {other:?}"),
4362        }
4363    }
4364
4365    // ---------------------------------------------------------------------
4366    // Additional coverage: FromProof wrapper methods
4367    // ---------------------------------------------------------------------
4368
4369    /// FromProof impl that always succeeds with a Some value; used for
4370    /// exercising the `from_proof*` wrappers' happy paths.
4371    #[derive(Debug, PartialEq)]
4372    struct PresentFromProof(u32);
4373
4374    impl FromProof<()> for PresentFromProof {
4375        type Request = ();
4376        type Response = ();
4377
4378        fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
4379            _request: I,
4380            _response: O,
4381            _network: Network,
4382            _platform_version: &PlatformVersion,
4383            _provider: &'a dyn ContextProvider,
4384        ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
4385        where
4386            Self: Sized + 'a,
4387        {
4388            Ok((
4389                Some(PresentFromProof(7)),
4390                ResponseMetadata {
4391                    height: 123,
4392                    ..Default::default()
4393                },
4394                Proof::default(),
4395            ))
4396        }
4397    }
4398
4399    #[test]
4400    fn from_proof_with_metadata_returns_value_and_metadata() {
4401        // Ensures the `from_proof_with_metadata` wrapper unwraps Some correctly.
4402        let provider = unreachable_provider();
4403        let (value, mtd) = <PresentFromProof as FromProof<()>>::from_proof_with_metadata(
4404            (),
4405            (),
4406            Network::Testnet,
4407            default_platform_version(),
4408            &provider,
4409        )
4410        .unwrap();
4411        assert_eq!(value, PresentFromProof(7));
4412        assert_eq!(mtd.height, 123);
4413    }
4414
4415    #[test]
4416    fn from_proof_with_metadata_and_proof_returns_all_three() {
4417        let provider = unreachable_provider();
4418        let (value, mtd, _proof) =
4419            <PresentFromProof as FromProof<()>>::from_proof_with_metadata_and_proof(
4420                (),
4421                (),
4422                Network::Testnet,
4423                default_platform_version(),
4424                &provider,
4425            )
4426            .unwrap();
4427        assert_eq!(value, PresentFromProof(7));
4428        assert_eq!(mtd.height, 123);
4429    }
4430
4431    #[test]
4432    fn from_proof_on_missing_returns_not_found_via_wrapper() {
4433        // `from_proof` forwards to `maybe_from_proof` then maps None -> NotFound.
4434        let provider = unreachable_provider();
4435        let err = <MissingFromProof as FromProof<()>>::from_proof_with_metadata(
4436            (),
4437            (),
4438            Network::Testnet,
4439            default_platform_version(),
4440            &provider,
4441        )
4442        .unwrap_err();
4443        assert!(matches!(err, Error::NotFound), "got: {err:?}");
4444    }
4445
4446    #[test]
4447    fn from_proof_with_metadata_and_proof_missing_returns_not_found() {
4448        let provider = unreachable_provider();
4449        let err = <MissingFromProof as FromProof<()>>::from_proof_with_metadata_and_proof(
4450            (),
4451            (),
4452            Network::Testnet,
4453            default_platform_version(),
4454            &provider,
4455        )
4456        .unwrap_err();
4457        assert!(matches!(err, Error::NotFound), "got: {err:?}");
4458    }
4459
4460    #[test]
4461    fn maybe_from_proof_delegates_to_with_metadata_and_forwards_none() {
4462        // `maybe_from_proof` discards metadata/proof when forwarding the
4463        // underlying `(None, _, _)` shape.
4464        let provider = unreachable_provider();
4465        let result = <MissingFromProof as FromProof<()>>::maybe_from_proof(
4466            (),
4467            (),
4468            Network::Testnet,
4469            default_platform_version(),
4470            &provider,
4471        )
4472        .unwrap();
4473        assert!(result.is_none(), "MissingFromProof must bubble None");
4474    }
4475
4476    // ---------------------------------------------------------------------
4477    // Additional coverage: more FromProof impls' decode-error paths
4478    // ---------------------------------------------------------------------
4479
4480    fn default_metadata_with_epoch(epoch: u32) -> ResponseMetadata {
4481        ResponseMetadata {
4482            epoch,
4483            ..Default::default()
4484        }
4485    }
4486
4487    #[test]
4488    fn identity_keys_rejects_bad_identity_id_length() {
4489        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4490        use platform::get_identity_keys_response::{
4491            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4492        };
4493
4494        let response = platform::GetIdentityKeysResponse {
4495            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4496                result: Some(V0Result::Proof(Proof::default())),
4497                metadata: Some(ResponseMetadata::default()),
4498            })),
4499        };
4500        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4501            identity_id: vec![0u8; 5], // must be 32
4502            request_type: None,
4503            limit: None,
4504            offset: None,
4505            prove: true,
4506        }
4507        .into();
4508        let provider = unreachable_provider();
4509        let err =
4510            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4511                request,
4512                response,
4513                Network::Testnet,
4514                default_platform_version(),
4515                &provider,
4516            )
4517            .unwrap_err();
4518        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4519    }
4520
4521    #[test]
4522    fn identity_keys_rejects_overflowing_limit() {
4523        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4524        use platform::get_identity_keys_response::{
4525            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4526        };
4527
4528        let response = platform::GetIdentityKeysResponse {
4529            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4530                result: Some(V0Result::Proof(Proof::default())),
4531                metadata: Some(ResponseMetadata::default()),
4532            })),
4533        };
4534        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4535            identity_id: vec![0u8; 32], // valid
4536            request_type: None,
4537            limit: Some(100_000),
4538            offset: None,
4539            prove: true,
4540        }
4541        .into();
4542        let provider = unreachable_provider();
4543        let err =
4544            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4545                request,
4546                response,
4547                Network::Testnet,
4548                default_platform_version(),
4549                &provider,
4550            )
4551            .unwrap_err();
4552        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4553    }
4554
4555    #[test]
4556    fn identity_keys_rejects_overflowing_offset() {
4557        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4558        use platform::get_identity_keys_response::{
4559            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4560        };
4561
4562        let response = platform::GetIdentityKeysResponse {
4563            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4564                result: Some(V0Result::Proof(Proof::default())),
4565                metadata: Some(ResponseMetadata::default()),
4566            })),
4567        };
4568        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4569            identity_id: vec![0u8; 32],
4570            request_type: None,
4571            limit: None,
4572            offset: Some(100_000),
4573            prove: true,
4574        }
4575        .into();
4576        let provider = unreachable_provider();
4577        let err =
4578            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4579                request,
4580                response,
4581                Network::Testnet,
4582                default_platform_version(),
4583                &provider,
4584            )
4585            .unwrap_err();
4586        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
4587    }
4588
4589    #[test]
4590    fn identity_keys_rejects_missing_key_request_type() {
4591        // limit/offset are valid and identity_id is valid, so execution
4592        // reaches parse_key_request_type which errors on None.
4593        use dapi_grpc::platform::v0::get_identity_keys_request::GetIdentityKeysRequestV0;
4594        use platform::get_identity_keys_response::{
4595            get_identity_keys_response_v0::Result as V0Result, GetIdentityKeysResponseV0, Version,
4596        };
4597
4598        let response = platform::GetIdentityKeysResponse {
4599            version: Some(Version::V0(GetIdentityKeysResponseV0 {
4600                result: Some(V0Result::Proof(Proof::default())),
4601                metadata: Some(ResponseMetadata::default()),
4602            })),
4603        };
4604        let request: platform::GetIdentityKeysRequest = GetIdentityKeysRequestV0 {
4605            identity_id: vec![0u8; 32],
4606            request_type: None,
4607            limit: None,
4608            offset: None,
4609            prove: true,
4610        }
4611        .into();
4612        let provider = unreachable_provider();
4613        let err =
4614            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4615                request,
4616                response,
4617                Network::Testnet,
4618                default_platform_version(),
4619                &provider,
4620            )
4621            .unwrap_err();
4622        match err {
4623            Error::RequestError { error } => {
4624                assert!(error.contains("missing key request type"), "got: {error}");
4625            }
4626            other => panic!("expected RequestError, got: {other:?}"),
4627        }
4628    }
4629
4630    #[test]
4631    fn identity_keys_no_proof_when_response_empty() {
4632        let request = platform::GetIdentityKeysRequest::default();
4633        let response = platform::GetIdentityKeysResponse::default();
4634        let provider = unreachable_provider();
4635        let err =
4636            <IdentityPublicKeys as FromProof<platform::GetIdentityKeysRequest>>::maybe_from_proof(
4637                request,
4638                response,
4639                Network::Testnet,
4640                default_platform_version(),
4641                &provider,
4642            )
4643            .unwrap_err();
4644        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4645    }
4646
4647    #[test]
4648    fn identity_nonce_rejects_bad_identity_id_length() {
4649        use dapi_grpc::platform::v0::get_identity_nonce_request::GetIdentityNonceRequestV0;
4650        use platform::get_identity_nonce_response::{
4651            get_identity_nonce_response_v0::Result as V0Result, GetIdentityNonceResponseV0, Version,
4652        };
4653
4654        let response = platform::GetIdentityNonceResponse {
4655            version: Some(Version::V0(GetIdentityNonceResponseV0 {
4656                result: Some(V0Result::Proof(Proof::default())),
4657                metadata: Some(ResponseMetadata::default()),
4658            })),
4659        };
4660        let request: platform::GetIdentityNonceRequest = GetIdentityNonceRequestV0 {
4661            identity_id: vec![0u8; 1], // must be 32
4662            prove: true,
4663        }
4664        .into();
4665        let provider = unreachable_provider();
4666        let err = <IdentityNonceFetcher as FromProof<
4667            platform::GetIdentityNonceRequest,
4668        >>::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 identity_nonce_no_proof_when_response_empty() {
4681        let request = platform::GetIdentityNonceRequest::default();
4682        let response = platform::GetIdentityNonceResponse::default();
4683        let provider = unreachable_provider();
4684        let err = <IdentityNonceFetcher as FromProof<
4685            platform::GetIdentityNonceRequest,
4686        >>::maybe_from_proof(
4687            request,
4688            response,
4689            Network::Testnet,
4690            default_platform_version(),
4691            &provider,
4692        )
4693        .unwrap_err();
4694        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4695    }
4696
4697    #[test]
4698    fn identity_contract_nonce_rejects_bad_identity_id_length() {
4699        use dapi_grpc::platform::v0::get_identity_contract_nonce_request::GetIdentityContractNonceRequestV0;
4700        use platform::get_identity_contract_nonce_response::{
4701            get_identity_contract_nonce_response_v0::Result as V0Result,
4702            GetIdentityContractNonceResponseV0, Version,
4703        };
4704
4705        let response = platform::GetIdentityContractNonceResponse {
4706            version: Some(Version::V0(GetIdentityContractNonceResponseV0 {
4707                result: Some(V0Result::Proof(Proof::default())),
4708                metadata: Some(ResponseMetadata::default()),
4709            })),
4710        };
4711        let request: platform::GetIdentityContractNonceRequest =
4712            GetIdentityContractNonceRequestV0 {
4713                identity_id: vec![0u8; 10], // must be 32
4714                contract_id: vec![0u8; 32],
4715                prove: true,
4716            }
4717            .into();
4718        let provider = unreachable_provider();
4719        let err = <IdentityContractNonceFetcher as FromProof<
4720            platform::GetIdentityContractNonceRequest,
4721        >>::maybe_from_proof(
4722            request,
4723            response,
4724            Network::Testnet,
4725            default_platform_version(),
4726            &provider,
4727        )
4728        .unwrap_err();
4729        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4730    }
4731
4732    #[test]
4733    fn identity_contract_nonce_rejects_bad_contract_id_length() {
4734        use dapi_grpc::platform::v0::get_identity_contract_nonce_request::GetIdentityContractNonceRequestV0;
4735        use platform::get_identity_contract_nonce_response::{
4736            get_identity_contract_nonce_response_v0::Result as V0Result,
4737            GetIdentityContractNonceResponseV0, Version,
4738        };
4739
4740        let response = platform::GetIdentityContractNonceResponse {
4741            version: Some(Version::V0(GetIdentityContractNonceResponseV0 {
4742                result: Some(V0Result::Proof(Proof::default())),
4743                metadata: Some(ResponseMetadata::default()),
4744            })),
4745        };
4746        let request: platform::GetIdentityContractNonceRequest =
4747            GetIdentityContractNonceRequestV0 {
4748                identity_id: vec![0u8; 32],
4749                contract_id: vec![0u8; 10], // must be 32
4750                prove: true,
4751            }
4752            .into();
4753        let provider = unreachable_provider();
4754        let err = <IdentityContractNonceFetcher as FromProof<
4755            platform::GetIdentityContractNonceRequest,
4756        >>::maybe_from_proof(
4757            request,
4758            response,
4759            Network::Testnet,
4760            default_platform_version(),
4761            &provider,
4762        )
4763        .unwrap_err();
4764        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4765    }
4766
4767    #[test]
4768    fn identity_balance_rejects_bad_id_length() {
4769        use dapi_grpc::platform::v0::get_identity_balance_request::GetIdentityBalanceRequestV0;
4770        use platform::get_identity_balance_response::{
4771            get_identity_balance_response_v0::Result as V0Result, GetIdentityBalanceResponseV0,
4772            Version,
4773        };
4774
4775        let response = platform::GetIdentityBalanceResponse {
4776            version: Some(Version::V0(GetIdentityBalanceResponseV0 {
4777                result: Some(V0Result::Proof(Proof::default())),
4778                metadata: Some(ResponseMetadata::default()),
4779            })),
4780        };
4781        let request: platform::GetIdentityBalanceRequest = GetIdentityBalanceRequestV0 {
4782            id: vec![0u8; 5],
4783            prove: true,
4784        }
4785        .into();
4786        let provider = unreachable_provider();
4787        let err =
4788            <IdentityBalance as FromProof<platform::GetIdentityBalanceRequest>>::maybe_from_proof(
4789                request,
4790                response,
4791                Network::Testnet,
4792                default_platform_version(),
4793                &provider,
4794            )
4795            .unwrap_err();
4796        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4797    }
4798
4799    #[test]
4800    fn identity_balance_empty_version_on_request_version_none() {
4801        // response OK; request.version None -> EmptyVersion.
4802        use platform::get_identity_balance_response::{
4803            get_identity_balance_response_v0::Result as V0Result, GetIdentityBalanceResponseV0,
4804            Version,
4805        };
4806        let response = platform::GetIdentityBalanceResponse {
4807            version: Some(Version::V0(GetIdentityBalanceResponseV0 {
4808                result: Some(V0Result::Proof(Proof::default())),
4809                metadata: Some(ResponseMetadata::default()),
4810            })),
4811        };
4812        let request = platform::GetIdentityBalanceRequest { version: None };
4813        let provider = unreachable_provider();
4814        let err =
4815            <IdentityBalance as FromProof<platform::GetIdentityBalanceRequest>>::maybe_from_proof(
4816                request,
4817                response,
4818                Network::Testnet,
4819                default_platform_version(),
4820                &provider,
4821            )
4822            .unwrap_err();
4823        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4824    }
4825
4826    #[test]
4827    fn identity_balance_and_revision_rejects_bad_id_length() {
4828        use dapi_grpc::platform::v0::get_identity_balance_and_revision_request::GetIdentityBalanceAndRevisionRequestV0;
4829        use platform::get_identity_balance_and_revision_response::{
4830            get_identity_balance_and_revision_response_v0::Result as V0Result,
4831            GetIdentityBalanceAndRevisionResponseV0, Version,
4832        };
4833        let response = platform::GetIdentityBalanceAndRevisionResponse {
4834            version: Some(Version::V0(GetIdentityBalanceAndRevisionResponseV0 {
4835                result: Some(V0Result::Proof(Proof::default())),
4836                metadata: Some(ResponseMetadata::default()),
4837            })),
4838        };
4839        let request: platform::GetIdentityBalanceAndRevisionRequest =
4840            GetIdentityBalanceAndRevisionRequestV0 {
4841                id: vec![0u8; 5],
4842                prove: true,
4843            }
4844            .into();
4845        let provider = unreachable_provider();
4846        let err = <IdentityBalanceAndRevision as FromProof<
4847            platform::GetIdentityBalanceAndRevisionRequest,
4848        >>::maybe_from_proof(
4849            request,
4850            response,
4851            Network::Testnet,
4852            default_platform_version(),
4853            &provider,
4854        )
4855        .unwrap_err();
4856        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4857    }
4858
4859    #[test]
4860    fn identities_balances_empty_version_none() {
4861        use platform::get_identities_balances_response::{
4862            get_identities_balances_response_v0::Result as V0Result,
4863            GetIdentitiesBalancesResponseV0, Version,
4864        };
4865        let response = platform::GetIdentitiesBalancesResponse {
4866            version: Some(Version::V0(GetIdentitiesBalancesResponseV0 {
4867                result: Some(V0Result::Proof(Proof::default())),
4868                metadata: Some(ResponseMetadata::default()),
4869            })),
4870        };
4871        let request = platform::GetIdentitiesBalancesRequest { version: None };
4872        let provider = unreachable_provider();
4873        let err =
4874            <IdentityBalances as FromProof<platform::GetIdentitiesBalancesRequest>>::maybe_from_proof(
4875                request,
4876                response,
4877                Network::Testnet,
4878                default_platform_version(),
4879                &provider,
4880            )
4881            .unwrap_err();
4882        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
4883    }
4884
4885    #[test]
4886    fn data_contract_rejects_bad_id_length() {
4887        use dapi_grpc::platform::v0::get_data_contract_request::GetDataContractRequestV0;
4888        use platform::get_data_contract_response::{
4889            get_data_contract_response_v0::Result as V0Result, GetDataContractResponseV0, Version,
4890        };
4891        let response = platform::GetDataContractResponse {
4892            version: Some(Version::V0(GetDataContractResponseV0 {
4893                result: Some(V0Result::Proof(Proof::default())),
4894                metadata: Some(ResponseMetadata::default()),
4895            })),
4896        };
4897        let request: platform::GetDataContractRequest = GetDataContractRequestV0 {
4898            id: vec![0u8; 5],
4899            prove: true,
4900        }
4901        .into();
4902        let provider = unreachable_provider();
4903        let err = <DataContract as FromProof<platform::GetDataContractRequest>>::maybe_from_proof(
4904            request,
4905            response,
4906            Network::Testnet,
4907            default_platform_version(),
4908            &provider,
4909        )
4910        .unwrap_err();
4911        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4912    }
4913
4914    #[test]
4915    fn data_contract_no_proof_when_response_empty() {
4916        let request = platform::GetDataContractRequest::default();
4917        let response = platform::GetDataContractResponse::default();
4918        let provider = unreachable_provider();
4919        let err = <DataContract as FromProof<platform::GetDataContractRequest>>::maybe_from_proof(
4920            request,
4921            response,
4922            Network::Testnet,
4923            default_platform_version(),
4924            &provider,
4925        )
4926        .unwrap_err();
4927        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
4928    }
4929
4930    #[test]
4931    fn data_contract_with_serialization_rejects_bad_id_length() {
4932        // This hits the second `FromProof for (DataContract, Vec<u8>)` impl.
4933        use dapi_grpc::platform::v0::get_data_contract_request::GetDataContractRequestV0;
4934        use platform::get_data_contract_response::{
4935            get_data_contract_response_v0::Result as V0Result, GetDataContractResponseV0, Version,
4936        };
4937        let response = platform::GetDataContractResponse {
4938            version: Some(Version::V0(GetDataContractResponseV0 {
4939                result: Some(V0Result::Proof(Proof::default())),
4940                metadata: Some(ResponseMetadata::default()),
4941            })),
4942        };
4943        let request: platform::GetDataContractRequest = GetDataContractRequestV0 {
4944            id: vec![0u8; 5],
4945            prove: true,
4946        }
4947        .into();
4948        let provider = unreachable_provider();
4949        let err =
4950            <(DataContract, Vec<u8>) as FromProof<platform::GetDataContractRequest>>::maybe_from_proof(
4951                request,
4952                response,
4953                Network::Testnet,
4954                default_platform_version(),
4955                &provider,
4956            )
4957            .unwrap_err();
4958        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4959    }
4960
4961    #[test]
4962    fn data_contract_history_rejects_bad_id_length() {
4963        use dapi_grpc::platform::v0::get_data_contract_history_request::GetDataContractHistoryRequestV0;
4964        use platform::get_data_contract_history_response::{
4965            get_data_contract_history_response_v0::Result as V0Result,
4966            GetDataContractHistoryResponseV0, Version,
4967        };
4968        let response = platform::GetDataContractHistoryResponse {
4969            version: Some(Version::V0(GetDataContractHistoryResponseV0 {
4970                result: Some(V0Result::Proof(Proof::default())),
4971                metadata: Some(ResponseMetadata::default()),
4972            })),
4973        };
4974        let request: platform::GetDataContractHistoryRequest = GetDataContractHistoryRequestV0 {
4975            id: vec![0u8; 5],
4976            limit: None,
4977            offset: None,
4978            start_at_ms: 0,
4979            prove: true,
4980        }
4981        .into();
4982        let provider = unreachable_provider();
4983        let err = <DataContractHistory as FromProof<
4984            platform::GetDataContractHistoryRequest,
4985        >>::maybe_from_proof(
4986            request,
4987            response,
4988            Network::Testnet,
4989            default_platform_version(),
4990            &provider,
4991        )
4992        .unwrap_err();
4993        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
4994    }
4995
4996    #[test]
4997    fn data_contract_history_rejects_overflowing_limit() {
4998        use dapi_grpc::platform::v0::get_data_contract_history_request::GetDataContractHistoryRequestV0;
4999        use platform::get_data_contract_history_response::{
5000            get_data_contract_history_response_v0::Result as V0Result,
5001            GetDataContractHistoryResponseV0, Version,
5002        };
5003        let response = platform::GetDataContractHistoryResponse {
5004            version: Some(Version::V0(GetDataContractHistoryResponseV0 {
5005                result: Some(V0Result::Proof(Proof::default())),
5006                metadata: Some(ResponseMetadata::default()),
5007            })),
5008        };
5009        let request: platform::GetDataContractHistoryRequest = GetDataContractHistoryRequestV0 {
5010            id: vec![0u8; 32],
5011            limit: Some(100_000),
5012            offset: None,
5013            start_at_ms: 0,
5014            prove: true,
5015        }
5016        .into();
5017        let provider = unreachable_provider();
5018        let err = <DataContractHistory as FromProof<
5019            platform::GetDataContractHistoryRequest,
5020        >>::maybe_from_proof(
5021            request,
5022            response,
5023            Network::Testnet,
5024            default_platform_version(),
5025            &provider,
5026        )
5027        .unwrap_err();
5028        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5029    }
5030
5031    #[test]
5032    fn document_history_no_proof_when_response_empty() {
5033        let request = platform::GetDocumentHistoryRequest::default();
5034        let response = platform::GetDocumentHistoryResponse::default();
5035        let provider = unreachable_provider();
5036
5037        let err = document_history_error(request, response, &provider);
5038
5039        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5040    }
5041
5042    #[test]
5043    fn document_history_empty_response_metadata() {
5044        use platform::get_document_history_response::{
5045            get_document_history_response_v0::Result as V0Result, GetDocumentHistoryResponseV0,
5046            Version,
5047        };
5048        let request = platform::GetDocumentHistoryRequest::default();
5049        let response = platform::GetDocumentHistoryResponse {
5050            version: Some(Version::V0(GetDocumentHistoryResponseV0 {
5051                result: Some(V0Result::Proof(Proof::default())),
5052                metadata: None,
5053            })),
5054        };
5055        let provider = unreachable_provider();
5056
5057        let err = document_history_error(request, response, &provider);
5058
5059        assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
5060    }
5061
5062    #[test]
5063    fn document_history_empty_version() {
5064        let request = platform::GetDocumentHistoryRequest { version: None };
5065        let response = document_history_response_with_proof_and_metadata();
5066        let provider = unreachable_provider();
5067
5068        let err = document_history_error(request, response, &provider);
5069
5070        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5071    }
5072
5073    #[test]
5074    fn document_history_rejects_bad_contract_id_length() {
5075        let request =
5076            document_history_request(vec![0u8; 5], "niceDocument", vec![1u8; 32], None, None);
5077        let response = document_history_response_with_proof_and_metadata();
5078        let provider = unreachable_provider();
5079
5080        let err = document_history_error(request, response, &provider);
5081
5082        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5083    }
5084
5085    #[test]
5086    fn document_history_rejects_bad_document_id_length() {
5087        let request =
5088            document_history_request(vec![0u8; 32], "niceDocument", vec![1u8; 5], None, None);
5089        let response = document_history_response_with_proof_and_metadata();
5090        let provider = unreachable_provider();
5091
5092        let err = document_history_error(request, response, &provider);
5093
5094        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5095    }
5096
5097    #[test]
5098    fn document_history_rejects_overflowing_limit() {
5099        let request = document_history_request(
5100            vec![0u8; 32],
5101            "niceDocument",
5102            vec![1u8; 32],
5103            Some(100_000),
5104            None,
5105        );
5106        let response = document_history_response_with_proof_and_metadata();
5107        let provider = unreachable_provider();
5108
5109        let err = document_history_error(request, response, &provider);
5110
5111        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5112    }
5113
5114    #[test]
5115    fn document_history_returns_not_found_when_contract_provider_misses() {
5116        let request =
5117            document_history_request(vec![0u8; 32], "niceDocument", vec![1u8; 32], None, None);
5118        let response = document_history_response_with_proof_and_metadata();
5119        let provider = NoDataContractProvider;
5120
5121        let err = document_history_error(request, response, &provider);
5122
5123        assert!(matches!(err, Error::NotFound), "got: {err:?}");
5124    }
5125
5126    #[test]
5127    fn document_history_rejects_unknown_document_type() {
5128        let data_contract = dpp::tests::fixtures::get_data_contract_fixture(
5129            None,
5130            0,
5131            default_platform_version().protocol_version,
5132        )
5133        .data_contract_owned();
5134        let contract_id = data_contract.id().to_vec();
5135        let provider = StaticDataContractProvider {
5136            data_contract: std::sync::Arc::new(data_contract),
5137        };
5138        let request =
5139            document_history_request(contract_id, "missingDocument", vec![1u8; 32], None, None);
5140        let response = document_history_response_with_proof_and_metadata();
5141
5142        let err = document_history_error(request, response, &provider);
5143
5144        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5145    }
5146
5147    #[test]
5148    fn address_info_rejects_bad_address_bytes() {
5149        // PlatformAddress::from_bytes fails for invalid lengths.
5150        use dapi_grpc::platform::v0::get_address_info_request::GetAddressInfoRequestV0;
5151        use platform::get_address_info_response::{
5152            get_address_info_response_v0::Result as V0Result, GetAddressInfoResponseV0, Version,
5153        };
5154        let response = platform::GetAddressInfoResponse {
5155            version: Some(Version::V0(GetAddressInfoResponseV0 {
5156                result: Some(V0Result::Proof(Proof::default())),
5157                metadata: Some(ResponseMetadata::default()),
5158            })),
5159        };
5160        let request: platform::GetAddressInfoRequest = GetAddressInfoRequestV0 {
5161            address: vec![0u8; 3], // not a valid address length
5162            prove: true,
5163        }
5164        .into();
5165        let provider = unreachable_provider();
5166        let err = <AddressInfo as FromProof<platform::GetAddressInfoRequest>>::maybe_from_proof(
5167            request,
5168            response,
5169            Network::Testnet,
5170            default_platform_version(),
5171            &provider,
5172        )
5173        .unwrap_err();
5174        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5175    }
5176
5177    #[test]
5178    fn addresses_infos_rejects_bad_address_bytes() {
5179        use dapi_grpc::platform::v0::get_addresses_infos_request::GetAddressesInfosRequestV0;
5180        use platform::get_addresses_infos_response::{
5181            get_addresses_infos_response_v0::Result as V0Result, GetAddressesInfosResponseV0,
5182            Version,
5183        };
5184        let response = platform::GetAddressesInfosResponse {
5185            version: Some(Version::V0(GetAddressesInfosResponseV0 {
5186                result: Some(V0Result::Proof(Proof::default())),
5187                metadata: Some(ResponseMetadata::default()),
5188            })),
5189        };
5190        let request: platform::GetAddressesInfosRequest = GetAddressesInfosRequestV0 {
5191            addresses: vec![vec![0u8; 3]],
5192            prove: true,
5193        }
5194        .into();
5195        let provider = unreachable_provider();
5196        let err =
5197            <AddressInfos as FromProof<platform::GetAddressesInfosRequest>>::maybe_from_proof(
5198                request,
5199                response,
5200                Network::Testnet,
5201                default_platform_version(),
5202                &provider,
5203            )
5204            .unwrap_err();
5205        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5206    }
5207
5208    #[test]
5209    fn addresses_trunk_state_grove_no_proof() {
5210        // GroveTrunkQueryResult impl ignores the request entirely, so the
5211        // first failure possible is NoProofInResult when the response is empty.
5212        let response = platform::GetAddressesTrunkStateResponse::default();
5213        let request = platform::GetAddressesTrunkStateRequest::default();
5214        let provider = unreachable_provider();
5215        let err = <GroveTrunkQueryResult as FromProof<
5216            platform::GetAddressesTrunkStateRequest,
5217        >>::maybe_from_proof(
5218            request,
5219            response,
5220            Network::Testnet,
5221            default_platform_version(),
5222            &provider,
5223        )
5224        .unwrap_err();
5225        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5226    }
5227
5228    #[test]
5229    fn platform_address_trunk_state_no_proof() {
5230        // PlatformAddressTrunkState wraps the GroveTrunkQueryResult impl; same error.
5231        let response = platform::GetAddressesTrunkStateResponse::default();
5232        let request = platform::GetAddressesTrunkStateRequest::default();
5233        let provider = unreachable_provider();
5234        let err = <PlatformAddressTrunkState as FromProof<
5235            platform::GetAddressesTrunkStateRequest,
5236        >>::maybe_from_proof(
5237            request,
5238            response,
5239            Network::Testnet,
5240            default_platform_version(),
5241            &provider,
5242        )
5243        .unwrap_err();
5244        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5245    }
5246
5247    #[test]
5248    fn epochs_info_empty_version_none() {
5249        use platform::get_epochs_info_response::{
5250            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
5251        };
5252        let response = platform::GetEpochsInfoResponse {
5253            version: Some(Version::V0(GetEpochsInfoResponseV0 {
5254                result: Some(V0Result::Proof(Proof::default())),
5255                metadata: Some(ResponseMetadata::default()),
5256            })),
5257        };
5258        let request = platform::GetEpochsInfoRequest { version: None };
5259        let provider = unreachable_provider();
5260        let err =
5261            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
5262                request,
5263                response,
5264                Network::Testnet,
5265                default_platform_version(),
5266                &provider,
5267            )
5268            .unwrap_err();
5269        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5270    }
5271
5272    #[test]
5273    fn epochs_info_rejects_overflowing_count() {
5274        // start_epoch valid, but count > u16::MAX -> try_u32_to_u16 errors.
5275        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
5276        use platform::get_epochs_info_response::{
5277            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
5278        };
5279        let response = platform::GetEpochsInfoResponse {
5280            version: Some(Version::V0(GetEpochsInfoResponseV0 {
5281                result: Some(V0Result::Proof(Proof::default())),
5282                metadata: Some(default_metadata_with_epoch(10)),
5283            })),
5284        };
5285        let request = platform::GetEpochsInfoRequest {
5286            version: Some(platform::get_epochs_info_request::Version::V0(
5287                GetEpochsInfoRequestV0 {
5288                    start_epoch: Some(0),
5289                    count: 100_000,
5290                    ascending: true,
5291                    prove: true,
5292                },
5293            )),
5294        };
5295        let provider = unreachable_provider();
5296        let err =
5297            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
5298                request,
5299                response,
5300                Network::Testnet,
5301                default_platform_version(),
5302                &provider,
5303            )
5304            .unwrap_err();
5305        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5306    }
5307
5308    #[test]
5309    fn epochs_info_ascending_without_start_ignores_metadata_epoch() {
5310        // Ascending queries without a start are request-derived from epoch 0,
5311        // so unsigned metadata must not participate in query selection.
5312        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
5313        use platform::get_epochs_info_response::{
5314            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
5315        };
5316        let response = platform::GetEpochsInfoResponse {
5317            version: Some(Version::V0(GetEpochsInfoResponseV0 {
5318                result: Some(V0Result::Proof(Proof::default())),
5319                metadata: Some(default_metadata_with_epoch(70_000)),
5320            })),
5321        };
5322        let request = platform::GetEpochsInfoRequest {
5323            version: Some(platform::get_epochs_info_request::Version::V0(
5324                GetEpochsInfoRequestV0 {
5325                    start_epoch: None,
5326                    count: 1,
5327                    ascending: true,
5328                    prove: true,
5329                },
5330            )),
5331        };
5332        let provider = unreachable_provider();
5333        let err =
5334            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
5335                request,
5336                response,
5337                Network::Testnet,
5338                default_platform_version(),
5339                &provider,
5340            )
5341            .unwrap_err();
5342        assert!(
5343            !matches!(err, Error::RequestError { .. }),
5344            "metadata epoch unexpectedly influenced the request: {err:?}"
5345        );
5346    }
5347
5348    #[test]
5349    fn epochs_info_descending_requires_explicit_start_epoch() {
5350        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
5351        use platform::get_epochs_info_response::{
5352            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
5353        };
5354        let response = platform::GetEpochsInfoResponse {
5355            version: Some(Version::V0(GetEpochsInfoResponseV0 {
5356                result: Some(V0Result::Proof(Proof::default())),
5357                metadata: Some(default_metadata_with_epoch(10)),
5358            })),
5359        };
5360        let request = platform::GetEpochsInfoRequest {
5361            version: Some(platform::get_epochs_info_request::Version::V0(
5362                GetEpochsInfoRequestV0 {
5363                    start_epoch: None,
5364                    count: 1,
5365                    ascending: false,
5366                    prove: true,
5367                },
5368            )),
5369        };
5370        let provider = unreachable_provider();
5371
5372        let err =
5373            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
5374                request,
5375                response,
5376                Network::Testnet,
5377                default_platform_version(),
5378                &provider,
5379            )
5380            .unwrap_err();
5381
5382        match err {
5383            Error::RequestError { error } => assert!(error.contains("explicit start epoch")),
5384            other => panic!("expected explicit-epoch request error, got: {other:?}"),
5385        }
5386    }
5387
5388    #[test]
5389    fn epochs_info_descending_with_explicit_max_start_passes_guard() {
5390        // The SDK's `fetch_current` sends a descending query with an explicit
5391        // start (the hinted current epoch index). Any explicit start — up to
5392        // MAX_EPOCH, the highest index that fits Drive's epoch key encoding —
5393        // makes the request fully self-describing, so it must get past the
5394        // explicit-start guard and proceed to proof verification (which here
5395        // fails on the garbage proof, not on the request shape).
5396        use dapi_grpc::platform::v0::get_epochs_info_request::GetEpochsInfoRequestV0;
5397        use dpp::block::epoch::MAX_EPOCH;
5398        use platform::get_epochs_info_response::{
5399            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
5400        };
5401        let response = platform::GetEpochsInfoResponse {
5402            version: Some(Version::V0(GetEpochsInfoResponseV0 {
5403                result: Some(V0Result::Proof(Proof::default())),
5404                metadata: Some(default_metadata_with_epoch(10)),
5405            })),
5406        };
5407        let request = platform::GetEpochsInfoRequest {
5408            version: Some(platform::get_epochs_info_request::Version::V0(
5409                GetEpochsInfoRequestV0 {
5410                    start_epoch: Some(MAX_EPOCH as u32),
5411                    count: 1,
5412                    ascending: false,
5413                    prove: true,
5414                },
5415            )),
5416        };
5417        let provider = unreachable_provider();
5418
5419        let err =
5420            <ExtendedEpochInfos as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
5421                request,
5422                response,
5423                Network::Testnet,
5424                default_platform_version(),
5425                &provider,
5426            )
5427            .unwrap_err();
5428
5429        assert!(
5430            !matches!(err, Error::RequestError { .. }),
5431            "explicit-start descending query must pass the guard, got: {err:?}"
5432        );
5433    }
5434
5435    #[test]
5436    fn extended_epoch_info_single_bubbles_empty_version() {
5437        // Ensures the wrapper passes through error from inner impl.
5438        use platform::get_epochs_info_response::{
5439            get_epochs_info_response_v0::Result as V0Result, GetEpochsInfoResponseV0, Version,
5440        };
5441        let response = platform::GetEpochsInfoResponse {
5442            version: Some(Version::V0(GetEpochsInfoResponseV0 {
5443                result: Some(V0Result::Proof(Proof::default())),
5444                metadata: Some(ResponseMetadata::default()),
5445            })),
5446        };
5447        let request = platform::GetEpochsInfoRequest { version: None };
5448        let provider = unreachable_provider();
5449        let err =
5450            <ExtendedEpochInfo as FromProof<platform::GetEpochsInfoRequest>>::maybe_from_proof(
5451                request,
5452                response,
5453                Network::Testnet,
5454                default_platform_version(),
5455                &provider,
5456            )
5457            .unwrap_err();
5458        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5459    }
5460
5461    #[test]
5462    fn finalized_epoch_infos_rejects_overflowing_start_index() {
5463        use dapi_grpc::platform::v0::get_finalized_epoch_infos_request::GetFinalizedEpochInfosRequestV0;
5464        use platform::get_finalized_epoch_infos_response::{
5465            get_finalized_epoch_infos_response_v0::Result as V0Result,
5466            GetFinalizedEpochInfosResponseV0, Version,
5467        };
5468        let response = platform::GetFinalizedEpochInfosResponse {
5469            version: Some(Version::V0(GetFinalizedEpochInfosResponseV0 {
5470                result: Some(V0Result::Proof(Proof::default())),
5471                metadata: Some(ResponseMetadata::default()),
5472            })),
5473        };
5474        let request: platform::GetFinalizedEpochInfosRequest = GetFinalizedEpochInfosRequestV0 {
5475            start_epoch_index: 100_000,
5476            start_epoch_index_included: true,
5477            end_epoch_index: 1,
5478            end_epoch_index_included: true,
5479            prove: true,
5480        }
5481        .into();
5482        let provider = unreachable_provider();
5483        let err = <FinalizedEpochInfos as FromProof<
5484            platform::GetFinalizedEpochInfosRequest,
5485        >>::maybe_from_proof(
5486            request,
5487            response,
5488            Network::Testnet,
5489            default_platform_version(),
5490            &provider,
5491        )
5492        .unwrap_err();
5493        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5494    }
5495
5496    #[test]
5497    fn finalized_epoch_infos_rejects_overflowing_end_index() {
5498        use dapi_grpc::platform::v0::get_finalized_epoch_infos_request::GetFinalizedEpochInfosRequestV0;
5499        use platform::get_finalized_epoch_infos_response::{
5500            get_finalized_epoch_infos_response_v0::Result as V0Result,
5501            GetFinalizedEpochInfosResponseV0, Version,
5502        };
5503        let response = platform::GetFinalizedEpochInfosResponse {
5504            version: Some(Version::V0(GetFinalizedEpochInfosResponseV0 {
5505                result: Some(V0Result::Proof(Proof::default())),
5506                metadata: Some(ResponseMetadata::default()),
5507            })),
5508        };
5509        let request: platform::GetFinalizedEpochInfosRequest = GetFinalizedEpochInfosRequestV0 {
5510            start_epoch_index: 1,
5511            start_epoch_index_included: true,
5512            end_epoch_index: 100_000,
5513            end_epoch_index_included: true,
5514            prove: true,
5515        }
5516        .into();
5517        let provider = unreachable_provider();
5518        let err = <FinalizedEpochInfos as FromProof<
5519            platform::GetFinalizedEpochInfosRequest,
5520        >>::maybe_from_proof(
5521            request,
5522            response,
5523            Network::Testnet,
5524            default_platform_version(),
5525            &provider,
5526        )
5527        .unwrap_err();
5528        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5529    }
5530
5531    #[test]
5532    fn upgrade_state_no_proof_when_response_empty() {
5533        // No request dependency, so the first error must come from the response.
5534        let response = GetProtocolVersionUpgradeStateResponse::default();
5535        let request = GetProtocolVersionUpgradeStateRequest::default();
5536        let provider = unreachable_provider();
5537        let err = <ProtocolVersionUpgrades as FromProof<
5538            GetProtocolVersionUpgradeStateRequest,
5539        >>::maybe_from_proof(
5540            request,
5541            response,
5542            Network::Testnet,
5543            default_platform_version(),
5544            &provider,
5545        )
5546        .unwrap_err();
5547        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5548    }
5549
5550    #[test]
5551    fn upgrade_vote_status_no_proof_when_response_empty() {
5552        let response = GetProtocolVersionUpgradeVoteStatusResponse::default();
5553        let request = GetProtocolVersionUpgradeVoteStatusRequest::default();
5554        let provider = unreachable_provider();
5555        let err = <MasternodeProtocolVotes as FromProof<
5556            GetProtocolVersionUpgradeVoteStatusRequest,
5557        >>::maybe_from_proof(
5558            request,
5559            response,
5560            Network::Testnet,
5561            default_platform_version(),
5562            &provider,
5563        )
5564        .unwrap_err();
5565        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
5566    }
5567
5568    #[test]
5569    fn upgrade_vote_status_rejects_overflowing_count() {
5570        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_request::GetProtocolVersionUpgradeVoteStatusRequestV0;
5571        use dapi_grpc::platform::v0::get_protocol_version_upgrade_vote_status_response::{
5572            get_protocol_version_upgrade_vote_status_response_v0::Result as V0Result,
5573            GetProtocolVersionUpgradeVoteStatusResponseV0, Version,
5574        };
5575
5576        let response = GetProtocolVersionUpgradeVoteStatusResponse {
5577            version: Some(Version::V0(GetProtocolVersionUpgradeVoteStatusResponseV0 {
5578                result: Some(V0Result::Proof(Proof::default())),
5579                metadata: Some(ResponseMetadata::default()),
5580            })),
5581        };
5582        // empty start_pro_tx_hash (None branch), but count overflow
5583        let request: GetProtocolVersionUpgradeVoteStatusRequest =
5584            GetProtocolVersionUpgradeVoteStatusRequestV0 {
5585                start_pro_tx_hash: vec![],
5586                count: 100_000,
5587                prove: true,
5588            }
5589            .into();
5590        let provider = unreachable_provider();
5591        let err = <MasternodeProtocolVotes as FromProof<
5592            GetProtocolVersionUpgradeVoteStatusRequest,
5593        >>::maybe_from_proof(
5594            request,
5595            response,
5596            Network::Testnet,
5597            default_platform_version(),
5598            &provider,
5599        )
5600        .unwrap_err();
5601        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5602    }
5603
5604    #[test]
5605    fn path_elements_empty_version_when_no_version() {
5606        // Response has full v0 shell, request has None -> EmptyVersion.
5607        use platform::get_path_elements_response::{
5608            get_path_elements_response_v0::Result as V0Result, GetPathElementsResponseV0, Version,
5609        };
5610        let response = GetPathElementsResponse {
5611            version: Some(Version::V0(GetPathElementsResponseV0 {
5612                result: Some(V0Result::Proof(Proof::default())),
5613                metadata: Some(ResponseMetadata::default()),
5614            })),
5615        };
5616        let request = GetPathElementsRequest { version: None };
5617        let provider = unreachable_provider();
5618        let err = <Elements as FromProof<GetPathElementsRequest>>::maybe_from_proof(
5619            request,
5620            response,
5621            Network::Testnet,
5622            default_platform_version(),
5623            &provider,
5624        )
5625        .unwrap_err();
5626        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5627    }
5628
5629    #[test]
5630    fn identities_contract_keys_rejects_bad_identity_id() {
5631        use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
5632        use platform::get_identities_contract_keys_response::{
5633            get_identities_contract_keys_response_v0::Result as V0Result,
5634            GetIdentitiesContractKeysResponseV0, Version,
5635        };
5636        let response = platform::GetIdentitiesContractKeysResponse {
5637            version: Some(Version::V0(GetIdentitiesContractKeysResponseV0 {
5638                result: Some(V0Result::Proof(Proof::default())),
5639                metadata: Some(ResponseMetadata::default()),
5640            })),
5641        };
5642        let request: platform::GetIdentitiesContractKeysRequest =
5643            GetIdentitiesContractKeysRequestV0 {
5644                identities_ids: vec![vec![0u8; 5]], // bad
5645                contract_id: vec![0u8; 32],
5646                document_type_name: None,
5647                purposes: vec![0],
5648                prove: true,
5649            }
5650            .into();
5651        let provider = unreachable_provider();
5652        let err = <IdentitiesContractKeys as FromProof<
5653            platform::GetIdentitiesContractKeysRequest,
5654        >>::maybe_from_proof(
5655            request,
5656            response,
5657            Network::Testnet,
5658            default_platform_version(),
5659            &provider,
5660        )
5661        .unwrap_err();
5662        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5663    }
5664
5665    #[test]
5666    fn identities_contract_keys_rejects_bad_contract_id() {
5667        use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
5668        use platform::get_identities_contract_keys_response::{
5669            get_identities_contract_keys_response_v0::Result as V0Result,
5670            GetIdentitiesContractKeysResponseV0, Version,
5671        };
5672        let response = platform::GetIdentitiesContractKeysResponse {
5673            version: Some(Version::V0(GetIdentitiesContractKeysResponseV0 {
5674                result: Some(V0Result::Proof(Proof::default())),
5675                metadata: Some(ResponseMetadata::default()),
5676            })),
5677        };
5678        let request: platform::GetIdentitiesContractKeysRequest =
5679            GetIdentitiesContractKeysRequestV0 {
5680                identities_ids: vec![vec![0u8; 32]],
5681                contract_id: vec![0u8; 5], // bad
5682                document_type_name: None,
5683                purposes: vec![0],
5684                prove: true,
5685            }
5686            .into();
5687        let provider = unreachable_provider();
5688        let err = <IdentitiesContractKeys as FromProof<
5689            platform::GetIdentitiesContractKeysRequest,
5690        >>::maybe_from_proof(
5691            request,
5692            response,
5693            Network::Testnet,
5694            default_platform_version(),
5695            &provider,
5696        )
5697        .unwrap_err();
5698        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5699    }
5700
5701    #[test]
5702    fn identities_contract_keys_rejects_bad_purpose() {
5703        use dapi_grpc::platform::v0::get_identities_contract_keys_request::GetIdentitiesContractKeysRequestV0;
5704        use platform::get_identities_contract_keys_response::{
5705            get_identities_contract_keys_response_v0::Result as V0Result,
5706            GetIdentitiesContractKeysResponseV0, Version,
5707        };
5708        let response = platform::GetIdentitiesContractKeysResponse {
5709            version: Some(Version::V0(GetIdentitiesContractKeysResponseV0 {
5710                result: Some(V0Result::Proof(Proof::default())),
5711                metadata: Some(ResponseMetadata::default()),
5712            })),
5713        };
5714        // purpose 250 is not a valid Purpose value.
5715        let request: platform::GetIdentitiesContractKeysRequest =
5716            GetIdentitiesContractKeysRequestV0 {
5717                identities_ids: vec![vec![0u8; 32]],
5718                contract_id: vec![0u8; 32],
5719                document_type_name: None,
5720                purposes: vec![250],
5721                prove: true,
5722            }
5723            .into();
5724        let provider = unreachable_provider();
5725        let err = <IdentitiesContractKeys as FromProof<
5726            platform::GetIdentitiesContractKeysRequest,
5727        >>::maybe_from_proof(
5728            request,
5729            response,
5730            Network::Testnet,
5731            default_platform_version(),
5732            &provider,
5733        )
5734        .unwrap_err();
5735        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
5736    }
5737
5738    #[test]
5739    fn prefunded_balance_empty_version_on_request_version_none() {
5740        // response has proof; request.version = None -> EmptyVersion
5741        use platform::get_prefunded_specialized_balance_response::{
5742            get_prefunded_specialized_balance_response_v0::Result as V0Result,
5743            GetPrefundedSpecializedBalanceResponseV0, Version,
5744        };
5745        let response = platform::GetPrefundedSpecializedBalanceResponse {
5746            version: Some(Version::V0(GetPrefundedSpecializedBalanceResponseV0 {
5747                result: Some(V0Result::Proof(Proof::default())),
5748                metadata: Some(ResponseMetadata::default()),
5749            })),
5750        };
5751        let request = platform::GetPrefundedSpecializedBalanceRequest { version: None };
5752        let provider = unreachable_provider();
5753        let err = <PrefundedSpecializedBalance as FromProof<
5754            platform::GetPrefundedSpecializedBalanceRequest,
5755        >>::maybe_from_proof(
5756            request,
5757            response,
5758            Network::Testnet,
5759            default_platform_version(),
5760            &provider,
5761        )
5762        .unwrap_err();
5763        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5764    }
5765
5766    #[test]
5767    fn evonodes_proposed_epoch_blocks_by_ids_empty_version() {
5768        use platform::get_evonodes_proposed_epoch_blocks_response::{
5769            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5770            GetEvonodesProposedEpochBlocksResponseV0, Version,
5771        };
5772        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5773            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5774                result: Some(V0Result::Proof(Proof::default())),
5775                metadata: Some(ResponseMetadata::default()),
5776            })),
5777        };
5778        let request = platform::GetEvonodesProposedEpochBlocksByIdsRequest { version: None };
5779        let provider = unreachable_provider();
5780        let err = <ProposerBlockCounts as FromProof<
5781            platform::GetEvonodesProposedEpochBlocksByIdsRequest,
5782        >>::maybe_from_proof(
5783            request,
5784            response,
5785            Network::Testnet,
5786            default_platform_version(),
5787            &provider,
5788        )
5789        .unwrap_err();
5790        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5791    }
5792
5793    #[test]
5794    fn evonodes_proposed_epoch_blocks_by_ids_rejects_overflowing_epoch() {
5795        // Request sets epoch > u16::MAX -> try_u32_to_u16 error.
5796        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_ids_request::GetEvonodesProposedEpochBlocksByIdsRequestV0;
5797        use platform::get_evonodes_proposed_epoch_blocks_response::{
5798            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5799            GetEvonodesProposedEpochBlocksResponseV0, Version,
5800        };
5801        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5802            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5803                result: Some(V0Result::Proof(Proof::default())),
5804                metadata: Some(default_metadata_with_epoch(1)),
5805            })),
5806        };
5807        let request: platform::GetEvonodesProposedEpochBlocksByIdsRequest =
5808            GetEvonodesProposedEpochBlocksByIdsRequestV0 {
5809                epoch: Some(100_000),
5810                ids: vec![],
5811                prove: true,
5812            }
5813            .into();
5814        let provider = unreachable_provider();
5815        let err = <ProposerBlockCounts as FromProof<
5816            platform::GetEvonodesProposedEpochBlocksByIdsRequest,
5817        >>::maybe_from_proof(
5818            request,
5819            response,
5820            Network::Testnet,
5821            default_platform_version(),
5822            &provider,
5823        )
5824        .unwrap_err();
5825        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5826    }
5827
5828    #[test]
5829    fn evonodes_proposed_epoch_blocks_by_ids_requires_explicit_epoch() {
5830        // An omitted epoch must fail before unsigned metadata can select the
5831        // proof query.
5832        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_ids_request::GetEvonodesProposedEpochBlocksByIdsRequestV0;
5833        use platform::get_evonodes_proposed_epoch_blocks_response::{
5834            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5835            GetEvonodesProposedEpochBlocksResponseV0, Version,
5836        };
5837        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5838            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5839                result: Some(V0Result::Proof(Proof::default())),
5840                metadata: Some(default_metadata_with_epoch(99_999)),
5841            })),
5842        };
5843        let request: platform::GetEvonodesProposedEpochBlocksByIdsRequest =
5844            GetEvonodesProposedEpochBlocksByIdsRequestV0 {
5845                epoch: None,
5846                ids: vec![],
5847                prove: true,
5848            }
5849            .into();
5850        let provider = unreachable_provider();
5851        let err = <ProposerBlockCounts as FromProof<
5852            platform::GetEvonodesProposedEpochBlocksByIdsRequest,
5853        >>::maybe_from_proof(
5854            request,
5855            response,
5856            Network::Testnet,
5857            default_platform_version(),
5858            &provider,
5859        )
5860        .unwrap_err();
5861        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5862    }
5863
5864    #[test]
5865    fn evonodes_proposed_epoch_blocks_by_range_empty_version() {
5866        use platform::get_evonodes_proposed_epoch_blocks_response::{
5867            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5868            GetEvonodesProposedEpochBlocksResponseV0, Version,
5869        };
5870        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5871            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5872                result: Some(V0Result::Proof(Proof::default())),
5873                metadata: Some(ResponseMetadata::default()),
5874            })),
5875        };
5876        let request = platform::GetEvonodesProposedEpochBlocksByRangeRequest { version: None };
5877        let provider = unreachable_provider();
5878        let err = <ProposerBlockCounts as FromProof<
5879            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5880        >>::maybe_from_proof(
5881            request,
5882            response,
5883            Network::Testnet,
5884            default_platform_version(),
5885            &provider,
5886        )
5887        .unwrap_err();
5888        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
5889    }
5890
5891    #[test]
5892    fn evonodes_proposed_epoch_blocks_by_range_requires_explicit_epoch() {
5893        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::GetEvonodesProposedEpochBlocksByRangeRequestV0;
5894        use platform::get_evonodes_proposed_epoch_blocks_response::{
5895            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5896            GetEvonodesProposedEpochBlocksResponseV0, Version,
5897        };
5898        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5899            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5900                result: Some(V0Result::Proof(Proof::default())),
5901                metadata: Some(default_metadata_with_epoch(99_999)),
5902            })),
5903        };
5904        let request: platform::GetEvonodesProposedEpochBlocksByRangeRequest =
5905            GetEvonodesProposedEpochBlocksByRangeRequestV0 {
5906                epoch: None,
5907                limit: None,
5908                start: None,
5909                prove: true,
5910            }
5911            .into();
5912        let provider = unreachable_provider();
5913        let err = <ProposerBlockCounts as FromProof<
5914            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5915        >>::maybe_from_proof(
5916            request,
5917            response,
5918            Network::Testnet,
5919            default_platform_version(),
5920            &provider,
5921        )
5922        .unwrap_err();
5923
5924        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
5925    }
5926
5927    #[test]
5928    fn evonodes_proposed_epoch_blocks_by_range_rejects_bad_start_after() {
5929        // Start::StartAfter with wrong length must fail.
5930        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::{
5931            get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start,
5932            GetEvonodesProposedEpochBlocksByRangeRequestV0,
5933        };
5934        use platform::get_evonodes_proposed_epoch_blocks_response::{
5935            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5936            GetEvonodesProposedEpochBlocksResponseV0, Version,
5937        };
5938        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5939            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5940                result: Some(V0Result::Proof(Proof::default())),
5941                metadata: Some(default_metadata_with_epoch(1)),
5942            })),
5943        };
5944        let request: platform::GetEvonodesProposedEpochBlocksByRangeRequest =
5945            GetEvonodesProposedEpochBlocksByRangeRequestV0 {
5946                epoch: Some(1),
5947                limit: None,
5948                start: Some(Start::StartAfter(vec![0u8; 5])),
5949                prove: true,
5950            }
5951            .into();
5952        let provider = unreachable_provider();
5953        let err = <ProposerBlockCounts as FromProof<
5954            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5955        >>::maybe_from_proof(
5956            request,
5957            response,
5958            Network::Testnet,
5959            default_platform_version(),
5960            &provider,
5961        )
5962        .unwrap_err();
5963        assert!(matches!(err, Error::DriveError { .. }), "got: {err:?}");
5964    }
5965
5966    #[test]
5967    fn evonodes_proposed_epoch_blocks_by_range_rejects_bad_start_at() {
5968        // Start::StartAt with wrong length must fail.
5969        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::{
5970            get_evonodes_proposed_epoch_blocks_by_range_request_v0::Start,
5971            GetEvonodesProposedEpochBlocksByRangeRequestV0,
5972        };
5973        use platform::get_evonodes_proposed_epoch_blocks_response::{
5974            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
5975            GetEvonodesProposedEpochBlocksResponseV0, Version,
5976        };
5977        let response = platform::GetEvonodesProposedEpochBlocksResponse {
5978            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
5979                result: Some(V0Result::Proof(Proof::default())),
5980                metadata: Some(default_metadata_with_epoch(1)),
5981            })),
5982        };
5983        let request: platform::GetEvonodesProposedEpochBlocksByRangeRequest =
5984            GetEvonodesProposedEpochBlocksByRangeRequestV0 {
5985                epoch: Some(1),
5986                limit: None,
5987                start: Some(Start::StartAt(vec![0u8; 4])),
5988                prove: true,
5989            }
5990            .into();
5991        let provider = unreachable_provider();
5992        let err = <ProposerBlockCounts as FromProof<
5993            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
5994        >>::maybe_from_proof(
5995            request,
5996            response,
5997            Network::Testnet,
5998            default_platform_version(),
5999            &provider,
6000        )
6001        .unwrap_err();
6002        assert!(matches!(err, Error::DriveError { .. }), "got: {err:?}");
6003    }
6004
6005    #[test]
6006    fn evonodes_proposed_epoch_blocks_by_range_rejects_overflow_limit() {
6007        use dapi_grpc::platform::v0::get_evonodes_proposed_epoch_blocks_by_range_request::GetEvonodesProposedEpochBlocksByRangeRequestV0;
6008        use platform::get_evonodes_proposed_epoch_blocks_response::{
6009            get_evonodes_proposed_epoch_blocks_response_v0::Result as V0Result,
6010            GetEvonodesProposedEpochBlocksResponseV0, Version,
6011        };
6012        let response = platform::GetEvonodesProposedEpochBlocksResponse {
6013            version: Some(Version::V0(GetEvonodesProposedEpochBlocksResponseV0 {
6014                result: Some(V0Result::Proof(Proof::default())),
6015                metadata: Some(default_metadata_with_epoch(1)),
6016            })),
6017        };
6018        let request: platform::GetEvonodesProposedEpochBlocksByRangeRequest =
6019            GetEvonodesProposedEpochBlocksByRangeRequestV0 {
6020                epoch: Some(1),
6021                limit: Some(100_000),
6022                start: None,
6023                prove: true,
6024            }
6025            .into();
6026        let provider = unreachable_provider();
6027        let err = <ProposerBlockCounts as FromProof<
6028            platform::GetEvonodesProposedEpochBlocksByRangeRequest,
6029        >>::maybe_from_proof(
6030            request,
6031            response,
6032            Network::Testnet,
6033            default_platform_version(),
6034            &provider,
6035        )
6036        .unwrap_err();
6037        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
6038    }
6039
6040    #[test]
6041    fn shielded_pool_state_no_proof_when_response_empty() {
6042        let response = platform::GetShieldedPoolStateResponse::default();
6043        let request = platform::GetShieldedPoolStateRequest::default();
6044        let provider = unreachable_provider();
6045        let err = <ShieldedPoolState as FromProof<
6046            platform::GetShieldedPoolStateRequest,
6047        >>::maybe_from_proof(
6048            request,
6049            response,
6050            Network::Testnet,
6051            default_platform_version(),
6052            &provider,
6053        )
6054        .unwrap_err();
6055        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
6056    }
6057
6058    #[test]
6059    fn shielded_notes_count_no_proof_when_response_empty() {
6060        let response = platform::GetShieldedNotesCountResponse::default();
6061        let request = platform::GetShieldedNotesCountRequest::default();
6062        let provider = unreachable_provider();
6063        let err = <ShieldedNotesCount as FromProof<
6064            platform::GetShieldedNotesCountRequest,
6065        >>::maybe_from_proof(
6066            request,
6067            response,
6068            Network::Testnet,
6069            default_platform_version(),
6070            &provider,
6071        )
6072        .unwrap_err();
6073        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
6074    }
6075
6076    #[test]
6077    fn shielded_anchors_no_proof_when_response_empty() {
6078        let response = platform::GetShieldedAnchorsResponse::default();
6079        let request = platform::GetShieldedAnchorsRequest::default();
6080        let provider = unreachable_provider();
6081        let err =
6082            <ShieldedAnchors as FromProof<platform::GetShieldedAnchorsRequest>>::maybe_from_proof(
6083                request,
6084                response,
6085                Network::Testnet,
6086                default_platform_version(),
6087                &provider,
6088            )
6089            .unwrap_err();
6090        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
6091    }
6092
6093    #[test]
6094    fn most_recent_shielded_anchor_no_proof_when_response_empty() {
6095        let response = platform::GetMostRecentShieldedAnchorResponse::default();
6096        let request = platform::GetMostRecentShieldedAnchorRequest::default();
6097        let provider = unreachable_provider();
6098        let err = <MostRecentShieldedAnchor as FromProof<
6099            platform::GetMostRecentShieldedAnchorRequest,
6100        >>::maybe_from_proof(
6101            request,
6102            response,
6103            Network::Testnet,
6104            default_platform_version(),
6105            &provider,
6106        )
6107        .unwrap_err();
6108        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
6109    }
6110
6111    #[test]
6112    fn shielded_encrypted_notes_empty_version_on_request_version_none() {
6113        use platform::get_shielded_encrypted_notes_response::{
6114            get_shielded_encrypted_notes_response_v0::Result as V0Result,
6115            GetShieldedEncryptedNotesResponseV0, Version,
6116        };
6117        let response = platform::GetShieldedEncryptedNotesResponse {
6118            version: Some(Version::V0(GetShieldedEncryptedNotesResponseV0 {
6119                result: Some(V0Result::Proof(Proof::default())),
6120                metadata: Some(ResponseMetadata::default()),
6121            })),
6122        };
6123        let request = platform::GetShieldedEncryptedNotesRequest { version: None };
6124        let provider = unreachable_provider();
6125        let err = <ShieldedEncryptedNotes as FromProof<
6126            platform::GetShieldedEncryptedNotesRequest,
6127        >>::maybe_from_proof(
6128            request,
6129            response,
6130            Network::Testnet,
6131            default_platform_version(),
6132            &provider,
6133        )
6134        .unwrap_err();
6135        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
6136    }
6137
6138    #[test]
6139    fn shielded_encrypted_notes_no_proof_when_response_empty() {
6140        let response = platform::GetShieldedEncryptedNotesResponse::default();
6141        let request = platform::GetShieldedEncryptedNotesRequest::default();
6142        let provider = unreachable_provider();
6143        let err = <ShieldedEncryptedNotes as FromProof<
6144            platform::GetShieldedEncryptedNotesRequest,
6145        >>::maybe_from_proof(
6146            request,
6147            response,
6148            Network::Testnet,
6149            default_platform_version(),
6150            &provider,
6151        )
6152        .unwrap_err();
6153        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
6154    }
6155
6156    #[test]
6157    fn shielded_nullifiers_empty_version_on_request_version_none() {
6158        use platform::get_shielded_nullifiers_response::{
6159            get_shielded_nullifiers_response_v0::Result as V0Result,
6160            GetShieldedNullifiersResponseV0, Version,
6161        };
6162        let response = platform::GetShieldedNullifiersResponse {
6163            version: Some(Version::V0(GetShieldedNullifiersResponseV0 {
6164                result: Some(V0Result::Proof(Proof::default())),
6165                metadata: Some(ResponseMetadata::default()),
6166            })),
6167        };
6168        let request = platform::GetShieldedNullifiersRequest { version: None };
6169        let provider = unreachable_provider();
6170        let err = <ShieldedNullifierStatuses as FromProof<
6171            platform::GetShieldedNullifiersRequest,
6172        >>::maybe_from_proof(
6173            request,
6174            response,
6175            Network::Testnet,
6176            default_platform_version(),
6177            &provider,
6178        )
6179        .unwrap_err();
6180        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
6181    }
6182
6183    #[test]
6184    fn recent_address_balance_changes_empty_version() {
6185        use platform::get_recent_address_balance_changes_response::{
6186            get_recent_address_balance_changes_response_v0::Result as V0Result,
6187            GetRecentAddressBalanceChangesResponseV0, Version,
6188        };
6189        let response = platform::GetRecentAddressBalanceChangesResponse {
6190            version: Some(Version::V0(GetRecentAddressBalanceChangesResponseV0 {
6191                result: Some(V0Result::Proof(Proof::default())),
6192                metadata: Some(ResponseMetadata::default()),
6193            })),
6194        };
6195        let request = platform::GetRecentAddressBalanceChangesRequest { version: None };
6196        let provider = unreachable_provider();
6197        let err = <RecentAddressBalanceChanges as FromProof<
6198            platform::GetRecentAddressBalanceChangesRequest,
6199        >>::maybe_from_proof(
6200            request,
6201            response,
6202            Network::Testnet,
6203            default_platform_version(),
6204            &provider,
6205        )
6206        .unwrap_err();
6207        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
6208    }
6209
6210    #[test]
6211    fn recent_compacted_address_balance_changes_empty_version() {
6212        use platform::get_recent_compacted_address_balance_changes_response::{
6213            get_recent_compacted_address_balance_changes_response_v0::Result as V0Result,
6214            GetRecentCompactedAddressBalanceChangesResponseV0, Version,
6215        };
6216        let response = platform::GetRecentCompactedAddressBalanceChangesResponse {
6217            version: Some(Version::V0(
6218                GetRecentCompactedAddressBalanceChangesResponseV0 {
6219                    result: Some(V0Result::Proof(Proof::default())),
6220                    metadata: Some(ResponseMetadata::default()),
6221                },
6222            )),
6223        };
6224        let request = platform::GetRecentCompactedAddressBalanceChangesRequest { version: None };
6225        let provider = unreachable_provider();
6226        let err = <RecentCompactedAddressBalanceChanges as FromProof<
6227            platform::GetRecentCompactedAddressBalanceChangesRequest,
6228        >>::maybe_from_proof(
6229            request,
6230            response,
6231            Network::Testnet,
6232            default_platform_version(),
6233            &provider,
6234        )
6235        .unwrap_err();
6236        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
6237    }
6238
6239    #[test]
6240    fn vote_identity_vote_rejects_bad_identity_id_length() {
6241        // The Vote impl validates id_in_request length before delegating.
6242        use dapi_grpc::platform::v0::get_contested_resource_identity_votes_request::GetContestedResourceIdentityVotesRequestV0;
6243        let request: platform::GetContestedResourceIdentityVotesRequest =
6244            GetContestedResourceIdentityVotesRequestV0 {
6245                identity_id: vec![0u8; 5], // bad
6246                limit: None,
6247                offset: None,
6248                order_ascending: true,
6249                start_at_vote_poll_id_info: None,
6250                prove: true,
6251            }
6252            .into();
6253        let response = platform::GetContestedResourceIdentityVotesResponse::default();
6254        let provider = unreachable_provider();
6255        let err = <Vote as FromProof<
6256            platform::GetContestedResourceIdentityVotesRequest,
6257        >>::maybe_from_proof(
6258            request,
6259            response,
6260            Network::Testnet,
6261            default_platform_version(),
6262            &provider,
6263        )
6264        .unwrap_err();
6265        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
6266    }
6267
6268    #[test]
6269    fn vote_identity_vote_empty_version_on_request_none() {
6270        let request = platform::GetContestedResourceIdentityVotesRequest { version: None };
6271        let response = platform::GetContestedResourceIdentityVotesResponse::default();
6272        let provider = unreachable_provider();
6273        let err = <Vote as FromProof<
6274            platform::GetContestedResourceIdentityVotesRequest,
6275        >>::maybe_from_proof(
6276            request,
6277            response,
6278            Network::Testnet,
6279            default_platform_version(),
6280            &provider,
6281        )
6282        .unwrap_err();
6283        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
6284    }
6285
6286    #[test]
6287    fn resource_votes_by_identity_empty_version_via_try_from_request() {
6288        // Delegates to ContestedResourceVotesGivenByIdentityQuery::try_from_request,
6289        // which returns Error::EmptyVersion when the request version is missing.
6290        let request = platform::GetContestedResourceIdentityVotesRequest { version: None };
6291        let response = platform::GetContestedResourceIdentityVotesResponse::default();
6292        let provider = unreachable_provider();
6293        let err = <ResourceVotesByIdentity as FromProof<
6294            platform::GetContestedResourceIdentityVotesRequest,
6295        >>::maybe_from_proof(
6296            request,
6297            response,
6298            Network::Testnet,
6299            default_platform_version(),
6300            &provider,
6301        )
6302        .unwrap_err();
6303        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
6304    }
6305
6306    // ---------------------------------------------------------------------
6307    // IntoOption for more types
6308    // ---------------------------------------------------------------------
6309
6310    #[test]
6311    fn into_option_indexmap_empty_vs_with_entry_only_none() {
6312        use dpp::prelude::Identifier;
6313        let empty: RetrievedObjects<Identifier, u32> = RetrievedObjects::new();
6314        assert!(empty.into_option().is_none());
6315
6316        let mut map: RetrievedObjects<Identifier, u32> = RetrievedObjects::new();
6317        map.insert(Identifier::new([7u8; 32]), None);
6318        let mapped = map.into_option();
6319        assert!(
6320            mapped.is_some(),
6321            "absence markers must be preserved in into_option"
6322        );
6323        assert_eq!(mapped.unwrap().len(), 1);
6324    }
6325
6326    // ---------------------------------------------------------------------
6327    // Additional: extend existing malformed request tests to all StateTransitions
6328    // ---------------------------------------------------------------------
6329
6330    #[test]
6331    fn broadcast_state_transition_no_proof_when_response_empty() {
6332        let request = platform::BroadcastStateTransitionRequest {
6333            state_transition: vec![],
6334        };
6335        let response = platform::WaitForStateTransitionResultResponse::default();
6336        let provider = unreachable_provider();
6337        let err = <StateTransitionProofOutcome as FromProof<
6338            platform::BroadcastStateTransitionRequest,
6339        >>::maybe_from_proof(
6340            request,
6341            response,
6342            Network::Testnet,
6343            default_platform_version(),
6344            &provider,
6345        )
6346        .unwrap_err();
6347        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
6348    }
6349
6350    #[test]
6351    fn broadcast_state_transition_protocol_error_fires_before_metadata_check() {
6352        // This test pins the ORDERING of validation in
6353        // `StateTransitionProofOutcome::maybe_from_proof` for broadcast
6354        // state transitions: proof extraction -> state_transition decode ->
6355        // metadata check. An invalid state_transition payload triggers
6356        // `ProtocolError` on decode BEFORE the missing-metadata branch is
6357        // reached, so even though `metadata: None` here, the assertion
6358        // targets `ProtocolError`, not `EmptyResponseMetadata`.
6359        //
6360        // (For the happy-path `EmptyResponseMetadata` branch, a valid
6361        // serialized state transition would be needed; that is covered
6362        // elsewhere. This test deliberately documents the decode-first
6363        // ordering.)
6364        use platform::wait_for_state_transition_result_response::{
6365            wait_for_state_transition_result_response_v0::Result as V0Result, Version,
6366            WaitForStateTransitionResultResponseV0,
6367        };
6368        let request = platform::BroadcastStateTransitionRequest {
6369            state_transition: vec![0xFFu8; 4],
6370        };
6371        let response = platform::WaitForStateTransitionResultResponse {
6372            version: Some(Version::V0(WaitForStateTransitionResultResponseV0 {
6373                result: Some(V0Result::Proof(Proof::default())),
6374                metadata: None, // missing — would trigger EmptyResponseMetadata if reached
6375            })),
6376        };
6377        let provider = unreachable_provider();
6378        let err = <StateTransitionProofOutcome as FromProof<
6379            platform::BroadcastStateTransitionRequest,
6380        >>::maybe_from_proof(
6381            request,
6382            response,
6383            Network::Testnet,
6384            default_platform_version(),
6385            &provider,
6386        )
6387        .unwrap_err();
6388        assert!(matches!(err, Error::ProtocolError { .. }), "got: {err:?}");
6389    }
6390
6391    /// Every direct-proof `FromProof` path rejects a legacy envelope before
6392    /// its bytes reach Drive, whatever else the request carries. The provider
6393    /// panics if touched, so a case that reaches it has lost the gate.
6394    mod legacy_envelope_gate {
6395        use super::*;
6396        use crate::types::groups::{GroupActionSigners, GroupActions, Groups};
6397        use crate::types::identity_token_balance::{
6398            IdentitiesTokenBalances, IdentityTokenBalances,
6399        };
6400        use crate::types::token_info::{IdentitiesTokenInfos, IdentityTokenInfos};
6401        use crate::types::token_status::TokenStatuses;
6402        use dpp::balances::total_single_token_balance::TotalSingleTokenBalance;
6403        use dpp::data_contract::group::Group;
6404        use dpp::tokens::contract_info::TokenContractInfo;
6405
6406        fn legacy_proof() -> Proof {
6407            Proof {
6408                grovedb_proof: bincode::encode_to_vec(
6409                    0u32,
6410                    bincode::config::standard().with_big_endian(),
6411                )
6412                .expect("V0 envelope discriminant should encode"),
6413                ..Proof::default()
6414            }
6415        }
6416
6417        fn id(byte: u8) -> Vec<u8> {
6418            vec![byte; 32]
6419        }
6420
6421        macro_rules! request {
6422            ($module:ident, $v0:ident, $request:ident { $($field:ident: $value:expr),* $(,)? }) => {
6423                {
6424                    // Some request bodies list every field; keep one macro
6425                    // shape for all of them.
6426                    #[allow(clippy::needless_update)]
6427                    let body = platform::$module::$v0 {
6428                        $($field: $value,)*
6429                        ..Default::default()
6430                    };
6431                    platform::$request {
6432                        version: Some(platform::$module::Version::V0(body)),
6433                    }
6434                }
6435            };
6436        }
6437
6438        macro_rules! legacy_response {
6439            ($module:ident, $v0_module:ident, $v0:ident, $response:ident) => {
6440                platform::$response {
6441                    version: Some(platform::$module::Version::V0(platform::$module::$v0 {
6442                        result: Some(platform::$module::$v0_module::Result::Proof(legacy_proof())),
6443                        metadata: Some(ResponseMetadata::default()),
6444                    })),
6445                }
6446            };
6447        }
6448
6449        macro_rules! assert_rejects_legacy_envelope {
6450            ($target:ty, $request_type:ty, $request:expr, $response:expr) => {{
6451                let error = <$target as FromProof<$request_type>>::maybe_from_proof(
6452                    $request,
6453                    $response,
6454                    Network::Testnet,
6455                    PlatformVersion::latest(),
6456                    &unreachable_provider(),
6457                )
6458                .expect_err(concat!(
6459                    stringify!($target),
6460                    " must reject a legacy envelope"
6461                ));
6462                assert!(
6463                    matches!(
6464                        error,
6465                        Error::UnsupportedGroveDBProofVersion {
6466                            version: 0,
6467                            minimum: 1,
6468                            ..
6469                        }
6470                    ),
6471                    "{}: {error:?}",
6472                    stringify!($target)
6473                );
6474            }};
6475        }
6476
6477        #[test]
6478        fn identity_queries() {
6479            assert_rejects_legacy_envelope!(
6480                Identity,
6481                platform::GetIdentityRequest,
6482                request!(
6483                    get_identity_request,
6484                    GetIdentityRequestV0,
6485                    GetIdentityRequest {
6486                        id: id(1),
6487                        prove: true
6488                    }
6489                ),
6490                legacy_response!(
6491                    get_identity_response,
6492                    get_identity_response_v0,
6493                    GetIdentityResponseV0,
6494                    GetIdentityResponse
6495                )
6496            );
6497            assert_rejects_legacy_envelope!(
6498                Identity,
6499                platform::GetIdentityByPublicKeyHashRequest,
6500                request!(
6501                    get_identity_by_public_key_hash_request,
6502                    GetIdentityByPublicKeyHashRequestV0,
6503                    GetIdentityByPublicKeyHashRequest {
6504                        public_key_hash: vec![1; 20],
6505                        prove: true
6506                    }
6507                ),
6508                legacy_response!(
6509                    get_identity_by_public_key_hash_response,
6510                    get_identity_by_public_key_hash_response_v0,
6511                    GetIdentityByPublicKeyHashResponseV0,
6512                    GetIdentityByPublicKeyHashResponse
6513                )
6514            );
6515            assert_rejects_legacy_envelope!(
6516                IdentityPublicKeys,
6517                platform::GetIdentityKeysRequest,
6518                request!(
6519                    get_identity_keys_request,
6520                    GetIdentityKeysRequestV0,
6521                    GetIdentityKeysRequest {
6522                        identity_id: id(1),
6523                        request_type: Some(platform::KeyRequestType {
6524                            request: Some(platform::key_request_type::Request::AllKeys(
6525                                platform::AllKeys {}
6526                            )),
6527                        }),
6528                        prove: true
6529                    }
6530                ),
6531                legacy_response!(
6532                    get_identity_keys_response,
6533                    get_identity_keys_response_v0,
6534                    GetIdentityKeysResponseV0,
6535                    GetIdentityKeysResponse
6536                )
6537            );
6538            assert_rejects_legacy_envelope!(
6539                IdentityNonceFetcher,
6540                platform::GetIdentityNonceRequest,
6541                request!(
6542                    get_identity_nonce_request,
6543                    GetIdentityNonceRequestV0,
6544                    GetIdentityNonceRequest {
6545                        identity_id: id(1),
6546                        prove: true
6547                    }
6548                ),
6549                legacy_response!(
6550                    get_identity_nonce_response,
6551                    get_identity_nonce_response_v0,
6552                    GetIdentityNonceResponseV0,
6553                    GetIdentityNonceResponse
6554                )
6555            );
6556            assert_rejects_legacy_envelope!(
6557                IdentityContractNonceFetcher,
6558                platform::GetIdentityContractNonceRequest,
6559                request!(
6560                    get_identity_contract_nonce_request,
6561                    GetIdentityContractNonceRequestV0,
6562                    GetIdentityContractNonceRequest {
6563                        identity_id: id(1),
6564                        contract_id: id(2),
6565                        prove: true
6566                    }
6567                ),
6568                legacy_response!(
6569                    get_identity_contract_nonce_response,
6570                    get_identity_contract_nonce_response_v0,
6571                    GetIdentityContractNonceResponseV0,
6572                    GetIdentityContractNonceResponse
6573                )
6574            );
6575            assert_rejects_legacy_envelope!(
6576                IdentityBalance,
6577                platform::GetIdentityBalanceRequest,
6578                request!(
6579                    get_identity_balance_request,
6580                    GetIdentityBalanceRequestV0,
6581                    GetIdentityBalanceRequest {
6582                        id: id(1),
6583                        prove: true
6584                    }
6585                ),
6586                legacy_response!(
6587                    get_identity_balance_response,
6588                    get_identity_balance_response_v0,
6589                    GetIdentityBalanceResponseV0,
6590                    GetIdentityBalanceResponse
6591                )
6592            );
6593            assert_rejects_legacy_envelope!(
6594                IdentityBalances,
6595                platform::GetIdentitiesBalancesRequest,
6596                request!(
6597                    get_identities_balances_request,
6598                    GetIdentitiesBalancesRequestV0,
6599                    GetIdentitiesBalancesRequest {
6600                        ids: vec![id(1)],
6601                        prove: true
6602                    }
6603                ),
6604                legacy_response!(
6605                    get_identities_balances_response,
6606                    get_identities_balances_response_v0,
6607                    GetIdentitiesBalancesResponseV0,
6608                    GetIdentitiesBalancesResponse
6609                )
6610            );
6611            assert_rejects_legacy_envelope!(
6612                IdentityBalanceAndRevision,
6613                platform::GetIdentityBalanceAndRevisionRequest,
6614                request!(
6615                    get_identity_balance_and_revision_request,
6616                    GetIdentityBalanceAndRevisionRequestV0,
6617                    GetIdentityBalanceAndRevisionRequest {
6618                        id: id(1),
6619                        prove: true
6620                    }
6621                ),
6622                legacy_response!(
6623                    get_identity_balance_and_revision_response,
6624                    get_identity_balance_and_revision_response_v0,
6625                    GetIdentityBalanceAndRevisionResponseV0,
6626                    GetIdentityBalanceAndRevisionResponse
6627                )
6628            );
6629            assert_rejects_legacy_envelope!(
6630                IdentitiesContractKeys,
6631                platform::GetIdentitiesContractKeysRequest,
6632                request!(
6633                    get_identities_contract_keys_request,
6634                    GetIdentitiesContractKeysRequestV0,
6635                    GetIdentitiesContractKeysRequest {
6636                        identities_ids: vec![id(1)],
6637                        contract_id: id(2),
6638                        prove: true
6639                    }
6640                ),
6641                legacy_response!(
6642                    get_identities_contract_keys_response,
6643                    get_identities_contract_keys_response_v0,
6644                    GetIdentitiesContractKeysResponseV0,
6645                    GetIdentitiesContractKeysResponse
6646                )
6647            );
6648        }
6649
6650        #[test]
6651        fn contract_and_address_queries() {
6652            assert_rejects_legacy_envelope!(
6653                DataContract,
6654                platform::GetDataContractRequest,
6655                request!(
6656                    get_data_contract_request,
6657                    GetDataContractRequestV0,
6658                    GetDataContractRequest {
6659                        id: id(1),
6660                        prove: true
6661                    }
6662                ),
6663                legacy_response!(
6664                    get_data_contract_response,
6665                    get_data_contract_response_v0,
6666                    GetDataContractResponseV0,
6667                    GetDataContractResponse
6668                )
6669            );
6670            assert_rejects_legacy_envelope!(
6671                (DataContract, Vec<u8>),
6672                platform::GetDataContractRequest,
6673                request!(
6674                    get_data_contract_request,
6675                    GetDataContractRequestV0,
6676                    GetDataContractRequest {
6677                        id: id(1),
6678                        prove: true
6679                    }
6680                ),
6681                legacy_response!(
6682                    get_data_contract_response,
6683                    get_data_contract_response_v0,
6684                    GetDataContractResponseV0,
6685                    GetDataContractResponse
6686                )
6687            );
6688            assert_rejects_legacy_envelope!(
6689                DataContracts,
6690                platform::GetDataContractsRequest,
6691                request!(
6692                    get_data_contracts_request,
6693                    GetDataContractsRequestV0,
6694                    GetDataContractsRequest {
6695                        ids: vec![id(1)],
6696                        prove: true
6697                    }
6698                ),
6699                legacy_response!(
6700                    get_data_contracts_response,
6701                    get_data_contracts_response_v0,
6702                    GetDataContractsResponseV0,
6703                    GetDataContractsResponse
6704                )
6705            );
6706            assert_rejects_legacy_envelope!(
6707                DataContractHistory,
6708                platform::GetDataContractHistoryRequest,
6709                request!(
6710                    get_data_contract_history_request,
6711                    GetDataContractHistoryRequestV0,
6712                    GetDataContractHistoryRequest {
6713                        id: id(1),
6714                        prove: true
6715                    }
6716                ),
6717                legacy_response!(
6718                    get_data_contract_history_response,
6719                    get_data_contract_history_response_v0,
6720                    GetDataContractHistoryResponseV0,
6721                    GetDataContractHistoryResponse
6722                )
6723            );
6724            assert_rejects_legacy_envelope!(
6725                AddressInfos,
6726                platform::GetAddressesInfosRequest,
6727                request!(
6728                    get_addresses_infos_request,
6729                    GetAddressesInfosRequestV0,
6730                    GetAddressesInfosRequest { prove: true }
6731                ),
6732                legacy_response!(
6733                    get_addresses_infos_response,
6734                    get_addresses_infos_response_v0,
6735                    GetAddressesInfosResponseV0,
6736                    GetAddressesInfosResponse
6737                )
6738            );
6739            assert_rejects_legacy_envelope!(
6740                RecentAddressBalanceChanges,
6741                platform::GetRecentAddressBalanceChangesRequest,
6742                request!(
6743                    get_recent_address_balance_changes_request,
6744                    GetRecentAddressBalanceChangesRequestV0,
6745                    GetRecentAddressBalanceChangesRequest {
6746                        start_height: 1,
6747                        prove: true
6748                    }
6749                ),
6750                legacy_response!(
6751                    get_recent_address_balance_changes_response,
6752                    get_recent_address_balance_changes_response_v0,
6753                    GetRecentAddressBalanceChangesResponseV0,
6754                    GetRecentAddressBalanceChangesResponse
6755                )
6756            );
6757            // The trunk response carries its proof directly rather than in a
6758            // `result` oneof, so it cannot use `legacy_response!`. Without the
6759            // gate a V0 envelope reaches GroveDB's V0 trunk verifier, whose
6760            // `count == 0` fast path skips the value-hash chain and lets a
6761            // prover present the address tree as empty.
6762            use platform::get_addresses_trunk_state_response::{
6763                GetAddressesTrunkStateResponseV0, Version as TrunkStateVersion,
6764            };
6765            let legacy_trunk_state_response = || platform::GetAddressesTrunkStateResponse {
6766                version: Some(TrunkStateVersion::V0(GetAddressesTrunkStateResponseV0 {
6767                    proof: Some(legacy_proof()),
6768                    metadata: Some(ResponseMetadata::default()),
6769                })),
6770            };
6771            assert_rejects_legacy_envelope!(
6772                GroveTrunkQueryResult,
6773                platform::GetAddressesTrunkStateRequest,
6774                request!(
6775                    get_addresses_trunk_state_request,
6776                    GetAddressesTrunkStateRequestV0,
6777                    GetAddressesTrunkStateRequest {}
6778                ),
6779                legacy_trunk_state_response()
6780            );
6781            assert_rejects_legacy_envelope!(
6782                PlatformAddressTrunkState,
6783                platform::GetAddressesTrunkStateRequest,
6784                request!(
6785                    get_addresses_trunk_state_request,
6786                    GetAddressesTrunkStateRequestV0,
6787                    GetAddressesTrunkStateRequest {}
6788                ),
6789                legacy_trunk_state_response()
6790            );
6791            assert_rejects_legacy_envelope!(
6792                PrefundedSpecializedBalance,
6793                platform::GetPrefundedSpecializedBalanceRequest,
6794                request!(
6795                    get_prefunded_specialized_balance_request,
6796                    GetPrefundedSpecializedBalanceRequestV0,
6797                    GetPrefundedSpecializedBalanceRequest {
6798                        id: id(1),
6799                        prove: true
6800                    }
6801                ),
6802                legacy_response!(
6803                    get_prefunded_specialized_balance_response,
6804                    get_prefunded_specialized_balance_response_v0,
6805                    GetPrefundedSpecializedBalanceResponseV0,
6806                    GetPrefundedSpecializedBalanceResponse
6807                )
6808            );
6809            assert_rejects_legacy_envelope!(
6810                VotePollsGroupedByTimestamp,
6811                platform::GetVotePollsByEndDateRequest,
6812                request!(
6813                    get_vote_polls_by_end_date_request,
6814                    GetVotePollsByEndDateRequestV0,
6815                    GetVotePollsByEndDateRequest { prove: true }
6816                ),
6817                legacy_response!(
6818                    get_vote_polls_by_end_date_response,
6819                    get_vote_polls_by_end_date_response_v0,
6820                    GetVotePollsByEndDateResponseV0,
6821                    GetVotePollsByEndDateResponse
6822                )
6823            );
6824        }
6825
6826        #[test]
6827        fn system_queries() {
6828            assert_rejects_legacy_envelope!(
6829                ExtendedEpochInfos,
6830                platform::GetEpochsInfoRequest,
6831                request!(
6832                    get_epochs_info_request,
6833                    GetEpochsInfoRequestV0,
6834                    GetEpochsInfoRequest {
6835                        count: 1,
6836                        ascending: true,
6837                        prove: true
6838                    }
6839                ),
6840                legacy_response!(
6841                    get_epochs_info_response,
6842                    get_epochs_info_response_v0,
6843                    GetEpochsInfoResponseV0,
6844                    GetEpochsInfoResponse
6845                )
6846            );
6847            assert_rejects_legacy_envelope!(
6848                FinalizedEpochInfos,
6849                platform::GetFinalizedEpochInfosRequest,
6850                request!(
6851                    get_finalized_epoch_infos_request,
6852                    GetFinalizedEpochInfosRequestV0,
6853                    GetFinalizedEpochInfosRequest {
6854                        end_epoch_index: 1,
6855                        prove: true
6856                    }
6857                ),
6858                legacy_response!(
6859                    get_finalized_epoch_infos_response,
6860                    get_finalized_epoch_infos_response_v0,
6861                    GetFinalizedEpochInfosResponseV0,
6862                    GetFinalizedEpochInfosResponse
6863                )
6864            );
6865            assert_rejects_legacy_envelope!(
6866                ProtocolVersionUpgrades,
6867                platform::GetProtocolVersionUpgradeStateRequest,
6868                request!(
6869                    get_protocol_version_upgrade_state_request,
6870                    GetProtocolVersionUpgradeStateRequestV0,
6871                    GetProtocolVersionUpgradeStateRequest { prove: true }
6872                ),
6873                legacy_response!(
6874                    get_protocol_version_upgrade_state_response,
6875                    get_protocol_version_upgrade_state_response_v0,
6876                    GetProtocolVersionUpgradeStateResponseV0,
6877                    GetProtocolVersionUpgradeStateResponse
6878                )
6879            );
6880            assert_rejects_legacy_envelope!(
6881                MasternodeProtocolVotes,
6882                platform::GetProtocolVersionUpgradeVoteStatusRequest,
6883                request!(
6884                    get_protocol_version_upgrade_vote_status_request,
6885                    GetProtocolVersionUpgradeVoteStatusRequestV0,
6886                    GetProtocolVersionUpgradeVoteStatusRequest {
6887                        count: 1,
6888                        prove: true
6889                    }
6890                ),
6891                legacy_response!(
6892                    get_protocol_version_upgrade_vote_status_response,
6893                    get_protocol_version_upgrade_vote_status_response_v0,
6894                    GetProtocolVersionUpgradeVoteStatusResponseV0,
6895                    GetProtocolVersionUpgradeVoteStatusResponse
6896                )
6897            );
6898            assert_rejects_legacy_envelope!(
6899                Elements,
6900                platform::GetPathElementsRequest,
6901                request!(
6902                    get_path_elements_request,
6903                    GetPathElementsRequestV0,
6904                    GetPathElementsRequest { prove: true }
6905                ),
6906                legacy_response!(
6907                    get_path_elements_response,
6908                    get_path_elements_response_v0,
6909                    GetPathElementsResponseV0,
6910                    GetPathElementsResponse
6911                )
6912            );
6913            assert_rejects_legacy_envelope!(
6914                TotalCreditsInPlatform,
6915                platform::GetTotalCreditsInPlatformRequest,
6916                request!(
6917                    get_total_credits_in_platform_request,
6918                    GetTotalCreditsInPlatformRequestV0,
6919                    GetTotalCreditsInPlatformRequest { prove: true }
6920                ),
6921                legacy_response!(
6922                    get_total_credits_in_platform_response,
6923                    get_total_credits_in_platform_response_v0,
6924                    GetTotalCreditsInPlatformResponseV0,
6925                    GetTotalCreditsInPlatformResponse
6926                )
6927            );
6928            assert_rejects_legacy_envelope!(
6929                ProposerBlockCounts,
6930                platform::GetEvonodesProposedEpochBlocksByIdsRequest,
6931                request!(
6932                    get_evonodes_proposed_epoch_blocks_by_ids_request,
6933                    GetEvonodesProposedEpochBlocksByIdsRequestV0,
6934                    GetEvonodesProposedEpochBlocksByIdsRequest {
6935                        epoch: Some(1),
6936                        ids: vec![id(1)],
6937                        prove: true
6938                    }
6939                ),
6940                legacy_response!(
6941                    get_evonodes_proposed_epoch_blocks_response,
6942                    get_evonodes_proposed_epoch_blocks_response_v0,
6943                    GetEvonodesProposedEpochBlocksResponseV0,
6944                    GetEvonodesProposedEpochBlocksResponse
6945                )
6946            );
6947            assert_rejects_legacy_envelope!(
6948                ProposerBlockCounts,
6949                platform::GetEvonodesProposedEpochBlocksByRangeRequest,
6950                request!(
6951                    get_evonodes_proposed_epoch_blocks_by_range_request,
6952                    GetEvonodesProposedEpochBlocksByRangeRequestV0,
6953                    GetEvonodesProposedEpochBlocksByRangeRequest {
6954                        epoch: Some(1),
6955                        prove: true
6956                    }
6957                ),
6958                legacy_response!(
6959                    get_evonodes_proposed_epoch_blocks_response,
6960                    get_evonodes_proposed_epoch_blocks_response_v0,
6961                    GetEvonodesProposedEpochBlocksResponseV0,
6962                    GetEvonodesProposedEpochBlocksResponse
6963                )
6964            );
6965        }
6966
6967        #[test]
6968        fn shielded_queries() {
6969            assert_rejects_legacy_envelope!(
6970                ShieldedPoolState,
6971                platform::GetShieldedPoolStateRequest,
6972                request!(
6973                    get_shielded_pool_state_request,
6974                    GetShieldedPoolStateRequestV0,
6975                    GetShieldedPoolStateRequest {
6976                        prove: true,
6977                        token_id: None
6978                    }
6979                ),
6980                legacy_response!(
6981                    get_shielded_pool_state_response,
6982                    get_shielded_pool_state_response_v0,
6983                    GetShieldedPoolStateResponseV0,
6984                    GetShieldedPoolStateResponse
6985                )
6986            );
6987            assert_rejects_legacy_envelope!(
6988                ShieldedNotesCount,
6989                platform::GetShieldedNotesCountRequest,
6990                request!(
6991                    get_shielded_notes_count_request,
6992                    GetShieldedNotesCountRequestV0,
6993                    GetShieldedNotesCountRequest {
6994                        prove: true,
6995                        token_id: None
6996                    }
6997                ),
6998                legacy_response!(
6999                    get_shielded_notes_count_response,
7000                    get_shielded_notes_count_response_v0,
7001                    GetShieldedNotesCountResponseV0,
7002                    GetShieldedNotesCountResponse
7003                )
7004            );
7005            assert_rejects_legacy_envelope!(
7006                ShieldedAnchors,
7007                platform::GetShieldedAnchorsRequest,
7008                request!(
7009                    get_shielded_anchors_request,
7010                    GetShieldedAnchorsRequestV0,
7011                    GetShieldedAnchorsRequest {
7012                        prove: true,
7013                        token_id: None
7014                    }
7015                ),
7016                legacy_response!(
7017                    get_shielded_anchors_response,
7018                    get_shielded_anchors_response_v0,
7019                    GetShieldedAnchorsResponseV0,
7020                    GetShieldedAnchorsResponse
7021                )
7022            );
7023            assert_rejects_legacy_envelope!(
7024                MostRecentShieldedAnchor,
7025                platform::GetMostRecentShieldedAnchorRequest,
7026                request!(
7027                    get_most_recent_shielded_anchor_request,
7028                    GetMostRecentShieldedAnchorRequestV0,
7029                    GetMostRecentShieldedAnchorRequest {
7030                        prove: true,
7031                        token_id: None
7032                    }
7033                ),
7034                legacy_response!(
7035                    get_most_recent_shielded_anchor_response,
7036                    get_most_recent_shielded_anchor_response_v0,
7037                    GetMostRecentShieldedAnchorResponseV0,
7038                    GetMostRecentShieldedAnchorResponse
7039                )
7040            );
7041            assert_rejects_legacy_envelope!(
7042                ShieldedEncryptedNotes,
7043                platform::GetShieldedEncryptedNotesRequest,
7044                request!(
7045                    get_shielded_encrypted_notes_request,
7046                    GetShieldedEncryptedNotesRequestV0,
7047                    GetShieldedEncryptedNotesRequest {
7048                        count: 1,
7049                        prove: true,
7050                        token_id: None
7051                    }
7052                ),
7053                legacy_response!(
7054                    get_shielded_encrypted_notes_response,
7055                    get_shielded_encrypted_notes_response_v0,
7056                    GetShieldedEncryptedNotesResponseV0,
7057                    GetShieldedEncryptedNotesResponse
7058                )
7059            );
7060            assert_rejects_legacy_envelope!(
7061                ShieldedNullifierStatuses,
7062                platform::GetShieldedNullifiersRequest,
7063                request!(
7064                    get_shielded_nullifiers_request,
7065                    GetShieldedNullifiersRequestV0,
7066                    GetShieldedNullifiersRequest {
7067                        nullifiers: vec![id(1)],
7068                        prove: true,
7069                        token_id: None
7070                    }
7071                ),
7072                legacy_response!(
7073                    get_shielded_nullifiers_response,
7074                    get_shielded_nullifiers_response_v0,
7075                    GetShieldedNullifiersResponseV0,
7076                    GetShieldedNullifiersResponse
7077                )
7078            );
7079        }
7080
7081        #[test]
7082        fn token_and_group_queries() {
7083            assert_rejects_legacy_envelope!(
7084                IdentityTokenBalances,
7085                platform::GetIdentityTokenBalancesRequest,
7086                request!(
7087                    get_identity_token_balances_request,
7088                    GetIdentityTokenBalancesRequestV0,
7089                    GetIdentityTokenBalancesRequest {
7090                        identity_id: id(1),
7091                        token_ids: vec![id(2)],
7092                        prove: true
7093                    }
7094                ),
7095                legacy_response!(
7096                    get_identity_token_balances_response,
7097                    get_identity_token_balances_response_v0,
7098                    GetIdentityTokenBalancesResponseV0,
7099                    GetIdentityTokenBalancesResponse
7100                )
7101            );
7102            assert_rejects_legacy_envelope!(
7103                IdentitiesTokenBalances,
7104                platform::GetIdentitiesTokenBalancesRequest,
7105                request!(
7106                    get_identities_token_balances_request,
7107                    GetIdentitiesTokenBalancesRequestV0,
7108                    GetIdentitiesTokenBalancesRequest {
7109                        token_id: id(2),
7110                        identity_ids: vec![id(1)],
7111                        prove: true
7112                    }
7113                ),
7114                legacy_response!(
7115                    get_identities_token_balances_response,
7116                    get_identities_token_balances_response_v0,
7117                    GetIdentitiesTokenBalancesResponseV0,
7118                    GetIdentitiesTokenBalancesResponse
7119                )
7120            );
7121            assert_rejects_legacy_envelope!(
7122                IdentityTokenInfos,
7123                platform::GetIdentityTokenInfosRequest,
7124                request!(
7125                    get_identity_token_infos_request,
7126                    GetIdentityTokenInfosRequestV0,
7127                    GetIdentityTokenInfosRequest {
7128                        identity_id: id(1),
7129                        token_ids: vec![id(2)],
7130                        prove: true
7131                    }
7132                ),
7133                legacy_response!(
7134                    get_identity_token_infos_response,
7135                    get_identity_token_infos_response_v0,
7136                    GetIdentityTokenInfosResponseV0,
7137                    GetIdentityTokenInfosResponse
7138                )
7139            );
7140            assert_rejects_legacy_envelope!(
7141                IdentitiesTokenInfos,
7142                platform::GetIdentitiesTokenInfosRequest,
7143                request!(
7144                    get_identities_token_infos_request,
7145                    GetIdentitiesTokenInfosRequestV0,
7146                    GetIdentitiesTokenInfosRequest {
7147                        token_id: id(2),
7148                        identity_ids: vec![id(1)],
7149                        prove: true
7150                    }
7151                ),
7152                legacy_response!(
7153                    get_identities_token_infos_response,
7154                    get_identities_token_infos_response_v0,
7155                    GetIdentitiesTokenInfosResponseV0,
7156                    GetIdentitiesTokenInfosResponse
7157                )
7158            );
7159            assert_rejects_legacy_envelope!(
7160                TokenStatuses,
7161                platform::GetTokenStatusesRequest,
7162                request!(
7163                    get_token_statuses_request,
7164                    GetTokenStatusesRequestV0,
7165                    GetTokenStatusesRequest {
7166                        token_ids: vec![id(2)],
7167                        prove: true
7168                    }
7169                ),
7170                legacy_response!(
7171                    get_token_statuses_response,
7172                    get_token_statuses_response_v0,
7173                    GetTokenStatusesResponseV0,
7174                    GetTokenStatusesResponse
7175                )
7176            );
7177            assert_rejects_legacy_envelope!(
7178                TotalSingleTokenBalance,
7179                platform::GetTokenTotalSupplyRequest,
7180                request!(
7181                    get_token_total_supply_request,
7182                    GetTokenTotalSupplyRequestV0,
7183                    GetTokenTotalSupplyRequest {
7184                        token_id: id(2),
7185                        prove: true
7186                    }
7187                ),
7188                legacy_response!(
7189                    get_token_total_supply_response,
7190                    get_token_total_supply_response_v0,
7191                    GetTokenTotalSupplyResponseV0,
7192                    GetTokenTotalSupplyResponse
7193                )
7194            );
7195            assert_rejects_legacy_envelope!(
7196                TokenContractInfo,
7197                platform::GetTokenContractInfoRequest,
7198                request!(
7199                    get_token_contract_info_request,
7200                    GetTokenContractInfoRequestV0,
7201                    GetTokenContractInfoRequest {
7202                        token_id: id(2),
7203                        prove: true
7204                    }
7205                ),
7206                legacy_response!(
7207                    get_token_contract_info_response,
7208                    get_token_contract_info_response_v0,
7209                    GetTokenContractInfoResponseV0,
7210                    GetTokenContractInfoResponse
7211                )
7212            );
7213            assert_rejects_legacy_envelope!(
7214                TokenDirectPurchasePrices,
7215                platform::GetTokenDirectPurchasePricesRequest,
7216                request!(
7217                    get_token_direct_purchase_prices_request,
7218                    GetTokenDirectPurchasePricesRequestV0,
7219                    GetTokenDirectPurchasePricesRequest {
7220                        token_ids: vec![id(2)],
7221                        prove: true
7222                    }
7223                ),
7224                legacy_response!(
7225                    get_token_direct_purchase_prices_response,
7226                    get_token_direct_purchase_prices_response_v0,
7227                    GetTokenDirectPurchasePricesResponseV0,
7228                    GetTokenDirectPurchasePricesResponse
7229                )
7230            );
7231            assert_rejects_legacy_envelope!(
7232                TokenPreProgrammedDistributions,
7233                platform::GetTokenPreProgrammedDistributionsRequest,
7234                request!(
7235                    get_token_pre_programmed_distributions_request,
7236                    GetTokenPreProgrammedDistributionsRequestV0,
7237                    GetTokenPreProgrammedDistributionsRequest {
7238                        token_id: id(2),
7239                        prove: true
7240                    }
7241                ),
7242                legacy_response!(
7243                    get_token_pre_programmed_distributions_response,
7244                    get_token_pre_programmed_distributions_response_v0,
7245                    GetTokenPreProgrammedDistributionsResponseV0,
7246                    GetTokenPreProgrammedDistributionsResponse
7247                )
7248            );
7249            assert_rejects_legacy_envelope!(
7250                Group,
7251                platform::GetGroupInfoRequest,
7252                request!(
7253                    get_group_info_request,
7254                    GetGroupInfoRequestV0,
7255                    GetGroupInfoRequest {
7256                        contract_id: id(2),
7257                        prove: true
7258                    }
7259                ),
7260                legacy_response!(
7261                    get_group_info_response,
7262                    get_group_info_response_v0,
7263                    GetGroupInfoResponseV0,
7264                    GetGroupInfoResponse
7265                )
7266            );
7267            assert_rejects_legacy_envelope!(
7268                Groups,
7269                platform::GetGroupInfosRequest,
7270                request!(
7271                    get_group_infos_request,
7272                    GetGroupInfosRequestV0,
7273                    GetGroupInfosRequest {
7274                        contract_id: id(2),
7275                        prove: true
7276                    }
7277                ),
7278                legacy_response!(
7279                    get_group_infos_response,
7280                    get_group_infos_response_v0,
7281                    GetGroupInfosResponseV0,
7282                    GetGroupInfosResponse
7283                )
7284            );
7285            assert_rejects_legacy_envelope!(
7286                GroupActions,
7287                platform::GetGroupActionsRequest,
7288                request!(
7289                    get_group_actions_request,
7290                    GetGroupActionsRequestV0,
7291                    GetGroupActionsRequest {
7292                        contract_id: id(2),
7293                        prove: true
7294                    }
7295                ),
7296                legacy_response!(
7297                    get_group_actions_response,
7298                    get_group_actions_response_v0,
7299                    GetGroupActionsResponseV0,
7300                    GetGroupActionsResponse
7301                )
7302            );
7303            assert_rejects_legacy_envelope!(
7304                GroupActionSigners,
7305                platform::GetGroupActionSignersRequest,
7306                request!(
7307                    get_group_action_signers_request,
7308                    GetGroupActionSignersRequestV0,
7309                    GetGroupActionSignersRequest {
7310                        contract_id: id(2),
7311                        action_id: id(3),
7312                        prove: true
7313                    }
7314                ),
7315                legacy_response!(
7316                    get_group_action_signers_response,
7317                    get_group_action_signers_response_v0,
7318                    GetGroupActionSignersResponseV0,
7319                    GetGroupActionSignersResponse
7320                )
7321            );
7322        }
7323    }
7324}