Per-lane rewards in pallet-bridge-relayers (#1665)

* per-lane rewards in pallet-bridge-relayers

* add lane id to RewardPaid event

* clippy

* fix benchmarks
This commit is contained in:
Svyatoslav Nikolsky
2022-11-28 16:49:05 +03:00
committed by Bastian Köcher
parent c03d99cd09
commit ea98118031
6 changed files with 147 additions and 72 deletions
+3 -2
View File
@@ -29,9 +29,10 @@ const REWARD_AMOUNT: u32 = u32::MAX;
benchmarks! { benchmarks! {
// Benchmark `claim_rewards` call. // Benchmark `claim_rewards` call.
claim_rewards { claim_rewards {
let lane = [0, 0, 0, 0];
let relayer: T::AccountId = whitelisted_caller(); let relayer: T::AccountId = whitelisted_caller();
RelayerRewards::<T>::insert(&relayer, T::Reward::from(REWARD_AMOUNT)); RelayerRewards::<T>::insert(&relayer, lane, T::Reward::from(REWARD_AMOUNT));
}: _(RawOrigin::Signed(relayer)) }: _(RawOrigin::Signed(relayer), lane)
verify { verify {
// we can't check anything here, because `PaymentProcedure` is responsible for // we can't check anything here, because `PaymentProcedure` is responsible for
// payment logic, so we assume that if call has succeeded, the procedure has // payment logic, so we assume that if call has succeeded, the procedure has
+78 -28
View File
@@ -20,8 +20,10 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs)] #![warn(missing_docs)]
use bp_messages::LaneId;
use bp_relayers::PaymentProcedure; use bp_relayers::PaymentProcedure;
use sp_arithmetic::traits::AtLeast32BitUnsigned; use frame_support::sp_runtime::Saturating;
use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero};
use sp_std::marker::PhantomData; use sp_std::marker::PhantomData;
use weights::WeightInfo; use weights::WeightInfo;
@@ -63,24 +65,54 @@ pub mod pallet {
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/// Claim accumulated rewards. /// Claim accumulated rewards.
#[pallet::weight(T::WeightInfo::claim_rewards())] #[pallet::weight(T::WeightInfo::claim_rewards())]
pub fn claim_rewards(origin: OriginFor<T>) -> DispatchResult { pub fn claim_rewards(origin: OriginFor<T>, lane_id: LaneId) -> DispatchResult {
let relayer = ensure_signed(origin)?; let relayer = ensure_signed(origin)?;
RelayerRewards::<T>::try_mutate_exists(&relayer, |maybe_reward| -> DispatchResult { RelayerRewards::<T>::try_mutate_exists(
let reward = maybe_reward.take().ok_or(Error::<T>::NoRewardForRelayer)?; &relayer,
T::PaymentProcedure::pay_reward(&relayer, reward).map_err(|e| { lane_id,
log::trace!( |maybe_reward| -> DispatchResult {
target: LOG_TARGET, let reward = maybe_reward.take().ok_or(Error::<T>::NoRewardForRelayer)?;
"Failed to pay rewards to {:?}: {:?}", T::PaymentProcedure::pay_reward(&relayer, lane_id, reward).map_err(|e| {
relayer, log::trace!(
e, target: LOG_TARGET,
); "Failed to pay {:?} rewards to {:?}: {:?}",
Error::<T>::FailedToPayReward lane_id,
})?; relayer,
e,
);
Error::<T>::FailedToPayReward
})?;
Self::deposit_event(Event::<T>::RewardPaid { relayer: relayer.clone(), reward }); Self::deposit_event(Event::<T>::RewardPaid {
Ok(()) relayer: relayer.clone(),
}) lane_id,
reward,
});
Ok(())
},
)
}
}
impl<T: Config> Pallet<T> {
/// Register reward for given relayer.
pub fn register_relayer_reward(lane_id: LaneId, relayer: &T::AccountId, reward: T::Reward) {
if reward.is_zero() {
return
}
RelayerRewards::<T>::mutate(relayer, lane_id, |old_reward: &mut Option<T::Reward>| {
let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward);
*old_reward = Some(new_reward);
log::trace!(
target: crate::LOG_TARGET,
"Relayer {:?} can now claim reward: {:?}",
relayer,
new_reward,
);
});
} }
} }
@@ -91,6 +123,8 @@ pub mod pallet {
RewardPaid { RewardPaid {
/// Relayer account that has been rewarded. /// Relayer account that has been rewarded.
relayer: T::AccountId, relayer: T::AccountId,
/// Relayer has received reward for serving this lane.
lane_id: LaneId,
/// Reward amount. /// Reward amount.
reward: T::Reward, reward: T::Reward,
}, },
@@ -106,8 +140,15 @@ pub mod pallet {
/// Map of the relayer => accumulated reward. /// Map of the relayer => accumulated reward.
#[pallet::storage] #[pallet::storage]
pub type RelayerRewards<T: Config> = pub type RelayerRewards<T: Config> = StorageDoubleMap<
StorageMap<_, Blake2_128Concat, T::AccountId, T::Reward, OptionQuery>; _,
Blake2_128Concat,
T::AccountId,
Identity,
LaneId,
T::Reward,
OptionQuery,
>;
} }
#[cfg(test)] #[cfg(test)]
@@ -129,7 +170,7 @@ mod tests {
fn root_cant_claim_anything() { fn root_cant_claim_anything() {
run_test(|| { run_test(|| {
assert_noop!( assert_noop!(
Pallet::<TestRuntime>::claim_rewards(RuntimeOrigin::root()), Pallet::<TestRuntime>::claim_rewards(RuntimeOrigin::root(), TEST_LANE_ID),
DispatchError::BadOrigin, DispatchError::BadOrigin,
); );
}); });
@@ -139,7 +180,10 @@ mod tests {
fn relayer_cant_claim_if_no_reward_exists() { fn relayer_cant_claim_if_no_reward_exists() {
run_test(|| { run_test(|| {
assert_noop!( assert_noop!(
Pallet::<TestRuntime>::claim_rewards(RuntimeOrigin::signed(REGULAR_RELAYER)), Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(REGULAR_RELAYER),
TEST_LANE_ID
),
Error::<TestRuntime>::NoRewardForRelayer, Error::<TestRuntime>::NoRewardForRelayer,
); );
}); });
@@ -148,9 +192,12 @@ mod tests {
#[test] #[test]
fn relayer_cant_claim_if_payment_procedure_fails() { fn relayer_cant_claim_if_payment_procedure_fails() {
run_test(|| { run_test(|| {
RelayerRewards::<TestRuntime>::insert(FAILING_RELAYER, 100); RelayerRewards::<TestRuntime>::insert(FAILING_RELAYER, TEST_LANE_ID, 100);
assert_noop!( assert_noop!(
Pallet::<TestRuntime>::claim_rewards(RuntimeOrigin::signed(FAILING_RELAYER)), Pallet::<TestRuntime>::claim_rewards(
RuntimeOrigin::signed(FAILING_RELAYER),
TEST_LANE_ID
),
Error::<TestRuntime>::FailedToPayReward, Error::<TestRuntime>::FailedToPayReward,
); );
}); });
@@ -161,11 +208,12 @@ mod tests {
run_test(|| { run_test(|| {
get_ready_for_events(); get_ready_for_events();
RelayerRewards::<TestRuntime>::insert(REGULAR_RELAYER, 100); RelayerRewards::<TestRuntime>::insert(REGULAR_RELAYER, TEST_LANE_ID, 100);
assert_ok!(Pallet::<TestRuntime>::claim_rewards(RuntimeOrigin::signed( assert_ok!(Pallet::<TestRuntime>::claim_rewards(
REGULAR_RELAYER RuntimeOrigin::signed(REGULAR_RELAYER),
))); TEST_LANE_ID
assert_eq!(RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER), None); ));
assert_eq!(RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER, TEST_LANE_ID), None);
//Check if the `RewardPaid` event was emitted. //Check if the `RewardPaid` event was emitted.
assert_eq!( assert_eq!(
@@ -174,6 +222,7 @@ mod tests {
phase: Phase::Initialization, phase: Phase::Initialization,
event: TestEvent::Relayers(RewardPaid { event: TestEvent::Relayers(RewardPaid {
relayer: REGULAR_RELAYER, relayer: REGULAR_RELAYER,
lane_id: TEST_LANE_ID,
reward: 100 reward: 100
}), }),
topics: vec![], topics: vec![],
@@ -189,7 +238,8 @@ mod tests {
run_test(|| { run_test(|| {
assert_eq!(Balances::balance(&1), 0); assert_eq!(Balances::balance(&1), 0);
assert_eq!(Balances::total_issuance(), 0); assert_eq!(Balances::total_issuance(), 0);
bp_relayers::MintReward::<Balances, AccountId>::pay_reward(&1, 100).unwrap(); bp_relayers::MintReward::<Balances, AccountId>::pay_reward(&1, TEST_LANE_ID, 100)
.unwrap();
assert_eq!(Balances::balance(&1), 100); assert_eq!(Balances::balance(&1), 100);
assert_eq!(Balances::total_issuance(), 100); assert_eq!(Balances::total_issuance(), 100);
}); });
+11 -2
View File
@@ -18,7 +18,9 @@
use crate as pallet_bridge_relayers; use crate as pallet_bridge_relayers;
use bp_messages::{source_chain::ForbidOutboundMessages, target_chain::ForbidInboundMessages}; use bp_messages::{
source_chain::ForbidOutboundMessages, target_chain::ForbidInboundMessages, LaneId,
};
use bp_relayers::PaymentProcedure; use bp_relayers::PaymentProcedure;
use frame_support::{parameter_types, weights::RuntimeDbWeight}; use frame_support::{parameter_types, weights::RuntimeDbWeight};
use sp_core::H256; use sp_core::H256;
@@ -124,6 +126,9 @@ impl pallet_bridge_relayers::Config for TestRuntime {
type WeightInfo = (); type WeightInfo = ();
} }
/// Message lane that we're using in tests.
pub const TEST_LANE_ID: LaneId = [0, 0, 0, 0];
/// Regular relayer that may receive rewards. /// Regular relayer that may receive rewards.
pub const REGULAR_RELAYER: AccountId = 1; pub const REGULAR_RELAYER: AccountId = 1;
@@ -136,7 +141,11 @@ pub struct TestPaymentProcedure;
impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure { impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
type Error = (); type Error = ();
fn pay_reward(relayer: &AccountId, _reward: Balance) -> Result<(), Self::Error> { fn pay_reward(
relayer: &AccountId,
_lane_id: LaneId,
_reward: Balance,
) -> Result<(), Self::Error> {
match *relayer { match *relayer {
FAILING_RELAYER => Err(()), FAILING_RELAYER => Err(()),
_ => Ok(()), _ => Ok(()),
+41 -36
View File
@@ -17,7 +17,7 @@
//! Code that allows relayers pallet to be used as a delivery+dispatch payment mechanism //! Code that allows relayers pallet to be used as a delivery+dispatch payment mechanism
//! for the messages pallet. //! for the messages pallet.
use crate::{Config, RelayerRewards}; use crate::{Config, Pallet};
use bp_messages::source_chain::{MessageDeliveryAndDispatchPayment, RelayersRewards}; use bp_messages::source_chain::{MessageDeliveryAndDispatchPayment, RelayersRewards};
use frame_support::sp_runtime::SaturatedConversion; use frame_support::sp_runtime::SaturatedConversion;
@@ -39,7 +39,7 @@ where
type Error = &'static str; type Error = &'static str;
fn pay_relayers_rewards( fn pay_relayers_rewards(
_lane_id: bp_messages::LaneId, lane_id: bp_messages::LaneId,
messages_relayers: VecDeque<bp_messages::UnrewardedRelayer<T::AccountId>>, messages_relayers: VecDeque<bp_messages::UnrewardedRelayer<T::AccountId>>,
confirmation_relayer: &T::AccountId, confirmation_relayer: &T::AccountId,
received_range: &RangeInclusive<bp_messages::MessageNonce>, received_range: &RangeInclusive<bp_messages::MessageNonce>,
@@ -52,8 +52,9 @@ where
register_relayers_rewards::<T>( register_relayers_rewards::<T>(
confirmation_relayer, confirmation_relayer,
relayers_rewards, relayers_rewards,
lane_id,
// TODO (https://github.com/paritytech/parity-bridges-common/issues/1318): this shall be fixed // TODO (https://github.com/paritytech/parity-bridges-common/issues/1318): this shall be fixed
// in some way. ATM the future of the `register_relayers_rewards` is not yet known // in some way. ATM the future of the `register_relayer_reward` is not yet known
100_000_u32.into(), 100_000_u32.into(),
10_000_u32.into(), 10_000_u32.into(),
); );
@@ -64,6 +65,7 @@ where
fn register_relayers_rewards<T: Config>( fn register_relayers_rewards<T: Config>(
confirmation_relayer: &T::AccountId, confirmation_relayer: &T::AccountId,
relayers_rewards: RelayersRewards<T::AccountId>, relayers_rewards: RelayersRewards<T::AccountId>,
lane_id: bp_messages::LaneId,
delivery_fee: T::Reward, delivery_fee: T::Reward,
confirmation_fee: T::Reward, confirmation_fee: T::Reward,
) { ) {
@@ -87,7 +89,7 @@ fn register_relayers_rewards<T: Config>(
relayer_reward = relayer_reward.saturating_sub(confirmation_reward); relayer_reward = relayer_reward.saturating_sub(confirmation_reward);
confirmation_relayer_reward = confirmation_relayer_reward =
confirmation_relayer_reward.saturating_add(confirmation_reward); confirmation_relayer_reward.saturating_add(confirmation_reward);
register_relayer_reward::<T>(&relayer, relayer_reward); Pallet::<T>::register_relayer_reward(lane_id, &relayer, relayer_reward);
} else { } else {
// If delivery confirmation is submitted by this relayer, let's add confirmation fee // If delivery confirmation is submitted by this relayer, let's add confirmation fee
// from other relayers to this relayer reward. // from other relayers to this relayer reward.
@@ -97,32 +99,17 @@ fn register_relayers_rewards<T: Config>(
} }
// finally - pay reward to confirmation relayer // finally - pay reward to confirmation relayer
register_relayer_reward::<T>(confirmation_relayer, confirmation_relayer_reward); Pallet::<T>::register_relayer_reward(
} lane_id,
confirmation_relayer,
/// Remember that the reward shall be paid to the relayer. confirmation_relayer_reward,
fn register_relayer_reward<T: Config>(relayer: &T::AccountId, reward: T::Reward) { );
if reward.is_zero() {
return
}
RelayerRewards::<T>::mutate(relayer, |old_reward: &mut Option<T::Reward>| {
let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward);
*old_reward = Some(new_reward);
log::trace!(
target: crate::LOG_TARGET,
"Relayer {:?} can now claim reward: {:?}",
relayer,
new_reward,
);
});
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::mock::*; use crate::{mock::*, RelayerRewards};
const RELAYER_1: AccountId = 1; const RELAYER_1: AccountId = 1;
const RELAYER_2: AccountId = 2; const RELAYER_2: AccountId = 2;
@@ -135,32 +122,50 @@ mod tests {
#[test] #[test]
fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() { fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() {
run_test(|| { run_test(|| {
register_relayers_rewards::<TestRuntime>(&RELAYER_2, relayers_rewards(), 50, 10); register_relayers_rewards::<TestRuntime>(
&RELAYER_2,
relayers_rewards(),
TEST_LANE_ID,
50,
10,
);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1), Some(80)); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_LANE_ID), Some(80));
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2), Some(170)); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_LANE_ID), Some(170));
}); });
} }
#[test] #[test]
fn confirmation_relayer_is_rewarded_if_it_has_not_delivered_any_delivered_messages() { fn confirmation_relayer_is_rewarded_if_it_has_not_delivered_any_delivered_messages() {
run_test(|| { run_test(|| {
register_relayers_rewards::<TestRuntime>(&RELAYER_3, relayers_rewards(), 50, 10); register_relayers_rewards::<TestRuntime>(
&RELAYER_3,
relayers_rewards(),
TEST_LANE_ID,
50,
10,
);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1), Some(80)); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_LANE_ID), Some(80));
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2), Some(120)); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_LANE_ID), Some(120));
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_3), Some(50)); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_3, TEST_LANE_ID), Some(50));
}); });
} }
#[test] #[test]
fn only_confirmation_relayer_is_rewarded_if_confirmation_fee_has_significantly_increased() { fn only_confirmation_relayer_is_rewarded_if_confirmation_fee_has_significantly_increased() {
run_test(|| { run_test(|| {
register_relayers_rewards::<TestRuntime>(&RELAYER_3, relayers_rewards(), 50, 1000); register_relayers_rewards::<TestRuntime>(
&RELAYER_3,
relayers_rewards(),
TEST_LANE_ID,
50,
1000,
);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1), None); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_1, TEST_LANE_ID), None);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2), None); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_2, TEST_LANE_ID), None);
assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_3), Some(250)); assert_eq!(RelayerRewards::<TestRuntime>::get(RELAYER_3, TEST_LANE_ID), Some(250));
}); });
} }
} }
+6 -1
View File
@@ -8,6 +8,10 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies] [dependencies]
# Bridge Dependencies
bp-messages = { path = "../messages", default-features = false }
# Substrate Dependencies # Substrate Dependencies
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 }
@@ -21,7 +25,8 @@ hex-literal = "0.3"
[features] [features]
default = ["std"] default = ["std"]
std = [ std = [
"bp-messages/std",
"frame-support/std", "frame-support/std",
"sp-runtime/std", "sp-runtime/std",
"sp-std/std", "sp-std/std",
] ]
+8 -3
View File
@@ -19,6 +19,7 @@
#![warn(missing_docs)] #![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
use bp_messages::LaneId;
use sp_std::{fmt::Debug, marker::PhantomData}; use sp_std::{fmt::Debug, marker::PhantomData};
/// Reward payment procedure. /// Reward payment procedure.
@@ -26,8 +27,8 @@ pub trait PaymentProcedure<Relayer, Reward> {
/// Error that may be returned by the procedure. /// Error that may be returned by the procedure.
type Error: Debug; type Error: Debug;
/// Pay reward to the relayer. /// Pay reward to the relayer for serving given message lane.
fn pay_reward(relayer: &Relayer, reward: Reward) -> Result<(), Self::Error>; fn pay_reward(relayer: &Relayer, lane_id: LaneId, reward: Reward) -> Result<(), Self::Error>;
} }
/// Reward payment procedure that is simply minting given amount of tokens. /// Reward payment procedure that is simply minting given amount of tokens.
@@ -39,7 +40,11 @@ where
{ {
type Error = sp_runtime::DispatchError; type Error = sp_runtime::DispatchError;
fn pay_reward(relayer: &Relayer, reward: T::Balance) -> Result<(), Self::Error> { fn pay_reward(
relayer: &Relayer,
_lane_id: LaneId,
reward: T::Balance,
) -> Result<(), Self::Error> {
T::mint_into(relayer, reward) T::mint_into(relayer, reward)
} }
} }