Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions prdoc/pr_8310.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
title: 'epm: enhance fallback election handling at genesis block'
doc:
- audience: Runtime Dev
description: |-
Prepare the snapshot for fallback elections at block zero, ensuring successful execution even at genesis.
Modify the fallback logic to include a check for the genesis block alongside the existing runtime benchmarks feature.

This change improves the robustness of the election provider by ensuring that fallback elections can be executed from the very first block.
While the solution has the benefit to be limited in scope and not invasive, a better fix would be probably not to rely on the asap() method at all for genesis block but ensure that the session manager correctly calls new_session() or new_session_genesis() respectively.

Fix #8302 as introduced by #8127.
Note also that the regression described in #8302 does NOT affect any running chain, but mostly testing.
crates:
- name: pallet-election-provider-multi-phase
bump: patch
38 changes: 28 additions & 10 deletions substrate/frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1664,8 +1664,14 @@ impl<T: Config> Pallet<T> {
.or_else(|_| {
log!(warn, "No solution queued, falling back to instant fallback.",);

#[cfg(feature = "runtime-benchmarks")]
Self::asap();
// At block zero (genesis) we need to prepare the snapshot just like we do in
// benchmarks. This ensures the fallback can run successfully even at the very
// first block.
if cfg!(feature = "runtime-benchmarks") ||
frame_system::Pallet::<T>::block_number().is_zero()
{
Self::do_asap()
}

let (voters, targets, desired_targets) = if T::Fallback::bother() {
let RoundSnapshot { voters, targets } = Snapshot::<T>::get().ok_or(
Expand Down Expand Up @@ -1709,6 +1715,24 @@ impl<T: Config> Pallet<T> {
let desired_targets = supports.len() as u32;
Self::register_weight(T::WeightInfo::elect_queued(active_voters, desired_targets));
}

/// Prepare snapshot for fallback election. This is normally only called during benchmarking
/// via the public asap() function, but we also call it at genesis block (block 0) to ensure
/// the fallback can run.
///
/// Ideally, we should rely on the session manager to be the solely responsbile for handling
/// genesis block and calling `new_session` or `new_session_genesis` accordingly instead of
/// handling this here.
fn do_asap() {
// prepare our snapshot so we can "hopefully" run a fallback.
if !Snapshot::<T>::exists() {
Self::create_snapshot()
.inspect_err(|e| {
crate::log!(error, "failed to create snapshot while asap-preparing: {:?}", e)
})
.unwrap()
}
}
}

#[cfg(feature = "try-runtime")]
Expand Down Expand Up @@ -1848,16 +1872,10 @@ impl<T: Config> ElectionProvider for Pallet<T> {
}
}

/// Public function only compiled with benchmarks feature
#[cfg(feature = "runtime-benchmarks")]
fn asap() {
// prepare our snapshot so we can "hopefully" run a fallback.
if !Snapshot::<T>::exists() {
Self::create_snapshot()
.inspect_err(|e| {
crate::log!(error, "failed to create snapshot while asap-preparing: {:?}", e)
})
.unwrap()
}
Self::do_asap()
}
}

Expand Down
18 changes: 18 additions & 0 deletions substrate/frame/staking-async/ah-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,16 @@ pub mod pallet {
<Self as pallet_session::SessionManager<_>>::start_session(start_index)
}

fn new_session_genesis(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] we also need to cleanup comment at line 530 since we now implement new_session_genesis :)

new_index: SessionIndex,
) -> Option<Vec<(T::AccountId, sp_staking::Exposure<T::AccountId, BalanceOf<T>>)>> {
if Mode::<T>::get() == OperatingMode::Passive {
T::Fallback::new_session_genesis(new_index)
} else {
None
}
}

fn end_session(end_index: SessionIndex) {
<Self as pallet_session::SessionManager<_>>::end_session(end_index)
}
Expand All @@ -555,6 +565,14 @@ pub mod pallet {
}
}

fn new_session_genesis(new_index: SessionIndex) -> Option<Vec<T::AccountId>> {
if Mode::<T>::get() == OperatingMode::Passive {
T::Fallback::new_session_genesis(new_index)
} else {
None
}
}

fn end_session(session_index: u32) {
match Mode::<T>::get() {
OperatingMode::Passive => T::Fallback::end_session(session_index),
Expand Down
Loading