Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ impl pallet_staking_async_rc_client::Config for Runtime {
type ValidatorSetExportSession = ConstU32<4>;
type RelayChainSessionKeys = RelayChainSessionKeys;
type Balance = Balance;
type MinSetKeysBond = ConstU128<{ 10 * UNITS }>;
// | Key | Crypto | Public Key | Signature |
// |---------------------|---------|------------|-----------|
// | grandpa | Ed25519 | 32 bytes | 64 bytes |
Expand Down
16 changes: 16 additions & 0 deletions prdoc/pr_11168.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
title: Add MinSetKeysBond check in rc_client::set_keys
doc:
- audience: Runtime Dev
description: |-
Add a configurable MinSetKeysBond threshold (hardcoded to 10 WND on asset-hub-westend) that rejects set_keys when active bond
is insufficient. Set to 0 to disable.
crates:
- name: asset-hub-westend-runtime
bump: minor
validate: false
- name: pallet-staking-async-rc-client
bump: minor
validate: false
- name: pallet-staking-async
bump: minor
validate: false
5 changes: 5 additions & 0 deletions substrate/frame/staking-async/ahm-test/src/ah/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ frame::deps::sp_runtime::impl_opaque_keys! {
}
}

parameter_types! {
pub static MinSetKeysBond: Balance = 0;
}

impl pallet_staking_async_rc_client::Config for Runtime {
type AHStakingInterface = Staking;
type SendToRelayChain = DeliverToRelay;
Expand All @@ -482,6 +486,7 @@ impl pallet_staking_async_rc_client::Config for Runtime {
type RelayChainSessionKeys = RCSessionKeys;
type Balance = Balance;
type MaxSessionKeysLength = ConstU32<256>;
type MinSetKeysBond = MinSetKeysBond;
type WeightInfo = ();
}

Expand Down
51 changes: 50 additions & 1 deletion substrate/frame/staking-async/ahm-test/src/ah/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@ fn era_lifecycle_test() {
mod session_keys {
use super::*;
use crate::ah::mock::{
Balances, LocalQueue, OutgoingMessages, ProxyType, PurgeKeysExecutionCost,
Balances, LocalQueue, MinSetKeysBond, OutgoingMessages, ProxyType, PurgeKeysExecutionCost,
SetKeysExecutionCost,
};
use frame_support::{assert_noop, BoundedVec};
Expand Down Expand Up @@ -1458,6 +1458,55 @@ mod session_keys {
});
}

#[test]
fn set_keys_insufficient_bond() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let validator: AccountId = 1;
let keys = make_session_keys();

// GIVEN: MinSetKeysBond is set higher than the validator's active bond (100)
MinSetKeysBond::set(101);

// WHEN: Validator tries to set keys
// THEN: InsufficientBond error is returned
assert_noop!(
rc_client::Pallet::<T>::set_keys(
RuntimeOrigin::signed(validator),
keys.clone(),
None,
),
rc_client::Error::<T>::InsufficientBond
);

// GIVEN: MinSetKeysBond equals the validator's active bond
MinSetKeysBond::set(100);

// WHEN: Validator sets keys with exact bond
// THEN: Succeeds
assert_ok!(rc_client::Pallet::<T>::set_keys(
RuntimeOrigin::signed(validator),
keys.clone(),
None,
));
});
}

#[test]
fn set_keys_min_bond_zero_disables_check() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// GIVEN: MinSetKeysBond is 0 (default in tests) — check is disabled
let validator: AccountId = 1;
let keys = make_session_keys();

// WHEN/THEN: set_keys succeeds regardless of bond amount
assert_ok!(rc_client::Pallet::<T>::set_keys(
RuntimeOrigin::signed(validator),
keys,
None,
));
});
}

