1use std::fmt::Debug;
22
23use codec::Codec;
24
25use sc_client_api::UsageProvider;
26use sp_api::{ApiExt, Core, ProvideRuntimeApi};
27use sp_application_crypto::{AppCrypto, AppPublic};
28use sp_blockchain::Result as CResult;
29use sp_consensus::Error as ConsensusError;
30use sp_consensus_aura::digests::SealLookupError;
31use sp_consensus_slots::Slot;
32use sp_core::crypto::{ByteArray, Pair};
33use sp_keystore::KeystorePtr;
34use sp_runtime::{
35 traits::{Block as BlockT, Header, NumberFor, Zero},
36 DigestItem,
37};
38
39pub use sc_consensus_slots::check_equivocation;
40pub use sp_consensus_aura::digests::PreDigestLookupError;
41
42use super::{AuraApi, AuthorityId, CompatibilityMode, CompatibleDigestItem, SlotDuration};
43
44pub fn slot_duration<A, B, C>(client: &C) -> CResult<SlotDuration>
46where
47 A: Codec,
48 B: BlockT,
49 C: ProvideRuntimeApi<B> + UsageProvider<B>,
50 C::Api: AuraApi<B, A>,
51{
52 slot_duration_at(client, client.usage_info().chain.best_hash)
53}
54
55pub fn slot_duration_at<A, B, C>(client: &C, block_hash: B::Hash) -> CResult<SlotDuration>
57where
58 A: Codec,
59 B: BlockT,
60 C: ProvideRuntimeApi<B>,
61 C::Api: AuraApi<B, A>,
62{
63 let mut runtime_api = client.runtime_api();
64 runtime_api.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
65 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
154pub fn find_pre_digest<B: BlockT>(header: &B::Header) -> Result<Slot, PreDigestLookupError> {
160 if header.number().is_zero() {
161 return Ok(0.into());
162 }
163
164 sp_consensus_aura::digests::find_pre_digest(header.digest())
165}
166
167pub fn fetch_authorities_with_compatibility_mode<A, B, C>(
173 client: &C,
174 parent_hash: B::Hash,
175 context_block_number: NumberFor<B>,
176 compatibility_mode: &CompatibilityMode<NumberFor<B>>,
177) -> Result<Vec<A>, ConsensusError>
178where
179 A: Codec + Debug,
180 B: BlockT,
181 C: ProvideRuntimeApi<B>,
182 C::Api: AuraApi<B, A>,
183{
184 let mut runtime_api = client.runtime_api();
185
186 match compatibility_mode {
187 CompatibilityMode::None => {},
188 CompatibilityMode::UseInitializeBlock { until } => {
190 if *until > context_block_number {
191 runtime_api
192 .initialize_block(
193 parent_hash,
194 &B::Header::new(
195 context_block_number,
196 Default::default(),
197 Default::default(),
198 parent_hash,
199 Default::default(),
200 ),
201 )
202 .map_err(|_| ConsensusError::InvalidAuthoritiesSet)?;
203 }
204 },
205 }
206
207 runtime_api.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
208 runtime_api
209 .authorities(parent_hash)
210 .ok()
211 .ok_or(ConsensusError::InvalidAuthoritiesSet)
212}
213
214pub fn fetch_authorities<A, B, C>(
216 client: &C,
217 parent_hash: B::Hash,
218) -> Result<Vec<A>, ConsensusError>
219where
220 A: Codec + Debug,
221 B: BlockT,
222 C: ProvideRuntimeApi<B>,
223 C::Api: AuraApi<B, A>,
224{
225 let mut runtime_api = client.runtime_api();
226 runtime_api.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
227 runtime_api
228 .authorities(parent_hash)
229 .ok()
230 .ok_or(ConsensusError::InvalidAuthoritiesSet)
231}
232
233#[derive(Debug, thiserror::Error)]
235pub enum SealVerificationError<Header> {
236 #[error("Header slot is in the future")]
238 Deferred(Header, Slot),
239
240 #[error("Header is unsealed.")]
242 Unsealed,
243
244 #[error("Header has a malformed seal")]
246 BadSeal,
247
248 #[error("Header has a bad signature")]
250 BadSignature,
251
252 #[error("No slot author for provided slot")]
254 SlotAuthorNotFound,
255
256 #[error("Header has no valid slot pre-digest")]
258 InvalidPreDigest(PreDigestLookupError),
259}
260
261pub fn check_header_slot_and_seal<B: BlockT, P: Pair>(
270 slot_now: Slot,
271 mut header: B::Header,
272 authorities: &[AuthorityId<P>],
273) -> Result<(B::Header, Slot, DigestItem), SealVerificationError<B::Header>>
274where
275 P::Signature: Codec,
276 P::Public: Codec + PartialEq + Clone,
277{
278 let (seal, sig) = sp_consensus_aura::digests::take_seal::<P::Signature>(header.digest_mut())
279 .map_err(|e| match e {
280 SealLookupError::Unsealed => SealVerificationError::Unsealed,
281 SealLookupError::BadSeal | SealLookupError::MultipleSeals => {
282 SealVerificationError::BadSeal
283 },
284 })?;
285
286 let slot = find_pre_digest::<B>(&header).map_err(SealVerificationError::InvalidPreDigest)?;
287
288 if slot > slot_now {
289 header.digest_mut().push(seal);
290 return Err(SealVerificationError::Deferred(header, slot));
291 } else {
292 let expected_author =
295 slot_author::<P>(slot, authorities).ok_or(SealVerificationError::SlotAuthorNotFound)?;
296
297 let pre_hash = header.hash();
298
299 if P::verify(&sig, pre_hash.as_ref(), expected_author) {
300 Ok((header, slot, seal))
301 } else {
302 Err(SealVerificationError::BadSignature)
303 }
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use sp_keyring::sr25519::Keyring;
311
312 #[test]
313 fn authorities_call_works() {
314 let client = substrate_test_runtime_client::new();
315
316 assert_eq!(client.chain_info().best_number, 0);
317 assert_eq!(
318 fetch_authorities_with_compatibility_mode(
319 &client,
320 client.chain_info().best_hash,
321 1,
322 &CompatibilityMode::None
323 )
324 .unwrap(),
325 vec![
326 Keyring::Alice.public().into(),
327 Keyring::Bob.public().into(),
328 Keyring::Charlie.public().into()
329 ]
330 );
331
332 assert_eq!(
333 fetch_authorities(&client, client.chain_info().best_hash).unwrap(),
334 vec![
335 Keyring::Alice.public().into(),
336 Keyring::Bob.public().into(),
337 Keyring::Charlie.public().into()
338 ]
339 );
340 }
341}