Skip to content

Commit

Permalink
feat: refactor price fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
freak12techno committed Sep 17, 2024
1 parent 492c69e commit ee87b65
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 119 deletions.
4 changes: 1 addition & 3 deletions pkg/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
fetchersPkg "main/pkg/fetchers"
"main/pkg/fs"
generatorsPkg "main/pkg/generators"
coingeckoPkg "main/pkg/price_fetchers/coingecko"
statePkg "main/pkg/state"
"main/pkg/tendermint"
"main/pkg/tracing"
Expand Down Expand Up @@ -69,7 +68,6 @@ func NewApp(configPath string, filesystem fs.FS, version string) *App {
}

tracer := tracing.InitTracer(appConfig.TracingConfig, version)
coingecko := coingeckoPkg.NewCoingecko(appConfig, logger, tracer)

rpcs := make(map[string]*tendermint.RPCWithConsumers, len(appConfig.Chains))

Expand All @@ -90,7 +88,7 @@ func NewApp(configPath string, filesystem fs.FS, version string) *App {
fetchersPkg.NewValidatorsFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewConsumerValidatorsFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewStakingParamsFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewPriceFetcher(logger, appConfig, tracer, coingecko),
fetchersPkg.NewPriceFetcher(logger, appConfig, tracer),
fetchersPkg.NewNodeInfoFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewConsumerInfoFetcher(logger, appConfig.Chains, rpcs, tracer),
fetchersPkg.NewValidatorConsumersFetcher(logger, appConfig.Chains, rpcs, tracer),
Expand Down
22 changes: 0 additions & 22 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,6 @@ func (c *Config) DisplayWarnings() []Warning {
return warnings
}

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

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

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

return currencies
}