/// End-to-end test: set keys on AssetHub, verify on RelayChain, then purge and verify.
#[test]
fn set_and_purge_keys_e2e() {
Expand Down
23 changes: 22 additions & 1 deletion substrate/frame/staking-async/rc-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,8 @@ where
pub trait AHStakingInterface {
/// The validator account id type.
type AccountId;
/// The balance type.
type Balance: BalanceTrait;
/// Maximum number of validators that the staking system may have.
type MaxValidatorSet: Get<u32>;

Expand Down Expand Up @@ -864,6 +866,9 @@ pub trait AHStakingInterface {
///
/// Returns true if the account has called `validate()` and is in the `Validators` storage.
fn is_validator(who: &Self::AccountId) -> bool;

/// Returns the active bonded amount for a stash, or `None` if not bonded.
fn active_stake(who: &Self::AccountId) -> Option<Self::Balance>;
}

/// The communication trait of `pallet-staking-async` -> `pallet-staking-async-rc-client`.
Expand Down Expand Up @@ -1006,7 +1011,10 @@ pub mod pallet {
type RelayChainOrigin: EnsureOrigin<Self::RuntimeOrigin>;

/// Our communication handle to the local staking pallet.
type AHStakingInterface: AHStakingInterface<AccountId = Self::AccountId>;
type AHStakingInterface: AHStakingInterface<
AccountId = Self::AccountId,
Balance = Self::Balance,
>;

/// Our communication handle to the relay chain.
type SendToRelayChain: SendToRelayChain<
Expand Down Expand Up @@ -1058,6 +1066,10 @@ pub mod pallet {
#[pallet::constant]
type MaxSessionKeysLength: Get<u32>;

/// Minimum active bond required to call `set_keys`. Set to 0 to disable.
#[pallet::constant]
type MinSetKeysBond: Get<BalanceOf<Self>>;

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
Expand All @@ -1076,6 +1088,8 @@ pub mod pallet {
InvalidKeys,
/// Delivery fees exceeded the specified maximum.
FeesExceededMax,
/// The stash's active bond is below `MinSetKeysBond`.
InsufficientBond,
}

#[pallet::event]
Expand Down Expand Up @@ -1293,6 +1307,13 @@ pub mod pallet {
// Only registered validators can set session keys
ensure!(T::AHStakingInterface::is_validator(&stash), Error::<T>::NotValidator);

let min_bond = T::MinSetKeysBond::get();
if !min_bond.is_zero() {
let active = T::AHStakingInterface::active_stake(&stash)
.ok_or(Error::<T>::InsufficientBond)?;
ensure!(active >= min_bond, Error::<T>::InsufficientBond);
}

// Validate keys: decode as RelayChainSessionKeys to ensure correct format
let _ = T::RelayChainSessionKeys::decode(&mut &keys[..])
.map_err(|_| Error::<T>::InvalidKeys)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ impl pallet_staking_async_rc_client::Config for Runtime {
type RelayChainSessionKeys = RelayChainSessionKeys;
type Balance = Balance;
type MaxSessionKeysLength = ConstU32<256>;
type MinSetKeysBond = ConstU128<{ 10_000 * UNITS }>;
type WeightInfo = ();
}

Expand Down
5 changes: 5 additions & 0 deletions substrate/frame/staking-async/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {

impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
type AccountId = T::AccountId;
type Balance = BalanceOf<T>;
type MaxValidatorSet = T::MaxValidatorSet;

/// When we receive a session report from the relay chain, it kicks off the next session.
Expand Down Expand Up @@ -1351,6 +1352,10 @@ impl<T: Config> rc_client::AHStakingInterface for Pallet<T> {
fn is_validator(who: &Self::AccountId) -> bool {
Validators::<T>::contains_key(who)
}

fn active_stake(who: &Self::AccountId) -> Option<BalanceOf<T>> {
Self::ledger(StakingAccount::Stash(who.clone())).ok().map(|l| l.active)
}
}

impl<T: Config> ScoreProvider<T::AccountId> for Pallet<T> {
Expand Down
Loading