// Copyright 2021 Parity Technologies (UK) Ltd. // This file is part of Parity Bridges Common. // Parity Bridges Common is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity Bridges Common is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . use crate::{Config, Pallet}; use bp_runtime::FilterCall; use frame_support::{dispatch::CallableCallFor, traits::IsSubType}; use sp_runtime::{ traits::Header, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, }; /// Validate Grandpa headers in order to avoid "mining" transactions that provide outdated /// bridged chain headers. Without this validation, even honest relayers may lose their funds /// if there are multiple relays running and submitting the same information. impl< Call: IsSubType, T>>, T: frame_system::Config + Config, I: 'static, > FilterCall for Pallet { fn validate(call: &::RuntimeCall) -> TransactionValidity { let bundled_block_number = match call.is_sub_type() { Some(crate::Call::::submit_finality_proof { ref finality_target, .. }) => *finality_target.number(), _ => return Ok(ValidTransaction::default()), }; let best_finalized = crate::BestFinalized::::get(); let best_finalized_number = match best_finalized { Some(best_finalized_id) => best_finalized_id.number(), None => return InvalidTransaction::Call.into(), }; if best_finalized_number >= bundled_block_number { log::trace!( target: crate::LOG_TARGET, "Rejecting obsolete bridged header: bundled {:?}, best {:?}", bundled_block_number, best_finalized_number, ); return InvalidTransaction::Stale.into() } Ok(ValidTransaction::default()) } } #[cfg(test)] mod tests { use super::FilterCall; use crate::{ mock::{run_test, test_header, RuntimeCall, TestNumber, TestRuntime}, BestFinalized, }; use bp_runtime::HeaderId; use bp_test_utils::make_default_justification; fn validate_block_submit(num: TestNumber) -> bool { crate::Pallet::::validate(&RuntimeCall::Grandpa(crate::Call::< TestRuntime, (), >::submit_finality_proof { finality_target: Box::new(test_header(num)), justification: make_default_justification(&test_header(num)), })) .is_ok() } fn sync_to_header_10() { let header10_hash = sp_core::H256::default(); BestFinalized::::put(HeaderId(10, header10_hash)); } #[test] fn extension_rejects_obsolete_header() { run_test(|| { // when current best finalized is #10 and we're trying to import header#5 => tx is // rejected sync_to_header_10(); assert!(!validate_block_submit(5)); }); } #[test] fn extension_rejects_same_header() { run_test(|| { // when current best finalized is #10 and we're trying to import header#10 => tx is // rejected sync_to_header_10(); assert!(!validate_block_submit(10)); }); } #[test] fn extension_accepts_new_header() { run_test(|| { // when current best finalized is #10 and we're trying to import header#15 => tx is // accepted sync_to_header_10(); assert!(validate_block_submit(15)); }); } }