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

R4R: blockly minting #2825

Merged
merged 45 commits into from
Nov 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
93242a2
untested core minting updates
rigelrozanski Nov 15, 2018
d9a53e5
fix initialMinter
rigelrozanski Nov 15, 2018
1cc058f
fix existing tests
rigelrozanski Nov 15, 2018
32e652f
working writing test
rigelrozanski Nov 16, 2018
b9b979f
NextProvision test, some fixes
rigelrozanski Nov 16, 2018
f31a7e8
update mechanism to use average block time, PENDING, wip docs
rigelrozanski Nov 16, 2018
3dc8ab1
1 line compile fix
rigelrozanski Nov 16, 2018
5b0f3c9
finish updating docs
rigelrozanski Nov 16, 2018
5f70992
compile fix
rigelrozanski Nov 16, 2018
01a9246
address @cwgoes comments
rigelrozanski Nov 19, 2018
bb8c8ed
uint fixes
rigelrozanski Nov 19, 2018
fa469c2
benchmark using Int
rigelrozanski Nov 20, 2018
e1af6c6
use decimal
rigelrozanski Nov 20, 2018
58d34b7
Merge remote-tracking branch 'origin/develop' into rigel/blockly-mint
rigelrozanski Nov 20, 2018
4d11313
doc update
rigelrozanski Nov 20, 2018
c6ce1e6
Merge branch 'develop' into rigel/blockly-mint
cwgoes Nov 20, 2018
9c74ae1
lint
rigelrozanski Nov 20, 2018
8b082e9
defensive checks
rigelrozanski Nov 20, 2018
3f8176b
some debugging output
rigelrozanski Nov 20, 2018
3cc2495
correct the defensive checks addresses
rigelrozanski Nov 21, 2018
94c888a
delegation share invariance
rigelrozanski Nov 21, 2018
1480510
...
rigelrozanski Nov 21, 2018
eb2bdcc
...
rigelrozanski Nov 21, 2018
f4782fe
del accum invariance
rigelrozanski Nov 21, 2018
d510df1
better invar output
rigelrozanski Nov 21, 2018
fb8aa02
PositiveDelegationInvariant
rigelrozanski Nov 22, 2018
2abab59
resolved Dec bug
rigelrozanski Nov 22, 2018
d41eeb1
PENDING.md
rigelrozanski Nov 22, 2018
b678bcc
Merge branch 'develop' into rigel/blockly-mint
rigelrozanski Nov 22, 2018
fffc100
test cover fix
rigelrozanski Nov 22, 2018
0da12f8
Merge branch 'rigel/blockly-mint' of https://github.com/cosmos/cosmos…
rigelrozanski Nov 22, 2018
ccfbfcf
...
rigelrozanski Nov 22, 2018
b466775
demonstrate defensive accum height for zero-delegations
rigelrozanski Nov 22, 2018
05ddb7b
add back in the zero-delegation invar
rigelrozanski Nov 22, 2018
607eaca
note on defensive code
rigelrozanski Nov 22, 2018
d2f59fc
Update x/distribution/types/delegator_info.go
alexanderbez Nov 23, 2018
c0239e0
Update x/distribution/types/delegator_info.go
alexanderbez Nov 23, 2018
4959289
Update Decimal Format()
jaekwon Nov 24, 2018
9a66906
Clip withdrawal tokens
jaekwon Nov 24, 2018
0088c22
oops
jaekwon Nov 24, 2018
50e119a
bez comments, lint
rigelrozanski Nov 25, 2018
5ddd549
Merge pull request #2895 from cosmos/jae/blockly-mint
rigelrozanski Nov 25, 2018
807a4fd
Merge remote-tracking branch 'origin/develop' into rigel/blockly-mint
rigelrozanski Nov 26, 2018
4605acb
lint, bez comments
rigelrozanski Nov 26, 2018
94717fa
Fix typo in errors.go
cwgoes Nov 26, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ BREAKING CHANGES
* [cli] [\#2874](https://github.com/cosmos/cosmos-sdk/pull/2874) `gaiacli tx sign` takes an optional `--output-document` flag to support output redirection.

* Gaia
* [mint] [\#2825] minting now occurs every block, inflation parameter updates still hourly

* SDK
* [\#2752](https://github.com/cosmos/cosmos-sdk/pull/2752) Don't hardcode bondable denom.
Expand Down Expand Up @@ -71,6 +72,7 @@ IMPROVEMENTS
- #2821 Codespaces are now strings
- #2779 Introduce `ValidateBasic` to the `Tx` interface and call it in the ante
handler.
- #2825 More staking and distribution invariants

* Tendermint
- #2796 Update to go-amino 0.14.1
Expand Down
36 changes: 20 additions & 16 deletions cmd/gaia/app/sim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
if numInitiallyBonded > numAccs {
numInitiallyBonded = numAccs
}
fmt.Printf("Selected randomly generated parameters for simulated genesis: {amount of steak per account: %v, initially bonded validators: %v}\n", amount, numInitiallyBonded)
fmt.Printf("Selected randomly generated parameters for simulated genesis:\n"+
"\t{amount of steak per account: %v, initially bonded validators: %v}\n",
amount, numInitiallyBonded)

// Randomly generate some genesis accounts
for _, acc := range accs {
Expand All @@ -86,7 +88,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
GovernancePenalty: sdk.NewDecWithPrec(1, 2),
},
}
fmt.Printf("Selected randomly generated governance parameters: %+v\n", govGenesis)
fmt.Printf("Selected randomly generated governance parameters:\n\t%+v\n", govGenesis)

stakeGenesis := stake.GenesisState{
Pool: stake.InitialPool(),
Params: stake.Params{
Expand All @@ -95,7 +98,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
BondDenom: stakeTypes.DefaultBondDenom,
},
}
fmt.Printf("Selected randomly generated staking parameters: %+v\n", stakeGenesis)
fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakeGenesis)

slashingGenesis := slashing.GenesisState{
Params: slashing.Params{
MaxEvidenceAge: stakeGenesis.Params.UnbondingTime,
Expand All @@ -107,21 +111,21 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage {
SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))),
},
}
fmt.Printf("Selected randomly generated slashing parameters: %+v\n", slashingGenesis)
fmt.Printf("Selected randomly generated slashing parameters:\n\t%+v\n", slashingGenesis)

