referrerpolicy=no-referrer-when-downgrade

pallet_bags_list/list/
mod.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Implementation of a "bags list": a semi-sorted list where ordering granularity is dictated by
19//! configurable thresholds that delineate the boundaries of bags. It uses a pattern of composite
20//! data structures, where multiple storage items are masked by one outer API. See
21//! [`crate::ListNodes`], [`crate::ListBags`] for more information.
22//!
23//! The outer API of this module is the [`List`] struct. It wraps all acceptable operations on top
24//! of the aggregate linked list. All operations with the bags list should happen through this
25//! interface.
26
27use crate::Config;
28use alloc::{
29	boxed::Box,
30	collections::{btree_map::BTreeMap, btree_set::BTreeSet},
31};
32use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
33use core::{iter, marker::PhantomData};
34use frame_election_provider_support::ScoreProvider;
35use frame_support::{
36	defensive, ensure,
37	traits::{Defensive, DefensiveOption, Get},
38	CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PalletError, PartialEqNoBound,
39};
40use scale_info::TypeInfo;
41use sp_runtime::traits::{Bounded, Zero};
42
43#[cfg(any(
44	test,
45	feature = "try-runtime",
46	feature = "fuzz",
47	feature = "std",
48	feature = "runtime-benchmarks"
49))]
50use alloc::vec::Vec;
51#[cfg(any(test, feature = "try-runtime", feature = "fuzz"))]
52use sp_runtime::TryRuntimeError;
53
54#[derive(
55	Debug,
56	PartialEq,
57	Eq,
58	Encode,
59	Decode,
60	DecodeWithMemTracking,
61	MaxEncodedLen,
62	TypeInfo,
63	PalletError,
64)]
65pub enum ListError {
66	/// A duplicate id has been detected.
67	Duplicate,
68	/// An Id does not have a greater score than another Id.
69	NotHeavier,
70	/// Attempted to place node in front of a node in another bag.
71	NotInSameBag,
72	/// Given node id was not found.
73	NodeNotFound,
74	/// The List is locked, therefore updates cannot happen now.
75	Locked,
76}
77
78#[cfg(test)]
79mod tests;
80
81/// Given a certain score, to which bag does it belong to?
82///
83/// Bags are identified by their upper threshold; the value returned by this function is guaranteed
84/// to be a member of `T::BagThresholds`.
85///
86/// Note that even if the thresholds list does not have `T::Score::max_value()` as its final member,
87/// this function behaves as if it does.
88pub fn notional_bag_for<T: Config<I>, I: 'static>(score: T::Score) -> T::Score {
89	let thresholds = T::BagThresholds::get();
90	let idx = thresholds.partition_point(|&threshold| score > threshold);
91	thresholds.get(idx).copied().unwrap_or_else(T::Score::max_value)
92}
93
94/// The **ONLY** entry point of this module. All operations to the bags-list should happen through
95/// this interface. It is forbidden to access other module members directly.
96// Data structure providing efficient mostly-accurate selection of the top N id by `Score`.
97//
98// It's implemented as a set of linked lists. Each linked list comprises a bag of ids of
99// arbitrary and unbounded length, all having a score within a particular constant range.
100// This structure means that ids can be added and removed in `O(1)` time.
101//
102// Iteration is accomplished by chaining the iteration of each bag, from greatest to least. While
103// the users within any particular bag are sorted in an entirely arbitrary order, the overall score
104// decreases as successive bags are reached. This means that it is valid to truncate
105// iteration at any desired point; only those ids in the lowest bag can be excluded. This
106// satisfies both the desire for fairness and the requirement for efficiency.
107pub struct List<T: Config<I>, I: 'static = ()>(PhantomData<(T, I)>);
108
109impl<T: Config<I>, I: 'static> List<T, I> {
110	/// Remove all data associated with the list from storage.
111	///
112	/// ## WARNING
113	///
114	/// this function should generally not be used in production as it could lead to a very large
115	/// number of storage accesses.
116	pub(crate) fn unsafe_clear() {
117		#[allow(deprecated)]
118		crate::ListBags::<T, I>::remove_all(None);
119		#[allow(deprecated)]
120		crate::ListNodes::<T, I>::remove_all();
121	}
122
123	/// Regenerate all of the data from the given ids.
124	///
125	/// WARNING: this is expensive and should only ever be performed when the list needs to be
126	/// generated from scratch. Care needs to be taken to ensure
127	///
128	/// This may or may not need to be called at genesis as well, based on the configuration of the
129	/// pallet using this `List`.
130	///
131	/// Returns the number of ids migrated.
132	pub fn unsafe_regenerate(
133		all: impl IntoIterator<Item = T::AccountId>,
134		score_of: Box<dyn Fn(&T::AccountId) -> Option<T::Score>>,
135	) -> u32 {
136		// NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_regenerate.
137		// I.e. because it can lead to many storage accesses.
138		// So it is ok to call it as caller must ensure the conditions.
139		Self::unsafe_clear();
140		Self::insert_many(all, score_of)
141	}
142
143	/// Migrate the list from one set of thresholds to another.
144	///
145	/// This should only be called as part of an intentional migration; it's fairly expensive.
146	///
147	/// Returns the number of accounts affected.
148	///
149	/// Preconditions:
150	///
151	/// - `old_thresholds` is the previous list of thresholds.
152	/// - All `bag_upper` currently in storage are members of `old_thresholds`.
153	/// - `T::BagThresholds` has already been updated and is the new set of thresholds.
154	///
155	/// Postconditions:
156	///
157	/// - All `bag_upper` currently in storage are members of `T::BagThresholds`.
158	/// - No id is changed unless required to by the difference between the old threshold list and
159	///   the new.
160	/// - ids whose bags change at all are implicitly rebagged into the appropriate bag in the new
161	///   threshold set.
162	#[allow(dead_code)]
163	pub fn migrate(old_thresholds: &[T::Score]) -> u32 {
164		let new_thresholds = T::BagThresholds::get();
165		if new_thresholds == old_thresholds {
166			return 0;
167		}
168
169		// we can't check all preconditions, but we can check one
170		debug_assert!(
171			crate::ListBags::<T, I>::iter()
172				.all(|(threshold, _)| old_thresholds.contains(&threshold)),
173			"not all `bag_upper` currently in storage are members of `old_thresholds`",
174		);
175		debug_assert!(
176			crate::ListNodes::<T, I>::iter()
177				.all(|(_, node)| old_thresholds.contains(&node.bag_upper)),
178			"not all `node.bag_upper` currently in storage are members of `old_thresholds`",
179		);
180
181		let old_set: BTreeSet<_> = old_thresholds.iter().copied().collect();
182		let new_set: BTreeSet<_> = new_thresholds.iter().copied().collect();
183
184		// accounts that need to be rebagged
185		let mut affected_accounts = BTreeSet::new();
186		// track affected old bags to make sure we only iterate them once
187		let mut affected_old_bags = BTreeSet::new();
188
189		let new_bags = new_set.difference(&old_set).copied();
190		// a new bag means that all accounts previously using the old bag's threshold must now
191		// be rebagged
192		for inserted_bag in new_bags {
193			let affected_bag = {
194				// this recreates `notional_bag_for` logic, but with the old thresholds.
195				let idx = old_thresholds.partition_point(|&threshold| inserted_bag > threshold);
196				old_thresholds.get(idx).copied().unwrap_or_else(T::Score::max_value)
197			};
198			if !affected_old_bags.insert(affected_bag) {
199				// If the previous threshold list was [10, 20], and we insert [3, 5], then there's
200				// no point iterating through bag 10 twice.
201				continue;
202			}
203
204			if let Some(bag) = Bag::<T, I>::get(affected_bag) {
205				affected_accounts.extend(bag.iter().map(|node| node.id));
206			}
207		}
208
209		let removed_bags = old_set.difference(&new_set).copied();
210		// a removed bag means that all members of that bag must be rebagged
211		for removed_bag in removed_bags.clone() {
212			if !affected_old_bags.insert(removed_bag) {
213				continue;
214			}
215
216			if let Some(bag) = Bag::<T, I>::get(removed_bag) {
217				affected_accounts.extend(bag.iter().map(|node| node.id));
218			}
219		}
220
221		// migrate the voters whose bag has changed
222		let num_affected = affected_accounts.len() as u32;
223		let score_of = T::ScoreProvider::score;
224		let _removed = Self::remove_many(&affected_accounts);
225		debug_assert_eq!(_removed, num_affected);
226		let _inserted = Self::insert_many(affected_accounts.into_iter(), score_of);
227		debug_assert_eq!(_inserted, num_affected);
228
229		// we couldn't previously remove the old bags because both insertion and removal assume that
230		// it's always safe to add a bag if it's not present. Now that that's sorted, we can get rid
231		// of them.
232		//
233		// it's pretty cheap to iterate this again, because both sets are in-memory and require no
234		// lookups.
235		for removed_bag in removed_bags {
236			debug_assert!(
237				!crate::ListNodes::<T, I>::iter().any(|(_, node)| node.bag_upper == removed_bag),
238				"no id should be present in a removed bag",
239			);
240			crate::ListBags::<T, I>::remove(removed_bag);
241		}
242
243		num_affected
244	}
245
246	/// Returns `true` if the list contains `id`, otherwise returns `false`.
247	pub(crate) fn contains(id: &T::AccountId) -> bool {
248		crate::ListNodes::<T, I>::contains_key(id)
249	}
250
251	/// Get the score of the given node,
252	pub fn get_score(id: &T::AccountId) -> Result<T::Score, ListError> {
253		Node::<T, I>::get(id).map(|node| node.score()).ok_or(ListError::NodeNotFound)
254	}
255
256	/// Iterate over all nodes in all bags in the list.
257	///
258	/// Full iteration can be expensive; it's recommended to limit the number of items with
259	/// `.take(n)`, or call `.next()` one by one.
260	pub(crate) fn iter() -> impl Iterator<Item = Node<T, I>> {
261		// We need a touch of special handling here: because we permit `T::BagThresholds` to
262		// omit the final bound, we need to ensure that we explicitly include that threshold in the
263		// list.
264		//
265		// It's important to retain the ability to omit the final bound because it makes tests much
266		// easier; they can just configure `type BagThresholds = ()`.
267		let thresholds = T::BagThresholds::get();
268		let iter = thresholds.iter().copied();
269		let iter: Box<dyn Iterator<Item = T::Score>> = if thresholds.last() ==
270			Some(&T::Score::max_value())
271		{
272			// in the event that they included it, we can just pass the iterator through unchanged.
273			Box::new(iter.rev())
274		} else {
275			// otherwise, insert it here.
276			Box::new(iter.chain(iter::once(T::Score::max_value())).rev())
277		};
278
279		iter.filter_map(Bag::get).flat_map(|bag| bag.iter())
280	}
281
282	/// Same as `iter`, but we start from a specific node.
283	///
284	/// All items after this node are returned, excluding `start` itself.
285	pub(crate) fn iter_from(
286		start: &T::AccountId,
287	) -> Result<impl Iterator<Item = Node<T, I>>, ListError> {
288		// We chain two iterators:
289		// 1. from the given `start` till the end of the bag
290		// 2. all the bags that come after `start`'s bag.
291
292		let start_node = Node::<T, I>::get(start).ok_or(ListError::NodeNotFound)?;
293		let start_node_upper = start_node.bag_upper;
294		let start_bag = core::iter::successors(start_node.next(), |prev| prev.next());
295
296		let thresholds = T::BagThresholds::get();
297		let idx = thresholds.partition_point(|&threshold| start_node_upper > threshold);
298		let leftover_bags = thresholds
299			.into_iter()
300			.take(idx)
301			.copied()
302			.rev()
303			.filter_map(Bag::get)
304			.flat_map(|bag| bag.iter());
305
306		crate::log!(
307			debug,
308			"starting to iterate from {:?}, who's bag is {:?}, and there are {:?} leftover bags",
309			&start,
310			start_node_upper,
311			idx
312		);
313		Ok(start_bag.chain(leftover_bags))
314	}
315
316	/// Insert several ids into the appropriate bags in the list. Continues with insertions
317	/// if duplicates are detected.
318	///
319	/// Returns the final count of number of ids inserted.
320	fn insert_many(
321		ids: impl IntoIterator<Item = T::AccountId>,
322		score_of: impl Fn(&T::AccountId) -> Option<T::Score>,
323	) -> u32 {
324		let mut count = 0;
325		ids.into_iter().for_each(|v| {
326			if let Some(score) = score_of(&v) {
327				if Self::insert(v, score).is_ok() {
328					count += 1;
329				}
330			} else {
331				// nada
332			}
333		});
334
335		count
336	}
337
338	/// Insert a new id into the appropriate bag in the list.
339	///
340	/// Returns an error if the list already contains `id`.
341	pub(crate) fn insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> {
342		if Self::contains(&id) {
343			return Err(ListError::Duplicate);
344		}
345
346		let bag_score = notional_bag_for::<T, I>(score);
347		let mut bag = Bag::<T, I>::get_or_make(bag_score);
348		// unchecked insertion is okay; we just got the correct `notional_bag_for`.
349		bag.insert_unchecked(id.clone(), score);
350
351		// new inserts are always the tail, so we must write the bag.
352		bag.put();
353
354		crate::log!(
355			trace,
356			"inserted {:?} with score {:?} into bag {:?}, new count is {}",
357			id,
358			score,
359			bag_score,
360			crate::ListNodes::<T, I>::count(),
361		);
362
363		Ok(())
364	}
365
366	/// Remove an id from the list, returning an error if `id` does not exists.
367	pub(crate) fn remove(id: &T::AccountId) -> Result<(), ListError> {
368		if !Self::contains(id) {
369			return Err(ListError::NodeNotFound);
370		}
371		let _ = Self::remove_many(core::iter::once(id));
372		Ok(())
373	}
374
375	/// Remove many ids from the list.
376	///
377	/// This is more efficient than repeated calls to `Self::remove`.
378	///
379	/// Returns the final count of number of ids removed.
380	fn remove_many<'a>(ids: impl IntoIterator<Item = &'a T::AccountId>) -> u32 {
381		let mut bags = BTreeMap::new();
382		let mut count = 0;
383
384		for id in ids.into_iter() {
385			let node = match Node::<T, I>::get(id) {
386				Some(node) => node,
387				None => continue,
388			};
389			count += 1;
390
391			if !node.is_terminal() {
392				// this node is not a head or a tail and thus the bag does not need to be updated
393				node.excise()
394			} else {
395				// this node is a head or tail, so the bag needs to be updated
396				let bag = bags
397					.entry(node.bag_upper)
398					.or_insert_with(|| Bag::<T, I>::get_or_make(node.bag_upper));
399				// node.bag_upper must be correct, therefore this bag will contain this node.
400				bag.remove_node_unchecked(&node);
401			}
402
403			// now get rid of the node itself
404			node.remove_from_storage_unchecked()
405		}
406
407		for (_, bag) in bags {
408			bag.put();
409		}
410
411		count
412	}
413
414	/// Update a node's position in the list.
415	///
416	/// If the node was in the correct bag, no effect. If the node was in the incorrect bag, they
417	/// are moved into the correct bag.
418	///
419	/// Returns `Some((old_idx, new_idx))` if the node moved, otherwise `None`. In both cases, the
420	/// node's score is written to the `score` field. Thus, this is not a noop, even if `None`.
421	///
422	/// This operation is somewhat more efficient than simply calling [`self.remove`] followed by
423	/// [`self.insert`]. However, given large quantities of nodes to move, it may be more efficient
424	/// to call [`self.remove_many`] followed by [`self.insert_many`].
425	pub(crate) fn update_position_for(
426		mut node: Node<T, I>,
427		new_score: T::Score,
428	) -> Option<(T::Score, T::Score)> {
429		node.score = new_score;
430		if node.is_misplaced(new_score) {
431			let old_bag_upper = node.bag_upper;
432
433			if !node.is_terminal() {
434				// this node is not a head or a tail, so we can just cut it out of the list. update
435				// and put the prev and next of this node, we do `node.put` inside `insert_note`.
436				node.excise();
437			} else if let Some(mut bag) = Bag::<T, I>::get(node.bag_upper) {
438				// this is a head or tail, so the bag must be updated.
439				bag.remove_node_unchecked(&node);
440				bag.put();
441			} else {
442				frame_support::defensive!(
443					"Node did not have a bag; BagsList is in an inconsistent state"
444				);
445			}
446
447			// put the node into the appropriate new bag.
448			let new_bag_upper = notional_bag_for::<T, I>(new_score);
449			let mut bag = Bag::<T, I>::get_or_make(new_bag_upper);
450			// prev, next, and bag_upper of the node are updated inside `insert_node`, also
451			// `node.put` is in there.
452			bag.insert_node_unchecked(node);
453			bag.put();
454
455			Some((old_bag_upper, new_bag_upper))
456		} else {
457			// just write the new score.
458			node.put();
459			None
460		}
461	}
462
463	/// Put `heavier_id` to the position directly in front of `lighter_id`. Both ids must be in the
464	/// same bag and the `score_of` `lighter_id` must be less than that of `heavier_id`.
465	pub(crate) fn put_in_front_of(
466		lighter_id: &T::AccountId,
467		heavier_id: &T::AccountId,
468	) -> Result<(), ListError> {
469		let lighter_node = Node::<T, I>::get(&lighter_id).ok_or(ListError::NodeNotFound)?;
470		let heavier_node = Node::<T, I>::get(&heavier_id).ok_or(ListError::NodeNotFound)?;
471
472		ensure!(lighter_node.bag_upper == heavier_node.bag_upper, ListError::NotInSameBag);
473
474		// this is the most expensive check, so we do it last.
475		ensure!(
476			T::ScoreProvider::score(&heavier_id) > T::ScoreProvider::score(&lighter_id),
477			ListError::NotHeavier
478		);
479
480		// remove the heavier node from this list. Note that this removes the node from storage and
481		// decrements the node counter.
482		let _ =
483			Self::remove(&heavier_id).defensive_proof("both nodes have been checked to exist; qed");
484
485		// re-fetch `lighter_node` from storage since it may have been updated when `heavier_node`
486		// was removed.
487		let lighter_node =
488			Node::<T, I>::get(lighter_id).defensive_ok_or_else(|| ListError::NodeNotFound)?;
489
490		// insert `heavier_node` directly in front of `lighter_node`. This will update both nodes
491		// in storage and update the node counter.
492		Self::insert_at_unchecked(lighter_node, heavier_node);
493
494		Ok(())
495	}
496
497	/// Insert `node` directly in front of `at`.
498	///
499	/// WARNINGS:
500	/// - this is a naive function in that it does not check if `node` belongs to the same bag as
501	/// `at`. It is expected that the call site will check preconditions.
502	/// - this will panic if `at.bag_upper` is not a bag that already exists in storage.
503	fn insert_at_unchecked(mut at: Node<T, I>, mut node: Node<T, I>) {
504		// connect `node` to its new `prev`.
505		node.prev = at.prev.clone();
506		if let Some(mut prev) = at.prev() {
507			prev.next = Some(node.id().clone());
508			prev.put()
509		}
510
511		// connect `node` and `at`.
512		node.next = Some(at.id().clone());
513		at.prev = Some(node.id().clone());
514
515		if node.is_terminal() {
516			// `node` is the new head, so we make sure the bag is updated. Note,
517			// since `node` is always in front of `at` we know that 1) there is always at least 2
518			// nodes in the bag, and 2) only `node` could be the head and only `at` could be the
519			// tail.
520			let mut bag = Bag::<T, I>::get(at.bag_upper)
521				.expect("given nodes must always have a valid bag. qed.");
522
523			if node.prev == None {
524				bag.head = Some(node.id().clone())
525			}
526
527			bag.put()
528		};
529
530		// write the updated nodes to storage.
531		at.put();
532		node.put();
533	}
534
535	/// Check the internal state of the list.
536	///
537	/// This should be called from the call-site, whenever one of the mutating apis (e.g. `insert`)
538	/// is being used, after all other staking data (such as counter) has been updated. It checks:
539	///
540	/// * there are no duplicate ids,
541	/// * length of this list is in sync with `ListNodes::count()`,
542	/// * and sanity-checks all bags and nodes. This will cascade down all the checks and makes sure
543	/// all bags and nodes are checked per *any* update to `List`.
544	#[cfg(any(test, feature = "try-runtime", feature = "fuzz"))]
545	pub(crate) fn do_try_state() -> Result<(), TryRuntimeError> {
546		let mut seen_in_list = BTreeSet::new();
547		ensure!(
548			Self::iter().map(|node| node.id).all(|id| seen_in_list.insert(id)),
549			"duplicate identified"
550		);
551
552		let iter_count = Self::iter().count() as u32;
553		let stored_count = crate::ListNodes::<T, I>::count();
554		let nodes_count = crate::ListNodes::<T, I>::iter().count() as u32;
555		ensure!(iter_count == stored_count, "iter_count != stored_count");
556		ensure!(stored_count == nodes_count, "stored_count != nodes_count");
557
558		crate::log!(trace, "count of nodes: {}", stored_count);
559
560		let active_bags = {
561			let thresholds = T::BagThresholds::get().iter().copied();
562			let thresholds: Vec<T::Score> =
563				if thresholds.clone().last() == Some(T::Score::max_value()) {
564					// in the event that they included it, we don't need to make any changes
565					thresholds.collect()
566				} else {
567					// otherwise, insert it here.
568					thresholds.chain(iter::once(T::Score::max_value())).collect()
569				};
570			thresholds.into_iter().filter_map(|t| Bag::<T, I>::get(t))
571		};
572
573		// build map of bags and the corresponding nodes to avoid multiple lookups
574		let mut bags_map = BTreeMap::<T::Score, Vec<T::AccountId>>::new();
575
576		active_bags.clone().try_for_each(|b| {
577			bags_map.insert(
578				b.bag_upper,
579				b.iter().map(|n: Node<T, I>| n.id().clone()).collect::<Vec<_>>(),
580			);
581			b.do_try_state()
582		})?;
583
584		let nodes_in_bags_count =
585			active_bags.clone().fold(0u32, |acc, cur| acc + cur.iter().count() as u32);
586		ensure!(nodes_count == nodes_in_bags_count, "stored_count != nodes_in_bags_count");
587
588		crate::log!(trace, "count of active bags {}", active_bags.count());
589
590		// check that all nodes are sane. We check the `ListNodes` storage item directly in case we
591		// have some "stale" nodes that are not in a bag.
592		for (_id, node) in crate::ListNodes::<T, I>::iter() {
593			// check that the node is in the correct bag
594			let expected_bag = bags_map
595				.get(&node.bag_upper)
596				.ok_or("bag not found for the node in active bags")?;
597			frame_support::ensure!(expected_bag.contains(node.id()), "node not found in the bag");
598
599			// verify node state
600			node.do_try_state()?
601		}
602
603		Ok(())
604	}
605
606	/// Returns the nodes of all non-empty bags. For testing and benchmarks.
607	#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
608	#[allow(dead_code)]
609	pub(crate) fn get_bags() -> Vec<(T::Score, Vec<T::AccountId>)> {
610		use frame_support::traits::Get as _;
611
612		let thresholds = T::BagThresholds::get();
613		let iter = thresholds.iter().copied();
614		let iter: Box<dyn Iterator<Item = T::Score>> = if thresholds.last() ==
615			Some(&T::Score::max_value())
616		{
617			// in the event that they included it, we can just pass the iterator through unchanged.
618			Box::new(iter)
619		} else {
620			// otherwise, insert it here.
621			Box::new(iter.chain(core::iter::once(T::Score::max_value())))
622		};
623
624		iter.filter_map(|t| {
625			Bag::<T, I>::get(t)
626				.map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::<Vec<_>>()))
627		})
628		.collect::<Vec<_>>()
629	}
630}
631
632/// A Bag is a doubly-linked list of ids, where each id is mapped to a [`Node`].
633///
634/// Note that we maintain both head and tail pointers. While it would be possible to get away with
635/// maintaining only a head pointer and cons-ing elements onto the front of the list, it's more
636/// desirable to ensure that there is some element of first-come, first-serve to the list's
637/// iteration so that there's no incentive to churn ids positioning to improve the chances of
638/// appearing within the ids set.
639#[derive(
640	DefaultNoBound,
641	Encode,
642	Decode,
643	MaxEncodedLen,
644	TypeInfo,
645	DebugNoBound,
646	CloneNoBound,
647	PartialEqNoBound,
648	EqNoBound,
649)]
650#[codec(mel_bound())]
651#[scale_info(skip_type_params(T, I))]
652pub struct Bag<T: Config<I>, I: 'static = ()> {
653	pub head: Option<T::AccountId>,
654	pub tail: Option<T::AccountId>,
655
656	#[codec(skip)]
657	pub bag_upper: T::Score,
658	#[codec(skip)]
659	pub _phantom: PhantomData<I>,
660}
661
662impl<T: Config<I>, I: 'static> Bag<T, I> {
663	#[cfg(test)]
664	pub(crate) fn new(
665		head: Option<T::AccountId>,
666		tail: Option<T::AccountId>,
667		bag_upper: T::Score,
668	) -> Self {
669		Self { head, tail, bag_upper, _phantom: PhantomData }
670	}
671
672	/// Get a bag by its upper score.
673	pub(crate) fn get(bag_upper: T::Score) -> Option<Bag<T, I>> {
674		crate::ListBags::<T, I>::try_get(bag_upper).ok().map(|mut bag| {
675			bag.bag_upper = bag_upper;
676			bag
677		})
678	}
679
680	/// Get a bag by its upper score or make it, appropriately initialized. Does not check if
681	/// if `bag_upper` is a valid threshold.
682	fn get_or_make(bag_upper: T::Score) -> Bag<T, I> {
683		Self::get(bag_upper).unwrap_or(Bag { bag_upper, ..Default::default() })
684	}
685
686	/// `True` if self is empty.
687	fn is_empty(&self) -> bool {
688		self.head.is_none() && self.tail.is_none()
689	}
690
691	/// Put the bag back into storage.
692	fn put(self) {
693		if self.is_empty() {
694			crate::ListBags::<T, I>::remove(self.bag_upper);
695		} else {
696			crate::ListBags::<T, I>::insert(self.bag_upper, self);
697		}
698	}
699
700	/// Get the head node in this bag.
701	fn head(&self) -> Option<Node<T, I>> {
702		self.head.as_ref().and_then(|id| Node::get(id))
703	}
704
705	/// Get the tail node in this bag.
706	fn tail(&self) -> Option<Node<T, I>> {
707		self.tail.as_ref().and_then(|id| Node::get(id))
708	}
709
710	/// Iterate over the nodes in this bag.
711	pub(crate) fn iter(&self) -> impl Iterator<Item = Node<T, I>> {
712		core::iter::successors(self.head(), |prev| prev.next())
713	}
714
715	/// Insert a new id into this bag.
716	///
717	/// This is private on purpose because it's naive: it doesn't check whether this is the
718	/// appropriate bag for this id at all. Generally, use [`List::insert`] instead.
719	///
720	/// Storage note: this modifies storage, but only for the nodes. You still need to call
721	/// `self.put()` after use.
722	fn insert_unchecked(&mut self, id: T::AccountId, score: T::Score) {
723		// insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long
724		// as this bag is the correct one, we're good. All calls to this must come after getting the
725		// correct [`notional_bag_for`].
726		self.insert_node_unchecked(Node::<T, I> {
727			id,
728			prev: None,
729			next: None,
730			bag_upper: Zero::zero(),
731			score,
732			_phantom: PhantomData,
733		});
734	}
735
736	/// Insert a node into this bag.
737	///
738	/// This is private on purpose because it's naive; it doesn't check whether this is the
739	/// appropriate bag for this node at all. Generally, use [`List::insert`] instead.
740	///
741	/// Storage note: this modifies storage, but only for the node. You still need to call
742	/// `self.put()` after use.
743	fn insert_node_unchecked(&mut self, mut node: Node<T, I>) {
744		if let Some(tail) = &self.tail {
745			if *tail == node.id {
746				// this should never happen, but this check prevents one path to a worst case
747				// infinite loop.
748				defensive!("system logic error: inserting a node who has the id of tail");
749				return;
750			};
751		}
752
753		// re-set the `bag_upper`. Regardless of whatever the node had previously, now it is going
754		// to be `self.bag_upper`.
755		node.bag_upper = self.bag_upper;
756
757		let id = node.id.clone();
758		// update this node now, treating it as the new tail.
759		node.prev = self.tail.clone();
760		node.next = None;
761		node.put();
762
763		// update the previous tail.
764		if let Some(mut old_tail) = self.tail() {
765			old_tail.next = Some(id.clone());
766			old_tail.put();
767		}
768		self.tail = Some(id.clone());
769
770		// ensure head exist. This is only set when the length of the bag is just 1, i.e. if this is
771		// the first insertion into the bag. In this case, both head and tail should point to the
772		// same node.
773		if self.head.is_none() {
774			self.head = Some(id);
775			debug_assert!(self.iter().count() == 1);
776		}
777	}
778
779	/// Remove a node from this bag.
780	///
781	/// This is private on purpose because it doesn't check whether this bag contains the node in
782	/// the first place. Generally, use [`List::remove`] instead, similar to `insert_unchecked`.
783	///
784	/// Storage note: this modifies storage, but only for adjacent nodes. You still need to call
785	/// `self.put()` and `ListNodes::remove(id)` to update storage for the bag and `node`.
786	fn remove_node_unchecked(&mut self, node: &Node<T, I>) {
787		// reassign neighboring nodes.
788		node.excise();
789
790		// clear the bag head/tail pointers as necessary.
791		if self.tail.as_ref() == Some(&node.id) {
792			self.tail = node.prev.clone();
793		}
794		if self.head.as_ref() == Some(&node.id) {
795			self.head = node.next.clone();
796		}
797	}
798
799	/// Check the internal state of the bag.
800	///
801	/// Should be called by the call-site, after any mutating operation on a bag. The call site of
802	/// this struct is always `List`.
803	///
804	/// * Ensures head has no prev.
805	/// * Ensures tail has no next.
806	/// * Ensures there are no loops, traversal from head to tail is correct.
807	#[cfg(any(test, feature = "try-runtime", feature = "fuzz"))]
808	fn do_try_state(&self) -> Result<(), TryRuntimeError> {
809		frame_support::ensure!(
810			self.head()
811				.map(|head| head.prev().is_none())
812				// if there is no head, then there must not be a tail, meaning that the bag is
813				// empty.
814				.unwrap_or_else(|| self.tail.is_none()),
815			"head has a prev"
816		);
817
818		frame_support::ensure!(
819			self.tail()
820				.map(|tail| tail.next().is_none())
821				// if there is no tail, then there must not be a head, meaning that the bag is
822				// empty.
823				.unwrap_or_else(|| self.head.is_none()),
824			"tail has a next"
825		);
826
827		let mut seen_in_bag = BTreeSet::new();
828		frame_support::ensure!(
829			self.iter()
830				.map(|node| node.id)
831				// each voter is only seen once, thus there is no cycle within a bag
832				.all(|voter| seen_in_bag.insert(voter)),
833			"duplicate found in bag"
834		);
835
836		Ok(())
837	}
838
839	/// Iterate over the nodes in this bag (public for tests).
840	#[cfg(feature = "std")]
841	#[allow(dead_code)]
842	pub fn std_iter(&self) -> impl Iterator<Item = Node<T, I>> {
843		core::iter::successors(self.head(), |prev| prev.next())
844	}
845}
846
847/// A Node is the fundamental element comprising the doubly-linked list described by `Bag`.
848#[derive(
849	Encode, Decode, MaxEncodedLen, TypeInfo, CloneNoBound, PartialEqNoBound, EqNoBound, DebugNoBound,
850)]
851#[codec(mel_bound())]
852#[scale_info(skip_type_params(T, I))]
853pub struct Node<T: Config<I>, I: 'static = ()> {
854	pub id: T::AccountId,
855	pub prev: Option<T::AccountId>,
856	pub next: Option<T::AccountId>,
857	pub bag_upper: T::Score,
858	pub score: T::Score,
859	#[codec(skip)]
860	pub _phantom: PhantomData<I>,
861}
862
863impl<T: Config<I>, I: 'static> Node<T, I> {
864	/// Get a node by id.
865	pub fn get(id: &T::AccountId) -> Option<Node<T, I>> {
866		crate::ListNodes::<T, I>::try_get(id).ok()
867	}
868
869	/// Put the node back into storage.
870	fn put(self) {
871		crate::ListNodes::<T, I>::insert(self.id.clone(), self);
872	}
873
874	/// Update neighboring nodes to point to reach other.
875	///
876	/// Only updates storage for adjacent nodes, but not `self`; so the user may need to call
877	/// `self.put`.
878	fn excise(&self) {
879		// Update previous node.
880		if let Some(mut prev) = self.prev() {
881			prev.next = self.next.clone();
882			prev.put();
883		}
884		// Update next self.
885		if let Some(mut next) = self.next() {
886			next.prev = self.prev.clone();
887			next.put();
888		}
889	}
890
891	/// This is a naive function that removes a node from the `ListNodes` storage item.
892	///
893	/// It is naive because it does not check if the node has first been removed from its bag.
894	fn remove_from_storage_unchecked(&self) {
895		crate::ListNodes::<T, I>::remove(&self.id)
896	}
897
898	/// Get the previous node in the bag.
899	fn prev(&self) -> Option<Node<T, I>> {
900		self.prev.as_ref().and_then(|id| Node::get(id))
901	}
902
903	/// Get the next node in the bag.
904	fn next(&self) -> Option<Node<T, I>> {
905		self.next.as_ref().and_then(|id| Node::get(id))
906	}
907
908	/// `true` when this voter is in the wrong bag.
909	pub fn is_misplaced(&self, current_score: T::Score) -> bool {
910		notional_bag_for::<T, I>(current_score) != self.bag_upper
911	}
912
913	/// `true` when this voter is a bag head or tail.
914	fn is_terminal(&self) -> bool {
915		self.prev.is_none() || self.next.is_none()
916	}
917
918	/// Get the underlying voter.
919	pub(crate) fn id(&self) -> &T::AccountId {
920		&self.id
921	}
922
923	/// Get the current vote weight of the node.
924	pub(crate) fn score(&self) -> T::Score {
925		self.score
926	}
927
928	/// Get the underlying voter (public fo tests).
929	#[cfg(feature = "std")]
930	#[allow(dead_code)]
931	pub fn std_id(&self) -> &T::AccountId {
932		&self.id
933	}
934
935	#[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))]
936	pub fn set_score(&mut self, s: T::Score) {
937		self.score = s
938	}
939
940	/// The bag this nodes belongs to (public for benchmarks).
941	#[cfg(feature = "runtime-benchmarks")]
942	#[allow(dead_code)]
943	pub fn bag_upper(&self) -> T::Score {
944		self.bag_upper
945	}
946
947	#[cfg(any(test, feature = "try-runtime", feature = "fuzz"))]
948	fn do_try_state(&self) -> Result<(), TryRuntimeError> {
949		let expected_bag = Bag::<T, I>::get(self.bag_upper).ok_or("bag not found for node")?;
950		let id = self.id();
951
952		let non_terminal_check = !self.is_terminal() &&
953			expected_bag.head.as_ref() != Some(id) &&
954			expected_bag.tail.as_ref() != Some(id);
955		let terminal_check =
956			expected_bag.head.as_ref() == Some(id) || expected_bag.tail.as_ref() == Some(id);
957		frame_support::ensure!(
958			non_terminal_check || terminal_check,
959			"a terminal node is neither its bag head or tail"
960		);
961
962		Ok(())
963	}
964}