mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 07:31:02 +00:00
RefundRelayerForMessagesFromParachain improvements (#1879)
* RefundRelayerForMessagesFromParachain improvements * Address code review comments
This commit is contained in:
committed by
Bastian Köcher
parent
83c3975349
commit
c9c6d566d0
@@ -608,7 +608,6 @@ pub type BridgeRefundRialtoParachainRelayers =
|
|||||||
RialtoGrandpaInstance,
|
RialtoGrandpaInstance,
|
||||||
WithRialtoParachainsInstance,
|
WithRialtoParachainsInstance,
|
||||||
WithRialtoParachainMessagesInstance,
|
WithRialtoParachainMessagesInstance,
|
||||||
BridgeRejectObsoleteHeadersAndMessages,
|
|
||||||
RialtoParachainId,
|
RialtoParachainId,
|
||||||
RialtoParachainMessagesLane,
|
RialtoParachainMessagesLane,
|
||||||
Runtime,
|
Runtime,
|
||||||
|
|||||||
@@ -18,14 +18,16 @@
|
|||||||
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use bp_runtime::FilterCall;
|
use crate::messages_call_ext::MessagesCallSubType;
|
||||||
|
use pallet_bridge_grandpa::CallSubType as GrandpaCallSubType;
|
||||||
|
use pallet_bridge_parachains::CallSubType as ParachainsCallSubtype;
|
||||||
use sp_runtime::transaction_validity::TransactionValidity;
|
use sp_runtime::transaction_validity::TransactionValidity;
|
||||||
use xcm::v3::NetworkId;
|
use xcm::v3::NetworkId;
|
||||||
|
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod messages_api;
|
pub mod messages_api;
|
||||||
pub mod messages_benchmarking;
|
pub mod messages_benchmarking;
|
||||||
pub mod messages_extension;
|
pub mod messages_call_ext;
|
||||||
pub mod parachains_benchmarking;
|
pub mod parachains_benchmarking;
|
||||||
pub mod refund_relayer_extension;
|
pub mod refund_relayer_extension;
|
||||||
|
|
||||||
@@ -44,21 +46,39 @@ pub trait BridgeRuntimeFilterCall<Call> {
|
|||||||
fn validate(call: &Call) -> TransactionValidity;
|
fn validate(call: &Call) -> TransactionValidity;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Call, T, I> BridgeRuntimeFilterCall<Call> for pallet_bridge_grandpa::Pallet<T, I>
|
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall> for pallet_bridge_grandpa::Pallet<T, I>
|
||||||
where
|
where
|
||||||
pallet_bridge_grandpa::Pallet<T, I>: FilterCall<Call>,
|
T: pallet_bridge_grandpa::Config<I>,
|
||||||
|
T::RuntimeCall: GrandpaCallSubType<T, I>,
|
||||||
{
|
{
|
||||||
fn validate(call: &Call) -> TransactionValidity {
|
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
|
||||||
<pallet_bridge_grandpa::Pallet<T, I> as FilterCall<Call>>::validate(call)
|
GrandpaCallSubType::<T, I>::check_obsolete_submit_finality_proof(call)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Call, T, I> BridgeRuntimeFilterCall<Call> for pallet_bridge_parachains::Pallet<T, I>
|
impl<T, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
|
||||||
|
for pallet_bridge_parachains::Pallet<T, I>
|
||||||
where
|
where
|
||||||
pallet_bridge_parachains::Pallet<T, I>: FilterCall<Call>,
|
T: pallet_bridge_parachains::Config<I>,
|
||||||
|
T::RuntimeCall: ParachainsCallSubtype<T, I>,
|
||||||
{
|
{
|
||||||
fn validate(call: &Call) -> TransactionValidity {
|
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
|
||||||
<pallet_bridge_parachains::Pallet<T, I> as FilterCall<Call>>::validate(call)
|
ParachainsCallSubtype::<T, I>::check_obsolete_submit_parachain_heads(call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: pallet_bridge_messages::Config<I>, I: 'static> BridgeRuntimeFilterCall<T::RuntimeCall>
|
||||||
|
for pallet_bridge_messages::Pallet<T, I>
|
||||||
|
where
|
||||||
|
T::RuntimeCall: MessagesCallSubType<T, I>,
|
||||||
|
{
|
||||||
|
/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
|
||||||
|
/// transactions, that are delivering outdated messages/confirmations. Without this validation,
|
||||||
|
/// even honest relayers may lose their funds if there are multiple relays running and
|
||||||
|
/// submitting the same messages/confirmations.
|
||||||
|
fn validate(call: &T::RuntimeCall) -> TransactionValidity {
|
||||||
|
call.check_obsolete_receive_messages_proof()?;
|
||||||
|
call.check_obsolete_receive_messages_delivery_proof()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+103
-50
@@ -14,20 +14,56 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// 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/>.
|
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::{
|
use crate::messages::{
|
||||||
messages::{
|
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
|
||||||
source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
|
|
||||||
},
|
|
||||||
BridgeRuntimeFilterCall,
|
|
||||||
};
|
};
|
||||||
|
use bp_messages::{LaneId, MessageNonce};
|
||||||
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
|
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
|
||||||
use pallet_bridge_messages::{Config, Pallet};
|
use pallet_bridge_messages::{Config, Pallet};
|
||||||
use sp_runtime::transaction_validity::TransactionValidity;
|
use sp_runtime::{transaction_validity::TransactionValidity, RuntimeDebug};
|
||||||
|
|
||||||
|
/// Info about a `ReceiveMessagesProof` call which tries to update a single lane.
|
||||||
|
#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
|
||||||
|
pub struct ReceiveMessagesProofInfo {
|
||||||
|
pub lane_id: LaneId,
|
||||||
|
pub best_proof_nonce: MessageNonce,
|
||||||
|
pub best_stored_nonce: MessageNonce,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper struct that provides methods for working with the `ReceiveMessagesProof` call.
|
||||||
|
pub struct ReceiveMessagesProofHelper<T: Config<I>, I: 'static> {
|
||||||
|
pub _phantom_data: sp_std::marker::PhantomData<(T, I)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config<I>, I: 'static> ReceiveMessagesProofHelper<T, I> {
|
||||||
|
/// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
|
||||||
|
/// it contained.
|
||||||
|
pub fn was_partially_successful(info: &ReceiveMessagesProofInfo) -> bool {
|
||||||
|
let inbound_lane_data = pallet_bridge_messages::InboundLanes::<T, I>::get(info.lane_id);
|
||||||
|
inbound_lane_data.last_delivered_nonce() > info.best_stored_nonce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`.
|
||||||
|
pub trait MessagesCallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||||
|
IsSubType<CallableCallFor<Pallet<T, I>, T>>
|
||||||
|
{
|
||||||
|
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
|
||||||
|
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo>;
|
||||||
|
|
||||||
|
/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call,
|
||||||
|
/// if the call is for the provided lane.
|
||||||
|
fn receive_messages_proof_info_for(&self, lane_id: LaneId) -> Option<ReceiveMessagesProofInfo>;
|
||||||
|
|
||||||
|
/// Check that a `ReceiveMessagesProof` call is trying to deliver at least some messages that
|
||||||
|
/// are better than the ones we know of.
|
||||||
|
fn check_obsolete_receive_messages_proof(&self) -> TransactionValidity;
|
||||||
|
|
||||||
|
/// Check that a `ReceiveMessagesDeliveryProof` call is trying to deliver at least some message
|
||||||
|
/// confirmations that are better than the ones we know of.
|
||||||
|
fn check_obsolete_receive_messages_delivery_proof(&self) -> TransactionValidity;
|
||||||
|
}
|
||||||
|
|
||||||
/// Validate messages in order to avoid "mining" messages delivery and delivery confirmation
|
|
||||||
/// transactions, that are delivering outdated messages/confirmations. Without this validation,
|
|
||||||
/// even honest relayers may lose their funds if there are multiple relays running and submitting
|
|
||||||
/// the same messages/confirmations.
|
|
||||||
impl<
|
impl<
|
||||||
BridgedHeaderHash,
|
BridgedHeaderHash,
|
||||||
SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain<
|
SourceHeaderChain: bp_messages::target_chain::SourceHeaderChain<
|
||||||
@@ -42,52 +78,69 @@ impl<
|
|||||||
T: frame_system::Config<RuntimeCall = Call>
|
T: frame_system::Config<RuntimeCall = Call>
|
||||||
+ Config<I, SourceHeaderChain = SourceHeaderChain, TargetHeaderChain = TargetHeaderChain>,
|
+ Config<I, SourceHeaderChain = SourceHeaderChain, TargetHeaderChain = TargetHeaderChain>,
|
||||||
I: 'static,
|
I: 'static,
|
||||||
> BridgeRuntimeFilterCall<Call> for Pallet<T, I>
|
> MessagesCallSubType<T, I> for T::RuntimeCall
|
||||||
{
|
{
|
||||||
fn validate(call: &Call) -> TransactionValidity {
|
fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo> {
|
||||||
match call.is_sub_type() {
|
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_proof {
|
||||||
Some(pallet_bridge_messages::Call::<T, I>::receive_messages_proof {
|
ref proof,
|
||||||
ref proof,
|
..
|
||||||
..
|
}) = self.is_sub_type()
|
||||||
}) => {
|
{
|
||||||
let inbound_lane_data =
|
let inbound_lane_data = pallet_bridge_messages::InboundLanes::<T, I>::get(proof.lane);
|
||||||
pallet_bridge_messages::InboundLanes::<T, I>::get(proof.lane);
|
|
||||||
if proof.nonces_end <= inbound_lane_data.last_delivered_nonce() {
|
return Some(ReceiveMessagesProofInfo {
|
||||||
log::trace!(
|
lane_id: proof.lane,
|
||||||
target: pallet_bridge_messages::LOG_TARGET,
|
best_proof_nonce: proof.nonces_end,
|
||||||
"Rejecting obsolete messages delivery transaction: \
|
best_stored_nonce: inbound_lane_data.last_delivered_nonce(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_messages_proof_info_for(&self, lane_id: LaneId) -> Option<ReceiveMessagesProofInfo> {
|
||||||
|
self.receive_messages_proof_info().filter(|info| info.lane_id == lane_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_obsolete_receive_messages_proof(&self) -> TransactionValidity {
|
||||||
|
if let Some(proof_info) = self.receive_messages_proof_info() {
|
||||||
|
if proof_info.best_proof_nonce <= proof_info.best_stored_nonce {
|
||||||
|
log::trace!(
|
||||||
|
target: pallet_bridge_messages::LOG_TARGET,
|
||||||
|
"Rejecting obsolete messages delivery transaction: \
|
||||||
lane {:?}, bundled {:?}, best {:?}",
|
lane {:?}, bundled {:?}, best {:?}",
|
||||||
proof.lane,
|
proof_info.lane_id,
|
||||||
proof.nonces_end,
|
proof_info.best_proof_nonce,
|
||||||
inbound_lane_data.last_delivered_nonce(),
|
proof_info.best_stored_nonce,
|
||||||
);
|
);
|
||||||
|
|
||||||
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
|
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Some(pallet_bridge_messages::Call::<T, I>::receive_messages_delivery_proof {
|
|
||||||
ref proof,
|
|
||||||
ref relayers_state,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let latest_delivered_nonce = relayers_state.last_delivered_nonce;
|
|
||||||
|
|
||||||
let outbound_lane_data =
|
Ok(sp_runtime::transaction_validity::ValidTransaction::default())
|
||||||
pallet_bridge_messages::OutboundLanes::<T, I>::get(proof.lane);
|
}
|
||||||
if latest_delivered_nonce <= outbound_lane_data.latest_received_nonce {
|
|
||||||
log::trace!(
|
fn check_obsolete_receive_messages_delivery_proof(&self) -> TransactionValidity {
|
||||||
target: pallet_bridge_messages::LOG_TARGET,
|
if let Some(pallet_bridge_messages::Call::<T, I>::receive_messages_delivery_proof {
|
||||||
"Rejecting obsolete messages confirmation transaction: \
|
ref proof,
|
||||||
|
ref relayers_state,
|
||||||
|
..
|
||||||
|
}) = self.is_sub_type()
|
||||||
|
{
|
||||||
|
let outbound_lane_data = pallet_bridge_messages::OutboundLanes::<T, I>::get(proof.lane);
|
||||||
|
if relayers_state.last_delivered_nonce <= outbound_lane_data.latest_received_nonce {
|
||||||
|
log::trace!(
|
||||||
|
target: pallet_bridge_messages::LOG_TARGET,
|
||||||
|
"Rejecting obsolete messages confirmation transaction: \
|
||||||
lane {:?}, bundled {:?}, best {:?}",
|
lane {:?}, bundled {:?}, best {:?}",
|
||||||
proof.lane,
|
proof.lane,
|
||||||
latest_delivered_nonce,
|
relayers_state.last_delivered_nonce,
|
||||||
outbound_lane_data.latest_received_nonce,
|
outbound_lane_data.latest_received_nonce,
|
||||||
);
|
);
|
||||||
|
|
||||||
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
|
return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(sp_runtime::transaction_validity::ValidTransaction::default())
|
Ok(sp_runtime::transaction_validity::ValidTransaction::default())
|
||||||
@@ -19,37 +19,32 @@
|
|||||||
//! with calls that are: delivering new messsage and all necessary underlying headers
|
//! with calls that are: delivering new messsage and all necessary underlying headers
|
||||||
//! (parachain or relay chain).
|
//! (parachain or relay chain).
|
||||||
|
|
||||||
use crate::messages::target::FromBridgedChainMessagesProof;
|
use crate::messages_call_ext::{
|
||||||
|
MessagesCallSubType, ReceiveMessagesProofHelper, ReceiveMessagesProofInfo,
|
||||||
use bp_messages::{target_chain::SourceHeaderChain, LaneId, MessageNonce};
|
};
|
||||||
use bp_polkadot_core::parachains::ParaId;
|
use bp_messages::LaneId;
|
||||||
use bp_runtime::{Chain, HashOf};
|
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
dispatch::{CallableCallFor, DispatchInfo, Dispatchable, PostDispatchInfo},
|
dispatch::{CallableCallFor, DispatchInfo, Dispatchable, PostDispatchInfo},
|
||||||
traits::IsSubType,
|
traits::IsSubType,
|
||||||
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
||||||
};
|
};
|
||||||
use pallet_bridge_grandpa::{
|
use pallet_bridge_grandpa::{CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper};
|
||||||
BridgedChain, Call as GrandpaCall, Config as GrandpaConfig, Pallet as GrandpaPallet,
|
use pallet_bridge_messages::Config as MessagesConfig;
|
||||||
};
|
|
||||||
use pallet_bridge_messages::{
|
|
||||||
Call as MessagesCall, Config as MessagesConfig, Pallet as MessagesPallet,
|
|
||||||
};
|
|
||||||
use pallet_bridge_parachains::{
|
use pallet_bridge_parachains::{
|
||||||
Call as ParachainsCall, Config as ParachainsConfig, Pallet as ParachainsPallet, RelayBlockHash,
|
BoundedBridgeGrandpaConfig, CallSubType as ParachainsCallSubType, Config as ParachainsConfig,
|
||||||
RelayBlockHasher, RelayBlockNumber,
|
RelayBlockNumber, SubmitParachainHeadsHelper, SubmitParachainHeadsInfo,
|
||||||
};
|
};
|
||||||
use pallet_bridge_relayers::{Config as RelayersConfig, Pallet as RelayersPallet};
|
use pallet_bridge_relayers::{Config as RelayersConfig, Pallet as RelayersPallet};
|
||||||
use pallet_transaction_payment::{Config as TransactionPaymentConfig, OnChargeTransaction};
|
use pallet_transaction_payment::{Config as TransactionPaymentConfig, OnChargeTransaction};
|
||||||
use pallet_utility::{Call as UtilityCall, Config as UtilityConfig, Pallet as UtilityPallet};
|
use pallet_utility::{Call as UtilityCall, Config as UtilityConfig, Pallet as UtilityPallet};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
traits::{DispatchInfoOf, Get, Header as HeaderT, PostDispatchInfoOf, SignedExtension, Zero},
|
traits::{DispatchInfoOf, Get, PostDispatchInfoOf, SignedExtension, Zero},
|
||||||
transaction_validity::{TransactionValidity, TransactionValidityError, ValidTransaction},
|
transaction_validity::{TransactionValidity, TransactionValidityError, ValidTransaction},
|
||||||
DispatchResult, FixedPointOperand,
|
DispatchResult, FixedPointOperand,
|
||||||
};
|
};
|
||||||
use sp_std::marker::PhantomData;
|
use sp_std::{marker::PhantomData, vec, vec::Vec};
|
||||||
|
|
||||||
// TODO (https://github.com/paritytech/parity-bridges-common/issues/1667):
|
// TODO (https://github.com/paritytech/parity-bridges-common/issues/1667):
|
||||||
// support multiple bridges in this extension
|
// support multiple bridges in this extension
|
||||||
@@ -81,6 +76,7 @@ where
|
|||||||
pallet_transaction_payment::Pallet::<R>::compute_actual_fee(len as _, info, post_info, tip)
|
pallet_transaction_payment::Pallet::<R>::compute_actual_fee(len as _, info, post_info, tip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signed extension that refunds relayer for new messages coming from the parachain.
|
/// Signed extension that refunds relayer for new messages coming from the parachain.
|
||||||
///
|
///
|
||||||
/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
|
/// Also refunds relayer for successful finality delivery if it comes in batch (`utility.batchAll`)
|
||||||
@@ -99,18 +95,40 @@ where
|
|||||||
RuntimeDebugNoBound,
|
RuntimeDebugNoBound,
|
||||||
TypeInfo,
|
TypeInfo,
|
||||||
)]
|
)]
|
||||||
#[scale_info(skip_type_params(RT, GI, PI, MI, BE, PID, LID, FEE))]
|
#[scale_info(skip_type_params(RT, GI, PI, MI, PID, LID, FEE))]
|
||||||
#[allow(clippy::type_complexity)] // TODO: get rid of that in https://github.com/paritytech/parity-bridges-common/issues/1666
|
pub struct RefundRelayerForMessagesFromParachain<RT, GI, PI, MI, PID, LID, FEE>(
|
||||||
pub struct RefundRelayerForMessagesFromParachain<RT, GI, PI, MI, BE, PID, LID, FEE>(
|
PhantomData<(RT, GI, PI, MI, PID, LID, FEE)>,
|
||||||
PhantomData<(RT, GI, PI, MI, BE, PID, LID, FEE)>,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl<R, GI, PI, MI, PID, LID, FEE>
|
||||||
|
RefundRelayerForMessagesFromParachain<R, GI, PI, MI, PID, LID, FEE>
|
||||||
|
where
|
||||||
|
R: UtilityConfig<RuntimeCall = CallOf<R>>,
|
||||||
|
CallOf<R>: IsSubType<CallableCallFor<UtilityPallet<R>, R>>,
|
||||||
|
{
|
||||||
|
fn expand_call<'a>(&self, call: &'a CallOf<R>) -> Option<Vec<&'a CallOf<R>>> {
|
||||||
|
let calls = match call.is_sub_type() {
|
||||||
|
Some(UtilityCall::<R>::batch_all { ref calls }) => {
|
||||||
|
if calls.len() > 3 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
calls.iter().collect()
|
||||||
|
},
|
||||||
|
Some(_) => return None,
|
||||||
|
None => vec![call],
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`.
|
/// Data that is crafted in `pre_dispatch` method and used at `post_dispatch`.
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[cfg_attr(test, derive(Debug))]
|
||||||
pub struct PreDispatchData<AccountId> {
|
pub struct PreDispatchData<AccountId> {
|
||||||
/// Transaction submitter (relayer) account.
|
/// Transaction submitter (relayer) account.
|
||||||
pub relayer: AccountId,
|
relayer: AccountId,
|
||||||
/// Type of the call.
|
/// Type of the call.
|
||||||
pub call_type: CallType,
|
pub call_type: CallType,
|
||||||
}
|
}
|
||||||
@@ -119,89 +137,51 @@ pub struct PreDispatchData<AccountId> {
|
|||||||
#[derive(Clone, Copy, PartialEq, RuntimeDebugNoBound)]
|
#[derive(Clone, Copy, PartialEq, RuntimeDebugNoBound)]
|
||||||
pub enum CallType {
|
pub enum CallType {
|
||||||
/// Relay chain finality + parachain finality + message delivery calls.
|
/// Relay chain finality + parachain finality + message delivery calls.
|
||||||
AllFinalityAndDelivery(ExpectedRelayChainState, ExpectedParachainState, MessagesState),
|
AllFinalityAndDelivery(RelayBlockNumber, SubmitParachainHeadsInfo, ReceiveMessagesProofInfo),
|
||||||
/// Parachain finality + message delivery calls.
|
/// Parachain finality + message delivery calls.
|
||||||
ParachainFinalityAndDelivery(ExpectedParachainState, MessagesState),
|
ParachainFinalityAndDelivery(SubmitParachainHeadsInfo, ReceiveMessagesProofInfo),
|
||||||
/// Standalone message delivery call.
|
/// Standalone message delivery call.
|
||||||
Delivery(MessagesState),
|
Delivery(ReceiveMessagesProofInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallType {
|
impl CallType {
|
||||||
/// Returns the pre-dispatch messages pallet state.
|
/// Returns the pre-dispatch messages pallet state.
|
||||||
fn pre_dispatch_messages_state(&self) -> MessagesState {
|
fn receive_messages_proof_info(&self) -> ReceiveMessagesProofInfo {
|
||||||
match *self {
|
match *self {
|
||||||
Self::AllFinalityAndDelivery(_, _, messages_state) => messages_state,
|
Self::AllFinalityAndDelivery(_, _, info) => info,
|
||||||
Self::ParachainFinalityAndDelivery(_, messages_state) => messages_state,
|
Self::ParachainFinalityAndDelivery(_, info) => info,
|
||||||
Self::Delivery(messages_state) => messages_state,
|
Self::Delivery(info) => info,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expected post-dispatch state of the relay chain pallet.
|
|
||||||
#[derive(Clone, Copy, PartialEq, RuntimeDebugNoBound)]
|
|
||||||
pub struct ExpectedRelayChainState {
|
|
||||||
/// Best known relay chain block number.
|
|
||||||
pub best_block_number: RelayBlockNumber,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Expected post-dispatch state of the parachain pallet.
|
|
||||||
#[derive(Clone, Copy, PartialEq, RuntimeDebugNoBound)]
|
|
||||||
pub struct ExpectedParachainState {
|
|
||||||
/// At which relay block the parachain head has been updated?
|
|
||||||
pub at_relay_block_number: RelayBlockNumber,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pre-dispatch state of messages pallet.
|
|
||||||
///
|
|
||||||
/// This struct is for pre-dispatch state of the pallet, not the expected post-dispatch state.
|
|
||||||
/// That's because message delivery transaction may deliver some of messages that it brings.
|
|
||||||
/// If this happens, we consider it "helpful" and refund its cost. If transaction fails to
|
|
||||||
/// deliver at least one message, it is considered wrong and is not refunded.
|
|
||||||
#[derive(Clone, Copy, PartialEq, RuntimeDebugNoBound)]
|
|
||||||
pub struct MessagesState {
|
|
||||||
/// Best delivered message nonce.
|
|
||||||
pub best_nonce: MessageNonce,
|
|
||||||
}
|
|
||||||
|
|
||||||
// without this typedef rustfmt fails with internal err
|
// without this typedef rustfmt fails with internal err
|
||||||
type BalanceOf<R> =
|
type BalanceOf<R> =
|
||||||
<<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance;
|
<<R as TransactionPaymentConfig>::OnChargeTransaction as OnChargeTransaction<R>>::Balance;
|
||||||
type CallOf<R> = <R as frame_system::Config>::RuntimeCall;
|
type CallOf<R> = <R as frame_system::Config>::RuntimeCall;
|
||||||
|
|
||||||
impl<R, GI, PI, MI, BE, PID, LID, FEE> SignedExtension
|
impl<R, GI, PI, MI, PID, LID, FEE> SignedExtension
|
||||||
for RefundRelayerForMessagesFromParachain<R, GI, PI, MI, BE, PID, LID, FEE>
|
for RefundRelayerForMessagesFromParachain<R, GI, PI, MI, PID, LID, FEE>
|
||||||
where
|
where
|
||||||
R: 'static
|
R: 'static
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ frame_system::Config
|
|
||||||
+ UtilityConfig<RuntimeCall = CallOf<R>>
|
+ UtilityConfig<RuntimeCall = CallOf<R>>
|
||||||
+ GrandpaConfig<GI>
|
+ BoundedBridgeGrandpaConfig<GI>
|
||||||
+ ParachainsConfig<PI, BridgesGrandpaPalletInstance = GI>
|
+ ParachainsConfig<PI, BridgesGrandpaPalletInstance = GI>
|
||||||
+ MessagesConfig<MI>
|
+ MessagesConfig<MI>
|
||||||
+ RelayersConfig,
|
+ RelayersConfig,
|
||||||
GI: 'static + Send + Sync,
|
GI: 'static + Send + Sync,
|
||||||
PI: 'static + Send + Sync,
|
PI: 'static + Send + Sync,
|
||||||
MI: 'static + Send + Sync,
|
MI: 'static + Send + Sync,
|
||||||
BE: 'static
|
|
||||||
+ Send
|
|
||||||
+ Sync
|
|
||||||
+ Default
|
|
||||||
+ SignedExtension<AccountId = R::AccountId, Call = CallOf<R>>,
|
|
||||||
PID: 'static + Send + Sync + Get<u32>,
|
PID: 'static + Send + Sync + Get<u32>,
|
||||||
LID: 'static + Send + Sync + Get<LaneId>,
|
LID: 'static + Send + Sync + Get<LaneId>,
|
||||||
FEE: 'static + Send + Sync + TransactionFeeCalculation<<R as RelayersConfig>::Reward>,
|
FEE: 'static + Send + Sync + TransactionFeeCalculation<<R as RelayersConfig>::Reward>,
|
||||||
<R as frame_system::Config>::RuntimeCall:
|
CallOf<R>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||||
Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
+ IsSubType<CallableCallFor<UtilityPallet<R>, R>>
|
||||||
CallOf<R>: IsSubType<CallableCallFor<UtilityPallet<R>, R>>
|
+ GrandpaCallSubType<R, GI>
|
||||||
+ IsSubType<CallableCallFor<GrandpaPallet<R, GI>, R>>
|
+ ParachainsCallSubType<R, PI>
|
||||||
+ IsSubType<CallableCallFor<ParachainsPallet<R, PI>, R>>
|
+ MessagesCallSubType<R, MI>,
|
||||||
+ IsSubType<CallableCallFor<MessagesPallet<R, MI>, R>>,
|
|
||||||
<R as GrandpaConfig<GI>>::BridgedChain:
|
|
||||||
Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>,
|
|
||||||
<R as MessagesConfig<MI>>::SourceHeaderChain: SourceHeaderChain<
|
|
||||||
MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<R, GI>>>,
|
|
||||||
>,
|
|
||||||
{
|
{
|
||||||
const IDENTIFIER: &'static str = "RefundRelayerForMessagesFromParachain";
|
const IDENTIFIER: &'static str = "RefundRelayerForMessagesFromParachain";
|
||||||
type AccountId = R::AccountId;
|
type AccountId = R::AccountId;
|
||||||
@@ -215,17 +195,20 @@ where
|
|||||||
|
|
||||||
fn validate(
|
fn validate(
|
||||||
&self,
|
&self,
|
||||||
who: &Self::AccountId,
|
_who: &Self::AccountId,
|
||||||
call: &Self::Call,
|
call: &Self::Call,
|
||||||
info: &DispatchInfoOf<Self::Call>,
|
_info: &DispatchInfoOf<Self::Call>,
|
||||||
len: usize,
|
_len: usize,
|
||||||
) -> TransactionValidity {
|
) -> TransactionValidity {
|
||||||
// reject batch transactions with obsolete headers
|
let calls = match self.expand_call(call) {
|
||||||
if let Some(UtilityCall::<R>::batch_all { ref calls }) = call.is_sub_type() {
|
Some(calls) => calls,
|
||||||
for nested_call in calls {
|
None => return Ok(ValidTransaction::default()),
|
||||||
let reject_obsolete_transactions = BE::default();
|
};
|
||||||
reject_obsolete_transactions.pre_dispatch(who, nested_call, info, len)?;
|
|
||||||
}
|
for call in calls {
|
||||||
|
call.check_obsolete_submit_finality_proof()?;
|
||||||
|
call.check_obsolete_submit_parachain_heads()?;
|
||||||
|
call.check_obsolete_receive_messages_proof()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ValidTransaction::default())
|
Ok(ValidTransaction::default())
|
||||||
@@ -241,40 +224,37 @@ where
|
|||||||
// reject batch transactions with obsolete headers
|
// reject batch transactions with obsolete headers
|
||||||
self.validate(who, call, info, len).map(drop)?;
|
self.validate(who, call, info, len).map(drop)?;
|
||||||
|
|
||||||
// now try to check if tx matches one of types we support
|
// Try to check if the tx matches one of types we support.
|
||||||
let parse_call_type = || {
|
let parse_call_type = || {
|
||||||
if let Some(UtilityCall::<R>::batch_all { ref calls }) = call.is_sub_type() {
|
let mut calls = self.expand_call(call)?.into_iter();
|
||||||
if calls.len() == 3 {
|
match calls.len() {
|
||||||
return Some(CallType::AllFinalityAndDelivery(
|
3 => Some(CallType::AllFinalityAndDelivery(
|
||||||
extract_expected_relay_chain_state::<R, GI>(&calls[0])?,
|
calls.next()?.submit_finality_proof_info()?,
|
||||||
extract_expected_parachain_state::<R, GI, PI, PID>(&calls[1])?,
|
calls.next()?.submit_parachain_heads_info_for(PID::get())?,
|
||||||
extract_messages_state::<R, GI, MI, LID>(&calls[2])?,
|
calls.next()?.receive_messages_proof_info_for(LID::get())?,
|
||||||
))
|
)),
|
||||||
}
|
2 => Some(CallType::ParachainFinalityAndDelivery(
|
||||||
if calls.len() == 2 {
|
calls.next()?.submit_parachain_heads_info_for(PID::get())?,
|
||||||
return Some(CallType::ParachainFinalityAndDelivery(
|
calls.next()?.receive_messages_proof_info_for(LID::get())?,
|
||||||
extract_expected_parachain_state::<R, GI, PI, PID>(&calls[0])?,
|
)),
|
||||||
extract_messages_state::<R, GI, MI, LID>(&calls[1])?,
|
1 => Some(CallType::Delivery(
|
||||||
))
|
calls.next()?.receive_messages_proof_info_for(LID::get())?,
|
||||||
}
|
)),
|
||||||
return None
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(CallType::Delivery(extract_messages_state::<R, GI, MI, LID>(call)?))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(parse_call_type()
|
Ok(parse_call_type().map(|call_type| {
|
||||||
.map(|call_type| {
|
log::trace!(
|
||||||
log::trace!(
|
target: "runtime::bridge",
|
||||||
target: "runtime::bridge",
|
"RefundRelayerForMessagesFromParachain from parachain {} via {:?} \
|
||||||
"RefundRelayerForMessagesFromParachain from parachain {} via {:?} parsed bridge transaction in pre-dispatch: {:?}",
|
parsed bridge transaction in pre-dispatch: {:?}",
|
||||||
PID::get(),
|
PID::get(),
|
||||||
LID::get(),
|
LID::get(),
|
||||||
call_type,
|
call_type,
|
||||||
);
|
);
|
||||||
PreDispatchData { relayer: who.clone(), call_type }
|
PreDispatchData { relayer: who.clone(), call_type }
|
||||||
})
|
}))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_dispatch(
|
fn post_dispatch(
|
||||||
@@ -284,40 +264,37 @@ where
|
|||||||
len: usize,
|
len: usize,
|
||||||
result: &DispatchResult,
|
result: &DispatchResult,
|
||||||
) -> Result<(), TransactionValidityError> {
|
) -> Result<(), TransactionValidityError> {
|
||||||
// we never refund anything if it is not bridge transaction or if it is a bridge
|
// We don't refund anything if the transaction has failed.
|
||||||
// transaction that we do not support here
|
if result.is_err() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't refund anything for transactions that we don't support.
|
||||||
let (relayer, call_type) = match pre {
|
let (relayer, call_type) = match pre {
|
||||||
Some(Some(pre)) => (pre.relayer, pre.call_type),
|
Some(Some(pre)) => (pre.relayer, pre.call_type),
|
||||||
_ => return Ok(()),
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// we never refund anything if transaction has failed
|
|
||||||
if result.is_err() {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if relay chain state has been updated
|
// check if relay chain state has been updated
|
||||||
if let CallType::AllFinalityAndDelivery(expected_relay_chain_state, _, _) = call_type {
|
if let CallType::AllFinalityAndDelivery(relay_block_number, _, _) = call_type {
|
||||||
let actual_relay_chain_state = relay_chain_state::<R, GI>();
|
if !SubmitFinalityProofHelper::<R, GI>::was_successful(relay_block_number) {
|
||||||
if actual_relay_chain_state != Some(expected_relay_chain_state) {
|
|
||||||
// we only refund relayer if all calls have updated chain state
|
// we only refund relayer if all calls have updated chain state
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// there's a conflict between how bridge GRANDPA pallet works and the
|
// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
|
||||||
// `AllFinalityAndDelivery` transaction. If relay chain header is mandatory, the GRANDPA
|
// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
|
||||||
// pallet returns `Pays::No`, because such transaction is mandatory for operating the
|
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
|
||||||
// bridge. But `utility.batchAll` transaction always requires payment. But in both cases
|
// `utility.batchAll` transaction always requires payment. But in both cases we'll
|
||||||
// we'll refund relayer - either explicitly here, or using `Pays::No` if he's choosing
|
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
|
||||||
// to submit dedicated transaction.
|
// to submit dedicated transaction.
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if parachain state has been updated
|
// check if parachain state has been updated
|
||||||
match call_type {
|
match call_type {
|
||||||
CallType::AllFinalityAndDelivery(_, expected_parachain_state, _) |
|
CallType::AllFinalityAndDelivery(_, parachain_heads_info, _) |
|
||||||
CallType::ParachainFinalityAndDelivery(expected_parachain_state, _) => {
|
CallType::ParachainFinalityAndDelivery(parachain_heads_info, _) => {
|
||||||
let actual_parachain_state = parachain_state::<R, PI, PID>();
|
if !SubmitParachainHeadsHelper::<R, PI>::was_successful(¶chain_heads_info) {
|
||||||
if actual_parachain_state != Some(expected_parachain_state) {
|
|
||||||
// we only refund relayer if all calls have updated chain state
|
// we only refund relayer if all calls have updated chain state
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
@@ -325,11 +302,10 @@ where
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if messages have been delivered
|
// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
|
||||||
let actual_messages_state = messages_state::<R, MI, LID>();
|
// it contained. If this happens, we consider the transaction "helpful" and refund it.
|
||||||
let pre_dispatch_messages_state = call_type.pre_dispatch_messages_state();
|
let messages_proof_info = call_type.receive_messages_proof_info();
|
||||||
if actual_messages_state == Some(pre_dispatch_messages_state) {
|
if !ReceiveMessagesProofHelper::<R, MI>::was_partially_successful(&messages_proof_info) {
|
||||||
// we only refund relayer if all calls have updated chain state
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,125 +335,22 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts expected relay chain state from the call.
|
|
||||||
fn extract_expected_relay_chain_state<R, GI>(call: &CallOf<R>) -> Option<ExpectedRelayChainState>
|
|
||||||
where
|
|
||||||
R: GrandpaConfig<GI>,
|
|
||||||
GI: 'static,
|
|
||||||
<R as GrandpaConfig<GI>>::BridgedChain: Chain<BlockNumber = RelayBlockNumber>,
|
|
||||||
CallOf<R>: IsSubType<CallableCallFor<GrandpaPallet<R, GI>, R>>,
|
|
||||||
{
|
|
||||||
if let Some(GrandpaCall::<R, GI>::submit_finality_proof { ref finality_target, .. }) =
|
|
||||||
call.is_sub_type()
|
|
||||||
{
|
|
||||||
return Some(ExpectedRelayChainState { best_block_number: *finality_target.number() })
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts expected parachain state from the call.
|
|
||||||
fn extract_expected_parachain_state<R, GI, PI, PID>(
|
|
||||||
call: &CallOf<R>,
|
|
||||||
) -> Option<ExpectedParachainState>
|
|
||||||
where
|
|
||||||
R: GrandpaConfig<GI> + ParachainsConfig<PI, BridgesGrandpaPalletInstance = GI>,
|
|
||||||
GI: 'static,
|
|
||||||
PI: 'static,
|
|
||||||
PID: Get<u32>,
|
|
||||||
<R as GrandpaConfig<GI>>::BridgedChain:
|
|
||||||
Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>,
|
|
||||||
CallOf<R>: IsSubType<CallableCallFor<ParachainsPallet<R, PI>, R>>,
|
|
||||||
{
|
|
||||||
if let Some(ParachainsCall::<R, PI>::submit_parachain_heads {
|
|
||||||
ref at_relay_block,
|
|
||||||
ref parachains,
|
|
||||||
..
|
|
||||||
}) = call.is_sub_type()
|
|
||||||
{
|
|
||||||
if parachains.len() != 1 || parachains[0].0 != ParaId(PID::get()) {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(ExpectedParachainState { at_relay_block_number: at_relay_block.0 })
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts messages state from the call.
|
|
||||||
fn extract_messages_state<R, GI, MI, LID>(call: &CallOf<R>) -> Option<MessagesState>
|
|
||||||
where
|
|
||||||
R: GrandpaConfig<GI> + MessagesConfig<MI>,
|
|
||||||
GI: 'static,
|
|
||||||
MI: 'static,
|
|
||||||
LID: Get<LaneId>,
|
|
||||||
CallOf<R>: IsSubType<CallableCallFor<MessagesPallet<R, MI>, R>>,
|
|
||||||
<R as MessagesConfig<MI>>::SourceHeaderChain: SourceHeaderChain<
|
|
||||||
MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<R, GI>>>,
|
|
||||||
>,
|
|
||||||
{
|
|
||||||
if let Some(MessagesCall::<R, MI>::receive_messages_proof { ref proof, .. }) =
|
|
||||||
call.is_sub_type()
|
|
||||||
{
|
|
||||||
if LID::get() != proof.lane {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(MessagesState {
|
|
||||||
best_nonce: MessagesPallet::<R, MI>::inbound_lane_data(proof.lane)
|
|
||||||
.last_delivered_nonce(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns relay chain state that we are interested in.
|
|
||||||
fn relay_chain_state<R, GI>() -> Option<ExpectedRelayChainState>
|
|
||||||
where
|
|
||||||
R: GrandpaConfig<GI>,
|
|
||||||
GI: 'static,
|
|
||||||
<R as GrandpaConfig<GI>>::BridgedChain: Chain<BlockNumber = RelayBlockNumber>,
|
|
||||||
{
|
|
||||||
GrandpaPallet::<R, GI>::best_finalized_number()
|
|
||||||
.map(|best_block_number| ExpectedRelayChainState { best_block_number })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns parachain state that we are interested in.
|
|
||||||
fn parachain_state<R, PI, PID>() -> Option<ExpectedParachainState>
|
|
||||||
where
|
|
||||||
R: ParachainsConfig<PI>,
|
|
||||||
PI: 'static,
|
|
||||||
PID: Get<u32>,
|
|
||||||
{
|
|
||||||
ParachainsPallet::<R, PI>::best_parachain_info(ParaId(PID::get())).map(|para_info| {
|
|
||||||
ExpectedParachainState {
|
|
||||||
at_relay_block_number: para_info.best_head_hash.at_relay_block_number,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns messages state that we are interested in.
|
|
||||||
fn messages_state<R, MI, LID>() -> Option<MessagesState>
|
|
||||||
where
|
|
||||||
R: MessagesConfig<MI>,
|
|
||||||
MI: 'static,
|
|
||||||
LID: Get<LaneId>,
|
|
||||||
{
|
|
||||||
Some(MessagesState {
|
|
||||||
best_nonce: MessagesPallet::<R, MI>::inbound_lane_data(LID::get()).last_delivered_nonce(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{messages::target::FromBridgedChainMessagesProof, mock::*};
|
use crate::{messages::target::FromBridgedChainMessagesProof, mock::*};
|
||||||
use bp_messages::InboundLaneData;
|
use bp_messages::{InboundLaneData, MessageNonce};
|
||||||
use bp_parachains::{BestParaHeadHash, ParaInfo};
|
use bp_parachains::{BestParaHeadHash, ParaInfo};
|
||||||
use bp_polkadot_core::parachains::ParaHeadsProof;
|
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
|
||||||
use bp_runtime::HeaderId;
|
use bp_runtime::HeaderId;
|
||||||
use bp_test_utils::make_default_justification;
|
use bp_test_utils::make_default_justification;
|
||||||
use frame_support::{assert_storage_noop, parameter_types, weights::Weight};
|
use frame_support::{assert_storage_noop, parameter_types, weights::Weight};
|
||||||
use sp_runtime::{transaction_validity::InvalidTransaction, DispatchError};
|
use pallet_bridge_grandpa::Call as GrandpaCall;
|
||||||
|
use pallet_bridge_messages::Call as MessagesCall;
|
||||||
|
use pallet_bridge_parachains::{Call as ParachainsCall, RelayBlockHash};
|
||||||
|
use sp_runtime::{
|
||||||
|
traits::Header as HeaderT, transaction_validity::InvalidTransaction, DispatchError,
|
||||||
|
};
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub TestParachain: u32 = 1000;
|
pub TestParachain: u32 = 1000;
|
||||||
@@ -489,7 +362,6 @@ mod tests {
|
|||||||
(),
|
(),
|
||||||
(),
|
(),
|
||||||
(),
|
(),
|
||||||
BridgeRejectObsoleteHeadersAndMessages,
|
|
||||||
TestParachain,
|
TestParachain,
|
||||||
TestLaneId,
|
TestLaneId,
|
||||||
TestRuntime,
|
TestRuntime,
|
||||||
@@ -506,6 +378,7 @@ mod tests {
|
|||||||
fn initialize_environment(
|
fn initialize_environment(
|
||||||
best_relay_header_number: RelayBlockNumber,
|
best_relay_header_number: RelayBlockNumber,
|
||||||
parachain_head_at_relay_header_number: RelayBlockNumber,
|
parachain_head_at_relay_header_number: RelayBlockNumber,
|
||||||
|
parachain_head_hash: ParaHash,
|
||||||
best_delivered_message: MessageNonce,
|
best_delivered_message: MessageNonce,
|
||||||
) {
|
) {
|
||||||
let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default());
|
let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default());
|
||||||
@@ -515,7 +388,7 @@ mod tests {
|
|||||||
let para_info = ParaInfo {
|
let para_info = ParaInfo {
|
||||||
best_head_hash: BestParaHeadHash {
|
best_head_hash: BestParaHeadHash {
|
||||||
at_relay_block_number: parachain_head_at_relay_header_number,
|
at_relay_block_number: parachain_head_at_relay_header_number,
|
||||||
head_hash: Default::default(),
|
head_hash: parachain_head_hash,
|
||||||
},
|
},
|
||||||
next_imported_hash_position: 0,
|
next_imported_hash_position: 0,
|
||||||
};
|
};
|
||||||
@@ -598,9 +471,17 @@ mod tests {
|
|||||||
PreDispatchData {
|
PreDispatchData {
|
||||||
relayer: relayer_account_at_this_chain(),
|
relayer: relayer_account_at_this_chain(),
|
||||||
call_type: CallType::AllFinalityAndDelivery(
|
call_type: CallType::AllFinalityAndDelivery(
|
||||||
ExpectedRelayChainState { best_block_number: 200 },
|
200,
|
||||||
ExpectedParachainState { at_relay_block_number: 200 },
|
SubmitParachainHeadsInfo {
|
||||||
MessagesState { best_nonce: 100 },
|
at_relay_block_number: 200,
|
||||||
|
para_id: ParaId(TestParachain::get()),
|
||||||
|
para_head_hash: [1u8; 32].into(),
|
||||||
|
},
|
||||||
|
ReceiveMessagesProofInfo {
|
||||||
|
lane_id: TEST_LANE_ID,
|
||||||
|
best_proof_nonce: 200,
|
||||||
|
best_stored_nonce: 100,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -609,8 +490,16 @@ mod tests {
|
|||||||
PreDispatchData {
|
PreDispatchData {
|
||||||
relayer: relayer_account_at_this_chain(),
|
relayer: relayer_account_at_this_chain(),
|
||||||
call_type: CallType::ParachainFinalityAndDelivery(
|
call_type: CallType::ParachainFinalityAndDelivery(
|
||||||
ExpectedParachainState { at_relay_block_number: 200 },
|
SubmitParachainHeadsInfo {
|
||||||
MessagesState { best_nonce: 100 },
|
at_relay_block_number: 200,
|
||||||
|
para_id: ParaId(TestParachain::get()),
|
||||||
|
para_head_hash: [1u8; 32].into(),
|
||||||
|
},
|
||||||
|
ReceiveMessagesProofInfo {
|
||||||
|
lane_id: TEST_LANE_ID,
|
||||||
|
best_proof_nonce: 200,
|
||||||
|
best_stored_nonce: 100,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -618,7 +507,11 @@ mod tests {
|
|||||||
fn delivery_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
|
fn delivery_pre_dispatch_data() -> PreDispatchData<ThisChainAccountId> {
|
||||||
PreDispatchData {
|
PreDispatchData {
|
||||||
relayer: relayer_account_at_this_chain(),
|
relayer: relayer_account_at_this_chain(),
|
||||||
call_type: CallType::Delivery(MessagesState { best_nonce: 100 }),
|
call_type: CallType::Delivery(ReceiveMessagesProofInfo {
|
||||||
|
lane_id: TEST_LANE_ID,
|
||||||
|
best_proof_nonce: 200,
|
||||||
|
best_stored_nonce: 100,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,7 +571,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn validate_allows_non_obsolete_transactions() {
|
fn validate_allows_non_obsolete_transactions() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
assert_eq!(run_validate(message_delivery_call(200)), Ok(ValidTransaction::default()),);
|
assert_eq!(run_validate(message_delivery_call(200)), Ok(ValidTransaction::default()),);
|
||||||
|
|
||||||
@@ -697,7 +590,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn ext_rejects_batch_with_obsolete_relay_chain_header() {
|
fn ext_rejects_batch_with_obsolete_relay_chain_header() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_pre_dispatch(all_finality_and_delivery_batch_call(100, 200, 200)),
|
run_pre_dispatch(all_finality_and_delivery_batch_call(100, 200, 200)),
|
||||||
@@ -714,7 +607,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn ext_rejects_batch_with_obsolete_parachain_head() {
|
fn ext_rejects_batch_with_obsolete_parachain_head() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_pre_dispatch(all_finality_and_delivery_batch_call(101, 100, 200)),
|
run_pre_dispatch(all_finality_and_delivery_batch_call(101, 100, 200)),
|
||||||
@@ -741,7 +634,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn ext_rejects_batch_with_obsolete_messages() {
|
fn ext_rejects_batch_with_obsolete_messages() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 100)),
|
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 100)),
|
||||||
@@ -768,7 +661,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pre_dispatch_parses_batch_with_relay_chain_and_parachain_headers() {
|
fn pre_dispatch_parses_batch_with_relay_chain_and_parachain_headers() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
|
run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)),
|
||||||
@@ -780,7 +673,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pre_dispatch_parses_batch_with_parachain_header() {
|
fn pre_dispatch_parses_batch_with_parachain_header() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
|
run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)),
|
||||||
@@ -792,7 +685,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pre_dispatch_fails_to_parse_batch_with_multiple_parachain_headers() {
|
fn pre_dispatch_fails_to_parse_batch_with_multiple_parachain_headers() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
let call = RuntimeCall::Utility(UtilityCall::batch_all {
|
let call = RuntimeCall::Utility(UtilityCall::batch_all {
|
||||||
calls: vec![
|
calls: vec![
|
||||||
@@ -815,7 +708,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pre_dispatch_parses_message_delivery_transaction() {
|
fn pre_dispatch_parses_message_delivery_transaction() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, 100);
|
initialize_environment(100, 100, Default::default(), 100);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_pre_dispatch(message_delivery_call(200)),
|
run_pre_dispatch(message_delivery_call(200)),
|
||||||
@@ -844,7 +737,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_ignores_transaction_that_has_not_updated_relay_chain_state() {
|
fn post_dispatch_ignores_transaction_that_has_not_updated_relay_chain_state() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 200, 200);
|
initialize_environment(100, 200, Default::default(), 200);
|
||||||
|
|
||||||
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
|
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
|
||||||
});
|
});
|
||||||
@@ -853,7 +746,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_ignores_transaction_that_has_not_updated_parachain_state() {
|
fn post_dispatch_ignores_transaction_that_has_not_updated_parachain_state() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 100, 200);
|
initialize_environment(200, 100, Default::default(), 200);
|
||||||
|
|
||||||
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
|
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
|
||||||
assert_storage_noop!(run_post_dispatch(
|
assert_storage_noop!(run_post_dispatch(
|
||||||
@@ -866,7 +759,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_ignores_transaction_that_has_not_delivered_any_messages() {
|
fn post_dispatch_ignores_transaction_that_has_not_delivered_any_messages() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 200, 100);
|
initialize_environment(200, 200, Default::default(), 100);
|
||||||
|
|
||||||
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
|
assert_storage_noop!(run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(())));
|
||||||
assert_storage_noop!(run_post_dispatch(
|
assert_storage_noop!(run_post_dispatch(
|
||||||
@@ -880,7 +773,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_refunds_relayer_in_all_finality_batch() {
|
fn post_dispatch_refunds_relayer_in_all_finality_batch() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 200, 200);
|
initialize_environment(200, 200, [1u8; 32].into(), 200);
|
||||||
|
|
||||||
run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(()));
|
run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -896,7 +789,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_refunds_relayer_in_parachain_finality_batch() {
|
fn post_dispatch_refunds_relayer_in_parachain_finality_batch() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 200, 200);
|
initialize_environment(200, 200, [1u8; 32].into(), 200);
|
||||||
|
|
||||||
run_post_dispatch(Some(parachain_finality_pre_dispatch_data()), Ok(()));
|
run_post_dispatch(Some(parachain_finality_pre_dispatch_data()), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -912,7 +805,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_refunds_relayer_in_message_delivery_transaction() {
|
fn post_dispatch_refunds_relayer_in_message_delivery_transaction() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 200, 200);
|
initialize_environment(200, 200, Default::default(), 200);
|
||||||
|
|
||||||
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
|
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ It'd be better for anyone (for chain and for submitters) to reject all transacti
|
|||||||
already known headers to the pallet. This way, we leave block space to other useful transactions and
|
already known headers to the pallet. This way, we leave block space to other useful transactions and
|
||||||
we don't charge concurrent submitters for their honest actions.
|
we don't charge concurrent submitters for their honest actions.
|
||||||
|
|
||||||
To deal with that, we have a [signed extension](./src/extension.rs) that may be added to the runtime.
|
To deal with that, we have a [signed extension](./src/call_ext) that may be added to the runtime.
|
||||||
It does exactly what is required - rejects all transactions with already known headers. The submitter
|
It does exactly what is required - rejects all transactions with already known headers. The submitter
|
||||||
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
||||||
is built.
|
is built.
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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<T: Config<I>, I: 'static> {
|
||||||
|
pub _phantom_data: sp_std::marker::PhantomData<(T, I)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config<I>, I: 'static> SubmitFinalityProofHelper<T, I> {
|
||||||
|
/// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best
|
||||||
|
/// one we know.
|
||||||
|
pub fn check_obsolete(
|
||||||
|
finality_target: BlockNumberOf<T::BridgedChain>,
|
||||||
|
) -> Result<(), Error<T, I>> {
|
||||||
|
let best_finalized = crate::BestFinalized::<T, I>::get().ok_or_else(|| {
|
||||||
|
log::trace!(
|
||||||
|
target: crate::LOG_TARGET,
|
||||||
|
"Cannot finalize header {:?} because pallet is not yet initialized",
|
||||||
|
finality_target,
|
||||||
|
);
|
||||||
|
<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,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(Error::<T, I>::OldHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the `SubmitFinalityProof` was successfully executed.
|
||||||
|
pub fn was_successful(finality_target: BlockNumberOf<T::BridgedChain>) -> bool {
|
||||||
|
match crate::BestFinalized::<T, I>::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<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||||
|
IsSubType<CallableCallFor<Pallet<T, I>, T>>
|
||||||
|
{
|
||||||
|
/// Extract the finality target from a `SubmitParachainHeads` call.
|
||||||
|
fn submit_finality_proof_info(&self) -> Option<BlockNumberOf<T::BridgedChain>> {
|
||||||
|
if let Some(crate::Call::<T, I>::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::<T, I>::check_obsolete(finality_target) {
|
||||||
|
Ok(_) => Ok(ValidTransaction::default()),
|
||||||
|
Err(Error::<T, I>::OldHeader) => InvalidTransaction::Stale.into(),
|
||||||
|
Err(_) => InvalidTransaction::Call.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config<I>, I: 'static> CallSubType<T, I> for T::RuntimeCall where
|
||||||
|
T::RuntimeCall: IsSubType<CallableCallFor<Pallet<T, I>, 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::<TestRuntime, ()>::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::<TestRuntime, ()>::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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
// 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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<CallableCallFor<Pallet<T, I>, T>>,
|
|
||||||
T: frame_system::Config<RuntimeCall = Call> + Config<I>,
|
|
||||||
I: 'static,
|
|
||||||
> FilterCall<Call> for Pallet<T, I>
|
|
||||||
{
|
|
||||||
fn validate(call: &<T as frame_system::Config>::RuntimeCall) -> TransactionValidity {
|
|
||||||
let bundled_block_number = match call.is_sub_type() {
|
|
||||||
Some(crate::Call::<T, I>::submit_finality_proof { ref finality_target, .. }) =>
|
|
||||||
*finality_target.number(),
|
|
||||||
_ => return Ok(ValidTransaction::default()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let best_finalized = crate::BestFinalized::<T, I>::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::<TestRuntime>::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::<TestRuntime, ()>::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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -44,7 +44,7 @@ use bp_header_chain::{
|
|||||||
};
|
};
|
||||||
use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
|
use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderId, HeaderOf, OwnedBridgeModule};
|
||||||
use finality_grandpa::voter_set::VoterSet;
|
use finality_grandpa::voter_set::VoterSet;
|
||||||
use frame_support::{dispatch::PostDispatchInfo, ensure, fail};
|
use frame_support::{dispatch::PostDispatchInfo, ensure};
|
||||||
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
|
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
traits::{Header as HeaderT, Zero},
|
traits::{Header as HeaderT, Zero},
|
||||||
@@ -52,7 +52,7 @@ use sp_runtime::{
|
|||||||
};
|
};
|
||||||
use sp_std::{boxed::Box, convert::TryInto};
|
use sp_std::{boxed::Box, convert::TryInto};
|
||||||
|
|
||||||
mod extension;
|
mod call_ext;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mock;
|
mod mock;
|
||||||
mod storage_types;
|
mod storage_types;
|
||||||
@@ -64,6 +64,7 @@ pub mod weights;
|
|||||||
pub mod benchmarking;
|
pub mod benchmarking;
|
||||||
|
|
||||||
// Re-export in crate namespace for `construct_runtime!`
|
// Re-export in crate namespace for `construct_runtime!`
|
||||||
|
pub use call_ext::*;
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
pub use weights::WeightInfo;
|
pub use weights::WeightInfo;
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ pub mod pallet {
|
|||||||
/// If successful in verification, it will write the target header to the underlying storage
|
/// If successful in verification, it will write the target header to the underlying storage
|
||||||
/// pallet.
|
/// pallet.
|
||||||
#[pallet::call_index(0)]
|
#[pallet::call_index(0)]
|
||||||
#[pallet::weight(T::WeightInfo::submit_finality_proof(
|
#[pallet::weight(<T::WeightInfo as WeightInfo>::submit_finality_proof(
|
||||||
justification.commit.precommits.len().saturated_into(),
|
justification.commit.precommits.len().saturated_into(),
|
||||||
justification.votes_ancestries.len().saturated_into(),
|
justification.votes_ancestries.len().saturated_into(),
|
||||||
))]
|
))]
|
||||||
@@ -174,22 +175,7 @@ pub mod pallet {
|
|||||||
finality_target
|
finality_target
|
||||||
);
|
);
|
||||||
|
|
||||||
let best_finalized_number = match BestFinalized::<T, I>::get() {
|
SubmitFinalityProofHelper::<T, I>::check_obsolete(*number)?;
|
||||||
Some(best_finalized_id) => best_finalized_id.number(),
|
|
||||||
None => {
|
|
||||||
log::error!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
"Cannot finalize header {:?} because pallet is not yet initialized",
|
|
||||||
finality_target,
|
|
||||||
);
|
|
||||||
fail!(<Error<T, I>>::NotInitialized);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// We do a quick check here to ensure that our header chain is making progress and isn't
|
|
||||||
// "travelling back in time" (which could be indicative of something bad, e.g a
|
|
||||||
// hard-fork).
|
|
||||||
ensure!(best_finalized_number < *number, <Error<T, I>>::OldHeader);
|
|
||||||
|
|
||||||
let authority_set = <CurrentAuthoritySet<T, I>>::get();
|
let authority_set = <CurrentAuthoritySet<T, I>>::get();
|
||||||
let unused_proof_size = authority_set.unused_proof_size();
|
let unused_proof_size = authority_set.unused_proof_size();
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ It'd be better for anyone (for chain and for submitters) to reject all transacti
|
|||||||
already known parachain heads to the pallet. This way, we leave block space to other useful transactions and
|
already known parachain heads to the pallet. This way, we leave block space to other useful transactions and
|
||||||
we don't charge concurrent submitters for their honest actions.
|
we don't charge concurrent submitters for their honest actions.
|
||||||
|
|
||||||
To deal with that, we have a [signed extension](./src/extension.rs) that may be added to the runtime.
|
To deal with that, we have a [signed extension](./src/call_ext) that may be added to the runtime.
|
||||||
It does exactly what is required - rejects all transactions with already known heads. The submitter
|
It does exactly what is required - rejects all transactions with already known heads. The submitter
|
||||||
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
pays nothing for such transactions - they're simply removed from the transaction pool, when the block
|
||||||
is built.
|
is built.
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::{Config, Pallet, RelayBlockNumber};
|
||||||
|
use bp_parachains::BestParaHeadHash;
|
||||||
|
use bp_polkadot_core::parachains::{ParaHash, ParaId};
|
||||||
|
use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
|
||||||
|
use sp_runtime::{
|
||||||
|
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
|
||||||
|
RuntimeDebug,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Info about a `SubmitParachainHeads` call which tries to update a single parachain.
|
||||||
|
#[derive(Copy, Clone, PartialEq, RuntimeDebug)]
|
||||||
|
pub struct SubmitParachainHeadsInfo {
|
||||||
|
pub at_relay_block_number: RelayBlockNumber,
|
||||||
|
pub para_id: ParaId,
|
||||||
|
pub para_head_hash: ParaHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper struct that provides methods for working with the `SubmitParachainHeads` call.
|
||||||
|
pub struct SubmitParachainHeadsHelper<T: Config<I>, I: 'static> {
|
||||||
|
pub _phantom_data: sp_std::marker::PhantomData<(T, I)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config<I>, I: 'static> SubmitParachainHeadsHelper<T, I> {
|
||||||
|
/// Check if the para head provided by the `SubmitParachainHeads` is better than the best one
|
||||||
|
/// we know.
|
||||||
|
pub fn is_obsolete(update: &SubmitParachainHeadsInfo) -> bool {
|
||||||
|
let stored_best_head = match crate::ParasInfo::<T, I>::get(update.para_id) {
|
||||||
|
Some(stored_best_head) => stored_best_head,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if stored_best_head.best_head_hash.at_relay_block_number >= update.at_relay_block_number {
|
||||||
|
log::trace!(
|
||||||
|
target: crate::LOG_TARGET,
|
||||||
|
"The parachain head can't be updated. The parachain head for {:?} \
|
||||||
|
was already updated at better relay chain block {} >= {}.",
|
||||||
|
update.para_id,
|
||||||
|
stored_best_head.best_head_hash.at_relay_block_number,
|
||||||
|
update.at_relay_block_number
|
||||||
|
);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if stored_best_head.best_head_hash.head_hash == update.para_head_hash {
|
||||||
|
log::trace!(
|
||||||
|
target: crate::LOG_TARGET,
|
||||||
|
"The parachain head can't be updated. The parachain head hash for {:?} \
|
||||||
|
was already updated to {} at block {} < {}.",
|
||||||
|
update.para_id,
|
||||||
|
update.para_head_hash,
|
||||||
|
stored_best_head.best_head_hash.at_relay_block_number,
|
||||||
|
update.at_relay_block_number
|
||||||
|
);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the `SubmitParachainHeads` was successfully executed.
|
||||||
|
pub fn was_successful(update: &SubmitParachainHeadsInfo) -> bool {
|
||||||
|
match crate::ParasInfo::<T, I>::get(update.para_id) {
|
||||||
|
Some(stored_best_head) =>
|
||||||
|
stored_best_head.best_head_hash ==
|
||||||
|
BestParaHeadHash {
|
||||||
|
at_relay_block_number: update.at_relay_block_number,
|
||||||
|
head_hash: update.para_head_hash,
|
||||||
|
},
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait representing a call that is a sub type of this pallet's call.
|
||||||
|
pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
|
||||||
|
IsSubType<CallableCallFor<Pallet<T, I>, T>>
|
||||||
|
{
|
||||||
|
/// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with
|
||||||
|
/// one single parachain entry.
|
||||||
|
fn one_entry_submit_parachain_heads_info(&self) -> Option<SubmitParachainHeadsInfo> {
|
||||||
|
if let Some(crate::Call::<T, I>::submit_parachain_heads {
|
||||||
|
ref at_relay_block,
|
||||||
|
ref parachains,
|
||||||
|
..
|
||||||
|
}) = self.is_sub_type()
|
||||||
|
{
|
||||||
|
if let &[(para_id, para_head_hash)] = parachains.as_slice() {
|
||||||
|
return Some(SubmitParachainHeadsInfo {
|
||||||
|
at_relay_block_number: at_relay_block.0,
|
||||||
|
para_id,
|
||||||
|
para_head_hash,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new instance of `SubmitParachainHeadsInfo` from a `SubmitParachainHeads` call with
|
||||||
|
/// one single parachain entry, if the entry is for the provided parachain id.
|
||||||
|
fn submit_parachain_heads_info_for(&self, para_id: u32) -> Option<SubmitParachainHeadsInfo> {
|
||||||
|
self.one_entry_submit_parachain_heads_info()
|
||||||
|
.filter(|update| update.para_id.0 == para_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate parachain heads in order to avoid "mining" transactions that provide
|
||||||
|
/// outdated bridged parachain heads. Without this validation, even honest relayers
|
||||||
|
/// may lose their funds if there are multiple relays running and submitting the
|
||||||
|
/// same information.
|
||||||
|
///
|
||||||
|
/// This validation only works with transactions that are updating single parachain
|
||||||
|
/// head. We can't use unbounded validation - it may take too long and either break
|
||||||
|
/// block production, or "eat" significant portion of block production time literally
|
||||||
|
/// for nothing. In addition, the single-parachain-head-per-transaction is how the
|
||||||
|
/// pallet will be used in our environment.
|
||||||
|
fn check_obsolete_submit_parachain_heads(&self) -> TransactionValidity
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let update = match self.one_entry_submit_parachain_heads_info() {
|
||||||
|
Some(update) => update,
|
||||||
|
None => return Ok(ValidTransaction::default()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if SubmitParachainHeadsHelper::<T, I>::is_obsolete(&update) {
|
||||||
|
return InvalidTransaction::Stale.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ValidTransaction::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I: 'static> CallSubType<T, I> for T::RuntimeCall
|
||||||
|
where
|
||||||
|
T: Config<I>,
|
||||||
|
T::RuntimeCall: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
mock::{run_test, RuntimeCall, TestRuntime},
|
||||||
|
CallSubType, ParaInfo, ParasInfo, RelayBlockNumber,
|
||||||
|
};
|
||||||
|
use bp_parachains::BestParaHeadHash;
|
||||||
|
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
|
||||||
|
|
||||||
|
fn validate_submit_parachain_heads(
|
||||||
|
num: RelayBlockNumber,
|
||||||
|
parachains: Vec<(ParaId, ParaHash)>,
|
||||||
|
) -> bool {
|
||||||
|
RuntimeCall::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads {
|
||||||
|
at_relay_block: (num, Default::default()),
|
||||||
|
parachains,
|
||||||
|
parachain_heads_proof: ParaHeadsProof(Vec::new()),
|
||||||
|
})
|
||||||
|
.check_obsolete_submit_parachain_heads()
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_to_relay_header_10() {
|
||||||
|
ParasInfo::<TestRuntime, ()>::insert(
|
||||||
|
ParaId(1),
|
||||||
|
ParaInfo {
|
||||||
|
best_head_hash: BestParaHeadHash {
|
||||||
|
at_relay_block_number: 10,
|
||||||
|
head_hash: [1u8; 32].into(),
|
||||||
|
},
|
||||||
|
next_imported_hash_position: 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extension_rejects_header_from_the_obsolete_relay_block() {
|
||||||
|
run_test(|| {
|
||||||
|
// when current best finalized is #10 and we're trying to import header#5 => tx is
|
||||||
|
// rejected
|
||||||
|
sync_to_relay_header_10();
|
||||||
|
assert!(!validate_submit_parachain_heads(5, vec![(ParaId(1), [1u8; 32].into())]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extension_rejects_header_from_the_same_relay_block() {
|
||||||
|
run_test(|| {
|
||||||
|
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||||
|
// rejected
|
||||||
|
sync_to_relay_header_10();
|
||||||
|
assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extension_rejects_header_from_new_relay_block_with_same_hash() {
|
||||||
|
run_test(|| {
|
||||||
|
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
||||||
|
// rejected
|
||||||
|
sync_to_relay_header_10();
|
||||||
|
assert!(!validate_submit_parachain_heads(20, vec![(ParaId(1), [1u8; 32].into())]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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_relay_header_10();
|
||||||
|
assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extension_accepts_if_more_than_one_parachain_is_submitted() {
|
||||||
|
run_test(|| {
|
||||||
|
// when current best finalized is #10 and we're trying to import header#5, but another
|
||||||
|
// parachain head is also supplied => tx is accepted
|
||||||
|
sync_to_relay_header_10();
|
||||||
|
assert!(validate_submit_parachain_heads(
|
||||||
|
5,
|
||||||
|
vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())]
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
// 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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};
|
|
||||||
|
|
||||||
/// Validate parachain heads in order to avoid "mining" transactions that provide
|
|
||||||
/// outdated bridged parachain heads. Without this validation, even honest relayers
|
|
||||||
/// may lose their funds if there are multiple relays running and submitting the
|
|
||||||
/// same information.
|
|
||||||
///
|
|
||||||
/// This validation only works with transactions that are updating single parachain
|
|
||||||
/// head. We can't use unbounded validation - it may take too long and either break
|
|
||||||
/// block production, or "eat" significant portion of block production time literally
|
|
||||||
/// for nothing. In addition, the single-parachain-head-per-transaction is how the
|
|
||||||
/// pallet will be used in our environment.
|
|
||||||
impl<
|
|
||||||
Call: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
|
|
||||||
T: frame_system::Config<RuntimeCall = Call> + Config<I>,
|
|
||||||
I: 'static,
|
|
||||||
> FilterCall<Call> for Pallet<T, I>
|
|
||||||
where
|
|
||||||
<T as pallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
|
|
||||||
bp_runtime::Chain<
|
|
||||||
BlockNumber = RelayBlockNumber,
|
|
||||||
Hash = RelayBlockHash,
|
|
||||||
Hasher = RelayBlockHasher,
|
|
||||||
>,
|
|
||||||
{
|
|
||||||
fn validate(call: &Call) -> TransactionValidity {
|
|
||||||
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,
|
|
||||||
..
|
|
||||||
}) => (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 maybe_stored_best_head = crate::ParasInfo::<T, I>::get(parachain);
|
|
||||||
let is_valid = Self::validate_updated_parachain_head(
|
|
||||||
parachain,
|
|
||||||
&maybe_stored_best_head,
|
|
||||||
updated_at_relay_block_number,
|
|
||||||
parachain_head_hash,
|
|
||||||
"Rejecting obsolete parachain-head transaction",
|
|
||||||
);
|
|
||||||
|
|
||||||
if is_valid {
|
|
||||||
Ok(ValidTransaction::default())
|
|
||||||
} else {
|
|
||||||
InvalidTransaction::Stale.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::{
|
|
||||||
extension::FilterCall,
|
|
||||||
mock::{run_test, RuntimeCall, TestRuntime},
|
|
||||||
ParaInfo, ParasInfo, RelayBlockNumber,
|
|
||||||
};
|
|
||||||
use bp_parachains::BestParaHeadHash;
|
|
||||||
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
|
|
||||||
|
|
||||||
fn validate_submit_parachain_heads(
|
|
||||||
num: RelayBlockNumber,
|
|
||||||
parachains: Vec<(ParaId, ParaHash)>,
|
|
||||||
) -> bool {
|
|
||||||
crate::Pallet::<TestRuntime>::validate(&RuntimeCall::Parachains(crate::Call::<
|
|
||||||
TestRuntime,
|
|
||||||
(),
|
|
||||||
>::submit_parachain_heads {
|
|
||||||
at_relay_block: (num, Default::default()),
|
|
||||||
parachains,
|
|
||||||
parachain_heads_proof: ParaHeadsProof(Vec::new()),
|
|
||||||
}))
|
|
||||||
.is_ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_to_relay_header_10() {
|
|
||||||
ParasInfo::<TestRuntime, ()>::insert(
|
|
||||||
ParaId(1),
|
|
||||||
ParaInfo {
|
|
||||||
best_head_hash: BestParaHeadHash {
|
|
||||||
at_relay_block_number: 10,
|
|
||||||
head_hash: [1u8; 32].into(),
|
|
||||||
},
|
|
||||||
next_imported_hash_position: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extension_rejects_header_from_the_obsolete_relay_block() {
|
|
||||||
run_test(|| {
|
|
||||||
// when current best finalized is #10 and we're trying to import header#5 => tx is
|
|
||||||
// rejected
|
|
||||||
sync_to_relay_header_10();
|
|
||||||
assert!(!validate_submit_parachain_heads(5, vec![(ParaId(1), [1u8; 32].into())]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extension_rejects_header_from_the_same_relay_block() {
|
|
||||||
run_test(|| {
|
|
||||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
|
||||||
// rejected
|
|
||||||
sync_to_relay_header_10();
|
|
||||||
assert!(!validate_submit_parachain_heads(10, vec![(ParaId(1), [1u8; 32].into())]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extension_rejects_header_from_new_relay_block_with_same_hash() {
|
|
||||||
run_test(|| {
|
|
||||||
// when current best finalized is #10 and we're trying to import header#10 => tx is
|
|
||||||
// rejected
|
|
||||||
sync_to_relay_header_10();
|
|
||||||
assert!(!validate_submit_parachain_heads(20, vec![(ParaId(1), [1u8; 32].into())]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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_relay_header_10();
|
|
||||||
assert!(validate_submit_parachain_heads(15, vec![(ParaId(1), [2u8; 32].into())]));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extension_accepts_if_more_than_one_parachain_is_submitted() {
|
|
||||||
run_test(|| {
|
|
||||||
// when current best finalized is #10 and we're trying to import header#5, but another
|
|
||||||
// parachain head is also supplied => tx is accepted
|
|
||||||
sync_to_relay_header_10();
|
|
||||||
assert!(validate_submit_parachain_heads(
|
|
||||||
5,
|
|
||||||
vec![(ParaId(1), [1u8; 32].into()), (ParaId(2), [1u8; 32].into())]
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,6 +41,7 @@ use bp_runtime::HeaderOf;
|
|||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
|
|
||||||
// Re-export in crate namespace for `construct_runtime!`.
|
// Re-export in crate namespace for `construct_runtime!`.
|
||||||
|
pub use call_ext::*;
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
|
|
||||||
pub mod weights;
|
pub mod weights;
|
||||||
@@ -49,7 +50,7 @@ pub mod weights_ext;
|
|||||||
#[cfg(feature = "runtime-benchmarks")]
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
pub mod benchmarking;
|
pub mod benchmarking;
|
||||||
|
|
||||||
mod extension;
|
mod call_ext;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mock;
|
mod mock;
|
||||||
|
|
||||||
@@ -136,10 +137,30 @@ pub mod pallet {
|
|||||||
BridgeModule(bp_runtime::OwnedBridgeModuleError),
|
BridgeModule(bp_runtime::OwnedBridgeModuleError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience trait for defining `BridgedChain` bounds.
|
||||||
|
pub trait BoundedBridgeGrandpaConfig<I: 'static>:
|
||||||
|
pallet_bridge_grandpa::Config<I, BridgedChain = Self::BridgedRelayChain>
|
||||||
|
{
|
||||||
|
type BridgedRelayChain: Chain<
|
||||||
|
BlockNumber = RelayBlockNumber,
|
||||||
|
Hash = RelayBlockHash,
|
||||||
|
Hasher = RelayBlockHasher,
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, I: 'static> BoundedBridgeGrandpaConfig<I> for T
|
||||||
|
where
|
||||||
|
T: pallet_bridge_grandpa::Config<I>,
|
||||||
|
T::BridgedChain:
|
||||||
|
Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash, Hasher = RelayBlockHasher>,
|
||||||
|
{
|
||||||
|
type BridgedRelayChain = T::BridgedChain;
|
||||||
|
}
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
#[pallet::disable_frame_system_supertrait_check]
|
#[pallet::disable_frame_system_supertrait_check]
|
||||||
pub trait Config<I: 'static = ()>:
|
pub trait Config<I: 'static = ()>:
|
||||||
pallet_bridge_grandpa::Config<Self::BridgesGrandpaPalletInstance>
|
BoundedBridgeGrandpaConfig<Self::BridgesGrandpaPalletInstance>
|
||||||
{
|
{
|
||||||
/// The overarching event type.
|
/// The overarching event type.
|
||||||
type RuntimeEvent: From<Event<Self, I>>
|
type RuntimeEvent: From<Event<Self, I>>
|
||||||
@@ -267,15 +288,7 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::call]
|
#[pallet::call]
|
||||||
impl<T: Config<I>, I: 'static> Pallet<T, I>
|
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||||
where
|
|
||||||
<T as pallet_bridge_grandpa::Config<T::BridgesGrandpaPalletInstance>>::BridgedChain:
|
|
||||||
bp_runtime::Chain<
|
|
||||||
BlockNumber = RelayBlockNumber,
|
|
||||||
Hash = RelayBlockHash,
|
|
||||||
Hasher = RelayBlockHasher,
|
|
||||||
>,
|
|
||||||
{
|
|
||||||
/// Submit proof of one or several parachain heads.
|
/// Submit proof of one or several parachain heads.
|
||||||
///
|
///
|
||||||
/// The proof is supposed to be proof of some `Heads` entries from the
|
/// The proof is supposed to be proof of some `Heads` entries from the
|
||||||
@@ -483,89 +496,38 @@ pub mod pallet {
|
|||||||
storage.read_and_decode_value(parachain_head_key.0.as_ref())
|
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.
|
|
||||||
///
|
|
||||||
/// Returns `true` if the pallet is ready to import given parachain head.
|
|
||||||
/// Returns `false` if the pallet already knows the same or better parachain head.
|
|
||||||
#[must_use]
|
|
||||||
pub fn validate_updated_parachain_head(
|
|
||||||
parachain: ParaId,
|
|
||||||
maybe_stored_best_head: &Option<ParaInfo>,
|
|
||||||
updated_at_relay_block_number: RelayBlockNumber,
|
|
||||||
updated_head_hash: ParaHash,
|
|
||||||
err_log_prefix: &str,
|
|
||||||
) -> bool {
|
|
||||||
let stored_best_head = match maybe_stored_best_head {
|
|
||||||
Some(stored_best_head) => stored_best_head,
|
|
||||||
None => return true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if stored_best_head.best_head_hash.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.best_head_hash.at_relay_block_number,
|
|
||||||
updated_at_relay_block_number
|
|
||||||
);
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if stored_best_head.best_head_hash.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.best_head_hash.at_relay_block_number,
|
|
||||||
updated_at_relay_block_number
|
|
||||||
);
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to update parachain head.
|
/// Try to update parachain head.
|
||||||
pub(super) fn update_parachain_head(
|
pub(super) fn update_parachain_head(
|
||||||
parachain: ParaId,
|
parachain: ParaId,
|
||||||
stored_best_head: Option<ParaInfo>,
|
stored_best_head: Option<ParaInfo>,
|
||||||
updated_at_relay_block_number: RelayBlockNumber,
|
new_at_relay_block_number: RelayBlockNumber,
|
||||||
updated_head_data: ParaStoredHeaderData,
|
new_head_data: ParaStoredHeaderData,
|
||||||
updated_head_hash: ParaHash,
|
new_head_hash: ParaHash,
|
||||||
) -> Result<UpdateParachainHeadArtifacts, ()> {
|
) -> Result<UpdateParachainHeadArtifacts, ()> {
|
||||||
// check if head has been already updated at better relay chain block. Without this
|
// check if head has been already updated at better relay chain block. Without this
|
||||||
// check, we may import heads in random order
|
// check, we may import heads in random order
|
||||||
let err_log_prefix = "The parachain head can't be updated";
|
let update = SubmitParachainHeadsInfo {
|
||||||
let is_valid = Self::validate_updated_parachain_head(
|
at_relay_block_number: new_at_relay_block_number,
|
||||||
parachain,
|
para_id: parachain,
|
||||||
&stored_best_head,
|
para_head_hash: new_head_hash,
|
||||||
updated_at_relay_block_number,
|
};
|
||||||
updated_head_hash,
|
if SubmitParachainHeadsHelper::<T, I>::is_obsolete(&update) {
|
||||||
err_log_prefix,
|
|
||||||
);
|
|
||||||
if !is_valid {
|
|
||||||
Self::deposit_event(Event::RejectedObsoleteParachainHead {
|
Self::deposit_event(Event::RejectedObsoleteParachainHead {
|
||||||
parachain,
|
parachain,
|
||||||
parachain_head_hash: updated_head_hash,
|
parachain_head_hash: new_head_hash,
|
||||||
});
|
});
|
||||||
return Err(())
|
return Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify that the parachain head data size is <= `MaxParaHeadDataSize`
|
// verify that the parachain head data size is <= `MaxParaHeadDataSize`
|
||||||
let updated_head_data =
|
let updated_head_data =
|
||||||
match StoredParaHeadDataOf::<T, I>::try_from_inner(updated_head_data) {
|
match StoredParaHeadDataOf::<T, I>::try_from_inner(new_head_data) {
|
||||||
Ok(updated_head_data) => updated_head_data,
|
Ok(updated_head_data) => updated_head_data,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"{}. The parachain head data size for {:?} is {}. It exceeds maximal configured size {}.",
|
"The parachain head can't be updated. The parachain head data size \
|
||||||
err_log_prefix,
|
for {:?} is {}. It exceeds maximal configured size {}.",
|
||||||
parachain,
|
parachain,
|
||||||
e.value_size,
|
e.value_size,
|
||||||
e.maximal_size,
|
e.maximal_size,
|
||||||
@@ -573,7 +535,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
Self::deposit_event(Event::RejectedLargeParachainHead {
|
Self::deposit_event(Event::RejectedLargeParachainHead {
|
||||||
parachain,
|
parachain,
|
||||||
parachain_head_hash: updated_head_hash,
|
parachain_head_hash: new_head_hash,
|
||||||
parachain_head_size: e.value_size as _,
|
parachain_head_size: e.value_size as _,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -589,8 +551,8 @@ pub mod pallet {
|
|||||||
ImportedParaHashes::<T, I>::try_get(parachain, next_imported_hash_position);
|
ImportedParaHashes::<T, I>::try_get(parachain, next_imported_hash_position);
|
||||||
let updated_best_para_head = ParaInfo {
|
let updated_best_para_head = ParaInfo {
|
||||||
best_head_hash: BestParaHeadHash {
|
best_head_hash: BestParaHeadHash {
|
||||||
at_relay_block_number: updated_at_relay_block_number,
|
at_relay_block_number: new_at_relay_block_number,
|
||||||
head_hash: updated_head_hash,
|
head_hash: new_head_hash,
|
||||||
},
|
},
|
||||||
next_imported_hash_position: (next_imported_hash_position + 1) %
|
next_imported_hash_position: (next_imported_hash_position + 1) %
|
||||||
T::HeadsToKeep::get(),
|
T::HeadsToKeep::get(),
|
||||||
@@ -598,14 +560,14 @@ pub mod pallet {
|
|||||||
ImportedParaHashes::<T, I>::insert(
|
ImportedParaHashes::<T, I>::insert(
|
||||||
parachain,
|
parachain,
|
||||||
next_imported_hash_position,
|
next_imported_hash_position,
|
||||||
updated_head_hash,
|
new_head_hash,
|
||||||
);
|
);
|
||||||
ImportedParaHeads::<T, I>::insert(parachain, updated_head_hash, updated_head_data);
|
ImportedParaHeads::<T, I>::insert(parachain, new_head_hash, updated_head_data);
|
||||||
log::trace!(
|
log::trace!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"Updated head of parachain {:?} to {}",
|
"Updated head of parachain {:?} to {}",
|
||||||
parachain,
|
parachain,
|
||||||
updated_head_hash,
|
new_head_hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
// remove old head
|
// remove old head
|
||||||
@@ -621,7 +583,7 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
Self::deposit_event(Event::UpdatedParachainHead {
|
Self::deposit_event(Event::UpdatedParachainHead {
|
||||||
parachain,
|
parachain,
|
||||||
parachain_head_hash: updated_head_hash,
|
parachain_head_hash: new_head_hash,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(UpdateParachainHeadArtifacts { best_head: updated_best_para_head, prune_happened })
|
Ok(UpdateParachainHeadArtifacts { best_head: updated_best_para_head, prune_happened })
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ pub use chain::{
|
|||||||
};
|
};
|
||||||
pub use frame_support::storage::storage_prefix as storage_value_final_key;
|
pub use frame_support::storage::storage_prefix as storage_value_final_key;
|
||||||
use num_traits::{CheckedSub, One};
|
use num_traits::{CheckedSub, One};
|
||||||
use sp_runtime::transaction_validity::TransactionValidity;
|
|
||||||
pub use storage_proof::{
|
pub use storage_proof::{
|
||||||
record_all_keys as record_all_trie_keys, Error as StorageProofError,
|
record_all_keys as record_all_trie_keys, Error as StorageProofError,
|
||||||
ProofSize as StorageProofSize, StorageProofChecker,
|
ProofSize as StorageProofSize, StorageProofChecker,
|
||||||
@@ -490,12 +489,6 @@ pub trait OwnedBridgeModule<T: frame_system::Config> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for querying whether a runtime call is valid.
|
|
||||||
pub trait FilterCall<Call> {
|
|
||||||
/// Checks if a runtime call is valid.
|
|
||||||
fn validate(call: &Call) -> TransactionValidity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// All extra operations with weights that we need in bridges.
|
/// All extra operations with weights that we need in bridges.
|
||||||
pub trait WeightExtraOps {
|
pub trait WeightExtraOps {
|
||||||
/// Checked division of individual components of two weights.
|
/// Checked division of individual components of two weights.
|
||||||
|
|||||||
Reference in New Issue
Block a user