Skip to content

Commit b47c31f

Browse files
sigurpolgithub-actions[bot]
authored andcommitted
staking-async: handle uninitialized state in try-state checks (#9747)
- Add early return in do_try_state when pallet is uninitialized - Add test for empty state validation Followup of #9721 . Once backported to `2507` and crate is published, should unlock polkadot-fellows/runtimes#904 --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent e2edcbd commit b47c31f

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

prdoc/pr_9747.prdoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
title: 'staking-async: handle uninitialized state in try-state checks'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
- Add early return in do_try_state when pallet is uninitialized
6+
- Add test for empty state validation
7+
crates:
8+
- name: pallet-staking-async
9+
bump: patch

substrate/frame/staking-async/src/pallet/impls.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,12 @@ impl<T: Config> sp_staking::StakingUnchecked for Pallet<T> {
17521752
#[cfg(any(test, feature = "try-runtime"))]
17531753
impl<T: Config> Pallet<T> {
17541754
pub(crate) fn do_try_state(_now: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1755+
// If the pallet is not initialized (both ActiveEra and CurrentEra are None),
1756+
// there's nothing to check, so return early.
1757+
if ActiveEra::<T>::get().is_none() && CurrentEra::<T>::get().is_none() {
1758+
return Ok(());
1759+
}
1760+
17551761
session_rotation::Rotator::<T>::do_try_state()?;
17561762
session_rotation::Eras::<T>::do_try_state()?;
17571763

@@ -1932,6 +1938,7 @@ impl<T: Config> Pallet<T> {
19321938
}
19331939

19341940
/// Invariants:
1941+
/// * ActiveEra is Some.
19351942
/// * For each paged era exposed validator, check if the exposure total is sane (exposure.total
19361943
/// = exposure.own + exposure.own).
19371944
/// * Paged exposures metadata (`ErasStakersOverview`) matches the paged exposures state.
@@ -1942,7 +1949,13 @@ impl<T: Config> Pallet<T> {
19421949
// Sanity check for the paged exposure of the active era.
19431950
let mut exposures: BTreeMap<T::AccountId, PagedExposureMetadata<BalanceOf<T>>> =
19441951
BTreeMap::new();
1945-
let era = ActiveEra::<T>::get().unwrap().index;
1952+
// If the pallet is not initialized, we return immediately from pallet's do_try_state() and
1953+
// we don't call this method. Otherwise, Eras::do_try_state enforces that both ActiveEra
1954+
// and CurrentEra are Some. Thus, we should never hit this error.
1955+
let era = ActiveEra::<T>::get()
1956+
.ok_or(TryRuntimeError::Other("ActiveEra must be set when checking paged exposures"))?
1957+
.index;
1958+
19461959
let accumulator_default = PagedExposureMetadata {
19471960
total: Zero::zero(),
19481961
own: Zero::zero(),

substrate/frame/staking-async/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ mod force_unstake_kill_stash;
4545
mod ledger;
4646
mod payout_stakers;
4747
mod slashing;
48+
mod try_state;
4849

4950
#[test]
5051
fn basic_setup_session_queuing_should_work() {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! Tests for try-state checks.
19+
20+
use super::*;
21+
use frame_support::assert_ok;
22+
23+
#[test]
24+
fn try_state_works_with_uninitialized_pallet() {
25+
sp_io::TestExternalities::default().execute_with(|| {
26+
// Verify the pallet is uninitialized
27+
assert!(ActiveEra::<Test>::get().is_none());
28+
assert!(CurrentEra::<Test>::get().is_none());
29+
assert_eq!(Bonded::<Test>::iter().count(), 0);
30+
assert_eq!(Ledger::<Test>::iter().count(), 0);
31+
assert_eq!(Validators::<Test>::iter().count(), 0);
32+
assert_eq!(Nominators::<Test>::iter().count(), 0);
33+
34+
// Try-state should pass with uninitialized state
35+
assert_ok!(Staking::do_try_state(System::block_number()));
36+
});
37+
}
38+
39+
#[test]
40+
fn try_state_detects_inconsistent_active_current_era() {
41+
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
42+
// Set only ActiveEra (CurrentEra remains None) - this violates the invariant
43+
ActiveEra::<Test>::put(ActiveEraInfo { index: 1, start: None });
44+
CurrentEra::<Test>::kill();
45+
46+
// Try-state should fail due to inconsistent state
47+
assert!(Staking::do_try_state(System::block_number()).is_err());
48+
49+
// Now set only CurrentEra (ActiveEra None) - this also violates the invariant
50+
ActiveEra::<Test>::kill();
51+
CurrentEra::<Test>::put(1);
52+
53+
// Try-state should fail due to inconsistent state
54+
assert!(Staking::do_try_state(System::block_number()).is_err());
55+
56+
// Both None should pass
57+
ActiveEra::<Test>::kill();
58+
CurrentEra::<Test>::kill();
59+
assert_ok!(Staking::do_try_state(System::block_number()));
60+
61+
// Both Some should pass (assuming other invariants are met)
62+
ActiveEra::<Test>::put(ActiveEraInfo { index: 1, start: None });
63+
CurrentEra::<Test>::put(1);
64+
// Need to set up bonded eras for this to pass
65+
use frame_support::BoundedVec;
66+
let bonded_eras: BoundedVec<(u32, u32), _> =
67+
BoundedVec::try_from(vec![(0, 0), (1, 0)]).unwrap();
68+
BondedEras::<Test>::put(bonded_eras);
69+
assert_ok!(Staking::do_try_state(System::block_number()));
70+
});
71+
}

0 commit comments

Comments
 (0)