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 denoms #36

Merged
merged 10 commits into from
Dec 10, 2023
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
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ linters:
- gocyclo
- maintidx
- deadcode
- depguard
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

![Latest release](https://img.shields.io/github/v/release/QuokkaStake/cosmos-validators-exporter)
[![Actions Status](https://github.com/QuokkaStake/cosmos-validators-exporter/workflows/test/badge.svg)](https://github.com/QuokkaStake/cosmos-validators-exporter/actions)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgit.luolix.top%2FQuokkaStake%2Fcosmos-validators-exporter.svg?type=shield)](https://app.fossa.com/projects/git%2Bgit.luolix.top%2FQuokkaStake%2Fcosmos-validators-exporter?ref=badge_shield)

cosmos-validators-exporter is a Prometheus scraper that fetches validators stats from an LCD server exposed by a fullnode.

Expand Down Expand Up @@ -108,7 +107,7 @@ All the metrics provided by cosmos-validators-exporter have the `cosmos_validato
When developing, we aimed to only return metrics that are required, and avoid creating metrics that can be computed on Grafana/Prometheus side. This decreases the amount of time series that this exporter will return, but will make writing queries more complex. Here are some examples of queries that we consider useful:

- `count(cosmos_validators_exporter_info)` - number of validators monitored
- `sum ((cosmos_validators_exporter_total_delegations) / on (chain) cosmos_validators_exporter_denom_coefficient * on (chain) cosmos_validators_exporter_price)` - total delegated tokens in $
- `sum((cosmos_validators_exporter_total_delegations) * on (chain) group_left(denom) cosmos_validators_exporter_base_denom / on (chain, denom) cosmos_validators_exporter_denom_coefficient * on (chain, denom) cosmos_validators_exporter_price)` - total delegated tokens in $
- `sum(cosmos_validators_exporter_delegations_count)` - total delegators count
- `cosmos_validators_exporter_total_delegations / on (chain) cosmos_validators_exporter_tokens_bonded_total` - voting power percent of your validator
- `1 - (cosmos_validators_exporter_missed_blocks / on (chain) cosmos_validators_exporter_missed_blocks_window)` - validator's uptime in %
Expand All @@ -120,7 +119,3 @@ All configuration is done via the .toml config file, which is passed to the appl
## How can I contribute?

Bug reports and feature requests are always welcome! If you want to contribute, feel free to open issues or PRs.


## License
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgit.luolix.top%2FQuokkaStake%2Fcosmos-validators-exporter.svg?type=large)](https://app.fossa.com/projects/git%2Bgit.luolix.top%2FQuokkaStake%2Fcosmos-validators-exporter?ref=badge_large)
66 changes: 43 additions & 23 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ listen-address = ":9560"
# Log level. Change it to "debug" or even trace for more verbosity and debugging. Defaults to "info".
level = "debug"
# Whether all the logs should be written in JSON instead of a pretty-printed text. Useful if you have
# logging solutions, like ELK. Defaults to false.
# logging solutions, like Elastic stack. Defaults to false.
json = false

# Per-chain config.
Expand All @@ -17,29 +17,47 @@ json = false
name = "bitsong"
# LCD endpoint to query data from. Required.
lcd-endpoint = "https://api.bitsong.quokkastake.io"
# Chain's base denom.
# This value is quite useless by itself, but it's exposed in `cosmos_validators_exporter_base_denom` metric,
# which later can be joined with other metrics (such as, total delegated tokens)
# and the price can be calculated based on that.
base-denom = "ubtsg"
# Denoms info.
# Used to 1) be exposed as a standalone metric, and 2) when calculating metric for token price.
# This is an an array of objects with following values:
# 1. coingecko-currency
# Coingecko currency, specify it if you want to also get the wallet balance
# in total in USD.
coingecko-currency = "bitsong"
# in total in USD. Optional.
# 2. dex-screener-chain-id and dex-screener-pair.
# dexscreener.com's chain ID (usually ""osmosis") and pair (usually pool ID).
# Won't be used if coingecko-currency is provided.
# Either coingecko-currency or these two params are required for getting token price.
dex-screener-chain-id = "osmosis"
dex-screener-pair = "832"
# The chain's base denom. Only balances with this denom will be used
# to calculate wallet's USD price.
base-denom = "ubtsg"
# The chain's display denom.
denom = "btsg"
# The coefficient you need to multiply base denom to to get 1 token on Coingecko.
# Example: on Cosmos network the base denom is uatom, 1 atom = 1_000_000 uatom
# and 1 atom on Coingecko = $10, and your wallet has 10 atom, or 10_000_000 uatom.
# Then you need to specify the following parameters:
# coingecko-currency = "cosmos-hub"
# base-denom = "uatom"
# denom-coefficient = 1000000
# and after that, the /metrics endpoint will return your total balance as $100.
# Defaults to 1000000
denom-coefficient = 1000000
# 3. denom
# The actual denom value (such as "uatom" for Cosmos Hub or "ibc/xxxxx" for IBC denoms). Required.
# 4. display-denom
# The denom that'll be returned in labels. Required.
# 5. denom-coefficient
# The coefficient you need to multiply base denom to to get 1 token on Coingecko/DexScreener.
# Is exposed by `cosmos_validators_exporter_denom_coefficient` metric.
# Optional, defaults to 1_000_000.
# You can calculate the actual price of something by doing the following:
# 1) taking the actual metric (either with "denom" label, like commission, or joining it with "base-denom" label
# of `cosmos_validators_exporter_base_denom` metric for the chain, which will add the label)
# 2) divide and join it with `cosmos_validators_exporter_denom_coefficient` metric for this chain for this denom
# 3) multiply it with `cosmos_validators_exporter_price` metric for this denom/chain.
# For example, with denom = "uatom", display-denom = "atom", "coingecko-currency" = "cosmos",
# if you have 10 ATOM unclaimed commission, and $ATOM price = 10, here's what'll happen on each step:
# 1) will return a metric with labels/values like this:
# {chain="cosmos",address="cosmosvaloperxxxx",denom="uatom"} 10000000
# 2) will return a metric like this
# {chain="cosmos",address="cosmosvaloperxxxx",denom="uatom",display-denom="atom"} 10
# 3) will return a metric like this:
# {chain="cosmos",address="cosmosvaloperxxxx",denom="uatom",display-denom="atom"} 100
# which you can use to display a worthwhile of your unclaimed commission and use display-denom
# label for legend.
denoms = [
{ denom = "uatom", display-denom = "atom", coingecko-currency = "cosmos", denom-coefficient = 1000000, dex-screener-chain-id = "osmosis", dex-screener-pair-id = "1" },
]
# Bech32 prefix for a wallet address (example: "cosmos" for a Cosmos wallet). If omitted,
# the self-delegation metric will not be present.
bech-wallet-prefix = "bitsong"
Expand All @@ -48,6 +66,8 @@ bech-wallet-prefix = "bitsong"
# signing-infos metrics (like missed blocks counter).
# You can get your consensus-address by running "<appd> tendermint show-address" on your validator node,
# if you are not using KMS solutions.
# If you are using it to track a consumer chain validator and if you are using the assigned key,
# please make sure to use the consensus address of this chain and not the provider chain one.
validators = [
{ address = "bitsongvaloper14rvn7anf22e00vj5x3al4w50ns78s7n42rc0ge", consensus-address = "bitsongvalcons16ktzzs4ra4kcw668demahue52zh2xjllwdd6n3" }
]
Expand Down Expand Up @@ -84,10 +104,10 @@ staking-params = true
[[chains]]
name = "emoney"
lcd-endpoint = "https://api.emoney.quokkastake.io"
coingecko-currency = "e-money"
base-denom = "ungm"
denom = "ngm"
bech-wallet-prefix = "emoney"
denoms = [
{ denom = "ungm", display-denom = "ngm", coingecko-currency = "emoney" }
]
validators = [
{ address = "emoneyvaloper1jk4n79c5gv36972ptnly3rvx5nvn3hl3hgly9g" }
]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require (
github.com/BurntSushi/toml v1.1.0
github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce
github.com/cosmos/cosmos-sdk v0.46.0
github.com/creasty/defaults v1.7.0
github.com/google/uuid v1.3.0
github.com/mcuadros/go-defaults v1.2.0
github.com/prometheus/client_golang v1.12.2
github.com/rs/zerolog v1.27.0
github.com/spf13/cobra v1.5.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/daixiang0/gci v0.3.3/go.mod h1:1Xr2bxnQbDxCqqulUOv8qpGqkgRw9RSCGGjEC2LjF8o=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
Expand Down Expand Up @@ -750,8 +752,6 @@ github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
github.com/mcuadros/go-defaults v1.2.0 h1:FODb8WSf0uGaY8elWJAkoLL0Ri6AlZ1bFlenk56oZtc=
github.com/mcuadros/go-defaults v1.2.0/go.mod h1:WEZtHEVIGYVDqkKSWBdWKUVdRyKlMfulPaGDWIVeCWY=
github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg=
github.com/mgechev/revive v1.2.1/go.mod h1:+Ro3wqY4vakcYNtkBWdZC7dBg1xSB6sp054wWwmeFm0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
Expand Down
37 changes: 19 additions & 18 deletions pkg/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"time"

"main/pkg/config"
"main/pkg/logger"
loggerPkg "main/pkg/logger"
queriersPkg "main/pkg/queriers"

"github.com/google/uuid"
Expand All @@ -27,34 +27,35 @@ type App struct {
func NewApp(configPath string) *App {
appConfig, err := config.GetConfig(configPath)
if err != nil {
logger.GetDefaultLogger().Fatal().Err(err).Msg("Could not load config")
loggerPkg.GetDefaultLogger().Fatal().Err(err).Msg("Could not load config")
}

if err = appConfig.Validate(); err != nil {
logger.GetDefaultLogger().Fatal().Err(err).Msg("Provided config is invalid!")
loggerPkg.GetDefaultLogger().Fatal().Err(err).Msg("Provided config is invalid!")
}

log := logger.GetLogger(appConfig.LogConfig)
logger := loggerPkg.GetLogger(appConfig.LogConfig)
appConfig.DisplayWarnings(logger)

coingecko := coingeckoPkg.NewCoingecko(appConfig, log)
dexScreener := dexScreenerPkg.NewDexScreener(log)
coingecko := coingeckoPkg.NewCoingecko(appConfig, logger)
dexScreener := dexScreenerPkg.NewDexScreener(logger)

queriers := []types.Querier{
queriersPkg.NewCommissionQuerier(log, appConfig),
queriersPkg.NewDelegationsQuerier(log, appConfig),
queriersPkg.NewUnbondsQuerier(log, appConfig),
queriersPkg.NewSelfDelegationsQuerier(log, appConfig),
queriersPkg.NewPriceQuerier(log, appConfig, coingecko, dexScreener),
queriersPkg.NewRewardsQuerier(log, appConfig),
queriersPkg.NewWalletQuerier(log, appConfig),
queriersPkg.NewSlashingParamsQuerier(log, appConfig),
queriersPkg.NewValidatorQuerier(log, appConfig),
queriersPkg.NewDenomCoefficientsQuerier(log, appConfig),
queriersPkg.NewSigningInfoQuerier(log, appConfig),
queriersPkg.NewCommissionQuerier(logger, appConfig),
queriersPkg.NewDelegationsQuerier(logger, appConfig),
queriersPkg.NewUnbondsQuerier(logger, appConfig),
queriersPkg.NewSelfDelegationsQuerier(logger, appConfig),
queriersPkg.NewPriceQuerier(logger, appConfig, coingecko, dexScreener),
queriersPkg.NewRewardsQuerier(logger, appConfig),
queriersPkg.NewWalletQuerier(logger, appConfig),
queriersPkg.NewSlashingParamsQuerier(logger, appConfig),
queriersPkg.NewValidatorQuerier(logger, appConfig),
queriersPkg.NewDenomCoefficientsQuerier(logger, appConfig),
queriersPkg.NewSigningInfoQuerier(logger, appConfig),
}

return &App{
Logger: log,
Logger: logger,
Config: appConfig,
Queriers: queriers,
}
Expand Down
107 changes: 88 additions & 19 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"fmt"
"os"

"github.com/rs/zerolog"

"github.com/BurntSushi/toml"
"github.com/mcuadros/go-defaults"
"github.com/creasty/defaults"
)

type Validator struct {
Expand All @@ -21,18 +23,56 @@ func (v *Validator) Validate() error {
return nil
}

type DenomInfo struct {
Denom string `toml:"denom"`
DenomCoefficient int64 `default:"1000000" toml:"denom-coefficient"`
DisplayDenom string `toml:"display-denom"`
CoingeckoCurrency string `toml:"coingecko-currency"`
DexScreenerChainID string `toml:"dex-screener-chain-id"`
DexScreenerPair string `toml:"dex-screener-pair"`
}

func (d *DenomInfo) Validate() error {
if d.Denom == "" {
return fmt.Errorf("empty denom name")
}

if d.Denom == "" {
return fmt.Errorf("empty display denom name")
}

return nil
}

func (d *DenomInfo) DisplayWarnings(chain *Chain, logger *zerolog.Logger) {
if d.CoingeckoCurrency == "" && (d.DexScreenerPair == "" || d.DexScreenerChainID == "") {
logger.Warn().
Str("chain", chain.Name).
Str("denom", d.Denom).
Msg("Currency code not set, not fetching exchange rate.")
}
}

type DenomInfos []*DenomInfo

func (d DenomInfos) Find(denom string) *DenomInfo {
for _, info := range d {
if denom == info.Denom {
return info
}
}

return nil
}

type Chain struct {
Name string `toml:"name"`
LCDEndpoint string `toml:"lcd-endpoint"`
CoingeckoCurrency string `toml:"coingecko-currency"`
DexScreenerChainID string `toml:"dex-screener-chain-id"`
DexScreenerPair string `toml:"dex-screener-pair"`
BaseDenom string `toml:"base-denom"`
Denom string `toml:"denom"`
DenomCoefficient int64 `toml:"denom-coefficient" default:"1000000"`
BechWalletPrefix string `toml:"bech-wallet-prefix"`
Validators []Validator `toml:"validators"`
Queries map[string]bool `toml:"queries"`
Name string `toml:"name"`
LCDEndpoint string `toml:"lcd-endpoint"`
BaseDenom string `toml:"base-denom"`
Denoms DenomInfos `toml:"denoms"`
BechWalletPrefix string `toml:"bech-wallet-prefix"`
Validators []Validator `toml:"validators"`
Queries map[string]bool `toml:"queries"`
}

func (c *Chain) Validate() error {
Expand All @@ -54,9 +94,27 @@ func (c *Chain) Validate() error {
}
}

for index, denomInfo := range c.Denoms {
if err := denomInfo.Validate(); err != nil {
return fmt.Errorf("error in denom #%d: %s", index, err)
}
}

return nil
}

func (c *Chain) DisplayWarnings(logger *zerolog.Logger) {
if c.BaseDenom == "" {
logger.Warn().
Str("chain", c.Name).
Msg("Base denom is not set")
}

for _, denom := range c.Denoms {
denom.DisplayWarnings(c, logger)
}
}

func (c *Chain) QueryEnabled(query string) bool {
if value, ok := c.Queries[query]; !ok {
return true // all queries are enabled by default
Expand All @@ -67,14 +125,14 @@ func (c *Chain) QueryEnabled(query string) bool {

type Config struct {
LogConfig LogConfig `toml:"log"`
ListenAddress string `toml:"listen-address" default:":9550"`
Timeout int `toml:"timeout" default:"10"`
ListenAddress string `default:":9550" toml:"listen-address"`
Timeout int `default:"10" toml:"timeout"`
Chains []Chain `toml:"chains"`
}

type LogConfig struct {
LogLevel string `toml:"level" default:"info"`
JSONOutput bool `toml:"json" default:"false"`
LogLevel string `default:"info" toml:"level"`
JSONOutput bool `default:"false" toml:"json"`
}

func (c *Config) Validate() error {
Expand All @@ -91,12 +149,20 @@ func (c *Config) Validate() error {
return nil
}

func (c *Config) DisplayWarnings(logger *zerolog.Logger) {
for _, chain := range c.Chains {
chain.DisplayWarnings(logger)
}
}

func (c *Config) GetCoingeckoCurrencies() []string {
currencies := []string{}

for _, chain := range c.Chains {
if chain.CoingeckoCurrency != "" {
currencies = append(currencies, chain.CoingeckoCurrency)
for _, denom := range chain.Denoms {
if denom.CoingeckoCurrency != "" {
currencies = append(currencies, denom.CoingeckoCurrency)
}
}
}

Expand All @@ -116,6 +182,9 @@ func GetConfig(path string) (*Config, error) {
return nil, err
}

defaults.SetDefaults(&configStruct)
if err = defaults.Set(&configStruct); err != nil {
return nil, err
}

return &configStruct, nil
}
Loading
Loading