Deinit zero-lamport account data (bp #14242) (#14265)

* Deinitialize nonce data upon zero balance

(cherry picked from commit 3881ae10fb)

* vote: Add helper for creating current-versioned states

(cherry picked from commit 5b903318b2)

* Deinitialize vote data upon zero balance

(cherry picked from commit db5bd6ea1a)

* Deinitialize stake data upon zero balance

(cherry picked from commit 50710473a8)

Co-authored-by: Trent Nelson <trent@solana.com>
This commit is contained in:
mergify[bot]
2020-12-23 04:34:52 +00:00
committed by GitHub
parent 09a3b5001c
commit 6f2b37b015
15 changed files with 151 additions and 81 deletions

View File

@@ -1004,7 +1004,7 @@ mod tests {
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
let vote_state_credits = vote_state.credits();
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(vote_state)))
.set_state(&VoteStateVersions::new_current(vote_state))
.unwrap();
let stake_pubkey = solana_sdk::pubkey::new_rand();
@@ -1902,7 +1902,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
assert_eq!(
stake_keyed_account.delegate(
@@ -1993,7 +1993,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
stake_keyed_account
@@ -2235,7 +2235,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
@@ -2351,7 +2351,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(

View File

@@ -99,9 +99,17 @@ impl StakeState {
}
pub fn lockup(&self) -> Option<Lockup> {
self.meta().map(|meta| meta.lockup)
}
pub fn meta_from(account: &Account) -> Option<Meta> {
Self::from(account).and_then(|state: Self| state.meta())
}
pub fn meta(&self) -> Option<Meta> {
match self {
StakeState::Stake(meta, _stake) => Some(meta.lockup),
StakeState::Initialized(meta) => Some(meta.lockup),
StakeState::Stake(meta, _stake) => Some(*meta),
StakeState::Initialized(meta) => Some(*meta),
_ => None,
}
}
@@ -1054,6 +1062,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
_ => return Err(InstructionError::InvalidAccountData),
}
// Deinitialize state upon zero balance
if lamports == self.lamports()? {
self.set_state(&StakeState::Uninitialized)?;
}
split.try_account_ref_mut()?.lamports += lamports;
self.try_account_ref_mut()?.lamports -= lamports;
Ok(())
@@ -1090,6 +1103,9 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
self.set_state(&merged_state)?;
}
// Source is about to be drained, deinitialize its state
source_account.set_state(&StakeState::Uninitialized)?;
// Drain the source stake account
let lamports = source_account.lamports()?;
source_account.try_account_ref_mut()?.lamports -= lamports;
@@ -1166,6 +1182,11 @@ impl<'a> StakeAccount for KeyedAccount<'a> {
return Err(InstructionError::InsufficientFunds);
}
// Deinitialize state upon zero balance
if lamports == self.lamports()? {
self.set_state(&StakeState::Uninitialized)?;
}
self.try_account_ref_mut()?.lamports -= lamports;
to.try_account_ref_mut()?.lamports += lamports;
Ok(())
@@ -1641,7 +1662,7 @@ mod tests {
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
let vote_state_credits = vote_state.credits();
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(vote_state)))
.set_state(&VoteStateVersions::new_current(vote_state))
.unwrap();
let stake_pubkey = solana_sdk::pubkey::new_rand();
@@ -2539,7 +2560,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
assert_eq!(
stake_keyed_account.delegate(
@@ -2630,7 +2651,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
stake_keyed_account
@@ -2828,6 +2849,7 @@ mod tests {
Ok(())
);
assert_eq!(stake_account.borrow().lamports, 0);
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
// reset balance
stake_account.borrow_mut().lamports = stake_lamports;
@@ -2872,7 +2894,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
@@ -2953,6 +2975,7 @@ mod tests {
Ok(())
);
assert_eq!(stake_account.borrow().lamports, 0);
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
}
#[test]
@@ -2988,7 +3011,7 @@ mod tests {
));
let vote_keyed_account = KeyedAccount::new(&vote_pubkey, false, &vote_account);
vote_keyed_account
.set_state(&VoteStateVersions::Current(Box::new(VoteState::default())))
.set_state(&VoteStateVersions::new_current(VoteState::default()))
.unwrap();
let signers = vec![stake_pubkey].into_iter().collect();
assert_eq!(
@@ -3128,6 +3151,7 @@ mod tests {
),
Ok(())
);
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
}
#[test]
@@ -3184,6 +3208,7 @@ mod tests {
),
Ok(())
);
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
}
}
@@ -3600,6 +3625,7 @@ mod tests {
),
Ok(())
);
assert_eq!(stake_keyed_account.state(), Ok(StakeState::Uninitialized));
}
#[test]
@@ -4498,7 +4524,7 @@ mod tests {
match state {
StakeState::Initialized(_) => {
assert_eq!(Ok(*state), split_stake_keyed_account.state());
assert_eq!(Ok(*state), stake_keyed_account.state());
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
StakeState::Stake(meta, stake) => {
assert_eq!(
@@ -4514,19 +4540,7 @@ mod tests {
)),
split_stake_keyed_account.state()
);
assert_eq!(
Ok(StakeState::Stake(
*meta,
Stake {
delegation: Delegation {
stake: 0,
..stake.delegation
},
..*stake
}
)),
stake_keyed_account.state()
);
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
_ => unreachable!(),
}
@@ -4608,19 +4622,7 @@ mod tests {
)),
split_stake_keyed_account.state()
);
assert_eq!(
Ok(StakeState::Stake(
meta,
Stake {
delegation: Delegation {
stake: 0,
..stake.delegation
},
..stake
}
)),
stake_keyed_account.state()
);
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
}
}
@@ -4722,9 +4724,9 @@ mod tests {
Ok(StakeState::Initialized(expected_split_meta)),
split_stake_keyed_account.state()
);
assert_eq!(Ok(*state), stake_keyed_account.state());
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
StakeState::Stake(meta, stake) => {
StakeState::Stake(_meta, stake) => {
// Expected stake should reflect original stake amount so that extra lamports
// from the rent_exempt_reserve inequality do not magically activate
let expected_stake = stake_lamports - rent_exempt_reserve;
@@ -4748,19 +4750,7 @@ mod tests {
+ expected_rent_exempt_reserve
+ (rent_exempt_reserve - expected_rent_exempt_reserve)
);
assert_eq!(
Ok(StakeState::Stake(
*meta,
Stake {
delegation: Delegation {
stake: 0,
..stake.delegation
},
..*stake
}
)),
stake_keyed_account.state()
);
assert_eq!(Ok(StakeState::Uninitialized), stake_keyed_account.state());
}
_ => unreachable!(),
}
@@ -4836,6 +4826,44 @@ mod tests {
stake_lamports * 2
);
assert_eq!(source_stake_keyed_account.account.borrow().lamports, 0);
// check state
match state {
StakeState::Initialized(meta) => {
assert_eq!(
stake_keyed_account.state(),
Ok(StakeState::Initialized(*meta)),
);
}
StakeState::Stake(meta, stake) => {
let expected_stake = stake.delegation.stake
+ source_state
.stake()
.map(|stake| stake.delegation.stake)
.unwrap_or_else(|| {
stake_lamports
- source_state.meta().unwrap().rent_exempt_reserve
});
assert_eq!(
stake_keyed_account.state(),
Ok(StakeState::Stake(
*meta,
Stake {
delegation: Delegation {
stake: expected_stake,
..stake.delegation
},
..*stake
}
)),
);
}
_ => unreachable!(),
}
assert_eq!(
source_stake_keyed_account.state(),
Ok(StakeState::Uninitialized)
);
}
}
}
@@ -5127,7 +5155,11 @@ mod tests {
let test_source_keyed =
KeyedAccount::new(source_account.unsigned_key(), true, &test_source_account);
test_stake_keyed.merge(&test_source_keyed, clock, stake_history, signers)
let result = test_stake_keyed.merge(&test_source_keyed, clock, stake_history, signers);
if result.is_ok() {
assert_eq!(test_source_keyed.state(), Ok(StakeState::Uninitialized),);
}
result
}
// stake activation epoch, source initialized succeeds

