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

stake: Allow stakes with unmatched credits observed to merge #18985

Merged
merged 3 commits into from Aug 4, 2021

Conversation

joncinque
Copy link
Contributor

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

@codecov
Copy link

codecov bot commented Jul 30, 2021

Codecov Report

Merging #18985 (df80ea3) into master (03353d5) will decrease coverage by 0.1%.
The diff coverage is 99.0%.

❗ Current head df80ea3 differs from pull request most recent head 3dd56ee. Consider uploading reports for the commit 3dd56ee to get more accurate results

@@            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     

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.

Looking good! I just have a couple tinfoil hat suggestions

programs/stake/src/stake_state.rs Outdated Show resolved Hide resolved
programs/stake/src/stake_state.rs Outdated Show resolved Hide resolved
(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 {
Copy link
Contributor

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?

Copy link
Member

@ryoqun ryoqun Aug 2, 2021

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?

Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor Author

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

Copy link
Contributor

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)

Copy link
Contributor Author

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 😄

programs/stake/src/stake_state.rs Show resolved Hide resolved
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.

Not much to add to Trent's comments; indeed looking good!

sdk/src/feature_set.rs Outdated Show resolved Hide resolved
programs/stake/src/stake_state.rs Outdated Show resolved Hide resolved
programs/stake/src/stake_state.rs Outdated Show resolved Hide resolved
@@ -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
Copy link
Member

Choose a reason for hiding this comment

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

According to this definition,

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)

Copy link
Contributor Author

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?

Copy link
Member

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! {
Copy link
Member

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

Copy link
Contributor Author

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 😁

Copy link
Contributor

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 🎉

ryoqun
ryoqun previously approved these changes Aug 2, 2021
Copy link
Member

@ryoqun ryoqun left a 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
Copy link
Member

Choose a reason for hiding this comment

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

👍

@mergify mergify bot dismissed ryoqun’s stale review August 3, 2021 17:53

Pull request has been modified.

t-nelson
t-nelson previously approved these changes Aug 3, 2021
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! I'll demote the ceil simplification to a nit. I'd like to see it, but won't block

(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 {
Copy link
Contributor

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! {
Copy link
Contributor

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 🎉

@mergify mergify bot dismissed t-nelson’s stale review August 3, 2021 20:40

Pull request has been modified.

@joncinque joncinque added the v1.7 label Aug 3, 2021
@joncinque joncinque merged commit 2b33c0c into solana-labs:master Aug 4, 2021
@joncinque joncinque deleted the st-act-merg branch August 4, 2021 14:43
mergify bot pushed a commit that referenced this pull request Aug 4, 2021
* 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
mergify bot added a commit that referenced this pull request Aug 4, 2021
…#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>
@brooksprumo brooksprumo mentioned this pull request Aug 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants