Skip to content

Commit 2398f57

Browse files
pallet-session: track consumer refs and release deposits for externally set keys (#11197)
Tracks whether session keys were set externally (via `SessionInterface`, e.g. from AH) or locally. Transitions between the two paths correctly manage the key deposit and consumer ref in both directions. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> (cherry picked from commit b965285)
1 parent f198330 commit 2398f57

File tree

3 files changed

+182
-5
lines changed

3 files changed

+182
-5
lines changed

prdoc/pr_11197.prdoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
title: 'pallet-session: track consumer refs and release deposits for externally set keys'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
Tracks whether session keys were set externally (via `SessionInterface`, e.g. from AH) or locally. Transitions between the two paths correctly manage the key deposit and consumer ref in both directions.
6+
crates:
7+
- name: pallet-session
8+
bump: minor

substrate/frame/session/src/lib.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,14 @@ pub mod pallet {
626626
pub type KeyOwner<T: Config> =
627627
StorageMap<_, Twox64Concat, (KeyTypeId, Vec<u8>), T::ValidatorId, OptionQuery>;
628628

629+
/// Accounts whose keys were set via `SessionInterface` (external path) without
630+
/// incrementing the consumer reference or placing a key deposit. `do_purge_keys`
631+
/// only decrements consumers for accounts that were registered through the local
632+
/// session pallet.
633+
#[pallet::storage]
634+
pub type ExternallySetKeys<T: Config> =
635+
StorageMap<_, Twox64Concat, T::AccountId, (), OptionQuery>;
636+
629637
#[pallet::event]
630638
#[pallet::generate_deposit(pub(super) fn deposit_event)]
631639
pub enum Event<T: Config> {
@@ -920,9 +928,12 @@ impl<T: Config> Pallet<T> {
920928

921929
let old_keys = Self::inner_set_keys(&who, keys)?;
922930

923-
// Place deposit on hold if this is a new registration (i.e. old_keys is None).
924-
// The hold call itself will return an error if funds are insufficient.
925-
if old_keys.is_none() {
931+
// Place deposit and increment consumer if this is a new local registration,
932+
// or if transitioning from external to local management.
933+
// We also clear `ExternallySetKeys` if set.
934+
let needs_local_setup =
935+
old_keys.is_none() || ExternallySetKeys::<T>::take(account).is_some();
936+
if needs_local_setup {
926937
let deposit = T::KeyDeposit::get();
927938
if !deposit.is_zero() {
928939
T::Currency::hold(&HoldReason::Keys.into(), account, deposit)?;
@@ -996,7 +1007,10 @@ impl<T: Config> Pallet<T> {
9961007
frame_support::traits::tokens::Precision::BestEffort,
9971008
);
9981009

999-
frame_system::Pallet::<T>::dec_consumers(account);
1010+
if ExternallySetKeys::<T>::take(account).is_none() {
1011+
// Consumer was incremented locally via `do_set_keys`, so decrement it.
1012+
frame_system::Pallet::<T>::dec_consumers(account);
1013+
}
10001014

10011015
Ok(())
10021016
}
@@ -1196,7 +1210,17 @@ impl<T: Config + historical::Config> SessionInterface for Pallet<T> {
11961210
fn set_keys(account: &Self::AccountId, keys: Self::Keys) -> DispatchResult {
11971211
let who = T::ValidatorIdOf::convert(account.clone())
11981212
.ok_or(Error::<T>::NoAssociatedValidatorId)?;
1199-
Self::inner_set_keys(&who, keys)?;
1213+
let old_keys = Self::inner_set_keys(&who, keys)?;
1214+
// Transitioning from local to external: clean up deposit and consumer ref.
1215+
if old_keys.is_some() && !ExternallySetKeys::<T>::contains_key(account) {
1216+
let _ = T::Currency::release_all(
1217+
&HoldReason::Keys.into(),
1218+
account,
1219+
frame_support::traits::tokens::Precision::BestEffort,
1220+
);
1221+
frame_system::Pallet::<T>::dec_consumers(account);
1222+
}
1223+
ExternallySetKeys::<T>::insert(account, ());
12001224
Ok(())
12011225
}
12021226

@@ -1209,6 +1233,14 @@ impl<T: Config + historical::Config> SessionInterface for Pallet<T> {
12091233
let key_data = old_keys.get_raw(*id);
12101234
Self::clear_key_owner(*id, key_data);
12111235
}
1236+
let _ = T::Currency::release_all(
1237+
&HoldReason::Keys.into(),
1238+
account,
1239+
frame_support::traits::tokens::Precision::BestEffort,
1240+
);
1241+
if ExternallySetKeys::<T>::take(account).is_none() {
1242+
frame_system::Pallet::<T>::dec_consumers(account);
1243+
}
12121244
Ok(())
12131245
}
12141246

substrate/frame/session/src/tests.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,143 @@ fn existing_validators_without_hold_are_except() {
627627
});
628628
}
629629

630+
#[cfg(feature = "historical")]
631+
mod externally_set_keys_tracking {
632+
use super::*;
633+
634+
const ACCOUNT: u64 = 1000;
635+
636+
fn setup_account() {
637+
frame_system::Pallet::<Test>::inc_providers(&ACCOUNT);
638+
ValidatorAccounts::mutate(|m| {
639+
m.insert(ACCOUNT, ACCOUNT);
640+
});
641+
}
642+
643+
fn set_local(key: u64) {
644+
let keys = UintAuthorityId(key).into();
645+
let proof = create_set_keys_proof(ACCOUNT, &UintAuthorityId(key));
646+
assert_ok!(Session::set_keys(RuntimeOrigin::signed(ACCOUNT), keys, proof));
647+
}
648+
649+
fn set_remote(key: u64) {
650+
<Session as SessionInterface>::set_keys(&ACCOUNT, UintAuthorityId(key).into()).unwrap();
651+
}
652+
653+
fn purge_local() {
654+
assert_ok!(Session::purge_keys(RuntimeOrigin::signed(ACCOUNT)));
655+
}
656+
657+
fn purge_remote() {
658+
<Session as SessionInterface>::purge_keys(&ACCOUNT).unwrap();
659+
}
660+
661+
fn assert_local_state(consumers_before: u32) {
662+
assert!(!ExternallySetKeys::<Test>::contains_key(&ACCOUNT));
663+
// +1 from session's inc_consumers, +1 from pallet-balances hold.
664+
assert_eq!(System::consumers(&ACCOUNT), consumers_before + 2);
665+
assert_eq!(session_hold(ACCOUNT), KeyDeposit::get());
666+
}
667+
668+
fn assert_remote_state(consumers_before: u32) {
669+
assert!(ExternallySetKeys::<Test>::contains_key(&ACCOUNT));
670+
assert_eq!(System::consumers(&ACCOUNT), consumers_before);
671+
assert_eq!(session_hold(ACCOUNT), 0);
672+
}
673+
674+
fn assert_clean_state(consumers_before: u32) {
675+
assert!(!ExternallySetKeys::<Test>::contains_key(&ACCOUNT));
676+
assert_eq!(System::consumers(&ACCOUNT), consumers_before);
677+
assert_eq!(session_hold(ACCOUNT), 0);
678+
}
679+
680+
#[test]
681+
fn set_local_purge_local() {
682+
new_test_ext().execute_with(|| {
683+
setup_account();
684+
let consumers_before = System::consumers(&ACCOUNT);
685+
686+
set_local(ACCOUNT);
687+
assert_local_state(consumers_before);
688+
689+
purge_local();
690+
assert_clean_state(consumers_before);
691+
});
692+
}
693+
694+
#[test]
695+
fn set_local_purge_remote() {
696+
new_test_ext().execute_with(|| {
697+
setup_account();
698+
let consumers_before = System::consumers(&ACCOUNT);
699+
700+
set_local(ACCOUNT);
701+
assert_local_state(consumers_before);
702+
703+
purge_remote();
704+
assert_clean_state(consumers_before);
705+
});
706+
}
707+
708+
#[test]
709+
fn set_remote_purge_local() {
710+
new_test_ext().execute_with(|| {
711+
setup_account();
712+
let consumers_before = System::consumers(&ACCOUNT);
713+
714+
set_remote(ACCOUNT);
715+
assert_remote_state(consumers_before);
716+
717+
purge_local();
718+
assert_clean_state(consumers_before);
719+
});
720+
}
721+
722+
#[test]
723+
fn set_remote_purge_remote() {
724+
new_test_ext().execute_with(|| {
725+
setup_account();
726+
let consumers_before = System::consumers(&ACCOUNT);
727+
728+
set_remote(ACCOUNT);
729+
assert_remote_state(consumers_before);
730+
731+
purge_remote();
732+
assert_clean_state(consumers_before);
733+
});
734+
}
735+
736+
#[test]
737+
fn set_local_to_remote() {
738+
new_test_ext().execute_with(|| {
739+
setup_account();
740+
let consumers_before = System::consumers(&ACCOUNT);
741+
742+
set_local(ACCOUNT);
743+
assert_local_state(consumers_before);
744+
745+
// Transition to remote: deposit released, consumer decremented.
746+
set_remote(70);
747+
assert_remote_state(consumers_before);
748+
});
749+
}
750+
751+
#[test]
752+
fn set_remote_to_local() {
753+
new_test_ext().execute_with(|| {
754+
setup_account();
755+
let consumers_before = System::consumers(&ACCOUNT);
756+
757+
set_remote(ACCOUNT);
758+
assert_remote_state(consumers_before);
759+
760+
// Transition to local: deposit placed, consumer incremented.
761+
set_local(70);
762+
assert_local_state(consumers_before);
763+
});
764+
}
765+
}
766+
630767
mod disabling_byzantine_threshold {
631768
use super::*;
632769
use crate::disabling::{DisablingStrategy, UpToLimitDisablingStrategy};

0 commit comments

Comments
 (0)