Add Bank support for "upgrade epochs" where all non-vote transactions will be rejected (bp #11082) (#11110)
* Add Bank support for "upgrade epochs" where all non-vote transactions will be rejected
(cherry picked from commit e5d8c4383f)
# Conflicts:
#	runtime/src/bank.rs
* Fix merge conflict
Co-authored-by: Michael Vines <mvines@gmail.com>
			
			
This commit is contained in:
		@@ -74,6 +74,7 @@ pub struct ErrorCounters {
 | 
			
		||||
    pub invalid_account_for_fee: usize,
 | 
			
		||||
    pub invalid_account_index: usize,
 | 
			
		||||
    pub invalid_program_for_execution: usize,
 | 
			
		||||
    pub not_allowed_during_cluster_maintenance: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug, PartialEq, Clone)]
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ use solana_sdk::{
 | 
			
		||||
    incinerator,
 | 
			
		||||
    inflation::Inflation,
 | 
			
		||||
    native_loader, nonce,
 | 
			
		||||
    program_utils::limited_deserialize,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
    signature::{Keypair, Signature},
 | 
			
		||||
    slot_hashes::SlotHashes,
 | 
			
		||||
@@ -56,7 +57,7 @@ use solana_sdk::{
 | 
			
		||||
    transaction::{Result, Transaction, TransactionError},
 | 
			
		||||
};
 | 
			
		||||
use solana_stake_program::stake_state::{self, Delegation};
 | 
			
		||||
use solana_vote_program::vote_state::VoteState;
 | 
			
		||||
use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::VoteState};
 | 
			
		||||