View File

@@ -19,6 +19,7 @@ use solana_sdk::{
sysvar::clock::Clock,
};
use std::boxed::Box;
use std::cmp::Ordering;
use std::collections::{HashSet, VecDeque};
mod vote_state_0_23_5;
@@ -214,7 +215,7 @@ impl VoteState {
pub fn size_of() -> usize {
// Upper limit on the size of the Vote State. Equal to
// size_of(VoteState) when votes.len() is MAX_LOCKOUT_HISTORY
let vote_state = VoteStateVersions::Current(Box::new(Self::get_max_sized_vote_state()));
let vote_state = VoteStateVersions::new_current(Self::get_max_sized_vote_state());
serialized_size(&vote_state).unwrap() as usize
}
@@ -594,7 +595,7 @@ pub fn authorize<S: std::hash::BuildHasher>(
}
}
vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state)))
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}
/// Update the node_pubkey, requires signature of the authorized voter
@@ -614,7 +615,7 @@ pub fn update_validator_identity<S: std::hash::BuildHasher>(
vote_state.node_pubkey = *node_pubkey;
vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state)))
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}
/// Update the vote account's commission
@@ -631,7 +632,7 @@ pub fn update_commission<S: std::hash::BuildHasher>(
vote_state.commission = commission;
vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state)))
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}
fn verify_authorized_signer<S: std::hash::BuildHasher>(
@@ -657,8 +658,13 @@ pub fn withdraw<S: std::hash::BuildHasher>(
verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
if vote_account.lamports()? < lamports {
return Err(InstructionError::InsufficientFunds);
match vote_account.lamports()?.cmp(&lamports) {
Ordering::Less => return Err(InstructionError::InsufficientFunds),
Ordering::Equal => {
// Deinitialize upon zero-balance
vote_account.set_state(&VoteStateVersions::new_current(VoteState::default()))?;
}
_ => (),
}
vote_account.try_account_ref_mut()?.lamports -= lamports;
to_account.try_account_ref_mut()?.lamports += lamports;
@@ -683,9 +689,9 @@ pub fn initialize_account<S: std::hash::BuildHasher>(
// node must agree to accept this vote account
verify_authorized_signer(&vote_init.node_pubkey, signers)?;
vote_account.set_state(&VoteStateVersions::Current(Box::new(VoteState::new(
vote_account.set_state(&VoteStateVersions::new_current(VoteState::new(
vote_init, clock,
))))
)))
}
pub fn process_vote<S: std::hash::BuildHasher>(
@@ -715,7 +721,7 @@ pub fn process_vote<S: std::hash::BuildHasher>(
.ok_or_else(|| VoteError::EmptySlots)
.and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
}
vote_account.set_state(&VoteStateVersions::Current(Box::new(vote_state)))
vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}
pub fn create_account_with_authorized(
@@ -737,7 +743,7 @@ pub fn create_account_with_authorized(
&Clock::default(),
);
let versioned = VoteStateVersions::Current(Box::new(vote_state));
let versioned = VoteStateVersions::new_current(vote_state);
VoteState::to(&versioned, &mut vote_account).unwrap();
vote_account
@@ -915,11 +921,11 @@ mod tests {
vote_state
.votes
.resize(MAX_LOCKOUT_HISTORY, Lockout::default());
let versioned = VoteStateVersions::Current(Box::new(vote_state));
let versioned = VoteStateVersions::new_current(vote_state);
assert!(VoteState::serialize(&versioned, &mut buffer[0..4]).is_err());
VoteState::serialize(&versioned, &mut buffer).unwrap();
assert_eq!(
VoteStateVersions::Current(Box::new(VoteState::deserialize(&buffer).unwrap())),
VoteStateVersions::new_current(VoteState::deserialize(&buffer).unwrap()),
versioned
);
}
@@ -1626,6 +1632,7 @@ mod tests {
let lamports = vote_account.borrow().lamports;
let keyed_accounts = &[KeyedAccount::new(&vote_pubkey, true, &vote_account)];
let signers: HashSet<Pubkey> = get_signers(keyed_accounts);
let pre_state: VoteStateVersions = vote_account.borrow().state().unwrap();
let res = withdraw(
&keyed_accounts[0],
lamports,
@@ -1635,9 +1642,13 @@ mod tests {
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());
// reset balance, verify that authorized_withdrawer works
// reset balance and restore state, verify that authorized_withdrawer works
vote_account.borrow_mut().lamports = lamports;
vote_account.borrow_mut().set_state(&pre_state).unwrap();
// authorize authorized_withdrawer
let authorized_withdrawer_pubkey = solana_sdk::pubkey::new_rand();
@@ -1671,6 +1682,9 @@ mod tests {
assert_eq!(res, Ok(()));
assert_eq!(vote_account.borrow().lamports, 0);
assert_eq!(withdrawer_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());
}
#[test]
@@ -1994,9 +2008,17 @@ mod tests {
)
});
let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
let versioned = VoteStateVersions::new_current(vote_state.take().unwrap());
VoteState::serialize(&versioned, &mut max_sized_data).unwrap();
vote_state = Some(versioned.convert_to_current());
}
}
#[test]
fn test_default_vote_state_is_uninitialized() {
// The default `VoteState` is stored to de-initialize a zero-balance vote account,
// so must remain such that `VoteStateVersions::is_uninitialized()` returns true
// when called on a `VoteStateVersions` that stores it
assert!(VoteStateVersions::new_current(VoteState::default()).is_uninitialized());
}
}

View File

@@ -8,6 +8,10 @@ pub enum VoteStateVersions {
}
impl VoteStateVersions {
pub fn new_current(vote_state: VoteState) -> Self {
Self::Current(Box::new(vote_state))
}
pub fn convert_to_current(self) -> VoteState {
match self {
VoteStateVersions::V0_23_5(state) => {