Skip to main content

drive_proof_verifier/proof/
identity_keys_remaining_budgets.rs

1use crate::error::MapGroveDbError;
2use crate::types::identity_keys_remaining_budgets::IdentityKeysRemainingBudgets;
3use crate::verify::{supported_grovedb_proof_bytes, verify_tenderdash_proof};
4use crate::{ContextProvider, Error, FromProof};
5use dapi_grpc::platform::v0::{
6    get_identity_keys_remaining_budgets_request, GetIdentityKeysRemainingBudgetsRequest,
7    GetIdentityKeysRemainingBudgetsResponse, Proof, ResponseMetadata,
8};
9use dapi_grpc::platform::VersionedGrpcResponse;
10use dpp::dashcore::Network;
11use dpp::identity::KeyID;
12use dpp::version::PlatformVersion;
13use drive::drive::Drive;
14use std::collections::BTreeSet;
15
16/// The identity and key ids a request asks about. The proof is verified against a query rebuilt
17/// from them, which answers each key id once, so a request the node would refuse (no key id, or
18/// a repeated one) is refused here too, before any verification.
19pub(crate) fn parse_request(
20    request: GetIdentityKeysRemainingBudgetsRequest,
21) -> Result<([u8; 32], Vec<KeyID>), Error> {
22    let get_identity_keys_remaining_budgets_request::Version::V0(v0) =
23        request.version.ok_or(Error::EmptyVersion)?;
24    let identity_id = <[u8; 32]>::try_from(v0.identity_id).map_err(|_| Error::RequestError {
25        error: "can't convert identity_id to [u8; 32]".to_string(),
26    })?;
27    if v0.key_ids.is_empty() {
28        return Err(Error::RequestError {
29            error: "key_ids must name at least one key".to_string(),
30        });
31    }
32    if v0.key_ids.iter().collect::<BTreeSet<_>>().len() != v0.key_ids.len() {
33        return Err(Error::RequestError {
34            error: "key_ids must not repeat a key id".to_string(),
35        });
36    }
37    Ok((identity_id, v0.key_ids))
38}
39
40impl FromProof<GetIdentityKeysRemainingBudgetsRequest> for IdentityKeysRemainingBudgets {
41    type Request = GetIdentityKeysRemainingBudgetsRequest;
42    type Response = GetIdentityKeysRemainingBudgetsResponse;
43
44    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
45        request: I,
46        response: O,
47        _network: Network,
48        platform_version: &PlatformVersion,
49        provider: &'a dyn ContextProvider,
50    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
51    where
52        Self: Sized + 'a,
53    {
54        let request: Self::Request = request.into();
55        let response: Self::Response = response.into();
56
57        let (identity_id, key_ids) = parse_request(request)?;
58
59        let metadata = response
60            .metadata()
61            .or(Err(Error::EmptyResponseMetadata))?
62            .clone();
63
64        let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
65
66        let (root_hash, result) = Drive::verify_identity_keys_remaining_budgets(
67            supported_grovedb_proof_bytes(&proof)?,
68            identity_id,
69            &key_ids,
70            false,
71            platform_version,
72        )
73        .map_drive_error(&proof, &metadata)?;
74
75        verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
76
77        Ok((Some(result), metadata, proof))
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use dapi_grpc::platform::v0::get_identity_keys_remaining_budgets_request::GetIdentityKeysRemainingBudgetsRequestV0;
85
86    fn request(
87        identity_id: Vec<u8>,
88        key_ids: Vec<KeyID>,
89    ) -> GetIdentityKeysRemainingBudgetsRequest {
90        GetIdentityKeysRemainingBudgetsRequest {
91            version: Some(get_identity_keys_remaining_budgets_request::Version::V0(
92                GetIdentityKeysRemainingBudgetsRequestV0 {
93                    identity_id,
94                    key_ids,
95                    prove: true,
96                },
97            )),
98        }
99    }
100
101    #[test]
102    fn should_parse_a_well_formed_request() {
103        let (identity_id, key_ids) =
104            parse_request(request(vec![4; 32], vec![7, 1])).expect("expected to parse");
105        assert_eq!(identity_id, [4; 32]);
106        assert_eq!(key_ids, vec![7, 1]);
107    }
108
109    #[test]
110    fn should_refuse_a_request_the_node_would_refuse_before_verifying_anything() {
111        assert!(matches!(
112            parse_request(GetIdentityKeysRemainingBudgetsRequest { version: None }),
113            Err(Error::EmptyVersion)
114        ));
115        for (bad_request, expected) in [
116            (request(vec![4; 31], vec![1]), "identity_id"),
117            (request(vec![4; 32], vec![]), "at least one key"),
118            (request(vec![4; 32], vec![2, 3, 2]), "must not repeat"),
119        ] {
120            assert!(
121                matches!(
122                    parse_request(bad_request),
123                    Err(Error::RequestError { error }) if error.contains(expected)
124                ),
125                "expected a request error about `{expected}`"
126            );
127        }
128    }
129}