github.com/cosmos/cosmos-sdk@v0.50.10/x/staking/keeper/invariants.go (about)

     1  package keeper
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  
     7  	"cosmossdk.io/math"
     8  
     9  	sdk "github.com/cosmos/cosmos-sdk/types"
    10  	"github.com/cosmos/cosmos-sdk/x/staking/types"
    11  )
    12  
    13  // RegisterInvariants registers all staking invariants
    14  func RegisterInvariants(ir sdk.InvariantRegistry, k *Keeper) {
    15  	ir.RegisterRoute(types.ModuleName, "module-accounts",
    16  		ModuleAccountInvariants(k))
    17  	ir.RegisterRoute(types.ModuleName, "nonnegative-power",
    18  		NonNegativePowerInvariant(k))
    19  	ir.RegisterRoute(types.ModuleName, "positive-delegation",
    20  		PositiveDelegationInvariant(k))
    21  	ir.RegisterRoute(types.ModuleName, "delegator-shares",
    22  		DelegatorSharesInvariant(k))
    23  }
    24  
    25  // AllInvariants runs all invariants of the staking module.
    26  func AllInvariants(k *Keeper) sdk.Invariant {
    27  	return func(ctx sdk.Context) (string, bool) {
    28  		res, stop := ModuleAccountInvariants(k)(ctx)
    29  		if stop {
    30  			return res, stop
    31  		}
    32  
    33  		res, stop = NonNegativePowerInvariant(k)(ctx)
    34  		if stop {
    35  			return res, stop
    36  		}
    37  
    38  		res, stop = PositiveDelegationInvariant(k)(ctx)
    39  		if stop {
    40  			return res, stop
    41  		}
    42  
    43  		return DelegatorSharesInvariant(k)(ctx)
    44  	}
    45  }
    46  
    47  // ModuleAccountInvariants checks that the bonded and notBonded ModuleAccounts pools
    48  // reflects the tokens actively bonded and not bonded
    49  func ModuleAccountInvariants(k *Keeper) sdk.Invariant {
    50  	return func(ctx sdk.Context) (string, bool) {
    51  		bonded := math.ZeroInt()
    52  		notBonded := math.ZeroInt()
    53  		bondedPool := k.GetBondedPool(ctx)
    54  		notBondedPool := k.GetNotBondedPool(ctx)
    55  		bondDenom, err := k.BondDenom(ctx)
    56  		if err != nil {
    57  			panic(err)
    58  		}
    59  
    60  		err = k.IterateValidators(ctx, func(_ int64, validator types.ValidatorI) bool {
    61  			switch validator.GetStatus() {
    62  			case types.Bonded:
    63  				bonded = bonded.Add(validator.GetTokens())
    64  			case types.Unbonding, types.Unbonded:
    65  				notBonded = notBonded.Add(validator.GetTokens())
    66  			default:
    67  				panic("invalid validator status")
    68  			}
    69  			return false
    70  		})
    71  		if err != nil {
    72  			panic(err)
    73  		}
    74  
    75  		err = k.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) bool {
    76  			for _, entry := range ubd.Entries {
    77  				notBonded = notBonded.Add(entry.Balance)
    78  			}
    79  			return false
    80  		})
    81  		if err != nil {
    82  			panic(err)
    83  		}
    84  
    85  		poolBonded := k.bankKeeper.GetBalance(ctx, bondedPool.GetAddress(), bondDenom)
    86  		poolNotBonded := k.bankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), bondDenom)
    87  		broken := !poolBonded.Amount.Equal(bonded) || !poolNotBonded.Amount.Equal(notBonded)
    88  
    89  		// Bonded tokens should equal sum of tokens with bonded validators
    90  		// Not-bonded tokens should equal unbonding delegations	plus tokens on unbonded validators
    91  		return sdk.FormatInvariant(types.ModuleName, "bonded and not bonded module account coins", fmt.Sprintf(
    92  			"\tPool's bonded tokens: %v\n"+
    93  				"\tsum of bonded tokens: %v\n"+
    94  				"not bonded token invariance:\n"+
    95  				"\tPool's not bonded tokens: %v\n"+
    96  				"\tsum of not bonded tokens: %v\n"+
    97  				"module accounts total (bonded + not bonded):\n"+
    98  				"\tModule Accounts' tokens: %v\n"+
    99  				"\tsum tokens:              %v\n",
   100  			poolBonded, bonded, poolNotBonded, notBonded, poolBonded.Add(poolNotBonded), bonded.Add(notBonded))), broken
   101  	}
   102  }
   103  
   104  // NonNegativePowerInvariant checks that all stored validators have >= 0 power.
   105  func NonNegativePowerInvariant(k *Keeper) sdk.Invariant {
   106  	return func(ctx sdk.Context) (string, bool) {
   107  		var (
   108  			msg    string
   109  			broken bool
   110  		)
   111  
   112  		iterator, err := k.ValidatorsPowerStoreIterator(ctx)
   113  		if err != nil {
   114  			panic(err)
   115  		}
   116  		for ; iterator.Valid(); iterator.Next() {
   117  			validator, err := k.GetValidator(ctx, iterator.Value())
   118  			if err != nil {
   119  				panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value()))
   120  			}
   121  
   122  			powerKey := types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx), k.ValidatorAddressCodec())
   123  
   124  			if !bytes.Equal(iterator.Key(), powerKey) {
   125  				broken = true
   126  				msg += fmt.Sprintf("power store invariance:\n\tvalidator.Power: %v"+
   127  					"\n\tkey should be: %v\n\tkey in store: %v\n",
   128  					validator.GetConsensusPower(k.PowerReduction(ctx)), powerKey, iterator.Key())
   129  			}
   130  
   131  			if validator.Tokens.IsNegative() {
   132  				broken = true
   133  				msg += fmt.Sprintf("\tnegative tokens for validator: %v\n", validator)
   134  			}
   135  		}
   136  		iterator.Close()
   137  
   138  		return sdk.FormatInvariant(types.ModuleName, "nonnegative power", fmt.Sprintf("found invalid validator powers\n%s", msg)), broken
   139  	}
   140  }
   141  
   142  // PositiveDelegationInvariant checks that all stored delegations have > 0 shares.
   143  func PositiveDelegationInvariant(k *Keeper) sdk.Invariant {
   144  	return func(ctx sdk.Context) (string, bool) {
   145  		var (
   146  			msg   string
   147  			count int
   148  		)
   149  
   150  		delegations, err := k.GetAllDelegations(ctx)
   151  		if err != nil {
   152  			panic(err)
   153  		}
   154  		for _, delegation := range delegations {
   155  			if delegation.Shares.IsNegative() {
   156  				count++
   157  				msg += fmt.Sprintf("\tdelegation with negative shares: %+v\n", delegation)
   158  			}
   159  
   160  			if delegation.Shares.IsZero() {
   161  				count++
   162  				msg += fmt.Sprintf("\tdelegation with zero shares: %+v\n", delegation)
   163  			}
   164  		}
   165  
   166  		broken := count != 0
   167  
   168  		return sdk.FormatInvariant(types.ModuleName, "positive delegations", fmt.Sprintf(
   169  			"%d invalid delegations found\n%s", count, msg)), broken
   170  	}
   171  }
   172  
   173  // DelegatorSharesInvariant checks whether all the delegator shares which persist
   174  // in the delegator object add up to the correct total delegator shares
   175  // amount stored in each validator.
   176  func DelegatorSharesInvariant(k *Keeper) sdk.Invariant {
   177  	return func(ctx sdk.Context) (string, bool) {
   178  		var (
   179  			msg    string
   180  			broken bool
   181  		)
   182  
   183  		validators, err := k.GetAllValidators(ctx)
   184  		if err != nil {
   185  			panic(err)
   186  		}
   187  
   188  		validatorsDelegationShares := map[string]math.LegacyDec{}
   189  
   190  		// initialize a map: validator -> its delegation shares
   191  		for _, validator := range validators {
   192  			validatorsDelegationShares[validator.GetOperator()] = math.LegacyZeroDec()
   193  		}
   194  
   195  		// iterate through all the delegations to calculate the total delegation shares for each validator
   196  		delegations, err := k.GetAllDelegations(ctx)
   197  		if err != nil {
   198  			panic(err)
   199  		}
   200  
   201  		for _, delegation := range delegations {
   202  			delegationValidatorAddr := delegation.GetValidatorAddr()
   203  			validatorDelegationShares := validatorsDelegationShares[delegationValidatorAddr]
   204  			validatorsDelegationShares[delegationValidatorAddr] = validatorDelegationShares.Add(delegation.Shares)
   205  		}
   206  
   207  		// for each validator, check if its total delegation shares calculated from the step above equals to its expected delegation shares
   208  		for _, validator := range validators {
   209  			expValTotalDelShares := validator.GetDelegatorShares()
   210  			calculatedValTotalDelShares := validatorsDelegationShares[validator.GetOperator()]
   211  			if !calculatedValTotalDelShares.Equal(expValTotalDelShares) {
   212  				broken = true
   213  				msg += fmt.Sprintf("broken delegator shares invariance:\n"+
   214  					"\tvalidator.DelegatorShares: %v\n"+
   215  					"\tsum of Delegator.Shares: %v\n", expValTotalDelShares, calculatedValTotalDelShares)
   216  			}
   217  		}
   218  
   219  		return sdk.FormatInvariant(types.ModuleName, "delegator shares", msg), broken
   220  	}
   221  }