diff --git a/feeder/priceprovider/sources/binance.go b/feeder/priceprovider/sources/binance.go index 20f6fcc..28bb5da 100644 --- a/feeder/priceprovider/sources/binance.go +++ b/feeder/priceprovider/sources/binance.go @@ -7,6 +7,7 @@ import ( "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" ) const ( @@ -31,7 +32,7 @@ func BinanceSymbolCsv(symbols set.Set[types.Symbol]) string { // BinancePriceUpdate returns the prices given the symbols or an error. // Uses the Binance API at https://docs.binance.us/#price-data. -func BinancePriceUpdate(symbols set.Set[types.Symbol]) (rawPrices map[types.Symbol]float64, err error) { +func BinancePriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (rawPrices map[types.Symbol]float64, err error) { url := "https://api.binance.us/api/v3/ticker/price?symbols=%5B" + BinanceSymbolCsv(symbols) + "%5D" resp, err := http.Get(url) if err != nil { diff --git a/feeder/priceprovider/sources/binance_test.go b/feeder/priceprovider/sources/binance_test.go index 66ead52..5807643 100644 --- a/feeder/priceprovider/sources/binance_test.go +++ b/feeder/priceprovider/sources/binance_test.go @@ -1,20 +1,21 @@ package sources import ( + "io" "testing" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) func TestBinanceSource(t *testing.T) { t.Run("success", func(t *testing.T) { - rawPrices, err := BinancePriceUpdate(set.New[types.Symbol]("BTCUSD", "ETHUSD")) + rawPrices, err := BinancePriceUpdate(set.New[types.Symbol]("BTCUSD", "ETHUSD"), zerolog.New(io.Discard)) require.NoError(t, err) require.Equal(t, 2, len(rawPrices)) require.NotZero(t, rawPrices["BTCUSD"]) require.NotZero(t, rawPrices["ETHUSD"]) }) - } diff --git a/feeder/priceprovider/sources/bitfinex.go b/feeder/priceprovider/sources/bitfinex.go index b4d9864..9e613c6 100644 --- a/feeder/priceprovider/sources/bitfinex.go +++ b/feeder/priceprovider/sources/bitfinex.go @@ -8,13 +8,14 @@ import ( "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" ) const ( Bitfinex = "bitfinex" ) -var _ types.FetchPricesFunc = BinancePriceUpdate +var _ types.FetchPricesFunc = BitfinexPriceUpdate func BitfinexSymbolCsv(symbols set.Set[types.Symbol]) string { s := "" @@ -25,7 +26,7 @@ func BitfinexSymbolCsv(symbols set.Set[types.Symbol]) string { } // BitfinexPriceUpdate returns the prices given the symbols or an error. -func BitfinexPriceUpdate(symbols set.Set[types.Symbol]) (rawPrices map[types.Symbol]float64, err error) { +func BitfinexPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (rawPrices map[types.Symbol]float64, err error) { type ticker []interface{} const size = 11 const lastPriceIndex = 7 diff --git a/feeder/priceprovider/sources/bitfinex_test.go b/feeder/priceprovider/sources/bitfinex_test.go index 027b138..e1a0422 100644 --- a/feeder/priceprovider/sources/bitfinex_test.go +++ b/feeder/priceprovider/sources/bitfinex_test.go @@ -1,20 +1,21 @@ package sources import ( + "io" "testing" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) func TestBitfinexSource(t *testing.T) { t.Run("success", func(t *testing.T) { - rawPrices, err := BitfinexPriceUpdate(set.New[types.Symbol]("tBTCUSD", "tETHUSD")) + rawPrices, err := BitfinexPriceUpdate(set.New[types.Symbol]("tBTCUSD", "tETHUSD"), zerolog.New(io.Discard)) require.NoError(t, err) require.Equal(t, 2, len(rawPrices)) require.NotZero(t, rawPrices["tBTCUSD"]) require.NotZero(t, rawPrices["tETHUSD"]) }) - } diff --git a/feeder/priceprovider/sources/coingecko.go b/feeder/priceprovider/sources/coingecko.go index 8ba3cc2..392eb37 100644 --- a/feeder/priceprovider/sources/coingecko.go +++ b/feeder/priceprovider/sources/coingecko.go @@ -9,6 +9,7 @@ import ( "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" ) const ( @@ -27,15 +28,13 @@ type CoingeckoConfig struct { } func CoingeckoPriceUpdate(sourceConfig json.RawMessage) types.FetchPricesFunc { - return func(symbols set.Set[types.Symbol]) (map[types.Symbol]float64, error) { + return func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) { c, err := extractConfig(sourceConfig) if err != nil { return nil, err } - baseURL := buildURL(symbols, c) - - res, err := http.Get(baseURL) + res, err := http.Get(buildURL(symbols, c)) if err != nil { return nil, err } @@ -46,7 +45,7 @@ func CoingeckoPriceUpdate(sourceConfig json.RawMessage) types.FetchPricesFunc { return nil, err } - rawPrices, err := extractPricesFromResponse(symbols, response) + rawPrices, err := extractPricesFromResponse(symbols, response, logger) if err != nil { return nil, err } @@ -67,7 +66,7 @@ func extractConfig(jsonConfig json.RawMessage) (*CoingeckoConfig, error) { return c, nil } -func extractPricesFromResponse(symbols set.Set[types.Symbol], response []byte) (map[types.Symbol]float64, error) { +func extractPricesFromResponse(symbols set.Set[types.Symbol], response []byte, logger zerolog.Logger) (map[types.Symbol]float64, error) { var result map[string]CoingeckoTicker err := json.Unmarshal(response, &result) if err != nil { @@ -79,7 +78,8 @@ func extractPricesFromResponse(symbols set.Set[types.Symbol], response []byte) ( if price, ok := result[string(symbol)]; ok { rawPrices[symbol] = price.Price } else { - return nil, fmt.Errorf("symbol %s not found in response: %s\n", symbol, response) + logger.Err(err).Msg(fmt.Sprintf("failed to parse price for %s on data source %s", symbol, Coingecko)) + continue } } @@ -101,8 +101,8 @@ func buildURL(symbols set.Set[types.Symbol], c *CoingeckoConfig) string { params.Add(ApiKeyParam, c.ApiKey) } - baseURL = baseURL + params.Encode() - return baseURL + url := baseURL + params.Encode() + return url } // coingeckoSymbolCsv returns the symbols as a comma separated string. diff --git a/feeder/priceprovider/sources/coingecko_test.go b/feeder/priceprovider/sources/coingecko_test.go index 61b8e60..7773859 100644 --- a/feeder/priceprovider/sources/coingecko_test.go +++ b/feeder/priceprovider/sources/coingecko_test.go @@ -2,11 +2,13 @@ package sources import ( "encoding/json" + "io" "testing" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" "github.com/jarcoal/httpmock" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) @@ -24,6 +26,7 @@ func TestCoingeckoPriceUpdate(t *testing.T) { "bitcoin", "ethereum", ), + zerolog.New(io.Discard), ) require.NoError(t, err) @@ -59,6 +62,7 @@ func TestCoingeckoWithConfig(t *testing.T) { "bitcoin", "ethereum", ), + zerolog.New(io.Discard), ) require.NoError(t, err) @@ -83,6 +87,7 @@ func TestCoingeckoWithConfig(t *testing.T) { "bitcoin", "ethereum", ), + zerolog.New(io.Discard), ) require.NoError(t, err) diff --git a/feeder/priceprovider/sources/coinmarketcap.go b/feeder/priceprovider/sources/coinmarketcap.go index 5dbbd02..e1d9504 100644 --- a/feeder/priceprovider/sources/coinmarketcap.go +++ b/feeder/priceprovider/sources/coinmarketcap.go @@ -10,6 +10,7 @@ import ( "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" ) const ( @@ -39,9 +40,9 @@ type CoinmarketcapConfig struct { ApiKey string `json:"api_key"` } -func CoinmarketcapPriceUpdate(rawConfig json.RawMessage) types.FetchPricesFunc { - return func(symbols set.Set[types.Symbol]) (map[types.Symbol]float64, error) { - config, err := getConfig(rawConfig) +func CoinmarketcapPriceUpdate(coinmarketcapConfig json.RawMessage) types.FetchPricesFunc { + return func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) { + config, err := getConfig(coinmarketcapConfig) if err != nil { return nil, err } @@ -63,7 +64,7 @@ func CoinmarketcapPriceUpdate(rawConfig json.RawMessage) types.FetchPricesFunc { return nil, err } - rawPrices, err := getPricesFromResponse(symbols, response) + rawPrices, err := getPricesFromResponse(symbols, response, logger) if err != nil { return nil, err } @@ -84,7 +85,7 @@ func getConfig(jsonConfig json.RawMessage) (*CoinmarketcapConfig, error) { return c, nil } -func getPricesFromResponse(symbols set.Set[types.Symbol], response []byte) (map[types.Symbol]float64, error) { +func getPricesFromResponse(symbols set.Set[types.Symbol], response []byte, logger zerolog.Logger) (map[types.Symbol]float64, error) { var respCmc CmcResponse err := json.Unmarshal(response, &respCmc) if err != nil { @@ -101,7 +102,8 @@ func getPricesFromResponse(symbols set.Set[types.Symbol], response []byte) (map[ if price, ok := cmcPrice[string(symbol)]; ok { rawPrices[symbol] = price } else { - return nil, fmt.Errorf("symbol %s not found in response: %s\n", symbol, response) + logger.Err(err).Msg(fmt.Sprintf("failed to parse price for %s on data source %s", symbol, CoinMarketCap)) + continue } } diff --git a/feeder/priceprovider/sources/coinmarketcap_test.go b/feeder/priceprovider/sources/coinmarketcap_test.go index c8a1189..c8ee795 100644 --- a/feeder/priceprovider/sources/coinmarketcap_test.go +++ b/feeder/priceprovider/sources/coinmarketcap_test.go @@ -2,11 +2,13 @@ package sources import ( "encoding/json" + "io" "testing" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" "github.com/jarcoal/httpmock" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) @@ -24,6 +26,7 @@ func TestCoinmarketcapPriceUpdate(t *testing.T) { "bitcoin", "ethereum", ), + zerolog.New(io.Discard), ) require.NoError(t, err) diff --git a/feeder/priceprovider/sources/gateio.go b/feeder/priceprovider/sources/gateio.go index 02fc99c..a6e994c 100644 --- a/feeder/priceprovider/sources/gateio.go +++ b/feeder/priceprovider/sources/gateio.go @@ -2,12 +2,14 @@ package sources import ( "encoding/json" + "fmt" "io" "net/http" "strconv" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" ) const ( @@ -18,7 +20,7 @@ var _ types.FetchPricesFunc = GateIoPriceUpdate // GateIoPriceUpdate returns the prices given the symbols or an error. // Uses the GateIo API at https://www.gate.io/docs/developers/apiv4/en/#get-details-of-a-specifc-currency-pair. -func GateIoPriceUpdate(symbols set.Set[types.Symbol]) (rawPrices map[types.Symbol]float64, err error) { +func GateIoPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (rawPrices map[types.Symbol]float64, err error) { url := "https://api.gateio.ws/api/v4/spot/tickers" resp, err := http.Get(url) if err != nil { @@ -46,7 +48,8 @@ func GateIoPriceUpdate(symbols set.Set[types.Symbol]) (rawPrices map[types.Symbo price, err := strconv.ParseFloat(ticker["last"].(string), 64) if err != nil { - price = -1 + logger.Err(err).Msg(fmt.Sprintf("failed to parse price for %s on data source %s", symbol, GateIo)) + continue } rawPrices[symbol] = price diff --git a/feeder/priceprovider/sources/gateio_test.go b/feeder/priceprovider/sources/gateio_test.go index 0e85cf7..2bdab81 100644 --- a/feeder/priceprovider/sources/gateio_test.go +++ b/feeder/priceprovider/sources/gateio_test.go @@ -1,16 +1,18 @@ package sources import ( + "io" "testing" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) func TestGateIoSource(t *testing.T) { t.Run("success", func(t *testing.T) { - rawPrices, err := GateIoPriceUpdate(set.New[types.Symbol]("BTC_USDT", "ETH_USDT")) + rawPrices, err := GateIoPriceUpdate(set.New[types.Symbol]("BTC_USDT", "ETH_USDT"), zerolog.New(io.Discard)) require.NoError(t, err) require.Equal(t, 2, len(rawPrices)) require.NotZero(t, rawPrices["BTC_USDT"]) diff --git a/feeder/priceprovider/sources/okex.go b/feeder/priceprovider/sources/okex.go index bf86686..4910fe5 100644 --- a/feeder/priceprovider/sources/okex.go +++ b/feeder/priceprovider/sources/okex.go @@ -2,12 +2,14 @@ package sources import ( "encoding/json" + "fmt" "io" "net/http" "strconv" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" ) const ( @@ -27,7 +29,7 @@ type Response struct { // OkexPriceUpdate returns the prices for given symbols or an error. // Uses OKEX API at https://www.okx.com/docs-v5/en/#rest-api-market-data. -func OkexPriceUpdate(symbols set.Set[types.Symbol]) (rawPrices map[types.Symbol]float64, err error) { +func OkexPriceUpdate(symbols set.Set[types.Symbol], logger zerolog.Logger) (rawPrices map[types.Symbol]float64, err error) { url := "https://www.okx.com/api/v5/market/tickers?instType=SPOT" resp, err := http.Get(url) @@ -57,7 +59,8 @@ func OkexPriceUpdate(symbols set.Set[types.Symbol]) (rawPrices map[types.Symbol] price, err := strconv.ParseFloat(ticker.Price, 64) if err != nil { - price = -1 + logger.Err(err).Msg(fmt.Sprintf("failed to parse price for %s on data source %s", symbol, Okex)) + continue } rawPrices[symbol] = price diff --git a/feeder/priceprovider/sources/okex_test.go b/feeder/priceprovider/sources/okex_test.go index c70785c..39f2bbe 100644 --- a/feeder/priceprovider/sources/okex_test.go +++ b/feeder/priceprovider/sources/okex_test.go @@ -1,16 +1,18 @@ package sources import ( + "io" "testing" "github.com/NibiruChain/nibiru/x/common/set" "github.com/NibiruChain/pricefeeder/types" + "github.com/rs/zerolog" "github.com/stretchr/testify/require" ) func TestOKexPriceUpdate(t *testing.T) { t.Run("success", func(t *testing.T) { - rawPrices, err := OkexPriceUpdate(set.New[types.Symbol]("BTC-USDT", "ETH-USDT")) + rawPrices, err := OkexPriceUpdate(set.New[types.Symbol]("BTC-USDT", "ETH-USDT"), zerolog.New(io.Discard)) require.NoError(t, err) require.Equal(t, 2, len(rawPrices)) require.NotZero(t, rawPrices["BTC-USDT"]) diff --git a/feeder/priceprovider/sources/tick_source.go b/feeder/priceprovider/sources/tick_source.go index 5c0a394..760ee78 100644 --- a/feeder/priceprovider/sources/tick_source.go +++ b/feeder/priceprovider/sources/tick_source.go @@ -8,10 +8,8 @@ import ( "github.com/rs/zerolog" ) -var ( - // UpdateTick defines the wait time between price updates. - UpdateTick = 8 * time.Second -) +// UpdateTick defines the wait time between price updates. +var UpdateTick = 8 * time.Second var _ types.Source = (*TickSource)(nil) @@ -41,7 +39,7 @@ type TickSource struct { done chan struct{} // internal signal to wait for shutdown operations tick *time.Ticker symbols set.Set[types.Symbol] // symbols as named on the third party data source - fetchPrices func(symbols set.Set[types.Symbol]) (map[types.Symbol]float64, error) + fetchPrices func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) priceUpdateChannel chan map[types.Symbol]types.RawPrice } @@ -56,7 +54,7 @@ func (s *TickSource) loop() { case <-s.tick.C: s.logger.Debug().Msg("received tick, updating prices") - rawPrices, err := s.fetchPrices(s.symbols) + rawPrices, err := s.fetchPrices(s.symbols, s.logger) if err != nil { s.logger.Err(err).Msg("failed to update prices") break // breaks the current select case, not the for cycle diff --git a/feeder/priceprovider/sources/tick_source_test.go b/feeder/priceprovider/sources/tick_source_test.go index 7e9fe59..bc8d376 100644 --- a/feeder/priceprovider/sources/tick_source_test.go +++ b/feeder/priceprovider/sources/tick_source_test.go @@ -26,10 +26,11 @@ func TestTickSource(t *testing.T) { expectedSymbols := set.New[types.Symbol]("tBTCUSDT") expectedPrices := map[types.Symbol]float64{"tBTCUSDT": 250_000.56} - ts := NewTickSource(expectedSymbols, func(symbols set.Set[types.Symbol]) (map[types.Symbol]float64, error) { - require.Equal(t, expectedSymbols, symbols) - return expectedPrices, nil - }, zerolog.New(io.Discard)) + ts := NewTickSource(expectedSymbols, + func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) { + require.Equal(t, expectedSymbols, symbols) + return expectedPrices, nil + }, zerolog.New(io.Discard)) defer ts.Close() @@ -61,7 +62,7 @@ func TestTickSource(t *testing.T) { expectedSymbols := set.New[types.Symbol]("tBTCUSDT") expectedPrices := map[types.Symbol]float64{"tBTCUSDT": 250_000.56} - ts := NewTickSource(expectedSymbols, func(symbols set.Set[types.Symbol]) (map[types.Symbol]float64, error) { + ts := NewTickSource(expectedSymbols, func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) { return expectedPrices, nil }, zerolog.New(mw)) @@ -80,7 +81,7 @@ func TestTickSource(t *testing.T) { return written, nil }} - ts := NewTickSource(set.New[types.Symbol]("tBTCUSDT"), func(symbols set.Set[types.Symbol]) (map[types.Symbol]float64, error) { + ts := NewTickSource(set.New[types.Symbol]("tBTCUSDT"), func(symbols set.Set[types.Symbol], logger zerolog.Logger) (map[types.Symbol]float64, error) { return nil, fmt.Errorf("sentinel error") }, zerolog.New(mw)) defer ts.Close() diff --git a/types/price.go b/types/price.go index 04d28fc..1ae69b6 100644 --- a/types/price.go +++ b/types/price.go @@ -5,6 +5,7 @@ import ( "github.com/NibiruChain/nibiru/x/common/asset" "github.com/NibiruChain/nibiru/x/common/set" + "github.com/rs/zerolog" ) const ( @@ -35,4 +36,4 @@ type Price struct { // The returned map must map symbol to its float64 price, or an error. // If there's a failure in updating only one price then the map can be returned // without the provided symbol. -type FetchPricesFunc func(symbols set.Set[Symbol]) (map[Symbol]float64, error) +type FetchPricesFunc func(symbols set.Set[Symbol], logger zerolog.Logger) (map[Symbol]float64, error)