diff --git a/programs/vote/src/vote_processor.rs b/programs/vote/src/vote_processor.rs index 73891623f7..2e84131c54 100644 --- a/programs/vote/src/vote_processor.rs +++ b/programs/vote/src/vote_processor.rs @@ -86,7 +86,14 @@ pub fn process_instruction( keyed_account_at_index(keyed_accounts, first_instruction_account + 1)?, invoke_context, )?; - vote_state::authorize(me, &voter_pubkey, vote_authorize, &signers, &clock) + vote_state::authorize( + me, + &voter_pubkey, + vote_authorize, + &signers, + &clock, + &invoke_context.feature_set, + ) } VoteInstruction::UpdateValidatorIdentity => vote_state::update_validator_identity( me, @@ -166,6 +173,7 @@ pub fn process_instruction( keyed_accounts, first_instruction_account + 1, )?)?, + &invoke_context.feature_set, ) } else { Err(InstructionError::InvalidInstructionData) diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index a613a7aa20..255c7c382b 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -10,7 +10,7 @@ use { account_utils::State, clock::{Epoch, Slot, UnixTimestamp}, epoch_schedule::MAX_LEADER_SCHEDULE_EPOCH_OFFSET, - feature_set::{filter_votes_outside_slot_hashes, FeatureSet}, + feature_set::{self, filter_votes_outside_slot_hashes, FeatureSet}, hash::Hash, instruction::InstructionError, keyed_account::KeyedAccount, @@ -921,21 +921,37 @@ pub fn authorize( vote_authorize: VoteAuthorize, signers: &HashSet, clock: &Clock, + feature_set: &FeatureSet, ) -> Result<(), InstructionError> { let mut vote_state: VoteState = State::::state(vote_account)?.convert_to_current(); - // current authorized signer must say "yay" match vote_authorize { VoteAuthorize::Voter => { + let authorized_withdrawer_signer = if feature_set + .is_active(&feature_set::vote_withdraw_authority_may_change_authorized_voter::id()) + { + verify_authorized_signer(&vote_state.authorized_withdrawer, signers).is_ok() + } else { + false + }; + vote_state.set_new_authorized_voter( authorized, clock.epoch, clock.leader_schedule_epoch + 1, - |epoch_authorized_voter| verify_authorized_signer(&epoch_authorized_voter, signers), + |epoch_authorized_voter| { + // current authorized withdrawer or authorized voter must say "yay" + if authorized_withdrawer_signer { + Ok(()) + } else { + verify_authorized_signer(&epoch_authorized_voter, signers) + } + }, )?; } VoteAuthorize::Withdrawer => { + // current authorized withdrawer must say "yay" verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?; vote_state.authorized_withdrawer = *authorized; } @@ -1558,6 +1574,7 @@ mod tests { leader_schedule_epoch: 2, ..Clock::default() }, + &FeatureSet::default(), ); assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); @@ -1573,6 +1590,7 @@ mod tests { leader_schedule_epoch: 2, ..Clock::default() }, + &FeatureSet::default(), ); assert_eq!(res, Ok(())); @@ -1588,6 +1606,7 @@ mod tests { leader_schedule_epoch: 2, ..Clock::default() }, + &FeatureSet::default(), ); assert_eq!(res, Err(VoteError::TooSoonToReauthorize.into())); @@ -1610,6 +1629,7 @@ mod tests { leader_schedule_epoch: 4, ..Clock::default() }, + &FeatureSet::default(), ); assert_eq!(res, Ok(())); @@ -1628,6 +1648,7 @@ mod tests { leader_schedule_epoch: 4, ..Clock::default() }, + &FeatureSet::default(), ); assert_eq!(res, Ok(())); @@ -1648,6 +1669,7 @@ mod tests { leader_schedule_epoch: 4, ..Clock::default() }, + &FeatureSet::default(), ); assert_eq!(res, Ok(())); @@ -1690,6 +1712,39 @@ mod tests { &FeatureSet::default(), ); assert_eq!(res, Ok(())); + + // verify authorized_withdrawer can authorize a new authorized_voter when + // `feature_set::vote_withdraw_authority_may_change_authorized_voter` is enabled + let keyed_accounts = &[ + KeyedAccount::new(&vote_pubkey, false, &vote_account), + KeyedAccount::new(&authorized_withdrawer_pubkey, true, &withdrawer_account), + ]; + let another_authorized_voter_pubkey = solana_sdk::pubkey::new_rand(); + let signers: HashSet = get_signers(keyed_accounts); + + for (feature_set, expected_res) in [ + ( + FeatureSet::default(), + Err(InstructionError::MissingRequiredSignature), + ), + (FeatureSet::all_enabled(), Ok(())), + ] + .into_iter() + { + let res = authorize( + &keyed_accounts[0], + &another_authorized_voter_pubkey, + VoteAuthorize::Voter, + &signers, + &Clock { + epoch: 4, + leader_schedule_epoch: 5, + ..Clock::default() + }, + &feature_set, + ); + assert_eq!(res, expected_res) + } } #[test] @@ -2189,6 +2244,7 @@ mod tests { VoteAuthorize::Withdrawer, &signers, &Clock::default(), + &FeatureSet::default(), ); assert_eq!(res, Ok(())); diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index 0a8ce93ae0..f5f7466e04 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -307,6 +307,10 @@ pub mod update_syscall_base_costs { solana_sdk::declare_id!("2h63t332mGCCsWK2nqqqHhN4U9ayyqhLVFvczznHDoTZ"); } +pub mod vote_withdraw_authority_may_change_authorized_voter { + solana_sdk::declare_id!("AVZS3ZsN4gi6Rkx2QUibYuSJG3S6QHib7xCYhG6vGJxU"); +} + lazy_static! { /// Map of feature identifiers to user-visible description pub static ref FEATURE_NAMES: HashMap = [ @@ -378,6 +382,7 @@ lazy_static! { (require_rent_exempt_accounts::id(), "require all new transaction accounts with data to be rent-exempt"), (filter_votes_outside_slot_hashes::id(), "filter vote slots older than the slot hashes history"), (update_syscall_base_costs::id(), "Update syscall base costs"), + (vote_withdraw_authority_may_change_authorized_voter::id(), "vote account withdraw authority may change the authorized voter #22521"), /*************** ADD NEW FEATURES HERE ***************/ ] .iter()