1use crate::error::MapGroveDbError;
4use crate::types::contract_groups::{
5 members_limit_from_request, members_query_from_request, ContractGroupInfo,
6 ContractGroupMembersPage, ContractGroupMembershipsForContract,
7};
8use crate::verify::{supported_grovedb_proof_bytes, verify_tenderdash_proof};
9use crate::{ContextProvider, Error, FromProof};
10use dapi_grpc::platform::v0::{
11 get_contract_group_info_request, get_contract_group_members_request,
12 get_contract_groups_for_contract_request, GetContractGroupInfoRequest,
13 GetContractGroupInfoResponse, GetContractGroupMembersRequest, GetContractGroupMembersResponse,
14 GetContractGroupsForContractRequest, GetContractGroupsForContractResponse, Proof,
15 ResponseMetadata,
16};
17use dapi_grpc::platform::VersionedGrpcResponse;
18use dpp::dashcore::Network;
19use dpp::identifier::Identifier;
20use dpp::version::PlatformVersion;
21use drive::drive::Drive;
22
23fn identifier_from_request(bytes: Vec<u8>, what: &str) -> Result<Identifier, Error> {
24 Identifier::from_bytes(&bytes).map_err(|_| Error::RequestError {
25 error: format!(
26 "{what} must be a 32 byte identifier, got {} bytes",
27 bytes.len()
28 ),
29 })
30}
31
32impl FromProof<GetContractGroupInfoRequest> for ContractGroupInfo {
33 type Request = GetContractGroupInfoRequest;
34 type Response = GetContractGroupInfoResponse;
35
36 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
37 request: I,
38 response: O,
39 _network: Network,
40 platform_version: &PlatformVersion,
41 provider: &'a dyn ContextProvider,
42 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
43 where
44 Self: Sized + 'a,
45 {
46 let request: Self::Request = request.into();
47 let response: Self::Response = response.into();
48
49 let get_contract_group_info_request::Version::V0(v0) =
50 request.version.ok_or(Error::EmptyVersion)?;
51 let contract_group_id = identifier_from_request(v0.contract_group_id, "contract_group_id")?;
52
53 let metadata = response
54 .metadata()
55 .or(Err(Error::EmptyResponseMetadata))?
56 .clone();
57 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
58
59 let (root_hash, info) = Drive::verify_contract_group_info(
60 supported_grovedb_proof_bytes(&proof)?,
61 contract_group_id,
62 platform_version,
63 )
64 .map_drive_error(&proof, &metadata)?;
65
66 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
67
68 Ok((info, metadata, proof))
69 }
70}
71
72impl FromProof<GetContractGroupMembersRequest> for ContractGroupMembersPage {
73 type Request = GetContractGroupMembersRequest;
74 type Response = GetContractGroupMembersResponse;
75
76 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
77 request: I,
78 response: O,
79 _network: Network,
80 platform_version: &PlatformVersion,
81 provider: &'a dyn ContextProvider,
82 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
83 where
84 Self: Sized + 'a,
85 {
86 let request: Self::Request = request.into();
87 let response: Self::Response = response.into();
88
89 let get_contract_group_members_request::Version::V0(v0) =
90 request.version.ok_or(Error::EmptyVersion)?;
91 let contract_group_id = identifier_from_request(v0.contract_group_id, "contract_group_id")?;
92 let query = members_query_from_request(v0.members)?;
93 let limit = members_limit_from_request(v0.limit, platform_version)?;
94
95 let metadata = response
96 .metadata()
97 .or(Err(Error::EmptyResponseMetadata))?
98 .clone();
99 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
100
101 let (root_hash, page) = Drive::verify_contract_group_members(
102 supported_grovedb_proof_bytes(&proof)?,
103 contract_group_id,
104 &query,
105 limit,
106 platform_version,
107 )
108 .map_drive_error(&proof, &metadata)?;
109
110 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
111
112 Ok((Some(page), metadata, proof))
115 }
116}
117
118impl FromProof<GetContractGroupsForContractRequest> for ContractGroupMembershipsForContract {
119 type Request = GetContractGroupsForContractRequest;
120 type Response = GetContractGroupsForContractResponse;
121
122 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
123 request: I,
124 response: O,
125 _network: Network,
126 platform_version: &PlatformVersion,
127 provider: &'a dyn ContextProvider,
128 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
129 where
130 Self: Sized + 'a,
131 {
132 let request: Self::Request = request.into();
133 let response: Self::Response = response.into();
134
135 let get_contract_groups_for_contract_request::Version::V0(v0) =
136 request.version.ok_or(Error::EmptyVersion)?;
137 let contract_id = identifier_from_request(v0.contract_id, "contract_id")?;
138
139 let metadata = response
140 .metadata()
141 .or(Err(Error::EmptyResponseMetadata))?
142 .clone();
143 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
144
145 let (root_hash, memberships) = Drive::verify_contract_group_memberships_for_contract(
146 supported_grovedb_proof_bytes(&proof)?,
147 contract_id,
148 platform_version,
149 )
150 .map_drive_error(&proof, &metadata)?;
151
152 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
153
154 Ok((Some(memberships), metadata, proof))
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use dapi_grpc::platform::v0::get_contract_group_info_request::GetContractGroupInfoRequestV0;
163 use dapi_grpc::platform::v0::get_contract_group_info_response::{
164 get_contract_group_info_response_v0::Result as InfoResult, GetContractGroupInfoResponseV0,
165 Version as InfoResponseVersion,
166 };
167 use dapi_grpc::platform::v0::get_contract_group_members_request::{
168 ContractMembersQuery, GetContractGroupMembersRequestV0,
169 };
170 use dapi_grpc::platform::v0::get_contract_group_members_response::{
171 get_contract_group_members_response_v0::Result as MembersResult,
172 GetContractGroupMembersResponseV0, Version as MembersResponseVersion,
173 };
174 use dapi_grpc::platform::v0::get_contract_group_members_request::get_contract_group_members_request_v0::Members;
175 use dash_context_provider::ContextProviderError;
176 use dpp::data_contract::TokenConfiguration;
177 use dpp::prelude::{CoreBlockHeight, DataContract};
178 use std::sync::Arc;
179
180 struct UnreachableProvider;
183
184 impl ContextProvider for UnreachableProvider {
185 fn get_data_contract(
186 &self,
187 _id: &Identifier,
188 _pv: &PlatformVersion,
189 ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
190 panic!("context provider should not be called")
191 }
192
193 fn get_token_configuration(
194 &self,
195 _id: &Identifier,
196 ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
197 panic!("context provider should not be called")
198 }
199
200 fn get_quorum_public_key(
201 &self,
202 _qt: u32,
203 _qh: [u8; 32],
204 _h: u32,
205 ) -> Result<[u8; 48], ContextProviderError> {
206 panic!("context provider should not be called")
207 }
208
209 fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
210 panic!("context provider should not be called")
211 }
212 }
213
214 fn info_request(contract_group_id: Vec<u8>) -> GetContractGroupInfoRequest {
215 GetContractGroupInfoRequest {
216 version: Some(get_contract_group_info_request::Version::V0(
217 GetContractGroupInfoRequestV0 {
218 contract_group_id,
219 prove: true,
220 },
221 )),
222 }
223 }
224
225 fn info_response(result: Option<InfoResult>) -> GetContractGroupInfoResponse {
226 GetContractGroupInfoResponse {
227 version: Some(InfoResponseVersion::V0(GetContractGroupInfoResponseV0 {
228 result,
229 metadata: Some(ResponseMetadata::default()),
230 })),
231 }
232 }
233
234 fn members_request(
235 members: Option<Members>,
236 limit: Option<u32>,
237 ) -> GetContractGroupMembersRequest {
238 GetContractGroupMembersRequest {
239 version: Some(get_contract_group_members_request::Version::V0(
240 GetContractGroupMembersRequestV0 {
241 contract_group_id: vec![1; 32],
242 members,
243 limit,
244 prove: true,
245 },
246 )),
247 }
248 }
249
250 fn members_response_with_proof() -> GetContractGroupMembersResponse {
251 GetContractGroupMembersResponse {
252 version: Some(MembersResponseVersion::V0(
253 GetContractGroupMembersResponseV0 {
254 result: Some(MembersResult::Proof(Proof::default())),
255 metadata: Some(ResponseMetadata::default()),
256 },
257 )),
258 }
259 }
260
261 #[test]
262 fn info_should_fail_with_empty_version_when_request_has_no_version() {
263 let err = <ContractGroupInfo as FromProof<_>>::maybe_from_proof(
264 GetContractGroupInfoRequest { version: None },
265 info_response(Some(InfoResult::Proof(Proof::default()))),
266 Network::Testnet,
267 PlatformVersion::latest(),
268 &UnreachableProvider,
269 )
270 .unwrap_err();
271 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
272 }
273
274 #[test]
275 fn info_should_reject_id_that_is_not_32_bytes() {
276 let err = <ContractGroupInfo as FromProof<_>>::maybe_from_proof(
277 info_request(vec![1; 5]),
278 info_response(Some(InfoResult::Proof(Proof::default()))),
279 Network::Testnet,
280 PlatformVersion::latest(),
281 &UnreachableProvider,
282 )
283 .unwrap_err();
284 assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
285 }
286
287 #[test]
288 fn info_should_fail_without_proof_when_response_carries_none() {
289 let err = <ContractGroupInfo as FromProof<_>>::maybe_from_proof(
290 info_request(vec![1; 32]),
291 info_response(None),
292 Network::Testnet,
293 PlatformVersion::latest(),
294 &UnreachableProvider,
295 )
296 .unwrap_err();
297 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
298 }
299
300 #[test]
301 fn members_should_reject_a_request_without_a_kind() {
302 let err = <ContractGroupMembersPage as FromProof<_>>::maybe_from_proof(
303 members_request(None, None),
304 members_response_with_proof(),
305 Network::Testnet,
306 PlatformVersion::latest(),
307 &UnreachableProvider,
308 )
309 .unwrap_err();
310 assert!(
311 matches!(&err, Error::RequestError { error } if error.contains("members")),
312 "got: {err:?}"
313 );
314 }
315
316 #[test]
317 fn members_should_reject_out_of_bounds_limits() {
318 let platform_version = PlatformVersion::latest();
319 let too_many = platform_version.drive_abci.query.max_returned_elements as u32 + 1;
320 for limit in [0, too_many] {
321 let err = <ContractGroupMembersPage as FromProof<_>>::maybe_from_proof(
322 members_request(
323 Some(Members::Contracts(ContractMembersQuery {
324 start_after: None,
325 })),
326 Some(limit),
327 ),
328 members_response_with_proof(),
329 Network::Testnet,
330 platform_version,
331 &UnreachableProvider,
332 )
333 .unwrap_err();
334 assert!(
335 matches!(&err, Error::RequestError { error } if error.contains("limit")),
336 "limit {limit}: {err:?}"
337 );
338 }
339 }
340
341 #[test]
342 fn members_should_reject_a_cursor_that_is_not_an_identifier() {
343 let err = <ContractGroupMembersPage as FromProof<_>>::maybe_from_proof(
344 members_request(
345 Some(Members::Contracts(ContractMembersQuery {
346 start_after: Some(vec![1; 3]),
347 })),
348 None,
349 ),
350 members_response_with_proof(),
351 Network::Testnet,
352 PlatformVersion::latest(),
353 &UnreachableProvider,
354 )
355 .unwrap_err();
356 assert!(
357 matches!(&err, Error::RequestError { error } if error.contains("start_after")),
358 "got: {err:?}"
359 );
360 }
361}