Filter out outdated slots (#22450)
* Filter out outdated slots
* Fixup error
(cherry picked from commit 4ab7d6c23e
)
# Conflicts:
# core/src/consensus.rs
# local-cluster/tests/local_cluster.rs
# programs/vote/src/vote_state/mod.rs
# sdk/src/feature_set.rs
This commit is contained in:
@@ -602,6 +602,9 @@ impl ClusterInfoVoteListener {
|
||||
|
||||
// The last vote slot, which is the greatest slot in the stack
|
||||
// of votes in a vote transaction, qualifies for optimistic confirmation.
|
||||
// We cannot count any other slots in this vote toward optimistic confirmation because:
|
||||
// 1) There may have been a switch between the earlier vote and the last vote
|
||||
// 2) We do not know the hash of the earlier slot
|
||||
if slot == last_vote_slot {
|
||||
let vote_accounts = epoch_stakes.stakes().vote_accounts();
|
||||
let stake = vote_accounts
|
||||
|
@@ -366,8 +366,13 @@ impl Tower {
|
||||
last_voted_slot_in_bank: Option<Slot>,
|
||||
) -> Vote {
|
||||
let vote = Vote::new(vec![slot], hash);
|
||||
<<<<<<< HEAD
|
||||
local_vote_state.process_vote_unchecked(&vote);
|
||||
let slots = if let Some(last_voted_slot_in_bank) = last_voted_slot_in_bank {
|
||||
=======
|
||||
local_vote_state.process_vote_unchecked(vote);
|
||||
let slots = if let Some(last_voted_slot) = last_voted_slot_in_bank {
|
||||
>>>>>>> 4ab7d6c23 (Filter out outdated slots (#22450))
|
||||
local_vote_state
|
||||
.votes
|
||||
.iter()
|
||||
@@ -2274,7 +2279,7 @@ pub mod test {
|
||||
hash: Hash::default(),
|
||||
timestamp: None,
|
||||
};
|
||||
local.process_vote_unchecked(&vote);
|
||||
local.process_vote_unchecked(vote);
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
let vote =
|
||||
Tower::apply_vote_and_generate_vote_diff(&mut local, 1, Hash::default(), Some(0));
|
||||
@@ -2290,7 +2295,7 @@ pub mod test {
|
||||
hash: Hash::default(),
|
||||
timestamp: None,
|
||||
};
|
||||
local.process_vote_unchecked(&vote);
|
||||
local.process_vote_unchecked(vote);
|
||||
assert_eq!(local.votes.len(), 1);
|
||||
|
||||
// First vote expired, so should be evicted from tower. Thus even with
|
||||
|
@@ -1922,6 +1922,208 @@ fn root_in_tower(tower_path: &Path, node_pubkey: &Pubkey) -> Option<Slot> {
|
||||
restore_tower(tower_path, node_pubkey).map(|tower| tower.root())
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
// This test verifies that even if votes from a validator end up taking too long to land, and thus
|
||||
// some of the referenced slots are slots are no longer present in the slot hashes sysvar,
|
||||
// consensus can still be attained.
|
||||
//
|
||||
// Validator A (60%)
|
||||
// Validator B (40%)
|
||||
// / --- 10 --- [..] --- 16 (B is voting, due to network issues is initally not able to see the other fork at all)
|
||||
// /
|
||||
// 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 (A votes 1 - 9 votes are landing normally. B does the same however votes are not landing)
|
||||
// \
|
||||
// \--[..]-- 73 (majority fork)
|
||||
// A is voting on the majority fork and B wants to switch to this fork however in this majority fork
|
||||
// the earlier votes for B (1 - 9) never landed so when B eventually goes to vote on 73, slots in
|
||||
// its local vote state are no longer present in slot hashes.
|
||||
//
|
||||
// 1. Wait for B's tower to see local vote state was updated to new fork
|
||||
// 2. Wait X blocks, check B's vote state on chain has been properly updated
|
||||
//
|
||||
// NOTE: it is not reliable for B to organically have 1 to reach 2^16 lockout, so we simulate the 6
|
||||
// consecutive votes on the minor fork by manually incrementing the confirmation levels for the
|
||||
// common ancestor votes in tower.
|
||||
// To allow this test to run in a reasonable time we change the
|
||||
// slot_hash expiry to 64 slots.
|
||||
|
||||
#[test]
|
||||
fn test_slot_hash_expiry() {
|
||||
solana_logger::setup_with_default(RUST_LOG_FILTER);
|
||||
solana_sdk::slot_hashes::set_entries_for_tests_only(64);
|
||||
|
||||
let slots_per_epoch = 2048;
|
||||
let node_stakes = vec![60, 40];
|
||||
let validator_keys = vec![
|
||||
"28bN3xyvrP4E8LwEgtLjhnkb7cY4amQb6DrYAbAYjgRV4GAGgkVM2K7wnxnAS7WDneuavza7x21MiafLu1HkwQt4",
|
||||
"2saHBBoTkLMmttmPQP8KfBkcCw45S5cwtV3wTdGCscRC8uxdgvHxpHiWXKx4LvJjNJtnNcbSv5NdheokFFqnNDt8",
|
||||
]
|
||||
.iter()
|
||||
.map(|s| (Arc::new(Keypair::from_base58_string(s)), true))
|
||||
.collect::<Vec<_>>();
|
||||
let node_vote_keys = vec![
|
||||
"3NDQ3ud86RTVg8hTy2dDWnS4P8NfjhZ2gDgQAJbr3heaKaUVS1FW3sTLKA1GmDrY9aySzsa4QxpDkbLv47yHxzr3",
|
||||
"46ZHpHE6PEvXYPu3hf9iQqjBk2ZNDaJ9ejqKWHEjxaQjpAGasKaWKbKHbP3646oZhfgDRzx95DH9PCBKKsoCVngk",
|
||||
]
|
||||
.iter()
|
||||
.map(|s| Arc::new(Keypair::from_base58_string(s)))
|
||||
.collect::<Vec<_>>();
|
||||
let vs = validator_keys
|
||||
.iter()
|
||||
.map(|(kp, _)| kp.pubkey())
|
||||
.collect::<Vec<_>>();
|
||||
let (a_pubkey, b_pubkey) = (vs[0], vs[1]);
|
||||
|
||||
// We want B to not vote (we are trying to simulate its votes not landing until it gets to the
|
||||
// minority fork)
|
||||
let mut validator_configs =
|
||||
make_identical_validator_configs(&ValidatorConfig::default(), node_stakes.len());
|
||||
validator_configs[1].voting_disabled = true;
|
||||
|
||||
let mut config = ClusterConfig {
|
||||
cluster_lamports: 100_000,
|
||||
node_stakes,
|
||||
validator_configs,
|
||||
validator_keys: Some(validator_keys),
|
||||
node_vote_keys: Some(node_vote_keys),
|
||||
slots_per_epoch,
|
||||
stakers_slot_offset: slots_per_epoch,
|
||||
skip_warmup_slots: true,
|
||||
..ClusterConfig::default()
|
||||
};
|
||||
let mut cluster = LocalCluster::new(&mut config, SocketAddrSpace::Unspecified);
|
||||
|
||||
let mut common_ancestor_slot = 8;
|
||||
|
||||
let a_ledger_path = cluster.ledger_path(&a_pubkey);
|
||||
let b_ledger_path = cluster.ledger_path(&b_pubkey);
|
||||
|
||||
// Immediately kill B (we just needed it for the initial stake distribution)
|
||||
info!("Killing B");
|
||||
let mut b_info = cluster.exit_node(&b_pubkey);
|
||||
|
||||
// Let A run for a while until we get to the common ancestor
|
||||
info!("Letting A run until common_ancestor_slot");
|
||||
loop {
|
||||
if let Some((last_vote, _)) = last_vote_in_tower(&a_ledger_path, &a_pubkey) {
|
||||
if last_vote >= common_ancestor_slot {
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
// Keep A running, but setup B so that it thinks it has voted up until common ancestor (but
|
||||
// doesn't know anything past that)
|
||||
{
|
||||
info!("Copying A's ledger to B");
|
||||
std::fs::remove_dir_all(&b_info.info.ledger_path).unwrap();
|
||||
let mut opt = fs_extra::dir::CopyOptions::new();
|
||||
opt.copy_inside = true;
|
||||
fs_extra::dir::copy(&a_ledger_path, &b_ledger_path, &opt).unwrap();
|
||||
|
||||
// remove A's tower in B's new copied ledger
|
||||
info!("Removing A's tower in B's ledger dir");
|
||||
remove_tower(&b_ledger_path, &a_pubkey);
|
||||
|
||||
// load A's tower and save it as B's tower
|
||||
info!("Loading A's tower");
|
||||
if let Some(mut a_tower) = restore_tower(&a_ledger_path, &a_pubkey) {
|
||||
a_tower.node_pubkey = b_pubkey;
|
||||
// Update common_ancestor_slot because A is still running
|
||||
if let Some(s) = a_tower.last_voted_slot() {
|
||||
common_ancestor_slot = s;
|
||||
info!("New common_ancestor_slot {}", common_ancestor_slot);
|
||||
} else {
|
||||
panic!("A's tower has no votes");
|
||||
}
|
||||
info!("Increase lockout by 6 confirmation levels and save as B's tower");
|
||||
a_tower.increase_lockout(6);
|
||||
save_tower(&b_ledger_path, &a_tower, &b_info.info.keypair);
|
||||
info!("B's new tower: {:?}", a_tower.tower_slots());
|
||||
} else {
|
||||
panic!("A's tower is missing");
|
||||
}
|
||||
|
||||
// Get rid of any slots past common_ancestor_slot
|
||||
info!("Removing extra slots from B's blockstore");
|
||||
let blockstore = open_blockstore(&b_ledger_path);
|
||||
purge_slots(&blockstore, common_ancestor_slot + 1, 100);
|
||||
}
|
||||
|
||||
info!(
|
||||
"Run A on majority fork until it reaches slot hash expiry {}",
|
||||
solana_sdk::slot_hashes::get_entries()
|
||||
);
|
||||
let mut last_vote_on_a;
|
||||
// Keep A running for a while longer so the majority fork has some decent size
|
||||
loop {
|
||||
last_vote_on_a = wait_for_last_vote_in_tower_to_land_in_ledger(&a_ledger_path, &a_pubkey);
|
||||
if last_vote_on_a
|
||||
>= common_ancestor_slot + 2 * (solana_sdk::slot_hashes::get_entries() as u64)
|
||||
{
|
||||
let blockstore = open_blockstore(&a_ledger_path);
|
||||
info!(
|
||||
"A majority fork: {:?}",
|
||||
AncestorIterator::new(last_vote_on_a, &blockstore).collect::<Vec<Slot>>()
|
||||
);
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
||||
// Kill A and restart B with voting. B should now fork off
|
||||
info!("Killing A");
|
||||
let a_info = cluster.exit_node(&a_pubkey);
|
||||
|
||||
info!("Restarting B");
|
||||
b_info.config.voting_disabled = false;
|
||||
cluster.restart_node(&b_pubkey, b_info, SocketAddrSpace::Unspecified);
|
||||
|
||||
// B will fork off and accumulate enough lockout
|
||||
info!("Allowing B to fork");
|
||||
loop {
|
||||
let blockstore = open_blockstore(&b_ledger_path);
|
||||
let last_vote = wait_for_last_vote_in_tower_to_land_in_ledger(&b_ledger_path, &b_pubkey);
|
||||
let mut ancestors = AncestorIterator::new(last_vote, &blockstore);
|
||||
if let Some(index) = ancestors.position(|x| x == common_ancestor_slot) {
|
||||
if index > 7 {
|
||||
info!(
|
||||
"B has forked for enough lockout: {:?}",
|
||||
AncestorIterator::new(last_vote, &blockstore).collect::<Vec<Slot>>()
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(1000));
|
||||
}
|
||||
|
||||
info!("Kill B");
|
||||
b_info = cluster.exit_node(&b_pubkey);
|
||||
|
||||
info!("Resolve the partition");
|
||||
{
|
||||
// Here we let B know about the missing blocks that A had produced on its partition
|
||||
let a_blockstore = open_blockstore(&a_ledger_path);
|
||||
let b_blockstore = open_blockstore(&b_ledger_path);
|
||||
copy_blocks(last_vote_on_a, &a_blockstore, &b_blockstore);
|
||||
}
|
||||
|
||||
// Now restart A and B and see if B is able to eventually switch onto the majority fork
|
||||
info!("Restarting A & B");
|
||||
cluster.restart_node(&a_pubkey, a_info, SocketAddrSpace::Unspecified);
|
||||
cluster.restart_node(&b_pubkey, b_info, SocketAddrSpace::Unspecified);
|
||||
|
||||
info!("Waiting for B to switch to majority fork and make a root");
|
||||
cluster_tests::check_for_new_roots(
|
||||
16,
|
||||
&[cluster.get_contact_info(&a_pubkey).unwrap().clone()],
|
||||
"test_slot_hashes_expiry",
|
||||
);
|
||||
}
|
||||
|
||||
>>>>>>> 4ab7d6c23 (Filter out outdated slots (#22450))
|
||||
enum ClusterMode {
|
||||
MasterOnly,
|
||||
MasterSlave,
|
||||
|
@@ -80,6 +80,9 @@ pub enum VoteError {
|
||||
|
||||
#[error("New state contained too many votes")]
|
||||
TooManyVotes,
|
||||
|
||||
#[error("every slot in the vote was older than the SlotHashes history")]
|
||||
VotesTooOldAllFiltered,
|
||||
}
|
||||
|
||||
impl<E> DecodeError<E> for VoteError {
|
||||
@@ -436,7 +439,14 @@ pub fn process_instruction(
|
||||
keyed_account_at_index(keyed_accounts, first_instruction_account + 2)?,
|
||||
invoke_context,
|
||||
)?;
|
||||
vote_state::process_vote(me, &slot_hashes, &clock, &vote, &signers)
|
||||
vote_state::process_vote(
|
||||
me,
|
||||
&slot_hashes,
|
||||
&clock,
|
||||
&vote,
|
||||
&signers,
|
||||
&invoke_context.feature_set,
|
||||
)
|
||||
}
|
||||
VoteInstruction::Withdraw(lamports) => {
|
||||
let to = keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?;
|
||||
|
@@ -10,7 +10,11 @@ use {
|
||||
account_utils::State,
|
||||
clock::{Epoch, Slot, UnixTimestamp},
|
||||
epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET,
|
||||
<<<<<<< HEAD
|
||||
feature_set::{self, FeatureSet},
|
||||
=======
|
||||
feature_set::{filter_votes_outside_slot_hashes, FeatureSet},
|
||||
>>>>>>> 4ab7d6c23 (Filter out outdated slots (#22450))
|
||||
hash::Hash,
|
||||
instruction::InstructionError,
|
||||
keyed_account::KeyedAccount,
|
||||
@@ -302,7 +306,8 @@ impl VoteState {
|
||||
|
||||
fn check_slots_are_valid(
|
||||
&self,
|
||||
vote: &Vote,
|
||||
vote_slots: &[Slot],
|
||||
vote_hash: &Hash,
|
||||
slot_hashes: &[(Slot, Hash)],
|
||||
) -> Result<(), VoteError> {
|
||||
// index into the vote's slots, sarting at the newest
|
||||
@@ -315,32 +320,32 @@ impl VoteState {
|
||||
|
||||
// Note:
|
||||
//
|
||||
// 1) `vote.slots` is sorted from oldest/smallest vote to newest/largest
|
||||
// 1) `vote_slots` is sorted from oldest/smallest vote to newest/largest
|
||||
// vote, due to the way votes are applied to the vote state (newest votes
|
||||
// pushed to the back), but `slot_hashes` is sorted smallest to largest.
|
||||
//
|
||||
// 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
|
||||
// the oldest/smallest vote
|
||||
while i < vote.slots.len() && j > 0 {
|
||||
// 1) increment `i` to find the smallest slot `s` in `vote.slots`
|
||||
while i < vote_slots.len() && j > 0 {
|
||||
// 1) increment `i` to find the smallest slot `s` in `vote_slots`
|
||||
// where `s` >= `last_voted_slot`
|
||||
if self
|
||||
.last_voted_slot()
|
||||
.map_or(false, |last_voted_slot| vote.slots[i] <= last_voted_slot)
|
||||
.map_or(false, |last_voted_slot| vote_slots[i] <= last_voted_slot)
|
||||
{
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2) Find the hash for this slot `s`.
|
||||
if vote.slots[i] != slot_hashes[j - 1].0 {
|
||||
if vote_slots[i] != slot_hashes[j - 1].0 {
|
||||
// Decrement `j` to find newer slots
|
||||
j -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3) Once the hash for `s` is found, bump `s` to the next slot
|
||||
// in `vote.slots` and continue.
|
||||
// in `vote_slots` and continue.
|
||||
i += 1;
|
||||
j -= 1;
|
||||
}
|
||||
@@ -348,30 +353,30 @@ impl VoteState {
|
||||
if j == slot_hashes.len() {
|
||||
// This means we never made it to steps 2) or 3) above, otherwise
|
||||
// `j` would have been decremented at least once. This means
|
||||
// there are not slots in `vote` greater than `last_voted_slot`
|
||||
// there are not slots in `vote_slots` greater than `last_voted_slot`
|
||||
debug!(
|
||||
"{} dropped vote {:?} too old: {:?} ",
|
||||
self.node_pubkey, vote, slot_hashes
|
||||
"{} dropped vote slots {:?}, vote hash: {:?} slot hashes:SlotHash {:?}, too old ",
|
||||
self.node_pubkey, vote_slots, vote_hash, slot_hashes
|
||||
);
|
||||
return Err(VoteError::VoteTooOld);
|
||||
}
|
||||
if i != vote.slots.len() {
|
||||
if i != vote_slots.len() {
|
||||
// This means there existed some slot for which we couldn't find
|
||||
// a matching slot hash in step 2)
|
||||
info!(
|
||||
"{} dropped vote {:?} failed to match slot: {:?}",
|
||||
self.node_pubkey, vote, slot_hashes,
|
||||
"{} dropped vote slots {:?} failed to match slot hashes: {:?}",
|
||||
self.node_pubkey, vote_slots, slot_hashes,
|
||||
);
|
||||
inc_new_counter_info!("dropped-vote-slot", 1);
|
||||
return Err(VoteError::SlotsMismatch);
|
||||
}
|
||||
if slot_hashes[j].1 != vote.hash {
|
||||
// This means the newest vote in the slot has a match that
|
||||
if &slot_hashes[j].1 != vote_hash {
|
||||
// This means the newest slot in the `vote_slots` has a match that
|
||||
// doesn't match the expected hash for that slot on this
|
||||
// fork
|
||||
warn!(
|
||||
"{} dropped vote {:?} failed to match hash {} {}",
|
||||
self.node_pubkey, vote, vote.hash, slot_hashes[j].1
|
||||
"{} dropped vote slots {:?} failed to match hash {} {}",
|
||||
self.node_pubkey, vote_slots, vote_hash, slot_hashes[j].1
|
||||
);
|
||||
inc_new_counter_info!("dropped-vote-hash", 1);
|
||||
return Err(VoteError::SlotHashMismatch);
|
||||
@@ -554,13 +559,36 @@ impl VoteState {
|
||||
vote: &Vote,
|
||||
slot_hashes: &[SlotHash],
|
||||
epoch: Epoch,
|
||||
feature_set: Option<&FeatureSet>,
|
||||
) -> Result<(), VoteError> {
|
||||
if vote.slots.is_empty() {
|
||||
return Err(VoteError::EmptySlots);
|
||||
}
|
||||
self.check_slots_are_valid(vote, slot_hashes)?;
|
||||
|
||||
vote.slots
|
||||
let filtered_vote_slots = feature_set.and_then(|feature_set| {
|
||||
if feature_set.is_active(&filter_votes_outside_slot_hashes::id()) {
|
||||
let earliest_slot_in_history =
|
||||
slot_hashes.last().map(|(slot, _hash)| *slot).unwrap_or(0);
|
||||
Some(
|
||||
vote.slots
|
||||
.iter()
|
||||
.filter(|slot| **slot >= earliest_slot_in_history)
|
||||
.cloned()
|
||||
.collect::<Vec<Slot>>(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let vote_slots = filtered_vote_slots.as_ref().unwrap_or(&vote.slots);
|
||||
if vote_slots.is_empty() {
|
||||
return Err(VoteError::VotesTooOldAllFiltered);
|
||||
}
|
||||
|
||||
self.check_slots_are_valid(vote_slots, &vote.hash, slot_hashes)?;
|
||||
|
||||
vote_slots
|
||||
.iter()
|
||||
.for_each(|s| self.process_next_vote_slot(*s, epoch));
|
||||
Ok(())
|
||||
@@ -619,9 +647,9 @@ impl VoteState {
|
||||
}
|
||||
|
||||
/// "unchecked" functions used by tests and Tower
|
||||
pub fn process_vote_unchecked(&mut self, vote: &Vote) {
|
||||
pub fn process_vote_unchecked(&mut self, vote: Vote) {
|
||||
let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
|
||||
let _ignored = self.process_vote(vote, &slot_hashes, self.current_epoch());
|
||||
let _ignored = self.process_vote(&vote, &slot_hashes, self.current_epoch(), None);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -632,7 +660,7 @@ impl VoteState {
|
||||
}
|
||||
|
||||
pub fn process_slot_vote_unchecked(&mut self, slot: Slot) {
|
||||
self.process_vote_unchecked(&Vote::new(vec![slot], Hash::default()));
|
||||
self.process_vote_unchecked(Vote::new(vec![slot], Hash::default()));
|
||||
}
|
||||
|
||||
pub fn nth_recent_vote(&self, position: usize) -> Option<&Lockout> {
|
||||
@@ -997,7 +1025,24 @@ pub fn process_vote<S: std::hash::BuildHasher>(
|
||||
let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
|
||||
verify_authorized_signer(&authorized_voter, signers)?;
|
||||
|
||||
<<<<<<< HEAD
|
||||
vote_state.process_vote(vote, slot_hashes, clock.epoch)?;
|
||||
=======
|
||||
Ok(vote_state)
|
||||
}
|
||||
|
||||
pub fn process_vote<S: std::hash::BuildHasher>(
|
||||
vote_account: &KeyedAccount,
|
||||
slot_hashes: &[SlotHash],
|
||||
clock: &Clock,
|
||||
vote: &Vote,
|
||||
signers: &HashSet<Pubkey, S>,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
|
||||
|
||||
vote_state.process_vote(vote, slot_hashes, clock.epoch, Some(feature_set))?;
|
||||
>>>>>>> 4ab7d6c23 (Filter out outdated slots (#22450))
|
||||
if let Some(timestamp) = vote.timestamp {
|
||||
vote.slots
|
||||
.iter()
|
||||
@@ -1008,6 +1053,38 @@ pub fn process_vote<S: std::hash::BuildHasher>(
|
||||
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
pub fn process_vote_state_update<S: std::hash::BuildHasher>(
|
||||
vote_account: &KeyedAccount,
|
||||
slot_hashes: &[SlotHash],
|
||||
clock: &Clock,
|
||||
vote_state_update: VoteStateUpdate,
|
||||
signers: &HashSet<Pubkey, S>,
|
||||
) -> Result<(), InstructionError> {
|
||||
let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
|
||||
{
|
||||
let vote = Vote {
|
||||
slots: vote_state_update
|
||||
.lockouts
|
||||
.iter()
|
||||
.map(|lockout| lockout.slot)
|
||||
.collect(),
|
||||
hash: vote_state_update.hash,
|
||||
timestamp: vote_state_update.timestamp,
|
||||
};
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, slot_hashes)?;
|
||||
}
|
||||
vote_state.process_new_vote_state(
|
||||
vote_state_update.lockouts,
|
||||
vote_state_update.root,
|
||||
vote_state_update.timestamp,
|
||||
clock.epoch,
|
||||
)?;
|
||||
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
|
||||
}
|
||||
|
||||
>>>>>>> 4ab7d6c23 (Filter out outdated slots (#22450))
|
||||
pub fn create_account_with_authorized(
|
||||
node_pubkey: &Pubkey,
|
||||
authorized_voter: &Pubkey,
|
||||
@@ -1231,8 +1308,9 @@ mod tests {
|
||||
epoch,
|
||||
..Clock::default()
|
||||
},
|
||||
&vote.clone(),
|
||||
vote,
|
||||
&signers,
|
||||
&FeatureSet::default(),
|
||||
)?;
|
||||
StateMut::<VoteStateVersions>::state(&*vote_account.borrow())
|
||||
.map(|versioned| versioned.convert_to_current())
|
||||
@@ -1441,6 +1519,7 @@ mod tests {
|
||||
},
|
||||
&vote,
|
||||
&signers,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||
|
||||
@@ -1457,6 +1536,7 @@ mod tests {
|
||||
},
|
||||
&vote,
|
||||
&signers,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
|
||||
@@ -1587,6 +1667,7 @@ mod tests {
|
||||
},
|
||||
&vote,
|
||||
&signers,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(res, Err(InstructionError::MissingRequiredSignature));
|
||||
|
||||
@@ -1608,6 +1689,7 @@ mod tests {
|
||||
},
|
||||
&vote,
|
||||
&signers,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(res, Ok(()));
|
||||
|
||||
@@ -1828,8 +1910,14 @@ mod tests {
|
||||
let vote = Vote::new(slots, Hash::default());
|
||||
let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
|
||||
|
||||
assert_eq!(vote_state_a.process_vote(&vote, &slot_hashes, 0), Ok(()));
|
||||
assert_eq!(vote_state_b.process_vote(&vote, &slot_hashes, 0), Ok(()));
|
||||
assert_eq!(
|
||||
vote_state_a.process_vote(&vote, &slot_hashes, 0, Some(&FeatureSet::default())),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
vote_state_b.process_vote(&vote, &slot_hashes, 0, Some(&FeatureSet::default())),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
|
||||
}
|
||||
|
||||
@@ -1839,10 +1927,13 @@ mod tests {
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(0, vote.hash)];
|
||||
assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(()));
|
||||
assert_eq!(
|
||||
vote_state.process_vote(&vote, &slot_hashes, 0, Some(&FeatureSet::default())),
|
||||
Ok(())
|
||||
);
|
||||
let recent = recent_votes(&vote_state);
|
||||
assert_eq!(
|
||||
vote_state.process_vote(&vote, &slot_hashes, 0),
|
||||
vote_state.process_vote(&vote, &slot_hashes, 0, Some(&FeatureSet::default())),
|
||||
Err(VoteError::VoteTooOld)
|
||||
);
|
||||
assert_eq!(recent, recent_votes(&vote_state));
|
||||
@@ -1854,7 +1945,7 @@ mod tests {
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote, &[]),
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, &[]),
|
||||
Err(VoteError::VoteTooOld)
|
||||
);
|
||||
}
|
||||
@@ -1866,7 +1957,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote, &slot_hashes),
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, &slot_hashes),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
@@ -1878,7 +1969,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote, &slot_hashes),
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, &slot_hashes),
|
||||
Err(VoteError::SlotHashMismatch)
|
||||
);
|
||||
}
|
||||
@@ -1890,7 +1981,7 @@ mod tests {
|
||||
let vote = Vote::new(vec![1], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(0, vote.hash)];
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote, &slot_hashes),
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, &slot_hashes),
|
||||
Err(VoteError::SlotsMismatch)
|
||||
);
|
||||
}
|
||||
@@ -1901,9 +1992,12 @@ mod tests {
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(()));
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote, &slot_hashes),
|
||||
vote_state.process_vote(&vote, &slot_hashes, 0, Some(&FeatureSet::default())),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, &slot_hashes),
|
||||
Err(VoteError::VoteTooOld)
|
||||
);
|
||||
}
|
||||
@@ -1914,12 +2008,15 @@ mod tests {
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(()));
|
||||
assert_eq!(
|
||||
vote_state.process_vote(&vote, &slot_hashes, 0, Some(&FeatureSet::default())),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let vote = Vote::new(vec![0, 1], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote, &slot_hashes),
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, &slot_hashes),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
@@ -1930,12 +2027,15 @@ mod tests {
|
||||
|
||||
let vote = Vote::new(vec![0], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
|
||||
assert_eq!(vote_state.process_vote(&vote, &slot_hashes, 0), Ok(()));
|
||||
assert_eq!(
|
||||
vote_state.process_vote(&vote, &slot_hashes, 0, Some(&FeatureSet::default())),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let vote = Vote::new(vec![1], Hash::default());
|
||||
let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
|
||||
assert_eq!(
|
||||
vote_state.check_slots_are_valid(&vote, &slot_hashes),
|
||||
vote_state.check_slots_are_valid(&vote.slots, &vote.hash, &slot_hashes),
|
||||
Ok(())
|
||||
);
|
||||
}
|
||||
@@ -1945,7 +2045,7 @@ mod tests {
|
||||
|
||||
let vote = Vote::new(vec![], Hash::default());
|
||||
assert_eq!(
|
||||
vote_state.process_vote(&vote, &[], 0),
|
||||
vote_state.process_vote(&vote, &[], 0, Some(&FeatureSet::default())),
|
||||
Err(VoteError::EmptySlots)
|
||||
);
|
||||
}
|
||||
@@ -3394,4 +3494,44 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(vote_state1.votes, good_votes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter_old_votes() {
|
||||
// Enable feature
|
||||
let mut feature_set = FeatureSet::default();
|
||||
feature_set.activate(&filter_votes_outside_slot_hashes::id(), 0);
|
||||
|
||||
let mut vote_state = VoteState::default();
|
||||
let old_vote_slot = 1;
|
||||
let vote = Vote::new(vec![old_vote_slot], Hash::default());
|
||||
|
||||
// Vote with all slots that are all older than the SlotHashes history should
|
||||
// error with `VotesTooOldAllFiltered`
|
||||
let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())];
|
||||
assert_eq!(
|
||||
vote_state.process_vote(&vote, &slot_hashes, 0, Some(&feature_set),),
|
||||
Err(VoteError::VotesTooOldAllFiltered)
|
||||
);
|
||||
|
||||
// Vote with only some slots older than the SlotHashes history should
|
||||
// filter out those older slots
|
||||
let vote_slot = 2;
|
||||
let vote_slot_hash = slot_hashes
|
||||
.iter()
|
||||
.find(|(slot, _hash)| *slot == vote_slot)
|
||||
.unwrap()
|
||||
.1;
|
||||
|
||||
let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash);
|
||||
vote_state
|
||||
.process_vote(&vote, &slot_hashes, 0, Some(&feature_set))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
vote_state.votes.into_iter().collect::<Vec<Lockout>>(),
|
||||
vec![Lockout {
|
||||
slot: vote_slot,
|
||||
confirmation_count: 1,
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -267,6 +267,7 @@ pub mod require_rent_exempt_accounts {
|
||||
solana_sdk::declare_id!("BkFDxiJQWZXGTZaJQxH7wVEHkAmwCgSEVkrvswFfRJPD");
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
pub mod vote_withdraw_authority_may_change_authorized_voter {
|
||||
solana_sdk::declare_id!("AVZS3ZsN4gi6Rkx2QUibYuSJG3S6QHib7xCYhG6vGJxU");
|
||||
}
|
||||
@@ -293,6 +294,10 @@ pub mod disable_bpf_deprecated_load_instructions {
|
||||
|
||||
pub mod disable_bpf_unresolved_symbols_at_runtime {
|
||||
solana_sdk::declare_id!("4yuaYAj2jGMGTh1sSmi4G2eFscsDq8qjugJXZoBN6YEa");
|
||||
=======
|
||||
pub mod filter_votes_outside_slot_hashes {
|
||||
solana_sdk::declare_id!("3gtZPqvPpsbXZVCx6hceMfWxtsmrjMzmg8C7PLKSxS2d");
|
||||
>>>>>>> 4ab7d6c23 (Filter out outdated slots (#22450))
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
@@ -356,6 +361,7 @@ lazy_static! {
|
||||
(cap_accounts_data_len::id(), "cap the accounts data len"),
|
||||
(max_tx_account_locks::id(), "enforce max number of locked accounts per transaction"),
|
||||
(require_rent_exempt_accounts::id(), "require all new transaction accounts with data to be rent-exempt"),
|
||||
<<<<<<< HEAD
|
||||
(vote_withdraw_authority_may_change_authorized_voter::id(), "vote account withdraw authority may change the authorized voter #22521"),
|
||||
(spl_associated_token_account_v1_0_4::id(), "SPL Associated Token Account Program release version 1.0.4, tied to token 3.3.0 #22648"),
|
||||
(update_syscall_base_costs::id(), "Update syscall base costs"),
|
||||
@@ -363,6 +369,9 @@ lazy_static! {
|
||||
(bank_tranaction_count_fix::id(), "Fixes Bank::transaction_count to include all committed transactions, not just successful ones"),
|
||||
(disable_bpf_deprecated_load_instructions::id(), "Disable ldabs* and ldind* BPF instructions"),
|
||||
(disable_bpf_unresolved_symbols_at_runtime::id(), "Disable reporting of unresolved BPF symbols at runtime"),
|
||||
=======
|
||||
(filter_votes_outside_slot_hashes::id(), "filter vote slots older than the slot hashes history"),
|
||||
>>>>>>> 4ab7d6c23 (Filter out outdated slots (#22450))
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
Reference in New Issue
Block a user