use crate::{
node::{Node, NodeId, NodeRef, NodeRole},
ExtendedBalance, IdentifierT, StakedAssignment,
};
use sp_arithmetic::traits::{Bounded, Zero};
use sp_std::{
collections::btree_map::{BTreeMap, Entry::*},
prelude::*,
};
type Map<A> = BTreeMap<(A, A), A>;
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
}
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()
}
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)
};
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]);
}
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();
for assignment_index in 0..assignments.len() {
let who = assignments[assignment_index].who.clone();
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();
if assignments[assignment_index]
.distribution
.iter()
.filter(|(t, _)| *t == v1 || *t == v2)
.count() != 2
{
continue
}
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'");
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();
debug_assert!(other_votes_count <= 2);
if other_votes_count < 2 {
*other_who = who.clone();
continue
} else if other_votes_count == 2 {
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
}
if other_cycle_votes[0].0 != who_cycle_votes[0].0 {
other_cycle_votes.swap(0, 1);
}
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)>>();
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 {
let sibling_index = 1 - min_index;
increase_indices.push(sibling_index);
decrease_indices.push(sibling_index + 2);
increase_indices.push(min_index + 2);
} else {
let sibling_index = 3 - min_index % 2;
increase_indices.push(sibling_index);
decrease_indices.push(sibling_index - 2);
increase_indices.push(min_index - 2);
}
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() };
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;
}
});
});
});
let who_removed = remove_indices.iter().any(|i| *i < 2usize);
let other_removed = remove_indices.into_iter().any(|i| i >= 2usize);
match (who_removed, other_removed) {
(false, true) => {
*other_who = who.clone();
},
(true, false) => {
},
(true, true) => {
entry.remove();
},
(false, false) => {
panic!("Duplicate voter (or other corrupt input).");
},
}
}
},
}
}
}
num_changed
}
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();
for assignment_index in 0..assignments.len() {
let voter = assignments[assignment_index].who.clone();
let mut dist_index = 0;
loop {
let maybe_dist = assignments[assignment_index].distribution.get(dist_index);
if maybe_dist.is_none() {
break
}
let (target, _) = maybe_dist.expect("Value checked to be some").clone();
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);
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();
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) => { },
};
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 {
merge(voter_root_path, target_root_path);
dist_index += 1;
} else {
let common_count = trailing_common(&voter_root_path, &target_root_path);
debug_assert!(common_count > 0);
debug_assert!(common_count <= voter_root_path.len());
debug_assert!(common_count <= target_root_path.len());
let cycle = target_root_path
.iter()
.take(target_root_path.len().saturating_sub(common_count).saturating_add(1))
.cloned()
.chain(
voter_root_path
.iter()
.take(voter_root_path.len().saturating_sub(common_count))
.rev()
.cloned(),
)
.collect::<Vec<NodeRef<A>>>();
debug_assert_eq!(cycle.len() % 2, 0);
let mut min_value: ExtendedBalance = Bounded::max_value();
let mut maybe_min_target: Option<A> = None;
let mut maybe_min_voter: Option<A> = None;
let mut maybe_min_index: Option<usize> = None;
let mut maybe_min_direction: Option<u32> = None;
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 {
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;
maybe_min_target = Some(next.clone());
maybe_min_voter = Some(current.clone());
maybe_min_index = Some(i);
maybe_min_direction = Some(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;
maybe_min_target = Some(prev.clone());
maybe_min_voter = Some(current.clone());
maybe_min_index = Some(i);
maybe_min_direction = Some(0);
}
})
});
}
}
let (min_value, min_target, min_voter, min_index, min_direction) =
match (
min_value,
maybe_min_target,
maybe_min_voter,
maybe_min_index,
maybe_min_direction,
) {
(
min_value,
Some(min_target),
Some(min_voter),
Some(min_index),
Some(min_direction),
) => (min_value, min_target, min_voter, min_index, min_direction),
_ => {
sp_runtime::print("UNREACHABLE code reached in `reduce` algorithm. This must be a bug.");
break
},
};
let target_chunk = target_root_path.len() - common_count;
let min_chain_in_voter = (min_index + min_direction as usize) > target_chunk;
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 target_ass_index == assignment_index {
should_inc_counter = false
}
ass.distribution.remove(idx);
num_changed += 1;
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 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;
}
});
});
}
}
let should_reorg = !(min_index == (cycle.len() - 1) && min_direction == 1);
if should_reorg {
let min_edge = vec![min_voter, min_target];
if min_chain_in_voter {
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 {
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);
}
}
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);
}
}
if should_inc_counter {
dist_index += 1;
}
}
}
}
num_changed
}
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() {
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);
assert_eq!(e.borrow().clone().parent.unwrap().borrow().id.who, 4u32); }
#[test]
fn merge_with_len_one() {
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);
assert_eq!(f.borrow().clone().parent.unwrap().borrow().id.who, 4u32); }
#[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)] },
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),] },
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),
(10, 1),
(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),
(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);
}
}