func GetConfig(path string, filesystem fs.FS) (*Config, error) {
configBytes, err := filesystem.ReadFile(path)
if err != nil {
Expand Down
24 changes: 0 additions & 24 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,30 +88,6 @@ func TestDisplayWarningsEmpty(t *testing.T) {
require.Empty(t, warnings)
}

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

config := Config{
Chains: []*Chain{{
Denoms: DenomInfos{
{Denom: "denom1", CoingeckoCurrency: "denom1"},
{Denom: "denom2"},
},
ConsumerChains: []*ConsumerChain{{
Denoms: DenomInfos{
{Denom: "denom3", CoingeckoCurrency: "denom3"},
{Denom: "denom4"},
},
}},
}},
}

currencies := config.GetCoingeckoCurrencies()
require.Len(t, currencies, 2)
require.Contains(t, currencies, "denom1")
require.Contains(t, currencies, "denom3")
}

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

Expand Down
9 changes: 9 additions & 0 deletions pkg/config/denom_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"errors"
"main/pkg/constants"
"main/pkg/types"
"math"

Expand Down Expand Up @@ -43,6 +44,14 @@ func (d *DenomInfo) DisplayWarnings(chain *Chain) []Warning {
return warnings
}

func (d *DenomInfo) PriceFetchers() []constants.PriceFetcherName {
if d.CoingeckoCurrency != "" {
return []constants.PriceFetcherName{constants.PriceFetcherNameCoingecko}
}

return []constants.PriceFetcherName{}
}

type DenomInfos []*DenomInfo

func (d DenomInfos) Find(denom string) *DenomInfo {
Expand Down
6 changes: 6 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package constants

type FetcherName string

type PriceFetcherName string

const (
FetcherNameSlashingParams FetcherName = "slashing-params"
FetcherNameSoftOptOutThreshold FetcherName = "soft-opt-out-threshold"
Expand Down Expand Up @@ -29,4 +31,8 @@ const (
ValidatorStatusBonded = "BOND_STATUS_BONDED"

HeaderBlockHeight = "Grpc-Metadata-X-Cosmos-Block-Height"

CoingeckoBaseCurrency string = "usd"

PriceFetcherNameCoingecko PriceFetcherName = "coingecko"
)
109 changes: 72 additions & 37 deletions pkg/fetchers/price.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,81 +4,116 @@ import (
"context"
"main/pkg/config"
"main/pkg/constants"
"main/pkg/price_fetchers"
coingeckoPkg "main/pkg/price_fetchers/coingecko"
"main/pkg/types"
"sync"

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

type PriceFetcher struct {
Logger zerolog.Logger
Config *config.Config
Tracer trace.Tracer
Coingecko *coingeckoPkg.Coingecko
Logger zerolog.Logger
Config *config.Config
Tracer trace.Tracer

CurrenciesRatesToChains map[string]map[string]float64
Fetchers map[constants.PriceFetcherName]price_fetchers.PriceFetcher
}

type PriceInfo struct {
Source constants.PriceFetcherName
BaseCurrency string
Value float64
}

type PriceData struct {
Prices map[string]map[string]float64
Prices map[string]map[string]PriceInfo
}

func NewPriceFetcher(
logger *zerolog.Logger,
config *config.Config,
tracer trace.Tracer,
coingecko *coingeckoPkg.Coingecko,
) *PriceFetcher {
fetchers := map[constants.PriceFetcherName]price_fetchers.PriceFetcher{
constants.PriceFetcherNameCoingecko: coingeckoPkg.NewCoingecko(config, logger, tracer),
}

return &PriceFetcher{
Logger: logger.With().Str("component", "price_fetcher").Logger(),
Config: config,
Tracer: tracer,
Coingecko: coingecko,
Logger: logger.With().Str("component", "price_fetcher").Logger(),
Config: config,
Tracer: tracer,
Fetchers: fetchers,
}
}

func (q *PriceFetcher) Fetch(
ctx context.Context,
) (interface{}, []*types.QueryInfo) {
currenciesList := q.Config.GetCoingeckoCurrencies()

var currenciesRates map[string]float64
var currenciesQuery *types.QueryInfo
queries := []*types.QueryInfo{}
denomsByPriceFetcher := map[constants.PriceFetcherName][]price_fetchers.ChainWithDenom{}

var queries []*types.QueryInfo
for _, chain := range q.Config.Chains {
for _, denom := range chain.Denoms {
for _, priceFetcher := range denom.PriceFetchers() {
denomsByPriceFetcher[priceFetcher] = append(denomsByPriceFetcher[priceFetcher], price_fetchers.ChainWithDenom{
Chain: chain.Name,
DenomInfo: denom,
})
}
}

if len(currenciesList) > 0 {
currenciesRates, currenciesQuery = q.Coingecko.FetchPrices(currenciesList, ctx)
for _, consumer := range chain.ConsumerChains {
for _, denom := range consumer.Denoms {
for _, priceFetcher := range denom.PriceFetchers() {
denomsByPriceFetcher[priceFetcher] = append(denomsByPriceFetcher[priceFetcher], price_fetchers.ChainWithDenom{
Chain: consumer.Name,
DenomInfo: denom,
})
}
}
}
}

if currenciesQuery != nil {
queries = append(queries, currenciesQuery)
}
var wg sync.WaitGroup
var mutex sync.Mutex
var denomsPrices = map[constants.PriceFetcherName][]price_fetchers.PriceInfo{}

q.CurrenciesRatesToChains = map[string]map[string]float64{}
for priceFetcher, denoms := range denomsByPriceFetcher {
wg.Add(1)

for _, chain := range q.Config.Chains {
q.CurrenciesRatesToChains[chain.Name] = make(map[string]float64)
q.ProcessDenoms(chain.Name, chain.Denoms, currenciesRates)
go func(priceFetcher constants.PriceFetcherName, denoms []price_fetchers.ChainWithDenom) {
defer wg.Done()

for _, consumer := range chain.ConsumerChains {
q.CurrenciesRatesToChains[consumer.Name] = make(map[string]float64)
q.ProcessDenoms(consumer.Name, consumer.Denoms, currenciesRates)
}
priceFetcherDenoms, priceFetcherQuery := q.Fetchers[priceFetcher].FetchPrices(denoms, ctx)

mutex.Lock()
queries = append(queries, priceFetcherQuery)
denomsPrices[priceFetcher] = priceFetcherDenoms
mutex.Unlock()
}(priceFetcher, denoms)
}

return PriceData{Prices: q.CurrenciesRatesToChains}, queries
}
wg.Wait()

prices := map[string]map[string]PriceInfo{}

func (q *PriceFetcher) ProcessDenoms(chainName string, denoms config.DenomInfos, currenciesRates map[string]float64) {
for _, denom := range denoms {
// using coingecko response
if rate, ok := currenciesRates[denom.CoingeckoCurrency]; ok {
q.CurrenciesRatesToChains[chainName][denom.DisplayDenom] = rate
continue
for priceFetcher, denomInfos := range denomsPrices {
for _, denomInfo := range denomInfos {
if _, ok := prices[denomInfo.Chain]; !ok {
prices[denomInfo.Chain] = map[string]PriceInfo{}
}

prices[denomInfo.Chain][denomInfo.Denom] = PriceInfo{
Source: priceFetcher,
BaseCurrency: denomInfo.BaseCurrency,
Value: denomInfo.Price,
}
}
}

return PriceData{Prices: prices}, queries
}

func (q *PriceFetcher) Name() constants.FetcherName {
Expand Down
29 changes: 12 additions & 17 deletions pkg/fetchers/price_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
configPkg "main/pkg/config"
"main/pkg/constants"
loggerPkg "main/pkg/logger"
coingeckoPkg "main/pkg/price_fetchers/coingecko"
"main/pkg/tracing"
"testing"

Expand All @@ -25,13 +24,11 @@ func TestPriceFetcherBase(t *testing.T) {
config := &configPkg.Config{Chains: chains}
logger := loggerPkg.GetNopLogger()
tracer := tracing.InitNoopTracer()
coingecko := coingeckoPkg.NewCoingecko(config, logger, tracer)

fetcher := NewPriceFetcher(
logger,
config,
tracer,
coingecko,
)

assert.NotNil(t, fetcher)
Expand All @@ -56,24 +53,19 @@ func TestPriceFetcherProviderCoingeckoError(t *testing.T) {
config := &configPkg.Config{Chains: chains}
logger := loggerPkg.GetNopLogger()
tracer := tracing.InitNoopTracer()
coingecko := coingeckoPkg.NewCoingecko(config, logger, tracer)

fetcher := NewPriceFetcher(
logger,
config,
tracer,
coingecko,
)
data, queries := fetcher.Fetch(context.Background())
assert.Len(t, queries, 1)
assert.False(t, queries[0].Success)

balanceData, ok := data.(PriceData)
assert.True(t, ok)

chainData, ok := balanceData.Prices["chain"]
assert.True(t, ok)
assert.Empty(t, chainData)
assert.Empty(t, balanceData.Prices)
}

//nolint:paralleltest // disabled due to httpmock usage
Expand All @@ -94,13 +86,11 @@ func TestPriceFetcherProviderCoingeckoSuccess(t *testing.T) {
config := &configPkg.Config{Chains: chains}
logger := loggerPkg.GetNopLogger()
tracer := tracing.InitNoopTracer()
coingecko := coingeckoPkg.NewCoingecko(config, logger, tracer)

fetcher := NewPriceFetcher(
logger,
config,
tracer,
coingecko,
)
data, queries := fetcher.Fetch(context.Background())
assert.Len(t, queries, 1)
Expand All @@ -114,7 +104,9 @@ func TestPriceFetcherProviderCoingeckoSuccess(t *testing.T) {

denomData, ok := chainData["atom"]
assert.True(t, ok)
assert.InEpsilon(t, 6.71, denomData, 0.01)
assert.InEpsilon(t, 6.71, denomData.Value, 0.01)
assert.Equal(t, constants.CoingeckoBaseCurrency, denomData.BaseCurrency)
assert.Equal(t, constants.PriceFetcherNameCoingecko, denomData.Source)
}

//nolint:paralleltest // disabled due to httpmock usage
Expand All @@ -131,20 +123,21 @@ func TestPriceFetcherConsumerCoingeckoSuccess(t *testing.T) {
chains := []*configPkg.Chain{{
Name: "chain",
ConsumerChains: []*configPkg.ConsumerChain{{
Name: "consumer",
Denoms: configPkg.DenomInfos{{Denom: "uatom", DisplayDenom: "atom", CoingeckoCurrency: "cosmos"}},
Name: "consumer",
Denoms: configPkg.DenomInfos{
{Denom: "uatom", DisplayDenom: "atom", CoingeckoCurrency: "cosmos"},
{Denom: "ustake", DisplayDenom: "stake"},
},
}},
}}
config := &configPkg.Config{Chains: chains}
logger := loggerPkg.GetNopLogger()
tracer := tracing.InitNoopTracer()
coingecko := coingeckoPkg.NewCoingecko(config, logger, tracer)

fetcher := NewPriceFetcher(
logger,
config,
tracer,
coingecko,
)
data, queries := fetcher.Fetch(context.Background())
assert.Len(t, queries, 1)
Expand All @@ -158,5 +151,7 @@ func TestPriceFetcherConsumerCoingeckoSuccess(t *testing.T) {

denomData, ok := chainData["atom"]
assert.True(t, ok)
assert.InEpsilon(t, 6.71, denomData, 0.01)
assert.InEpsilon(t, 6.71, denomData.Value, 0.01)
assert.Equal(t, constants.CoingeckoBaseCurrency, denomData.BaseCurrency)
assert.Equal(t, constants.PriceFetcherNameCoingecko, denomData.Source)
}
Loading

0 comments on commit ee87b65

Please sign in to comment.