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

feat(inflation): Implement Akash custom inflation function according to the whitepaper. #1352

Merged
merged 10 commits into from
Nov 24, 2021

Conversation

arijitAD
Copy link
Contributor

@arijitAD arijitAD commented Aug 23, 2021

Description

Motivation and Context

  • I have raised an issue to propose this change (required)
  • My issue has received approval from the maintainers or lead with the design/approved label

How Has This Been Tested?

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I've read the CONTRIBUTION guide
  • I have signed-off my commits with git commit -s
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@arijitAD arijitAD requested a review from boz August 23, 2021 11:16
@codecov
Copy link

codecov bot commented Aug 23, 2021

Codecov Report

Merging #1352 (50ac384) into master (9dbc53f) will increase coverage by 4.71%.
The diff coverage is 72.41%.

❗ Current head 50ac384 differs from pull request most recent head ededc6f. Consider uploading reports for the commit ededc6f to get more accurate results

@@            Coverage Diff             @@
##           master    #1352      +/-   ##
==========================================
+ Coverage   38.23%   42.94%   +4.71%     
==========================================
  Files         347      276      -71     
  Lines       20047    15528    -4519     
==========================================
- Hits         7664     6668     -996     
+ Misses      11552     8115    -3437     
+ Partials      831      745      -86     

app/app.go Outdated
panic(err)
}
minInflation := minter.Inflation.Sub(minter.Inflation.Mul(r))
maxInflation := minter.Inflation.Add(minter.Inflation.Mul(r))
Copy link
Contributor

Choose a reason for hiding this comment

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

There are more parameters to consider. In the default cosmos inflation calculation , there is:

  • current bonded ratio
  • target bonded ratio
  • max inflation rate change

We should continue to use these. My thought was to have:

  1. I(t) is the "ideal" inflation: ideal_inflation.
  2. If bonded_ratio=desired_ratio then target_inflation=ideal_inflation.
  3. If bonded_ratio<desired_ratio then target_inflation=ideal_inflation + adjustment.
  4. If bonded_ratio>desired_ratio then target_inflation=ideal_inflation - adjustment.
  5. max_inflation=I(t)*(1 + 0.05), min_inflation=I(t)*(1 - 0.05)
  6. Change current_inflation to target_inflation, bound by both max_inflation_change and {min,max}_inflation.

Is minter.Inflation eventually set to the return value of this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should continue to use these. My thought was to have:

  1. I(t) is the "ideal" inflation: ideal_inflation.
  2. If bonded_ratio=desired_ratio then target_inflation=ideal_inflation.
  3. If bonded_ratio<desired_ratio then target_inflation=ideal_inflation + adjustment.
  4. If bonded_ratio>desired_ratio then target_inflation=ideal_inflation - adjustment.

By adjustment did you mean inflationRateChange as in the new commit?

Is minter.Inflation eventually set to the return value of this function?

Yes, that's why I am calculating {min, max}_inflation based on minter.Inflation instead of ideal_inflation. Otherwise, there seems no point of applying {min, max} logic using ideal_inflation as the base for calculating {min, max}.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the issue I have is: does using idealInflation + inflationRateChange overly restrict the possible range? If so then the entire range of acceptable inflation rates isn't available.

I think that we should use idealInflation to get [max,min], and calculate currentInflation = minter.Inflation + inflationRateChange. This way it will continue moving until an acceptable bonded ratio is found (in theory).

We also need to make a minimum minInflation - it can be 0 for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that we should use idealInflation to get [max,min], and calculate currentInflation = minter.Inflation + inflationRateChange. This way it will continue moving until an acceptable bonded ratio is found (in theory).

We also need to make a minimum minInflation - it can be 0 for now.

Done

app/app.go Outdated
)

