-
Notifications
You must be signed in to change notification settings - Fork 117
/
keeper.go
233 lines (190 loc) · 7.99 KB
/
keeper.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
227
228
229
230
231
232
233
package keeper
import (
"bytes"
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
params "github.com/cosmos/cosmos-sdk/x/params/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/tendermint/tendermint/libs/log"
"github.com/axelarnetwork/axelar-core/utils"
"github.com/axelarnetwork/axelar-core/x/snapshot/exported"
"github.com/axelarnetwork/axelar-core/x/snapshot/types"
)
const (
operatorPrefix = "operator_"
proxyPrefix = "proxy_"
)
// Keeper represents the snapshot keeper
type Keeper struct {
storeKey sdk.StoreKey
staking types.StakingKeeper
bank types.BankKeeper
slasher types.Slasher
cdc codec.BinaryCodec
params params.Subspace
}
// NewKeeper creates a new keeper for the staking module
func NewKeeper(cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace params.Subspace, staking types.StakingKeeper, bank types.BankKeeper, slasher types.Slasher) Keeper {
return Keeper{
storeKey: key,
cdc: cdc,
staking: staking,
bank: bank,
params: paramSpace.WithKeyTable(types.KeyTable()),
slasher: slasher,
}
}
// Logger returns the logger
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// SetParams sets the module's parameters
func (k Keeper) SetParams(ctx sdk.Context, set types.Params) {
k.params.SetParamSet(ctx, &set)
}
// GetParams gets the module's parameters
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
k.params.GetParamSet(ctx, ¶ms)
return
}
// GetMinProxyBalance returns the minimum balance proxies must hold
func (k Keeper) GetMinProxyBalance(ctx sdk.Context) sdk.Int {
var minBalance int64
k.params.Get(ctx, types.KeyMinProxyBalance, &minBalance)
return sdk.NewInt(minBalance)
}
// ActivateProxy registers a proxy address for a given operator, which can broadcast messages in the principal's name
// The proxy will be marked as active and to be included in the next snapshot by default
func (k Keeper) ActivateProxy(ctx sdk.Context, operator sdk.ValAddress, proxy sdk.AccAddress) error {
if bytes.Equal(operator, proxy) {
return fmt.Errorf("proxy address cannot be the same as the operator address")
}
if existing, ok := k.getProxiedValidator(ctx, operator); ok && !existing.Proxy.Equals(proxy) {
return fmt.Errorf(
"proxy mismatch, expected %s, got %s",
existing.Proxy.String(),
proxy.String(),
)
}
if existing, ok := k.getProxiedValidator(ctx, proxy); ok && !existing.Validator.Equals(operator) {
return fmt.Errorf(
"validator mismatch, expected %s, got %s",
existing.Validator.String(),
operator.String(),
)
}
minBalance := k.GetMinProxyBalance(ctx)
denom := k.staking.BondDenom(ctx)
if balance := k.bank.SpendableBalance(ctx, proxy, denom); balance.Amount.LT(minBalance) {
return fmt.Errorf("account %s does not have sufficient funds to become a proxy (minimum %s%s, actual %s)",
proxy.String(), minBalance.String(), denom, balance.String())
}
k.setProxiedValidator(ctx, types.NewProxiedValidator(operator, proxy, true))
return nil
}
// DeactivateProxy deactivates the proxy address for a given operator
func (k Keeper) DeactivateProxy(ctx sdk.Context, operator sdk.ValAddress) error {
val := k.staking.Validator(ctx, operator)
if val == nil {
return fmt.Errorf("validator %s is unknown", operator.String())
}
proxiedValidator, ok := k.getProxiedValidator(ctx, operator)
if !ok {
return fmt.Errorf("validator %s has no proxy registered", operator.String())
}
proxiedValidator.Active = false
k.setProxiedValidator(ctx, proxiedValidator)
return nil
}
func (k Keeper) getProxiedValidator(ctx sdk.Context, addr sdk.Address) (types.ProxiedValidator, bool) {
var proxiedValidator types.ProxiedValidator
if bz := ctx.KVStore(k.storeKey).Get([]byte(proxyPrefix + addr.String())); bz != nil {
k.cdc.MustUnmarshalLengthPrefixed(bz, &proxiedValidator)
return proxiedValidator, true
} else if bz := ctx.KVStore(k.storeKey).Get([]byte(operatorPrefix + addr.String())); bz != nil {
k.cdc.MustUnmarshalLengthPrefixed(bz, &proxiedValidator)
return proxiedValidator, true
} else {
return types.ProxiedValidator{}, false
}
}
func (k Keeper) getProxiedValidators(ctx sdk.Context) []types.ProxiedValidator {
var proxiedValidators []types.ProxiedValidator
iter := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), []byte(proxyPrefix))
defer utils.CloseLogError(iter, k.Logger(ctx))
for ; iter.Valid(); iter.Next() {
var proxiedValidator types.ProxiedValidator
k.cdc.MustUnmarshalLengthPrefixed(iter.Value(), &proxiedValidator)
proxiedValidators = append(proxiedValidators, proxiedValidator)
}
return proxiedValidators
}
func (k Keeper) setProxiedValidator(ctx sdk.Context, proxiedValidator types.ProxiedValidator) {
bz := k.cdc.MustMarshalLengthPrefixed(&proxiedValidator)
ctx.KVStore(k.storeKey).Set([]byte(operatorPrefix+proxiedValidator.Validator.String()), bz)
ctx.KVStore(k.storeKey).Set([]byte(proxyPrefix+proxiedValidator.Proxy.String()), bz)
}
// GetOperator returns the principal address for a given proxy address. Returns nil if not set.
func (k Keeper) GetOperator(ctx sdk.Context, proxy sdk.AccAddress) sdk.ValAddress {
if proxiedValidator, ok := k.getProxiedValidator(ctx, proxy); ok && proxiedValidator.Active {
return proxiedValidator.Validator
}
return nil
}
// GetProxy returns the proxy address for a given operator address. Returns nil if not set.
// The bool value denotes wether or not the proxy is active and to be included in the next snapshot
func (k Keeper) GetProxy(ctx sdk.Context, operator sdk.ValAddress) (addr sdk.AccAddress, active bool) {
if proxiedValidator, ok := k.getProxiedValidator(ctx, operator); ok {
return proxiedValidator.Proxy, proxiedValidator.Active
}
return nil, false
}
// CreateSnapshot returns a new snapshot giving each candidate its proper weight,
// or returns an error if the threshold cannot be met given the total weight of all
// validators in the system; candidates are excluded if the given filterFunc is
// evaluated to false or their weight is zero (NOTE: snapshot itself does not keep track of the threshold)
func (k Keeper) CreateSnapshot(
ctx sdk.Context,
candidates []sdk.ValAddress,
filterFunc func(exported.ValidatorI) bool,
weightFunc func(consensusPower sdk.Uint) sdk.Uint,
threshold utils.Threshold,
) (exported.Snapshot, error) {
powerReduction := k.staking.PowerReduction(ctx)
participants := make([]exported.Participant, 0, len(candidates))
for _, candidate := range candidates {
validator := k.staking.Validator(ctx, candidate)
if validator == nil || !filterFunc(validator) {
continue
}
weight := weightFunc(sdk.NewUint(uint64(validator.GetConsensusPower(powerReduction))))
// Participants with zero weight are useless for all intents and purposes.
// We filter them out here so any process dealing with snapshots doesn't have to worry about them
if weight.IsZero() {
continue
}
participants = append(participants, exported.NewParticipant(validator.GetOperator(), weight))
}
bondedWeight := sdk.ZeroUint()
k.staking.IterateBondedValidatorsByPower(ctx, func(_ int64, v stakingtypes.ValidatorI) (stop bool) {
if v == nil {
panic("nil bonded validator received")
}
weight := weightFunc(sdk.NewUint(uint64(v.GetConsensusPower(powerReduction))))
bondedWeight = bondedWeight.Add(weight)
// we do not stop until we've iterated through all bonded validators.
// Due to the unknown nature of weightFunc, every validator might contribute
// some weight
return false
})
snapshot := exported.NewSnapshot(ctx.BlockTime(), ctx.BlockHeight(), participants, bondedWeight)
participantsWeight := snapshot.GetParticipantsWeight()
if participantsWeight.LT(snapshot.CalculateMinPassingWeight(threshold)) {
return exported.Snapshot{}, fmt.Errorf("given threshold %s cannot be met (participants weight: %s, bonded weight: %s)", threshold.String(), participantsWeight, bondedWeight)
}
if err := snapshot.ValidateBasic(); err != nil {
return exported.Snapshot{}, err
}
return snapshot, nil
}