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

Submitter: setGasPrice() refactor #2462

Merged
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
3 changes: 3 additions & 0 deletions ethergo/chain/gas/cmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@

// BumpByPercent bumps a gas price by a percentage.
func BumpByPercent(gasPrice *big.Int, percentIncrease int) *big.Int {
if gasPrice == nil {
return nil
}

Check warning on line 111 in ethergo/chain/gas/cmp.go

View check run for this annotation

Codecov / codecov/patch

ethergo/chain/gas/cmp.go#L110-L111

Added lines #L110 - L111 were not covered by tests
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
price := core.CopyBigInt(gasPrice)
calculatedGasPrice := big.NewFloat(0).Mul(big.NewFloat(1+0.01*float64(percentIncrease)), big.NewFloat(0).SetInt(price))
price, _ = calculatedGasPrice.Int(price)
Expand Down
28 changes: 14 additions & 14 deletions ethergo/submitter/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
DoNotBatch bool `yaml:"skip_batching"`
// MaxGasPrice is the maximum gas price to use for transactions
MaxGasPrice *big.Int `yaml:"max_gas_price"`
// BaseGasPrice is the gas price that will be used if 0 is returned from the gas price oracle
BaseGasPrice *big.Int `yaml:"base_gas_price"`
// MinGasPrice is the gas price that will be used if 0 is returned from the gas price oracle
MinGasPrice *big.Int `yaml:"min_gas_price"`
// BumpIntervalSeconds is the number of seconds to wait before bumping a transaction
BumpIntervalSeconds int `yaml:"bump_interval_seconds"`
// GasBumpPercentages is the percentage to bump the gas price by
Expand Down Expand Up @@ -67,8 +67,8 @@
// DefaultMaxPrice is the default max price of a tx.
var DefaultMaxPrice = big.NewInt(500 * params.GWei)

// DefaultBaseGasPrice is the default max price of a tx.
var DefaultBaseGasPrice = big.NewInt(1 * params.GWei)
// DefaultMinGasPrice is the default min price of a tx.
var DefaultMinGasPrice = big.NewInt(1 * params.GWei)

// note: there's probably a way to clean these getters up with generics, the real problem comes with the fact that
// that this would require the caller to override the entire struct, which is not ideal..
Expand Down Expand Up @@ -110,17 +110,17 @@
return
}

// GetBaseGasPrice returns the maximum gas price to use for transactions.
func (c *Config) GetBaseGasPrice(chainID int) (basePrice *big.Int) {
basePrice = c.BaseGasPrice
// GetMinGasPrice returns the minimum gas price to use for transactions.
func (c *Config) GetMinGasPrice(chainID int) (minPrice *big.Int) {
minPrice = c.MinGasPrice

Check warning on line 115 in ethergo/submitter/config/config.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/config/config.go#L114-L115

Added lines #L114 - L115 were not covered by tests

chainConfig, ok := c.Chains[chainID]
if ok && chainConfig.BaseGasPrice != nil {
basePrice = chainConfig.BaseGasPrice
if ok && chainConfig.MinGasPrice != nil {
minPrice = chainConfig.MinGasPrice

Check warning on line 119 in ethergo/submitter/config/config.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/config/config.go#L118-L119

Added lines #L118 - L119 were not covered by tests
}

if basePrice == nil || basePrice == big.NewInt(0) {
basePrice = DefaultBaseGasPrice
if minPrice == nil || minPrice == big.NewInt(0) {
minPrice = DefaultMinGasPrice

Check warning on line 123 in ethergo/submitter/config/config.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/config/config.go#L122-L123

Added lines #L122 - L123 were not covered by tests
}
return
}
Expand Down Expand Up @@ -217,9 +217,9 @@
c.MaxGasPrice = maxPrice
}

// SetBaseGasPrice is a helper function that sets the base gas price.
func (c *Config) SetBaseGasPrice(basePrice *big.Int) {
c.BaseGasPrice = basePrice
// SetMinGasPrice is a helper function that sets the base gas price.
func (c *Config) SetMinGasPrice(basePrice *big.Int) {
c.MinGasPrice = basePrice

Check warning on line 222 in ethergo/submitter/config/config.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/config/config.go#L221-L222

Added lines #L221 - L222 were not covered by tests
}

// SetGlobalEIP1559Support is a helper function that sets the global EIP1559 support.
Expand Down
8 changes: 4 additions & 4 deletions ethergo/submitter/config/iconfig_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

206 changes: 141 additions & 65 deletions ethergo/submitter/submitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ipfs/go-log"
"github.com/synapsecns/sanguine/core"
"github.com/synapsecns/sanguine/core/mapmutex"
Expand Down Expand Up @@ -346,119 +347,194 @@
}

// setGasPrice sets the gas price for the transaction.
// it bumps if prevtx is set
// nolint: cyclop
// TODO: use options.
// If a prevTx is specified, a bump will be attempted; otherwise values will be
// set from the gas oracle.
// If gas values exceed the configured max, an error will be returned.
func (t *txSubmitterImpl) setGasPrice(ctx context.Context, client client.EVM,
transactor *bind.TransactOpts, bigChainID *big.Int, prevTx *types.Transaction) (err error) {
ctx, span := t.metrics.Tracer().Start(ctx, "submitter.setGasPrice")

chainID := int(bigChainID.Uint64())
maxPrice := t.config.GetMaxGasPrice(chainID)
useDynamic := t.config.SupportsEIP1559(chainID)

defer func() {
if transactor.GasPrice != nil && maxPrice.Cmp(transactor.GasPrice) < 0 {
transactor.GasPrice = maxPrice
span.SetAttributes(
attribute.Int(metrics.ChainID, chainID),
attribute.Bool("use_dynamic", useDynamic),
attribute.String("gas_price", bigPtrToString(transactor.GasPrice)),
attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)),
attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)),
)
metrics.EndSpanWithErr(span, err)
}()

