diff --git a/CHANGELOG.md b/CHANGELOG.md index 5878d9b9..c6a554dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,12 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## Unreleased +## v2.4.0 + +- [337](https://github.com/ojo-network/price-feeder/pull/337) feat: support fixed gas for vote and prevote transactions. + +## Old + ### Improvements - [48](https://github.com/ojo-network/price-feeder/pull/48) Update goreleaser to have release process for umee price-feeder - [55](https://github.com/ojo-network/price-feeder/pull/55) Update DockerFile to work in umee's e2e test diff --git a/README.md b/README.md index ab1918d2..a51c1e66 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ core differences are as follows: - The `price-feeder` combines both `feeder` and `price-server` into a single Golang-based application for better UX, testability, and integration. -This instance of the price feeder is intended to be used as a library for [Umee's Price Feeder](https://github.com/umee-network/umee/tree/main/price-feeder), in order to prevent double work. - ## Background The `price-feeder` tool is responsible for performing the following: @@ -53,12 +51,25 @@ The keyring's password is defined via environment variables or user input. More information on the keyring can be found [here](#keyring) Please see the [example configuration](price-feeder.example.toml) for more details. The path to the node-config is required as the first argument. You can optionally add all configuration options to the node-config file or use the config-dir flag to point to a directory containing the other configuration files. -The files in the provider-config folder define what exchange rates to fetch and what providers to get them from. They also contain deviation thresholds and API endpoints. Please see the [example configuration](ojo-provider-config) for more details. +The files in the provider-config folder define what exchange rates to fetch and what providers to get them from. They also contain deviation thresholds and API endpoints. Please see the [example configuration](umee-provider-config) for more details. ```shell $ price-feeder /path/to/price_feeder_config.toml ``` +Chain rules for checking the free oracle transactions are: + +- must be only prevote or vote +- gas is limited to [`MaxMsgGasUsage`](https://github.com/ojo-network/ojo/blob/main/ante/fee.go#L13) constant. + +So, if you don't want to pay for gas, TX must be below `MaxMsgGasUsage`. If you set too much gas (which is what is happening when you use high `gas_adjustment`, eg more than 2), then the tx will allocate 2x gas, and hence will go above the free quota, so you would need to attach fee to pay for that gas. +The easiest is to just set constant gas. We recommend 10k below the `MaxMsgGasUsage`. + +In the PF config file you can set either: + +* `gas_adjustment` +* or `gas_prevote` and `gas_vote` - fixed amount of gas used for `MsgAggregateExchangeRatePrevote` and `MsgAggregateExchangeRateVote` transactions respectively. + ## Configuration ### `telemetry` diff --git a/cmd/price-feeder.go b/cmd/price-feeder.go index ed94a404..a522be42 100644 --- a/cmd/price-feeder.go +++ b/cmd/price-feeder.go @@ -150,6 +150,8 @@ func priceFeederCmdHandler(cmd *cobra.Command, args []string) error { cfg.Account.Validator, cfg.RPC.GRPCEndpoint, cfg.GasAdjustment, + cfg.GasPrevote, + cfg.GasVote, ) if err != nil { return err diff --git a/config/config.go b/config/config.go index 11a300dc..92cc6c2b 100644 --- a/config/config.go +++ b/config/config.go @@ -3,6 +3,7 @@ package config import ( "errors" "fmt" + "strings" "time" "github.com/cosmos/cosmos-sdk/telemetry" @@ -46,7 +47,9 @@ type ( Keyring Keyring `mapstructure:"keyring" validate:"required,gt=0,dive,required"` RPC RPC `mapstructure:"rpc" validate:"required,gt=0,dive,required"` Telemetry telemetry.Config `mapstructure:"telemetry"` - GasAdjustment float64 `mapstructure:"gas_adjustment" validate:"required"` + GasAdjustment float64 `mapstructure:"gas_adjustment"` + GasVote uint64 `mapstructure:"gas_vote"` + GasPrevote uint64 `mapstructure:"gas_prevote"` ProviderTimeout string `mapstructure:"provider_timeout"` ProviderMinOverride bool `mapstructure:"provider_min_override"` ProviderEndpoints []provider.Endpoint `mapstructure:"provider_endpoints" validate:"dive"` @@ -141,10 +144,12 @@ func (c Config) Validate() (err error) { if err = c.validateCurrencyPairs(); err != nil { return err } - if err = c.validateDeviations(); err != nil { return err } + if err = c.validateGas(); err != nil { + return err + } validate.RegisterStructValidation(telemetryValidation, telemetry.Config{}) validate.RegisterStructValidation(endpointValidation, provider.Endpoint{}) @@ -165,6 +170,26 @@ func (c Config) validateDeviations() error { return nil } +func (c Config) validateGas() error { + var errs []string + if (c.GasPrevote > 0) != (c.GasVote > 0) { + errs = append(errs, + fmt.Sprintf("%s%s", "if gas_prevote is set, then gas_vote must be set as well;", + "similarly, if gas_vote is set, then gas_prevote must be set as well"), + ) + } + if c.GasVote <= 0 && c.GasAdjustment <= 0 { + errs = append(errs, "either gas_vote and gas_prevote must be set or gas_adjustment must be set") + } + if c.GasAdjustment > 0 && c.GasVote > 0 { + errs = append(errs, "gas and gas adjustment may not both be set") + } + if len(errs) > 0 { + return errors.New(strings.Join(errs, ". ")) + } + return nil +} + func (c Config) validateCurrencyPairs() error { OUTER: for _, cp := range c.CurrencyPairs { diff --git a/config/supported_assets.go b/config/supported_assets.go index 1dde7b1d..096b9771 100644 --- a/config/supported_assets.go +++ b/config/supported_assets.go @@ -41,11 +41,13 @@ var ( {Base: "ATOM", Quote: "USD"}: {}, {Base: "OSMO", Quote: "USD"}: {}, - {Base: "OSMO", Quote: "USDT"}: {}, - {Base: "JUNO", Quote: "USDT"}: {}, - {Base: "UMEE", Quote: "USDT"}: {}, - {Base: "WETH", Quote: "USDC"}: {}, - {Base: "WBTC", Quote: "WETH"}: {}, + {Base: "OSMO", Quote: "USDT"}: {}, + {Base: "JUNO", Quote: "USDT"}: {}, + {Base: "UMEE", Quote: "USDT"}: {}, + {Base: "WETH", Quote: "USDC"}: {}, + {Base: "WBTC", Quote: "WETH"}: {}, + {Base: "STARS", Quote: "OSMO"}: {}, + {Base: "TIA", Quote: "USDT"}: {}, } SupportedUniswapCurrencies = map[string]struct{}{ diff --git a/oracle/client/client.go b/oracle/client/client.go index e85ea829..2a2a847a 100644 --- a/oracle/client/client.go +++ b/oracle/client/client.go @@ -43,6 +43,8 @@ type ( Encoding testutil.TestEncodingConfig GasPrices string GasAdjustment float64 + GasPrevote uint64 + GasVote uint64 GRPCEndpoint string KeyringPassphrase string ChainHeight *ChainHeight @@ -67,6 +69,8 @@ func NewOracleClient( validatorAddrString string, grpcEndpoint string, gasAdjustment float64, + gasPrevote uint64, + gasVote uint64, ) (OracleClient, error) { oracleAddr, err := sdk.AccAddressFromBech32(oracleAddrString) if err != nil { @@ -87,6 +91,8 @@ func NewOracleClient( ValidatorAddrString: validatorAddrString, Encoding: umeeapp.MakeEncodingConfig(), GasAdjustment: gasAdjustment, + GasPrevote: gasPrevote, + GasVote: gasVote, GRPCEndpoint: grpcEndpoint, } @@ -135,7 +141,7 @@ func (r *passReader) Read(p []byte) (n int, err error) { // BroadcastTx attempts to broadcast a signed transaction. If it fails, a few re-attempts // will be made until the transaction succeeds or ultimately times out or fails. // Ref: https://github.com/terra-money/oracle-feeder/blob/baef2a4a02f57a2ffeaa207932b2e03d7fb0fb25/feeder/src/vote.ts#L230 -func (oc OracleClient) BroadcastTx(nextBlockHeight, timeoutHeight int64, msgs ...sdk.Msg) error { +func (oc OracleClient) BroadcastTx(nextBlockHeight, timeoutHeight int64, isPrevote bool, msgs sdk.Msg) error { maxBlockHeight := nextBlockHeight + timeoutHeight lastCheckHeight := nextBlockHeight - 1 @@ -144,7 +150,7 @@ func (oc OracleClient) BroadcastTx(nextBlockHeight, timeoutHeight int64, msgs .. return err } - factory, err := oc.CreateTxFactory() + factory, err := oc.CreateTxFactory(isPrevote) if err != nil { return err } @@ -163,7 +169,7 @@ func (oc OracleClient) BroadcastTx(nextBlockHeight, timeoutHeight int64, msgs .. // set last check height to latest block height lastCheckHeight = latestBlockHeight - resp, err := BroadcastTx(clientCtx, factory, msgs...) + resp, err := BroadcastTx(clientCtx, factory, msgs) if resp != nil && resp.Code != 0 { telemetry.IncrCounter(1, "failure", "tx", "code") err = fmt.Errorf("invalid response code from tx: %d", resp.Code) @@ -263,21 +269,34 @@ func (oc OracleClient) CreateClientContext() (client.Context, error) { // CreateTxFactory creates an SDK Factory instance used for transaction // generation, signing and broadcasting. -func (oc OracleClient) CreateTxFactory() (tx.Factory, error) { +func (oc OracleClient) CreateTxFactory(isPrevote bool) (tx.Factory, error) { clientCtx, err := oc.CreateClientContext() if err != nil { return tx.Factory{}, err } - txFactory := tx.Factory{}. + if oc.GasAdjustment > 0 { + return tx.Factory{}. + WithAccountRetriever(clientCtx.AccountRetriever). + WithChainID(oc.ChainID). + WithTxConfig(clientCtx.TxConfig). + WithGasAdjustment(oc.GasAdjustment). + WithGasPrices(oc.GasPrices). + WithKeybase(clientCtx.Keyring). + WithSignMode(signing.SignMode_SIGN_MODE_DIRECT). + WithSimulateAndExecute(true), nil + } + gas := oc.GasVote + if isPrevote { + gas = oc.GasPrevote + } + return tx.Factory{}. WithAccountRetriever(clientCtx.AccountRetriever). WithChainID(oc.ChainID). WithTxConfig(clientCtx.TxConfig). - WithGasAdjustment(oc.GasAdjustment). + WithGas(gas). WithGasPrices(oc.GasPrices). WithKeybase(clientCtx.Keyring). WithSignMode(signing.SignMode_SIGN_MODE_DIRECT). - WithSimulateAndExecute(true) - - return txFactory, nil + WithSimulateAndExecute(true), nil } diff --git a/oracle/client/tx.go b/oracle/client/tx.go index 43c5f591..c42b04b6 100644 --- a/oracle/client/tx.go +++ b/oracle/client/tx.go @@ -19,12 +19,14 @@ func BroadcastTx(clientCtx client.Context, txf tx.Factory, msgs ...sdk.Msg) (*sd return nil, err } - _, adjusted, err := tx.CalculateGas(clientCtx, txf, msgs...) - if err != nil { - return nil, err - } + if txf.GasAdjustment() > 0 { + _, adjusted, err := tx.CalculateGas(clientCtx, txf, msgs...) + if err != nil { + return nil, err + } - txf = txf.WithGas(adjusted) + txf = txf.WithGas(adjusted) + } unsignedTx, err := txf.BuildUnsignedTx(msgs...) if err != nil { diff --git a/oracle/oracle.go b/oracle/oracle.go index 9ccfc439..367646b7 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -557,24 +557,24 @@ func (o *Oracle) tick(ctx context.Context) error { exchangeRatesStr := GenerateExchangeRatesString(o.prices) hash := oracletypes.GetAggregateVoteHash(salt, exchangeRatesStr, valAddr) - preVoteMsg := &oracletypes.MsgAggregateExchangeRatePrevote{ - Hash: hash.String(), // hash of prices from the oracle - Feeder: o.oracleClient.OracleAddrString, - Validator: valAddr.String(), - } - isPrevoteOnlyTx := o.previousPrevote == nil + if isPrevoteOnlyTx { // This timeout could be as small as oracleVotePeriod-indexInVotePeriod, // but we give it some extra time just in case. // // Ref : https://github.com/terra-money/oracle-feeder/blob/baef2a4a02f57a2ffeaa207932b2e03d7fb0fb25/feeder/src/vote.ts#L222 + preVoteMsg := &oracletypes.MsgAggregateExchangeRatePrevote{ + Hash: hash.String(), // hash of prices from the oracle + Feeder: o.oracleClient.OracleAddrString, + Validator: valAddr.String(), + } o.logger.Info(). Str("hash", hash.String()). Str("validator", preVoteMsg.Validator). Str("feeder", preVoteMsg.Feeder). Msg("broadcasting pre-vote") - if err := o.oracleClient.BroadcastTx(nextBlockHeight, oracleVotePeriod*2, preVoteMsg); err != nil { + if err := o.oracleClient.BroadcastTx(nextBlockHeight, oracleVotePeriod*2, isPrevoteOnlyTx, preVoteMsg); err != nil { return err } @@ -606,6 +606,7 @@ func (o *Oracle) tick(ctx context.Context) error { if err := o.oracleClient.BroadcastTx( nextBlockHeight, oracleVotePeriod-indexInVotePeriod, + isPrevoteOnlyTx, voteMsg, ); err != nil { return err diff --git a/price-feeder.example.toml b/price-feeder.example.toml index 5edf1d9c..e2af4584 100644 --- a/price-feeder.example.toml +++ b/price-feeder.example.toml @@ -1,8 +1,11 @@ config_dir = "umee-provider-config" - -gas_adjustment = 1.1 provider_timeout = "1000000s" +gas_prevote = 55000 +gas_vote = 160000 +# instead of fixed gas settings, gas adjustment can be used: +# gas_adjustment = 1.9 + [server] listen_addr = "0.0.0.0:7171" read_timeout = "20s" diff --git a/umee-provider-config/currency-pairs.toml b/umee-provider-config/currency-pairs.toml index fcbed8b3..7e8f35bf 100644 --- a/umee-provider-config/currency-pairs.toml +++ b/umee-provider-config/currency-pairs.toml @@ -398,4 +398,19 @@ providers = [ "bitget", "gate", ] -quote = "USDT" \ No newline at end of file +quote = "USDT" + +[[currency_pairs]] +base = "STRD" +providers = [ + "osmosis", +] +quote = "OSMO" + +[[currency_pairs]] +base = "STRD" +providers = [ + "osmosis", +] +quote = "USDC" + diff --git a/umee-provider-config/deviation-thresholds.toml b/umee-provider-config/deviation-thresholds.toml index c1a61870..d34b3811 100644 --- a/umee-provider-config/deviation-thresholds.toml +++ b/umee-provider-config/deviation-thresholds.toml @@ -148,4 +148,8 @@ threshold = "2" [[deviation_thresholds]] base = "TIA" -threshold = "2" \ No newline at end of file +threshold = "2" + +[[deviation_thresholds]] +base = "STRD" +threshold = "2"