-
Notifications
You must be signed in to change notification settings - Fork 303
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
Reverse staking rate calculations #2909
Conversation
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've done a self-review to point out the important parts of this change. Seeking review and testing to ensure this looks right. The desire of this change is that absolutely nothing should change observationally; this merely finishes the job of removing the next-rate-data calculations.
// We are transitioning to the next epoch, so set "cur_base_rate" to the previous "next_base_rate", and | ||
// update "next_base_rate". | ||
let current_base_rate = self.next_base_rate().await?; | ||
// We are transitioning to the next epoch, so set "cur_base_rate" to the base rate derived | ||
// from the allocation given by the distributions module, and remember the previous base | ||
// rate. | ||
let previous_base_rate = self.current_base_rate().await?; // now looking backwards, our current rate is the previous rate | ||
let current_base_rate: BaseRateData = | ||
previous_base_rate.next(chain_params.base_reward_rate); | ||
|
||
let next_base_rate = current_base_rate.next(chain_params.base_reward_rate); | ||
|
||
// rename to curr_rate so it lines up with next_rate (same # chars) | ||
tracing::debug!(curr_base_rate = ?current_base_rate); | ||
tracing::debug!(?next_base_rate); | ||
tracing::debug!(current_base_rate = ?current_base_rate); |
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.
Please check this: The intent of this change is to effect the exact same base rate change dynamics as previously, but without defining a next_base_rate
. Instead, we copy the current base rate (of the previous epoch) into previous_base_rate
, then redefine current_base_rate
to be the next
base rate of that.
let current_base_rate: BaseRateData = | ||
previous_base_rate.next(chain_params.base_reward_rate); |
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.
When this PR is finished, this definition of current_base_rate
will be replaced with a derivation from the allocation of penumbra tokens from the distributions module, converting that into the base rate needed to effect that inflation.
message NextValidatorRateRequest { | ||
// The expected chain id (empty string if no expectation). | ||
string chain_id = 1; | ||
core.crypto.v1alpha1.IdentityKey identity_key = 2; | ||
} | ||
|
||
message NextValidatorRateResponse { | ||
core.stake.v1alpha1.RateData data = 1; | ||
} |
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 messages aren't useful anymore. Deleting them causes a proto lint failure, but I think this is one of the acceptable cases, because they should never be used anymore (and aren't).
17f9833
to
6851e03
Compare
b606a44
to
21bef22
Compare
This prepares for moving the responsibility for the allocation of inflation into the distributions module
Co-authored-by: @zbuc
This allows components that don't do anything for a particular method to skip implementing it at all.
afbf20c
to
f48cdfb
Compare
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 made implementing each of these methods optional, so that Component
s don't have to implement the ones they don't do anything in.
// TODO: replace with a single checked_add_signed call when mixed_integer_ops lands in stable (1.66) | ||
let new_supply: Amount = if change < 0 { | ||
current_supply | ||
.value() | ||
.checked_sub(change.unsigned_abs()) | ||
.ok_or_else(|| { | ||
anyhow::anyhow!( | ||
"underflow updating token {} supply {} with delta {}", | ||
asset_id, | ||
current_supply, | ||
change | ||
) | ||
})? | ||
.into() | ||
} else { | ||
current_supply | ||
.value() | ||
.checked_add(change as u128) | ||
.ok_or_else(|| { | ||
anyhow::anyhow!( | ||
"overflow updating token {} supply {} with delta {}", | ||
asset_id, | ||
current_supply, | ||
change | ||
) | ||
})? | ||
.into() | ||
}; | ||
tracing::debug!(?current_supply, ?new_supply, ?change); | ||
let new_supply = current_supply.checked_add_signed(change).ok_or_else(|| { | ||
anyhow::anyhow!( | ||
"over-/under-flow updating token {} supply {} with delta {}", | ||
asset_id, | ||
current_supply, | ||
change | ||
) | ||
})?; |
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 followed the TODO, since we are now on a version of Rust that supports checked_add_signed
.
// This currently computes the new issuance by multiplying the total staking token ever | ||
// issued by the base reward rate. This is a stand-in for a more accurate and good model of | ||
// issuance, which will be implemented later. For now, this inflates the total issuance of | ||
// staking tokens by a fixed ratio per epoch. | ||
let base_reward_rate = self.get_chain_params().await?.base_reward_rate; | ||
let total_issued = self.total_issued().await?; | ||
const BPS_SQUARED: u64 = 1_0000_0000; // reward rate is measured in basis points squared | ||
let new_issuance = total_issued * base_reward_rate / BPS_SQUARED; | ||
let issuance = new_issuance + remainder; | ||
Ok((issuance, 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.
This new issuance algorithm is not numerically equivalent to the previous one, but that does not matter because we're going to entirely replace it with a time-based control mechanism soon. This algorithm just inflates the penumbra token at a particular rate every epoch (3 basis points is the default).
crates/core/num/src/allocate.rs
Outdated
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 standalone algorithm does optimal allocation of an integer amount to a list of weighted labels. This is overkill for the place where it's used in the distributions component, but will be crucial in implementing distributions within individual components.
We're worried about drift making rebasing difficult. Let's aim to get this one in during 61. |
This won't be mergeable any more, following #3077, and the work will have to be redone on top of the current |
Picking this up in a cherry-picked branch. |
Resolves #1481, resolves #2802. This happens in two steps:
The formula to adjust the
previous_base_rate_data
to reflect a particular desiredissuance
is:Rewards are issued by inflating the stake bonded only to active validators, so to invert the staking calculation, we need to calculate a reward rate (in basis points of basis points) such that the upcoming base rate data when applied to the active validator set will inflate the value of their stake by the desired issuance. This works out to be that we should multiply the base rate by
(issuance + total_active_stake) / total_active_stake
, or in other words calculate thenext
base rate using a reward rate ofissuance * BPS_SQUARED / total_active_stake
.Efficiently summing over all active validators requires an index tracking which validators are active. This could be done in this PR, or stubbed out with a filter over all validators and fixed up in #2921 to use the index of active validators defined there.
It is likely that the calculation of the new base rate data will result in a not-perfectly-precise creation of inflation (it will undershoot due to precision loss in division in several places). This can't be fixed in any given epoch, but to ensure that the total amount issued over time ends up being correct, we can carry forward to the next epoch a remainder to be fed forward into that epoch's issuance. This remainder can be calculated by summing the
unbonded_amount
across all active validators' upcomingRateData
(i.e. the new ones generated from the newBaseRateData
) of each validator's entire delegation pool, and then subtracting that amount from the actual desired issuance. It is not correct to calculate a hypotheticalunbonded_amount
based only on theBaseRateData
, because there are additional precision losses in the conversion toRateData
for each validator. The sum should always be positive, because division rounds down. The remainder should be stored in the state — it should not be fed forward into the next epoch's staking issuance directly, but rather should be treated as an additional input to the distributions module (because this gives the distributions module freedom to re-allocate that issuance elsewhere in the upcoming epoch, which it might want to do).Finally, the temporary determination of the correct issuance of staking rewards in the distributions module should be given by the product of the per-epoch staking reward rate set as a chain parameter, and the total value of all delegation tokens to any validator. This is, as noted above, temporary, and will be replaced with a per-epoch allocation that is scaled to epoch duration, and dynamically allocated based on other factors considered by the distributions module.