use std::{
 | 
			
		||||
    cell::RefCell,
 | 
			
		||||
    collections::{HashMap, HashSet},
 | 
			
		||||
@@ -1190,6 +1191,43 @@ impl Bank {
 | 
			
		||||
            })
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
    fn filter_by_vote_transactions(
 | 
			
		||||
        &self,
 | 
			
		||||
        txs: &[Transaction],
 | 
			
		||||
        iteration_order: Option<&[usize]>,
 | 
			
		||||
        lock_results: Vec<TransactionProcessResult>,
 | 
			
		||||
        error_counters: &mut ErrorCounters,
 | 
			
		||||
    ) -> Vec<TransactionProcessResult> {
 | 
			
		||||
        OrderedIterator::new(txs, iteration_order)
 | 
			
		||||
            .zip(lock_results.into_iter())
 | 
			
		||||
            .map(|(tx, lock_res)| {
 | 
			
		||||
                if lock_res.0.is_ok() {
 | 
			
		||||
                    if tx.message.instructions.len() == 1 {
 | 
			
		||||
                        let instruction = &tx.message.instructions[0];
 | 
			
		||||
                        let program_pubkey =
 | 
			
		||||
                            tx.message.account_keys[instruction.program_id_index as usize];
 | 
			
		||||
                        if program_pubkey == solana_vote_program::id() {
 | 
			
		||||
                            if let Ok(vote_instruction) =
 | 
			
		||||
                                limited_deserialize::<VoteInstruction>(&instruction.data)
 | 
			
		||||
                            {
 | 
			
		||||
                                match vote_instruction {
 | 
			
		||||
                                    VoteInstruction::Vote(_)
 | 
			
		||||
                                    | VoteInstruction::VoteSwitch(_, _) => {
 | 
			
		||||
                                        return lock_res;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    _ => {}
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    error_counters.not_allowed_during_cluster_maintenance += 1;
 | 
			
		||||
                    return (Err(TransactionError::ClusterMaintenance), lock_res.1);
 | 
			
		||||
                }
 | 
			
		||||
                lock_res
 | 
			
		||||
            })
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn check_hash_age(&self, hash: &Hash, max_age: usize) -> Option<bool> {
 | 
			
		||||
        self.blockhash_queue
 | 
			
		||||
@@ -1210,6 +1248,18 @@ impl Bank {
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Determine if the bank is currently in an upgrade epoch, where only votes are permitted
 | 
			
		||||
    fn upgrade_epoch(&self) -> bool {
 | 
			
		||||
        match self.operating_mode() {
 | 
			
		||||
            #[cfg(test)]
 | 
			
		||||
            OperatingMode::Development => self.epoch == 0xdead, // Value assumed by `test_upgrade_epoch()`
 | 
			
		||||
            #[cfg(not(test))]
 | 
			
		||||
            OperatingMode::Development => false,
 | 
			
		||||
            OperatingMode::Preview => false,
 | 
			
		||||
            OperatingMode::Stable => self.epoch == Epoch::max_value(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn check_transactions(
 | 
			
		||||
        &self,
 | 
			
		||||
        txs: &[Transaction],
 | 
			
		||||
@@ -1225,7 +1275,19 @@ impl Bank {
 | 
			
		||||
            max_age,
 | 
			
		||||
            &mut error_counters,
 | 
			
		||||
        );
 | 
			
		||||
        self.check_signatures(txs, iteration_order, age_results, &mut error_counters)
 | 
			
		||||
        let sigcheck_results =
 | 
			
		||||
            self.check_signatures(txs, iteration_order, age_results, &mut error_counters);
 | 
			
		||||
        if self.upgrade_epoch() {
 | 
			
		||||
            // Reject all non-vote transactions
 | 
			
		||||
            self.filter_by_vote_transactions(
 | 
			
		||||
                txs,
 | 
			
		||||
                iteration_order,
 | 
			
		||||
                sigcheck_results,
 | 
			
		||||
                &mut error_counters,
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            sigcheck_results
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn collect_balances(&self, batch: &[Transaction]) -> TransactionBalances {
 | 
			
		||||
@@ -1308,6 +1370,12 @@ impl Bank {
 | 
			
		||||
                error_counters.duplicate_signature
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        if 0 != error_counters.not_allowed_during_cluster_maintenance {
 | 
			
		||||
            inc_new_counter_error!(
 | 
			
		||||
                "bank-process_transactions-error-cluster-maintenance",
 | 
			
		||||
                error_counters.not_allowed_during_cluster_maintenance
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Converts Accounts into RefCell<Account>, this involves moving
 | 
			
		||||
@@ -7495,4 +7563,96 @@ mod tests {
 | 
			
		||||
        consumed_budgets.sort();
 | 
			
		||||
        assert_eq!(consumed_budgets, vec![0, 1, 8]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_upgrade_epoch() {
 | 
			
		||||
        let GenesisConfigInfo {
 | 
			
		||||
            mut genesis_config,
 | 
			
		||||
            mint_keypair,
 | 
			
		||||
            ..
 | 
			
		||||
        } = create_genesis_config_with_leader(500, &Pubkey::new_rand(), 0);
 | 
			
		||||
        genesis_config.fee_rate_governor = FeeRateGovernor::new(1, 0);
 | 
			
		||||
        let bank = Arc::new(Bank::new(&genesis_config));
 | 
			
		||||
 | 
			
		||||
        // Jump to the test-only upgrade epoch -- see `Bank::upgrade_epoch()`
 | 
			
		||||
        let bank = Bank::new_from_parent(
 | 
			
		||||
            &bank,
 | 
			
		||||
            &Pubkey::default(),
 | 
			
		||||
            genesis_config
 | 
			
		||||
                .epoch_schedule
 | 
			
		||||
                .get_first_slot_in_epoch(0xdead),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 500);
 | 
			
		||||
 | 
			
		||||
        // Normal transfers are not allowed
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bank.transfer(2, &mint_keypair, &mint_keypair.pubkey()),
 | 
			
		||||
            Err(TransactionError::ClusterMaintenance)
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 500); // no transaction fee charged
 | 
			
		||||
 | 
			
		||||
        let vote_pubkey = Pubkey::new_rand();
 | 
			
		||||
        let authorized_voter = Keypair::new();
 | 
			
		||||
 | 
			
		||||
        // VoteInstruction::Vote is allowed.  The transaction fails with a vote program instruction
 | 
			
		||||
        // error because the vote account is not actually setup
 | 
			
		||||
        let tx = Transaction::new_signed_with_payer(
 | 
			
		||||
            &[vote_instruction::vote(
 | 
			
		||||
                &vote_pubkey,
 | 
			
		||||
                &authorized_voter.pubkey(),
 | 
			
		||||
                Vote::new(vec![1], Hash::default()),
 | 
			
		||||
            )],
 | 
			
		||||
            Some(&mint_keypair.pubkey()),
 | 
			
		||||
            &[&mint_keypair, &authorized_voter],
 | 
			
		||||
            bank.last_blockhash(),
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bank.process_transaction(&tx),
 | 
			
		||||
            Err(TransactionError::InstructionError(
 | 
			
		||||
                0,
 | 
			
		||||
                InstructionError::InvalidAccountData
 | 
			
		||||
            ))
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 498); // transaction fee charged
 | 
			
		||||
 | 
			
		||||
        // VoteInstruction::VoteSwitch is allowed.  The transaction fails with a vote program
 | 
			
		||||
        // instruction error because the vote account is not actually setup
 | 
			
		||||
        let tx = Transaction::new_signed_with_payer(
 | 
			
		||||
            &[vote_instruction::vote_switch(
 | 
			
		||||
                &vote_pubkey,
 | 
			
		||||
                &authorized_voter.pubkey(),
 | 
			
		||||
                Vote::new(vec![1], Hash::default()),
 | 
			
		||||
                Hash::default(),
 | 
			
		||||
            )],
 | 
			
		||||
            Some(&mint_keypair.pubkey()),
 | 
			
		||||
            &[&mint_keypair, &authorized_voter],
 | 
			
		||||
            bank.last_blockhash(),
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bank.process_transaction(&tx),
 | 
			
		||||
            Err(TransactionError::InstructionError(
 | 
			
		||||
                0,
 | 
			
		||||
                InstructionError::InvalidAccountData
 | 
			
		||||
            ))
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 496); // transaction fee charged
 | 
			
		||||
 | 
			
		||||
        // Other vote program instructions, like VoteInstruction::UpdateCommission are not allowed
 | 
			
		||||
        let tx = Transaction::new_signed_with_payer(
 | 
			
		||||
            &[vote_instruction::update_commission(
 | 
			
		||||
                &vote_pubkey,
 | 
			
		||||
                &authorized_voter.pubkey(),
 | 
			
		||||
                123,
 | 
			
		||||
            )],
 | 
			
		||||
            Some(&mint_keypair.pubkey()),
 | 
			
		||||
            &[&mint_keypair, &authorized_voter],
 | 
			
		||||
            bank.last_blockhash(),
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            bank.process_transaction(&tx),
 | 
			
		||||
            Err(TransactionError::ClusterMaintenance)
 | 
			
		||||
        );
 | 
			
		||||
        assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 496); // no transaction fee charged
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -68,6 +68,9 @@ pub enum TransactionError {
 | 
			
		||||
    /// implies that account locks are not taken for this TX, and should
 | 
			
		||||
    /// not be unlocked.
 | 
			
		||||
    SanitizeFailure,
 | 
			
		||||
 | 
			
		||||
    /// Transactions are currently disabled due to cluster maintenance
 | 
			
		||||
    ClusterMaintenance,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type Result<T> = result::Result<T, TransactionError>;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user