mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-23 17:51:09 +00:00
Add extrinsic to improve position in a bag of bags-list (#9829)
* pallet-bags-list: Add `put_in_front_of` extrinsic This PR adds the extrinsic `put_in_front_of` which allows the user to specify a `lighter` and `heavier` account within the same bag. The extrinsic will move `heavier` directly in front of `lighter`. The parameter names refer to the fact that `lighter` must have a lower `VoteWeight` then `heavier`. In the ideal use case, where a user wants to improve the position of their account within a bag, the user would iterate the bag, starting from the head, and find the first node who's `VoteWeight` is less than theirs. They would then supply the `id` of the node as the `lighter` argument and their own `id` as the `heavier` argument. * Test & Benchmarks * Respect line width * Remove List::put_in_fron_of tests; Remove AlreadyHigher error * The dispatch origin for this call must be ... * Add some periods * Add back test to list module: put_in_front_of_exits_early_if_bag_not_found * add test tests::pallet::heavier_is_head_lighter_is_not_terminal * Cater for edge case of heavier being head * Add ExtBuilder::add_aux_data; try to make some tests use simpler data * Update frame/bags-list/src/list/tests.rs * make insert_at_unchecked infallible * Make it permissioned - only callable by heavier * Add test cases for insert_at_unchecked * Move counter update to insert_at; fix comments * Address some feedback * Make voteweight constructed with parameter_types * Always set vote weight for Ids in build * Add skip_genesis_ids * Do not pass weight fn to List put_in_front_of * Remove remants of CounterForListNodes * fmt * cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_bags_list --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/bags-list/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Delete messed up weights file * Some place holder stuff so we can run bench in CI * Fix * cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_bags_list --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/bags-list/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Update frame/bags-list/src/list/mod.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * fmt * Log + debug assert when refetching an Id Co-authored-by: Parity Bot <admin@parity.io> Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
@@ -376,6 +376,84 @@ impl<T: Config> List<T> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Put `heavier_id` to the position directly in front of `lighter_id`. Both ids must be in the
|
||||
/// same bag and the `weight_of` `lighter_id` must be less than that of `heavier_id`.
|
||||
pub(crate) fn put_in_front_of(
|
||||
lighter_id: &T::AccountId,
|
||||
heavier_id: &T::AccountId,
|
||||
) -> Result<(), crate::pallet::Error<T>> {
|
||||
use crate::pallet;
|
||||
use frame_support::ensure;
|
||||
|
||||
let lighter_node = Node::<T>::get(&lighter_id).ok_or(pallet::Error::IdNotFound)?;
|
||||
let heavier_node = Node::<T>::get(&heavier_id).ok_or(pallet::Error::IdNotFound)?;
|
||||
|
||||
ensure!(lighter_node.bag_upper == heavier_node.bag_upper, pallet::Error::NotInSameBag);
|
||||
|
||||
// this is the most expensive check, so we do it last.
|
||||
ensure!(
|
||||
T::VoteWeightProvider::vote_weight(&heavier_id) >
|
||||
T::VoteWeightProvider::vote_weight(&lighter_id),
|
||||
pallet::Error::NotHeavier
|
||||
);
|
||||
|
||||
// remove the heavier node from this list. Note that this removes the node from storage and
|
||||
// decrements the node counter.
|
||||
Self::remove(&heavier_id);
|
||||
|
||||
// re-fetch `lighter_node` from storage since it may have been updated when `heavier_node`
|
||||
// was removed.
|
||||
let lighter_node = Node::<T>::get(&lighter_id).ok_or_else(|| {
|
||||
debug_assert!(false, "id that should exist cannot be found");
|
||||
crate::log!(warn, "id that should exist cannot be found");
|
||||
pallet::Error::IdNotFound
|
||||
})?;
|
||||
|
||||
// insert `heavier_node` directly in front of `lighter_node`. This will update both nodes
|
||||
// in storage and update the node counter.
|
||||
Self::insert_at_unchecked(lighter_node, heavier_node);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert `node` directly in front of `at`.
|
||||
///
|
||||
/// WARNINGS:
|
||||
/// - this is a naive function in that it does not check if `node` belongs to the same bag as
|
||||
/// `at`. It is expected that the call site will check preconditions.
|
||||
/// - this will panic if `at.bag_upper` is not a bag that already exists in storage.
|
||||
fn insert_at_unchecked(mut at: Node<T>, mut node: Node<T>) {
|
||||
// connect `node` to its new `prev`.
|
||||
node.prev = at.prev.clone();
|
||||
if let Some(mut prev) = at.prev() {
|
||||
prev.next = Some(node.id().clone());
|
||||
prev.put()
|
||||
}
|
||||
|
||||
// connect `node` and `at`.
|
||||
node.next = Some(at.id().clone());
|
||||
at.prev = Some(node.id().clone());
|
||||
|
||||
if node.is_terminal() {
|
||||
// `node` is the new head, so we make sure the bag is updated. Note,
|
||||
// since `node` is always in front of `at` we know that 1) there is always at least 2
|
||||
// nodes in the bag, and 2) only `node` could be the head and only `at` could be the
|
||||
// tail.
|
||||
let mut bag = Bag::<T>::get(at.bag_upper)
|
||||
.expect("given nodes must always have a valid bag. qed.");
|
||||
|
||||
if node.prev == None {
|
||||
bag.head = Some(node.id().clone())
|
||||
}
|
||||
|
||||
bag.put()
|
||||
};
|
||||
|
||||
// write the updated nodes to storage.
|
||||
at.put();
|
||||
node.put();
|
||||
}
|
||||
|
||||
/// Sanity check the list.
|
||||
///
|
||||
/// This should be called from the call-site, whenever one of the mutating apis (e.g. `insert`)
|
||||
|
||||
@@ -383,6 +383,123 @@ mod list {
|
||||
assert!(non_existent_ids.iter().all(|id| !List::<Runtime>::contains(id)));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "given nodes must always have a valid bag. qed."]
|
||||
fn put_in_front_of_panics_if_bag_not_found() {
|
||||
ExtBuilder::default().skip_genesis_ids().build_and_execute_no_post_check(|| {
|
||||
let node_10_no_bag = Node::<Runtime> { id: 10, prev: None, next: None, bag_upper: 15 };
|
||||
let node_11_no_bag = Node::<Runtime> { id: 11, prev: None, next: None, bag_upper: 15 };
|
||||
|
||||
// given
|
||||
ListNodes::<Runtime>::insert(10, node_10_no_bag);
|
||||
ListNodes::<Runtime>::insert(11, node_11_no_bag);
|
||||
StakingMock::set_vote_weight_of(&10, 14);
|
||||
StakingMock::set_vote_weight_of(&11, 15);
|
||||
assert!(!ListBags::<Runtime>::contains_key(15));
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![]);
|
||||
|
||||
// then .. this panics
|
||||
let _ = List::<Runtime>::put_in_front_of(&10, &11);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_at_unchecked_at_is_only_node() {
|
||||
// Note that this `insert_at_unchecked` test should fail post checks because node 42 does
|
||||
// not get re-assigned the correct bagu pper. This is because `insert_at_unchecked` assumes
|
||||
// both nodes are already in the same bag with the correct bag upper.
|
||||
ExtBuilder::default().build_and_execute_no_post_check(|| {
|
||||
// given
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
|
||||
|
||||
// implicitly also test that `node`'s `prev`/`next` are correctly re-assigned.
|
||||
let node_42 =
|
||||
Node::<Runtime> { id: 42, prev: Some(1), next: Some(2), bag_upper: 1_000 };
|
||||
assert!(!crate::ListNodes::<Runtime>::contains_key(42));
|
||||
|
||||
let node_1 = crate::ListNodes::<Runtime>::get(&1).unwrap();
|
||||
|
||||
// when
|
||||
List::<Runtime>::insert_at_unchecked(node_1, node_42);
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
List::<Runtime>::get_bags(),
|
||||
vec![(10, vec![42, 1]), (1_000, vec![2, 3, 4])]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_at_unchecked_at_is_head() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
// given
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
|
||||
|
||||
// implicitly also test that `node`'s `prev`/`next` are correctly re-assigned.
|
||||
let node_42 = Node::<Runtime> { id: 42, prev: Some(4), next: None, bag_upper: 1_000 };
|
||||
assert!(!crate::ListNodes::<Runtime>::contains_key(42));
|
||||
|
||||
let node_2 = crate::ListNodes::<Runtime>::get(&2).unwrap();
|
||||
|
||||
// when
|
||||
List::<Runtime>::insert_at_unchecked(node_2, node_42);
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
List::<Runtime>::get_bags(),
|
||||
vec![(10, vec![1]), (1_000, vec![42, 2, 3, 4])]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_at_unchecked_at_is_non_terminal() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
// given
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
|
||||
|
||||
// implicitly also test that `node`'s `prev`/`next` are correctly re-assigned.
|
||||
let node_42 = Node::<Runtime> { id: 42, prev: None, next: Some(2), bag_upper: 1_000 };
|
||||
assert!(!crate::ListNodes::<Runtime>::contains_key(42));
|
||||
|
||||
let node_3 = crate::ListNodes::<Runtime>::get(&3).unwrap();
|
||||
|
||||
// when
|
||||
List::<Runtime>::insert_at_unchecked(node_3, node_42);
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
List::<Runtime>::get_bags(),
|
||||
vec![(10, vec![1]), (1_000, vec![2, 42, 3, 4])]
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_at_unchecked_at_is_tail() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
// given
|
||||
assert_eq!(List::<Runtime>::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]);
|
||||
|
||||
// implicitly also test that `node`'s `prev`/`next` are correctly re-assigned.
|
||||
let node_42 =
|
||||
Node::<Runtime> { id: 42, prev: Some(42), next: Some(42), bag_upper: 1_000 };
|
||||
assert!(!crate::ListNodes::<Runtime>::contains_key(42));
|
||||
|
||||
let node_4 = crate::ListNodes::<Runtime>::get(&4).unwrap();
|
||||
|
||||
// when
|
||||
List::<Runtime>::insert_at_unchecked(node_4, node_42);
|
||||
|
||||
// then
|
||||
assert_eq!(
|
||||
List::<Runtime>::get_bags(),
|
||||
vec![(10, vec![1]), (1_000, vec![2, 3, 42, 4])]
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
mod bags {
|
||||
|
||||
Reference in New Issue
Block a user