Allow fork choice to support multiple versions of a slot (#16266)

This commit is contained in:
carllin 2021-04-12 01:00:59 -07:00 committed by GitHub
parent ef30943c5c
commit dc7030ffaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 2034 additions and 637 deletions

View File

@ -18,9 +18,13 @@ pub enum SlotStateUpdate {
#[derive(PartialEq, Debug)]
pub enum ResultingStateChange {
MarkSlotDuplicate,
// Hash of our current frozen version of the slot
MarkSlotDuplicate(Hash),
// Hash of the cluster confirmed slot that is not equivalent
// to our frozen version of the slot
RepairDuplicateConfirmedVersion(Hash),
DuplicateConfirmedSlotMatchesCluster,
// Hash of our current frozen version of the slot
DuplicateConfirmedSlotMatchesCluster(Hash),
}
impl SlotStateUpdate {
@ -56,9 +60,9 @@ fn on_dead_slot(
// No need to check `is_slot_duplicate` and modify fork choice as dead slots
// are never frozen, and thus never added to fork choice. The state change for
// `MarkSlotDuplicate` will try to modify fork choice, but won't find the slot
// in the fork choice tree, so is equivalent to a
// in the fork choice tree, so is equivalent to a no-op
return vec![
ResultingStateChange::MarkSlotDuplicate,
ResultingStateChange::MarkSlotDuplicate(Hash::default()),
ResultingStateChange::RepairDuplicateConfirmedVersion(
*cluster_duplicate_confirmed_hash,
),
@ -92,7 +96,7 @@ fn on_frozen_slot(
slot, cluster_duplicate_confirmed_hash, bank_frozen_hash
);
return vec![
ResultingStateChange::MarkSlotDuplicate,
ResultingStateChange::MarkSlotDuplicate(*bank_frozen_hash),
ResultingStateChange::RepairDuplicateConfirmedVersion(
*cluster_duplicate_confirmed_hash,
),
@ -101,7 +105,9 @@ fn on_frozen_slot(
// If the versions match, then add the slot to the candidate
// set to account for the case where it was removed earlier
// by the `on_duplicate_slot()` handler
return vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster];
return vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster(
*bank_frozen_hash,
)];
}
}
@ -115,7 +121,7 @@ fn on_frozen_slot(
// 2) A gossip duplicate_confirmed version that didn't match our frozen
// version.
// In both cases, mark the progress map for this slot as duplicate
return vec![ResultingStateChange::MarkSlotDuplicate];
return vec![ResultingStateChange::MarkSlotDuplicate(*bank_frozen_hash)];
}
vec![]
@ -202,12 +208,12 @@ fn apply_state_changes(
) {
for state_change in state_changes {
match state_change {
ResultingStateChange::MarkSlotDuplicate => {
ResultingStateChange::MarkSlotDuplicate(bank_frozen_hash) => {
progress.set_unconfirmed_duplicate_slot(
slot,
descendants.get(&slot).unwrap_or(&HashSet::default()),
);
fork_choice.mark_fork_invalid_candidate(slot);
fork_choice.mark_fork_invalid_candidate(&(slot, bank_frozen_hash));
}
ResultingStateChange::RepairDuplicateConfirmedVersion(
cluster_duplicate_confirmed_hash,
@ -216,13 +222,13 @@ fn apply_state_changes(
// progress map from ReplayStage::confirm_forks to here.
repair_correct_version(slot, &cluster_duplicate_confirmed_hash);
}
ResultingStateChange::DuplicateConfirmedSlotMatchesCluster => {
ResultingStateChange::DuplicateConfirmedSlotMatchesCluster(bank_frozen_hash) => {
progress.set_confirmed_duplicate_slot(
slot,
ancestors.get(&slot).unwrap_or(&HashSet::default()),
descendants.get(&slot).unwrap_or(&HashSet::default()),
);
fork_choice.mark_fork_valid_candidate(slot);
fork_choice.mark_fork_valid_candidate(&(slot, bank_frozen_hash));
}
}
}
@ -388,7 +394,7 @@ mod test {
is_slot_duplicate,
is_dead
),
vec![ResultingStateChange::MarkSlotDuplicate]
vec![ResultingStateChange::MarkSlotDuplicate(bank_frozen_hash)]
);
}
@ -426,7 +432,9 @@ mod test {
is_slot_duplicate,
is_dead
),
vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster,]
vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster(
bank_frozen_hash
),]
);
// If the cluster_duplicate_confirmed_hash does not match, then we
@ -443,7 +451,7 @@ mod test {
is_dead
),
vec![
ResultingStateChange::MarkSlotDuplicate,
ResultingStateChange::MarkSlotDuplicate(bank_frozen_hash),
ResultingStateChange::RepairDuplicateConfirmedVersion(mismatched_hash),
]
);
@ -483,7 +491,7 @@ mod test {
is_slot_duplicate,
is_dead
),
vec![ResultingStateChange::MarkSlotDuplicate,]
vec![ResultingStateChange::MarkSlotDuplicate(bank_frozen_hash),]
);
// If the cluster_duplicate_confirmed_hash matches, we just confirm
@ -497,7 +505,9 @@ mod test {
is_slot_duplicate,
is_dead
),
vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster,]
vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster(
bank_frozen_hash
),]
);
// If the cluster_duplicate_confirmed_hash does not match, then we
@ -514,7 +524,7 @@ mod test {
is_dead
),
vec![
ResultingStateChange::MarkSlotDuplicate,
ResultingStateChange::MarkSlotDuplicate(bank_frozen_hash),
ResultingStateChange::RepairDuplicateConfirmedVersion(mismatched_hash),
]
);
@ -589,7 +599,7 @@ mod test {
is_dead
),
vec![
ResultingStateChange::MarkSlotDuplicate,
ResultingStateChange::MarkSlotDuplicate(bank_frozen_hash),
ResultingStateChange::RepairDuplicateConfirmedVersion(correct_hash),
]
);
@ -605,7 +615,7 @@ mod test {
is_dead
),
vec![
ResultingStateChange::MarkSlotDuplicate,
ResultingStateChange::MarkSlotDuplicate(bank_frozen_hash),
ResultingStateChange::RepairDuplicateConfirmedVersion(correct_hash),
]
);
@ -620,21 +630,22 @@ mod test {
ancestors,
descendants,
slot,
..
bank_forks,
} = setup();
// MarkSlotDuplicate should mark progress map and remove
// the slot from fork choice
let slot_hash = bank_forks.read().unwrap().get(slot).unwrap().hash();
apply_state_changes(
slot,
&mut progress,
&mut heaviest_subtree_fork_choice,
&ancestors,
&descendants,
vec![ResultingStateChange::MarkSlotDuplicate],
vec![ResultingStateChange::MarkSlotDuplicate(slot_hash)],
);
assert!(!heaviest_subtree_fork_choice
.is_candidate_slot(slot)
.is_candidate_slot(&(slot, slot_hash))
.unwrap());
for child_slot in descendants
.get(&slot)
@ -657,7 +668,9 @@ mod test {
&mut heaviest_subtree_fork_choice,
&ancestors,
&descendants,
vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster],
vec![ResultingStateChange::DuplicateConfirmedSlotMatchesCluster(
slot_hash,
)],
);
for child_slot in descendants
.get(&slot)
@ -670,7 +683,7 @@ mod test {
.is_none());
}
assert!(heaviest_subtree_fork_choice
.is_candidate_slot(slot)
.is_candidate_slot(&(slot, slot_hash))
.unwrap());
}
@ -686,7 +699,11 @@ mod test {
..
} = setup();
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), 3);
let slot3_hash = bank_forks.read().unwrap().get(3).unwrap().hash();
assert_eq!(
heaviest_subtree_fork_choice.best_overall_slot(),
(3, slot3_hash)
);
let root = 0;
let mut gossip_duplicate_confirmed_slots = GossipDuplicateConfirmedSlots::default();
@ -705,13 +722,16 @@ mod test {
SlotStateUpdate::DuplicateConfirmed,
);
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), 3);
assert_eq!(
heaviest_subtree_fork_choice.best_overall_slot(),
(3, slot3_hash)
);
// Mark 3 as duplicate, should not remove slot 2 from fork choice
check_slot_agrees_with_cluster(
3,
root,
Some(bank_forks.read().unwrap().get(3).unwrap().hash()),
Some(slot3_hash),
&gossip_duplicate_confirmed_slots,
&ancestors,
&descendants,
@ -720,7 +740,10 @@ mod test {
SlotStateUpdate::Duplicate,
);
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), 2);
assert_eq!(
heaviest_subtree_fork_choice.best_overall_slot(),
(2, slot2_hash)
);
}
#[test]
@ -735,7 +758,11 @@ mod test {
..
} = setup();
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), 3);
let slot3_hash = bank_forks.read().unwrap().get(3).unwrap().hash();
assert_eq!(
heaviest_subtree_fork_choice.best_overall_slot(),
(3, slot3_hash)
);
let root = 0;
let mut gossip_duplicate_confirmed_slots = GossipDuplicateConfirmedSlots::default();
// Mark 2 as duplicate confirmed
@ -751,10 +778,13 @@ mod test {
SlotStateUpdate::Duplicate,
);
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), 1);
let slot1_hash = bank_forks.read().unwrap().get(1).unwrap().hash();
assert_eq!(
heaviest_subtree_fork_choice.best_overall_slot(),
(1, slot1_hash)
);
// Mark slot 3 as duplicate confirmed, should mark slot 2 as duplicate confirmed as well
let slot3_hash = bank_forks.read().unwrap().get(3).unwrap().hash();
gossip_duplicate_confirmed_slots.insert(3, slot3_hash);
check_slot_agrees_with_cluster(
3,
@ -768,6 +798,9 @@ mod test {
SlotStateUpdate::DuplicateConfirmed,
);
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), 3);
assert_eq!(
heaviest_subtree_fork_choice.best_overall_slot(),
(3, slot3_hash)
);
}
}

