Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add inflation metric #77

Merged
merged 2 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions assets/inflation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"inflation": "0.100000000000000000"
}
2 changes: 2 additions & 0 deletions pkg/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func NewApp(configPath string, filesystem fs.FS, version string) *App {
fetchersPkg.NewConsumerInfoFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewValidatorConsumersFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewConsumerCommissionFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewInflationFetcher(logger, appConfig.Chains, rpcs, tracer),
}

generators := []generatorsPkg.Generator{
Expand All @@ -120,6 +121,7 @@ func NewApp(configPath string, filesystem fs.FS, version string) *App {
generatorsPkg.NewConsumerNeedsToSignGenerator(appConfig.Chains),
generatorsPkg.NewValidatorActiveGenerator(appConfig.Chains, logger),
generatorsPkg.NewValidatorCommissionRateGenerator(appConfig.Chains, logger),
generatorsPkg.NewInflationGenerator(),
}

server := &http.Server{Addr: appConfig.ListenAddress, Handler: nil}
Expand Down
1 change: 1 addition & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
FetcherNameStakingParams FetcherName = "staking_params"
FetcherNamePrice FetcherName = "price"
FetcherNameNodeInfo FetcherName = "node_info"
FetcherNameInflation FetcherName = "inflation"

MetricsPrefix string = "cosmos_validators_exporter_"

Expand Down
94 changes: 94 additions & 0 deletions pkg/fetchers/inflation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package fetchers

import (
"context"
"main/pkg/config"
"main/pkg/constants"
"main/pkg/tendermint"
"main/pkg/types"
"sync"

"cosmossdk.io/math"

"github.com/rs/zerolog"
"go.opentelemetry.io/otel/trace"
)

type InflationFetcher struct {
Logger zerolog.Logger
Chains []*config.Chain
RPCs map[string]*tendermint.RPCWithConsumers
Tracer trace.Tracer
}

type InflationData struct {
Inflation map[string]math.LegacyDec
}

func NewInflationFetcher(
logger *zerolog.Logger,
chains []*config.Chain,
rpcs map[string]*tendermint.RPCWithConsumers,
tracer trace.Tracer,
) *InflationFetcher {
return &InflationFetcher{
Logger: logger.With().Str("component", "inflation_fetcher").Logger(),
Chains: chains,
RPCs: rpcs,
Tracer: tracer,
}
}

func (q *InflationFetcher) Fetch(
ctx context.Context,
) (interface{}, []*types.QueryInfo) {
var queryInfos []*types.QueryInfo

allInflation := map[string]math.LegacyDec{}

var wg sync.WaitGroup
var mutex sync.Mutex

for _, chain := range q.Chains {
wg.Add(1)

rpc, _ := q.RPCs[chain.Name]

go func(rpc *tendermint.RPC, chain *config.Chain) {
defer wg.Done()
inflationResponse, query, err := rpc.GetInflation(ctx)

mutex.Lock()
defer mutex.Unlock()

if query != nil {
queryInfos = append(queryInfos, query)
}

if err != nil {
q.Logger.Error().
Err(err).
Str("chain", chain.Name).
Msg("Error querying validator delegators count")
return
}

if inflationResponse == nil {
return
}

allInflation[chain.Name] = inflationResponse.Inflation

// consumer chains do not have mint module, so no inflation, therefore
// we do not calculate it here
}(rpc.RPC, chain)
}

wg.Wait()

return InflationData{Inflation: allInflation}, queryInfos
}

func (q *InflationFetcher) Name() constants.FetcherName {
return constants.FetcherNameInflation
}
205 changes: 205 additions & 0 deletions pkg/fetchers/inflation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package fetchers

import (
"context"
"errors"
"fmt"
"main/assets"
"main/pkg/config"
"main/pkg/constants"
"main/pkg/logger"
"main/pkg/tendermint"
"main/pkg/tracing"
"testing"

"github.com/stretchr/testify/assert"

"github.com/jarcoal/httpmock"
)

func TestInflationFetcherBase(t *testing.T) {
t.Parallel()

chains := []*config.Chain{
{Name: "chain1", LCDEndpoint: "example1"},
{Name: "chain2", LCDEndpoint: "example2"},
}
rpcs := map[string]*tendermint.RPCWithConsumers{
"chain1": tendermint.RPCWithConsumersFromChain(
chains[0],
10,
*logger.GetNopLogger(),
tracing.InitNoopTracer(),
),
"chain2": tendermint.RPCWithConsumersFromChain(
chains[1],
10,
*logger.GetNopLogger(),
tracing.InitNoopTracer(),
),
}
fetcher := NewInflationFetcher(
logger.GetNopLogger(),
chains,
rpcs,
tracing.InitNoopTracer(),
)

assert.NotNil(t, fetcher)
assert.Equal(t, constants.FetcherNameInflation, fetcher.Name())
}

func TestInflationFetcherQueryDisabled(t *testing.T) {
t.Parallel()

chains := []*config.Chain{{
Name: "chain",
LCDEndpoint: "example",
BechWalletPrefix: "test",
Queries: map[string]bool{"inflation": false},
}}
rpcs := map[string]*tendermint.RPCWithConsumers{
"chain": tendermint.RPCWithConsumersFromChain(
chains[0],
10,
*logger.GetDefaultLogger(),
tracing.InitNoopTracer(),
),
}
fetcher := &InflationFetcher{
Logger: *logger.GetDefaultLogger(),
Chains: chains,
RPCs: rpcs,
Tracer: tracing.InitNoopTracer(),
}
data, queries := fetcher.Fetch(context.Background())
assert.Empty(t, queries)

paramsData, ok := data.(InflationData)
assert.True(t, ok)
assert.Empty(t, paramsData.Inflation)
}

//nolint:paralleltest // disabled due to httpmock usage
func TestInflationFetcherQueryError(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

httpmock.RegisterResponder(
"GET",
"https://api.cosmos.quokkastake.io/cosmos/mint/v1beta1/inflation",
httpmock.NewErrorResponder(errors.New("error")),
)

chains := []*config.Chain{{
Name: "chain",
LCDEndpoint: "https://api.cosmos.quokkastake.io",
BechWalletPrefix: "cosmos",
}}
rpcs := map[string]*tendermint.RPCWithConsumers{
"chain": tendermint.RPCWithConsumersFromChain(
chains[0],
10,
*logger.GetNopLogger(),
tracing.InitNoopTracer(),
),
}
fetcher := &InflationFetcher{
Logger: *logger.GetNopLogger(),
Chains: chains,
RPCs: rpcs,
Tracer: tracing.InitNoopTracer(),
}
data, queries := fetcher.Fetch(context.Background())
assert.Len(t, queries, 1)
assert.False(t, queries[0].Success)

paramsData, ok := data.(InflationData)
assert.True(t, ok)
assert.Empty(t, paramsData.Inflation)
}

//nolint:paralleltest // disabled due to httpmock usage
func TestInflationFetcherNodeError(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

httpmock.RegisterResponder(
"GET",
"https://api.cosmos.quokkastake.io/cosmos/mint/v1beta1/inflation",
httpmock.NewBytesResponder(200, assets.GetBytesOrPanic("error.json")),
)

chains := []*config.Chain{{
Name: "chain",
LCDEndpoint: "https://api.cosmos.quokkastake.io",
BechWalletPrefix: "cosmos",
}}
rpcs := map[string]*tendermint.RPCWithConsumers{
"chain": tendermint.RPCWithConsumersFromChain(
chains[0],
10,
*logger.GetNopLogger(),
tracing.InitNoopTracer(),
),
}
fetcher := &InflationFetcher{
Logger: *logger.GetNopLogger(),
Chains: chains,
RPCs: rpcs,
Tracer: tracing.InitNoopTracer(),
}
data, queries := fetcher.Fetch(context.Background())
assert.Len(t, queries, 1)
assert.False(t, queries[0].Success)

paramsData, ok := data.(InflationData)
assert.True(t, ok)
assert.Empty(t, paramsData.Inflation)
}

//nolint:paralleltest // disabled due to httpmock usage
func TestInflationFetcherQuerySuccess(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

httpmock.RegisterResponder(
"GET",
"https://api.cosmos.quokkastake.io/cosmos/mint/v1beta1/inflation",
httpmock.NewBytesResponder(200, assets.GetBytesOrPanic("inflation.json")),
)

chains := []*config.Chain{{
Name: "chain",
LCDEndpoint: "https://api.cosmos.quokkastake.io",
BechWalletPrefix: "cosmos",
ConsumerChains: []*config.ConsumerChain{{
Name: "consumer",
}},
}}
rpcs := map[string]*tendermint.RPCWithConsumers{
"chain": tendermint.RPCWithConsumersFromChain(
chains[0],
10,
*logger.GetNopLogger(),
tracing.InitNoopTracer(),
),
}
fetcher := &InflationFetcher{
Logger: *logger.GetNopLogger(),
Chains: chains,
RPCs: rpcs,
Tracer: tracing.InitNoopTracer(),
}
data, queries := fetcher.Fetch(context.Background())
assert.Len(t, queries, 1)
assert.True(t, queries[0].Success)

paramsData, ok := data.(InflationData)
assert.True(t, ok)

chainData, ok := paramsData.Inflation["chain"]
assert.True(t, ok)
assert.NotNil(t, chainData)
assert.Equal(t, "0.10", fmt.Sprintf("%.2f", chainData.MustFloat64()))
}
41 changes: 41 additions & 0 deletions pkg/generators/inflation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package generators

import (
"main/pkg/constants"
fetchersPkg "main/pkg/fetchers"
statePkg "main/pkg/state"

"github.com/prometheus/client_golang/prometheus"
)

type InflationGenerator struct {
}

func NewInflationGenerator() *InflationGenerator {
return &InflationGenerator{}
}

func (g *InflationGenerator) Generate(state *statePkg.State) []prometheus.Collector {
dataRaw, ok := state.Get(constants.FetcherNameInflation)
if !ok {
return []prometheus.Collector{}
}
freak12techno marked this conversation as resolved.
Show resolved Hide resolved

inflationGauge := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: constants.MetricsPrefix + "inflation",
Help: "Chain inflation",
},
[]string{"chain"},
)

data, _ := dataRaw.(fetchersPkg.InflationData)
freak12techno marked this conversation as resolved.
Show resolved Hide resolved

for chain, inflation := range data.Inflation {
inflationGauge.With(prometheus.Labels{
"chain": chain,
}).Set(inflation.MustFloat64())
}

return []prometheus.Collector{inflationGauge}
}
Loading
Loading