Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion ledger-tool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2052,7 +2052,15 @@ fn main() {
feature_account_balance,
),
);
base_bank.store_account(
&feature_set::rewrite_stake::id(),
&feature::create_account(
&Feature { activated_at: None },
feature_account_balance,
),
);

let mut store_failed_count = 0;
if base_bank
.get_account(&feature_set::secp256k1_program_enabled::id())
.is_some()
Expand All @@ -2064,6 +2072,21 @@ fn main() {
&Account::default(),
);
} else {
store_failed_count += 1;
}

if base_bank
.get_account(&feature_set::instructions_sysvar_enabled::id())
.is_some()
{
base_bank.store_account(
&feature_set::instructions_sysvar_enabled::id(),
&Account::default(),
);
} else {
store_failed_count += 1;
}
if store_failed_count >= 1 {
// we have no choice; maybe locally created blank cluster with
// not-Development cluster type.
let old_cap = base_bank.set_capitalization();
Expand All @@ -2073,7 +2096,10 @@ fn main() {
requested: increasing {} from {} to {}",
feature_account_balance, old_cap, new_cap,
);
assert_eq!(old_cap + feature_account_balance, new_cap);
assert_eq!(
old_cap + feature_account_balance * store_failed_count,
new_cap
);
}
}

Expand Down
204 changes: 204 additions & 0 deletions programs/stake/src/stake_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,24 @@ impl Meta {
}
Ok(())
}

pub fn rewrite_rent_exempt_reserve(
&mut self,
rent: &Rent,
data_len: usize,
) -> Option<(u64, u64)> {
let corrected_rent_exempt_reserve = rent.minimum_balance(data_len);
if corrected_rent_exempt_reserve != self.rent_exempt_reserve {
// We forcibly update rent_excempt_reserve even
// if rent_exempt_reserve > account_balance, hoping user might restore
// rent_exempt status by depositing.
let (old, new) = (self.rent_exempt_reserve, corrected_rent_exempt_reserve);
self.rent_exempt_reserve = corrected_rent_exempt_reserve;
Some((old, new))
} else {
None
}
}
}

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
Expand Down Expand Up @@ -392,6 +410,27 @@ impl Delegation {
(self.stake, 0)
}
}

fn rewrite_stake(
&mut self,
account_balance: u64,
rent_exempt_balance: u64,
) -> Option<(u64, u64)> {
// note that this will intentionally overwrite innocent
// deactivated-then-immeditealy-withdrawn stake accounts as well
// this is chosen to minimize the risks from complicated logic,
// over some unneeded rewrites
let corrected_stake = account_balance.saturating_sub(rent_exempt_balance);
if self.stake != corrected_stake {
// this could result in creating a 0-staked account;
// rewards and staking calc can handle it.
let (old, new) = (self.stake, corrected_stake);
self.stake = corrected_stake;
Some((old, new))
} else {
None
}
}
}

#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
Expand Down Expand Up @@ -1186,6 +1225,44 @@ fn calculate_split_rent_exempt_reserve(
lamports_per_byte_year * (split_data_len + ACCOUNT_STORAGE_OVERHEAD)
}

pub type RewriteStakeStatus = (&'static str, (u64, u64), (u64, u64));

pub fn rewrite_stakes(
stake_account: &mut Account,
rent: &Rent,
) -> Result<RewriteStakeStatus, InstructionError> {
match stake_account.state()? {
StakeState::Initialized(mut meta) => {
let meta_status = meta.rewrite_rent_exempt_reserve(rent, stake_account.data.len());

if meta_status.is_none() {
return Err(InstructionError::InvalidAccountData);
}

stake_account.set_state(&StakeState::Initialized(meta))?;
Ok(("initialized", meta_status.unwrap_or_default(), (0, 0)))
}
StakeState::Stake(mut meta, mut stake) => {
let meta_status = meta.rewrite_rent_exempt_reserve(rent, stake_account.data.len());
let stake_status = stake
.delegation
.rewrite_stake(stake_account.lamports, meta.rent_exempt_reserve);

if meta_status.is_none() && stake_status.is_none() {
return Err(InstructionError::InvalidAccountData);
}

stake_account.set_state(&StakeState::Stake(meta, stake))?;
Ok((
"stake",
meta_status.unwrap_or_default(),
stake_status.unwrap_or_default(),
))
}
_ => Err(InstructionError::InvalidAccountData),
}
}

// utility function, used by runtime::Stakes, tests
pub fn new_stake_history_entry<'a, I>(
epoch: Epoch,
Expand Down Expand Up @@ -4868,6 +4945,133 @@ mod tests {
);
}

