gemachain_vote_program/
vote_instruction.rs

1//! Vote program
2//! Receive and processes votes from validators
3
4use crate::{
5    id,
6    vote_state::{self, Vote, VoteAuthorize, VoteInit, VoteState},
7};
8use log::*;
9use num_derive::{FromPrimitive, ToPrimitive};
10use serde_derive::{Deserialize, Serialize};
11use gemachain_metrics::inc_new_counter_info;
12use gemachain_sdk::{
13    decode_error::DecodeError,
14    feature_set,
15    hash::Hash,
16    instruction::{AccountMeta, Instruction, InstructionError},
17    keyed_account::{from_keyed_account, get_signers, keyed_account_at_index, KeyedAccount},
18    process_instruction::InvokeContext,
19    program_utils::limited_deserialize,
20    pubkey::Pubkey,
21    system_instruction,
22    sysvar::{self, clock::Clock, slot_hashes::SlotHashes},
23};
24use std::collections::HashSet;
25use thiserror::Error;
26
27/// Reasons the stake might have had an error
28#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
29pub enum VoteError {
30    #[error("vote already recorded or not in slot hashes history")]
31    VoteTooOld,
32
33    #[error("vote slots do not match bank history")]
34    SlotsMismatch,
35
36    #[error("vote hash does not match bank hash")]
37    SlotHashMismatch,
38
39    #[error("vote has no slots, invalid")]
40    EmptySlots,
41
42    #[error("vote timestamp not recent")]
43    TimestampTooOld,
44
45    #[error("authorized voter has already been changed this epoch")]
46    TooSoonToReauthorize,
47}
48
49impl<E> DecodeError<E> for VoteError {
50    fn type_of() -> &'static str {
51        "VoteError"
52    }
53}
54
55#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
56pub enum VoteInstruction {
57    /// Initialize a vote account
58    ///
59    /// # Account references
60    ///   0. `[WRITE]` Uninitialized vote account
61    ///   1. `[]` Rent sysvar
62    ///   2. `[]` Clock sysvar
63    ///   3. `[SIGNER]` New validator identity (node_pubkey)
64    InitializeAccount(VoteInit),
65
66    /// Authorize a key to send votes or issue a withdrawal
67    ///
68    /// # Account references
69    ///   0. `[WRITE]` Vote account to be updated with the Pubkey for authorization
70    ///   1. `[]` Clock sysvar
71    ///   2. `[SIGNER]` Vote or withdraw authority
72    Authorize(Pubkey, VoteAuthorize),
73
74    /// A Vote instruction with recent votes
75    ///
76    /// # Account references
77    ///   0. `[WRITE]` Vote account to vote with
78    ///   1. `[]` Slot hashes sysvar
79    ///   2. `[]` Clock sysvar
80    ///   3. `[SIGNER]` Vote authority
81    Vote(Vote),
82
83    /// Withdraw some amount of funds
84    ///
85    /// # Account references
86    ///   0. `[WRITE]` Vote account to withdraw from
87    ///   1. `[WRITE]` Recipient account
88    ///   2. `[SIGNER]` Withdraw authority
89    Withdraw(u64),
90
91    /// Update the vote account's validator identity (node_pubkey)
92    ///
93    /// # Account references
94    ///   0. `[WRITE]` Vote account to be updated with the given authority public key
95    ///   1. `[SIGNER]` New validator identity (node_pubkey)
96    ///   2. `[SIGNER]` Withdraw authority
97    UpdateValidatorIdentity,
98
99    /// Update the commission for the vote account
100    ///
101    /// # Account references
102    ///   0. `[WRITE]` Vote account to be updated
103    ///   1. `[SIGNER]` Withdraw authority
104    UpdateCommission(u8),
105
106    /// A Vote instruction with recent votes
107    ///
108    /// # Account references
109    ///   0. `[WRITE]` Vote account to vote with
110    ///   1. `[]` Slot hashes sysvar
111    ///   2. `[]` Clock sysvar
112    ///   3. `[SIGNER]` Vote authority
113    VoteSwitch(Vote, Hash),
114
115    /// Authorize a key to send votes or issue a withdrawal
116    ///
117    /// This instruction behaves like `Authorize` with the additional requirement that the new vote
118    /// or withdraw authority must also be a signer.
119    ///
120    /// # Account references
121    ///   0. `[WRITE]` Vote account to be updated with the Pubkey for authorization
122    ///   1. `[]` Clock sysvar
123    ///   2. `[SIGNER]` Vote or withdraw authority
124    ///   3. `[SIGNER]` New vote or withdraw authority
125    AuthorizeChecked(VoteAuthorize),
126}
127
128fn initialize_account(vote_pubkey: &Pubkey, vote_init: &VoteInit) -> Instruction {
129    let account_metas = vec![
130        AccountMeta::new(*vote_pubkey, false),
131        AccountMeta::new_readonly(sysvar::rent::id(), false),
132        AccountMeta::new_readonly(sysvar::clock::id(), false),
133        AccountMeta::new_readonly(vote_init.node_pubkey, true),
134    ];
135
136    Instruction::new_with_bincode(
137        id(),
138        &VoteInstruction::InitializeAccount(*vote_init),
139        account_metas,
140    )
141}
142
143pub fn create_account(
144    from_pubkey: &Pubkey,
145    vote_pubkey: &Pubkey,
146    vote_init: &VoteInit,
147    carats: u64,
148) -> Vec<Instruction> {
149    let space = VoteState::size_of() as u64;
150    let create_ix =
151        system_instruction::create_account(from_pubkey, vote_pubkey, carats, space, &id());
152    let init_ix = initialize_account(vote_pubkey, vote_init);
153    vec![create_ix, init_ix]
154}
155
156pub fn create_account_with_seed(
157    from_pubkey: &Pubkey,
158    vote_pubkey: &Pubkey,
159    base: &Pubkey,
160    seed: &str,
161    vote_init: &VoteInit,
162    carats: u64,
163) -> Vec<Instruction> {
164    let space = VoteState::size_of() as u64;
165    let create_ix = system_instruction::create_account_with_seed(
166        from_pubkey,
167        vote_pubkey,
168        base,
169        seed,
170        carats,
171        space,
172        &id(),
173    );
174    let init_ix = initialize_account(vote_pubkey, vote_init);
175    vec![create_ix, init_ix]
176}
177
178pub fn authorize(
179    vote_pubkey: &Pubkey,
180    authorized_pubkey: &Pubkey, // currently authorized
181    new_authorized_pubkey: &Pubkey,
182    vote_authorize: VoteAuthorize,
183) -> Instruction {
184    let account_metas = vec![
185        AccountMeta::new(*vote_pubkey, false),
186        AccountMeta::new_readonly(sysvar::clock::id(), false),
187        AccountMeta::new_readonly(*authorized_pubkey, true),
188    ];
189
190    Instruction::new_with_bincode(
191        id(),
192        &VoteInstruction::Authorize(*new_authorized_pubkey, vote_authorize),
193        account_metas,
194    )
195}
196
197pub fn authorize_checked(
198    vote_pubkey: &Pubkey,
199    authorized_pubkey: &Pubkey, // currently authorized
200    new_authorized_pubkey: &Pubkey,
201    vote_authorize: VoteAuthorize,
202) -> Instruction {
203    let account_metas = vec![
204        AccountMeta::new(*vote_pubkey, false),
205        AccountMeta::new_readonly(sysvar::clock::id(), false),
206        AccountMeta::new_readonly(*authorized_pubkey, true),
207        AccountMeta::new_readonly(*new_authorized_pubkey, true),
208    ];
209
210    Instruction::new_with_bincode(
211        id(),
212        &VoteInstruction::AuthorizeChecked(vote_authorize),
213        account_metas,
214    )
215}
216
217pub fn update_validator_identity(
218    vote_pubkey: &Pubkey,
219    authorized_withdrawer_pubkey: &Pubkey,
220    node_pubkey: &Pubkey,
221) -> Instruction {
222    let account_metas = vec![
223        AccountMeta::new(*vote_pubkey, false),
224        AccountMeta::new_readonly(*node_pubkey, true),
225        AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
226    ];
227
228    Instruction::new_with_bincode(
229        id(),
230        &VoteInstruction::UpdateValidatorIdentity,
231        account_metas,
232    )
233}
234
235pub fn update_commission(
236    vote_pubkey: &Pubkey,
237    authorized_withdrawer_pubkey: &Pubkey,
238    commission: u8,
239) -> Instruction {
240    let account_metas = vec![
241        AccountMeta::new(*vote_pubkey, false),
242        AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
243    ];
244
245    Instruction::new_with_bincode(
246        id(),
247        &VoteInstruction::UpdateCommission(commission),
248        account_metas,
249    )
250}
251
252pub fn vote(vote_pubkey: &Pubkey, authorized_voter_pubkey: &Pubkey, vote: Vote) -> Instruction {
253    let account_metas = vec![
254        AccountMeta::new(*vote_pubkey, false),
255        AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
256        AccountMeta::new_readonly(sysvar::clock::id(), false),
257        AccountMeta::new_readonly(*authorized_voter_pubkey, true),
258    ];
259
260    Instruction::new_with_bincode(id(), &VoteInstruction::Vote(vote), account_metas)
261}
262
263pub fn vote_switch(
264    vote_pubkey: &Pubkey,
265    authorized_voter_pubkey: &Pubkey,
266    vote: Vote,
267    proof_hash: Hash,
268) -> Instruction {
269    let account_metas = vec![
270        AccountMeta::new(*vote_pubkey, false),
271        AccountMeta::new_readonly(sysvar::slot_hashes::id(), false),
272        AccountMeta::new_readonly(sysvar::clock::id(), false),
273        AccountMeta::new_readonly(*authorized_voter_pubkey, true),
274    ];
275
276    Instruction::new_with_bincode(
277        id(),
278        &VoteInstruction::VoteSwitch(vote, proof_hash),
279        account_metas,
280    )
281}
282
283pub fn withdraw(
284    vote_pubkey: &Pubkey,
285    authorized_withdrawer_pubkey: &Pubkey,
286    carats: u64,
287    to_pubkey: &Pubkey,
288) -> Instruction {
289    let account_metas = vec![
290        AccountMeta::new(*vote_pubkey, false),
291        AccountMeta::new(*to_pubkey, false),
292        AccountMeta::new_readonly(*authorized_withdrawer_pubkey, true),
293    ];
294
295    Instruction::new_with_bincode(id(), &VoteInstruction::Withdraw(carats), account_metas)
296}
297
298fn verify_rent_exemption(
299    keyed_account: &KeyedAccount,
300    rent_sysvar_account: &KeyedAccount,
301) -> Result<(), InstructionError> {
302    let rent: sysvar::rent::Rent = from_keyed_account(rent_sysvar_account)?;
303    if !rent.is_exempt(keyed_account.carats()?, keyed_account.data_len()?) {
304        Err(InstructionError::InsufficientFunds)
305    } else {
306        Ok(())
307    }
308}
309
310pub fn process_instruction(
311    _program_id: &Pubkey,
312    data: &[u8],
313    invoke_context: &mut dyn InvokeContext,
314) -> Result<(), InstructionError> {
315    let keyed_accounts = invoke_context.get_keyed_accounts()?;
316
317    trace!("process_instruction: {:?}", data);
318    trace!("keyed_accounts: {:?}", keyed_accounts);
319
320    let signers: HashSet<Pubkey> = get_signers(keyed_accounts);
321
322    let me = &mut keyed_account_at_index(keyed_accounts, 0)?;
323
324    if me.owner()? != id() {
325        return Err(InstructionError::InvalidAccountOwner);
326    }
327
328    match limited_deserialize(data)? {
329        VoteInstruction::InitializeAccount(vote_init) => {
330            verify_rent_exemption(me, keyed_account_at_index(keyed_accounts, 1)?)?;
331            vote_state::initialize_account(
332                me,
333                &vote_init,
334                &signers,
335                &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 2)?)?,
336                invoke_context.is_feature_active(&feature_set::check_init_vote_data::id()),
337            )
338        }
339        VoteInstruction::Authorize(voter_pubkey, vote_authorize) => vote_state::authorize(
340            me,
341            &voter_pubkey,
342            vote_authorize,
343            &signers,
344            &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 1)?)?,
345        ),
346        VoteInstruction::UpdateValidatorIdentity => vote_state::update_validator_identity(
347            me,
348            keyed_account_at_index(keyed_accounts, 1)?.unsigned_key(),
349            &signers,
350        ),
351        VoteInstruction::UpdateCommission(commission) => {
352            vote_state::update_commission(me, commission, &signers)
353        }
354        VoteInstruction::Vote(vote) | VoteInstruction::VoteSwitch(vote, _) => {
355            inc_new_counter_info!("vote-native", 1);
356            vote_state::process_vote(
357                me,
358                &from_keyed_account::<SlotHashes>(keyed_account_at_index(keyed_accounts, 1)?)?,
359                &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 2)?)?,
360                &vote,
361                &signers,
362            )
363        }
364        VoteInstruction::Withdraw(carats) => {
365            let to = keyed_account_at_index(keyed_accounts, 1)?;
366            vote_state::withdraw(me, carats, to, &signers)
367        }
368        VoteInstruction::AuthorizeChecked(vote_authorize) => {
369            if invoke_context.is_feature_active(&feature_set::vote_stake_checked_instructions::id())
370            {
371                let voter_pubkey = &keyed_account_at_index(keyed_accounts, 3)?
372                    .signer_key()
373                    .ok_or(InstructionError::MissingRequiredSignature)?;
374                vote_state::authorize(
375                    me,
376                    voter_pubkey,
377                    vote_authorize,
378                    &signers,
379                    &from_keyed_account::<Clock>(keyed_account_at_index(keyed_accounts, 1)?)?,
380                )
381            } else {
382                Err(InstructionError::InvalidInstructionData)
383            }
384        }
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use bincode::serialize;
392    use gemachain_sdk::{
393        account::{self, Account, AccountSharedData},
394        process_instruction::MockInvokeContext,
395        rent::Rent,
396    };
397    use std::cell::RefCell;
398    use std::str::FromStr;
399
400    fn create_default_account() -> RefCell<AccountSharedData> {
401        RefCell::new(AccountSharedData::default())
402    }
403
404    // these are for 100% coverage in this file
405    #[test]
406    fn test_vote_process_instruction_decode_bail() {
407        assert_eq!(
408            super::process_instruction(
409                &Pubkey::default(),
410                &[],
411                &mut MockInvokeContext::new(vec![])
412            ),
413            Err(InstructionError::NotEnoughAccountKeys),
414        );
415    }
416
417    #[allow(clippy::same_item_push)]
418    fn process_instruction(instruction: &Instruction) -> Result<(), InstructionError> {
419        let mut accounts: Vec<_> = instruction
420            .accounts
421            .iter()
422            .map(|meta| {
423                RefCell::new(if sysvar::clock::check_id(&meta.pubkey) {
424                    account::create_account_shared_data_for_test(&Clock::default())
425                } else if sysvar::slot_hashes::check_id(&meta.pubkey) {
426                    account::create_account_shared_data_for_test(&SlotHashes::default())
427                } else if sysvar::rent::check_id(&meta.pubkey) {
428                    account::create_account_shared_data_for_test(&Rent::free())
429                } else if meta.pubkey == invalid_vote_state_pubkey() {
430                    AccountSharedData::from(Account {
431                        owner: invalid_vote_state_pubkey(),
432                        ..Account::default()
433                    })
434                } else {
435                    AccountSharedData::from(Account {
436                        owner: id(),
437                        ..Account::default()
438                    })
439                })
440            })
441            .collect();
442
443        for _ in 0..instruction.accounts.len() {
444            accounts.push(RefCell::new(AccountSharedData::default()));
445        }
446        {
447            let keyed_accounts: Vec<_> = instruction
448                .accounts
449                .iter()
450                .zip(accounts.iter())
451                .map(|(meta, account)| KeyedAccount::new(&meta.pubkey, meta.is_signer, account))
452                .collect();
453            super::process_instruction(
454                &Pubkey::default(),
455                &instruction.data,
456                &mut MockInvokeContext::new(keyed_accounts),
457            )
458        }
459    }
460
461    fn invalid_vote_state_pubkey() -> Pubkey {
462        Pubkey::from_str("BadVote111111111111111111111111111111111111").unwrap()
463    }
464
465    #[test]
466    fn test_spoofed_vote() {
467        assert_eq!(
468            process_instruction(&vote(
469                &invalid_vote_state_pubkey(),
470                &Pubkey::default(),
471                Vote::default(),
472            )),
473            Err(InstructionError::InvalidAccountOwner),
474        );
475    }
476
477    #[test]
478    fn test_vote_process_instruction() {
479        gemachain_logger::setup();
480        let instructions = create_account(
481            &Pubkey::default(),
482            &Pubkey::default(),
483            &VoteInit::default(),
484            100,
485        );
486        assert_eq!(
487            process_instruction(&instructions[1]),
488            Err(InstructionError::InvalidAccountData),
489        );
490        assert_eq!(
491            process_instruction(&vote(
492                &Pubkey::default(),
493                &Pubkey::default(),
494                Vote::default(),
495            )),
496            Err(InstructionError::InvalidAccountData),
497        );
498        assert_eq!(
499            process_instruction(&vote_switch(
500                &Pubkey::default(),
501                &Pubkey::default(),
502                Vote::default(),
503                Hash::default(),
504            )),
505            Err(InstructionError::InvalidAccountData),
506        );
507        assert_eq!(
508            process_instruction(&authorize(
509                &Pubkey::default(),
510                &Pubkey::default(),
511                &Pubkey::default(),
512                VoteAuthorize::Voter,
513            )),
514            Err(InstructionError::InvalidAccountData),
515        );
516        assert_eq!(
517            process_instruction(&update_validator_identity(
518                &Pubkey::default(),
519                &Pubkey::default(),
520                &Pubkey::default(),
521            )),
522            Err(InstructionError::InvalidAccountData),
523        );
524        assert_eq!(
525            process_instruction(&update_commission(
526                &Pubkey::default(),
527                &Pubkey::default(),
528                0,
529            )),
530            Err(InstructionError::InvalidAccountData),
531        );
532
533        assert_eq!(
534            process_instruction(&withdraw(
535                &Pubkey::default(),
536                &Pubkey::default(),
537                0,
538                &Pubkey::default()
539            )),
540            Err(InstructionError::InvalidAccountData),
541        );
542    }
543
544    #[test]
545    fn test_vote_authorize_checked() {
546        let vote_pubkey = Pubkey::new_unique();
547        let authorized_pubkey = Pubkey::new_unique();
548        let new_authorized_pubkey = Pubkey::new_unique();
549
550        // Test with vanilla authorize accounts
551        let mut instruction = authorize_checked(
552            &vote_pubkey,
553            &authorized_pubkey,
554            &new_authorized_pubkey,
555            VoteAuthorize::Voter,
556        );
557        instruction.accounts = instruction.accounts[0..2].to_vec();
558        assert_eq!(
559            process_instruction(&instruction),
560            Err(InstructionError::NotEnoughAccountKeys),
561        );
562
563        let mut instruction = authorize_checked(
564            &vote_pubkey,
565            &authorized_pubkey,
566            &new_authorized_pubkey,
567            VoteAuthorize::Withdrawer,
568        );
569        instruction.accounts = instruction.accounts[0..2].to_vec();
570        assert_eq!(
571            process_instruction(&instruction),
572            Err(InstructionError::NotEnoughAccountKeys),
573        );
574
575        // Test with non-signing new_authorized_pubkey
576        let mut instruction = authorize_checked(
577            &vote_pubkey,
578            &authorized_pubkey,
579            &new_authorized_pubkey,
580            VoteAuthorize::Voter,
581        );
582        instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
583        assert_eq!(
584            process_instruction(&instruction),
585            Err(InstructionError::MissingRequiredSignature),
586        );
587
588        let mut instruction = authorize_checked(
589            &vote_pubkey,
590            &authorized_pubkey,
591            &new_authorized_pubkey,
592            VoteAuthorize::Withdrawer,
593        );
594        instruction.accounts[3] = AccountMeta::new_readonly(new_authorized_pubkey, false);
595        assert_eq!(
596            process_instruction(&instruction),
597            Err(InstructionError::MissingRequiredSignature),
598        );
599
600        // Test with new_authorized_pubkey signer
601        let vote_account = AccountSharedData::new_ref(100, VoteState::size_of(), &id());
602        let clock_address = sysvar::clock::id();
603        let clock_account = RefCell::new(account::create_account_shared_data_for_test(
604            &Clock::default(),
605        ));
606        let default_authorized_pubkey = Pubkey::default();
607        let authorized_account = create_default_account();
608        let new_authorized_account = create_default_account();
609        let keyed_accounts = vec![
610            KeyedAccount::new(&vote_pubkey, false, &vote_account),
611            KeyedAccount::new(&clock_address, false, &clock_account),
612            KeyedAccount::new(&default_authorized_pubkey, true, &authorized_account),
613            KeyedAccount::new(&new_authorized_pubkey, true, &new_authorized_account),
614        ];
615        assert_eq!(
616            super::process_instruction(
617                &Pubkey::default(),
618                &serialize(&VoteInstruction::AuthorizeChecked(VoteAuthorize::Voter)).unwrap(),
619                &mut MockInvokeContext::new(keyed_accounts)
620            ),
621            Ok(())
622        );
623
624        let keyed_accounts = vec![
625            KeyedAccount::new(&vote_pubkey, false, &vote_account),
626            KeyedAccount::new(&clock_address, false, &clock_account),
627            KeyedAccount::new(&default_authorized_pubkey, true, &authorized_account),
628            KeyedAccount::new(&new_authorized_pubkey, true, &new_authorized_account),
629        ];
630        assert_eq!(
631            super::process_instruction(
632                &Pubkey::default(),
633                &serialize(&VoteInstruction::AuthorizeChecked(
634                    VoteAuthorize::Withdrawer
635                ))
636                .unwrap(),
637                &mut MockInvokeContext::new(keyed_accounts)
638            ),
639            Ok(())
640        );
641    }
642
643    #[test]
644    fn test_minimum_balance() {
645        let rent = gemachain_sdk::rent::Rent::default();
646        let minimum_balance = rent.minimum_balance(VoteState::size_of());
647        // golden, may need updating when vote_state grows
648        assert!(minimum_balance as f64 / 10f64.powf(9.0) < 0.04)
649    }
650
651    #[test]
652    fn test_custom_error_decode() {
653        use num_traits::FromPrimitive;
654        fn pretty_err<T>(err: InstructionError) -> String
655        where
656            T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
657        {
658            if let InstructionError::Custom(code) = err {
659                let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
660                format!(
661                    "{:?}: {}::{:?} - {}",
662                    err,
663                    T::type_of(),
664                    specific_error,
665                    specific_error,
666                )
667            } else {
668                "".to_string()
669            }
670        }
671        assert_eq!(
672            "Custom(0): VoteError::VoteTooOld - vote already recorded or not in slot hashes history",
673            pretty_err::<VoteError>(VoteError::VoteTooOld.into())
674        )
675    }
676}