drive_proof_verifier/proof/
data_contracts_by_range.rs1use crate::error::MapGroveDbError;
2use crate::types::data_contracts_by_range::DataContractsByRange;
3use crate::verify::{supported_grovedb_proof_bytes, verify_tenderdash_proof};
4use crate::{ContextProvider, Error, FromProof};
5use dapi_grpc::platform::v0::get_data_contracts_by_range_request::get_data_contracts_by_range_request_v0::Start;
6use dapi_grpc::platform::v0::{
7 get_data_contracts_by_range_request, GetDataContractsByRangeRequest,
8 GetDataContractsResponse, Proof, ResponseMetadata,
9};
10use dapi_grpc::platform::VersionedGrpcResponse;
11use dpp::dashcore::Network;
12use dpp::version::PlatformVersion;
13use drive::drive::Drive;
14
15struct PageParameters {
17 start_at: Option<([u8; 32], bool)>,
18 limit: u16,
19 ids_only: bool,
20}
21
22fn page_parameters(
26 request: GetDataContractsByRangeRequest,
27 platform_version: &PlatformVersion,
28) -> Result<PageParameters, Error> {
29 let get_data_contracts_by_range_request::Version::V0(v0) =
30 request.version.ok_or(Error::EmptyVersion)?;
31
32 let max_returned_elements = platform_version.drive_abci.query.max_returned_elements;
33 let limit = match v0.limit {
34 None => max_returned_elements,
35 Some(requested) => match u16::try_from(requested) {
36 Ok(limit) if (1..=max_returned_elements).contains(&limit) => limit,
37 _ => {
38 return Err(Error::RequestError {
39 error: format!(
40 "limit {requested} is out of bounds, it must be between 1 and {max_returned_elements}"
41 ),
42 })
43 }
44 },
45 };
46
47 let start_at = match v0.start {
48 None => None,
49 Some(Start::StartAfter(cursor)) => {
50 Some((contract_id_from_cursor(cursor, "start_after")?, false))
51 }
52 Some(Start::StartAt(cursor)) => Some((contract_id_from_cursor(cursor, "start_at")?, true)),
53 };
54
55 Ok(PageParameters {
56 start_at,
57 limit,
58 ids_only: v0.ids_only,
59 })
60}
61
62fn contract_id_from_cursor(cursor: Vec<u8>, field: &str) -> Result<[u8; 32], Error> {
63 cursor
64 .try_into()
65 .map_err(|cursor: Vec<u8>| Error::RequestError {
66 error: format!(
67 "{field} must be a 32 byte contract id, got {} bytes",
68 cursor.len()
69 ),
70 })
71}
72
73impl FromProof<GetDataContractsByRangeRequest> for DataContractsByRange {
74 type Request = GetDataContractsByRangeRequest;
75 type Response = GetDataContractsResponse;
76
77 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
78 request: I,
79 response: O,
80 _network: Network,
81 platform_version: &PlatformVersion,
82 provider: &'a dyn ContextProvider,
83 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
84 where
85 Self: Sized + 'a,
86 {
87 let request: Self::Request = request.into();
88 let response: Self::Response = response.into();
89
90 let PageParameters {
91 start_at,
92 limit,
93 ids_only,
94 } = page_parameters(request, platform_version)?;
95
96 let metadata = response
97 .metadata()
98 .or(Err(Error::EmptyResponseMetadata))?
99 .clone();
100 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
101
102 let (root_hash, page) = Drive::verify_contracts_by_range(
103 supported_grovedb_proof_bytes(&proof)?,
104 start_at,
105 limit,
106 ids_only,
107 platform_version,
108 )
109 .map_drive_error(&proof, &metadata)?;
110
111 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
112
113 let page: DataContractsByRange = page.into_iter().collect();
116
117 Ok((Some(page), metadata, proof))
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use dapi_grpc::platform::v0::get_data_contracts_by_range_request::{
125 GetDataContractsByRangeRequestV0, Version as ReqVersion,
126 };
127 use dapi_grpc::platform::v0::get_data_contracts_response::{
128 get_data_contracts_response_v0::Result as RespResult, GetDataContractsResponseV0,
129 Version as RespVersion,
130 };
131 use dash_context_provider::ContextProviderError;
132 use dpp::data_contract::TokenConfiguration;
133 use dpp::prelude::{CoreBlockHeight, DataContract, Identifier};
134 use std::sync::Arc;
135
136 struct UnreachableProvider;
139
140 impl ContextProvider for UnreachableProvider {
141 fn get_data_contract(
142 &self,
143 _id: &Identifier,
144 _pv: &PlatformVersion,
145 ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
146 panic!("context provider should not be called")
147 }
148
149 fn get_token_configuration(
150 &self,
151 _id: &Identifier,
152 ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
153 panic!("context provider should not be called")
154 }
155
156 fn get_quorum_public_key(
157 &self,
158 _qt: u32,
159 _qh: [u8; 32],
160 _h: u32,
161 ) -> Result<[u8; 48], ContextProviderError> {
162 panic!("context provider should not be called")
163 }
164
165 fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
166 panic!("context provider should not be called")
167 }
168 }
169
170 fn pv() -> &'static PlatformVersion {
171 PlatformVersion::latest()
172 }
173
174 fn response_with_proof_and_metadata() -> GetDataContractsResponse {
175 GetDataContractsResponse {
176 version: Some(RespVersion::V0(GetDataContractsResponseV0 {
177 result: Some(RespResult::Proof(Proof::default())),
178 metadata: Some(ResponseMetadata::default()),
179 })),
180 }
181 }
182
183 fn request(v0: GetDataContractsByRangeRequestV0) -> GetDataContractsByRangeRequest {
184 GetDataContractsByRangeRequest {
185 version: Some(ReqVersion::V0(v0)),
186 }
187 }
188
189 fn page_v0(limit: Option<u32>, start: Option<Start>) -> GetDataContractsByRangeRequestV0 {
190 GetDataContractsByRangeRequestV0 {
191 limit,
192 start,
193 ids_only: false,
194 prove: true,
195 }
196 }
197
198 fn from_proof_error(request: GetDataContractsByRangeRequest) -> Error {
199 <DataContractsByRange as FromProof<_>>::maybe_from_proof(
200 request,
201 response_with_proof_and_metadata(),
202 Network::Testnet,
203 pv(),
204 &UnreachableProvider,
205 )
206 .unwrap_err()
207 }
208
209 #[test]
210 fn should_fail_with_empty_version_when_request_has_no_version() {
211 let err = from_proof_error(GetDataContractsByRangeRequest { version: None });
212 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
213 }
214
215 #[test]
216 fn should_reject_start_after_that_is_not_a_contract_id() {
217 let err = from_proof_error(request(page_v0(
218 None,
219 Some(Start::StartAfter(vec![0u8; 8])),
220 )));
221 assert!(
222 matches!(err, Error::RequestError { ref error } if error.contains("start_after")),
223 "got: {err:?}"
224 );
225 }
226
227 #[test]
228 fn should_reject_zero_limit() {
229 let err = from_proof_error(request(page_v0(Some(0), None)));
230 assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
231 }
232
233 #[test]
234 fn should_reject_limit_above_the_page_cap() {
235 let above_cap = pv().drive_abci.query.max_returned_elements as u32 + 1;
236 let err = from_proof_error(request(page_v0(Some(above_cap), None)));
237 assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
238 }
239}