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, 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#[derive(Clone, Decode, Encode, Eq, PartialEq, PalletError, Debug, TypeInfo)]
50pub enum HeaderChainError {
51 UnknownHeader,
53 StorageProof(StorageProofError),
55}
56
57#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)]
62pub struct StoredHeaderData<Number, Hash> {
63 pub number: Number,
65 pub state_root: Hash,
67}
68
69pub trait StoredHeaderDataBuilder<Number, Hash> {
71 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
81pub trait HeaderChain<C: Chain> {
83 fn finalized_header_state_root(header_hash: HashOf<C>) -> Option<HashOf<C>>;
85
86 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
97pub trait Parameter: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
101impl<T> Parameter for T where T: Codec + EncodeLike + Clone + Eq + Debug + TypeInfo {}
102
103#[derive(Default, Encode, Eq, Decode, RuntimeDebug, PartialEq, Clone, TypeInfo)]
105#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
106pub struct AuthoritySet {
107 pub authorities: AuthorityList,
109 pub set_id: SetId,
111}
112
113impl AuthoritySet {
114 pub fn new(authorities: AuthorityList, set_id: SetId) -> Self {
116 Self { authorities, set_id }
117 }
118}
119
120#[derive(
124 Default, Encode, Decode, RuntimeDebug, PartialEq, Eq, Clone, TypeInfo, Serialize, Deserialize,
125)]
126pub struct InitializationData<H: HeaderT> {
127 pub header: Box<H>,
129 pub authority_list: AuthorityList,
131 pub set_id: SetId,
133 pub operating_mode: BasicOperatingMode,
135}
136
137pub trait FinalityProof<Hash, Number>: Clone + Send + Sync + Debug {
139 fn target_header_hash(&self) -> Hash;
141
142 fn target_header_number(&self) -> Number;
144}
145
146pub trait ConsensusLogReader {
148 fn schedules_authorities_change(digest: &Digest) -> bool;
150}
151
152pub struct GrandpaConsensusLogReader<Number>(sp_std::marker::PhantomData<Number>);
154
155impl<Number: Codec> GrandpaConsensusLogReader<Number> {
156 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 digest.convert_first(|l| l.try_to(id).and_then(filter_log))
169 }
170
171 pub fn find_forced_change(digest: &Digest) -> Option<(Number, ScheduledChange<Number>)> {
174 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#[derive(Encode, Decode, Debug, PartialEq, Clone, TypeInfo)]
193pub struct HeaderFinalityInfo<FinalityProof, FinalityVerificationContext> {
194 pub finality_proof: FinalityProof,
196 pub new_verification_context: Option<FinalityVerificationContext>,
198}
199
200pub type StoredHeaderGrandpaInfo<Header> =
202 HeaderFinalityInfo<GrandpaJustification<Header>, AuthoritySet>;
203
204pub 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
222pub trait FindEquivocations<FinalityProof, FinalityVerificationContext, EquivocationProof> {
224 type Error: Debug;
226
227 fn find_equivocations(
229 verification_context: &FinalityVerificationContext,
230 synced_proof: &FinalityProof,
231 source_proofs: &[FinalityProof],
232 ) -> Result<Vec<EquivocationProof>, Self::Error>;
233}
234
235pub trait ChainWithGrandpa: Chain {
240 const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str;
246
247 const MAX_AUTHORITIES_COUNT: u32;
252
253 const REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY: u32;
260
261 const MAX_MANDATORY_HEADER_SIZE: u32;
268
269 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#[derive(Debug)]
302pub struct SubmitFinalityProofCallExtras {
303 pub is_weight_limit_exceeded: bool,
309 pub extra_size: u32,
315 pub is_mandatory_finality_target: bool,
318}
319
320pub fn submit_finality_proof_limits_extras<C: ChainWithGrandpa>(
325 header: &C::Header,
326 proof: &justification::GrandpaJustification<C::Header>,
327) -> SubmitFinalityProofCallExtras {
328 let precommits_len = proof.commit.precommits.len().saturated_into();
332 let required_precommits = precommits_len;
333
334 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 let is_mandatory_finality_target =
343 GrandpaConsensusLogReader::<BlockNumberOf<C>>::find_scheduled_change(header.digest())
344 .is_some();
345
346 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
362pub 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 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 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 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}