Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit e5670d2

Browse files
kianenigmaliamaharonKiChjangsam0x17juangirini
authored
Improve documentation for fast-unstake pallet (#14101)
* improve documentation of fast-unstake pallet * using Sam's crate now * fix * remove stuff not needed * Some updates * use new prelude. * update * some other fancy docs * Update frame/fast-unstake/src/lib.rs Co-authored-by: Liam Aharon <[email protected]> * Update frame/support/procedural/src/pallet/expand/mod.rs Co-authored-by: Liam Aharon <[email protected]> * update * Update frame/fast-unstake/src/lib.rs Co-authored-by: Keith Yeung <[email protected]> * update * fix no_std issue by updating to latest version of docify * get things compiling by adding a use for StakingInterface * fix docify paths to their proper values, still not working because bug * embed working 🎉 * update syn * upgrade to docify v0.1.10 for some fixes * Apply suggestions from code review Co-authored-by: Juan <[email protected]> Co-authored-by: Gonçalo Pestana <[email protected]> * improve docs * Update frame/support/procedural/src/pallet/expand/doc_only.rs Co-authored-by: Juan <[email protected]> * fmt * fix * Update frame/support/procedural/src/pallet/expand/doc_only.rs Co-authored-by: Muharem Ismailov <[email protected]> * Update frame/support/procedural/src/pallet/expand/config.rs Co-authored-by: Muharem Ismailov <[email protected]> * Update frame/support/procedural/src/pallet/expand/config.rs Co-authored-by: Muharem Ismailov <[email protected]> * remove redundant * update docify rev * update. * update. * update lock file --------- Co-authored-by: Liam Aharon <[email protected]> Co-authored-by: Keith Yeung <[email protected]> Co-authored-by: Sam Johnson <[email protected]> Co-authored-by: Juan <[email protected]> Co-authored-by: Gonçalo Pestana <[email protected]> Co-authored-by: Muharem Ismailov <[email protected]> Co-authored-by: parity-processbot <>
1 parent 5b11f65 commit e5670d2

File tree

15 files changed

+378
-121
lines changed

15 files changed

+378
-121
lines changed

Cargo.lock

+34
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frame/fast-unstake/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ frame-election-provider-support = { default-features = false, path = "../electio
2727

2828
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
2929

30+
docify = "0.1.13"
31+
3032
[dev-dependencies]
3133
pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" }
3234
sp-core = { version = "8.0.0", default-features = false, path = "../../primitives/core" }

frame/fast-unstake/src/lib.rs

+142-49
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,100 @@
1515
// See the License for the specific language governing permissions and
1616
// limitations under the License.
1717

18-
//! A pallet that's designed to JUST do the following:
18+
//! > Made with *Substrate*, for *Polkadot*.
1919
//!
20-
//! If a nominator is not exposed in any `ErasStakers` (i.e. "has not actively backed any
21-
//! validators in the last `BondingDuration` days"), then they can register themselves in this
22-
//! pallet, unstake faster than having to wait an entire bonding duration.
20+
//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) -
21+
//! [![polkadot]](https://polkadot.network)
2322
//!
24-
//! Appearing in the exposure of a validator means being exposed equal to that validator from the
25-
//! point of view of the staking system. This usually means earning rewards with the validator, and
26-
//! also being at the risk of slashing with the validator. This is equivalent to the "Active
27-
//! Nominator" role explained in the
28-
//! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/).
23+
//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
24+
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
2925
//!
30-
//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when
31-
//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of
32-
//! congestion, no FIFO ordering is provided.
26+
//! # Fast Unstake Pallet
27+
//!
28+
//! A pallet to allow participants of the staking system (represented by [`Config::Staking`], being
29+
//! [`StakingInterface`]) to unstake quicker, if and only if they meet the condition of not being
30+
//! exposed to any slashes.
31+
//!
32+
//! ## Overview
33+
//!
34+
//! If a nominator is not exposed anywhere in the staking system, checked via
35+
//! [`StakingInterface::is_exposed_in_era`] (i.e. "has not actively backed any validators in the
36+
//! last [`StakingInterface::bonding_duration`] days"), then they can register themselves in this
37+
//! pallet and unstake faster than having to wait an entire bonding duration.
38+
//!
39+
//! *Being exposed with validator* from the point of view of the staking system means earning
40+
//! rewards with the validator, and also being at the risk of slashing with the validator. This is
41+
//! equivalent to the "Active Nominator" role explained in
42+
//! [here](https://polkadot.network/blog/staking-update-february-2022/).
3343
//!
3444
//! Stakers who are certain about NOT being exposed can register themselves with
35-
//! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in
36-
//! the queue to be checked.
45+
//! [`Pallet::register_fast_unstake`]. This will chill, fully unbond the staker and place them
46+
//! in the queue to be checked.
3747
//!
38-
//! Once queued, but not being actively processed, stakers can withdraw their request via
39-
//! [`Call::deregister`].
48+
//! A successful registration implies being fully unbonded and chilled in the staking system. These
49+
//! effects persist even if the fast-unstake registration is retracted (see [`Pallet::deregister`]
50+
//! and further).
4051
//!
41-
//! Once queued, a staker wishing to unbond can perform no further action in pallet-staking. This is
42-
//! to prevent them from accidentally exposing themselves behind a validator etc.
52+
//! Once registered as a fast-unstaker, the staker will be queued and checked by the system. This
53+
//! can take a variable number of blocks based on demand, but will almost certainly be "faster" (as
54+
//! the name suggest) than waiting the standard bonding duration.
55+
//!
56+
//! A fast-unstaker is either in [`Queue`] or actively being checked, at which point it lives in
57+
//! [`Head`]. Once in [`Head`], the request cannot be retracted anymore. But, once in [`Queue`], it
58+
//! can, via [`Pallet::deregister`].
59+
//!
60+
//! A deposit equal to [`Config::Deposit`] is collected for this process, and is returned in case a
61+
//! successful unstake occurs (`Event::Unstaked` signals that).
4362
//!
4463
//! Once processed, if successful, no additional fee for the checking process is taken, and the
4564
//! staker is instantly unbonded.
4665
//!
47-
//! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras
48-
//! they will end up being slashed for the amount of wasted work they have inflicted on the chian.
66+
//! If unsuccessful, meaning that the staker was exposed, the aforementioned deposit will be slashed
67+
//! for the amount of wasted work they have inflicted on the chain.
68+
//!
69+
//! All in all, this pallet is meant to provide an easy off-ramp for some stakers.
70+
//!
71+
//! ### Example
72+
//!
73+
//! 1. Fast-unstake with multiple participants in the queue.
74+
#![doc = docify::embed!("frame/fast-unstake/src/tests.rs", successful_multi_queue)]
75+
//!
76+
//! 2. Fast unstake failing because a nominator is exposed.
77+
#![doc = docify::embed!("frame/fast-unstake/src/tests.rs", exposed_nominator_cannot_unstake)]
78+
//!
79+
//! ## Pallet API
80+
//!
81+
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
82+
//! including its configuration trait, dispatchables, storage items, events and errors.
83+
//!
84+
//! ## Low Level / Implementation Details
85+
//!
86+
//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when
87+
//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of
88+
//! congestion, no FIFO ordering is provided.
89+
//!
90+
//! A few important considerations can be concluded based on the `on_idle`-based implementation:
91+
//!
92+
//! * It is crucial for the weights of this pallet to be correct. The code inside
93+
//! [`Pallet::on_idle`] MUST be able to measure itself and report the remaining weight correctly
94+
//! after execution.
95+
//!
96+
//! * If the weight measurement is incorrect, it can lead to perpetual overweight (consequently
97+
//! slow) blocks.
98+
//!
99+
//! * The amount of weight that `on_idle` consumes is a direct function of [`ErasToCheckPerBlock`].
100+
//!
101+
//! * Thus, a correct value of [`ErasToCheckPerBlock`] (which can be set via [`Pallet::control`])
102+
//! should be chosen, such that a reasonable amount of weight is used `on_idle`. If
103+
//! [`ErasToCheckPerBlock`] is too large, `on_idle` will always conclude that it has not enough
104+
//! weight to proceed, and will early-return. Nonetheless, this should also be *safe* as long as
105+
//! the benchmarking/weights are *accurate*.
106+
//!
107+
//! * See the inline code-comments on `do_on_idle` (private) for more details.
108+
//!
109+
//! * For further safety, in case of any unforeseen errors, the pallet will emit
110+
//! [`Event::InternalError`] and set [`ErasToCheckPerBlock`] back to 0, which essentially means
111+
//! the pallet will halt/disable itself.
49112
50113
#![cfg_attr(not(feature = "std"), no_std)]
51114

@@ -64,9 +127,15 @@ pub mod migrations;
64127
pub mod types;
65128
pub mod weights;
66129

130+
// some extra imports for docs to link properly.
131+
#[cfg(doc)]
132+
pub use frame_support::traits::Hooks;
133+
#[cfg(doc)]
134+
pub use sp_staking::StakingInterface;
135+
136+
/// The logging target of this pallet.
67137
pub const LOG_TARGET: &'static str = "runtime::fast-unstake";
68138

69-
// syntactic sugar for logging.
70139
#[macro_export]
71140
macro_rules! log {
72141
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
@@ -94,16 +163,6 @@ pub mod pallet {
94163
#[cfg(feature = "try-runtime")]
95164
use sp_runtime::TryRuntimeError;
96165

97-
#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)]
98-
#[codec(mel_bound(T: Config))]
99-
#[scale_info(skip_type_params(T))]
100-
pub struct MaxChecking<T: Config>(sp_std::marker::PhantomData<T>);
101-
impl<T: Config> frame_support::traits::Get<u32> for MaxChecking<T> {
102-
fn get() -> u32 {
103-
T::Staking::bonding_duration() + 1
104-
}
105-
}
106-
107166
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
108167

109168
#[pallet::pallet]
@@ -125,7 +184,7 @@ pub mod pallet {
125184
#[pallet::constant]
126185
type Deposit: Get<BalanceOf<Self>>;
127186

128-
/// The origin that can control this pallet.
187+
/// The origin that can control this pallet, in other words invoke [`Pallet::control`].
129188
type ControlOrigin: frame_support::traits::EnsureOrigin<Self::RuntimeOrigin>;
130189

131190
/// Batch size.
@@ -136,56 +195,61 @@ pub mod pallet {
136195
/// The access to staking functionality.
137196
type Staking: StakingInterface<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
138197

198+
/// Maximum value for `ErasToCheckPerBlock`, checked in [`Pallet::control`].
199+
///
200+
/// This should be slightly bigger than the actual value in order to have accurate
201+
/// benchmarks.
202+
type MaxErasToCheckPerBlock: Get<u32>;
203+
139204
/// The weight information of this pallet.
140205
type WeightInfo: WeightInfo;
141206

142-
/// Maximum value for `ErasToCheckPerBlock`. This should be as close as possible, but more
143-
/// than the actual value, in order to have accurate benchmarks.
144-
type MaxErasToCheckPerBlock: Get<u32>;
145-
146207
/// Use only for benchmarking.
147208
#[cfg(feature = "runtime-benchmarks")]
148209
type MaxBackersPerValidator: Get<u32>;
149210
}
150211

151212
/// The current "head of the queue" being unstaked.
213+
///
214+
/// The head in itself can be a batch of up to [`Config::BatchSize`] stakers.
152215
#[pallet::storage]
153216
pub type Head<T: Config> = StorageValue<_, UnstakeRequest<T>, OptionQuery>;
154217

155218
/// The map of all accounts wishing to be unstaked.
156219
///
157220
/// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit.
158-
///
159-
/// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
221+
// Hasher: Twox safe since `AccountId` is a secure hash.
160222
#[pallet::storage]
161223
pub type Queue<T: Config> = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf<T>>;
162224

163225
/// Number of eras to check per block.
164226
///
165-
/// If set to 0, this pallet does absolutely nothing.
227+
/// If set to 0, this pallet does absolutely nothing. Cannot be set to more than
228+
/// [`Config::MaxErasToCheckPerBlock`].
166229
///
167-
/// Based on the amount of weight available at `on_idle`, up to this many eras of a single
168-
/// nominator might be checked.
230+
/// Based on the amount of weight available at [`Pallet::on_idle`], up to this many eras are
231+
/// checked. The checking is represented by updating [`UnstakeRequest::checked`], which is
232+
/// stored in [`Head`].
169233
#[pallet::storage]
234+
#[pallet::getter(fn eras_to_check_per_block)]
170235
pub type ErasToCheckPerBlock<T: Config> = StorageValue<_, u32, ValueQuery>;
171236

172-
/// The events of this pallet.
173237
#[pallet::event]
174238
#[pallet::generate_deposit(pub(super) fn deposit_event)]
175239
pub enum Event<T: Config> {
176240
/// A staker was unstaked.
177241
Unstaked { stash: T::AccountId, result: DispatchResult },
178242
/// A staker was slashed for requesting fast-unstake whilst being exposed.
179243
Slashed { stash: T::AccountId, amount: BalanceOf<T> },
180-
/// An internal error happened. Operations will be paused now.
181-
InternalError,
182244
/// A batch was partially checked for the given eras, but the process did not finish.
183245
BatchChecked { eras: Vec<EraIndex> },
184246
/// A batch of a given size was terminated.
185247
///
186248
/// This is always follows by a number of `Unstaked` or `Slashed` events, marking the end
187249
/// of the batch. A new batch will be created upon next block.
188250
BatchFinished { size: u32 },
251+
/// An internal error happened. Operations will be paused now.
252+
InternalError,
189253
}
190254

191255
#[pallet::error]
@@ -247,8 +311,12 @@ pub mod pallet {
247311
impl<T: Config> Pallet<T> {
248312
/// Register oneself for fast-unstake.
249313
///
250-
/// The dispatch origin of this call must be signed by the controller account, similar to
251-
/// `staking::unbond`.
314+
/// ## Dispatch Origin
315+
///
316+
/// The dispatch origin of this call must be *signed* by whoever is permitted to call
317+
/// unbond funds by the staking system. See [`Config::Staking`].
318+
///
319+
/// ## Details
252320
///
253321
/// The stash associated with the origin must have no ongoing unlocking chunks. If
254322
/// successful, this will fully unbond and chill the stash. Then, it will enqueue the stash
@@ -263,6 +331,10 @@ pub mod pallet {
263331
/// If the check fails, the stash remains chilled and waiting for being unbonded as in with
264332
/// the normal staking system, but they lose part of their unbonding chunks due to consuming
265333
/// the chain's resources.
334+
///
335+
/// ## Events
336+
///
337+
/// Some events from the staking and currency system might be emitted.
266338
#[pallet::call_index(0)]
267339
#[pallet::weight(<T as Config>::WeightInfo::register_fast_unstake())]
268340
pub fn register_fast_unstake(origin: OriginFor<T>) -> DispatchResult {
@@ -288,11 +360,22 @@ pub mod pallet {
288360

289361
/// Deregister oneself from the fast-unstake.
290362
///
363+
/// ## Dispatch Origin
364+
///
365+
/// The dispatch origin of this call must be *signed* by whoever is permitted to call
366+
/// unbond funds by the staking system. See [`Config::Staking`].
367+
///
368+
/// ## Details
369+
///
291370
/// This is useful if one is registered, they are still waiting, and they change their mind.
292371
///
293372
/// Note that the associated stash is still fully unbonded and chilled as a consequence of
294-
/// calling `register_fast_unstake`. This should probably be followed by a call to
295-
/// `Staking::rebond`.
373+
/// calling [`Pallet::register_fast_unstake`]. Therefore, this should probably be followed
374+
/// by a call to `rebond` in the staking system.
375+
///
376+
/// ## Events
377+
///
378+
/// Some events from the staking and currency system might be emitted.
296379
#[pallet::call_index(1)]
297380
#[pallet::weight(<T as Config>::WeightInfo::deregister())]
298381
pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
@@ -318,7 +401,17 @@ pub mod pallet {
318401

319402
/// Control the operation of this pallet.
320403
///
321-
/// Dispatch origin must be signed by the [`Config::ControlOrigin`].
404+
/// ## Dispatch Origin
405+
///
406+
/// The dispatch origin of this call must be [`Config::ControlOrigin`].
407+
///
408+
/// ## Details
409+
///
410+
/// Can set the number of eras to check per block, and potentially other admin work.
411+
///
412+
/// ## Events
413+
///
414+
/// No events are emitted from this dispatch.
322415
#[pallet::call_index(2)]
323416
#[pallet::weight(<T as Config>::WeightInfo::control())]
324417
pub fn control(origin: OriginFor<T>, eras_to_check: EraIndex) -> DispatchResult {

0 commit comments

Comments
 (0)