-
Notifications
You must be signed in to change notification settings - Fork 7
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: support Stork oracle #13
base: master
Are you sure you want to change the base?
Changes from 19 commits
94986be
0306079
addccfa
e05c1a1
289065f
14fc651
1f9176b
54a30d3
ca84799
7c72b62
58f2a05
4136531
7d7e2bb
6ef15fd
99be119
08e14c9
dee052d
3dcaef3
1bf959c
75bd2c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,9 +2,11 @@ package main | |||||||||||||
|
||||||||||||||
import ( | ||||||||||||||
"context" | ||||||||||||||
"encoding/base64" | ||||||||||||||
"fmt" | ||||||||||||||
"io/fs" | ||||||||||||||
"io/ioutil" | ||||||||||||||
"net/http" | ||||||||||||||
"net/url" | ||||||||||||||
"os" | ||||||||||||||
"path/filepath" | ||||||||||||||
"strings" | ||||||||||||||
|
@@ -13,6 +15,7 @@ import ( | |||||||||||||
exchangetypes "github.com/InjectiveLabs/sdk-go/chain/exchange/types" | ||||||||||||||
oracletypes "github.com/InjectiveLabs/sdk-go/chain/oracle/types" | ||||||||||||||
rpchttp "github.com/cometbft/cometbft/rpc/client/http" | ||||||||||||||
"github.com/gorilla/websocket" | ||||||||||||||
cli "github.com/jawher/mow.cli" | ||||||||||||||
"github.com/pkg/errors" | ||||||||||||||
"github.com/xlab/closer" | ||||||||||||||
|
@@ -49,13 +52,18 @@ func oracleCmd(cmd *cli.Cmd) { | |||||||||||||
// External Feeds params | ||||||||||||||
dynamicFeedsDir *string | ||||||||||||||
binanceBaseURL *string | ||||||||||||||
storkFeedsDir *string | ||||||||||||||
|
||||||||||||||
// Metrics | ||||||||||||||
statsdPrefix *string | ||||||||||||||
statsdAddr *string | ||||||||||||||
statsdStuckDur *string | ||||||||||||||
statsdMocking *string | ||||||||||||||
statsdDisabled *string | ||||||||||||||
|
||||||||||||||
// Stork Oracle websocket params | ||||||||||||||
websocketUrl *string | ||||||||||||||
websocketHeader *string | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
initCosmosOptions( | ||||||||||||||
|
@@ -82,6 +90,7 @@ func oracleCmd(cmd *cli.Cmd) { | |||||||||||||
cmd, | ||||||||||||||
&binanceBaseURL, | ||||||||||||||
&dynamicFeedsDir, | ||||||||||||||
&storkFeedsDir, | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
initStatsdOptions( | ||||||||||||||
|
@@ -93,7 +102,14 @@ func oracleCmd(cmd *cli.Cmd) { | |||||||||||||
&statsdDisabled, | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
initStorkOracleWebSocket( | ||||||||||||||
cmd, | ||||||||||||||
&websocketUrl, | ||||||||||||||
&websocketHeader, | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
cmd.Action = func() { | ||||||||||||||
ctx := context.Background() | ||||||||||||||
// ensure a clean exit | ||||||||||||||
defer closer.Close() | ||||||||||||||
|
||||||||||||||
|
@@ -150,12 +166,13 @@ func oracleCmd(cmd *cli.Cmd) { | |||||||||||||
log.Infoln("waiting for GRPC services") | ||||||||||||||
time.Sleep(1 * time.Second) | ||||||||||||||
|
||||||||||||||
daemonWaitCtx, cancelWait := context.WithTimeout(context.Background(), 10*time.Second) | ||||||||||||||
daemonWaitCtx, cancelWait := context.WithTimeout(ctx, 10*time.Second) | ||||||||||||||
defer cancelWait() | ||||||||||||||
|
||||||||||||||
daemonConn := cosmosClient.QueryClient() | ||||||||||||||
if err := waitForService(daemonWaitCtx, daemonConn); err != nil { | ||||||||||||||
panic(fmt.Errorf("failed to wait for cosmos client connection: %w", err)) | ||||||||||||||
} | ||||||||||||||
cancelWait() | ||||||||||||||
feedProviderConfigs := map[oracle.FeedProvider]interface{}{ | ||||||||||||||
oracle.FeedProviderBinance: &oracle.BinanceEndpointConfig{ | ||||||||||||||
BaseURL: *binanceBaseURL, | ||||||||||||||
|
@@ -173,7 +190,7 @@ func oracleCmd(cmd *cli.Cmd) { | |||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
cfgBody, err := ioutil.ReadFile(path) | ||||||||||||||
cfgBody, err := os.ReadFile(path) | ||||||||||||||
if err != nil { | ||||||||||||||
err = errors.Wrapf(err, "failed to read dynamic feed config") | ||||||||||||||
return err | ||||||||||||||
|
@@ -201,12 +218,61 @@ func oracleCmd(cmd *cli.Cmd) { | |||||||||||||
log.Infof("found %d dynamic feed configs", len(dynamicFeedConfigs)) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
storkFeedConfigs := make([]*oracle.StorkFeedConfig, 0, 10) | ||||||||||||||
if len(*storkFeedsDir) > 0 { | ||||||||||||||
err := filepath.WalkDir(*storkFeedsDir, func(path string, d fs.DirEntry, err error) error { | ||||||||||||||
if err != nil { | ||||||||||||||
return err | ||||||||||||||
} else if d.IsDir() { | ||||||||||||||
return nil | ||||||||||||||
} else if filepath.Ext(path) != ".toml" { | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
cfgBody, err := os.ReadFile(path) | ||||||||||||||
if err != nil { | ||||||||||||||
err = errors.Wrapf(err, "failed to read stork feed config") | ||||||||||||||
return err | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
feedCfg, err := oracle.ParseStorkFeedConfig(cfgBody) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should support price updates for multiple tickers There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated flow, can you plz recheck? |
||||||||||||||
if err != nil { | ||||||||||||||
log.WithError(err).WithFields(log.Fields{ | ||||||||||||||
"filename": d.Name(), | ||||||||||||||
}).Errorln("failed to parse stork feed config") | ||||||||||||||
return nil | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
storkFeedConfigs = append(storkFeedConfigs, feedCfg) | ||||||||||||||
|
||||||||||||||
return nil | ||||||||||||||
}) | ||||||||||||||
|
||||||||||||||
if err != nil { | ||||||||||||||
err = errors.Wrapf(err, "stork feeds dir is specified, but failed to read from it: %s", *storkFeedsDir) | ||||||||||||||
log.WithError(err).Fatalln("failed to load stork feeds") | ||||||||||||||
return | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
log.Infof("found %d stork feed configs", len(storkFeedConfigs)) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
storkWebsocket, err := ConnectWebSocket(ctx, *websocketUrl, *websocketHeader) | ||||||||||||||
if err != nil { | ||||||||||||||
err = errors.Wrapf(err, "can not connect with stork oracle websocket") | ||||||||||||||
log.WithError(err).Fatalln("failed to load stork feeds") | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be a fatal error; if the Stork feed WS is down for whatever reason this would prevent the whole Injective oracle from starting, if we cannot connect it should disable pulling/pushing Stork oracle prices. Ideally, it should try to reconnect so it can eventually resume the operations There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, we will try to reconnect ws with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, this should be fine 👍 |
||||||||||||||
return | ||||||||||||||
} | ||||||||||||||
log.Info("Connected to stork websocket") | ||||||||||||||
|
||||||||||||||
svc, err := oracle.NewService( | ||||||||||||||
cosmosClient, | ||||||||||||||
exchangetypes.NewQueryClient(daemonConn), | ||||||||||||||
oracletypes.NewQueryClient(daemonConn), | ||||||||||||||
feedProviderConfigs, | ||||||||||||||
dynamicFeedConfigs, | ||||||||||||||
storkFeedConfigs, | ||||||||||||||
storkWebsocket, | ||||||||||||||
) | ||||||||||||||
if err != nil { | ||||||||||||||
log.Fatalln(err) | ||||||||||||||
|
@@ -228,3 +294,34 @@ func oracleCmd(cmd *cli.Cmd) { | |||||||||||||
closer.Hold() | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
func ConnectWebSocket(ctx context.Context, websocketUrl, urlHeader string) (conn *websocket.Conn, err error) { | ||||||||||||||
u, err := url.Parse(websocketUrl) | ||||||||||||||
if err != nil { | ||||||||||||||
log.Fatal("Error parsing URL:", err) | ||||||||||||||
return &websocket.Conn{}, err | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this fatal will panic, it should return the error instead
Suggested change
|
||||||||||||||
} | ||||||||||||||
|
||||||||||||||
header := http.Header{} | ||||||||||||||
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(urlHeader))) | ||||||||||||||
|
||||||||||||||
dialer := websocket.DefaultDialer | ||||||||||||||
dialer.EnableCompression = true | ||||||||||||||
retries := 0 | ||||||||||||||
for { | ||||||||||||||
conn, _, err = websocket.DefaultDialer.DialContext(ctx, u.String(), header) | ||||||||||||||
if err != nil { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should retry only in case of no context error
Suggested change
|
||||||||||||||
log.Infof("Failed to connect to WebSocket server: %v", err) | ||||||||||||||
retries++ | ||||||||||||||
if retries > oracle.MaxRetriesReConnectWebSocket { | ||||||||||||||
log.Infof("Reached maximum retries (%d), exiting...", oracle.MaxRetriesReConnectWebSocket) | ||||||||||||||
return | ||||||||||||||
} | ||||||||||||||
log.Infof("Retrying connect %sth in 5s...", fmt.Sprint(retries)) | ||||||||||||||
time.Sleep(5 * time.Second) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is blocking, it should acknowledge the context
Suggested change
|
||||||||||||||
} else { | ||||||||||||||
log.Infof("Connected to WebSocket server") | ||||||||||||||
return | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
provider = "Stork" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file should be committed in a different folder, with the code as it is, we cannot have price feed and stork configs in the same folder, because they are parsed twice, as Ideally, it'll be better if both file types can coexist in the same folder, it'll require less DevOps overhead and it's much clearer to know all the enabled feeds, the oracleType should be used to discriminate which type of config and the puller to get the prices There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First we should separate them into different folders then process your idea in another pr to make this pr can be merged soon |
||
ticker = "BTCUSD" | ||
pullInterval = "1m" | ||
oracleType = "Stork" | ||
message = "{\"type\":\"subscribe\",\"trace_id\":\"123\",\"data\":[\"BTCUSD\",\"ETHUSD\"]}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide guidance for
STORK_WEBSOCKET_HEADER
.The variable is left empty, which might be confusing for users. Consider adding a comment or example value to clarify what kind of data might be expected here, such as authentication tokens or specific headers required by the Stork oracle.