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_and_verify_finalizes(
131 encoded: &[u8],
132 finalized_target: (Block::Hash, NumberFor<Block>),
133 set_id: u64,
134 voters: &VoterSet<AuthorityId>,
135 ) -> Result<Self, ClientError>
136 where
137 NumberFor<Block>: finality_grandpa::BlockNumberOps,
138 {
139 let justification = GrandpaJustification::<Block>::decode_all(&mut &*encoded)
140 .map_err(|_| ClientError::JustificationDecode)?;
141
142 if (
143 justification.justification.commit.target_hash,
144 justification.justification.commit.target_number,
145 ) != finalized_target
146 {
147 let msg = "invalid commit target in grandpa justification".to_string();
148 Err(ClientError::BadJustification(msg))
149 } else {
150 justification.verify_with_voter_set(set_id, voters).map(|_| justification)
151 }
152 }
153
154 pub fn verify(&self, set_id: u64, authorities: &AuthorityList) -> Result<(), ClientError>
156 where
157 NumberFor<Block>: finality_grandpa::BlockNumberOps,
158 {
159 let voters = VoterSet::new(authorities.iter().cloned())
160 .ok_or(ClientError::Consensus(sp_consensus::Error::InvalidAuthoritiesSet))?;
161
162 self.verify_with_voter_set(set_id, &voters)
163 }
164
165 pub(crate) fn verify_with_voter_set(
167 &self,
168 set_id: u64,
169 voters: &VoterSet<AuthorityId>,
170 ) -> Result<(), ClientError>
171 where
172 NumberFor<Block>: finality_grandpa::BlockNumberOps,
173 {
174 use finality_grandpa::Chain;
175
176 let ancestry_chain = AncestryChain::<Block>::new(&self.justification.votes_ancestries);
177
178 match finality_grandpa::validate_commit(&self.justification.commit, voters, &ancestry_chain)
179 {
180 Ok(ref result) if result.is_valid() => {},
181 _ => {
182 let msg = "invalid commit in grandpa justification".to_string();
183 return Err(ClientError::BadJustification(msg))
184 },
185 }
186
187 let base_hash = self
191 .justification
192 .commit
193 .precommits
194 .iter()
195 .map(|signed| &signed.precommit)
196 .min_by_key(|precommit| precommit.target_number)
197 .map(|precommit| precommit.target_hash)
198 .expect(
199 "can only fail if precommits is empty; \
200 commit has been validated above; \
201 valid commits must include precommits; \
202 qed.",
203 );
204
205 let mut buf = Vec::new();
206 let mut visited_hashes = HashSet::new();
207 for signed in self.justification.commit.precommits.iter() {
208 let signature_result = sp_consensus_grandpa::check_message_signature_with_buffer(
209 &finality_grandpa::Message::Precommit(signed.precommit.clone()),
210 &signed.id,
211 &signed.signature,
212 self.justification.round,
213 set_id,
214 &mut buf,
215 );
216 match signature_result {
217 sp_consensus_grandpa::SignatureResult::Invalid =>
218 return Err(ClientError::BadJustification(
219 "invalid signature for precommit in grandpa justification".to_string(),
220 )),
221 sp_consensus_grandpa::SignatureResult::OutdatedSet =>
222 return Err(ClientError::OutdatedJustification),
223 sp_consensus_grandpa::SignatureResult::Valid => {},
224 }
225
226 if base_hash == signed.precommit.target_hash {
227 continue
228 }
229
230 match ancestry_chain.ancestry(base_hash, signed.precommit.target_hash) {
231 Ok(route) => {
232 visited_hashes.insert(signed.precommit.target_hash);
235 for hash in route {
236 visited_hashes.insert(hash);
237 }
238 },
239 _ =>
240 return Err(ClientError::BadJustification(
241 "invalid precommit ancestry proof in grandpa justification".to_string(),
242 )),
243 }
244 }
245
246 let ancestry_hashes: HashSet<_> = self
247 .justification
248 .votes_ancestries
249 .iter()
250 .map(|h: &Block::Header| h.hash())
251 .collect();
252
253 if visited_hashes != ancestry_hashes {
254 return Err(ClientError::BadJustification(
255 "invalid precommit ancestries in grandpa justification with unused headers"
256 .to_string(),
257 ))
258 }
259
260 Ok(())
261 }
262
263 pub fn target(&self) -> (NumberFor<Block>, Block::Hash) {
265 (self.justification.commit.target_number, self.justification.commit.target_hash)
266 }
267}
268
269struct AncestryChain<Block: BlockT> {
273 ancestry: HashMap<Block::Hash, Block::Header>,
274}
275
276impl<Block: BlockT> AncestryChain<Block> {
277 fn new(ancestry: &[Block::Header]) -> AncestryChain<Block> {
278 let ancestry: HashMap<_, _> =
279 ancestry.iter().cloned().map(|h: Block::Header| (h.hash(), h)).collect();
280
281 AncestryChain { ancestry }
282 }
283}
284
285impl<Block: BlockT> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block>
286where
287 NumberFor<Block>: finality_grandpa::BlockNumberOps,
288{
289 fn ancestry(
290 &self,
291 base: Block::Hash,
292 block: Block::Hash,
293 ) -> Result<Vec<Block::Hash>, GrandpaError> {
294 let mut route = Vec::new();
295 let mut current_hash = block;
296 loop {
297 if current_hash == base {
298 break
299 }
300 match self.ancestry.get(¤t_hash) {
301 Some(current_header) => {
302 current_hash = *current_header.parent_hash();
303 route.push(current_hash);
304 },
305 _ => return Err(GrandpaError::NotDescendent),
306 }
307 }
308 route.pop(); Ok(route)
311 }
312}