From 6aaa7997002e723df8d9bfd43344cf9269a8cfdd Mon Sep 17 00:00:00 2001 From: Ashwin Sekar Date: Tue, 15 Nov 2022 15:20:35 -0800 Subject: [PATCH] Add dump_node to update stake for heaviest subtrees Additionally refactor subtrees to store children as a hashset --- core/src/heaviest_subtree_fork_choice.rs | 370 ++++++++++++++++++++--- core/src/repair_generic_traversal.rs | 1 - core/src/repair_weight.rs | 2 +- core/src/repair_weighted_traversal.rs | 1 - core/src/replay_stage.rs | 8 +- core/src/tree_diff.rs | 8 +- 6 files changed, 333 insertions(+), 57 deletions(-) diff --git a/core/src/heaviest_subtree_fork_choice.rs b/core/src/heaviest_subtree_fork_choice.rs index f9c97f4adb893b..b2cb444fc755ea 100644 --- a/core/src/heaviest_subtree_fork_choice.rs +++ b/core/src/heaviest_subtree_fork_choice.rs @@ -16,7 +16,7 @@ use { }, std::{ borrow::Borrow, - collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque}, + collections::{hash_map::Entry, hash_set::Iter, BTreeMap, HashMap, HashSet, VecDeque}, sync::{Arc, RwLock}, time::Instant, }, @@ -95,12 +95,15 @@ struct ForkInfo { // have to be a direct child in `children` best_slot: SlotHashKey, parent: Option, - children: Vec, + children: HashSet, // The latest ancestor of this node that has been marked invalid. If the slot // itself is a duplicate, this is set to the slot itself. latest_invalid_ancestor: Option, // Set to true if this slot or a child node was duplicate confirmed. is_duplicate_confirmed: bool, + // Set to true if this slot is dumped. This indicates to not aggregate stake to parents, and to + // not chose this path for best slot. + is_dumped: bool, } impl ForkInfo { @@ -114,7 +117,7 @@ impl ForkInfo { /// Returns if the fork rooted at this node is included in fork choice fn is_candidate(&self) -> bool { - self.latest_invalid_ancestor.is_none() + self.latest_invalid_ancestor.is_none() && !self.is_dumped } fn is_duplicate_confirmed(&self) -> bool { @@ -290,7 +293,7 @@ impl HeaviestSubtreeForkChoice { pub fn set_root(&mut self, new_root: SlotHashKey) { // Remove everything reachable from `self.root` but not `new_root`, // as those are now unrooted. - let remove_set = self.subtree_diff(self.root, new_root); + let remove_set = (&*self).subtree_diff(self.root, new_root); for node_key in remove_set { self.fork_infos .remove(&node_key) @@ -318,10 +321,11 @@ impl HeaviestSubtreeForkChoice { stake_voted_subtree: root_info.stake_voted_subtree, // The `best_slot` of a leaf is itself best_slot: root_info.best_slot, - children: vec![self.root], + children: HashSet::from([self.root]), parent: None, latest_invalid_ancestor: None, is_duplicate_confirmed: root_info.is_duplicate_confirmed, + is_dumped: false, }; self.fork_infos.insert(root_parent, root_parent_info); self.root = root_parent; @@ -349,12 +353,13 @@ impl HeaviestSubtreeForkChoice { stake_voted_subtree: 0, // The `best_slot` of a leaf is itself best_slot: slot_hash_key, - children: vec![], + children: HashSet::new(), parent, latest_invalid_ancestor: parent_latest_invalid_ancestor, // If the parent is none, then this is the root, which implies this must // have reached the duplicate confirmed threshold is_duplicate_confirmed: parent.is_none(), + is_dumped: false, }); if parent.is_none() { @@ -368,7 +373,7 @@ impl HeaviestSubtreeForkChoice { .get_mut(&parent) .unwrap() .children - .push(slot_hash_key); + .insert(slot_hash_key); // Propagate leaf up the tree to any ancestors who considered the previous leaf // the `best_slot` @@ -410,6 +415,59 @@ impl HeaviestSubtreeForkChoice { .map(|(slot_hash, fork_info)| (slot_hash, fork_info.stake_voted_subtree)) } + /// Dump the node `slot_hash_key` and propagate the stake subtraction up to the root of the + /// tree. Children of `slot_hash_key` are implicitely dumped as well, as if we were able to + /// chain them to a defunct parent that implies that they are defunct as well (consistent with + /// bank forks). + /// + /// Returns all removed slots + pub fn dump_node(&mut self, slot_hash_key: &SlotHashKey) -> Vec { + let parent = { + let mut node_to_dump = self + .fork_infos + .get_mut(slot_hash_key) + .expect("Slot hash key must exist in tree"); + // Remove stake to be aggregated up the tree + node_to_dump.stake_voted_subtree = 0; + node_to_dump.stake_voted_at = 0; + node_to_dump.is_dumped = true; + node_to_dump.parent + }; + + let mut update_operations: UpdateOperations = BTreeMap::new(); + // Aggregate up to the root + self.insert_aggregate_operations(&mut update_operations, *slot_hash_key); + self.process_update_operations(update_operations); + + // Remove node + all children + let mut to_visit = vec![*slot_hash_key]; + let mut removed = Vec::new(); + + while !to_visit.is_empty() { + let current_node = to_visit.pop().unwrap(); + let current_fork_info = self + .fork_infos + .remove(¤t_node) + .expect("Node must exist in tree"); + + removed.push(current_node.0); + to_visit.extend(current_fork_info.children.into_iter()); + } + + if let Some(parent) = parent { + // Remove link from parent + let parent_fork_info = self + .fork_infos + .get_mut(&parent) + .expect("Parent must exist in fork infos"); + parent_fork_info.children.remove(slot_hash_key); + } else { + warn!("Dumped root of tree {:?}", slot_hash_key); + } + + removed + } + #[cfg(test)] pub fn ancestors(&self, start_slot_hash_key: SlotHashKey) -> Vec { AncestorIterator::new(start_slot_hash_key, &self.fork_infos).collect() @@ -905,16 +963,18 @@ impl HeaviestSubtreeForkChoice { } } -impl TreeDiff for HeaviestSubtreeForkChoice { +impl<'a> TreeDiff<'a> for &'a HeaviestSubtreeForkChoice { type TreeKey = SlotHashKey; + type ChildIter = Iter<'a, SlotHashKey>; + fn contains_slot(&self, slot_hash_key: &SlotHashKey) -> bool { self.fork_infos.contains_key(slot_hash_key) } - fn children(&self, slot_hash_key: &SlotHashKey) -> Option<&[SlotHashKey]> { + fn children(&self, slot_hash_key: &SlotHashKey) -> Option { self.fork_infos .get(slot_hash_key) - .map(|fork_info| &fork_info.children[..]) + .map(|fork_info| fork_info.children.iter()) } } @@ -985,7 +1045,8 @@ impl ForkChoice for HeaviestSubtreeForkChoice { let mut update_operations = UpdateOperations::default(); // Notify all the children of this node that a parent was marked as invalid - for child_hash_key in self.subtree_diff(*invalid_slot_hash_key, SlotHashKey::default()) + for child_hash_key in + (&*self).subtree_diff(*invalid_slot_hash_key, SlotHashKey::default()) { self.do_insert_aggregate_operation( &mut update_operations, @@ -1017,7 +1078,7 @@ impl ForkChoice for HeaviestSubtreeForkChoice { let mut update_operations = UpdateOperations::default(); // Notify all the children of this node that a parent was marked as valid - for child_hash_key in self.subtree_diff(*valid_slot_hash_key, SlotHashKey::default()) { + for child_hash_key in (&*self).subtree_diff(*valid_slot_hash_key, SlotHashKey::default()) { self.do_insert_aggregate_operation( &mut update_operations, &Some(UpdateOperation::MarkValid(valid_slot_hash_key.0)), @@ -1072,6 +1133,7 @@ mod test { use { super::*, crate::vote_simulator::VoteSimulator, + itertools::Itertools, solana_runtime::{bank::Bank, bank_utils}, solana_sdk::{hash::Hash, slot_history::SlotHistory}, std::{collections::HashSet, ops::Range}, @@ -1148,11 +1210,11 @@ mod test { 0 ); assert_eq!( - heaviest_subtree_fork_choice + (&heaviest_subtree_fork_choice) .children(&(2, Hash::default())) .unwrap() - .to_vec(), - vec![(3, Hash::default())] + .collect_vec(), + vec![&(3, Hash::default())] ); assert_eq!( heaviest_subtree_fork_choice @@ -1256,10 +1318,11 @@ mod test { let bank1_hash = bank_forks.read().unwrap().get(1).unwrap().hash(); assert_eq!( - heaviest_subtree_fork_choice + (&heaviest_subtree_fork_choice) .children(&(0, bank0_hash)) - .unwrap(), - &[(1, bank1_hash)] + .unwrap() + .collect_vec(), + &[&(1, bank1_hash)] ); assert_eq!( @@ -1269,10 +1332,11 @@ mod test { let bank2_hash = bank_forks.read().unwrap().get(2).unwrap().hash(); let bank3_hash = bank_forks.read().unwrap().get(3).unwrap().hash(); assert_eq!( - heaviest_subtree_fork_choice + (&heaviest_subtree_fork_choice) .children(&(1, bank1_hash)) - .unwrap(), - &[(2, bank2_hash), (3, bank3_hash)] + .unwrap() + .collect_vec(), + &[&(2, bank2_hash), &(3, bank3_hash)] ); assert_eq!( heaviest_subtree_fork_choice.parent(&(2, bank2_hash)), @@ -1280,14 +1344,15 @@ mod test { ); let bank4_hash = bank_forks.read().unwrap().get(4).unwrap().hash(); assert_eq!( - heaviest_subtree_fork_choice + (&heaviest_subtree_fork_choice) .children(&(2, bank2_hash)) - .unwrap(), - &[(4, bank4_hash)] + .unwrap() + .collect_vec(), + &[&(4, bank4_hash)] ); // Check parent and children of invalid hash don't exist let invalid_hash = Hash::new_unique(); - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .children(&(2, invalid_hash)) .is_none()); assert!(heaviest_subtree_fork_choice @@ -1298,17 +1363,19 @@ mod test { heaviest_subtree_fork_choice.parent(&(3, bank3_hash)), Some((1, bank1_hash)) ); - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .children(&(3, bank3_hash)) .unwrap() + .collect_vec() .is_empty()); assert_eq!( heaviest_subtree_fork_choice.parent(&(4, bank4_hash)), Some((2, bank2_hash)) ); - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .children(&(4, bank4_hash)) .unwrap() + .collect_vec() .is_empty()); } @@ -1511,10 +1578,11 @@ mod test { let child = (11, Hash::new_unique()); heaviest_subtree_fork_choice.add_new_leaf_slot(child, Some(duplicate_parent)); assert_eq!( - heaviest_subtree_fork_choice + (&heaviest_subtree_fork_choice) .children(&duplicate_parent) - .unwrap(), - &[child] + .unwrap() + .collect_vec(), + &[&child] ); assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), child); @@ -1523,9 +1591,10 @@ mod test { .iter() .chain(std::iter::once(&duplicate_leaves_descended_from_4[1])) { - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .children(duplicate_leaf) .unwrap() + .collect_vec() .is_empty(),); } @@ -1533,10 +1602,11 @@ mod test { heaviest_subtree_fork_choice .add_new_leaf_slot(duplicate_parent, Some((4, Hash::default()))); assert_eq!( - heaviest_subtree_fork_choice + (&heaviest_subtree_fork_choice) .children(&duplicate_parent) - .unwrap(), - &[child] + .unwrap() + .collect_vec(), + &[&child] ); assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), child); } @@ -2810,20 +2880,21 @@ mod test { let mut heaviest_subtree_fork_choice = setup_forks(); // Diff of same root is empty, no matter root, intermediate node, or leaf - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .subtree_diff((0, Hash::default()), (0, Hash::default())) .is_empty()); - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .subtree_diff((5, Hash::default()), (5, Hash::default())) .is_empty()); - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .subtree_diff((6, Hash::default()), (6, Hash::default())) .is_empty()); // The set reachable from slot 3, excluding subtree 1, is just everything // in slot 3 since subtree 1 is an ancestor assert_eq!( - heaviest_subtree_fork_choice.subtree_diff((3, Hash::default()), (1, Hash::default())), + (&heaviest_subtree_fork_choice) + .subtree_diff((3, Hash::default()), (1, Hash::default())), vec![3, 5, 6] .into_iter() .map(|s| (s, Hash::default())) @@ -2833,7 +2904,8 @@ mod test { // The set reachable from slot 1, excluding subtree 3, is just 1 and // the subtree at 2 assert_eq!( - heaviest_subtree_fork_choice.subtree_diff((1, Hash::default()), (3, Hash::default())), + (&heaviest_subtree_fork_choice) + .subtree_diff((1, Hash::default()), (3, Hash::default())), vec![1, 2, 4] .into_iter() .map(|s| (s, Hash::default())) @@ -2843,7 +2915,8 @@ mod test { // The set reachable from slot 1, excluding leaf 6, is just everything // except leaf 6 assert_eq!( - heaviest_subtree_fork_choice.subtree_diff((0, Hash::default()), (6, Hash::default())), + (&heaviest_subtree_fork_choice) + .subtree_diff((0, Hash::default()), (6, Hash::default())), vec![0, 1, 3, 5, 2, 4] .into_iter() .map(|s| (s, Hash::default())) @@ -2854,7 +2927,7 @@ mod test { heaviest_subtree_fork_choice.set_root((1, Hash::default())); // Zero no longer exists, set reachable from 0 is empty - assert!(heaviest_subtree_fork_choice + assert!((&heaviest_subtree_fork_choice) .subtree_diff((0, Hash::default()), (6, Hash::default())) .is_empty()); } @@ -3402,6 +3475,213 @@ mod test { } } + #[test] + fn test_dump_node_simple() { + let mut heaviest_subtree_fork_choice = setup_forks(); + let stake = 100; + let (bank, vote_pubkeys) = bank_utils::setup_bank_and_vote_pubkeys_for_tests(4, stake); + + let pubkey_votes: Vec<(Pubkey, SlotHashKey)> = vec![ + (vote_pubkeys[0], (3, Hash::default())), + (vote_pubkeys[1], (2, Hash::default())), + (vote_pubkeys[2], (6, Hash::default())), + (vote_pubkeys[3], (4, Hash::default())), + ]; + + heaviest_subtree_fork_choice.add_votes( + pubkey_votes.iter(), + bank.epoch_stakes_map(), + bank.epoch_schedule(), + ); + heaviest_subtree_fork_choice.dump_node(&(5, Hash::default())); + + assert_eq!( + 3 * stake, + heaviest_subtree_fork_choice + .stake_voted_subtree(&(0, Hash::default())) + .unwrap() + ); + assert_eq!( + 2 * stake, + heaviest_subtree_fork_choice + .stake_voted_subtree(&(2, Hash::default())) + .unwrap() + ); + assert_eq!( + 1 * stake, + heaviest_subtree_fork_choice + .stake_voted_subtree(&(3, Hash::default())) + .unwrap() + ); + assert_eq!( + None, + heaviest_subtree_fork_choice.stake_voted_subtree(&(5, Hash::default())) + ); + assert_eq!( + None, + heaviest_subtree_fork_choice.stake_voted_subtree(&(6, Hash::default())) + ); + } + + #[test] + fn test_dump_node_unvoted() { + let mut heaviest_subtree_fork_choice = setup_forks(); + let stake = 100; + let (bank, vote_pubkeys) = bank_utils::setup_bank_and_vote_pubkeys_for_tests(4, stake); + + let pubkey_votes: Vec<(Pubkey, SlotHashKey)> = vec![ + (vote_pubkeys[0], (3, Hash::default())), + (vote_pubkeys[1], (5, Hash::default())), + (vote_pubkeys[2], (6, Hash::default())), + (vote_pubkeys[3], (1, Hash::default())), + ]; + + heaviest_subtree_fork_choice.add_votes( + pubkey_votes.iter(), + bank.epoch_stakes_map(), + bank.epoch_schedule(), + ); + heaviest_subtree_fork_choice.dump_node(&(2, Hash::default())); + + assert_eq!( + 4 * stake, + heaviest_subtree_fork_choice + .stake_voted_subtree(&(0, Hash::default())) + .unwrap() + ); + assert_eq!( + 3 * stake, + heaviest_subtree_fork_choice + .stake_voted_subtree(&(3, Hash::default())) + .unwrap() + ); + assert_eq!( + None, + heaviest_subtree_fork_choice.stake_voted_subtree(&(2, Hash::default())) + ); + assert_eq!( + None, + heaviest_subtree_fork_choice.stake_voted_subtree(&(4, Hash::default())) + ); + } + + #[test] + fn test_dump_node_on_best_path() { + let mut heaviest_subtree_fork_choice = setup_forks(); + let stake = 100; + let (bank, vote_pubkeys) = bank_utils::setup_bank_and_vote_pubkeys_for_tests(4, stake); + + let pubkey_votes: Vec<(Pubkey, SlotHashKey)> = vec![ + (vote_pubkeys[0], (2, Hash::default())), + (vote_pubkeys[1], (3, Hash::default())), + (vote_pubkeys[2], (5, Hash::default())), + (vote_pubkeys[3], (6, Hash::default())), + ]; + + heaviest_subtree_fork_choice.add_votes( + pubkey_votes.iter(), + bank.epoch_stakes_map(), + bank.epoch_schedule(), + ); + + assert_eq!(6, heaviest_subtree_fork_choice.best_overall_slot().0); + + heaviest_subtree_fork_choice.dump_node(&(6, Hash::default())); + assert_eq!(5, heaviest_subtree_fork_choice.best_overall_slot().0); + + heaviest_subtree_fork_choice.dump_node(&(3, Hash::default())); + assert_eq!(4, heaviest_subtree_fork_choice.best_overall_slot().0); + + heaviest_subtree_fork_choice.dump_node(&(1, Hash::default())); + assert_eq!(0, heaviest_subtree_fork_choice.best_overall_slot().0); + } + + #[test] + fn test_dump_with_dups() { + let ( + mut heaviest_subtree_fork_choice, + duplicate_leaves_descended_from_4, + duplicate_leaves_descended_from_5, + ) = setup_duplicate_forks(); + + let stake = 10; + let num_validators = 3; + let (bank, vote_pubkeys) = + bank_utils::setup_bank_and_vote_pubkeys_for_tests(num_validators, stake); + + let pubkey_votes: Vec<(Pubkey, SlotHashKey)> = vec![ + (vote_pubkeys[0], duplicate_leaves_descended_from_4[0]), + (vote_pubkeys[1], duplicate_leaves_descended_from_4[1]), + (vote_pubkeys[2], duplicate_leaves_descended_from_5[0]), + ]; + + // duplicate_leaves_descended_from_4 are sorted, and fork choice will pick the smaller + // one in the event of a tie + let expected_best_slot_hash = duplicate_leaves_descended_from_4[0]; + assert_eq!( + heaviest_subtree_fork_choice.add_votes( + pubkey_votes.iter(), + bank.epoch_stakes_map(), + bank.epoch_schedule() + ), + expected_best_slot_hash + ); + + assert_eq!( + heaviest_subtree_fork_choice.best_overall_slot(), + expected_best_slot_hash + ); + heaviest_subtree_fork_choice.dump_node(&expected_best_slot_hash); + + assert_eq!( + heaviest_subtree_fork_choice.best_overall_slot(), + duplicate_leaves_descended_from_4[1] + ); + } + + #[test] + fn test_dump_subtree_with_dups() { + let ( + mut heaviest_subtree_fork_choice, + duplicate_leaves_descended_from_4, + duplicate_leaves_descended_from_5, + ) = setup_duplicate_forks(); + + let stake = 10; + let num_validators = 3; + let (bank, vote_pubkeys) = + bank_utils::setup_bank_and_vote_pubkeys_for_tests(num_validators, stake); + + let pubkey_votes: Vec<(Pubkey, SlotHashKey)> = vec![ + (vote_pubkeys[0], duplicate_leaves_descended_from_4[0]), + (vote_pubkeys[1], duplicate_leaves_descended_from_4[1]), + (vote_pubkeys[2], duplicate_leaves_descended_from_5[0]), + ]; + + // duplicate_leaves_descended_from_4 are sorted, and fork choice will pick the smaller + // one in the event of a tie + let expected_best_slot_hash = duplicate_leaves_descended_from_4[0]; + assert_eq!( + heaviest_subtree_fork_choice.add_votes( + pubkey_votes.iter(), + bank.epoch_stakes_map(), + bank.epoch_schedule() + ), + expected_best_slot_hash + ); + + assert_eq!( + heaviest_subtree_fork_choice.best_overall_slot(), + expected_best_slot_hash + ); + heaviest_subtree_fork_choice.dump_node(&(2, Hash::default())); + + assert_eq!( + heaviest_subtree_fork_choice.best_overall_slot(), + duplicate_leaves_descended_from_5[0] + ); + } + fn setup_forks() -> HeaviestSubtreeForkChoice { /* Build fork structure: @@ -3463,16 +3743,16 @@ mod test { .add_new_leaf_slot(*duplicate_leaf, Some((5, Hash::default()))); } - let mut dup_children = heaviest_subtree_fork_choice + let mut dup_children = (&heaviest_subtree_fork_choice) .children(&(4, Hash::default())) .unwrap() - .to_vec(); + .copied() + .collect_vec(); dup_children.sort(); assert_eq!(dup_children, duplicate_leaves_descended_from_4); - let mut dup_children: Vec<_> = heaviest_subtree_fork_choice + let mut dup_children: Vec<_> = (&heaviest_subtree_fork_choice) .children(&(5, Hash::default())) .unwrap() - .iter() .copied() .filter(|(slot, _)| *slot == duplicate_slot) .collect(); diff --git a/core/src/repair_generic_traversal.rs b/core/src/repair_generic_traversal.rs index d150c18b52fb00..044a71647b7002 100644 --- a/core/src/repair_generic_traversal.rs +++ b/core/src/repair_generic_traversal.rs @@ -31,7 +31,6 @@ impl<'a> Iterator for GenericTraversal<'a> { .tree .children(&(slot, Hash::default())) .unwrap() - .iter() .map(|(child_slot, _)| *child_slot) .collect(); self.pending.extend(children); diff --git a/core/src/repair_weight.rs b/core/src/repair_weight.rs index f387d4ae219c40..b9b2145c22544b 100644 --- a/core/src/repair_weight.rs +++ b/core/src/repair_weight.rs @@ -287,7 +287,7 @@ impl RepairWeight { // Find all descendants of `self.root` that are not reachable from `new_root`. // These are exactly the unrooted slots, which can be purged and added to // `self.unrooted_slots`. - let unrooted_slots = new_root_tree.subtree_diff( + let unrooted_slots = (&new_root_tree).subtree_diff( (new_root_tree_root, Hash::default()), (new_root, Hash::default()), ); diff --git a/core/src/repair_weighted_traversal.rs b/core/src/repair_weighted_traversal.rs index 4e70276f039596..ad8ff748f210b0 100644 --- a/core/src/repair_weighted_traversal.rs +++ b/core/src/repair_weighted_traversal.rs @@ -52,7 +52,6 @@ impl<'a> Iterator for RepairWeightTraversal<'a> { .tree .children(&(slot, Hash::default())) .unwrap() - .iter() .map(|(child_slot, _)| Visit::Unvisited(*child_slot)) .collect(); diff --git a/core/src/replay_stage.rs b/core/src/replay_stage.rs index 9f886f47df384f..f72a7bf8297205 100644 --- a/core/src/replay_stage.rs +++ b/core/src/replay_stage.rs @@ -6273,13 +6273,10 @@ pub(crate) mod tests { // Check that the new branch with slot 2 is different than the original version. let bank_1_hash = bank_forks.read().unwrap().bank_hash(1).unwrap(); - let children_of_1 = heaviest_subtree_fork_choice + let children_of_1 = (&heaviest_subtree_fork_choice) .children(&(1, bank_1_hash)) .unwrap(); - let duplicate_versions_of_2 = children_of_1 - .iter() - .filter(|(slot, _hash)| *slot == 2) - .count(); + let duplicate_versions_of_2 = children_of_1.filter(|(slot, _hash)| *slot == 2).count(); assert_eq!(duplicate_versions_of_2, 2); let mut frozen_banks: Vec<_> = bank_forks @@ -6305,7 +6302,6 @@ pub(crate) mod tests { &mut heaviest_subtree_fork_choice, &mut latest_validator_votes_for_frozen_banks, ); - // Try to switch to vote to the heaviest slot 5, then return the vote results let (heaviest_bank, heaviest_bank_on_same_fork) = heaviest_subtree_fork_choice .select_forks(&frozen_banks, &tower, &progress, &ancestors, &bank_forks); diff --git a/core/src/tree_diff.rs b/core/src/tree_diff.rs index 358c49d4ed067a..1a03885c2bd31a 100644 --- a/core/src/tree_diff.rs +++ b/core/src/tree_diff.rs @@ -1,8 +1,10 @@ use std::{collections::HashSet, hash::Hash}; -pub trait TreeDiff { - type TreeKey: Hash + PartialEq + Eq + Copy; - fn children(&self, key: &Self::TreeKey) -> Option<&[Self::TreeKey]>; +pub trait TreeDiff<'a> { + type TreeKey: 'a + Hash + PartialEq + Eq + Copy; + type ChildIter: Iterator; + + fn children(&self, key: &Self::TreeKey) -> Option; fn contains_slot(&self, slot: &Self::TreeKey) -> bool;