Skip to content

Commit

Permalink
Merge PR cosmos#4389: stake invariance bug
Browse files Browse the repository at this point in the history
* add trouble seed

* currentStakeRoundUp is now always atleast currentStake + smallest-decimal-precision

* remove unused code

* remove debugs

* @alexanderbez comment

* compile fix

* better comment, increase tolerance to 3 smallest decimal points
  • Loading branch information
rigelrozanski authored May 25, 2019
1 parent 91dc870 commit 38f49e4
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 27 deletions.
1 change: 1 addition & 0 deletions .pending/bugfixes/sdk/4383---currentStakeR
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#4383 - currentStakeRoundUp is now always atleast currentStake + smallest-decimal-precision
2 changes: 1 addition & 1 deletion contrib/runsim/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var (
3012, 4728, 37827, 981928, 87821, 891823782,
989182, 89182391, 11, 22, 44, 77, 99, 2020,
3232, 123123, 124124, 582582, 18931893,
29892989, 30123012, 47284728, 7601778,
29892989, 30123012, 47284728, 7601778, 8090485,
}

// goroutine-safe process map
Expand Down
9 changes: 6 additions & 3 deletions simapp/sim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
lean bool
commit bool
period int
onOperation bool // TODO Remove in favor of binary search for invariant violation
)

