From b1262f48d7d871aef8c536f967d9e5d1c1b86108 Mon Sep 17 00:00:00 2001 From: Sergey Date: Wed, 10 Jul 2024 02:53:33 +0300 Subject: [PATCH] feat: allow ignoring denoms --- config.example.toml | 12 ++- pkg/config/denom_info.go | 17 ++-- pkg/generators/active_set_tokens.go | 4 + pkg/generators/active_set_tokens_test.go | 38 ++++++++ pkg/generators/balance.go | 8 ++ pkg/generators/balance_test.go | 7 ++ pkg/generators/commission.go | 4 + pkg/generators/commission_test.go | 5 ++ pkg/generators/rewards.go | 4 + pkg/generators/rewards_test.go | 5 ++ pkg/generators/self_delegation.go | 4 + pkg/generators/self_delegations_test.go | 6 ++ pkg/generators/single_validator_info.go | 4 + pkg/generators/single_validator_info_test.go | 91 ++++++++++++++++++++ pkg/generators/validators_info.go | 3 + pkg/generators/validators_info_test.go | 50 +++++++++++ 16 files changed, 256 insertions(+), 6 deletions(-) diff --git a/config.example.toml b/config.example.toml index 80ae970..593d113 100644 --- a/config.example.toml +++ b/config.example.toml @@ -36,11 +36,21 @@ base-denom = "uatom" # 4. denom-exponent # The exponent of a coefficient you need to multiply base denom to get 1 token on Coingecko. # Optional, defaults to 6 (so a coefficient == 1_000_000). +# 5. ignore +# Whether the denom should be ignored and not returned in metrics. +# If specified as true, the exporter would not export this value as metric value in all the places +# when it does a denom conversion (rewards, commission, self-delegation, voting power etc.) +# Ignoring a base denom is quite pointless as it would effectively strip most of the useful metrics. +# Useful for chains where there are tokens of really low value (see Cosmos Hub and their stXXX dust +# tokens for example). +# Optional, defaults to false (so, not ignored). +# Keep in mind that if ignore = false, but coingecko-currency is provided, it will still fetch +# Coingecko price for this token. # # You can calculate the actual price of something by multiplying the metric that has denoms by the # `cosmos_validators_exporter_price` metric (by chain + denom). denoms = [ - { denom = "uatom", display-denom = "atom", coingecko-currency = "cosmos", denom-exponent = 6 }, + { denom = "uatom", display-denom = "atom", coingecko-currency = "cosmos", denom-exponent = 6, ignore = false }, ] # Bech32 prefix for a wallet address (example: "cosmos" for a Cosmos wallet). If omitted, # the self-delegation metric will not be present. diff --git a/pkg/config/denom_info.go b/pkg/config/denom_info.go index f837331..d0908f0 100644 --- a/pkg/config/denom_info.go +++ b/pkg/config/denom_info.go @@ -4,13 +4,16 @@ import ( "errors" "main/pkg/types" "math" + + "github.com/guregu/null/v5" ) type DenomInfo struct { - Denom string `toml:"denom"` - DenomExponent int64 `default:"6" toml:"denom-exponent"` - DisplayDenom string `toml:"display-denom"` - CoingeckoCurrency string `toml:"coingecko-currency"` + Denom string `toml:"denom"` + DenomExponent int64 `default:"6" toml:"denom-exponent"` + DisplayDenom string `toml:"display-denom"` + CoingeckoCurrency string `toml:"coingecko-currency"` + Ignore null.Bool `default:"false" toml:"ignore"` } func (d *DenomInfo) Validate() error { @@ -18,7 +21,7 @@ func (d *DenomInfo) Validate() error { return errors.New("empty denom name") } - if d.DisplayDenom == "" { + if d.DisplayDenom == "" && !d.Ignore.Bool { return errors.New("empty display denom name") } @@ -55,6 +58,10 @@ func (d DenomInfos) Find(denom string) *DenomInfo { func (d DenomInfos) Convert(amount *types.Amount) *types.Amount { for _, info := range d { if info.Denom == amount.Denom { + if info.Ignore.Bool { + return nil + } + return &types.Amount{ Amount: amount.Amount / math.Pow(10, float64(info.DenomExponent)), Denom: info.DisplayDenom, diff --git a/pkg/generators/active_set_tokens.go b/pkg/generators/active_set_tokens.go index 927b95d..2a17c2d 100644 --- a/pkg/generators/active_set_tokens.go +++ b/pkg/generators/active_set_tokens.go @@ -67,6 +67,10 @@ func (g *ActiveSetTokensGenerator) Generate(state *statePkg.State) []prometheus. Denom: chain.BaseDenom, }) + if lastValidatorAmount == nil { + continue + } + if chainStakingParams != nil && len(activeValidators) >= chainStakingParams.StakingParams.MaxValidators { activeSetTokensGauge.With(prometheus.Labels{ "chain": chain.Name, diff --git a/pkg/generators/active_set_tokens_test.go b/pkg/generators/active_set_tokens_test.go index 0eaceb4..7b4c397 100644 --- a/pkg/generators/active_set_tokens_test.go +++ b/pkg/generators/active_set_tokens_test.go @@ -8,6 +8,8 @@ import ( "main/pkg/types" "testing" + "github.com/guregu/null/v5" + "cosmossdk.io/math" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -147,3 +149,39 @@ func TestActiveSetTokensGeneratorEnoughValidators(t *testing.T) { "denom": "atom", })), 0.01) } + +func TestActiveSetTokensGeneratorDenomIgnored(t *testing.T) { + t.Parallel() + + chains := []*config.Chain{{ + Name: "chain", + BaseDenom: "uatom", + Denoms: config.DenomInfos{{Denom: "uatom", Ignore: null.BoolFrom(true)}}, + }} + state := statePkg.NewState() + state.Set(constants.FetcherNameValidators, fetchers.ValidatorsData{ + Validators: map[string]*types.ValidatorsResponse{ + "chain": { + Validators: []types.Validator{ + {DelegatorShares: math.LegacyMustNewDecFromStr("2000000"), Status: constants.ValidatorStatusBonded}, + {DelegatorShares: math.LegacyMustNewDecFromStr("1000000")}, + {DelegatorShares: math.LegacyMustNewDecFromStr("3000000"), Status: constants.ValidatorStatusBonded}, + }, + }, + }, + }) + state.Set(constants.FetcherNameStakingParams, fetchers.StakingParamsData{ + Params: map[string]*types.StakingParamsResponse{ + "chain": { + StakingParams: types.StakingParams{MaxValidators: 2}, + }, + }, + }) + generator := NewActiveSetTokensGenerator(chains) + results := generator.Generate(state) + assert.NotEmpty(t, results) + + gauge, ok := results[0].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.Zero(t, testutil.CollectAndCount(gauge)) +} diff --git a/pkg/generators/balance.go b/pkg/generators/balance.go index dcdd4ee..eaf3e53 100644 --- a/pkg/generators/balance.go +++ b/pkg/generators/balance.go @@ -48,6 +48,10 @@ func (g *BalanceGenerator) Generate(state *statePkg.State) []prometheus.Collecto for _, balance := range validatorBalances { amountConverted := consumer.Denoms.Convert(&balance) + if amountConverted == nil { + continue + } + walletBalanceTokens.With(prometheus.Labels{ "chain": consumer.Name, "address": validator.Address, @@ -70,6 +74,10 @@ func (g *BalanceGenerator) Generate(state *statePkg.State) []prometheus.Collecto for _, balance := range validatorBalances { amountConverted := chain.Denoms.Convert(&balance) + if amountConverted == nil { + continue + } + walletBalanceTokens.With(prometheus.Labels{ "chain": chain.Name, "address": validator.Address, diff --git a/pkg/generators/balance_test.go b/pkg/generators/balance_test.go index 6c7a55f..8005d78 100644 --- a/pkg/generators/balance_test.go +++ b/pkg/generators/balance_test.go @@ -8,6 +8,8 @@ import ( "main/pkg/types" "testing" + "github.com/guregu/null/v5" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -33,12 +35,14 @@ func TestBalanceGeneratorNotEmptyState(t *testing.T) { "validator": { {Amount: 100000, Denom: "uatom"}, {Amount: 200000, Denom: "ustake"}, + {Amount: 300000, Denom: "uignored"}, }, }, "consumer": { "validator": { {Amount: 100000, Denom: "untrn"}, {Amount: 200000, Denom: "ustake"}, + {Amount: 300000, Denom: "uignored"}, }, }, }, @@ -50,11 +54,13 @@ func TestBalanceGeneratorNotEmptyState(t *testing.T) { Validators: []config.Validator{{Address: "validator"}, {Address: "validator2"}}, Denoms: config.DenomInfos{ {Denom: "uatom", DisplayDenom: "atom", DenomExponent: 6}, + {Denom: "uignored", Ignore: null.BoolFrom(true)}, }, ConsumerChains: []*config.ConsumerChain{{ Name: "consumer", Denoms: config.DenomInfos{ {Denom: "untrn", DisplayDenom: "ntrn", DenomExponent: 6}, + {Denom: "uignored", Ignore: null.BoolFrom(true)}, }, }}, }, @@ -72,6 +78,7 @@ func TestBalanceGeneratorNotEmptyState(t *testing.T) { gauge, ok := results[0].(*prometheus.GaugeVec) assert.True(t, ok) + assert.Equal(t, 4, testutil.CollectAndCount(gauge)) assert.InEpsilon(t, 0.1, testutil.ToFloat64(gauge.With(prometheus.Labels{ "chain": "chain", "address": "validator", diff --git a/pkg/generators/commission.go b/pkg/generators/commission.go index 6d233aa..9bbdd53 100644 --- a/pkg/generators/commission.go +++ b/pkg/generators/commission.go @@ -47,6 +47,10 @@ func (g *CommissionGenerator) Generate(state *statePkg.State) []prometheus.Colle for _, balance := range validatorCommissions { amountConverted := chain.Denoms.Convert(&balance) + if amountConverted == nil { + continue + } + commissionUnclaimedTokens.With(prometheus.Labels{ "chain": chain.Name, "address": validator.Address, diff --git a/pkg/generators/commission_test.go b/pkg/generators/commission_test.go index e10c319..36ed1df 100644 --- a/pkg/generators/commission_test.go +++ b/pkg/generators/commission_test.go @@ -8,6 +8,8 @@ import ( "main/pkg/types" "testing" + "github.com/guregu/null/v5" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -33,6 +35,7 @@ func TestCommissionGeneratorNotEmptyState(t *testing.T) { "validator": []types.Amount{ {Amount: 100000, Denom: "uatom"}, {Amount: 200000, Denom: "ustake"}, + {Amount: 300000, Denom: "uignored"}, }, }, }, @@ -44,6 +47,7 @@ func TestCommissionGeneratorNotEmptyState(t *testing.T) { Validators: []config.Validator{{Address: "validator"}, {Address: "validator2"}}, Denoms: config.DenomInfos{ {Denom: "uatom", DisplayDenom: "atom", DenomExponent: 6}, + {Denom: "uignored", Ignore: null.BoolFrom(true)}, }, }, { @@ -57,6 +61,7 @@ func TestCommissionGeneratorNotEmptyState(t *testing.T) { gauge, ok := results[0].(*prometheus.GaugeVec) assert.True(t, ok) + assert.Equal(t, 2, testutil.CollectAndCount(gauge)) assert.InEpsilon(t, 0.1, testutil.ToFloat64(gauge.With(prometheus.Labels{ "chain": "chain", "address": "validator", diff --git a/pkg/generators/rewards.go b/pkg/generators/rewards.go index add3a50..b7902e2 100644 --- a/pkg/generators/rewards.go +++ b/pkg/generators/rewards.go @@ -47,6 +47,10 @@ func (g *RewardsGenerator) Generate(state *statePkg.State) []prometheus.Collecto for _, balance := range validatorRewards { amountConverted := chain.Denoms.Convert(&balance) + if amountConverted == nil { + continue + } + selfDelegationRewardsTokens.With(prometheus.Labels{ "chain": chain.Name, "address": validator.Address, diff --git a/pkg/generators/rewards_test.go b/pkg/generators/rewards_test.go index 485c067..64e204a 100644 --- a/pkg/generators/rewards_test.go +++ b/pkg/generators/rewards_test.go @@ -8,6 +8,8 @@ import ( "main/pkg/types" "testing" + "github.com/guregu/null/v5" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -33,6 +35,7 @@ func TestRewardsGeneratorNotEmptyState(t *testing.T) { "validator": []types.Amount{ {Amount: 100000, Denom: "uatom"}, {Amount: 200000, Denom: "ustake"}, + {Amount: 300000, Denom: "uignored"}, }, }, }, @@ -44,6 +47,7 @@ func TestRewardsGeneratorNotEmptyState(t *testing.T) { Validators: []config.Validator{{Address: "validator"}, {Address: "validator2"}}, Denoms: config.DenomInfos{ {Denom: "uatom", DisplayDenom: "atom", DenomExponent: 6}, + {Denom: "uignored", Ignore: null.BoolFrom(true)}, }, }, { @@ -57,6 +61,7 @@ func TestRewardsGeneratorNotEmptyState(t *testing.T) { gauge, ok := results[0].(*prometheus.GaugeVec) assert.True(t, ok) + assert.Equal(t, 2, testutil.CollectAndCount(gauge)) assert.InEpsilon(t, 0.1, testutil.ToFloat64(gauge.With(prometheus.Labels{ "chain": "chain", "address": "validator", diff --git a/pkg/generators/self_delegation.go b/pkg/generators/self_delegation.go index af660df..7cebf5e 100644 --- a/pkg/generators/self_delegation.go +++ b/pkg/generators/self_delegation.go @@ -46,6 +46,10 @@ func (g *SelfDelegationGenerator) Generate(state *statePkg.State) []prometheus.C } amountConverted := chain.Denoms.Convert(validatorSelfDelegation) + if amountConverted == nil { + continue + } + selfDelegatedTokensGauge.With(prometheus.Labels{ "chain": chain.Name, "address": validator.Address, diff --git a/pkg/generators/self_delegations_test.go b/pkg/generators/self_delegations_test.go index 567a78c..1a907db 100644 --- a/pkg/generators/self_delegations_test.go +++ b/pkg/generators/self_delegations_test.go @@ -8,6 +8,8 @@ import ( "main/pkg/types" "testing" + "github.com/guregu/null/v5" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -32,6 +34,7 @@ func TestSelfDelegationGeneratorNotEmptyState(t *testing.T) { "chain": { "validator": {Amount: 100000, Denom: "uatom"}, "validator2": {Amount: 200000, Denom: "ustake"}, + "validator3": {Amount: 300000, Denom: "uignored"}, }, }, }) @@ -43,9 +46,11 @@ func TestSelfDelegationGeneratorNotEmptyState(t *testing.T) { {Address: "validator"}, {Address: "validator2"}, {Address: "validator3"}, + {Address: "validator4"}, }, Denoms: config.DenomInfos{ {Denom: "uatom", DisplayDenom: "atom", DenomExponent: 6}, + {Denom: "uignored", Ignore: null.BoolFrom(true)}, }, }, { @@ -59,6 +64,7 @@ func TestSelfDelegationGeneratorNotEmptyState(t *testing.T) { gauge, ok := results[0].(*prometheus.GaugeVec) assert.True(t, ok) + assert.Equal(t, 2, testutil.CollectAndCount(gauge)) assert.InEpsilon(t, 0.1, testutil.ToFloat64(gauge.With(prometheus.Labels{ "chain": "chain", "address": "validator", diff --git a/pkg/generators/single_validator_info.go b/pkg/generators/single_validator_info.go index c3a3c33..798faed 100644 --- a/pkg/generators/single_validator_info.go +++ b/pkg/generators/single_validator_info.go @@ -148,6 +148,10 @@ func (g *SingleValidatorInfoGenerator) Generate(state *statePkg.State) []prometh Denom: chain.BaseDenom, }) + if delegationsAmount == nil { + continue + } + delegationsGauge.With(prometheus.Labels{ "chain": chain.Name, "address": validatorAddr.Address, diff --git a/pkg/generators/single_validator_info_test.go b/pkg/generators/single_validator_info_test.go index ec28e27..dd7aad7 100644 --- a/pkg/generators/single_validator_info_test.go +++ b/pkg/generators/single_validator_info_test.go @@ -9,6 +9,8 @@ import ( "main/pkg/types" "testing" + "github.com/guregu/null/v5" + "cosmossdk.io/math" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -162,3 +164,92 @@ func TestSingleValidatorInfoGeneratorActive(t *testing.T) { "denom": "atom", })), 0.01) } + +func TestSingleValidatorInfoIgnoredBaseDenom(t *testing.T) { + t.Parallel() + + chains := []*config.Chain{{ + Name: "chain", + BaseDenom: "uatom", + Denoms: config.DenomInfos{{Denom: "uatom", Ignore: null.BoolFrom(true)}}, + Validators: []config.Validator{{Address: "cosmosvaloper1xqz9pemz5e5zycaa89kys5aw6m8rhgsvw4328e"}}, + }} + state := statePkg.NewState() + state.Set(constants.FetcherNameValidators, fetchers.ValidatorsData{ + Validators: map[string]*types.ValidatorsResponse{ + "chain": { + Validators: []types.Validator{ + { + DelegatorShares: math.LegacyMustNewDecFromStr("3000000"), + OperatorAddress: "cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en", + Status: constants.ValidatorStatusBonded, + }, + { + DelegatorShares: math.LegacyMustNewDecFromStr("2000000"), + OperatorAddress: "cosmosvaloper1xqz9pemz5e5zycaa89kys5aw6m8rhgsvw4328e", + Status: constants.ValidatorStatusBonded, + Description: types.ValidatorDescription{ + Moniker: "moniker", + SecurityContact: "contact", + Website: "website", + Details: "details", + Identity: "identity", + }, + Commission: types.ValidatorCommission{ + CommissionRates: types.ValidatorCommissionRates{ + Rate: math.LegacyMustNewDecFromStr("0.05"), + MaxRate: math.LegacyMustNewDecFromStr("0.2"), + MaxChangeRate: math.LegacyMustNewDecFromStr("0.01"), + }, + }, + }, + { + DelegatorShares: math.LegacyMustNewDecFromStr("1000000"), + OperatorAddress: "cosmosvaloper14lultfckehtszvzw4ehu0apvsr77afvyju5zzy", + Status: constants.ValidatorStatusBonded, + }, + }, + }, + }, + }) + generator := NewSingleValidatorInfoGenerator(chains, loggerPkg.GetNopLogger()) + results := generator.Generate(state) + assert.Len(t, results, 5) + + validatorInfoGauge, ok := results[0].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.InEpsilon(t, float64(1), testutil.ToFloat64(validatorInfoGauge.With(prometheus.Labels{ + "chain": "chain", + "address": "cosmosvaloper1xqz9pemz5e5zycaa89kys5aw6m8rhgsvw4328e", + "moniker": "moniker", + "details": "details", + "identity": "identity", + "security_contact": "contact", + "website": "website", + })), 0.01) + + isJailed, ok := results[1].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.Zero(t, testutil.ToFloat64(isJailed.With(prometheus.Labels{ + "chain": "chain", + "address": "cosmosvaloper1xqz9pemz5e5zycaa89kys5aw6m8rhgsvw4328e", + }))) + + commissionMaxGauge, ok := results[2].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.InEpsilon(t, 0.2, testutil.ToFloat64(commissionMaxGauge.With(prometheus.Labels{ + "chain": "chain", + "address": "cosmosvaloper1xqz9pemz5e5zycaa89kys5aw6m8rhgsvw4328e", + })), 0.01) + + commissionMaxChangeGauge, ok := results[3].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.InEpsilon(t, 0.01, testutil.ToFloat64(commissionMaxChangeGauge.With(prometheus.Labels{ + "chain": "chain", + "address": "cosmosvaloper1xqz9pemz5e5zycaa89kys5aw6m8rhgsvw4328e", + })), 0.01) + + delegationsGauge, ok := results[4].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.Zero(t, testutil.CollectAndCount(delegationsGauge)) +} diff --git a/pkg/generators/validators_info.go b/pkg/generators/validators_info.go index ffe2410..9fc0fd9 100644 --- a/pkg/generators/validators_info.go +++ b/pkg/generators/validators_info.go @@ -76,6 +76,9 @@ func (g *ValidatorsInfoGenerator) Generate(state *statePkg.State) []prometheus.C Denom: chain.BaseDenom, } totalBondedAmountConverted := chain.Denoms.Convert(totalBondedAmount) + if totalBondedAmountConverted == nil { + continue + } totalBondedTokensGauge.With(prometheus.Labels{ "chain": chain.Name, diff --git a/pkg/generators/validators_info_test.go b/pkg/generators/validators_info_test.go index 0f11901..3bb1f0a 100644 --- a/pkg/generators/validators_info_test.go +++ b/pkg/generators/validators_info_test.go @@ -8,6 +8,8 @@ import ( "main/pkg/types" "testing" + "github.com/guregu/null/v5" + "cosmossdk.io/math" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -85,6 +87,54 @@ func TestValidatorsInfoGeneratorNotConsumer(t *testing.T) { })), 0.01) } +func TestValidatorsInfoGeneratorNotConsumerIgnoredBaseDenom(t *testing.T) { + t.Parallel() + + state := statePkg.NewState() + state.Set(constants.FetcherNameValidators, fetchers.ValidatorsData{ + Validators: map[string]*types.ValidatorsResponse{ + "chain": { + Validators: []types.Validator{ + { + DelegatorShares: math.LegacyMustNewDecFromStr("2000000"), + OperatorAddress: "cosmosvaloper1c4k24jzduc365kywrsvf5ujz4ya6mwympnc4en", + Status: constants.ValidatorStatusBonded, + }, + { + DelegatorShares: math.LegacyMustNewDecFromStr("1000000"), + OperatorAddress: "cosmosvaloper1xqz9pemz5e5zycaa89kys5aw6m8rhgsvw4328e", + }, + { + DelegatorShares: math.LegacyMustNewDecFromStr("3000000"), + OperatorAddress: "cosmosvaloper14lultfckehtszvzw4ehu0apvsr77afvyju5zzy", + Status: constants.ValidatorStatusBonded, + }, + }, + }, + }, + }) + state.Set(constants.FetcherNameConsumerValidators, fetchers.ConsumerValidatorsData{}) + + chains := []*config.Chain{{ + Name: "chain", + BaseDenom: "uatom", + Denoms: config.DenomInfos{{Denom: "uatom", Ignore: null.BoolFrom(true)}}, + }, {Name: "chain2"}} + generator := NewValidatorsInfoGenerator(chains) + results := generator.Generate(state) + assert.Len(t, results, 2) + + validatorsCountGauge, ok := results[0].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.InEpsilon(t, float64(2), testutil.ToFloat64(validatorsCountGauge.With(prometheus.Labels{ + "chain": "chain", + })), 0.01) + + totalBondedGauge, ok := results[1].(*prometheus.GaugeVec) + assert.True(t, ok) + assert.Zero(t, testutil.CollectAndCount(totalBondedGauge)) +} + func TestValidatorsInfoGeneratorConsumer(t *testing.T) { t.Parallel()