1use crate::drive::votes::paths::{
2 VotePollPaths, RESOURCE_ABSTAIN_VOTE_TREE_KEY_U8_32, RESOURCE_LOCK_VOTE_TREE_KEY_U8_32,
3 RESOURCE_STORED_INFO_KEY_U8_32,
4};
5use crate::drive::votes::resolved::vote_polls::contested_document_resource_vote_poll::resolve::ContestedDocumentResourceVotePollResolver;
6use crate::drive::votes::resolved::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed;
7#[cfg(feature = "server")]
8use crate::drive::Drive;
9use crate::error::drive::DriveError;
10use crate::error::query::QuerySyntaxError;
11use crate::error::Error;
12#[cfg(feature = "server")]
13use crate::fees::op::LowLevelDriveOperation;
14#[cfg(feature = "server")]
15use crate::query::GroveError;
16use bincode::{Decode, Encode};
17use dpp::block::block_info::BlockInfo;
18use dpp::data_contract::DataContract;
19use dpp::identifier::Identifier;
20#[cfg(feature = "server")]
21use dpp::serialization::PlatformDeserializable;
22#[cfg(feature = "server")]
23use dpp::voting::contender_structs::ContenderWithSerializedDocumentV0;
24use dpp::voting::contender_structs::{
25 ContenderWithSerializedDocument, FinalizedContenderWithSerializedDocument,
26};
27#[cfg(feature = "server")]
28use dpp::voting::vote_info_storage::contested_document_vote_poll_stored_info::ContestedDocumentVotePollStoredInfo;
29#[cfg(feature = "server")]
30use dpp::voting::vote_info_storage::contested_document_vote_poll_stored_info::ContestedDocumentVotePollStoredInfoV0Getters;
31use dpp::voting::vote_info_storage::contested_document_vote_poll_winner_info::ContestedDocumentVotePollWinnerInfo;
32use dpp::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll;
33#[cfg(feature = "server")]
34use grovedb::query_result_type::QueryResultType;
35#[cfg(feature = "server")]
36use grovedb::{Element, TransactionArg};
37use grovedb::{PathQuery, Query, QueryItem, SizedQuery};
38use platform_version::version::PlatformVersion;
39
40#[derive(Debug, PartialEq, Clone, Copy, Encode, Decode)]
45pub enum ContestedDocumentVotePollDriveQueryResultType {
46 Documents,
48 VoteTally,
50 DocumentsAndVoteTally,
52 SingleDocumentByContender(Identifier),
54}
55
56impl ContestedDocumentVotePollDriveQueryResultType {
57 pub fn has_vote_tally(&self) -> bool {
59 match self {
60 ContestedDocumentVotePollDriveQueryResultType::Documents => false,
61 ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(_) => false,
62 ContestedDocumentVotePollDriveQueryResultType::VoteTally => true,
63 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => true,
64 }
65 }
66
67 pub fn has_documents(&self) -> bool {
69 match self {
70 ContestedDocumentVotePollDriveQueryResultType::Documents => true,
71 ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(_) => true,
72 ContestedDocumentVotePollDriveQueryResultType::VoteTally => false,
73 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => true,
74 }
75 }
76}
77
78impl TryFrom<i32> for ContestedDocumentVotePollDriveQueryResultType {
79 type Error = Error;
80
81 fn try_from(value: i32) -> Result<Self, Self::Error> {
82 match value {
83 0 => Ok(ContestedDocumentVotePollDriveQueryResultType::Documents),
84 1 => Ok(ContestedDocumentVotePollDriveQueryResultType::VoteTally),
85 2 => Ok(ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally),
86 3 => Err(Error::Query(QuerySyntaxError::Unsupported(
87 "unsupported to get SingleDocumentByContender query result type".to_string()
88 ))),
89 n => Err(Error::Query(QuerySyntaxError::Unsupported(format!(
90 "unsupported contested document vote poll drive query result type {}, only 0, 1, 2 and 3 are supported",
91 n
92 )))),
93 }
94 }
95}
96
97#[derive(Debug, PartialEq, Clone, Encode, Decode)]
99pub struct ContestedDocumentVotePollDriveQuery {
100 pub vote_poll: ContestedDocumentResourceVotePoll,
102 pub result_type: ContestedDocumentVotePollDriveQueryResultType,
104 pub offset: Option<u16>,
106 pub limit: Option<u16>,
108 pub start_at: Option<([u8; 32], bool)>,
110 pub allow_include_locked_and_abstaining_vote_tally: bool,
114}
115
116#[derive(Debug, PartialEq, Eq, Clone, Default)]
121pub struct ContestedDocumentVotePollDriveQueryExecutionResult {
122 pub contenders: Vec<ContenderWithSerializedDocument>,
124 pub locked_vote_tally: Option<u32>,
126 pub abstaining_vote_tally: Option<u32>,
128 pub winner: Option<(ContestedDocumentVotePollWinnerInfo, BlockInfo)>,
130 pub skipped: u16,
132}
133
134#[derive(Debug, PartialEq, Eq, Clone, Default)]
139pub struct FinalizedContestedDocumentVotePollDriveQueryExecutionResult {
140 pub contenders: Vec<FinalizedContenderWithSerializedDocument>,
142 pub locked_vote_tally: u32,
144 pub abstaining_vote_tally: u32,
146}
147
148impl TryFrom<ContestedDocumentVotePollDriveQueryExecutionResult>
149 for FinalizedContestedDocumentVotePollDriveQueryExecutionResult
150{
151 type Error = Error;
152
153 fn try_from(
154 value: ContestedDocumentVotePollDriveQueryExecutionResult,
155 ) -> Result<Self, Self::Error> {
156 let ContestedDocumentVotePollDriveQueryExecutionResult {
157 contenders,
158 locked_vote_tally,
159 abstaining_vote_tally,
160 ..
161 } = value;
162
163 let finalized_contenders = contenders
164 .into_iter()
165 .map(|contender| {
166 let finalized: FinalizedContenderWithSerializedDocument = contender.try_into()?;
167 Ok(finalized)
168 })
169 .collect::<Result<Vec<_>, Error>>()?;
170
171 Ok(
172 FinalizedContestedDocumentVotePollDriveQueryExecutionResult {
173 contenders: finalized_contenders,
174 locked_vote_tally: locked_vote_tally.ok_or(Error::Drive(
175 DriveError::CorruptedCodeExecution("expected a locked tally"),
176 ))?,
177 abstaining_vote_tally: abstaining_vote_tally.ok_or(Error::Drive(
178 DriveError::CorruptedCodeExecution("expected an abstaining tally"),
179 ))?,
180 },
181 )
182 }
183}
184
185impl ContestedDocumentVotePollDriveQuery {
186 #[cfg(feature = "server")]
187 pub fn resolve(
208 &self,
209 drive: &Drive,
210 transaction: TransactionArg,
211 platform_version: &PlatformVersion,
212 ) -> Result<ResolvedContestedDocumentVotePollDriveQuery<'_>, Error> {
213 let ContestedDocumentVotePollDriveQuery {
214 vote_poll,
215 result_type,
216 offset,
217 limit,
218 start_at,
219 allow_include_locked_and_abstaining_vote_tally,
220 } = self;
221 Ok(ResolvedContestedDocumentVotePollDriveQuery {
222 vote_poll: vote_poll.resolve_allow_borrowed(drive, transaction, platform_version)?,
223 result_type: *result_type,
224 offset: *offset,
225 limit: *limit,
226 start_at: *start_at,
227 allow_include_locked_and_abstaining_vote_tally:
228 *allow_include_locked_and_abstaining_vote_tally,
229 })
230 }
231
232 #[cfg(feature = "verify")]
233 pub fn resolve_with_known_contracts_provider<'a>(
235 &self,
236 known_contracts_provider_fn: &super::ContractLookupFn,
237 ) -> Result<ResolvedContestedDocumentVotePollDriveQuery<'a>, Error> {
238 let ContestedDocumentVotePollDriveQuery {
239 vote_poll,
240 result_type,
241 offset,
242 limit,
243 start_at,
244 allow_include_locked_and_abstaining_vote_tally,
245 } = self;
246 Ok(ResolvedContestedDocumentVotePollDriveQuery {
247 vote_poll: vote_poll
248 .resolve_with_known_contracts_provider(known_contracts_provider_fn)?,
249 result_type: *result_type,
250 offset: *offset,
251 limit: *limit,
252 start_at: *start_at,
253 allow_include_locked_and_abstaining_vote_tally:
254 *allow_include_locked_and_abstaining_vote_tally,
255 })
256 }
257
258 #[cfg(any(feature = "verify", feature = "server"))]
259 pub fn resolve_with_provided_borrowed_contract<'a>(
261 &self,
262 data_contract: &'a DataContract,
263 ) -> Result<ResolvedContestedDocumentVotePollDriveQuery<'a>, Error> {
264 let ContestedDocumentVotePollDriveQuery {
265 vote_poll,
266 result_type,
267 offset,
268 limit,
269 start_at,
270 allow_include_locked_and_abstaining_vote_tally,
271 } = self;
272 Ok(ResolvedContestedDocumentVotePollDriveQuery {
273 vote_poll: vote_poll.resolve_with_provided_borrowed_contract(data_contract)?,
274 result_type: *result_type,
275 offset: *offset,
276 limit: *limit,
277 start_at: *start_at,
278 allow_include_locked_and_abstaining_vote_tally:
279 *allow_include_locked_and_abstaining_vote_tally,
280 })
281 }
282
283 #[cfg(feature = "server")]
284 pub fn execute_with_proof(
286 self,
287 drive: &Drive,
288 block_info: Option<BlockInfo>,
289 transaction: TransactionArg,
290 platform_version: &PlatformVersion,
291 ) -> Result<(Vec<u8>, u64), Error> {
292 let mut drive_operations = vec![];
293 let items = self.execute_with_proof_internal(
294 drive,
295 transaction,
296 &mut drive_operations,
297 platform_version,
298 )?;
299 let cost = if let Some(block_info) = block_info {
300 let fee_result = Drive::calculate_fee(
301 None,
302 Some(drive_operations),
303 &block_info.epoch,
304 drive.config.epochs_per_era,
305 platform_version,
306 None,
307 )?;
308 fee_result.processing_fee
309 } else {
310 0
311 };
312 Ok((items, cost))
313 }
314
315 #[cfg(feature = "server")]
316 pub(crate) fn execute_with_proof_internal(
318 self,
319 drive: &Drive,
320 transaction: TransactionArg,
321 drive_operations: &mut Vec<LowLevelDriveOperation>,
322 platform_version: &PlatformVersion,
323 ) -> Result<Vec<u8>, Error> {
324 let resolved = self.resolve(drive, transaction, platform_version)?;
325 let path_query = resolved.construct_path_query(platform_version)?;
326 drive.grove_get_proved_path_query(
328 &path_query,
329 transaction,
330 drive_operations,
331 &platform_version.drive,
332 )
333 }
334
335 #[cfg(feature = "server")]
336 pub fn execute_no_proof_with_cost(
338 &self,
339 drive: &Drive,
340 block_info: Option<BlockInfo>,
341 transaction: TransactionArg,
342 platform_version: &PlatformVersion,
343 ) -> Result<(ContestedDocumentVotePollDriveQueryExecutionResult, u64), Error> {
344 let mut drive_operations = vec![];
345 let result =
346 self.execute_no_proof(drive, transaction, &mut drive_operations, platform_version)?;
347 let cost = if let Some(block_info) = block_info {
348 let fee_result = Drive::calculate_fee(
349 None,
350 Some(drive_operations),
351 &block_info.epoch,
352 drive.config.epochs_per_era,
353 platform_version,
354 None,
355 )?;
356 fee_result.processing_fee
357 } else {
358 0
359 };
360 Ok((result, cost))
361 }
362
363 #[cfg(feature = "server")]
364 pub fn execute_no_proof(
366 &self,
367 drive: &Drive,
368 transaction: TransactionArg,
369 drive_operations: &mut Vec<LowLevelDriveOperation>,
370 platform_version: &PlatformVersion,
371 ) -> Result<ContestedDocumentVotePollDriveQueryExecutionResult, Error> {
372 let resolved = self.resolve(drive, transaction, platform_version)?;
373 resolved.execute(drive, transaction, drive_operations, platform_version)
374 }
375}
376
377#[derive(Debug, PartialEq, Clone)]
379pub struct ResolvedContestedDocumentVotePollDriveQuery<'a> {
380 pub vote_poll: ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed<'a>,
382 pub result_type: ContestedDocumentVotePollDriveQueryResultType,
384 pub offset: Option<u16>,
386 pub limit: Option<u16>,
388 pub start_at: Option<([u8; 32], bool)>,
390 pub allow_include_locked_and_abstaining_vote_tally: bool,
392}
393
394impl ResolvedContestedDocumentVotePollDriveQuery<'_> {
395 pub fn construct_path_query(
397 &self,
398 platform_version: &PlatformVersion,
399 ) -> Result<PathQuery, Error> {
400 let path = self.vote_poll.contenders_path(platform_version)?;
401
402 let mut query = Query::new();
403
404 let allow_include_locked_and_abstaining_vote_tally = self
405 .allow_include_locked_and_abstaining_vote_tally
406 && self.result_type.has_vote_tally();
407
408 let limit = match &self.start_at {
413 None => {
414 if allow_include_locked_and_abstaining_vote_tally {
415 match &self.result_type {
416 ContestedDocumentVotePollDriveQueryResultType::Documents => {
417 query.insert_range_after(RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.to_vec()..);
419 self.limit
420 }
421 ContestedDocumentVotePollDriveQueryResultType::VoteTally => {
422 query.insert_all();
423 self.limit.map(|limit| limit.saturating_add(3))
424 }
425 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => {
426 query.insert_all();
427 self.limit.map(|limit| limit.saturating_mul(2).saturating_add(3))
428 }
429 ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(contender_id) => {
430 query.insert_key(contender_id.to_vec());
431 self.limit
432 }
433 }
434 } else {
435 match &self.result_type {
436 ContestedDocumentVotePollDriveQueryResultType::Documents => {
437 query.insert_range_after(RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.to_vec()..);
438 self.limit
439 }
440 ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(contender_id) => {
441 query.insert_key(contender_id.to_vec());
442 self.limit
443 }
444 ContestedDocumentVotePollDriveQueryResultType::VoteTally => {
445 query.insert_key(RESOURCE_STORED_INFO_KEY_U8_32.to_vec());
446 query.insert_range_after(RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.to_vec()..);
447 self.limit.map(|limit| limit.saturating_add(1))
448 }
449 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => {
450 query.insert_key(RESOURCE_STORED_INFO_KEY_U8_32.to_vec());
451 query.insert_range_after(RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.to_vec()..);
452 self.limit.map(|limit| limit.saturating_mul(2).saturating_add(1))
453 }
454 }
455 }
456 }
457 Some((starts_at_key_bytes, start_at_included)) => {
458 let starts_at_key = starts_at_key_bytes.to_vec();
459 match start_at_included {
460 true => query.insert_range_from(starts_at_key..),
461 false => query.insert_range_after(starts_at_key..),
462 }
463 match &self.result_type {
464 ContestedDocumentVotePollDriveQueryResultType::Documents
465 | ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(_)
466 | ContestedDocumentVotePollDriveQueryResultType::VoteTally => self.limit,
467 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => {
468 self.limit.map(|limit| limit.saturating_mul(2))
469 }
470 }
471 }
472 };
473
474 let (subquery_path, subquery) = match self.result_type {
475 ContestedDocumentVotePollDriveQueryResultType::Documents
476 | ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(_) => {
477 (Some(vec![vec![0]]), None)
478 }
479 ContestedDocumentVotePollDriveQueryResultType::VoteTally => (Some(vec![vec![1]]), None),
480 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => {
481 let mut query = Query::new();
482 query.insert_keys(vec![vec![0], vec![1]]);
483 (None, Some(query.into()))
484 }
485 };
486
487 query.default_subquery_branch.subquery_path = subquery_path;
488 query.default_subquery_branch.subquery = subquery;
489
490 if allow_include_locked_and_abstaining_vote_tally {
491 query.add_conditional_subquery(
492 QueryItem::Key(RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.to_vec()),
493 Some(vec![vec![1]]),
494 None,
495 );
496 query.add_conditional_subquery(
497 QueryItem::Key(RESOURCE_ABSTAIN_VOTE_TREE_KEY_U8_32.to_vec()),
498 Some(vec![vec![1]]),
499 None,
500 );
501 }
502
503 query.add_conditional_subquery(
504 QueryItem::Key(RESOURCE_STORED_INFO_KEY_U8_32.to_vec()),
505 None,
506 None,
507 );
508
509 Ok(PathQuery {
510 path,
511 query: SizedQuery {
512 query,
513 limit,
514 offset: self.offset,
515 },
516 })
517 }
518
519 #[cfg(feature = "server")]
520 pub fn execute(
522 &self,
523 drive: &Drive,
524 transaction: TransactionArg,
525 drive_operations: &mut Vec<LowLevelDriveOperation>,
526 platform_version: &PlatformVersion,
527 ) -> Result<ContestedDocumentVotePollDriveQueryExecutionResult, Error> {
528 let path_query = self.construct_path_query(platform_version)?;
529 let query_result = drive.grove_get_path_query(
531 &path_query,
532 transaction,
533 QueryResultType::QueryPathKeyElementTrioResultType,
534 drive_operations,
535 &platform_version.drive,
536 );
537 match query_result {
538 Err(Error::GroveDB(e))
539 if matches!(
540 e.as_ref(),
541 GroveError::PathKeyNotFound(_)
542 | GroveError::PathNotFound(_)
543 | GroveError::PathParentLayerNotFound(_)
544 ) =>
545 {
546 Ok(ContestedDocumentVotePollDriveQueryExecutionResult::default())
547 }
548 Err(e) => Err(e),
549 Ok((query_result_elements, skipped)) => {
550 match self.result_type {
551 ContestedDocumentVotePollDriveQueryResultType::Documents
552 | ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(_) =>
553 {
554 let contenders = query_result_elements
556 .to_path_key_elements()
557 .into_iter()
558 .map(|(mut path, _key, document)| {
559 let identity_id = path.pop().ok_or(Error::Drive(
560 DriveError::CorruptedDriveState(
561 "the path must have a last element".to_string(),
562 ),
563 ))?;
564 Ok(ContenderWithSerializedDocumentV0 {
565 identity_id: Identifier::try_from(identity_id)?,
566 serialized_document: Some(document.into_item_bytes()?),
567 vote_tally: None,
568 }
569 .into())
570 })
571 .collect::<Result<Vec<ContenderWithSerializedDocument>, Error>>()?;
572
573 Ok(ContestedDocumentVotePollDriveQueryExecutionResult {
574 contenders,
575 locked_vote_tally: None,
576 abstaining_vote_tally: None,
577 winner: None,
578 skipped,
579 })
580 }
581 ContestedDocumentVotePollDriveQueryResultType::VoteTally => {
582 let mut contenders = Vec::new();
583 let mut locked_vote_tally: Option<u32> = None;
584 let mut abstaining_vote_tally: Option<u32> = None;
585 let mut winner = None;
586
587 for (path, first_key, element) in
588 query_result_elements.to_path_key_elements().into_iter()
589 {
590 let Some(identity_bytes) = path.last() else {
591 return Err(Error::Drive(DriveError::CorruptedDriveState(
592 "the path must have a last element".to_string(),
593 )));
594 };
595 match element {
596 Element::SumTree(_, sum_tree_value, _) => {
597 if sum_tree_value < 0 || sum_tree_value > u32::MAX as i64 {
598 return Err(Error::Drive(DriveError::CorruptedDriveState(format!(
599 "sum tree value for vote tally must be between 0 and u32::Max, received {} from state",
600 sum_tree_value
601 ))));
602 }
603
604 if identity_bytes.as_slice()
605 == RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.as_slice()
606 {
607 locked_vote_tally = Some(sum_tree_value as u32);
608 } else if identity_bytes.as_slice()
609 == RESOURCE_ABSTAIN_VOTE_TREE_KEY_U8_32.as_slice()
610 {
611 abstaining_vote_tally = Some(sum_tree_value as u32);
612 } else {
613 contenders.push(
614 ContenderWithSerializedDocumentV0 {
615 identity_id: Identifier::try_from(identity_bytes)?,
616 serialized_document: None,
617 vote_tally: Some(sum_tree_value as u32),
618 }
619 .into(),
620 );
621 }
622 }
623 Element::Item(serialized_item_info, _) => {
624 if first_key.as_slice() == RESOURCE_STORED_INFO_KEY_U8_32 {
625 let finalized_contested_document_vote_poll_stored_info = ContestedDocumentVotePollStoredInfo::deserialize_from_bytes(&serialized_item_info)?;
627 if finalized_contested_document_vote_poll_stored_info
628 .vote_poll_status()
629 .awarded_or_locked()
630 {
631 locked_vote_tally = Some(
632 finalized_contested_document_vote_poll_stored_info
633 .last_locked_votes()
634 .ok_or(Error::Drive(
635 DriveError::CorruptedDriveState(
636 "we should have last locked votes"
637 .to_string(),
638 ),
639 ))?,
640 );
641 abstaining_vote_tally = Some(
642 finalized_contested_document_vote_poll_stored_info
643 .last_abstain_votes()
644 .ok_or(Error::Drive(
645 DriveError::CorruptedDriveState(
646 "we should have last abstain votes"
647 .to_string(),
648 ),
649 ))?,
650 );
651 winner = Some((
652 finalized_contested_document_vote_poll_stored_info.winner(),
653 finalized_contested_document_vote_poll_stored_info
654 .last_finalization_block().ok_or(Error::Drive(DriveError::CorruptedDriveState(
655 "we should have a last finalization block".to_string(),
656 )))?,
657 ));
658 contenders = finalized_contested_document_vote_poll_stored_info
659 .contender_votes_in_vec_of_contender_with_serialized_document().ok_or(Error::Drive(DriveError::CorruptedDriveState(
660 "we should have a last contender votes".to_string(),
661 )))?;
662 }
663 } else {
664 return Err(Error::Drive(
665 DriveError::CorruptedDriveState(
666 "the only item that should be returned should be stored info"
667 .to_string(),
668 ),
669 ));
670 }
671 }
672 _ => {
673 return Err(Error::Drive(DriveError::CorruptedDriveState(
674 "unexpected element type in result".to_string(),
675 )));
676 }
677 }
678 }
679 Ok(ContestedDocumentVotePollDriveQueryExecutionResult {
680 contenders,
681 locked_vote_tally,
682 abstaining_vote_tally,
683 winner,
684 skipped,
685 })
686 }
687 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally => {
688 let mut elements_iter =
689 query_result_elements.to_path_key_elements().into_iter();
690 let mut contenders = vec![];
691 let mut locked_vote_tally: Option<u32> = None;
692 let mut abstaining_vote_tally: Option<u32> = None;
693 let mut winner = None;
694
695 while let Some((path, first_key, element)) = elements_iter.next() {
697 let Some(identity_bytes) = path.last() else {
698 return Err(Error::Drive(DriveError::CorruptedDriveState(
699 "the path must have a last element".to_string(),
700 )));
701 };
702
703 match element {
704 Element::SumTree(_, sum_tree_value, _) => {
705 if sum_tree_value < 0 || sum_tree_value > u32::MAX as i64 {
706 return Err(Error::Drive(DriveError::CorruptedDriveState(format!(
707 "sum tree value for vote tally must be between 0 and u32::Max, received {} from state",
708 sum_tree_value
709 ))));
710 }
711
712 if identity_bytes.as_slice()
713 == RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.as_slice()
714 {
715 locked_vote_tally = Some(sum_tree_value as u32);
716 } else if identity_bytes.as_slice()
717 == RESOURCE_ABSTAIN_VOTE_TREE_KEY_U8_32.as_slice()
718 {
719 abstaining_vote_tally = Some(sum_tree_value as u32);
720 } else {
721 return Err(Error::Drive(DriveError::CorruptedDriveState(
722 "unexpected key for sum tree value".to_string(),
723 )));
724 }
725 }
726 Element::Item(serialized_item_info, _) => {
727 if first_key.as_slice() == RESOURCE_STORED_INFO_KEY_U8_32 {
728 let finalized_contested_document_vote_poll_stored_info = ContestedDocumentVotePollStoredInfo::deserialize_from_bytes(&serialized_item_info)?;
730 if finalized_contested_document_vote_poll_stored_info
731 .vote_poll_status()
732 .awarded_or_locked()
733 {
734 locked_vote_tally = Some(
735 finalized_contested_document_vote_poll_stored_info
736 .last_locked_votes()
737 .ok_or(Error::Drive(
738 DriveError::CorruptedDriveState(
739 "we should have last locked votes"
740 .to_string(),
741 ),
742 ))?,
743 );
744 abstaining_vote_tally = Some(
745 finalized_contested_document_vote_poll_stored_info
746 .last_abstain_votes()
747 .ok_or(Error::Drive(
748 DriveError::CorruptedDriveState(
749 "we should have last abstain votes"
750 .to_string(),
751 ),
752 ))?,
753 );
754 winner = Some((
755 finalized_contested_document_vote_poll_stored_info.winner(),
756 finalized_contested_document_vote_poll_stored_info
757 .last_finalization_block().ok_or(Error::Drive(DriveError::CorruptedDriveState(
758 "we should have a last finalization block".to_string(),
759 )))?,
760 ));
761 contenders = finalized_contested_document_vote_poll_stored_info
762 .contender_votes_in_vec_of_contender_with_serialized_document().ok_or(Error::Drive(DriveError::CorruptedDriveState(
763 "we should have a last contender votes".to_string(),
764 )))?;
765 }
766 } else {
767 if let Some((
769 path_tally,
770 second_key,
771 Element::SumTree(_, sum_tree_value, _),
772 )) = elements_iter.next()
773 {
774 if path != path_tally {
775 return Err(Error::Drive(DriveError::CorruptedDriveState(format!("the two results in a chunk when requesting documents and vote tally should both have the same path asc, got {}:{}, and {}:{}", path.iter().map(hex::encode).collect::<Vec<_>>().join("/"), hex::encode(first_key), path_tally.iter().map(hex::encode).collect::<Vec<_>>().join("/"), hex::encode(second_key)))));
776 }
777
778 if sum_tree_value < 0
779 || sum_tree_value > u32::MAX as i64
780 {
781 return Err(Error::Drive(DriveError::CorruptedDriveState(format!(
782 "sum tree value for vote tally must be between 0 and u32::Max, received {} from state",
783 sum_tree_value
784 ))));
785 }
786
787 let identity_id =
788 Identifier::from_bytes(identity_bytes)?;
789 let contender = ContenderWithSerializedDocumentV0 {
790 identity_id,
791 serialized_document: Some(serialized_item_info),
792 vote_tally: Some(sum_tree_value as u32),
793 }
794 .into();
795 contenders.push(contender);
796 } else {
797 return Err(Error::Drive(
798 DriveError::CorruptedDriveState(
799 "we should have a sum item after a normal item"
800 .to_string(),
801 ),
802 ));
803 }
804 }
805 }
806 _ => {
807 return Err(Error::Drive(DriveError::CorruptedDriveState(
808 "unexpected element type in result".to_string(),
809 )));
810 }
811 }
812 }
813
814 Ok(ContestedDocumentVotePollDriveQueryExecutionResult {
815 contenders,
816 locked_vote_tally,
817 abstaining_vote_tally,
818 winner,
819 skipped,
820 })
821 }
822 }
823 }
824 }
825 }
826}
827
828#[cfg(test)]
829mod tests {
830 use super::*;
831 use dpp::identifier::Identifier;
832 use dpp::tests::fixtures::get_dpns_data_contract_fixture;
833 use dpp::version::PlatformVersion;
834 use dpp::voting::contender_structs::ContenderWithSerializedDocumentV0;
835
836 use crate::drive::votes::resolved::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed;
837 use crate::util::object_size_info::DataContractResolvedInfo;
838
839 fn build_resolved_query(
842 contract: &dpp::data_contract::DataContract,
843 result_type: ContestedDocumentVotePollDriveQueryResultType,
844 offset: Option<u16>,
845 limit: Option<u16>,
846 start_at: Option<([u8; 32], bool)>,
847 allow_include_locked_and_abstaining: bool,
848 ) -> ResolvedContestedDocumentVotePollDriveQuery<'_> {
849 let document_type_name = "domain".to_string();
852 let index_name = "parentNameAndLabel".to_string();
853
854 let parent_domain_value = dpp::platform_value::Value::Text("dash".to_string());
855 let label_value = dpp::platform_value::Value::Text("test-name".to_string());
856
857 let index_values = vec![parent_domain_value, label_value];
858
859 let vote_poll = ContestedDocumentResourceVotePollWithContractInfoAllowBorrowed {
860 contract: DataContractResolvedInfo::BorrowedDataContract(contract),
861 document_type_name,
862 index_name,
863 index_values,
864 };
865
866 ResolvedContestedDocumentVotePollDriveQuery {
867 vote_poll,
868 result_type,
869 offset,
870 limit,
871 start_at,
872 allow_include_locked_and_abstaining_vote_tally: allow_include_locked_and_abstaining,
873 }
874 }
875
876 #[test]
881 fn has_vote_tally_returns_correct_values() {
882 use ContestedDocumentVotePollDriveQueryResultType::*;
883 assert!(!Documents.has_vote_tally());
884 assert!(VoteTally.has_vote_tally());
885 assert!(DocumentsAndVoteTally.has_vote_tally());
886 assert!(!SingleDocumentByContender(Identifier::default()).has_vote_tally());
887 }
888
889 #[test]
890 fn has_documents_returns_correct_values() {
891 use ContestedDocumentVotePollDriveQueryResultType::*;
892 assert!(Documents.has_documents());
893 assert!(!VoteTally.has_documents());
894 assert!(DocumentsAndVoteTally.has_documents());
895 assert!(SingleDocumentByContender(Identifier::default()).has_documents());
896 }
897
898 #[test]
903 fn try_from_i32_valid_values() {
904 let docs = ContestedDocumentVotePollDriveQueryResultType::try_from(0).unwrap();
905 assert_eq!(
906 docs,
907 ContestedDocumentVotePollDriveQueryResultType::Documents
908 );
909
910 let tally = ContestedDocumentVotePollDriveQueryResultType::try_from(1).unwrap();
911 assert_eq!(
912 tally,
913 ContestedDocumentVotePollDriveQueryResultType::VoteTally
914 );
915
916 let both = ContestedDocumentVotePollDriveQueryResultType::try_from(2).unwrap();
917 assert_eq!(
918 both,
919 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally
920 );
921 }
922
923 #[test]
924 fn try_from_i32_value_3_returns_unsupported_error() {
925 let result = ContestedDocumentVotePollDriveQueryResultType::try_from(3);
926 assert!(result.is_err());
927 let err = result.unwrap_err();
928 assert!(
929 matches!(err, Error::Query(QuerySyntaxError::Unsupported(msg)) if msg.contains("SingleDocumentByContender"))
930 );
931 }
932
933 #[test]
934 fn try_from_i32_out_of_range_returns_unsupported_error() {
935 let result = ContestedDocumentVotePollDriveQueryResultType::try_from(99);
936 assert!(result.is_err());
937 let err = result.unwrap_err();
938 assert!(
939 matches!(err, Error::Query(QuerySyntaxError::Unsupported(msg)) if msg.contains("99"))
940 );
941
942 let result_neg = ContestedDocumentVotePollDriveQueryResultType::try_from(-1);
943 assert!(result_neg.is_err());
944 }
945
946 #[test]
952 fn finalized_try_from_success_with_complete_data() {
953 let id = Identifier::from([0xAA; 32]);
954 let contender = ContenderWithSerializedDocumentV0 {
955 identity_id: id,
956 serialized_document: Some(vec![1, 2, 3]),
957 vote_tally: Some(42),
958 };
959 let result = ContestedDocumentVotePollDriveQueryExecutionResult {
960 contenders: vec![contender.into()],
961 locked_vote_tally: Some(10),
962 abstaining_vote_tally: Some(5),
963 winner: None,
964 skipped: 0,
965 };
966
967 let finalized: FinalizedContestedDocumentVotePollDriveQueryExecutionResult =
968 result.try_into().expect("should convert");
969 assert_eq!(finalized.contenders.len(), 1);
970 assert_eq!(finalized.locked_vote_tally, 10);
971 assert_eq!(finalized.abstaining_vote_tally, 5);
972 }
973
974 #[test]
975 fn finalized_try_from_fails_without_locked_tally() {
976 let result = ContestedDocumentVotePollDriveQueryExecutionResult {
977 contenders: vec![],
978 locked_vote_tally: None,
979 abstaining_vote_tally: Some(5),
980 winner: None,
981 skipped: 0,
982 };
983
984 let conversion: Result<FinalizedContestedDocumentVotePollDriveQueryExecutionResult, _> =
985 result.try_into();
986 assert!(conversion.is_err());
987 }
988
989 #[test]
990 fn finalized_try_from_fails_without_abstaining_tally() {
991 let result = ContestedDocumentVotePollDriveQueryExecutionResult {
992 contenders: vec![],
993 locked_vote_tally: Some(10),
994 abstaining_vote_tally: None,
995 winner: None,
996 skipped: 0,
997 };
998
999 let conversion: Result<FinalizedContestedDocumentVotePollDriveQueryExecutionResult, _> =
1000 result.try_into();
1001 assert!(conversion.is_err());
1002 }
1003
1004 #[test]
1005 fn finalized_try_from_fails_when_contender_missing_document() {
1006 let contender = ContenderWithSerializedDocumentV0 {
1007 identity_id: Identifier::from([0xBB; 32]),
1008 serialized_document: None, vote_tally: Some(10),
1010 };
1011 let result = ContestedDocumentVotePollDriveQueryExecutionResult {
1012 contenders: vec![contender.into()],
1013 locked_vote_tally: Some(10),
1014 abstaining_vote_tally: Some(5),
1015 winner: None,
1016 skipped: 0,
1017 };
1018
1019 let conversion: Result<FinalizedContestedDocumentVotePollDriveQueryExecutionResult, _> =
1020 result.try_into();
1021 assert!(conversion.is_err());
1022 }
1023
1024 #[test]
1025 fn finalized_try_from_fails_when_contender_missing_vote_tally() {
1026 let contender = ContenderWithSerializedDocumentV0 {
1027 identity_id: Identifier::from([0xCC; 32]),
1028 serialized_document: Some(vec![1]),
1029 vote_tally: None, };
1031 let result = ContestedDocumentVotePollDriveQueryExecutionResult {
1032 contenders: vec![contender.into()],
1033 locked_vote_tally: Some(10),
1034 abstaining_vote_tally: Some(5),
1035 winner: None,
1036 skipped: 0,
1037 };
1038
1039 let conversion: Result<FinalizedContestedDocumentVotePollDriveQueryExecutionResult, _> =
1040 result.try_into();
1041 assert!(conversion.is_err());
1042 }
1043
1044 #[test]
1049 fn construct_path_query_documents_no_start_no_tally() {
1050 let platform_version = PlatformVersion::latest();
1051 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1052 let contract = dpns.data_contract_owned();
1053
1054 let query = build_resolved_query(
1055 &contract,
1056 ContestedDocumentVotePollDriveQueryResultType::Documents,
1057 None, Some(5), None, false, );
1062
1063 let path_query = query
1064 .construct_path_query(platform_version)
1065 .expect("should build path query");
1066
1067 assert!(!path_query.path.is_empty());
1069
1070 assert_eq!(path_query.query.limit, Some(5));
1072 assert_eq!(path_query.query.offset, None);
1073
1074 let items = &path_query.query.query.items;
1076 assert_eq!(
1077 items.len(),
1078 1,
1079 "should have exactly 1 query item for Documents without tally"
1080 );
1081 assert!(
1082 matches!(&items[0], QueryItem::RangeAfter(..)),
1083 "expected RangeAfter, got {:?}",
1084 &items[0]
1085 );
1086
1087 assert_eq!(
1089 path_query.query.query.default_subquery_branch.subquery_path,
1090 Some(vec![vec![0]])
1091 );
1092 }
1093
1094 #[test]
1095 fn construct_path_query_vote_tally_with_locked_and_abstaining() {
1096 let platform_version = PlatformVersion::latest();
1097 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1098 let contract = dpns.data_contract_owned();
1099
1100 let query = build_resolved_query(
1101 &contract,
1102 ContestedDocumentVotePollDriveQueryResultType::VoteTally,
1103 None, Some(10), None, true, );
1108
1109 let path_query = query
1110 .construct_path_query(platform_version)
1111 .expect("should build path query");
1112
1113 assert_eq!(path_query.query.limit, Some(13));
1116
1117 let items = &path_query.query.query.items;
1119 assert_eq!(items.len(), 1);
1120 assert!(
1121 matches!(&items[0], QueryItem::RangeFull(..)),
1122 "expected RangeFull, got {:?}",
1123 &items[0]
1124 );
1125
1126 assert_eq!(
1128 path_query.query.query.default_subquery_branch.subquery_path,
1129 Some(vec![vec![1]])
1130 );
1131 }
1132
1133 #[test]
1134 fn construct_path_query_documents_and_vote_tally_with_locked_and_abstaining() {
1135 let platform_version = PlatformVersion::latest();
1136 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1137 let contract = dpns.data_contract_owned();
1138
1139 let query = build_resolved_query(
1140 &contract,
1141 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally,
1142 None, Some(10), None, true, );
1147
1148 let path_query = query
1149 .construct_path_query(platform_version)
1150 .expect("should build path query");
1151
1152 assert_eq!(path_query.query.limit, Some(23));
1154
1155 assert!(path_query
1157 .query
1158 .query
1159 .default_subquery_branch
1160 .subquery
1161 .is_some());
1162 assert!(path_query
1163 .query
1164 .query
1165 .default_subquery_branch
1166 .subquery_path
1167 .is_none());
1168 }
1169
1170 #[test]
1171 fn construct_path_query_vote_tally_without_locked_and_abstaining() {
1172 let platform_version = PlatformVersion::latest();
1173 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1174 let contract = dpns.data_contract_owned();
1175
1176 let query = build_resolved_query(
1177 &contract,
1178 ContestedDocumentVotePollDriveQueryResultType::VoteTally,
1179 None, Some(10), None, false, );
1184
1185 let path_query = query
1186 .construct_path_query(platform_version)
1187 .expect("should build path query");
1188
1189 assert_eq!(path_query.query.limit, Some(11));
1192
1193 let items = &path_query.query.query.items;
1195 assert_eq!(items.len(), 2);
1196 assert!(
1197 matches!(&items[0], QueryItem::Key(k) if *k == RESOURCE_STORED_INFO_KEY_U8_32.to_vec())
1198 );
1199 assert!(matches!(&items[1], QueryItem::RangeAfter(..)));
1200 }
1201
1202 #[test]
1203 fn construct_path_query_with_start_at_included() {
1204 let platform_version = PlatformVersion::latest();
1205 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1206 let contract = dpns.data_contract_owned();
1207
1208 let start_key = [0x42u8; 32];
1209 let query = build_resolved_query(
1210 &contract,
1211 ContestedDocumentVotePollDriveQueryResultType::Documents,
1212 None, Some(5), Some((start_key, true)), false,
1216 );
1217
1218 let path_query = query
1219 .construct_path_query(platform_version)
1220 .expect("should build path query");
1221
1222 let items = &path_query.query.query.items;
1224 assert_eq!(items.len(), 1);
1225 assert!(
1226 matches!(&items[0], QueryItem::RangeFrom(r) if r.start == start_key.to_vec()),
1227 "expected RangeFrom starting at start_key"
1228 );
1229 assert_eq!(path_query.query.limit, Some(5));
1230 }
1231
1232 #[test]
1233 fn construct_path_query_with_start_at_excluded() {
1234 let platform_version = PlatformVersion::latest();
1235 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1236 let contract = dpns.data_contract_owned();
1237
1238 let start_key = [0x42u8; 32];
1239 let query = build_resolved_query(
1240 &contract,
1241 ContestedDocumentVotePollDriveQueryResultType::Documents,
1242 None, Some(5), Some((start_key, false)), false,
1246 );
1247
1248 let path_query = query
1249 .construct_path_query(platform_version)
1250 .expect("should build path query");
1251
1252 let items = &path_query.query.query.items;
1254 assert_eq!(items.len(), 1);
1255 assert!(
1256 matches!(&items[0], QueryItem::RangeAfter(r) if r.start == start_key.to_vec()),
1257 "expected RangeAfter starting at start_key"
1258 );
1259 }
1260
1261 #[test]
1262 fn construct_path_query_with_offset() {
1263 let platform_version = PlatformVersion::latest();
1264 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1265 let contract = dpns.data_contract_owned();
1266
1267 let query = build_resolved_query(
1268 &contract,
1269 ContestedDocumentVotePollDriveQueryResultType::Documents,
1270 Some(3), Some(10), None, false,
1274 );
1275
1276 let path_query = query
1277 .construct_path_query(platform_version)
1278 .expect("should build path query");
1279
1280 assert_eq!(path_query.query.offset, Some(3));
1281 assert_eq!(path_query.query.limit, Some(10));
1282 }
1283
1284 #[test]
1285 fn construct_path_query_no_limit() {
1286 let platform_version = PlatformVersion::latest();
1287 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1288 let contract = dpns.data_contract_owned();
1289
1290 let query = build_resolved_query(
1291 &contract,
1292 ContestedDocumentVotePollDriveQueryResultType::Documents,
1293 None, None, None, false,
1297 );
1298
1299 let path_query = query
1300 .construct_path_query(platform_version)
1301 .expect("should build path query");
1302
1303 assert_eq!(path_query.query.limit, None);
1304 }
1305
1306 #[test]
1307 fn construct_path_query_documents_and_vote_tally_with_start_at_doubles_limit() {
1308 let platform_version = PlatformVersion::latest();
1309 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1310 let contract = dpns.data_contract_owned();
1311
1312 let start_key = [0x50u8; 32];
1313 let query = build_resolved_query(
1314 &contract,
1315 ContestedDocumentVotePollDriveQueryResultType::DocumentsAndVoteTally,
1316 None, Some(10), Some((start_key, true)), false,
1320 );
1321
1322 let path_query = query
1323 .construct_path_query(platform_version)
1324 .expect("should build path query");
1325
1326 assert_eq!(path_query.query.limit, Some(20));
1328 }
1329
1330 #[test]
1331 fn construct_path_query_single_document_by_contender() {
1332 let platform_version = PlatformVersion::latest();
1333 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1334 let contract = dpns.data_contract_owned();
1335
1336 let contender_id = Identifier::from([0xDD; 32]);
1337 let query = build_resolved_query(
1338 &contract,
1339 ContestedDocumentVotePollDriveQueryResultType::SingleDocumentByContender(contender_id),
1340 None, Some(1), None, false,
1344 );
1345
1346 let path_query = query
1347 .construct_path_query(platform_version)
1348 .expect("should build path query");
1349
1350 let items = &path_query.query.query.items;
1352 assert_eq!(items.len(), 1);
1353 assert!(
1354 matches!(&items[0], QueryItem::Key(k) if k.as_slice() == contender_id.as_bytes()),
1355 "expected Key with contender ID"
1356 );
1357 assert_eq!(path_query.query.limit, Some(1));
1358
1359 assert_eq!(
1361 path_query.query.query.default_subquery_branch.subquery_path,
1362 Some(vec![vec![0]])
1363 );
1364 }
1365
1366 #[test]
1367 fn construct_path_query_has_conditional_subquery_for_stored_info() {
1368 let platform_version = PlatformVersion::latest();
1369 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1370 let contract = dpns.data_contract_owned();
1371
1372 let query = build_resolved_query(
1373 &contract,
1374 ContestedDocumentVotePollDriveQueryResultType::Documents,
1375 None,
1376 Some(5),
1377 None,
1378 false,
1379 );
1380
1381 let path_query = query
1382 .construct_path_query(platform_version)
1383 .expect("should build path query");
1384
1385 let conditional = path_query
1387 .query
1388 .query
1389 .conditional_subquery_branches
1390 .as_ref()
1391 .expect("should have conditional branches");
1392 let stored_info_key = QueryItem::Key(RESOURCE_STORED_INFO_KEY_U8_32.to_vec());
1393 assert!(
1394 conditional.contains_key(&stored_info_key),
1395 "should have conditional subquery for stored info key"
1396 );
1397 }
1398
1399 #[test]
1400 fn construct_path_query_with_locked_abstaining_has_conditional_subqueries() {
1401 let platform_version = PlatformVersion::latest();
1402 let dpns = get_dpns_data_contract_fixture(None, 0, platform_version.protocol_version);
1403 let contract = dpns.data_contract_owned();
1404
1405 let query = build_resolved_query(
1406 &contract,
1407 ContestedDocumentVotePollDriveQueryResultType::VoteTally,
1408 None,
1409 Some(5),
1410 None,
1411 true, );
1413
1414 let path_query = query
1415 .construct_path_query(platform_version)
1416 .expect("should build path query");
1417
1418 let conditional = path_query
1419 .query
1420 .query
1421 .conditional_subquery_branches
1422 .as_ref()
1423 .expect("should have conditional branches");
1424
1425 let lock_key = QueryItem::Key(RESOURCE_LOCK_VOTE_TREE_KEY_U8_32.to_vec());
1427 let abstain_key = QueryItem::Key(RESOURCE_ABSTAIN_VOTE_TREE_KEY_U8_32.to_vec());
1428 assert!(
1429 conditional.contains_key(&lock_key),
1430 "should have conditional subquery for lock key"
1431 );
1432 assert!(
1433 conditional.contains_key(&abstain_key),
1434 "should have conditional subquery for abstain key"
1435 );
1436 }
1437}