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.convert_first(|log| {
189 log.consensus_try_to(&GRANDPA_ENGINE_ID)
190 .and_then(ConsensusLog::try_into_forced_change)
191 })
192 }
193}
194
195impl<Number: Codec> ConsensusLogReader for GrandpaConsensusLogReader<Number> {
196 fn schedules_authorities_change(digest: &Digest) -> bool {
197 GrandpaConsensusLogReader::<Number>::find_scheduled_change(digest).is_some()
198 }
199}
200
201#[derive(Encode, Decode, DecodeWithMemTracking, Debug, PartialEq, Clone, TypeInfo)]
203pub struct HeaderFinalityInfo<FinalityProof, FinalityVerificationContext> {
204 pub finality_proof: FinalityProof,
206 pub new_verification_context: Option<FinalityVerificationContext>,
208}
209
210pub type StoredHeaderGrandpaInfo<Header> =
212 HeaderFinalityInfo<GrandpaJustification<Header>, AuthoritySet>;
213
214pub type HeaderGrandpaInfo<Header> =
216 HeaderFinalityInfo<GrandpaJustification<Header>, JustificationVerificationContext>;
217
218impl<Header: HeaderT> TryFrom<StoredHeaderGrandpaInfo<Header>> for HeaderGrandpaInfo<Header> {
219 type Error = JustificationVerificationError;
220
221 fn try_from(grandpa_info: StoredHeaderGrandpaInfo<Header>) -> Result<Self, Self::Error> {
222 Ok(Self {
223 finality_proof: grandpa_info.finality_proof,
224 new_verification_context: match grandpa_info.new_verification_context {
225 Some(authority_set) => Some(authority_set.try_into()?),
226 None => None,
227 },
228 })
229 }
230}
231
232pub trait FindEquivocations<FinalityProof, FinalityVerificationContext, EquivocationProof> {
234 type Error: Debug;
236
237 fn find_equivocations(
239 verification_context: &FinalityVerificationContext,
240 synced_proof: &FinalityProof,
241 source_proofs: &[FinalityProof],
242 ) -> Result<Vec<EquivocationProof>, Self::Error>;
243}
244
245pub trait ChainWithGrandpa: Chain {
250 const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str;
256
257 const MAX_AUTHORITIES_COUNT: u32;
262
263 const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32;
270
271 const MAX_MANDATORY_HEADER_SIZE: u32;
278
279 const AVERAGE_HEADER_SIZE: u32;
293}
294
295impl<T> ChainWithGrandpa for T
296where
297 T: Chain + UnderlyingChainProvider,
298 T::Chain: ChainWithGrandpa,
299{
300 const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str =
301 <T::Chain as ChainWithGrandpa>::WITH_CHAIN_GRANDPA_PALLET_NAME;
302 const MAX_AUTHORITIES_COUNT: u32 = <T::Chain as ChainWithGrandpa>::MAX_AUTHORITIES_COUNT;
303 const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 =
304 <T::Chain as ChainWithGrandpa>::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
305 const MAX_MANDATORY_HEADER_SIZE: u32 =
306 <T::Chain as ChainWithGrandpa>::MAX_MANDATORY_HEADER_SIZE;
307 const AVERAGE_HEADER_SIZE: u32 = <T::Chain as ChainWithGrandpa>::AVERAGE_HEADER_SIZE;
308}
309
310#[derive(Debug)]
312pub struct SubmitFinalityProofCallExtras {
313 pub is_weight_limit_exceeded: bool,
319 pub extra_size: u32,
325 pub is_mandatory_finality_target: bool,
328}
329
330pub fn submit_finality_proof_limits_extras<C: ChainWithGrandpa>(
335 header: &C::Header,
336 proof: &justification::GrandpaJustification<C::Header>,
337) -> SubmitFinalityProofCallExtras {
338 let precommits_len = proof.commit.precommits.len().saturated_into();
342 let required_precommits = precommits_len;
343
344 let votes_ancestries_len: u32 = proof.votes_ancestries.len().saturated_into();
347 let is_weight_limit_exceeded =
348 votes_ancestries_len > C::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY;
349
350 let is_mandatory_finality_target =
353 GrandpaConsensusLogReader::<BlockNumberOf<C>>::find_scheduled_change(header.digest())
354 .is_some();
355
356 let actual_call_size: u32 =
358 header.encoded_size().saturating_add(proof.encoded_size()).saturated_into();
359 let max_expected_call_size = max_expected_submit_finality_proof_arguments_size::<C>(
360 is_mandatory_finality_target,
361 required_precommits,
362 );
363 let extra_size = actual_call_size.saturating_sub(max_expected_call_size);
364
365 SubmitFinalityProofCallExtras {
366 is_weight_limit_exceeded,
367 extra_size,
368 is_mandatory_finality_target,
369 }
370}
371
372pub fn max_expected_submit_finality_proof_arguments_size<C: ChainWithGrandpa>(
374 is_mandatory_finality_target: bool,
375 precommits: u32,
376) -> u32 {
377 let max_expected_justification_size =
378 GrandpaJustification::<HeaderOf<C>>::max_reasonable_size::<C>(precommits);
379
380 let max_expected_finality_target_size = if is_mandatory_finality_target {
382 C::MAX_MANDATORY_HEADER_SIZE
383 } else {
384 C::AVERAGE_HEADER_SIZE
385 };
386 max_expected_finality_target_size.saturating_add(max_expected_justification_size)
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use bp_runtime::ChainId;
393 use frame_support::weights::Weight;
394 use sp_runtime::{
395 testing::H256, traits::BlakeTwo256, DigestItem, MultiSignature, StateVersion,
396 };
397
398 struct TestChain;
399
400 impl Chain for TestChain {
401 const ID: ChainId = *b"test";
402
403 type BlockNumber = u32;
404 type Hash = H256;
405 type Hasher = BlakeTwo256;
406 type Header = sp_runtime::generic::Header<u32, BlakeTwo256>;
407 type AccountId = u64;
408 type Balance = u64;
409 type Nonce = u64;
410 type Signature = MultiSignature;
411
412 const STATE_VERSION: StateVersion = StateVersion::V1;
413
414 fn max_extrinsic_size() -> u32 {
415 0
416 }
417 fn max_extrinsic_weight() -> Weight {
418 Weight::zero()
419 }
420 }
421
422 impl ChainWithGrandpa for TestChain {
423 const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "Test";
424 const MAX_AUTHORITIES_COUNT: u32 = 128;
425 const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32 = 2;
426 const MAX_MANDATORY_HEADER_SIZE: u32 = 100_000;
427 const AVERAGE_HEADER_SIZE: u32 = 1_024;
428 }
429
430 #[test]
431 fn max_expected_submit_finality_proof_arguments_size_respects_mandatory_argument() {
432 assert!(
433 max_expected_submit_finality_proof_arguments_size::<TestChain>(true, 100) >
434 max_expected_submit_finality_proof_arguments_size::<TestChain>(false, 100),
435 );
436 }
437
438 #[test]
439 fn find_forced_change_returns_first_match() {
440 let change = ScheduledChange { next_authorities: vec![], delay: 3u64 };
441 let mut digest = Digest::default();
442 assert_eq!(GrandpaConsensusLogReader::<u64>::find_forced_change(&digest), None);
443
444 digest.push(DigestItem::Consensus(
445 GRANDPA_ENGINE_ID,
446 ConsensusLog::ForcedChange(7, change.clone()).encode(),
447 ));
448 assert_eq!(
449 GrandpaConsensusLogReader::find_forced_change(&digest),
450 Some((7, change.clone()))
451 );
452
453 digest.push(DigestItem::Consensus(
454 GRANDPA_ENGINE_ID,
455 ConsensusLog::ForcedChange(9, change.clone()).encode(),
456 ));
457 assert_eq!(GrandpaConsensusLogReader::find_forced_change(&digest), Some((7, change)));
458 }
459
460 #[test]
461 fn find_forced_change_skips_other_grandpa_logs() {
462 let change = ScheduledChange { next_authorities: vec![], delay: 3u64 };
463 for preceding in [
464 ConsensusLog::OnDisabled(0),
465 ConsensusLog::ScheduledChange(change.clone()),
466 ConsensusLog::Pause(1),
467 ConsensusLog::Resume(1),
468 ] {
469 let mut digest = Digest::default();
470 digest.push(DigestItem::Consensus(GRANDPA_ENGINE_ID, preceding.encode()));
471 assert_eq!(GrandpaConsensusLogReader::<u64>::find_forced_change(&digest), None);
472
473 digest.push(DigestItem::Consensus(
474 GRANDPA_ENGINE_ID,
475 ConsensusLog::ForcedChange(7, change.clone()).encode(),
476 ));
477 assert_eq!(
478 GrandpaConsensusLogReader::find_forced_change(&digest),
479 Some((7, change.clone())),
480 "forced change must be found after {preceding:?}",
481 );
482 }
483 }
484
485 #[test]
486 fn find_scheduled_change_works() {
487 let scheduled_change = ScheduledChange { next_authorities: vec![], delay: 0 };
488
489 let mut digest = Digest::default();
491 digest.push(DigestItem::Consensus(
492 GRANDPA_ENGINE_ID,
493 ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
494 ));
495 assert_eq!(
496 GrandpaConsensusLogReader::find_scheduled_change(&digest),
497 Some(scheduled_change.clone())
498 );
499
500 let mut digest = Digest::default();
502 digest.push(DigestItem::Consensus(
503 GRANDPA_ENGINE_ID,
504 ConsensusLog::<u64>::OnDisabled(0).encode(),
505 ));
506 digest.push(DigestItem::Consensus(
507 GRANDPA_ENGINE_ID,
508 ConsensusLog::ScheduledChange(scheduled_change.clone()).encode(),
509 ));
510 assert_eq!(
511 GrandpaConsensusLogReader::find_scheduled_change(&digest),
512 Some(scheduled_change.clone())
513 );
514 }
515}