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 },
222 sp_consensus_grandpa::SignatureResult::OutdatedSet => {
223 return Err(ClientError::OutdatedJustification)
224 },
225 sp_consensus_grandpa::SignatureResult::Valid => {},
226 }
227
228 if base_hash == signed.precommit.target_hash {
229 continue;
230 }
231
232 match ancestry_chain.ancestry(base_hash, signed.precommit.target_hash) {
233 Ok(route) => {
234 visited_hashes.insert(signed.precommit.target_hash);
237 for hash in route {
238 visited_hashes.insert(hash);
239 }
240 },
241 _ => {
242 return Err(ClientError::BadJustification(
243 "invalid precommit ancestry proof in grandpa justification".to_string(),
244 ))
245 },
246 }
247 }
248
249 let ancestry_hashes: HashSet<_> = self
250 .justification
251 .votes_ancestries
252 .iter()
253 .map(|h: &Block::Header| h.hash())
254 .collect();
255
256 if visited_hashes != ancestry_hashes {
257 return Err(ClientError::BadJustification(
258 "invalid precommit ancestries in grandpa justification with unused headers"
259 .to_string(),
260 ));
261 }
262
263 Ok(())
264 }
265
266 pub fn target(&self) -> (NumberFor<Block>, Block::Hash) {
268 (self.justification.commit.target_number, self.justification.commit.target_hash)
269 }
270}
271
272struct AncestryChain<Block: BlockT> {
276 ancestry: HashMap<Block::Hash, Block::Header>,
277}
278
279impl<Block: BlockT> AncestryChain<Block> {
280 fn new(ancestry: &[Block::Header]) -> AncestryChain<Block> {
281 let ancestry: HashMap<_, _> =
282 ancestry.iter().cloned().map(|h: Block::Header| (h.hash(), h)).collect();
283
284 AncestryChain { ancestry }
285 }
286}
287
288impl<Block: BlockT> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block>
289where
290 NumberFor<Block>: finality_grandpa::BlockNumberOps,
291{
292 fn ancestry(
293 &self,
294 base: Block::Hash,
295 block: Block::Hash,
296 ) -> Result<Vec<Block::Hash>, GrandpaError> {
297 let mut route = Vec::new();
298 let mut current_hash = block;
299 loop {
300 if current_hash == base {
301 break;
302 }
303 match self.ancestry.get(¤t_hash) {
304 Some(current_header) => {
305 current_hash = *current_header.parent_hash();
306 route.push(current_hash);
307 },
308 _ => return Err(GrandpaError::NotDescendent),
309 }
310 }
311 route.pop(); Ok(route)
314 }
315}