referrerpolicy=no-referrer-when-downgrade

polkadot_statement_distribution/v2/
candidates.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! The [`Candidates`] store tracks information about advertised candidates
18//! as well as which peers have advertised them.
19//!
20//! Due to the request-oriented nature of this protocol, we often learn
21//! about candidates just as a hash, alongside claimed properties that the
22//! receipt would commit to. However, it is only later on that we can
23//! confirm those claimed properties. This store lets us keep track of
24//! all candidates which are currently 'relevant' after spam-protection, and
25//! gives us the ability to detect mis-advertisements after the fact
26//! and punish them accordingly.
27
28use 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/// This encapsulates the correct and incorrect advertisers
44/// post-confirmation of a candidate.
45#[derive(Debug, Default, PartialEq)]
46pub struct PostConfirmationReckoning {
47	/// Peers which advertised correctly.
48	pub correct: HashSet<PeerId>,
49	/// Peers which advertised the candidate incorrectly.
50	pub incorrect: HashSet<PeerId>,
51}
52
53/// Outputs generated by initial confirmation of a candidate.
54#[derive(Debug, PartialEq)]
55pub struct PostConfirmation {
56	/// The hypothetical candidate used to determine importability and membership
57	/// in the hypothetical frontier.
58	pub hypothetical: HypotheticalCandidate,
59	/// A "reckoning" of peers who have advertised the candidate previously,
60	/// either accurately or inaccurately.
61	pub reckoning: PostConfirmationReckoning,
62}
63
64/// A tracker for all known candidates in the view.
65///
66/// See module docs for more info.
67#[derive(Default)]
68pub struct Candidates {
69	candidates: HashMap<CandidateHash, CandidateState>,
70	by_parent: HashMap<(Hash, ParaId), HashSet<CandidateHash>>,
71}
72
73impl Candidates {
74	/// Insert an advertisement.
75	///
76	/// This should be invoked only after performing
77	/// spam protection and only for advertisements that
78	/// are valid within the current view. [`Candidates`] never prunes
79	/// candidate by peer ID, to avoid peers skirting misbehavior
80	/// reports by disconnecting intermittently. Therefore, this presumes
81	/// that spam protection limits the peers which can send advertisements
82	/// about unconfirmed candidates.
83	///
84	/// It returns either `Ok(())` or an immediate error in the
85	/// case that the candidate is already known and reality conflicts
86	/// with the advertisement.
87	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	/// Note that a candidate has been confirmed. If the candidate has just been
143	/// confirmed (previous state was `Unconfirmed`), then this returns `Some`. Otherwise, `None`.
144	///
145	/// If we are confirming for the first time, then remove any outdated claims, and generate a
146	/// reckoning of which peers advertised correctly and incorrectly.
147	///
148	/// This does no sanity-checking of input data, and will overwrite already-confirmed candidates.
149	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					// Update the by-parent-hash index not to store any outdated
198					// claims.
199					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	/// Whether a candidate is confirmed.
228	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	/// Get a reference to the candidate, if it's known and confirmed.
236	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	/// Whether statements from a candidate are importable.
244	///
245	/// This is only true when the candidate is known, confirmed,
246	/// and is importable in a fragment chain.
247	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	/// Note that a candidate is importable in a fragment chain indicated by the given
252	/// leaf hash.
253	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	/// Get all hypothetical candidates which should be tested
284	/// for inclusion in the frontier.
285	///
286	/// Provide optional parent parablock information to filter hypotheticals to only
287	/// potential children of that parent.
288	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	/// Prune all candidates according to the relay-parent predicate
322	/// provided.
323	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/// A bad advertisement was recognized.
367#[derive(Debug, PartialEq)]
368pub struct BadAdvertisement;
369
370#[derive(Debug, PartialEq)]
371enum CandidateState {
372	Unconfirmed(UnconfirmedCandidate),
373	Confirmed(ConfirmedCandidate),
374}
375
376/// Claims made alongside the advertisement of a candidate.
377#[derive(Debug, Clone, PartialEq, Eq, Hash)]
378struct CandidateClaims {
379	/// The relay-parent committed to by the candidate.
380	relay_parent: Hash,
381	/// The group index assigned to this candidate.
382	group_index: GroupIndex,
383	/// The hash of the parent head-data and the ParaId. This is optional,
384	/// as only some types of advertisements include this data.
385	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// properties of an unconfirmed but hypothetically importable candidate.
403#[derive(Debug, Hash, PartialEq, Eq)]
404struct UnconfirmedImportable {
405	relay_parent: Hash,
406	parent_hash: Hash,
407	para_id: ParaId,
408}
409
410// An unconfirmed candidate may have have been advertised under
411// multiple identifiers. We track here, on the basis of unique identifier,
412// the peers which advertised each candidate in a specific way.
413#[derive(Debug, PartialEq)]
414struct UnconfirmedCandidate {
415	claims: Vec<(PeerId, CandidateClaims)>,
416	// ref-counted
417	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		// This does no deduplication, but this is only called after
424		// spam prevention is already done. In practice we expect that
425		// each peer will be able to announce the same candidate about 1 time per live relay-parent,
426		// but in doing so it limits the amount of other candidates it can advertise. on balance,
427		// memory consumption is bounded in the same way.
428		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/// A confirmed candidate.
520#[derive(Debug, PartialEq)]
521pub struct ConfirmedCandidate {
522	receipt: Arc<CommittedCandidateReceipt>,
523	persisted_validation_data: PersistedValidationData,
524	assigned_group: GroupIndex,
525	parent_hash: Hash,
526	// active leaves statements about this candidate are importable under.
527	importable_under: HashSet<Hash>,
528}
529
530impl ConfirmedCandidate {
531	/// Get the relay-parent of the candidate.
532	pub fn relay_parent(&self) -> Hash {
533		self.receipt.descriptor.relay_parent()
534	}
535
536	/// Get the para-id of the candidate.
537	pub fn para_id(&self) -> ParaId {
538		self.receipt.descriptor.para_id()
539	}
540
541	/// Get the underlying candidate receipt.
542	pub fn candidate_receipt(&self) -> &Arc<CommittedCandidateReceipt> {
543		&self.receipt
544	}
545
546	/// Get the persisted validation data.
547	pub fn persisted_validation_data(&self) -> &PersistedValidationData {
548		&self.persisted_validation_data
549	}
550
551	/// Whether the candidate is importable.
552	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	/// Get the parent head data hash.
560	pub fn parent_head_data_hash(&self) -> Hash {
561		self.parent_hash
562	}
563
564	/// Get the group index of the assigned group. Note that this is in the context
565	/// of the state of the chain at the candidate's relay parent and its para-id.
566	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		// Confirm a candidate first.
614		candidates.confirm_candidate(candidate_hash_a, candidate_a, pvd_a, group_index_a);
615
616		// Relay parent does not match.
617		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		// Group index does not match.
629		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		// Parent head data does not match.
641		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		// Para ID does not match.
653		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		// Everything matches.
665		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	// Tests that:
678	//
679	// - When the advertisement matches, confirming does not change the parent hash index.
680	// - When it doesn't match, confirming updates the index. Specifically, confirming should prune
681	//   unconfirmed claims.
682	#[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		// Insert some unconfirmed candidates.
739
740		// Advertise A without parent hash.
741		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		// Advertise A with parent hash and ID.
748		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		// Advertise B with parent A.
764		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		// Advertise C with parent A.
783		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		// Advertise D with parent A.
805		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		// Insert confirmed candidates and check parent hash index.
827
828		// Confirmation matches advertisement. Index should be unchanged.
829		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		// Confirmation does not match advertisement. Index should be updated.
853		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		// Make a new candidate for C with a different para ID.
867		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		// Insert some unconfirmed candidates.
947
948		// Advertise A without parent hash.
949		candidates
950			.insert_unconfirmed(peer_a, candidate_hash_a, relay_hash, group_index, None)
951			.ok()
952			.unwrap();
953
954		// Advertise A with parent hash and ID.
955		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		// (Correctly) advertise B with parent A. Do it from a couple of peers.
967		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		// (Wrongly) advertise C with parent A. Do it from a couple peers.
989		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		// Advertise D. Do it correctly from one peer (parent B) and wrongly from another (parent
1011		// A).
1012		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		// Insert confirmed candidates and check parent hash index.
1046
1047		// Confirmation matches advertisement.
1048		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		// Confirm candidate with two wrong peers (different group index).
1091		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		// Confirm candidate with one wrong peer (different parent head data).
1121		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		// Confirm A.
1200		candidates.confirm_candidate(
1201			candidate_hash_a,
1202			candidate_a.clone(),
1203			pvd_a.clone(),
1204			group_index,
1205		);
1206
1207		// Advertise B with parent A.
1208		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		// Advertise C with parent A.
1220		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		// Advertise D with parent B.
1232		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}