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 }