#[test]
fn test_meta_rewrite_rent_exempt_reserve() {
let right_data_len = std::mem::size_of::<StakeState>() as u64;
let rent = Rent::default();
let expected_rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);

let test_cases = [
(
right_data_len + 100,
Some((
rent.minimum_balance(right_data_len as usize + 100),
expected_rent_exempt_reserve,
)),
), // large data_len, too small rent exempt
(right_data_len, None), // correct
(
right_data_len - 100,
Some((
rent.minimum_balance(right_data_len as usize - 100),
expected_rent_exempt_reserve,
)),
), // small data_len, too large rent exempt
];
for (data_len, expected_rewrite) in &test_cases {
let rent_exempt_reserve = rent.minimum_balance(*data_len as usize);
let mut meta = Meta {
rent_exempt_reserve,
..Meta::default()
};
let actual_rewrite = meta.rewrite_rent_exempt_reserve(&rent, right_data_len as usize);
assert_eq!(actual_rewrite, *expected_rewrite);
assert_eq!(meta.rent_exempt_reserve, expected_rent_exempt_reserve);
}
}

#[test]
fn test_stake_rewrite_stake() {
let right_data_len = std::mem::size_of::<StakeState>() as u64;
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);
let expected_stake = 1000;
let account_balance = rent_exempt_reserve + expected_stake;

let test_cases = [
(9999, Some((9999, expected_stake))), // large stake
(1000, None), // correct
(42, Some((42, expected_stake))), // small stake
];
for (staked_amount, expected_rewrite) in &test_cases {
let mut delegation = Delegation {
stake: *staked_amount,
..Delegation::default()
};
let actual_rewrite = delegation.rewrite_stake(account_balance, rent_exempt_reserve);
assert_eq!(actual_rewrite, *expected_rewrite);
assert_eq!(delegation.stake, expected_stake);
}
}

enum ExpectedRewriteResult {
NotRewritten,
Rewritten,
}

#[test]
fn test_rewrite_stakes_initialized() {
let right_data_len = std::mem::size_of::<StakeState>();
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);
let expected_stake = 1000;
let account_balance = rent_exempt_reserve + expected_stake;

let test_cases = [
(1, ExpectedRewriteResult::Rewritten),
(0, ExpectedRewriteResult::NotRewritten),
];
for (offset, expected_rewrite) in &test_cases {
let meta = Meta {
rent_exempt_reserve: rent_exempt_reserve + offset,
..Meta::default()
};
let mut account = Account::new(account_balance, right_data_len, &id());
account.set_state(&StakeState::Initialized(meta)).unwrap();
let result = rewrite_stakes(&mut account, &rent);
match expected_rewrite {
ExpectedRewriteResult::NotRewritten => assert!(result.is_err()),
ExpectedRewriteResult::Rewritten => assert!(result.is_ok()),
}
}
}

#[test]
fn test_rewrite_stakes_stake() {
let right_data_len = std::mem::size_of::<StakeState>();
let rent = Rent::default();
let rent_exempt_reserve = rent.minimum_balance(right_data_len as usize);
let expected_stake = 1000;
let account_balance = rent_exempt_reserve + expected_stake;

let test_cases = [
(1, 9999, ExpectedRewriteResult::Rewritten), // bad meta, bad stake
(1, 1000, ExpectedRewriteResult::Rewritten), // bad meta, good stake
(0, 9999, ExpectedRewriteResult::Rewritten), // good meta, bad stake
(0, 1000, ExpectedRewriteResult::NotRewritten), // good meta, good stake
];
for (offset, staked_amount, expected_rewrite) in &test_cases {
let meta = Meta {
rent_exempt_reserve: rent_exempt_reserve + offset,
..Meta::default()
};
let stake = Stake {
delegation: (Delegation {
stake: *staked_amount,
..Delegation::default()
}),
..Stake::default()
};
let mut account = Account::new(account_balance, right_data_len, &id());
account.set_state(&StakeState::Stake(meta, stake)).unwrap();
let result = rewrite_stakes(&mut account, &rent);
match expected_rewrite {
ExpectedRewriteResult::NotRewritten => assert!(result.is_err()),
ExpectedRewriteResult::Rewritten => assert!(result.is_ok()),
}
}
}

#[test]
fn test_calculate_lamports_per_byte_year() {
let rent = Rent::default();
Expand Down
Loading