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