-
Notifications
You must be signed in to change notification settings - Fork 0
/
gridiron_valset.go
490 lines (437 loc) · 17.3 KB
/
gridiron_valset.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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
package contract
import (
"encoding/json"
"errors"
"strconv"
"time"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
cryptosecp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/mage-coven/gridiron/x/poe/types"
)
func DecimalFromPercentage(percent sdk.Dec) *sdk.Dec {
if percent.IsZero() {
return nil
}
res := percent.QuoInt64(100)
return &res
}
func DecimalFromProMille(promille int64) *sdk.Dec {
res := sdk.NewDec(promille).QuoInt64(1000)
return &res
}
// ValsetInitMsg Valset contract init message
// See https://github.com/mage-coven/gridiron-contracts/tree/v0.5.0-alpha/contracts/gridiron-valset/src/msg.rs
type ValsetInitMsg struct {
Admin string `json:"admin,omitempty"`
Membership string `json:"membership"`
MinPoints uint64 `json:"min_points"`
MaxValidators uint32 `json:"max_validators"`
EpochLength uint64 `json:"epoch_length"`
EpochReward sdk.Coin `json:"epoch_reward"`
InitialKeys []Validator `json:"initial_keys"`
Scaling uint32 `json:"scaling,omitempty"`
// Percentage of total accumulated fees which is subtracted from tokens minted as a rewards. A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0
FeePercentage *sdk.Dec `json:"fee_percentage,omitempty"`
// If set to true, we will auto-unjail any validator after their jailtime is over.
AutoUnjail bool `json:"auto_unjail"`
// This contract receives the rewards that don't go to the validator (set ot tg4-engagement)
DistributionContracts []DistributionContract `json:"distribution_contracts,omitempty"`
// This is the code-id of the cw2222-compliant contract used to handle rewards for the validators.
// Generally, it should the tg4-engagement code id.
ValidatorGroupCodeID uint64 `json:"validator_group_code_id"`
// When a validator joins the valset, verify they sign the first block, or jail them for a period otherwise.
// The verification happens every time the validator becomes an active validator, including when they are unjailed
// or when they just gain enough power to participate.
VerifyValidators bool `json:"verify_validators"`
// The duration in seconds to jail a validator for in case they don't sign their first epoch boundary block.
// After the period, they have to pass verification again, ad infinitum.
OfflineJailDuration uint64 `json:"offline_jail_duration"`
}
type DistributionContract struct {
Address string `json:"contract"`
// Ratio of total reward tokens for an epoch to be sent to that contract for further distribution.
// Range 0 - 1
Ratio sdk.Dec `json:"ratio"`
}
type Validator struct {
Operator string `json:"operator"`
ValidatorPubkey ValidatorPubkey `json:"validator_pubkey"`
}
// TG4ValsetExecute Valset contract validator key registration
// See https://github.com/mage-coven/gridiron-contracts/tree/v0.5.0-alpha/contracts/gridiron-valset/src/msg.rs
type TG4ValsetExecute struct {
RegisterValidatorKey *RegisterValidatorKey `json:"register_validator_key,omitempty"`
UpdateMetadata *ValidatorMetadata `json:"update_metadata,omitempty"`
// Jails validator. Can be executed only by the admin.
Jail *JailMsg `json:"jail,omitempty"`
// Unjails validator. Admin can unjail anyone anytime, others can unjail only themselves and
// only if the jail period passed.
Unjail *UnjailMsg `json:"unjail,omitempty"`
// UpdateAdmin set a new admin address
UpdateAdmin *TG4UpdateAdminMsg `json:"update_admin,omitempty"`
UpdateConfig *UpdateConfigMsg `json:"update_config,omitempty"`
}
type UpdateConfigMsg struct {
MinPoints uint64 `json:"min_points,omitempty"`
MaxValidators uint32 `json:"max_validators,omitempty"`
}
type JailMsg struct {
Operator string `json:"operator"`
// Duration for how long validator is jailed (in seconds)
Duration JailingDuration `json:"duration"`
}
type JailingDuration struct {
Duration uint64 `json:"duration,omitempty"`
Forever *struct{} `json:"forever,omitempty"`
}
type UnjailMsg struct {
// Address to unjail. Optional, as if not provided it is assumed to be the sender of the
// message (for convenience when unjailing self after the jail period).
Operator string `json:"operator,omitempty"`
}
type RegisterValidatorKey struct {
PubKey ValidatorPubkey `json:"pubkey"`
Metadata ValidatorMetadata `json:"metadata"`
}
type ValidatorMetadata struct {
// moniker defines a human-readable name for the validator.
Moniker string `json:"moniker"`
// identity defines an optional identity signature (ex. UPort or Keybase).
Identity string `json:"identity,omitempty"`
// website defines an optional website link.
Website string `json:"website,omitempty"`
// security_contact defines an optional email for security contact.
SecurityContact string `json:"security_contact,omitempty"`
// details define other optional details.
Details string `json:"details,omitempty"`
}
func MetadataFromDescription(description stakingtypes.Description) ValidatorMetadata {
return ValidatorMetadata{
Moniker: description.Moniker,
Identity: description.Identity,
Website: description.Website,
SecurityContact: description.SecurityContact,
Details: description.Details,
}
}
func (m ValidatorMetadata) ToDescription() stakingtypes.Description {
return stakingtypes.Description{
Moniker: m.Moniker,
Identity: m.Identity,
Website: m.Website,
SecurityContact: m.SecurityContact,
Details: m.Details,
}
}
// ValsetQuery will create many queries for the valset contract
// See https://github.com/mage-coven/gridiron-contracts/tree/v0.5.0-alpha/contracts/gridiron-valset/src/msg.rs
type ValsetQuery struct {
Config *struct{} `json:"configuration,omitempty"`
Epoch *struct{} `json:"epoch,omitempty"`
Validator *ValidatorQuery `json:"validator,omitempty"`
ListValidators *ListValidatorsQuery `json:"list_validators,omitempty"`
ListActiveValidators *ListValidatorsQuery `json:"list_active_validators,omitempty"`
SimulateActiveValidators *struct{} `json:"simulate_active_validators,omitempty"`
ListValidatorSlashing *ValidatorQuery `json:"list_validator_slashing,omitempty"`
}
type ValidatorQuery struct {
Operator string `json:"operator"`
}
type ListValidatorsQuery struct {
StartAfter string `json:"start_after,omitempty"`
Limit int `json:"limit,omitempty"`
}
// ValsetConfigResponse Response to `config` query
type ValsetConfigResponse struct {
Membership string `json:"membership"`
MinPoints uint64 `json:"min_points"`
MaxValidators uint32 `json:"max_validators"`
Scaling uint32 `json:"scaling,omitempty"`
EpochReward sdk.Coin `json:"epoch_reward"`
// Percentage of total accumulated fees which is subtracted from tokens minted as a rewards. A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0
FeePercentage sdk.Dec `json:"fee_percentage"`
DistributionContracts []DistributionContract `json:"distribution_contracts,omitempty"`
ValidatorGroup string `json:"validator_group"`
AutoUnjail bool `json:"auto_unjail"`
}
// ValsetEpochQueryResponse Response to `config` query
type ValsetEpochResponse struct {
// Number of seconds in one epoch. We update the Tendermint validator set only once per epoch.
EpochLength uint64 `json:"epoch_length"`
// The current epoch # (block.time/epoch_length, rounding down)
CurrentEpoch uint64 `json:"current_epoch"`
// The last time we updated the validator set - block time (in seconds)
LastUpdateTime uint64 `json:"last_update_time"`
// The last time we updated the validator set - block height
LastUpdateHeight uint64 `json:"last_update_height"`
}
type OperatorResponse struct {
Operator string `json:"operator"`
Pubkey ValidatorPubkey `json:"pubkey"`
Metadata ValidatorMetadata `json:"metadata"`
JailedUntil *JailingPeriod `json:"jailed_until,omitempty"`
ActiveValidator bool `json:"active_validator"`
}
type JailingPeriod struct {
Start time.Time `json:"start,omitempty"`
End JailingEnd `json:"end,omitempty"`
}
type JailingEnd struct {
Forever bool `json:"forever,omitempty"`
Until time.Time `json:"until,omitempty"`
}
func (j *JailingPeriod) UnmarshalJSON(data []byte) error {
var r struct {
Start string `json:"start,omitempty"`
End struct {
Until string `json:"until,omitempty"`
Forever *struct{} `json:"forever,omitempty"`
} `json:"end,omitempty"`
}
if err := json.Unmarshal(data, &r); err != nil {
return err
}
if r.Start != "" {
start, err := strconv.ParseInt(r.Start, 10, 64)
if err != nil {
return err
}
j.Start = time.Unix(0, start).UTC()
}
switch {
case r.End.Forever != nil:
j.End.Forever = true
case r.End.Until != "":
until, err := strconv.ParseInt(r.End.Until, 10, 64)
if err != nil {
return err
}
j.End.Until = time.Unix(0, until).UTC()
default:
return errors.New("unknown json data: ")
}
return nil
}
func (v OperatorResponse) ToValidator() (stakingtypes.Validator, error) {
pubKey, err := toCosmosPubKey(v.Pubkey)
if err != nil {
return stakingtypes.Validator{}, sdkerrors.Wrap(err, "convert to cosmos key")
}
any, err := codectypes.NewAnyWithValue(pubKey)
if err != nil {
return stakingtypes.Validator{}, sdkerrors.Wrap(err, "convert to any type")
}
status := stakingtypes.Bonded
if !v.ActiveValidator {
status = stakingtypes.Unbonded
}
return stakingtypes.Validator{
OperatorAddress: v.Operator,
ConsensusPubkey: any,
Description: v.Metadata.ToDescription(),
DelegatorShares: sdk.OneDec(),
Status: status,
Jailed: v.JailedUntil != nil,
}, nil
}
func toCosmosPubKey(key ValidatorPubkey) (cryptotypes.PubKey, error) {
switch {
case key.Ed25519 != nil:
return &ed25519.PubKey{Key: key.Ed25519}, nil
case key.Secp256k1 != nil:
return &cryptosecp256k1.PubKey{Key: key.Secp256k1}, nil
default:
return nil, types.ErrValidatorPubKeyTypeNotSupported
}
}
type ValidatorInfo struct {
Operator string `json:"operator"`
ValidatorPubkey ValidatorPubkey `json:"validator_pubkey"`
Power uint64 `json:"power"`
}
type ValidatorResponse struct {
Validator *OperatorResponse `json:"validator"`
}
type ListValidatorsResponse struct {
Validators []OperatorResponse `json:"validators"`
}
// PaginationCursor implements PageableResult.PaginationCursor with a custom key which is implemented in the contract.
func (l ListValidatorsResponse) PaginationCursor(_ []byte) (PaginationCursor, error) {
if len(l.Validators) == 0 {
return nil, nil
}
return PaginationCursor(l.Validators[len(l.Validators)-1].Operator), nil
}
type ListActiveValidatorsResponse struct {
Validators []ValidatorInfo `json:"validators"`
}
func (l ListActiveValidatorsResponse) PaginationCursor(_ []byte) (PaginationCursor, error) {
if len(l.Validators) == 0 {
return nil, nil
}
return PaginationCursor(l.Validators[len(l.Validators)-1].Operator), nil
}
type ListValidatorSlashingResponse struct {
Operator string `json:"operator"`
StartHeight uint64 `json:"start_height"`
Slashing []ValidatorSlashing `json:"slashing"`
}
type ValidatorSlashing struct {
Height uint64 `json:"slash_height"`
Portion sdk.Dec `json:"portion"`
}
type SimulateActiveValidatorsResponse = ListActiveValidatorsResponse
func QueryValsetEpoch(ctx sdk.Context, k types.SmartQuerier, valset sdk.AccAddress) (*ValsetEpochResponse, error) {
query := ValsetQuery{Epoch: &struct{}{}}
var response ValsetEpochResponse
err := doQuery(ctx, k, valset, query, &response)
return &response, err
}
func SimulateActiveValidators(ctx sdk.Context, k types.SmartQuerier, valset sdk.AccAddress) ([]ValidatorInfo, error) {
query := ValsetQuery{SimulateActiveValidators: &struct{}{}}
var response ListActiveValidatorsResponse
err := doQuery(ctx, k, valset, query, &response)
return response.Validators, err
}
type ValsetContractAdapter struct {
BaseContractAdapter
}
// NewValsetContractAdapter constructor
func NewValsetContractAdapter(contractAddr sdk.AccAddress, twasmKeeper types.TWasmKeeper, addressLookupErr error) *ValsetContractAdapter {
return &ValsetContractAdapter{
BaseContractAdapter: NewBaseContractAdapter(
contractAddr,
twasmKeeper,
addressLookupErr,
),
}
}
// QueryValidator query a single validator and map to the sdk type. returns nil when not found
func (v ValsetContractAdapter) QueryValidator(ctx sdk.Context, opAddr sdk.AccAddress) (*stakingtypes.Validator, error) {
query := ValsetQuery{Validator: &ValidatorQuery{Operator: opAddr.String()}}
var rsp ValidatorResponse
err := v.doQuery(ctx, query, &rsp)
if err != nil {
return nil, sdkerrors.Wrap(err, "contract query")
}
if rsp.Validator == nil {
return nil, nil
}
val, err := rsp.Validator.ToValidator()
return &val, err
}
// QueryRawValidator query a single validator as the contract returns it. returns nil when not found
func (v ValsetContractAdapter) QueryRawValidator(ctx sdk.Context, opAddr sdk.AccAddress) (ValidatorResponse, error) {
query := ValsetQuery{Validator: &ValidatorQuery{Operator: opAddr.String()}}
var rsp ValidatorResponse
err := v.doQuery(ctx, query, &rsp)
return rsp, sdkerrors.Wrap(err, "contract query")
}
// ListValidators query all validators
func (v ValsetContractAdapter) ListValidators(ctx sdk.Context, pagination *Paginator) ([]stakingtypes.Validator, PaginationCursor, error) {
startAfter, limit := pagination.ToQuery()
query := ValsetQuery{ListValidators: &ListValidatorsQuery{StartAfter: startAfter, Limit: limit}}
var rsp ListValidatorsResponse
cursor, err := v.doPageableQuery(ctx, query, &rsp)
if err != nil {
return nil, nil, sdkerrors.Wrap(err, "contract query")
}
vals := make([]stakingtypes.Validator, len(rsp.Validators))
for i, v := range rsp.Validators {
vals[i], err = v.ToValidator()
if err != nil {
return nil, nil, err
}
}
// always return the cursor and let the client figure out if they want to do another call
// a simple len(res.validators) < limit check to clear the cursor would not work because
// the contract also has a max limit that may be < our limit
return vals, cursor, nil
}
// IterateActiveValidators iterate through the active validator set.
func (v ValsetContractAdapter) IterateActiveValidators(ctx sdk.Context, callback func(ValidatorInfo) bool, pagination *Paginator) error {
startAfter, limit := pagination.ToQuery()
var rsp ListActiveValidatorsResponse
cursor, err := v.doPageableQuery(ctx, ValsetQuery{ListActiveValidators: &ListValidatorsQuery{StartAfter: startAfter, Limit: limit}}, &rsp)
if err != nil {
return err
}
for !cursor.Empty() {
for _, v := range rsp.Validators {
if callback(v) {
return nil
}
}
newCursor, err := v.doPageableQuery(ctx, ValsetQuery{ListActiveValidators: &ListValidatorsQuery{StartAfter: cursor.String()}}, &rsp)
if err != nil {
return err
}
cursor = newCursor
}
return nil
}
func (v ValsetContractAdapter) ListValidatorSlashing(ctx sdk.Context, opAddr sdk.AccAddress) ([]ValidatorSlashing, error) {
query := ValsetQuery{ListValidatorSlashing: &ValidatorQuery{Operator: opAddr.String()}}
var rsp ListValidatorSlashingResponse
err := v.doQuery(ctx, query, &rsp)
if err != nil {
return nil, sdkerrors.Wrap(err, "contract query")
}
return rsp.Slashing, nil
}
// QueryConfig query contract configuration
func (v ValsetContractAdapter) QueryConfig(ctx sdk.Context) (*ValsetConfigResponse, error) {
if v.addressLookupErr != nil {
return nil, v.addressLookupErr
}
query := ValsetQuery{Config: &struct{}{}}
var rsp ValsetConfigResponse
err := v.doQuery(ctx, query, &rsp)
if err != nil {
return nil, sdkerrors.Wrap(err, "contract query")
}
return &rsp, err
}
// UpdateAdmin sets a new admin address
func (v ValsetContractAdapter) UpdateAdmin(ctx sdk.Context, newAdmin sdk.AccAddress, sender sdk.AccAddress) error {
bech32AdminAddr := newAdmin.String()
msg := TG4ValsetExecute{
UpdateAdmin: &TG4UpdateAdminMsg{NewAdmin: &bech32AdminAddr},
}
return v.doExecute(ctx, msg, sender)
}
// JailValidator is for testing propose only. On a chain the OC does this
func (v ValsetContractAdapter) JailValidator(ctx sdk.Context, nodeOperator sdk.AccAddress, duration time.Duration, forever bool, sender sdk.AccAddress) error {
if time.Duration(int64(duration.Seconds()))*time.Second != duration {
return sdkerrors.Wrap(types.ErrInvalid, "must fit into seconds")
}
var jailDuration JailingDuration
switch {
case forever && duration > time.Nanosecond:
return sdkerrors.Wrap(types.ErrInvalid, "either duration or forever")
case forever:
jailDuration = JailingDuration{Forever: &struct{}{}}
case duration > time.Nanosecond:
jailDuration = JailingDuration{Duration: uint64(duration.Seconds())}
default:
return types.ErrEmpty
}
msg := TG4ValsetExecute{
Jail: &JailMsg{
Operator: nodeOperator.String(),
Duration: jailDuration,
},
}
return v.doExecute(ctx, msg, sender)
}
func (v ValsetContractAdapter) UnjailValidator(ctx sdk.Context, sender sdk.AccAddress) error {
msg := TG4ValsetExecute{
Unjail: &UnjailMsg{},
}
return v.doExecute(ctx, msg, sender)
}