Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bad rent in Bank::deposit as if since epoch 0 #10468

Merged
merged 8 commits into from
Aug 11, 2020

Conversation

ryoqun
Copy link
Member

@ryoqun ryoqun commented Jun 9, 2020

Problem

When an account is created via Bank::deposit(), Bank charges too much rent on it: all the epochs since genesis if the account doesn't exist yet.... In other words, it collects rent for the number of epochs.

Also, the rent timing was wrongly delayed: #10468 (comment)

Mostly, this only affects tests. The only case which hits the production is collector_id's fee distribution because it uses Bank::deposit(). So the impact should be fairly small.

This should be a rolling update, otherwise this is a DOS vulnerability. These old/new behaviors are themselves consistent across validators.

Summary of Changes

Calculate rent for newly-funded accounts since the current epoch not epoch 0

I've changed mind: Disable rent collection entirely; for the reasoning, see the comments and document updates.

  • gating logic (ugh)
  • unit test

context

found while finishing #10099
FYI: @mvines (About rolling update).

@ryoqun ryoqun requested a review from t-nelson June 9, 2020 08:33
@@ -155,7 +155,8 @@ impl Accounts {
AccountsDB::load(storage, ancestors, accounts_index, key)
.map(|(mut account, _)| {
if message.is_writable(i) && !account.executable {
Copy link
Member Author

@ryoqun ryoqun Jun 9, 2020

Choose a reason for hiding this comment

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

Well, these inconsistent predicates are concerning me. Ideally, I want to put everything into RentCollector::update/RentCollector::from_existing_account(). But, to avoid any additional risks, I've punted it. ;)

@@ -738,8 +739,7 @@ impl Accounts {
);
if message.is_writable(i) {
if account.rent_epoch == 0 {
Copy link
Member Author

@ryoqun ryoqun Jun 9, 2020

Choose a reason for hiding this comment

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

Well, these inconsistent predicates are concerning me. Ideally, I want to put everything into RentCollector::update/RentCollector::collect_from_existing_account(). But, to avoid any additional risks, I've punted it. ;)

@@ -154,8 +154,9 @@ impl Accounts {
let (account, rent) =
AccountsDB::load(storage, ancestors, accounts_index, key)
.map(|(mut account, _)| {
if message.is_writable(i) && !account.executable {
let rent_due = rent_collector.update(&key, &mut account);
if message.is_writable(i) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I removed !account.executable because this one was fairly easy to prove it's redundant.

@codecov
Copy link

codecov bot commented Jun 9, 2020

Codecov Report

Merging #10468 into master will increase coverage by 0.0%.
The diff coverage is 92.4%.

@@           Coverage Diff           @@
##           master   #10468   +/-   ##
=======================================
  Coverage    82.4%    82.4%           
=======================================
  Files         314      314           
  Lines       73713    73765   +52     
=======================================
+ Hits        60740    60795   +55     
+ Misses      12973    12970    -3     

t-nelson
t-nelson previously approved these changes Jun 9, 2020
Copy link
Contributor

@t-nelson t-nelson left a comment

Choose a reason for hiding this comment

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

Nice catch!

These changes look good to me!

One nit, the from_ prefix felt like a conversion before rereading deeper. Maybe collect_ would read a little better

@stale
Copy link

stale bot commented Jun 16, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale [bot only] Added to stale content; results in auto-close after a week. label Jun 16, 2020
@stale
Copy link

stale bot commented Jun 23, 2020

This stale pull request has been automatically closed. Thank you for your contributions.

@stale stale bot closed this Jun 23, 2020
@ryoqun ryoqun reopened this Jun 25, 2020
@stale stale bot removed the stale [bot only] Added to stale content; results in auto-close after a week. label Jun 25, 2020
@ryoqun
Copy link
Member Author

ryoqun commented Jun 25, 2020

Now #10206 is landed, I'll work on this!!

@stale
Copy link

stale bot commented Jul 2, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale [bot only] Added to stale content; results in auto-close after a week. label Jul 2, 2020
@stale
Copy link

stale bot commented Jul 10, 2020

This stale pull request has been automatically closed. Thank you for your contributions.

@stale stale bot closed this Jul 10, 2020
@ryoqun ryoqun reopened this Jul 10, 2020
@stale stale bot removed the stale [bot only] Added to stale content; results in auto-close after a week. label Jul 10, 2020
@stale
Copy link

stale bot commented Jul 17, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale [bot only] Added to stale content; results in auto-close after a week. label Jul 17, 2020
@ryoqun ryoqun force-pushed the bad-rent-in-bank-deposit branch from d1b6b0a to 25adb08 Compare July 23, 2020 12:25
@stale stale bot removed the stale [bot only] Added to stale content; results in auto-close after a week. label Jul 23, 2020
@mergify mergify bot dismissed t-nelson’s stale review July 23, 2020 12:26

Pull request has been modified.

@ryoqun ryoqun requested a review from t-nelson July 29, 2020 15:24
@ryoqun
Copy link
Member Author

ryoqun commented Jul 29, 2020

One nit, the from_ prefix felt like a conversion before rereading deeper. Maybe collect_ would read a little better

Thanks for suggestion! I fixed that!

@t-nelson Could you review this pr? I've finished to work on this. And I think this is ready for merge. :)

Also, while writing tests, I found yet another bug, though: https://github.com/solana-labs/solana/pull/10468/files#r462385554

t-nelson
t-nelson previously approved these changes Jul 30, 2020
Copy link
Contributor

@t-nelson t-nelson left a comment

Choose a reason for hiding this comment

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

lgtm!

let account = self.get_account(pubkey);
let created = account.is_none();

let mut account = account.unwrap_or_default();
account.lamports += lamports;
Copy link
Member Author

@ryoqun ryoqun Jul 31, 2020

Choose a reason for hiding this comment

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

@t-nelson Ugh, I've found some concerns while testing against local/testnet/mainnet-beta....

Specifically, the order of deposit and rent is significant in a corner case when the account is to be reclaimed by rent, meaning there is too little lamports at the time of loading..

Also, there is some inconsistency for rent collection timing semantics with regards to #10426 (comment). Strictly abide by the rules, we would end up doing rent collection twice in deposit because we're both loading and storing at once. But I think the divergent behavior here is tolerable because bank::deposit() is fairly limited use.

I'm thinking for a good compromise... At bottom, I want to finish this without much complexities introduced and without any cluster consistency issue when updating.

Copy link
Member Author

@ryoqun ryoqun Aug 3, 2020

Choose a reason for hiding this comment

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

Ok, my thinking is making progress with a fresh brain after weekend.

Come to think of it, update_rewards() doesn't care about rents at all.

So, maybe deposit() should follow the suit as well? So, the general idea would be that any kinds of protocol-level account manipulation is entirely exempt from rent manipulation. That will simplify the reasoning a lot.

Even if we do that, the accounts will be rent-collected at least once an epoch by eager rent collection. So skipping rents here shouldn't be an escape hatch.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've checked the origin of rent in deposit(): https://github.com/solana-labs/solana/pull/6947/files#r347114185

As far as I understand, it seems that the reasoning is that all updates should be rent-collected. This is now realized by the eager rent collection now.

Also, I found another protocol-level account manipulation: collected rent distribution. That should be free of rent collection to avoid recursion..

So, I'm more inclined to the general consistent idea of no rent collection for protocol-level account manipulation.

Also, this would slightly alleviate rather hefty processing of update_rewards by avoiding rent collection there for the consistency of rent-collection coverage.

@ryoqun ryoqun changed the title Fix bad rent in Bank::deposit as if since epoch 0 [no merge] Fix bad rent in Bank::deposit as if since epoch 0 Jul 31, 2020
@ryoqun
Copy link
Member Author

ryoqun commented Jul 31, 2020

no merge please, I found some issues to be addressed after local testing..: https://github.com/solana-labs/solana/pull/10468/files#r463443390

@mergify mergify bot dismissed t-nelson’s stale review August 3, 2020 02:44

Pull request has been modified.

@ryoqun ryoqun requested a review from t-nelson August 3, 2020 03:38
@ryoqun ryoqun changed the title [no merge] Fix bad rent in Bank::deposit as if since epoch 0 Fix bad rent in Bank::deposit as if since epoch 0 Aug 3, 2020
@ryoqun
Copy link
Member Author

ryoqun commented Aug 3, 2020

@t-nelson I've completely turned around the solution after this course of thinking: #10468 (comment).

In short, this is my preferred new solution: https://github.com/solana-labs/solana/pull/10468/files#diff-80339caf6f22201fa3399a7761c91b88R17

How does this look for you? :)

@mvines mvines added the v1.3 label Aug 6, 2020
Copy link
Contributor

@t-nelson t-nelson left a comment

Choose a reason for hiding this comment

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

This LGTM! Let's give @rwalker-com a few days to comment on the proposal changes, then go ahead and merge

- The distribution of transaction fee at the end of every epoch

Even if those processes are out of scope of rent collection, all of manipulated accounts will eventually be handled by the \(2\) mechanism.

Copy link
Contributor

Choose a reason for hiding this comment

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

This sounds good to me. It'd be great to get a comment from @rwalker-com to be sure we aren't missing anything. 🙏

Choose a reason for hiding this comment

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

lgtm, and this is consistent with what I intended, but the call flow to load and store accounts is pretty tortured, and has many paths. were the bank to document which APIs were for runtime access of accounts, it would go a long way to making these easier to reason about.

Copy link
Member Author

@ryoqun ryoqun Aug 11, 2020

Choose a reason for hiding this comment

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

the call flow to load and store accounts is pretty tortured ...

@rwalker-com (UPDATED) Yeah, I think so too... I'll address the documetation work along with #10426

Anyway, thanks for confirming this pr!

Copy link
Member Author

Choose a reason for hiding this comment

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

Err, wrong user @rwalker-apple...

Copy link

@rwalker-apple rwalker-apple left a comment

Choose a reason for hiding this comment

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

lgtm

@ryoqun ryoqun merged commit 6c242f3 into solana-labs:master Aug 11, 2020
mergify bot pushed a commit that referenced this pull request Aug 11, 2020
* Fix bad rent in Bank::deposit as if since epoch 0

* Remove redundant predicate

* Rename

* Start to add tests with some cleanup

* Forgot to add refactor code...

* Enchance test

* Really fix rent timing in deposit with robust test

* Simplify new behavior by disabling rent altogether

(cherry picked from commit 6c242f3)

# Conflicts:
#	runtime/src/accounts.rs
#	runtime/src/rent_collector.rs
mergify bot pushed a commit that referenced this pull request Aug 11, 2020
* Fix bad rent in Bank::deposit as if since epoch 0

* Remove redundant predicate

* Rename

* Start to add tests with some cleanup

* Forgot to add refactor code...

* Enchance test

* Really fix rent timing in deposit with robust test

* Simplify new behavior by disabling rent altogether

(cherry picked from commit 6c242f3)
@ryoqun
Copy link
Member Author

ryoqun commented Aug 11, 2020

todo

  • write local manual test steps for posterity

mergify bot added a commit that referenced this pull request Aug 11, 2020
* Fix bad rent in Bank::deposit as if since epoch 0

* Remove redundant predicate

* Rename

* Start to add tests with some cleanup

* Forgot to add refactor code...

* Enchance test

* Really fix rent timing in deposit with robust test

* Simplify new behavior by disabling rent altogether

(cherry picked from commit 6c242f3)

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
mergify bot added a commit that referenced this pull request Aug 11, 2020
* Fix bad rent in Bank::deposit as if since epoch 0 (#10468)

* Fix bad rent in Bank::deposit as if since epoch 0

* Remove redundant predicate

* Rename

* Start to add tests with some cleanup

* Forgot to add refactor code...

* Enchance test

* Really fix rent timing in deposit with robust test

* Simplify new behavior by disabling rent altogether

(cherry picked from commit 6c242f3)

# Conflicts:
#	runtime/src/accounts.rs
#	runtime/src/rent_collector.rs

* Fix conflict

* Fix clippy

Co-authored-by: Ryo Onodera <ryoqun@gmail.com>
@ryoqun
Copy link
Member Author

ryoqun commented Aug 17, 2020

[ ] write local manual test steps for posterity

First of all, the manual test for this fix needed to be rather complicated because voting validators cause its fee-paying accounts (= identity accounts) to be touched via the vote transactions by themselves. This causes rent collection, meaning Bank::deposit() becomes no-op with regards to the rent collection. Also, normal pay transactions doesn't touch Bank::deposit(), so...

In short, run a local cluster with two validators, which are both staked. One is running with unpatched version, the other with patched version. And intentionally make one of validator not to vote at all, meaning the validator is only producing blocks while collecting fees. (= which touches Bank::deposit()).

Specifically, like this:

# validator 1
# warmup is needed to accelerate new validator joining.
$ SOLANA_RUN_SH_GENESIS_ARGS="--enable-warmup-epochs" ./run.sh

# validator 2 (preps and actual running)
$ ./target/release/solana --url http://localhost:8899 airdrop 100000
$ ./target/release/solana --url http://localhost:8899 create-vote-account /tmp/vote.json /tmp/node.json
$ ./target/release/solana --url http://localhost:8899 pay /tmp/node.json 2
$ ./target/release/solana --url http://localhost:8899 create-stake-account /tmp/stake.json 0.04851334
# force is needed to stake to non-voting vote account.
$ ./target/release/solana --url http://localhost:8899 delegate-stake --force /tmp/stake.json /tmp/vote.json
# --no-voting didn't work? just don't give a voting-pubkey, so that ephemeral voting key effectively disables voting.
$ ./solana-validator{,-bad-rent} --ledger ledger{,bad-rent} --identity /tmp/node.json --entrypoint localhost:8001 --log - --rpc-port 8665 --snapshot-interval-slots 0

Then wait to see the cluster dies without or with mix of differing validators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants