referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_pvf_checker/
interest_view.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
17use polkadot_primitives::{Hash, ValidationCodeHash};
18use std::collections::{
19	btree_map::{self, BTreeMap},
20	HashSet,
21};
22
23/// Whether the PVF passed pre-checking or not.
24#[derive(Copy, Clone, Debug)]
25pub enum Judgement {
26	Valid,
27	Invalid,
28}
29
30impl Judgement {
31	/// Whether the PVF is valid or not.
32	pub fn is_valid(&self) -> bool {
33		match self {
34			Judgement::Valid => true,
35			Judgement::Invalid => false,
36		}
37	}
38}
39
40/// Data about a particular validation code.
41#[derive(Default, Debug)]
42struct PvfData {
43	/// If `Some` then the PVF pre-checking was run for this PVF. If `None` we are either waiting
44	/// for the judgement to come in or the PVF pre-checking failed.
45	judgement: Option<Judgement>,
46
47	/// The set of block hashes where this PVF was seen.
48	seen_in: HashSet<Hash>,
49}
50
51impl PvfData {
52	/// Initialize a new `PvfData` which is awaiting for the initial judgement.
53	fn pending(origin: Hash) -> Self {
54		// Preallocate the hashset with 5 items. This is the anticipated maximum leaves we can
55		// deal at the same time. In the vast majority of the cases it will have length of 1.
56		let mut seen_in = HashSet::with_capacity(5);
57		seen_in.insert(origin);
58		Self { judgement: None, seen_in }
59	}
60
61	/// Mark the `PvfData` as seen in the provided relay-chain block referenced by `relay_hash`.
62	pub fn seen_in(&mut self, relay_hash: Hash) {
63		self.seen_in.insert(relay_hash);
64	}
65
66	/// Removes the given `relay_hash` from the set of seen in, and returns if the set is now empty.
67	pub fn remove_origin(&mut self, relay_hash: &Hash) -> bool {
68		self.seen_in.remove(relay_hash);
69		self.seen_in.is_empty()
70	}
71}
72
73/// The result of [`InterestView::on_leaves_update`].
74pub struct OnLeavesUpdateOutcome {
75	/// The list of PVFs that we first seen in the activated block.
76	pub newcomers: Vec<ValidationCodeHash>,
77	/// The number of PVFs that were removed from the view.
78	pub left_num: usize,
79}
80
81/// A structure that keeps track of relevant PVFs and judgements about them. A relevant PVF is one
82/// that resides in at least a single active leaf.
83#[derive(Debug)]
84pub struct InterestView {
85	active_leaves: BTreeMap<Hash, HashSet<ValidationCodeHash>>,
86	pvfs: BTreeMap<ValidationCodeHash, PvfData>,
87}
88
89impl InterestView {
90	pub fn new() -> Self {
91		Self { active_leaves: BTreeMap::new(), pvfs: BTreeMap::new() }
92	}
93
94	pub fn on_leaves_update(
95		&mut self,
96		activated: Option<(Hash, Vec<ValidationCodeHash>)>,
97		deactivated: &[Hash],
98	) -> OnLeavesUpdateOutcome {
99		let mut newcomers = Vec::new();
100
101		if let Some((leaf, pending_pvfs)) = activated {
102			for pvf in &pending_pvfs {
103				match self.pvfs.entry(*pvf) {
104					btree_map::Entry::Vacant(v) => {
105						v.insert(PvfData::pending(leaf));
106						newcomers.push(*pvf);
107					},
108					btree_map::Entry::Occupied(mut o) => {
109						o.get_mut().seen_in(leaf);
110					},
111				}
112			}
113			self.active_leaves.entry(leaf).or_default().extend(pending_pvfs);
114		}
115
116		let mut left_num = 0;
117		for leaf in deactivated {
118			let pvfs = self.active_leaves.remove(leaf);
119			for pvf in pvfs.into_iter().flatten() {
120				if let btree_map::Entry::Occupied(mut o) = self.pvfs.entry(pvf) {
121					let now_empty = o.get_mut().remove_origin(leaf);
122					if now_empty {
123						left_num += 1;
124						o.remove();
125					}
126				}
127			}
128		}
129
130		OnLeavesUpdateOutcome { newcomers, left_num }
131	}
132
133	/// Handles a new judgement for the given `pvf`.
134	///
135	/// Returns `Err` if the given PVF hash is not known.
136	pub fn on_judgement(
137		&mut self,
138		subject: ValidationCodeHash,
139		judgement: Judgement,
140	) -> Result<(), ()> {
141		match self.pvfs.get_mut(&subject) {
142			Some(data) => {
143				data.judgement = Some(judgement);
144				Ok(())
145			},
146			None => Err(()),
147		}
148	}
149
150	/// Returns all PVFs that previously received a judgement.
151	pub fn judgements(&self) -> impl Iterator<Item = (ValidationCodeHash, Judgement)> + '_ {
152		self.pvfs
153			.iter()
154			.filter_map(|(code_hash, data)| data.judgement.map(|judgement| (*code_hash, judgement)))
155	}
156}