mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
fix: bound rewards window processing per block
This commit is contained in:
parent
e7474c92ee
commit
a5c3266c5f
4 changed files with 202 additions and 134 deletions
|
|
@ -23,7 +23,7 @@ use crate::Pallet as ExternalValidatorsRewards;
|
|||
use {
|
||||
crate::types::BenchmarkHelper,
|
||||
frame_benchmarking::{account, v2::*, BenchmarkError},
|
||||
frame_support::traits::{Currency, EnsureOrigin},
|
||||
frame_support::traits::{Currency, EnsureOrigin, Hooks},
|
||||
};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
|
@ -108,12 +108,44 @@ mod benchmarks {
|
|||
// on_initialize: unsent queue is empty (2 reads for head+tail)
|
||||
#[benchmark]
|
||||
fn process_unsent_reward_eras_empty() -> Result<(), BenchmarkError> {
|
||||
// Ensure queue is empty (default state: head == tail == 0)
|
||||
assert!(ExternalValidatorsRewards::<T>::unsent_queue_is_empty());
|
||||
// Exercise the "empty slot at head" fallback path that returns the empty weight.
|
||||
<UnsentRewardHead<T>>::put(0);
|
||||
<UnsentRewardTail<T>>::put(1);
|
||||
frame_system::Pallet::<T>::set_block_number(1u32.into());
|
||||
|
||||
#[block]
|
||||
{
|
||||
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
|
||||
<ExternalValidatorsRewards<T> as Hooks<
|
||||
frame_system::pallet_prelude::BlockNumberFor<T>,
|
||||
>>::on_initialize(1u32.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn process_closed_windows_idle() -> Result<(), BenchmarkError> {
|
||||
pallet_timestamp::Now::<T>::put(35_000u64);
|
||||
|
||||
#[block]
|
||||
{
|
||||
ExternalValidatorsRewards::<T>::process_closed_windows(35, 0, 10);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn process_closed_windows_processed() -> Result<(), BenchmarkError> {
|
||||
frame_system::Pallet::<T>::set_block_number(0u32.into());
|
||||
T::BenchmarkHelper::setup();
|
||||
setup_window_reward_state::<T>(20, 42);
|
||||
<NextWindowToSubmit<T>>::put(20);
|
||||
pallet_timestamp::Now::<T>::put(35_000u64);
|
||||
|
||||
#[block]
|
||||
{
|
||||
ExternalValidatorsRewards::<T>::process_closed_windows(35, 0, 10);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -123,10 +155,13 @@ mod benchmarks {
|
|||
#[benchmark]
|
||||
fn process_unsent_reward_eras_expired() -> Result<(), BenchmarkError> {
|
||||
push_unsent_entry::<T>(999, 99, 10);
|
||||
frame_system::Pallet::<T>::set_block_number(1u32.into());
|
||||
|
||||
#[block]
|
||||
{
|
||||
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
|
||||
<ExternalValidatorsRewards<T> as Hooks<
|
||||
frame_system::pallet_prelude::BlockNumberFor<T>,
|
||||
>>::on_initialize(1u32.into());
|
||||
}
|
||||
|
||||
// Entry should have been removed
|
||||
|
|
@ -143,10 +178,13 @@ mod benchmarks {
|
|||
setup_window_reward_state::<T>(0, 42);
|
||||
|
||||
push_unsent_entry::<T>(0, 0, 10);
|
||||
frame_system::Pallet::<T>::set_block_number(1u32.into());
|
||||
|
||||
#[block]
|
||||
{
|
||||
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
|
||||
<ExternalValidatorsRewards<T> as Hooks<
|
||||
frame_system::pallet_prelude::BlockNumberFor<T>,
|
||||
>>::on_initialize(1u32.into());
|
||||
}
|
||||
|
||||
assert!(ExternalValidatorsRewards::<T>::unsent_queue_is_empty());
|
||||
|
|
@ -162,10 +200,13 @@ mod benchmarks {
|
|||
setup_window_reward_state::<T>(0, 42);
|
||||
|
||||
push_unsent_entry::<T>(0, 0, 10);
|
||||
frame_system::Pallet::<T>::set_block_number(1u32.into());
|
||||
|
||||
#[block]
|
||||
{
|
||||
ExternalValidatorsRewards::<T>::process_unsent_reward_eras();
|
||||
<ExternalValidatorsRewards<T> as Hooks<
|
||||
frame_system::pallet_prelude::BlockNumberFor<T>,
|
||||
>>::on_initialize(1u32.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -193,7 +193,16 @@ pub mod pallet {
|
|||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<frame_system::pallet_prelude::BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(_n: frame_system::pallet_prelude::BlockNumberFor<T>) -> Weight {
|
||||
Self::process_unsent_reward_eras()
|
||||
let head = UnsentRewardHead::<T>::get();
|
||||
let tail = UnsentRewardTail::<T>::get();
|
||||
|
||||
if head != tail {
|
||||
return Self::process_unsent_reward_eras_from(head, tail);
|
||||
}
|
||||
|
||||
let (genesis, interval) = Self::rewards_window_config();
|
||||
let now = Self::now_seconds();
|
||||
Self::process_closed_windows(now, genesis, interval)
|
||||
}
|
||||
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
|
|
@ -210,9 +219,6 @@ pub mod pallet {
|
|||
// in-flight era is not partially accounted under mixed semantics.
|
||||
WindowModeStartsAtEra::<T>::put(cutover_era);
|
||||
NextWindowToSubmit::<T>::kill();
|
||||
for slot in 0..UNSENT_QUEUE_CAPACITY {
|
||||
UnsentRewardWindow::<T>::remove(slot);
|
||||
}
|
||||
UnsentRewardHead::<T>::put(0);
|
||||
UnsentRewardTail::<T>::put(0);
|
||||
STORAGE_VERSION.put::<Pallet<T>>();
|
||||
|
|
@ -223,7 +229,7 @@ pub mod pallet {
|
|||
cutover_era,
|
||||
);
|
||||
|
||||
T::DbWeight::get().reads_writes(2, 69)
|
||||
T::DbWeight::get().reads_writes(2, 5)
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
|
|
@ -257,7 +263,7 @@ pub mod pallet {
|
|||
"Window cutover era was not initialized correctly",
|
||||
);
|
||||
frame_support::ensure!(
|
||||
NextWindowToSubmit::<T>::get() == 0,
|
||||
NextWindowToSubmit::<T>::get().is_none(),
|
||||
"NextWindowToSubmit was not reset",
|
||||
);
|
||||
frame_support::ensure!(
|
||||
|
|
@ -268,10 +274,6 @@ pub mod pallet {
|
|||
UnsentRewardTail::<T>::get() == 0,
|
||||
"UnsentRewardTail was not reset",
|
||||
);
|
||||
frame_support::ensure!(
|
||||
UnsentRewardWindow::<T>::iter().next().is_none(),
|
||||
"UnsentRewardWindow entries were not cleared",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -429,7 +431,7 @@ pub mod pallet {
|
|||
|
||||
/// Pointer to the next window start to submit.
|
||||
#[pallet::storage]
|
||||
pub type NextWindowToSubmit<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
pub type NextWindowToSubmit<T: Config> = StorageValue<_, u32, OptionQuery>;
|
||||
|
||||
/// Era at which window mode becomes active after a live upgrade.
|
||||
/// `0` means window mode is active immediately (fresh chains/tests).
|
||||
|
|
@ -495,19 +497,6 @@ pub mod pallet {
|
|||
WindowOperatorPoints::<T>::remove(window_start);
|
||||
}
|
||||
|
||||
fn earliest_window_with_state() -> Option<u32> {
|
||||
let mut earliest = None;
|
||||
|
||||
for window in WindowOperatorPoints::<T>::iter_keys() {
|
||||
earliest = Some(earliest.map_or(window, |current: u32| current.min(window)));
|
||||
}
|
||||
for window in WindowInflationAmount::<T>::iter_keys() {
|
||||
earliest = Some(earliest.map_or(window, |current: u32| current.min(window)));
|
||||
}
|
||||
|
||||
earliest
|
||||
}
|
||||
|
||||
fn window_rewards_info(
|
||||
window_start: u32,
|
||||
window_index: u32,
|
||||
|
|
@ -538,12 +527,21 @@ pub mod pallet {
|
|||
genesis: u32,
|
||||
interval: u32,
|
||||
) {
|
||||
if inflation_amount == 0 || era_end <= era_start {
|
||||
if era_end <= era_start {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut window_start = Self::window_start_for(era_start, genesis, interval);
|
||||
|
||||
if NextWindowToSubmit::<T>::get().is_none() {
|
||||
NextWindowToSubmit::<T>::put(window_start);
|
||||
}
|
||||
|
||||
if inflation_amount == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let era_duration = era_end.saturating_sub(era_start).max(1);
|
||||
let mut window_start = Self::window_start_for(era_start, genesis, interval);
|
||||
let mut allocated = 0u128;
|
||||
let mut last_window = None;
|
||||
|
||||
|
|
@ -579,85 +577,85 @@ pub mod pallet {
|
|||
}
|
||||
}
|
||||
|
||||
fn process_closed_windows(now: u32, genesis: u32, interval: u32) {
|
||||
let mut next_window = NextWindowToSubmit::<T>::get();
|
||||
pub(crate) fn process_closed_windows(now: u32, genesis: u32, interval: u32) -> Weight {
|
||||
let Some(mut next_window) = NextWindowToSubmit::<T>::get() else {
|
||||
return T::WeightInfo::process_closed_windows_idle();
|
||||
};
|
||||
|
||||
if next_window == 0 {
|
||||
next_window = Self::earliest_window_with_state().unwrap_or_else(|| {
|
||||
Self::window_start_for(now.saturating_sub(interval), genesis, interval)
|
||||
});
|
||||
if next_window.saturating_add(interval) > now {
|
||||
return T::WeightInfo::process_closed_windows_idle();
|
||||
}
|
||||
|
||||
while next_window.saturating_add(interval) <= now {
|
||||
let inflation_amount = WindowInflationAmount::<T>::get(next_window);
|
||||
let operator_points = WindowOperatorPoints::<T>::get(next_window);
|
||||
let total_points: u128 = operator_points.values().map(|p| *p as u128).sum();
|
||||
let window_index = Self::window_index_for(next_window, genesis, interval);
|
||||
let inflation_amount = WindowInflationAmount::<T>::get(next_window);
|
||||
let operator_points = WindowOperatorPoints::<T>::get(next_window);
|
||||
let total_points: u128 = operator_points.values().map(|p| *p as u128).sum();
|
||||
let window_index = Self::window_index_for(next_window, genesis, interval);
|
||||
|
||||
if total_points == 0 || inflation_amount == 0 {
|
||||
Self::clear_window(next_window);
|
||||
Self::deposit_event(Event::RewardsWindowSkipped {
|
||||
if total_points == 0 || inflation_amount == 0 {
|
||||
Self::clear_window(next_window);
|
||||
Self::deposit_event(Event::RewardsWindowSkipped {
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
});
|
||||
next_window = next_window.saturating_add(interval);
|
||||
NextWindowToSubmit::<T>::put(next_window);
|
||||
return T::WeightInfo::process_closed_windows_processed();
|
||||
}
|
||||
|
||||
let utils = RewardsPeriodUtils {
|
||||
period_index: window_index,
|
||||
period_start: next_window,
|
||||
duration: interval,
|
||||
total_points,
|
||||
individual_points: operator_points.into_iter().collect(),
|
||||
inflation_amount,
|
||||
};
|
||||
|
||||
match Self::send_rewards_message(&utils) {
|
||||
Some(message_id) => {
|
||||
Self::deposit_event(Event::RewardsWindowSubmitted {
|
||||
message_id,
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
total_points,
|
||||
inflation_amount,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
Self::deposit_event(Event::RewardsWindowSubmissionFailed {
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
});
|
||||
next_window = next_window.saturating_add(interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
let utils = RewardsPeriodUtils {
|
||||
period_index: window_index,
|
||||
period_start: next_window,
|
||||
duration: interval,
|
||||
total_points,
|
||||
individual_points: operator_points.into_iter().collect(),
|
||||
inflation_amount,
|
||||
};
|
||||
let queued_window = QueuedRewardsWindow {
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
duration: interval,
|
||||
};
|
||||
|
||||
match Self::send_rewards_message(&utils) {
|
||||
Some(message_id) => {
|
||||
Self::deposit_event(Event::RewardsWindowSubmitted {
|
||||
message_id,
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
total_points,
|
||||
inflation_amount,
|
||||
});
|
||||
if Self::unsent_queue_push(queued_window) {
|
||||
next_window = next_window.saturating_add(interval);
|
||||
NextWindowToSubmit::<T>::put(next_window);
|
||||
return T::WeightInfo::process_closed_windows_processed();
|
||||
}
|
||||
None => {
|
||||
Self::deposit_event(Event::RewardsWindowSubmissionFailed {
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
});
|
||||
|
||||
let queued_window = QueuedRewardsWindow {
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
duration: interval,
|
||||
};
|
||||
|
||||
if Self::unsent_queue_push(queued_window) {
|
||||
next_window = next_window.saturating_add(interval);
|
||||
continue;
|
||||
}
|
||||
|
||||
log::error!(
|
||||
target: "ext_validators_rewards",
|
||||
"Unsent reward queue full, cannot enqueue window {}",
|
||||
next_window,
|
||||
);
|
||||
Self::deposit_event(Event::UnsentWindowQueueFull {
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
});
|
||||
break;
|
||||
}
|
||||
log::error!(
|
||||
target: "ext_validators_rewards",
|
||||
"Unsent reward queue full, cannot enqueue window {}",
|
||||
next_window,
|
||||
);
|
||||
Self::deposit_event(Event::UnsentWindowQueueFull {
|
||||
window_start: next_window,
|
||||
window_index,
|
||||
});
|
||||
return T::WeightInfo::process_closed_windows_processed();
|
||||
}
|
||||
|
||||
Self::clear_window(next_window);
|
||||
next_window = next_window.saturating_add(interval);
|
||||
}
|
||||
|
||||
Self::clear_window(next_window);
|
||||
next_window = next_window.saturating_add(interval);
|
||||
NextWindowToSubmit::<T>::put(next_window);
|
||||
T::WeightInfo::process_closed_windows_processed()
|
||||
}
|
||||
|
||||
/// Reward validators. Does not check if the validators are valid, caller needs to make sure of that.
|
||||
|
|
@ -794,10 +792,7 @@ pub mod pallet {
|
|||
/// Process at most one unsent reward window per block.
|
||||
/// On failure the head pointer advances to the next entry so a single
|
||||
/// stuck window does not block retries for subsequent windows.
|
||||
pub(crate) fn process_unsent_reward_eras() -> Weight {
|
||||
let head = UnsentRewardHead::<T>::get();
|
||||
let tail = UnsentRewardTail::<T>::get();
|
||||
|
||||
fn process_unsent_reward_eras_from(head: u32, tail: u32) -> Weight {
|
||||
if head == tail {
|
||||
return T::WeightInfo::process_unsent_reward_eras_empty();
|
||||
}
|
||||
|
|
@ -1284,8 +1279,6 @@ pub mod pallet {
|
|||
T::WeightInfo::on_era_end(),
|
||||
DispatchClass::Mandatory,
|
||||
);
|
||||
|
||||
Self::process_closed_windows(now, genesis, interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ use {
|
|||
sp_core::H160,
|
||||
};
|
||||
|
||||
fn run_rewards_initialize() {
|
||||
let next_block = System::block_number() + 1;
|
||||
System::set_block_number(next_block);
|
||||
Timestamp::set_timestamp(Timestamp::get() + BLOCK_TIME);
|
||||
let _ = <crate::Pallet<Test> as Hooks<u64>>::on_initialize(next_block);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_setup_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
|
|
@ -82,22 +89,13 @@ fn runtime_upgrade_schedules_window_mode_for_next_era_and_resets_retry_state() {
|
|||
StorageVersion::new(1).put::<crate::Pallet<Test>>();
|
||||
crate::WindowModeStartsAtEra::<Test>::put(0);
|
||||
crate::NextWindowToSubmit::<Test>::put(123);
|
||||
crate::UnsentRewardWindow::<Test>::insert(
|
||||
5,
|
||||
crate::QueuedRewardsWindow {
|
||||
window_start: 100,
|
||||
window_index: 10,
|
||||
duration: 10,
|
||||
},
|
||||
);
|
||||
crate::UnsentRewardHead::<Test>::put(5);
|
||||
crate::UnsentRewardTail::<Test>::put(9);
|
||||
|
||||
let _ = <crate::Pallet<Test> as Hooks<u64>>::on_runtime_upgrade();
|
||||
|
||||
assert_eq!(crate::WindowModeStartsAtEra::<Test>::get(), 8);
|
||||
assert_eq!(crate::NextWindowToSubmit::<Test>::get(), 0);
|
||||
assert_eq!(crate::UnsentRewardWindow::<Test>::iter().count(), 0);
|
||||
assert_eq!(crate::NextWindowToSubmit::<Test>::get(), None);
|
||||
assert_eq!(crate::UnsentRewardHead::<Test>::get(), 0);
|
||||
assert_eq!(crate::UnsentRewardTail::<Test>::get(), 0);
|
||||
assert_eq!(
|
||||
|
|
@ -188,7 +186,7 @@ fn transition_era_on_era_end_is_skipped_before_cutover() {
|
|||
"transition era should not emit window-processing events before cutover"
|
||||
);
|
||||
assert_eq!(crate::WindowInflationAmount::<Test>::iter().count(), 0);
|
||||
assert_eq!(crate::NextWindowToSubmit::<Test>::get(), 0);
|
||||
assert_eq!(crate::NextWindowToSubmit::<Test>::get(), None);
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +244,8 @@ fn window_mode_submits_closed_windows_and_advances_pointer() {
|
|||
|
||||
run_to_block(15); // now = 45s, window [30,40) is closed
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
run_rewards_initialize();
|
||||
|
||||
// Inflation is 42 (default). After 20% treasury split: rewards_amount = 34.
|
||||
// Era spans 20s to 45s = 25s. Window [20,30) overlap = 10s, [30,40) overlap = 10s,
|
||||
|
|
@ -267,7 +267,7 @@ fn window_mode_submits_closed_windows_and_advances_pointer() {
|
|||
|
||||
assert_eq!(
|
||||
pallet_external_validators_rewards::NextWindowToSubmit::<Test>::get(),
|
||||
40
|
||||
Some(40)
|
||||
);
|
||||
assert!(
|
||||
pallet_external_validators_rewards::WindowOperatorPoints::<Test>::get(30).is_empty()
|
||||
|
|
@ -311,7 +311,7 @@ fn window_mode_era_inflation_split_across_multiple_windows() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
}
|
||||
|
||||
// on_era_end allocates inflation across windows then processes closed ones.
|
||||
// on_era_end allocates inflation across windows; on_initialize submits closed ones.
|
||||
// Era spans from 5s to now=35s (30s duration).
|
||||
// Window overlaps:
|
||||
// [0,10): overlap [5,10) = 5s → 5/30 of inflation
|
||||
|
|
@ -319,6 +319,9 @@ fn window_mode_era_inflation_split_across_multiple_windows() {
|
|||
// [20,30): overlap [20,30) = 10s → 10/30 of inflation
|
||||
// [30,40): overlap [30,35) = 5s → 5/30 of inflation (current, not closed)
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
run_rewards_initialize();
|
||||
run_rewards_initialize();
|
||||
|
||||
// on_era_end allocates mint_result.rewards_amount (post-treasury split) to windows.
|
||||
// Inflation = 42, treasury = 20% of 42 = 8, rewards_amount = 34.
|
||||
|
|
@ -327,7 +330,7 @@ fn window_mode_era_inflation_split_across_multiple_windows() {
|
|||
let treasury_amount = InflationTreasuryProportion::get().mul_floor(inflation);
|
||||
let rewards_amount = inflation - treasury_amount;
|
||||
|
||||
// Closed windows are cleared by process_closed_windows, so verify via events.
|
||||
// Closed windows are cleared one per block, so verify via events after draining them.
|
||||
let events = System::events();
|
||||
|
||||
let expected_w0 = rewards_amount * 5 / 30;
|
||||
|
|
@ -431,6 +434,9 @@ fn window_mode_submits_multiple_closed_windows_in_single_era_end() {
|
|||
// Window [60,70) is current.
|
||||
run_to_block(35); // now = 65s
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
run_rewards_initialize();
|
||||
run_rewards_initialize();
|
||||
|
||||
let events = System::events();
|
||||
|
||||
|
|
@ -475,7 +481,7 @@ fn window_mode_submits_multiple_closed_windows_in_single_era_end() {
|
|||
// NextWindowToSubmit should advance past all closed windows
|
||||
assert_eq!(
|
||||
pallet_external_validators_rewards::NextWindowToSubmit::<Test>::get(),
|
||||
60
|
||||
Some(60)
|
||||
);
|
||||
|
||||
// All closed windows should have their storage cleared
|
||||
|
|
@ -522,6 +528,8 @@ fn window_mode_delivery_failure_emits_submission_failed_event() {
|
|||
// Advance to block 15 (now=45s), window [30,40) is closed
|
||||
run_to_block(15); // now = 45s
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
run_rewards_initialize();
|
||||
|
||||
let events = System::events();
|
||||
|
||||
|
|
@ -563,7 +571,7 @@ fn window_mode_delivery_failure_emits_submission_failed_event() {
|
|||
// NextWindowToSubmit should still advance
|
||||
assert_eq!(
|
||||
pallet_external_validators_rewards::NextWindowToSubmit::<Test>::get(),
|
||||
40
|
||||
Some(40)
|
||||
);
|
||||
assert_eq!(unsent_len(), 1, "failed window should be queued for retry");
|
||||
})
|
||||
|
|
@ -660,6 +668,7 @@ fn test_on_era_end() {
|
|||
|
||||
run_to_block(10);
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
|
||||
let inflation =
|
||||
<Test as pallet_external_validators_rewards::Config>::EraInflationProvider::get();
|
||||
|
|
@ -4285,6 +4294,7 @@ fn send_failure_emits_window_submission_failed_and_queues_window() {
|
|||
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
|
||||
// Verify window submission failure event
|
||||
let window_index = window_start.saturating_sub(genesis) / window_duration;
|
||||
|
|
@ -4333,6 +4343,7 @@ fn failed_window_is_retried_on_initialize() {
|
|||
|
||||
Timestamp::set_timestamp(((window_start + duration + 1) as u64) * 1000);
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
|
||||
assert_eq!(unsent_len(), 1);
|
||||
|
||||
|
|
@ -4340,7 +4351,7 @@ fn failed_window_is_retried_on_initialize() {
|
|||
|
||||
// Sending should succeed (send_message_fails is false by default)
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
|
||||
// Queue should be empty
|
||||
assert!(unsent_is_empty());
|
||||
|
|
@ -4381,7 +4392,7 @@ fn on_initialize_retries_and_succeeds() {
|
|||
push_unsent(window_start, window_index, duration);
|
||||
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
|
||||
assert!(unsent_is_empty());
|
||||
|
||||
|
|
@ -4414,18 +4425,18 @@ fn on_initialize_moves_failed_entry_to_back() {
|
|||
push_unsent(40, 4, duration);
|
||||
|
||||
// First call: tries window 30, fails, moves it to the back of the queue
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
assert_eq!(unsent_len(), 2);
|
||||
|
||||
// Second call: tries window 40, fails, moves it to the back
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
assert_eq!(unsent_len(), 2);
|
||||
|
||||
Mock::mutate(|mock| mock.send_message_fails = false);
|
||||
|
||||
// Third call: window 30 (now at front again), succeeds
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
assert_eq!(unsent_len(), 1);
|
||||
System::assert_has_event(RuntimeEvent::ExternalValidatorsRewards(
|
||||
crate::Event::RewardsWindowRetried {
|
||||
|
|
@ -4438,7 +4449,7 @@ fn on_initialize_moves_failed_entry_to_back() {
|
|||
));
|
||||
|
||||
// Fourth call: window 40 succeeds
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
assert!(unsent_is_empty());
|
||||
})
|
||||
}
|
||||
|
|
@ -4452,7 +4463,7 @@ fn on_initialize_removes_expired_window() {
|
|||
push_unsent(999, 99, duration);
|
||||
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
|
||||
assert!(unsent_is_empty());
|
||||
|
||||
|
|
@ -4471,7 +4482,7 @@ fn on_initialize_noop_when_queue_empty() {
|
|||
run_to_block(1);
|
||||
System::reset_events();
|
||||
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
|
||||
// No events should be emitted
|
||||
let events = System::events();
|
||||
|
|
@ -4495,7 +4506,7 @@ fn on_initialize_processes_only_head() {
|
|||
push_unsent(40, 4, duration);
|
||||
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
|
||||
assert_eq!(unsent_len(), 1);
|
||||
})
|
||||
|
|
@ -4602,10 +4613,11 @@ fn send_failure_retains_window_and_advances_pointer() {
|
|||
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::on_era_end(1);
|
||||
run_rewards_initialize();
|
||||
|
||||
let next_window = crate::NextWindowToSubmit::<Test>::get();
|
||||
assert!(
|
||||
next_window > window_start,
|
||||
next_window == Some(window_start + window_duration),
|
||||
"NextWindowToSubmit should advance past the failed window"
|
||||
);
|
||||
|
||||
|
|
@ -4681,15 +4693,15 @@ fn head_of_line_blocking_avoided() {
|
|||
Mock::mutate(|mock| mock.send_message_fails = true);
|
||||
|
||||
// Block 1: tries window 30, fails, advances head → window 40
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
// Block 2: tries window 40, fails, advances head → window 50
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
|
||||
Mock::mutate(|mock| mock.send_message_fails = false);
|
||||
|
||||
// Block 3: tries window 50, succeeds
|
||||
System::reset_events();
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
System::assert_has_event(RuntimeEvent::ExternalValidatorsRewards(
|
||||
crate::Event::RewardsWindowRetried {
|
||||
message_id: Default::default(),
|
||||
|
|
@ -4701,7 +4713,7 @@ fn head_of_line_blocking_avoided() {
|
|||
));
|
||||
|
||||
// Block 4: wraps around to window 30, succeeds
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
System::assert_has_event(RuntimeEvent::ExternalValidatorsRewards(
|
||||
crate::Event::RewardsWindowRetried {
|
||||
message_id: Default::default(),
|
||||
|
|
@ -4713,7 +4725,7 @@ fn head_of_line_blocking_avoided() {
|
|||
));
|
||||
|
||||
// Block 5: window 40 succeeds
|
||||
ExternalValidatorsRewards::process_unsent_reward_eras();
|
||||
run_rewards_initialize();
|
||||
assert!(unsent_is_empty());
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ use core::marker::PhantomData;
|
|||
/// Weight functions needed for pallet_external_validators_rewards.
|
||||
pub trait WeightInfo {
|
||||
fn on_era_end() -> Weight;
|
||||
fn process_closed_windows_idle() -> Weight;
|
||||
fn process_closed_windows_processed() -> Weight;
|
||||
fn process_unsent_reward_eras_empty() -> Weight;
|
||||
fn process_unsent_reward_eras_expired() -> Weight;
|
||||
fn process_unsent_reward_eras_success() -> Weight;
|
||||
|
|
@ -90,6 +92,17 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
|||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
|
||||
fn process_closed_windows_idle() -> Weight {
|
||||
Weight::from_parts(10_000_000, 0)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
}
|
||||
|
||||
fn process_closed_windows_processed() -> Weight {
|
||||
// Use the existing resend success path as an upper bound for a single
|
||||
// closed-window submission/skip attempt.
|
||||
Self::process_unsent_reward_eras_success()
|
||||
}
|
||||
|
||||
fn process_unsent_reward_eras_empty() -> Weight {
|
||||
// 1 read for UnsentRewardEras
|
||||
Weight::from_parts(5_000_000, 0)
|
||||
|
|
@ -149,6 +162,15 @@ impl WeightInfo for () {
|
|||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
|
||||
fn process_closed_windows_idle() -> Weight {
|
||||
Weight::from_parts(10_000_000, 0)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
}
|
||||
|
||||
fn process_closed_windows_processed() -> Weight {
|
||||
Self::process_unsent_reward_eras_success()
|
||||
}
|
||||
|
||||
fn process_unsent_reward_eras_empty() -> Weight {
|
||||
Weight::from_parts(5_000_000, 0)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
|
|
|
|||
Loading…
Reference in a new issue