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 core::fmt::Debug;
27use frame_support::{
28	dispatch::CallableCallFor,
29	traits::{Get, IsSubType},
30	weights::Weight,
31};
32use sp_consensus_grandpa::SetId;
33use sp_runtime::{
34	traits::{CheckedSub, Header, Zero},
35	transaction_validity::{InvalidTransaction, TransactionValidityError},
36	SaturatedConversion,
37};
38
39/// Verified `SubmitFinalityProofInfo<N>`.
40#[derive(Copy, Clone, PartialEq, Debug)]
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			},
254			Err(Error::<T, I>::OldHeader) => Err(InvalidTransaction::Stale.into()),
255			Err(_) => Err(InvalidTransaction::Call.into()),
256		}
257	}
258}
259
260impl<T: Config<I>, I: 'static> CallSubType<T, I> for T::RuntimeCall where
261	T::RuntimeCall: IsSubType<CallableCallFor<Pallet<T, I>, T>>
262{
263}
264
265/// Extract finality proof info from the submitted header and justification.
266pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
267	finality_target: &BridgedHeader<T, I>,
268	justification: &GrandpaJustification<BridgedHeader<T, I>>,
269	current_set_id: Option<SetId>,
270	is_free_execution_expected: bool,
271) -> SubmitFinalityProofInfo<BridgedBlockNumber<T, I>> {
272	// check if call exceeds limits. In other words - whether some size or weight is included
273	// in the call
274	let extras =
275		submit_finality_proof_limits_extras::<T::BridgedChain>(finality_target, justification);
276
277	// We do care about extra weight because of more-than-expected headers in the votes
278	// ancestries. But we have problems computing extra weight for additional headers (weight of
279	// additional header is too small, so that our benchmarks aren't detecting that). So if there
280	// are more than expected headers in votes ancestries, we will treat the whole call weight
281	// as an extra weight.
282	let extra_weight = if extras.is_weight_limit_exceeded {
283		let precommits_len = justification.commit.precommits.len().saturated_into();
284		let votes_ancestries_len = justification.votes_ancestries.len().saturated_into();
285		T::WeightInfo::submit_finality_proof(precommits_len, votes_ancestries_len)
286	} else {
287		Weight::zero()
288	};
289
290	SubmitFinalityProofInfo {
291		block_number: *finality_target.number(),
292		current_set_id,
293		is_mandatory: extras.is_mandatory_finality_target,
294		is_free_execution_expected,
295		extra_weight,
296		extra_size: extras.extra_size,
297	}
298}
299
300#[cfg(test)]
301mod tests {
302	use crate::{
303		call_ext::CallSubType,
304		mock::{
305			run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber,
306			TestRuntime,
307		},
308		BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode,
309		StoredAuthoritySet, WeightInfo,
310	};
311	use bp_header_chain::{ChainWithGrandpa, SubmitFinalityProofInfo};
312	use bp_runtime::{BasicOperatingMode, HeaderId};
313	use bp_test_utils::{
314		make_default_justification, make_justification_for_header, JustificationGeneratorParams,
315		TEST_GRANDPA_SET_ID,
316	};
317	use codec::Encode;
318	use frame_support::weights::Weight;
319	use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion};
320
321	fn validate_block_submit(num: TestNumber) -> bool {
322		let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
323			finality_target: Box::new(test_header(num)),
324			justification: make_default_justification(&test_header(num)),
325			// not initialized => zero
326			current_set_id: 0,
327			is_free_execution_expected: false,
328		};
329		RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
330			bridge_grandpa_call,
331		))
332		.is_ok()
333	}
334
335	fn sync_to_header_10() {
336		let header10_hash = sp_core::H256::default();
337		BestFinalized::<TestRuntime, ()>::put(HeaderId(10, header10_hash));
338	}
339
340	#[test]
341	fn extension_rejects_obsolete_header() {
342		run_test(|| {
343			// when current best finalized is #10 and we're trying to import header#5 => tx is
344			// rejected
345			sync_to_header_10();
346			assert!(!validate_block_submit(5));
347		});
348	}
349
350	#[test]
351	fn extension_rejects_same_header() {
352		run_test(|| {
353			// when current best finalized is #10 and we're trying to import header#10 => tx is
354			// rejected
355			sync_to_header_10();
356			assert!(!validate_block_submit(10));
357		});
358	}
359
360	#[test]
361	fn extension_rejects_new_header_if_pallet_is_halted() {
362		run_test(|| {
363			// when pallet is halted => tx is rejected
364			sync_to_header_10();
365			PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
366
367			assert!(!validate_block_submit(15));
368		});
369	}
370
371	#[test]
372	fn extension_rejects_new_header_if_set_id_is_invalid() {
373		run_test(|| {
374			// when set id is different from the passed one => tx is rejected
375			sync_to_header_10();
376			let next_set = StoredAuthoritySet::<TestRuntime, ()>::try_new(vec![], 0x42).unwrap();
377			CurrentAuthoritySet::<TestRuntime, ()>::put(next_set);
378
379			assert!(!validate_block_submit(15));
380		});
381	}
382
383	#[test]
384	fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted(
385	) {
386		run_test(|| {
387			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
388				finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)),
389				justification: make_default_justification(&test_header(
390					10 + FreeHeadersInterval::get() as u64,
391				)),
392				current_set_id: 0,
393				is_free_execution_expected: true,
394			};
395			sync_to_header_10();
396
397			// when we can accept free headers => Ok
398			FreeHeadersRemaining::<TestRuntime, ()>::put(2);
399			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
400				bridge_grandpa_call.clone(),
401			),)
402			.is_ok());
403
404			// when we can NOT accept free headers => Err
405			FreeHeadersRemaining::<TestRuntime, ()>::put(0);
406			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
407				bridge_grandpa_call.clone(),
408			),)
409			.is_err());
410
411			// when called outside of transaction => Ok
412			FreeHeadersRemaining::<TestRuntime, ()>::kill();
413			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
414				bridge_grandpa_call,
415			),)
416			.is_ok());
417		})
418	}
419
420	#[test]
421	fn extension_rejects_new_header_if_it_overflow_size_limits() {
422		run_test(|| {
423			let mut large_finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
424			large_finality_target
425				.digest_mut()
426				.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
427			let justification_params = JustificationGeneratorParams {
428				header: large_finality_target.clone(),
429				..Default::default()
430			};
431			let large_justification = make_justification_for_header(justification_params);
432
433			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
434				finality_target: Box::new(large_finality_target),
435				justification: large_justification,
436				current_set_id: 0,
437				is_free_execution_expected: true,
438			};
439			sync_to_header_10();
440
441			// if overflow size limits => Err
442			FreeHeadersRemaining::<TestRuntime, ()>::put(2);
443			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
444				bridge_grandpa_call.clone(),
445			),)
446			.is_err());
447		})
448	}
449
450	#[test]
451	fn extension_rejects_new_header_if_it_overflow_weight_limits() {
452		run_test(|| {
453			let finality_target = test_header(10 + FreeHeadersInterval::get() as u64);
454			let justification_params = JustificationGeneratorParams {
455				header: finality_target.clone(),
456				ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
457				..Default::default()
458			};
459			let justification = make_justification_for_header(justification_params);
460
461			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
462				finality_target: Box::new(finality_target),
463				justification,
464				current_set_id: 0,
465				is_free_execution_expected: true,
466			};
467			sync_to_header_10();
468
469			// if overflow weight limits => Err
470			FreeHeadersRemaining::<TestRuntime, ()>::put(2);
471			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
472				bridge_grandpa_call.clone(),
473			),)
474			.is_err());
475		})
476	}
477
478	#[test]
479	fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected(
480	) {
481		run_test(|| {
482			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
483				finality_target: Box::new(test_header(100)),
484				justification: make_default_justification(&test_header(100)),
485				current_set_id: 0,
486				is_free_execution_expected: true,
487			};
488			sync_to_header_10();
489
490			// when `improved_by` is less than the free interval
491			BestFinalized::<TestRuntime, ()>::put(HeaderId(
492				100 - FreeHeadersInterval::get() as u64 + 1,
493				sp_core::H256::default(),
494			));
495			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
496				bridge_grandpa_call.clone(),
497			),)
498			.is_err());
499
500			// when `improved_by` is equal to the free interval
501			BestFinalized::<TestRuntime, ()>::put(HeaderId(
502				100 - FreeHeadersInterval::get() as u64,
503				sp_core::H256::default(),
504			));
505			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
506				bridge_grandpa_call.clone(),
507			),)
508			.is_ok());
509
510			// when `improved_by` is larger than the free interval
511			BestFinalized::<TestRuntime, ()>::put(HeaderId(
512				100 - FreeHeadersInterval::get() as u64 - 1,
513				sp_core::H256::default(),
514			));
515			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
516				bridge_grandpa_call.clone(),
517			),)
518			.is_ok());
519
520			// when `improved_by` is less than the free interval BUT it is a mandatory header
521			let mut mandatory_header = test_header(100);
522			let consensus_log = sp_consensus_grandpa::ConsensusLog::<TestNumber>::ScheduledChange(
523				sp_consensus_grandpa::ScheduledChange {
524					next_authorities: bp_test_utils::authority_list(),
525					delay: 0,
526				},
527			);
528			mandatory_header.digest = sp_runtime::Digest {
529				logs: vec![DigestItem::Consensus(
530					sp_consensus_grandpa::GRANDPA_ENGINE_ID,
531					consensus_log.encode(),
532				)],
533			};
534			let justification = make_justification_for_header(JustificationGeneratorParams {
535				header: mandatory_header.clone(),
536				set_id: 1,
537				..Default::default()
538			});
539			let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
540				finality_target: Box::new(mandatory_header),
541				justification,
542				current_set_id: 0,
543				is_free_execution_expected: true,
544			};
545			BestFinalized::<TestRuntime, ()>::put(HeaderId(
546				100 - FreeHeadersInterval::get() as u64 + 1,
547				sp_core::H256::default(),
548			));
549			assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
550				bridge_grandpa_call.clone(),
551			),)
552			.is_ok());
553		})
554	}
555
556	#[test]
557	fn extension_accepts_new_header() {
558		run_test(|| {
559			// when current best finalized is #10 and we're trying to import header#15 => tx is
560			// accepted
561			sync_to_header_10();
562			assert!(validate_block_submit(15));
563		});
564	}
565
566	#[test]
567	fn submit_finality_proof_info_is_parsed() {
568		// when `submit_finality_proof` is used, `current_set_id` is set to `None`
569		let deprecated_call =
570			RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof {
571				finality_target: Box::new(test_header(42)),
572				justification: make_default_justification(&test_header(42)),
573			});
574		assert_eq!(
575			deprecated_call.submit_finality_proof_info(),
576			Some(SubmitFinalityProofInfo {
577				block_number: 42,
578				current_set_id: None,
579				extra_weight: Weight::zero(),
580				extra_size: 0,
581				is_mandatory: false,
582				is_free_execution_expected: false,
583			})
584		);
585
586		// when `submit_finality_proof_ex` is used, `current_set_id` is set to `Some`
587		let deprecated_call =
588			RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
589				finality_target: Box::new(test_header(42)),
590				justification: make_default_justification(&test_header(42)),
591				current_set_id: 777,
592				is_free_execution_expected: false,
593			});
594		assert_eq!(
595			deprecated_call.submit_finality_proof_info(),
596			Some(SubmitFinalityProofInfo {
597				block_number: 42,
598				current_set_id: Some(777),
599				extra_weight: Weight::zero(),
600				extra_size: 0,
601				is_mandatory: false,
602				is_free_execution_expected: false,
603			})
604		);
605	}
606
607	#[test]
608	fn extension_returns_correct_extra_size_if_call_arguments_are_too_large() {
609		// when call arguments are below our limit => no refund
610		let small_finality_target = test_header(1);
611		let justification_params = JustificationGeneratorParams {
612			header: small_finality_target.clone(),
613			..Default::default()
614		};
615		let small_justification = make_justification_for_header(justification_params);
616		let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
617			finality_target: Box::new(small_finality_target),
618			justification: small_justification,
619			current_set_id: TEST_GRANDPA_SET_ID,
620			is_free_execution_expected: false,
621		});
622		assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0);
623
624		// when call arguments are too large => partial refund
625		let mut large_finality_target = test_header(1);
626		large_finality_target
627			.digest_mut()
628			.push(DigestItem::Other(vec![42u8; 1024 * 1024]));
629		let justification_params = JustificationGeneratorParams {
630			header: large_finality_target.clone(),
631			..Default::default()
632		};
633		let large_justification = make_justification_for_header(justification_params);
634		let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
635			finality_target: Box::new(large_finality_target),
636			justification: large_justification,
637			current_set_id: TEST_GRANDPA_SET_ID,
638			is_free_execution_expected: false,
639		});
640		assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0);
641	}
642
643	#[test]
644	fn extension_returns_correct_extra_weight_if_there_are_too_many_headers_in_votes_ancestry() {
645		let finality_target = test_header(1);
646		let mut justification_params = JustificationGeneratorParams {
647			header: finality_target.clone(),
648			ancestors: TestBridgedChain::REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY,
649			..Default::default()
650		};
651
652		// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY` headers => no refund
653		let justification = make_justification_for_header(justification_params.clone());
654		let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
655			finality_target: Box::new(finality_target.clone()),
656			justification,
657			current_set_id: TEST_GRANDPA_SET_ID,
658			is_free_execution_expected: false,
659		});
660		assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero());
661
662		// when there are `REASONABLE_HEADERS_IN_JUSTIFICATION_ANCESTRY + 1` headers => full refund
663		justification_params.ancestors += 1;
664		let justification = make_justification_for_header(justification_params);
665		let call_weight = <TestRuntime as Config>::WeightInfo::submit_finality_proof(
666			justification.commit.precommits.len().saturated_into(),
667			justification.votes_ancestries.len().saturated_into(),
668		);
669		let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex {
670			finality_target: Box::new(finality_target),
671			justification,
672			current_set_id: TEST_GRANDPA_SET_ID,
673			is_free_execution_expected: false,
674		});
675		assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight);
676	}
677
678	#[test]
679	fn check_obsolete_submit_finality_proof_returns_correct_improved_by() {
680		run_test(|| {
681			fn make_call(number: u64) -> RuntimeCall {
682				RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
683					finality_target: Box::new(test_header(number)),
684					justification: make_default_justification(&test_header(number)),
685					current_set_id: 0,
686					is_free_execution_expected: false,
687				})
688			}
689
690			sync_to_header_10();
691
692			// when the difference between headers is 1
693			assert_eq!(
694				RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11))
695					.unwrap()
696					.unwrap()
697					.improved_by,
698				1,
699			);
700
701			// when the difference between headers is 2
702			assert_eq!(
703				RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12))
704					.unwrap()
705					.unwrap()
706					.improved_by,
707				2,
708			);
709		})
710	}
711
712	#[test]
713	fn check_obsolete_submit_finality_proof_ignores_other_calls() {
714		run_test(|| {
715			let call =
716				RuntimeCall::System(frame_system::Call::<TestRuntime>::remark { remark: vec![42] });
717
718			assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None));
719		})
720	}
721}