1#![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, DecodeWithMemTracking, 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, 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#[derive(
50 Clone, Decode, DecodeWithMemTracking, Encode, Eq, PartialEq, PalletError, Debug, TypeInfo,
51)]
52pub enum HeaderChainError {
53 UnknownHeader,
55 StorageProof(StorageProofError),
57}
58
59#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, Debug, TypeInfo)]
64pub struct StoredHeaderData<Number, Hash> {
65 pub number: Number,
67 pub state_root: Hash,
69}
70
71pub trait StoredHeaderDataBuilder<Number, Hash> {
73 fn build(&self) -> StoredHeaderData<Number, Hash>;
75}
76
77impl<H: HeaderT> StoredHeaderDataBuilder<H::Number, H::Hash> for H {
78 fn build(&self) -> StoredHeaderData<H::Number, H::Hash> {
79 StoredHeaderData { number: *self.number(), state_root: *self.state_root() }
80 }
81}
82
83pub trait HeaderChain<C: Chain> {
85 fn finalized_header_state_root(header_hash: HashOf<C>) -> Option<HashOf<C>>;
87
88 fn verify_storage_proof(
90 header_hash: HashOf<C>,
91 storage_proof: RawStorageProof,
92 ) -> Result<StorageProofChecker<HasherOf<C>>, HeaderChainError> {
93 let state_root = Self::finalized_header_state_root(header_hash)
94 .ok_or(HeaderChainError::UnknownHeader)?;
95 StorageProofChecker::new(state_root, storage_proof).map_err(HeaderChainError::StorageProof)
96 }
97}
98
99pub trait Parameter: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
103impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
104
105#[derive(Default, Encode, Eq, Decode, DecodeWithMemTracking, Debug, PartialEq, Clone, TypeInfo)]
107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
108pub struct AuthoritySet {
109 pub authorities: AuthorityList,
111 pub set_id: SetId,
113}
114
115impl AuthoritySet {
116 pub fn new(authorities: AuthorityList, set_id: SetId) -> Self {
118 Self { authorities, set_id }
119 }
120}
121
122#[derive(
126 Default,
127 Encode,
128 Decode,
129 DecodeWithMemTracking,
130 Debug,
131 PartialEq,
132 Eq,
133 Clone,
134 TypeInfo,
135 Serialize,
136 Deserialize,
137)]
138pub struct InitializationData<H: HeaderT> {
139 pub header: Box<H>,
141 pub authority_list: AuthorityList,
143 pub set_id: SetId,
145 pub operating_mode: BasicOperatingMode,
147}
148
149pub trait FinalityProof<Hash, Number>: Clone + Send + Sync + Debug {
151 fn target_header_hash(&self) -> Hash;
153
154 fn target_header_number(&self) -> Number;
156}
157
158pub trait ConsensusLogReader {
160 fn schedules_authorities_change(digest: &Digest) -> bool;
162}
163
164pub struct GrandpaConsensusLogReader<Number>(sp_std::marker::PhantomData<Number>);
166
167impl<Number: Codec> GrandpaConsensusLogReader<Number> {
168 pub fn find_scheduled_change(digest: &Digest) -> Option<ScheduledChange<Number>> {
170 use sp_runtime::generic::OpaqueDigestItemId;
171 let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
172
173 let filter_log = |log: ConsensusLog<Number>| match log {
174 ConsensusLog::ScheduledChange(change) => Some(change),
175 _ => None,
176 };
177
178 digest.convert_first(|l| l.try_to(id).and_then(filter_log))
181 }
182
183 pub fn find_forced_change(digest: &Digest) -> Option<(Number, ScheduledChange<Number>)> {
186 digest
189 .convert_first(|log| log.consensus_try_to(&GRANDPA_ENGINE_ID))
190 .and_then(|log| match log {
191 ConsensusLog::ForcedChange(delay, change) => Some((delay, change)),
192 _ => None,
193 })
194 }
195}
196
197impl<Number: Codec> ConsensusLogReader for GrandpaConsensusLogReader<Number> {
198 fn schedules_authorities_change(digest: &Digest) -> bool {
199 GrandpaConsensusLogReader::<Number>::find_scheduled_change(digest).is_some()
200 }
201}
202
203#[derive(Encode, Decode, DecodeWithMemTracking, Debug, PartialEq, Clone, TypeInfo)]
205pub struct HeaderFinalityInfo<FinalityProof, FinalityVerificationContext> {
206 pub finality_proof: FinalityProof,
208 pub new_verification_context: Option<FinalityVerificationContext>,
210}
211
212pub type StoredHeaderGrandpaInfo<Header> =
214 HeaderFinalityInfo<GrandpaJustification<Header>, AuthoritySet>;
215
216pub type HeaderGrandpaInfo<Header> =
218 HeaderFinalityInfo<GrandpaJustification<Header>, JustificationVerificationContext>;
219
220impl<Header: HeaderT> TryFrom<StoredHeaderGrandpaInfo<Header>> for HeaderGrandpaInfo<Header> {
221 type Error = JustificationVerificationError;
222
223 fn try_from(grandpa_info: StoredHeaderGrandpaInfo<Header>) -> Result<Self, Self::Error> {
224 Ok(Self {
225 finality_proof: grandpa_info.finality_proof,
226 new_verification_context: match grandpa_info.new_verification_context {
227 Some(authority_set) => Some(authority_set.try_into()?),
228 None => None,
229 },
230 })
231 }
232}
233
234pub trait FindEquivocations<FinalityProof, FinalityVerificationContext, EquivocationProof> {
236 type Error: Debug;
238
239 fn find_equivocations(
241 verification_context: &FinalityVerificationContext,
242 synced_proof: &FinalityProof,
243 source_proofs: &[FinalityProof],
244 ) -> Result<Vec<EquivocationProof>, Self::Error>;
245}
246
247pub trait ChainWithGrandpa: Chain {
252 const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str;
258
259 const MAX_AUTHORITIES_COUNT: u32;
264
265 const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32;
272
273 const MAX_MANDATORY_HEADER_SIZE: u32;
280
281 const AVERAGE_HEADER_SIZE: u32;
295}
296
297impl<T> ChainWithGrandpa for T
298where
299 T: Chain + UnderlyingChainProvider,
300 T::Chain: ChainWithGrandpa,
301{
302 const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str =
303 <T::Chain as ChainWithGrandpa>::WITH_CHAIN_GRANDPA_PALLET_NAME;
304 const MAX_AUTHORITIES_COUNT: u32 = <T::Chain as ChainWithGrandpa>::MAX_AUTHORITIES_COUNT;
305 const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 =
306 <T::Chain as ChainWithGrandpa>::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
307 const MAX_MANDATORY_HEADER_SIZE: u32 =
308 <T::Chain as ChainWithGrandpa>::MAX_MANDATORY_HEADER_SIZE;
309 const AVERAGE_HEADER_SIZE: u32 = <T::Chain as ChainWithGrandpa>::AVERAGE_HEADER_SIZE;
310}
311
312#[derive(Debug)]
314pub struct SubmitFinalityProofCallExtras {
315 pub is_weight_limit_exceeded: bool,
321 pub extra_size: u32,
327 pub is_mandatory_finality_target: bool,
330}
331
332pub fn submit_finality_proof_limits_extras<C: ChainWithGrandpa>(
337 header: &C::Header,
338 proof: &justification::GrandpaJustification<C::Header>,
339) -> SubmitFinalityProofCallExtras {
340 let precommits_len = proof.commit.precommits.len().saturated_into();
344 let required_precommits = precommits_len;
345
346 let votes_ancestries_len: u32 = proof.votes_ancestries.len().saturated_into();
349 let is_weight_limit_exceeded =
350 votes_ancestries_len > C::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
351
352 let is_mandatory_finality_target =
355 GrandpaConsensusLogReader::<BlockNumberOf<C>>::find_scheduled_change(header.digest())
356 .is_some();
357
358 let actual_call_size: u32 =
360 header.encoded_size().saturating_add(proof.encoded_size()).saturated_into();
361 let max_expected_call_size = max_expected_submit_finality_proof_arguments_size::<C>(
362 is_mandatory_finality_target,
363 required_precommits,
364 );
365 let extra_size = actual_call_size.saturating_sub(max_expected_call_size);
366
367 SubmitFinalityProofCallExtras {
368 is_weight_limit_exceeded,
369 extra_size,
370 is_mandatory_finality_target,
371 }
372}
373
374pub fn max_expected_submit_finality_proof_arguments_size<C: ChainWithGrandpa>(
376 is_mandatory_finality_target: bool,
377 precommits: u32,
378) -> u32 {
379 let max_expected_justification_size =
380 GrandpaJustification::<HeaderOf<C>>::max_reasonable_size::<C>(precommits);
381
382 let max_expected_finality_target_size = if is_mandatory_finality_target {
384 C::MAX_MANDATORY_HEADER_SIZE
385 } else {
386 C::AVERAGE_HEADER_SIZE
387 };
388 max_expected_finality_target_size.saturating_add(max_expected_justification_size)
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use bp_runtime::ChainId;
395 use frame_support::weights::Weight;
396 use sp_runtime::{
397 testing::H256, traits::BlakeTwo256, DigestItem, MultiSignature, StateVersion,
398 };
399
400 struct TestChain;
401
402 impl Chain for TestChain {
403 const ID: ChainId = *b"test";
404
405 type BlockNumber = u32;
406 type Hash = H256;
407 type Hasher = BlakeTwo256;
408 type Header = sp_runtime::generic::Header<u32, BlakeTwo256>;
409 type AccountId = u64;
410 type Balance = u64;
411 type Nonce = u64;
412 type Signature = MultiSignature;
413
414 const STATE_VERSION: StateVersion = StateVersion::V1;
415
416 fn max_extrinsic_size() -> u32 {
417 0
418 }
419 fn max_extrinsic_weight() -> Weight {
420 Weight::zero()
421 }
422 }
423
424 impl ChainWithGrandpa for TestChain {
425 const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "Test";
426 const MAX_AUTHORITIES_COUNT: u32 = 128;
427 const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 2;
428 const MAX_MANDATORY_HEADER_SIZE: u32 = 100_000;
429 const AVERAGE_HEADER_SIZE: u32 = 1_024;
430 }
431
432 #[test]
433 fn max_expected_submit_finality_proof_arguments_size_respects_mandatory_argument() {
434 assert!(
435 max_expected_submit_finality_proof_arguments_size::<TestChain>(true, 100) >
436 max_expected_submit_finality_proof_arguments_size::<TestChain>(false, 100),
437 );
438 }
439
440 #[test]
441 fn find_scheduled_change_works() {
442 let scheduled_change = ScheduledChange { next_authorities: vec![], delay: 0 };
443
444 let mut digest = Digest::default();
446 digest.push(DigestItem::Consensus(
447 GRANDPA_ENGINE_ID,
448 ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
449 ));
450 assert_eq!(
451 GrandpaConsensusLogReader::find_scheduled_change(&digest),
452 Some(scheduled_change.clone())
453 );
454
455 let mut digest = Digest::default();
457 digest.push(DigestItem::Consensus(
458 GRANDPA_ENGINE_ID,
459 ConsensusLog::<u64>::OnDisabled(0).encode(),
460 ));
461 digest.push(DigestItem::Consensus(
462 GRANDPA_ENGINE_ID,
463 ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
464 ));
465 assert_eq!(
466 GrandpaConsensusLogReader::find_scheduled_change(&digest),
467 Some(scheduled_change.clone())
468 );
469 }
470}