parent
407b1d3e6f
commit
29611fb61d
|
@ -62,7 +62,7 @@
|
||||||
- [Blocktree](blocktree.md)
|
- [Blocktree](blocktree.md)
|
||||||
- [Cluster Software Installation and Updates](installer.md)
|
- [Cluster Software Installation and Updates](installer.md)
|
||||||
- [Deterministic Transaction Fees](transaction-fees.md)
|
- [Deterministic Transaction Fees](transaction-fees.md)
|
||||||
- [Fork Selection](fork-selection.md)
|
- [Tower BFT](tower-bft.md)
|
||||||
- [Leader-to-Leader Transition](leader-leader-transition.md)
|
- [Leader-to-Leader Transition](leader-leader-transition.md)
|
||||||
- [Leader-to-Validator Transition](leader-validator-transition.md)
|
- [Leader-to-Validator Transition](leader-validator-transition.md)
|
||||||
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
|
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
|
||||||
|
|
|
@ -4,7 +4,7 @@ A validator votes on a PoH hash for two purposes. First, the vote indicates it
|
||||||
believes the ledger is valid up until that point in time. Second, since many
|
believes the ledger is valid up until that point in time. Second, since many
|
||||||
valid forks may exist at a given height, the vote also indicates exclusive
|
valid forks may exist at a given height, the vote also indicates exclusive
|
||||||
support for the fork. This document describes only the former. The latter is
|
support for the fork. This document describes only the former. The latter is
|
||||||
described in [fork selection](fork-selection.md).
|
described in [Tower BFT](tower-bft.md).
|
||||||
|
|
||||||
## Current Design
|
## Current Design
|
||||||
|
|
||||||
|
@ -50,12 +50,11 @@ log the time since the NewBlock transaction was submitted.
|
||||||
|
|
||||||
### Finality and Payouts
|
### Finality and Payouts
|
||||||
|
|
||||||
Locktower is the proposed [fork selection](fork-selection.md) algorithm. It
|
[Tower BFT](tower-bft.md) is the proposed fork selection algorithm. It proposes
|
||||||
proposes that payment to miners be postponed until the *stack* of validator
|
that payment to miners be postponed until the *stack* of validator votes reaches
|
||||||
votes reaches a certain depth, at which point rollback is not economically
|
a certain depth, at which point rollback is not economically feasible. The vote
|
||||||
feasible. The vote program may therefore implement locktower. Vote instructions
|
program may therefore implement Tower BFT. Vote instructions would need to
|
||||||
would need to reference a global locktower account so that it can track
|
reference a global Tower account so that it can track cross-block state.
|
||||||
cross-block state.
|
|
||||||
|
|
||||||
## Challenges
|
## Challenges
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ Validators can ignore forks at other points (e.g. from the wrong leader), or
|
||||||
slash the leader responsible for the fork.
|
slash the leader responsible for the fork.
|
||||||
|
|
||||||
Validators vote based on a greedy choice to maximize their reward described in
|
Validators vote based on a greedy choice to maximize their reward described in
|
||||||
[forks selection](fork-selection.md).
|
[Tower BFT](tower-bft.md).
|
||||||
|
|
||||||
### Validator's View
|
### Validator's View
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ ends up scheduled for the first two epochs because the leader schedule is also
|
||||||
generated at slot 0 for the next epoch. The length of the first two epochs can
|
generated at slot 0 for the next epoch. The length of the first two epochs can
|
||||||
be specified in the genesis block as well. The minimum length of the first
|
be specified in the genesis block as well. The minimum length of the first
|
||||||
epochs must be greater than or equal to the maximum rollback depth as defined in
|
epochs must be greater than or equal to the maximum rollback depth as defined in
|
||||||
[fork selection](fork-selection.md).
|
[Tower BFT](tower-bft.md).
|
||||||
|
|
||||||
## Leader Schedule Generation Algorithm
|
## Leader Schedule Generation Algorithm
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ The program should have a list of slots which are valid storage mining slots.
|
||||||
This list should be maintained by keeping track of slots which are rooted slots in which a significant
|
This list should be maintained by keeping track of slots which are rooted slots in which a significant
|
||||||
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
|
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
|
||||||
number of slots would be added to this set. The program should check that the slot is in this set. The set can
|
number of slots would be added to this set. The program should check that the slot is in this set. The set can
|
||||||
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/locktower state.
|
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/Tower BFT state.
|
||||||
|
|
||||||
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
|
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
|
||||||
the previous storage epoch PoH value.
|
the previous storage epoch PoH value.
|
||||||
|
|
|
@ -60,7 +60,7 @@ The read is satisfied by pointing to a memory-mapped location in the
|
||||||
|
|
||||||
## Root Forks
|
## Root Forks
|
||||||
|
|
||||||
The [fork selection algorithm](fork-selection.md) eventually selects a fork as a
|
[Tower BFT](tower-bft.md) eventually selects a fork as a
|
||||||
root fork and the fork is squashed. A squashed/root fork cannot be rolled back.
|
root fork and the fork is squashed. A squashed/root fork cannot be rolled back.
|
||||||
|
|
||||||
When a fork is squashed, all accounts in its parents not already present in the
|
When a fork is squashed, all accounts in its parents not already present in the
|
||||||
|
|
|
@ -77,7 +77,7 @@ count as stakes.
|
||||||
### VoteInstruction::Vote(Vec<Vote>)
|
### VoteInstruction::Vote(Vec<Vote>)
|
||||||
|
|
||||||
* `account[0]` - RW - The VoteState
|
* `account[0]` - RW - The VoteState
|
||||||
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Fork Selection](fork-selection.md)
|
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Tower BFT](tower-bft.md)
|
||||||
|
|
||||||
|
|
||||||
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
|
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Fork Selection
|
# Tower BFT
|
||||||
|
|
||||||
This design describes a *Fork Selection* algorithm. It addresses the following
|
This design describes Solana's *Tower BFT* algorithm. It addresses the
|
||||||
problems:
|
following problems:
|
||||||
|
|
||||||
* Some forks may not end up accepted by the super-majority of the cluster, and
|
* Some forks may not end up accepted by the super-majority of the cluster, and
|
||||||
voters need to recover from voting on such forks.
|
voters need to recover from voting on such forks.
|
|
@ -85,7 +85,7 @@ impl Service for ClusterInfoVoteListener {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::locktower::MAX_RECENT_VOTES;
|
use crate::consensus::MAX_RECENT_VOTES;
|
||||||
use crate::packet;
|
use crate::packet;
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::hash::Hash;
|
||||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||||
|
|
|
@ -4,10 +4,10 @@ use crate::blocktree::Blocktree;
|
||||||
/// All tests must start from an entry point and a funding keypair and
|
/// All tests must start from an entry point and a funding keypair and
|
||||||
/// discover the rest of the network.
|
/// discover the rest of the network.
|
||||||
use crate::cluster_info::FULLNODE_PORT_RANGE;
|
use crate::cluster_info::FULLNODE_PORT_RANGE;
|
||||||
|
use crate::consensus::VOTE_THRESHOLD_DEPTH;
|
||||||
use crate::contact_info::ContactInfo;
|
use crate::contact_info::ContactInfo;
|
||||||
use crate::entry::{Entry, EntrySlice};
|
use crate::entry::{Entry, EntrySlice};
|
||||||
use crate::gossip_service::discover_cluster;
|
use crate::gossip_service::discover_cluster;
|
||||||
use crate::locktower::VOTE_THRESHOLD_DEPTH;
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use solana_client::thin_client::create_client;
|
use solana_client::thin_client::create_client;
|
||||||
use solana_runtime::epoch_schedule::MINIMUM_SLOTS_PER_EPOCH;
|
use solana_runtime::epoch_schedule::MINIMUM_SLOTS_PER_EPOCH;
|
||||||
|
|
|
@ -29,7 +29,7 @@ pub struct StakeLockout {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Locktower {
|
pub struct Tower {
|
||||||
epoch_stakes: EpochStakes,
|
epoch_stakes: EpochStakes,
|
||||||
threshold_depth: usize,
|
threshold_depth: usize,
|
||||||
threshold_size: f64,
|
threshold_size: f64,
|
||||||
|
@ -68,7 +68,7 @@ impl EpochStakes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Locktower {
|
impl Tower {
|
||||||
pub fn new_from_forks(bank_forks: &BankForks, my_pubkey: &Pubkey) -> Self {
|
pub fn new_from_forks(bank_forks: &BankForks, my_pubkey: &Pubkey) -> Self {
|
||||||
let mut frozen_banks: Vec<_> = bank_forks.frozen_banks().values().cloned().collect();
|
let mut frozen_banks: Vec<_> = bank_forks.frozen_banks().values().cloned().collect();
|
||||||
frozen_banks.sort_by_key(|b| (b.parents().len(), b.slot()));
|
frozen_banks.sort_by_key(|b| (b.parents().len(), b.slot()));
|
||||||
|
@ -80,7 +80,7 @@ impl Locktower {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut locktower = Self {
|
let mut tower = Self {
|
||||||
epoch_stakes,
|
epoch_stakes,
|
||||||
threshold_depth: VOTE_THRESHOLD_DEPTH,
|
threshold_depth: VOTE_THRESHOLD_DEPTH,
|
||||||
threshold_size: VOTE_THRESHOLD_SIZE,
|
threshold_size: VOTE_THRESHOLD_SIZE,
|
||||||
|
@ -88,10 +88,9 @@ impl Locktower {
|
||||||
recent_votes: VecDeque::default(),
|
recent_votes: VecDeque::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let bank = locktower.find_heaviest_bank(bank_forks).unwrap();
|
let bank = tower.find_heaviest_bank(bank_forks).unwrap();
|
||||||
locktower.lockouts =
|
tower.lockouts = Self::initialize_lockouts_from_bank(&bank, tower.epoch_stakes.epoch);
|
||||||
Self::initialize_lockouts_from_bank(&bank, locktower.epoch_stakes.epoch);
|
tower
|
||||||
locktower
|
|
||||||
}
|
}
|
||||||
pub fn new(epoch_stakes: EpochStakes, threshold_depth: usize, threshold_size: f64) -> Self {
|
pub fn new(epoch_stakes: EpochStakes, threshold_depth: usize, threshold_size: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -120,7 +119,7 @@ impl Locktower {
|
||||||
let vote_state = VoteState::from(&account);
|
let vote_state = VoteState::from(&account);
|
||||||
if vote_state.is_none() {
|
if vote_state.is_none() {
|
||||||
datapoint_warn!(
|
datapoint_warn!(
|
||||||
"locktower_warn",
|
"tower_warn",
|
||||||
(
|
(
|
||||||
"warn",
|
"warn",
|
||||||
format!("Unable to get vote_state from account {}", key),
|
format!("Unable to get vote_state from account {}", key),
|
||||||
|
@ -141,7 +140,7 @@ impl Locktower {
|
||||||
);
|
);
|
||||||
debug!("observed root {}", vote_state.root_slot.unwrap_or(0) as i64);
|
debug!("observed root {}", vote_state.root_slot.unwrap_or(0) as i64);
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
"locktower-observed",
|
"tower-observed",
|
||||||
(
|
(
|
||||||
"slot",
|
"slot",
|
||||||
vote_state.nth_recent_vote(0).map(|v| v.slot).unwrap_or(0),
|
vote_state.nth_recent_vote(0).map(|v| v.slot).unwrap_or(0),
|
||||||
|
@ -223,14 +222,14 @@ impl Locktower {
|
||||||
"epoch_stakes cannot move backwards"
|
"epoch_stakes cannot move backwards"
|
||||||
);
|
);
|
||||||
info!(
|
info!(
|
||||||
"Locktower updated epoch bank slot: {} epoch: {}",
|
"Tower updated epoch bank slot: {} epoch: {}",
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
self.epoch_stakes.epoch
|
self.epoch_stakes.epoch
|
||||||
);
|
);
|
||||||
self.epoch_stakes =
|
self.epoch_stakes =
|
||||||
EpochStakes::new_from_bank(bank, &self.epoch_stakes.delegate_pubkey);
|
EpochStakes::new_from_bank(bank, &self.epoch_stakes.delegate_pubkey);
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
"locktower-epoch",
|
"tower-epoch",
|
||||||
("epoch", self.epoch_stakes.epoch, i64),
|
("epoch", self.epoch_stakes.epoch, i64),
|
||||||
("self_staked", self.epoch_stakes.self_staked, i64),
|
("self_staked", self.epoch_stakes.self_staked, i64),
|
||||||
("total_staked", self.epoch_stakes.total_staked, i64)
|
("total_staked", self.epoch_stakes.total_staked, i64)
|
||||||
|
@ -256,7 +255,7 @@ impl Locktower {
|
||||||
.retain(|vote| slots.iter().any(|slot| vote.slot == *slot));
|
.retain(|vote| slots.iter().any(|slot| vote.slot == *slot));
|
||||||
|
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
"locktower-vote",
|
"tower-vote",
|
||||||
("latest", slot, i64),
|
("latest", slot, i64),
|
||||||
("root", self.lockouts.root_slot.unwrap_or(0), i64)
|
("root", self.lockouts.root_slot.unwrap_or(0), i64)
|
||||||
);
|
);
|
||||||
|
@ -429,11 +428,11 @@ mod test {
|
||||||
fn test_collect_vote_lockouts_no_epoch_stakes() {
|
fn test_collect_vote_lockouts_no_epoch_stakes() {
|
||||||
let accounts = gen_stakes(&[(1, &[0])]);
|
let accounts = gen_stakes(&[(1, &[0])]);
|
||||||
let epoch_stakes = EpochStakes::new_for_tests(2);
|
let epoch_stakes = EpochStakes::new_for_tests(2);
|
||||||
let locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
let tower = Tower::new(epoch_stakes, 0, 0.67);
|
||||||
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
|
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
let staked_lockouts = locktower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
let staked_lockouts = tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
||||||
assert!(staked_lockouts.is_empty());
|
assert!(staked_lockouts.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,11 +441,11 @@ mod test {
|
||||||
//two accounts voting for slot 0 with 1 token staked
|
//two accounts voting for slot 0 with 1 token staked
|
||||||
let accounts = gen_stakes(&[(1, &[0]), (1, &[0])]);
|
let accounts = gen_stakes(&[(1, &[0]), (1, &[0])]);
|
||||||
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
|
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
|
||||||
let locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
let tower = Tower::new(epoch_stakes, 0, 0.67);
|
||||||
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
|
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
let staked_lockouts = locktower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
let staked_lockouts = tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
|
||||||
assert_eq!(staked_lockouts[&0].stake, 2);
|
assert_eq!(staked_lockouts[&0].stake, 2);
|
||||||
assert_eq!(staked_lockouts[&0].lockout, 2 + 2 + 4 + 4);
|
assert_eq!(staked_lockouts[&0].lockout, 2 + 2 + 4 + 4);
|
||||||
}
|
}
|
||||||
|
@ -457,14 +456,14 @@ mod test {
|
||||||
//two accounts voting for slot 0 with 1 token staked
|
//two accounts voting for slot 0 with 1 token staked
|
||||||
let accounts = gen_stakes(&[(1, &votes), (1, &votes)]);
|
let accounts = gen_stakes(&[(1, &votes), (1, &votes)]);
|
||||||
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
|
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
|
||||||
let mut locktower = Locktower::new(epoch_stakes, 0, 0.67);
|
let mut tower = Tower::new(epoch_stakes, 0, 0.67);
|
||||||
let mut ancestors = HashMap::new();
|
let mut ancestors = HashMap::new();
|
||||||
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
|
||||||
locktower.record_vote(i as u64, Hash::default());
|
tower.record_vote(i as u64, Hash::default());
|
||||||
ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
|
ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
|
||||||
}
|
}
|
||||||
assert_eq!(locktower.lockouts.root_slot, Some(0));
|
assert_eq!(tower.lockouts.root_slot, Some(0));
|
||||||
let staked_lockouts = locktower.collect_vote_lockouts(
|
let staked_lockouts = tower.collect_vote_lockouts(
|
||||||
MAX_LOCKOUT_HISTORY as u64,
|
MAX_LOCKOUT_HISTORY as u64,
|
||||||
accounts.into_iter(),
|
accounts.into_iter(),
|
||||||
&ancestors,
|
&ancestors,
|
||||||
|
@ -478,8 +477,8 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_weight_skips_root() {
|
fn test_calculate_weight_skips_root() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
locktower.lockouts.root_slot = Some(1);
|
tower.lockouts.root_slot = Some(1);
|
||||||
let stakes = vec![
|
let stakes = vec![
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
|
@ -498,12 +497,12 @@ mod test {
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(locktower.calculate_weight(&stakes), 0u128);
|
assert_eq!(tower.calculate_weight(&stakes), 0u128);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calculate_weight() {
|
fn test_calculate_weight() {
|
||||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let stakes = vec![(
|
let stakes = vec![(
|
||||||
0,
|
0,
|
||||||
StakeLockout {
|
StakeLockout {
|
||||||
|
@ -513,12 +512,12 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(locktower.calculate_weight(&stakes), 8u128);
|
assert_eq!(tower.calculate_weight(&stakes), 8u128);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_vote_threshold_without_votes() {
|
fn test_check_vote_threshold_without_votes() {
|
||||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = vec![(
|
let stakes = vec![(
|
||||||
0,
|
0,
|
||||||
StakeLockout {
|
StakeLockout {
|
||||||
|
@ -528,12 +527,12 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
assert!(locktower.check_vote_stake_threshold(0, &stakes));
|
assert!(tower.check_vote_stake_threshold(0, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_slot_confirmed_not_enough_stake_failure() {
|
fn test_is_slot_confirmed_not_enough_stake_failure() {
|
||||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = vec![(
|
let stakes = vec![(
|
||||||
0,
|
0,
|
||||||
StakeLockout {
|
StakeLockout {
|
||||||
|
@ -543,19 +542,19 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
assert!(!locktower.is_slot_confirmed(0, &stakes));
|
assert!(!tower.is_slot_confirmed(0, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_slot_confirmed_unknown_slot() {
|
fn test_is_slot_confirmed_unknown_slot() {
|
||||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = HashMap::new();
|
let stakes = HashMap::new();
|
||||||
assert!(!locktower.is_slot_confirmed(0, &stakes));
|
assert!(!tower.is_slot_confirmed(0, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_slot_confirmed_pass() {
|
fn test_is_slot_confirmed_pass() {
|
||||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = vec![(
|
let stakes = vec![(
|
||||||
0,
|
0,
|
||||||
StakeLockout {
|
StakeLockout {
|
||||||
|
@ -565,68 +564,68 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
assert!(locktower.is_slot_confirmed(0, &stakes));
|
assert!(tower.is_slot_confirmed(0, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_locked_out_empty() {
|
fn test_is_locked_out_empty() {
|
||||||
let locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let descendants = HashMap::new();
|
let descendants = HashMap::new();
|
||||||
assert!(!locktower.is_locked_out(0, &descendants));
|
assert!(!tower.is_locked_out(0, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_locked_out_root_slot_child_pass() {
|
fn test_is_locked_out_root_slot_child_pass() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let descendants = vec![(0, vec![1].into_iter().collect())]
|
let descendants = vec![(0, vec![1].into_iter().collect())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.lockouts.root_slot = Some(0);
|
tower.lockouts.root_slot = Some(0);
|
||||||
assert!(!locktower.is_locked_out(1, &descendants));
|
assert!(!tower.is_locked_out(1, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_locked_out_root_slot_sibling_fail() {
|
fn test_is_locked_out_root_slot_sibling_fail() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let descendants = vec![(0, vec![1].into_iter().collect())]
|
let descendants = vec![(0, vec![1].into_iter().collect())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.lockouts.root_slot = Some(0);
|
tower.lockouts.root_slot = Some(0);
|
||||||
assert!(locktower.is_locked_out(2, &descendants));
|
assert!(tower.is_locked_out(2, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_already_voted() {
|
fn test_check_already_voted() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
assert!(locktower.has_voted(0));
|
assert!(tower.has_voted(0));
|
||||||
assert!(!locktower.has_voted(1));
|
assert!(!tower.has_voted(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_locked_out_double_vote() {
|
fn test_is_locked_out_double_vote() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())]
|
let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1, Hash::default());
|
tower.record_vote(1, Hash::default());
|
||||||
assert!(locktower.is_locked_out(0, &descendants));
|
assert!(tower.is_locked_out(0, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_locked_out_child() {
|
fn test_is_locked_out_child() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let descendants = vec![(0, vec![1].into_iter().collect())]
|
let descendants = vec![(0, vec![1].into_iter().collect())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
assert!(!locktower.is_locked_out(1, &descendants));
|
assert!(!tower.is_locked_out(1, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_locked_out_sibling() {
|
fn test_is_locked_out_sibling() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let descendants = vec![
|
let descendants = vec![
|
||||||
(0, vec![1, 2].into_iter().collect()),
|
(0, vec![1, 2].into_iter().collect()),
|
||||||
(1, HashSet::new()),
|
(1, HashSet::new()),
|
||||||
|
@ -634,30 +633,30 @@ mod test {
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1, Hash::default());
|
tower.record_vote(1, Hash::default());
|
||||||
assert!(locktower.is_locked_out(2, &descendants));
|
assert!(tower.is_locked_out(2, &descendants));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_locked_out_last_vote_expired() {
|
fn test_is_locked_out_last_vote_expired() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
|
||||||
let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())]
|
let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1, Hash::default());
|
tower.record_vote(1, Hash::default());
|
||||||
assert!(!locktower.is_locked_out(4, &descendants));
|
assert!(!tower.is_locked_out(4, &descendants));
|
||||||
locktower.record_vote(4, Hash::default());
|
tower.record_vote(4, Hash::default());
|
||||||
assert_eq!(locktower.lockouts.votes[0].slot, 0);
|
assert_eq!(tower.lockouts.votes[0].slot, 0);
|
||||||
assert_eq!(locktower.lockouts.votes[0].confirmation_count, 2);
|
assert_eq!(tower.lockouts.votes[0].confirmation_count, 2);
|
||||||
assert_eq!(locktower.lockouts.votes[1].slot, 4);
|
assert_eq!(tower.lockouts.votes[1].slot, 4);
|
||||||
assert_eq!(locktower.lockouts.votes[1].confirmation_count, 1);
|
assert_eq!(tower.lockouts.votes[1].confirmation_count, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_vote_threshold_below_threshold() {
|
fn test_check_vote_threshold_below_threshold() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = vec![(
|
let stakes = vec![(
|
||||||
0,
|
0,
|
||||||
StakeLockout {
|
StakeLockout {
|
||||||
|
@ -667,12 +666,12 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
assert!(!tower.check_vote_stake_threshold(1, &stakes));
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_vote_threshold_above_threshold() {
|
fn test_check_vote_threshold_above_threshold() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = vec![(
|
let stakes = vec![(
|
||||||
0,
|
0,
|
||||||
StakeLockout {
|
StakeLockout {
|
||||||
|
@ -682,13 +681,13 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
assert!(locktower.check_vote_stake_threshold(1, &stakes));
|
assert!(tower.check_vote_stake_threshold(1, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_vote_threshold_above_threshold_after_pop() {
|
fn test_check_vote_threshold_above_threshold_after_pop() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = vec![(
|
let stakes = vec![(
|
||||||
0,
|
0,
|
||||||
StakeLockout {
|
StakeLockout {
|
||||||
|
@ -698,18 +697,18 @@ mod test {
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
locktower.record_vote(1, Hash::default());
|
tower.record_vote(1, Hash::default());
|
||||||
locktower.record_vote(2, Hash::default());
|
tower.record_vote(2, Hash::default());
|
||||||
assert!(locktower.check_vote_stake_threshold(6, &stakes));
|
assert!(tower.check_vote_stake_threshold(6, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_vote_threshold_above_threshold_no_stake() {
|
fn test_check_vote_threshold_above_threshold_no_stake() {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let stakes = HashMap::new();
|
let stakes = HashMap::new();
|
||||||
locktower.record_vote(0, Hash::default());
|
tower.record_vote(0, Hash::default());
|
||||||
assert!(!locktower.check_vote_stake_threshold(1, &stakes));
|
assert!(!tower.check_vote_stake_threshold(1, &stakes));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -724,7 +723,7 @@ mod test {
|
||||||
ancestors.insert(2, set);
|
ancestors.insert(2, set);
|
||||||
let set: HashSet<u64> = vec![0u64].into_iter().collect();
|
let set: HashSet<u64> = vec![0u64].into_iter().collect();
|
||||||
ancestors.insert(1, set);
|
ancestors.insert(1, set);
|
||||||
Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
Tower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||||
assert_eq!(stake_lockouts[&0].lockout, 2);
|
assert_eq!(stake_lockouts[&0].lockout, 2);
|
||||||
assert_eq!(stake_lockouts[&1].lockout, 2);
|
assert_eq!(stake_lockouts[&1].lockout, 2);
|
||||||
assert_eq!(stake_lockouts[&2].lockout, 2);
|
assert_eq!(stake_lockouts[&2].lockout, 2);
|
||||||
|
@ -742,12 +741,12 @@ mod test {
|
||||||
slot: 2,
|
slot: 2,
|
||||||
confirmation_count: 1,
|
confirmation_count: 1,
|
||||||
};
|
};
|
||||||
Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
Tower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||||
let vote = Lockout {
|
let vote = Lockout {
|
||||||
slot: 1,
|
slot: 1,
|
||||||
confirmation_count: 2,
|
confirmation_count: 2,
|
||||||
};
|
};
|
||||||
Locktower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
Tower::update_ancestor_lockouts(&mut stake_lockouts, &vote, &ancestors);
|
||||||
assert_eq!(stake_lockouts[&0].lockout, 2 + 4);
|
assert_eq!(stake_lockouts[&0].lockout, 2 + 4);
|
||||||
assert_eq!(stake_lockouts[&1].lockout, 2 + 4);
|
assert_eq!(stake_lockouts[&1].lockout, 2 + 4);
|
||||||
assert_eq!(stake_lockouts[&2].lockout, 2);
|
assert_eq!(stake_lockouts[&2].lockout, 2);
|
||||||
|
@ -760,7 +759,7 @@ mod test {
|
||||||
account.lamports = 1;
|
account.lamports = 1;
|
||||||
let set: HashSet<u64> = vec![0u64, 1u64].into_iter().collect();
|
let set: HashSet<u64> = vec![0u64, 1u64].into_iter().collect();
|
||||||
let ancestors: HashMap<u64, HashSet<u64>> = [(2u64, set)].into_iter().cloned().collect();
|
let ancestors: HashMap<u64, HashSet<u64>> = [(2u64, set)].into_iter().cloned().collect();
|
||||||
Locktower::update_ancestor_stakes(&mut stake_lockouts, 2, account.lamports, &ancestors);
|
Tower::update_ancestor_stakes(&mut stake_lockouts, 2, account.lamports, &ancestors);
|
||||||
assert_eq!(stake_lockouts[&0].stake, 1);
|
assert_eq!(stake_lockouts[&0].stake, 1);
|
||||||
assert_eq!(stake_lockouts[&1].stake, 1);
|
assert_eq!(stake_lockouts[&1].stake, 1);
|
||||||
assert_eq!(stake_lockouts[&2].stake, 1);
|
assert_eq!(stake_lockouts[&2].stake, 1);
|
||||||
|
@ -782,51 +781,48 @@ mod test {
|
||||||
let total_stake = 4;
|
let total_stake = 4;
|
||||||
let threshold_size = 0.67;
|
let threshold_size = 0.67;
|
||||||
let threshold_stake = (f64::ceil(total_stake as f64 * threshold_size)) as u64;
|
let threshold_stake = (f64::ceil(total_stake as f64 * threshold_size)) as u64;
|
||||||
let locktower_votes: Vec<u64> = (0..VOTE_THRESHOLD_DEPTH as u64).collect();
|
let tower_votes: Vec<u64> = (0..VOTE_THRESHOLD_DEPTH as u64).collect();
|
||||||
let accounts = gen_stakes(&[
|
let accounts = gen_stakes(&[
|
||||||
(threshold_stake, &[(VOTE_THRESHOLD_DEPTH - 2) as u64]),
|
(threshold_stake, &[(VOTE_THRESHOLD_DEPTH - 2) as u64]),
|
||||||
(total_stake - threshold_stake, &locktower_votes[..]),
|
(total_stake - threshold_stake, &tower_votes[..]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Initialize locktower
|
// Initialize tower
|
||||||
let stakes: HashMap<_, _> = accounts.iter().map(|(pk, (s, _))| (*pk, *s)).collect();
|
let stakes: HashMap<_, _> = accounts.iter().map(|(pk, (s, _))| (*pk, *s)).collect();
|
||||||
let epoch_stakes = EpochStakes::new(0, stakes, &Pubkey::default());
|
let epoch_stakes = EpochStakes::new(0, stakes, &Pubkey::default());
|
||||||
let mut locktower = Locktower::new(epoch_stakes, VOTE_THRESHOLD_DEPTH, threshold_size);
|
let mut tower = Tower::new(epoch_stakes, VOTE_THRESHOLD_DEPTH, threshold_size);
|
||||||
|
|
||||||
// CASE 1: Record the first VOTE_THRESHOLD locktower votes for fork 2. We want to
|
// CASE 1: Record the first VOTE_THRESHOLD tower votes for fork 2. We want to
|
||||||
// evaluate a vote on slot VOTE_THRESHOLD_DEPTH. The nth most recent vote should be
|
// evaluate a vote on slot VOTE_THRESHOLD_DEPTH. The nth most recent vote should be
|
||||||
// for slot 0, which is common to all account vote states, so we should pass the
|
// for slot 0, which is common to all account vote states, so we should pass the
|
||||||
// threshold check
|
// threshold check
|
||||||
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64;
|
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64;
|
||||||
for vote in &locktower_votes {
|
for vote in &tower_votes {
|
||||||
locktower.record_vote(*vote, Hash::default());
|
tower.record_vote(*vote, Hash::default());
|
||||||
}
|
}
|
||||||
let stakes_lockouts = locktower.collect_vote_lockouts(
|
let stakes_lockouts =
|
||||||
vote_to_evaluate,
|
tower.collect_vote_lockouts(vote_to_evaluate, accounts.clone().into_iter(), &ancestors);
|
||||||
accounts.clone().into_iter(),
|
assert!(tower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
||||||
&ancestors,
|
|
||||||
);
|
|
||||||
assert!(locktower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
|
||||||
|
|
||||||
// CASE 2: Now we want to evaluate a vote for slot VOTE_THRESHOLD_DEPTH + 1. This slot
|
// CASE 2: Now we want to evaluate a vote for slot VOTE_THRESHOLD_DEPTH + 1. This slot
|
||||||
// will expire the vote in one of the vote accounts, so we should have insufficient
|
// will expire the vote in one of the vote accounts, so we should have insufficient
|
||||||
// stake to pass the threshold
|
// stake to pass the threshold
|
||||||
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64 + 1;
|
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64 + 1;
|
||||||
let stakes_lockouts =
|
let stakes_lockouts =
|
||||||
locktower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
|
tower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
|
||||||
assert!(!locktower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
assert!(!tower.check_vote_stake_threshold(vote_to_evaluate, &stakes_lockouts));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vote_and_check_recent(num_votes: usize) {
|
fn vote_and_check_recent(num_votes: usize) {
|
||||||
let mut locktower = Locktower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
|
||||||
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
|
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
|
||||||
let expected: Vec<_> = (start..num_votes)
|
let expected: Vec<_> = (start..num_votes)
|
||||||
.map(|i| Vote::new(i as u64, Hash::default()))
|
.map(|i| Vote::new(i as u64, Hash::default()))
|
||||||
.collect();
|
.collect();
|
||||||
for i in 0..num_votes {
|
for i in 0..num_votes {
|
||||||
locktower.record_vote(i as u64, Hash::default());
|
tower.record_vote(i as u64, Hash::default());
|
||||||
}
|
}
|
||||||
assert_eq!(expected, locktower.recent_votes())
|
assert_eq!(expected, tower.recent_votes())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
|
@ -30,6 +30,7 @@ pub mod cluster;
|
||||||
pub mod cluster_info;
|
pub mod cluster_info;
|
||||||
pub mod cluster_info_repair_listener;
|
pub mod cluster_info_repair_listener;
|
||||||
pub mod cluster_tests;
|
pub mod cluster_tests;
|
||||||
|
pub mod consensus;
|
||||||
pub mod entry;
|
pub mod entry;
|
||||||
pub mod erasure;
|
pub mod erasure;
|
||||||
pub mod fetch_stage;
|
pub mod fetch_stage;
|
||||||
|
@ -41,7 +42,6 @@ pub mod leader_schedule_cache;
|
||||||
pub mod leader_schedule_utils;
|
pub mod leader_schedule_utils;
|
||||||
pub mod local_cluster;
|
pub mod local_cluster;
|
||||||
pub mod local_vote_signer_service;
|
pub mod local_vote_signer_service;
|
||||||
pub mod locktower;
|
|
||||||
pub mod packet;
|
pub mod packet;
|
||||||
pub mod poh;
|
pub mod poh;
|
||||||
pub mod poh_recorder;
|
pub mod poh_recorder;
|
||||||
|
|
|
@ -4,10 +4,10 @@ use crate::bank_forks::BankForks;
|
||||||
use crate::blocktree::Blocktree;
|
use crate::blocktree::Blocktree;
|
||||||
use crate::blocktree_processor;
|
use crate::blocktree_processor;
|
||||||
use crate::cluster_info::ClusterInfo;
|
use crate::cluster_info::ClusterInfo;
|
||||||
|
use crate::consensus::{StakeLockout, Tower};
|
||||||
use crate::entry::{Entry, EntrySlice};
|
use crate::entry::{Entry, EntrySlice};
|
||||||
use crate::leader_schedule_cache::LeaderScheduleCache;
|
use crate::leader_schedule_cache::LeaderScheduleCache;
|
||||||
use crate::leader_schedule_utils;
|
use crate::leader_schedule_utils;
|
||||||
use crate::locktower::{Locktower, StakeLockout};
|
|
||||||
use crate::packet::BlobError;
|
use crate::packet::BlobError;
|
||||||
use crate::poh_recorder::PohRecorder;
|
use crate::poh_recorder::PohRecorder;
|
||||||
use crate::result::{Error, Result};
|
use crate::result::{Error, Result};
|
||||||
|
@ -102,7 +102,7 @@ impl ReplayStage {
|
||||||
let poh_recorder = poh_recorder.clone();
|
let poh_recorder = poh_recorder.clone();
|
||||||
let my_pubkey = *my_pubkey;
|
let my_pubkey = *my_pubkey;
|
||||||
let mut ticks_per_slot = 0;
|
let mut ticks_per_slot = 0;
|
||||||
let mut locktower = Locktower::new_from_forks(&bank_forks.read().unwrap(), &my_pubkey);
|
let mut tower = Tower::new_from_forks(&bank_forks.read().unwrap(), &my_pubkey);
|
||||||
// Start the replay stage loop
|
// Start the replay stage loop
|
||||||
let leader_schedule_cache = leader_schedule_cache.clone();
|
let leader_schedule_cache = leader_schedule_cache.clone();
|
||||||
let vote_account = *vote_account;
|
let vote_account = *vote_account;
|
||||||
|
@ -142,8 +142,7 @@ impl ReplayStage {
|
||||||
ticks_per_slot = bank.ticks_per_slot();
|
ticks_per_slot = bank.ticks_per_slot();
|
||||||
}
|
}
|
||||||
|
|
||||||
let votable =
|
let votable = Self::generate_votable_banks(&bank_forks, &tower, &mut progress);
|
||||||
Self::generate_votable_banks(&bank_forks, &locktower, &mut progress);
|
|
||||||
|
|
||||||
if let Some((_, bank)) = votable.last() {
|
if let Some((_, bank)) = votable.last() {
|
||||||
subscriptions.notify_subscribers(bank.slot(), &bank_forks);
|
subscriptions.notify_subscribers(bank.slot(), &bank_forks);
|
||||||
|
@ -151,7 +150,7 @@ impl ReplayStage {
|
||||||
Self::handle_votable_bank(
|
Self::handle_votable_bank(
|
||||||
&bank,
|
&bank,
|
||||||
&bank_forks,
|
&bank_forks,
|
||||||
&mut locktower,
|
&mut tower,
|
||||||
&mut progress,
|
&mut progress,
|
||||||
&vote_account,
|
&vote_account,
|
||||||
&voting_keypair,
|
&voting_keypair,
|
||||||
|
@ -318,7 +317,7 @@ impl ReplayStage {
|
||||||
fn handle_votable_bank<T>(
|
fn handle_votable_bank<T>(
|
||||||
bank: &Arc<Bank>,
|
bank: &Arc<Bank>,
|
||||||
bank_forks: &Arc<RwLock<BankForks>>,
|
bank_forks: &Arc<RwLock<BankForks>>,
|
||||||
locktower: &mut Locktower,
|
tower: &mut Tower,
|
||||||
progress: &mut HashMap<u64, ForkProgress>,
|
progress: &mut HashMap<u64, ForkProgress>,
|
||||||
vote_account: &Pubkey,
|
vote_account: &Pubkey,
|
||||||
voting_keypair: &Option<Arc<T>>,
|
voting_keypair: &Option<Arc<T>>,
|
||||||
|
@ -330,7 +329,7 @@ impl ReplayStage {
|
||||||
where
|
where
|
||||||
T: 'static + KeypairUtil + Send + Sync,
|
T: 'static + KeypairUtil + Send + Sync,
|
||||||
{
|
{
|
||||||
if let Some(new_root) = locktower.record_vote(bank.slot(), bank.hash()) {
|
if let Some(new_root) = tower.record_vote(bank.slot(), bank.hash()) {
|
||||||
// get the root bank before squash
|
// get the root bank before squash
|
||||||
let root_bank = bank_forks
|
let root_bank = bank_forks
|
||||||
.read()
|
.read()
|
||||||
|
@ -352,7 +351,7 @@ impl ReplayStage {
|
||||||
Self::handle_new_root(&bank_forks, progress);
|
Self::handle_new_root(&bank_forks, progress);
|
||||||
root_bank_sender.send(rooted_banks)?;
|
root_bank_sender.send(rooted_banks)?;
|
||||||
}
|
}
|
||||||
locktower.update_epoch(&bank);
|
tower.update_epoch(&bank);
|
||||||
if let Some(ref voting_keypair) = voting_keypair {
|
if let Some(ref voting_keypair) = voting_keypair {
|
||||||
let node_keypair = cluster_info.read().unwrap().keypair.clone();
|
let node_keypair = cluster_info.read().unwrap().keypair.clone();
|
||||||
|
|
||||||
|
@ -360,7 +359,7 @@ impl ReplayStage {
|
||||||
let vote_ix = vote_instruction::vote(
|
let vote_ix = vote_instruction::vote(
|
||||||
&vote_account,
|
&vote_account,
|
||||||
&voting_keypair.pubkey(),
|
&voting_keypair.pubkey(),
|
||||||
locktower.recent_votes(),
|
tower.recent_votes(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut vote_tx =
|
let mut vote_tx =
|
||||||
|
@ -438,11 +437,11 @@ impl ReplayStage {
|
||||||
|
|
||||||
fn generate_votable_banks(
|
fn generate_votable_banks(
|
||||||
bank_forks: &Arc<RwLock<BankForks>>,
|
bank_forks: &Arc<RwLock<BankForks>>,
|
||||||
locktower: &Locktower,
|
tower: &Tower,
|
||||||
progress: &mut HashMap<u64, ForkProgress>,
|
progress: &mut HashMap<u64, ForkProgress>,
|
||||||
) -> Vec<(u128, Arc<Bank>)> {
|
) -> Vec<(u128, Arc<Bank>)> {
|
||||||
let locktower_start = Instant::now();
|
let tower_start = Instant::now();
|
||||||
// Locktower voting
|
// Tower voting
|
||||||
let descendants = bank_forks.read().unwrap().descendants();
|
let descendants = bank_forks.read().unwrap().descendants();
|
||||||
let ancestors = bank_forks.read().unwrap().ancestors();
|
let ancestors = bank_forks.read().unwrap().ancestors();
|
||||||
let frozen_banks = bank_forks.read().unwrap().frozen_banks();
|
let frozen_banks = bank_forks.read().unwrap().frozen_banks();
|
||||||
|
@ -456,24 +455,24 @@ impl ReplayStage {
|
||||||
is_votable
|
is_votable
|
||||||
})
|
})
|
||||||
.filter(|b| {
|
.filter(|b| {
|
||||||
let is_recent_epoch = locktower.is_recent_epoch(b);
|
let is_recent_epoch = tower.is_recent_epoch(b);
|
||||||
trace!("bank is is_recent_epoch: {} {}", b.slot(), is_recent_epoch);
|
trace!("bank is is_recent_epoch: {} {}", b.slot(), is_recent_epoch);
|
||||||
is_recent_epoch
|
is_recent_epoch
|
||||||
})
|
})
|
||||||
.filter(|b| {
|
.filter(|b| {
|
||||||
let has_voted = locktower.has_voted(b.slot());
|
let has_voted = tower.has_voted(b.slot());
|
||||||
trace!("bank is has_voted: {} {}", b.slot(), has_voted);
|
trace!("bank is has_voted: {} {}", b.slot(), has_voted);
|
||||||
!has_voted
|
!has_voted
|
||||||
})
|
})
|
||||||
.filter(|b| {
|
.filter(|b| {
|
||||||
let is_locked_out = locktower.is_locked_out(b.slot(), &descendants);
|
let is_locked_out = tower.is_locked_out(b.slot(), &descendants);
|
||||||
trace!("bank is is_locked_out: {} {}", b.slot(), is_locked_out);
|
trace!("bank is is_locked_out: {} {}", b.slot(), is_locked_out);
|
||||||
!is_locked_out
|
!is_locked_out
|
||||||
})
|
})
|
||||||
.map(|bank| {
|
.map(|bank| {
|
||||||
(
|
(
|
||||||
bank,
|
bank,
|
||||||
locktower.collect_vote_lockouts(
|
tower.collect_vote_lockouts(
|
||||||
bank.slot(),
|
bank.slot(),
|
||||||
bank.vote_accounts().into_iter(),
|
bank.vote_accounts().into_iter(),
|
||||||
&ancestors,
|
&ancestors,
|
||||||
|
@ -481,43 +480,42 @@ impl ReplayStage {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.filter(|(b, stake_lockouts)| {
|
.filter(|(b, stake_lockouts)| {
|
||||||
let vote_threshold =
|
let vote_threshold = tower.check_vote_stake_threshold(b.slot(), &stake_lockouts);
|
||||||
locktower.check_vote_stake_threshold(b.slot(), &stake_lockouts);
|
Self::confirm_forks(tower, stake_lockouts, progress, bank_forks);
|
||||||
Self::confirm_forks(locktower, stake_lockouts, progress, bank_forks);
|
|
||||||
debug!("bank vote_threshold: {} {}", b.slot(), vote_threshold);
|
debug!("bank vote_threshold: {} {}", b.slot(), vote_threshold);
|
||||||
vote_threshold
|
vote_threshold
|
||||||
})
|
})
|
||||||
.map(|(b, stake_lockouts)| (locktower.calculate_weight(&stake_lockouts), b.clone()))
|
.map(|(b, stake_lockouts)| (tower.calculate_weight(&stake_lockouts), b.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
votable.sort_by_key(|b| b.0);
|
votable.sort_by_key(|b| b.0);
|
||||||
let ms = timing::duration_as_ms(&locktower_start.elapsed());
|
let ms = timing::duration_as_ms(&tower_start.elapsed());
|
||||||
|
|
||||||
trace!("votable_banks {}", votable.len());
|
trace!("votable_banks {}", votable.len());
|
||||||
if !votable.is_empty() {
|
if !votable.is_empty() {
|
||||||
let weights: Vec<u128> = votable.iter().map(|x| x.0).collect();
|
let weights: Vec<u128> = votable.iter().map(|x| x.0).collect();
|
||||||
info!(
|
info!(
|
||||||
"@{:?} locktower duration: {:?} len: {} weights: {:?}",
|
"@{:?} tower duration: {:?} len: {} weights: {:?}",
|
||||||
timing::timestamp(),
|
timing::timestamp(),
|
||||||
ms,
|
ms,
|
||||||
votable.len(),
|
votable.len(),
|
||||||
weights
|
weights
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
inc_new_counter_info!("replay_stage-locktower_duration", ms as usize);
|
inc_new_counter_info!("replay_stage-tower_duration", ms as usize);
|
||||||
|
|
||||||
votable
|
votable
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_forks(
|
fn confirm_forks(
|
||||||
locktower: &Locktower,
|
tower: &Tower,
|
||||||
stake_lockouts: &HashMap<u64, StakeLockout>,
|
stake_lockouts: &HashMap<u64, StakeLockout>,
|
||||||
progress: &mut HashMap<u64, ForkProgress>,
|
progress: &mut HashMap<u64, ForkProgress>,
|
||||||
bank_forks: &Arc<RwLock<BankForks>>,
|
bank_forks: &Arc<RwLock<BankForks>>,
|
||||||
) {
|
) {
|
||||||
progress.retain(|slot, prog| {
|
progress.retain(|slot, prog| {
|
||||||
let duration = timing::timestamp() - prog.started_ms;
|
let duration = timing::timestamp() - prog.started_ms;
|
||||||
if locktower.is_slot_confirmed(*slot, stake_lockouts)
|
if tower.is_slot_confirmed(*slot, stake_lockouts)
|
||||||
&& bank_forks
|
&& bank_forks
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -5366,8 +5366,8 @@
|
||||||
{
|
{
|
||||||
"aliasColors": {
|
"aliasColors": {
|
||||||
"cluster-info.repair": "#ba43a9",
|
"cluster-info.repair": "#ba43a9",
|
||||||
"locktower-observed.squash_account": "#0a437c",
|
"tower-observed.squash_account": "#0a437c",
|
||||||
"locktower-observed.squash_cache": "#ea6460",
|
"tower-observed.squash_cache": "#ea6460",
|
||||||
"replay_stage-new_leader.last": "#00ffbb",
|
"replay_stage-new_leader.last": "#00ffbb",
|
||||||
"window-service.receive": "#b7dbab",
|
"window-service.receive": "#b7dbab",
|
||||||
"window-stage.consumed": "#5195ce"
|
"window-stage.consumed": "#5195ce"
|
||||||
|
@ -5465,7 +5465,7 @@
|
||||||
"measurement": "cluster_info-vote-count",
|
"measurement": "cluster_info-vote-count",
|
||||||
"orderByTime": "ASC",
|
"orderByTime": "ASC",
|
||||||
"policy": "autogen",
|
"policy": "autogen",
|
||||||
"query": "SELECT mean(\"squash_accounts_ms\") AS \"squash_account\" FROM \"$testnet\".\"autogen\".\"locktower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
"query": "SELECT mean(\"squash_accounts_ms\") AS \"squash_account\" FROM \"$testnet\".\"autogen\".\"tower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"refId": "B",
|
"refId": "B",
|
||||||
"resultFormat": "time_series",
|
"resultFormat": "time_series",
|
||||||
|
@ -5504,7 +5504,7 @@
|
||||||
"measurement": "cluster_info-vote-count",
|
"measurement": "cluster_info-vote-count",
|
||||||
"orderByTime": "ASC",
|
"orderByTime": "ASC",
|
||||||
"policy": "autogen",
|
"policy": "autogen",
|
||||||
"query": "SELECT mean(\"squash_cache_ms\") AS \"squash_cache\" FROM \"$testnet\".\"autogen\".\"locktower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
"query": "SELECT mean(\"squash_cache_ms\") AS \"squash_cache\" FROM \"$testnet\".\"autogen\".\"tower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"refId": "C",
|
"refId": "C",
|
||||||
"resultFormat": "time_series",
|
"resultFormat": "time_series",
|
||||||
|
@ -5576,7 +5576,7 @@
|
||||||
"cluster-info.repair": "#ba43a9",
|
"cluster-info.repair": "#ba43a9",
|
||||||
"fetch_stage-discard_forwards.sum": "#00ffbb",
|
"fetch_stage-discard_forwards.sum": "#00ffbb",
|
||||||
"fetch_stage-honor_forwards.sum": "#bf1b00",
|
"fetch_stage-honor_forwards.sum": "#bf1b00",
|
||||||
"locktower-vote.last": "#00ffbb",
|
"tower-vote.last": "#00ffbb",
|
||||||
"replay_stage-new_leader.last": "#00ffbb",
|
"replay_stage-new_leader.last": "#00ffbb",
|
||||||
"window-service.receive": "#b7dbab",
|
"window-service.receive": "#b7dbab",
|
||||||
"window-stage.consumed": "#5195ce"
|
"window-stage.consumed": "#5195ce"
|
||||||
|
@ -5892,7 +5892,7 @@
|
||||||
"cluster-info.repair": "#ba43a9",
|
"cluster-info.repair": "#ba43a9",
|
||||||
"fetch_stage-discard_forwards.sum": "#00ffbb",
|
"fetch_stage-discard_forwards.sum": "#00ffbb",
|
||||||
"fetch_stage-honor_forwards.sum": "#bf1b00",
|
"fetch_stage-honor_forwards.sum": "#bf1b00",
|
||||||
"locktower-vote.last": "#00ffbb",
|
"tower-vote.last": "#00ffbb",
|
||||||
"poh_recorder-record_lock_contention.sum": "#5195ce",
|
"poh_recorder-record_lock_contention.sum": "#5195ce",
|
||||||
"poh_recorder-tick_lock_contention.sum": "#962d82",
|
"poh_recorder-tick_lock_contention.sum": "#962d82",
|
||||||
"replay_stage-new_leader.last": "#00ffbb",
|
"replay_stage-new_leader.last": "#00ffbb",
|
||||||
|
@ -7089,7 +7089,7 @@
|
||||||
"measurement": "cluster_info-vote-count",
|
"measurement": "cluster_info-vote-count",
|
||||||
"orderByTime": "ASC",
|
"orderByTime": "ASC",
|
||||||
"policy": "autogen",
|
"policy": "autogen",
|
||||||
"query": "SELECT last(\"latest\") - last(\"root\") FROM \"$testnet\".\"autogen\".\"locktower-vote\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
"query": "SELECT last(\"latest\") - last(\"root\") FROM \"$testnet\".\"autogen\".\"tower-vote\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"resultFormat": "time_series",
|
"resultFormat": "time_series",
|
||||||
|
@ -7126,7 +7126,7 @@
|
||||||
],
|
],
|
||||||
"orderByTime": "ASC",
|
"orderByTime": "ASC",
|
||||||
"policy": "default",
|
"policy": "default",
|
||||||
"query": "SELECT last(\"slot\") - last(\"root\") FROM \"$testnet\".\"autogen\".\"locktower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
"query": "SELECT last(\"slot\") - last(\"root\") FROM \"$testnet\".\"autogen\".\"tower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"refId": "B",
|
"refId": "B",
|
||||||
"resultFormat": "time_series",
|
"resultFormat": "time_series",
|
||||||
|
@ -7190,7 +7190,7 @@
|
||||||
{
|
{
|
||||||
"aliasColors": {
|
"aliasColors": {
|
||||||
"cluster-info.repair": "#ba43a9",
|
"cluster-info.repair": "#ba43a9",
|
||||||
"locktower-vote.last": "#00ffbb",
|
"tower-vote.last": "#00ffbb",
|
||||||
"replay_stage-new_leader.last": "#00ffbb",
|
"replay_stage-new_leader.last": "#00ffbb",
|
||||||
"window-service.receive": "#b7dbab",
|
"window-service.receive": "#b7dbab",
|
||||||
"window-stage.consumed": "#5195ce"
|
"window-stage.consumed": "#5195ce"
|
||||||
|
@ -7249,7 +7249,7 @@
|
||||||
"measurement": "cluster_info-vote-count",
|
"measurement": "cluster_info-vote-count",
|
||||||
"orderByTime": "ASC",
|
"orderByTime": "ASC",
|
||||||
"policy": "autogen",
|
"policy": "autogen",
|
||||||
"query": "SELECT last(\"root\") FROM \"$testnet\".\"autogen\".\"locktower-vote\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
"query": "SELECT last(\"root\") FROM \"$testnet\".\"autogen\".\"tower-vote\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"resultFormat": "time_series",
|
"resultFormat": "time_series",
|
||||||
|
@ -7286,7 +7286,7 @@
|
||||||
],
|
],
|
||||||
"orderByTime": "ASC",
|
"orderByTime": "ASC",
|
||||||
"policy": "default",
|
"policy": "default",
|
||||||
"query": "SELECT last(\"root\") FROM \"$testnet\".\"autogen\".\"locktower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
"query": "SELECT last(\"root\") FROM \"$testnet\".\"autogen\".\"tower-observed\" WHERE host_id =~ /$hostid/ AND $timeFilter GROUP BY time($__interval)",
|
||||||
"rawQuery": true,
|
"rawQuery": true,
|
||||||
"refId": "B",
|
"refId": "B",
|
||||||
"resultFormat": "time_series",
|
"resultFormat": "time_series",
|
||||||
|
|
|
@ -525,7 +525,7 @@ impl Bank {
|
||||||
let squash_cache_ms = duration_as_ms(&squash_cache_start.elapsed());
|
let squash_cache_ms = duration_as_ms(&squash_cache_start.elapsed());
|
||||||
|
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
"locktower-observed",
|
"tower-observed",
|
||||||
("squash_accounts_ms", squash_accounts_ms, i64),
|
("squash_accounts_ms", squash_accounts_ms, i64),
|
||||||
("squash_cache_ms", squash_cache_ms, i64)
|
("squash_cache_ms", squash_cache_ms, i64)
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue