referrerpolicy=no-referrer-when-downgrade

pallet_bridge_grandpa/
call_ext.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
17use crate::{
18	weights::WeightInfo, BestFinalized, BridgedBlockNumber, BridgedHeader, Config,
19	CurrentAuthoritySet, Error, FreeHeadersRemaining, Pallet,
20};
21use bp_header_chain::{
22	justification::GrandpaJustification, submit_finality_proof_limits_extras,
23	SubmitFinalityProofInfo,
24};
25use bp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule};
26use frame_support::{
27	dispatch::CallableCallFor,
28	traits::{Get, IsSubType},
29	weights::Weight,
30};
31use sp_consensus_grandpa::SetId;
32use sp_runtime::{
33	traits::{CheckedSub, Header, Zero},
34	transaction_validity::{InvalidTransaction, TransactionValidityError},
35	RuntimeDebug, SaturatedConversion,
36};
37use sp_std::fmt::Debug;
38
39/// Verified `SubmitFinalityProofInfo<N>`.
40#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
41pub struct VerifiedSubmitFinalityProofInfo<N: Debug> {
42	/// Base call information.
43	pub base: SubmitFinalityProofInfo<N>,
44	/// A difference between bundled bridged header and best bridged header known to us
45	/// before the call.
46	pub improved_by: N,
47}
48
49/// Helper struct that provides methods for working with the `SubmitFinalityProof` call.
50pub struct SubmitFinalityProofHelper<T: Config<I>, I: 'static> {
51	_phantom_data: sp_std::marker::PhantomData<(T, I)>,
52}
53
54impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
55	/// Returns `true` if we may fit more free headers into the current block. If `false` is
56	/// returned, the call will be paid even if `is_free_execution_expected` has been set
57	/// to `true`.
58	pub fn has_free_header_slots() -> bool {
59		// `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept
60		// this header for free. That is a small cheat - it is `None` if executed outside of
61		// transaction (e.g. during block initialization). Normal relayer would never submit
62		// such calls, but if he did, that is not our problem. During normal transactions,
63		// the `FreeHeadersRemaining` is always `Some(_)`.
64		let free_headers_remaining = FreeHeadersRemaining::<T, I>::get().unwrap_or(u32::MAX);
65		free_headers_remaining > 0
66	}
67
68	/// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the
69	/// best one we know (2) if `current_set_id` matches the current authority set id, if specified
70	/// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected`
71	/// is `true`.
72	///
73	/// Returns number of headers between the current best finalized header, known to the pallet
74	/// and the bundled header.
75	pub fn check_obsolete_from_extension(
76		call_info: &SubmitFinalityProofInfo<BlockNumberOf<T::BridgedChain>>,
77	) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
78		// do basic checks first
79		let improved_by = Self::check_obsolete(call_info.block_number, call_info.current_set_id)?;
80
81		// if submitter has NOT specified that it wants free execution, then we are done
82		if !call_info.is_free_execution_expected {
83			return Ok(improved_by);
84		}
85
86		// else - if we can not accept more free headers, "reject" the transaction
87		if !Self::has_free_header_slots() {
88			tracing::trace!(
89				target: crate::LOG_TARGET,
90				chain_id=?T::BridgedChain::ID,
91				block_number=?call_info.block_number,
92				"Cannot accept free header. No more free slots remaining"
93			);
94
95			return Err(Error::<T, I>::FreeHeadersLimitExceded);
96		}
97
98		// ensure that the `improved_by` is larger than the configured free interval
99		if !call_info.is_mandatory {
100			if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
101				if improved_by < free_headers_interval.into() {
102					tracing::trace!(
103						target: crate::LOG_TARGET,
104						chain_id=?T::BridgedChain::ID,
105						block_number=?call_info.block_number,
106						?improved_by,
107						%free_headers_interval,
108						"Cannot accept free header. Too small difference between submitted headers"
109					);
110
111					return Err(Error::<T, I>::BelowFreeHeaderInterval);
112				}
113			}
114		}
115
116		// let's also check whether the header submission fits the hardcoded limits. A normal
117		// relayer would check that before submitting a transaction (since limits are constants
118		// and do not depend on a volatile runtime state), but the ckeck itself is cheap, so
119		// let's do it here too
120		if !call_info.fits_limits() {
121			return Err(Error::<T, I>::HeaderOverflowLimits);
122		}
123
124		Ok(improved_by)
125	}
126
127	/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
128	/// one we know. Additionally, checks if `current_set_id` matches the current authority set
129	/// id, if specified. This method is called by the call code and the transaction extension,
130	/// so it does not check the free execution.
131	///
132	/// Returns number of headers between the current best finalized header, known to the pallet
133	/// and the bundled header.
134	pub fn check_obsolete(
135		finality_target: BlockNumberOf<T::BridgedChain>,
136		current_set_id: Option<SetId>,
137	) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
138		let best_finalized = BestFinalized::<T, I>::get().ok_or_else(|| {
139			tracing::trace!(
140				target: crate::LOG_TARGET,
141				header=?finality_target,
142				"Cannot finalize header because pallet is not yet initialized"
143			);
144			<Error<T, I>>::NotInitialized
145		})?;
146
147		let improved_by = match finality_target.checked_sub(&best_finalized.number()) {
148			Some(improved_by) if improved_by > Zero::zero() => improved_by,
149			_ => {
150				tracing::trace!(
151					target: crate::LOG_TARGET,
152					bundled=?finality_target,
153					best=?best_finalized,
154					"Cannot finalize obsolete header"
155				);
156
157				return Err(Error::<T, I>::OldHeader)
158			},
159		};
160
161		if let Some(current_set_id) = current_set_id {
162			let actual_set_id = <CurrentAuthoritySet<T, I>>::get().set_id;
163			if current_set_id != actual_set_id {
164				tracing::trace!(
165					target: crate::LOG_TARGET,
166					bundled=?current_set_id,
167					best=?actual_set_id,
168					"Cannot finalize header signed by unknown authority set"
169				);
170
171				return Err(Error::<T, I>::InvalidAuthoritySetId)
172			}
173		}
174
175		Ok(improved_by)
176	}
177
178	/// Check if the `SubmitFinalityProof` was successfully executed.
179	pub fn was_successful(finality_target: BlockNumberOf<T::BridgedChain>) -> bool {
180		match BestFinalized::<T, I>::get() {
181			Some(best_finalized) => best_finalized.number() == finality_target,
182			None => false,
183		}
184	}
185}
186
187/// Trait representing a call that is a sub type of this pallet's call.
188pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
189	IsSubType<CallableCallFor<Pallet<T, I>, T>>
190{
191	/// Extract finality proof info from a runtime call.
192	fn submit_finality_proof_info(
193		&self,
194	) -> Option<SubmitFinalityProofInfo<BridgedBlockNumber<T, I>>> {
195		if let Some(crate::Call::<T, I>::submit_finality_proof { finality_target, justification }) =
196			self.is_sub_type()
197		{
198			return Some(submit_finality_proof_info_from_args::<T, I>(
199				finality_target,
200				justification,
201				None,
202				false,
203			))
204		} else if let Some(crate::Call::<T, I>::submit_finality_proof_ex {
205			finality_target,
206			justification,
207			current_set_id,
208			is_free_execution_expected,
209		}) = self.is_sub_type()
210		{
211			return Some(submit_finality_proof_info_from_args::<T, I>(
212				finality_target,
213				justification,
214				Some(*current_set_id),
215				*is_free_execution_expected,
216			))
217		}
218
219		None
220	}
221
222	/// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated
223	/// bridged chain headers. Without this validation, even honest relayers may lose their funds
224	/// if there are multiple relays running and submitting the same information.
225	///
226	/// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pallet.
227	/// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pallet and
228	/// we believe the call brings header that improves the pallet state.
229	/// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pallet and we
230	/// believe that the call will fail.
231	fn check_obsolete_submit_finality_proof(
232		&self,
233	) -> Result<
234		Option<VerifiedSubmitFinalityProofInfo<BridgedBlockNumber<T, I>>>,
235		TransactionValidityError,
236	>
237	where
238		Self: Sized,
239	{
240		let call_info = match self.submit_finality_proof_info() {
241			Some(finality_proof) => finality_proof,
242			_ => return Ok(None),
243		};
244
245		if Pallet::<T, I>::ensure_not_halted().is_err() {
246			return Err(InvalidTransaction::Call.into())
247		}
248
249		let result = SubmitFinalityProofHelper::<T, I>::check_obsolete_from_extension(&call_info);
250		match result {
251			Ok(improved_by) =>
252				Ok(Some(VerifiedSubmitFinalityProofInfo { base: call_info, improved_by })),
253			Err(Error::<T, I>::OldHeader) => Err(InvalidTransaction::Stale.into()),
254			Err(_) => Err(InvalidTransaction::Call.into()),
255		}
256	}
257}
258
259impl<T: Config<I>, I: 'static> CallSubType<T, I> for T::RuntimeCall where
260	T::RuntimeCall: IsSubType<CallableCallFor<Pallet<T, I>, T>>
261{
262}
263
264/// Extract finality proof info from the submitted header and justification.
265pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
266	finality_target: &BridgedHeader<T, I>,
267	justification: &GrandpaJustification<BridgedHeader<T, I>>,
268	current_set_id: Option<SetId>,
269	is_free_execution_expected: bool,
270) -> SubmitFinalityProofInfo<BridgedBlockNumber<T, I>> {
271	// check if call exceeds limits. In other words - whether some size or weight is included
272	// in the call
273	let extras =
274		submit_finality_proof_limits_extras::<T::BridgedChain>(finality_target, justification);
275
276	// We do care about extra weight because of more-than-expected headers in the votes
277	// ancestries. But we have problems computing extra weight for additional headers (weight of
278	// additional header is too small, so that our benchmarks aren't detecting that). So if there
279	// are more than expected headers in votes ancestries, we will treat the whole call weight
280	// as an extra weight.
281	let extra_weight = if extras.is_weight_limit_exceeded {
282		let precommits_len = justification.commit.precommits.len().saturated_into();
283		let votes_ancestries_len = justification.votes_ancestries.len().saturated_into();
284		T::WeightInfo::submit_finality_proof(precommits_len, votes_ancestries_len)
285	} else {
286		Weight::zero()
287	};
288
289	SubmitFinalityProofInfo {
290		block_number: *finality_target.number(),
291		current_set_id,
292		is_mandatory: extras.is_mandatory_finality_target,
293		is_free_execution_expected,
294		extra_weight,
295		extra_size: extras.extra_size,
296	}
297}
298
299#[cfg(test)]
300mod tests {
301	use crate::{
302		call_ext::CallSubType,
303		mock::{
304			run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber,
305			TestRuntime,
306		},
307		BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode,
308		StoredAuthoritySet, WeightInfo,
309	};
310	use bp_header_chain::{ChainWithGrandpa, SubmitFinalityProofInfo};
311	use bp_runtime::{BasicOperatingMode, HeaderId};
312	use bp_test_utils::{
313		make_default_justification, make_justification_for_header, JustificationGeneratorParams,
314		TEST_GRANDPA_SET_ID,
315	};
316	use codec::Encode;
317	use frame_support::weights::Weight;
318	use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion};
319
320	fn validate_block_submit(num: TestNumber) -> bool {
321		let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
322			finality_target: Box::new(test_header(num)),
323			justification: make_default_justification(&test_header(num)),
324			// not initialized => zero
325			current_set_id: 0,
326			is_free_execution_expected: false,
327		};
328		RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
329			bridge_grandpa_call,
330		))
331		.is_ok()
332	}
333
334	fn sync_to_header_10() {
335		let header10_hash = sp_core::H256::default();
336		BestFinalized::<TestRuntime, ()>::put(HeaderId(10, header10_hash));
337	}
338
339	#[test]
340	fn extension_rejects_obsolete_header() {
341		run_test(|| {
342			// when current best finalized is #10 and we're trying to import header#5 => tx is
343			// rejected
344			sync_to_header_10();
345			assert!(!validate_block_submit(5));
346		});
347	}
348
349	#[test]
350	fn extension_rejects_same_header() {
351		run_test(|| {
352			// when current best finalized is #10 and we're trying to import header#10 => tx is
353			// rejected
354			sync_to_header_10();
355			assert!(!validate_block_submit(10));
356		});
357	}
358
359	#[test]
360	fn extension_rejects_new_header_if_pallet_is_halted() {
361		run_test(|| {
362			// when pallet is halted => tx is rejected
363			sync_to_header_10();
364			PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
365
366			assert!(!validate_block_submit(15));
367		});
368	}
369
370	#[test]
371	fn extension_rejects_new_header_if_set_id_is_invalid() {
372		run_test(|| {
373			// when set id is different from the passed one => tx is rejected
374			sync_to_header_10();
375			let next_set = StoredAuthoritySet::<TestRuntime, ()>::try_new(vec![], 0x42).unwrap();
376			CurrentAuthoritySet::<TestRuntime, ()>::put(next_set);
377
378			assert!(!validate_block_submit(15));
379		});
380	}
381
382	#[test]
383	fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted(
384	) {
385		run_test(|| {
386			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
387				finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)),
388				justification: make_default_justification(&test_header(
389					10 + FreeHeadersInterval::get() as u64,
390				)),
391				current_set_id: 0,
392				is_free_execution_expected: true,
393			};
394			sync_to_header_10();
395
396			// when we can accept free headers => Ok
397			FreeHeadersRemaining::<TestRuntime, ()>::put(2);
398			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
399				bridge_grandpa_call.clone(),
400			),)
401			.is_ok());
402
403			// when we can NOT accept free headers => Err
404			FreeHeadersRemaining::<TestRuntime, ()>::put(0);
405			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
406				bridge_grandpa_call.clone(),
407			),)
408			.is_err());
409
410			// when called outside of transaction => Ok
411			FreeHeadersRemaining::<TestRuntime, ()>::kill();
412			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
413				bridge_grandpa_call,
414			),)
415			.is_ok());
416		})
417	}
418
419	#[test]
420	fn extension_rejects_new_header_if_it_overflow_size_limits() {
421		run_test(|| {
422			let mut large_finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
423			large_finality_target
424				.digest_mut()
425				.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
426			let justification_params = JustificationGeneratorParams {
427				header: large_finality_target.clone(),
428				..Default::default()
429			};
430			let large_justification = make_justification_for_header(justification_params);
431
432			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
433				finality_target: Box::new(large_finality_target),
434				justification: large_justification,
435				current_set_id: 0,
436				is_free_execution_expected: true,
437			};
438			sync_to_header_10();
439
440			// if overflow size limits => Err
441			FreeHeadersRemaining::<TestRuntime, ()>::put(2);
442			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
443				bridge_grandpa_call.clone(),
444			),)
445			.is_err());
446		})
447	}
448
449	#[test]
450	fn extension_rejects_new_header_if_it_overflow_weight_limits() {
451		run_test(|| {
452			let finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
453			let justification_params = JustificationGeneratorParams {
454				header: finality_target.clone(),
455				ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
456				..Default::default()
457			};
458			let justification = make_justification_for_header(justification_params);
459
460			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
461				finality_target: Box::new(finality_target),
462				justification,
463				current_set_id: 0,
464				is_free_execution_expected: true,
465			};
466			sync_to_header_10();
467
468			// if overflow weight limits => Err
469			FreeHeadersRemaining::<TestRuntime, ()>::put(2);
470			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
471				bridge_grandpa_call.clone(),
472			),)
473			.is_err());
474		})
475	}
476
477	#[test]
478	fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected(
479	) {
480		run_test(|| {
481			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
482				finality_target: Box::new(test_header(100)),
483				justification: make_default_justification(&test_header(100)),
484				current_set_id: 0,
485				is_free_execution_expected: true,
486			};
487			sync_to_header_10();
488
489			// when `improved_by` is less than the free interval
490			BestFinalized::<TestRuntime, ()>::put(HeaderId(
491				100 - FreeHeadersInterval::get() as u64 + 1,
492				sp_core::H256::default(),
493			));
494			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
495				bridge_grandpa_call.clone(),
496			),)
497			.is_err());
498
499			// when `improved_by` is equal to the free interval
500			BestFinalized::<TestRuntime, ()>::put(HeaderId(
501				100 - FreeHeadersInterval::get() as u64,
502				sp_core::H256::default(),
503			));
504			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
505				bridge_grandpa_call.clone(),
506			),)
507			.is_ok());
508
509			// when `improved_by` is larger than the free interval
510			BestFinalized::<TestRuntime, ()>::put(HeaderId(
511				100 - FreeHeadersInterval::get() as u64 - 1,
512				sp_core::H256::default(),
513			));
514			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
515				bridge_grandpa_call.clone(),
516			),)
517			.is_ok());
518
519			// when `improved_by` is less than the free interval BUT it is a mandatory header
520			let mut mandatory_header = test_header(100);
521			let consensus_log = sp_consensus_grandpa::ConsensusLog::<TestNumber>::ScheduledChange(
522				sp_consensus_grandpa::ScheduledChange {
523					next_authorities: bp_test_utils::authority_list(),
524					delay: 0,
525				},
526			);
527			mandatory_header.digest = sp_runtime::Digest {
528				logs: vec![DigestItem::Consensus(
529					sp_consensus_grandpa::GRANDPA_ENGINE_ID,
530					consensus_log.encode(),
531				)],
532			};
533			let justification = make_justification_for_header(JustificationGeneratorParams {
534				header: mandatory_header.clone(),
535				set_id: 1,
536				..Default::default()
537			});
538			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
539				finality_target: Box::new(mandatory_header),
540				justification,
541				current_set_id: 0,
542				is_free_execution_expected: true,
543			};
544			BestFinalized::<TestRuntime, ()>::put(HeaderId(
545				100 - FreeHeadersInterval::get() as u64 + 1,
546				sp_core::H256::default(),
547			));
548			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
549				bridge_grandpa_call.clone(),
550			),)
551			.is_ok());
552		})
553	}
554
555	#[test]
556	fn extension_accepts_new_header() {
557		run_test(|| {
558			// when current best finalized is #10 and we're trying to import header#15 => tx is
559			// accepted
560			sync_to_header_10();
561			assert!(validate_block_submit(15));
562		});
563	}
564
565	#[test]
566	fn submit_finality_proof_info_is_parsed() {
567		// when `submit_finality_proof` is used, `current_set_id` is set to `None`
568		let deprecated_call =
569			RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof {
570				finality_target: Box::new(test_header(42)),
571				justification: make_default_justification(&test_header(42)),
572			});
573		assert_eq!(
574			deprecated_call.submit_finality_proof_info(),
575			Some(SubmitFinalityProofInfo {
576				block_number: 42,
577				current_set_id: None,
578				extra_weight: Weight::zero(),
579				extra_size: 0,
580				is_mandatory: false,
581				is_free_execution_expected: false,
582			})
583		);
584
585		// when `submit_finality_proof_ex` is used, `current_set_id` is set to `Some`
586		let deprecated_call =
587			RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
588				finality_target: Box::new(test_header(42)),
589				justification: make_default_justification(&test_header(42)),
590				current_set_id: 777,
591				is_free_execution_expected: false,
592			});
593		assert_eq!(
594			deprecated_call.submit_finality_proof_info(),
595			Some(SubmitFinalityProofInfo {
596				block_number: 42,
597				current_set_id: Some(777),
598				extra_weight: Weight::zero(),
599				extra_size: 0,
600				is_mandatory: false,
601				is_free_execution_expected: false,
602			})
603		);
604	}
605
606	#[test]
607	fn extension_returns_correct_extra_size_if_call_arguments_are_too_large() {
608		// when call arguments are below our limit => no refund
609		let small_finality_target = test_header(1);
610		let justification_params = JustificationGeneratorParams {
611			header: small_finality_target.clone(),
612			..Default::default()
613		};
614		let small_justification = make_justification_for_header(justification_params);
615		let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
616			finality_target: Box::new(small_finality_target),
617			justification: small_justification,
618			current_set_id: TEST_GRANDPA_SET_ID,
619			is_free_execution_expected: false,
620		});
621		assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0);
622
623		// when call arguments are too large => partial refund
624		let mut large_finality_target = test_header(1);
625		large_finality_target
626			.digest_mut()
627			.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
628		let justification_params = JustificationGeneratorParams {
629			header: large_finality_target.clone(),
630			..Default::default()
631		};
632		let large_justification = make_justification_for_header(justification_params);
633		let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
634			finality_target: Box::new(large_finality_target),
635			justification: large_justification,
636			current_set_id: TEST_GRANDPA_SET_ID,
637			is_free_execution_expected: false,
638		});
639		assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0);
640	}
641
642	#[test]
643	fn extension_returns_correct_extra_weight_if_there_are_too_many_headers_in_votes_ancestry() {
644		let finality_target = test_header(1);
645		let mut justification_params = JustificationGeneratorParams {
646			header: finality_target.clone(),
647			ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
648			..Default::default()
649		};
650
651		// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY` headers => no refund
652		let justification = make_justification_for_header(justification_params.clone());
653		let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
654			finality_target: Box::new(finality_target.clone()),
655			justification,
656			current_set_id: TEST_GRANDPA_SET_ID,
657			is_free_execution_expected: false,
658		});
659		assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero());
660
661		// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY + 1` headers => full refund
662		justification_params.ancestors += 1;
663		let justification = make_justification_for_header(justification_params);
664		let call_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
665			justification.commit.precommits.len().saturated_into(),
666			justification.votes_ancestries.len().saturated_into(),
667		);
668		let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
669			finality_target: Box::new(finality_target),
670			justification,
671			current_set_id: TEST_GRANDPA_SET_ID,
672			is_free_execution_expected: false,
673		});
674		assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight);
675	}
676
677	#[test]
678	fn check_obsolete_submit_finality_proof_returns_correct_improved_by() {
679		run_test(|| {
680			fn make_call(number: u64) -> RuntimeCall {
681				RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
682					finality_target: Box::new(test_header(number)),
683					justification: make_default_justification(&test_header(number)),
684					current_set_id: 0,
685					is_free_execution_expected: false,
686				})
687			}
688
689			sync_to_header_10();
690
691			// when the difference between headers is 1
692			assert_eq!(
693				RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11))
694					.unwrap()
695					.unwrap()
696					.improved_by,
697				1,
698			);
699
700			// when the difference between headers is 2
701			assert_eq!(
702				RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12))
703					.unwrap()
704					.unwrap()
705					.improved_by,
706				2,
707			);
708		})
709	}
710
711	#[test]
712	fn check_obsolete_submit_finality_proof_ignores_other_calls() {
713		run_test(|| {
714			let call =
715				RuntimeCall::System(frame_system::Call::<TestRuntime>::remark { remark: vec![42] });
716
717			assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None));
718		})
719	}
720}