mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-09 16:27:58 +00:00
make BagsList::put_in_front_of be permissionless (#14714)
* make BagsList::put_in_fron_of be permissionless * Update frame/bags-list/src/lib.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * improve docs as well * update lock * Update frame/bags-list/Cargo.toml Co-authored-by: Sam Johnson <sam@durosoft.com> * fix * Update frame/bags-list/src/lib.rs Co-authored-by: Michael Assaf <94772640+snowmead@users.noreply.github.com> --------- Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Sam Johnson <sam@durosoft.com> Co-authored-by: Michael Assaf <94772640+snowmead@users.noreply.github.com>
This commit is contained in:
Generated
+2
@@ -5871,6 +5871,8 @@ dependencies = [
|
||||
name = "pallet-bags-list"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"aquamarine",
|
||||
"docify",
|
||||
"frame-benchmarking",
|
||||
"frame-election-provider-support",
|
||||
"frame-support",
|
||||
|
||||
@@ -27,6 +27,8 @@ frame-election-provider-support = { version = "4.0.0-dev", default-features = fa
|
||||
|
||||
# third party
|
||||
log = { version = "0.4.17", default-features = false }
|
||||
docify = "0.2.1"
|
||||
aquamarine = { version = "0.3.2" }
|
||||
|
||||
# Optional imports for benchmarking
|
||||
frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false }
|
||||
|
||||
@@ -15,21 +15,52 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Bags-List Pallet
|
||||
//! > Made with *Substrate*, for *Polkadot*.
|
||||
//!
|
||||
//! A semi-sorted list, where items hold an `AccountId` based on some `Score`. The
|
||||
//! `AccountId` (`id` for short) might be synonym to a `voter` or `nominator` in some context, and
|
||||
//! `Score` signifies the chance of each id being included in the final
|
||||
//! [`SortedListProvider::iter`].
|
||||
//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) -
|
||||
//! [![polkadot]](https://polkadot.network)
|
||||
//!
|
||||
//! It implements [`frame_election_provider_support::SortedListProvider`] to provide a semi-sorted
|
||||
//! list of accounts to another pallet. It needs some other pallet to give it some information about
|
||||
//! the weights of accounts via [`frame_election_provider_support::ScoreProvider`].
|
||||
//! [polkadot]:
|
||||
//! https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
|
||||
//! [github]:
|
||||
//! https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
|
||||
//!
|
||||
//! This pallet is not configurable at genesis. Whoever uses it should call appropriate functions of
|
||||
//! the `SortedListProvider` (e.g. `on_insert`, or `unsafe_regenerate`) at their genesis.
|
||||
//! # Bags-List Pallet
|
||||
//!
|
||||
//! # Goals
|
||||
//! An onchain implementation of a semi-sorted linked list, with permissionless sorting and update
|
||||
//! operations.
|
||||
//!
|
||||
//! ## Pallet API
|
||||
//!
|
||||
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
|
||||
//! including its configuration trait, dispatchables, storage items, events and errors.
|
||||
//!
|
||||
//! This pallet provides an implementation of
|
||||
//! [`frame_election_provider_support::SortedListProvider`] and it can typically be used by another
|
||||
//! pallet via this API.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! This pallet splits `AccountId`s into different bags. Within a bag, these `AccountId`s are stored
|
||||
//! as nodes in a linked-list manner. This pallet then provides iteration over all bags, which
|
||||
//! basically allows an infinitely large list of items to be kept in a sorted manner.
|
||||
//!
|
||||
//! Each bags has a upper and lower range of scores, denoted by [`Config::BagThresholds`]. All nodes
|
||||
//! within a bag must be within the range of the bag. If not, the permissionless [`Pallet::rebag`]
|
||||
//! can be used to move any node to the right bag.
|
||||
//!
|
||||
//! Once a `rebag` happens, the order within a node is still not enforced. To move a node to the
|
||||
//! optimal position in a bag, the [`Pallet::put_in_front_of`] or [`Pallet::put_in_front_of_other`]
|
||||
//! can be used.
|
||||
//!
|
||||
//! Additional reading, about how this pallet is used in the context of Polkadot's staking system:
|
||||
//! <https://polkadot.network/blog/staking-update-september-2021/#bags-list-in-depth>
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! See [`example`] for a diagram of `rebag` and `put_in_front_of` operations.
|
||||
//!
|
||||
//! ## Low Level / Implementation Details
|
||||
//!
|
||||
//! The data structure exposed by this pallet aims to be optimized for:
|
||||
//!
|
||||
@@ -37,7 +68,7 @@
|
||||
//! - iteration over the top* N items by score, where the precise ordering of items doesn't
|
||||
//! particularly matter.
|
||||
//!
|
||||
//! # Details
|
||||
//! ### Further Details
|
||||
//!
|
||||
//! - items are kept in bags, which are delineated by their range of score (See
|
||||
//! [`Config::BagThresholds`]).
|
||||
@@ -53,6 +84,44 @@
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(doc)]
|
||||
#[cfg_attr(doc, aquamarine::aquamarine)]
|
||||
///
|
||||
/// In this example, assuming each node has an equal id and score (eg. node 21 has a score of 21),
|
||||
/// the node 22 can be moved from bag 1 to bag 0 with the `rebag` operation.
|
||||
///
|
||||
/// Once the whole list is iterated, assuming the above above rebag happens, the order of iteration
|
||||
/// would be: `25, 21, 22, 12, 22, 5, 7, 3`.
|
||||
///
|
||||
/// Moreover, in bag2, node 7 can be moved to the front of node 5 with the `put_in_front_of`, as it
|
||||
/// has a higher score.
|
||||
///
|
||||
/// ```mermaid
|
||||
/// graph LR
|
||||
/// Bag0 --> Bag1 --> Bag2
|
||||
///
|
||||
/// subgraph Bag0[Bag 0: 21-30 DOT]
|
||||
/// direction LR
|
||||
/// 25 --> 21 --> 22X[22]
|
||||
/// end
|
||||
///
|
||||
/// subgraph Bag1[Bag 1: 11-20 DOT]
|
||||
/// direction LR
|
||||
/// 12 --> 22
|
||||
/// end
|
||||
///
|
||||
/// subgraph Bag2[Bag 2: 0-10 DOT]
|
||||
/// direction LR
|
||||
/// 5 --> 7 --> 3
|
||||
/// end
|
||||
///
|
||||
/// style 22X stroke-dasharray: 5 5,opacity:50%
|
||||
/// ```
|
||||
///
|
||||
/// The equivalent of this in code would be:
|
||||
#[doc = docify::embed!("src/tests.rs", examples_work)]
|
||||
pub mod example {}
|
||||
|
||||
use codec::FullCodec;
|
||||
use frame_election_provider_support::{ScoreProvider, SortedListProvider};
|
||||
use frame_system::ensure_signed;
|
||||
@@ -240,9 +309,11 @@ pub mod pallet {
|
||||
/// Move the caller's Id directly in front of `lighter`.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ and can only be called by the Id of
|
||||
/// the account going in front of `lighter`.
|
||||
/// the account going in front of `lighter`. Fee is payed by the origin under all
|
||||
/// circumstances.
|
||||
///
|
||||
/// Only works if:
|
||||
///
|
||||
/// Only works if
|
||||
/// - both nodes are within the same bag,
|
||||
/// - and `origin` has a greater `Score` than `lighter`.
|
||||
#[pallet::call_index(1)]
|
||||
@@ -257,6 +328,24 @@ pub mod pallet {
|
||||
.map_err::<Error<T, I>, _>(Into::into)
|
||||
.map_err::<DispatchError, _>(Into::into)
|
||||
}
|
||||
|
||||
/// Same as [`Pallet::put_in_front_of`], but it can be called by anyone.
|
||||
///
|
||||
/// Fee is paid by the origin under all circumstances.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::WeightInfo::put_in_front_of())]
|
||||
pub fn put_in_front_of_other(
|
||||
origin: OriginFor<T>,
|
||||
heavier: AccountIdLookupOf<T>,
|
||||
lighter: AccountIdLookupOf<T>,
|
||||
) -> DispatchResult {
|
||||
let _ = ensure_signed(origin)?;
|
||||
let lighter = T::Lookup::lookup(lighter)?;
|
||||
let heavier = T::Lookup::lookup(heavier)?;
|
||||
List::<T, I>::put_in_front_of(&lighter, &heavier)
|
||||
.map_err::<Error<T, I>, _>(Into::into)
|
||||
.map_err::<DispatchError, _>(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
|
||||
@@ -22,6 +22,62 @@ use frame_election_provider_support::{SortedListProvider, VoteWeight};
|
||||
use list::Bag;
|
||||
use mock::{test_utils::*, *};
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn examples_work() {
|
||||
ExtBuilder::default()
|
||||
.skip_genesis_ids()
|
||||
// initially set the score of 11 for 22 to push it next to 12
|
||||
.add_ids(vec![(25, 25), (21, 21), (12, 12), (22, 11), (5, 5), (7, 7), (3, 3)])
|
||||
.build_and_execute(|| {
|
||||
// initial bags
|
||||
assert_eq!(
|
||||
List::<Runtime>::get_bags(),
|
||||
vec![
|
||||
// bag 0 -> 10
|
||||
(10, vec![5, 7, 3]),
|
||||
// bag 10 -> 20
|
||||
(20, vec![12, 22]),
|
||||
// bag 20 -> 30
|
||||
(30, vec![25, 21])
|
||||
]
|
||||
);
|
||||
|
||||
// set score of 22 to 22
|
||||
StakingMock::set_score_of(&22, 22);
|
||||
|
||||
// now we rebag 22 to the first bag
|
||||
assert_ok!(BagsList::rebag(RuntimeOrigin::signed(42), 22));
|
||||
|
||||
assert_eq!(
|
||||
List::<Runtime>::get_bags(),
|
||||
vec![
|
||||
// bag 0 -> 10
|
||||
(10, vec![5, 7, 3]),
|
||||
// bag 10 -> 20
|
||||
(20, vec![12]),
|
||||
// bag 20 -> 30
|
||||
(30, vec![25, 21, 22])
|
||||
]
|
||||
);
|
||||
|
||||
// now we put 7 at the front of bag 0
|
||||
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(7), 5));
|
||||
|
||||
assert_eq!(
|
||||
List::<Runtime>::get_bags(),
|
||||
vec![
|
||||
// bag 0 -> 10
|
||||
(10, vec![7, 5, 3]),
|
||||
// bag 10 -> 20
|
||||
(20, vec![12]),
|
||||
// bag 20 -> 30
|
||||
(30, vec![25, 21, 22])
|
||||
]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
mod pallet {
|
||||
use super::*;
|
||||
|
||||
@@ -207,6 +263,25 @@ mod pallet {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_in_front_of_other_can_be_permissionless() {
|
||||
ExtBuilder::default()
|
||||
.skip_genesis_ids()
|
||||
.add_ids(vec![(10, 15), (11, 16), (12, 19)])
|
||||
.build_and_execute(|| {
|
||||
// given
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![10, 11, 12])]);
|
||||
// 11 now has more weight than 10 and can be moved before it.
|
||||
StakingMock::set_score_of(&11u32, 17);
|
||||
|
||||
// when
|
||||
assert_ok!(BagsList::put_in_front_of_other(RuntimeOrigin::signed(42), 11u32, 10));
|
||||
|
||||
// then
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![(20, vec![11, 10, 12])]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_in_front_of_two_node_bag_heavier_is_tail() {
|
||||
ExtBuilder::default()
|
||||
@@ -368,7 +443,7 @@ mod pallet {
|
||||
StakingMock::set_score_of(&4, 999);
|
||||
|
||||
// when
|
||||
BagsList::put_in_front_of(RuntimeOrigin::signed(2), 4).unwrap();
|
||||
assert_ok!(BagsList::put_in_front_of(RuntimeOrigin::signed(2), 4));
|
||||
|
||||
// then
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![3, 2, 4])]);
|
||||
|
||||
Reference in New Issue
Block a user