1use crate::{Config, GrandpaPalletOf, Pallet, RelayBlockNumber};
18use bp_header_chain::HeaderChain;
19use bp_parachains::{BestParaHeadHash, SubmitParachainHeadsInfo};
20use bp_runtime::{HeaderId, OwnedBridgeModule};
21use frame_support::{
22 dispatch::CallableCallFor,
23 traits::{Get, IsSubType},
24};
25use pallet_bridge_grandpa::SubmitFinalityProofHelper;
26use sp_runtime::{
27 traits::Zero,
28 transaction_validity::{InvalidTransaction, TransactionValidityError},
29 Debug,
30};
31
32#[derive(PartialEq, Debug)]
34pub struct VerifiedSubmitParachainHeadsInfo {
35 pub base: SubmitParachainHeadsInfo,
37 pub improved_by: RelayBlockNumber,
40}
41
42pub struct SubmitParachainHeadsHelper<T: Config<I>, I: 'static> {
44 _phantom_data: sp_std::marker::PhantomData<(T, I)>,
45}
46
47impl<T: Config<I>, I: 'static> SubmitParachainHeadsHelper<T, I> {
48 pub fn check_obsolete_from_extension(
51 update: &SubmitParachainHeadsInfo,
52 ) -> Result<RelayBlockNumber, TransactionValidityError> {
53 let improved_by = Self::check_obsolete(update)?;
55
56 if !update.is_free_execution_expected {
58 return Ok(improved_by);
59 }
60
61 if !SubmitFinalityProofHelper::<T, T::BridgesGrandpaPalletInstance>::has_free_header_slots()
63 {
64 tracing::trace!(
65 target: crate::LOG_TARGET,
66 para_id=?update.para_id,
67 "The free parachain head can't be updated: no more free slots left in the block."
68 );
69
70 return Err(InvalidTransaction::Call.into());
71 }
72
73 let free_headers_interval = match T::FreeHeadersInterval::get() {
76 Some(free_headers_interval) => free_headers_interval,
77 None => return Ok(improved_by),
78 };
79
80 if improved_by < free_headers_interval {
82 tracing::trace!(
83 target: crate::LOG_TARGET,
84 para_id=?update.para_id,
85 %improved_by,
86 "The free parachain head can't be updated: it improves previous
87 best head while at least {free_headers_interval} is expected."
88 );
89
90 return Err(InvalidTransaction::Stale.into());
91 }
92
93 Ok(improved_by)
94 }
95
96 pub fn check_obsolete(
99 update: &SubmitParachainHeadsInfo,
100 ) -> Result<RelayBlockNumber, TransactionValidityError> {
101 let improved_by = match crate::ParasInfo::<T, I>::get(update.para_id) {
103 Some(stored_best_head) => {
104 let improved_by = match update
105 .at_relay_block
106 .0
107 .checked_sub(stored_best_head.best_head_hash.at_relay_block_number)
108 {
109 Some(improved_by) if improved_by > Zero::zero() => improved_by,
110 _ => {
111 tracing::trace!(
112 target: crate::LOG_TARGET,
113 para_id=?update.para_id,
114 "The parachain head can't be updated. The parachain head \
115 was already updated at better relay chain block {} >= {}.",
116 stored_best_head.best_head_hash.at_relay_block_number,
117 update.at_relay_block.0
118 );
119 return Err(InvalidTransaction::Stale.into());
120 },
121 };
122
123 if stored_best_head.best_head_hash.head_hash == update.para_head_hash {
124 tracing::trace!(
125 target: crate::LOG_TARGET,
126 para_id=?update.para_id,
127 para_head_hash=%update.para_head_hash,
128 "The parachain head can't be updated. The parachain head hash \
129 was already updated at block {} < {}.",
130 stored_best_head.best_head_hash.at_relay_block_number,
131 update.at_relay_block.0
132 );
133 return Err(InvalidTransaction::Stale.into());
134 }
135
136 improved_by
137 },
138 None => RelayBlockNumber::MAX,
139 };
140
141 if GrandpaPalletOf::<T, I>::finalized_header_state_root(update.at_relay_block.1).is_none() {
144 tracing::trace!(
145 target: crate::LOG_TARGET,
146 para_id=?update.para_id,
147 at_relay_block=?update.at_relay_block,
148 "The parachain head can't be updated. Relay chain header used to create \
149 parachain proof is missing from the storage."
150 );
151
152 return Err(InvalidTransaction::Call.into());
153 }
154
155 Ok(improved_by)
156 }
157
158 pub fn was_successful(update: &SubmitParachainHeadsInfo) -> bool {
160 match crate::ParasInfo::<T, I>::get(update.para_id) {
161 Some(stored_best_head) => {
162 stored_best_head.best_head_hash ==
163 BestParaHeadHash {
164 at_relay_block_number: update.at_relay_block.0,
165 head_hash: update.para_head_hash,
166 }
167 },
168 None => false,
169 }
170 }
171}
172
173pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
175 IsSubType<CallableCallFor<Pallet<T, I>, T>>
176{
177 fn one_entry_submit_parachain_heads_info(&self) -> Option<SubmitParachainHeadsInfo> {
180 match self.is_sub_type() {
181 Some(crate::Call::<T, I>::submit_parachain_heads {
182 ref at_relay_block,
183 ref parachains,
184 ..
185 }) => match ¶chains[..] {
186 &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo {
187 at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
188 para_id,
189 para_head_hash,
190 is_free_execution_expected: false,
191 }),
192 _ => None,
193 },
194 Some(crate::Call::<T, I>::submit_parachain_heads_ex {
195 ref at_relay_block,
196 ref parachains,
197 is_free_execution_expected,
198 ..
199 }) => match ¶chains[..] {
200 &[(para_id, para_head_hash)] => Some(SubmitParachainHeadsInfo {
201 at_relay_block: HeaderId(at_relay_block.0, at_relay_block.1),
202 para_id,
203 para_head_hash,
204 is_free_execution_expected: *is_free_execution_expected,
205 }),
206 _ => None,
207 },
208 _ => None,
209 }
210 }
211
212 fn submit_parachain_heads_info_for(&self, para_id: u32) -> Option<SubmitParachainHeadsInfo> {
215 self.one_entry_submit_parachain_heads_info()
216 .filter(|update| update.para_id.0 == para_id)
217 }
218
219 fn check_obsolete_submit_parachain_heads(
230 &self,
231 ) -> Result<Option<VerifiedSubmitParachainHeadsInfo>, TransactionValidityError>
232 where
233 Self: Sized,
234 {
235 let update = match self.one_entry_submit_parachain_heads_info() {
236 Some(update) => update,
237 None => return Ok(None),
238 };
239
240 if Pallet::<T, I>::ensure_not_halted().is_err() {
241 return Err(InvalidTransaction::Call.into());
242 }
243
244 SubmitParachainHeadsHelper::<T, I>::check_obsolete_from_extension(&update)
245 .map(|improved_by| Some(VerifiedSubmitParachainHeadsInfo { base: update, improved_by }))
246 }
247}
248
249impl<T, I: 'static> CallSubType<T, I> for T::RuntimeCall
250where
251 T: Config<I>,
252 T::RuntimeCall: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
253{
254}
255
256#[cfg(test)]
257mod tests {
258 use crate::{
259 mock::{run_test, FreeHeadersInterval, RuntimeCall, TestRuntime},
260 CallSubType, PalletOperatingMode, ParaInfo, ParasInfo, RelayBlockHash, RelayBlockNumber,
261 };
262 use bp_header_chain::StoredHeaderData;
263 use bp_parachains::BestParaHeadHash;
264 use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
265 use bp_runtime::BasicOperatingMode;
266
267 fn validate_submit_parachain_heads(
268 num: RelayBlockNumber,
269 parachains: Vec<(ParaId, ParaHash)>,
270 ) -> bool {
271 RuntimeCall::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads_ex {
272 at_relay_block: (num, [num as u8; 32].into()),
273 parachains,
274 parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
275 is_free_execution_expected: false,
276 })
277 .check_obsolete_submit_parachain_heads()
278 .is_ok()
279 }
280
281 fn validate_free_submit_parachain_heads(
282 num: RelayBlockNumber,
283 parachains: Vec<(ParaId, ParaHash)>,
284 ) -> bool {
285 RuntimeCall::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads_ex {
286 at_relay_block: (num, [num as u8; 32].into()),
287 parachains,
288 parachain_heads_proof: ParaHeadsProof { storage_proof: Default::default() },
289 is_free_execution_expected: true,
290 })
291 .check_obsolete_submit_parachain_heads()
292 .is_ok()
293 }
294
295 fn insert_relay_block(num: RelayBlockNumber) {
296 pallet_bridge_grandpa::ImportedHeaders::<TestRuntime, crate::Instance1>::insert(
297 RelayBlockHash::from([num as u8; 32]),
298 StoredHeaderData { number: num, state_root: RelayBlockHash::from([10u8; 32]) },
299 );
300 }
301
302 fn sync_to_relay_header_10() {
303 ParasInfo::<TestRuntime, ()>::insert(
304 ParaId(1),
305 ParaInfo {
306 best_head_hash: BestParaHeadHash {
307 at_relay_block_number: 10,
308 head_hash: [1u8; 32].into(),
309 },
310 next_imported_hash_position: 0,
311 },
312 );
313 }
314
315 #[test]
316 fn extension_rejects_header_from_the_obsolete_relay_block() {
317 run_test(|| {
318 sync_to_relay_header_10();
321 assert!(!validate_submit_parachain_heads(5, vec![(ParaId(1), [1u8; 32].into())]));
322 });
323 }
324
325 #[test]
326 fn extension_rejects_header_from_the_same_relay_block() {
327 run_test(|| {
328 sync_to_relay_header_10();
331 assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
332 });
333 }
334
335 #[test]
336 fn extension_rejects_header_from_new_relay_block_with_same_hash() {
337 run_test(|| {
338 sync_to_relay_header_10();
341 assert!(!validate_submit_parachain_heads(20, vec![(ParaId(1), [1u8; 32].into())]));
342 });
343 }
344
345 #[test]
346 fn extension_rejects_header_if_pallet_is_halted() {
347 run_test(|| {
348 sync_to_relay_header_10();
350 PalletOperatingMode::<TestRuntime, ()>::put(BasicOperatingMode::Halted);
351
352 assert!(!validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
353 });
354 }
355
356 #[test]
357 fn extension_accepts_new_header() {
358 run_test(|| {
359 sync_to_relay_header_10();
362 insert_relay_block(15);
363 assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
364 });
365 }
366
367 #[test]
368 fn extension_accepts_if_more_than_one_parachain_is_submitted() {
369 run_test(|| {
370 sync_to_relay_header_10();
373 assert!(validate_submit_parachain_heads(
374 5,
375 vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())]
376 ));
377 });
378 }
379
380 #[test]
381 fn extension_rejects_initial_parachain_head_if_missing_relay_chain_header() {
382 run_test(|| {
383 assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
385 insert_relay_block(10);
387 assert!(validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
388 });
389 }
390
391 #[test]
392 fn extension_rejects_free_parachain_head_if_missing_relay_chain_header() {
393 run_test(|| {
394 sync_to_relay_header_10();
395 assert!(!validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
397 insert_relay_block(15);
399 assert!(validate_submit_parachain_heads(15, vec![(ParaId(2), [15u8; 32].into())]));
400 });
401 }
402
403 #[test]
404 fn extension_rejects_free_parachain_head_if_no_free_slots_remaining() {
405 run_test(|| {
406 sync_to_relay_header_10();
409 insert_relay_block(15);
410 assert!(!validate_free_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
413 assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
415 });
416 }
417
418 #[test]
419 fn extension_rejects_free_parachain_head_if_improves_by_is_below_expected() {
420 run_test(|| {
421 sync_to_relay_header_10();
424 insert_relay_block(10 + FreeHeadersInterval::get() - 1);
425 insert_relay_block(10 + FreeHeadersInterval::get());
426 let relay_header = 10 + FreeHeadersInterval::get() - 1;
428 assert!(!validate_free_submit_parachain_heads(
429 relay_header,
430 vec![(ParaId(1), [2u8; 32].into())]
431 ));
432 let relay_header = 10 + FreeHeadersInterval::get();
434 assert!(validate_free_submit_parachain_heads(
435 relay_header,
436 vec![(ParaId(1), [2u8; 32].into())]
437 ));
438 });
439 }
440}