// 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, Error, Pallet};
use bp_runtime::BlockNumberOf;
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
use sp_runtime::{
traits::Header,
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
};
/// Helper struct that provides methods for working with the `SubmitFinalityProof` call.
pub struct SubmitFinalityProofHelper, I: 'static> {
pub _phantom_data: sp_std::marker::PhantomData<(T, I)>,
}
impl, I: 'static> SubmitFinalityProofHelper {
/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
/// one we know.
pub fn check_obsolete(
finality_target: BlockNumberOf,
) -> Result<(), Error> {
let best_finalized = crate::BestFinalized::::get().ok_or_else(|| {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize header {:?} because pallet is not yet initialized",
finality_target,
);
>::NotInitialized
})?;
if best_finalized.number() >= finality_target {
log::trace!(
target: crate::LOG_TARGET,
"Cannot finalize obsolete header: bundled {:?}, best {:?}",
finality_target,
best_finalized,
);
return Err(Error::::OldHeader)
}
Ok(())
}
/// Check if the `SubmitFinalityProof` was successfully executed.
pub fn was_successful(finality_target: BlockNumberOf) -> bool {
match crate::BestFinalized::::get() {
Some(best_finalized) => best_finalized.number() == finality_target,
None => false,
}
}
}
/// Trait representing a call that is a sub type of this pallet's call.
pub trait CallSubType, I: 'static>:
IsSubType, T>>
{
/// Extract the finality target from a `SubmitParachainHeads` call.
fn submit_finality_proof_info(&self) -> Option> {
if let Some(crate::Call::::submit_finality_proof { finality_target, .. }) =
self.is_sub_type()
{
return Some(*finality_target.number())
}
None
}
/// 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.
fn check_obsolete_submit_finality_proof(&self) -> TransactionValidity
where
Self: Sized,
{
let finality_target = match self.submit_finality_proof_info() {
Some(finality_proof) => finality_proof,
_ => return Ok(ValidTransaction::default()),
};
match SubmitFinalityProofHelper::::check_obsolete(finality_target) {
Ok(_) => Ok(ValidTransaction::default()),
Err(Error::::OldHeader) => InvalidTransaction::Stale.into(),
Err(_) => InvalidTransaction::Call.into(),
}
}
}
impl, I: 'static> CallSubType for T::RuntimeCall where
T::RuntimeCall: IsSubType, T>>
{
}
#[cfg(test)]
mod tests {
use crate::{
call_ext::CallSubType,
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 {
let bridge_grandpa_call = crate::Call::::submit_finality_proof {
finality_target: Box::new(test_header(num)),
justification: make_default_justification(&test_header(num)),
};
RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
bridge_grandpa_call,
))
.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));
});
}
}