mintGenesis := mint.GenesisState{
Minter: mint.Minter{
InflationLastTime: time.Unix(0, 0),
Inflation: sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
},
Params: mint.Params{
MintDenom: stakeTypes.DefaultBondDenom,
InflationRateChange: sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
InflationMax: sdk.NewDecWithPrec(20, 2),
InflationMin: sdk.NewDecWithPrec(7, 2),
GoalBonded: sdk.NewDecWithPrec(67, 2),
},
Minter: mint.InitialMinter(
sdk.NewDecWithPrec(int64(r.Intn(99)), 2)),
Params: mint.NewParams(
stakeTypes.DefaultBondDenom,
sdk.NewDecWithPrec(int64(r.Intn(99)), 2),
sdk.NewDecWithPrec(20, 2),
sdk.NewDecWithPrec(7, 2),
sdk.NewDecWithPrec(67, 2),
uint64(60*60*8766/5)),
}
fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis)
fmt.Printf("Selected randomly generated minting parameters:\n\t%+v\n", mintGenesis)

var validators []stake.Validator
var delegations []stake.Delegation

Expand Down
42 changes: 32 additions & 10 deletions docs/spec/mint/begin_block.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Begin-Block

## Inflation
Inflation occurs at the beginning of each block, however minting parameters
are only calculated once per hour.

Inflation occurs at the beginning of each block.
## NextInflationRate

### NextInflation
The target annual inflation rate is recalculated at the first block of each new
hour. The inflation is also subject to a rate change (positive or negative)
depending on the distance from the desired ratio (67%). The maximum rate change
possible is defined to be 13% per year, however the annual inflation is capped
as between 7% and 20%.

The target annual inflation rate is recalculated for each provisions cycle. The
inflation is also subject to a rate change (positive or negative) depending on
the distance from the desired ratio (67%). The maximum rate change possible is
defined to be 13% per year, however the annual inflation is capped as between
7% and 20%.

NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) {
```
NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) {
inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange
inflationRateChange = inflationRateChangePerYear/hrsPerYr

Expand All @@ -26,3 +26,25 @@ NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) {
}

return inflation
```

## NextAnnualProvisions

Calculate the annual provisions based on current total supply and inflation
rate. This parameter is calculated once per block.

```
NextAnnualProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Dec) {
return Inflation * totalSupply
```

## BlockProvision

Calculate the provisions generated for each block based on current
annual provisions

```
BlockProvision(params Params) sdk.Coin {
provisionAmt = AnnualProvisions/ params.BlocksPerYear
return sdk.NewCoin(params.MintDenom, provisionAmt.Truncate())
```
6 changes: 4 additions & 2 deletions docs/spec/mint/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ The minter is a space for holding current inflation information.

```golang
type Minter struct {
InflationLastTime time.Time // block time which the last inflation was processed
Inflation sdk.Dec // current annual inflation rate
LastUpdate time.Time // time which the last update was made to the minter
Inflation sdk.Dec // current annual inflation rate
AnnualProvisions sdk.Dec // current annual exptected provisions
}
```

Expand All @@ -26,6 +27,7 @@ type Params struct {
InflationMax sdk.Dec // maximum inflation rate
InflationMin sdk.Dec // minimum inflation rate
GoalBonded sdk.Dec // goal of percent bonded atoms
BlocksPerYear uint64 // expected blocks per year
}
```

2 changes: 1 addition & 1 deletion types/coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Coin struct {
// the amount is negative.
func NewCoin(denom string, amount Int) Coin {
if amount.LT(ZeroInt()) {
panic("negative coin amount")
panic(fmt.Sprintf("negative coin amount: %v\n", amount))
}

return Coin{
Expand Down
19 changes: 19 additions & 0 deletions types/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,21 @@ func NewDecFromStr(str string) (d Dec, err Error) {
return Dec{combined}, nil
}

// Decimal from string, panic on error
func MustNewDecFromStr(s string) Dec {
dec, err := NewDecFromStr(s)
if err != nil {
panic(err)
}
return dec
}

//______________________________________________________________________________________________
//nolint
func (d Dec) IsNil() bool { return d.Int == nil } // is decimal nil
func (d Dec) IsZero() bool { return (d.Int).Sign() == 0 } // is equal to zero
func (d Dec) IsNegative() bool { return (d.Int).Sign() == -1 } // is negative
func (d Dec) IsPositive() bool { return (d.Int).Sign() == 1 } // is positive
func (d Dec) Equal(d2 Dec) bool { return (d.Int).Cmp(d2.Int) == 0 } // equal decimals
func (d Dec) GT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) > 0 } // greater than
func (d Dec) GTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) >= 0 } // greater than or equal
Expand Down Expand Up @@ -252,6 +263,14 @@ func (d Dec) IsInteger() bool {
return new(big.Int).Rem(d.Int, precisionReuse).Sign() == 0
}

// format decimal state
func (d Dec) Format(s fmt.State, verb rune) {
_, err := s.Write([]byte(d.String()))
if err != nil {
panic(err)
}
}

func (d Dec) String() string {
bz, err := d.Int.MarshalText()
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions x/distribution/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import (
// Create a new validator distribution record
func (k Keeper) onValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {

// defensive check for existence
if k.HasValidatorDistInfo(ctx, valAddr) {
panic("validator dist info already exists (not cleaned up properly)")
}

height := ctx.BlockHeight()
vdi := types.ValidatorDistInfo{
OperatorAddr: valAddr,
Expand Down
22 changes: 21 additions & 1 deletion x/distribution/keeper/test_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ func (fck DummyFeeCollectionKeeper) ClearCollectedFees(_ sdk.Context) {
//__________________________________________________________________________________
// used in simulation

// iterate over all the validator distribution infos (inefficient, just used to check invariants)
// iterate over all the validator distribution infos (inefficient, just used to
// check invariants)
func (k Keeper) IterateValidatorDistInfos(ctx sdk.Context,
fn func(index int64, distInfo types.ValidatorDistInfo) (stop bool)) {

Expand All @@ -179,3 +180,22 @@ func (k Keeper) IterateValidatorDistInfos(ctx sdk.Context,
index++
}
}

// iterate over all the delegation distribution infos (inefficient, just used
// to check invariants)
func (k Keeper) IterateDelegationDistInfos(ctx sdk.Context,
fn func(index int64, distInfo types.DelegationDistInfo) (stop bool)) {

store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey)
defer iter.Close()
index := int64(0)
for ; iter.Valid(); iter.Next() {
var ddi types.DelegationDistInfo
k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &ddi)
if fn(index, ddi) {
return
}
index++
}
}
7 changes: 7 additions & 0 deletions x/distribution/keeper/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ func (k Keeper) SetValidatorDistInfo(ctx sdk.Context, vdi types.ValidatorDistInf

// remove a validator distribution info
func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) {

// defensive check
vdi := k.GetValidatorDistInfo(ctx, valAddr)
if vdi.DelAccum.Accum.IsPositive() {
panic("Should not delete validator with unwithdrawn delegator accum")
}

store := ctx.KVStore(k.storeKey)
store.Delete(GetValidatorDistInfoKey(valAddr))
}
Expand Down
82 changes: 82 additions & 0 deletions x/distribution/simulation/invariants.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant {
if err != nil {
return err
}
err = DelAccumInvariants(d, sk)(app)
if err != nil {
return err
}
return nil
}
}
Expand Down Expand Up @@ -48,3 +52,81 @@ func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria
return nil
}
}

// DelAccumInvariants checks that each validator del accum == sum all delegators' accum
func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant {
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 a great invariant but it is very slow, I wonder if we should make it periodic by default.

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 we should leave it at every block until we upgrade for more capable feature set in the simulator - From a debugging perspective it's nice to not have to be an expert and just "know" that we should reset an invariant to be running each block when it fails to determine the location of the bug. I'm also not so worried about the speed of the simulator - this doesn't seem to be an issue as I've been running things locally.

To improve the speed let's focus on things like the ability to load the simulator from a simulated state etc.

As per running this every block on chain - yeah that seems like a performance issue - not sure how to adjust that (is this per block invariants stuff already merged? - feel free to push here to make that fix, or we could add another PR)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should run this on-chain and not run it for blockchain syncing for new nodes... I think it would be nice to halt the chain as soon as we know that something is off, at least in the beginning.

Copy link
Contributor

Choose a reason for hiding this comment

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

💯 - we can do this very easily, just edit the runtime invariants in cmd/gaia/app/invariants.go

Copy link
Contributor

Choose a reason for hiding this comment

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

(right now we aren't running it on-chain @rigelrozanski, you have to add it explicitly)

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm also not so worried about the speed of the simulator - this doesn't seem to be an issue as I've been running things locally.

OK, maybe it's just my slow laptop - but we should ensure we can still run 500/1000-block simulations as they do catch issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, depends on how fast it is... checking...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ahhhhhhs didn't realize how much slower this is

I think that we should probably add an option to just "enable" or "disable" slow invar checks for the simulation here... maybe a flag

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I much prefer periodic invariants, which achieve essentially the same speed benefit while still ensuring errors are caught (and once caught, you can disable periodicity to figure out the exact operation).


return func(app *baseapp.BaseApp) error {
mockHeader := abci.Header{Height: app.LastBlockHeight() + 1}
ctx := app.NewContext(false, mockHeader)
height := ctx.BlockHeight()

totalDelAccumFromVal := make(map[string]sdk.Dec) // key is the valOpAddr string
totalDelAccum := make(map[string]sdk.Dec)

// iterate the validators
iterVal := func(_ int64, vdi distr.ValidatorDistInfo) bool {
key := vdi.OperatorAddr.String()
validator := sk.Validator(ctx, vdi.OperatorAddr)
totalDelAccumFromVal[key] = vdi.GetTotalDelAccum(height,
validator.GetDelegatorShares())

// also initialize the delegation map
totalDelAccum[key] = sdk.ZeroDec()

return false
}
k.IterateValidatorDistInfos(ctx, iterVal)

// iterate the delegations
iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool {
key := ddi.ValOperatorAddr.String()
delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr)
totalDelAccum[key] = totalDelAccum[key].Add(
ddi.GetDelAccum(height, delegation.GetShares()))
return false
}
k.IterateDelegationDistInfos(ctx, iterDel)

// compare
for key, delAccumFromVal := range totalDelAccumFromVal {
sumDelAccum := totalDelAccum[key]

if !sumDelAccum.Equal(delAccumFromVal) {

logDelAccums := ""
iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool {
keyLog := ddi.ValOperatorAddr.String()
if keyLog == key {
delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr)
accum := ddi.GetDelAccum(height, delegation.GetShares())
if accum.IsPositive() {
logDelAccums += fmt.Sprintf("\n\t\tdel: %v, accum: %v",
ddi.DelegatorAddr.String(),
accum.String())
}
}
return false
}
k.IterateDelegationDistInfos(ctx, iterDel)

operAddr, err := sdk.ValAddressFromBech32(key)
if err != nil {
panic(err)
}
validator := sk.Validator(ctx, operAddr)

return fmt.Errorf("delegator accum invariance: \n"+
"\tvalidator key: %v\n"+
"\tvalidator: %+v\n"+
"\tsum delegator accum: %v\n"+
"\tvalidator's total delegator accum: %v\n"+
"\tlog of delegations with accum: %v\n",
key, validator, sumDelAccum.String(),
delAccumFromVal.String(), logDelAccums)
}
}

return nil
}
}
19 changes: 18 additions & 1 deletion x/distribution/types/dec_coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ func NewDecCoin(denom string, amount int64) DecCoin {
}
}

func NewDecCoinFromDec(denom string, amount sdk.Dec) DecCoin {
return DecCoin{
Denom: denom,
Amount: amount,
}
}

func NewDecCoinFromCoin(coin sdk.Coin) DecCoin {
return DecCoin{
Denom: coin.Denom,
Expand Down Expand Up @@ -140,7 +147,7 @@ func (coins DecCoins) MulDec(d sdk.Dec) DecCoins {
return res
}

// divide all the coins by a multiple
// divide all the coins by a decimal
func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins {
res := make([]DecCoin, len(coins))
for i, coin := range coins {
Expand Down Expand Up @@ -176,3 +183,13 @@ func (coins DecCoins) AmountOf(denom string) sdk.Dec {
}
}
}

// returns the amount of a denom from deccoins
func (coins DecCoins) HasNegative() bool {
for _, coin := range coins {
if coin.Amount.IsNegative() {
return true
}
}
return false
}
Loading