bp_header_chain/justification/verification/
mod.rs1pub mod equivocation;
20pub mod optimizer;
21pub mod strict;
22
23use crate::{justification::GrandpaJustification, AuthoritySet};
24
25use bp_runtime::HeaderId;
26use finality_grandpa::voter_set::VoterSet;
27use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, SetId};
28use sp_runtime::{traits::Header as HeaderT, RuntimeDebug};
29use sp_std::{
30 collections::{
31 btree_map::{
32 BTreeMap,
33 Entry::{Occupied, Vacant},
34 },
35 btree_set::BTreeSet,
36 },
37 prelude::*,
38 vec,
39 vec::Vec,
40};
41
42type SignedPrecommit<Header> = finality_grandpa::SignedPrecommit<
43 <Header as HeaderT>::Hash,
44 <Header as HeaderT>::Number,
45 AuthoritySignature,
46 AuthorityId,
47>;
48
49#[derive(RuntimeDebug)]
51pub struct AncestryChain<Header: HeaderT> {
52 base: HeaderId<Header::Hash, Header::Number>,
54 parents: BTreeMap<Header::Hash, Header::Hash>,
56 unvisited: BTreeSet<Header::Hash>,
58}
59
60impl<Header: HeaderT> AncestryChain<Header> {
61 pub fn new(
66 justification: &GrandpaJustification<Header>,
67 ) -> (AncestryChain<Header>, Vec<usize>) {
68 let mut parents = BTreeMap::new();
69 let mut unvisited = BTreeSet::new();
70 let mut ignored_idxs = Vec::new();
71 for (idx, ancestor) in justification.votes_ancestries.iter().enumerate() {
72 let hash = ancestor.hash();
73 match parents.entry(hash) {
74 Occupied(_) => {
75 ignored_idxs.push(idx);
76 },
77 Vacant(entry) => {
78 entry.insert(*ancestor.parent_hash());
79 unvisited.insert(hash);
80 },
81 }
82 }
83 (AncestryChain { base: justification.commit_target_id(), parents, unvisited }, ignored_idxs)
84 }
85
86 pub fn parent_hash_of(&self, hash: &Header::Hash) -> Option<&Header::Hash> {
88 self.parents.get(hash)
89 }
90
91 pub fn ancestry(
93 &self,
94 precommit_target_hash: &Header::Hash,
95 precommit_target_number: &Header::Number,
96 ) -> Option<Vec<Header::Hash>> {
97 if precommit_target_number < &self.base.number() {
98 return None
99 }
100
101 let mut route = vec![];
102 let mut current_hash = *precommit_target_hash;
103 loop {
104 if current_hash == self.base.hash() {
105 break
106 }
107
108 current_hash = match self.parent_hash_of(¤t_hash) {
109 Some(parent_hash) => {
110 let is_visited_before = self.unvisited.get(¤t_hash).is_none();
111 if is_visited_before {
112 return Some(route)
115 }
116 route.push(current_hash);
117
118 *parent_hash
119 },
120 None => return None,
121 };
122 }
123
124 Some(route)
125 }
126
127 fn mark_route_as_visited(&mut self, route: Vec<Header::Hash>) {
128 for hash in route {
129 self.unvisited.remove(&hash);
130 }
131 }
132
133 fn is_fully_visited(&self) -> bool {
134 self.unvisited.is_empty()
135 }
136}
137
138#[derive(Eq, RuntimeDebug, PartialEq)]
140pub enum Error {
141 InvalidAuthorityList,
143 InvalidJustificationTarget,
145 DuplicateVotesAncestries,
147 Precommit(PrecommitError),
149 TooLowCumulativeWeight,
152 RedundantVotesAncestries,
154}
155
156#[derive(Eq, RuntimeDebug, PartialEq)]
158pub enum PrecommitError {
159 RedundantAuthorityVote,
161 UnknownAuthorityVote,
163 DuplicateAuthorityVote,
165 InvalidAuthoritySignature,
167 UnrelatedAncestryVote,
170}
171
172#[derive(RuntimeDebug)]
174pub struct JustificationVerificationContext {
175 pub voter_set: VoterSet<AuthorityId>,
177 pub authority_set_id: SetId,
179}
180
181impl TryFrom<AuthoritySet> for JustificationVerificationContext {
182 type Error = Error;
183
184 fn try_from(authority_set: AuthoritySet) -> Result<Self, Self::Error> {
185 let voter_set =
186 VoterSet::new(authority_set.authorities).ok_or(Error::InvalidAuthorityList)?;
187 Ok(JustificationVerificationContext { voter_set, authority_set_id: authority_set.set_id })
188 }
189}
190
191enum IterationFlow {
192 Run,
193 Skip,
194}
195
196trait JustificationVerifier<Header: HeaderT> {
198 fn process_duplicate_votes_ancestries(
200 &mut self,
201 duplicate_votes_ancestries: Vec<usize>,
202 ) -> Result<(), Error>;
203
204 fn process_redundant_vote(
205 &mut self,
206 precommit_idx: usize,
207 ) -> Result<IterationFlow, PrecommitError>;
208
209 fn process_known_authority_vote(
210 &mut self,
211 precommit_idx: usize,
212 signed: &SignedPrecommit<Header>,
213 ) -> Result<IterationFlow, PrecommitError>;
214
215 fn process_unknown_authority_vote(
216 &mut self,
217 precommit_idx: usize,
218 ) -> Result<(), PrecommitError>;
219
220 fn process_unrelated_ancestry_vote(
221 &mut self,
222 precommit_idx: usize,
223 ) -> Result<IterationFlow, PrecommitError>;
224
225 fn process_invalid_signature_vote(
226 &mut self,
227 precommit_idx: usize,
228 ) -> Result<(), PrecommitError>;
229
230 fn process_valid_vote(&mut self, signed: &SignedPrecommit<Header>);
231
232 fn process_redundant_votes_ancestries(
234 &mut self,
235 redundant_votes_ancestries: BTreeSet<Header::Hash>,
236 ) -> Result<(), Error>;
237
238 fn verify_justification(
239 &mut self,
240 finalized_target: (Header::Hash, Header::Number),
241 context: &JustificationVerificationContext,
242 justification: &GrandpaJustification<Header>,
243 ) -> Result<(), Error> {
244 if (justification.commit.target_hash, justification.commit.target_number) !=
246 finalized_target
247 {
248 return Err(Error::InvalidJustificationTarget)
249 }
250
251 let threshold = context.voter_set.threshold().get();
252 let (mut chain, ignored_idxs) = AncestryChain::new(justification);
253 let mut signature_buffer = Vec::new();
254 let mut cumulative_weight = 0u64;
255
256 if !ignored_idxs.is_empty() {
257 self.process_duplicate_votes_ancestries(ignored_idxs)?;
258 }
259
260 for (precommit_idx, signed) in justification.commit.precommits.iter().enumerate() {
261 if cumulative_weight >= threshold {
262 let action =
263 self.process_redundant_vote(precommit_idx).map_err(Error::Precommit)?;
264 if matches!(action, IterationFlow::Skip) {
265 continue
266 }
267 }
268
269 let authority_info = match context.voter_set.get(&signed.id) {
271 Some(authority_info) => {
272 let action = self
275 .process_known_authority_vote(precommit_idx, signed)
276 .map_err(Error::Precommit)?;
277 if matches!(action, IterationFlow::Skip) {
278 continue
279 }
280
281 authority_info
282 },
283 None => {
284 self.process_unknown_authority_vote(precommit_idx).map_err(Error::Precommit)?;
285 continue
286 },
287 };
288
289 let maybe_route =
291 chain.ancestry(&signed.precommit.target_hash, &signed.precommit.target_number);
292 if maybe_route.is_none() {
293 let action = self
294 .process_unrelated_ancestry_vote(precommit_idx)
295 .map_err(Error::Precommit)?;
296 if matches!(action, IterationFlow::Skip) {
297 continue
298 }
299 }
300
301 if !sp_consensus_grandpa::check_message_signature_with_buffer(
303 &finality_grandpa::Message::Precommit(signed.precommit.clone()),
304 &signed.id,
305 &signed.signature,
306 justification.round,
307 context.authority_set_id,
308 &mut signature_buffer,
309 ) {
310 self.process_invalid_signature_vote(precommit_idx).map_err(Error::Precommit)?;
311 continue
312 }
313
314 self.process_valid_vote(signed);
316 if let Some(route) = maybe_route {
317 chain.mark_route_as_visited(route);
318 cumulative_weight = cumulative_weight.saturating_add(authority_info.weight().get());
319 }
320 }
321
322 if cumulative_weight < threshold {
325 return Err(Error::TooLowCumulativeWeight)
326 }
327
328 if !chain.is_fully_visited() {
330 self.process_redundant_votes_ancestries(chain.unvisited)?;
331 }
332
333 Ok(())
334 }
335}