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 log::trace;
24
25use codec::Codec;
26
27use sc_client_api::UsageProvider;
28use sp_api::{ApiExt, 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
46/// Get the slot duration for Aura by reading from a runtime API at the best block's state.
47pub 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
57/// Get the slot duration for Aura by reading from a runtime API at a given block's state.
58pub 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	let mut runtime_api = client.runtime_api();
66	runtime_api.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
67	runtime_api.slot_duration(block_hash).map_err(|err| err.into())
68}
69
70/// Get the slot author for given block along with authorities.
71pub fn slot_author<P: Pair>(slot: Slot, authorities: &[AuthorityId<P>]) -> Option<&AuthorityId<P>> {
72	if authorities.is_empty() {
73		return None;
74	}
75
76	let idx = *slot % (authorities.len() as u64);
77	assert!(
78		idx <= usize::MAX as u64,
79		"It is impossible to have a vector with length beyond the address space; qed",
80	);
81
82	let current_author = authorities.get(idx as usize).expect(
83		"authorities not empty; index constrained to list length;this is a valid index; qed",
84	);
85
86	Some(current_author)
87}
88
89/// Attempt to claim a slot using a keystore.
90///
91/// This returns `None` if the slot author is not locally controlled, and `Some` if it is,
92/// with the public key of the slot author.
93pub async fn claim_slot<P: Pair>(
94	slot: Slot,
95	authorities: &[AuthorityId<P>],
96	keystore: &KeystorePtr,
97) -> Option<P::Public> {
98	let expected_author = slot_author::<P>(slot, authorities);
99	expected_author.and_then(|p| {
100		if keystore.has_keys(&[(p.to_raw_vec(), sp_application_crypto::key_types::AURA)]) {
101			Some(p.clone())
102		} else {
103			None
104		}
105	})
106}
107
108/// Produce the pre-runtime digest containing the slot info.
109///
110/// This is intended to be put into the block header prior to runtime execution,
111/// so the runtime can read the slot in this way.
112pub fn pre_digest<P: Pair>(slot: Slot) -> sp_runtime::DigestItem
113where
114	P::Signature: Codec,
115{
116	<DigestItem as CompatibleDigestItem<P::Signature>>::aura_pre_digest(slot)
117}
118
119/// Produce the seal digest item by signing the hash of a block.
120///
121/// Note that after this is added to a block header, the hash of the block will change.
122pub fn seal<Hash, P>(
123	header_hash: &Hash,
124	public: &P::Public,
125	keystore: &KeystorePtr,
126) -> Result<sp_runtime::DigestItem, ConsensusError>
127where
128	Hash: AsRef<[u8]>,
129	P: Pair,
130	P::Signature: Codec + TryFrom<Vec<u8>>,
131	P::Public: AppPublic,
132{
133	let signature = keystore
134		.sign_with(
135			<AuthorityId<P> as AppCrypto>::ID,
136			<AuthorityId<P> as AppCrypto>::CRYPTO_ID,
137			public.as_slice(),
138			header_hash.as_ref(),
139		)
140		.map_err(|e| ConsensusError::CannotSign(format!("{}. Key: {:?}", e, public)))?
141		.ok_or_else(|| {
142			ConsensusError::CannotSign(format!("Could not find key in keystore. Key: {:?}", public))
143		})?;
144
145	let signature = signature
146		.as_slice()
147		.try_into()
148		.map_err(|_| ConsensusError::InvalidSignature(signature, public.to_raw_vec()))?;
149
150	let signature_digest_item =
151		<DigestItem as CompatibleDigestItem<P::Signature>>::aura_seal(signature);
152
153	Ok(signature_digest_item)
154}
155
156/// Errors in pre-digest lookup.
157#[derive(Debug, thiserror::Error)]
158pub enum PreDigestLookupError {
159	/// Multiple Aura pre-runtime headers
160	#[error("Multiple Aura pre-runtime headers")]
161	MultipleHeaders,
162	/// No Aura pre-runtime digest found
163	#[error("No Aura pre-runtime digest found")]
164	NoDigestFound,
165}
166
167/// Extract a pre-digest from a block header.
168///
169/// This fails if there is no pre-digest or there are multiple.
170///
171/// Returns the `slot` stored in the pre-digest or an error if no pre-digest was found.
172pub fn find_pre_digest<B: BlockT, Signature: Codec>(
173	header: &B::Header,
174) -> Result<Slot, PreDigestLookupError> {
175	if header.number().is_zero() {
176		return Ok(0.into());
177	}
178
179	let mut pre_digest: Option<Slot> = None;
180	for log in header.digest().logs() {
181		trace!(target: LOG_TARGET, "Checking log {:?}", log);
182		match (CompatibleDigestItem::<Signature>::as_aura_pre_digest(log), pre_digest.is_some()) {
183			(Some(_), true) => return Err(PreDigestLookupError::MultipleHeaders),
184			(None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"),
185			(s, false) => pre_digest = s,
186		}
187	}
188	pre_digest.ok_or_else(|| PreDigestLookupError::NoDigestFound)
189}
190
191/// Fetch the current set of authorities from the runtime at a specific block.
192///
193/// The compatibility mode and context block number informs this function whether
194/// to initialize the hypothetical block created by the runtime API as backwards compatibility
195/// for older chains.
196pub fn fetch_authorities_with_compatibility_mode<A, B, C>(
197	client: &C,
198	parent_hash: B::Hash,
199	context_block_number: NumberFor<B>,
200	compatibility_mode: &CompatibilityMode<NumberFor<B>>,
201) -> Result<Vec<A>, ConsensusError>
202where
203	A: Codec + Debug,
204	B: BlockT,
205	C: ProvideRuntimeApi<B>,
206	C::Api: AuraApi<B, A>,
207{
208	let mut runtime_api = client.runtime_api();
209
210	match compatibility_mode {
211		CompatibilityMode::None => {},
212		// Use `initialize_block` until we hit the block that should disable the mode.
213		CompatibilityMode::UseInitializeBlock { until } => {
214			if *until > context_block_number {
215				runtime_api
216					.initialize_block(
217						parent_hash,
218						&B::Header::new(
219							context_block_number,
220							Default::default(),
221							Default::default(),
222							parent_hash,
223							Default::default(),
224						),
225					)
226					.map_err(|_| ConsensusError::InvalidAuthoritiesSet)?;
227			}
228		},
229	}
230
231	runtime_api.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
232	runtime_api
233		.authorities(parent_hash)
234		.ok()
235		.ok_or(ConsensusError::InvalidAuthoritiesSet)
236}
237
238/// Load the current set of authorities from a runtime at a specific block.
239pub fn fetch_authorities<A, B, C>(
240	client: &C,
241	parent_hash: B::Hash,
242) -> Result<Vec<A>, ConsensusError>
243where
244	A: Codec + Debug,
245	B: BlockT,
246	C: ProvideRuntimeApi<B>,
247	C::Api: AuraApi<B, A>,
248{
249	let mut runtime_api = client.runtime_api();
250	runtime_api.set_call_context(sp_core::traits::CallContext::Onchain { import: false });
251	runtime_api
252		.authorities(parent_hash)
253		.ok()
254		.ok_or(ConsensusError::InvalidAuthoritiesSet)
255}
256
257/// Errors in slot and seal verification.
258#[derive(Debug, thiserror::Error)]
259pub enum SealVerificationError<Header> {
260	/// Header is deferred to the future.
261	#[error("Header slot is in the future")]
262	Deferred(Header, Slot),
263
264	/// The header has no seal digest.
265	#[error("Header is unsealed.")]
266	Unsealed,
267
268	/// The header has a malformed seal.
269	#[error("Header has a malformed seal")]
270	BadSeal,
271
272	/// The header has a bad signature.
273	#[error("Header has a bad signature")]
274	BadSignature,
275
276	/// No slot author found.
277	#[error("No slot author for provided slot")]
278	SlotAuthorNotFound,
279
280	/// Header has no valid slot pre-digest.
281	#[error("Header has no valid slot pre-digest")]
282	InvalidPreDigest(PreDigestLookupError),
283}
284
285/// Check a header has been signed by the right key. If the slot is too far in the future, an error
286/// will be returned. If it's successful, returns the pre-header (i.e. without the seal),
287/// the slot, and the digest item containing the seal.
288///
289/// Note that this does not check for equivocations, and [`check_equivocation`] is recommended
290/// for that purpose.
291///
292/// This digest item will always return `Some` when used with `as_aura_seal`.
293pub fn check_header_slot_and_seal<B: BlockT, P: Pair>(
294	slot_now: Slot,
295	mut header: B::Header,
296	authorities: &[AuthorityId<P>],
297) -> Result<(B::Header, Slot, DigestItem), SealVerificationError<B::Header>>
298where
299	P::Signature: Codec,
300	P::Public: Codec + PartialEq + Clone,
301{
302	let seal = header.digest_mut().pop().ok_or(SealVerificationError::Unsealed)?;
303
304	let sig = seal.as_aura_seal().ok_or(SealVerificationError::BadSeal)?;
305
306	let slot = find_pre_digest::<B, P::Signature>(&header)
307		.map_err(SealVerificationError::InvalidPreDigest)?;
308
309	if slot > slot_now {
310		header.digest_mut().push(seal);
311		return Err(SealVerificationError::Deferred(header, slot));
312	} else {
313		// check the signature is valid under the expected authority and
314		// chain state.
315		let expected_author =
316			slot_author::<P>(slot, authorities).ok_or(SealVerificationError::SlotAuthorNotFound)?;
317
318		let pre_hash = header.hash();
319
320		if P::verify(&sig, pre_hash.as_ref(), expected_author) {
321			Ok((header, slot, seal))
322		} else {
323			Err(SealVerificationError::BadSignature)
324		}
325	}
326}
327
328#[cfg(test)]
329mod tests {
330	use super::*;
331	use sp_keyring::sr25519::Keyring;
332
333	#[test]
334	fn authorities_call_works() {
335		let client = substrate_test_runtime_client::new();
336
337		assert_eq!(client.chain_info().best_number, 0);
338		assert_eq!(
339			fetch_authorities_with_compatibility_mode(
340				&client,
341				client.chain_info().best_hash,
342				1,
343				&CompatibilityMode::None
344			)
345			.unwrap(),
346			vec![
347				Keyring::Alice.public().into(),
348				Keyring::Bob.public().into(),
349				Keyring::Charlie.public().into()
350			]
351		);
352
353		assert_eq!(
354			fetch_authorities(&client, client.chain_info().best_hash).unwrap(),
355			vec![
356				Keyring::Alice.public().into(),
357				Keyring::Bob.public().into(),
358				Keyring::Charlie.public().into()
359			]
360		);
361	}
362}