1use std::{
20 collections::{HashMap, HashSet},
21 marker::PhantomData,
22 sync::Arc,
23};
24
25use codec::{Decode, DecodeAll, Encode};
26use finality_grandpa::{voter_set::VoterSet, Error as GrandpaError};
27use sp_blockchain::{Error as ClientError, HeaderBackend};
28use sp_consensus_grandpa::AuthorityId;
29use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
30
31use crate::{AuthorityList, Commit, Error};
32
33#[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
42pub struct GrandpaJustification<Block: BlockT> {
43 pub justification: sp_consensus_grandpa::GrandpaJustification<Block::Header>,
45 _block: PhantomData<Block>,
46}
47
48impl<Block: BlockT> From<sp_consensus_grandpa::GrandpaJustification<Block::Header>>
49 for GrandpaJustification<Block>
50{
51 fn from(justification: sp_consensus_grandpa::GrandpaJustification<Block::Header>) -> Self {
52 Self { justification, _block: Default::default() }
53 }
54}
55
56impl<Block: BlockT> Into<sp_consensus_grandpa::GrandpaJustification<Block::Header>>
57 for GrandpaJustification<Block>
58{
59 fn into(self) -> sp_consensus_grandpa::GrandpaJustification<Block::Header> {
60 self.justification
61 }
62}
63
64impl<Block: BlockT> GrandpaJustification<Block> {
65 pub fn from_commit<C>(
68 client: &Arc<C>,
69 round: u64,
70 commit: Commit<Block::Header>,
71 ) -> Result<Self, Error>
72 where
73 C: HeaderBackend<Block>,
74 {
75 let mut votes_ancestries_hashes = HashSet::new();
76 let mut votes_ancestries = Vec::new();
77
78 let error = || {
79 let msg = "invalid precommits for target commit".to_string();
80 Err(Error::Client(ClientError::BadJustification(msg)))
81 };
82
83 let (base_hash, base_number) = match commit
87 .precommits
88 .iter()
89 .map(|signed| &signed.precommit)
90 .min_by_key(|precommit| precommit.target_number)
91 .map(|precommit| (precommit.target_hash, precommit.target_number))
92 {
93 None => return error(),
94 Some(base) => base,
95 };
96
97 for signed in commit.precommits.iter() {
98 let mut current_hash = signed.precommit.target_hash;
99 loop {
100 if current_hash == base_hash {
101 break;
102 }
103
104 match client.header(current_hash)? {
105 Some(current_header) => {
106 if *current_header.number() <= base_number {
110 return error();
111 }
112
113 let parent_hash = *current_header.parent_hash();
114 if votes_ancestries_hashes.insert(current_hash) {
115 votes_ancestries.push(current_header);
116 }
117
118 current_hash = parent_hash;
119 },
120 _ => return error(),
121 }
122 }
123 }
124
125 Ok(sp_consensus_grandpa::GrandpaJustification { round, commit, votes_ancestries }.into())
126 }
127
128 pub fn decode(encoded: &[u8]) -> Result<Self, ClientError> {
130 GrandpaJustification::<Block>::decode_all(&mut &*encoded)
131 .map_err(|_| ClientError::JustificationDecode)
132 }
133
134 pub fn verify_finalizes(
137 &self,
138 finalized_target: (Block::Hash, NumberFor<Block>),
139 set_id: u64,
140 voters: &VoterSet<AuthorityId>,
141 ) -> Result<(), ClientError>
142 where
143 NumberFor<Block>: finality_grandpa::BlockNumberOps,
144 {
145 if (self.justification.commit.target_hash, self.justification.commit.target_number) !=
146 finalized_target
147 {
148 let msg = "invalid commit target in grandpa justification".to_string();
149 return Err(ClientError::BadJustification(msg));
150 }
151
152 self.verify_with_voter_set(set_id, voters)
153 }
154
155 pub fn decode_and_verify_finalizes(
158 encoded: &[u8],
159 finalized_target: (Block::Hash, NumberFor<Block>),
160 set_id: u64,
161 voters: &VoterSet<AuthorityId>,
162 ) -> Result<Self, ClientError>
163 where
164 NumberFor<Block>: finality_grandpa::BlockNumberOps,
165 {
166 let justification = Self::decode(encoded)?;
167 justification.verify_finalizes(finalized_target, set_id, voters)?;
168 Ok(justification)
169 }
170
171 pub fn verify(&self, set_id: u64, authorities: &AuthorityList) -> Result<(), ClientError>
173 where
174 NumberFor<Block>: finality_grandpa::BlockNumberOps,
175 {
176 let voters = VoterSet::new(authorities.iter().cloned())
177 .ok_or(ClientError::Consensus(sp_consensus::Error::InvalidAuthoritiesSet))?;
178
179 self.verify_with_voter_set(set_id, &voters)
180 }
181
182 pub(crate) fn verify_with_voter_set(
184 &self,
185 set_id: u64,
186 voters: &VoterSet<AuthorityId>,
187 ) -> Result<(), ClientError>
188 where
189 NumberFor<Block>: finality_grandpa::BlockNumberOps,
190 {
191 use finality_grandpa::Chain;
192
193 let ancestry_chain = AncestryChain::<Block>::new(&self.justification.votes_ancestries);
194
195 match finality_grandpa::validate_commit(&self.justification.commit, voters, &ancestry_chain)
196 {
197 Ok(ref result) if result.is_valid() => {},
198 _ => {
199 let msg = "invalid commit in grandpa justification".to_string();
200 return Err(ClientError::BadJustification(msg));
201 },
202 }
203
204 let base_hash = self
208 .justification
209 .commit
210 .precommits
211 .iter()
212 .map(|signed| &signed.precommit)
213 .min_by_key(|precommit| precommit.target_number)
214 .map(|precommit| precommit.target_hash)
215 .expect(
216 "can only fail if precommits is empty; \
217 commit has been validated above; \
218 valid commits must include precommits; \
219 qed.",
220 );
221
222 let mut buf = Vec::new();
223 let mut visited_hashes = HashSet::new();
224 for signed in self.justification.commit.precommits.iter() {
225 let signature_result = sp_consensus_grandpa::check_message_signature_with_buffer(
226 &finality_grandpa::Message::Precommit(signed.precommit.clone()),
227 &signed.id,
228 &signed.signature,
229 self.justification.round,
230 set_id,
231 &mut buf,
232 );
233 match signature_result {
234 sp_consensus_grandpa::SignatureResult::Invalid => {
235 return Err(ClientError::BadJustification(
236 "invalid signature for precommit in grandpa justification".to_string(),
237 ))
238 },
239 sp_consensus_grandpa::SignatureResult::OutdatedSet => {
240 return Err(ClientError::OutdatedJustification)
241 },
242 sp_consensus_grandpa::SignatureResult::Valid => {},
243 }
244
245 if base_hash == signed.precommit.target_hash {
246 continue;
247 }
248
249 match ancestry_chain.ancestry(base_hash, signed.precommit.target_hash) {
250 Ok(route) => {
251 visited_hashes.insert(signed.precommit.target_hash);
254 for hash in route {
255 visited_hashes.insert(hash);
256 }
257 },
258 _ => {
259 return Err(ClientError::BadJustification(
260 "invalid precommit ancestry proof in grandpa justification".to_string(),
261 ))
262 },
263 }
264 }
265
266 let ancestry_hashes: HashSet<_> = self
267 .justification
268 .votes_ancestries
269 .iter()
270 .map(|h: &Block::Header| h.hash())
271 .collect();
272
273 if visited_hashes != ancestry_hashes {
274 return Err(ClientError::BadJustification(
275 "invalid precommit ancestries in grandpa justification with unused headers"
276 .to_string(),
277 ));
278 }
279
280 Ok(())
281 }
282
283 pub fn target(&self) -> (NumberFor<Block>, Block::Hash) {
285 (self.justification.commit.target_number, self.justification.commit.target_hash)
286 }
287}
288
289struct AncestryChain<Block: BlockT> {
293 ancestry: HashMap<Block::Hash, Block::Header>,
294}
295
296impl<Block: BlockT> AncestryChain<Block> {
297 fn new(ancestry: &[Block::Header]) -> AncestryChain<Block> {
298 let ancestry: HashMap<_, _> =
299 ancestry.iter().cloned().map(|h: Block::Header| (h.hash(), h)).collect();
300
301 AncestryChain { ancestry }
302 }
303}
304
305impl<Block: BlockT> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block>
306where
307 NumberFor<Block>: finality_grandpa::BlockNumberOps,
308{
309 fn ancestry(
310 &self,
311 base: Block::Hash,
312 block: Block::Hash,
313 ) -> Result<Vec<Block::Hash>, GrandpaError> {
314 let mut route = Vec::new();
315 let mut current_hash = block;
316 loop {
317 if current_hash == base {
318 break;
319 }
320 match self.ancestry.get(¤t_hash) {
321 Some(current_header) => {
322 current_hash = *current_header.parent_hash();
323 route.push(current_hash);
324 },
325 _ => return Err(GrandpaError::NotDescendent),
326 }
327 }
328 route.pop(); Ok(route)
331 }
332}