View File

@ -197,8 +197,9 @@ impl Tower {
);
let root = root_bank.slot();
let (best_slot, best_hash) = heaviest_subtree_fork_choice.best_overall_slot();
let heaviest_bank = bank_forks
.get(heaviest_subtree_fork_choice.best_overall_slot())
.get_with_checked_hash((best_slot, best_hash))
.expect(
"The best overall slot must be one of `frozen_banks` which all exist in bank_forks",
)
@ -436,6 +437,10 @@ impl Tower {
self.last_vote.last_voted_slot()
}
pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
self.last_vote.last_voted_slot_hash()
}
pub fn stray_restored_slot(&self) -> Option<Slot> {
self.stray_restored_slot
}
@ -1372,8 +1377,10 @@ pub mod test {
}
}
new_bank.freeze();
self.heaviest_subtree_fork_choice
.add_new_leaf_slot(new_bank.slot(), Some(new_bank.parent_slot()));
self.heaviest_subtree_fork_choice.add_new_leaf_slot(
(new_bank.slot(), new_bank.hash()),
Some((new_bank.parent_slot(), new_bank.parent_hash())),
);
self.bank_forks.write().unwrap().insert(new_bank);
walk.forward();
}

View File

@ -4,7 +4,6 @@ use crate::{
replay_stage::HeaviestForkFailures,
};
use solana_runtime::{bank::Bank, bank_forks::BankForks};
use solana_sdk::clock::Slot;
use std::{
collections::{HashMap, HashSet},
sync::{Arc, RwLock},
@ -17,6 +16,7 @@ pub(crate) struct SelectVoteAndResetForkResult {
}
pub(crate) trait ForkChoice {
type ForkChoiceKey;
fn compute_bank_stats(
&mut self,
bank: &Bank,
@ -38,7 +38,7 @@ pub(crate) trait ForkChoice {
bank_forks: &RwLock<BankForks>,
) -> (Arc<Bank>, Option<Arc<Bank>>);
fn mark_fork_invalid_candidate(&mut self, invalid_slot: Slot);
fn mark_fork_invalid_candidate(&mut self, invalid_slot: &Self::ForkChoiceKey);
fn mark_fork_valid_candidate(&mut self, valid_slot: Slot);
fn mark_fork_valid_candidate(&mut self, valid_slot: &Self::ForkChoiceKey);
}

File diff suppressed because it is too large Load Diff

View File

@ -257,6 +257,7 @@ pub(crate) struct ForkStats {
pub(crate) is_supermajority_confirmed: bool,
pub(crate) computed: bool,
pub(crate) lockout_intervals: LockoutIntervals,
pub(crate) bank_hash: Option<Hash>,
}
#[derive(Clone, Default)]
@ -398,6 +399,12 @@ impl ProgressMap {
.map(|fork_progress| fork_progress.is_dead)
}
pub fn get_hash(&self, slot: Slot) -> Option<Hash> {
self.progress_map
.get(&slot)
.and_then(|fork_progress| fork_progress.fork_stats.bank_hash)
}
pub fn is_propagated(&self, slot: Slot) -> bool {
let leader_slot_to_check = self.get_latest_leader_slot(slot);

View File

@ -8,6 +8,7 @@ use solana_runtime::{contains::Contains, epoch_stakes::EpochStakes};
use solana_sdk::{
clock::Slot,
epoch_schedule::{Epoch, EpochSchedule},
hash::Hash,
pubkey::Pubkey,
};
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
@ -30,7 +31,7 @@ pub struct RepairWeight {
impl RepairWeight {
pub fn new(root: Slot) -> Self {
let root_tree = HeaviestSubtreeForkChoice::new(root);
let root_tree = HeaviestSubtreeForkChoice::new((root, Hash::default()));
let slot_to_tree: HashMap<Slot, Slot> = vec![(root, root)].into_iter().collect();
let trees: HashMap<Slot, HeaviestSubtreeForkChoice> =
vec![(root, root_tree)].into_iter().collect();
@ -102,7 +103,12 @@ impl RepairWeight {
new_ancestors.push_back(slot);
if new_ancestors.len() > 1 {
for i in 0..new_ancestors.len() - 1 {
tree.add_new_leaf_slot(new_ancestors[i + 1], Some(new_ancestors[i]));
// TODO: Repair right now does not distinguish between votes for different
// versions of the same slot.
tree.add_new_leaf_slot(
(new_ancestors[i + 1], Hash::default()),
Some((new_ancestors[i], Hash::default())),
);
self.slot_to_tree.insert(new_ancestors[i + 1], tree_root);
}
}
@ -122,7 +128,13 @@ impl RepairWeight {
.get_mut(&tree_root)
.expect("`slot_to_tree` and `self.trees` must be in sync");
let updates: Vec<_> = updates.into_iter().collect();
tree.add_votes(&updates, epoch_stakes, epoch_schedule);
tree.add_votes(
updates
.iter()
.map(|(pubkey, slot)| (*pubkey, (*slot, Hash::default()))),
epoch_stakes,
epoch_schedule,
);
}
}
@ -189,7 +201,9 @@ impl RepairWeight {
.remove(&subtree_root)
.expect("Must exist, was found in `self.trees` above");
self.remove_tree_slots(
subtree.all_slots_stake_voted_subtree().iter().map(|x| &x.0),
subtree
.all_slots_stake_voted_subtree()
.map(|((slot, _), _)| slot),
new_root,
);
}
@ -202,10 +216,16 @@ 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(new_root_tree_root, new_root);
self.remove_tree_slots(unrooted_slots.iter(), new_root);
let unrooted_slots = new_root_tree.subtree_diff(
(new_root_tree_root, Hash::default()),
(new_root, Hash::default()),
);
self.remove_tree_slots(
unrooted_slots.iter().map(|slot_hash| &slot_hash.0),
new_root,
);
new_root_tree.set_root(new_root);
new_root_tree.set_root((new_root, Hash::default()));
// Update `self.slot_to_tree` to reflect new root
self.rename_tree_root(&new_root_tree, new_root);
@ -259,7 +279,7 @@ impl RepairWeight {
.map(|(slot, tree)| {
(
*slot,
tree.stake_voted_subtree(*slot)
tree.stake_voted_subtree(&(*slot, Hash::default()))
.expect("Tree must have weight at its own root"),
)
})
@ -343,7 +363,7 @@ impl RepairWeight {
for ancestor in new_ancestors.iter().skip(num_skip).rev() {
self.slot_to_tree.insert(*ancestor, orphan_tree_root);
heaviest_tree.add_root_parent(*ancestor);
heaviest_tree.add_root_parent((*ancestor, Hash::default()));
}
}
if let Some(parent_tree_root) = parent_tree_root {
@ -386,8 +406,7 @@ impl RepairWeight {
self.remove_tree_slots(
orphan_tree
.all_slots_stake_voted_subtree()
.iter()
.map(|x| &x.0),
.map(|((slot, _), _)| slot),
self.root,
);
None
@ -403,8 +422,10 @@ impl RepairWeight {
// Update `self.slot_to_tree`
self.slot_to_tree.insert(new_tree_root, new_tree_root);
self.trees
.insert(new_tree_root, HeaviestSubtreeForkChoice::new(new_tree_root));
self.trees.insert(
new_tree_root,
HeaviestSubtreeForkChoice::new((new_tree_root, Hash::default())),
);
}
fn find_ancestor_subtree_of_slot(
@ -460,13 +481,18 @@ impl RepairWeight {
.get_mut(&root2)
.expect("tree to be merged into must exist");
tree2.merge(tree1, merge_leaf, epoch_stakes, epoch_schedule);
tree2.merge(
tree1,
&(merge_leaf, Hash::default()),
epoch_stakes,
epoch_schedule,
);
}
// Update all slots in the `tree1` to point to `root2`,
fn rename_tree_root(&mut self, tree1: &HeaviestSubtreeForkChoice, root2: Slot) {
let all_slots = tree1.all_slots_stake_voted_subtree();
for (slot, _) in all_slots {
for ((slot, _), _) in all_slots {
*self
.slot_to_tree
.get_mut(&slot)
@ -560,7 +586,14 @@ mod test {
// repair_weight should contain one subtree 0->1
assert_eq!(repair_weight.trees.len(), 1);
assert_eq!(repair_weight.trees.get(&0).unwrap().ancestors(1), vec![0]);
assert_eq!(
repair_weight
.trees
.get(&0)
.unwrap()
.ancestors((1, Hash::default())),
vec![(0, Hash::default())]
);
for i in &[0, 1] {
assert_eq!(*repair_weight.slot_to_tree.get(i).unwrap(), 0);
}
@ -577,11 +610,25 @@ mod test {
);
assert_eq!(repair_weight.trees.len(), 1);
assert_eq!(
repair_weight.trees.get(&0).unwrap().ancestors(4),
repair_weight
.trees
.get(&0)
.unwrap()
.ancestors((4, Hash::default()))
.into_iter()
.map(|slot_hash| slot_hash.0)
.collect::<Vec<_>>(),
vec![2, 1, 0]
);
assert_eq!(
repair_weight.trees.get(&0).unwrap().ancestors(6),
repair_weight
.trees
.get(&0)
.unwrap()
.ancestors((6, Hash::default()))
.into_iter()
.map(|slot_hash| slot_hash.0)
.collect::<Vec<_>>(),
vec![5, 3, 1, 0]
);
for slot in 0..=6 {
@ -590,7 +637,7 @@ mod test {
.trees
.get(&0)
.unwrap()
.stake_voted_at(slot)
.stake_voted_at(&(slot, Hash::default()))
.unwrap();
if slot == 6 {
assert_eq!(stake_voted_at, 3 * stake);
@ -604,7 +651,7 @@ mod test {
.trees
.get(&0)
.unwrap()
.stake_voted_subtree(*slot)
.stake_voted_subtree(&(*slot, Hash::default()))
.unwrap();
assert_eq!(stake_voted_subtree, 3 * stake);
}
@ -613,7 +660,7 @@ mod test {
.trees
.get(&0)
.unwrap()
.stake_voted_subtree(*slot)
.stake_voted_subtree(&(*slot, Hash::default()))
.unwrap();
assert_eq!(stake_voted_subtree, 0);
}
@ -637,8 +684,20 @@ mod test {
// Should contain two trees, one for main fork, one for the orphan
// branch
assert_eq!(repair_weight.trees.len(), 2);
assert_eq!(repair_weight.trees.get(&0).unwrap().ancestors(1), vec![0]);
assert!(repair_weight.trees.get(&8).unwrap().ancestors(8).is_empty());
assert_eq!(
repair_weight
.trees
.get(&0)
.unwrap()
.ancestors((1, Hash::default())),
vec![(0, Hash::default())]
);
assert!(repair_weight
.trees
.get(&8)
.unwrap()
.ancestors((8, Hash::default()))
.is_empty());
let votes = vec![(1, vote_pubkeys.clone()), (10, vote_pubkeys.clone())];
let mut repair_weight = RepairWeight::new(0);
@ -652,8 +711,22 @@ mod test {
// Should contain two trees, one for main fork, one for the orphan
// branch
assert_eq!(repair_weight.trees.len(), 2);
assert_eq!(repair_weight.trees.get(&0).unwrap().ancestors(1), vec![0]);
assert_eq!(repair_weight.trees.get(&8).unwrap().ancestors(10), vec![8]);
assert_eq!(
repair_weight
.trees
.get(&0)
.unwrap()
.ancestors((1, Hash::default())),
vec![(0, Hash::default())]
);
assert_eq!(
repair_weight
.trees
.get(&8)
.unwrap()
.ancestors((10, Hash::default())),
vec![(8, Hash::default())]
);
// Connect orphan back to main fork
blockstore.add_tree(tr(6) / (tr(8)), true, true, 2, Hash::default());
@ -672,7 +745,14 @@ mod test {
bank.epoch_schedule(),
);
assert_eq!(
repair_weight.trees.get(&8).unwrap().ancestors(11),
repair_weight
.trees
.get(&8)
.unwrap()
.ancestors((11, Hash::default()))
.into_iter()
.map(|slot_hash| slot_hash.0)
.collect::<Vec<_>>(),
vec![10, 8]
);
@ -784,13 +864,13 @@ mod test {
.trees
.get(&8)
.unwrap()
.stake_voted_subtree(8)
.stake_voted_subtree(&(8, Hash::default()))
.unwrap(),
repair_weight
.trees
.get(&20)
.unwrap()
.stake_voted_subtree(20)
.stake_voted_subtree(&(20, Hash::default()))
.unwrap()
);
assert_eq!(repairs.len(), 1);
@ -929,11 +1009,11 @@ mod test {
assert_eq!(*repair_weight.slot_to_tree.get(&2).unwrap(), 1);
// Trees tracked should be updated
assert_eq!(repair_weight.trees.get(&1).unwrap().root(), 1);
assert_eq!(repair_weight.trees.get(&1).unwrap().root().0, 1);
// Orphan slots should not be changed
for orphan in &[8, 20] {
assert_eq!(repair_weight.trees.get(orphan).unwrap().root(), *orphan);
assert_eq!(repair_weight.trees.get(orphan).unwrap().root().0, *orphan);
assert_eq!(repair_weight.slot_to_tree.get(orphan).unwrap(), orphan);
}
}
@ -957,7 +1037,7 @@ mod test {
// Orphan slots should not be changed
for orphan in &[8, 20] {
assert_eq!(repair_weight.trees.get(orphan).unwrap().root(), *orphan);
assert_eq!(repair_weight.trees.get(orphan).unwrap().root().0, *orphan);
assert_eq!(repair_weight.slot_to_tree.get(orphan).unwrap(), orphan);
}
}
@ -979,7 +1059,7 @@ mod test {
assert!(!repair_weight.slot_to_tree.contains_key(&8));
// Other higher orphan branch rooted at slot `20` remains unchanged
assert_eq!(repair_weight.trees.get(&20).unwrap().root(), 20);
assert_eq!(repair_weight.trees.get(&20).unwrap().root().0, 20);
assert_eq!(*repair_weight.slot_to_tree.get(&20).unwrap(), 20);
}
@ -1020,7 +1100,7 @@ mod test {
// Orphan 20 should still exist
assert_eq!(repair_weight.trees.len(), 2);
assert_eq!(repair_weight.trees.get(&20).unwrap().root(), 20);
assert_eq!(repair_weight.trees.get(&20).unwrap().root().0, 20);
assert_eq!(*repair_weight.slot_to_tree.get(&20).unwrap(), 20);
// Now set root at a slot 30 that doesnt exist in `repair_weight`, but is
@ -1191,7 +1271,7 @@ mod test {
// Check orphans are present
for orphan in &[8, 20] {
assert_eq!(repair_weight.trees.get(orphan).unwrap().root(), *orphan);
assert_eq!(repair_weight.trees.get(orphan).unwrap().root().0, *orphan);
assert_eq!(repair_weight.slot_to_tree.get(orphan).unwrap(), orphan);
}
(blockstore, bank, repair_weight)
@ -1208,7 +1288,10 @@ mod test {
assert!(!repair_weight.unrooted_slots.contains(&old_root));
// Validate new root
assert_eq!(repair_weight.trees.get(&new_root).unwrap().root(), new_root);
assert_eq!(
repair_weight.trees.get(&new_root).unwrap().root().0,
new_root
);
assert_eq!(
*repair_weight.slot_to_tree.get(&new_root).unwrap(),
new_root

View File

@ -4,7 +4,7 @@ use crate::{
};
use solana_ledger::blockstore::Blockstore;
use solana_runtime::contains::Contains;
use solana_sdk::clock::Slot;
use solana_sdk::{clock::Slot, hash::Hash};
use std::collections::{HashMap, HashSet};
#[derive(Debug, PartialEq)]
@ -32,7 +32,7 @@ impl<'a> RepairWeightTraversal<'a> {
pub fn new(tree: &'a HeaviestSubtreeForkChoice) -> Self {
Self {
tree,
pending: vec![Visit::Unvisited(tree.root())],
pending: vec![Visit::Unvisited(tree.root().0)],
}
}
}
@ -48,16 +48,20 @@ impl<'a> Iterator for RepairWeightTraversal<'a> {
self.pending.push(Visit::Visited(slot));
let mut children: Vec<_> = self
.tree
.children(slot)
.children(&(slot, Hash::default()))
.unwrap()
.iter()
.map(|child_slot| Visit::Unvisited(*child_slot))
.map(|(child_slot, _)| Visit::Unvisited(*child_slot))
.collect();
// Sort children by weight to prioritize visiting the heaviest
// ones first
children
.sort_by(|slot1, slot2| self.tree.max_by_weight(slot1.slot(), slot2.slot()));
children.sort_by(|slot1, slot2| {
self.tree.max_by_weight(
(slot1.slot(), Hash::default()),
(slot2.slot(), Hash::default()),
)
});
self.pending.extend(children);
}
next
@ -87,6 +91,9 @@ pub fn get_best_repair_shreds<'a>(
.entry(next.slot())
.or_insert_with(|| blockstore.meta(next.slot()).unwrap());
// May not exist if blockstore purged the SlotMeta due to something
// like duplicate slots. TODO: Account for duplicate slot may be in orphans, especially
// if earlier duplicate was already removed
if let Some(slot_meta) = slot_meta {
match next {
Visit::Unvisited(slot) => {
@ -137,7 +144,7 @@ pub mod test {
#[test]
fn test_weighted_repair_traversal_single() {
let heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new(42);
let heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new((42, Hash::default()));
let weighted_traversal = RepairWeightTraversal::new(&heaviest_subtree_fork_choice);
let steps: Vec<_> = weighted_traversal.collect();
assert_eq!(steps, vec![Visit::Unvisited(42), Visit::Visited(42)]);
@ -174,7 +181,7 @@ pub mod test {
// Add a vote to branch with slot 5,
// should prioritize that branch
heaviest_subtree_fork_choice.add_votes(
&[(vote_pubkeys[0], 5)],
[(vote_pubkeys[0], (5, Hash::default()))].iter(),
bank.epoch_stakes_map(),
bank.epoch_schedule(),
);
@ -227,8 +234,8 @@ pub mod test {
// Add some leaves to blockstore, attached to the current best leaf, should prioritize
// repairing those new leaves before trying other branches
repairs = vec![];
let best_overall_slot = heaviest_subtree_fork_choice.best_overall_slot();
assert_eq!(heaviest_subtree_fork_choice.best_overall_slot(), 4);
let best_overall_slot = heaviest_subtree_fork_choice.best_overall_slot().0;
assert_eq!(best_overall_slot, 4);
blockstore.add_tree(
tr(best_overall_slot) / (tr(6) / tr(7)),
true,
@ -406,6 +413,7 @@ pub mod test {
slot 4 |
slot 5
*/
let forks = tr(0) / (tr(1) / (tr(2) / (tr(4))) / (tr(3) / (tr(5))));
let ledger_path = get_tmp_ledger_path!();
let blockstore = Blockstore::open(&ledger_path).unwrap();

View File

@ -753,8 +753,10 @@ impl ReplayStage {
);
}
let root = root_bank.slot();
let heaviest_subtree_fork_choice =
HeaviestSubtreeForkChoice::new_from_frozen_banks(root, &frozen_banks);
let heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new_from_frozen_banks(
(root, root_bank.hash()),
&frozen_banks,
);
(progress, heaviest_subtree_fork_choice)
}
@ -1609,8 +1611,12 @@ impl ReplayStage {
transaction_status_sender.send_transaction_status_freeze_message(&bank);
}
bank.freeze();
heaviest_subtree_fork_choice
.add_new_leaf_slot(bank.slot(), Some(bank.parent_slot()));
let bank_hash = bank.hash();
assert_ne!(bank_hash, Hash::default());
heaviest_subtree_fork_choice.add_new_leaf_slot(
(bank.slot(), bank.hash()),
Some((bank.parent_slot(), bank.parent_hash())),
);
check_slot_agrees_with_cluster(
bank.slot(),
bank_forks.read().unwrap().root(),
@ -1652,7 +1658,7 @@ impl ReplayStage {
vote_tracker: &VoteTracker,
cluster_slots: &ClusterSlots,
bank_forks: &RwLock<BankForks>,
heaviest_subtree_fork_choice: &mut dyn ForkChoice,
heaviest_subtree_fork_choice: &mut HeaviestSubtreeForkChoice,
) -> Vec<Slot> {
frozen_banks.sort_by_key(|bank| bank.slot());
let mut new_stats = vec![];
@ -1694,6 +1700,7 @@ impl ReplayStage {
stats.voted_stakes = voted_stakes;
stats.lockout_intervals = lockout_intervals;
stats.block_height = bank.block_height();
stats.bank_hash = Some(bank.hash());
stats.computed = true;
new_stats.push(bank_slot);
datapoint_info!(
@ -2202,9 +2209,9 @@ impl ReplayStage {
}
}
progress.handle_new_root(&r_bank_forks);
heaviest_subtree_fork_choice.set_root(new_root);
heaviest_subtree_fork_choice.set_root((new_root, r_bank_forks.root_bank().hash()));
let mut slots_ge_root = gossip_duplicate_confirmed_slots.split_off(&new_root);
// gossip_duplicate_confirmed_slots now only contains entries >= `new_root`
// gossip_confirmed_slots now only contains entries >= `new_root`
std::mem::swap(gossip_duplicate_confirmed_slots, &mut slots_ge_root);
let mut slots_ge_root = gossip_verified_vote_hashes.split_off(&new_root);
@ -2598,14 +2605,19 @@ pub(crate) mod tests {
let genesis_config = create_genesis_config(10_000).genesis_config;
let bank0 = Bank::new(&genesis_config);
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank0)));
let root = 3;
let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new(root);
let root_bank = Bank::new_from_parent(
bank_forks.read().unwrap().get(0).unwrap(),
&Pubkey::default(),
root,
);
root_bank.freeze();
let root_hash = root_bank.hash();
bank_forks.write().unwrap().insert(root_bank);
let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new((root, root_hash));
let mut progress = ProgressMap::default();
for i in 0..=root {
progress.insert(
@ -2680,8 +2692,10 @@ pub(crate) mod tests {
&Pubkey::default(),
root,
);
root_bank.freeze();
let root_hash = root_bank.hash();
bank_forks.write().unwrap().insert(root_bank);
let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new(root);
let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new((root, root_hash));
let mut progress = ProgressMap::default();
for i in 0..=root {
progress.insert(
@ -2975,7 +2989,7 @@ pub(crate) mod tests {
&HashMap::new(),
&HashMap::new(),
&mut progress,
&mut HeaviestSubtreeForkChoice::new(0),
&mut HeaviestSubtreeForkChoice::new((0, Hash::default())),
);
}
@ -3376,8 +3390,7 @@ pub(crate) mod tests {
// Create the tree of banks in a BankForks object
let forks = tr(0) / (tr(1)) / (tr(2));
vote_simulator.fill_bank_forks(forks.clone(), &HashMap::new());
let mut heaviest_subtree_fork_choice = HeaviestSubtreeForkChoice::new_from_tree(forks);
vote_simulator.fill_bank_forks(forks, &HashMap::new());
let mut frozen_banks: Vec<_> = vote_simulator
.bank_forks
.read()
@ -3386,6 +3399,7 @@ pub(crate) mod tests {
.values()
.cloned()
.collect();
let mut heaviest_subtree_fork_choice = &mut vote_simulator.heaviest_subtree_fork_choice;
let ancestors = vote_simulator.bank_forks.read().unwrap().ancestors();
ReplayStage::compute_bank_stats(
@ -3400,9 +3414,27 @@ pub(crate) mod tests {
&mut heaviest_subtree_fork_choice,
);
let bank1 = vote_simulator
.bank_forks
.read()
.unwrap()
.get(1)
.unwrap()
.clone();
let bank2 = vote_simulator
.bank_forks
.read()
.unwrap()
.get(2)
.unwrap()
.clone();
assert_eq!(
heaviest_subtree_fork_choice.stake_voted_subtree(1).unwrap(),
heaviest_subtree_fork_choice.stake_voted_subtree(2).unwrap()
heaviest_subtree_fork_choice
.stake_voted_subtree(&(1, bank1.hash()))
.unwrap(),
heaviest_subtree_fork_choice
.stake_voted_subtree(&(2, bank2.hash()))
.unwrap()
);
let (heaviest_bank, _) = heaviest_subtree_fork_choice.select_forks(
@ -3480,8 +3512,9 @@ pub(crate) mod tests {
assert_eq!(
vote_simulator
.heaviest_subtree_fork_choice
.best_slot(bank.slot())
.unwrap(),
.best_slot(&(bank.slot(), bank.hash()))
.unwrap()
.0,
3
);
}

View File

@ -1,30 +1,30 @@
use solana_sdk::clock::Slot;
use std::collections::HashSet;
use std::{collections::HashSet, hash::Hash};
pub trait TreeDiff {
fn children(&self, slot: Slot) -> Option<&[Slot]>;
type TreeKey: Hash + PartialEq + Eq + Copy;
fn children(&self, key: &Self::TreeKey) -> Option<&[Self::TreeKey]>;
fn contains_slot(&self, slot: Slot) -> bool;
fn contains_slot(&self, slot: &Self::TreeKey) -> bool;
// Find all nodes reachable from `root1`, excluding subtree at `root2`
fn subtree_diff(&self, root1: Slot, root2: Slot) -> HashSet<Slot> {
if !self.contains_slot(root1) {
fn subtree_diff(&self, root1: Self::TreeKey, root2: Self::TreeKey) -> HashSet<Self::TreeKey> {
if !self.contains_slot(&root1) {
return HashSet::new();
}
let mut pending_slots = vec![root1];
let mut pending_keys = vec![root1];
let mut reachable_set = HashSet::new();
while !pending_slots.is_empty() {
let current_slot = pending_slots.pop().unwrap();
if current_slot == root2 {
while !pending_keys.is_empty() {
let current_key = pending_keys.pop().unwrap();
if current_key == root2 {
continue;
}
reachable_set.insert(current_slot);
for child in self
.children(current_slot)
.children(&current_key)
.expect("slot was discovered earlier, must exist")
{
pending_slots.push(*child);
pending_keys.push(*child);
}
reachable_set.insert(current_key);
}
reachable_set

View File

@ -59,6 +59,10 @@ impl Vote {
pub fn last_voted_slot(&self) -> Option<Slot> {
self.slots.last().copied()
}
pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
self.slots.last().copied().map(|slot| (slot, self.hash))
}
}
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]

View File

@ -2127,6 +2127,10 @@ impl Bank {
self.parent_slot
}
pub fn parent_hash(&self) -> Hash {
self.parent_hash
}
fn process_genesis_config(&mut self, genesis_config: &GenesisConfig) {
// Bootstrap validator collects fees until `new_from_parent` is called.
self.fee_rate_governor = genesis_config.fee_rate_governor.clone();

View File

@ -6,7 +6,7 @@ use crate::{
};
use log::*;
use solana_metrics::inc_new_counter_info;
use solana_sdk::{clock::Slot, timing};
use solana_sdk::{clock::Slot, hash::Hash, timing};
use std::{
collections::{hash_map::Entry, HashMap, HashSet},
ops::Index,
@ -106,6 +106,17 @@ impl BankForks {
self.banks.get(&bank_slot)
}
pub fn get_with_checked_hash(
&self,
(bank_slot, expected_hash): (Slot, Hash),
) -> Option<&Arc<Bank>> {
let maybe_bank = self.banks.get(&bank_slot);
if let Some(bank) = maybe_bank {
assert_eq!(bank.hash(), expected_hash);
}
maybe_bank
}
pub fn root_bank(&self) -> Arc<Bank> {
self[self.root()].clone()
}