automerge
This commit is contained in:
@ -9,10 +9,14 @@ use solana_sdk::{
|
|||||||
account::Account,
|
account::Account,
|
||||||
clock::{Slot, UnixTimestamp},
|
clock::{Slot, UnixTimestamp},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
|
instruction::Instruction,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
};
|
};
|
||||||
use solana_vote_program::vote_state::{
|
use solana_vote_program::{
|
||||||
BlockTimestamp, Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY, TIMESTAMP_SLOT_INTERVAL,
|
vote_instruction,
|
||||||
|
vote_state::{
|
||||||
|
BlockTimestamp, Lockout, Vote, VoteState, MAX_LOCKOUT_HISTORY, TIMESTAMP_SLOT_INTERVAL,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap, HashSet},
|
collections::{BTreeMap, HashMap, HashSet},
|
||||||
@ -20,6 +24,39 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub enum SwitchForkDecision {
|
||||||
|
SwitchProof(Hash),
|
||||||
|
NoSwitch,
|
||||||
|
FailedSwitchThreshold,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwitchForkDecision {
|
||||||
|
pub fn to_vote_instruction(
|
||||||
|
&self,
|
||||||
|
vote: Vote,
|
||||||
|
vote_account_pubkey: &Pubkey,
|
||||||
|
authorized_voter_pubkey: &Pubkey,
|
||||||
|
) -> Option<Instruction> {
|
||||||
|
match self {
|
||||||
|
SwitchForkDecision::FailedSwitchThreshold => None,
|
||||||
|
SwitchForkDecision::NoSwitch => Some(vote_instruction::vote(
|
||||||
|
vote_account_pubkey,
|
||||||
|
authorized_voter_pubkey,
|
||||||
|
vote,
|
||||||
|
)),
|
||||||
|
SwitchForkDecision::SwitchProof(switch_proof_hash) => {
|
||||||
|
Some(vote_instruction::vote_switch(
|
||||||
|
vote_account_pubkey,
|
||||||
|
authorized_voter_pubkey,
|
||||||
|
vote,
|
||||||
|
*switch_proof_hash,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
|
pub const VOTE_THRESHOLD_DEPTH: usize = 8;
|
||||||
pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64;
|
pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64;
|
||||||
pub const SWITCH_FORK_THRESHOLD: f64 = 0.38;
|
pub const SWITCH_FORK_THRESHOLD: f64 = 0.38;
|
||||||
@ -345,7 +382,7 @@ impl Tower {
|
|||||||
progress: &ProgressMap,
|
progress: &ProgressMap,
|
||||||
total_stake: u64,
|
total_stake: u64,
|
||||||
epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
|
epoch_vote_accounts: &HashMap<Pubkey, (u64, Account)>,
|
||||||
) -> bool {
|
) -> SwitchForkDecision {
|
||||||
self.last_vote()
|
self.last_vote()
|
||||||
.slots
|
.slots
|
||||||
.last()
|
.last()
|
||||||
@ -355,14 +392,18 @@ impl Tower {
|
|||||||
|
|
||||||
if switch_slot == *last_vote || switch_slot_ancestors.contains(last_vote) {
|
if switch_slot == *last_vote || switch_slot_ancestors.contains(last_vote) {
|
||||||
// If the `switch_slot is a descendant of the last vote,
|
// If the `switch_slot is a descendant of the last vote,
|
||||||
// no switching proof is neceessary
|
// no switching proof is necessary
|
||||||
return true;
|
return SwitchForkDecision::NoSwitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should never consider switching to an ancestor
|
// Should never consider switching to an ancestor
|
||||||
// of your last vote
|
// of your last vote
|
||||||
assert!(!last_vote_ancestors.contains(&switch_slot));
|
assert!(!last_vote_ancestors.contains(&switch_slot));
|
||||||
|
|
||||||
|
// By this point, we know the `switch_slot` is on a different fork
|
||||||
|
// (is neither an ancestor nor descendant of `last_vote`), so a
|
||||||
|
// switching proof is necessary
|
||||||
|
let switch_proof = Hash::default();
|
||||||
let mut locked_out_stake = 0;
|
let mut locked_out_stake = 0;
|
||||||
let mut locked_out_vote_accounts = HashSet::new();
|
let mut locked_out_vote_accounts = HashSet::new();
|
||||||
for (candidate_slot, descendants) in descendants.iter() {
|
for (candidate_slot, descendants) in descendants.iter() {
|
||||||
@ -423,9 +464,14 @@ impl Tower {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(locked_out_stake as f64 / total_stake as f64) > SWITCH_FORK_THRESHOLD
|
|
||||||
|
if (locked_out_stake as f64 / total_stake as f64) > SWITCH_FORK_THRESHOLD {
|
||||||
|
SwitchForkDecision::SwitchProof(switch_proof)
|
||||||
|
} else {
|
||||||
|
SwitchForkDecision::FailedSwitchThreshold
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or(true)
|
.unwrap_or(SwitchForkDecision::NoSwitch)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_vote_stake_threshold(
|
pub fn check_vote_stake_threshold(
|
||||||
@ -583,7 +629,7 @@ pub mod test {
|
|||||||
cluster_info_vote_listener::VoteTracker,
|
cluster_info_vote_listener::VoteTracker,
|
||||||
cluster_slots::ClusterSlots,
|
cluster_slots::ClusterSlots,
|
||||||
progress_map::ForkProgress,
|
progress_map::ForkProgress,
|
||||||
replay_stage::{HeaviestForkFailures, ReplayStage},
|
replay_stage::{HeaviestForkFailures, ReplayStage, SelectVoteAndResetForkResult},
|
||||||
};
|
};
|
||||||
use solana_ledger::bank_forks::BankForks;
|
use solana_ledger::bank_forks::BankForks;
|
||||||
use solana_runtime::{
|
use solana_runtime::{
|
||||||
@ -716,7 +762,10 @@ pub mod test {
|
|||||||
|
|
||||||
// Try to vote on the given slot
|
// Try to vote on the given slot
|
||||||
let descendants = self.bank_forks.read().unwrap().descendants();
|
let descendants = self.bank_forks.read().unwrap().descendants();
|
||||||
let (_, _, failure_reasons) = ReplayStage::select_vote_and_reset_forks(
|
let SelectVoteAndResetForkResult {
|
||||||
|
heaviest_fork_failures,
|
||||||
|
..
|
||||||
|
} = ReplayStage::select_vote_and_reset_forks(
|
||||||
&Some(vote_bank.clone()),
|
&Some(vote_bank.clone()),
|
||||||
&None,
|
&None,
|
||||||
&ancestors,
|
&ancestors,
|
||||||
@ -727,8 +776,8 @@ pub mod test {
|
|||||||
|
|
||||||
// Make sure this slot isn't locked out or failing threshold
|
// Make sure this slot isn't locked out or failing threshold
|
||||||
info!("Checking vote: {}", vote_bank.slot());
|
info!("Checking vote: {}", vote_bank.slot());
|
||||||
if !failure_reasons.is_empty() {
|
if !heaviest_fork_failures.is_empty() {
|
||||||
return failure_reasons;
|
return heaviest_fork_failures;
|
||||||
}
|
}
|
||||||
let vote = tower.new_vote_from_bank(&vote_bank, &my_vote_pubkey).0;
|
let vote = tower.new_vote_from_bank(&vote_bank, &my_vote_pubkey).0;
|
||||||
if let Some(new_root) = tower.record_bank_vote(vote) {
|
if let Some(new_root) = tower.record_bank_vote(vote) {
|
||||||
@ -905,6 +954,34 @@ pub mod test {
|
|||||||
stakes
|
stakes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_vote_instruction() {
|
||||||
|
let vote = Vote::default();
|
||||||
|
let mut decision = SwitchForkDecision::FailedSwitchThreshold;
|
||||||
|
assert!(decision
|
||||||
|
.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default())
|
||||||
|
.is_none());
|
||||||
|
decision = SwitchForkDecision::NoSwitch;
|
||||||
|
assert_eq!(
|
||||||
|
decision.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default()),
|
||||||
|
Some(vote_instruction::vote(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&Pubkey::default(),
|
||||||
|
vote.clone(),
|
||||||
|
))
|
||||||
|
);
|
||||||
|
decision = SwitchForkDecision::SwitchProof(Hash::default());
|
||||||
|
assert_eq!(
|
||||||
|
decision.to_vote_instruction(vote.clone(), &Pubkey::default(), &Pubkey::default()),
|
||||||
|
Some(vote_instruction::vote_switch(
|
||||||
|
&Pubkey::default(),
|
||||||
|
&Pubkey::default(),
|
||||||
|
vote,
|
||||||
|
Hash::default()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_votes() {
|
fn test_simple_votes() {
|
||||||
// Init state
|
// Init state
|
||||||
@ -975,85 +1052,106 @@ pub mod test {
|
|||||||
tower.record_vote(47, Hash::default());
|
tower.record_vote(47, Hash::default());
|
||||||
|
|
||||||
// Trying to switch to a descendant of last vote should always work
|
// Trying to switch to a descendant of last vote should always work
|
||||||
assert!(tower.check_switch_threshold(
|
assert_eq!(
|
||||||
48,
|
tower.check_switch_threshold(
|
||||||
&ancestors,
|
48,
|
||||||
&descendants,
|
&ancestors,
|
||||||
&vote_simulator.progress,
|
&descendants,
|
||||||
total_stake,
|
&vote_simulator.progress,
|
||||||
bank0.epoch_vote_accounts(0).unwrap(),
|
total_stake,
|
||||||
));
|
bank0.epoch_vote_accounts(0).unwrap(),
|
||||||
|
),
|
||||||
|
SwitchForkDecision::NoSwitch
|
||||||
|
);
|
||||||
|
|
||||||
// Trying to switch to another fork at 110 should fail
|
// Trying to switch to another fork at 110 should fail
|
||||||
assert!(!tower.check_switch_threshold(
|
assert_eq!(
|
||||||
110,
|
tower.check_switch_threshold(
|
||||||
&ancestors,
|
110,
|
||||||
&descendants,
|
&ancestors,
|
||||||
&vote_simulator.progress,
|
&descendants,
|
||||||
total_stake,
|
&vote_simulator.progress,
|
||||||
bank0.epoch_vote_accounts(0).unwrap(),
|
total_stake,
|
||||||
));
|
bank0.epoch_vote_accounts(0).unwrap(),
|
||||||
|
),
|
||||||
|
SwitchForkDecision::FailedSwitchThreshold
|
||||||
|
);
|
||||||
|
|
||||||
// Adding another validator lockout on a descendant of last vote should
|
// Adding another validator lockout on a descendant of last vote should
|
||||||
// not count toward the switch threshold
|
// not count toward the switch threshold
|
||||||
vote_simulator.simulate_lockout_interval(50, (49, 100), &other_vote_account);
|
vote_simulator.simulate_lockout_interval(50, (49, 100), &other_vote_account);
|
||||||
assert!(!tower.check_switch_threshold(
|
assert_eq!(
|
||||||
110,
|
tower.check_switch_threshold(
|
||||||
&ancestors,
|
110,
|
||||||
&descendants,
|
&ancestors,
|
||||||
&vote_simulator.progress,
|
&descendants,
|
||||||
total_stake,
|
&vote_simulator.progress,
|
||||||
bank0.epoch_vote_accounts(0).unwrap(),
|
total_stake,
|
||||||
));
|
bank0.epoch_vote_accounts(0).unwrap(),
|
||||||
|
),
|
||||||
|
SwitchForkDecision::FailedSwitchThreshold
|
||||||
|
);
|
||||||
|
|
||||||
// Adding another validator lockout on an ancestor of last vote should
|
// Adding another validator lockout on an ancestor of last vote should
|
||||||
// not count toward the switch threshold
|
// not count toward the switch threshold
|
||||||
vote_simulator.simulate_lockout_interval(50, (45, 100), &other_vote_account);
|
vote_simulator.simulate_lockout_interval(50, (45, 100), &other_vote_account);
|
||||||
assert!(!tower.check_switch_threshold(
|
assert_eq!(
|
||||||
110,
|
tower.check_switch_threshold(
|
||||||
&ancestors,
|
110,
|
||||||
&descendants,
|
&ancestors,
|
||||||
&vote_simulator.progress,
|
&descendants,
|
||||||
total_stake,
|
&vote_simulator.progress,
|
||||||
bank0.epoch_vote_accounts(0).unwrap(),
|
total_stake,
|
||||||
));
|
bank0.epoch_vote_accounts(0).unwrap(),
|
||||||
|
),
|
||||||
|
SwitchForkDecision::FailedSwitchThreshold
|
||||||
|
);
|
||||||
|
|
||||||
// Adding another validator lockout on a different fork, but the lockout
|
// Adding another validator lockout on a different fork, but the lockout
|
||||||
// doesn't cover the last vote, should not satisfy the switch threshold
|
// doesn't cover the last vote, should not satisfy the switch threshold
|
||||||
vote_simulator.simulate_lockout_interval(14, (12, 46), &other_vote_account);
|
vote_simulator.simulate_lockout_interval(14, (12, 46), &other_vote_account);
|
||||||
assert!(!tower.check_switch_threshold(
|
assert_eq!(
|
||||||
110,
|
tower.check_switch_threshold(
|
||||||
&ancestors,
|
110,
|
||||||
&descendants,
|
&ancestors,
|
||||||
&vote_simulator.progress,
|
&descendants,
|
||||||
total_stake,
|
&vote_simulator.progress,
|
||||||
bank0.epoch_vote_accounts(0).unwrap(),
|
total_stake,
|
||||||
));
|
bank0.epoch_vote_accounts(0).unwrap(),
|
||||||
|
),
|
||||||
|
SwitchForkDecision::FailedSwitchThreshold
|
||||||
|
);
|
||||||
|
|
||||||
// Adding another validator lockout on a different fork, and the lockout
|
// Adding another validator lockout on a different fork, and the lockout
|
||||||
// covers the last vote, should satisfy the switch threshold
|
// covers the last vote, should satisfy the switch threshold
|
||||||
vote_simulator.simulate_lockout_interval(14, (12, 47), &other_vote_account);
|
vote_simulator.simulate_lockout_interval(14, (12, 47), &other_vote_account);
|
||||||
assert!(tower.check_switch_threshold(
|
assert_eq!(
|
||||||
110,
|
tower.check_switch_threshold(
|
||||||
&ancestors,
|
110,
|
||||||
&descendants,
|
&ancestors,
|
||||||
&vote_simulator.progress,
|
&descendants,
|
||||||
total_stake,
|
&vote_simulator.progress,
|
||||||
bank0.epoch_vote_accounts(0).unwrap(),
|
total_stake,
|
||||||
));
|
bank0.epoch_vote_accounts(0).unwrap(),
|
||||||
|
),
|
||||||
|
SwitchForkDecision::SwitchProof(Hash::default())
|
||||||
|
);
|
||||||
|
|
||||||
// If we set a root, then any lockout intervals below the root shouldn't
|
// If we set a root, then any lockout intervals below the root shouldn't
|
||||||
// count toward the switch threshold. This means the other validator's
|
// count toward the switch threshold. This means the other validator's
|
||||||
// vote lockout no longer counts
|
// vote lockout no longer counts
|
||||||
vote_simulator.set_root(43);
|
vote_simulator.set_root(43);
|
||||||
assert!(!tower.check_switch_threshold(
|
assert_eq!(
|
||||||
110,
|
tower.check_switch_threshold(
|
||||||
&vote_simulator.bank_forks.read().unwrap().ancestors(),
|
110,
|
||||||
&vote_simulator.bank_forks.read().unwrap().descendants(),
|
&vote_simulator.bank_forks.read().unwrap().ancestors(),
|
||||||
&vote_simulator.progress,
|
&vote_simulator.bank_forks.read().unwrap().descendants(),
|
||||||
total_stake,
|
&vote_simulator.progress,
|
||||||
bank0.epoch_vote_accounts(0).unwrap(),
|
total_stake,
|
||||||
));
|
bank0.epoch_vote_accounts(0).unwrap(),
|
||||||
|
),
|
||||||
|
SwitchForkDecision::FailedSwitchThreshold
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -6,7 +6,7 @@ use crate::{
|
|||||||
cluster_info_vote_listener::VoteTracker,
|
cluster_info_vote_listener::VoteTracker,
|
||||||
cluster_slots::ClusterSlots,
|
cluster_slots::ClusterSlots,
|
||||||
commitment::{AggregateCommitmentService, BlockCommitmentCache, CommitmentAggregationData},
|
commitment::{AggregateCommitmentService, BlockCommitmentCache, CommitmentAggregationData},
|
||||||
consensus::{StakeLockout, Tower},
|
consensus::{StakeLockout, SwitchForkDecision, Tower},
|
||||||
poh_recorder::{PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS},
|
poh_recorder::{PohRecorder, GRACE_TICKS_FACTOR, MAX_GRACE_SLOTS},
|
||||||
progress_map::{ForkProgress, ForkStats, ProgressMap, PropagatedStats},
|
progress_map::{ForkProgress, ForkStats, ProgressMap, PropagatedStats},
|
||||||
pubkey_references::PubkeyReferences,
|
pubkey_references::PubkeyReferences,
|
||||||
@ -55,6 +55,7 @@ use std::{
|
|||||||
pub const MAX_ENTRY_RECV_PER_ITER: usize = 512;
|
pub const MAX_ENTRY_RECV_PER_ITER: usize = 512;
|
||||||
pub const SUPERMINORITY_THRESHOLD: f64 = 1f64 / 3f64;
|
pub const SUPERMINORITY_THRESHOLD: f64 = 1f64 / 3f64;
|
||||||
pub const MAX_UNCONFIRMED_SLOTS: usize = 5;
|
pub const MAX_UNCONFIRMED_SLOTS: usize = 5;
|
||||||
|
pub const UNLOCK_SWITCH_VOTE_SLOT: Slot = 5_000_000;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub(crate) enum HeaviestForkFailures {
|
pub(crate) enum HeaviestForkFailures {
|
||||||
@ -139,6 +140,12 @@ impl ReplayTiming {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SelectVoteAndResetForkResult {
|
||||||
|
pub vote_bank: Option<(Arc<Bank>, SwitchForkDecision)>,
|
||||||
|
pub reset_bank: Option<Arc<Bank>>,
|
||||||
|
pub heaviest_fork_failures: Vec<HeaviestForkFailures>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ReplayStage {
|
pub struct ReplayStage {
|
||||||
t_replay: JoinHandle<Result<()>>,
|
t_replay: JoinHandle<Result<()>>,
|
||||||
commitment_service: AggregateCommitmentService,
|
commitment_service: AggregateCommitmentService,
|
||||||
@ -316,15 +323,18 @@ impl ReplayStage {
|
|||||||
Self::report_memory(&allocated, "select_fork", start);
|
Self::report_memory(&allocated, "select_fork", start);
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let (vote_bank, reset_bank, failure_reasons) =
|
let SelectVoteAndResetForkResult {
|
||||||
Self::select_vote_and_reset_forks(
|
vote_bank,
|
||||||
&heaviest_bank,
|
reset_bank,
|
||||||
&heaviest_bank_on_same_fork,
|
heaviest_fork_failures,
|
||||||
&ancestors,
|
} = Self::select_vote_and_reset_forks(
|
||||||
&descendants,
|
&heaviest_bank,
|
||||||
&progress,
|
&heaviest_bank_on_same_fork,
|
||||||
&tower,
|
&ancestors,
|
||||||
);
|
&descendants,
|
||||||
|
&progress,
|
||||||
|
&tower,
|
||||||
|
);
|
||||||
let select_vote_and_reset_forks_elapsed = now.elapsed().as_micros();
|
let select_vote_and_reset_forks_elapsed = now.elapsed().as_micros();
|
||||||
replay_timing.update(
|
replay_timing.update(
|
||||||
compute_bank_stats_elapsed as u64,
|
compute_bank_stats_elapsed as u64,
|
||||||
@ -333,15 +343,15 @@ impl ReplayStage {
|
|||||||
|
|
||||||
if heaviest_bank.is_some()
|
if heaviest_bank.is_some()
|
||||||
&& tower.is_recent(heaviest_bank.as_ref().unwrap().slot())
|
&& tower.is_recent(heaviest_bank.as_ref().unwrap().slot())
|
||||||
&& !failure_reasons.is_empty()
|
&& !heaviest_fork_failures.is_empty()
|
||||||
{
|
{
|
||||||
info!(
|
info!(
|
||||||
"Couldn't vote on heaviest fork: {:?}, failure_reasons: {:?}",
|
"Couldn't vote on heaviest fork: {:?}, heaviest_fork_failures: {:?}",
|
||||||
heaviest_bank.as_ref().map(|b| b.slot()),
|
heaviest_bank.as_ref().map(|b| b.slot()),
|
||||||
failure_reasons
|
heaviest_fork_failures
|
||||||
);
|
);
|
||||||
|
|
||||||
for r in failure_reasons {
|
for r in heaviest_fork_failures {
|
||||||
if let HeaviestForkFailures::NoPropagatedConfirmation(slot) = r {
|
if let HeaviestForkFailures::NoPropagatedConfirmation(slot) = r {
|
||||||
if let Some(latest_leader_slot) =
|
if let Some(latest_leader_slot) =
|
||||||
progress.get_latest_leader_slot(slot)
|
progress.get_latest_leader_slot(slot)
|
||||||
@ -355,7 +365,7 @@ impl ReplayStage {
|
|||||||
let start = allocated.get();
|
let start = allocated.get();
|
||||||
|
|
||||||
// Vote on a fork
|
// Vote on a fork
|
||||||
if let Some(ref vote_bank) = vote_bank {
|
if let Some((ref vote_bank, ref switch_fork_decision)) = vote_bank {
|
||||||
if let Some(votable_leader) =
|
if let Some(votable_leader) =
|
||||||
leader_schedule_cache.slot_leader_at(vote_bank.slot(), Some(vote_bank))
|
leader_schedule_cache.slot_leader_at(vote_bank.slot(), Some(vote_bank))
|
||||||
{
|
{
|
||||||
@ -369,6 +379,7 @@ impl ReplayStage {
|
|||||||
|
|
||||||
Self::handle_votable_bank(
|
Self::handle_votable_bank(
|
||||||
&vote_bank,
|
&vote_bank,
|
||||||
|
switch_fork_decision,
|
||||||
&bank_forks,
|
&bank_forks,
|
||||||
&mut tower,
|
&mut tower,
|
||||||
&mut progress,
|
&mut progress,
|
||||||
@ -394,7 +405,10 @@ impl ReplayStage {
|
|||||||
if last_reset != reset_bank.last_blockhash() {
|
if last_reset != reset_bank.last_blockhash() {
|
||||||
info!(
|
info!(
|
||||||
"vote bank: {:?} reset bank: {:?}",
|
"vote bank: {:?} reset bank: {:?}",
|
||||||
vote_bank.as_ref().map(|b| b.slot()),
|
vote_bank.as_ref().map(|(b, switch_fork_decision)| (
|
||||||
|
b.slot(),
|
||||||
|
switch_fork_decision
|
||||||
|
)),
|
||||||
reset_bank.slot(),
|
reset_bank.slot(),
|
||||||
);
|
);
|
||||||
let fork_progress = progress
|
let fork_progress = progress
|
||||||
@ -420,7 +434,8 @@ impl ReplayStage {
|
|||||||
tpu_has_bank = false;
|
tpu_has_bank = false;
|
||||||
|
|
||||||
if !partition
|
if !partition
|
||||||
&& vote_bank.as_ref().map(|b| b.slot()) != Some(reset_bank.slot())
|
&& vote_bank.as_ref().map(|(b, _)| b.slot())
|
||||||
|
!= Some(reset_bank.slot())
|
||||||
{
|
{
|
||||||
warn!(
|
warn!(
|
||||||
"PARTITION DETECTED waiting to join fork: {} last vote: {:?}",
|
"PARTITION DETECTED waiting to join fork: {} last vote: {:?}",
|
||||||
@ -434,7 +449,8 @@ impl ReplayStage {
|
|||||||
);
|
);
|
||||||
partition = true;
|
partition = true;
|
||||||
} else if partition
|
} else if partition
|
||||||
&& vote_bank.as_ref().map(|b| b.slot()) == Some(reset_bank.slot())
|
&& vote_bank.as_ref().map(|(b, _)| b.slot())
|
||||||
|
== Some(reset_bank.slot())
|
||||||
{
|
{
|
||||||
warn!(
|
warn!(
|
||||||
"PARTITION resolved fork: {} last vote: {:?}",
|
"PARTITION resolved fork: {} last vote: {:?}",
|
||||||
@ -841,6 +857,7 @@ impl ReplayStage {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn handle_votable_bank(
|
fn handle_votable_bank(
|
||||||
bank: &Arc<Bank>,
|
bank: &Arc<Bank>,
|
||||||
|
switch_fork_decision: &SwitchForkDecision,
|
||||||
bank_forks: &Arc<RwLock<BankForks>>,
|
bank_forks: &Arc<RwLock<BankForks>>,
|
||||||
tower: &mut Tower,
|
tower: &mut Tower,
|
||||||
progress: &mut ProgressMap,
|
progress: &mut ProgressMap,
|
||||||
@ -917,6 +934,7 @@ impl ReplayStage {
|
|||||||
authorized_voter_keypairs,
|
authorized_voter_keypairs,
|
||||||
tower.last_vote_and_timestamp(),
|
tower.last_vote_and_timestamp(),
|
||||||
tower_index,
|
tower_index,
|
||||||
|
switch_fork_decision,
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -928,6 +946,7 @@ impl ReplayStage {
|
|||||||
authorized_voter_keypairs: &[Arc<Keypair>],
|
authorized_voter_keypairs: &[Arc<Keypair>],
|
||||||
vote: Vote,
|
vote: Vote,
|
||||||
tower_index: usize,
|
tower_index: usize,
|
||||||
|
switch_fork_decision: &SwitchForkDecision,
|
||||||
) {
|
) {
|
||||||
if authorized_voter_keypairs.is_empty() {
|
if authorized_voter_keypairs.is_empty() {
|
||||||
return;
|
return;
|
||||||
@ -978,11 +997,21 @@ impl ReplayStage {
|
|||||||
let node_keypair = cluster_info.keypair.clone();
|
let node_keypair = cluster_info.keypair.clone();
|
||||||
|
|
||||||
// Send our last few votes along with the new one
|
// Send our last few votes along with the new one
|
||||||
let vote_ix = vote_instruction::vote(
|
let vote_ix = if bank.slot() > UNLOCK_SWITCH_VOTE_SLOT {
|
||||||
&vote_account_pubkey,
|
switch_fork_decision
|
||||||
&authorized_voter_keypair.pubkey(),
|
.to_vote_instruction(
|
||||||
vote,
|
vote,
|
||||||
);
|
&vote_account_pubkey,
|
||||||
|
&authorized_voter_keypair.pubkey(),
|
||||||
|
)
|
||||||
|
.expect("Switch threshold failure should not lead to voting")
|
||||||
|
} else {
|
||||||
|
vote_instruction::vote(
|
||||||
|
&vote_account_pubkey,
|
||||||
|
&authorized_voter_keypair.pubkey(),
|
||||||
|
vote,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
|
let mut vote_tx = Transaction::new_with_payer(&[vote_ix], Some(&node_keypair.pubkey()));
|
||||||
|
|
||||||
@ -1389,11 +1418,7 @@ impl ReplayStage {
|
|||||||
descendants: &HashMap<u64, HashSet<u64>>,
|
descendants: &HashMap<u64, HashSet<u64>>,
|
||||||
progress: &ProgressMap,
|
progress: &ProgressMap,
|
||||||
tower: &Tower,
|
tower: &Tower,
|
||||||
) -> (
|
) -> SelectVoteAndResetForkResult {
|
||||||
Option<Arc<Bank>>,
|
|
||||||
Option<Arc<Bank>>,
|
|
||||||
Vec<HeaviestForkFailures>,
|
|
||||||
) {
|
|
||||||
// Try to vote on the actual heaviest fork. If the heaviest bank is
|
// Try to vote on the actual heaviest fork. If the heaviest bank is
|
||||||
// locked out or fails the threshold check, the validator will:
|
// locked out or fails the threshold check, the validator will:
|
||||||
// 1) Not continue to vote on current fork, waiting for lockouts to expire/
|
// 1) Not continue to vote on current fork, waiting for lockouts to expire/
|
||||||
@ -1410,7 +1435,7 @@ impl ReplayStage {
|
|||||||
let mut failure_reasons = vec![];
|
let mut failure_reasons = vec![];
|
||||||
let selected_fork = {
|
let selected_fork = {
|
||||||
if let Some(bank) = heaviest_bank {
|
if let Some(bank) = heaviest_bank {
|
||||||
let switch_threshold = tower.check_switch_threshold(
|
let switch_fork_decision = tower.check_switch_threshold(
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
&ancestors,
|
&ancestors,
|
||||||
&descendants,
|
&descendants,
|
||||||
@ -1420,30 +1445,30 @@ impl ReplayStage {
|
|||||||
"Bank epoch vote accounts must contain entry for the bank's own epoch",
|
"Bank epoch vote accounts must contain entry for the bank's own epoch",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if !switch_threshold {
|
if switch_fork_decision == SwitchForkDecision::FailedSwitchThreshold {
|
||||||
// If we can't switch, then reset to the the next votable
|
// If we can't switch, then reset to the the next votable
|
||||||
// bank on the same fork as our last vote, but don't vote
|
// bank on the same fork as our last vote, but don't vote
|
||||||
info!(
|
info!(
|
||||||
"Waiting to switch to {}, voting on {:?} on same fork for now",
|
"Waiting to switch vote to {}, resetting to slot {:?} on same fork for now",
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
heaviest_bank_on_same_fork.as_ref().map(|b| b.slot())
|
heaviest_bank_on_same_fork.as_ref().map(|b| b.slot())
|
||||||
);
|
);
|
||||||
failure_reasons.push(HeaviestForkFailures::FailedSwitchThreshold(bank.slot()));
|
failure_reasons.push(HeaviestForkFailures::FailedSwitchThreshold(bank.slot()));
|
||||||
heaviest_bank_on_same_fork
|
heaviest_bank_on_same_fork
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|b| (b, switch_threshold))
|
.map(|b| (b, switch_fork_decision))
|
||||||
} else {
|
} else {
|
||||||
// If the switch threshold is observed, halt voting on
|
// If the switch threshold is observed, halt voting on
|
||||||
// the current fork and attempt to vote/reset Poh to
|
// the current fork and attempt to vote/reset Poh to
|
||||||
// the heaviest bank
|
// the heaviest bank
|
||||||
heaviest_bank.as_ref().map(|b| (b, switch_threshold))
|
heaviest_bank.as_ref().map(|b| (b, switch_fork_decision))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((bank, switch_threshold)) = selected_fork {
|
if let Some((bank, switch_fork_decision)) = selected_fork {
|
||||||
let (is_locked_out, vote_threshold, is_leader_slot, fork_weight) = {
|
let (is_locked_out, vote_threshold, is_leader_slot, fork_weight) = {
|
||||||
let fork_stats = progress.get_fork_stats(bank.slot()).unwrap();
|
let fork_stats = progress.get_fork_stats(bank.slot()).unwrap();
|
||||||
let propagated_stats = &progress.get_propagated_stats(bank.slot()).unwrap();
|
let propagated_stats = &progress.get_propagated_stats(bank.slot()).unwrap();
|
||||||
@ -1466,18 +1491,31 @@ impl ReplayStage {
|
|||||||
if !propagation_confirmed {
|
if !propagation_confirmed {
|
||||||
failure_reasons.push(HeaviestForkFailures::NoPropagatedConfirmation(bank.slot()));
|
failure_reasons.push(HeaviestForkFailures::NoPropagatedConfirmation(bank.slot()));
|
||||||
}
|
}
|
||||||
if !switch_threshold {
|
|
||||||
failure_reasons.push(HeaviestForkFailures::FailedSwitchThreshold(bank.slot()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_locked_out && vote_threshold && propagation_confirmed && switch_threshold {
|
if !is_locked_out
|
||||||
|
&& vote_threshold
|
||||||
|
&& propagation_confirmed
|
||||||
|
&& switch_fork_decision != SwitchForkDecision::FailedSwitchThreshold
|
||||||
|
{
|
||||||
info!("voting: {} {}", bank.slot(), fork_weight);
|
info!("voting: {} {}", bank.slot(), fork_weight);
|
||||||
(Some(bank.clone()), Some(bank.clone()), failure_reasons)
|
SelectVoteAndResetForkResult {
|
||||||
|
vote_bank: Some((bank.clone(), switch_fork_decision)),
|
||||||
|
reset_bank: Some(bank.clone()),
|
||||||
|
heaviest_fork_failures: failure_reasons,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
(None, Some(bank.clone()), failure_reasons)
|
SelectVoteAndResetForkResult {
|
||||||
|
vote_bank: None,
|
||||||
|
reset_bank: Some(bank.clone()),
|
||||||
|
heaviest_fork_failures: failure_reasons,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(None, None, failure_reasons)
|
SelectVoteAndResetForkResult {
|
||||||
|
vote_bank: None,
|
||||||
|
reset_bank: None,
|
||||||
|
heaviest_fork_failures: failure_reasons,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user