mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-17 05:25:42 +00:00
Bridge: make some headers submissions free (#4102)
supersedes https://github.com/paritytech/parity-bridges-common/pull/2873 Draft because of couple of TODOs: - [x] fix remaining TODOs; - [x] double check that all changes from https://github.com/paritytech/parity-bridges-common/pull/2873 are correctly ported; - [x] create a separate PR (on top of that one or a follow up?) for https://github.com/paritytech/polkadot-sdk/tree/sv-try-new-bridge-fees; - [x] fix compilation issues (haven't checked, but there should be many). --------- Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
committed by
GitHub
parent
4f3d43a0c4
commit
a633e954f3
@@ -15,20 +15,24 @@
|
||||
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{
|
||||
weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error,
|
||||
Pallet,
|
||||
weights::WeightInfo, BestFinalized, BridgedBlockNumber, BridgedHeader, Config,
|
||||
CurrentAuthoritySet, Error, FreeHeadersRemaining, Pallet,
|
||||
};
|
||||
use bp_header_chain::{
|
||||
justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size,
|
||||
ChainWithGrandpa, GrandpaConsensusLogReader,
|
||||
};
|
||||
use bp_runtime::{BlockNumberOf, OwnedBridgeModule};
|
||||
use bp_runtime::{BlockNumberOf, Chain, OwnedBridgeModule};
|
||||
use codec::Encode;
|
||||
use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight};
|
||||
use frame_support::{
|
||||
dispatch::CallableCallFor,
|
||||
traits::{Get, IsSubType},
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_consensus_grandpa::SetId;
|
||||
use sp_runtime::{
|
||||
traits::{Header, Zero},
|
||||
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
|
||||
traits::{CheckedSub, Header, Zero},
|
||||
transaction_validity::{InvalidTransaction, TransactionValidityError},
|
||||
RuntimeDebug, SaturatedConversion,
|
||||
};
|
||||
|
||||
@@ -40,6 +44,11 @@ pub struct SubmitFinalityProofInfo<N> {
|
||||
/// An identifier of the validators set that has signed the submitted justification.
|
||||
/// It might be `None` if deprecated version of the `submit_finality_proof` is used.
|
||||
pub current_set_id: Option<SetId>,
|
||||
/// If `true`, then the call proves new **mandatory** header.
|
||||
pub is_mandatory: bool,
|
||||
/// If `true`, then the call must be free (assuming that everything else is valid) to
|
||||
/// be treated as valid.
|
||||
pub is_free_execution_expected: bool,
|
||||
/// Extra weight that we assume is included in the call.
|
||||
///
|
||||
/// We have some assumptions about headers and justifications of the bridged chain.
|
||||
@@ -54,6 +63,16 @@ pub struct SubmitFinalityProofInfo<N> {
|
||||
pub extra_size: u32,
|
||||
}
|
||||
|
||||
/// Verified `SubmitFinalityProofInfo<N>`.
|
||||
#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
|
||||
pub struct VerifiedSubmitFinalityProofInfo<N> {
|
||||
/// Base call information.
|
||||
pub base: SubmitFinalityProofInfo<N>,
|
||||
/// A difference between bundled bridged header and best bridged header known to us
|
||||
/// before the call.
|
||||
pub improved_by: N,
|
||||
}
|
||||
|
||||
impl<N> SubmitFinalityProofInfo<N> {
|
||||
/// Returns `true` if call size/weight is below our estimations for regular calls.
|
||||
pub fn fits_limits(&self) -> bool {
|
||||
@@ -67,14 +86,86 @@ pub struct SubmitFinalityProofHelper<T: Config<I>, I: 'static> {
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
|
||||
/// Returns `true` if we may fit more free headers into the current block. If `false` is
|
||||
/// returned, the call will be paid even if `is_free_execution_expected` has been set
|
||||
/// to `true`.
|
||||
pub fn has_free_header_slots() -> bool {
|
||||
// `unwrap_or(u32::MAX)` means that if `FreeHeadersRemaining` is `None`, we may accept
|
||||
// this header for free. That is a small cheat - it is `None` if executed outside of
|
||||
// transaction (e.g. during block initialization). Normal relayer would never submit
|
||||
// such calls, but if he did, that is not our problem. During normal transactions,
|
||||
// the `FreeHeadersRemaining` is always `Some(_)`.
|
||||
let free_headers_remaining = FreeHeadersRemaining::<T, I>::get().unwrap_or(u32::MAX);
|
||||
free_headers_remaining > 0
|
||||
}
|
||||
|
||||
/// Check that the: (1) GRANDPA head provided by the `SubmitFinalityProof` is better than the
|
||||
/// best one we know (2) if `current_set_id` matches the current authority set id, if specified
|
||||
/// and (3) whether transaction MAY be free for the submitter if `is_free_execution_expected`
|
||||
/// is `true`.
|
||||
///
|
||||
/// Returns number of headers between the current best finalized header, known to the pallet
|
||||
/// and the bundled header.
|
||||
pub fn check_obsolete_from_extension(
|
||||
call_info: &SubmitFinalityProofInfo<BlockNumberOf<T::BridgedChain>>,
|
||||
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
|
||||
// do basic checks first
|
||||
let improved_by = Self::check_obsolete(call_info.block_number, call_info.current_set_id)?;
|
||||
|
||||
// if submitter has NOT specified that it wants free execution, then we are done
|
||||
if !call_info.is_free_execution_expected {
|
||||
return Ok(improved_by);
|
||||
}
|
||||
|
||||
// else - if we can not accept more free headers, "reject" the transaction
|
||||
if !Self::has_free_header_slots() {
|
||||
log::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
"Cannot accept free {:?} header {:?}. No more free slots remaining",
|
||||
T::BridgedChain::ID,
|
||||
call_info.block_number,
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::FreeHeadersLimitExceded);
|
||||
}
|
||||
|
||||
// ensure that the `improved_by` is larger than the configured free interval
|
||||
if !call_info.is_mandatory {
|
||||
if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
|
||||
if improved_by < free_headers_interval.into() {
|
||||
log::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
"Cannot accept free {:?} header {:?}. Too small difference \
|
||||
between submitted headers: {:?} vs {}",
|
||||
T::BridgedChain::ID,
|
||||
call_info.block_number,
|
||||
improved_by,
|
||||
free_headers_interval,
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::BelowFreeHeaderInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we do not check whether the header matches free submission criteria here - it is the
|
||||
// relayer responsibility to check that
|
||||
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
|
||||
/// one we know. Additionally, checks if `current_set_id` matches the current authority set
|
||||
/// id, if specified.
|
||||
/// id, if specified. This method is called by the call code and the transaction extension,
|
||||
/// so it does not check the free execution.
|
||||
///
|
||||
/// Returns number of headers between the current best finalized header, known to the pallet
|
||||
/// and the bundled header.
|
||||
pub fn check_obsolete(
|
||||
finality_target: BlockNumberOf<T::BridgedChain>,
|
||||
current_set_id: Option<SetId>,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
let best_finalized = crate::BestFinalized::<T, I>::get().ok_or_else(|| {
|
||||
) -> Result<BlockNumberOf<T::BridgedChain>, Error<T, I>> {
|
||||
let best_finalized = BestFinalized::<T, I>::get().ok_or_else(|| {
|
||||
log::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
"Cannot finalize header {:?} because pallet is not yet initialized",
|
||||
@@ -83,16 +174,19 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
|
||||
<Error<T, I>>::NotInitialized
|
||||
})?;
|
||||
|
||||
if best_finalized.number() >= finality_target {
|
||||
log::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
"Cannot finalize obsolete header: bundled {:?}, best {:?}",
|
||||
finality_target,
|
||||
best_finalized,
|
||||
);
|
||||
let improved_by = match finality_target.checked_sub(&best_finalized.number()) {
|
||||
Some(improved_by) if improved_by > Zero::zero() => improved_by,
|
||||
_ => {
|
||||
log::trace!(
|
||||
target: crate::LOG_TARGET,
|
||||
"Cannot finalize obsolete header: bundled {:?}, best {:?}",
|
||||
finality_target,
|
||||
best_finalized,
|
||||
);
|
||||
|
||||
return Err(Error::<T, I>::OldHeader)
|
||||
}
|
||||
return Err(Error::<T, I>::OldHeader)
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(current_set_id) = current_set_id {
|
||||
let actual_set_id = <CurrentAuthoritySet<T, I>>::get().set_id;
|
||||
@@ -108,12 +202,12 @@ impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(improved_by)
|
||||
}
|
||||
|
||||
/// Check if the `SubmitFinalityProof` was successfully executed.
|
||||
pub fn was_successful(finality_target: BlockNumberOf<T::BridgedChain>) -> bool {
|
||||
match crate::BestFinalized::<T, I>::get() {
|
||||
match BestFinalized::<T, I>::get() {
|
||||
Some(best_finalized) => best_finalized.number() == finality_target,
|
||||
None => false,
|
||||
}
|
||||
@@ -135,17 +229,20 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
finality_target,
|
||||
justification,
|
||||
None,
|
||||
false,
|
||||
))
|
||||
} else if let Some(crate::Call::<T, I>::submit_finality_proof_ex {
|
||||
finality_target,
|
||||
justification,
|
||||
current_set_id,
|
||||
is_free_execution_expected,
|
||||
}) = self.is_sub_type()
|
||||
{
|
||||
return Some(submit_finality_proof_info_from_args::<T, I>(
|
||||
finality_target,
|
||||
justification,
|
||||
Some(*current_set_id),
|
||||
*is_free_execution_expected,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -155,26 +252,36 @@ pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||
/// 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
|
||||
///
|
||||
/// Returns `Ok(None)` if the call is not the `submit_finality_proof` call of our pallet.
|
||||
/// Returns `Ok(Some(_))` if the call is the `submit_finality_proof` call of our pallet and
|
||||
/// we believe the call brings header that improves the pallet state.
|
||||
/// Returns `Err(_)` if the call is the `submit_finality_proof` call of our pallet and we
|
||||
/// believe that the call will fail.
|
||||
fn check_obsolete_submit_finality_proof(
|
||||
&self,
|
||||
) -> Result<
|
||||
Option<VerifiedSubmitFinalityProofInfo<BridgedBlockNumber<T, I>>>,
|
||||
TransactionValidityError,
|
||||
>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let finality_target = match self.submit_finality_proof_info() {
|
||||
let call_info = match self.submit_finality_proof_info() {
|
||||
Some(finality_proof) => finality_proof,
|
||||
_ => return Ok(ValidTransaction::default()),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
if Pallet::<T, I>::ensure_not_halted().is_err() {
|
||||
return InvalidTransaction::Call.into()
|
||||
return Err(InvalidTransaction::Call.into())
|
||||
}
|
||||
|
||||
match SubmitFinalityProofHelper::<T, I>::check_obsolete(
|
||||
finality_target.block_number,
|
||||
finality_target.current_set_id,
|
||||
) {
|
||||
Ok(_) => Ok(ValidTransaction::default()),
|
||||
Err(Error::<T, I>::OldHeader) => InvalidTransaction::Stale.into(),
|
||||
Err(_) => InvalidTransaction::Call.into(),
|
||||
let result = SubmitFinalityProofHelper::<T, I>::check_obsolete_from_extension(&call_info);
|
||||
match result {
|
||||
Ok(improved_by) =>
|
||||
Ok(Some(VerifiedSubmitFinalityProofInfo { base: call_info, improved_by })),
|
||||
Err(Error::<T, I>::OldHeader) => Err(InvalidTransaction::Stale.into()),
|
||||
Err(_) => Err(InvalidTransaction::Call.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,6 +296,7 @@ pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
|
||||
finality_target: &BridgedHeader<T, I>,
|
||||
justification: &GrandpaJustification<BridgedHeader<T, I>>,
|
||||
current_set_id: Option<SetId>,
|
||||
is_free_execution_expected: bool,
|
||||
) -> SubmitFinalityProofInfo<BridgedBlockNumber<T, I>> {
|
||||
let block_number = *finality_target.number();
|
||||
|
||||
@@ -230,16 +338,26 @@ pub(crate) fn submit_finality_proof_info_from_args<T: Config<I>, I: 'static>(
|
||||
);
|
||||
let extra_size = actual_call_size.saturating_sub(max_expected_call_size);
|
||||
|
||||
SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size }
|
||||
SubmitFinalityProofInfo {
|
||||
block_number,
|
||||
current_set_id,
|
||||
is_mandatory: is_mandatory_finality_target,
|
||||
is_free_execution_expected,
|
||||
extra_weight,
|
||||
extra_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
call_ext::CallSubType,
|
||||
mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime},
|
||||
BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet,
|
||||
SubmitFinalityProofInfo, WeightInfo,
|
||||
mock::{
|
||||
run_test, test_header, FreeHeadersInterval, RuntimeCall, TestBridgedChain, TestNumber,
|
||||
TestRuntime,
|
||||
},
|
||||
BestFinalized, Config, CurrentAuthoritySet, FreeHeadersRemaining, PalletOperatingMode,
|
||||
StoredAuthoritySet, SubmitFinalityProofInfo, WeightInfo,
|
||||
};
|
||||
use bp_header_chain::ChainWithGrandpa;
|
||||
use bp_runtime::{BasicOperatingMode, HeaderId};
|
||||
@@ -247,6 +365,7 @@ mod tests {
|
||||
make_default_justification, make_justification_for_header, JustificationGeneratorParams,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
};
|
||||
use codec::Encode;
|
||||
use frame_support::weights::Weight;
|
||||
use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion};
|
||||
|
||||
@@ -256,6 +375,7 @@ mod tests {
|
||||
justification: make_default_justification(&test_header(num)),
|
||||
// not initialized => zero
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: false,
|
||||
};
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call,
|
||||
@@ -311,6 +431,121 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_free_execution_is_requested_and_free_submissions_are_not_accepted(
|
||||
) {
|
||||
run_test(|| {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(10 + FreeHeadersInterval::get() as u64)),
|
||||
justification: make_default_justification(&test_header(
|
||||
10 + FreeHeadersInterval::get() as u64,
|
||||
)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// when we can accept free headers => Ok
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(2);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when we can NOT accept free headers => Err
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::put(0);
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
|
||||
// when called outside of transaction => Ok
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::kill();
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call,
|
||||
),)
|
||||
.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_rejects_new_header_if_free_execution_is_requested_and_improved_by_is_below_expected(
|
||||
) {
|
||||
run_test(|| {
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(100)),
|
||||
justification: make_default_justification(&test_header(100)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
sync_to_header_10();
|
||||
|
||||
// when `improved_by` is less than the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 + 1,
|
||||
sp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_err());
|
||||
|
||||
// when `improved_by` is equal to the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64,
|
||||
sp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when `improved_by` is larger than the free interval
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 - 1,
|
||||
sp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
|
||||
// when `improved_by` is less than the free interval BUT it is a mandatory header
|
||||
let mut mandatory_header = test_header(100);
|
||||
let consensus_log = sp_consensus_grandpa::ConsensusLog::<TestNumber>::ScheduledChange(
|
||||
sp_consensus_grandpa::ScheduledChange {
|
||||
next_authorities: bp_test_utils::authority_list(),
|
||||
delay: 0,
|
||||
},
|
||||
);
|
||||
mandatory_header.digest = sp_runtime::Digest {
|
||||
logs: vec![DigestItem::Consensus(
|
||||
sp_consensus_grandpa::GRANDPA_ENGINE_ID,
|
||||
consensus_log.encode(),
|
||||
)],
|
||||
};
|
||||
let justification = make_justification_for_header(JustificationGeneratorParams {
|
||||
header: mandatory_header.clone(),
|
||||
set_id: 1,
|
||||
..Default::default()
|
||||
});
|
||||
let bridge_grandpa_call = crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(mandatory_header),
|
||||
justification,
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: true,
|
||||
};
|
||||
BestFinalized::<TestRuntime, ()>::put(HeaderId(
|
||||
100 - FreeHeadersInterval::get() as u64 + 1,
|
||||
sp_core::H256::default(),
|
||||
));
|
||||
assert!(RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa(
|
||||
bridge_grandpa_call.clone(),
|
||||
),)
|
||||
.is_ok());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_accepts_new_header() {
|
||||
run_test(|| {
|
||||
@@ -336,6 +571,8 @@ mod tests {
|
||||
current_set_id: None,
|
||||
extra_weight: Weight::zero(),
|
||||
extra_size: 0,
|
||||
is_mandatory: false,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -345,6 +582,7 @@ mod tests {
|
||||
finality_target: Box::new(test_header(42)),
|
||||
justification: make_default_justification(&test_header(42)),
|
||||
current_set_id: 777,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(
|
||||
deprecated_call.submit_finality_proof_info(),
|
||||
@@ -353,6 +591,8 @@ mod tests {
|
||||
current_set_id: Some(777),
|
||||
extra_weight: Weight::zero(),
|
||||
extra_size: 0,
|
||||
is_mandatory: false,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -370,6 +610,7 @@ mod tests {
|
||||
finality_target: Box::new(small_finality_target),
|
||||
justification: small_justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0);
|
||||
|
||||
@@ -387,6 +628,7 @@ mod tests {
|
||||
finality_target: Box::new(large_finality_target),
|
||||
justification: large_justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0);
|
||||
}
|
||||
@@ -406,6 +648,7 @@ mod tests {
|
||||
finality_target: Box::new(finality_target.clone()),
|
||||
justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero());
|
||||
|
||||
@@ -420,7 +663,52 @@ mod tests {
|
||||
finality_target: Box::new(finality_target),
|
||||
justification,
|
||||
current_set_id: TEST_GRANDPA_SET_ID,
|
||||
is_free_execution_expected: false,
|
||||
});
|
||||
assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_obsolete_submit_finality_proof_returns_correct_improved_by() {
|
||||
run_test(|| {
|
||||
fn make_call(number: u64) -> RuntimeCall {
|
||||
RuntimeCall::Grandpa(crate::Call::<TestRuntime, ()>::submit_finality_proof_ex {
|
||||
finality_target: Box::new(test_header(number)),
|
||||
justification: make_default_justification(&test_header(number)),
|
||||
current_set_id: 0,
|
||||
is_free_execution_expected: false,
|
||||
})
|
||||
}
|
||||
|
||||
sync_to_header_10();
|
||||
|
||||
// when the difference between headers is 1
|
||||
assert_eq!(
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(11))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.improved_by,
|
||||
1,
|
||||
);
|
||||
|
||||
// when the difference between headers is 2
|
||||
assert_eq!(
|
||||
RuntimeCall::check_obsolete_submit_finality_proof(&make_call(12))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.improved_by,
|
||||
2,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_obsolete_submit_finality_proof_ignores_other_calls() {
|
||||
run_test(|| {
|
||||
let call =
|
||||
RuntimeCall::System(frame_system::Call::<TestRuntime>::remark { remark: vec![42] });
|
||||
|
||||
assert_eq!(RuntimeCall::check_obsolete_submit_finality_proof(&call), Ok(None));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ use bp_header_chain::{
|
||||
};
|
||||
use bp_runtime::{BlockNumberOf, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
|
||||
use frame_support::{dispatch::PostDispatchInfo, ensure, DefaultNoBound};
|
||||
use sp_consensus_grandpa::SetId;
|
||||
use sp_runtime::{
|
||||
traits::{Header as HeaderT, Zero},
|
||||
SaturatedConversion,
|
||||
@@ -57,6 +58,7 @@ mod storage_types;
|
||||
|
||||
/// Module, containing weights for this pallet.
|
||||
pub mod weights;
|
||||
pub mod weights_ext;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking;
|
||||
@@ -65,6 +67,7 @@ pub mod benchmarking;
|
||||
pub use call_ext::*;
|
||||
pub use pallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
pub use weights_ext::WeightInfoExt;
|
||||
|
||||
/// The target that will be used when publishing logs related to this pallet.
|
||||
pub const LOG_TARGET: &str = "runtime::bridge-grandpa";
|
||||
@@ -101,17 +104,31 @@ pub mod pallet {
|
||||
/// The chain we are bridging to here.
|
||||
type BridgedChain: ChainWithGrandpa;
|
||||
|
||||
/// Maximal number of "free" mandatory header transactions per block.
|
||||
/// Maximal number of "free" header transactions per block.
|
||||
///
|
||||
/// To be able to track the bridged chain, the pallet requires all headers that are
|
||||
/// changing GRANDPA authorities set at the bridged chain (we call them mandatory).
|
||||
/// So it is a common good deed to submit mandatory headers to the pallet. However, if the
|
||||
/// bridged chain gets compromised, its validators may generate as many mandatory headers
|
||||
/// as they want. And they may fill the whole block (at this chain) for free. This constants
|
||||
/// limits number of calls that we may refund in a single block. All calls above this
|
||||
/// limit are accepted, but are not refunded.
|
||||
/// So it is a common good deed to submit mandatory headers to the pallet.
|
||||
///
|
||||
/// The pallet may be configured (see `[Self::FreeHeadersInterval]`) to import some
|
||||
/// non-mandatory headers for free as well. It also may be treated as a common good
|
||||
/// deed, because it may help to reduce bridge fees - this cost may be deducted from
|
||||
/// bridge fees, paid by message senders.
|
||||
///
|
||||
/// However, if the bridged chain gets compromised, its validators may generate as many
|
||||
/// "free" headers as they want. And they may fill the whole block (at this chain) for
|
||||
/// free. This constants limits number of calls that we may refund in a single block.
|
||||
/// All calls above this limit are accepted, but are not refunded.
|
||||
#[pallet::constant]
|
||||
type MaxFreeMandatoryHeadersPerBlock: Get<u32>;
|
||||
type MaxFreeHeadersPerBlock: Get<u32>;
|
||||
|
||||
/// The distance between bridged chain headers, that may be submitted for free. The
|
||||
/// first free header is header number zero, the next one is header number
|
||||
/// `FreeHeadersInterval::get()` or any of its descendant if that header has not
|
||||
/// been submitted. In other words, interval between free headers should be at least
|
||||
/// `FreeHeadersInterval`.
|
||||
#[pallet::constant]
|
||||
type FreeHeadersInterval: Get<Option<u32>>;
|
||||
|
||||
/// Maximal number of finalized headers to keep in the storage.
|
||||
///
|
||||
@@ -124,7 +141,7 @@ pub mod pallet {
|
||||
type HeadersToKeep: Get<u32>;
|
||||
|
||||
/// Weights gathered through benchmarking.
|
||||
type WeightInfo: WeightInfo;
|
||||
type WeightInfo: WeightInfoExt;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
@@ -133,12 +150,12 @@ pub mod pallet {
|
||||
#[pallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
|
||||
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
|
||||
FreeMandatoryHeadersRemaining::<T, I>::put(T::MaxFreeMandatoryHeadersPerBlock::get());
|
||||
FreeHeadersRemaining::<T, I>::put(T::MaxFreeHeadersPerBlock::get());
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn on_finalize(_n: BlockNumberFor<T>) {
|
||||
FreeMandatoryHeadersRemaining::<T, I>::kill();
|
||||
FreeHeadersRemaining::<T, I>::kill();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +172,7 @@ pub mod pallet {
|
||||
/// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the
|
||||
/// `submit_finality_proof_ex` call without current authority set id check.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T::WeightInfo as WeightInfo>::submit_finality_proof(
|
||||
#[pallet::weight(T::WeightInfo::submit_finality_proof_weight(
|
||||
justification.commit.precommits.len().saturated_into(),
|
||||
justification.votes_ancestries.len().saturated_into(),
|
||||
))]
|
||||
@@ -175,6 +192,8 @@ pub mod pallet {
|
||||
// the `submit_finality_proof_ex` also reads this value, but it is done from the
|
||||
// cache, so we don't treat it as an additional db access
|
||||
<CurrentAuthoritySet<T, I>>::get().set_id,
|
||||
// cannot enforce free execution using this call
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -250,8 +269,14 @@ pub mod pallet {
|
||||
/// - verification is not optimized or invalid;
|
||||
///
|
||||
/// - header contains forced authorities set change or change with non-zero delay.
|
||||
///
|
||||
/// The `is_free_execution_expected` parameter is not really used inside the call. It is
|
||||
/// used by the transaction extension, which should be registered at the runtime level. If
|
||||
/// this parameter is `true`, the transaction will be treated as invalid, if the call won't
|
||||
/// be executed for free. If transaction extension is not used by the runtime, this
|
||||
/// parameter is not used at all.
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight(<T::WeightInfo as WeightInfo>::submit_finality_proof(
|
||||
#[pallet::weight(T::WeightInfo::submit_finality_proof_weight(
|
||||
justification.commit.precommits.len().saturated_into(),
|
||||
justification.votes_ancestries.len().saturated_into(),
|
||||
))]
|
||||
@@ -260,6 +285,7 @@ pub mod pallet {
|
||||
finality_target: Box<BridgedHeader<T, I>>,
|
||||
justification: GrandpaJustification<BridgedHeader<T, I>>,
|
||||
current_set_id: sp_consensus_grandpa::SetId,
|
||||
_is_free_execution_expected: bool,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
|
||||
ensure_signed(origin)?;
|
||||
@@ -273,7 +299,8 @@ pub mod pallet {
|
||||
|
||||
// it checks whether the `number` is better than the current best block number
|
||||
// and whether the `current_set_id` matches the best known set id
|
||||
SubmitFinalityProofHelper::<T, I>::check_obsolete(number, Some(current_set_id))?;
|
||||
let improved_by =
|
||||
SubmitFinalityProofHelper::<T, I>::check_obsolete(number, Some(current_set_id))?;
|
||||
|
||||
let authority_set = <CurrentAuthoritySet<T, I>>::get();
|
||||
let unused_proof_size = authority_set.unused_proof_size();
|
||||
@@ -283,23 +310,16 @@ pub mod pallet {
|
||||
|
||||
let maybe_new_authority_set =
|
||||
try_enact_authority_change::<T, I>(&finality_target, set_id)?;
|
||||
let may_refund_call_fee = maybe_new_authority_set.is_some() &&
|
||||
// if we have seen too many mandatory headers in this block, we don't want to refund
|
||||
Self::free_mandatory_headers_remaining() > 0 &&
|
||||
// if arguments out of expected bounds, we don't want to refund
|
||||
submit_finality_proof_info_from_args::<T, I>(&finality_target, &justification, Some(current_set_id))
|
||||
.fits_limits();
|
||||
let may_refund_call_fee = may_refund_call_fee::<T, I>(
|
||||
&finality_target,
|
||||
&justification,
|
||||
current_set_id,
|
||||
improved_by,
|
||||
);
|
||||
if may_refund_call_fee {
|
||||
FreeMandatoryHeadersRemaining::<T, I>::mutate(|count| {
|
||||
*count = count.saturating_sub(1)
|
||||
});
|
||||
on_free_header_imported::<T, I>();
|
||||
}
|
||||
insert_header::<T, I>(*finality_target, hash);
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Successfully imported finalized header with hash {:?}!",
|
||||
hash
|
||||
);
|
||||
|
||||
// mandatory header is a header that changes authorities set. The pallet can't go
|
||||
// further without importing this header. So every bridge MUST import mandatory headers.
|
||||
@@ -311,6 +331,13 @@ pub mod pallet {
|
||||
// to pay for the transaction.
|
||||
let pays_fee = if may_refund_call_fee { Pays::No } else { Pays::Yes };
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Successfully imported finalized header with hash {:?}! Free: {}",
|
||||
hash,
|
||||
if may_refund_call_fee { "Yes" } else { "No" },
|
||||
);
|
||||
|
||||
// the proof size component of the call weight assumes that there are
|
||||
// `MaxBridgedAuthorities` in the `CurrentAuthoritySet` (we use `MaxEncodedLen`
|
||||
// estimation). But if their number is lower, then we may "refund" some `proof_size`,
|
||||
@@ -335,20 +362,18 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Number mandatory headers that we may accept in the current block for free (returning
|
||||
/// `Pays::No`).
|
||||
/// Number of free header submissions that we may yet accept in the current block.
|
||||
///
|
||||
/// If the `FreeMandatoryHeadersRemaining` hits zero, all following mandatory headers in the
|
||||
/// If the `FreeHeadersRemaining` hits zero, all following mandatory headers in the
|
||||
/// current block are accepted with fee (`Pays::Yes` is returned).
|
||||
///
|
||||
/// The `FreeMandatoryHeadersRemaining` is an ephemeral value that is set to
|
||||
/// `MaxFreeMandatoryHeadersPerBlock` at each block initialization and is killed on block
|
||||
/// The `FreeHeadersRemaining` is an ephemeral value that is set to
|
||||
/// `MaxFreeHeadersPerBlock` at each block initialization and is killed on block
|
||||
/// finalization. So it never ends up in the storage trie.
|
||||
#[pallet::storage]
|
||||
#[pallet::whitelist_storage]
|
||||
#[pallet::getter(fn free_mandatory_headers_remaining)]
|
||||
pub(super) type FreeMandatoryHeadersRemaining<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, u32, ValueQuery>;
|
||||
pub type FreeHeadersRemaining<T: Config<I>, I: 'static = ()> =
|
||||
StorageValue<_, u32, OptionQuery>;
|
||||
|
||||
/// Hash of the header used to bootstrap the pallet.
|
||||
#[pallet::storage]
|
||||
@@ -473,6 +498,68 @@ pub mod pallet {
|
||||
/// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match
|
||||
/// the id of the current set, known to the pallet.
|
||||
InvalidAuthoritySetId,
|
||||
/// The submitter wanted free execution, but we can't fit more free transactions
|
||||
/// to the block.
|
||||
FreeHeadersLimitExceded,
|
||||
/// The submitter wanted free execution, but the difference between best known and
|
||||
/// bundled header numbers is below the `FreeHeadersInterval`.
|
||||
BelowFreeHeaderInterval,
|
||||
}
|
||||
|
||||
/// Called when new free header is imported.
|
||||
pub fn on_free_header_imported<T: Config<I>, I: 'static>() {
|
||||
FreeHeadersRemaining::<T, I>::mutate(|count| {
|
||||
*count = match *count {
|
||||
None => None,
|
||||
// the signed extension expects that `None` means outside of block
|
||||
// execution - i.e. when transaction is validated from the transaction pool,
|
||||
// so use `saturating_sub` and don't go from `Some(0)`->`None`
|
||||
Some(count) => Some(count.saturating_sub(1)),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Return true if we may refund transaction cost to the submitter. In other words,
|
||||
/// this transaction is considered as common good deed w.r.t to pallet configuration.
|
||||
fn may_refund_call_fee<T: Config<I>, I: 'static>(
|
||||
finality_target: &BridgedHeader<T, I>,
|
||||
justification: &GrandpaJustification<BridgedHeader<T, I>>,
|
||||
current_set_id: SetId,
|
||||
improved_by: BridgedBlockNumber<T, I>,
|
||||
) -> bool {
|
||||
// if we have refunded too much at this block => not refunding
|
||||
if FreeHeadersRemaining::<T, I>::get().unwrap_or(0) == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if size/weight of call is larger than expected => not refunding
|
||||
let call_info = submit_finality_proof_info_from_args::<T, I>(
|
||||
&finality_target,
|
||||
&justification,
|
||||
Some(current_set_id),
|
||||
// this function is called from the transaction body and we do not want
|
||||
// to do MAY-be-free-executed checks here - they had to be done in the
|
||||
// transaction extension before
|
||||
false,
|
||||
);
|
||||
if !call_info.fits_limits() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if that's a mandatory header => refund
|
||||
if call_info.is_mandatory {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if configuration allows free non-mandatory headers and the header
|
||||
// matches criteria => refund
|
||||
if let Some(free_headers_interval) = T::FreeHeadersInterval::get() {
|
||||
if improved_by >= free_headers_interval.into() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check the given header for a GRANDPA scheduled authority set change. If a change
|
||||
@@ -692,8 +779,8 @@ pub fn initialize_for_benchmarks<T: Config<I>, I: 'static>(header: BridgedHeader
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::{
|
||||
run_test, test_header, RuntimeEvent as TestEvent, RuntimeOrigin, System, TestBridgedChain,
|
||||
TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES,
|
||||
run_test, test_header, FreeHeadersInterval, RuntimeEvent as TestEvent, RuntimeOrigin,
|
||||
System, TestBridgedChain, TestHeader, TestNumber, TestRuntime, MAX_BRIDGED_AUTHORITIES,
|
||||
};
|
||||
use bp_header_chain::BridgeGrandpaCall;
|
||||
use bp_runtime::BasicOperatingMode;
|
||||
@@ -747,6 +834,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -766,6 +854,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
set_id,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -794,6 +883,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
set_id,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1009,6 +1099,7 @@ mod tests {
|
||||
Box::new(header.clone()),
|
||||
justification.clone(),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
),
|
||||
<Error<TestRuntime>>::InvalidJustification
|
||||
);
|
||||
@@ -1018,6 +1109,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
next_set_id,
|
||||
false,
|
||||
),
|
||||
<Error<TestRuntime>>::InvalidAuthoritySetId
|
||||
);
|
||||
@@ -1039,6 +1131,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
),
|
||||
<Error<TestRuntime>>::InvalidJustification
|
||||
);
|
||||
@@ -1069,6 +1162,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
),
|
||||
<Error<TestRuntime>>::InvalidAuthoritySet
|
||||
);
|
||||
@@ -1108,6 +1202,7 @@ mod tests {
|
||||
Box::new(header.clone()),
|
||||
justification.clone(),
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
);
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No);
|
||||
@@ -1171,6 +1266,7 @@ mod tests {
|
||||
Box::new(header.clone()),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
);
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
|
||||
@@ -1203,6 +1299,7 @@ mod tests {
|
||||
Box::new(header.clone()),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
);
|
||||
assert_ok!(result);
|
||||
assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes);
|
||||
@@ -1233,6 +1330,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
),
|
||||
<Error<TestRuntime>>::UnsupportedScheduledChange
|
||||
);
|
||||
@@ -1259,6 +1357,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
),
|
||||
<Error<TestRuntime>>::UnsupportedScheduledChange
|
||||
);
|
||||
@@ -1285,6 +1384,7 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
),
|
||||
<Error<TestRuntime>>::TooManyAuthoritiesInSet
|
||||
);
|
||||
@@ -1350,12 +1450,13 @@ mod tests {
|
||||
Box::new(header),
|
||||
invalid_justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
initialize_substrate_bridge();
|
||||
|
||||
for _ in 0..<TestRuntime as Config>::MaxFreeMandatoryHeadersPerBlock::get() + 1 {
|
||||
for _ in 0..<TestRuntime as Config>::MaxFreeHeadersPerBlock::get() + 1 {
|
||||
assert_err!(submit_invalid_request(), <Error<TestRuntime>>::InvalidJustification);
|
||||
}
|
||||
|
||||
@@ -1423,6 +1524,64 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn may_import_non_mandatory_header_for_free() {
|
||||
run_test(|| {
|
||||
initialize_substrate_bridge();
|
||||
|
||||
// set best finalized to `100`
|
||||
const BEST: u8 = 12;
|
||||
fn reset_best() {
|
||||
BestFinalized::<TestRuntime, ()>::set(Some(HeaderId(
|
||||
BEST as _,
|
||||
Default::default(),
|
||||
)));
|
||||
}
|
||||
|
||||
// non-mandatory header is imported with fee
|
||||
reset_best();
|
||||
let non_free_header_number = BEST + FreeHeadersInterval::get() as u8 - 1;
|
||||
let result = submit_finality_proof(non_free_header_number);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
|
||||
|
||||
// non-mandatory free header is imported without fee
|
||||
reset_best();
|
||||
let free_header_number = BEST + FreeHeadersInterval::get() as u8;
|
||||
let result = submit_finality_proof(free_header_number);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
|
||||
// another non-mandatory free header is imported without fee
|
||||
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 2;
|
||||
let result = submit_finality_proof(free_header_number);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
|
||||
// now the rate limiter starts charging fees even for free headers
|
||||
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 3;
|
||||
let result = submit_finality_proof(free_header_number);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::Yes);
|
||||
|
||||
// check that we can import for free if `improved_by` is larger
|
||||
// than the free interval
|
||||
next_block();
|
||||
reset_best();
|
||||
let free_header_number = FreeHeadersInterval::get() as u8 + 42;
|
||||
let result = submit_finality_proof(free_header_number);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
|
||||
// check that the rate limiter shares the counter between mandatory
|
||||
// and free non-mandatory headers
|
||||
next_block();
|
||||
reset_best();
|
||||
let free_header_number = BEST + FreeHeadersInterval::get() as u8 * 4;
|
||||
let result = submit_finality_proof(free_header_number);
|
||||
assert_eq!(result.unwrap().pays_fee, Pays::No);
|
||||
let result = submit_mandatory_finality_proof(free_header_number + 1, 1);
|
||||
assert_eq!(result.expect("call failed").pays_fee, Pays::No);
|
||||
let result = submit_mandatory_finality_proof(free_header_number + 2, 2);
|
||||
assert_eq!(result.expect("call failed").pays_fee, Pays::Yes);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_prune_headers_over_headers_to_keep_parameter() {
|
||||
run_test(|| {
|
||||
@@ -1519,9 +1678,23 @@ mod tests {
|
||||
Box::new(header),
|
||||
justification,
|
||||
TEST_GRANDPA_SET_ID,
|
||||
false,
|
||||
),
|
||||
DispatchError::BadOrigin,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn on_free_header_imported_never_sets_to_none() {
|
||||
run_test(|| {
|
||||
FreeHeadersRemaining::<TestRuntime, ()>::set(Some(2));
|
||||
on_free_header_imported::<TestRuntime, ()>();
|
||||
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(1));
|
||||
on_free_header_imported::<TestRuntime, ()>();
|
||||
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
|
||||
on_free_header_imported::<TestRuntime, ()>();
|
||||
assert_eq!(FreeHeadersRemaining::<TestRuntime, ()>::get(), Some(0));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +48,16 @@ impl frame_system::Config for TestRuntime {
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxFreeMandatoryHeadersPerBlock: u32 = 2;
|
||||
pub const MaxFreeHeadersPerBlock: u32 = 2;
|
||||
pub const FreeHeadersInterval: u32 = 32;
|
||||
pub const HeadersToKeep: u32 = 5;
|
||||
}
|
||||
|
||||
impl grandpa::Config for TestRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BridgedChain = TestBridgedChain;
|
||||
type MaxFreeMandatoryHeadersPerBlock = MaxFreeMandatoryHeadersPerBlock;
|
||||
type MaxFreeHeadersPerBlock = MaxFreeHeadersPerBlock;
|
||||
type FreeHeadersInterval = FreeHeadersInterval;
|
||||
type HeadersToKeep = HeadersToKeep;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (C) 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Weight-related utilities.
|
||||
|
||||
use crate::weights::{BridgeWeight, WeightInfo};
|
||||
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
/// Extended weight info.
|
||||
pub trait WeightInfoExt: WeightInfo {
|
||||
// Our configuration assumes that the runtime has special signed extensions used to:
|
||||
//
|
||||
// 1) boost priority of `submit_finality_proof` transactions;
|
||||
//
|
||||
// 2) slash relayer if he submits an invalid transaction.
|
||||
//
|
||||
// We read and update storage values of other pallets (`pallet-bridge-relayers` and
|
||||
// balances/assets pallet). So we need to add this weight to the weight of our call.
|
||||
// Hence two following methods.
|
||||
|
||||
/// Extra weight that is added to the `submit_finality_proof` call weight by signed extensions
|
||||
/// that are declared at runtime level.
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight;
|
||||
|
||||
// Functions that are directly mapped to extrinsics weights.
|
||||
|
||||
/// Weight of message delivery extrinsic.
|
||||
fn submit_finality_proof_weight(precommits_len: u32, votes_ancestries_len: u32) -> Weight {
|
||||
let base_weight = Self::submit_finality_proof(precommits_len, votes_ancestries_len);
|
||||
base_weight.saturating_add(Self::submit_finality_proof_overhead_from_runtime())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: frame_system::Config> WeightInfoExt for BridgeWeight<T> {
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl WeightInfoExt for () {
|
||||
fn submit_finality_proof_overhead_from_runtime() -> Weight {
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user