-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Burn fees collected into invalid accounts #33887
Burn fees collected into invalid accounts #33887
Conversation
Codecov Report
@@ Coverage Diff @@
## master #33887 +/- ##
========================================
Coverage 81.8% 81.9%
========================================
Files 810 811 +1
Lines 218148 218368 +220
========================================
+ Hits 178650 178952 +302
+ Misses 39498 39416 -82 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple questions and nits.
I only commented on a couple, but I'd love to see all the test match result
s cleaned up for readability.
runtime/src/bank/fee_distribution.rs
Outdated
warn!( | ||
"Rent distribution of {rent_to_be_paid} to {pubkey} results in \ | ||
invalid RentState: {recipient_post_rent_state:?}" | ||
); | ||
datapoint_warn!( | ||
"bank-rent_distribution_invalid_state", | ||
("slot", self.slot(), i64), | ||
("pubkey", pubkey.to_string(), String), | ||
("rent_to_be_paid", rent_to_be_paid, i64) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just calling out that we do lose this warning and datapoint in the new deposit_fees
. Hopefully, prevent_rent_paying_rent_recipients()
will be active before this PR is released, so we'll no longer care about tracking that specific case so carefully.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I was curious if you wanted to keep that or not, I can keep it in if you want!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jeffwashington , @brooksprumo , I think you're the main users of this datapoint currently. Thoughts on removing it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest patch of this PR removes this particular datapoint_warn but has two datapoint_warn's that track how many lamports were burned during both tx and rent fee distribution
datapoint_warn!(
"bank-burned_rent",
("slot", self.slot(), i64),
("num_lamports", rent_to_burn, i64)
);
datapoint_warn!(
"bank-burned_fee",
("slot", self.slot(), i64),
("num_lamports", deposit, i64),
("error", err.to_string(), String),
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wdyt about adding the pubkey back into the bank-burned_fee
datapoint?
I know that putting text/pubkeys into metrics isn't great, but it may be useful for any debugging. Since this is an exceptional case, I would expect this datapoint to not trigger often.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For bank-burned_fee
we track the slot which allows us to infer who the leader was that failed to collect tx fees
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me, thanks!
runtime/src/bank/fee_distribution.rs
Outdated
error!( | ||
"Burned {} rent lamports instead of sending to {}", | ||
rent_to_be_paid, pubkey | ||
); | ||
datapoint_error!( | ||
"bank-burned_rent", | ||
("slot", self.slot(), i64), | ||
("num_lamports", rent_to_be_paid, i64) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we maybe want to back down these, or at least the log? It's not really the kind of thing a node operator would interpret as an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good idea. I think that if a validator is burning its own fees it should see an error but it won't care about other validators. Do you care if I remove the datapoint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know of a need for the datapoint and am personally fine with its removal, but that might be a good question for @jeffwashington or @HaoranYi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a datapoint_info!
will be enough. I think it will be better if we accumulate all the burned fees and submit one data point for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@HaoranYi is there a specific reason why you would like to track this datapoint? I can't think of any reason personally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It gives us the visibility of how much fees are burned in the network. The amount of the fees burned should be very small. To me, being able to run such a query and verify the amount of fees burned, could be a useful sanity check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I'll add it back in, thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the datapoint back in but downgraded it to "warn"
fbeea2e
to
4cb3bca
Compare
} | ||
|
||
// Deposits fees into a specified account and if successful, returns the new balance of that account | ||
fn deposit_fees( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we split this fu into two fns? one for transaction fee specificially and one for rent specificially?
For transaction fee, we don't need to check for rent state transition. Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the same checks (including rent state transition) in both cases, that's why I created this function. If we collect transaction fees into an account whose post balance is not greater than or equal to the rent exempt balance, we need to skip the fee deposit because otherwise we allow creating rent paying accounts. Make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah.
It seems that the current code still allows transaction fees to be collected into an account whose post balance is not greater than or equal to the rent exempt balance. Should we create a separate PR to fix this for current code and backport it to v1.17?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what you mean, can you elaborate more? By the "current code" you mean the code in this PR right? And under what conditions are you saying that the code "still allows transaction fees to be collected into an account whose post balance is not greater than or equal to the rent exempt balance"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By "current code", I mean master.
In master, we don't validate the post balance of the account that transaction fees are deposit to. If the balance of the account pre-transaction fee was zero, then after the transaction fee deposit, we introduce a "new" rent paying account, right?
The new code (i.e. deposit_fee fn) in the PR will prevent this from happening. What I mean is that we pull this piece out in a new PR to patch master separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, I'd rather not break up apart this PR unless we have a good reason to. I purposely setup this PR to introduce the shared deposit_fee
function to ensure that all invalid cases are handled for each type of fee collection. Before, without a common function, we missed the rent paying edge case for collected transaction fees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. I think we might want to port this feature back to 1.17 to close those edge cases as soon as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work. Just a few nits and suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm. It just occurred to me that, since this adds a feature gate, it really should have been a SIMD first.
} | ||
} | ||
} | ||
}); | ||
self.rewards.write().unwrap().append(&mut rewards); | ||
|
||
if rent_to_burn > 0 { | ||
self.capitalization.fetch_sub(rent_to_burn, Relaxed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change to a single subtraction per loop looks fine, but since it was potentially substantive, it would have been nice if called out in its own commit.
runtime/src/bank/fee_distribution.rs
Outdated
warn!( | ||
"Rent distribution of {rent_to_be_paid} to {pubkey} results in \ | ||
invalid RentState: {recipient_post_rent_state:?}" | ||
); | ||
datapoint_warn!( | ||
"bank-rent_distribution_invalid_state", | ||
("slot", self.slot(), i64), | ||
("pubkey", pubkey.to_string(), String), | ||
("rent_to_be_paid", rent_to_be_paid, i64) | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wdyt about adding the pubkey back into the bank-burned_fee
datapoint?
I know that putting text/pubkeys into metrics isn't great, but it may be useful for any debugging. Since this is an exceptional case, I would expect this datapoint to not trigger often.
Good point, I created a SIMD to improve visibility and explain this change in more detail: solana-foundation/solana-improvement-documents#85 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
* refactor: create bank::fee_distribution module * feature: add checks to fee distribution * refactor: move Bank::deposit fn into test_utils * feedback * feedback 2 * add datapoints * change to datapoint_warn * typo (cherry picked from commit ebe8afb) # Conflicts: # runtime/src/bank.rs # runtime/src/bank/tests.rs
…#33951) * Burn fees collected into invalid accounts (#33887) * refactor: create bank::fee_distribution module * feature: add checks to fee distribution * refactor: move Bank::deposit fn into test_utils * feedback * feedback 2 * add datapoints * change to datapoint_warn * typo (cherry picked from commit ebe8afb) # Conflicts: # runtime/src/bank.rs # runtime/src/bank/tests.rs * resolve conflicts --------- Co-authored-by: Justin Starry <justin.starry@icloud.com> Co-authored-by: Justin Starry <justin@solana.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that the refactors have been squashed into the logical changes is pretty not great. this should have been at least two PRs, probably more.
have we checked how many mb validators will be effected by this change?
do we intend to prevent assignment of invalid fee-collection accounts?
@@ -3805,7 +3804,7 @@ impl Bank { | |||
.stakes_cache | |||
.stakes() | |||
.highest_staked_node() | |||
.unwrap_or_default(); | |||
.unwrap_or_else(Pubkey::new_unique); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method should absolutely not be used outside of tests under any circumstances. can we just fail/panic instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only used in tests right now... using the system program (default pubkey) as the highest staked node should also not happen outside of tests in any circumstances. I'll clean it up in a follow up change though to be sure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR fix is being worked on here: #34135
fees: u64, | ||
options: DepositFeeOptions, | ||
) -> Result<u64, DepositFeeError> { | ||
let mut account = self.get_account_with_fixed_root(pubkey).unwrap_or_default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not fail directly here rather than do the whole footgun-prone unwrap_or_default()
thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's fine if the account doesn't exist as long as the fees top up the balance to at least rent exempt.
Noted, will do that next time.
Right now the validator implementation forces nodes to use the fee collector account (also known as node id account) as the fee payer for their votes (therefore must already be a valid system account). And tpu votes also checks that the node id is the payer for the votes and uses that to weight vote transactions for block inclusion. So no one will be impacted.
Not now.. it isn't possible to enforce that a fee-collection account stays valid. Its owner can change at any time and it can be withdrawn to zero lamports at any time. Best thing I think we could do in the future is to create an enshrined "NodeAccount" or "FeeCollectorAccount" that must always be valid and used separately from fee payments. But that's a big lift and probably not necessary. If validators want fees, they won't create funky fee collection accounts. |
…labs#33887) (solana-labs#33951) * Burn fees collected into invalid accounts (solana-labs#33887) * refactor: create bank::fee_distribution module * feature: add checks to fee distribution * refactor: move Bank::deposit fn into test_utils * feedback * feedback 2 * add datapoints * change to datapoint_warn * typo (cherry picked from commit ebe8afb) # Conflicts: # runtime/src/bank.rs # runtime/src/bank/tests.rs * resolve conflicts --------- Co-authored-by: Justin Starry <justin.starry@icloud.com> Co-authored-by: Justin Starry <justin@solana.com>
this was way too big to backport. cc/ @willhickey if we're developing a fix with intention to backport, it must be done in the smallest change set possible. now we have a mass of refactor to sift through if we need to debug compatibility issues (which we are ATM) |
…labs#33887) (solana-labs#33951) * Burn fees collected into invalid accounts (solana-labs#33887) * refactor: create bank::fee_distribution module * feature: add checks to fee distribution * refactor: move Bank::deposit fn into test_utils * feedback * feedback 2 * add datapoints * change to datapoint_warn * typo (cherry picked from commit ebe8afb) # Conflicts: # runtime/src/bank.rs # runtime/src/bank/tests.rs * resolve conflicts --------- Co-authored-by: Justin Starry <justin.starry@icloud.com> Co-authored-by: Justin Starry <justin@solana.com>
Problem
Validators can currently collect transaction and rent fees into any account type they wish. This is unnecessarily permissive and fees should only be allowed to be collected into system owned accounts that are rent exempt.
Summary of Changes
bank::fee_distribution
module to avoid increasing the size ofbank.rs
Bank::deposit
intotest_utils
module to discourage its usageFeature Gate Issue: #33888