t.bumpGasFromPrevTx(ctx, transactor, prevTx, chainID, useDynamic)
t.applyGasFloor(ctx, transactor, chainID, useDynamic)

err = t.applyGasFromOracle(ctx, transactor, client, useDynamic)
if err != nil {
return fmt.Errorf("could not populate gas from oracle: %w", err)
}

Check warning on line 377 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L376-L377

Added lines #L376 - L377 were not covered by tests

err = t.applyGasCeil(ctx, transactor, chainID, useDynamic)
if err != nil {
return fmt.Errorf("could not apply gas ceil: %w", err)
}
return nil
}

// bumpGasFromPrevTx populates the gas fields from the previous transaction and bumps
// the appropriate values corresponding to the configured GasBumpPercentage.
// Note that in the event of a tx type mismatch, gasFeeCap is copied to gasPrice,
// and gasPrice is copied to both gasFeeCap and gasTipCap in the opposite scenario.
//
//nolint:nestif
func (t *txSubmitterImpl) bumpGasFromPrevTx(ctx context.Context, transactor *bind.TransactOpts, prevTx *types.Transaction, chainID int, currentDynamic bool) {
if prevTx == nil {
return
}

_, span := t.metrics.Tracer().Start(ctx, "submitter.bumpGasFromPrevTx")

defer func() {
span.SetAttributes(
attribute.String("gas_price", bigPtrToString(transactor.GasPrice)),
attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)),
attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)),
)
metrics.EndSpan(span)
}()

prevDynamic := prevTx.Type() == types.DynamicFeeTxType
bumpPct := t.config.GetGasBumpPercentage(chainID)
if currentDynamic {
if prevDynamic {
transactor.GasFeeCap = gas.BumpByPercent(core.CopyBigInt(prevTx.GasFeeCap()), bumpPct)
transactor.GasTipCap = gas.BumpByPercent(core.CopyBigInt(prevTx.GasTipCap()), bumpPct)
} else {
transactor.GasFeeCap = gas.BumpByPercent(core.CopyBigInt(prevTx.GasPrice()), bumpPct)
transactor.GasTipCap = gas.BumpByPercent(core.CopyBigInt(prevTx.GasPrice()), bumpPct)
}
if transactor.GasFeeCap != nil && maxPrice.Cmp(transactor.GasFeeCap) < 0 {
transactor.GasFeeCap = maxPrice
} else {
if prevDynamic {
transactor.GasPrice = gas.BumpByPercent(core.CopyBigInt(prevTx.GasFeeCap()), bumpPct)
} else {
transactor.GasPrice = gas.BumpByPercent(core.CopyBigInt(prevTx.GasPrice()), bumpPct)
}
}
}

