referrerpolicy=no-referrer-when-downgrade

sc_consensus_aura/
standalone.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Standalone functions used within the implementation of Aura.
20
21use 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
44/// Get the slot duration for Aura by reading from a runtime API at the best block's state.
45pub 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
55/// Get the slot duration for Aura by reading from a runtime API at a given block's state.
56pub 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
68/// Get the slot author for given block along with authorities.
69pub 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
87/// Attempt to claim a slot using a keystore.
88///
89/// This returns `None` if the slot author is not locally controlled, and `Some` if it is,
90/// with the public key of the slot author.
91pub 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
106/// Produce the pre-runtime digest containing the slot info.
107///
108/// This is intended to be put into the block header prior to runtime execution,
109/// so the runtime can read the slot in this way.
110pub 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
117/// Produce the seal digest item by signing the hash of a block.
118///
119/// Note that after this is added to a block header, the hash of the block will change.
120pub 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/// Extract a pre-digest from a block header.
155///
156/// This fails if there is no pre-digest or there are multiple.
157///
158/// Returns the `slot` stored in the pre-digest or an error if no pre-digest was found.
159pub 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
167/// Fetch the current set of authorities from the runtime at a specific block.
168///
169/// The compatibility mode and context block number informs this function whether
170/// to initialize the hypothetical block created by the runtime API as backwards compatibility
171/// for older chains.
172pub 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		// Use `initialize_block` until we hit the block that should disable the mode.
189		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
214/// Load the current set of authorities from a runtime at a specific block.
215pub 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/// Errors in slot and seal verification.
234#[derive(Debug, thiserror::Error)]
235pub enum SealVerificationError<Header> {
236	/// Header is deferred to the future.
237	#[error("Header slot is in the future")]
238	Deferred(Header, Slot),
239
240	/// The header has no seal digest.
241	#[error("Header is unsealed.")]
242	Unsealed,
243
244	/// The header has a malformed seal.
245	#[error("Header has a malformed seal")]
246	BadSeal,
247
248	/// The header has a bad signature.
249	#[error("Header has a bad signature")]
250	BadSignature,
251
252	/// No slot author found.
253	#[error("No slot author for provided slot")]
254	SlotAuthorNotFound,
255
256	/// Header has no valid slot pre-digest.
257	#[error("Header has no valid slot pre-digest")]
258	InvalidPreDigest(PreDigestLookupError),
259}
260
261/// Check a header has been signed by the right key. If the slot is too far in the future, an error
262/// will be returned. If it's successful, returns the pre-header (i.e. without the seal),
263/// the slot, and the digest item containing the seal.
264///
265/// Note that this does not check for equivocations, and [`check_equivocation`] is recommended
266/// for that purpose.
267///
268/// This digest item will always return `Some` when used with `as_aura_seal`.
269pub 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		// check the signature is valid under the expected authority and
293		// chain state.
294		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}