drive/query/
contested_resource_votes_given_by_identity_query.rs

1use crate::drive::votes::paths::vote_contested_resource_identity_votes_tree_path_for_identity_vec;
2#[cfg(feature = "server")]
3use crate::drive::votes::storage_form::contested_document_resource_reference_storage_form::ContestedDocumentResourceVoteReferenceStorageForm;
4#[cfg(feature = "server")]
5use crate::drive::votes::storage_form::contested_document_resource_storage_form::ContestedDocumentResourceVoteStorageForm;
6#[cfg(feature = "server")]
7use crate::drive::votes::tree_path_storage_form::TreePathStorageForm;
8#[cfg(feature = "server")]
9use crate::drive::Drive;
10#[cfg(feature = "server")]
11use crate::error::drive::DriveError;
12use crate::error::Error;
13#[cfg(feature = "server")]
14use crate::fees::op::LowLevelDriveOperation;
15#[cfg(feature = "server")]
16use crate::query::GroveError;
17use crate::query::Query;
18#[cfg(feature = "server")]
19use dpp::bincode;
20#[cfg(feature = "server")]
21use dpp::block::block_info::BlockInfo;
22use dpp::identifier::Identifier;
23#[cfg(feature = "server")]
24use grovedb::query_result_type::{QueryResultElements, QueryResultType};
25#[cfg(feature = "server")]
26use grovedb::TransactionArg;
27use grovedb::{PathQuery, SizedQuery};
28#[cfg(feature = "server")]
29use platform_version::version::PlatformVersion;
30#[cfg(feature = "server")]
31use std::collections::BTreeMap;
32
33/// Vote Poll Drive Query struct
34#[derive(Debug, PartialEq, Clone)]
35pub struct ContestedResourceVotesGivenByIdentityQuery {
36    /// Which contestant do we want to get the votes for
37    pub identity_id: Identifier,
38    /// Offset
39    pub offset: Option<u16>,
40    /// Limit
41    pub limit: Option<u16>,
42    /// Start at vote id
43    pub start_at: Option<([u8; 32], bool)>,
44    /// Ascending
45    pub order_ascending: bool,
46}
47
48impl ContestedResourceVotesGivenByIdentityQuery {
49    #[cfg(feature = "server")]
50    /// Executes a query with proof and returns the items and fee.
51    pub fn execute_with_proof(
52        self,
53        drive: &Drive,
54        block_info: Option<BlockInfo>,
55        transaction: TransactionArg,
56        platform_version: &PlatformVersion,
57    ) -> Result<(Vec<u8>, u64), Error> {
58        let mut drive_operations = vec![];
59        let items = self.execute_with_proof_internal(
60            drive,
61            transaction,
62            &mut drive_operations,
63            platform_version,
64        )?;
65        let cost = if let Some(block_info) = block_info {
66            let fee_result = Drive::calculate_fee(
67                None,
68                Some(drive_operations),
69                &block_info.epoch,
70                drive.config.epochs_per_era,
71                platform_version,
72                None,
73            )?;
74            fee_result.processing_fee
75        } else {
76            0
77        };
78        Ok((items, cost))
79    }
80
81    #[cfg(feature = "server")]
82    /// Executes an internal query with proof and returns the items.
83    pub(crate) fn execute_with_proof_internal(
84        self,
85        drive: &Drive,
86        transaction: TransactionArg,
87        drive_operations: &mut Vec<LowLevelDriveOperation>,
88        platform_version: &PlatformVersion,
89    ) -> Result<Vec<u8>, Error> {
90        let path_query = self.construct_path_query()?;
91        drive.grove_get_proved_path_query(
92            &path_query,
93            transaction,
94            drive_operations,
95            &platform_version.drive,
96        )
97    }
98
99    #[cfg(feature = "server")]
100    /// Executes a query with no proof and returns the items, skipped items, and fee.
101    pub fn execute_no_proof_with_cost(
102        &self,
103        drive: &Drive,
104        block_info: Option<BlockInfo>,
105        transaction: TransactionArg,
106        platform_version: &PlatformVersion,
107    ) -> Result<
108        (
109            BTreeMap<Identifier, ContestedDocumentResourceVoteStorageForm>,
110            u64,
111        ),
112        Error,
113    > {
114        let mut drive_operations = vec![];
115        let result =
116            self.execute_no_proof(drive, transaction, &mut drive_operations, platform_version)?;
117        let cost = if let Some(block_info) = block_info {
118            let fee_result = Drive::calculate_fee(
119                None,
120                Some(drive_operations),
121                &block_info.epoch,
122                drive.config.epochs_per_era,
123                platform_version,
124                None,
125            )?;
126            fee_result.processing_fee
127        } else {
128            0
129        };
130        Ok((result, cost))
131    }
132
133    #[cfg(feature = "server")]
134    /// Executes an internal query with no proof and returns the values and skipped items.
135    pub fn execute_no_proof(
136        &self,
137        drive: &Drive,
138        transaction: TransactionArg,
139        drive_operations: &mut Vec<LowLevelDriveOperation>,
140        platform_version: &PlatformVersion,
141    ) -> Result<BTreeMap<Identifier, ContestedDocumentResourceVoteStorageForm>, Error> {
142        let path_query = self.construct_path_query()?;
143        let query_result = drive.grove_get_raw_path_query(
144            &path_query,
145            transaction,
146            QueryResultType::QueryPathKeyElementTrioResultType,
147            drive_operations,
148            &platform_version.drive,
149        );
150        match query_result {
151            Err(Error::GroveDB(e))
152                if matches!(
153                    e.as_ref(),
154                    GroveError::PathKeyNotFound(_)
155                        | GroveError::PathNotFound(_)
156                        | GroveError::PathParentLayerNotFound(_)
157                ) =>
158            {
159                Ok(BTreeMap::new())
160            }
161            Err(e) => Err(e),
162            Ok((query_result_elements, _)) => {
163                let voters =
164                    query_result_elements
165                        .to_path_key_elements()
166                        .into_iter()
167                        .map(|(path, key, element)| {
168                            let serialized_reference = element.into_item_bytes()?;
169                            let bincode_config = bincode::config::standard()
170                                .with_big_endian()
171                                .with_no_limit();
172                            let reference: ContestedDocumentResourceVoteReferenceStorageForm =
173                                bincode::decode_from_slice(&serialized_reference, bincode_config)
174                                    .map_err(|e| {
175                                        Error::Drive(DriveError::CorruptedSerialization(format!(
176                                            "serialization of reference {} is corrupted: {}",
177                                            hex::encode(serialized_reference),
178                                            e
179                                        )))
180                                    })?
181                                    .0;
182                            let absolute_path = reference
183                                .reference_path_type
184                                .absolute_path(path.as_slice(), Some(key.as_slice()))?;
185                            let vote_id = Identifier::from_vec(key)?;
186                            Ok((
187                                vote_id,
188                                ContestedDocumentResourceVoteStorageForm::try_from_tree_path(
189                                    absolute_path,
190                                )?,
191                            ))
192                        })
193                        .collect::<Result<
194                            BTreeMap<Identifier, ContestedDocumentResourceVoteStorageForm>,
195                            Error,
196                        >>()?;
197
198                Ok(voters)
199            }
200        }
201    }
202
203    #[cfg(feature = "server")]
204    #[allow(unused)]
205    /// Executes an internal query with no proof and returns the values and skipped items.
206    pub(crate) fn execute_no_proof_internal(
207        &self,
208        drive: &Drive,
209        result_type: QueryResultType,
210        transaction: TransactionArg,
211        drive_operations: &mut Vec<LowLevelDriveOperation>,
212        platform_version: &PlatformVersion,
213    ) -> Result<(QueryResultElements, u16), Error> {
214        let path_query = self.construct_path_query()?;
215        let query_result = drive.grove_get_path_query(
216            &path_query,
217            transaction,
218            result_type,
219            drive_operations,
220            &platform_version.drive,
221        );
222        match query_result {
223            Err(Error::GroveDB(e))
224                if matches!(
225                    e.as_ref(),
226                    GroveError::PathKeyNotFound(_)
227                        | GroveError::PathNotFound(_)
228                        | GroveError::PathParentLayerNotFound(_)
229                ) =>
230            {
231                Ok((QueryResultElements::new(), 0))
232            }
233            _ => {
234                let (data, skipped) = query_result?;
235                {
236                    Ok((data, skipped))
237                }
238            }
239        }
240    }
241    /// Operations to construct a path query.
242    pub fn construct_path_query(&self) -> Result<PathQuery, Error> {
243        let path = vote_contested_resource_identity_votes_tree_path_for_identity_vec(
244            self.identity_id.as_bytes(),
245        );
246
247        let mut query = Query::new_with_direction(self.order_ascending);
248
249        // this is a range on all elements
250        match &self.start_at {
251            None => {
252                query.insert_all();
253            }
254            Some((starts_at_key_bytes, start_at_included)) => {
255                let starts_at_key = starts_at_key_bytes.to_vec();
256                match self.order_ascending {
257                    true => match start_at_included {
258                        true => query.insert_range_from(starts_at_key..),
259                        false => query.insert_range_after(starts_at_key..),
260                    },
261                    false => match start_at_included {
262                        true => query.insert_range_to_inclusive(..=starts_at_key),
263                        false => query.insert_range_to(..starts_at_key),
264                    },
265                }
266            }
267        }
268
269        Ok(PathQuery {
270            path,
271            query: SizedQuery {
272                query,
273                limit: self.limit,
274                offset: self.offset,
275            },
276        })
277    }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use crate::drive::votes::paths::{CONTESTED_RESOURCE_TREE_KEY, IDENTITY_VOTES_TREE_KEY};
284    use crate::drive::RootTree;
285    use grovedb::QueryItem;
286
287    fn expected_base_path(identity_id: &[u8; 32]) -> Vec<Vec<u8>> {
288        vec![
289            vec![RootTree::Votes as u8],
290            vec![CONTESTED_RESOURCE_TREE_KEY as u8],
291            vec![IDENTITY_VOTES_TREE_KEY as u8],
292            identity_id.to_vec(),
293        ]
294    }
295
296    // -----------------------------------------------------------------------
297    // construct_path_query
298    // -----------------------------------------------------------------------
299
300    #[test]
301    fn construct_path_query_no_start_ascending() {
302        let identity_id = Identifier::from([0xAA; 32]);
303        let query = ContestedResourceVotesGivenByIdentityQuery {
304            identity_id,
305            offset: None,
306            limit: Some(10),
307            start_at: None,
308            order_ascending: true,
309        };
310
311        let pq = query
312            .construct_path_query()
313            .expect("should build path query");
314        assert_eq!(pq.path, expected_base_path(identity_id.as_bytes()));
315        assert_eq!(pq.query.limit, Some(10));
316        assert_eq!(pq.query.offset, None);
317        assert!(pq.query.query.left_to_right);
318
319        // No start_at means insert_all -> RangeFull
320        let items = &pq.query.query.items;
321        assert_eq!(items.len(), 1);
322        assert!(matches!(&items[0], QueryItem::RangeFull(..)));
323    }
324
325    #[test]
326    fn construct_path_query_no_start_descending() {
327        let identity_id = Identifier::from([0xBB; 32]);
328        let query = ContestedResourceVotesGivenByIdentityQuery {
329            identity_id,
330            offset: None,
331            limit: None,
332            start_at: None,
333            order_ascending: false,
334        };
335
336        let pq = query
337            .construct_path_query()
338            .expect("should build path query");
339        assert!(!pq.query.query.left_to_right);
340        assert_eq!(pq.query.limit, None);
341    }
342
343    #[test]
344    fn construct_path_query_start_at_included_ascending() {
345        let identity_id = Identifier::from([0xCC; 32]);
346        let start_key = [0x42u8; 32];
347        let query = ContestedResourceVotesGivenByIdentityQuery {
348            identity_id,
349            offset: None,
350            limit: Some(5),
351            start_at: Some((start_key, true)),
352            order_ascending: true,
353        };
354
355        let pq = query
356            .construct_path_query()
357            .expect("should build path query");
358        let items = &pq.query.query.items;
359        assert_eq!(items.len(), 1);
360        assert!(
361            matches!(&items[0], QueryItem::RangeFrom(r) if r.start == start_key.to_vec()),
362            "ascending + included = RangeFrom"
363        );
364    }
365
366    #[test]
367    fn construct_path_query_start_at_excluded_ascending() {
368        let identity_id = Identifier::from([0xDD; 32]);
369        let start_key = [0x42u8; 32];
370        let query = ContestedResourceVotesGivenByIdentityQuery {
371            identity_id,
372            offset: None,
373            limit: Some(5),
374            start_at: Some((start_key, false)),
375            order_ascending: true,
376        };
377
378        let pq = query
379            .construct_path_query()
380            .expect("should build path query");
381        let items = &pq.query.query.items;
382        assert_eq!(items.len(), 1);
383        assert!(
384            matches!(&items[0], QueryItem::RangeAfter(r) if r.start == start_key.to_vec()),
385            "ascending + excluded = RangeAfter"
386        );
387    }
388
389    #[test]
390    fn construct_path_query_start_at_included_descending() {
391        let identity_id = Identifier::from([0xEE; 32]);
392        let start_key = [0x42u8; 32];
393        let query = ContestedResourceVotesGivenByIdentityQuery {
394            identity_id,
395            offset: None,
396            limit: Some(5),
397            start_at: Some((start_key, true)),
398            order_ascending: false,
399        };
400
401        let pq = query
402            .construct_path_query()
403            .expect("should build path query");
404        let items = &pq.query.query.items;
405        assert_eq!(items.len(), 1);
406        assert!(
407            matches!(&items[0], QueryItem::RangeToInclusive(r) if r.end == start_key.to_vec()),
408            "descending + included = RangeToInclusive"
409        );
410    }
411
412    #[test]
413    fn construct_path_query_start_at_excluded_descending() {
414        let identity_id = Identifier::from([0xFF; 32]);
415        let start_key = [0x42u8; 32];
416        let query = ContestedResourceVotesGivenByIdentityQuery {
417            identity_id,
418            offset: None,
419            limit: Some(5),
420            start_at: Some((start_key, false)),
421            order_ascending: false,
422        };
423
424        let pq = query
425            .construct_path_query()
426            .expect("should build path query");
427        let items = &pq.query.query.items;
428        assert_eq!(items.len(), 1);
429        assert!(
430            matches!(&items[0], QueryItem::RangeTo(r) if r.end == start_key.to_vec()),
431            "descending + excluded = RangeTo"
432        );
433    }
434
435    #[test]
436    fn construct_path_query_with_offset_and_limit() {
437        let identity_id = Identifier::from([0x11; 32]);
438        let query = ContestedResourceVotesGivenByIdentityQuery {
439            identity_id,
440            offset: Some(7),
441            limit: Some(25),
442            start_at: None,
443            order_ascending: true,
444        };
445
446        let pq = query
447            .construct_path_query()
448            .expect("should build path query");
449        assert_eq!(pq.query.limit, Some(25));
450        assert_eq!(pq.query.offset, Some(7));
451    }
452
453    #[test]
454    fn construct_path_query_identity_id_appears_in_path() {
455        let identity_id = Identifier::from([0x99; 32]);
456        let query = ContestedResourceVotesGivenByIdentityQuery {
457            identity_id,
458            offset: None,
459            limit: None,
460            start_at: None,
461            order_ascending: true,
462        };
463
464        let pq = query
465            .construct_path_query()
466            .expect("should build path query");
467        // The 4th path element should be the identity_id
468        assert_eq!(pq.path.len(), 4);
469        assert_eq!(pq.path[3], identity_id.as_bytes().to_vec());
470    }
471}