var minTipCap = big.NewInt(10 * params.Wei)

// applyGasFloor applies the min gas price from the config if values are unset.
//
//nolint:cyclop,nestif
func (t *txSubmitterImpl) applyGasFloor(ctx context.Context, transactor *bind.TransactOpts, chainID int, useDynamic bool) {
_, span := t.metrics.Tracer().Start(ctx, "submitter.applyGasFloor")

defer func() {
span.SetAttributes(
attribute.String("gas_price", bigPtrToString(transactor.GasPrice)),
attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)),
attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)),
)
metrics.EndSpanWithErr(span, err)
metrics.EndSpan(span)
}()

// TODO: cache both of these values
shouldBump := true
useEIP1559 := t.config.SupportsEIP1559(chainID)
if useEIP1559 {
transactor.GasFeeCap, err = client.SuggestGasPrice(ctx)
if err != nil {
return fmt.Errorf("could not get gas price: %w", err)
gasFloor := t.config.GetMinGasPrice(chainID)
if useDynamic {
if transactor.GasFeeCap == nil || transactor.GasFeeCap.Cmp(gasFloor) < 0 {
transactor.GasFeeCap = gasFloor
}
// don't bump fee cap if we hit the max configured gas price
if transactor.GasFeeCap.Cmp(maxPrice) > 0 {
transactor.GasFeeCap = maxPrice
shouldBump = false
span.AddEvent("not bumping fee cap since max price is reached")
if transactor.GasTipCap == nil || transactor.GasTipCap.Cmp(gasFloor) < 0 {
transactor.GasTipCap = minTipCap
}
} else if transactor.GasPrice == nil || transactor.GasPrice.Cmp(gasFloor) < 0 {
transactor.GasPrice = gasFloor
}
}

// applyGasFromOracle fetches gas values from a RPC endpoint and attempts to set them.
// If values are already specified, they will be overridden if the oracle values are higher.
func (t *txSubmitterImpl) applyGasFromOracle(ctx context.Context, transactor *bind.TransactOpts, client client.EVM, useDynamic bool) (err error) {
ctx, span := t.metrics.Tracer().Start(ctx, "submitter.applyGasFromOracle")

transactor.GasTipCap, err = client.SuggestGasTipCap(ctx)
defer func() {
metrics.EndSpanWithErr(span, err)
}()

if useDynamic {
suggestedGasFeeCap, err := client.SuggestGasPrice(ctx)
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("could not get gas fee cap: %w", err)
}

Check warning on line 470 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L469-L470

Added lines #L469 - L470 were not covered by tests
transactor.GasFeeCap = maxOfBig(transactor.GasFeeCap, suggestedGasFeeCap)
suggestedGasTipCap, err := client.SuggestGasTipCap(ctx)
if err != nil {
return fmt.Errorf("could not get gas tip cap: %w", err)
}
transactor.GasTipCap = maxOfBig(transactor.GasTipCap, suggestedGasTipCap)
span.SetAttributes(
attribute.String("suggested_gas_fee_cap", bigPtrToString(suggestedGasFeeCap)),
attribute.String("suggested_gas_tip_cap", bigPtrToString(suggestedGasTipCap)),
attribute.String("gas_fee_cap", bigPtrToString(transactor.GasFeeCap)),
attribute.String("gas_tip_cap", bigPtrToString(transactor.GasTipCap)),
)
} else {
transactor.GasPrice, err = client.SuggestGasPrice(ctx)
suggestedGasPrice, err := client.SuggestGasPrice(ctx)
if err != nil {
return fmt.Errorf("could not get gas price: %w", err)
}
transactor.GasPrice = maxOfBig(transactor.GasPrice, suggestedGasPrice)
span.SetAttributes(
attribute.String("suggested_gas_price", bigPtrToString(suggestedGasPrice)),
attribute.String("gas_price", bigPtrToString(transactor.GasPrice)),
)
}
t.applyBaseGasPrice(transactor, chainID)
return nil
}
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved

//nolint: nestif
if prevTx != nil && shouldBump {
gasBlock, err := t.getGasBlock(ctx, client, chainID)
if err != nil {
span.AddEvent("could not get gas block", trace.WithAttributes(attribute.String("error", err.Error())))
return err
}
// applyGasCeil evaluates current gas values versus the configured maximum, and
// returns an error if they exceed the maximum.
func (t *txSubmitterImpl) applyGasCeil(ctx context.Context, transactor *bind.TransactOpts, chainID int, useDynamic bool) (err error) {
_, span := t.metrics.Tracer().Start(ctx, "submitter.applyGasCeil")

// if the prev tx was greater than this one, we should bump the gas price from that point
if prevTx.Type() == types.LegacyTxType {
if prevTx.GasPrice().Cmp(transactor.GasPrice) > 0 {
transactor.GasPrice = core.CopyBigInt(prevTx.GasPrice())
}
} else {
if prevTx.GasTipCap().Cmp(transactor.GasTipCap) > 0 {
transactor.GasTipCap = core.CopyBigInt(prevTx.GasTipCap())
}
maxPrice := t.config.GetMaxGasPrice(chainID)

if prevTx.GasFeeCap().Cmp(transactor.GasFeeCap) > 0 {
transactor.GasFeeCap = core.CopyBigInt(prevTx.GasFeeCap())
}
defer func() {
span.SetAttributes(attribute.String("max_price", bigPtrToString(maxPrice)))
metrics.EndSpanWithErr(span, err)
}()

if useDynamic {
if transactor.GasFeeCap.Cmp(maxPrice) > 0 {
return fmt.Errorf("gas fee cap %s exceeds max price %s", transactor.GasFeeCap, maxPrice)
}
if transactor.GasTipCap.Cmp(transactor.GasFeeCap) > 0 {
transactor.GasTipCap = core.CopyBigInt(transactor.GasFeeCap)
span.AddEvent("tip cap exceeds fee cap; setting tip cap to fee cap")

Check warning on line 515 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L514-L515

Added lines #L514 - L515 were not covered by tests
}
gas.BumpGasFees(transactor, t.config.GetGasBumpPercentage(chainID), gasBlock.BaseFee, maxPrice)
} else {
// if we're not bumping, we should still make sure the gas price is at least 10 because 10% of 10 wei is a whole number.
// TODO: this should be customizable.
if useEIP1559 {
transactor.GasTipCap = maxOfBig(transactor.GasTipCap, big.NewInt(10))
} else {
transactor.GasPrice = maxOfBig(transactor.GasPrice, big.NewInt(10))
if transactor.GasPrice.Cmp(maxPrice) > 0 {
return fmt.Errorf("gas price %s exceeds max price %s", transactor.GasPrice, maxPrice)
}
}
return nil
}

// b must not be nil.
func maxOfBig(a, b *big.Int) *big.Int {
if a == nil {
return b
}
if b == nil {
return a
}

Check warning on line 531 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L530-L531

Added lines #L530 - L531 were not covered by tests
if a.Cmp(b) > 0 {
return a
}
return b
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
}

// applyBaseGasPrice applies the base gas price to the transactor if a gas price value is zero.
func (t *txSubmitterImpl) applyBaseGasPrice(transactor *bind.TransactOpts, chainID int) {
if t.config.SupportsEIP1559(chainID) {
if transactor.GasFeeCap == nil || transactor.GasFeeCap.Cmp(big.NewInt(0)) == 0 {
transactor.GasFeeCap = t.config.GetBaseGasPrice(chainID)
}
// TODO: we need to keep gas tip cap non-zero, but below the base gas price.
} else {
if transactor.GasPrice == nil || transactor.GasPrice.Cmp(big.NewInt(0)) == 0 {
transactor.GasPrice = t.config.GetBaseGasPrice(chainID)
}
}
}

// getGasBlock gets the gas block for the given chain.
func (t *txSubmitterImpl) getGasBlock(ctx context.Context, chainClient client.EVM, chainID int) (gasBlock *types.Header, err error) {
ctx, span := t.metrics.Tracer().Start(ctx, "submitter.getGasBlock")
Expand Down
Loading
Loading