bp_header_chain/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Defines traits which represent a common interface for Substrate pallets which want to
18//! incorporate bridge functionality.
19
20#![warn(missing_docs)]
21#![cfg_attr(not(feature = "std"), no_std)]
22
23use crate::justification::{
24	GrandpaJustification, JustificationVerificationContext, JustificationVerificationError,
25};
26use bp_runtime::{
27	BasicOperatingMode, BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf, RawStorageProof,
28	StorageProofChecker, StorageProofError, UnderlyingChainProvider,
29};
30use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen};
31use core::{clone::Clone, cmp::Eq, default::Default, fmt::Debug};
32use frame_support::PalletError;
33use scale_info::TypeInfo;
34use serde::{Deserialize, Serialize};
35use sp_consensus_grandpa::{
36	AuthorityList, ConsensusLog, ScheduledChange, SetId, GRANDPA_ENGINE_ID,
37};
38use sp_runtime::{traits::Header as HeaderT, Digest, RuntimeDebug, SaturatedConversion};
39use sp_std::{boxed::Box, vec::Vec};
40
41pub use call_info::{BridgeGrandpaCall, BridgeGrandpaCallOf, SubmitFinalityProofInfo};
42
43mod call_info;
44
45pub mod justification;
46pub mod storage_keys;
47
48/// Header chain error.
49#[derive(Clone, Decode, Encode, Eq, PartialEq, PalletError, Debug, TypeInfo)]
50pub enum HeaderChainError {
51	/// Header with given hash is missing from the chain.
52	UnknownHeader,
53	/// Error generated by the `storage_proof` module.
54	StorageProof(StorageProofError),
55}
56
57/// Header data that we're storing on-chain.
58///
59/// Even though we may store full header, our applications (XCM) only use couple of header
60/// fields. Extracting those values makes on-chain storage and PoV smaller, which is good.
61#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
62pub struct StoredHeaderData<Number, Hash> {
63	/// Header number.
64	pub number: Number,
65	/// Header state root.
66	pub state_root: Hash,
67}
68
69/// Stored header data builder.
70pub trait StoredHeaderDataBuilder<Number, Hash> {
71	/// Build header data from self.
72	fn build(&self) -> StoredHeaderData<Number, Hash>;
73}
74
75impl<H: HeaderT> StoredHeaderDataBuilder<H::Number, H::Hash> for H {
76	fn build(&self) -> StoredHeaderData<H::Number, H::Hash> {
77		StoredHeaderData { number: *self.number(), state_root: *self.state_root() }
78	}
79}
80
81/// Substrate header chain, abstracted from the way it is stored.
82pub trait HeaderChain<C: Chain> {
83	/// Returns state (storage) root of given finalized header.
84	fn finalized_header_state_root(header_hash: HashOf<C>) -> Option<HashOf<C>>;
85
86	/// Get storage proof checker using finalized header.
87	fn verify_storage_proof(
88		header_hash: HashOf<C>,
89		storage_proof: RawStorageProof,
90	) -> Result<StorageProofChecker<HasherOf<C>>, HeaderChainError> {
91		let state_root = Self::finalized_header_state_root(header_hash)
92			.ok_or(HeaderChainError::UnknownHeader)?;
93		StorageProofChecker::new(state_root, storage_proof).map_err(HeaderChainError::StorageProof)
94	}
95}
96
97/// A type that can be used as a parameter in a dispatchable function.
98///
99/// When using `decl_module` all arguments for call functions must implement this trait.
100pub trait Parameter: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
101impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
102
103/// A GRANDPA Authority List and ID.
104#[derive(Default, Encode, Eq, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo)]
105#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
106pub struct AuthoritySet {
107	/// List of GRANDPA authorities for the current round.
108	pub authorities: AuthorityList,
109	/// Monotonic identifier of the current GRANDPA authority set.
110	pub set_id: SetId,
111}
112
113impl AuthoritySet {
114	/// Create a new GRANDPA Authority Set.
115	pub fn new(authorities: AuthorityList, set_id: SetId) -> Self {
116		Self { authorities, set_id }
117	}
118}
119
120/// Data required for initializing the GRANDPA bridge pallet.
121///
122/// The bridge needs to know where to start its sync from, and this provides that initial context.
123#[derive(
124	Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, Clone, TypeInfo, Serialize, Deserialize,
125)]
126pub struct InitializationData<H: HeaderT> {
127	/// The header from which we should start syncing.
128	pub header: Box<H>,
129	/// The initial authorities of the pallet.
130	pub authority_list: AuthorityList,
131	/// The ID of the initial authority set.
132	pub set_id: SetId,
133	/// Pallet operating mode.
134	pub operating_mode: BasicOperatingMode,
135}
136
137/// Abstract finality proof that is justifying block finality.
138pub trait FinalityProof<Hash, Number>: Clone + Send + Sync + Debug {
139	/// Return hash of header that this proof is generated for.
140	fn target_header_hash(&self) -> Hash;
141
142	/// Return number of header that this proof is generated for.
143	fn target_header_number(&self) -> Number;
144}
145
146/// A trait that provides helper methods for querying the consensus log.
147pub trait ConsensusLogReader {
148	/// Returns true if digest contains item that schedules authorities set change.
149	fn schedules_authorities_change(digest: &Digest) -> bool;
150}
151
152/// A struct that provides helper methods for querying the GRANDPA consensus log.
153pub struct GrandpaConsensusLogReader<Number>(sp_std::marker::PhantomData<Number>);
154
155impl<Number: Codec> GrandpaConsensusLogReader<Number> {
156	/// Find and return scheduled (regular) change digest item.
157	pub fn find_scheduled_change(digest: &Digest) -> Option<ScheduledChange<Number>> {
158		use sp_runtime::generic::OpaqueDigestItemId;
159		let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
160
161		let filter_log = |log: ConsensusLog<Number>| match log {
162			ConsensusLog::ScheduledChange(change) => Some(change),
163			_ => None,
164		};
165
166		// find the first consensus digest with the right ID which converts to
167		// the right kind of consensus log.
168		digest.convert_first(|l| l.try_to(id).and_then(filter_log))
169	}
170
171	/// Find and return forced change digest item. Or light client can't do anything
172	/// with forced changes, so we can't accept header with the forced change digest.
173	pub fn find_forced_change(digest: &Digest) -> Option<(Number, ScheduledChange<Number>)> {
174		// find the first consensus digest with the right ID which converts to
175		// the right kind of consensus log.
176		digest
177			.convert_first(|log| log.consensus_try_to(&GRANDPA_ENGINE_ID))
178			.and_then(|log| match log {
179				ConsensusLog::ForcedChange(delay, change) => Some((delay, change)),
180				_ => None,
181			})
182	}
183}
184
185impl<Number: Codec> ConsensusLogReader for GrandpaConsensusLogReader<Number> {
186	fn schedules_authorities_change(digest: &Digest) -> bool {
187		GrandpaConsensusLogReader::<Number>::find_scheduled_change(digest).is_some()
188	}
189}
190
191/// The finality-related info associated to a header.
192#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)]
193pub struct HeaderFinalityInfo<FinalityProof, FinalityVerificationContext> {
194	/// The header finality proof.
195	pub finality_proof: FinalityProof,
196	/// The new verification context introduced by the header.
197	pub new_verification_context: Option<FinalityVerificationContext>,
198}
199
200/// Grandpa-related info associated to a header. This info can be saved to events.
201pub type StoredHeaderGrandpaInfo<Header> =
202	HeaderFinalityInfo<GrandpaJustification<Header>, AuthoritySet>;
203
204/// Processed Grandpa-related info associated to a header.
205pub type HeaderGrandpaInfo<Header> =
206	HeaderFinalityInfo<GrandpaJustification<Header>, JustificationVerificationContext>;
207
208impl<Header: HeaderT> TryFrom<StoredHeaderGrandpaInfo<Header>> for HeaderGrandpaInfo<Header> {
209	type Error = JustificationVerificationError;
210
211	fn try_from(grandpa_info: StoredHeaderGrandpaInfo<Header>) -> Result<Self, Self::Error> {
212		Ok(Self {
213			finality_proof: grandpa_info.finality_proof,
214			new_verification_context: match grandpa_info.new_verification_context {
215				Some(authority_set) => Some(authority_set.try_into()?),
216				None => None,
217			},
218		})
219	}
220}
221
222/// Helper trait for finding equivocations in finality proofs.
223pub trait FindEquivocations<FinalityProof, FinalityVerificationContext, EquivocationProof> {
224	/// The type returned when encountering an error while looking for equivocations.
225	type Error: Debug;
226
227	/// Find equivocations.
228	fn find_equivocations(
229		verification_context: &FinalityVerificationContext,
230		synced_proof: &FinalityProof,
231		source_proofs: &[FinalityProof],
232	) -> Result<Vec<EquivocationProof>, Self::Error>;
233}
234
235/// Substrate-based chain that is using direct GRANDPA finality.
236///
237/// Keep in mind that parachains are relying on relay chain GRANDPA, so they should not implement
238/// this trait.
239pub trait ChainWithGrandpa: Chain {
240	/// Name of the bridge GRANDPA pallet (used in `construct_runtime` macro call) that is deployed
241	/// at some other chain to bridge with this `ChainWithGrandpa`.
242	///
243	/// We assume that all chains that are bridging with this `ChainWithGrandpa` are using
244	/// the same name.
245	const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str;
246
247	/// Max number of GRANDPA authorities at the chain.
248	///
249	/// This is a strict constant. If bridged chain will have more authorities than that,
250	/// the GRANDPA bridge pallet may halt.
251	const MAX_AUTHORITIES_COUNT: u32;
252
253	/// Max reasonable number of headers in `votes_ancestries` vector of the GRANDPA justification.
254	///
255	/// This isn't a strict limit. The relay may submit justifications with more headers in its
256	/// ancestry and the pallet will accept such justification. The limit is only used to compute
257	/// maximal refund amount and submitting justifications which exceed the limit, may be costly
258	/// to submitter.
259	const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32;
260
261	/// Maximal size of the mandatory chain header. Mandatory header is the header that enacts new
262	/// GRANDPA authorities set (so it has large digest inside).
263	///
264	/// This isn't a strict limit. The relay may submit larger headers and the pallet will accept
265	/// the call. The limit is only used to compute maximal refund amount and doing calls which
266	/// exceed the limit, may be costly to submitter.
267	const MAX_MANDATORY_HEADER_SIZE: u32;
268
269	/// Average size of the chain header. We don't expect to see there headers that change GRANDPA
270	/// authorities set (GRANDPA will probably be able to finalize at least one additional header
271	/// per session on non test chains), so this is average size of headers that aren't changing the
272	/// set.
273	///
274	/// This isn't a strict limit. The relay may submit justifications with larger headers and the
275	/// pallet will accept the call. However, if the total size of all `submit_finality_proof`
276	/// arguments exceeds the maximal size, computed using this average size, relayer will only get
277	/// partial refund.
278	///
279	/// We expect some headers on production chains that are above this size. But they are rare and
280	/// if rellayer cares about its profitability, we expect it'll select other headers for
281	/// submission.
282	const AVERAGE_HEADER_SIZE: u32;
283}
284
285impl<T> ChainWithGrandpa for T
286where
287	T: Chain + UnderlyingChainProvider,
288	T::Chain: ChainWithGrandpa,
289{
290	const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str =
291		<T::Chain as ChainWithGrandpa>::WITH_CHAIN_GRANDPA_PALLET_NAME;
292	const MAX_AUTHORITIES_COUNT: u32 = <T::Chain as ChainWithGrandpa>::MAX_AUTHORITIES_COUNT;
293	const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 =
294		<T::Chain as ChainWithGrandpa>::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
295	const MAX_MANDATORY_HEADER_SIZE: u32 =
296		<T::Chain as ChainWithGrandpa>::MAX_MANDATORY_HEADER_SIZE;
297	const AVERAGE_HEADER_SIZE: u32 = <T::Chain as ChainWithGrandpa>::AVERAGE_HEADER_SIZE;
298}
299
300/// Result of checking maximal expected submit finality proof call weight and size.
301#[derive(Debug)]
302pub struct SubmitFinalityProofCallExtras {
303	/// If true, the call weight is larger than what we have assumed.
304	///
305	/// We have some assumptions about headers and justifications of the bridged chain.
306	/// We know that if our assumptions are correct, then the call must not have the
307	/// weight above some limit. The fee paid for weight above that limit, is never refunded.
308	pub is_weight_limit_exceeded: bool,
309	/// Extra size (in bytes) that we assume are included in the call.
310	///
311	/// We have some assumptions about headers and justifications of the bridged chain.
312	/// We know that if our assumptions are correct, then the call must not have the
313	/// weight above some limit. The fee paid for bytes above that limit, is never refunded.
314	pub extra_size: u32,
315	/// A flag that is true if the header is the mandatory header that enacts new
316	/// authorities set.
317	pub is_mandatory_finality_target: bool,
318}
319
320/// Checks whether the given `header` and its finality `proof` fit the maximal expected
321/// call limits (size and weight). The submission may be refunded sometimes (see pallet
322/// configuration for details), but it should fit some limits. If the call has some extra
323/// weight and/or size included, though, we won't refund it or refund will be partial.
324pub fn submit_finality_proof_limits_extras<C: ChainWithGrandpa>(
325	header: &C::Header,
326	proof: &justification::GrandpaJustification<C::Header>,
327) -> SubmitFinalityProofCallExtras {
328	// the `submit_finality_proof` call will reject justifications with invalid, duplicate,
329	// unknown and extra signatures. It'll also reject justifications with less than necessary
330	// signatures. So we do not care about extra weight because of additional signatures here.
331	let precommits_len = proof.commit.precommits.len().saturated_into();
332	let required_precommits = precommits_len;
333
334	// the weight check is simple - we assume that there are no more than the `limit`
335	// headers in the ancestry proof
336	let votes_ancestries_len: u32 = proof.votes_ancestries.len().saturated_into();
337	let is_weight_limit_exceeded =
338		votes_ancestries_len > C::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
339
340	// check if the `finality_target` is a mandatory header. If so, we are ready to refund larger
341	// size
342	let is_mandatory_finality_target =
343		GrandpaConsensusLogReader::<BlockNumberOf<C>>::find_scheduled_change(header.digest())
344			.is_some();
345
346	// we can estimate extra call size easily, without any additional significant overhead
347	let actual_call_size: u32 =
348		header.encoded_size().saturating_add(proof.encoded_size()).saturated_into();
349	let max_expected_call_size = max_expected_submit_finality_proof_arguments_size::<C>(
350		is_mandatory_finality_target,
351		required_precommits,
352	);
353	let extra_size = actual_call_size.saturating_sub(max_expected_call_size);
354
355	SubmitFinalityProofCallExtras {
356		is_weight_limit_exceeded,
357		extra_size,
358		is_mandatory_finality_target,
359	}
360}
361
362/// Returns maximal expected size of `submit_finality_proof` call arguments.
363pub fn max_expected_submit_finality_proof_arguments_size<C: ChainWithGrandpa>(
364	is_mandatory_finality_target: bool,
365	precommits: u32,
366) -> u32 {
367	let max_expected_justification_size =
368		GrandpaJustification::<HeaderOf<C>>::max_reasonable_size::<C>(precommits);
369
370	// call arguments are header and justification
371	let max_expected_finality_target_size = if is_mandatory_finality_target {
372		C::MAX_MANDATORY_HEADER_SIZE
373	} else {
374		C::AVERAGE_HEADER_SIZE
375	};
376	max_expected_finality_target_size.saturating_add(max_expected_justification_size)
377}
378
379#[cfg(test)]
380mod tests {
381	use super::*;
382	use bp_runtime::ChainId;
383	use frame_support::weights::Weight;
384	use sp_runtime::{
385		testing::H256, traits::BlakeTwo256, DigestItem, MultiSignature, StateVersion,
386	};
387
388	struct TestChain;
389
390	impl Chain for TestChain {
391		const ID: ChainId = *b"test";
392
393		type BlockNumber = u32;
394		type Hash = H256;
395		type Hasher = BlakeTwo256;
396		type Header = sp_runtime::generic::Header<u32, BlakeTwo256>;
397		type AccountId = u64;
398		type Balance = u64;
399		type Nonce = u64;
400		type Signature = MultiSignature;
401
402		const STATE_VERSION: StateVersion = StateVersion::V1;
403
404		fn max_extrinsic_size() -> u32 {
405			0
406		}
407		fn max_extrinsic_weight() -> Weight {
408			Weight::zero()
409		}
410	}
411
412	impl ChainWithGrandpa for TestChain {
413		const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "Test";
414		const MAX_AUTHORITIES_COUNT: u32 = 128;
415		const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 2;
416		const MAX_MANDATORY_HEADER_SIZE: u32 = 100_000;
417		const AVERAGE_HEADER_SIZE: u32 = 1_024;
418	}
419
420	#[test]
421	fn max_expected_submit_finality_proof_arguments_size_respects_mandatory_argument() {
422		assert!(
423			max_expected_submit_finality_proof_arguments_size::<TestChain>(true, 100) >
424				max_expected_submit_finality_proof_arguments_size::<TestChain>(false, 100),
425		);
426	}
427
428	#[test]
429	fn find_scheduled_change_works() {
430		let scheduled_change = ScheduledChange { next_authorities: vec![], delay: 0 };
431
432		// first
433		let mut digest = Digest::default();
434		digest.push(DigestItem::Consensus(
435			GRANDPA_ENGINE_ID,
436			ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
437		));
438		assert_eq!(
439			GrandpaConsensusLogReader::find_scheduled_change(&digest),
440			Some(scheduled_change.clone())
441		);
442
443		// not first
444		let mut digest = Digest::default();
445		digest.push(DigestItem::Consensus(
446			GRANDPA_ENGINE_ID,
447			ConsensusLog::<u64>::OnDisabled(0).encode(),
448		));
449		digest.push(DigestItem::Consensus(
450			GRANDPA_ENGINE_ID,
451			ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
452		));
453		assert_eq!(
454			GrandpaConsensusLogReader::find_scheduled_change(&digest),
455			Some(scheduled_change.clone())
456		);
457	}
458}