Skip to content
Closed
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
4 changes: 2 additions & 2 deletions polkadot/runtime/test-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@ impl pallet_session::Config for Runtime {
}

impl pallet_session::historical::Config for Runtime {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Runtime>;
}

pallet_staking_reward_curve::build! {
Expand Down
4 changes: 2 additions & 2 deletions polkadot/runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,8 @@ impl pallet_session::Config for Runtime {
}

impl pallet_session::historical::Config for Runtime {
type FullIdentification = pallet_staking::Exposure<AccountId, Balance>;
type FullIdentificationOf = pallet_staking::ExposureOf<Runtime>;
type FullIdentification = pallet_staking::ExistenceOrLegacyExposure<AccountId, Balance>;
type FullIdentificationOf = pallet_staking::ExistenceOrLegacyExposureOf<Runtime>;
}

pub struct MaybeSignedPhase;
Expand Down
55 changes: 55 additions & 0 deletions prdoc/pr_7936.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
title: 'Replace Validator FullIdentification from `Exposure` to `Existence`'
doc:
- audience: Runtime Dev
description: |-
This introduces a new type in `pallet-staking`, `ExistenceOf`, which replaces `ExposureOf`.
With this change, runtimes can be configured to identify a validator solely by their presence,
rather than using full exposure data.

This is particularly useful when configuring historical sessions, for example:

```rust
impl pallet_session::historical::Config for Runtime {
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Runtime>;
}
```

However, for existing runtimes that depend on the `Exposure` type for `pallet-offences` - often configured like this:

```rust
impl pallet_offences::Config for Runtime {
...
type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>;
}
```

Where `IdentificationTuple` is defined as:
```rust
pub type IdentificationTuple<T> = (<T as pallet_session::Config>::ValidatorId, <T as Config>::FullIdentification);
```

You should use `ExistenceOrLegacyExposureOf` instead. This type includes a custom encoder/decoder that supports both
the legacy `Exposure` type and the new `Existence` type.

This compatibility layer is necessary because `pallet-offences` stores the `FullIdentification` type in its storage.
If you replace `FullIdentification` with `Existence` directly, any previously stored items using `Exposure` will
fail to decode. `ExistenceOrLegacyExposureOf` ensures backward compatibility after this change.

crates:
- name: pallet-babe
bump: patch
- name: pallet-beefy
bump: patch
- name: pallet-grandpa
bump: patch
- name: pallet-offences-benchmarking
bump: patch
- name: pallet-root-offences
bump: patch
- name: pallet-session-benchmarking
bump: patch
- name: pallet-staking
bump: major
- name: westend-runtime
bump: minor
4 changes: 2 additions & 2 deletions substrate/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,8 +693,8 @@ impl pallet_session::Config for Runtime {
}

impl pallet_session::historical::Config for Runtime {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Runtime>;
}

pallet_staking_reward_curve::build! {
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/babe/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ impl pallet_session::Config for Test {
}

impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Test>;
}

impl pallet_authorship::Config for Test {
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/beefy/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ impl pallet_session::Config for Test {
}

impl pallet_session::historical::Config for Test {
type FullIdentification = pallet_staking::Exposure<u64, u128>;
type FullIdentificationOf = pallet_staking::ExposureOf<Self>;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Test>;
}

impl pallet_authorship::Config for Test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ impl pallet_session::Config for Runtime {
type WeightInfo = ();
}
impl pallet_session::historical::Config for Runtime {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Runtime>;
}

frame_election_provider_support::generate_solution_type!(
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/grandpa/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ impl pallet_session::Config for Test {
}

impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Test>;
}

impl pallet_authorship::Config for Test {
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/offences/benchmarking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ impl pallet_timestamp::Config for Test {
type WeightInfo = ();
}
impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Test>;
}

sp_runtime::impl_opaque_keys! {
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/root-offences/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ pub mod pallet {
+ pallet_staking::Config
+ pallet_session::Config<ValidatorId = <Self as frame_system::Config>::AccountId>
+ pallet_session::historical::Config<
FullIdentification = (),
FullIdentificationOf = pallet_staking::NullIdentity,
FullIdentification = pallet_staking::Existence,
FullIdentificationOf = pallet_staking::ExistenceOf<Self>,
>
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/root-offences/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ impl pallet_staking::Config for Test {
}

impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Test>;
}

sp_runtime::impl_opaque_keys! {
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/session/benchmarking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ impl pallet_timestamp::Config for Test {
type WeightInfo = ();
}
impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = pallet_staking::NullIdentity;
type FullIdentification = pallet_staking::Existence;
type FullIdentificationOf = pallet_staking::ExistenceOf<Test>;
}

sp_runtime::impl_opaque_keys! {
Expand Down
122 changes: 117 additions & 5 deletions substrate/frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@ mod pallet;
extern crate alloc;

use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec};
use codec::{Decode, DecodeWithMemTracking, Encode, HasCompact, MaxEncodedLen};
use codec::{
Decode, DecodeWithMemTracking, Encode, EncodeLike, HasCompact, Input, MaxEncodedLen, Output,
};
use frame_support::{
defensive, defensive_assert,
traits::{
Expand Down Expand Up @@ -1066,8 +1068,10 @@ impl<T: Config> Convert<T::AccountId, Option<T::AccountId>> for StashOf<T> {
///
/// Active exposure is the exposure of the validator set currently validating, i.e. in
/// `active_era`. It can differ from the latest planned exposure in `current_era`.
#[deprecated(note = "Use `ExistenceOf` or `ExistenceOrLegacyExposureOf` instead")]
pub struct ExposureOf<T>(core::marker::PhantomData<T>);

#[allow(deprecated)]
impl<T: Config> Convert<T::AccountId, Option<Exposure<T::AccountId, BalanceOf<T>>>>
for ExposureOf<T>
{
Expand All @@ -1077,10 +1081,69 @@ impl<T: Config> Convert<T::AccountId, Option<Exposure<T::AccountId, BalanceOf<T>
}
}

pub struct NullIdentity;
impl<T> Convert<T, Option<()>> for NullIdentity {
fn convert(_: T) -> Option<()> {
Some(())
/// A type representing the presence of a validator. Encodes as a unit type.
pub type Existence = ();

/// A converter type that returns `Some(())` if the validator exists in the current active era,
/// otherwise `None`. This serves as a lightweight presence check for validators.
pub struct ExistenceOf<T>(core::marker::PhantomData<T>);
impl<T: Config> Convert<T::AccountId, Option<Existence>> for ExistenceOf<T> {
fn convert(validator: T::AccountId) -> Option<Existence> {
Validators::<T>::contains_key(&validator).then_some(())
}
}

/// A compatibility wrapper type used to represent the presence of a validator in the current era.
/// Encodes as type [`Existence`] but can decode from legacy [`Exposure`] values for backward
/// compatibility.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, RuntimeDebug, TypeInfo, DecodeWithMemTracking)]
pub enum ExistenceOrLegacyExposure<A, B: HasCompact> {
/// Validator exists in the current era.
Exists,
/// Legacy `Exposure` data, retained for decoding compatibility.
Exposure(Exposure<A, B>),
}

/// Converts a validator account ID to a Some([`ExistenceOrLegacyExposure::Exists`]) if the
/// validator exists in the current era, otherwise `None`.
pub struct ExistenceOrLegacyExposureOf<T>(core::marker::PhantomData<T>);

impl<T: Config> Convert<T::AccountId, Option<ExistenceOrLegacyExposure<T::AccountId, BalanceOf<T>>>>
for ExistenceOrLegacyExposureOf<T>
{
fn convert(
validator: T::AccountId,
) -> Option<ExistenceOrLegacyExposure<T::AccountId, BalanceOf<T>>> {
ActiveEra::<T>::get()
.map(|active_era| ErasStakersOverview::<T>::contains_key(active_era.index, &validator))
.unwrap_or(false)
.then_some(ExistenceOrLegacyExposure::Exists)
}
}

impl<A, B: HasCompact> Encode for ExistenceOrLegacyExposure<A, B>
where
Exposure<A, B>: Encode,
{
fn encode_to<T: Output + ?Sized>(&self, dest: &mut T) {
match self {
ExistenceOrLegacyExposure::Exists => (),
ExistenceOrLegacyExposure::Exposure(exposure) => exposure.encode_to(dest),
}
}
}

impl<A, B: HasCompact> EncodeLike for ExistenceOrLegacyExposure<A, B> where Exposure<A, B>: Encode {}

impl<A, B: HasCompact> Decode for ExistenceOrLegacyExposure<A, B>
where
Exposure<A, B>: Decode,
{
fn decode<I: Input>(input: &mut I) -> Result<Self, codec::Error> {
match input.remaining_len() {
Ok(Some(x)) if x > 0 => Ok(ExistenceOrLegacyExposure::Exposure(Decode::decode(input)?)),
_ => Ok(ExistenceOrLegacyExposure::Exists),
}
}
}

Expand Down Expand Up @@ -1369,3 +1432,52 @@ impl BenchmarkingConfig for TestBenchmarkingConfig {
type MaxValidators = frame_support::traits::ConstU32<100>;
type MaxNominators = frame_support::traits::ConstU32<100>;
}

#[cfg(test)]
mod test {
use crate::ExistenceOrLegacyExposure;
use codec::{Decode, Encode};
use sp_staking::{Exposure, IndividualExposure};

#[test]
fn existence_encodes_decodes_correctly() {
let encoded_existence = ExistenceOrLegacyExposure::<u32, u32>::Exists.encode();
assert!(encoded_existence.is_empty());

// try decoding the existence
let decoded_existence =
ExistenceOrLegacyExposure::<u32, u32>::decode(&mut encoded_existence.as_slice())
.unwrap();
assert!(matches!(decoded_existence, ExistenceOrLegacyExposure::Exists));

// check that round-trip encoding works
assert_eq!(encoded_existence, decoded_existence.encode());
}

#[test]
fn legacy_existence_encodes_decodes_correctly() {
let legacy_exposure = Exposure::<u32, u32> {
total: 1,
own: 2,
others: vec![IndividualExposure { who: 3, value: 4 }],
};

let encoded_legacy_exposure = legacy_exposure.encode();

// try decoding the legacy exposure
let decoded_legacy_exposure =
ExistenceOrLegacyExposure::<u32, u32>::decode(&mut encoded_legacy_exposure.as_slice())
.unwrap();
assert_eq!(
decoded_legacy_exposure,
ExistenceOrLegacyExposure::Exposure(Exposure {
total: 1,
own: 2,
others: vec![IndividualExposure { who: 3, value: 4 }]
})
);

// round trip encoding works
assert_eq!(encoded_legacy_exposure, decoded_legacy_exposure.encode());
}
}
4 changes: 2 additions & 2 deletions substrate/frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ impl pallet_session::Config for Test {
}

impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = NullIdentity;
type FullIdentification = Existence;
type FullIdentificationOf = ExistenceOf<Test>;
}
impl pallet_authorship::Config for Test {
type FindAuthor = Author11;
Expand Down
Loading
Loading