use crate::hash::hash_node;
use crate::node::{is_null_node, is_null_node_ref, is_wrapper_node, Node, NodeRef};
use crate::Validation;
pub type TallyList = Vec<u32>;
pub fn combine_tally(
left: &Option<TallyList>,
right: &Option<TallyList>,
) -> Result<TallyList, String> {
if left.is_none() {
return Err("Left node does not contain a tally".to_string());
}
if right.is_none() {
return Ok(left.as_ref().unwrap().clone());
}
let left = left.as_ref().unwrap();
let right = right.as_ref().unwrap();
if left.len() != right.len() {
return Err(format!(
"Left tally length is not equal to right tally \
length ({} != {})",
left.len(),
right.len()
));
}
Ok(left.iter().zip(right.iter()).map(|(l, r)| l + r).collect())
}
pub fn has_one_vote(tally: &[u32]) -> bool {
tally.iter().sum::<u32>() == 1
}
pub fn pretty_print_tally(node: &NodeRef, level: usize) {
match node {
Some(n) => {
if is_null_node(n) {
println!("{:indent$}Ø", "", indent = level * 4);
return;
}
let prefix = match n.vote {
Some((v, _)) => [v[0], v[1]],
None => {
let h = hash_node(n, &Validation::Strict).unwrap().0;
[h.as_slice()[0], h.as_slice()[1]]
}
};
if is_wrapper_node(node) {
print!(
"{:indent$}{:x?}=>{:?} --> ",
"",
prefix,
tally_node(n, &Validation::Strict).unwrap().unwrap(),
indent = level * 4
);
pretty_print_tally(&n.left, 0);
assert!(n.right.is_none());
return;
}
pretty_print_tally(&n.right, level + 1);
println!(
"{:indent$}{:x?}=>{:?}",
"",
prefix,
tally_node(n, &Validation::Strict).unwrap().unwrap(),
indent = level * 4
);
pretty_print_tally(&n.left, level + 1);
}
None => {}
}
}
pub fn tally_node(node: &Node, v: &Validation) -> Result<Option<TallyList>, String> {
if is_null_node(node) {
return Ok(None);
}
if let Some((_, tally)) = &node.vote {
if node.left.is_some() || node.right.is_some() {
return Err("Leaf has a child".to_string());
}
if !matches!(v, Validation::Relaxed) && !has_one_vote(tally) {
return Err("Leaf casts more than 1 vote".to_string());
}
return Ok(Some(tally.to_vec()));
}
if node.right.is_none() || is_null_node_ref(&node.right) {
return tally_node_ref(&node.left, v);
}
Ok(Some(combine_tally(
&tally_node_ref(&node.left, v)?,
&tally_node_ref(&node.right, v)?,
)?))
}
pub fn tally_node_ref(node: &NodeRef, v: &Validation) -> Result<Option<TallyList>, String> {
if let Some(n) = node {
tally_node(n, v)
} else {
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generate::generate_tree;
#[test]
fn test_pretty_print() {
let head = generate_tree(
vec![
([0x11; 32], vec![1, 0, 0]),
([0x22; 32], vec![0, 0, 1]),
([0x33; 32], vec![0, 0, 1]),
([0x44; 32], vec![0, 0, 1]),
([0x55; 32], vec![0, 0, 1]),
],
false,
)
.unwrap();
pretty_print_tally(&head, 0);
assert_eq!(1, 1);
}
#[test]
fn test_combine_tally() {
assert_eq!(
Ok(vec![2, 0, 1]),
combine_tally(&Some(vec![1, 0, 0]), &Some(vec![1, 0, 1]))
);
assert_eq!(Ok(vec![2, 0]), combine_tally(&Some(vec![2, 0]), &None,));
assert!(combine_tally(&None, &Some(vec![2, 0])).is_err());
assert!(combine_tally(&Some(vec![0, 0, 1]), &Some(vec![2, 0])).is_err());
}
}