Skip to content

Commit

Permalink
Dynamic rate limit
Browse files Browse the repository at this point in the history
  • Loading branch information
aurexav committed Apr 17, 2024
1 parent c59955e commit f584143
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 140 deletions.
4 changes: 2 additions & 2 deletions node/src/chain_spec/crab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ pub fn genesis_config() -> ChainSpec {
darwinia_staking: DarwiniaStakingConfig {
now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
elapsed_time: 0,
max_unstake_ring: 10_000_000 * UNIT,
rate_limit: 20_000_000 * UNIT,
collator_count: 6,
collators: collators
.iter()
Expand Down Expand Up @@ -304,7 +304,7 @@ fn testnet_genesis(
darwinia_staking: DarwiniaStakingConfig {
now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
elapsed_time: 0,
max_unstake_ring: 10_000_000 * UNIT,
rate_limit: 20_000_000 * UNIT,
collator_count: collators.len() as _,
collators: collators.iter().map(|(a, _)| (a.to_owned(), UNIT)).collect(),
},
Expand Down
4 changes: 2 additions & 2 deletions node/src/chain_spec/darwinia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub fn genesis_config() -> ChainSpec {
darwinia_staking: DarwiniaStakingConfig {
now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
elapsed_time: 0,
max_unstake_ring: 10_000_000 * UNIT,
rate_limit: 20_000_000 * UNIT,
collator_count: 5,
collators: collators
.iter()
Expand Down Expand Up @@ -302,7 +302,7 @@ fn testnet_genesis(
darwinia_staking: DarwiniaStakingConfig {
now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
elapsed_time: 0,
max_unstake_ring: 10_000_000 * UNIT,
rate_limit: 20_000_000 * UNIT,
collator_count: collators.len() as _,
collators: collators.iter().map(|(a, _)| (a.to_owned(), UNIT)).collect(),
},
Expand Down
4 changes: 2 additions & 2 deletions node/src/chain_spec/pangolin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ pub fn genesis_config() -> ChainSpec {
darwinia_staking: DarwiniaStakingConfig {
now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
elapsed_time: 0,
max_unstake_ring: 10_000_000 * UNIT,
rate_limit: 20_000_000 * UNIT,
collator_count: 3,
collators: vec![
(array_bytes::hex_n_into_unchecked::<_, _, 20>(C1), UNIT),
Expand Down Expand Up @@ -292,7 +292,7 @@ fn testnet_genesis(
darwinia_staking: DarwiniaStakingConfig {
now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
elapsed_time: 0,
max_unstake_ring: 10_000_000 * UNIT,
rate_limit: 20_000_000 * UNIT,
collator_count: collators.len() as _,
collators: collators.iter().map(|(a, _)| (a.to_owned(), UNIT)).collect(),
},
Expand Down
2 changes: 1 addition & 1 deletion pallet/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ mod benchmarks {
}

#[benchmark]
fn set_max_unstake_ring() {
fn set_rate_limit() {
// Worst-case scenario:
//
// Set max unstake ring successfully.
Expand Down
116 changes: 90 additions & 26 deletions pallet/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ pub mod pallet {
pub enum Error<T> {
/// Exceed maximum deposit count.
ExceedMaxDeposits,
/// Exceed maximum unstake amount.
ExceedMaxUnstakeAmount,
/// Exceed rate limit.
ExceedRateLimit,
/// Deposit not found.
DepositNotFound,
/// You are not a staker.
Expand Down Expand Up @@ -282,19 +282,19 @@ pub mod pallet {
#[pallet::getter(fn elapsed_time)]
pub type ElapsedTime<T: Config> = StorageValue<_, Moment, ValueQuery>;

/// Max unstake RING limit.
/// Rate limit.
///
/// The maximum RING amount that can be unstaked in a session.
/// The maximum amount of RING that can be staked or unstaked in one session.
#[pallet::storage]
#[pallet::getter(fn max_unstake_ring)]
pub type MaxUnstakeRing<T: Config> = StorageValue<_, Balance, ValueQuery>;
#[pallet::getter(fn rate_limit)]
pub type RateLimit<T: Config> = StorageValue<_, Balance, ValueQuery>;

/// Unstake accumulator.
/// Rate limit state.
///
/// Tracks the total RING amount being unstaked in a session.
/// Tracks the rate limit state in a session.
#[pallet::storage]
#[pallet::getter(fn accumulate_unstake)]
pub type AccumulateUnstake<T: Config> = StorageValue<_, Balance, ValueQuery>;
#[pallet::getter(fn rate_limit_state)]
pub type RateLimitState<T: Config> = StorageValue<_, RateLimiter, ValueQuery>;

#[derive(DefaultNoBound)]
#[pallet::genesis_config]
Expand All @@ -303,8 +303,8 @@ pub mod pallet {
pub now: Moment,
/// The running time of Darwinia1.
pub elapsed_time: Moment,
/// Max unstake RING limit.
pub max_unstake_ring: Balance,
/// Rate limit.
pub rate_limit: Balance,
/// Genesis collator count.
pub collator_count: u32,
/// Genesis collator preferences.
Expand All @@ -320,7 +320,7 @@ pub mod pallet {

<SessionStartTime<T>>::put(self.now);
<ElapsedTime<T>>::put(self.elapsed_time);
<MaxUnstakeRing<T>>::put(self.max_unstake_ring);
<RateLimit<T>>::put(self.rate_limit);
<CollatorCount<T>>::put(self.collator_count);

self.collators.iter().for_each(|(who, ring_amount)| {
Expand Down Expand Up @@ -376,7 +376,7 @@ pub mod pallet {
return Ok(());
}

<Ledgers<T>>::try_mutate(&who, |l| {
let flow_in_amount = <Ledgers<T>>::try_mutate(&who, |l| {
let l = if let Some(l) = l {
l
} else {
Expand All @@ -386,18 +386,29 @@ pub mod pallet {

l.as_mut().expect("[pallet::staking] `l` must be some; qed")
};
let mut v = ring_amount;

if ring_amount != 0 {
Self::stake_ring(&who, &mut l.ring, ring_amount)?;
}

for d in deposits.clone() {
v = v.saturating_add(T::Deposit::amount(&who, d).unwrap_or_default());

Self::stake_deposit(&who, l, d)?;
}

DispatchResult::Ok(())
<Result<_, DispatchError>>::Ok(v)
})?;

if let Some(r) =
<RateLimitState<T>>::get().flow_in(flow_in_amount, <RateLimit<T>>::get())
{
<RateLimitState<T>>::put(r);
} else {
Err(<Error<T>>::ExceedRateLimit)?;
}

Self::deposit_event(Event::Staked { who, ring_amount, deposits });

Ok(())
Expand All @@ -417,10 +428,9 @@ pub mod pallet {
return Ok(());
}

let mut acc = <AccumulateUnstake<T>>::get().saturating_add(ring_amount);

<Ledgers<T>>::try_mutate(&who, |l| {
let flow_out_amount = <Ledgers<T>>::try_mutate(&who, |l| {
let l = l.as_mut().ok_or(<Error<T>>::NotStaker)?;
let mut v = ring_amount;

if ring_amount != 0 {
l.ring = l
Expand All @@ -432,7 +442,7 @@ pub mod pallet {
}

for d in deposits {
acc = acc.saturating_add(T::Deposit::amount(&who, d).unwrap_or_default());
v = v.saturating_add(T::Deposit::amount(&who, d).unwrap_or_default());

l.deposits.remove(
l.deposits
Expand All @@ -444,13 +454,15 @@ pub mod pallet {
T::Deposit::unstake(&who, d)?;
}

DispatchResult::Ok(())
<Result<_, DispatchError>>::Ok(v)
})?;

if acc <= <MaxUnstakeRing<T>>::get() {
<AccumulateUnstake<T>>::put(acc);
if let Some(r) =
<RateLimitState<T>>::get().flow_out(flow_out_amount, <RateLimit<T>>::get())
{
<RateLimitState<T>>::put(r);
} else {
Err(<Error<T>>::ExceedMaxUnstakeAmount)?;
Err(<Error<T>>::ExceedRateLimit)?;
}

Self::try_clean_ledger_of(&who);
Expand Down Expand Up @@ -522,11 +534,11 @@ pub mod pallet {

/// Set max unstake RING limit.
#[pallet::call_index(9)]
#[pallet::weight(<T as Config>::WeightInfo::set_max_unstake_ring())]
pub fn set_max_unstake_ring(origin: OriginFor<T>, amount: Balance) -> DispatchResult {
#[pallet::weight(<T as Config>::WeightInfo::set_rate_limit())]
pub fn set_rate_limit(origin: OriginFor<T>, amount: Balance) -> DispatchResult {
ensure_root(origin)?;

<MaxUnstakeRing<T>>::put(amount);
<RateLimit<T>>::put(amount);

Ok(())
}
Expand Down Expand Up @@ -676,6 +688,7 @@ pub mod pallet {

/// Prepare the session state.
pub fn prepare_new_session(index: u32) -> Option<Vec<T::AccountId>> {
<RateLimitState<T>>::kill();
<Pallet<T>>::shift_exposure_cache_states();

#[allow(deprecated)]
Expand Down Expand Up @@ -810,6 +823,57 @@ where
}
}

/// Staking rate limiter.
#[derive(Clone, Debug, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
pub enum RateLimiter {
/// Positive balance.
Pos(Balance),
/// Negative balance.
Neg(Balance),
}
impl RateLimiter {
fn flow_in(self, amount: Balance, limit: Balance) -> Option<Self> {
match self {
Self::Pos(v) => v.checked_add(amount).filter(|&v| v <= limit).map(Self::Pos),
Self::Neg(v) =>
if v >= amount {
Some(Self::Neg(v - amount))
} else {
let v = amount - v;

if v <= limit {
Some(Self::Pos(v))
} else {
None
}
},
}
}

fn flow_out(self, amount: Balance, limit: Balance) -> Option<Self> {
match self {
Self::Pos(v) =>
if v >= amount {
Some(Self::Pos(v - amount))
} else {
let v = amount - v;

if v <= limit {
Some(Self::Neg(v))
} else {
None
}
},
Self::Neg(v) => v.checked_add(amount).filter(|&new_v| new_v <= limit).map(Self::Neg),
}
}
}
impl Default for RateLimiter {
fn default() -> Self {
Self::Pos(0)
}
}

/// Exposure cache's state.
#[allow(missing_docs)]
#[cfg_attr(any(test, feature = "runtime-benchmarks", feature = "try-runtime"), derive(PartialEq))]
Expand Down
2 changes: 1 addition & 1 deletion pallet/staking/src/migration/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ where
<RingPool<T>>::kill();
<KtonPool<T>>::kill();
<MigrationStartBlock<T>>::kill();
<MaxUnstakeRing<T>>::put(10_000_000 * UNIT);
<RateLimit<T>>::put(10_000_000 * UNIT);

This comment has been minimized.

Copy link
@boundless-forest

boundless-forest Apr 17, 2024

Member

20_000_000? Keep it the same as the genesis config.

<Ledgers<T>>::translate::<OldLedger<T>, _>(|a, o| {
w += 6;

Expand Down
2 changes: 1 addition & 1 deletion pallet/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ impl ExtBuilder {
.assimilate_storage(&mut storage)
.unwrap();
darwinia_staking::GenesisConfig::<Runtime> {
max_unstake_ring: 100 * UNIT,
rate_limit: 100 * UNIT,
collator_count: self.collator_count,
collators: if self.genesis_collator {
(1..=self.collator_count).map(|i| (i, UNIT)).collect()
Expand Down
Loading

0 comments on commit f584143

Please sign in to comment.