1use std::fmt::Debug;
22
23use log::trace;
24
25use codec::Codec;
26
27use sc_client_api::UsageProvider;
28use sp_api::{Core, ProvideRuntimeApi};
29use sp_application_crypto::{AppCrypto, AppPublic};
30use sp_blockchain::Result as CResult;
31use sp_consensus::Error as ConsensusError;
32use sp_consensus_slots::Slot;
33use sp_core::crypto::{ByteArray, Pair};
34use sp_keystore::KeystorePtr;
35use sp_runtime::{
36 traits::{Block as BlockT, Header, NumberFor, Zero},
37 DigestItem,
38};
39
40pub use sc_consensus_slots::check_equivocation;
41
42use super::{
43 AuraApi, AuthorityId, CompatibilityMode, CompatibleDigestItem, SlotDuration, LOG_TARGET,
44};
45
46pub fn slot_duration<A, B, C>(client: &C) -> CResult<SlotDuration>
48where
49 A: Codec,
50 B: BlockT,
51 C: ProvideRuntimeApi<B> + UsageProvider<B>,
52 C::Api: AuraApi<B, A>,
53{
54 slot_duration_at(client, client.usage_info().chain.best_hash)
55}
56
57pub fn slot_duration_at<A, B, C>(client: &C, block_hash: B::Hash) -> CResult<SlotDuration>
59where
60 A: Codec,
61 B: BlockT,
62 C: ProvideRuntimeApi<B>,
63 C::Api: AuraApi<B, A>,
64{
65 client.runtime_api().slot_duration(block_hash).map_err(|err| err.into())
66}
67
68pub fn slot_author<P: Pair>(slot: Slot, authorities: &[AuthorityId<P>]) -> Option<&AuthorityId<P>> {
70 if authorities.is_empty() {
71 return None
72 }
73
74 let idx = *slot % (authorities.len() as u64);
75 assert!(
76 idx <= usize::MAX as u64,
77 "It is impossible to have a vector with length beyond the address space; qed",
78 );
79
80 let current_author = authorities.get(idx as usize).expect(
81 "authorities not empty; index constrained to list length;this is a valid index; qed",
82 );
83
84 Some(current_author)
85}
86
87pub async fn claim_slot<P: Pair>(
92 slot: Slot,
93 authorities: &[AuthorityId<P>],
94 keystore: &KeystorePtr,
95) -> Option<P::Public> {
96 let expected_author = slot_author::<P>(slot, authorities);
97 expected_author.and_then(|p| {
98 if keystore.has_keys(&[(p.to_raw_vec(), sp_application_crypto::key_types::AURA)]) {
99 Some(p.clone())
100 } else {
101 None
102 }
103 })
104}
105
106pub fn pre_digest<P: Pair>(slot: Slot) -> sp_runtime::DigestItem
111where
112 P::Signature: Codec,
113{
114 <DigestItem as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)
115}
116
117pub fn seal<Hash, P>(
121 header_hash: &Hash,
122 public: &P::Public,
123 keystore: &KeystorePtr,
124) -> Result<sp_runtime::DigestItem, ConsensusError>
125where
126 Hash: AsRef<[u8]>,
127 P: Pair,
128 P::Signature: Codec + TryFrom<Vec<u8>>,
129 P::Public: AppPublic,
130{
131 let signature = keystore
132 .sign_with(
133 <AuthorityId<P> as AppCrypto>::ID,
134 <AuthorityId<P> as AppCrypto>::CRYPTO_ID,
135 public.as_slice(),
136 header_hash.as_ref(),
137 )
138 .map_err(|e| ConsensusError::CannotSign(format!("{}. Key: {:?}", e, public)))?
139 .ok_or_else(|| {
140 ConsensusError::CannotSign(format!("Could not find key in keystore. Key: {:?}", public))
141 })?;
142
143 let signature = signature
144 .as_slice()
145 .try_into()
146 .map_err(|_| ConsensusError::InvalidSignature(signature, public.to_raw_vec()))?;
147
148 let signature_digest_item =
149 <DigestItem as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
150
151 Ok(signature_digest_item)
152}
153
154#[derive(Debug, thiserror::Error)]
156pub enum PreDigestLookupError {
157 #[error("Multiple Aura pre-runtime headers")]
159 MultipleHeaders,
160 #[error("No Aura pre-runtime digest found")]
162 NoDigestFound,
163}
164
165pub fn find_pre_digest<B: BlockT, Signature: Codec>(
171 header: &B::Header,
172) -> Result<Slot, PreDigestLookupError> {
173 if header.number().is_zero() {
174 return Ok(0.into())
175 }
176
177 let mut pre_digest: Option<Slot> = None;
178 for log in header.digest().logs() {
179 trace!(target: LOG_TARGET, "Checking log {:?}", log);
180 match (CompatibleDigestItem::<Signature>::as_aura_pre_digest(log), pre_digest.is_some()) {
181 (Some(_), true) => return Err(PreDigestLookupError::MultipleHeaders),
182 (None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
183 (s, false) => pre_digest = s,
184 }
185 }
186 pre_digest.ok_or_else(|| PreDigestLookupError::NoDigestFound)
187}
188
189pub fn fetch_authorities_with_compatibility_mode<A, B, C>(
195 client: &C,
196 parent_hash: B::Hash,
197 context_block_number: NumberFor<B>,
198 compatibility_mode: &CompatibilityMode<NumberFor<B>>,
199) -> Result<Vec<A>, ConsensusError>
200where
201 A: Codec + Debug,
202 B: BlockT,
203 C: ProvideRuntimeApi<B>,
204 C::Api: AuraApi<B, A>,
205{
206 let runtime_api = client.runtime_api();
207
208 match compatibility_mode {
209 CompatibilityMode::None => {},
210 CompatibilityMode::UseInitializeBlock { until } =>
212 if *until > context_block_number {
213 runtime_api
214 .initialize_block(
215 parent_hash,
216 &B::Header::new(
217 context_block_number,
218 Default::default(),
219 Default::default(),
220 parent_hash,
221 Default::default(),
222 ),
223 )
224 .map_err(|_| ConsensusError::InvalidAuthoritiesSet)?;
225 },
226 }
227
228 runtime_api
229 .authorities(parent_hash)
230 .ok()
231 .ok_or(ConsensusError::InvalidAuthoritiesSet)
232}
233
234pub fn fetch_authorities<A, B, C>(
236 client: &C,
237 parent_hash: B::Hash,
238) -> Result<Vec<A>, ConsensusError>
239where
240 A: Codec + Debug,
241 B: BlockT,
242 C: ProvideRuntimeApi<B>,
243 C::Api: AuraApi<B, A>,
244{
245 client
246 .runtime_api()
247 .authorities(parent_hash)
248 .ok()
249 .ok_or(ConsensusError::InvalidAuthoritiesSet)
250}
251
252#[derive(Debug, thiserror::Error)]
254pub enum SealVerificationError<Header> {
255 #[error("Header slot is in the future")]
257 Deferred(Header, Slot),
258
259 #[error("Header is unsealed.")]
261 Unsealed,
262
263 #[error("Header has a malformed seal")]
265 BadSeal,
266
267 #[error("Header has a bad signature")]
269 BadSignature,
270
271 #[error("No slot author for provided slot")]
273 SlotAuthorNotFound,
274
275 #[error("Header has no valid slot pre-digest")]
277 InvalidPreDigest(PreDigestLookupError),
278}
279
280pub fn check_header_slot_and_seal<B: BlockT, P: Pair>(
289 slot_now: Slot,
290 mut header: B::Header,
291 authorities: &[AuthorityId<P>],
292) -> Result<(B::Header, Slot, DigestItem), SealVerificationError<B::Header>>
293where
294 P::Signature: Codec,
295 P::Public: Codec + PartialEq + Clone,
296{
297 let seal = header.digest_mut().pop().ok_or(SealVerificationError::Unsealed)?;
298
299 let sig = seal.as_aura_seal().ok_or(SealVerificationError::BadSeal)?;
300
301 let slot = find_pre_digest::<B, P::Signature>(&header)
302 .map_err(SealVerificationError::InvalidPreDigest)?;
303
304 if slot > slot_now {
305 header.digest_mut().push(seal);
306 return Err(SealVerificationError::Deferred(header, slot))
307 } else {
308 let expected_author =
311 slot_author::<P>(slot, authorities).ok_or(SealVerificationError::SlotAuthorNotFound)?;
312
313 let pre_hash = header.hash();
314
315 if P::verify(&sig, pre_hash.as_ref(), expected_author) {
316 Ok((header, slot, seal))
317 } else {
318 Err(SealVerificationError::BadSignature)
319 }
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use sp_keyring::sr25519::Keyring;
327
328 #[test]
329 fn authorities_call_works() {
330 let client = substrate_test_runtime_client::new();
331
332 assert_eq!(client.chain_info().best_number, 0);
333 assert_eq!(
334 fetch_authorities_with_compatibility_mode(
335 &client,
336 client.chain_info().best_hash,
337 1,
338 &CompatibilityMode::None
339 )
340 .unwrap(),
341 vec![
342 Keyring::Alice.public().into(),
343 Keyring::Bob.public().into(),
344 Keyring::Charlie.public().into()
345 ]
346 );
347
348 assert_eq!(
349 fetch_authorities(&client, client.chain_info().best_hash).unwrap(),
350 vec![
351 Keyring::Alice.public().into(),
352 Keyring::Bob.public().into(),
353 Keyring::Charlie.public().into()
354 ]
355 );
356 }
357}