func init() {
Expand All @@ -61,15 +62,16 @@ func init() {
flag.BoolVar(&lean, "SimulationLean", false, "lean simulation log output")
flag.BoolVar(&commit, "SimulationCommit", false, "have the simulation commit")
flag.IntVar(&period, "SimulationPeriod", 1, "run slow invariants only once every period assertions")
flag.BoolVar(&onOperation, "SimulateEveryOperation", false, "run slow invariants every operation")
}

// helper function for populating input for SimulateFromSeed
func getSimulateFromSeedInput(tb testing.TB, w io.Writer, app *SimApp) (
testing.TB, io.Writer, *baseapp.BaseApp, simulation.AppStateFn, int64,
simulation.WeightedOperations, sdk.Invariants, int, int, bool, bool) {
simulation.WeightedOperations, sdk.Invariants, int, int, bool, bool, bool) {

return tb, w, app.BaseApp, appStateFn, seed,
testAndRunTxs(app), invariants(app), numBlocks, blockSize, commit, lean
testAndRunTxs(app), invariants(app), numBlocks, blockSize, commit, lean, onOperation
}

func appStateFromGenesisFileFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time,
Expand Down Expand Up @@ -584,6 +586,7 @@ func TestAppStateDeterminism(t *testing.T) {
100,
true,
false,
false,
)
appHash := app.LastCommitID().Hash
appHashList[j] = appHash
Expand All @@ -609,7 +612,7 @@ func BenchmarkInvariants(b *testing.B) {
// 2. Run parameterized simulation (w/o invariants)
_, err := simulation.SimulateFromSeed(
b, ioutil.Discard, app.BaseApp, appStateFn, seed, testAndRunTxs(app),
[]sdk.Invariant{}, numBlocks, blockSize, commit, lean,
[]sdk.Invariant{}, numBlocks, blockSize, commit, lean, onOperation,
)
if err != nil {
fmt.Println(err)
Expand Down
5 changes: 3 additions & 2 deletions types/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ func precisionInt() *big.Int {
}

// nolint - common values
func ZeroDec() Dec { return Dec{new(big.Int).Set(zeroInt)} }
func OneDec() Dec { return Dec{precisionInt()} }
func ZeroDec() Dec { return Dec{new(big.Int).Set(zeroInt)} }
func OneDec() Dec { return Dec{precisionInt()} }
func SmallestDec() Dec { return Dec{new(big.Int).Set(oneInt)} }

// calculate the precision multiplier
func calcPrecisionMultiplier(prec int64) *big.Int {
Expand Down
23 changes: 20 additions & 3 deletions x/distribution/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,26 @@ func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, d
// we had arbitrary-precision rationals.
currentStake := val.TokensFromShares(del.GetShares())
if stake.GT(currentStake) {
// account for rounding errors due to stake being multiplied by slash fractions
currentStakeRoundUp := val.TokensFromSharesRoundUp(del.GetShares())
if stake.Equal(currentStakeRoundUp) {

// Account for rounding inconsistencies between:
// currentStake: calculated as in staking with a single computation
// stake: calculated as an accumulation of stake
// calculations across validator's distribution periods
// These inconsistencies are due to differing order of operations which
// will inevitably have different accumulated rounding and may lead to
// the smallest decimal place being one greater in stake than
// currentStake. When we calculated slashing by period, even if we
// round down for each slash fraction, it's possible due to how much is
// being rounded that we slash less when slashing by period instead of
// for when we slash without periods. In other words, the single slash,
// and the slashing by period could both be rounding down but the
// slashing by period is simply rounding down less, thus making stake >
// currentStake
//
// A small amount of this error is tolerated and corrected for,
// however any greater amount should be considered a breach in expected
// behaviour.
if stake.Equal(currentStake.Add(sdk.SmallestDec().MulInt64(3))) {
stake = currentStake
} else {
panic(fmt.Sprintf("calculated final stake for delegator %s greater than current stake"+
Expand Down
3 changes: 0 additions & 3 deletions x/simulation/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ const (

// Maximum time per block
maxTimePerBlock int64 = 10000

// TODO Remove in favor of binary search for invariant violation
onOperation bool = false
)

// TODO explain transitional matrix usage
Expand Down
21 changes: 6 additions & 15 deletions x/simulation/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ import (
// AppStateFn returns the app state json bytes, the genesis accounts, and the chain identifier
type AppStateFn func(r *rand.Rand, accs []Account, genesisTimestamp time.Time) (appState json.RawMessage, accounts []Account, chainId string)

// Simulate tests application by sending random messages.
func Simulate(t *testing.T, app *baseapp.BaseApp,
appStateFn AppStateFn, ops WeightedOperations,
invariants sdk.Invariants, numBlocks, blockSize int, commit, lean bool) (bool, error) {

time := time.Now().UnixNano()
return SimulateFromSeed(t, os.Stdout, app, appStateFn, time, ops,
invariants, numBlocks, blockSize, commit, lean)
}

// initialize the chain for the simulation
func initChain(
r *rand.Rand, params Params, accounts []Account,
Expand All @@ -56,7 +46,7 @@ func SimulateFromSeed(
tb testing.TB, w io.Writer, app *baseapp.BaseApp,
appStateFn AppStateFn, seed int64, ops WeightedOperations,
invariants sdk.Invariants,
numBlocks, blockSize int, commit, lean bool,
numBlocks, blockSize int, commit, lean, onOperation bool,
) (stopEarly bool, simError error) {

// in case we have to end early, don't os.Exit so that we can run cleanup code.
Expand Down Expand Up @@ -116,7 +106,7 @@ func SimulateFromSeed(
blockSimulator := createBlockSimulator(
testingMode, tb, t, w, params, eventStats.tally, invariants,
ops, operationQueue, timeOperationQueue,
numBlocks, blockSize, logWriter, lean)
numBlocks, blockSize, logWriter, lean, onOperation)

if !testingMode {
b.ResetTimer()
Expand Down Expand Up @@ -229,7 +219,7 @@ type blockSimFn func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, w io.Writer, params Params,
event func(string), invariants sdk.Invariants, ops WeightedOperations,
operationQueue OperationQueue, timeOperationQueue []FutureOperation,
totalNumBlocks, avgBlockSize int, logWriter LogWriter, lean bool) blockSimFn {
totalNumBlocks, avgBlockSize int, logWriter LogWriter, lean, onOperation bool) blockSimFn {

lastBlocksizeState := 0 // state for [4 * uniform distribution]
blocksize := 0
Expand Down Expand Up @@ -274,10 +264,11 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, w io.Wr
queueOperations(operationQueue, timeOperationQueue, futureOps)
if testingMode {
if onOperation {
fmt.Fprintf(w, "\rSimulating... block %d/%d, operation %d/%d. ",
header.Height, totalNumBlocks, opCount, blocksize)
eventStr := fmt.Sprintf("operation: %v", opMsg.String())
assertAllInvariants(t, app, invariants, eventStr, logWriter)
}
if opCount%50 == 0 {
} else if opCount%50 == 0 {
fmt.Fprintf(w, "\rSimulating... block %d/%d, operation %d/%d. ",
header.Height, totalNumBlocks, opCount, blocksize)
}
Expand Down

0 comments on commit 38f49e4

Please sign in to comment.