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 c3917085f4f691..ba84fa9bc4790e 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -612,6 +612,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 { @@ -621,7 +624,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 @@ -734,11 +741,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(()) } @@ -748,6 +763,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); @@ -769,6 +786,8 @@ pub fn process_vote( slot_hashes, epoch, current_slot, + timely_vote_credits, + deprecate_unused_legacy_vote_plumbing, ) } @@ -785,6 +804,8 @@ pub fn process_vote_unchecked(vote_state: &mut VoteState, vote: Vote) -> Result< &slot_hashes, vote_state.current_epoch(), 0, + true, + true, ) } @@ -1067,7 +1088,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() @@ -1250,7 +1282,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()), @@ -1732,11 +1764,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)); @@ -1749,12 +1781,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)); @@ -1814,7 +1846,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!( @@ -1830,7 +1862,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(()) ); @@ -1849,7 +1881,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(()) ); @@ -1866,7 +1898,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) ); } @@ -2163,7 +2195,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(()) ); @@ -3055,7 +3089,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) ); @@ -3069,7 +3103,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 @@ -3098,8 +3132,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 8853d5de6da143..1bb8c7dc88d91c 100644 --- a/sdk/program/src/vote/state/mod.rs +++ b/sdk/program/src/vote/state/mod.rs @@ -514,6 +514,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 @@ -526,13 +528,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()); @@ -577,7 +587,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) @@ -585,7 +600,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 8536282cee8efe..bb7c50f460fd81 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -780,6 +780,10 @@ pub mod remove_rounding_in_fee_calculation { solana_sdk::declare_id!("BtVN7YjDzNE6Dk7kTT7YTDgMNUZTNgiSJgsdzAeTg2jF"); } +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 = [ @@ -970,6 +974,7 @@ lazy_static! { (enable_gossip_duplicate_proof_ingestion::id(), "enable gossip duplicate proof ingestion #32963"), (enable_chained_merkle_shreds::id(), "Enable chained Merkle shreds #34916"), (remove_rounding_in_fee_calculation::id(), "Removing unwanted rounding in fee calculation #34982"), + (deprecate_unused_legacy_vote_plumbing::id(), "Deprecate unused legacy vote tx plumbing"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()