mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 19:38:02 +00:00
e3e651f72c
* Happy new year Updates the copyright years and fixes wrong license headers. * Fix the template * Split HEADER into HEADER-APACHE & HEADER-GPL
1079 lines
31 KiB
Rust
1079 lines
31 KiB
Rust
// This file is part of Substrate.
|
||
|
||
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
|
||
// SPDX-License-Identifier: Apache-2.0
|
||
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
//! Rust implementation of the Phragmén reduce algorithm. This can be used by any off chain
|
||
//! application to reduce cycles from the edge assignment, which will result in smaller size.
|
||
//!
|
||
//! ### Notions
|
||
//! - `m`: size of the committee to elect.
|
||
//! - `k`: maximum allowed votes (16 as of this writing).
|
||
//! - `nv ∈ E` means that nominator `n ∈ N` supports the election of candidate `v ∈ V`.
|
||
//! - A valid solution consists of a tuple `(S, W)` , where `S ⊆ V` is a committee of m validators,
|
||
//! and `W : E → R ≥ 0` is an edge weight vector which describes how the budget of each nominator
|
||
//! n is fractionally assigned to n 's elected neighbors.
|
||
//! - `E_w := { e ∈ E : w_e > 0 }`.
|
||
//!
|
||
//! ### Algorithm overview
|
||
//!
|
||
//! > We consider the input edge weight vector `w` as a directed flow over `E_w` , where the flow in
|
||
//! > each edge is directed from the nominator to the validator. We build `w′` from `w` by removing
|
||
//! > **circulations** to cancel out the flow over as many edges as possible, while preserving flow
|
||
//! > demands over all vertices and without reverting the flow direction over any edge. As long as
|
||
//! > there is a cycle, we can remove an additional circulation and eliminate at least one new edge
|
||
//! > from `E_w′` . This shows that the algorithm always progresses and will eventually finish with
|
||
//! > an acyclic edge support. We keep a data structure that represents a forest of rooted trees,
|
||
//! > which is initialized as a collection of singletons – one per vertex – and to which edges in
|
||
//! > `E_w` are added one by one, causing the trees to merge. Whenever a new edge creates a cycle,
|
||
//! > we detect it and destroy it by removing a circulation. We also run a pre-computation which is
|
||
//! > designed to detect and remove cycles of length four exclusively. This pre-computation is
|
||
//! > optional, and if we skip it then the running time becomes `O (|E_w| ⋅ m), so the
|
||
//! > pre-computation makes sense only if `m >> k` and `|E_w| >> m^2`.
|
||
//!
|
||
//! ### Resources:
|
||
//!
|
||
//! 1. <https://hackmd.io/JOn9x98iS0e0DPWQ87zGWg?view>
|
||
|
||
use crate::node::{Node, NodeId, NodeRef, NodeRole};
|
||
use crate::{ExtendedBalance, IdentifierT, StakedAssignment};
|
||
use sp_arithmetic::traits::{Bounded, Zero};
|
||
use sp_std::{
|
||
collections::btree_map::{BTreeMap, Entry::*},
|
||
vec,
|
||
prelude::*,
|
||
};
|
||
|
||
/// Map type used for reduce_4. Can be easily swapped with HashMap.
|
||
type Map<A> = BTreeMap<(A, A), A>;
|
||
|
||
/// Returns all combinations of size two in the collection `input` with no repetition.
|
||
fn combinations_2<T: Clone>(input: &[T]) -> Vec<(T, T)> {
|
||
let n = input.len();
|
||
if n < 2 {
|
||
return Default::default();
|
||
}
|
||
|
||
let mut comb = Vec::with_capacity(n * (n - 1) / 2);
|
||
for i in 0..n {
|
||
for j in i + 1..n {
|
||
comb.push((input[i].clone(), input[j].clone()))
|
||
}
|
||
}
|
||
comb
|
||
}
|
||
|
||
/// Returns the count of trailing common elements in two slices.
|
||
pub(crate) fn trailing_common<T: Eq>(t1: &[T], t2: &[T]) -> usize {
|
||
t1.iter().rev().zip(t2.iter().rev()).take_while(|e| e.0 == e.1).count()
|
||
}
|
||
|
||
/// Merges two parent roots as described by the reduce algorithm.
|
||
fn merge<A: IdentifierT>(voter_root_path: Vec<NodeRef<A>>, target_root_path: Vec<NodeRef<A>>) {
|
||
let (shorter_path, longer_path) = if voter_root_path.len() <= target_root_path.len() {
|
||
(voter_root_path, target_root_path)
|
||
} else {
|
||
(target_root_path, voter_root_path)
|
||
};
|
||
|
||
// iterate from last to beginning, skipping the first one. This asserts that
|
||
// indexing is always correct.
|
||
shorter_path
|
||
.iter()
|
||
.zip(shorter_path.iter().skip(1))
|
||
.for_each(|(voter, next)| Node::set_parent_of(&next, &voter));
|
||
Node::set_parent_of(&shorter_path[0], &longer_path[0]);
|
||
}
|
||
|
||
/// Reduce only redundant edges with cycle length of 4.
|
||
///
|
||
/// Returns the number of edges removed.
|
||
///
|
||
/// It is strictly assumed that the `who` attribute of all provided assignments are unique. The
|
||
/// result will most likely be corrupt otherwise.
|
||
///
|
||
/// O(|E_w| ⋅ k).
|
||
fn reduce_4<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 {
|
||
let mut combination_map: Map<A> = Map::new();
|
||
let mut num_changed: u32 = Zero::zero();
|
||
|
||
// we have to use the old fashioned loops here with manual indexing. Borrowing assignments will
|
||
// not work since then there is NO way to mutate it inside.
|
||
for assignment_index in 0..assignments.len() {
|
||
let who = assignments[assignment_index].who.clone();
|
||
|
||
// all combinations for this particular voter
|
||
let distribution_ids = &assignments[assignment_index]
|
||
.distribution
|
||
.iter()
|
||
.map(|(t, _p)| t.clone())
|
||
.collect::<Vec<A>>();
|
||
let candidate_combinations = combinations_2(distribution_ids);
|
||
|
||
for (v1, v2) in candidate_combinations {
|
||
match combination_map.entry((v1.clone(), v2.clone())) {
|
||
Vacant(entry) => {
|
||
entry.insert(who.clone());
|
||
}
|
||
Occupied(mut entry) => {
|
||
let other_who = entry.get_mut();
|
||
|
||
// double check if who is still voting for this pair. If not, it means that this
|
||
// pair is no longer valid and must have been removed in previous rounds. The
|
||
// reason for this is subtle; candidate_combinations is created once while the
|
||
// inner loop might remove some edges. Note that if count() > 2, the we have
|
||
// duplicates.
|
||
if assignments[assignment_index]
|
||
.distribution
|
||
.iter()
|
||
.filter(|(t, _)| *t == v1 || *t == v2)
|
||
.count() != 2
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// check if other_who voted for the same pair v1, v2.
|
||
let maybe_other_assignments = assignments.iter().find(|a| a.who == *other_who);
|
||
if maybe_other_assignments.is_none() {
|
||
continue;
|
||
}
|
||
let other_assignment =
|
||
maybe_other_assignments.expect("value is checked to be 'Some'");
|
||
|
||
// Collect potential cycle votes
|
||
let mut other_cycle_votes = other_assignment
|
||
.distribution
|
||
.iter()
|
||
.filter_map(|(t, w)| {
|
||
if *t == v1 || *t == v2 {
|
||
Some((t.clone(), *w))
|
||
} else {
|
||
None
|
||
}
|
||
})
|
||
.collect::<Vec<(A, ExtendedBalance)>>();
|
||
|
||
let other_votes_count = other_cycle_votes.len();
|
||
|
||
// If the length is more than 2, then we have identified duplicates. For now, we
|
||
// just skip. Later on we can early exit and stop processing this data since it
|
||
// is corrupt anyhow.
|
||
debug_assert!(other_votes_count <= 2);
|
||
|
||
if other_votes_count < 2 {
|
||
// This is not a cycle. Replace and continue.
|
||
*other_who = who.clone();
|
||
continue;
|
||
} else if other_votes_count == 2 {
|
||
// This is a cycle.
|
||
let mut who_cycle_votes: Vec<(A, ExtendedBalance)> = Vec::with_capacity(2);
|
||
assignments[assignment_index]
|
||
.distribution
|
||
.iter()
|
||
.for_each(|(t, w)| {
|
||
if *t == v1 || *t == v2 {
|
||
who_cycle_votes.push((t.clone(), *w));
|
||
}
|
||
});
|
||
|
||
if who_cycle_votes.len() != 2 {
|
||
continue;
|
||
}
|
||
|
||
// Align the targets similarly. This helps with the circulation below.
|
||
if other_cycle_votes[0].0 != who_cycle_votes[0].0 {
|
||
other_cycle_votes.swap(0, 1);
|
||
}
|
||
|
||
// Find min
|
||
let mut min_value: ExtendedBalance = Bounded::max_value();
|
||
let mut min_index: usize = 0;
|
||
let cycle = who_cycle_votes
|
||
.iter()
|
||
.chain(other_cycle_votes.iter())
|
||
.enumerate()
|
||
.map(|(index, (t, w))| {
|
||
if *w <= min_value {
|
||
min_value = *w;
|
||
min_index = index;
|
||
}
|
||
(t.clone(), *w)
|
||
})
|
||
.collect::<Vec<(A, ExtendedBalance)>>();
|
||
|
||
// min was in the first part of the chained iters
|
||
let mut increase_indices: Vec<usize> = Vec::new();
|
||
let mut decrease_indices: Vec<usize> = Vec::new();
|
||
decrease_indices.push(min_index);
|
||
if min_index < 2 {
|
||
// min_index == 0 => sibling_index <- 1
|
||
// min_index == 1 => sibling_index <- 0
|
||
let sibling_index = 1 - min_index;
|
||
increase_indices.push(sibling_index);
|
||
// valid because the two chained sections of `cycle` are aligned;
|
||
// index [0, 2] are both voting for v1 or both v2. Same goes for [1, 3].
|
||
decrease_indices.push(sibling_index + 2);
|
||
increase_indices.push(min_index + 2);
|
||
} else {
|
||
// min_index == 2 => sibling_index <- 3
|
||
// min_index == 3 => sibling_index <- 2
|
||
let sibling_index = 3 - min_index % 2;
|
||
increase_indices.push(sibling_index);
|
||
// valid because the two chained sections of `cycle` are aligned;
|
||
// index [0, 2] are both voting for v1 or both v2. Same goes for [1, 3].
|
||
decrease_indices.push(sibling_index - 2);
|
||
increase_indices.push(min_index - 2);
|
||
}
|
||
|
||
// apply changes
|
||
let mut remove_indices: Vec<usize> = Vec::with_capacity(1);
|
||
increase_indices.into_iter().for_each(|i| {
|
||
let voter = if i < 2 {
|
||
who.clone()
|
||
} else {
|
||
other_who.clone()
|
||
};
|
||
// Note: so this is pretty ambiguous. We should only look for one
|
||
// assignment that meets this criteria and if we find multiple then that
|
||
// is a corrupt input. Same goes for the next block.
|
||
assignments
|
||
.iter_mut()
|
||
.filter(|a| a.who == voter)
|
||
.for_each(|ass| {
|
||
ass.distribution
|
||
.iter_mut()
|
||
.position(|(t, _)| *t == cycle[i].0)
|
||
.map(|idx| {
|
||
let next_value =
|
||
ass.distribution[idx].1.saturating_add(min_value);
|
||
ass.distribution[idx].1 = next_value;
|
||
});
|
||
});
|
||
});
|
||
decrease_indices.into_iter().for_each(|i| {
|
||
let voter = if i < 2 {
|
||
who.clone()
|
||
} else {
|
||
other_who.clone()
|
||
};
|
||
assignments
|
||
.iter_mut()
|
||
.filter(|a| a.who == voter)
|
||
.for_each(|ass| {
|
||
ass.distribution
|
||
.iter_mut()
|
||
.position(|(t, _)| *t == cycle[i].0)
|
||
.map(|idx| {
|
||
let next_value =
|
||
ass.distribution[idx].1.saturating_sub(min_value);
|
||
if next_value.is_zero() {
|
||
ass.distribution.remove(idx);
|
||
remove_indices.push(i);
|
||
num_changed += 1;
|
||
} else {
|
||
ass.distribution[idx].1 = next_value;
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// remove either one of them.
|
||
let who_removed = remove_indices.iter().find(|i| **i < 2usize).is_some();
|
||
let other_removed =
|
||
remove_indices.into_iter().find(|i| *i >= 2usize).is_some();
|
||
|
||
match (who_removed, other_removed) {
|
||
(false, true) => {
|
||
*other_who = who.clone();
|
||
}
|
||
(true, false) => {
|
||
// nothing, other_who can stay there.
|
||
}
|
||
(true, true) => {
|
||
// remove and don't replace
|
||
entry.remove();
|
||
}
|
||
(false, false) => {
|
||
// Neither of the edges was removed? impossible.
|
||
panic!("Duplicate voter (or other corrupt input).");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
num_changed
|
||
}
|
||
|
||
/// Reduce redundant edges from the edge weight graph, with all possible length.
|
||
///
|
||
/// To get the best performance, this should be called after `reduce_4()`.
|
||
///
|
||
/// Returns the number of edges removed.
|
||
///
|
||
/// It is strictly assumed that the `who` attribute of all provided assignments are unique. The
|
||
/// result will most likely be corrupt otherwise.
|
||
///
|
||
/// O(|Ew| ⋅ m)
|
||
fn reduce_all<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 {
|
||
let mut num_changed: u32 = Zero::zero();
|
||
let mut tree: BTreeMap<NodeId<A>, NodeRef<A>> = BTreeMap::new();
|
||
|
||
// NOTE: This code can heavily use an index cache. Looking up a pair of (voter, target) in the
|
||
// assignments happens numerous times and and we can save time. For now it is written as such
|
||
// because abstracting some of this code into a function/closure is super hard due to borrow
|
||
// checks (and most likely needs unsafe code at the end). For now I will keep it as it and
|
||
// refactor later.
|
||
|
||
// a flat iterator of (voter, target) over all pairs of votes. Similar to reduce_4, we loop
|
||
// without borrowing.
|
||
for assignment_index in 0..assignments.len() {
|
||
let voter = assignments[assignment_index].who.clone();
|
||
|
||
let mut dist_index = 0;
|
||
loop {
|
||
// A distribution could have been removed. We don't know for sure. Hence, we check.
|
||
let maybe_dist = assignments[assignment_index].distribution.get(dist_index);
|
||
if maybe_dist.is_none() {
|
||
// The rest of this loop is moot.
|
||
break;
|
||
}
|
||
let (target, _) = maybe_dist.expect("Value checked to be some").clone();
|
||
|
||
// store if they existed already.
|
||
let voter_id = NodeId::from(voter.clone(), NodeRole::Voter);
|
||
let target_id = NodeId::from(target.clone(), NodeRole::Target);
|
||
let voter_exists = tree.contains_key(&voter_id);
|
||
let target_exists = tree.contains_key(&target_id);
|
||
|
||
// create both.
|
||
let voter_node = tree
|
||
.entry(voter_id.clone())
|
||
.or_insert_with(|| Node::new(voter_id).into_ref())
|
||
.clone();
|
||
let target_node = tree
|
||
.entry(target_id.clone())
|
||
.or_insert_with(|| Node::new(target_id).into_ref())
|
||
.clone();
|
||
|
||
// If one exists but the other one doesn't, or if both does not, then set the existing
|
||
// one as the parent of the non-existing one and move on. Else, continue with the rest
|
||
// of the code.
|
||
match (voter_exists, target_exists) {
|
||
(false, false) => {
|
||
Node::set_parent_of(&target_node, &voter_node);
|
||
dist_index += 1;
|
||
continue;
|
||
}
|
||
(false, true) => {
|
||
Node::set_parent_of(&voter_node, &target_node);
|
||
dist_index += 1;
|
||
continue;
|
||
}
|
||
(true, false) => {
|
||
Node::set_parent_of(&target_node, &voter_node);
|
||
dist_index += 1;
|
||
continue;
|
||
}
|
||
(true, true) => { /* don't continue and execute the rest */ }
|
||
};
|
||
|
||
let (voter_root, voter_root_path) = Node::root(&voter_node);
|
||
let (target_root, target_root_path) = Node::root(&target_node);
|
||
|
||
if voter_root != target_root {
|
||
// swap
|
||
merge(voter_root_path, target_root_path);
|
||
dist_index += 1;
|
||
} else {
|
||
// find common and cycle.
|
||
let common_count = trailing_common(&voter_root_path, &target_root_path);
|
||
|
||
// because roots are the same.
|
||
#[cfg(feature = "std")]
|
||
debug_assert_eq!(
|
||
target_root_path.last().unwrap(),
|
||
voter_root_path.last().unwrap()
|
||
);
|
||
debug_assert!(common_count > 0);
|
||
|
||
// cycle part of each path will be `path[path.len() - common_count - 1 : 0]`
|
||
// NOTE: the order of chaining is important! it is always build from [target, ...,
|
||
// voter]
|
||
let cycle = target_root_path
|
||
.iter()
|
||
.take(target_root_path.len() - common_count + 1)
|
||
.cloned()
|
||
.chain(
|
||
voter_root_path
|
||
.iter()
|
||
.take(voter_root_path.len() - common_count)
|
||
.rev()
|
||
.cloned(),
|
||
)
|
||
.collect::<Vec<NodeRef<A>>>();
|
||
|
||
// a cycle's length shall always be multiple of two.
|
||
#[cfg(feature = "std")]
|
||
debug_assert_eq!(cycle.len() % 2, 0);
|
||
|
||
// find minimum of cycle.
|
||
let mut min_value: ExtendedBalance = Bounded::max_value();
|
||
// The voter and the target pair that create the min edge.
|
||
let mut min_target: A = Default::default();
|
||
let mut min_voter: A = Default::default();
|
||
// The index of the min in opaque cycle list.
|
||
let mut min_index = 0usize;
|
||
// 1 -> next // 0 -> prev
|
||
let mut min_direction = 0u32;
|
||
// helpers
|
||
let next_index = |i| {
|
||
if i < (cycle.len() - 1) {
|
||
i + 1
|
||
} else {
|
||
0
|
||
}
|
||
};
|
||
let prev_index = |i| {
|
||
if i > 0 {
|
||
i - 1
|
||
} else {
|
||
cycle.len() - 1
|
||
}
|
||
};
|
||
for i in 0..cycle.len() {
|
||
if cycle[i].borrow().id.role == NodeRole::Voter {
|
||
// NOTE: sadly way too many clones since I don't want to make A: Copy
|
||
let current = cycle[i].borrow().id.who.clone();
|
||
let next = cycle[next_index(i)].borrow().id.who.clone();
|
||
let prev = cycle[prev_index(i)].borrow().id.who.clone();
|
||
assignments.iter().find(|a| a.who == current).map(|ass| {
|
||
ass.distribution.iter().find(|d| d.0 == next).map(|(_, w)| {
|
||
if *w < min_value {
|
||
min_value = *w;
|
||
min_target = next.clone();
|
||
min_voter = current.clone();
|
||
min_index = i;
|
||
min_direction = 1;
|
||
}
|
||
})
|
||
});
|
||
assignments.iter().find(|a| a.who == current).map(|ass| {
|
||
ass.distribution.iter().find(|d| d.0 == prev).map(|(_, w)| {
|
||
if *w < min_value {
|
||
min_value = *w;
|
||
min_target = prev.clone();
|
||
min_voter = current.clone();
|
||
min_index = i;
|
||
min_direction = 0;
|
||
}
|
||
})
|
||
});
|
||
}
|
||
}
|
||
|
||
// if the min edge is in the voter's sub-chain.
|
||
// [target, ..., X, Y, ... voter]
|
||
let target_chunk = target_root_path.len() - common_count;
|
||
let min_chain_in_voter = (min_index + min_direction as usize) > target_chunk;
|
||
|
||
// walk over the cycle and update the weights
|
||
let mut should_inc_counter = true;
|
||
let start_operation_add = ((min_index % 2) + min_direction as usize) % 2 == 1;
|
||
let mut additional_removed = Vec::new();
|
||
for i in 0..cycle.len() {
|
||
let current = cycle[i].borrow();
|
||
if current.id.role == NodeRole::Voter {
|
||
let prev = cycle[prev_index(i)].borrow();
|
||
assignments
|
||
.iter_mut()
|
||
.enumerate()
|
||
.filter(|(_, a)| a.who == current.id.who)
|
||
.for_each(|(target_ass_index, ass)| {
|
||
ass.distribution
|
||
.iter_mut()
|
||
.position(|(t, _)| *t == prev.id.who)
|
||
.map(|idx| {
|
||
let next_value = if i % 2 == 0 {
|
||
if start_operation_add {
|
||
ass.distribution[idx].1.saturating_add(min_value)
|
||
} else {
|
||
ass.distribution[idx].1.saturating_sub(min_value)
|
||
}
|
||
} else {
|
||
if start_operation_add {
|
||
ass.distribution[idx].1.saturating_sub(min_value)
|
||
} else {
|
||
ass.distribution[idx].1.saturating_add(min_value)
|
||
}
|
||
};
|
||
|
||
if next_value.is_zero() {
|
||
// if the removed edge is from the current assignment, dis_index
|
||
// should NOT be increased.
|
||
if target_ass_index == assignment_index {
|
||
should_inc_counter = false
|
||
}
|
||
ass.distribution.remove(idx);
|
||
num_changed += 1;
|
||
// only add if this is not the min itself.
|
||
if !(i == min_index && min_direction == 0) {
|
||
additional_removed.push((
|
||
cycle[i].clone(),
|
||
cycle[prev_index(i)].clone(),
|
||
));
|
||
}
|
||
} else {
|
||
ass.distribution[idx].1 = next_value;
|
||
}
|
||
});
|
||
});
|
||
|
||
let next = cycle[next_index(i)].borrow();
|
||
assignments
|
||
.iter_mut()
|
||
.enumerate()
|
||
.filter(|(_, a)| a.who == current.id.who)
|
||
.for_each(|(target_ass_index, ass)| {
|
||
ass.distribution
|
||
.iter_mut()
|
||
.position(|(t, _)| *t == next.id.who)
|
||
.map(|idx| {
|
||
let next_value = if i % 2 == 0 {
|
||
if start_operation_add {
|
||
ass.distribution[idx].1.saturating_sub(min_value)
|
||
} else {
|
||
ass.distribution[idx].1.saturating_add(min_value)
|
||
}
|
||
} else {
|
||
if start_operation_add {
|
||
ass.distribution[idx].1.saturating_add(min_value)
|
||
} else {
|
||
ass.distribution[idx].1.saturating_sub(min_value)
|
||
}
|
||
};
|
||
|
||
if next_value.is_zero() {
|
||
// if the removed edge is from the current assignment, dis_index
|
||
// should NOT be increased.
|
||
if target_ass_index == assignment_index {
|
||
should_inc_counter = false
|
||
}
|
||
ass.distribution.remove(idx);
|
||
num_changed += 1;
|
||
if !(i == min_index && min_direction == 1) {
|
||
additional_removed.push((
|
||
cycle[i].clone(),
|
||
cycle[next_index(i)].clone(),
|
||
));
|
||
}
|
||
} else {
|
||
ass.distribution[idx].1 = next_value;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// don't do anything if the edge removed itself. This is always the first and last
|
||
// element
|
||
let should_reorg = !(min_index == (cycle.len() - 1) && min_direction == 1);
|
||
|
||
// re-org.
|
||
if should_reorg {
|
||
let min_edge = vec![min_voter, min_target];
|
||
if min_chain_in_voter {
|
||
// NOTE: safe; voter_root_path is always bigger than 1 element.
|
||
for i in 0..voter_root_path.len() - 1 {
|
||
let current = voter_root_path[i].clone().borrow().id.who.clone();
|
||
let next = voter_root_path[i + 1].clone().borrow().id.who.clone();
|
||
if min_edge.contains(¤t) && min_edge.contains(&next) {
|
||
break;
|
||
}
|
||
Node::set_parent_of(&voter_root_path[i + 1], &voter_root_path[i]);
|
||
}
|
||
Node::set_parent_of(&voter_node, &target_node);
|
||
} else {
|
||
// NOTE: safe; target_root_path is always bigger than 1 element.
|
||
for i in 0..target_root_path.len() - 1 {
|
||
let current = target_root_path[i].clone().borrow().id.who.clone();
|
||
let next = target_root_path[i + 1].clone().borrow().id.who.clone();
|
||
if min_edge.contains(¤t) && min_edge.contains(&next) {
|
||
break;
|
||
}
|
||
Node::set_parent_of(&target_root_path[i + 1], &target_root_path[i]);
|
||
}
|
||
Node::set_parent_of(&target_node, &voter_node);
|
||
}
|
||
}
|
||
|
||
// remove every other node which has collapsed to zero
|
||
for (r1, r2) in additional_removed {
|
||
if Node::is_parent_of(&r1, &r2) {
|
||
Node::remove_parent(&r1);
|
||
} else if Node::is_parent_of(&r2, &r1) {
|
||
Node::remove_parent(&r2);
|
||
}
|
||
}
|
||
|
||
// increment the counter if needed.
|
||
if should_inc_counter {
|
||
dist_index += 1;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
num_changed
|
||
}
|
||
|
||
/// Reduce the given [`Vec<StakedAssignment<IdentifierT>>`]. This removes redundant edges from
|
||
/// without changing the overall backing of any of the elected candidates.
|
||
///
|
||
/// Returns the number of edges removed.
|
||
///
|
||
/// IMPORTANT: It is strictly assumed that the `who` attribute of all provided assignments are
|
||
/// unique. The result will most likely be corrupt otherwise. Furthermore, if the _distribution
|
||
/// vector_ contains duplicate ids, only the first instance is ever updates.
|
||
///
|
||
/// O(min{ |Ew| ⋅ k + m3 , |Ew| ⋅ m })
|
||
pub fn reduce<A: IdentifierT>(assignments: &mut Vec<StakedAssignment<A>>) -> u32 where {
|
||
let mut num_changed = reduce_4(assignments);
|
||
num_changed += reduce_all(assignments);
|
||
num_changed
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn merging_works() {
|
||
// D <-- A <-- B <-- C
|
||
//
|
||
// F <-- E
|
||
let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref();
|
||
let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref();
|
||
let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref();
|
||
let c = Node::new(NodeId::from(4, NodeRole::Target)).into_ref();
|
||
let e = Node::new(NodeId::from(5, NodeRole::Target)).into_ref();
|
||
let f = Node::new(NodeId::from(6, NodeRole::Target)).into_ref();
|
||
|
||
Node::set_parent_of(&c, &b);
|
||
Node::set_parent_of(&b, &a);
|
||
Node::set_parent_of(&a, &d);
|
||
Node::set_parent_of(&e, &f);
|
||
|
||
let path1 = vec![c.clone(), b.clone(), a.clone(), d.clone()];
|
||
let path2 = vec![e.clone(), f.clone()];
|
||
|
||
merge(path1, path2);
|
||
// D <-- A <-- B <-- C
|
||
// |
|
||
// F --> E --> -->
|
||
assert_eq!(e.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c
|
||
}
|
||
|
||
#[test]
|
||
fn merge_with_len_one() {
|
||
// D <-- A <-- B <-- C
|
||
//
|
||
// F <-- E
|
||
let d = Node::new(NodeId::from(1, NodeRole::Target)).into_ref();
|
||
let a = Node::new(NodeId::from(2, NodeRole::Target)).into_ref();
|
||
let b = Node::new(NodeId::from(3, NodeRole::Target)).into_ref();
|
||
let c = Node::new(NodeId::from(4, NodeRole::Target)).into_ref();
|
||
let f = Node::new(NodeId::from(6, NodeRole::Target)).into_ref();
|
||
|
||
Node::set_parent_of(&c, &b);
|
||
Node::set_parent_of(&b, &a);
|
||
Node::set_parent_of(&a, &d);
|
||
|
||
let path1 = vec![c.clone(), b.clone(), a.clone(), d.clone()];
|
||
let path2 = vec![f.clone()];
|
||
|
||
merge(path1, path2);
|
||
// D <-- A <-- B <-- C
|
||
// |
|
||
// F --> -->
|
||
assert_eq!(f.borrow().clone().parent.unwrap().borrow().id.who, 4u32); // c
|
||
}
|
||
|
||
#[test]
|
||
fn basic_reduce_4_cycle_works() {
|
||
use super::*;
|
||
|
||
let assignments = vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 25), (20, 75)],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 50), (20, 50)],
|
||
},
|
||
];
|
||
|
||
let mut new_assignments = assignments.clone();
|
||
let num_reduced = reduce_4(&mut new_assignments);
|
||
|
||
assert_eq!(num_reduced, 1);
|
||
assert_eq!(
|
||
new_assignments,
|
||
vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(20, 100),],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 75), (20, 25),],
|
||
},
|
||
],
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn basic_reduce_all_cycles_works() {
|
||
let mut assignments = vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 10)],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 15), (20, 5)],
|
||
},
|
||
StakedAssignment {
|
||
who: 3,
|
||
distribution: vec![(20, 15), (40, 15)],
|
||
},
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(20, 10), (30, 10), (40, 20)],
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![(20, 20), (30, 10), (40, 20)],
|
||
},
|
||
];
|
||
|
||
assert_eq!(3, reduce_all(&mut assignments));
|
||
|
||
assert_eq!(
|
||
assignments,
|
||
vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 10),]
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 15), (20, 5),],
|
||
},
|
||
StakedAssignment {
|
||
who: 3,
|
||
distribution: vec![(20, 30),],
|
||
},
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(40, 40),]
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![(20, 15), (30, 20), (40, 15),],
|
||
},
|
||
],
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn basic_reduce_works() {
|
||
let mut assignments = vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 10)],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 15), (20, 5)],
|
||
},
|
||
StakedAssignment {
|
||
who: 3,
|
||
distribution: vec![(20, 15), (40, 15)],
|
||
},
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(20, 10), (30, 10), (40, 20)],
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![(20, 20), (30, 10), (40, 20)],
|
||
},
|
||
];
|
||
|
||
assert_eq!(3, reduce(&mut assignments));
|
||
|
||
assert_eq!(
|
||
assignments,
|
||
vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 10),]
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 15), (20, 5),],
|
||
},
|
||
StakedAssignment {
|
||
who: 3,
|
||
distribution: vec![(20, 30),],
|
||
},
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(40, 40),]
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![(20, 15), (30, 20), (40, 15),],
|
||
},
|
||
],
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn should_deal_with_self_vote() {
|
||
let mut assignments = vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 10)],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 15), (20, 5)],
|
||
},
|
||
StakedAssignment {
|
||
who: 3,
|
||
distribution: vec![(20, 15), (40, 15)],
|
||
},
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(20, 10), (30, 10), (40, 20)],
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![(20, 20), (30, 10), (40, 20)],
|
||
},
|
||
// self vote from 10 and 20 to itself.
|
||
StakedAssignment {
|
||
who: 10,
|
||
distribution: vec![(10, 100)],
|
||
},
|
||
StakedAssignment {
|
||
who: 20,
|
||
distribution: vec![(20, 200)],
|
||
},
|
||
];
|
||
|
||
assert_eq!(3, reduce(&mut assignments));
|
||
|
||
assert_eq!(
|
||
assignments,
|
||
vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 10),]
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 15), (20, 5),],
|
||
},
|
||
StakedAssignment {
|
||
who: 3,
|
||
distribution: vec![(20, 30),],
|
||
},
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(40, 40),]
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![(20, 15), (30, 20), (40, 15),],
|
||
},
|
||
// should stay untouched.
|
||
StakedAssignment {
|
||
who: 10,
|
||
distribution: vec![(10, 100)]
|
||
},
|
||
StakedAssignment {
|
||
who: 20,
|
||
distribution: vec![(20, 200)]
|
||
},
|
||
],
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn reduce_3_common_votes_same_weight() {
|
||
let mut assignments = vec![
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![
|
||
(
|
||
1000000,
|
||
100,
|
||
),
|
||
(
|
||
1000002,
|
||
100,
|
||
),
|
||
(
|
||
1000004,
|
||
100,
|
||
),
|
||
],
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![
|
||
(
|
||
1000000,
|
||
100,
|
||
),
|
||
(
|
||
1000002,
|
||
100,
|
||
),
|
||
(
|
||
1000004,
|
||
100,
|
||
),
|
||
],
|
||
},
|
||
];
|
||
|
||
reduce_4(&mut assignments);
|
||
|
||
assert_eq!(
|
||
assignments,
|
||
vec![
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(1000000, 200,), (1000004, 100,),],
|
||
},
|
||
StakedAssignment {
|
||
who: 5,
|
||
distribution: vec![(1000002, 200,), (1000004, 100,),],
|
||
},
|
||
],
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
#[should_panic]
|
||
fn reduce_panics_on_duplicate_voter() {
|
||
let mut assignments = vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 10), (20, 10)],
|
||
},
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 15), (20, 5)],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(10, 15), (20, 15)],
|
||
},
|
||
];
|
||
|
||
reduce(&mut assignments);
|
||
}
|
||
|
||
#[test]
|
||
fn should_deal_with_duplicates_target() {
|
||
let mut assignments = vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 15), (20, 5)],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![
|
||
(10, 15),
|
||
(20, 15),
|
||
// duplicate
|
||
(10, 1),
|
||
// duplicate
|
||
(20, 1),
|
||
],
|
||
},
|
||
];
|
||
|
||
reduce(&mut assignments);
|
||
|
||
assert_eq!(
|
||
assignments,
|
||
vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(10, 20),],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![
|
||
(10, 10),
|
||
(20, 20),
|
||
// duplicate votes are silently ignored.
|
||
(10, 1),
|
||
(20, 1),
|
||
],
|
||
},
|
||
],
|
||
)
|
||
}
|
||
|
||
#[test]
|
||
fn bound_should_be_kept() {
|
||
let mut assignments = vec![
|
||
StakedAssignment {
|
||
who: 1,
|
||
distribution: vec![(103, 72), (101, 53), (100, 83), (102, 38)],
|
||
},
|
||
StakedAssignment {
|
||
who: 2,
|
||
distribution: vec![(103, 18), (101, 36), (102, 54), (100, 94)],
|
||
},
|
||
StakedAssignment {
|
||
who: 3,
|
||
distribution: vec![(100, 96), (101, 35), (102, 52), (103, 69)],
|
||
},
|
||
StakedAssignment {
|
||
who: 4,
|
||
distribution: vec![(102, 34), (100, 47), (103, 91), (101, 73)],
|
||
},
|
||
];
|
||
|
||
let winners = vec![103, 101, 100, 102];
|
||
|
||
let n = 4;
|
||
let m = winners.len() as u32;
|
||
let num_reduced = reduce_all(&mut assignments);
|
||
assert!(16 - num_reduced <= n + m);
|
||
}
|
||
}
|