diff --git a/bridges/modules/parachains/src/extension.rs b/bridges/modules/parachains/src/extension.rs index 3ed6dd9cbeda54cc1b8fd9a0684a1800fc740d93..18e718da0e46f7346fb665d94cd65ac739223715 100644 --- a/bridges/modules/parachains/src/extension.rs +++ b/bridges/modules/parachains/src/extension.rs @@ -17,7 +17,7 @@ use crate::{Config, Pallet, RelayBlockHash, RelayBlockHasher, RelayBlockNumber}; use bp_runtime::FilterCall; use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; -use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}; +use sp_runtime::transaction_validity::{TransactionValidity, ValidTransaction}; /// Validate parachain heads in order to avoid "mining" transactions that provide /// outdated bridged parachain heads. Without this validation, even honest relayers @@ -43,45 +43,27 @@ where >, { fn validate(call: &Call) -> TransactionValidity { - let (bundled_relay_block_number, parachains) = match call.is_sub_type() { + let (updated_at_relay_block_number, parachains) = match call.is_sub_type() { Some(crate::Call::<T, I>::submit_parachain_heads { ref at_relay_block, ref parachains, .. - }) if parachains.len() == 1 => (at_relay_block.0, parachains), + }) => (at_relay_block.0, parachains), + _ => return Ok(ValidTransaction::default()), + }; + let (parachain, parachain_head_hash) = match parachains.as_slice() { + &[(parachain, parachain_head_hash)] => (parachain, parachain_head_hash), _ => return Ok(ValidTransaction::default()), }; - let (parachain, parachain_head_hash) = - parachains.get(0).expect("verified by match condition; qed"); - let best_parachain_head = crate::BestParaHeads::<T, I>::get(parachain); - - match best_parachain_head { - Some(best_parachain_head) - if best_parachain_head.at_relay_block_number >= bundled_relay_block_number => - { - log::trace!( - target: crate::LOG_TARGET, - "Rejecting obsolete parachain-head {:?} transaction: \ - bundled relay block number: {:?} \ - best relay block number: {:?}", - parachain, - bundled_relay_block_number, - best_parachain_head.at_relay_block_number, - ); - InvalidTransaction::Stale.into() - }, - Some(best_parachain_head) if best_parachain_head.head_hash == *parachain_head_hash => { - log::trace!( - target: crate::LOG_TARGET, - "Rejecting obsolete parachain-head {:?} transaction: head hash {:?}", - parachain, - best_parachain_head.head_hash, - ); - InvalidTransaction::Stale.into() - }, - _ => Ok(ValidTransaction::default()), - } + let maybe_stored_best_head = crate::BestParaHeads::<T, I>::get(parachain); + Self::validate_updated_parachain_head( + parachain, + &maybe_stored_best_head, + updated_at_relay_block_number, + parachain_head_hash, + "Rejecting obsolete parachain-head transaction", + ) } } diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 05e14575eacf3f012a4977d25ff5bb41a7315bd2..7d12ee34e08cf809765fa7b70d45cb7fee05c603 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -373,6 +373,48 @@ pub mod pallet { storage.read_and_decode_value(parachain_head_key.0.as_ref()) } + /// Check if para head has been already updated at better relay chain block. + /// Without this check, we may import heads in random order. + pub fn validate_updated_parachain_head( + parachain: ParaId, + maybe_stored_best_head: &Option<BestParaHead>, + updated_at_relay_block_number: RelayBlockNumber, + updated_head_hash: ParaHash, + err_log_prefix: &str, + ) -> TransactionValidity { + let stored_best_head = match maybe_stored_best_head { + Some(stored_best_head) => stored_best_head, + None => return Ok(ValidTransaction::default()), + }; + + if stored_best_head.at_relay_block_number >= updated_at_relay_block_number { + log::trace!( + target: LOG_TARGET, + "{}. The parachain head for {:?} was already updated at better relay chain block {} >= {}.", + err_log_prefix, + parachain, + stored_best_head.at_relay_block_number, + updated_at_relay_block_number + ); + return InvalidTransaction::Stale.into() + } + + if stored_best_head.head_hash == updated_head_hash { + log::trace!( + target: LOG_TARGET, + "{}. The parachain head hash for {:?} was already updated to {} at block {} < {}.", + err_log_prefix, + parachain, + updated_head_hash, + stored_best_head.at_relay_block_number, + updated_at_relay_block_number + ); + return InvalidTransaction::Stale.into() + } + + Ok(ValidTransaction::default()) + } + /// Try to update parachain head. pub(super) fn update_parachain_head( parachain: ParaId, @@ -383,41 +425,16 @@ pub mod pallet { ) -> Result<UpdateParachainHeadArtifacts, ()> { // check if head has been already updated at better relay chain block. Without this // check, we may import heads in random order - let next_imported_hash_position = match stored_best_head { - Some(stored_best_head) - if stored_best_head.at_relay_block_number <= updated_at_relay_block_number => - { - // check if this head has already been imported before - if updated_head_hash == stored_best_head.head_hash { - log::trace!( - target: LOG_TARGET, - "The head of parachain {:?} can't be updated to {}, because it has been already updated \ - to the same value at previous relay chain block: {} < {}", - parachain, - updated_head_hash, - stored_best_head.at_relay_block_number, - updated_at_relay_block_number, - ); - return Err(()) - } - - stored_best_head.next_imported_hash_position - }, - None => 0, - Some(stored_best_head) => { - log::trace!( - target: LOG_TARGET, - "The head of parachain {:?} can't be updated to {}, because it has been already updated \ - to {} at better relay chain block: {} > {}", - parachain, - updated_head_hash, - stored_best_head.head_hash, - stored_best_head.at_relay_block_number, - updated_at_relay_block_number, - ); - return Err(()) - }, - }; + Self::validate_updated_parachain_head( + parachain, + &stored_best_head, + updated_at_relay_block_number, + updated_head_hash, + "The parachain head can't be updated", + ) + .map_err(|_| ())?; + let next_imported_hash_position = stored_best_head + .map_or(0, |stored_best_head| stored_best_head.next_imported_hash_position); // insert updated best parachain head let head_hash_to_prune = diff --git a/bridges/relays/lib-substrate-relay/src/parachains/source.rs b/bridges/relays/lib-substrate-relay/src/parachains/source.rs index ecc940467035ee236a29dcddfb108fbf695cbb1d..ea647c740d10fab4115b9914ba41ff909939fe95 100644 --- a/bridges/relays/lib-substrate-relay/src/parachains/source.rs +++ b/bridges/relays/lib-substrate-relay/src/parachains/source.rs @@ -161,11 +161,11 @@ where at_block: HeaderIdOf<P::SourceRelayChain>, parachains: &[ParaId], ) -> Result<(ParaHeadsProof, Vec<ParaHash>), Self::Error> { - if parachains.len() != 1 || parachains[0].0 != P::SOURCE_PARACHAIN_PARA_ID { + let parachain = ParaId(P::SOURCE_PARACHAIN_PARA_ID); + if parachains != [parachain] { return Err(SubstrateError::Custom(format!( "Trying to prove unexpected parachains {:?}. Expected {:?}", - parachains, - P::SOURCE_PARACHAIN_PARA_ID, + parachains, parachain, ))) }