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: downtime detector command #293

Closed
wants to merge 4 commits into from
Closed
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
142 changes: 142 additions & 0 deletions cmd/downtime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package cmd

import (
"context"
"fmt"
"io"
"os"
"strings"
"time"

"github.com/rs/zerolog"
"github.com/spf13/cobra"

"github.com/ojo-network/price-feeder/config"
"github.com/ojo-network/price-feeder/oracle"
"github.com/ojo-network/price-feeder/oracle/client"
)

func getDowntimeCmd() *cobra.Command {
downtimeCmd := &cobra.Command{
Use: "downtime [config-file]",
Args: cobra.ExactArgs(1),
Short: "Produces a list of assets which currently have downtime",
RunE: func(cmd *cobra.Command, args []string) error {
logLvlStr, err := cmd.Flags().GetString(flagLogLevel)
if err != nil {
return err
}

logLvl, err := zerolog.ParseLevel(logLvlStr)
if err != nil {
return err
}

logFormatStr, err := cmd.Flags().GetString(flagLogFormat)
if err != nil {
return err
}

var logWriter io.Writer
switch strings.ToLower(logFormatStr) {
case logLevelJSON:
logWriter = os.Stderr

case logLevelText:
logWriter = zerolog.ConsoleWriter{Out: os.Stderr}

default:
return fmt.Errorf("invalid logging format: %s", logFormatStr)
}

logger := zerolog.New(logWriter).Level(logLvl).With().Timestamp().Logger()

cfg, err := config.LoadConfigFromFlags(args[0], "")
if err != nil {
return err
}

ctx, cancel := context.WithCancel(cmd.Context())

// listen for and trap any OS signal to gracefully shutdown and exit
trapSignal(cancel, logger)

rpcTimeout, err := time.ParseDuration(cfg.RPC.RPCTimeout)
if err != nil {
return fmt.Errorf("failed to parse RPC timeout: %w", err)
}

// Gather pass via env variable || std input
keyringPass, err := getKeyringPassword()
if err != nil {
return err
}

oracleClient, err := client.NewOracleClient(
ctx,
logger,
cfg.Account.ChainID,
cfg.Keyring.Backend,
cfg.Keyring.Dir,
keyringPass,
cfg.RPC.TMRPCEndpoint,
rpcTimeout,
cfg.Account.Address,
cfg.Account.Validator,
cfg.RPC.GRPCEndpoint,
cfg.GasAdjustment,
)
if err != nil {
return err
}

providerTimeout, err := time.ParseDuration(cfg.ProviderTimeout)
if err != nil {
return fmt.Errorf("failed to parse provider timeout: %w", err)
}
deviations, err := cfg.DeviationsMap()
if err != nil {
return err
}
oracle := oracle.New(
logger,
oracleClient,
cfg.ProviderPairs(),
providerTimeout,
deviations,
cfg.ProviderEndpointsMap(),
)

params, err := oracle.GetParams(ctx)
if err != nil {
return err
}
rates, err := oracle.GetExchangeRates(ctx)
if err != nil {
return err
}
// Find which assets are missing in rates
// We cannot compare length, because there may be
// multiple oracle entries for a single exchange rate.
existingRates := make(map[string]interface{}, len(rates))
for _, rate := range rates {
existingRates[strings.ToUpper(rate.Denom)] = struct{}{}
}
var missingRates []string
for _, asset := range params.AcceptList {
if _, ok := existingRates[strings.ToUpper(asset.SymbolDenom)]; !ok {
missingRates = append(missingRates, asset.SymbolDenom)
}
}

if len(missingRates) < 1 {
fmt.Println("No downtime detected")
return nil
}
fmt.Println("Missing rates for assets: ", missingRates)
return nil
},
}

return downtimeCmd
}
1 change: 1 addition & 0 deletions cmd/price-feeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func init() {
rootCmd.PersistentFlags().Bool(flagSkipProviderCheck, false, "skip the coingecko API provider check")

rootCmd.AddCommand(getVersionCmd())
rootCmd.AddCommand(getDowntimeCmd())
}

// Execute adds all child commands to the root command and sets flags appropriately.
Expand Down
27 changes: 26 additions & 1 deletion oracle/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,6 @@ func (o *Oracle) GetComputedPrices(
providerCandles types.AggregatedProviderCandles,
providerPrices types.AggregatedProviderPrices,
) (types.CurrencyPairDec, error) {

conversionRates, err := CalcCurrencyPairRates(
providerCandles,
providerPrices,
Expand Down Expand Up @@ -394,6 +393,32 @@ func (o *Oracle) GetParams(ctx context.Context) (oracletypes.Params, error) {
return queryResponse.Params, nil
}

// GetParams returns the current on-chain exchange rates.
func (o *Oracle) GetExchangeRates(ctx context.Context) (sdk.DecCoins, error) {
grpcConn, err := grpc.Dial(
o.oracleClient.GRPCEndpoint,
// the Cosmos SDK doesn't support any transport security mechanism
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(dialerFunc),
)
if err != nil {
return sdk.DecCoins{}, fmt.Errorf("failed to dial Cosmos gRPC service: %w", err)
}

defer grpcConn.Close()
queryClient := oracletypes.NewQueryClient(grpcConn)

ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()

queryResponse, err := queryClient.ExchangeRates(ctx, &oracletypes.QueryExchangeRates{})
if err != nil {
return sdk.DecCoins{}, fmt.Errorf("failed to get x/oracle exchange rates: %w", err)
}

return queryResponse.ExchangeRates, nil
}

func (o *Oracle) getOrSetProvider(ctx context.Context, providerName types.ProviderName) (provider.Provider, error) {
var (
priceProvider provider.Provider
Expand Down
Loading