/
validators.go
142 lines (119 loc) · 3.95 KB
/
validators.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
package watcher
import (
"context"
"fmt"
"sort"
"time"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/types/query"
staking "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/kilnfi/cosmos-validator-watcher/pkg/metrics"
"github.com/kilnfi/cosmos-validator-watcher/pkg/rpc"
"github.com/rs/zerolog/log"
"github.com/shopspring/decimal"
)
type ValidatorsWatcher struct {
metrics *metrics.Metrics
validators []TrackedValidator
pool *rpc.Pool
opts ValidatorsWatcherOptions
}
type ValidatorsWatcherOptions struct {
Denom string
DenomExponent uint
}
func NewValidatorsWatcher(validators []TrackedValidator, metrics *metrics.Metrics, pool *rpc.Pool, opts ValidatorsWatcherOptions) *ValidatorsWatcher {
return &ValidatorsWatcher{
metrics: metrics,
validators: validators,
pool: pool,
opts: opts,
}
}
func (w *ValidatorsWatcher) Start(ctx context.Context) error {
ticker := time.NewTicker(30 * time.Second)
for {
node := w.pool.GetSyncedNode()
if node == nil {
log.Warn().Msg("no node available to fetch validators")
} else if err := w.fetchValidators(ctx, node); err != nil {
log.Error().Err(err).
Str("node", node.Redacted()).
Msg("failed to fetch staking validators")
}
select {
case <-ctx.Done():
return nil
case <-ticker.C:
}
}
}
func (w *ValidatorsWatcher) fetchValidators(ctx context.Context, node *rpc.Node) error {
clientCtx := (client.Context{}).WithClient(node.Client)
queryClient := staking.NewQueryClient(clientCtx)
validators, err := queryClient.Validators(ctx, &staking.QueryValidatorsRequest{
Pagination: &query.PageRequest{
Limit: 3000,
},
})
if err != nil {
return fmt.Errorf("failed to get validators: %w", err)
}
w.handleValidators(node.ChainID(), validators.Validators)
return nil
}
func (w *ValidatorsWatcher) handleValidators(chainID string, validators []staking.Validator) {
// Sort validators by tokens & status (bonded, unbonded, jailed)
sort.Sort(RankedValidators(validators))
denomExponent := w.opts.DenomExponent
if denomExponent == 0 {
denomExponent = 1
}
seatPrice := decimal.Zero
for _, val := range validators {
tokens := decimal.NewFromBigInt(val.Tokens.BigInt(), -int32(denomExponent))
if val.Status == staking.Bonded && (seatPrice.IsZero() || seatPrice.GreaterThan(tokens)) {
seatPrice = tokens
}
w.metrics.SeatPrice.WithLabelValues(chainID, w.opts.Denom).Set(seatPrice.InexactFloat64())
}
for _, tracked := range w.validators {
name := tracked.Name
for i, val := range validators {
pubkey := ed25519.PubKey{Key: val.ConsensusPubkey.Value[2:]}
address := pubkey.Address().String()
if tracked.Address == address {
var (
rank = i + 1
isBonded = val.Status == staking.Bonded
isJailed = val.Jailed
tokens = decimal.NewFromBigInt(val.Tokens.BigInt(), -int32(denomExponent))
)
w.metrics.Rank.WithLabelValues(chainID, address, name).Set(float64(rank))
w.metrics.Tokens.WithLabelValues(chainID, address, name, w.opts.Denom).Set(tokens.InexactFloat64())
w.metrics.IsBonded.WithLabelValues(chainID, address, name).Set(metrics.BoolToFloat64(isBonded))
w.metrics.IsJailed.WithLabelValues(chainID, address, name).Set(metrics.BoolToFloat64(isJailed))
break
}
}
}
}
type RankedValidators []staking.Validator
func (p RankedValidators) Len() int { return len(p) }
func (p RankedValidators) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (s RankedValidators) Less(i, j int) bool {
// Jailed validators are always last
if s[i].Jailed && !s[j].Jailed {
return false
} else if !s[i].Jailed && s[j].Jailed {
return true
}
// Not bonded validators are after bonded validators
if s[i].Status == staking.Bonded && s[j].Status != staking.Bonded {
return true
} else if s[i].Status != staking.Bonded && s[j].Status == staking.Bonded {
return false
}
return s[i].Tokens.BigInt().Cmp(s[j].Tokens.BigInt()) > 0
}