Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

accounts: Don't collect rent on newly created accounts #26851

Merged
merged 1 commit into from
Aug 2, 2022

Conversation

joncinque
Copy link
Contributor

Problem

This is a doozy for a one-line change.

With #26479 activated, the rent epoch of accounts is no longer updated. If you start a new cluster (or test validator) with this enabled, there may be some accounts with rent_epoch = 0.

When one of those accounts is closed, the runtime thinks it's a new account since its rent epoch is 0, tries to collect rent on it, and cleans up the account in the process.

This is normally OK, but if the account in question is a stake account, the stakes cache will go out of sync and eventually crash the bank during epoch rollover on this assertion:

assert_eq!(

Summary of Changes

Very simply, don't collect rent on newly created accounts if the feature flag from #26479 is enabled. Since newly created accounts cannot be rent-paying, this rent collection never happened anyway. The rent collector code can be cleaned up once the feature is removed.

Fixes #

Copy link
Contributor

@CriesofCarrots CriesofCarrots left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks right to me. Might not hurt to have @jeffwashington eyeball this as well

@jeffwashington
Copy link
Contributor

solana/runtime/src/accounts.rs

Lines 1303 to 1307 in 9ef684f

loaded_transaction.rent_debits.insert(
address,
rent,
account.lamports(),
);

I don't understand the ramifications. Is it this line that is the problem? rent should be 0.

@joncinque
Copy link
Contributor Author

@jeffwashington The issue is actually here

let account = std::mem::take(account);
-- we collect rent on any newly created accounts, and delete any of them that don't have enough lamports for rent. Since accounts created at epoch 0 never have their rent epoch advanced, when closing these accounts, they'll get re-initialized too early.

@jeffwashington
Copy link
Contributor

when closing these accounts, they'll get re-initialized too early.

Closing means setting lamports to 0?
How would we be re-initializing them (setting AccountSharedData to default?) 'too' early?

@joncinque
Copy link
Contributor Author

Closing means setting lamports to 0?

Correct

How would we be re-initializing them (setting AccountSharedData to default?) 'too' early?

The account is re-initialized before updating the stakes cache. If it's a stake account, we lose any delegation information, causing the stakes cache to go out of sync.

Comment on lines +1292 to +1293
if !preserve_rent_epoch_for_rent_exempt_accounts
&& account.rent_epoch() == INITIAL_RENT_EPOCH
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do we still need to take this branch?

We no longer create rent-paying accounts; so in what scenario do we still need to take this branch and collect rent on a new account?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is still taken when an account with rent epoch 0 is referenced in a transaction. On every transaction, there's a rent collection that takes place for new accounts, and we check if an account is new based on rent_epoch == 0.

However, if an account has been closed (emptied of lamports), we still try to do this rent collection afterwards, and since that account now owes rent, the runtime reinitializes the account to Account:;default(). This is mostly OK, but for stake accounts, this means that the cache falls out of sync, since the cache update happens after the reinitialization.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On every transaction, there's a rent collection that takes place for new accounts,

ok, my question is why do we still need this rent collection for new accounts if we no longer allow one to create new rent-paying accounts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason that I can see, it was simply just never removed. Do you prefer just removing this whole branch rather than tying it to your feature?

Copy link
Contributor

@behzadnouri behzadnouri Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, if that is the case that the branch is never taken anyways, then lets remove it entirely.
cc @CriesofCarrots to confirm that the branch is redundant.

Copy link
Contributor

@CriesofCarrots CriesofCarrots Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I have confirmed that when we reach this line, all transactions that leave any account in a newly rent-paying state will:
a. have execution_status.is_err() (TransactionError::InvalidRentPayingAccount or TransactionError::InsufficientFundsForRent)
b. not feature a rent-paying nonce account, because SystemProgram enforces this
c. not feature a rent-paying fee_payer account, because such a transaction will have result TransactionExecutionResult::NotExecuted and not reach this line

Thus any tx with a rent-paying account will not follow this branch.

Transactions that create rent-exempt accounts still take the branch, and call collect_from_created_account which includes

account.set_rent_epoch(self.epoch);

So I think the feature gating in this PR is still necessary in order to ensure consistency of the rent_epoch field on account creation when nodes update to the change set.

Handling of RentPaying cases inside collect_from_created_account (ie calling collect_from_existing_account) should not be necessary at all, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for looking into all this! And great catch on setting the rent epoch. @behzadnouri are you ok with using your existing feature flag? Since we're no longer advancing rent epoch with your change, it makes sense to also not set rent epoch at creation with the same feature, but I can be convinced otherwise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're no longer advancing rent epoch with your change, it makes sense to also not set rent epoch at creation with the same feature, but I can be convinced otherwise.

This sounds correct to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we can remove the block entirely on feature cleanup :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joncinque yeah, sounds good.

@joncinque
Copy link
Contributor Author

@jeffwashington do this get your explicit approval too? I want to be sure before merging.

@jeffwashington
Copy link
Contributor

jeffwashington commented Aug 2, 2022 via email

@joncinque joncinque merged commit f210182 into solana-labs:master Aug 2, 2022
@joncinque joncinque deleted the no-rent branch August 2, 2022 14:34
mergify bot pushed a commit that referenced this pull request Aug 2, 2022
@joncinque
Copy link
Contributor Author

Thank you for all the gracious reviews!

mergify bot added a commit that referenced this pull request Aug 3, 2022
…) (#26884)

* accounts: Don't collect rent on newly created accounts (#26851)

(cherry picked from commit f210182)

* Disable new feature in test for backwards compatibility

Co-authored-by: Jon Cinque <[email protected]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants