1use polkadot_node_network_protocol::PeerId;
29use polkadot_node_subsystem::messages::HypotheticalCandidate;
30use polkadot_primitives::{
31 CandidateHash, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, GroupIndex, Hash,
32 Id as ParaId, PersistedValidationData,
33};
34
35use std::{
36 collections::{
37 hash_map::{Entry, HashMap},
38 HashSet,
39 },
40 sync::Arc,
41};
42
43#[derive(Debug, Default, PartialEq)]
46pub struct PostConfirmationReckoning {
47 pub correct: HashSet<PeerId>,
49 pub incorrect: HashSet<PeerId>,
51}
52
53#[derive(Debug, PartialEq)]
55pub struct PostConfirmation {
56 pub hypothetical: HypotheticalCandidate,
59 pub reckoning: PostConfirmationReckoning,
62}
63
64#[derive(Default)]
68pub struct Candidates {
69 candidates: HashMap<CandidateHash, CandidateState>,
70 by_parent: HashMap<(Hash, ParaId), HashSet<CandidateHash>>,
71}
72
73impl Candidates {
74 pub fn insert_unconfirmed(
88 &mut self,
89 peer: PeerId,
90 candidate_hash: CandidateHash,
91 claimed_relay_parent: Hash,
92 claimed_group_index: GroupIndex,
93 claimed_parent_hash_and_id: Option<(Hash, ParaId)>,
94 ) -> Result<(), BadAdvertisement> {
95 let entry = self.candidates.entry(candidate_hash).or_insert_with(|| {
96 CandidateState::Unconfirmed(UnconfirmedCandidate {
97 claims: Vec::new(),
98 parent_claims: HashMap::new(),
99 unconfirmed_importable_under: HashSet::new(),
100 })
101 });
102
103 match entry {
104 CandidateState::Confirmed(ref c) => {
105 if c.relay_parent() != claimed_relay_parent {
106 return Err(BadAdvertisement)
107 }
108
109 if c.group_index() != claimed_group_index {
110 return Err(BadAdvertisement)
111 }
112
113 if let Some((claimed_parent_hash, claimed_id)) = claimed_parent_hash_and_id {
114 if c.parent_head_data_hash() != claimed_parent_hash {
115 return Err(BadAdvertisement)
116 }
117
118 if c.para_id() != claimed_id {
119 return Err(BadAdvertisement)
120 }
121 }
122 },
123 CandidateState::Unconfirmed(ref mut c) => {
124 c.add_claims(
125 peer,
126 CandidateClaims {
127 relay_parent: claimed_relay_parent,
128 group_index: claimed_group_index,
129 parent_hash_and_id: claimed_parent_hash_and_id,
130 },
131 );
132
133 if let Some(parent_claims) = claimed_parent_hash_and_id {
134 self.by_parent.entry(parent_claims).or_default().insert(candidate_hash);
135 }
136 },
137 }
138
139 Ok(())
140 }
141
142 pub fn confirm_candidate(
150 &mut self,
151 candidate_hash: CandidateHash,
152 candidate_receipt: CommittedCandidateReceipt,
153 persisted_validation_data: PersistedValidationData,
154 assigned_group: GroupIndex,
155 ) -> Option<PostConfirmation> {
156 let parent_hash = persisted_validation_data.parent_head.hash();
157 let relay_parent = candidate_receipt.descriptor.relay_parent();
158 let para_id = candidate_receipt.descriptor.para_id();
159
160 let prev_state = self.candidates.insert(
161 candidate_hash,
162 CandidateState::Confirmed(ConfirmedCandidate {
163 receipt: Arc::new(candidate_receipt),
164 persisted_validation_data,
165 assigned_group,
166 parent_hash,
167 importable_under: HashSet::new(),
168 }),
169 );
170 let new_confirmed =
171 match self.candidates.get_mut(&candidate_hash).expect("just inserted; qed") {
172 CandidateState::Confirmed(x) => x,
173 _ => panic!("just inserted as confirmed; qed"),
174 };
175
176 self.by_parent.entry((parent_hash, para_id)).or_default().insert(candidate_hash);
177
178 match prev_state {
179 None => Some(PostConfirmation {
180 reckoning: Default::default(),
181 hypothetical: new_confirmed.to_hypothetical(candidate_hash),
182 }),
183 Some(CandidateState::Confirmed(_)) => None,
184 Some(CandidateState::Unconfirmed(u)) => Some({
185 let mut reckoning = PostConfirmationReckoning::default();
186
187 for (leaf_hash, x) in u.unconfirmed_importable_under {
188 if x.relay_parent == relay_parent &&
189 x.parent_hash == parent_hash &&
190 x.para_id == para_id
191 {
192 new_confirmed.importable_under.insert(leaf_hash);
193 }
194 }
195
196 for (peer, claims) in u.claims {
197 if let Some((claimed_parent_hash, claimed_id)) = claims.parent_hash_and_id {
200 if claimed_parent_hash != parent_hash || claimed_id != para_id {
201 if let Entry::Occupied(mut e) =
202 self.by_parent.entry((claimed_parent_hash, claimed_id))
203 {
204 e.get_mut().remove(&candidate_hash);
205 if e.get().is_empty() {
206 e.remove();
207 }
208 }
209 }
210 }
211
212 if claims.check(relay_parent, assigned_group, parent_hash, para_id) {
213 reckoning.correct.insert(peer);
214 } else {
215 reckoning.incorrect.insert(peer);
216 }
217 }
218
219 PostConfirmation {
220 reckoning,
221 hypothetical: new_confirmed.to_hypothetical(candidate_hash),
222 }
223 }),
224 }
225 }
226
227 pub fn is_confirmed(&self, candidate_hash: &CandidateHash) -> bool {
229 match self.candidates.get(candidate_hash) {
230 Some(CandidateState::Confirmed(_)) => true,
231 _ => false,
232 }
233 }
234
235 pub fn get_confirmed(&self, candidate_hash: &CandidateHash) -> Option<&ConfirmedCandidate> {
237 match self.candidates.get(candidate_hash) {
238 Some(CandidateState::Confirmed(ref c)) => Some(c),
239 _ => None,
240 }
241 }
242
243 pub fn is_importable(&self, candidate_hash: &CandidateHash) -> bool {
248 self.get_confirmed(candidate_hash).map_or(false, |c| c.is_importable(None))
249 }
250
251 pub fn note_importable_under(&mut self, candidate: &HypotheticalCandidate, leaf_hash: Hash) {
254 match candidate {
255 HypotheticalCandidate::Incomplete {
256 candidate_hash,
257 candidate_para,
258 parent_head_data_hash,
259 candidate_relay_parent,
260 } => {
261 let u = UnconfirmedImportable {
262 relay_parent: *candidate_relay_parent,
263 parent_hash: *parent_head_data_hash,
264 para_id: *candidate_para,
265 };
266
267 if let Some(&mut CandidateState::Unconfirmed(ref mut c)) =
268 self.candidates.get_mut(&candidate_hash)
269 {
270 c.note_maybe_importable_under(leaf_hash, u);
271 }
272 },
273 HypotheticalCandidate::Complete { candidate_hash, .. } => {
274 if let Some(&mut CandidateState::Confirmed(ref mut c)) =
275 self.candidates.get_mut(&candidate_hash)
276 {
277 c.importable_under.insert(leaf_hash);
278 }
279 },
280 }
281 }
282
283 pub fn frontier_hypotheticals(
289 &self,
290 parent: Option<(Hash, ParaId)>,
291 ) -> Vec<HypotheticalCandidate> {
292 fn extend_hypotheticals<'a>(
293 v: &mut Vec<HypotheticalCandidate>,
294 i: impl IntoIterator<Item = (&'a CandidateHash, &'a CandidateState)>,
295 maybe_required_parent: Option<(Hash, ParaId)>,
296 ) {
297 for (c_hash, candidate) in i {
298 match candidate {
299 CandidateState::Unconfirmed(u) =>
300 u.extend_hypotheticals(*c_hash, v, maybe_required_parent),
301 CandidateState::Confirmed(c) => v.push(c.to_hypothetical(*c_hash)),
302 }
303 }
304 }
305
306 let mut v = Vec::new();
307 if let Some(parent) = parent {
308 let maybe_children = self.by_parent.get(&parent);
309 let i = maybe_children
310 .into_iter()
311 .flatten()
312 .filter_map(|c_hash| self.candidates.get_key_value(c_hash));
313
314 extend_hypotheticals(&mut v, i, Some(parent));
315 } else {
316 extend_hypotheticals(&mut v, self.candidates.iter(), None);
317 }
318 v
319 }
320
321 pub fn on_deactivate_leaves(
324 &mut self,
325 leaves: &[Hash],
326 relay_parent_live: impl Fn(&Hash) -> bool,
327 ) {
328 let by_parent = &mut self.by_parent;
329 let mut remove_parent_claims = |c_hash, parent_hash, id| {
330 if let Entry::Occupied(mut e) = by_parent.entry((parent_hash, id)) {
331 e.get_mut().remove(&c_hash);
332 if e.get().is_empty() {
333 e.remove();
334 }
335 }
336 };
337 self.candidates.retain(|c_hash, state| match state {
338 CandidateState::Confirmed(ref mut c) =>
339 if !relay_parent_live(&c.relay_parent()) {
340 remove_parent_claims(*c_hash, c.parent_head_data_hash(), c.para_id());
341 false
342 } else {
343 for leaf_hash in leaves {
344 c.importable_under.remove(leaf_hash);
345 }
346 true
347 },
348 CandidateState::Unconfirmed(ref mut c) => {
349 c.on_deactivate_leaves(
350 leaves,
351 |parent_hash, id| remove_parent_claims(*c_hash, parent_hash, id),
352 &relay_parent_live,
353 );
354 c.has_claims()
355 },
356 });
357
358 gum::trace!(
359 target: crate::LOG_TARGET,
360 "Candidates remaining after cleanup: {}",
361 self.candidates.len(),
362 );
363 }
364}
365
366#[derive(Debug, PartialEq)]
368pub struct BadAdvertisement;
369
370#[derive(Debug, PartialEq)]
371enum CandidateState {
372 Unconfirmed(UnconfirmedCandidate),
373 Confirmed(ConfirmedCandidate),
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Hash)]
378struct CandidateClaims {
379 relay_parent: Hash,
381 group_index: GroupIndex,
383 parent_hash_and_id: Option<(Hash, ParaId)>,
386}
387
388impl CandidateClaims {
389 fn check(
390 &self,
391 relay_parent: Hash,
392 group_index: GroupIndex,
393 parent_hash: Hash,
394 para_id: ParaId,
395 ) -> bool {
396 self.relay_parent == relay_parent &&
397 self.group_index == group_index &&
398 self.parent_hash_and_id.map_or(true, |p| p == (parent_hash, para_id))
399 }
400}
401
402#[derive(Debug, Hash, PartialEq, Eq)]
404struct UnconfirmedImportable {
405 relay_parent: Hash,
406 parent_hash: Hash,
407 para_id: ParaId,
408}
409
410#[derive(Debug, PartialEq)]
414struct UnconfirmedCandidate {
415 claims: Vec<(PeerId, CandidateClaims)>,
416 parent_claims: HashMap<(Hash, ParaId), Vec<(Hash, usize)>>,
418 unconfirmed_importable_under: HashSet<(Hash, UnconfirmedImportable)>,
419}
420
421impl UnconfirmedCandidate {
422 fn add_claims(&mut self, peer: PeerId, claims: CandidateClaims) {
423 if let Some(parent_claims) = claims.parent_hash_and_id {
429 let sub_claims = self.parent_claims.entry(parent_claims).or_default();
430 match sub_claims.iter().position(|x| x.0 == claims.relay_parent) {
431 Some(p) => sub_claims[p].1 += 1,
432 None => sub_claims.push((claims.relay_parent, 1)),
433 }
434 }
435 self.claims.push((peer, claims));
436 }
437
438 fn note_maybe_importable_under(
439 &mut self,
440 active_leaf: Hash,
441 unconfirmed_importable: UnconfirmedImportable,
442 ) {
443 self.unconfirmed_importable_under.insert((active_leaf, unconfirmed_importable));
444 }
445
446 fn on_deactivate_leaves(
447 &mut self,
448 leaves: &[Hash],
449 mut remove_parent_index: impl FnMut(Hash, ParaId),
450 relay_parent_live: impl Fn(&Hash) -> bool,
451 ) {
452 self.claims.retain(|c| {
453 if relay_parent_live(&c.1.relay_parent) {
454 true
455 } else {
456 if let Some(parent_claims) = c.1.parent_hash_and_id {
457 if let Entry::Occupied(mut e) = self.parent_claims.entry(parent_claims) {
458 if let Some(p) = e.get().iter().position(|x| x.0 == c.1.relay_parent) {
459 let sub_claims = e.get_mut();
460 sub_claims[p].1 -= 1;
461 if sub_claims[p].1 == 0 {
462 sub_claims.remove(p);
463 }
464 };
465
466 if e.get().is_empty() {
467 remove_parent_index(parent_claims.0, parent_claims.1);
468 e.remove();
469 }
470 }
471 }
472
473 false
474 }
475 });
476
477 self.unconfirmed_importable_under
478 .retain(|(l, props)| leaves.contains(l) && relay_parent_live(&props.relay_parent));
479 }
480
481 fn extend_hypotheticals(
482 &self,
483 candidate_hash: CandidateHash,
484 v: &mut Vec<HypotheticalCandidate>,
485 required_parent: Option<(Hash, ParaId)>,
486 ) {
487 fn extend_hypotheticals_inner<'a>(
488 candidate_hash: CandidateHash,
489 v: &mut Vec<HypotheticalCandidate>,
490 i: impl IntoIterator<Item = (&'a (Hash, ParaId), &'a Vec<(Hash, usize)>)>,
491 ) {
492 for ((parent_head_hash, para_id), possible_relay_parents) in i {
493 for (relay_parent, _rc) in possible_relay_parents {
494 v.push(HypotheticalCandidate::Incomplete {
495 candidate_hash,
496 candidate_para: *para_id,
497 parent_head_data_hash: *parent_head_hash,
498 candidate_relay_parent: *relay_parent,
499 });
500 }
501 }
502 }
503
504 match required_parent {
505 Some(parent) => extend_hypotheticals_inner(
506 candidate_hash,
507 v,
508 self.parent_claims.get_key_value(&parent),
509 ),
510 None => extend_hypotheticals_inner(candidate_hash, v, self.parent_claims.iter()),
511 }
512 }
513
514 fn has_claims(&self) -> bool {
515 !self.claims.is_empty()
516 }
517}
518
519#[derive(Debug, PartialEq)]
521pub struct ConfirmedCandidate {
522 receipt: Arc<CommittedCandidateReceipt>,
523 persisted_validation_data: PersistedValidationData,
524 assigned_group: GroupIndex,
525 parent_hash: Hash,
526 importable_under: HashSet<Hash>,
528}
529
530impl ConfirmedCandidate {
531 pub fn relay_parent(&self) -> Hash {
533 self.receipt.descriptor.relay_parent()
534 }
535
536 pub fn para_id(&self) -> ParaId {
538 self.receipt.descriptor.para_id()
539 }
540
541 pub fn candidate_receipt(&self) -> &Arc<CommittedCandidateReceipt> {
543 &self.receipt
544 }
545
546 pub fn persisted_validation_data(&self) -> &PersistedValidationData {
548 &self.persisted_validation_data
549 }
550
551 pub fn is_importable<'a>(&self, under_active_leaf: impl Into<Option<&'a Hash>>) -> bool {
553 match under_active_leaf.into() {
554 Some(h) => self.importable_under.contains(h),
555 None => !self.importable_under.is_empty(),
556 }
557 }
558
559 pub fn parent_head_data_hash(&self) -> Hash {
561 self.parent_hash
562 }
563
564 pub fn group_index(&self) -> GroupIndex {
567 self.assigned_group
568 }
569
570 fn to_hypothetical(&self, candidate_hash: CandidateHash) -> HypotheticalCandidate {
571 HypotheticalCandidate::Complete {
572 candidate_hash,
573 receipt: self.receipt.clone(),
574 persisted_validation_data: self.persisted_validation_data.clone(),
575 }
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 use polkadot_primitives::HeadData;
583 use polkadot_primitives_test_helpers::make_candidate;
584
585 #[test]
586 fn inserting_unconfirmed_rejects_on_incompatible_claims() {
587 let relay_head_data_a = HeadData(vec![1, 2, 3]);
588 let relay_head_data_b = HeadData(vec![4, 5, 6]);
589 let relay_hash_a = relay_head_data_a.hash();
590 let relay_hash_b = relay_head_data_b.hash();
591
592 let para_id_a = 1.into();
593 let para_id_b = 2.into();
594
595 let (candidate_a, pvd_a) = make_candidate(
596 relay_hash_a,
597 1,
598 para_id_a,
599 relay_head_data_a,
600 HeadData(vec![1]),
601 Hash::from_low_u64_be(1000).into(),
602 );
603
604 let candidate_hash_a = candidate_a.hash();
605
606 let peer = PeerId::random();
607
608 let group_index_a = 100.into();
609 let group_index_b = 200.into();
610
611 let mut candidates = Candidates::default();
612
613 candidates.confirm_candidate(candidate_hash_a, candidate_a, pvd_a, group_index_a);
615
616 assert_eq!(
618 candidates.insert_unconfirmed(
619 peer,
620 candidate_hash_a,
621 relay_hash_b,
622 group_index_a,
623 Some((relay_hash_a, para_id_a)),
624 ),
625 Err(BadAdvertisement)
626 );
627
628 assert_eq!(
630 candidates.insert_unconfirmed(
631 peer,
632 candidate_hash_a,
633 relay_hash_a,
634 group_index_b,
635 Some((relay_hash_a, para_id_a)),
636 ),
637 Err(BadAdvertisement)
638 );
639
640 assert_eq!(
642 candidates.insert_unconfirmed(
643 peer,
644 candidate_hash_a,
645 relay_hash_a,
646 group_index_a,
647 Some((relay_hash_b, para_id_a)),
648 ),
649 Err(BadAdvertisement)
650 );
651
652 assert_eq!(
654 candidates.insert_unconfirmed(
655 peer,
656 candidate_hash_a,
657 relay_hash_a,
658 group_index_a,
659 Some((relay_hash_a, para_id_b)),
660 ),
661 Err(BadAdvertisement)
662 );
663
664 assert_eq!(
666 candidates.insert_unconfirmed(
667 peer,
668 candidate_hash_a,
669 relay_hash_a,
670 group_index_a,
671 Some((relay_hash_a, para_id_a)),
672 ),
673 Ok(())
674 );
675 }
676
677 #[test]
683 fn confirming_maintains_parent_hash_index() {
684 let relay_head_data = HeadData(vec![1, 2, 3]);
685 let relay_hash = relay_head_data.hash();
686
687 let candidate_head_data_a = HeadData(vec![1]);
688 let candidate_head_data_b = HeadData(vec![2]);
689 let candidate_head_data_c = HeadData(vec![3]);
690 let candidate_head_data_d = HeadData(vec![4]);
691 let candidate_head_data_hash_a = candidate_head_data_a.hash();
692 let candidate_head_data_hash_b = candidate_head_data_b.hash();
693 let candidate_head_data_hash_c = candidate_head_data_c.hash();
694
695 let (candidate_a, pvd_a) = make_candidate(
696 relay_hash,
697 1,
698 1.into(),
699 relay_head_data,
700 candidate_head_data_a.clone(),
701 Hash::from_low_u64_be(1000).into(),
702 );
703 let (candidate_b, pvd_b) = make_candidate(
704 relay_hash,
705 1,
706 1.into(),
707 candidate_head_data_a,
708 candidate_head_data_b.clone(),
709 Hash::from_low_u64_be(2000).into(),
710 );
711 let (candidate_c, _) = make_candidate(
712 relay_hash,
713 1,
714 1.into(),
715 candidate_head_data_b.clone(),
716 candidate_head_data_c.clone(),
717 Hash::from_low_u64_be(3000).into(),
718 );
719 let (candidate_d, pvd_d) = make_candidate(
720 relay_hash,
721 1,
722 1.into(),
723 candidate_head_data_c.clone(),
724 candidate_head_data_d,
725 Hash::from_low_u64_be(4000).into(),
726 );
727
728 let candidate_hash_a = candidate_a.hash();
729 let candidate_hash_b = candidate_b.hash();
730 let candidate_hash_c = candidate_c.hash();
731 let candidate_hash_d = candidate_d.hash();
732
733 let peer = PeerId::random();
734 let group_index = 100.into();
735
736 let mut candidates = Candidates::default();
737
738 candidates
742 .insert_unconfirmed(peer, candidate_hash_a, relay_hash, group_index, None)
743 .ok()
744 .unwrap();
745 assert_eq!(candidates.by_parent, HashMap::default());
746
747 candidates
749 .insert_unconfirmed(
750 peer,
751 candidate_hash_a,
752 relay_hash,
753 group_index,
754 Some((relay_hash, 1.into())),
755 )
756 .ok()
757 .unwrap();
758 assert_eq!(
759 candidates.by_parent,
760 HashMap::from([((relay_hash, 1.into()), HashSet::from([candidate_hash_a]))])
761 );
762
763 candidates
765 .insert_unconfirmed(
766 peer,
767 candidate_hash_b,
768 relay_hash,
769 group_index,
770 Some((candidate_head_data_hash_a, 1.into())),
771 )
772 .ok()
773 .unwrap();
774 assert_eq!(
775 candidates.by_parent,
776 HashMap::from([
777 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
778 ((candidate_head_data_hash_a, 1.into()), HashSet::from([candidate_hash_b]))
779 ])
780 );
781
782 candidates
784 .insert_unconfirmed(
785 peer,
786 candidate_hash_c,
787 relay_hash,
788 group_index,
789 Some((candidate_head_data_hash_a, 1.into())),
790 )
791 .ok()
792 .unwrap();
793 assert_eq!(
794 candidates.by_parent,
795 HashMap::from([
796 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
797 (
798 (candidate_head_data_hash_a, 1.into()),
799 HashSet::from([candidate_hash_b, candidate_hash_c])
800 )
801 ])
802 );
803
804 candidates
806 .insert_unconfirmed(
807 peer,
808 candidate_hash_d,
809 relay_hash,
810 group_index,
811 Some((candidate_head_data_hash_a, 1.into())),
812 )
813 .ok()
814 .unwrap();
815 assert_eq!(
816 candidates.by_parent,
817 HashMap::from([
818 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
819 (
820 (candidate_head_data_hash_a, 1.into()),
821 HashSet::from([candidate_hash_b, candidate_hash_c, candidate_hash_d])
822 )
823 ])
824 );
825
826 candidates.confirm_candidate(candidate_hash_a, candidate_a, pvd_a, group_index);
830 assert_eq!(
831 candidates.by_parent,
832 HashMap::from([
833 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
834 (
835 (candidate_head_data_hash_a, 1.into()),
836 HashSet::from([candidate_hash_b, candidate_hash_c, candidate_hash_d])
837 )
838 ])
839 );
840 candidates.confirm_candidate(candidate_hash_b, candidate_b, pvd_b, group_index);
841 assert_eq!(
842 candidates.by_parent,
843 HashMap::from([
844 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
845 (
846 (candidate_head_data_hash_a, 1.into()),
847 HashSet::from([candidate_hash_b, candidate_hash_c, candidate_hash_d])
848 )
849 ])
850 );
851
852 candidates.confirm_candidate(candidate_hash_d, candidate_d, pvd_d, group_index);
854 assert_eq!(
855 candidates.by_parent,
856 HashMap::from([
857 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
858 (
859 (candidate_head_data_hash_a, 1.into()),
860 HashSet::from([candidate_hash_b, candidate_hash_c])
861 ),
862 ((candidate_head_data_hash_c, 1.into()), HashSet::from([candidate_hash_d]))
863 ])
864 );
865
866 let (new_candidate_c, new_pvd_c) = make_candidate(
868 relay_hash,
869 1,
870 2.into(),
871 candidate_head_data_b,
872 candidate_head_data_c.clone(),
873 Hash::from_low_u64_be(3000).into(),
874 );
875 candidates.confirm_candidate(candidate_hash_c, new_candidate_c, new_pvd_c, group_index);
876 assert_eq!(
877 candidates.by_parent,
878 HashMap::from([
879 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
880 ((candidate_head_data_hash_a, 1.into()), HashSet::from([candidate_hash_b])),
881 ((candidate_head_data_hash_b, 2.into()), HashSet::from([candidate_hash_c])),
882 ((candidate_head_data_hash_c, 1.into()), HashSet::from([candidate_hash_d]))
883 ])
884 );
885 }
886
887 #[test]
888 fn test_returned_post_confirmation() {
889 let relay_head_data = HeadData(vec![1, 2, 3]);
890 let relay_hash = relay_head_data.hash();
891
892 let candidate_head_data_a = HeadData(vec![1]);
893 let candidate_head_data_b = HeadData(vec![2]);
894 let candidate_head_data_c = HeadData(vec![3]);
895 let candidate_head_data_d = HeadData(vec![4]);
896 let candidate_head_data_hash_a = candidate_head_data_a.hash();
897 let candidate_head_data_hash_b = candidate_head_data_b.hash();
898
899 let (candidate_a, pvd_a) = make_candidate(
900 relay_hash,
901 1,
902 1.into(),
903 relay_head_data,
904 candidate_head_data_a.clone(),
905 Hash::from_low_u64_be(1000).into(),
906 );
907 let (candidate_b, pvd_b) = make_candidate(
908 relay_hash,
909 1,
910 1.into(),
911 candidate_head_data_a.clone(),
912 candidate_head_data_b.clone(),
913 Hash::from_low_u64_be(2000).into(),
914 );
915 let (candidate_c, _) = make_candidate(
916 relay_hash,
917 1,
918 1.into(),
919 candidate_head_data_a.clone(),
920 candidate_head_data_c.clone(),
921 Hash::from_low_u64_be(3000).into(),
922 );
923 let (candidate_d, pvd_d) = make_candidate(
924 relay_hash,
925 1,
926 1.into(),
927 candidate_head_data_b.clone(),
928 candidate_head_data_d,
929 Hash::from_low_u64_be(4000).into(),
930 );
931
932 let candidate_hash_a = candidate_a.hash();
933 let candidate_hash_b = candidate_b.hash();
934 let candidate_hash_c = candidate_c.hash();
935 let candidate_hash_d = candidate_d.hash();
936
937 let peer_a = PeerId::random();
938 let peer_b = PeerId::random();
939 let peer_c = PeerId::random();
940 let peer_d = PeerId::random();
941
942 let group_index = 100.into();
943
944 let mut candidates = Candidates::default();
945
946 candidates
950 .insert_unconfirmed(peer_a, candidate_hash_a, relay_hash, group_index, None)
951 .ok()
952 .unwrap();
953
954 candidates
956 .insert_unconfirmed(
957 peer_a,
958 candidate_hash_a,
959 relay_hash,
960 group_index,
961 Some((relay_hash, 1.into())),
962 )
963 .ok()
964 .unwrap();
965
966 candidates
968 .insert_unconfirmed(
969 peer_a,
970 candidate_hash_b,
971 relay_hash,
972 group_index,
973 Some((candidate_head_data_hash_a, 1.into())),
974 )
975 .ok()
976 .unwrap();
977 candidates
978 .insert_unconfirmed(
979 peer_b,
980 candidate_hash_b,
981 relay_hash,
982 group_index,
983 Some((candidate_head_data_hash_a, 1.into())),
984 )
985 .ok()
986 .unwrap();
987
988 candidates
990 .insert_unconfirmed(
991 peer_b,
992 candidate_hash_c,
993 relay_hash,
994 group_index,
995 Some((candidate_head_data_hash_a, 1.into())),
996 )
997 .ok()
998 .unwrap();
999 candidates
1000 .insert_unconfirmed(
1001 peer_c,
1002 candidate_hash_c,
1003 relay_hash,
1004 group_index,
1005 Some((candidate_head_data_hash_a, 1.into())),
1006 )
1007 .ok()
1008 .unwrap();
1009
1010 candidates
1013 .insert_unconfirmed(
1014 peer_c,
1015 candidate_hash_d,
1016 relay_hash,
1017 group_index,
1018 Some((candidate_head_data_hash_b, 1.into())),
1019 )
1020 .ok()
1021 .unwrap();
1022 candidates
1023 .insert_unconfirmed(
1024 peer_d,
1025 candidate_hash_d,
1026 relay_hash,
1027 group_index,
1028 Some((candidate_head_data_hash_a, 1.into())),
1029 )
1030 .ok()
1031 .unwrap();
1032
1033 assert_eq!(
1034 candidates.by_parent,
1035 HashMap::from([
1036 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
1037 (
1038 (candidate_head_data_hash_a, 1.into()),
1039 HashSet::from([candidate_hash_b, candidate_hash_c, candidate_hash_d])
1040 ),
1041 ((candidate_head_data_hash_b, 1.into()), HashSet::from([candidate_hash_d]))
1042 ])
1043 );
1044
1045 let post_confirmation = candidates.confirm_candidate(
1049 candidate_hash_a,
1050 candidate_a.clone(),
1051 pvd_a.clone(),
1052 group_index,
1053 );
1054 assert_eq!(
1055 post_confirmation,
1056 Some(PostConfirmation {
1057 hypothetical: HypotheticalCandidate::Complete {
1058 candidate_hash: candidate_hash_a,
1059 receipt: Arc::new(candidate_a),
1060 persisted_validation_data: pvd_a,
1061 },
1062 reckoning: PostConfirmationReckoning {
1063 correct: HashSet::from([peer_a]),
1064 incorrect: HashSet::from([]),
1065 },
1066 })
1067 );
1068
1069 let post_confirmation = candidates.confirm_candidate(
1070 candidate_hash_b,
1071 candidate_b.clone(),
1072 pvd_b.clone(),
1073 group_index,
1074 );
1075 assert_eq!(
1076 post_confirmation,
1077 Some(PostConfirmation {
1078 hypothetical: HypotheticalCandidate::Complete {
1079 candidate_hash: candidate_hash_b,
1080 receipt: Arc::new(candidate_b),
1081 persisted_validation_data: pvd_b,
1082 },
1083 reckoning: PostConfirmationReckoning {
1084 correct: HashSet::from([peer_a, peer_b]),
1085 incorrect: HashSet::from([]),
1086 },
1087 })
1088 );
1089
1090 let (new_candidate_c, new_pvd_c) = make_candidate(
1092 relay_hash,
1093 1,
1094 2.into(),
1095 candidate_head_data_b,
1096 candidate_head_data_c.clone(),
1097 Hash::from_low_u64_be(3000).into(),
1098 );
1099 let post_confirmation = candidates.confirm_candidate(
1100 candidate_hash_c,
1101 new_candidate_c.clone(),
1102 new_pvd_c.clone(),
1103 group_index,
1104 );
1105 assert_eq!(
1106 post_confirmation,
1107 Some(PostConfirmation {
1108 hypothetical: HypotheticalCandidate::Complete {
1109 candidate_hash: candidate_hash_c,
1110 receipt: Arc::new(new_candidate_c),
1111 persisted_validation_data: new_pvd_c,
1112 },
1113 reckoning: PostConfirmationReckoning {
1114 correct: HashSet::from([]),
1115 incorrect: HashSet::from([peer_b, peer_c]),
1116 },
1117 })
1118 );
1119
1120 let post_confirmation = candidates.confirm_candidate(
1122 candidate_hash_d,
1123 candidate_d.clone(),
1124 pvd_d.clone(),
1125 group_index,
1126 );
1127 assert_eq!(
1128 post_confirmation,
1129 Some(PostConfirmation {
1130 hypothetical: HypotheticalCandidate::Complete {
1131 candidate_hash: candidate_hash_d,
1132 receipt: Arc::new(candidate_d),
1133 persisted_validation_data: pvd_d,
1134 },
1135 reckoning: PostConfirmationReckoning {
1136 correct: HashSet::from([peer_c]),
1137 incorrect: HashSet::from([peer_d]),
1138 },
1139 })
1140 );
1141 }
1142
1143 #[test]
1144 fn test_hypothetical_frontiers() {
1145 let relay_head_data = HeadData(vec![1, 2, 3]);
1146 let relay_hash = relay_head_data.hash();
1147
1148 let candidate_head_data_a = HeadData(vec![1]);
1149 let candidate_head_data_b = HeadData(vec![2]);
1150 let candidate_head_data_c = HeadData(vec![3]);
1151 let candidate_head_data_d = HeadData(vec![4]);
1152 let candidate_head_data_hash_a = candidate_head_data_a.hash();
1153 let candidate_head_data_hash_b = candidate_head_data_b.hash();
1154 let candidate_head_data_hash_d = candidate_head_data_d.hash();
1155
1156 let (candidate_a, pvd_a) = make_candidate(
1157 relay_hash,
1158 1,
1159 1.into(),
1160 relay_head_data,
1161 candidate_head_data_a.clone(),
1162 Hash::from_low_u64_be(1000).into(),
1163 );
1164 let (candidate_b, _) = make_candidate(
1165 relay_hash,
1166 1,
1167 1.into(),
1168 candidate_head_data_a.clone(),
1169 candidate_head_data_b.clone(),
1170 Hash::from_low_u64_be(2000).into(),
1171 );
1172 let (candidate_c, _) = make_candidate(
1173 relay_hash,
1174 1,
1175 1.into(),
1176 candidate_head_data_a.clone(),
1177 candidate_head_data_c.clone(),
1178 Hash::from_low_u64_be(3000).into(),
1179 );
1180 let (candidate_d, _) = make_candidate(
1181 relay_hash,
1182 1,
1183 1.into(),
1184 candidate_head_data_b.clone(),
1185 candidate_head_data_d,
1186 Hash::from_low_u64_be(4000).into(),
1187 );
1188
1189 let candidate_hash_a = candidate_a.hash();
1190 let candidate_hash_b = candidate_b.hash();
1191 let candidate_hash_c = candidate_c.hash();
1192 let candidate_hash_d = candidate_d.hash();
1193
1194 let peer = PeerId::random();
1195 let group_index = 100.into();
1196
1197 let mut candidates = Candidates::default();
1198
1199 candidates.confirm_candidate(
1201 candidate_hash_a,
1202 candidate_a.clone(),
1203 pvd_a.clone(),
1204 group_index,
1205 );
1206
1207 candidates
1209 .insert_unconfirmed(
1210 peer,
1211 candidate_hash_b,
1212 relay_hash,
1213 group_index,
1214 Some((candidate_head_data_hash_a, 1.into())),
1215 )
1216 .ok()
1217 .unwrap();
1218
1219 candidates
1221 .insert_unconfirmed(
1222 peer,
1223 candidate_hash_c,
1224 relay_hash,
1225 group_index,
1226 Some((candidate_head_data_hash_a, 1.into())),
1227 )
1228 .ok()
1229 .unwrap();
1230
1231 candidates
1233 .insert_unconfirmed(
1234 peer,
1235 candidate_hash_d,
1236 relay_hash,
1237 group_index,
1238 Some((candidate_head_data_hash_b, 1.into())),
1239 )
1240 .ok()
1241 .unwrap();
1242
1243 assert_eq!(
1244 candidates.by_parent,
1245 HashMap::from([
1246 ((relay_hash, 1.into()), HashSet::from([candidate_hash_a])),
1247 (
1248 (candidate_head_data_hash_a, 1.into()),
1249 HashSet::from([candidate_hash_b, candidate_hash_c])
1250 ),
1251 ((candidate_head_data_hash_b, 1.into()), HashSet::from([candidate_hash_d]))
1252 ])
1253 );
1254
1255 let hypothetical_a = HypotheticalCandidate::Complete {
1256 candidate_hash: candidate_hash_a,
1257 receipt: Arc::new(candidate_a),
1258 persisted_validation_data: pvd_a,
1259 };
1260 let hypothetical_b = HypotheticalCandidate::Incomplete {
1261 candidate_hash: candidate_hash_b,
1262 candidate_para: 1.into(),
1263 parent_head_data_hash: candidate_head_data_hash_a,
1264 candidate_relay_parent: relay_hash,
1265 };
1266 let hypothetical_c = HypotheticalCandidate::Incomplete {
1267 candidate_hash: candidate_hash_c,
1268 candidate_para: 1.into(),
1269 parent_head_data_hash: candidate_head_data_hash_a,
1270 candidate_relay_parent: relay_hash,
1271 };
1272 let hypothetical_d = HypotheticalCandidate::Incomplete {
1273 candidate_hash: candidate_hash_d,
1274 candidate_para: 1.into(),
1275 parent_head_data_hash: candidate_head_data_hash_b,
1276 candidate_relay_parent: relay_hash,
1277 };
1278
1279 let hypotheticals = candidates.frontier_hypotheticals(Some((relay_hash, 1.into())));
1280 assert_eq!(hypotheticals.len(), 1);
1281 assert!(hypotheticals.contains(&hypothetical_a));
1282
1283 let hypotheticals =
1284 candidates.frontier_hypotheticals(Some((candidate_head_data_hash_a, 2.into())));
1285 assert_eq!(hypotheticals.len(), 0);
1286
1287 let hypotheticals =
1288 candidates.frontier_hypotheticals(Some((candidate_head_data_hash_a, 1.into())));
1289 assert_eq!(hypotheticals.len(), 2);
1290 assert!(hypotheticals.contains(&hypothetical_b));
1291 assert!(hypotheticals.contains(&hypothetical_c));
1292
1293 let hypotheticals =
1294 candidates.frontier_hypotheticals(Some((candidate_head_data_hash_d, 1.into())));
1295 assert_eq!(hypotheticals.len(), 0);
1296
1297 let hypotheticals = candidates.frontier_hypotheticals(None);
1298 assert_eq!(hypotheticals.len(), 4);
1299 assert!(hypotheticals.contains(&hypothetical_a));
1300 assert!(hypotheticals.contains(&hypothetical_b));
1301 assert!(hypotheticals.contains(&hypothetical_c));
1302 assert!(hypotheticals.contains(&hypothetical_d));
1303 }
1304}