mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 11:01:01 +00:00
Slash relayers for invalid transactions (#2025)
* slash relayer balance for invalid transactions * require some gap before unstake is possible * more clippy * log priority boost * add issue ref to TODO * fix typo * is_message_delivery_call -> is_receive_messages_proof_call * moved is_receive_messages_proof_call above * only slash relayers for priority transactions * Update primitives/relayers/src/registration.rs Co-authored-by: Adrian Catangiu <adrian@parity.io> * Update primitives/relayers/src/registration.rs Co-authored-by: Adrian Catangiu <adrian@parity.io> * Update bin/runtime-common/src/refund_relayer_extension.rs Co-authored-by: Adrian Catangiu <adrian@parity.io> * Update bin/runtime-common/src/refund_relayer_extension.rs Co-authored-by: Adrian Catangiu <adrian@parity.io> * Update bin/runtime-common/src/refund_relayer_extension.rs Co-authored-by: Adrian Catangiu <adrian@parity.io> * Update modules/relayers/src/lib.rs Co-authored-by: Adrian Catangiu <adrian@parity.io> * Update primitives/relayers/src/registration.rs Co-authored-by: Adrian Catangiu <adrian@parity.io> * benificiary -> beneficiary --------- Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
committed by
Bastian Köcher
parent
3b47f957db
commit
53e1b7e264
@@ -372,6 +372,7 @@ parameter_types! {
|
|||||||
/// Authorities are changing every 5 minutes.
|
/// Authorities are changing every 5 minutes.
|
||||||
pub const Period: BlockNumber = bp_millau::SESSION_LENGTH;
|
pub const Period: BlockNumber = bp_millau::SESSION_LENGTH;
|
||||||
pub const Offset: BlockNumber = 0;
|
pub const Offset: BlockNumber = 0;
|
||||||
|
pub const RelayerStakeReserveId: [u8; 8] = *b"brdgrlrs";
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_session::Config for Runtime {
|
impl pallet_session::Config for Runtime {
|
||||||
@@ -392,6 +393,14 @@ impl pallet_bridge_relayers::Config for Runtime {
|
|||||||
type Reward = Balance;
|
type Reward = Balance;
|
||||||
type PaymentProcedure =
|
type PaymentProcedure =
|
||||||
bp_relayers::PayRewardFromAccount<pallet_balances::Pallet<Runtime>, AccountId>;
|
bp_relayers::PayRewardFromAccount<pallet_balances::Pallet<Runtime>, AccountId>;
|
||||||
|
type StakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
|
||||||
|
AccountId,
|
||||||
|
BlockNumber,
|
||||||
|
Balances,
|
||||||
|
RelayerStakeReserveId,
|
||||||
|
ConstU64<1_000>,
|
||||||
|
ConstU64<8>,
|
||||||
|
>;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -533,6 +533,7 @@ impl pallet_bridge_relayers::Config for Runtime {
|
|||||||
type Reward = Balance;
|
type Reward = Balance;
|
||||||
type PaymentProcedure =
|
type PaymentProcedure =
|
||||||
bp_relayers::PayRewardFromAccount<pallet_balances::Pallet<Runtime>, AccountId>;
|
bp_relayers::PayRewardFromAccount<pallet_balances::Pallet<Runtime>, AccountId>;
|
||||||
|
type StakeAndSlash = ();
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -389,6 +389,7 @@ impl pallet_bridge_relayers::Config for Runtime {
|
|||||||
type Reward = Balance;
|
type Reward = Balance;
|
||||||
type PaymentProcedure =
|
type PaymentProcedure =
|
||||||
bp_relayers::PayRewardFromAccount<pallet_balances::Pallet<Runtime>, AccountId>;
|
bp_relayers::PayRewardFromAccount<pallet_balances::Pallet<Runtime>, AccountId>;
|
||||||
|
type StakeAndSlash = ();
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pallet-bridge-relayers = { path = "../../modules/relayers", default-features = f
|
|||||||
|
|
||||||
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
|
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
|
||||||
@@ -62,6 +63,7 @@ std = [
|
|||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"hash-db/std",
|
"hash-db/std",
|
||||||
"log/std",
|
"log/std",
|
||||||
|
"pallet-balances/std",
|
||||||
"pallet-bridge-grandpa/std",
|
"pallet-bridge-grandpa/std",
|
||||||
"pallet-bridge-messages/std",
|
"pallet-bridge-messages/std",
|
||||||
"pallet-bridge-parachains/std",
|
"pallet-bridge-parachains/std",
|
||||||
|
|||||||
@@ -115,6 +115,16 @@ pub enum CallInfo {
|
|||||||
ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo),
|
ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CallInfo {
|
||||||
|
/// Returns range of messages, bundled with the call.
|
||||||
|
pub fn bundled_messages(&self) -> RangeInclusive<MessageNonce> {
|
||||||
|
match *self {
|
||||||
|
Self::ReceiveMessagesProof(ref info) => info.base.bundled_range.clone(),
|
||||||
|
Self::ReceiveMessagesDeliveryProof(ref info) => info.0.bundled_range.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper struct that provides methods for working with a call supported by `CallInfo`.
|
/// Helper struct that provides methods for working with a call supported by `CallInfo`.
|
||||||
pub struct CallHelper<T: Config<I>, I: 'static> {
|
pub struct CallHelper<T: Config<I>, I: 'static> {
|
||||||
pub _phantom_data: sp_std::marker::PhantomData<(T, I)>,
|
pub _phantom_data: sp_std::marker::PhantomData<(T, I)>,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ use crate::messages::{
|
|||||||
use bp_header_chain::{ChainWithGrandpa, HeaderChain};
|
use bp_header_chain::{ChainWithGrandpa, HeaderChain};
|
||||||
use bp_messages::{target_chain::ForbidInboundMessages, LaneId, MessageNonce};
|
use bp_messages::{target_chain::ForbidInboundMessages, LaneId, MessageNonce};
|
||||||
use bp_parachains::SingleParaStoredHeaderDataBuilder;
|
use bp_parachains::SingleParaStoredHeaderDataBuilder;
|
||||||
|
use bp_relayers::PayRewardFromAccount;
|
||||||
use bp_runtime::{Chain, ChainId, Parachain, UnderlyingChainProvider};
|
use bp_runtime::{Chain, ChainId, Parachain, UnderlyingChainProvider};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
@@ -83,6 +84,20 @@ pub type BridgedChainHasher = BlakeTwo256;
|
|||||||
pub type BridgedChainHeader =
|
pub type BridgedChainHeader =
|
||||||
sp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
|
sp_runtime::generic::Header<BridgedChainBlockNumber, BridgedChainHasher>;
|
||||||
|
|
||||||
|
/// Rewards payment procedure.
|
||||||
|
pub type TestPaymentProcedure = PayRewardFromAccount<Balances, ThisChainAccountId>;
|
||||||
|
/// Stake that we are using in tests.
|
||||||
|
pub type TestStake = ConstU64<5_000>;
|
||||||
|
/// Stake and slash mechanism to use in tests.
|
||||||
|
pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
|
||||||
|
ThisChainAccountId,
|
||||||
|
ThisChainBlockNumber,
|
||||||
|
Balances,
|
||||||
|
ReserveId,
|
||||||
|
TestStake,
|
||||||
|
ConstU32<8>,
|
||||||
|
>;
|
||||||
|
|
||||||
/// Message lane used in tests.
|
/// Message lane used in tests.
|
||||||
pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 0]);
|
pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 0]);
|
||||||
/// Bridged chain id used in tests.
|
/// Bridged chain id used in tests.
|
||||||
@@ -128,6 +143,7 @@ parameter_types! {
|
|||||||
pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value();
|
pub MaximumMultiplier: Multiplier = sp_runtime::traits::Bounded::max_value();
|
||||||
pub const MaxUnrewardedRelayerEntriesAtInboundLane: MessageNonce = 16;
|
pub const MaxUnrewardedRelayerEntriesAtInboundLane: MessageNonce = 16;
|
||||||
pub const MaxUnconfirmedMessagesAtInboundLane: MessageNonce = 1_000;
|
pub const MaxUnconfirmedMessagesAtInboundLane: MessageNonce = 1_000;
|
||||||
|
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
|
||||||
}
|
}
|
||||||
|
|
||||||
impl frame_system::Config for TestRuntime {
|
impl frame_system::Config for TestRuntime {
|
||||||
@@ -244,7 +260,8 @@ impl pallet_bridge_messages::Config for TestRuntime {
|
|||||||
impl pallet_bridge_relayers::Config for TestRuntime {
|
impl pallet_bridge_relayers::Config for TestRuntime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type Reward = ThisChainBalance;
|
type Reward = ThisChainBalance;
|
||||||
type PaymentProcedure = ();
|
type PaymentProcedure = TestPaymentProcedure;
|
||||||
|
type StakeAndSlash = TestStakeAndSlash;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,3 +417,8 @@ impl ThisChainWithMessages for BridgedChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BridgedChainWithMessages for BridgedChain {}
|
impl BridgedChainWithMessages for BridgedChain {}
|
||||||
|
|
||||||
|
/// Run test within test externalities.
|
||||||
|
pub fn run_test(test: impl FnOnce()) {
|
||||||
|
sp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
use crate::messages_call_ext::{
|
use crate::messages_call_ext::{
|
||||||
CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType,
|
CallHelper as MessagesCallHelper, CallInfo as MessagesCallInfo, MessagesCallSubType,
|
||||||
};
|
};
|
||||||
use bp_messages::LaneId;
|
use bp_messages::{LaneId, MessageNonce};
|
||||||
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
|
use bp_relayers::{RewardsAccountOwner, RewardsAccountParams};
|
||||||
use bp_runtime::{RangeInclusiveExt, StaticStrProvider};
|
use bp_runtime::{RangeInclusiveExt, StaticStrProvider};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
@@ -30,7 +30,7 @@ use frame_support::{
|
|||||||
dispatch::{CallableCallFor, DispatchInfo, Dispatchable, PostDispatchInfo},
|
dispatch::{CallableCallFor, DispatchInfo, Dispatchable, PostDispatchInfo},
|
||||||
traits::IsSubType,
|
traits::IsSubType,
|
||||||
weights::Weight,
|
weights::Weight,
|
||||||
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebug, RuntimeDebugNoBound,
|
||||||
};
|
};
|
||||||
use pallet_bridge_grandpa::{
|
use pallet_bridge_grandpa::{
|
||||||
CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo,
|
CallSubType as GrandpaCallSubType, SubmitFinalityProofHelper, SubmitFinalityProofInfo,
|
||||||
@@ -53,6 +53,7 @@ use sp_runtime::{
|
|||||||
};
|
};
|
||||||
use sp_std::{marker::PhantomData, vec, vec::Vec};
|
use sp_std::{marker::PhantomData, vec, vec::Vec};
|
||||||
|
|
||||||
|
type AccountIdOf<R> = <R as frame_system::Config>::AccountId;
|
||||||
// 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;
|
||||||
@@ -158,6 +159,14 @@ pub enum CallInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CallInfo {
|
impl CallInfo {
|
||||||
|
/// Returns true if call is a message delivery call (with optional finality calls).
|
||||||
|
fn is_receive_messages_proof_call(&self) -> bool {
|
||||||
|
match self.messages_call_info() {
|
||||||
|
MessagesCallInfo::ReceiveMessagesProof(_) => true,
|
||||||
|
MessagesCallInfo::ReceiveMessagesDeliveryProof(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the pre-dispatch `finality_target` sent to the `SubmitFinalityProof` call.
|
/// Returns the pre-dispatch `finality_target` sent to the `SubmitFinalityProof` call.
|
||||||
fn submit_finality_proof_info(&self) -> Option<SubmitFinalityProofInfo<RelayBlockNumber>> {
|
fn submit_finality_proof_info(&self) -> Option<SubmitFinalityProofInfo<RelayBlockNumber>> {
|
||||||
match *self {
|
match *self {
|
||||||
@@ -185,6 +194,17 @@ impl CallInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The actions on relayer account that need to be performed because of his actions.
|
||||||
|
#[derive(RuntimeDebug, PartialEq)]
|
||||||
|
enum RelayerAccountAction<AccountId, Reward> {
|
||||||
|
/// Do nothing with relayer account.
|
||||||
|
None,
|
||||||
|
/// Reward the relayer.
|
||||||
|
Reward(AccountId, RewardsAccountParams, Reward),
|
||||||
|
/// Slash the relayer.
|
||||||
|
Slash(AccountId, RewardsAccountParams),
|
||||||
|
}
|
||||||
|
|
||||||
/// Signed extension that refunds a relayer for new messages coming from a parachain.
|
/// Signed extension that refunds a relayer for new messages coming from a 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`)
|
||||||
@@ -205,7 +225,25 @@ impl CallInfo {
|
|||||||
)]
|
)]
|
||||||
#[scale_info(skip_type_params(Runtime, Para, Msgs, Refund, Priority, Id))]
|
#[scale_info(skip_type_params(Runtime, Para, Msgs, Refund, Priority, Id))]
|
||||||
pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>(
|
pub struct RefundBridgedParachainMessages<Runtime, Para, Msgs, Refund, Priority, Id>(
|
||||||
PhantomData<(Runtime, Para, Msgs, Refund, Priority, Id)>,
|
PhantomData<(
|
||||||
|
// runtime with `frame-utility`, `pallet-bridge-grandpa`, `pallet-bridge-parachains`,
|
||||||
|
// `pallet-bridge-messages` and `pallet-bridge-relayers` pallets deployed
|
||||||
|
Runtime,
|
||||||
|
// implementation of `RefundableParachainId` trait, which specifies the instance of
|
||||||
|
// the used `pallet-bridge-parachains` pallet and the bridged parachain id
|
||||||
|
Para,
|
||||||
|
// implementation of `RefundableMessagesLaneId` trait, which specifies the instance of
|
||||||
|
// the used `pallet-bridge-messages` pallet and the lane within this pallet
|
||||||
|
Msgs,
|
||||||
|
// implementation of the `RefundCalculator` trait, that is used to compute refund that
|
||||||
|
// we give to relayer for his transaction
|
||||||
|
Refund,
|
||||||
|
// getter for per-message `TransactionPriority` boost that we give to message
|
||||||
|
// delivery transactions
|
||||||
|
Priority,
|
||||||
|
// the runtime-unique identifier of this signed extension
|
||||||
|
Id,
|
||||||
|
)>,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl<Runtime, Para, Msgs, Refund, Priority, Id>
|
impl<Runtime, Para, Msgs, Refund, Priority, Id>
|
||||||
@@ -215,9 +253,13 @@ where
|
|||||||
Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
|
Runtime: UtilityConfig<RuntimeCall = CallOf<Runtime>>
|
||||||
+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
|
+ BoundedBridgeGrandpaConfig<Runtime::BridgesGrandpaPalletInstance>
|
||||||
+ ParachainsConfig<Para::Instance>
|
+ ParachainsConfig<Para::Instance>
|
||||||
+ MessagesConfig<Msgs::Instance>,
|
+ MessagesConfig<Msgs::Instance>
|
||||||
|
+ RelayersConfig,
|
||||||
Para: RefundableParachainId,
|
Para: RefundableParachainId,
|
||||||
Msgs: RefundableMessagesLaneId,
|
Msgs: RefundableMessagesLaneId,
|
||||||
|
Refund: RefundCalculator<Balance = Runtime::Reward>,
|
||||||
|
Priority: Get<TransactionPriority>,
|
||||||
|
Id: StaticStrProvider,
|
||||||
CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
CallOf<Runtime>: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
|
||||||
+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
|
+ IsSubType<CallableCallFor<UtilityPallet<Runtime>, Runtime>>
|
||||||
+ GrandpaCallSubType<Runtime, Runtime::BridgesGrandpaPalletInstance>
|
+ GrandpaCallSubType<Runtime, Runtime::BridgesGrandpaPalletInstance>
|
||||||
@@ -268,6 +310,179 @@ where
|
|||||||
call.check_obsolete_call()?;
|
call.check_obsolete_call()?;
|
||||||
Ok(call)
|
Ok(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given post-dispatch information, analyze the outcome of relayer call and return
|
||||||
|
/// actions that need to be performed on relayer account.
|
||||||
|
fn analyze_call_result(
|
||||||
|
pre: Option<Option<PreDispatchData<Runtime::AccountId>>>,
|
||||||
|
info: &DispatchInfo,
|
||||||
|
post_info: &PostDispatchInfo,
|
||||||
|
len: usize,
|
||||||
|
result: &DispatchResult,
|
||||||
|
) -> RelayerAccountAction<AccountIdOf<Runtime>, Runtime::Reward> {
|
||||||
|
let mut extra_weight = Weight::zero();
|
||||||
|
let mut extra_size = 0;
|
||||||
|
|
||||||
|
// We don't refund anything for transactions that we don't support.
|
||||||
|
let (relayer, call_info) = match pre {
|
||||||
|
Some(Some(pre)) => (pre.relayer, pre.call_info),
|
||||||
|
_ => return RelayerAccountAction::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// now we know that the relayer either needs to be rewarded, or slashed
|
||||||
|
// => let's prepare the correspondent account that pays reward/receives slashed amount
|
||||||
|
let reward_account_params = RewardsAccountParams::new(
|
||||||
|
Msgs::Id::get(),
|
||||||
|
Runtime::BridgedChainId::get(),
|
||||||
|
if call_info.is_receive_messages_proof_call() {
|
||||||
|
RewardsAccountOwner::ThisChain
|
||||||
|
} else {
|
||||||
|
RewardsAccountOwner::BridgedChain
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// prepare return value for the case if the call has failed or it has not caused
|
||||||
|
// expected side effects (e.g. not all messages have been accepted)
|
||||||
|
//
|
||||||
|
// we are not checking if relayer is registered here - it happens during the slash attempt
|
||||||
|
//
|
||||||
|
// there are couple of edge cases here:
|
||||||
|
//
|
||||||
|
// - when the relayer becomes registered during message dispatch: this is unlikely + relayer
|
||||||
|
// should be ready for slashing after registration;
|
||||||
|
//
|
||||||
|
// - when relayer is registered after `validate` is called and priority is not boosted:
|
||||||
|
// relayer should be ready for slashing after registration.
|
||||||
|
let may_slash_relayer =
|
||||||
|
Self::bundled_messages_for_priority_boost(Some(&call_info)).is_some();
|
||||||
|
let slash_relayer_if_delivery_result = may_slash_relayer
|
||||||
|
.then(|| RelayerAccountAction::Slash(relayer.clone(), reward_account_params))
|
||||||
|
.unwrap_or(RelayerAccountAction::None);
|
||||||
|
|
||||||
|
// We don't refund anything if the transaction has failed.
|
||||||
|
if let Err(e) = result {
|
||||||
|
log::trace!(
|
||||||
|
target: "runtime::bridge",
|
||||||
|
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid messages transaction: {:?}",
|
||||||
|
Self::IDENTIFIER,
|
||||||
|
Para::Id::get(),
|
||||||
|
Msgs::Id::get(),
|
||||||
|
relayer,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
return slash_relayer_if_delivery_result
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if relay chain state has been updated
|
||||||
|
if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
|
||||||
|
if !SubmitFinalityProofHelper::<Runtime, Runtime::BridgesGrandpaPalletInstance>::was_successful(
|
||||||
|
finality_proof_info.block_number,
|
||||||
|
) {
|
||||||
|
// we only refund relayer if all calls have updated chain state
|
||||||
|
log::trace!(
|
||||||
|
target: "runtime::bridge",
|
||||||
|
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid relay chain finality proof",
|
||||||
|
Self::IDENTIFIER,
|
||||||
|
Para::Id::get(),
|
||||||
|
Msgs::Id::get(),
|
||||||
|
relayer,
|
||||||
|
);
|
||||||
|
return slash_relayer_if_delivery_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
|
||||||
|
// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
|
||||||
|
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
|
||||||
|
// `utility.batchAll` transaction always requires payment. But in both cases we'll
|
||||||
|
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
|
||||||
|
// to submit dedicated transaction.
|
||||||
|
|
||||||
|
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
|
||||||
|
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
|
||||||
|
extra_weight = finality_proof_info.extra_weight;
|
||||||
|
extra_size = finality_proof_info.extra_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if parachain state has been updated
|
||||||
|
if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
|
||||||
|
if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
|
||||||
|
para_proof_info,
|
||||||
|
) {
|
||||||
|
// we only refund relayer if all calls have updated chain state
|
||||||
|
log::trace!(
|
||||||
|
target: "runtime::bridge",
|
||||||
|
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid parachain finality proof",
|
||||||
|
Self::IDENTIFIER,
|
||||||
|
Para::Id::get(),
|
||||||
|
Msgs::Id::get(),
|
||||||
|
relayer,
|
||||||
|
);
|
||||||
|
return slash_relayer_if_delivery_result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the `ReceiveMessagesProof` call delivered at least some of the messages that
|
||||||
|
// it contained. If this happens, we consider the transaction "helpful" and refund it.
|
||||||
|
let msgs_call_info = call_info.messages_call_info();
|
||||||
|
if !MessagesCallHelper::<Runtime, Msgs::Instance>::was_successful(msgs_call_info) {
|
||||||
|
log::trace!(
|
||||||
|
target: "runtime::bridge",
|
||||||
|
"{} from parachain {} via {:?}: relayer {:?} has submitted invalid messages call",
|
||||||
|
Self::IDENTIFIER,
|
||||||
|
Para::Id::get(),
|
||||||
|
Msgs::Id::get(),
|
||||||
|
relayer,
|
||||||
|
);
|
||||||
|
return slash_relayer_if_delivery_result
|
||||||
|
}
|
||||||
|
|
||||||
|
// regarding the tip - refund that happens here (at this side of the bridge) isn't the whole
|
||||||
|
// relayer compensation. He'll receive some amount at the other side of the bridge. It shall
|
||||||
|
// (in theory) cover the tip there. Otherwise, if we'll be compensating tip here, some
|
||||||
|
// malicious relayer may use huge tips, effectively depleting account that pay rewards. The
|
||||||
|
// cost of this attack is nothing. Hence we use zero as tip here.
|
||||||
|
let tip = Zero::zero();
|
||||||
|
|
||||||
|
// decrease post-dispatch weight/size using extra weight/size that we know now
|
||||||
|
let post_info_len = len.saturating_sub(extra_size as usize);
|
||||||
|
let mut post_info = *post_info;
|
||||||
|
post_info.actual_weight =
|
||||||
|
Some(post_info.actual_weight.unwrap_or(info.weight).saturating_sub(extra_weight));
|
||||||
|
|
||||||
|
// compute the relayer refund
|
||||||
|
let refund = Refund::compute_refund(info, &post_info, post_info_len, tip);
|
||||||
|
|
||||||
|
// we can finally reward relayer
|
||||||
|
RelayerAccountAction::Reward(relayer, reward_account_params, refund)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of bundled messages `Some(_)`, if the given call info is a:
|
||||||
|
///
|
||||||
|
/// - message delivery transaction;
|
||||||
|
///
|
||||||
|
/// - with reasonable bundled messages that may be accepted by the messages pallet.
|
||||||
|
///
|
||||||
|
/// This function is used to check whether the transaction priority should be
|
||||||
|
/// virtually boosted. The relayer registration (we only boost priority for registered
|
||||||
|
/// relayer transactions) must be checked outside.
|
||||||
|
fn bundled_messages_for_priority_boost(call_info: Option<&CallInfo>) -> Option<MessageNonce> {
|
||||||
|
// we only boost priority of message delivery transactions
|
||||||
|
let parsed_call = match call_info {
|
||||||
|
Some(parsed_call) if parsed_call.is_receive_messages_proof_call() => parsed_call,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// compute total number of messages in transaction
|
||||||
|
let bundled_messages =
|
||||||
|
parsed_call.messages_call_info().bundled_messages().checked_len().unwrap_or(0);
|
||||||
|
|
||||||
|
// a quick check to avoid invalid high-priority transactions
|
||||||
|
if bundled_messages > Runtime::MaxUnconfirmedMessagesAtInboundLane::get() {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(bundled_messages)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Runtime, Para, Msgs, Refund, Priority, Id> SignedExtension
|
impl<Runtime, Para, Msgs, Refund, Priority, Id> SignedExtension
|
||||||
@@ -302,37 +517,49 @@ 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 {
|
||||||
// this is the only relevant line of code for the `pre_dispatch`
|
// this is the only relevant line of code for the `pre_dispatch`
|
||||||
//
|
//
|
||||||
// we're not calling `validato` from `pre_dispatch` directly because of performance
|
// we're not calling `validate` from `pre_dispatch` directly because of performance
|
||||||
// reasons, so if you're adding some code that may fail here, please check if it needs
|
// reasons, so if you're adding some code that may fail here, please check if it needs
|
||||||
// to be added to the `pre_dispatch` as well
|
// to be added to the `pre_dispatch` as well
|
||||||
let parsed_call = self.parse_and_check_for_obsolete_call(call)?;
|
let parsed_call = self.parse_and_check_for_obsolete_call(call)?;
|
||||||
|
|
||||||
// the following code just plays with transaction priority and never returns an error
|
// the following code just plays with transaction priority and never returns an error
|
||||||
let mut valid_transaction = ValidTransactionBuilder::default();
|
|
||||||
if let Some(parsed_call) = parsed_call {
|
|
||||||
// we give delivery transactions some boost, that depends on number of messages inside
|
|
||||||
let messages_call_info = parsed_call.messages_call_info();
|
|
||||||
if let MessagesCallInfo::ReceiveMessagesProof(info) = messages_call_info {
|
|
||||||
// compute total number of messages in transaction
|
|
||||||
let bundled_messages = info.base.bundled_range.checked_len().unwrap_or(0);
|
|
||||||
|
|
||||||
// a quick check to avoid invalid high-priority transactions
|
// we only boost priority of presumably correct message delivery transactions
|
||||||
if bundled_messages <= Runtime::MaxUnconfirmedMessagesAtInboundLane::get() {
|
let bundled_messages = match Self::bundled_messages_for_priority_boost(parsed_call.as_ref())
|
||||||
let priority_boost = crate::priority_calculator::compute_priority_boost::<
|
{
|
||||||
Priority,
|
Some(bundled_messages) => bundled_messages,
|
||||||
>(bundled_messages);
|
None => return Ok(Default::default()),
|
||||||
valid_transaction = valid_transaction.priority(priority_boost);
|
};
|
||||||
}
|
|
||||||
}
|
// we only boost priority if relayer has staked required balance
|
||||||
|
if !RelayersPallet::<Runtime>::is_registration_active(who) {
|
||||||
|
return Ok(Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compute priority boost
|
||||||
|
let priority_boost =
|
||||||
|
crate::priority_calculator::compute_priority_boost::<Priority>(bundled_messages);
|
||||||
|
let valid_transaction = ValidTransactionBuilder::default().priority(priority_boost);
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
target: "runtime::bridge",
|
||||||
|
"{} from parachain {} via {:?} has boosted priority of message delivery transaction \
|
||||||
|
of relayer {:?}: {} messages -> {} priority",
|
||||||
|
Self::IDENTIFIER,
|
||||||
|
Para::Id::get(),
|
||||||
|
Msgs::Id::get(),
|
||||||
|
who,
|
||||||
|
bundled_messages,
|
||||||
|
priority_boost,
|
||||||
|
);
|
||||||
|
|
||||||
valid_transaction.build()
|
valid_transaction.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,118 +593,15 @@ where
|
|||||||
len: usize,
|
len: usize,
|
||||||
result: &DispatchResult,
|
result: &DispatchResult,
|
||||||
) -> Result<(), TransactionValidityError> {
|
) -> Result<(), TransactionValidityError> {
|
||||||
let mut extra_weight = Weight::zero();
|
let call_result = Self::analyze_call_result(pre, info, post_info, len, result);
|
||||||
let mut extra_size = 0;
|
|
||||||
|
|
||||||
// We don't refund anything if the transaction has failed.
|
match call_result {
|
||||||
if result.is_err() {
|
RelayerAccountAction::None => (),
|
||||||
return Ok(())
|
RelayerAccountAction::Reward(relayer, reward_account, reward) => {
|
||||||
}
|
|
||||||
|
|
||||||
// We don't refund anything for transactions that we don't support.
|
|
||||||
let (relayer, call_info) = match pre {
|
|
||||||
Some(Some(pre)) => (pre.relayer, pre.call_info),
|
|
||||||
_ => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// check if relay chain state has been updated
|
|
||||||
if let Some(finality_proof_info) = call_info.submit_finality_proof_info() {
|
|
||||||
if !SubmitFinalityProofHelper::<Runtime, Runtime::BridgesGrandpaPalletInstance>::was_successful(
|
|
||||||
finality_proof_info.block_number,
|
|
||||||
) {
|
|
||||||
// we only refund relayer if all calls have updated chain state
|
|
||||||
log::trace!(
|
|
||||||
target: "runtime::bridge",
|
|
||||||
"{} from parachain {} via {:?}: failed to refund relayer {:?}, because \
|
|
||||||
relay chain finality proof has not been accepted",
|
|
||||||
Self::IDENTIFIER,
|
|
||||||
Para::Id::get(),
|
|
||||||
Msgs::Id::get(),
|
|
||||||
relayer,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// there's a conflict between how bridge GRANDPA pallet works and a `utility.batchAll`
|
|
||||||
// transaction. If relay chain header is mandatory, the GRANDPA pallet returns
|
|
||||||
// `Pays::No`, because such transaction is mandatory for operating the bridge. But
|
|
||||||
// `utility.batchAll` transaction always requires payment. But in both cases we'll
|
|
||||||
// refund relayer - either explicitly here, or using `Pays::No` if he's choosing
|
|
||||||
// to submit dedicated transaction.
|
|
||||||
|
|
||||||
// submitter has means to include extra weight/bytes in the `submit_finality_proof`
|
|
||||||
// call, so let's subtract extra weight/size to avoid refunding for this extra stuff
|
|
||||||
extra_weight = finality_proof_info.extra_weight;
|
|
||||||
extra_size = finality_proof_info.extra_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if parachain state has been updated
|
|
||||||
if let Some(para_proof_info) = call_info.submit_parachain_heads_info() {
|
|
||||||
if !SubmitParachainHeadsHelper::<Runtime, Para::Instance>::was_successful(
|
|
||||||
para_proof_info,
|
|
||||||
) {
|
|
||||||
// we only refund relayer if all calls have updated chain state
|
|
||||||
log::trace!(
|
|
||||||
target: "runtime::bridge",
|
|
||||||
"{} from parachain {} via {:?}: failed to refund relayer {:?}, because \
|
|
||||||
parachain finality proof has not been accepted",
|
|
||||||
Self::IDENTIFIER,
|
|
||||||
Para::Id::get(),
|
|
||||||
Msgs::Id::get(),
|
|
||||||
relayer,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the `ReceiveMessagesProof` call delivered all the messages that
|
|
||||||
// it contained. If this happens, we consider the transaction "helpful" and refund it.
|
|
||||||
let msgs_call_info = call_info.messages_call_info();
|
|
||||||
if !MessagesCallHelper::<Runtime, Msgs::Instance>::was_successful(msgs_call_info) {
|
|
||||||
log::trace!(
|
|
||||||
target: "runtime::bridge",
|
|
||||||
"{} from parachain {} via {:?}: failed to refund relayer {:?}, because \
|
|
||||||
some of messages have not been accepted",
|
|
||||||
Self::IDENTIFIER,
|
|
||||||
Para::Id::get(),
|
|
||||||
Msgs::Id::get(),
|
|
||||||
relayer,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// regarding the tip - refund that happens here (at this side of the bridge) isn't the whole
|
|
||||||
// relayer compensation. He'll receive some amount at the other side of the bridge. It shall
|
|
||||||
// (in theory) cover the tip there. Otherwise, if we'll be compensating tip here, some
|
|
||||||
// malicious relayer may use huge tips, effectively depleting account that pay rewards. The
|
|
||||||
// cost of this attack is nothing. Hence we use zero as tip here.
|
|
||||||
let tip = Zero::zero();
|
|
||||||
|
|
||||||
// decrease post-dispatch weight/size using extra weight/size that we know now
|
|
||||||
let post_info_len = len.saturating_sub(extra_size as usize);
|
|
||||||
let mut post_info = *post_info;
|
|
||||||
post_info.actual_weight =
|
|
||||||
Some(post_info.actual_weight.unwrap_or(info.weight).saturating_sub(extra_weight));
|
|
||||||
|
|
||||||
// compute the relayer refund
|
|
||||||
let refund = Refund::compute_refund(info, &post_info, post_info_len, tip);
|
|
||||||
|
|
||||||
// finally - register refund in relayers pallet
|
|
||||||
let rewards_account_owner = match msgs_call_info {
|
|
||||||
MessagesCallInfo::ReceiveMessagesProof(_) => RewardsAccountOwner::ThisChain,
|
|
||||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(_) => RewardsAccountOwner::BridgedChain,
|
|
||||||
};
|
|
||||||
RelayersPallet::<Runtime>::register_relayer_reward(
|
RelayersPallet::<Runtime>::register_relayer_reward(
|
||||||
RewardsAccountParams::new(
|
reward_account,
|
||||||
Msgs::Id::get(),
|
|
||||||
Runtime::BridgedChainId::get(),
|
|
||||||
rewards_account_owner,
|
|
||||||
),
|
|
||||||
&relayer,
|
&relayer,
|
||||||
refund,
|
reward,
|
||||||
);
|
);
|
||||||
|
|
||||||
log::trace!(
|
log::trace!(
|
||||||
@@ -486,9 +610,13 @@ where
|
|||||||
Self::IDENTIFIER,
|
Self::IDENTIFIER,
|
||||||
Para::Id::get(),
|
Para::Id::get(),
|
||||||
Msgs::Id::get(),
|
Msgs::Id::get(),
|
||||||
refund,
|
reward,
|
||||||
relayer,
|
relayer,
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
RelayerAccountAction::Slash(relayer, slash_account) =>
|
||||||
|
RelayersPallet::<Runtime>::slash_and_deregister(&relayer, slash_account),
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -509,10 +637,14 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData, UnrewardedRelayersState};
|
use bp_messages::{InboundLaneData, MessageNonce, OutboundLaneData, UnrewardedRelayersState};
|
||||||
use bp_parachains::{BestParaHeadHash, ParaInfo};
|
use bp_parachains::{BestParaHeadHash, ParaInfo};
|
||||||
use bp_polkadot_core::parachains::{ParaHash, ParaHeadsProof, ParaId};
|
use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
|
||||||
use bp_runtime::HeaderId;
|
use bp_runtime::HeaderId;
|
||||||
use bp_test_utils::{make_default_justification, test_keyring};
|
use bp_test_utils::{make_default_justification, test_keyring};
|
||||||
use frame_support::{assert_storage_noop, parameter_types, weights::Weight};
|
use frame_support::{
|
||||||
|
assert_storage_noop, parameter_types,
|
||||||
|
traits::{fungible::Mutate, ReservableCurrency},
|
||||||
|
weights::Weight,
|
||||||
|
};
|
||||||
use pallet_bridge_grandpa::{Call as GrandpaCall, StoredAuthoritySet};
|
use pallet_bridge_grandpa::{Call as GrandpaCall, StoredAuthoritySet};
|
||||||
use pallet_bridge_messages::Call as MessagesCall;
|
use pallet_bridge_messages::Call as MessagesCall;
|
||||||
use pallet_bridge_parachains::{Call as ParachainsCall, RelayBlockHash};
|
use pallet_bridge_parachains::{Call as ParachainsCall, RelayBlockHash};
|
||||||
@@ -547,6 +679,22 @@ mod tests {
|
|||||||
StrTestExtension,
|
StrTestExtension,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
fn initial_balance_of_relayer_account_at_this_chain() -> ThisChainBalance {
|
||||||
|
let test_stake: ThisChainBalance = TestStake::get();
|
||||||
|
ExistentialDeposit::get().saturating_add(test_stake * 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// in tests, the following accounts are equal (because of how `into_sub_account_truncating`
|
||||||
|
// works)
|
||||||
|
|
||||||
|
fn delivery_rewards_account() -> ThisChainAccountId {
|
||||||
|
TestPaymentProcedure::rewards_account(MsgProofsRewardsAccount::get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirmation_rewards_account() -> ThisChainAccountId {
|
||||||
|
TestPaymentProcedure::rewards_account(MsgDeliveryProofsRewardsAccount::get())
|
||||||
|
}
|
||||||
|
|
||||||
fn relayer_account_at_this_chain() -> ThisChainAccountId {
|
fn relayer_account_at_this_chain() -> ThisChainAccountId {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@@ -558,7 +706,6 @@ 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_message: MessageNonce,
|
best_message: MessageNonce,
|
||||||
) {
|
) {
|
||||||
let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
|
let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect();
|
||||||
@@ -572,7 +719,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: parachain_head_hash,
|
head_hash: [parachain_head_at_relay_header_number as u8; 32].into(),
|
||||||
},
|
},
|
||||||
next_imported_hash_position: 0,
|
next_imported_hash_position: 0,
|
||||||
};
|
};
|
||||||
@@ -586,6 +733,14 @@ mod tests {
|
|||||||
let out_lane_data =
|
let out_lane_data =
|
||||||
OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
|
OutboundLaneData { latest_received_nonce: best_message, ..Default::default() };
|
||||||
pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(lane_id, out_lane_data);
|
pallet_bridge_messages::OutboundLanes::<TestRuntime>::insert(lane_id, out_lane_data);
|
||||||
|
|
||||||
|
Balances::mint_into(&delivery_rewards_account(), ExistentialDeposit::get()).unwrap();
|
||||||
|
Balances::mint_into(&confirmation_rewards_account(), ExistentialDeposit::get()).unwrap();
|
||||||
|
Balances::mint_into(
|
||||||
|
&relayer_account_at_this_chain(),
|
||||||
|
initial_balance_of_relayer_account_at_this_chain(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_relay_header_call(relay_header_number: RelayBlockNumber) -> RuntimeCall {
|
fn submit_relay_header_call(relay_header_number: RelayBlockNumber) -> RuntimeCall {
|
||||||
@@ -609,7 +764,10 @@ mod tests {
|
|||||||
) -> RuntimeCall {
|
) -> RuntimeCall {
|
||||||
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
|
RuntimeCall::BridgeParachains(ParachainsCall::submit_parachain_heads {
|
||||||
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
|
at_relay_block: (parachain_head_at_relay_header_number, RelayBlockHash::default()),
|
||||||
parachains: vec![(ParaId(TestParachain::get()), [1u8; 32].into())],
|
parachains: vec![(
|
||||||
|
ParaId(TestParachain::get()),
|
||||||
|
[parachain_head_at_relay_header_number as u8; 32].into(),
|
||||||
|
)],
|
||||||
parachain_heads_proof: ParaHeadsProof(vec![]),
|
parachain_heads_proof: ParaHeadsProof(vec![]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -711,7 +869,7 @@ mod tests {
|
|||||||
SubmitParachainHeadsInfo {
|
SubmitParachainHeadsInfo {
|
||||||
at_relay_block_number: 200,
|
at_relay_block_number: 200,
|
||||||
para_id: ParaId(TestParachain::get()),
|
para_id: ParaId(TestParachain::get()),
|
||||||
para_head_hash: [1u8; 32].into(),
|
para_head_hash: [200u8; 32].into(),
|
||||||
},
|
},
|
||||||
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
|
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
|
||||||
base: BaseMessagesProofInfo {
|
base: BaseMessagesProofInfo {
|
||||||
@@ -740,7 +898,7 @@ mod tests {
|
|||||||
SubmitParachainHeadsInfo {
|
SubmitParachainHeadsInfo {
|
||||||
at_relay_block_number: 200,
|
at_relay_block_number: 200,
|
||||||
para_id: ParaId(TestParachain::get()),
|
para_id: ParaId(TestParachain::get()),
|
||||||
para_head_hash: [1u8; 32].into(),
|
para_head_hash: [200u8; 32].into(),
|
||||||
},
|
},
|
||||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
|
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
|
||||||
BaseMessagesProofInfo {
|
BaseMessagesProofInfo {
|
||||||
@@ -760,7 +918,7 @@ mod tests {
|
|||||||
SubmitParachainHeadsInfo {
|
SubmitParachainHeadsInfo {
|
||||||
at_relay_block_number: 200,
|
at_relay_block_number: 200,
|
||||||
para_id: ParaId(TestParachain::get()),
|
para_id: ParaId(TestParachain::get()),
|
||||||
para_head_hash: [1u8; 32].into(),
|
para_head_hash: [200u8; 32].into(),
|
||||||
},
|
},
|
||||||
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
|
MessagesCallInfo::ReceiveMessagesProof(ReceiveMessagesProofInfo {
|
||||||
base: BaseMessagesProofInfo {
|
base: BaseMessagesProofInfo {
|
||||||
@@ -784,7 +942,7 @@ mod tests {
|
|||||||
SubmitParachainHeadsInfo {
|
SubmitParachainHeadsInfo {
|
||||||
at_relay_block_number: 200,
|
at_relay_block_number: 200,
|
||||||
para_id: ParaId(TestParachain::get()),
|
para_id: ParaId(TestParachain::get()),
|
||||||
para_head_hash: [1u8; 32].into(),
|
para_head_hash: [200u8; 32].into(),
|
||||||
},
|
},
|
||||||
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
|
MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
|
||||||
BaseMessagesProofInfo {
|
BaseMessagesProofInfo {
|
||||||
@@ -829,8 +987,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_test(test: impl FnOnce()) {
|
fn set_bundled_range_end(
|
||||||
sp_io::TestExternalities::new(Default::default()).execute_with(test)
|
mut pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
|
||||||
|
end: MessageNonce,
|
||||||
|
) -> PreDispatchData<ThisChainAccountId> {
|
||||||
|
let msg_info = match pre_dispatch_data.call_info {
|
||||||
|
CallInfo::AllFinalityAndMsgs(_, _, ref mut info) => info,
|
||||||
|
CallInfo::ParachainFinalityAndMsgs(_, ref mut info) => info,
|
||||||
|
CallInfo::Msgs(ref mut info) => info,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let MessagesCallInfo::ReceiveMessagesProof(ref mut msg_info) = msg_info {
|
||||||
|
msg_info.base.bundled_range = *msg_info.base.bundled_range.start()..=end
|
||||||
|
}
|
||||||
|
|
||||||
|
pre_dispatch_data
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_validate(call: RuntimeCall) -> TransactionValidity {
|
fn run_validate(call: RuntimeCall) -> TransactionValidity {
|
||||||
@@ -838,6 +1009,13 @@ mod tests {
|
|||||||
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
|
extension.validate(&relayer_account_at_this_chain(), &call, &DispatchInfo::default(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity {
|
||||||
|
run_validate(call).map(|mut tx| {
|
||||||
|
tx.priority = 0;
|
||||||
|
tx
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn run_pre_dispatch(
|
fn run_pre_dispatch(
|
||||||
call: RuntimeCall,
|
call: RuntimeCall,
|
||||||
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
|
) -> Result<Option<PreDispatchData<ThisChainAccountId>>, TransactionValidityError> {
|
||||||
@@ -883,10 +1061,49 @@ mod tests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_doesnt_boost_transaction_priority_if_relayer_is_not_registered() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_environment(100, 100, 100);
|
||||||
|
Balances::set_balance(&relayer_account_at_this_chain(), ExistentialDeposit::get());
|
||||||
|
|
||||||
|
// message delivery is failing
|
||||||
|
assert_eq!(run_validate(message_delivery_call(200)), Ok(Default::default()),);
|
||||||
|
assert_eq!(
|
||||||
|
run_validate(parachain_finality_and_delivery_batch_call(200, 200)),
|
||||||
|
Ok(Default::default()),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_validate(all_finality_and_delivery_batch_call(200, 200, 200)),
|
||||||
|
Ok(Default::default()),
|
||||||
|
);
|
||||||
|
// message confirmation validation is passing
|
||||||
|
assert_eq!(
|
||||||
|
run_validate_ignore_priority(message_confirmation_call(200)),
|
||||||
|
Ok(Default::default()),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_validate_ignore_priority(parachain_finality_and_confirmation_batch_call(
|
||||||
|
200, 200
|
||||||
|
)),
|
||||||
|
Ok(Default::default()),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_validate_ignore_priority(all_finality_and_confirmation_batch_call(
|
||||||
|
200, 200, 200
|
||||||
|
)),
|
||||||
|
Ok(Default::default()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_boosts_priority_of_message_delivery_transactons() {
|
fn validate_boosts_priority_of_message_delivery_transactons() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, Default::default(), 100);
|
initialize_environment(100, 100, 100);
|
||||||
|
|
||||||
|
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let priority_of_100_messages_delivery =
|
let priority_of_100_messages_delivery =
|
||||||
run_validate(message_delivery_call(200)).unwrap().priority;
|
run_validate(message_delivery_call(200)).unwrap().priority;
|
||||||
@@ -913,7 +1130,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn validate_does_not_boost_priority_of_message_delivery_transactons_with_too_many_messages() {
|
fn validate_does_not_boost_priority_of_message_delivery_transactons_with_too_many_messages() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, Default::default(), 100);
|
initialize_environment(100, 100, 100);
|
||||||
|
|
||||||
|
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let priority_of_max_messages_delivery = run_validate(message_delivery_call(
|
let priority_of_max_messages_delivery = run_validate(message_delivery_call(
|
||||||
100 + MaxUnconfirmedMessagesAtInboundLane::get(),
|
100 + MaxUnconfirmedMessagesAtInboundLane::get(),
|
||||||
@@ -938,14 +1158,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, Default::default(), 100);
|
initialize_environment(100, 100, 100);
|
||||||
|
|
||||||
fn run_validate_ignore_priority(call: RuntimeCall) -> TransactionValidity {
|
|
||||||
run_validate(call).map(|mut tx| {
|
|
||||||
tx.priority = 0;
|
|
||||||
tx
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_validate_ignore_priority(message_delivery_call(200)),
|
run_validate_ignore_priority(message_delivery_call(200)),
|
||||||
@@ -983,7 +1196,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, Default::default(), 100);
|
initialize_environment(100, 100, 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)),
|
||||||
@@ -1000,7 +1213,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, Default::default(), 100);
|
initialize_environment(100, 100, 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)),
|
||||||
@@ -1025,7 +1238,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, Default::default(), 100);
|
initialize_environment(100, 100, 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)),
|
||||||
@@ -1068,7 +1281,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, Default::default(), 100);
|
initialize_environment(100, 100, 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)),
|
||||||
@@ -1084,7 +1297,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, Default::default(), 100);
|
initialize_environment(100, 100, 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)),
|
||||||
@@ -1100,7 +1313,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, Default::default(), 100);
|
initialize_environment(100, 100, 100);
|
||||||
|
|
||||||
let call = RuntimeCall::Utility(UtilityCall::batch_all {
|
let call = RuntimeCall::Utility(UtilityCall::batch_all {
|
||||||
calls: vec![
|
calls: vec![
|
||||||
@@ -1123,7 +1336,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pre_dispatch_parses_message_transaction() {
|
fn pre_dispatch_parses_message_transaction() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(100, 100, Default::default(), 100);
|
initialize_environment(100, 100, 100);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run_pre_dispatch(message_delivery_call(200)),
|
run_pre_dispatch(message_delivery_call(200)),
|
||||||
@@ -1156,7 +1369,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, Default::default(), 200);
|
initialize_environment(100, 200, 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(())));
|
||||||
});
|
});
|
||||||
@@ -1165,7 +1378,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, Default::default(), 200);
|
initialize_environment(200, 100, 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(
|
||||||
@@ -1178,7 +1391,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, Default::default(), 100);
|
initialize_environment(200, 200, 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(
|
||||||
@@ -1202,7 +1415,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_ignores_transaction_that_has_not_delivered_all_messages() {
|
fn post_dispatch_ignores_transaction_that_has_not_delivered_all_messages() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 200, Default::default(), 150);
|
initialize_environment(200, 200, 150);
|
||||||
|
|
||||||
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(
|
||||||
@@ -1226,7 +1439,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_refunds_relayer_in_all_finality_batch_with_extra_weight() {
|
fn post_dispatch_refunds_relayer_in_all_finality_batch_with_extra_weight() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 200, [1u8; 32].into(), 200);
|
initialize_environment(200, 200, 200);
|
||||||
|
|
||||||
let mut dispatch_info = dispatch_info();
|
let mut dispatch_info = dispatch_info();
|
||||||
dispatch_info.weight = Weight::from_parts(
|
dispatch_info.weight = Weight::from_parts(
|
||||||
@@ -1275,7 +1488,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, [1u8; 32].into(), 200);
|
initialize_environment(200, 200, 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!(
|
||||||
@@ -1300,7 +1513,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, [1u8; 32].into(), 200);
|
initialize_environment(200, 200, 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!(
|
||||||
@@ -1325,7 +1538,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn post_dispatch_refunds_relayer_in_message_transaction() {
|
fn post_dispatch_refunds_relayer_in_message_transaction() {
|
||||||
run_test(|| {
|
run_test(|| {
|
||||||
initialize_environment(200, 200, Default::default(), 200);
|
initialize_environment(200, 200, 200);
|
||||||
|
|
||||||
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
|
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1346,4 +1559,149 @@ mod tests {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn post_dispatch_slashing_relayer_stake() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_environment(200, 200, 100);
|
||||||
|
|
||||||
|
let delivery_rewards_account_balance =
|
||||||
|
Balances::free_balance(delivery_rewards_account());
|
||||||
|
|
||||||
|
let test_stake: ThisChainBalance = TestStake::get();
|
||||||
|
Balances::set_balance(
|
||||||
|
&relayer_account_at_this_chain(),
|
||||||
|
ExistentialDeposit::get() + test_stake * 10,
|
||||||
|
);
|
||||||
|
|
||||||
|
// slashing works for message delivery calls
|
||||||
|
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
|
||||||
|
run_post_dispatch(Some(delivery_pre_dispatch_data()), Ok(()));
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
|
||||||
|
assert_eq!(
|
||||||
|
delivery_rewards_account_balance + test_stake,
|
||||||
|
Balances::free_balance(delivery_rewards_account())
|
||||||
|
);
|
||||||
|
|
||||||
|
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
|
||||||
|
run_post_dispatch(Some(parachain_finality_pre_dispatch_data()), Ok(()));
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
|
||||||
|
assert_eq!(
|
||||||
|
delivery_rewards_account_balance + test_stake * 2,
|
||||||
|
Balances::free_balance(delivery_rewards_account())
|
||||||
|
);
|
||||||
|
|
||||||
|
BridgeRelayers::register(RuntimeOrigin::signed(relayer_account_at_this_chain()), 1000)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
|
||||||
|
run_post_dispatch(Some(all_finality_pre_dispatch_data()), Ok(()));
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), 0);
|
||||||
|
assert_eq!(
|
||||||
|
delivery_rewards_account_balance + test_stake * 3,
|
||||||
|
Balances::free_balance(delivery_rewards_account())
|
||||||
|
);
|
||||||
|
|
||||||
|
// reserve doesn't work for message confirmation calls
|
||||||
|
let confirmation_rewards_account_balance =
|
||||||
|
Balances::free_balance(confirmation_rewards_account());
|
||||||
|
|
||||||
|
Balances::reserve(&relayer_account_at_this_chain(), test_stake).unwrap();
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
confirmation_rewards_account_balance,
|
||||||
|
Balances::free_balance(confirmation_rewards_account())
|
||||||
|
);
|
||||||
|
run_post_dispatch(Some(confirmation_pre_dispatch_data()), Ok(()));
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
|
||||||
|
|
||||||
|
run_post_dispatch(Some(parachain_finality_confirmation_pre_dispatch_data()), Ok(()));
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
|
||||||
|
|
||||||
|
run_post_dispatch(Some(all_finality_confirmation_pre_dispatch_data()), Ok(()));
|
||||||
|
assert_eq!(Balances::reserved_balance(relayer_account_at_this_chain()), test_stake);
|
||||||
|
|
||||||
|
// check that unreserve has happened, not slashing
|
||||||
|
assert_eq!(
|
||||||
|
delivery_rewards_account_balance + test_stake * 3,
|
||||||
|
Balances::free_balance(delivery_rewards_account())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
confirmation_rewards_account_balance,
|
||||||
|
Balances::free_balance(confirmation_rewards_account())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_analyze_call_result(
|
||||||
|
pre_dispatch_data: PreDispatchData<ThisChainAccountId>,
|
||||||
|
dispatch_result: DispatchResult,
|
||||||
|
) -> RelayerAccountAction<ThisChainAccountId, ThisChainBalance> {
|
||||||
|
TestExtension::analyze_call_result(
|
||||||
|
Some(Some(pre_dispatch_data)),
|
||||||
|
&dispatch_info(),
|
||||||
|
&post_dispatch_info(),
|
||||||
|
1024,
|
||||||
|
&dispatch_result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn analyze_call_result_shall_not_slash_for_transactions_with_too_many_messages() {
|
||||||
|
run_test(|| {
|
||||||
|
initialize_environment(100, 100, 100);
|
||||||
|
|
||||||
|
// the `analyze_call_result` should return slash if number of bundled messages is
|
||||||
|
// within reasonable limits
|
||||||
|
assert_eq!(
|
||||||
|
run_analyze_call_result(all_finality_pre_dispatch_data(), Ok(())),
|
||||||
|
RelayerAccountAction::Slash(
|
||||||
|
relayer_account_at_this_chain(),
|
||||||
|
MsgProofsRewardsAccount::get()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_analyze_call_result(parachain_finality_pre_dispatch_data(), Ok(())),
|
||||||
|
RelayerAccountAction::Slash(
|
||||||
|
relayer_account_at_this_chain(),
|
||||||
|
MsgProofsRewardsAccount::get()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_analyze_call_result(delivery_pre_dispatch_data(), Ok(())),
|
||||||
|
RelayerAccountAction::Slash(
|
||||||
|
relayer_account_at_this_chain(),
|
||||||
|
MsgProofsRewardsAccount::get()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// the `analyze_call_result` should not return slash if number of bundled messages is
|
||||||
|
// larger than the
|
||||||
|
assert_eq!(
|
||||||
|
run_analyze_call_result(
|
||||||
|
set_bundled_range_end(all_finality_pre_dispatch_data(), 1_000_000),
|
||||||
|
Ok(())
|
||||||
|
),
|
||||||
|
RelayerAccountAction::None,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_analyze_call_result(
|
||||||
|
set_bundled_range_end(parachain_finality_pre_dispatch_data(), 1_000_000),
|
||||||
|
Ok(())
|
||||||
|
),
|
||||||
|
RelayerAccountAction::None,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
run_analyze_call_result(
|
||||||
|
set_bundled_range_end(delivery_pre_dispatch_data(), 1_000_000),
|
||||||
|
Ok(())
|
||||||
|
),
|
||||||
|
RelayerAccountAction::None,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,20 +20,25 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
use bp_relayers::{PaymentProcedure, RelayerRewardsKeyProvider, RewardsAccountParams};
|
use bp_relayers::{
|
||||||
|
PaymentProcedure, Registration, RelayerRewardsKeyProvider, RewardsAccountParams, StakeAndSlash,
|
||||||
|
};
|
||||||
use bp_runtime::StorageDoubleMapKeyProvider;
|
use bp_runtime::StorageDoubleMapKeyProvider;
|
||||||
use frame_support::sp_runtime::Saturating;
|
use frame_support::fail;
|
||||||
use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero};
|
use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero};
|
||||||
|
use sp_runtime::{traits::CheckedSub, Saturating};
|
||||||
use sp_std::marker::PhantomData;
|
use sp_std::marker::PhantomData;
|
||||||
|
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
pub use payment_adapter::DeliveryConfirmationPaymentsAdapter;
|
pub use payment_adapter::DeliveryConfirmationPaymentsAdapter;
|
||||||
|
pub use stake_adapter::StakeAndSlashNamed;
|
||||||
pub use weights::WeightInfo;
|
pub use weights::WeightInfo;
|
||||||
|
|
||||||
pub mod benchmarking;
|
pub mod benchmarking;
|
||||||
|
|
||||||
mod mock;
|
mod mock;
|
||||||
mod payment_adapter;
|
mod payment_adapter;
|
||||||
|
mod stake_adapter;
|
||||||
|
|
||||||
pub mod weights;
|
pub mod weights;
|
||||||
|
|
||||||
@@ -56,8 +61,10 @@ pub mod pallet {
|
|||||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||||
/// Type of relayer reward.
|
/// Type of relayer reward.
|
||||||
type Reward: AtLeast32BitUnsigned + Copy + Parameter + MaxEncodedLen;
|
type Reward: AtLeast32BitUnsigned + Copy + Parameter + MaxEncodedLen;
|
||||||
/// Pay rewards adapter.
|
/// Pay rewards scheme.
|
||||||
type PaymentProcedure: PaymentProcedure<Self::AccountId, Self::Reward>;
|
type PaymentProcedure: PaymentProcedure<Self::AccountId, Self::Reward>;
|
||||||
|
/// Stake and slash scheme.
|
||||||
|
type StakeAndSlash: StakeAndSlash<Self::AccountId, Self::BlockNumber, Self::Reward>;
|
||||||
/// Pallet call weights.
|
/// Pallet call weights.
|
||||||
type WeightInfo: WeightInfo;
|
type WeightInfo: WeightInfo;
|
||||||
}
|
}
|
||||||
@@ -102,9 +109,194 @@ pub mod pallet {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register relayer or update its registration.
|
||||||
|
///
|
||||||
|
/// Registration allows relayer to get priority boost for its message delivery transactions.
|
||||||
|
#[pallet::call_index(1)]
|
||||||
|
#[pallet::weight(Weight::zero())] // TODO: https://github.com/paritytech/parity-bridges-common/issues/2033
|
||||||
|
pub fn register(origin: OriginFor<T>, valid_till: T::BlockNumber) -> DispatchResult {
|
||||||
|
let relayer = ensure_signed(origin)?;
|
||||||
|
|
||||||
|
// valid till must be larger than the current block number and the lease must be larger
|
||||||
|
// than the `RequiredRegistrationLease`
|
||||||
|
let lease = valid_till.saturating_sub(frame_system::Pallet::<T>::block_number());
|
||||||
|
ensure!(
|
||||||
|
lease > Pallet::<T>::required_registration_lease(),
|
||||||
|
Error::<T>::InvalidRegistrationLease
|
||||||
|
);
|
||||||
|
|
||||||
|
RegisteredRelayers::<T>::try_mutate(&relayer, |maybe_registration| -> DispatchResult {
|
||||||
|
let mut registration = maybe_registration
|
||||||
|
.unwrap_or_else(|| Registration { valid_till, stake: Zero::zero() });
|
||||||
|
|
||||||
|
// new `valid_till` must be larger (or equal) than the old one
|
||||||
|
ensure!(
|
||||||
|
valid_till >= registration.valid_till,
|
||||||
|
Error::<T>::CannotReduceRegistrationLease,
|
||||||
|
);
|
||||||
|
registration.valid_till = valid_till;
|
||||||
|
|
||||||
|
// regarding stake, there are three options:
|
||||||
|
// - if relayer stake is larger than required stake, we may do unreserve
|
||||||
|
// - if relayer stake equals to required stake, we do nothing
|
||||||
|
// - if relayer stake is smaller than required stake, we do additional reserve
|
||||||
|
let required_stake = Pallet::<T>::required_stake();
|
||||||
|
if let Some(to_unreserve) = registration.stake.checked_sub(&required_stake) {
|
||||||
|
Self::do_unreserve(&relayer, to_unreserve)?;
|
||||||
|
} else if let Some(to_reserve) = required_stake.checked_sub(®istration.stake) {
|
||||||
|
T::StakeAndSlash::reserve(&relayer, to_reserve).map_err(|e| {
|
||||||
|
log::trace!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"Failed to reserve {:?} on relayer {:?} account: {:?}",
|
||||||
|
to_reserve,
|
||||||
|
relayer,
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
|
||||||
|
Error::<T>::FailedToReserve
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
registration.stake = required_stake;
|
||||||
|
|
||||||
|
Self::deposit_event(Event::<T>::RegistrationUpdated {
|
||||||
|
relayer: relayer.clone(),
|
||||||
|
registration,
|
||||||
|
});
|
||||||
|
|
||||||
|
*maybe_registration = Some(registration);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Deregister` relayer.
|
||||||
|
///
|
||||||
|
/// After this call, message delivery transactions of the relayer won't get any priority
|
||||||
|
/// boost.
|
||||||
|
#[pallet::call_index(2)]
|
||||||
|
#[pallet::weight(Weight::zero())] // TODO: https://github.com/paritytech/parity-bridges-common/issues/2033
|
||||||
|
pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
|
||||||
|
let relayer = ensure_signed(origin)?;
|
||||||
|
|
||||||
|
RegisteredRelayers::<T>::try_mutate(&relayer, |maybe_registration| -> DispatchResult {
|
||||||
|
let registration = match maybe_registration.take() {
|
||||||
|
Some(registration) => registration,
|
||||||
|
None => fail!(Error::<T>::NotRegistered),
|
||||||
|
};
|
||||||
|
|
||||||
|
// we can't deregister until `valid_till + 1`
|
||||||
|
ensure!(
|
||||||
|
registration.valid_till < frame_system::Pallet::<T>::block_number(),
|
||||||
|
Error::<T>::RegistrationIsStillActive,
|
||||||
|
);
|
||||||
|
|
||||||
|
// if stake is non-zero, we should do unreserve
|
||||||
|
if !registration.stake.is_zero() {
|
||||||
|
Self::do_unreserve(&relayer, registration.stake)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::deposit_event(Event::<T>::Deregistered { relayer: relayer.clone() });
|
||||||
|
|
||||||
|
*maybe_registration = None;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
|
/// Returns true if given relayer registration is active at current block.
|
||||||
|
///
|
||||||
|
/// This call respects both `RequiredStake` and `RequiredRegistrationLease`, meaning that
|
||||||
|
/// it'll return false if registered stake is lower than required or if remaining lease
|
||||||
|
/// is less than `RequiredRegistrationLease`.
|
||||||
|
pub fn is_registration_active(relayer: &T::AccountId) -> bool {
|
||||||
|
let registration = match Self::registered_relayer(relayer) {
|
||||||
|
Some(registration) => registration,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// registration is inactive if relayer stake is less than required
|
||||||
|
if registration.stake < Self::required_stake() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// registration is inactive if it ends soon
|
||||||
|
let remaining_lease = registration
|
||||||
|
.valid_till
|
||||||
|
.saturating_sub(frame_system::Pallet::<T>::block_number());
|
||||||
|
if remaining_lease <= Self::required_registration_lease() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Slash and `deregister` relayer. This function slashes all staked balance.
|
||||||
|
///
|
||||||
|
/// It may fail inside, but error is swallowed and we only log it.
|
||||||
|
pub fn slash_and_deregister(
|
||||||
|
relayer: &T::AccountId,
|
||||||
|
slash_destination: RewardsAccountParams,
|
||||||
|
) {
|
||||||
|
let registration = match RegisteredRelayers::<T>::take(relayer) {
|
||||||
|
Some(registration) => registration,
|
||||||
|
None => {
|
||||||
|
log::trace!(
|
||||||
|
target: crate::LOG_TARGET,
|
||||||
|
"Cannot slash unregistered relayer {:?}",
|
||||||
|
relayer,
|
||||||
|
);
|
||||||
|
|
||||||
|
return
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
match T::StakeAndSlash::repatriate_reserved(
|
||||||
|
relayer,
|
||||||
|
slash_destination,
|
||||||
|
registration.stake,
|
||||||
|
) {
|
||||||
|
Ok(failed_to_slash) if failed_to_slash.is_zero() => {
|
||||||
|
log::trace!(
|
||||||
|
target: crate::LOG_TARGET,
|
||||||
|
"Relayer account {:?} has been slashed for {:?}. Funds were deposited to {:?}",
|
||||||
|
relayer,
|
||||||
|
registration.stake,
|
||||||
|
slash_destination,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Ok(failed_to_slash) => {
|
||||||
|
log::trace!(
|
||||||
|
target: crate::LOG_TARGET,
|
||||||
|
"Relayer account {:?} has been partially slashed for {:?}. Funds were deposited to {:?}. \
|
||||||
|
Failed to slash: {:?}",
|
||||||
|
relayer,
|
||||||
|
registration.stake,
|
||||||
|
slash_destination,
|
||||||
|
failed_to_slash,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
// TODO: document this. Where?
|
||||||
|
|
||||||
|
// it may fail if there's no beneficiary account. For us it means that this
|
||||||
|
// account must exists before we'll deploy the bridge
|
||||||
|
log::debug!(
|
||||||
|
target: crate::LOG_TARGET,
|
||||||
|
"Failed to slash relayer account {:?}: {:?}. Maybe beneficiary account doesn't exist? \
|
||||||
|
Beneficiary: {:?}, amount: {:?}, failed to slash: {:?}",
|
||||||
|
relayer,
|
||||||
|
e,
|
||||||
|
slash_destination,
|
||||||
|
registration.stake,
|
||||||
|
registration.stake,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Register reward for given relayer.
|
/// Register reward for given relayer.
|
||||||
pub fn register_relayer_reward(
|
pub fn register_relayer_reward(
|
||||||
rewards_account_params: RewardsAccountParams,
|
rewards_account_params: RewardsAccountParams,
|
||||||
@@ -132,6 +324,42 @@ pub mod pallet {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return required registration lease.
|
||||||
|
fn required_registration_lease() -> T::BlockNumber {
|
||||||
|
<T::StakeAndSlash as StakeAndSlash<
|
||||||
|
T::AccountId,
|
||||||
|
T::BlockNumber,
|
||||||
|
T::Reward,
|
||||||
|
>>::RequiredRegistrationLease::get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return required stake.
|
||||||
|
fn required_stake() -> T::Reward {
|
||||||
|
<T::StakeAndSlash as StakeAndSlash<
|
||||||
|
T::AccountId,
|
||||||
|
T::BlockNumber,
|
||||||
|
T::Reward,
|
||||||
|
>>::RequiredStake::get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Unreserve` given amount on relayer account.
|
||||||
|
fn do_unreserve(relayer: &T::AccountId, amount: T::Reward) -> DispatchResult {
|
||||||
|
let failed_to_unreserve = T::StakeAndSlash::unreserve(relayer, amount);
|
||||||
|
if !failed_to_unreserve.is_zero() {
|
||||||
|
log::trace!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"Failed to unreserve {:?}/{:?} on relayer {:?} account",
|
||||||
|
failed_to_unreserve,
|
||||||
|
amount,
|
||||||
|
relayer,
|
||||||
|
);
|
||||||
|
|
||||||
|
fail!(Error::<T>::FailedToUnreserve)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::event]
|
#[pallet::event]
|
||||||
@@ -146,6 +374,25 @@ pub mod pallet {
|
|||||||
/// Reward amount.
|
/// Reward amount.
|
||||||
reward: T::Reward,
|
reward: T::Reward,
|
||||||
},
|
},
|
||||||
|
/// Relayer registration has been added or updated.
|
||||||
|
RegistrationUpdated {
|
||||||
|
/// Relayer account that has been registered.
|
||||||
|
relayer: T::AccountId,
|
||||||
|
/// Relayer registration.
|
||||||
|
registration: Registration<T::BlockNumber, T::Reward>,
|
||||||
|
},
|
||||||
|
/// Relayer has been `deregistered`.
|
||||||
|
Deregistered {
|
||||||
|
/// Relayer account that has been `deregistered`.
|
||||||
|
relayer: T::AccountId,
|
||||||
|
},
|
||||||
|
/// Relayer has been slashed and `deregistered`.
|
||||||
|
SlashedAndDeregistered {
|
||||||
|
/// Relayer account that has been `deregistered`.
|
||||||
|
relayer: T::AccountId,
|
||||||
|
/// Registration that was removed.
|
||||||
|
registration: Registration<T::BlockNumber, T::Reward>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::error]
|
#[pallet::error]
|
||||||
@@ -154,6 +401,19 @@ pub mod pallet {
|
|||||||
NoRewardForRelayer,
|
NoRewardForRelayer,
|
||||||
/// Reward payment procedure has failed.
|
/// Reward payment procedure has failed.
|
||||||
FailedToPayReward,
|
FailedToPayReward,
|
||||||
|
/// The relayer has tried to register for past block or registration lease
|
||||||
|
/// is too short.
|
||||||
|
InvalidRegistrationLease,
|
||||||
|
/// New registration lease is less than the previous one.
|
||||||
|
CannotReduceRegistrationLease,
|
||||||
|
/// Failed to reserve enough funds on relayer account.
|
||||||
|
FailedToReserve,
|
||||||
|
/// Failed to `unreserve` enough funds on relayer account.
|
||||||
|
FailedToUnreserve,
|
||||||
|
/// Cannot `deregister` if not registered.
|
||||||
|
NotRegistered,
|
||||||
|
/// Failed to `deregister` relayer, because lease is still active.
|
||||||
|
RegistrationIsStillActive,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map of the relayer => accumulated reward.
|
/// Map of the relayer => accumulated reward.
|
||||||
@@ -168,6 +428,22 @@ pub mod pallet {
|
|||||||
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Value,
|
<RelayerRewardsKeyProviderOf<T> as StorageDoubleMapKeyProvider>::Value,
|
||||||
OptionQuery,
|
OptionQuery,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
/// Relayers that have reserved some of their balance to get free priority boost
|
||||||
|
/// for their message delivery transactions.
|
||||||
|
///
|
||||||
|
/// Other relayers may submit transactions as well, but they will have default
|
||||||
|
/// priority and will be rejected (without significant tip) in case if registered
|
||||||
|
/// relayer is present.
|
||||||
|
#[pallet::storage]
|
||||||
|
#[pallet::getter(fn registered_relayer)]
|
||||||
|
pub type RegisteredRelayers<T: Config> = StorageMap<
|
||||||
|
_,
|
||||||
|
Blake2_128Concat,
|
||||||
|
T::AccountId,
|
||||||
|
Registration<T::BlockNumber, T::Reward>,
|
||||||
|
OptionQuery,
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -253,10 +529,10 @@ mod tests {
|
|||||||
None
|
None
|
||||||
);
|
);
|
||||||
|
|
||||||
//Check if the `RewardPaid` event was emitted.
|
// Check if the `RewardPaid` event was emitted.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::<TestRuntime>::events(),
|
System::<TestRuntime>::events().last(),
|
||||||
vec![EventRecord {
|
Some(&EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: TestEvent::Relayers(RewardPaid {
|
event: TestEvent::Relayers(RewardPaid {
|
||||||
relayer: REGULAR_RELAYER,
|
relayer: REGULAR_RELAYER,
|
||||||
@@ -264,7 +540,7 @@ mod tests {
|
|||||||
reward: 100
|
reward: 100
|
||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
}],
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -306,4 +582,295 @@ mod tests {
|
|||||||
assert_eq!(Balances::balance(&1), 200);
|
assert_eq!(Balances::balance(&1), 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_fails_if_valid_till_is_a_past_block() {
|
||||||
|
run_test(|| {
|
||||||
|
System::<TestRuntime>::set_block_number(100);
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 50),
|
||||||
|
Error::<TestRuntime>::InvalidRegistrationLease,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_fails_if_valid_till_lease_is_less_than_required() {
|
||||||
|
run_test(|| {
|
||||||
|
System::<TestRuntime>::set_block_number(100);
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::register(
|
||||||
|
RuntimeOrigin::signed(REGISTER_RELAYER),
|
||||||
|
99 + Lease::get()
|
||||||
|
),
|
||||||
|
Error::<TestRuntime>::InvalidRegistrationLease,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_works() {
|
||||||
|
run_test(|| {
|
||||||
|
get_ready_for_events();
|
||||||
|
|
||||||
|
assert_ok!(Pallet::<TestRuntime>::register(
|
||||||
|
RuntimeOrigin::signed(REGISTER_RELAYER),
|
||||||
|
150
|
||||||
|
));
|
||||||
|
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
|
||||||
|
assert_eq!(
|
||||||
|
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
|
||||||
|
Some(Registration { valid_till: 150, stake: Stake::get() }),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
System::<TestRuntime>::events().last(),
|
||||||
|
Some(&EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: TestEvent::Relayers(Event::RegistrationUpdated {
|
||||||
|
relayer: REGISTER_RELAYER,
|
||||||
|
registration: Registration { valid_till: 150, stake: Stake::get() },
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_fails_if_new_valid_till_is_lesser_than_previous() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_ok!(Pallet::<TestRuntime>::register(
|
||||||
|
RuntimeOrigin::signed(REGISTER_RELAYER),
|
||||||
|
150
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 125),
|
||||||
|
Error::<TestRuntime>::CannotReduceRegistrationLease,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_fails_if_it_cant_unreserve_some_balance_if_required_stake_decreases() {
|
||||||
|
run_test(|| {
|
||||||
|
RegisteredRelayers::<TestRuntime>::insert(
|
||||||
|
REGISTER_RELAYER,
|
||||||
|
Registration { valid_till: 150, stake: Stake::get() + 1 },
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150),
|
||||||
|
Error::<TestRuntime>::FailedToUnreserve,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_unreserves_some_balance_if_required_stake_decreases() {
|
||||||
|
run_test(|| {
|
||||||
|
get_ready_for_events();
|
||||||
|
|
||||||
|
RegisteredRelayers::<TestRuntime>::insert(
|
||||||
|
REGISTER_RELAYER,
|
||||||
|
Registration { valid_till: 150, stake: Stake::get() + 1 },
|
||||||
|
);
|
||||||
|
TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() + 1).unwrap();
|
||||||
|
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get() + 1);
|
||||||
|
let free_balance = Balances::free_balance(REGISTER_RELAYER);
|
||||||
|
|
||||||
|
assert_ok!(Pallet::<TestRuntime>::register(
|
||||||
|
RuntimeOrigin::signed(REGISTER_RELAYER),
|
||||||
|
150
|
||||||
|
));
|
||||||
|
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
|
||||||
|
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + 1);
|
||||||
|
assert_eq!(
|
||||||
|
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
|
||||||
|
Some(Registration { valid_till: 150, stake: Stake::get() }),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
System::<TestRuntime>::events().last(),
|
||||||
|
Some(&EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: TestEvent::Relayers(Event::RegistrationUpdated {
|
||||||
|
relayer: REGISTER_RELAYER,
|
||||||
|
registration: Registration { valid_till: 150, stake: Stake::get() }
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_fails_if_it_cant_reserve_some_balance() {
|
||||||
|
run_test(|| {
|
||||||
|
Balances::set_balance(®ISTER_RELAYER, 0);
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150),
|
||||||
|
Error::<TestRuntime>::FailedToReserve,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_fails_if_it_cant_reserve_some_balance_if_required_stake_increases() {
|
||||||
|
run_test(|| {
|
||||||
|
RegisteredRelayers::<TestRuntime>::insert(
|
||||||
|
REGISTER_RELAYER,
|
||||||
|
Registration { valid_till: 150, stake: Stake::get() - 1 },
|
||||||
|
);
|
||||||
|
Balances::set_balance(®ISTER_RELAYER, 0);
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::register(RuntimeOrigin::signed(REGISTER_RELAYER), 150),
|
||||||
|
Error::<TestRuntime>::FailedToReserve,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn register_reserves_some_balance_if_required_stake_increases() {
|
||||||
|
run_test(|| {
|
||||||
|
get_ready_for_events();
|
||||||
|
|
||||||
|
RegisteredRelayers::<TestRuntime>::insert(
|
||||||
|
REGISTER_RELAYER,
|
||||||
|
Registration { valid_till: 150, stake: Stake::get() - 1 },
|
||||||
|
);
|
||||||
|
TestStakeAndSlash::reserve(®ISTER_RELAYER, Stake::get() - 1).unwrap();
|
||||||
|
|
||||||
|
let free_balance = Balances::free_balance(REGISTER_RELAYER);
|
||||||
|
assert_ok!(Pallet::<TestRuntime>::register(
|
||||||
|
RuntimeOrigin::signed(REGISTER_RELAYER),
|
||||||
|
150
|
||||||
|
));
|
||||||
|
assert_eq!(Balances::reserved_balance(REGISTER_RELAYER), Stake::get());
|
||||||
|
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance - 1);
|
||||||
|
assert_eq!(
|
||||||
|
Pallet::<TestRuntime>::registered_relayer(REGISTER_RELAYER),
|
||||||
|
Some(Registration { valid_till: 150, stake: Stake::get() }),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
System::<TestRuntime>::events().last(),
|
||||||
|
Some(&EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: TestEvent::Relayers(Event::RegistrationUpdated {
|
||||||
|
relayer: REGISTER_RELAYER,
|
||||||
|
registration: Registration { valid_till: 150, stake: Stake::get() }
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deregister_fails_if_not_registered() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)),
|
||||||
|
Error::<TestRuntime>::NotRegistered,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deregister_fails_if_registration_is_still_active() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_ok!(Pallet::<TestRuntime>::register(
|
||||||
|
RuntimeOrigin::signed(REGISTER_RELAYER),
|
||||||
|
150
|
||||||
|
));
|
||||||
|
|
||||||
|
System::<TestRuntime>::set_block_number(100);
|
||||||
|
|
||||||
|
assert_noop!(
|
||||||
|
Pallet::<TestRuntime>::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)),
|
||||||
|
Error::<TestRuntime>::RegistrationIsStillActive,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deregister_works() {
|
||||||
|
run_test(|| {
|
||||||
|
get_ready_for_events();
|
||||||
|
|
||||||
|
assert_ok!(Pallet::<TestRuntime>::register(
|
||||||
|
RuntimeOrigin::signed(REGISTER_RELAYER),
|
||||||
|
150
|
||||||
|
));
|
||||||
|
|
||||||
|
System::<TestRuntime>::set_block_number(151);
|
||||||
|
|
||||||
|
let reserved_balance = Balances::reserved_balance(REGISTER_RELAYER);
|
||||||
|
let free_balance = Balances::free_balance(REGISTER_RELAYER);
|
||||||
|
assert_ok!(Pallet::<TestRuntime>::deregister(RuntimeOrigin::signed(REGISTER_RELAYER)));
|
||||||
|
assert_eq!(
|
||||||
|
Balances::reserved_balance(REGISTER_RELAYER),
|
||||||
|
reserved_balance - Stake::get()
|
||||||
|
);
|
||||||
|
assert_eq!(Balances::free_balance(REGISTER_RELAYER), free_balance + Stake::get());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
System::<TestRuntime>::events().last(),
|
||||||
|
Some(&EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: TestEvent::Relayers(Event::Deregistered { relayer: REGISTER_RELAYER }),
|
||||||
|
topics: vec![],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_registration_active_is_false_for_unregistered_relayer() {
|
||||||
|
run_test(|| {
|
||||||
|
assert!(!Pallet::<TestRuntime>::is_registration_active(®ISTER_RELAYER));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_registration_active_is_false_when_stake_is_too_low() {
|
||||||
|
run_test(|| {
|
||||||
|
RegisteredRelayers::<TestRuntime>::insert(
|
||||||
|
REGISTER_RELAYER,
|
||||||
|
Registration { valid_till: 150, stake: Stake::get() - 1 },
|
||||||
|
);
|
||||||
|
assert!(!Pallet::<TestRuntime>::is_registration_active(®ISTER_RELAYER));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_registration_active_is_false_when_remaining_lease_is_too_low() {
|
||||||
|
run_test(|| {
|
||||||
|
System::<TestRuntime>::set_block_number(150 - Lease::get());
|
||||||
|
|
||||||
|
RegisteredRelayers::<TestRuntime>::insert(
|
||||||
|
REGISTER_RELAYER,
|
||||||
|
Registration { valid_till: 150, stake: Stake::get() },
|
||||||
|
);
|
||||||
|
assert!(!Pallet::<TestRuntime>::is_registration_active(®ISTER_RELAYER));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_registration_active_is_true_when_relayer_is_properly_registeered() {
|
||||||
|
run_test(|| {
|
||||||
|
System::<TestRuntime>::set_block_number(150 - Lease::get());
|
||||||
|
|
||||||
|
RegisteredRelayers::<TestRuntime>::insert(
|
||||||
|
REGISTER_RELAYER,
|
||||||
|
Registration { valid_till: 151, stake: Stake::get() },
|
||||||
|
);
|
||||||
|
assert!(Pallet::<TestRuntime>::is_registration_active(®ISTER_RELAYER));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,10 @@
|
|||||||
use crate as pallet_bridge_relayers;
|
use crate as pallet_bridge_relayers;
|
||||||
|
|
||||||
use bp_messages::LaneId;
|
use bp_messages::LaneId;
|
||||||
use bp_relayers::{PaymentProcedure, RewardsAccountOwner, RewardsAccountParams};
|
use bp_relayers::{
|
||||||
use frame_support::{parameter_types, weights::RuntimeDbWeight};
|
PayRewardFromAccount, PaymentProcedure, RewardsAccountOwner, RewardsAccountParams,
|
||||||
|
};
|
||||||
|
use frame_support::{parameter_types, traits::fungible::Mutate, weights::RuntimeDbWeight};
|
||||||
use sp_core::H256;
|
use sp_core::H256;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
testing::Header as SubstrateHeader,
|
testing::Header as SubstrateHeader,
|
||||||
@@ -29,6 +31,16 @@ use sp_runtime::{
|
|||||||
|
|
||||||
pub type AccountId = u64;
|
pub type AccountId = u64;
|
||||||
pub type Balance = u64;
|
pub type Balance = u64;
|
||||||
|
pub type BlockNumber = u64;
|
||||||
|
|
||||||
|
pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed<
|
||||||
|
AccountId,
|
||||||
|
BlockNumber,
|
||||||
|
Balances,
|
||||||
|
ReserveId,
|
||||||
|
Stake,
|
||||||
|
Lease,
|
||||||
|
>;
|
||||||
|
|
||||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
||||||
@@ -47,13 +59,17 @@ frame_support::construct_runtime! {
|
|||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
|
||||||
|
pub const ExistentialDeposit: Balance = 1;
|
||||||
|
pub const ReserveId: [u8; 8] = *b"brdgrlrs";
|
||||||
|
pub const Stake: Balance = 1_000;
|
||||||
|
pub const Lease: BlockNumber = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl frame_system::Config for TestRuntime {
|
impl frame_system::Config for TestRuntime {
|
||||||
type RuntimeOrigin = RuntimeOrigin;
|
type RuntimeOrigin = RuntimeOrigin;
|
||||||
type Index = u64;
|
type Index = u64;
|
||||||
type RuntimeCall = RuntimeCall;
|
type RuntimeCall = RuntimeCall;
|
||||||
type BlockNumber = u64;
|
type BlockNumber = BlockNumber;
|
||||||
type Hash = H256;
|
type Hash = H256;
|
||||||
type Hashing = BlakeTwo256;
|
type Hashing = BlakeTwo256;
|
||||||
type AccountId = AccountId;
|
type AccountId = AccountId;
|
||||||
@@ -81,11 +97,11 @@ impl pallet_balances::Config for TestRuntime {
|
|||||||
type Balance = Balance;
|
type Balance = Balance;
|
||||||
type DustRemoval = ();
|
type DustRemoval = ();
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type ExistentialDeposit = frame_support::traits::ConstU64<1>;
|
type ExistentialDeposit = ExistentialDeposit;
|
||||||
type AccountStore = frame_system::Pallet<TestRuntime>;
|
type AccountStore = frame_system::Pallet<TestRuntime>;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
type MaxReserves = ();
|
type MaxReserves = ConstU32<1>;
|
||||||
type ReserveIdentifier = ();
|
type ReserveIdentifier = [u8; 8];
|
||||||
type HoldIdentifier = ();
|
type HoldIdentifier = ();
|
||||||
type FreezeIdentifier = ();
|
type FreezeIdentifier = ();
|
||||||
type MaxHolds = ConstU32<0>;
|
type MaxHolds = ConstU32<0>;
|
||||||
@@ -96,6 +112,7 @@ impl pallet_bridge_relayers::Config for TestRuntime {
|
|||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type Reward = Balance;
|
type Reward = Balance;
|
||||||
type PaymentProcedure = TestPaymentProcedure;
|
type PaymentProcedure = TestPaymentProcedure;
|
||||||
|
type StakeAndSlash = TestStakeAndSlash;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,9 +138,18 @@ pub const REGULAR_RELAYER: AccountId = 1;
|
|||||||
/// Relayer that can't receive rewards.
|
/// Relayer that can't receive rewards.
|
||||||
pub const FAILING_RELAYER: AccountId = 2;
|
pub const FAILING_RELAYER: AccountId = 2;
|
||||||
|
|
||||||
|
/// Relayer that is able to register.
|
||||||
|
pub const REGISTER_RELAYER: AccountId = 42;
|
||||||
|
|
||||||
/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
|
/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
|
||||||
pub struct TestPaymentProcedure;
|
pub struct TestPaymentProcedure;
|
||||||
|
|
||||||
|
impl TestPaymentProcedure {
|
||||||
|
pub fn rewards_account(params: RewardsAccountParams) -> AccountId {
|
||||||
|
PayRewardFromAccount::<(), AccountId>::rewards_account(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
|
impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
@@ -147,5 +173,10 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
|
|||||||
|
|
||||||
/// Run pallet test.
|
/// Run pallet test.
|
||||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||||
new_test_ext().execute_with(test)
|
new_test_ext().execute_with(|| {
|
||||||
|
Balances::mint_into(®ISTER_RELAYER, ExistentialDeposit::get() + 10 * Stake::get())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
test()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
// Copyright 2019-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/>.
|
||||||
|
|
||||||
|
//! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash`
|
||||||
|
//! mechanism of the relayers pallet.
|
||||||
|
|
||||||
|
use bp_relayers::{PayRewardFromAccount, RewardsAccountParams, StakeAndSlash};
|
||||||
|
use codec::Codec;
|
||||||
|
use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency};
|
||||||
|
use sp_runtime::{traits::Get, DispatchError, DispatchResult};
|
||||||
|
use sp_std::{fmt::Debug, marker::PhantomData};
|
||||||
|
|
||||||
|
/// `StakeAndSlash` that works with `NamedReservableCurrency` and uses named
|
||||||
|
/// reservations.
|
||||||
|
///
|
||||||
|
/// **WARNING**: this implementation assumes that the relayers pallet is configured to
|
||||||
|
/// use the [`bp_relayers::PayRewardFromAccount`] as its relayers payment scheme.
|
||||||
|
pub struct StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>(
|
||||||
|
PhantomData<(AccountId, BlockNumber, Currency, ReserveId, Stake, Lease)>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
|
||||||
|
StakeAndSlash<AccountId, BlockNumber, Currency::Balance>
|
||||||
|
for StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
|
||||||
|
where
|
||||||
|
AccountId: Codec + Debug,
|
||||||
|
Currency: NamedReservableCurrency<AccountId>,
|
||||||
|
ReserveId: Get<Currency::ReserveIdentifier>,
|
||||||
|
Stake: Get<Currency::Balance>,
|
||||||
|
Lease: Get<BlockNumber>,
|
||||||
|
{
|
||||||
|
type RequiredStake = Stake;
|
||||||
|
type RequiredRegistrationLease = Lease;
|
||||||
|
|
||||||
|
fn reserve(relayer: &AccountId, amount: Currency::Balance) -> DispatchResult {
|
||||||
|
Currency::reserve_named(&ReserveId::get(), relayer, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unreserve(relayer: &AccountId, amount: Currency::Balance) -> Currency::Balance {
|
||||||
|
Currency::unreserve_named(&ReserveId::get(), relayer, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repatriate_reserved(
|
||||||
|
relayer: &AccountId,
|
||||||
|
beneficiary: RewardsAccountParams,
|
||||||
|
amount: Currency::Balance,
|
||||||
|
) -> Result<Currency::Balance, DispatchError> {
|
||||||
|
let beneficiary_account =
|
||||||
|
PayRewardFromAccount::<(), AccountId>::rewards_account(beneficiary);
|
||||||
|
Currency::repatriate_reserved_named(
|
||||||
|
&ReserveId::get(),
|
||||||
|
relayer,
|
||||||
|
&beneficiary_account,
|
||||||
|
amount,
|
||||||
|
BalanceStatus::Free,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::mock::*;
|
||||||
|
|
||||||
|
use frame_support::traits::fungible::Mutate;
|
||||||
|
|
||||||
|
fn test_stake() -> Balance {
|
||||||
|
Stake::get()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reserve_works() {
|
||||||
|
run_test(|| {
|
||||||
|
assert!(TestStakeAndSlash::reserve(&1, test_stake()).is_err());
|
||||||
|
assert_eq!(Balances::free_balance(1), 0);
|
||||||
|
assert_eq!(Balances::reserved_balance(1), 0);
|
||||||
|
|
||||||
|
Balances::mint_into(&2, test_stake() - 1).unwrap();
|
||||||
|
assert!(TestStakeAndSlash::reserve(&2, test_stake()).is_err());
|
||||||
|
assert_eq!(Balances::free_balance(2), test_stake() - 1);
|
||||||
|
assert_eq!(Balances::reserved_balance(2), 0);
|
||||||
|
|
||||||
|
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||||
|
assert_eq!(TestStakeAndSlash::reserve(&3, test_stake()), Ok(()));
|
||||||
|
assert_eq!(Balances::free_balance(3), test_stake());
|
||||||
|
assert_eq!(Balances::reserved_balance(3), test_stake());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unreserve_works() {
|
||||||
|
run_test(|| {
|
||||||
|
assert_eq!(TestStakeAndSlash::unreserve(&1, test_stake()), test_stake());
|
||||||
|
assert_eq!(Balances::free_balance(1), 0);
|
||||||
|
assert_eq!(Balances::reserved_balance(1), 0);
|
||||||
|
|
||||||
|
Balances::mint_into(&2, test_stake() * 2).unwrap();
|
||||||
|
TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TestStakeAndSlash::unreserve(&2, test_stake()),
|
||||||
|
test_stake() - test_stake() / 3
|
||||||
|
);
|
||||||
|
assert_eq!(Balances::free_balance(2), test_stake() * 2);
|
||||||
|
assert_eq!(Balances::reserved_balance(2), 0);
|
||||||
|
|
||||||
|
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||||
|
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||||
|
assert_eq!(TestStakeAndSlash::unreserve(&3, test_stake()), 0);
|
||||||
|
assert_eq!(Balances::free_balance(3), test_stake() * 2);
|
||||||
|
assert_eq!(Balances::reserved_balance(3), 0);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repatriate_reserved_works() {
|
||||||
|
run_test(|| {
|
||||||
|
let beneficiary = TEST_REWARDS_ACCOUNT_PARAMS;
|
||||||
|
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
|
||||||
|
|
||||||
|
let mut expected_balance = ExistentialDeposit::get();
|
||||||
|
Balances::mint_into(&beneficiary_account, expected_balance).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
TestStakeAndSlash::repatriate_reserved(&1, beneficiary, test_stake()),
|
||||||
|
Ok(test_stake())
|
||||||
|
);
|
||||||
|
assert_eq!(Balances::free_balance(1), 0);
|
||||||
|
assert_eq!(Balances::reserved_balance(1), 0);
|
||||||
|
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||||
|
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||||
|
|
||||||
|
expected_balance += test_stake() / 3;
|
||||||
|
Balances::mint_into(&2, test_stake() * 2).unwrap();
|
||||||
|
TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TestStakeAndSlash::repatriate_reserved(&2, beneficiary, test_stake()),
|
||||||
|
Ok(test_stake() - test_stake() / 3)
|
||||||
|
);
|
||||||
|
assert_eq!(Balances::free_balance(2), test_stake() * 2 - test_stake() / 3);
|
||||||
|
assert_eq!(Balances::reserved_balance(2), 0);
|
||||||
|
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||||
|
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||||
|
|
||||||
|
expected_balance += test_stake();
|
||||||
|
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||||
|
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()),
|
||||||
|
Ok(0)
|
||||||
|
);
|
||||||
|
assert_eq!(Balances::free_balance(3), test_stake());
|
||||||
|
assert_eq!(Balances::reserved_balance(3), 0);
|
||||||
|
assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
|
||||||
|
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repatriate_reserved_doesnt_work_when_beneficiary_account_is_missing() {
|
||||||
|
run_test(|| {
|
||||||
|
let beneficiary = TEST_REWARDS_ACCOUNT_PARAMS;
|
||||||
|
let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
|
||||||
|
|
||||||
|
Balances::mint_into(&3, test_stake() * 2).unwrap();
|
||||||
|
TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
|
||||||
|
assert!(TestStakeAndSlash::repatriate_reserved(&3, beneficiary, test_stake()).is_err());
|
||||||
|
assert_eq!(Balances::free_balance(3), test_stake());
|
||||||
|
assert_eq!(Balances::reserved_balance(3), test_stake());
|
||||||
|
assert_eq!(Balances::free_balance(beneficiary_account), 0);
|
||||||
|
assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
pub use registration::{Registration, StakeAndSlash};
|
||||||
|
|
||||||
use bp_messages::LaneId;
|
use bp_messages::LaneId;
|
||||||
use bp_runtime::{ChainId, StorageDoubleMapKeyProvider};
|
use bp_runtime::{ChainId, StorageDoubleMapKeyProvider};
|
||||||
use frame_support::{traits::tokens::Preservation, Blake2_128Concat, Identity};
|
use frame_support::{traits::tokens::Preservation, Blake2_128Concat, Identity};
|
||||||
@@ -30,6 +32,8 @@ use sp_runtime::{
|
|||||||
};
|
};
|
||||||
use sp_std::{fmt::Debug, marker::PhantomData};
|
use sp_std::{fmt::Debug, marker::PhantomData};
|
||||||
|
|
||||||
|
mod registration;
|
||||||
|
|
||||||
/// The owner of the sovereign account that should pay the rewards.
|
/// The owner of the sovereign account that should pay the rewards.
|
||||||
///
|
///
|
||||||
/// Each of the 2 final points connected by a bridge owns a sovereign account at each end of the
|
/// Each of the 2 final points connected by a bridge owns a sovereign account at each end of the
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// 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/>.
|
||||||
|
|
||||||
|
//! Bridge relayers registration and slashing scheme.
|
||||||
|
//!
|
||||||
|
//! There is an option to add a refund-relayer signed extension that will compensate
|
||||||
|
//! relayer costs of the message delivery and confirmation transactions (as well as
|
||||||
|
//! required finality proofs). This extension boosts priority of message delivery
|
||||||
|
//! transactions, based on the number of bundled messages. So transaction with more
|
||||||
|
//! messages has larger priority than the transaction with less messages.
|
||||||
|
//! See [`bridge_runtime_common::priority_calculator`] for details;
|
||||||
|
//!
|
||||||
|
//! This encourages relayers to include more messages to their delivery transactions.
|
||||||
|
//! At the same time, we are not verifying storage proofs before boosting
|
||||||
|
//! priority. Instead, we simply trust relayer, when it says that transaction delivers
|
||||||
|
//! `N` messages.
|
||||||
|
//!
|
||||||
|
//! This allows relayers to submit transactions which declare large number of bundled
|
||||||
|
//! transactions to receive priority boost for free, potentially pushing actual delivery
|
||||||
|
//! transactions from the block (or even transaction queue). Such transactions are
|
||||||
|
//! not free, but their cost is relatively small.
|
||||||
|
//!
|
||||||
|
//! To alleviate that, we only boost transactions of relayers that have some stake
|
||||||
|
//! that guarantees that their transactions are valid. Such relayers get priority
|
||||||
|
//! for free, but they risk to lose their stake.
|
||||||
|
|
||||||
|
use crate::RewardsAccountParams;
|
||||||
|
|
||||||
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
|
use scale_info::TypeInfo;
|
||||||
|
use sp_runtime::{
|
||||||
|
traits::{Get, Zero},
|
||||||
|
DispatchError, DispatchResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Relayer registration.
|
||||||
|
#[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
|
||||||
|
pub struct Registration<BlockNumber, Balance> {
|
||||||
|
/// The last block number, where this registration is considered active.
|
||||||
|
///
|
||||||
|
/// Relayer has an option to renew his registration (this may be done before it
|
||||||
|
/// is spoiled as well). Starting from block `valid_till + 1`, relayer may `deregister`
|
||||||
|
/// himself and get his stake back.
|
||||||
|
///
|
||||||
|
/// Please keep in mind that priority boost stops working some blocks before the
|
||||||
|
/// registration ends (see [`StakeAndSlash::RequiredRegistrationLease`]).
|
||||||
|
pub valid_till: BlockNumber,
|
||||||
|
/// Active relayer stake, which is mapped to the relayer reserved balance.
|
||||||
|
///
|
||||||
|
/// If `stake` is less than the [`StakeAndSlash::RequiredStake`], the registration
|
||||||
|
/// is considered inactive even if `valid_till + 1` is not yet reached.
|
||||||
|
pub stake: Balance,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relayer stake-and-slash mechanism.
|
||||||
|
pub trait StakeAndSlash<AccountId, BlockNumber, Balance> {
|
||||||
|
/// The stake that the relayer must have to have its transactions boosted.
|
||||||
|
type RequiredStake: Get<Balance>;
|
||||||
|
/// Required **remaining** registration lease to be able to get transaction priority boost.
|
||||||
|
///
|
||||||
|
/// If the difference between registration's `valid_till` and the current block number
|
||||||
|
/// is less than the `RequiredRegistrationLease`, it becomes inactive and relayer transaction
|
||||||
|
/// won't get priority boost. This period exists, because priority is calculated when
|
||||||
|
/// transaction is placed to the queue (and it is reevaluated periodically) and then some time
|
||||||
|
/// may pass before transaction will be included into the block.
|
||||||
|
type RequiredRegistrationLease: Get<BlockNumber>;
|
||||||
|
|
||||||
|
/// Reserve the given amount at relayer account.
|
||||||
|
fn reserve(relayer: &AccountId, amount: Balance) -> DispatchResult;
|
||||||
|
/// `Unreserve` the given amount from relayer account.
|
||||||
|
///
|
||||||
|
/// Returns amount that we have failed to `unreserve`.
|
||||||
|
fn unreserve(relayer: &AccountId, amount: Balance) -> Balance;
|
||||||
|
/// Slash up to `amount` from reserved balance of account `relayer` and send funds to given
|
||||||
|
/// `beneficiary`.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(_)` with non-zero balance if we have failed to repatriate some portion of stake.
|
||||||
|
fn repatriate_reserved(
|
||||||
|
relayer: &AccountId,
|
||||||
|
beneficiary: RewardsAccountParams,
|
||||||
|
amount: Balance,
|
||||||
|
) -> Result<Balance, DispatchError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<AccountId, BlockNumber, Balance> StakeAndSlash<AccountId, BlockNumber, Balance> for ()
|
||||||
|
where
|
||||||
|
Balance: Default + Zero,
|
||||||
|
BlockNumber: Default,
|
||||||
|
{
|
||||||
|
type RequiredStake = ();
|
||||||
|
type RequiredRegistrationLease = ();
|
||||||
|
|
||||||
|
fn reserve(_relayer: &AccountId, _amount: Balance) -> DispatchResult {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unreserve(_relayer: &AccountId, _amount: Balance) -> Balance {
|
||||||
|
Zero::zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn repatriate_reserved(
|
||||||
|
_relayer: &AccountId,
|
||||||
|
_beneficiary: RewardsAccountParams,
|
||||||
|
_amount: Balance,
|
||||||
|
) -> Result<Balance, DispatchError> {
|
||||||
|
Ok(Zero::zero())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user