Add Bank support for "upgrade epochs" where all non-vote transactions will be rejected
This commit is contained in:
@ -74,6 +74,7 @@ pub struct ErrorCounters {
|
|||||||
pub invalid_account_for_fee: usize,
|
pub invalid_account_for_fee: usize,
|
||||||
pub invalid_account_index: usize,
|
pub invalid_account_index: usize,
|
||||||
pub invalid_program_for_execution: usize,
|
pub invalid_program_for_execution: usize,
|
||||||
|
pub not_allowed_during_cluster_maintenance: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, Clone)]
|
#[derive(Default, Debug, PartialEq, Clone)]
|
||||||
|
@ -45,6 +45,7 @@ use solana_sdk::{
|
|||||||
incinerator,
|
incinerator,
|
||||||
inflation::Inflation,
|
inflation::Inflation,
|
||||||
native_loader, nonce,
|
native_loader, nonce,
|
||||||
|
program_utils::limited_deserialize,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{Keypair, Signature},
|
signature::{Keypair, Signature},
|
||||||
slot_hashes::SlotHashes,
|
slot_hashes::SlotHashes,
|
||||||
@ -55,7 +56,7 @@ use solana_sdk::{
|
|||||||
transaction::{Result, Transaction, TransactionError},
|
transaction::{Result, Transaction, TransactionError},
|
||||||
};
|
};
|
||||||
use solana_stake_program::stake_state::{self, Delegation};
|
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::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
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;
|
pub const MAX_LEADER_SCHEDULE_STAKES: Epoch = 5;
|
||||||
|
|
||||||
type BankStatusCache = StatusCache<Result<()>>;
|
type BankStatusCache = StatusCache<Result<()>>;
|
||||||
#[frozen_abi(digest = "9chBcbXVJ4fK7uGgydQzam5aHipaAKFw6V4LDFpjbE4w")]
|
#[frozen_abi(digest = "BHtoJzwGJ1seQ2gZmtPSLLgdvq3gRZMj5mpUJsX4wGHT")]
|
||||||
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
pub type BankSlotDelta = SlotDelta<Result<()>>;
|
||||||
type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>;
|
type TransactionAccountRefCells = Vec<Rc<RefCell<Account>>>;
|
||||||
type TransactionLoaderRefCells = Vec<Vec<(Pubkey, RefCell<Account>)>>;
|
type TransactionLoaderRefCells = Vec<Vec<(Pubkey, RefCell<Account>)>>;
|
||||||
@ -1404,6 +1405,43 @@ impl Bank {
|
|||||||
})
|
})
|
||||||
.collect()
|
.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> {
|
pub fn check_hash_age(&self, hash: &Hash, max_age: usize) -> Option<bool> {
|
||||||
self.blockhash_queue
|
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(
|
pub fn check_transactions(
|
||||||
&self,
|
&self,
|
||||||
txs: &[Transaction],
|
txs: &[Transaction],
|
||||||
@ -1439,7 +1489,19 @@ impl Bank {
|
|||||||
max_age,
|
max_age,
|
||||||
&mut error_counters,
|
&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 {
|
pub fn collect_balances(&self, batch: &[Transaction]) -> TransactionBalances {
|
||||||
@ -1522,6 +1584,12 @@ impl Bank {
|
|||||||
error_counters.duplicate_signature
|
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
|
/// Converts Accounts into RefCell<Account>, this involves moving
|
||||||
@ -7728,4 +7796,96 @@ mod tests {
|
|||||||
consumed_budgets.sort();
|
consumed_budgets.sort();
|
||||||
assert_eq!(consumed_budgets, vec![0, 1, 8]);
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,9 @@ pub enum TransactionError {
|
|||||||
/// not be unlocked.
|
/// not be unlocked.
|
||||||
#[error("Transaction failed to sanitize accounts offsets correctly")]
|
#[error("Transaction failed to sanitize accounts offsets correctly")]
|
||||||
SanitizeFailure,
|
SanitizeFailure,
|
||||||
|
|
||||||
|
#[error("Transactions are currently disabled due to cluster maintenance")]
|
||||||
|
ClusterMaintenance,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, TransactionError>;
|
pub type Result<T> = result::Result<T, TransactionError>;
|
||||||
|
Reference in New Issue
Block a user