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