From 0d561bea6740d992d8213ec00028860c4d061fd4 Mon Sep 17 00:00:00 2001 From: Albert Le Batteux Date: Mon, 23 Jan 2023 16:38:11 +0000 Subject: [PATCH 1/3] chore(ci): dockerized :whale: --- .github/workflows/docker.yml | 39 ++++++++++++++++++++++++++++++++++++ Dockerfile | 14 +++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 .github/workflows/docker.yml create mode 100644 Dockerfile diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..cfabc5e --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,39 @@ +name: docker + +on: + push: + branches: + - 'master' + tags: + - 'v*' + +jobs: + docker: + runs-on: ubuntu-latest + steps: + + - name: Checkout + uses: actions/checkout@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,value=latest + type=semver,pattern=v{{version}} + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4c328a1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.19-alpine AS builder + +COPY . /app + +WORKDIR /app + +RUN go build -o cosmos-exporter + + +FROM alpine + +COPY --from=builder /app/cosmos-exporter /usr/local/bin/cosmos-exporter + +ENTRYPOINT [ "/usr/local/bin/cosmos-exporter" ] From b2d030920223e0bacc2a24f5401b5d2646d0e806 Mon Sep 17 00:00:00 2001 From: Albert Le Batteux Date: Thu, 26 Jan 2023 15:19:57 +0000 Subject: [PATCH 2/3] feat: general add token price --- general.go | 23 +++++ go.mod | 1 + pkg/cosmosdirectory/get.go | 56 +++++++++++ pkg/cosmosdirectory/get_test.go | 52 ++++++++++ pkg/cosmosdirectory/types.go | 172 ++++++++++++++++++++++++++++++++ 5 files changed, 304 insertions(+) create mode 100644 pkg/cosmosdirectory/get.go create mode 100644 pkg/cosmosdirectory/get_test.go create mode 100644 pkg/cosmosdirectory/types.go diff --git a/general.go b/general.go index 589ec1c..81a4b4c 100644 --- a/general.go +++ b/general.go @@ -2,6 +2,7 @@ package main import ( "context" + "main/pkg/cosmosdirectory" "net/http" "strconv" "sync" @@ -75,6 +76,14 @@ func GeneralHandler(w http.ResponseWriter, r *http.Request, grpcConn *grpc.Clien []string{"denom"}, ) + generalTokenPrice := prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "cosmos_token_price", + Help: "Cosmos token price", + ConstLabels: ConstLabels, + }, + ) + registry := prometheus.NewRegistry() registry.MustRegister(generalBondedTokensGauge) registry.MustRegister(generalNotBondedTokensGauge) @@ -82,9 +91,23 @@ func GeneralHandler(w http.ResponseWriter, r *http.Request, grpcConn *grpc.Clien registry.MustRegister(generalSupplyTotalGauge) registry.MustRegister(generalInflationGauge) registry.MustRegister(generalAnnualProvisions) + registry.MustRegister(generalTokenPrice) var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + chain, err := cosmosdirectory.GetChainByChainID(ChainID) + if err != nil { + sublogger.Error().Err(err).Msg("Could not get chain informations") + return + } + + price := chain.GetPriceUSD() + generalTokenPrice.Set(price) + }() + wg.Add(1) go func() { defer wg.Done() diff --git a/go.mod b/go.mod index d87b265..09fa170 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.7.0 github.com/tendermint/tendermint v0.34.9 google.golang.org/grpc v1.35.0 ) diff --git a/pkg/cosmosdirectory/get.go b/pkg/cosmosdirectory/get.go new file mode 100644 index 0000000..d59b9ff --- /dev/null +++ b/pkg/cosmosdirectory/get.go @@ -0,0 +1,56 @@ +package cosmosdirectory + +import ( + "encoding/json" + "errors" + "net/http" + "time" +) + +const CosmosDirectoryURL = "https://chains.cosmos.directory" + +func query() (*CosmosDirectory, error) { + httpClient := &http.Client{Timeout: 2 * time.Second} + r, err := httpClient.Get(CosmosDirectoryURL) + if err != nil { + return nil, err + } + defer r.Body.Close() + + resp := &CosmosDirectory{} + + err = json.NewDecoder(r.Body).Decode(resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func GetChain(name string) (*Chain, error) { + resp, err := query() + if err != nil { + return nil, err + } + + for _, chain := range resp.Chains { + if chain.Name == name || chain.ChainName == name { + return &chain, nil + } + } + return nil, errors.New("chain not found") +} + +func GetChainByChainID(chainID string) (*Chain, error) { + resp, err := query() + if err != nil { + return nil, err + } + + for _, chain := range resp.Chains { + if chain.ChainID == chainID { + return &chain, nil + } + } + return nil, errors.New("chain not found") +} diff --git a/pkg/cosmosdirectory/get_test.go b/pkg/cosmosdirectory/get_test.go new file mode 100644 index 0000000..354eb2f --- /dev/null +++ b/pkg/cosmosdirectory/get_test.go @@ -0,0 +1,52 @@ +package cosmosdirectory_test + +import ( + "fmt" + "main/pkg/cosmosdirectory" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetChain(t *testing.T) { + tests := []struct { + Chain string + }{ + {Chain: "juno"}, + {Chain: "stargaze"}, + {Chain: "cheqd"}, + } + + for _, test := range tests { + t.Run(test.Chain, func(t *testing.T) { + chain, err := cosmosdirectory.GetChain(test.Chain) + require.NoError(t, err) + require.NotNil(t, chain) + require.NotZero(t, chain.GetPriceUSD()) + }) + } +} + +func TestGetChainByChainID(t *testing.T) { + tests := []struct { + ChainID string + }{ + {ChainID: "juno-1"}, + {ChainID: "stargaze-1"}, + {ChainID: "cheqd-mainnet-1"}, + {ChainID: "kichain-2"}, + {ChainID: "Oraichain"}, + {ChainID: "chihuahua-1"}, + } + + for _, test := range tests { + t.Run(test.ChainID, func(t *testing.T) { + chain, err := cosmosdirectory.GetChainByChainID(test.ChainID) + require.NoError(t, err) + require.NotNil(t, chain) + require.NotZero(t, chain.GetPriceUSD()) + + fmt.Printf("price: %v\n", chain.GetPriceUSD()) + }) + } +} diff --git a/pkg/cosmosdirectory/types.go b/pkg/cosmosdirectory/types.go new file mode 100644 index 0000000..afca79a --- /dev/null +++ b/pkg/cosmosdirectory/types.go @@ -0,0 +1,172 @@ +package cosmosdirectory + +type CosmosDirectory struct { + Repository Repository `json:"repository"` + Chains []Chain `json:"chains"` +} + +type Repository struct { + Url string `json:"url"` + Branch string `json:"branch"` + Commit string `json:"commit"` + Timestamp float64 `json:"timestamp"` +} + +type Chain struct { + Decimals float64 `json:"decimals"` + BestApis BestApis `json:"best_apis"` + Versions Versions `json:"versions"` + Prices Prices `json:"prices"` + Name string `json:"name"` + ChainName string `json:"chain_name"` + Denom string `json:"denom"` + Bech32_prefix string `json:"bech32_prefix"` + Image string `json:"image"` + Height float64 `json:"height"` + ProxyStatus ProxyStatus `json:"proxy_status"` + Assets []Assets `json:"assets"` + Network_type string `json:"network_type"` + Pretty_name string `json:"pretty_name"` + ChainID string `json:"chain_id"` + Coingecko_id string `json:"coingecko_id"` + Explorers []Explorers `json:"explorers"` + Display string `json:"display"` + Params Params `json:"params"` + Path string `json:"path"` + Status string `json:"status"` + Symbol string `json:"symbol"` +} + +func (chain Chain) GetPriceUSD() float64 { + prices, exist := chain.Prices.Coingecko[chain.Display].(map[string]interface{}) + if !exist { + return float64(0) + } + price, exist := prices["usd"].(float64) + if !exist { + return float64(0) + } + return price +} + +type CoingeckoPrices map[string]interface{} // map[string]interface{} + +type Prices struct { + // Coingecko map[string]CoingeckoPrices `json:"coingecko"` + Coingecko map[string]interface{} `json:"coingecko"` + // Coingecko Coingecko `json:"coingecko"` +} + +type Rpc struct { + Address string `json:"address"` + Provider string `json:"provider"` +} + +type Versions struct { + ApplicationVersion string `json:"application_version"` + CosmosSDKVersion string `json:"cosmos_sdk_version"` + TendermintVersion string `json:"tendermint_version"` +} + +type Denom_units struct { + Denom string `json:"denom"` + Exponent float64 `json:"exponent"` +} + +type Staking struct { + MaxValidators float64 `json:"max_validators"` + MaxEntries float64 `json:"max_entries"` + HistoricalEntries float64 `json:"historical_entries"` + Bond_denom string `json:"bond_denom"` + UnbondingTime string `json:"unbonding_time"` +} + +type Distribution struct { + CommunityTax string `json:"community_tax"` + Base_proposer_reward string `json:"base_proposer_reward"` + Bonus_proposer_reward string `json:"bonus_proposer_reward"` + Withdraw_addr_enabled bool `json:"withdraw_addr_enabled"` +} + +type BestApis struct { + Rest []Rest `json:"rest"` + Rpc []Rpc `json:"rpc"` +} + +type Explorers struct { + Tx_page string `json:"tx_page"` + Kind string `json:"kind"` + Url string `json:"url"` +} + +type Coingecko struct { + Usd float64 `json:"usd"` +} + +type Acre struct { + Usd float64 `json:"usd"` +} + +type ProxyStatus struct { + Rest bool `json:"rest"` + Rpc bool `json:"rpc"` +} + +type Assets struct { + Denom string `json:"denom"` + Coingecko_id string `json:"coingecko_id"` + Base Base `json:"base"` + Denom_units []Denom_units `json:"denom_units"` + Image string `json:"image"` + Symbol string `json:"symbol"` + Description string `json:"description"` + Decimals float64 `json:"decimals"` + Display Display `json:"display"` + Logo_URIs Logo_URIs `json:"logo_URIs"` + Prices Prices `json:"prices"` + Name string `json:"name"` +} + +type Display struct { + Denom string `json:"denom"` + Exponent float64 `json:"exponent"` +} + +type Params struct { + Authz bool `json:"authz"` + Actual_blocks_per_year float64 `json:"actual_blocks_per_year"` + UnbondingTime float64 `json:"unbonding_time"` + Bonded_tokens string `json:"bonded_tokens"` + Total_supply string `json:"total_supply"` + Actual_block_time float64 `json:"actual_block_time"` + Current_block_height string `json:"current_block_height"` + MaxValidators float64 `json:"max_validators"` + Staking Staking `json:"staking"` + Slashing Slashing `json:"slashing"` + Bonded_ratio float64 `json:"bonded_ratio"` + CommunityTax float64 `json:"community_tax"` + Distribution Distribution `json:"distribution"` +} + +type Slashing struct { + Downtime_jail_duration string `json:"downtime_jail_duration"` + Slash_fraction_double_sign string `json:"slash_fraction_double_sign"` + Slash_fraction_downtime string `json:"slash_fraction_downtime"` + Signed_blocks_window string `json:"signed_blocks_window"` + Min_signed_per_window string `json:"min_signed_per_window"` +} + +type Base struct { + Denom string `json:"denom"` + Exponent float64 `json:"exponent"` +} + +type Logo_URIs struct { + Png string `json:"png"` + Svg string `json:"svg"` +} + +type Rest struct { + Provider string `json:"provider"` + Address string `json:"address"` +} From 7277006fc60bd1713c5ae680d02e8e18fbefd8b3 Mon Sep 17 00:00:00 2001 From: Raphael Thurnherr Date: Fri, 25 Feb 2022 20:04:01 +0100 Subject: [PATCH 3/3] fix validator ranking --- validator.go | 7 ++++++- validators.go | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/validator.go b/validator.go index a121b9e..01ae918 100644 --- a/validator.go +++ b/validator.go @@ -576,10 +576,15 @@ func ValidatorHandler(w http.ResponseWriter, r *http.Request, grpcConn *grpc.Cli validators := stakingRes.Validators - // sorting by delegator shares to display rankings + // sorting by delegator shares to display rankings (unbonded go last) sort.Slice(validators, func(i, j int) bool { firstShares, firstErr := strconv.ParseFloat(validators[i].DelegatorShares.String(), 64) secondShares, secondErr := strconv.ParseFloat(validators[j].DelegatorShares.String(), 64) + if !validators[i].IsBonded() && validators[j].IsBonded() { + return false + } else if validators[i].IsBonded() && !validators[j].IsBonded() { + return true + } if firstErr != nil || secondErr != nil { sublogger.Error(). diff --git a/validators.go b/validators.go index 753e407..aa0c5cf 100644 --- a/validators.go +++ b/validators.go @@ -151,8 +151,13 @@ func ValidatorsHandler(w http.ResponseWriter, r *http.Request, grpcConn *grpc.Cl Msg("Finished querying validators") validators = validatorsResponse.Validators - // sorting by delegator shares to display rankings + // sorting by delegator shares to display rankings (unbonded go last) sort.Slice(validators, func(i, j int) bool { + if !validators[i].IsBonded() && validators[j].IsBonded() { + return false + } else if validators[i].IsBonded() && !validators[j].IsBonded() { + return true + } return validators[i].DelegatorShares.RoundInt64() > validators[j].DelegatorShares.RoundInt64() }) }()