Skip to content

Commit

Permalink
Showing 18 changed files with 6,482 additions and 697 deletions.
2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ builds:
goos:
- 'linux'
- 'darwin'
- main: 'cmd/slinky-config/main.go'
- main: 'cmd/slinky-config/main.go'
goos:
- 'linux'
- 'darwin'
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ build: tidy
@go build -o ./build/ ./...

run-oracle-server: build
@./build/oracle --oracle-config-path ${ORACLE_CONFIG_FILE} --market-config-path ${MARKET_CONFIG_FILE}
@./build/slinky --oracle-config-path ${ORACLE_CONFIG_FILE} --market-config-path ${MARKET_CONFIG_FILE}

run-oracle-client: build
@./build/client --host localhost --port 8080
@@ -43,7 +43,7 @@ run-prom-client:

update-local-configs: build
@echo "Updating local config..."
@./build/config --oracle-config-path ${ORACLE_CONFIG_FILE} --market-config-path ${MARKET_CONFIG_FILE}
@./build/slinky-config --oracle-config-path ${ORACLE_CONFIG_FILE} --market-config-path ${MARKET_CONFIG_FILE}

start-oracle:
@echo "Starting oracle side-car, blockchain, and prometheus dashboard..."
@@ -54,7 +54,8 @@ stop-oracle:
@$(DOCKER_COMPOSE) -f docker-compose.yml down

install: tidy
@go install -mod=readonly $(BUILD_FLAGS) ./cmd/oracle
@go install -mod=readonly $(BUILD_FLAGS) ./cmd/slinky
@go install -mod=readonly $(BUILD_FLAGS) ./cmd/slinky-config

.PHONY: build run-oracle-server install

