Skip to main content

drive_proof_verifier/proof/
data_contracts_latest_versions.rs

1use crate::error::MapGroveDbError;
2use crate::types::data_contracts_latest_versions::{
3    DataContractLatestVersion, DataContractsLatestVersions,
4};
5use crate::verify::{supported_grovedb_proof_bytes, verify_tenderdash_proof};
6use crate::{ContextProvider, Error, FromProof};
7use dapi_grpc::platform::v0::{
8    get_data_contracts_latest_versions_request, GetDataContractsLatestVersionsRequest,
9    GetDataContractsLatestVersionsResponse, Proof, ResponseMetadata,
10};
11use dapi_grpc::platform::VersionedGrpcResponse;
12use dpp::dashcore::Network;
13use dpp::data_contract::accessors::v0::DataContractV0Getters;
14use dpp::prelude::Identifier;
15use dpp::version::PlatformVersion;
16use drive::drive::Drive;
17
18impl FromProof<GetDataContractsLatestVersionsRequest> for DataContractsLatestVersions {
19    type Request = GetDataContractsLatestVersionsRequest;
20    type Response = GetDataContractsLatestVersionsResponse;
21
22    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
23        request: I,
24        response: O,
25        _network: Network,
26        platform_version: &PlatformVersion,
27        provider: &'a dyn ContextProvider,
28    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
29    where
30        Self: Sized + 'a,
31    {
32        let request: Self::Request = request.into();
33        let response: Self::Response = response.into();
34
35        let proof = response.proof().or(Err(Error::NoProofInResult))?;
36        let mtd = response.metadata().or(Err(Error::EmptyResponseMetadata))?;
37
38        let get_data_contracts_latest_versions_request::Version::V0(v0) =
39            request.version.ok_or(Error::EmptyVersion)?;
40        let include_contracts = v0.include_contracts;
41        let ids = v0
42            .ids
43            .into_iter()
44            .map(|id| {
45                let len = id.len();
46                id.try_into().map_err(|_| Error::RequestError {
47                    error: format!("wrong id size: expected: 32, got: {len}"),
48                })
49            })
50            .collect::<Result<Vec<[u8; 32]>, Error>>()?;
51
52        // From protocol version 14 (helper version 1) every contract carries a version item,
53        // and a request without the contracts is answered by a proof of those items. Before
54        // that, and whenever the contracts were asked for, the proof is the multi-contract
55        // proof, which carries the contracts: each version is read off the verified contract,
56        // and the contract is kept only when it was asked for.
57        //
58        // The shape follows the protocol version the responding node ran at, which the
59        // metadata carries and the quorum signature covers, not the version this client is
60        // seeded with: a client still at 13 must accept the version item proof of an upgraded
61        // network (its version only ratchets after a proof verifies), and a client already at
62        // 14 must accept the multi-contract proof of a network that has not activated yet. A
63        // wrong shape can only fail verification, never pass a bad proof.
64        let response_platform_version =
65            PlatformVersion::get(mtd.protocol_version).unwrap_or(platform_version);
66        let from_version_items = match response_platform_version
67            .drive_abci
68            .query
69            .data_contract_query_helpers
70            .latest_versions_read
71        {
72            0 => false,
73            1 => !include_contracts,
74            version => {
75                return Err(Error::DriveError {
76                    error: format!(
77                        "unknown data contract latest versions helper version {version}"
78                    ),
79                })
80            }
81        };
82
83        let proof_bytes = supported_grovedb_proof_bytes(proof)?;
84
85        let versions = if from_version_items {
86            let (root_hash, versions) = Drive::verify_contracts_versions(
87                proof_bytes,
88                ids.as_slice(),
89                response_platform_version,
90            )
91            .map_drive_error(proof, mtd)?;
92
93            verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
94
95            versions
96                .into_iter()
97                .map(|(id, maybe_version)| {
98                    let id = identifier_from_bytes(&id)?;
99                    let entry = maybe_version.map(|version| DataContractLatestVersion {
100                        version,
101                        data_contract: None,
102                    });
103                    Ok((id, entry))
104                })
105                .collect::<Result<DataContractsLatestVersions, Error>>()?
106        } else {
107            let (root_hash, contracts) = Drive::verify_contracts(
108                proof_bytes,
109                false,
110                ids.as_slice(),
111                response_platform_version,
112            )
113            .map_drive_error(proof, mtd)?;
114
115            verify_tenderdash_proof(proof, mtd, &root_hash, provider)?;
116
117            contracts
118                .into_iter()
119                .map(|(id, maybe_contract)| {
120                    let id = identifier_from_bytes(&id)?;
121                    let entry = maybe_contract.map(|contract| DataContractLatestVersion {
122                        version: contract.version(),
123                        data_contract: include_contracts.then_some(contract),
124                    });
125                    Ok((id, entry))
126                })
127                .collect::<Result<DataContractsLatestVersions, Error>>()?
128        };
129
130        // Every requested id has an entry, so the result is never absent.
131        Ok((Some(versions), mtd.clone(), proof.clone()))
132    }
133}
134
135fn identifier_from_bytes(bytes: &[u8; 32]) -> Result<Identifier, Error> {
136    Identifier::from_bytes(bytes).map_err(|e| Error::ResultEncodingError {
137        error: e.to_string(),
138    })
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use dapi_grpc::platform::v0::get_data_contracts_latest_versions_request::{
145        GetDataContractsLatestVersionsRequestV0, Version as ReqVersion,
146    };
147    use dapi_grpc::platform::v0::get_data_contracts_latest_versions_response::{
148        get_data_contracts_latest_versions_response_v0::Result as RespResult,
149        GetDataContractsLatestVersionsResponseV0, Version as RespVersion,
150    };
151    use dash_context_provider::ContextProviderError;
152    use dpp::data_contract::TokenConfiguration;
153    use dpp::prelude::{CoreBlockHeight, DataContract};
154    use std::sync::Arc;
155
156    /// Context provider that panics if called: every test here fails on the request before
157    /// proof verification, so the provider must stay unreachable.
158    struct UnreachableProvider;
159
160    impl ContextProvider for UnreachableProvider {
161        fn get_data_contract(
162            &self,
163            _id: &Identifier,
164            _pv: &PlatformVersion,
165        ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
166            panic!("context provider should not be called")
167        }
168
169        fn get_token_configuration(
170            &self,
171            _id: &Identifier,
172        ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
173            panic!("context provider should not be called")
174        }
175
176        fn get_quorum_public_key(
177            &self,
178            _qt: u32,
179            _qh: [u8; 32],
180            _h: u32,
181        ) -> Result<[u8; 48], ContextProviderError> {
182            panic!("context provider should not be called")
183        }
184
185        fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
186            panic!("context provider should not be called")
187        }
188    }
189
190    fn response_with_proof_and_metadata() -> GetDataContractsLatestVersionsResponse {
191        GetDataContractsLatestVersionsResponse {
192            version: Some(RespVersion::V0(GetDataContractsLatestVersionsResponseV0 {
193                result: Some(RespResult::Proof(Proof::default())),
194                metadata: Some(ResponseMetadata::default()),
195            })),
196        }
197    }
198
199    fn request(ids: Vec<Vec<u8>>) -> GetDataContractsLatestVersionsRequest {
200        GetDataContractsLatestVersionsRequest {
201            version: Some(ReqVersion::V0(GetDataContractsLatestVersionsRequestV0 {
202                ids,
203                include_contracts: false,
204                prove: true,
205            })),
206        }
207    }
208
209    fn from_proof_error(
210        request: GetDataContractsLatestVersionsRequest,
211        response: GetDataContractsLatestVersionsResponse,
212    ) -> Error {
213        <DataContractsLatestVersions as FromProof<_>>::maybe_from_proof(
214            request,
215            response,
216            Network::Testnet,
217            PlatformVersion::latest(),
218            &UnreachableProvider,
219        )
220        .unwrap_err()
221    }
222
223    #[test]
224    fn should_fail_with_empty_version_when_request_has_no_version() {
225        let err = from_proof_error(
226            GetDataContractsLatestVersionsRequest { version: None },
227            response_with_proof_and_metadata(),
228        );
229        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
230    }
231
232    #[test]
233    fn should_reject_id_that_is_not_32_bytes() {
234        let err = from_proof_error(
235            request(vec![vec![1u8; 32], vec![0u8; 5]]),
236            response_with_proof_and_metadata(),
237        );
238        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
239    }
240
241    #[test]
242    fn should_fail_without_proof_when_response_is_empty() {
243        let err = from_proof_error(
244            request(vec![vec![0u8; 32]]),
245            GetDataContractsLatestVersionsResponse::default(),
246        );
247        assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
248    }
249}