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:
mergify[bot]
2020-07-17 17:29:34 +00:00
committed by GitHub
parent 45ce1b4f96
commit dca00d1bde
3 changed files with 166 additions and 2 deletions

View File

@ -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)]

View File

@ -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
}
}

View File

@ -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>;