diff --git a/programs/vote/src/vote_instruction.rs b/programs/vote/src/vote_instruction.rs index 028f4ee9af..54559b3549 100644 --- a/programs/vote/src/vote_instruction.rs +++ b/programs/vote/src/vote_instruction.rs @@ -413,7 +413,21 @@ pub fn process_instruction( } else { None }; - vote_state::withdraw(me, lamports, to, &signers, rent_sysvar.as_deref()) + let clock_if_feature_active = if invoke_context.is_feature_active( + &feature_set::reject_vote_account_close_unless_zero_credit_epoch::id(), + ) { + Some(invoke_context.get_sysvar_cache().get_clock()?) + } else { + None + }; + vote_state::withdraw( + me, + lamports, + to, + &signers, + rent_sysvar.as_deref(), + clock_if_feature_active.as_deref(), + ) } VoteInstruction::AuthorizeChecked(vote_authorize) => { if invoke_context.is_feature_active(&feature_set::vote_stake_checked_instructions::id()) diff --git a/programs/vote/src/vote_state/mod.rs b/programs/vote/src/vote_state/mod.rs index 754888e44d..ba2d67097f 100644 --- a/programs/vote/src/vote_state/mod.rs +++ b/programs/vote/src/vote_state/mod.rs @@ -682,6 +682,7 @@ pub fn withdraw( to_account: &KeyedAccount, signers: &HashSet, rent_sysvar: Option<&Rent>, + clock: Option<&Clock>, ) -> Result<(), InstructionError> { let vote_state: VoteState = State::::state(vote_account)?.convert_to_current(); @@ -694,8 +695,23 @@ pub fn withdraw( .ok_or(InstructionError::InsufficientFunds)?; if remaining_balance == 0 { - // Deinitialize upon zero-balance - vote_account.set_state(&VoteStateVersions::new_current(VoteState::default()))?; + let reject_active_vote_account_close = clock + .zip(vote_state.epoch_credits.last()) + .map(|(clock, (last_epoch_with_credits, _, _))| { + let current_epoch = clock.epoch; + // if current_epoch - last_epoch_with_credits < 2 then the validator has received credits + // either in the current epoch or the previous epoch. If it's >= 2 then it has been at least + // one full epoch since the validator has received credits. + current_epoch.saturating_sub(*last_epoch_with_credits) < 2 + }) + .unwrap_or(false); + + if reject_active_vote_account_close { + return Err(InstructionError::ActiveVoteAccountClose); + } else { + // Deinitialize upon zero-balance + vote_account.set_state(&VoteStateVersions::new_current(VoteState::default()))?; + } } else if let Some(rent_sysvar) = rent_sysvar { let min_rent_exempt_balance = rent_sysvar.minimum_balance(vote_account.data_len()?); if remaining_balance < min_rent_exempt_balance { @@ -813,6 +829,7 @@ mod tests { keyed_account::{get_signers, keyed_account_at_index}, }, std::cell::RefCell, + std::convert::TryFrom, }; const MAX_RECENT_VOTES: usize = 16; @@ -940,6 +957,39 @@ mod tests { ) } + fn create_test_account_with_epoch_credits( + credits_to_append: &[u64], + ) -> (Pubkey, RefCell) { + let (vote_pubkey, vote_account) = create_test_account(); + let vote_account_space = vote_account.borrow().data().len(); + + let mut vote_state = VoteState::from(&*vote_account.borrow_mut()).unwrap(); + vote_state.authorized_withdrawer = vote_pubkey; + + vote_state.epoch_credits = Vec::new(); + + let mut current_epoch_credits = 0; + let mut previous_epoch_credits = 0; + for (epoch, credits) in credits_to_append.iter().enumerate() { + current_epoch_credits += credits; + vote_state.epoch_credits.push(( + u64::try_from(epoch).unwrap(), + current_epoch_credits, + previous_epoch_credits, + )); + previous_epoch_credits = current_epoch_credits; + } + + let lamports = vote_account.borrow().lamports(); + let mut vote_account_with_epoch_credits = + AccountSharedData::new(lamports, vote_account_space, &vote_pubkey); + let versioned = VoteStateVersions::new_current(vote_state); + VoteState::to(&versioned, &mut vote_account_with_epoch_credits); + let ref_vote_account_with_epoch_credits = RefCell::new(vote_account_with_epoch_credits); + + (vote_pubkey, ref_vote_account_with_epoch_credits) + } + fn simulate_process_vote( vote_pubkey: &Pubkey, vote_account: &RefCell, @@ -1663,6 +1713,13 @@ mod tests { #[test] fn test_vote_state_withdraw() { let (vote_pubkey, vote_account) = create_test_account(); + let credits_through_epoch_1: Vec = vec![2, 1]; + let credits_through_epoch_2: Vec = vec![2, 1, 3]; + + let clock_epoch_3 = &Clock { + epoch: 3, + ..Clock::default() + }; // unsigned request let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, false, &vote_account)]; @@ -1677,6 +1734,7 @@ mod tests { ), &signers, None, + None, ); assert_eq!(res, Err(InstructionError::MissingRequiredSignature)); @@ -1694,17 +1752,24 @@ mod tests { ), &signers, None, + Some(&Clock::default()), ); assert_eq!(res, Err(InstructionError::InsufficientFunds)); - // non rent exempt withdraw, before feature activation + // non rent exempt withdraw, before 7txXZZD6 feature activation + // without 0 credit epoch, before ALBk3EWd feature activation { - let (vote_pubkey, vote_account) = create_test_account(); - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let lamports = vote_account.borrow().lamports(); + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_2); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); let rent_sysvar = Rent::default(); let minimum_balance = rent_sysvar - .minimum_balance(vote_account.borrow().data().len()) + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) .max(1); assert!(minimum_balance <= lamports); let signers: HashSet = get_signers(keyed_accounts); @@ -1718,18 +1783,121 @@ mod tests { ), &signers, None, + None, ); assert_eq!(res, Ok(())); } - // non rent exempt withdraw, after feature activation + // non rent exempt withdraw, before 7txXZZD6 feature activation + // with 0 credit epoch, before ALBk3EWd feature activation { - let (vote_pubkey, vote_account) = create_test_account(); - let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; - let lamports = vote_account.borrow().lamports(); + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_1); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); let rent_sysvar = Rent::default(); let minimum_balance = rent_sysvar - .minimum_balance(vote_account.borrow().data().len()) + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) + .max(1); + assert!(minimum_balance <= lamports); + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports - minimum_balance + 1, + &KeyedAccount::new( + &solana_sdk::pubkey::new_rand(), + false, + &RefCell::new(AccountSharedData::default()), + ), + &signers, + None, + None, + ); + assert_eq!(res, Ok(())); + } + + // non rent exempt withdraw, before 7txXZZD6 feature activation + // without 0 credit epoch, after ALBk3EWd feature activation + { + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_2); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); + let rent_sysvar = Rent::default(); + let minimum_balance = rent_sysvar + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) + .max(1); + assert!(minimum_balance <= lamports); + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports - minimum_balance + 1, + &KeyedAccount::new( + &solana_sdk::pubkey::new_rand(), + false, + &RefCell::new(AccountSharedData::default()), + ), + &signers, + None, + Some(clock_epoch_3), + ); + assert_eq!(res, Ok(())); + } + + // non rent exempt withdraw, before 7txXZZD6 feature activation + // with 0 credit epoch, after ALBk3EWd activation + { + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_1); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); + let rent_sysvar = Rent::default(); + let minimum_balance = rent_sysvar + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) + .max(1); + assert!(minimum_balance <= lamports); + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports - minimum_balance + 1, + &KeyedAccount::new( + &solana_sdk::pubkey::new_rand(), + false, + &RefCell::new(AccountSharedData::default()), + ), + &signers, + None, + Some(clock_epoch_3), + ); + assert_eq!(res, Ok(())); + } + + // non rent exempt withdraw, after 7txXZZD6 feature activation + // with 0 credit epoch, before ALBk3EWd feature activation + { + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_1); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); + let rent_sysvar = Rent::default(); + let minimum_balance = rent_sysvar + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) .max(1); assert!(minimum_balance <= lamports); let signers: HashSet = get_signers(keyed_accounts); @@ -1743,11 +1911,108 @@ mod tests { ), &signers, Some(&rent_sysvar), + None, ); assert_eq!(res, Err(InstructionError::InsufficientFunds)); } - // partial valid withdraw, after feature activation + // non rent exempt withdraw, after 7txXZZD6 feature activation + // without 0 credit epoch, before ALBk3EWd feature activation + { + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_2); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); + let rent_sysvar = Rent::default(); + let minimum_balance = rent_sysvar + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) + .max(1); + assert!(minimum_balance <= lamports); + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports - minimum_balance + 1, + &KeyedAccount::new( + &solana_sdk::pubkey::new_rand(), + false, + &RefCell::new(AccountSharedData::default()), + ), + &signers, + Some(&rent_sysvar), + None, + ); + assert_eq!(res, Err(InstructionError::InsufficientFunds)); + } + + // non rent exempt withdraw, after 7txXZZD6 feature activation + // with 0 credit epoch, after ALBk3EWd feature activation + { + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_1); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); + let rent_sysvar = Rent::default(); + let minimum_balance = rent_sysvar + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) + .max(1); + assert!(minimum_balance <= lamports); + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports - minimum_balance + 1, + &KeyedAccount::new( + &solana_sdk::pubkey::new_rand(), + false, + &RefCell::new(AccountSharedData::default()), + ), + &signers, + Some(&rent_sysvar), + Some(clock_epoch_3), + ); + assert_eq!(res, Err(InstructionError::InsufficientFunds)); + } + + // non rent exempt withdraw, after 7txXZZD6 feature activation + // without 0 credit epoch, after ALBk3EWd feature activation + { + let (vote_pubkey, vote_account_with_epoch_credits) = + create_test_account_with_epoch_credits(&credits_through_epoch_2); + let keyed_accounts = &[KeyedAccount::new( + &vote_pubkey, + true, + &vote_account_with_epoch_credits, + )]; + let lamports = vote_account_with_epoch_credits.borrow().lamports(); + let rent_sysvar = Rent::default(); + let minimum_balance = rent_sysvar + .minimum_balance(vote_account_with_epoch_credits.borrow().data().len()) + .max(1); + assert!(minimum_balance <= lamports); + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports - minimum_balance + 1, + &KeyedAccount::new( + &solana_sdk::pubkey::new_rand(), + false, + &RefCell::new(AccountSharedData::default()), + ), + &signers, + Some(&rent_sysvar), + Some(clock_epoch_3), + ); + assert_eq!(res, Err(InstructionError::InsufficientFunds)); + } + + // partial valid withdraw, after 7txXZZD6 feature activation { let to_account = RefCell::new(AccountSharedData::default()); let (vote_pubkey, vote_account) = create_test_account(); @@ -1766,6 +2031,7 @@ mod tests { &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), &signers, Some(&rent_sysvar), + Some(&Clock::default()), ); assert_eq!(res, Ok(())); assert_eq!( @@ -1775,12 +2041,45 @@ mod tests { assert_eq!(to_account.borrow().lamports(), withdraw_lamports); } - // full withdraw, before/after activation + // full withdraw, before/after 7txXZZD6 feature activation + // with/without 0 credit epoch, before ALBk3EWd feature activation + { + let rent_sysvar = Rent::default(); + for rent_sysvar in &[None, Some(rent_sysvar)] { + for credits in &[&credits_through_epoch_1, &credits_through_epoch_2] { + let to_account = RefCell::new(AccountSharedData::default()); + let (vote_pubkey, vote_account) = + create_test_account_with_epoch_credits(credits); + let lamports = vote_account.borrow().lamports(); + let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports, + &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), + &signers, + rent_sysvar.as_ref(), + None, + ); + assert_eq!(res, Ok(())); + assert_eq!(vote_account.borrow().lamports(), 0); + assert_eq!(to_account.borrow().lamports(), lamports); + let post_state: VoteStateVersions = vote_account.borrow().state().unwrap(); + // State has been deinitialized since balance is zero + assert!(post_state.is_uninitialized()); + } + } + } + + // full withdraw, before/after 7txXZZD6 feature activation + // with 0 credit epoch, after ALBk3EWd feature activation { let rent_sysvar = Rent::default(); for rent_sysvar in &[None, Some(rent_sysvar)] { let to_account = RefCell::new(AccountSharedData::default()); - let (vote_pubkey, vote_account) = create_test_account(); + // let (vote_pubkey, vote_account) = create_test_account(); + let (vote_pubkey, vote_account) = + create_test_account_with_epoch_credits(&credits_through_epoch_1); let lamports = vote_account.borrow().lamports(); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; let signers: HashSet = get_signers(keyed_accounts); @@ -1790,6 +2089,7 @@ mod tests { &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), &signers, rent_sysvar.as_ref(), + Some(clock_epoch_3), ); assert_eq!(res, Ok(())); assert_eq!(vote_account.borrow().lamports(), 0); @@ -1800,6 +2100,35 @@ mod tests { } } + // full withdraw, before/after 7txXZZD6 feature activation + // without 0 credit epoch, after ALBk3EWd feature activation + { + let rent_sysvar = Rent::default(); + for rent_sysvar in &[None, Some(rent_sysvar)] { + let to_account = RefCell::new(AccountSharedData::default()); + // let (vote_pubkey, vote_account) = create_test_account(); + let (vote_pubkey, vote_account) = + create_test_account_with_epoch_credits(&credits_through_epoch_2); + let lamports = vote_account.borrow().lamports(); + let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; + let signers: HashSet = get_signers(keyed_accounts); + let res = withdraw( + &keyed_accounts[0], + lamports, + &KeyedAccount::new(&solana_sdk::pubkey::new_rand(), false, &to_account), + &signers, + rent_sysvar.as_ref(), + Some(clock_epoch_3), + ); + assert_eq!(res, Err(InstructionError::ActiveVoteAccountClose)); + assert_eq!(vote_account.borrow().lamports(), lamports); + assert_eq!(to_account.borrow().lamports(), 0); + let post_state: VoteStateVersions = vote_account.borrow().state().unwrap(); + // State is still initialized + assert!(!post_state.is_uninitialized()); + } + } + // authorize authorized_withdrawer let authorized_withdrawer_pubkey = solana_sdk::pubkey::new_rand(); let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)]; @@ -1828,6 +2157,7 @@ mod tests { withdrawer_keyed_account, &signers, None, + None, ); assert_eq!(res, Ok(())); assert_eq!(vote_account.borrow().lamports(), 0); diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 55561087b6..16a7c00ce5 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -200,7 +200,7 @@ impl RentDebits { } type BankStatusCache = StatusCache>; -#[frozen_abi(digest = "5Br3PNyyX1L7XoS4jYLt5JTeMXowLSsu7v9LhokC8vnq")] +#[frozen_abi(digest = "2gyFzcvK5sXe1NgcGWnd4ATf3pCBaS43sNVPSDdjd7Rj")] pub type BankSlotDelta = SlotDelta>; pub(crate) type TransactionAccountRefCell = (Pubkey, Rc>); type TransactionAccountRefCells = Vec; diff --git a/sdk/program/src/instruction.rs b/sdk/program/src/instruction.rs index 7161489d52..5c7c170c2b 100644 --- a/sdk/program/src/instruction.rs +++ b/sdk/program/src/instruction.rs @@ -236,7 +236,15 @@ pub enum InstructionError { /// Illegal account owner #[error("Provided owner is not allowed")] IllegalOwner, - // Note: For any new error added here an equivilent ProgramError and it's + + /// Accounts data budget exceeded + #[error("Requested account data allocation exceeded the accounts data budget")] + AccountsDataBudgetExceeded, + + /// Active vote account close + #[error("Cannot close vote account unless it stopped voting at least one full epoch ago")] + ActiveVoteAccountClose, + // Note: For any new error added here an equivalent ProgramError and its // conversions must also be added } diff --git a/sdk/program/src/program_error.rs b/sdk/program/src/program_error.rs index cc0f18f186..0f8e02dab1 100644 --- a/sdk/program/src/program_error.rs +++ b/sdk/program/src/program_error.rs @@ -49,6 +49,10 @@ pub enum ProgramError { UnsupportedSysvar, #[error("Provided owner is not allowed")] IllegalOwner, + #[error("Requested account data allocation exceeded the accounts data budget")] + AccountsDataBudgetExceeded, + #[error("Cannot close vote account unless it stopped voting at least one full epoch ago")] + ActiveVoteAccountClose, } pub trait PrintProgramError { @@ -87,6 +91,8 @@ impl PrintProgramError for ProgramError { Self::AccountNotRentExempt => msg!("Error: AccountNotRentExempt"), Self::UnsupportedSysvar => msg!("Error: UnsupportedSysvar"), Self::IllegalOwner => msg!("Error: IllegalOwner"), + Self::AccountsDataBudgetExceeded => msg!("Error: AccountsDataBudgetExceeded"), + Self::ActiveVoteAccountClose => msg!("Error: ActiveVoteAccountClose"), } } } @@ -117,6 +123,8 @@ pub const BORSH_IO_ERROR: u64 = to_builtin!(15); pub const ACCOUNT_NOT_RENT_EXEMPT: u64 = to_builtin!(16); pub const UNSUPPORTED_SYSVAR: u64 = to_builtin!(17); pub const ILLEGAL_OWNER: u64 = to_builtin!(18); +pub const ACCOUNTS_DATA_BUDGET_EXCEEDED: u64 = to_builtin!(19); +pub const ACTIVE_VOTE_ACCOUNT_CLOSE: u64 = to_builtin!(20); // Warning: Any new program errors added here must also be: // - Added to the below conversions // - Added as an equivilent to InstructionError @@ -143,6 +151,8 @@ impl From for u64 { ProgramError::AccountNotRentExempt => ACCOUNT_NOT_RENT_EXEMPT, ProgramError::UnsupportedSysvar => UNSUPPORTED_SYSVAR, ProgramError::IllegalOwner => ILLEGAL_OWNER, + ProgramError::AccountsDataBudgetExceeded => ACCOUNTS_DATA_BUDGET_EXCEEDED, + ProgramError::ActiveVoteAccountClose => ACTIVE_VOTE_ACCOUNT_CLOSE, ProgramError::Custom(error) => { if error == 0 { CUSTOM_ZERO @@ -175,6 +185,8 @@ impl From for ProgramError { ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt, UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar, ILLEGAL_OWNER => Self::IllegalOwner, + ACCOUNTS_DATA_BUDGET_EXCEEDED => Self::AccountsDataBudgetExceeded, + ACTIVE_VOTE_ACCOUNT_CLOSE => Self::ActiveVoteAccountClose, _ => Self::Custom(error as u32), } } @@ -203,6 +215,8 @@ impl TryFrom for ProgramError { Self::Error::AccountNotRentExempt => Ok(Self::AccountNotRentExempt), Self::Error::UnsupportedSysvar => Ok(Self::UnsupportedSysvar), Self::Error::IllegalOwner => Ok(Self::IllegalOwner), + Self::Error::AccountsDataBudgetExceeded => Ok(Self::AccountsDataBudgetExceeded), + Self::Error::ActiveVoteAccountClose => Ok(Self::ActiveVoteAccountClose), _ => Err(error), } } @@ -233,6 +247,8 @@ where ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt, UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar, ILLEGAL_OWNER => Self::IllegalOwner, + ACCOUNTS_DATA_BUDGET_EXCEEDED => Self::AccountsDataBudgetExceeded, + ACTIVE_VOTE_ACCOUNT_CLOSE => Self::ActiveVoteAccountClose, _ => { // A valid custom error has no bits set in the upper 32 if error >> BUILTIN_BIT_SHIFT == 0 { diff --git a/sdk/src/feature_set.rs b/sdk/src/feature_set.rs index b7a7bbc94e..0f4a894026 100644 --- a/sdk/src/feature_set.rs +++ b/sdk/src/feature_set.rs @@ -289,6 +289,10 @@ pub mod spl_associated_token_account_v1_0_4 { solana_sdk::declare_id!("FaTa4SpiaSNH44PGC4z8bnGVTkSRYaWvrBs3KTu8XQQq"); } +pub mod reject_vote_account_close_unless_zero_credit_epoch { + solana_sdk::declare_id!("ALBk3EWdeAg2WAGf6GPDUf1nynyNqCdEVmgouG7rpuCj"); +} + pub mod bank_tranaction_count_fix { solana_sdk::declare_id!("Vo5siZ442SaZBKPXNocthiXysNviW4UYPwRFggmbgAp"); } @@ -369,6 +373,7 @@ lazy_static! { (reject_non_rent_exempt_vote_withdraws::id(), "fail vote withdraw instructions which leave the account non-rent-exempt"), (evict_invalid_stakes_cache_entries::id(), "evict invalid stakes cache entries on epoch boundaries"), (spl_associated_token_account_v1_0_4::id(), "SPL Associated Token Account Program release version 1.0.4, tied to token 3.3.0 #22648"), + (reject_vote_account_close_unless_zero_credit_epoch::id(), "fail vote account withdraw to 0 unless account earned 0 credits in last completed epoch"), (bank_tranaction_count_fix::id(), "Fixes Bank::transaction_count to include all committed transactions, not just successful ones"), (update_syscall_base_costs::id(), "Update syscall base costs"), /*************** ADD NEW FEATURES HERE ***************/ diff --git a/storage-proto/proto/transaction_by_addr.proto b/storage-proto/proto/transaction_by_addr.proto index 0278d81d7c..34b0eb93e0 100644 --- a/storage-proto/proto/transaction_by_addr.proto +++ b/storage-proto/proto/transaction_by_addr.proto @@ -103,6 +103,8 @@ enum InstructionErrorType { ARITHMETIC_OVERFLOW = 47; UNSUPPORTED_SYSVAR = 48; ILLEGAL_OWNER = 49; + ACCOUNTS_DATA_BUDGET_EXCEEDED = 50; + ACTIVE_VOTE_ACCOUNT_CLOSE = 51; } message UnixTimestamp { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index 533993f1da..0a8a3f97e2 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -522,6 +522,8 @@ impl TryFrom for TransactionError { 47 => InstructionError::ArithmeticOverflow, 48 => InstructionError::UnsupportedSysvar, 49 => InstructionError::IllegalOwner, + 50 => InstructionError::AccountsDataBudgetExceeded, + 51 => InstructionError::ActiveVoteAccountClose, _ => return Err("Invalid InstructionError"), }; @@ -773,6 +775,12 @@ impl From for tx_by_addr::TransactionError { InstructionError::IllegalOwner => { tx_by_addr::InstructionErrorType::IllegalOwner } + InstructionError::AccountsDataBudgetExceeded => { + tx_by_addr::InstructionErrorType::AccountsDataBudgetExceeded + } + InstructionError::ActiveVoteAccountClose => { + tx_by_addr::InstructionErrorType::ActiveVoteAccountClose + } } as i32, custom: match instruction_error { InstructionError::Custom(custom) => {