46 changes: 31 additions & 15 deletions cmd/config/main.go → cmd/slinky-config/main.go
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/spf13/cobra"
@@ -34,7 +35,7 @@ import (

var (
rootCmd = &cobra.Command{
Use: "config",
Use: "slinky-config",
Short: "Create configuration required for running slinky.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
@@ -222,77 +223,77 @@ func init() {
"oracle-config-path",
"",
"oracle.json",
"path to write the oracle config file to. this file is required to run the oracle.",
"Path to write the oracle config file to. This file is required to run the oracle.",
)
rootCmd.Flags().StringVarP(
&marketCfgPath,
"market-config-path",
"",
"market.json",
"path to write the market config file to. this file is required to run the oracle.",
"Path to write the market config file to. This file is required to run the oracle.",
)
rootCmd.Flags().StringVarP(
&chain,
"chain-id",
"chain",
"",
"",
"chain-id that we expect the oracle to be running on. ex dydx-mainnet-1, dydx-testnet-4. this should only be specified if required by the chain.",
"Chain that we expect the oracle to be running on {dydx, \"\"}. This should only be specified if required by the chain.",
)
rootCmd.Flags().StringVarP(
&nodeURL,
"node-http-url",
"",
"",
"http endpoint of the cosmos sdk node corresponding to the chain id (typically something like localhost:1317). this should only be specified if required by the chain.",
"Http endpoint of the cosmos sdk node corresponding to the chain (typically localhost:1317 or a remote API). This should only be specified if required by the chain.",
)
rootCmd.Flags().StringVarP(
&host,
"host",
"",
"0.0.0.0",
"host is the oracle / prometheus server host.",
"Host is the oracle / prometheus server host.",
)
rootCmd.Flags().StringVarP(
&pricesPort,
"port",
"",
"8080",
"port that the oracle will make prices available on. to query prices after starting the oracle, use the following command: curl http://<host>:<port>/slinky/oracle/v1/prices",
"Port that the oracle will make prices available on. To query prices after starting the oracle, use the following command: curl http://<host>:<port>/slinky/oracle/v1/prices",
)
rootCmd.Flags().StringVarP(
&prometheusPort,
"prometheus-port",
"",
"8002",
"port that the prometheus server will listen on. to query prometheus metrics after starting the oracle, use the following command: curl http://<host>:<port>/metrics",
"Port that the prometheus server will listen on. To query prometheus metrics after starting the oracle, use the following command: curl http://<host>:<port>/metrics",
)
rootCmd.Flags().BoolVarP(
&disabledMetrics,
"disable-metrics",
"",
false,
"flag that disables the prometheus server. if this is enabled the prometheus port must be specified. to query prometheus metrics after starting the oracle, use the following command: curl http://<host>:<port>/metrics",
"Flag that disables the prometheus server. If this is enabled the prometheus port must be specified. To query prometheus metrics after starting the oracle, use the following command: curl http://<host>:<port>/metrics",
)
rootCmd.Flags().BoolVarP(
&debug,
"debug-mode",
"",
false,
"flag that enables debug mode. specifically the side-car will run in debug mode. this is useful for local development / debugging.",
"Flag that enables debug mode for the side-car. This is useful for local development / debugging.",
)
rootCmd.Flags().DurationVarP(
&updateInterval,
"update-interval",
"",
500*time.Millisecond,
"interval at which the oracle will update the prices. this should be set to the interval desired by the chain.",
"Interval at which the oracle will update the prices. This should be set to the interval desired by the chain.",
)
rootCmd.Flags().DurationVarP(
&maxPriceAge,
"max-price-age",
"",
2*time.Minute,
"maximum age of a price that the oracle will accept. this should be set to the maximum age desired by the chain.",
"Maximum age of a price that the oracle will accept. This should be set to the maximum age desired by the chain.",
)
}

@@ -306,7 +307,7 @@ func main() {
func createOracleConfig() error {
// If the providers is not empty, filter the providers to include only the
// providers that are specified.
if chain == constants.DYDXMainnet.ID || chain == constants.DYDXTestnet.ID {
if strings.ToLower(chain) == constants.DYDX {
// Filter out the providers that are not supported by the dYdX chain.
validProviders := make(map[string]struct{})
for _, providers := range dydx.ProviderMapping {
@@ -382,7 +383,7 @@ func createOracleConfig() error {
// oracle is always started using the market map that is expected to be stored by the
// market map module.
func createMarketMap() error {
if chain == constants.DYDXMainnet.ID || chain == constants.DYDXTestnet.ID {
if strings.ToLower(chain) == constants.DYDX {
fmt.Fprintf(
os.Stderr,
"dYdX chain requires the use of a predetermined market map. please use the market map provided by the Skip/dYdX team or the default market map provided in /config/dydx/market.json",
@@ -397,6 +398,7 @@ func createMarketMap() error {
// TickersToProviders defines a map of tickers to their respective providers. This
// contains all of the providers that are supported per ticker.
tickersToProviders = make(map[string]mmtypes.Providers)
tickersToPaths = make(map[string]mmtypes.Paths)
)

// Iterate through all of the provider ticker configurations and update the
@@ -422,13 +424,27 @@ func createMarketMap() error {
providers := tickersToProviders[tickerStr].Providers
providers = append(providers, config)
tickersToProviders[tickerStr] = mmtypes.Providers{Providers: providers}

if _, ok := tickersToPaths[tickerStr]; !ok {
tickersToPaths[tickerStr] = mmtypes.Paths{}
}
paths := tickersToPaths[tickerStr].Paths
paths = append(paths, mmtypes.Path{Operations: []mmtypes.Operation{
{
CurrencyPair: ticker.CurrencyPair,
Invert: false,
Provider: config.Name,
},
}})
tickersToPaths[tickerStr] = mmtypes.Paths{Paths: paths}
}
}

// Create a new market map from the provider to market map.
marketMap := mmtypes.MarketMap{
Tickers: tickers,
Providers: tickersToProviders,
Paths: tickersToPaths,
}

// Validate the market map.
113 changes: 38 additions & 75 deletions cmd/oracle/main.go → cmd/slinky/main.go
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@ import (

"github.com/skip-mev/slinky/oracle"
"github.com/skip-mev/slinky/oracle/config"
"github.com/skip-mev/slinky/oracle/constants"
oraclemetrics "github.com/skip-mev/slinky/oracle/metrics"
"github.com/skip-mev/slinky/oracle/orchestrator"
"github.com/skip-mev/slinky/oracle/types"
@@ -36,12 +35,11 @@ var (
},
}

oracleCfgPath string
marketCfgPath string
runPprof bool
profilePort string
chain string
updateLocalConfig bool
oracleCfgPath string
marketCfgPath string
updateMarketCfgPath string
runPprof bool
profilePort string
)

func init() {
@@ -50,43 +48,37 @@ func init() {
"oracle-config-path",
"",
"oracle.json",
"path to the oracle config file",
"Path to the oracle config file.",
)
rootCmd.Flags().StringVarP(
&marketCfgPath,
"market-config-path",
"",
"market.json",
"path to the market config file",
"",
"Path to the market config file. If you supplied a node URL in your config, this will not be required.",
)
rootCmd.Flags().StringVarP(
&updateMarketCfgPath,
"update-market-config-path",
"",
"",
"Path where the current market config will be written. Overwrites any pre-existing file. Requires an http-node-url/marketmap provider in your oracle.json config.",
)
rootCmd.Flags().BoolVarP(
&runPprof,
"run-pprof",
"",
false,
"run pprof server",
"Run pprof server.",
)
rootCmd.Flags().StringVarP(
&profilePort,
"pprof-port",
"",
"6060",
"port for the pprof server to listen on",
)
rootCmd.Flags().StringVarP(
&chain,
"chain-id",
"",
"",
"the chain id for which the side car should run for (ex. dydx-mainnet-1)",
)
rootCmd.Flags().BoolVarP(
&updateLocalConfig,
"update-local-market-config",
"",
true,
"update the market map config when a new one is received; this will overwrite the existing config file.",
"Port for the pprof server to listen on.",
)
rootCmd.MarkFlagsMutuallyExclusive("update-market-config-path", "market-config-path")
}

// start the oracle-grpc server + oracle process, cancel on interrupt or terminate.
@@ -110,9 +102,12 @@ func runOracle() error {
return fmt.Errorf("failed to read oracle config file: %s", err.Error())
}

marketCfg, err := types.ReadMarketConfigFromFile(marketCfgPath)
if err != nil {
return fmt.Errorf("failed to read market config file: %s", err.Error())
var marketCfg mmtypes.MarketMap
if marketCfgPath != "" {
marketCfg, err = types.ReadMarketConfigFromFile(marketCfgPath)
if err != nil {
return fmt.Errorf("failed to read market config file: %s", err.Error())
}
}

var logger *zap.Logger
@@ -135,31 +130,33 @@ func runOracle() error {
)

metrics := oraclemetrics.NewMetricsFromConfig(cfg.Metrics)
aggregator, err := oraclemath.NewMedianAggregator(
logger,
marketCfg,
metrics,
)
if err != nil {
return fmt.Errorf("failed to create data aggregator: %w", err)
}

// Define the orchestrator and oracle options. These determine how the orchestrator and oracle are created & executed.
orchestratorOpts := []orchestrator.Option{
orchestrator.WithLogger(logger),
orchestrator.WithMarketMap(marketCfg),
orchestrator.WithPriceAPIQueryHandlerFactory(oraclefactory.APIQueryHandlerFactory), // Replace with custom API query handler factory.
orchestrator.WithPriceWebSocketQueryHandlerFactory(oraclefactory.WebSocketQueryHandlerFactory), // Replace with custom websocket query handler factory.
orchestrator.WithMarketMapperFactory(oraclefactory.MarketMapProviderFactory),
orchestrator.WithAggregator(aggregator),
}
if updateMarketCfgPath != "" {
orchestratorOpts = append(orchestratorOpts, orchestrator.WithWriteTo(updateMarketCfgPath))
}
oracleOpts := []oracle.Option{
oracle.WithLogger(logger),
oracle.WithUpdateInterval(cfg.UpdateInterval),
oracle.WithMetrics(metrics),
oracle.WithMaxCacheAge(cfg.MaxPriceAge),
}

if chain == constants.DYDXMainnet.ID || chain == constants.DYDXTestnet.ID {
customOrchestratorOps, customOracleOpts, err := dydxOptions(logger, marketCfg, metrics)
if err != nil {
return fmt.Errorf("failed to create dydx orchestrator and oracle options: %w", err)
}

orchestratorOpts = append(orchestratorOpts, customOrchestratorOps...)
oracleOpts = append(oracleOpts, customOracleOpts...)
} else {
logger.Warn("no custom orchestrator or oracle options for chain; running default version")
oracle.WithDataAggregator(aggregator),
}

// Create the orchestrator and start the orchestrator.
@@ -227,37 +224,3 @@ func runOracle() error {
}
return nil
}

// dydxOptions specifies the custom orchestrator and oracle options for dYdX.
func dydxOptions(
logger *zap.Logger,
marketCfg mmtypes.MarketMap,
metrics oraclemetrics.Metrics,
) ([]orchestrator.Option, []oracle.Option, error) {
// dYdX uses the median index price aggregation strategy.
logger.Info("running dYdX sidecar; adding custom options for orchestrator and oracle")
aggregator, err := oraclemath.NewMedianAggregator(
logger,
marketCfg,
metrics,
)
if err != nil {
return nil, nil, err
}

// The oracle must be configured with the median index price aggregator.
customOracleOpts := []oracle.Option{
oracle.WithDataAggregator(aggregator),
}

// Additionally, dYdX requires a custom market map provider that fetches market params from the chain.
customOrchestratorOps := []orchestrator.Option{
orchestrator.WithMarketMapperFactory(oraclefactory.DefaultDYDXMarketMapProvider),
orchestrator.WithAggregator(aggregator),
}
if updateLocalConfig {
customOrchestratorOps = append(customOrchestratorOps, orchestrator.WithWriteTo(marketCfgPath))
}

return customOrchestratorOps, customOracleOpts, nil
}
Loading

0 comments on commit 2f64382

Please sign in to comment.