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: implement support for Osmosis EIP-1559 #1412

Merged
merged 1 commit into from
Mar 6, 2024
Merged
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
13 changes: 11 additions & 2 deletions relayer/chains/cosmos/cosmos_chain_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,15 @@ func (ccp *CosmosChainProcessor) CurrentBlockHeight(ctx context.Context, persist

func (ccp *CosmosChainProcessor) CurrentRelayerBalance(ctx context.Context) {
// memoize the current gas prices to only show metrics for "interesting" denoms
gasPrice := ccp.chainProvider.PCfg.GasPrices

if ccp.parsedGasPrices == nil {
gp, err := sdk.ParseDecCoins(ccp.chainProvider.PCfg.GasPrices)
dynamicFee := ccp.chainProvider.DynamicFee(ctx)
if dynamicFee != "" {
gasPrice = dynamicFee
}

gp, err := sdk.ParseDecCoins(gasPrice)
if err != nil {
ccp.log.Error(
"Failed to parse gas prices",
Expand All @@ -575,11 +582,13 @@ func (ccp *CosmosChainProcessor) CurrentRelayerBalance(ctx context.Context) {
zap.Error(err),
)
}

// Print the relevant gas prices
for _, gasDenom := range *ccp.parsedGasPrices {
bal := relayerWalletBalances.AmountOf(gasDenom.Denom)

// Convert to a big float to get a float64 for metrics
f, _ := big.NewFloat(0.0).SetInt(bal.BigInt()).Float64()
ccp.metrics.SetWalletBalance(ccp.chainProvider.ChainId(), ccp.chainProvider.PCfg.GasPrices, ccp.chainProvider.Key(), address, gasDenom.Denom, f)
ccp.metrics.SetWalletBalance(ccp.chainProvider.ChainId(), gasPrice, ccp.chainProvider.Key(), address, gasDenom.Denom, f)
}
}
79 changes: 79 additions & 0 deletions relayer/chains/cosmos/fee_market.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cosmos

import (
"context"
"fmt"
"regexp"
"strings"

sdkmath "cosmossdk.io/math"
"go.uber.org/zap"
)

const queryPath = "/osmosis.txfees.v1beta1.Query/GetEipBaseFee"

// DynamicFee queries the dynamic gas price base fee and returns a string with the base fee and token denom concatenated.
// If the chain does not have dynamic fees enabled in the config, nothing happens and an empty string is always returned.
func (cc *CosmosProvider) DynamicFee(ctx context.Context) string {
if !cc.PCfg.DynamicGasPrice {
return ""
}

dynamicFee, err := cc.QueryBaseFee(ctx)
if err != nil {
// If there was an error querying the dynamic base fee, do nothing and fall back to configured gas price.
cc.log.Warn("Failed to query the dynamic gas price base fee", zap.Error(err))
return ""
}

return dynamicFee
}

// QueryBaseFee attempts to make an ABCI query to retrieve the base fee on chains using the Osmosis EIP-1559 implementation.
// This is currently hardcoded to only work on Osmosis.
func (cc *CosmosProvider) QueryBaseFee(ctx context.Context) (string, error) {
resp, err := cc.RPCClient.ABCIQuery(ctx, queryPath, nil)
if err != nil || resp.Response.Code != 0 {
return "", err
}

// The response value contains the data link escape control character which must be removed before parsing.
cleanedString := strings.ReplaceAll(strings.TrimSpace(string(resp.Response.Value)), "\u0010", "")

decFee, err := sdkmath.LegacyNewDecFromStr(cleanedString)
if err != nil {
return "", err
}

baseFee, err := decFee.Float64()
if err != nil {
return "", err
}

// The current EIP-1559 implementation returns an integer and does not return any value that tells us how many
// decimal places we need to account for.
//
// This may be problematic because we are assuming that we always need to move the decimal 18 places.
fee := baseFee / 1e18

denom, err := parseTokenDenom(cc.PCfg.GasPrices)
if err != nil {
return "", err
}

return fmt.Sprintf("%f%s", fee, denom), nil
}

// parseTokenDenom takes a string in the format numericGasPrice + tokenDenom (e.g. 0.0025uosmo),
// and parses the tokenDenom portion (e.g. uosmo) before returning just the token denom.
func parseTokenDenom(gasPrice string) (string, error) {
regex := regexp.MustCompile(`^0\.\d+([a-zA-Z]+)$`)

matches := regex.FindStringSubmatch(gasPrice)

if len(matches) != 2 {
return "", fmt.Errorf("failed to parse token denom from string %s", gasPrice)
}

return matches[1], nil
}
61 changes: 61 additions & 0 deletions relayer/chains/cosmos/fee_market_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cosmos

import (
"context"
"testing"

"github.com/stretchr/testify/require"
)

var (
coinType = 118

testCfg = CosmosProviderConfig{
KeyDirectory: "",
Key: "default",
ChainName: "osmosis",
ChainID: "osmosis-1",
RPCAddr: "https://rpc.osmosis.strange.love:443",
AccountPrefix: "osmo",
KeyringBackend: "test",
DynamicGasPrice: true,
GasAdjustment: 1.2,
GasPrices: "0.0025uosmo",
MinGasAmount: 1,
MaxGasAmount: 0,
Debug: false,
Timeout: "30s",
BlockTimeout: "30s",
OutputFormat: "json",
SignModeStr: "direct",
ExtraCodecs: nil,
Modules: nil,
Slip44: &coinType,
SigningAlgorithm: "",
Broadcast: "batch",
MinLoopDuration: 0,
ExtensionOptions: nil,
FeeGrants: nil,
}
)

func TestQueryBaseFee(t *testing.T) {
p, err := testCfg.NewProvider(nil, t.TempDir(), true, testCfg.ChainName)
require.NoError(t, err)

ctx := context.Background()
err = p.Init(ctx)
require.NoError(t, err)

cp := p.(*CosmosProvider)

baseFee, err := cp.QueryBaseFee(ctx)
require.NoError(t, err)
require.NotEqual(t, "", baseFee)
}

func TestParseDenom(t *testing.T) {
denom, err := parseTokenDenom(testCfg.GasPrices)
require.NoError(t, err)
require.Equal(t, "uosmo", denom)
}
15 changes: 14 additions & 1 deletion relayer/chains/cosmos/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,20 @@ func (cc *CosmosProvider) TxServiceBroadcast(ctx context.Context, req *tx.Broadc

wg.Add(1)

if err := cc.broadcastTx(ctx, req.TxBytes, nil, nil, ctx, blockTimeout, []func(*provider.RelayerTxResponse, error){callback}); err != nil {
dynamicFee := cc.DynamicFee(ctx)

err = cc.broadcastTx(
ctx,
req.TxBytes,
nil,
nil,
ctx,
blockTimeout,
[]func(*provider.RelayerTxResponse, error){callback},
dynamicFee,
)

if err != nil {
return nil, err
}

Expand Down
6 changes: 1 addition & 5 deletions relayer/chains/cosmos/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ var (
_ provider.ProviderConfig = &CosmosProviderConfig{}
)

const (
cometEncodingThreshold = "v0.37.0-alpha"
cometBlockResultsThreshold = "v0.38.0-alpha"
)

type CosmosProviderConfig struct {
KeyDirectory string `json:"key-directory" yaml:"key-directory"`
Key string `json:"key" yaml:"key"`
Expand All @@ -45,6 +40,7 @@ type CosmosProviderConfig struct {
RPCAddr string `json:"rpc-addr" yaml:"rpc-addr"`
AccountPrefix string `json:"account-prefix" yaml:"account-prefix"`
KeyringBackend string `json:"keyring-backend" yaml:"keyring-backend"`
DynamicGasPrice bool `json:"dynamic-gas-price" yaml:"dynamic-gas-price"`
GasAdjustment float64 `json:"gas-adjustment" yaml:"gas-adjustment"`
GasPrices string `json:"gas-prices" yaml:"gas-prices"`
MinGasAmount uint64 `json:"min-gas-amount" yaml:"min-gas-amount"`
Expand Down
56 changes: 47 additions & 9 deletions relayer/chains/cosmos/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,19 @@ func (cc *CosmosProvider) SendMessagesToMempool(
sequenceGuard.Mu.Lock()
defer sequenceGuard.Mu.Unlock()

txBytes, sequence, fees, err := cc.buildMessages(ctx, msgs, memo, 0, txSignerKey, feegranterKeyOrAddr, sequenceGuard)
dynamicFee := cc.DynamicFee(ctx)

txBytes, sequence, fees, err := cc.buildMessages(
ctx,
msgs,
memo,
0,
txSignerKey,
feegranterKeyOrAddr,
sequenceGuard,
dynamicFee,
)

if err != nil {
// Account sequence mismatch errors can happen on the simulated transaction also.
if strings.Contains(err.Error(), legacyerrors.ErrWrongSequence.Error()) {
Expand All @@ -182,7 +194,18 @@ func (cc *CosmosProvider) SendMessagesToMempool(
return err
}

if err := cc.broadcastTx(ctx, txBytes, msgs, fees, asyncCtx, defaultBroadcastWaitTimeout, asyncCallbacks); err != nil {
err = cc.broadcastTx(
ctx,
txBytes,
msgs,
fees,
asyncCtx,
defaultBroadcastWaitTimeout,
asyncCallbacks,
dynamicFee,
)

if err != nil {
if strings.Contains(err.Error(), legacyerrors.ErrWrongSequence.Error()) {
cc.handleAccountSequenceMismatchError(sequenceGuard, err)
}
Expand Down Expand Up @@ -253,7 +276,9 @@ func (cc *CosmosProvider) SendMsgsWith(ctx context.Context, msgs []sdk.Msg, memo
rand.Seed(time.Now().UnixNano())
feegrantKeyAcc, _ := cc.GetKeyAddressForKey(feegranterKey)

txf, err := cc.PrepareFactory(cc.TxFactory(), signingKey)
dynamicFee := cc.DynamicFee(ctx)

txf, err := cc.PrepareFactory(cc.TxFactory(dynamicFee), signingKey)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -359,6 +384,7 @@ func (cc *CosmosProvider) broadcastTx(
asyncCtx context.Context, // context for async wait for block inclusion after successful tx broadcast
asyncTimeout time.Duration, // timeout for waiting for block inclusion
asyncCallbacks []func(*provider.RelayerTxResponse, error), // callback for success/fail of the wait for block inclusion
dynamicFee string,
) error {
res, err := cc.RPCClient.BroadcastTxSync(ctx, tx)
isErr := err != nil
Expand Down Expand Up @@ -389,7 +415,7 @@ func (cc *CosmosProvider) broadcastTx(
return fmt.Errorf("failed to get relayer bech32 wallet address: %w", err)

}
cc.UpdateFeesSpent(cc.ChainId(), cc.Key(), address, fees)
cc.UpdateFeesSpent(cc.ChainId(), cc.Key(), address, fees, dynamicFee)

// TODO: maybe we need to check if the node has tx indexing enabled?
// if not, we need to find a new way to block until inclusion in a block
Expand Down Expand Up @@ -596,6 +622,7 @@ func (cc *CosmosProvider) buildMessages(
txSignerKey string,
feegranterKeyOrAddr string,
sequenceGuard *WalletState,
dynamicFee string,
) (
txBytes []byte,
sequence uint64,
Expand All @@ -607,7 +634,7 @@ func (cc *CosmosProvider) buildMessages(

cMsgs := CosmosMsgs(msgs...)

txf, err := cc.PrepareFactory(cc.TxFactory(), txSignerKey)
txf, err := cc.PrepareFactory(cc.TxFactory(dynamicFee), txSignerKey)
if err != nil {
return nil, 0, sdk.Coins{}, err
}
Expand Down Expand Up @@ -1594,7 +1621,7 @@ func (cc *CosmosProvider) NewClientState(
}, nil
}

func (cc *CosmosProvider) UpdateFeesSpent(chain, key, address string, fees sdk.Coins) {
func (cc *CosmosProvider) UpdateFeesSpent(chain, key, address string, fees sdk.Coins, dynamicFee string) {
// Don't set the metrics in testing
if cc.metrics == nil {
return
Expand All @@ -1604,10 +1631,15 @@ func (cc *CosmosProvider) UpdateFeesSpent(chain, key, address string, fees sdk.C
cc.TotalFees = cc.TotalFees.Add(fees...)
cc.totalFeesMu.Unlock()

gasPrice := cc.PCfg.GasPrices
if dynamicFee != "" {
gasPrice = dynamicFee
}

for _, fee := range cc.TotalFees {
// Convert to a big float to get a float64 for metrics
f, _ := big.NewFloat(0.0).SetInt(fee.Amount.BigInt()).Float64()
cc.metrics.SetFeesSpent(chain, cc.PCfg.GasPrices, key, address, fee.GetDenom(), f)
cc.metrics.SetFeesSpent(chain, gasPrice, key, address, fee.GetDenom(), f)
}
}

Expand Down Expand Up @@ -1780,13 +1812,19 @@ func (cc *CosmosProvider) CalculateGas(ctx context.Context, txf tx.Factory, sign
}

// TxFactory instantiates a new tx factory with the appropriate configuration settings for this chain.
func (cc *CosmosProvider) TxFactory() tx.Factory {
func (cc *CosmosProvider) TxFactory(dynamicFee string) tx.Factory {
gasPrice := cc.PCfg.GasPrices

if dynamicFee != "" {
gasPrice = dynamicFee
}

return tx.Factory{}.
WithAccountRetriever(cc).
WithChainID(cc.PCfg.ChainID).
WithTxConfig(cc.Cdc.TxConfig).
WithGasAdjustment(cc.PCfg.GasAdjustment).
WithGasPrices(cc.PCfg.GasPrices).
WithGasPrices(gasPrice).
WithKeybase(cc.Keybase).
WithSignMode(cc.PCfg.SignMode())
}
Expand Down
Loading