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 12 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
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(10 * params.GWei)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Min gas price of 10 Gwei is an overshoot.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense- I'm confused of the intention here though in the base PR?

https://github.com/synapsecns/sanguine/pull/2432/files#diff-9cb5c46a90d8a5d8f06ab1c53594fbc934b93eaf5131c538424f0c4666f94f49R429

To me this seems like we will never set gas price below 10 gwei if no prevTx is supplied

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unit there is in wei

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's introduce a min value for GasPrice/GasFeeCap (DefaultMinGasPrice) and a separate one for GasTipCap. The latter one is set to 10 Wei (aka just 10) in the base PR.


// 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 maximum gas price to use for transactions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// GetMinGasPrice returns the maximum gas price to use for transactions.
// 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.

222 changes: 157 additions & 65 deletions ethergo/submitter/submitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,119 +346,211 @@
}

// 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)
shouldBump := prevTx != nil

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.Bool("should_bump", shouldBump),
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.populateGasFromPrevTx(ctx, transactor, prevTx, useDynamic)
t.applyGasFloor(ctx, transactor, chainID, useDynamic, shouldBump)

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 378 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L377-L378

Added lines #L377 - L378 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
}

// populateGasFromPrevTx populates the gas fields from the previous transaction.
// 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) populateGasFromPrevTx(ctx context.Context, transactor *bind.TransactOpts, prevTx *types.Transaction, currentDynamic bool) {
if prevTx == nil {
return
}

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

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
if currentDynamic {
if prevDynamic {
transactor.GasFeeCap = core.CopyBigInt(prevTx.GasFeeCap())
transactor.GasTipCap = core.CopyBigInt(prevTx.GasTipCap())
} else {
transactor.GasFeeCap = core.CopyBigInt(prevTx.GasPrice())
transactor.GasTipCap = core.CopyBigInt(prevTx.GasPrice())
}
if transactor.GasFeeCap != nil && maxPrice.Cmp(transactor.GasFeeCap) < 0 {
transactor.GasFeeCap = maxPrice
} else {
if prevDynamic {
transactor.GasPrice = core.CopyBigInt(prevTx.GasFeeCap())
} else {
transactor.GasPrice = core.CopyBigInt(prevTx.GasPrice())
}
}
}
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved

// applyGasFloor applies the min gas price from the config if values are unset.
// Otherwise, gas values are bumped by the configured GasBumpPercentage.
//
//nolint:cyclop,nestif
func (t *txSubmitterImpl) applyGasFloor(ctx context.Context, transactor *bind.TransactOpts, chainID int, useDynamic, shouldBump 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)),
attribute.Bool("should_bump", shouldBump),
)
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 shouldBump {
bumpPct := t.config.GetGasBumpPercentage(chainID)
if useDynamic {
if transactor.GasFeeCap == nil {
transactor.GasFeeCap = gasFloor
span.AddEvent("gas fee cap is nil; setting to gas floor")

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

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L448-L449

Added lines #L448 - L449 were not covered by tests
} else {
transactor.GasFeeCap = gas.BumpByPercent(transactor.GasFeeCap, bumpPct)
}
if transactor.GasTipCap == nil {
transactor.GasTipCap = gasFloor
span.AddEvent("gas tip cap is nil; setting to gas floor")

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

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L454-L455

Added lines #L454 - L455 were not covered by tests
} else {
transactor.GasTipCap = gas.BumpByPercent(transactor.GasTipCap, bumpPct)
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
transactor.GasPrice = gas.BumpByPercent(transactor.GasPrice, bumpPct)
}
// 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")
} else {
if useDynamic {
if transactor.GasFeeCap == nil || transactor.GasFeeCap.Cmp(gasFloor) < 0 {
transactor.GasFeeCap = gasFloor
}
if transactor.GasTipCap == nil || transactor.GasTipCap.Cmp(gasFloor) < 0 {
transactor.GasTipCap = gasFloor
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
}
} 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")

defer func() {
metrics.EndSpanWithErr(span, err)
}()

transactor.GasTipCap, err = client.SuggestGasTipCap(ctx)
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 489 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L488-L489

Added lines #L488 - L489 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
}

//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)
}
gas.BumpGasFees(transactor, t.config.GetGasBumpPercentage(chainID), gasBlock.BaseFee, maxPrice)
// TODO: should maxPrice apply to GasTipCap as well?
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
} 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 547 in ethergo/submitter/submitter.go

View check run for this annotation

Codecov / codecov/patch

ethergo/submitter/submitter.go#L546-L547

Added lines #L546 - L547 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