diff --git a/programs/vote/benches/process_vote.rs b/programs/vote/benches/process_vote.rs index 9008971f086237..a092056c9353ff 100644 --- a/programs/vote/benches/process_vote.rs +++ b/programs/vote/benches/process_vote.rs @@ -48,7 +48,7 @@ fn create_accounts() -> (Slot, SlotHashes, Vec, Vec = vec![0; VoteState::size_of()]; let versioned = VoteStateVersions::new_current(vote_state); diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 871f4696c1a078..b68a0ee04ac135 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -616,6 +616,9 @@ pub fn process_new_vote_state( let timely_vote_credits = feature_set.map_or(false, |f| { f.is_active(&feature_set::timely_vote_credits::id()) }); + let deprecate_unused_legacy_vote_plumbing = feature_set.map_or(false, |f| { + f.is_active(&feature_set::deprecate_unused_legacy_vote_plumbing::id()) + }); let mut earned_credits = if timely_vote_credits { 0_u64 } else { 1_u64 }; if let Some(new_root) = new_root { @@ -625,7 +628,11 @@ pub fn process_new_vote_state( if current_vote.slot() <= new_root { if timely_vote_credits || (current_vote.slot() != new_root) { earned_credits = earned_credits - .checked_add(vote_state.credits_for_vote_at_index(current_vote_state_index)) + .checked_add(vote_state.credits_for_vote_at_index( + current_vote_state_index, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + )) .expect("`earned_credits` does not overflow"); } current_vote_state_index = current_vote_state_index @@ -738,11 +745,19 @@ pub fn process_vote_unfiltered( slot_hashes: &[SlotHash], epoch: Epoch, current_slot: Slot, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, ) -> Result<(), VoteError> { check_slots_are_valid(vote_state, vote_slots, &vote.hash, slot_hashes)?; - vote_slots - .iter() - .for_each(|s| vote_state.process_next_vote_slot(*s, epoch, current_slot)); + vote_slots.iter().for_each(|s| { + vote_state.process_next_vote_slot( + *s, + epoch, + current_slot, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + ) + }); Ok(()) } @@ -752,6 +767,8 @@ pub fn process_vote( slot_hashes: &[SlotHash], epoch: Epoch, current_slot: Slot, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, ) -> Result<(), VoteError> { if vote.slots.is_empty() { return Err(VoteError::EmptySlots); @@ -773,6 +790,8 @@ pub fn process_vote( slot_hashes, epoch, current_slot, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, ) } @@ -789,6 +808,8 @@ pub fn process_vote_unchecked(vote_state: &mut VoteState, vote: Vote) -> Result< &slot_hashes, vote_state.current_epoch(), 0, + true, + true, ) } @@ -1036,7 +1057,18 @@ pub fn process_vote_with_account( ) -> Result<(), InstructionError> { let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?; - process_vote(&mut vote_state, vote, slot_hashes, clock.epoch, clock.slot)?; + let timely_vote_credits = feature_set.is_active(&feature_set::timely_vote_credits::id()); + let deprecate_unused_legacy_vote_plumbing = + feature_set.is_active(&feature_set::deprecate_unused_legacy_vote_plumbing::id()); + process_vote( + &mut vote_state, + vote, + slot_hashes, + clock.epoch, + clock.slot, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + )?; if let Some(timestamp) = vote.timestamp { vote.slots .iter() @@ -1219,7 +1251,7 @@ mod tests { 134, 135, ] .into_iter() - .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0)); + .for_each(|v| vote_state.process_next_vote_slot(v, 4, 0, false, true)); let version1_14_11_serialized = bincode::serialize(&VoteStateVersions::V1_14_11(Box::new( VoteState1_14_11::from(vote_state.clone()), @@ -1511,11 +1543,11 @@ mod tests { let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect(); assert_eq!( - process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state_a, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); assert_eq!( - process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state_b, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b)); @@ -1528,12 +1560,12 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(0, vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); let recent = recent_votes(&vote_state); assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Err(VoteError::VoteTooOld) ); assert_eq!(recent, recent_votes(&vote_state)); @@ -1593,7 +1625,7 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); assert_eq!( @@ -1609,7 +1641,7 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); @@ -1628,7 +1660,7 @@ mod tests { let vote = Vote::new(vec![0], Hash::default()); let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Ok(()) ); @@ -1645,7 +1677,7 @@ mod tests { let vote = Vote::new(vec![], Hash::default()); assert_eq!( - process_vote(&mut vote_state, &vote, &[], 0, 0), + process_vote(&mut vote_state, &vote, &[], 0, 0, true, true), Err(VoteError::EmptySlots) ); } @@ -1901,7 +1933,9 @@ mod tests { &vote, &slot_hashes, 0, - vote_group.1 // vote_group.1 is the slot in which the vote was cast + vote_group.1, // vote_group.1 is the slot in which the vote was cast + true, + true ), Ok(()) ); @@ -2793,7 +2827,7 @@ mod tests { // error with `VotesTooOldAllFiltered` let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())]; assert_eq!( - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0), + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true), Err(VoteError::VotesTooOldAllFiltered) ); @@ -2807,7 +2841,7 @@ mod tests { .1; let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash); - process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0).unwrap(); + process_vote(&mut vote_state, &vote, &slot_hashes, 0, 0, true, true).unwrap(); assert_eq!( vote_state .votes @@ -2836,8 +2870,17 @@ mod tests { .unwrap() .1; let vote = Vote::new(vote_slots, vote_hash); - process_vote_unfiltered(&mut vote_state, &vote.slots, &vote, slot_hashes, 0, 0) - .unwrap(); + process_vote_unfiltered( + &mut vote_state, + &vote.slots, + &vote, + slot_hashes, + 0, + 0, + true, + true, + ) + .unwrap(); } vote_state diff --git a/sdk/program/src/vote/state/mod.rs b/sdk/program/src/vote/state/mod.rs index 31e2ea6f31e7ee..eb9ec9282f4fd3 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -448,6 +448,8 @@ impl VoteState { next_vote_slot: Slot, epoch: Epoch, current_slot: Slot, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, ) { // Ignore votes for slots earlier than we already have votes for if self @@ -460,13 +462,21 @@ impl VoteState { self.pop_expired_votes(next_vote_slot); let landed_vote = LandedVote { - latency: Self::compute_vote_latency(next_vote_slot, current_slot), + latency: if timely_vote_credits || !deprecate_unused_legacy_vote_plumbing { + Self::compute_vote_latency(next_vote_slot, current_slot) + } else { + 0 + }, lockout: Lockout::new(next_vote_slot), }; // Once the stack is full, pop the oldest lockout and distribute rewards if self.votes.len() == MAX_LOCKOUT_HISTORY { - let credits = self.credits_for_vote_at_index(0); + let credits = self.credits_for_vote_at_index( + 0, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, + ); let landed_vote = self.votes.pop_front().unwrap(); self.root_slot = Some(landed_vote.slot()); @@ -511,7 +521,12 @@ impl VoteState { } /// Returns the credits to award for a vote at the given lockout slot index - pub fn credits_for_vote_at_index(&self, index: usize) -> u64 { + pub fn credits_for_vote_at_index( + &self, + index: usize, + timely_vote_credits: bool, + deprecate_unused_legacy_vote_plumbing: bool, + ) -> u64 { let latency = self .votes .get(index) @@ -519,7 +534,7 @@ impl VoteState { // If latency is 0, this means that the Lockout was created and stored from a software version that did not // store vote latencies; in this case, 1 credit is awarded - if latency == 0 { + if latency == 0 || (deprecate_unused_legacy_vote_plumbing && !timely_vote_credits) { 1 } else { match latency.checked_sub(VOTE_CREDITS_GRACE_SLOTS) { diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 4b8289d7c995b7..86c0b9e66784df 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -748,6 +748,10 @@ pub mod disable_bpf_loader_instructions { solana_sdk::declare_id!("7WeS1vfPRgeeoXArLh7879YcB9mgE9ktjPDtajXeWfXn"); } +pub mod deprecate_unused_legacy_vote_plumbing { + solana_sdk::declare_id!("6Uf8S75PVh91MYgPQSHnjRAPQq6an5BDv9vomrCwDqLe"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -930,6 +934,7 @@ lazy_static! { (index_erasure_conflict_duplicate_proofs::id(), "generate duplicate proofs for index and erasure conflicts #34360"), (curve25519_restrict_msm_length::id(), "restrict curve25519 multiscalar multiplication vector lengths #34763"), (disable_bpf_loader_instructions::id(), "disable bpf loader management instructions #34194"), + (deprecate_unused_legacy_vote_plumbing::id(), "Deprecate unused legacy vote tx plumbing"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()