referrerpolicy=no-referrer-when-downgrade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.

// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.

use polkadot_primitives::{Hash, ValidationCodeHash};
use std::collections::{
	btree_map::{self, BTreeMap},
	HashSet,
};

/// Whether the PVF passed pre-checking or not.
#[derive(Copy, Clone, Debug)]
pub enum Judgement {
	Valid,
	Invalid,
}

impl Judgement {
	/// Whether the PVF is valid or not.
	pub fn is_valid(&self) -> bool {
		match self {
			Judgement::Valid => true,
			Judgement::Invalid => false,
		}
	}
}

/// Data about a particular validation code.
#[derive(Default, Debug)]
struct PvfData {
	/// If `Some` then the PVF pre-checking was run for this PVF. If `None` we are either waiting
	/// for the judgement to come in or the PVF pre-checking failed.
	judgement: Option<Judgement>,

	/// The set of block hashes where this PVF was seen.
	seen_in: HashSet<Hash>,
}

impl PvfData {
	/// Initialize a new `PvfData` which is awaiting for the initial judgement.
	fn pending(origin: Hash) -> Self {
		// Preallocate the hashset with 5 items. This is the anticipated maximum leaves we can
		// deal at the same time. In the vast majority of the cases it will have length of 1.
		let mut seen_in = HashSet::with_capacity(5);
		seen_in.insert(origin);
		Self { judgement: None, seen_in }
	}

	/// Mark a the `PvfData` as seen in the provided relay-chain block referenced by `relay_hash`.
	pub fn seen_in(&mut self, relay_hash: Hash) {
		self.seen_in.insert(relay_hash);
	}

	/// Removes the given `relay_hash` from the set of seen in, and returns if the set is now empty.
	pub fn remove_origin(&mut self, relay_hash: &Hash) -> bool {
		self.seen_in.remove(relay_hash);
		self.seen_in.is_empty()
	}
}

/// The result of [`InterestView::on_leaves_update`].
pub struct OnLeavesUpdateOutcome {
	/// The list of PVFs that we first seen in the activated block.
	pub newcomers: Vec<ValidationCodeHash>,
	/// The number of PVFs that were removed from the view.
	pub left_num: usize,
}

/// A structure that keeps track of relevant PVFs and judgements about them. A relevant PVF is one
/// that resides in at least a single active leaf.
#[derive(Debug)]
pub struct InterestView {
	active_leaves: BTreeMap<Hash, HashSet<ValidationCodeHash>>,
	pvfs: BTreeMap<ValidationCodeHash, PvfData>,
}

impl InterestView {
	pub fn new() -> Self {
		Self { active_leaves: BTreeMap::new(), pvfs: BTreeMap::new() }
	}

	pub fn on_leaves_update(
		&mut self,
		activated: Option<(Hash, Vec<ValidationCodeHash>)>,
		deactivated: &[Hash],
	) -> OnLeavesUpdateOutcome {
		let mut newcomers = Vec::new();

		if let Some((leaf, pending_pvfs)) = activated {
			for pvf in &pending_pvfs {
				match self.pvfs.entry(*pvf) {
					btree_map::Entry::Vacant(v) => {
						v.insert(PvfData::pending(leaf));
						newcomers.push(*pvf);
					},
					btree_map::Entry::Occupied(mut o) => {
						o.get_mut().seen_in(leaf);
					},
				}
			}
			self.active_leaves.entry(leaf).or_default().extend(pending_pvfs);
		}

		let mut left_num = 0;
		for leaf in deactivated {
			let pvfs = self.active_leaves.remove(leaf);
			for pvf in pvfs.into_iter().flatten() {
				if let btree_map::Entry::Occupied(mut o) = self.pvfs.entry(pvf) {
					let now_empty = o.get_mut().remove_origin(leaf);
					if now_empty {
						left_num += 1;
						o.remove();
					}
				}
			}
		}

		OnLeavesUpdateOutcome { newcomers, left_num }
	}

	/// Handles a new judgement for the given `pvf`.
	///
	/// Returns `Err` if the given PVF hash is not known.
	pub fn on_judgement(
		&mut self,
		subject: ValidationCodeHash,
		judgement: Judgement,
	) -> Result<(), ()> {
		match self.pvfs.get_mut(&subject) {
			Some(data) => {
				data.judgement = Some(judgement);
				Ok(())
			},
			None => Err(()),
		}
	}

	/// Returns all PVFs that previously received a judgement.
	pub fn judgements(&self) -> impl Iterator<Item = (ValidationCodeHash, Judgement)> + '_ {
		self.pvfs
			.iter()
			.filter_map(|(code_hash, data)| data.judgement.map(|judgement| (*code_hash, judgement)))
	}
}