Add Bank support for "upgrade epochs" where all non-vote transactions will be rejected

This commit is contained in:
Michael Vines 2020-07-15 12:50:38 -07:00
parent fdff681bcc
commit e5d8c4383f
3 changed files with 167 additions and 3 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

@ -45,6 +45,7 @@ use solana_sdk::{
incinerator,
inflation::Inflation,
native_loader, nonce,
program_utils::limited_deserialize,
pubkey::Pubkey,
signature::{Keypair, Signature},
slot_hashes::SlotHashes,
@ -55,7 +56,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},
@ -73,7 +74,7 @@ pub const SECONDS_PER_YEAR: f64 = 365.25 * 24.0 * 60.0 * 60.0;
pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
type BankStatusCache = StatusCache<Result<()>>;
#[frozen_abi(digest = "9chBcbXVJ4fK7uGgydQzam5aHipaAKFw6V4LDFpjbE4w")]
#[frozen_abi(digest = "BHtoJzwGJ1seQ2gZmtPSLLgdvq3gRZMj5mpUJsX4wGHT")]
pub type BankSlotDelta = SlotDelta<Result<()>>;
type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>;
type TransactionLoaderRefCells = Vec<Vec<(Pubkey, RefCell<Account>)>>;
@ -1404,6 +1405,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
@ -1424,6 +1462,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],
@ -1439,7 +1489,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 {
@ -1522,6 +1584,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
@ -7728,4 +7796,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

@ -83,6 +83,9 @@ pub enum TransactionError {
/// not be unlocked.
#[error("Transaction failed to sanitize accounts offsets correctly")]
SanitizeFailure,
#[error("Transactions are currently disabled due to cluster maintenance")]
ClusterMaintenance,
}
pub type Result<T> = result::Result<T, TransactionError>;