func init() {
var err error
genesisTimeStr := "2020-09-25T14:00:00Z"
Copy link
Contributor

Choose a reason for hiding this comment

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

Might want to make this a var so that it can be set at build time. Would prefer to not have a global genesisTime, but it's okay for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@github-actions
Copy link

github-actions bot commented Sep 7, 2021

Marked as stale; will be closed in five days.

Cut bait or go fishing!

@github-actions github-actions bot added the stale label Sep 7, 2021
@github-actions github-actions bot closed this Sep 15, 2021
@arijitAD arijitAD reopened this Sep 15, 2021
@github-actions github-actions bot closed this Sep 21, 2021
@troian troian deleted the test-inflation branch September 25, 2021 21:30
@arijitAD arijitAD reopened this Oct 26, 2021
go.mod Outdated
@@ -52,7 +52,7 @@ require (
)

replace (
github.com/cosmos/cosmos-sdk => github.com/ovrclk/cosmos-sdk v0.43.0-patches
github.com/cosmos/cosmos-sdk => github.com/ovrclk/cosmos-sdk v0.42.0-alpha1.0.20210909100325-11e54c8fc931
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The latest master uses v0.44.1-patches branch. Should we wait for cosmos/cosmos-sdk#10441 to get released or should we just make the changes required for custom inflation function in our v0.44.1-patches branch so that we can go ahead with merging this PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, let's carry the patch over into v0.44.4-patches for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@boz boz added keepalive Exempt these when managing stale PRs and removed stale labels Oct 27, 2021
Copy link
Contributor

@boz boz 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. Some comments inline.

app/app.go Outdated
func init() {
var err error

genesisTime, err = time.Parse(time.RFC3339, GenesisTimeStr)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be passed in; it's defined in the genesis and will be different for different chains (testnet, etc...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Reading from genesis file now.

app/app.go Outdated
@@ -463,6 +479,45 @@ func NewApp(
return app
}

func inflationCalculator(ctx sdk.Context, minter minttypes.Minter, params minttypes.Params, bondedRatio sdk.Dec) sdk.Dec {
i0 := 100.0
t := ctx.BlockTime().Sub(genesisTime).Seconds() / (60 * 60 * 8766) // years passed
Copy link
Contributor

Choose a reason for hiding this comment

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

Should t be an integer number of years since genesis?

It's hard to understand where the 60*60*8766 comes from.

I think we should use days since genesis.

Copy link
Contributor Author

@arijitAD arijitAD Oct 29, 2021

Choose a reason for hiding this comment

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

Both t and tHalf have been defined in years in the whitepaper. It doesn't matter whether we use days or years to represent both of them, we are finally calculating t/tHalf which is a float value with no units.

Added an explanation for 60*60*8766

Copy link
Contributor

Choose a reason for hiding this comment

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

But t will be a severe step function at each year passing, instead of a more incremental daily increase.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, t is a float value. It isn't an int, so it won't be a step function. Float value will increase continuously, unlike int.
i.e., t can represent 0.5 years. It won't go to 1 year directly from 0 years.

app/app.go Outdated
@@ -102,6 +104,8 @@ import (

const (
AppName = "akash"

tHalf = 2 // years
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be an on-chain parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made it a parameter of deployment module. There is no way to extend the parameters of the mint module.

It is not a clean way of handling this parameter, but the other way would mean creating a new module just to store this parameter.

app/app.go Outdated
@@ -463,6 +479,45 @@ func NewApp(
return app
}

func inflationCalculator(ctx sdk.Context, minter minttypes.Minter, params minttypes.Params, bondedRatio sdk.Dec) sdk.Dec {
i0 := 100.0
Copy link
Contributor

Choose a reason for hiding this comment

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

this is unnecessary; use a comment with 100.0 when it's used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

@boz boz left a comment

Choose a reason for hiding this comment

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

Looking great.

Let's not pollute x/deployment - make a new module for it (x/inflation), and put the actual inflation calculation in there as well.

I commented inline re a couple more parameters that we should make driven by governance.

go.mod Outdated
@@ -52,7 +52,7 @@ require (
)

replace (
github.com/cosmos/cosmos-sdk => github.com/ovrclk/cosmos-sdk v0.43.0-patches
github.com/cosmos/cosmos-sdk => github.com/ovrclk/cosmos-sdk v0.42.0-alpha1.0.20210909100325-11e54c8fc931
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, let's carry the patch over into v0.44.4-patches for now.

app/app.go Outdated
// Number of seconds in a minute = 60
// => 60 * 60 * 8766 = Number of seconds per year
t := ctx.BlockTime().Sub(genesisTime).Seconds() / (60 * 60 * 8766) // years passed
i := 100.0 * math.Pow(2, -t/tHalf) // 100 = initial inflation (i0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Initial inflation was not 100%. Let's make this a parameter as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

app/app.go Outdated

// min, max currentInflation based on a defined range parameter 'r'
// currentInflation range = [I(t) - I(t) * R, I(t) + I(t) * R]
r, err := sdk.NewDecFromStr("0.05") // let's say we allow 5% variance
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's make the variance a parameter too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@arijitAD arijitAD marked this pull request as ready for review November 1, 2021 12:39
app/app.go Outdated Show resolved Hide resolved
@arijitAD
Copy link
Contributor Author

arijitAD commented Nov 2, 2021

app/export.go:232:7: G404: Use of weak random number generator (math/rand instead of crypto/rand) (gosec)
	r := rand.New(rand.NewSource(seed))

This lint failure can't be fixed. The function from cosmos-sdk that we need to use, accepts only math/rand and not crypto/rand. So, we have no choice. Also, it is used only in tests. Doesn't affect security in any sensible way.

@arijitAD arijitAD requested a review from boz November 2, 2021 16:03
@boz
Copy link
Contributor

boz commented Nov 4, 2021

app/export.go:232:7: G404: Use of weak random number generator (math/rand instead of crypto/rand) (gosec)
	r := rand.New(rand.NewSource(seed))

This lint failure can't be fixed. The function from cosmos-sdk that we need to use, accepts only math/rand and not crypto/rand. So, we have no choice. Also, it is used only in tests. Doesn't affect security in any sensible way.

you can disable the check for that line with // nolint: gosec like here

@boz
Copy link
Contributor

boz commented Nov 4, 2021

Please rebase against master.

@arijitAD
Copy link
Contributor Author

arijitAD commented Nov 5, 2021

you can disable the check for that line with // nolint: gosec like here

Thanks, Done.

app/app.go Outdated
Comment on lines 183 to 194
if v := appOpts.Get("GenesisTime"); v != nil {
// in tests, GenesisTime is supplied using appOpts
genTime, ok := v.(time.Time)
if !ok {
panic("expected GenesisTime to be a Time value")
}
inflationtypes.GenesisTime = genTime
} else if genDoc, err := tmtypes.GenesisDocFromFile(filepath.Join(homePath, "config/genesis.json")); err != nil {
panic(err)
} else {
inflationtypes.GenesisTime = genDoc.GenesisTime
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Put this in a helper function that returns the genesis time.

Please don't use global variables for state. Find another way to pass the parameters into the inflation calculation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

x/inflation/alias.go Show resolved Hide resolved
InflationParamSubspace paramstypes.Subspace
)

func InflationCalculator(ctx sdk.Context, minter minttypes.Minter, params minttypes.Params, bondedRatio sdk.Dec) sdk.Dec {
Copy link
Contributor

Choose a reason for hiding this comment

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

agreed. in particular, rounding issues will likely cause different nodes to produce different values.

var inflationParams Params
InflationParamSubspace.GetParamSet(ctx, &inflationParams)
tHalf := float64(inflationParams.InflationDecayFactor)
initialInflation, err := strconv.ParseFloat(inflationParams.InitialInflation, 32)
Copy link
Contributor

Choose a reason for hiding this comment

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

Params should be represented by the appropriate type here. See how x/deploy does it here.

I agree with @hydrogen18 - we should use a decimal or rational 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.

Done

// Number of minutes in an hour = 60
// Number of seconds in a minute = 60
// => 60 * 60 * 8766 = Number of seconds per year
t := ctx.BlockTime().Sub(GenesisTime).Seconds() / (60 * 60 * 8766) // years passed
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I believe it's equivalent. I can double check.

I still think that the above calculation should not use integer division - it will be 0 for a whole year, then 1 for a year, then 2, etc..., causing unnecessary and unwanted discontinuities on the anniversary of genesis.

// => 60 * 60 * 8766 = Number of seconds per year
t := ctx.BlockTime().Sub(GenesisTime).Seconds() / (60 * 60 * 8766) // years passed
i := initialInflation * math.Pow(2, -t/tHalf)
idealInflation := sdk.NewDec(int64(i))
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that there is any loss of precision from int to decimal.

I do think that we should use a consistent type from the outset of this whole thing - rational and decimal for arbitrary precision, then truncate as necessary at the end (or continuously via some large fixed precision).

Comment on lines 14 to 15
DefaultInitialInflation string = "100.0"
DefaultVarince string = "0.05"
Copy link
Contributor

Choose a reason for hiding this comment

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

Make these sdk.Decimal or even integers that are used to populate a decimal later. Maybe look at how some other parameters in cosmos-sdk are implemented.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines 21 to 28
DefaultInitialInflation = sdk.NewDec(100)
DefaultVarince = sdk.MustNewDecFromStr("0.05")

MaxInitialInflation = sdk.NewDec(100)
MinInitialInflation = sdk.ZeroDec()

MaxVariance = sdk.NewDec(1)
MinVariance = sdk.ZeroDec()
Copy link
Contributor

Choose a reason for hiding this comment

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

Make these functions so that they're constant-like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines 26 to 29
t := ctx.BlockTime().Sub(genesisTime).Seconds() / 31557600 // years passed (can be a fraction, eg: 0.5)
idealInflation := inflationParams.InitialInflation.Mul(sdk.MustNewDecFromStr(
strconv.FormatFloat(math.Pow(2, -t/tHalf), 'f', -1, 64),
))
Copy link
Contributor

Choose a reason for hiding this comment

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

Use sdk.Dec or standard big.*.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's no Pow() equivalent on sdk.Dec or big.* that accepts a fraction. That's why using this way of formatting the Pow() float value to a string and parsing a sdk.Dec from that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. How annoying.

How about using this from this, or something similar?

Copy link
Contributor Author

@arijitAD arijitAD Nov 11, 2021

Choose a reason for hiding this comment

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

@arijitAD
Copy link
Contributor Author

This seems to have genesis time as a configurable option? Wouldn't this create all sorts of issues if one validator was using a different time?

The option is used to to supply the genesis time, only to make unit tests pass. Actual validators would have the genesis time in the config file, and that should be same for all validators, I think.

@hydrogen18
Copy link
Contributor

This seems to have genesis time as a configurable option? Wouldn't this create all sorts of issues if one validator was using a different time?

The option is used to to supply the genesis time, only to make unit tests pass. Actual validators would have the genesis time in the config file, and that should be same for all validators, I think.

Can't the tests just assume some random arbitrary genesis time then? Just pick a time that makes sense, i.e. sometime in 2018 or something?

@boz
Copy link
Contributor

boz commented Nov 11, 2021

@arijitAD, this looks promising.

@boz
Copy link
Contributor

boz commented Nov 11, 2021

There's also this, which looks like it's more actively maintained: https://github.com/ericlagergren/decimal

@arijitAD
Copy link
Contributor Author

arijitAD commented Nov 11, 2021

Can't the tests just assume some random arbitrary genesis time then? Just pick a time that makes sense, i.e. sometime in 2018 or something?

No, having a random genesis time doesn't work with tests. They start failing in that case. Seems, there are some parts in tests which depend on the genesis time being correctly set as per the test seed.

@arijitAD arijitAD requested a review from boz November 11, 2021 20:23
),
),
)
idealInflation := inflationParams.InitialInflation.Mul(sdk.MustNewDecFromStr(pow.String()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Use Int() or Float().

Multiply then divide by precision if needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

inflationParams.InitialInflation is a sdk.Dec. sdk.Dec has 18 precision digits.
pow is a decimal.Big. I am using the same precision of 18 digits for decimal.Big everywhere.

So, converting pow to string and parsing it into sdk.Dec won't lose any precision.

Could you please elaborate why and where should we use Int()/Float()? Also, why and where we need to multiply and divide by precision?

Copy link
Contributor

@boz boz Nov 12, 2021

Choose a reason for hiding this comment

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

We don't need that much precision, especially for the final result.

If the result is less than one and we use 6 units of precision then you can make an sdk.Decimal by with something like:

sdk.Decimal(big.Int(val * 10^6)) / 10^6

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

(gogoproto.moretags) = "yaml:\"initial_inflation\""
];

// Variance defines the fraction by which inflation can vary from its previous value in a block.
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't it how much it can vary from the ideal inflation curve?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, updated comment.

}

func validateInitialInflation(i interface{}) error {
v, ok := i.(sdk.Dec)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a string only in proto definition. In Go code, it maps to sdk.Dec because of this configured option in the proto definition:

(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",

This is how other modules in cosmos-sdk are mapping their parameters to sdk.Dec.

@hydrogen18
Copy link
Contributor

Can't the tests just assume some random arbitrary genesis time then? Just pick a time that makes sense, i.e. sometime in 2018 or something?

No, having a random genesis time doesn't work with tests. They start failing in that case. Seems, there are some parts in tests which depend on the genesis time being correctly set as per the test seed.

If that is the case then can OptsWithGenesisTime be moved into a _test.go file and in the regular codepath we continue to use simapp.EmptyAppOptions{} ?

@arijitAD
Copy link
Contributor Author

If that is the case then can OptsWithGenesisTime be moved into a _test.go file and in the regular codepath we continue to use simapp.EmptyAppOptions{} ?

No, it doesn't seem feasible. There are existing test utility functions in non _test.go files, which have to use OptsWithGenesisTime.

Copy link
Contributor

@boz boz left a comment

Choose a reason for hiding this comment

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

Looks great!

We'll have to adjust some of the defaults to make sure it matches mainnet, but this is good to go for now.

@hydrogen18
Copy link
Contributor

The e2e tests are failing with

E[2021-11-22|13:45:28.281] applying statefulSet                         module=provider-cluster-kube err="StatefulSet.apps \"web\" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden" lease=akash1v7u8mq7npa6g7pcncnqgshc5dw5cf5m40gww26/100/1/1/akash1ra7cz7jsljstqk3230426949s79j079h8ptjg7 service=web
I[2021-11-22|13:45:28.284] declaring hostname               

@troian is this something added with storage?

@troian
Copy link
Member

troian commented Nov 23, 2021

@hydrogen18 correct, i’ll give it a check why it updated multiple times

@arijitAD arijitAD merged commit c89507d into master Nov 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
keepalive Exempt these when managing stale PRs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants