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
stake: Allow stakes with unmatched credits observed to merge #18985
Conversation
Codecov Report
@@ Coverage Diff @@
## master #18985 +/- ##
=========================================
- Coverage 82.7% 82.6% -0.2%
=========================================
Files 449 449
Lines 127997 127274 -723
=========================================
- Hits 105968 105180 -788
- Misses 22029 22094 +65 |
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.
Looking good! I just have a couple tinfoil hat suggestions
programs/stake/src/stake_state.rs
Outdated
(merge_credits_observed as u128).checked_mul(merge_lamports as u128)?; | ||
let total_weighted_credits = stake_weighted_credits.checked_add(merge_weighted_credits)?; | ||
let effective_credits_observed = total_weighted_credits.checked_div(total_stake)?; | ||
if total_weighted_credits.checked_rem(total_stake)? > 0 { |
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'd get the same effect by adding half of total_stake
to total_weighted_credits
before dividing, no?
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.
... but still do we have to ceil if total_stake
isn't divisible by half? (note: i'm weak at math.. haven't dove any proof thinking)...
this reminds of @behzadnouri 's concise math technique (#16457 (comment)). but we're doing financial math here instead of engineering math.
so better of being verbose and rigorous, instead of succinctness?
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.
In this case, we're not trying to round up or down, we're always trying to ceiling the value if it's at all above. This is all to make sure that the resulting stake does not get too much credit. If we ever round down, then your merged stake gets one more credit than it truly deserves.
Simple example: if I merge a stake of 100 with credits_observed = 1001 into a stake of 200 with credits_observed = 1000, with rounding that would produce a stake of 300 with credits_observed = 1000, which would get you more rewards than if the stakes had stayed separated. Does that make sense?
this reminds of @behzadnouri 's concise math technique (#16457 (comment)). but we're doing financial math here instead of engineering math.
@ryoqun are you suggesting to add a comment that explains the math with concise variable names?
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.
@joncinque I think @t-nelson 's suggestion always does ceiling. so i think we're at the same page of ceiling.
this reminds of @behzadnouri 's concise math technique (#16457 (comment)). but we're doing financial math here instead of engineering math.
@ryoqun are you suggesting to add a comment that explains the math with concise variable names?
nope. it's just my random thought... xD no need to explain this ceiling part as a comment
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.
Ah I see, yes, to do the ceiling in one go, we'd have to add total_stake - 1
before dividing by total_stake
. I'm fine with either
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.
Right, my main goal was to eliminate the second division and branching logic (Lazy Trent sees a branch and would rather eliminate it than write a test)
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, branches suck, will remove 😄
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.
Not much to add to Trent's comments; indeed looking good!
programs/stake/src/stake_state.rs
Outdated
@@ -1023,6 +1057,34 @@ impl MergeKind { | |||
} | |||
} | |||
|
|||
/// Calculate the effective credits observed for two stakes whenn merging | |||
/// | |||
/// When merging two activating or active stakes, the credits observed of the |
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.
According to this definition,
solana/programs/stake/src/stake_state.rs
Lines 888 to 899 in 5e27789
match stake.delegation.stake_activating_and_deactivating( | |
clock.epoch, | |
Some(stake_history), | |
true, | |
) { | |
/* | |
(e, a, d): e - effective, a - activating, d - deactivating */ | |
(0, 0, 0) => Ok(Self::Inactive(meta, stake_keyed_account.lamports()?)), | |
(0, _, _) => Ok(Self::ActivationEpoch(meta, stake)), | |
(_, 0, 0) => Ok(Self::FullyActive(meta, stake)), | |
_ => { | |
let err = StakeError::MergeTransientStake; |
we shouldn't be merging activating stakes.
so, When merging two just-delegated or fully-active stakes, ...
would be more accurate.
or When merging two ActivationEpoch or FullyActive stakes, ...
(yeah, we lack good terminology here)
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.
Ah I see, I always thought ActivationEpoch
= activating
, but as you point out, a stake can be activating
and also be in a transient state with some active and some activating. I worry that just-delegated
doesn't make it clear that the stake is only activating. What about purely-activating
?
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.
hmm, purely-activating
sounds to me to indicate the transient activating stake account.
maybe, just using enum variant should be ok. it's just a source code comment after all?
@t-nelson do you have some word on this? (iirc, ActivationEpoch
came from you?).
} | ||
} | ||
|
||
proptest! { |
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 proptest!
beast is new to me... seems you broght it here from the spl land. xD
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's a great way to test maxes and mins on your calculations! https://altsysrq.github.io/proptest-book/intro.html is a good guide 😁
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.
These docs look way nicer than the ones on docs.rs 🎉
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.
@joncinque well done pr. I left some cosmetic review comments only. thanks for showing your courage to do the surgery of our heart of inflation without hesitation. xD
@@ -957,6 +958,7 @@ impl MergeKind { | |||
} | |||
} | |||
|
|||
// Remove this when the `stake_merge_with_unmatched_credits_observed` feature is removed |
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.
👍
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! I'll demote the ceil simplification to a nit. I'd like to see it, but won't block
programs/stake/src/stake_state.rs
Outdated
(merge_credits_observed as u128).checked_mul(merge_lamports as u128)?; | ||
let total_weighted_credits = stake_weighted_credits.checked_add(merge_weighted_credits)?; | ||
let effective_credits_observed = total_weighted_credits.checked_div(total_stake)?; | ||
if total_weighted_credits.checked_rem(total_stake)? > 0 { |
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.
Right, my main goal was to eliminate the second division and branching logic (Lazy Trent sees a branch and would rather eliminate it than write a test)
} | ||
} | ||
|
||
proptest! { |
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.
These docs look way nicer than the ones on docs.rs 🎉
* stake: Allow stakes with unmatched credits observed to merge * Address feedback * Remove branch by doing a ceiling in one calc (cherry picked from commit 2b33c0c) # Conflicts: # Cargo.lock # programs/stake/Cargo.toml # sdk/src/feature_set.rs
…#18985) (#19055) * stake: Allow stakes with unmatched credits observed to merge (#18985) * stake: Allow stakes with unmatched credits observed to merge * Address feedback * Remove branch by doing a ceiling in one calc (cherry picked from commit 2b33c0c) # Conflicts: # Cargo.lock # programs/stake/Cargo.toml # sdk/src/feature_set.rs * Fix merge conflicts * Fix clippy lint * Add back whitespace Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
Problem
It isn't possible to merge active / activating stakes with unmatched credits observed, but mathematically speaking, there's no reason to not allow it.
Summary of Changes
Allow it! This is essentially just calculating the stake-weighted average. If there's any remainder while doing the calculation, we take the ceiling, since reward payout takes
current_credits_observed - stake.credits_observed
. This means you could lose out on some fractional value when doing an active stake merge. The main idea is to avoid the opposite issue, of getting more rewards than you should due to a merge.Fixes #18411 Fixes #18942