Use fork weight instead of individual bank weight for fork selection. (#7079)

* Fix weight calculation

* Fix tests

* fork weight

* wait until nodes are in the leader schedule

* enable sanity

* fewer long tests
This commit is contained in:
anatoly yakovenko 2019-11-21 15:47:08 -08:00 committed by GitHub
parent c965a110f2
commit d9e7a5fcbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 133 additions and 313 deletions

View File

@ -67,12 +67,13 @@ impl Tower {
bank_slot: u64,
vote_accounts: F,
ancestors: &HashMap<Slot, HashSet<u64>>,
) -> (HashMap<Slot, StakeLockout>, u64)
) -> (HashMap<Slot, StakeLockout>, u64, u128)
where
F: Iterator<Item = (Pubkey, (u64, Account))>,
{
let mut stake_lockouts = HashMap::new();
let mut total_stake = 0;
let mut total_weight = 0;
for (key, (lamports, account)) in vote_accounts {
if lamports == 0 {
continue;
@ -114,8 +115,10 @@ impl Tower {
vote_state.process_slot_vote_unchecked(bank_slot);
for vote in &vote_state.votes {
total_weight += vote.lockout() as u128 * lamports as u128;
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
}
if start_root != vote_state.root_slot {
if let Some(root) = start_root {
let vote = Lockout {
@ -123,6 +126,7 @@ impl Tower {
slot: root,
};
trace!("ROOT: {}", vote.slot);
total_weight += vote.lockout() as u128 * lamports as u128;
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
}
}
@ -131,6 +135,7 @@ impl Tower {
confirmation_count: MAX_LOCKOUT_HISTORY as u32,
slot: root,
};
total_weight += vote.lockout() as u128 * lamports as u128;
Self::update_ancestor_lockouts(&mut stake_lockouts, &vote, ancestors);
}
@ -153,7 +158,7 @@ impl Tower {
}
total_stake += lamports;
}
(stake_lockouts, total_stake)
(stake_lockouts, total_stake, total_weight)
}
pub fn is_slot_confirmed(
@ -245,18 +250,6 @@ impl Tower {
self.lockouts.root_slot
}
pub fn calculate_weight(&self, stake_lockouts: &HashMap<Slot, StakeLockout>) -> u128 {
let mut sum = 0u128;
let root_slot = self.lockouts.root_slot.unwrap_or(0);
for (slot, stake_lockout) in stake_lockouts {
if self.lockouts.root_slot.is_some() && *slot <= root_slot {
continue;
}
sum += u128::from(stake_lockout.lockout) * u128::from(stake_lockout.stake)
}
sum
}
// a slot is not recent if it's older than the newest vote we have
pub fn is_recent(&self, slot: u64) -> bool {
if let Some(last_vote) = self.lockouts.votes.back() {
@ -304,7 +297,6 @@ impl Tower {
false
}
pub fn check_vote_stake_threshold(
&self,
slot: u64,
@ -378,9 +370,9 @@ impl Tower {
}
fn bank_weight(&self, bank: &Bank, ancestors: &HashMap<Slot, HashSet<Slot>>) -> u128 {
let (stake_lockouts, _) =
let (_, _, bank_weight) =
self.collect_vote_lockouts(bank.slot(), bank.vote_accounts().into_iter(), ancestors);
self.calculate_weight(&stake_lockouts)
bank_weight
}
fn find_heaviest_bank(&self, bank_forks: &BankForks) -> Option<Arc<Bank>> {
@ -458,17 +450,22 @@ mod test {
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
.into_iter()
.collect();
let (staked_lockouts, total_staked) =
let (staked_lockouts, total_staked, bank_weight) =
tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
assert_eq!(staked_lockouts[&0].stake, 2);
assert_eq!(staked_lockouts[&0].lockout, 2 + 2 + 4 + 4);
assert_eq!(total_staked, 2);
// Each acccount has 1 vote in it. After simulating a vote in collect_vote_lockouts,
// the account will have 2 votes, with lockout 2 + 4 = 6. So expected weight for
// two acccounts is 2 * 6 = 12
assert_eq!(bank_weight, 12)
}
#[test]
fn test_collect_vote_lockouts_root() {
let votes: Vec<u64> = (0..MAX_LOCKOUT_HISTORY as u64).into_iter().collect();
//two accounts voting for slot 0 with 1 token staked
//two accounts voting for slots 0..MAX_LOCKOUT_HISTORY with 1 token staked
let accounts = gen_stakes(&[(1, &votes), (1, &votes)]);
let mut tower = Tower::new_for_tests(0, 0.67);
let mut ancestors = HashMap::new();
@ -476,8 +473,21 @@ mod test {
tower.record_vote(i as u64, Hash::default());
ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
}
let root = Lockout {
confirmation_count: MAX_LOCKOUT_HISTORY as u32,
slot: 0,
};
let root_weight = root.lockout() as u128;
let vote_account_expected_weight = tower
.lockouts
.votes
.iter()
.map(|v| v.lockout() as u128)
.sum::<u128>()
+ root_weight;
let expected_bank_weight = 2 * vote_account_expected_weight;
assert_eq!(tower.lockouts.root_slot, Some(0));
let (staked_lockouts, _total_staked) = tower.collect_vote_lockouts(
let (staked_lockouts, _total_staked, bank_weight) = tower.collect_vote_lockouts(
MAX_LOCKOUT_HISTORY as u64,
accounts.into_iter(),
&ancestors,
@ -487,46 +497,7 @@ mod test {
}
// should be the sum of all the weights for root
assert!(staked_lockouts[&0].lockout > (2 * (1 << MAX_LOCKOUT_HISTORY)));
}
#[test]
fn test_calculate_weight_skips_root() {
let mut tower = Tower::new_for_tests(0, 0.67);
tower.lockouts.root_slot = Some(1);
let stakes = vec![
(
0,
StakeLockout {
stake: 1,
lockout: 8,
},
),
(
1,
StakeLockout {
stake: 1,
lockout: 8,
},
),
]
.into_iter()
.collect();
assert_eq!(tower.calculate_weight(&stakes), 0u128);
}
#[test]
fn test_calculate_weight() {
let tower = Tower::new_for_tests(0, 0.67);
let stakes = vec![(
0,
StakeLockout {
stake: 1,
lockout: 8,
},
)]
.into_iter()
.collect();
assert_eq!(tower.calculate_weight(&stakes), 8u128);
assert_eq!(bank_weight, expected_bank_weight);
}
#[test]
@ -876,7 +847,7 @@ mod test {
for vote in &tower_votes {
tower.record_vote(*vote, Hash::default());
}
let (staked_lockouts, total_staked) =
let (staked_lockouts, total_staked, _) =
tower.collect_vote_lockouts(vote_to_evaluate, accounts.clone().into_iter(), &ancestors);
assert!(tower.check_vote_stake_threshold(vote_to_evaluate, &staked_lockouts, total_staked));
@ -884,7 +855,7 @@ mod test {
// will expire the vote in one of the vote accounts, so we should have insufficient
// stake to pass the threshold
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64 + 1;
let (staked_lockouts, total_staked) =
let (staked_lockouts, total_staked, _) =
tower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
assert!(!tower.check_vote_stake_threshold(
vote_to_evaluate,

View File

@ -44,7 +44,7 @@ use std::{
pub const MAX_ENTRY_RECV_PER_ITER: usize = 512;
type VoteAndPoHBank = (Option<(Arc<Bank>, u64)>, Option<Arc<Bank>>);
type VoteAndPoHBank = Option<(Arc<Bank>, ForkStats)>;
// Implement a destructor for the ReplayStage thread to signal it exited
// even on panics
@ -83,6 +83,7 @@ struct ReplaySlotStats {
#[derive(Debug, Clone, Default)]
struct ForkStats {
weight: u128,
fork_weight: u128,
total_staked: u64,
slot: Slot,
block_height: u64,
@ -210,7 +211,10 @@ impl ReplayStage {
let mut progress = HashMap::new();
// Initialize progress map with any root banks
for bank in bank_forks.read().unwrap().frozen_banks().values() {
progress.insert(bank.slot(), ForkProgress::new(bank.slot(), bank.last_blockhash()));
progress.insert(
bank.slot(),
ForkProgress::new(bank.slot(), bank.last_blockhash()),
);
}
let mut current_leader = None;
let mut last_reset = Hash::default();
@ -234,7 +238,11 @@ impl ReplayStage {
);
datapoint_debug!(
"replay_stage-memory",
("generate_new_bank_forks", (allocated.get() - start) as i64, i64),
(
"generate_new_bank_forks",
(allocated.get() - start) as i64,
i64
),
);
let mut tpu_has_bank = poh_recorder.lock().unwrap().has_bank();
@ -256,17 +264,26 @@ impl ReplayStage {
let ancestors = Arc::new(bank_forks.read().unwrap().ancestors());
loop {
let start = allocated.get();
let (vote_bank, heaviest) = Self::select_fork(&ancestors, &bank_forks, &tower, &mut progress);
let vote_bank = Self::select_fork(
&my_pubkey,
&ancestors,
&bank_forks,
&tower,
&mut progress,
);
datapoint_debug!(
"replay_stage-memory",
("select_fork", (allocated.get() - start) as i64, i64),
);
let done = vote_bank.is_none();
let mut vote_bank_slot = 0;
let reset_bank = vote_bank.as_ref().map(|b| b.0.clone()).or(heaviest);
if vote_bank.is_none() {
break;
}
let (bank, stats) = vote_bank.unwrap();
let mut done = false;
let mut vote_bank_slot = None;
let start = allocated.get();
if let Some((bank, total_staked)) = vote_bank {
info!("voting: {}", bank.slot());
if !stats.is_locked_out && stats.vote_threshold {
info!("voting: {} {}", bank.slot(), stats.fork_weight);
subscriptions.notify_subscribers(bank.slot(), &bank_forks);
if let Some(votable_leader) =
leader_schedule_cache.slot_leader_at(bank.slot(), Some(&bank))
@ -278,7 +295,7 @@ impl ReplayStage {
&votable_leader,
);
}
vote_bank_slot = bank.slot();
vote_bank_slot = Some(bank.slot());
Self::handle_votable_bank(
&bank,
&bank_forks,
@ -290,7 +307,7 @@ impl ReplayStage {
&blocktree,
&leader_schedule_cache,
&root_bank_sender,
total_staked,
stats.total_staked,
&lockouts_sender,
&snapshot_package_sender,
)?;
@ -300,29 +317,44 @@ impl ReplayStage {
("votable_bank", (allocated.get() - start) as i64, i64),
);
let start = allocated.get();
if let Some(bank) = reset_bank {
if last_reset != bank.last_blockhash() {
Self::reset_poh_recorder(
&my_pubkey,
&blocktree,
&bank,
&poh_recorder,
&leader_schedule_cache,
if last_reset != bank.last_blockhash() {
Self::reset_poh_recorder(
&my_pubkey,
&blocktree,
&bank,
&poh_recorder,
&leader_schedule_cache,
);
last_reset = bank.last_blockhash();
tpu_has_bank = false;
info!(
"vote bank: {:?} reset bank: {}",
vote_bank_slot,
bank.slot()
);
if !partition && vote_bank_slot != Some(bank.slot()) {
warn!(
"PARTITION DETECTED waiting to join fork: {} last vote: {:?}",
bank.slot(),
tower.last_vote()
);
last_reset = bank.last_blockhash();
tpu_has_bank = false;
info!("vote bank: {} reset bank: {}", vote_bank_slot, bank.slot());
if !partition && vote_bank_slot != bank.slot() {
warn!("PARTITION DETECTED waiting to join fork: {} last vote: {:?}", bank.slot(), tower.last_vote());
inc_new_counter_info!("replay_stage-partition_detected", 1);
datapoint_info!("replay_stage-partition", ("slot", bank.slot() as i64, i64));
partition = true;
} else if partition && vote_bank_slot == bank.slot() {
warn!("PARTITION resolved fork: {} last vote: {:?}", bank.slot(), tower.last_vote());
partition = false;
inc_new_counter_info!("replay_stage-partition_resolved", 1);
}
inc_new_counter_info!("replay_stage-partition_detected", 1);
datapoint_info!(
"replay_stage-partition",
("slot", bank.slot() as i64, i64)
);
partition = true;
} else if partition && vote_bank_slot == Some(bank.slot()) {
warn!(
"PARTITION resolved fork: {} last vote: {:?}",
bank.slot(),
tower.last_vote()
);
partition = false;
inc_new_counter_info!("replay_stage-partition_resolved", 1);
}
} else {
done = true;
}
datapoint_debug!(
"replay_stage-memory",
@ -354,8 +386,11 @@ impl ReplayStage {
datapoint_debug!(
"replay_stage-memory",
("start_leader", (allocated.get() - start) as i64, i64),
);
datapoint_debug!("replay_stage", ("duration", duration_as_ms(&now.elapsed()) as i64, i64));
);
datapoint_debug!(
"replay_stage",
("duration", duration_as_ms(&now.elapsed()) as i64, i64)
);
if did_complete_bank {
//just processed a bank, skip the signal; maybe there's more slots available
continue;
@ -733,6 +768,7 @@ impl ReplayStage {
}
fn select_fork(
my_pubkey: &Pubkey,
ancestors: &HashMap<u64, HashSet<u64>>,
bank_forks: &Arc<RwLock<BankForks>>,
tower: &Tower,
@ -770,28 +806,44 @@ impl ReplayStage {
if !stats.computed {
stats.slot = bank.slot();
let (stake_lockouts, total_staked) = tower.collect_vote_lockouts(
let (stake_lockouts, total_staked, bank_weight) = tower.collect_vote_lockouts(
bank.slot(),
bank.vote_accounts().into_iter(),
&ancestors,
);
Self::confirm_forks(tower, &stake_lockouts, total_staked, progress, bank_forks);
stats.total_staked = total_staked;
stats.weight = tower.calculate_weight(&stake_lockouts);
stats.weight = bank_weight;
stats.fork_weight = stats.weight
+ bank
.parent()
.and_then(|b| progress.get(&b.slot()))
.map(|x| x.fork_stats.fork_weight)
.unwrap_or(0);
datapoint_warn!(
"bank_weight",
("slot", bank.slot(), i64),
// u128 too large for influx, convert to hex
("weight", format!("{:X}", stats.weight), String),
);
warn!(
"{} slot_weight: {} {} {} {}",
my_pubkey,
stats.slot,
stats.weight,
stats.fork_weight,
bank.parent().map(|b| b.slot()).unwrap_or(0)
);
stats.stake_lockouts = stake_lockouts;
stats.block_height = bank.block_height();
stats.computed = true;
}
stats.vote_threshold = tower.check_vote_stake_threshold(
bank.slot(),
&stats.stake_lockouts,
stats.total_staked,
);
if !stats.computed {
if !stats.vote_threshold {
debug!("vote threshold check failed: {}", bank.slot());
}
stats.computed = true;
}
stats.is_locked_out = tower.is_locked_out(bank.slot(), &ancestors);
stats.has_voted = tower.has_voted(bank.slot());
stats.is_recent = tower.is_recent(bank.slot());
@ -811,25 +863,11 @@ impl ReplayStage {
.filter(|s| s.is_recent && !s.has_voted && !s.vote_threshold)
.count();
let mut candidates: Vec<_> = frozen_banks
.iter()
.zip(stats.iter())
.filter(|(_, stats)| stats.is_recent && !stats.has_voted)
.collect();
let mut candidates: Vec<_> = frozen_banks.iter().zip(stats.iter()).collect();
//highest weight, lowest slot first
candidates.sort_by_key(|b| (b.1.weight, 0i64 - b.1.slot as i64));
candidates.iter().for_each(|(_, stats)| {
let mut parents: Vec<_> = if let Some(set) = ancestors.get(&stats.slot) {
set.iter().collect()
} else {
vec![]
};
parents.sort();
debug!("{}: {:?} {:?}", stats.slot, stats, parents,);
});
let rv = Self::pick_best_fork(ancestors, &candidates);
candidates.sort_by_key(|b| (b.1.fork_weight, 0i64 - b.1.slot as i64));
let rv = candidates.last();
let ms = timing::duration_as_ms(&tower_start.elapsed());
let weights: Vec<(u128, u64, u64)> = candidates
.iter()
@ -842,7 +880,7 @@ impl ReplayStage {
candidates.len(),
stats.iter().filter(|s| !s.has_voted).count(),
weights,
rv.0.is_some()
rv.is_some()
);
datapoint_debug!(
"replay_stage-select_fork",
@ -859,66 +897,7 @@ impl ReplayStage {
),
("tower_duration", ms as i64, i64),
);
rv
}
fn pick_best_fork(
ancestors: &HashMap<u64, HashSet<u64>>,
best_banks: &[(&Arc<Bank>, &ForkStats)],
) -> VoteAndPoHBank {
if best_banks.is_empty() {
return (None, None);
}
let mut vote = None;
let (mut best_bank, best_stats) = best_banks.last().unwrap();
debug!("best bank: {:?}", best_stats);
let mut by_slot: Vec<_> = best_banks.iter().collect();
by_slot.sort_by_key(|x| x.1.slot);
//look for the oldest ancestors of the best bank
if let Some(best_ancestors) = ancestors.get(&best_stats.slot) {
for (parent, parent_stats) in by_slot.iter() {
if parent_stats.is_locked_out || !parent_stats.vote_threshold {
continue;
}
if !best_ancestors.contains(&parent_stats.slot) {
continue;
}
debug!("best bank found ancestor: {}", parent_stats.slot);
inc_new_counter_info!("replay_stage-pick_best_fork-ancestor", 1);
vote = Some(((*parent).clone(), parent_stats.total_staked));
break;
}
}
//look for the heaviest child of the best bank
if vote.is_none() {
for (child, child_stats) in best_banks.iter().rev() {
let has_best = best_stats.slot == child_stats.slot
|| ancestors
.get(&child.slot())
.map(|set| set.contains(&best_stats.slot))
.unwrap_or(false);
if !has_best {
continue;
}
best_bank = child;
if child_stats.is_locked_out || !child_stats.vote_threshold {
continue;
}
datapoint_info!(
"replay_stage-pick_best_fork",
("slot", best_bank.slot(), i64),
("weight", child_stats.weight, f64),
);
inc_new_counter_info!("replay_stage-pick_best_fork-child", 1);
debug!("best bank found child: {}", child_stats.slot);
vote = Some(((*child).clone(), child_stats.total_staked));
break;
}
}
if vote.is_none() {
inc_new_counter_info!("replay_stage-fork_selection-heavy_bank_lockout", 1);
}
(vote, Some((*best_bank).clone()))
rv.cloned().map(|x| (x.0.clone(), x.1.clone()))
}
fn confirm_forks(
@ -1179,7 +1158,6 @@ mod test {
use solana_vote_program::vote_state::VoteState;
use std::{
fs::remove_dir_all,
iter::FromIterator,
sync::{Arc, RwLock},
};
@ -1585,135 +1563,6 @@ mod test {
);
}
#[test]
fn test_pick_best_fork() {
let leader_pubkey = Pubkey::new_rand();
let leader_lamports = 3;
let genesis_config_info =
create_genesis_config_with_leader(50, &leader_pubkey, leader_lamports);
let mut genesis_config = genesis_config_info.genesis_config;
genesis_config.epoch_schedule.warmup = false;
genesis_config.ticks_per_slot = 4;
let bank_default = Arc::new(Bank::new(&genesis_config));
// Create bank forks such that
// /- 9 - 11
// 7 - 8
// \- 10 - 12
//
let mut ancestors: HashMap<u64, HashSet<u64>> = HashMap::new();
ancestors.insert(8, HashSet::from_iter(vec![7u64]));
ancestors.insert(9, HashSet::from_iter(vec![8, 7]));
ancestors.insert(11, HashSet::from_iter(vec![9, 8, 7]));
ancestors.insert(10, HashSet::from_iter(vec![8, 7]));
ancestors.insert(12, HashSet::from_iter(vec![10, 8, 7]));
let bank7 = Arc::new(Bank::new_from_parent(&bank_default, &Pubkey::new_rand(), 7));
let bank8 = Arc::new(Bank::new_from_parent(&bank7, &Pubkey::new_rand(), 8));
let bank9 = Arc::new(Bank::new_from_parent(&bank8, &Pubkey::new_rand(), 9));
let bank11 = Arc::new(Bank::new_from_parent(&bank9, &Pubkey::new_rand(), 11));
let bank10 = Arc::new(Bank::new_from_parent(&bank8, &Pubkey::new_rand(), 10));
let bank12 = Arc::new(Bank::new_from_parent(&bank10, &Pubkey::new_rand(), 12));
let banks = vec![
(bank7, ForkStats::default()),
(bank8, ForkStats::default()),
(bank9, ForkStats::default()),
(bank10, ForkStats::default()),
(bank11, ForkStats::default()),
(bank12, ForkStats::default()),
];
// Fork stats are: no lockouts, and vote_threshold is met for all banks
let mut best_banks: Vec<_> = banks
.into_iter()
.map(|(b, mut f)| {
f.slot = b.slot();
f.is_locked_out = false;
f.vote_threshold = true;
(b, f)
})
.collect();
let best_banks_ref: Vec<_> = best_banks.iter().map(|(b, f)| (b, f)).collect();
let res = ReplayStage::pick_best_fork(&ancestors, &best_banks_ref[..]);
assert!(res.0.is_some());
assert_eq!(res.0.unwrap().0.slot(), 7);
// Set bank7 as locked out. Should return bank8 as best fork
best_banks[0].1.is_locked_out = true;
let best_banks_ref: Vec<_> = best_banks.iter().map(|(b, f)| (b, f)).collect();
let res = ReplayStage::pick_best_fork(&ancestors, &best_banks_ref[..]);
assert!(res.0.is_some());
assert_eq!(res.0.unwrap().0.slot(), 8);
// Set bank8 as missing vote threshold. Should return bank10 as best fork (as 9 on a different fork)
best_banks[1].1.vote_threshold = false;
let best_banks_ref: Vec<_> = best_banks.iter().map(|(b, f)| (b, f)).collect();
let res = ReplayStage::pick_best_fork(&ancestors, &best_banks_ref[..]);
assert!(res.0.is_some());
assert_eq!(res.0.unwrap().0.slot(), 10);
// Set bank10 also as missing vote threshold. It should return 12 (oldest child of best bank)
best_banks[3].1.vote_threshold = false;
let best_banks_ref: Vec<_> = best_banks.iter().map(|(b, f)| (b, f)).collect();
let res = ReplayStage::pick_best_fork(&ancestors, &best_banks_ref[..]);
assert!(res.0.is_some());
assert_eq!(res.0.unwrap().0.slot(), 12);
// Set bank12 also as missing vote threshold.
best_banks[5].1.vote_threshold = false;
let best_banks_ref: Vec<_> = best_banks.iter().map(|(b, f)| (b, f)).collect();
let res = ReplayStage::pick_best_fork(&ancestors, &best_banks_ref[..]);
assert!(res.0.is_none());
assert!(res.1.is_some());
assert_eq!(res.1.unwrap().slot(), 12);
// Test if heaviest bank has a leaf which is not the heaviest leaf
// e.g. heaviest bank is 9, but it's leaf (11) is lighter than 12
let bank7 = Arc::new(Bank::new_from_parent(&bank_default, &Pubkey::new_rand(), 7));
let bank8 = Arc::new(Bank::new_from_parent(&bank7, &Pubkey::new_rand(), 8));
let bank9 = Arc::new(Bank::new_from_parent(&bank8, &Pubkey::new_rand(), 9));
let bank11 = Arc::new(Bank::new_from_parent(&bank9, &Pubkey::new_rand(), 11));
let bank10 = Arc::new(Bank::new_from_parent(&bank8, &Pubkey::new_rand(), 10));
let bank12 = Arc::new(Bank::new_from_parent(&bank10, &Pubkey::new_rand(), 12));
let banks = vec![
(bank7, ForkStats::default()),
(bank8, ForkStats::default()),
(bank10, ForkStats::default()),
(bank11, ForkStats::default()),
(bank12, ForkStats::default()),
(bank9, ForkStats::default()),
];
// Assume everything is locked out (or does not have threshold)
let best_banks: Vec<_> = banks
.into_iter()
.map(|(b, mut f)| {
f.slot = b.slot();
f.is_locked_out = true;
f.vote_threshold = true;
(b, f)
})
.collect();
let best_banks_ref: Vec<_> = best_banks.iter().map(|(b, f)| (b, f)).collect();
// It should return 11, as it's child of heaviest bank
let res = ReplayStage::pick_best_fork(&ancestors, &best_banks_ref[..]);
assert!(res.0.is_none());
assert!(res.1.is_some());
assert_eq!(res.1.unwrap().slot(), 11);
}
#[test]
fn test_write_persist_transaction_status() {
let GenesisConfigInfo {

View File

@ -200,7 +200,7 @@ fn run_network_partition(partitions: &[usize]) {
..ClusterConfig::default()
};
let now = timestamp();
let partition_start = now + 30_000;
let partition_start = now + 60_000;
let partition_end = partition_start + 10_000;
let mut total = 0;
for (j, pn) in partitions.iter().enumerate() {