Skip to content

Commit

Permalink
combine EVM legacy + dynamic fee estimation behind a single interface
Browse files Browse the repository at this point in the history
  • Loading branch information
aalu1418 committed Feb 24, 2023
1 parent e7527e6 commit 625c44f
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 44 deletions.
75 changes: 69 additions & 6 deletions core/chains/evm/gas/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func IsBumpErr(err error) bool {
}

// NewEstimator returns the estimator for a given config
func NewEstimator(lggr logger.Logger, ethClient evmclient.Client, cfg Config) Estimator {
func NewEstimator(lggr logger.Logger, ethClient evmclient.Client, cfg Config) FeeEstimator {
s := cfg.GasEstimatorMode()
lggr.Infow(fmt.Sprintf("Initializing EVM gas estimator in mode: %s", s),
"estimatorMode", s,
Expand All @@ -51,16 +51,16 @@ func NewEstimator(lggr logger.Logger, ethClient evmclient.Client, cfg Config) Es
)
switch s {
case "Arbitrum":
return NewArbitrumEstimator(lggr, cfg, ethClient, ethClient)
return NewWrappedEvmEstimator(NewArbitrumEstimator(lggr, cfg, ethClient, ethClient), cfg)
case "BlockHistory":
return NewBlockHistoryEstimator(lggr, ethClient, cfg, *ethClient.ChainID())
return NewWrappedEvmEstimator(NewBlockHistoryEstimator(lggr, ethClient, cfg, *ethClient.ChainID()), cfg)
case "FixedPrice":
return NewFixedPriceEstimator(cfg, lggr)
return NewWrappedEvmEstimator(NewFixedPriceEstimator(cfg, lggr), cfg)
case "Optimism2", "L2Suggested":
return NewL2SuggestedPriceEstimator(lggr, ethClient)
return NewWrappedEvmEstimator(NewL2SuggestedPriceEstimator(lggr, ethClient), cfg)
default:
lggr.Warnf("GasEstimator: unrecognised mode '%s', falling back to FixedPriceEstimator", s)
return NewFixedPriceEstimator(cfg, lggr)
return NewWrappedEvmEstimator(NewFixedPriceEstimator(cfg, lggr), cfg)
}
}

Expand Down Expand Up @@ -106,6 +106,69 @@ type Estimator interface {
BumpDynamicFee(ctx context.Context, original DynamicFee, gasLimit uint32, maxGasPriceWei *assets.Wei, attempts []PriorAttempt) (bumped DynamicFee, chainSpecificGasLimit uint32, err error)
}

type EvmFee struct {
Legacy *assets.Wei
Dynamic *DynamicFee
}

type FeeEstimator interface {
OnNewLongestChain(context.Context, *evmtypes.Head)
Start(context.Context) error
Close() error

GetFee(ctx context.Context, calldata []byte, gasLimit uint32, maxGasPriceWei *assets.Wei, opts ...Opt) (fee EvmFee, chainSpecificGasLimit uint32, err error)
BumpFee(ctx context.Context, originalFee EvmFee, gasLimit uint32, maxGasPriceWei *assets.Wei, attempts []PriorAttempt) (bumpedFee EvmFee, chainSpecificGasLimit uint32, err error)
}

type WrappedEvmEstimator struct {
Estimator
EIP1559Enabled bool
}

var _ FeeEstimator = (*WrappedEvmEstimator)(nil)

func NewWrappedEvmEstimator(e Estimator, cfg Config) FeeEstimator {
return &WrappedEvmEstimator{
Estimator: e,
EIP1559Enabled: cfg.EvmEIP1559DynamicFees(),
}
}

func (e WrappedEvmEstimator) GetFee(ctx context.Context, calldata []byte, gasLimit uint32, maxGasPriceWei *assets.Wei, opts ...Opt) (fee EvmFee, chainSpecificGasLimit uint32, err error) {
// get dynamic fee
if e.EIP1559Enabled {
var dynamicFee DynamicFee
dynamicFee, chainSpecificGasLimit, err = e.Estimator.GetDynamicFee(ctx, gasLimit, maxGasPriceWei)
fee.Dynamic = &dynamicFee
return
}

// get legacy fee
fee.Legacy, chainSpecificGasLimit, err = e.Estimator.GetLegacyGas(ctx, calldata, gasLimit, maxGasPriceWei, opts...)
return
}

func (e WrappedEvmEstimator) BumpFee(ctx context.Context, originalFee EvmFee, gasLimit uint32, maxGasPriceWei *assets.Wei, attempts []PriorAttempt) (bumpedFee EvmFee, chainSpecificGasLimit uint32, err error) {
// validate only 1 fee type is present
if (originalFee.Dynamic == nil && originalFee.Legacy == nil) || (originalFee.Dynamic != nil && originalFee.Legacy != nil) {
err = errors.New("only one dynamic or legacy fee can be defined")
return
}

// bump fee based on what fee the tx has previously used (not based on config)
// bump dynamic original
if originalFee.Dynamic != nil {
var bumpedDynamic DynamicFee
bumpedDynamic, chainSpecificGasLimit, err = e.Estimator.BumpDynamicFee(ctx, *originalFee.Dynamic, gasLimit, maxGasPriceWei, attempts)
bumpedFee.Dynamic = &bumpedDynamic
return
}

// bump legacy fee
bumpedFee.Legacy, chainSpecificGasLimit, err = e.Estimator.BumpLegacyGas(ctx, originalFee.Legacy, gasLimit, maxGasPriceWei, attempts)
return
}

// Opt is an option for a gas estimator
type Opt int

Expand Down
55 changes: 31 additions & 24 deletions core/chains/evm/txmgr/eth_broadcaster.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type EthBroadcaster struct {
q pg.Q
ethClient evmclient.Client
ChainKeyStore
estimator gas.Estimator
estimator gas.FeeEstimator
resumeCallback ResumeCallback

// autoSyncNonce, if set, will cause EthBroadcaster to fast-forward the nonce
Expand Down Expand Up @@ -122,7 +122,7 @@ type EthBroadcaster struct {
// NewEthBroadcaster returns a new concrete EthBroadcaster
func NewEthBroadcaster(db *sqlx.DB, ethClient evmclient.Client, config Config, keystore KeyStore,
eventBroadcaster pg.EventBroadcaster,
keyStates []ethkey.State, estimator gas.Estimator, resumeCallback ResumeCallback,
keyStates []ethkey.State, estimator gas.FeeEstimator, resumeCallback ResumeCallback,
logger logger.Logger, checkerFactory TransmitCheckerFactory, autoSyncNonce bool) *EthBroadcaster {

triggers := make(map[gethCommon.Address]chan struct{})
Expand Down Expand Up @@ -384,21 +384,18 @@ func (eb *EthBroadcaster) processUnstartedEthTxs(ctx context.Context, fromAddres
n++
var a EthTxAttempt
keySpecificMaxGasPriceWei := eb.config.KeySpecificMaxGasPriceWei(etx.FromAddress)
fee, gasLimit, err := eb.estimator.GetFee(ctx, etx.EncodedPayload, etx.GasLimit, keySpecificMaxGasPriceWei)
if err != nil {
return errors.Wrap(err, "failed to get fee"), true
}

if eb.config.EvmEIP1559DynamicFees() {
fee, gasLimit, err := eb.estimator.GetDynamicFee(ctx, etx.GasLimit, keySpecificMaxGasPriceWei)
if err != nil {
return errors.Wrap(err, "failed to get dynamic gas fee"), true
}
a, err = eb.NewDynamicFeeAttempt(*etx, fee, gasLimit)
a, err = eb.NewDynamicFeeAttempt(*etx, *fee.Dynamic, gasLimit)
if err != nil {
return errors.Wrap(err, "processUnstartedEthTxs failed on NewDynamicFeeAttempt"), true
}
} else {
gasPrice, gasLimit, err := eb.estimator.GetLegacyGas(ctx, etx.EncodedPayload, etx.GasLimit, keySpecificMaxGasPriceWei)
if err != nil {
return errors.Wrap(err, "failed to estimate gas"), true
}
a, err = eb.NewLegacyAttempt(*etx, gasPrice, gasLimit)
a, err = eb.NewLegacyAttempt(*etx, fee.Legacy, gasLimit)
if err != nil {
return errors.Wrap(err, "processUnstartedEthTxs failed on NewLegacyAttempt"), true
}
Expand Down Expand Up @@ -777,26 +774,34 @@ func (eb *EthBroadcaster) tryAgainBumpingGas(ctx context.Context, lgr logger.Log

func (eb *EthBroadcaster) tryAgainBumpingLegacyGas(ctx context.Context, lgr logger.Logger, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) (err error, retryable bool) {
keySpecificMaxGasPriceWei := eb.config.KeySpecificMaxGasPriceWei(etx.FromAddress)
bumpedGasPrice, bumpedGasLimit, err := eb.estimator.BumpLegacyGas(ctx, attempt.GasPrice, etx.GasLimit, keySpecificMaxGasPriceWei, nil)
bumpedGasPrice, bumpedGasLimit, err := eb.estimator.BumpFee(ctx, gas.EvmFee{Legacy: attempt.GasPrice}, etx.GasLimit, keySpecificMaxGasPriceWei, nil)
if err != nil {
return errors.Wrap(err, "tryAgainBumpingLegacyGas failed"), true
}
if bumpedGasPrice.Cmp(attempt.GasPrice) == 0 || bumpedGasPrice.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 {
return errors.Errorf("hit gas price bump ceiling, will not bump further"), true // TODO: Is this terminal or retryable? Is it possible to send unsaved attempts here?
}
return eb.tryAgainWithNewLegacyGas(ctx, lgr, etx, attempt, initialBroadcastAt, bumpedGasPrice, bumpedGasLimit)

// NOTE: commented out - err handling before catches when price bump ceiling is hit
// TODO: remove
// if bumpedGasPrice.Cmp(attempt.GasPrice) == 0 || bumpedGasPrice.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 {
// return errors.Errorf("hit gas price bump ceiling, will not bump further"), true // TODO: Is this terminal or retryable? Is it possible to send unsaved attempts here?
// }
return eb.tryAgainWithNewLegacyGas(ctx, lgr, etx, attempt, initialBroadcastAt, bumpedGasPrice.Legacy, bumpedGasLimit)
}

func (eb *EthBroadcaster) tryAgainBumpingDynamicFeeGas(ctx context.Context, lgr logger.Logger, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) (err error, retryable bool) {
keySpecificMaxGasPriceWei := eb.config.KeySpecificMaxGasPriceWei(etx.FromAddress)
bumpedFee, bumpedGasLimit, err := eb.estimator.BumpDynamicFee(ctx, attempt.DynamicFee(), etx.GasLimit, keySpecificMaxGasPriceWei, nil)

prevFee := attempt.DynamicFee() // TODO: make a method for tx to construct
bumpedFee, bumpedGasLimit, err := eb.estimator.BumpFee(ctx, gas.EvmFee{Dynamic: &prevFee}, etx.GasLimit, keySpecificMaxGasPriceWei, nil)
if err != nil {
return errors.Wrap(err, "tryAgainBumpingDynamicFeeGas failed"), true
}
if bumpedFee.TipCap.Cmp(attempt.GasTipCap) == 0 || bumpedFee.FeeCap.Cmp(attempt.GasFeeCap) == 0 || bumpedFee.TipCap.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 || bumpedFee.TipCap.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 {
return errors.Errorf("hit gas price bump ceiling, will not bump further"), true // TODO: Is this terminal or retryable? Is it possible to send unsaved attempts here?
}
return eb.tryAgainWithNewDynamicFeeGas(ctx, lgr, etx, attempt, initialBroadcastAt, bumpedFee, bumpedGasLimit)

// NOTE: commented out - err handling before catches when price bump ceiling is hit
// TODO: remove
// if bumpedFee.TipCap.Cmp(attempt.GasTipCap) == 0 || bumpedFee.FeeCap.Cmp(attempt.GasFeeCap) == 0 || bumpedFee.TipCap.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 || bumpedFee.TipCap.Cmp(eb.config.EvmMaxGasPriceWei()) >= 0 {
// return errors.Errorf("hit gas price bump ceiling, will not bump further"), true // TODO: Is this terminal or retryable? Is it possible to send unsaved attempts here?
// }
return eb.tryAgainWithNewDynamicFeeGas(ctx, lgr, etx, attempt, initialBroadcastAt, *bumpedFee.Dynamic, bumpedGasLimit)
}

func (eb *EthBroadcaster) tryAgainWithNewEstimation(ctx context.Context, lgr logger.Logger, sendError *evmclient.SendError, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time) (err error, retryable bool) {
Expand All @@ -806,13 +811,15 @@ func (eb *EthBroadcaster) tryAgainWithNewEstimation(ctx context.Context, lgr log
return err, false
}
keySpecificMaxGasPriceWei := eb.config.KeySpecificMaxGasPriceWei(etx.FromAddress)
gasPrice, gasLimit, err := eb.estimator.GetLegacyGas(ctx, etx.EncodedPayload, etx.GasLimit, keySpecificMaxGasPriceWei, gas.OptForceRefetch)
gasPrice, gasLimit, err := eb.estimator.GetFee(ctx, etx.EncodedPayload, etx.GasLimit, keySpecificMaxGasPriceWei, gas.OptForceRefetch)
if err != nil {
return errors.Wrap(err, "tryAgainWithNewEstimation failed to estimate gas"), true
}
lgr.Warnw("L2 rejected transaction due to incorrect fee, re-estimated and will try again",
"etxID", etx.ID, "err", err, "newGasPrice", gasPrice, "newGasLimit", gasLimit)
return eb.tryAgainWithNewLegacyGas(ctx, lgr, etx, attempt, initialBroadcastAt, gasPrice, gasLimit)

// TODO: nil handling for gasPrice.Legacy? will this ever be reached where EIP1559 is enabled on a L2 but a legacy tx?
return eb.tryAgainWithNewLegacyGas(ctx, lgr, etx, attempt, initialBroadcastAt, gasPrice.Legacy, gasLimit)
}

func (eb *EthBroadcaster) tryAgainWithNewLegacyGas(ctx context.Context, lgr logger.Logger, etx EthTx, attempt EthTxAttempt, initialBroadcastAt time.Time, newGasPrice *assets.Wei, newGasLimit uint32) (err error, retyrable bool) {
Expand Down
20 changes: 10 additions & 10 deletions core/chains/evm/txmgr/eth_confirmer.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ type EthConfirmer struct {
q pg.Q
ethClient evmclient.Client
ChainKeyStore
estimator gas.Estimator
estimator gas.FeeEstimator
resumeCallback ResumeCallback

keyStates []ethkey.State
Expand All @@ -140,7 +140,7 @@ type EthConfirmer struct {

// NewEthConfirmer instantiates a new eth confirmer
func NewEthConfirmer(db *sqlx.DB, ethClient evmclient.Client, config Config, keystore KeyStore,
keyStates []ethkey.State, estimator gas.Estimator, resumeCallback ResumeCallback, lggr logger.Logger) *EthConfirmer {
keyStates []ethkey.State, estimator gas.FeeEstimator, resumeCallback ResumeCallback, lggr logger.Logger) *EthConfirmer {

ctx, cancel := context.WithCancel(context.Background())
lggr = lggr.Named("EthConfirmer")
Expand Down Expand Up @@ -939,23 +939,23 @@ func (ec *EthConfirmer) bumpGas(ctx context.Context, etx EthTx, previousAttempts
keySpecificMaxGasPriceWei := ec.config.KeySpecificMaxGasPriceWei(etx.FromAddress)
switch previousAttempt.TxType {
case 0x0: // Legacy
var bumpedGasPrice *assets.Wei
var bumpedFee gas.EvmFee
var bumpedGasLimit uint32
bumpedGasPrice, bumpedGasLimit, err = ec.estimator.BumpLegacyGas(ctx, previousAttempt.GasPrice, etx.GasLimit, keySpecificMaxGasPriceWei, priorAttempts)
bumpedFee, bumpedGasLimit, err = ec.estimator.BumpFee(ctx, gas.EvmFee{Legacy: previousAttempt.GasPrice}, etx.GasLimit, keySpecificMaxGasPriceWei, priorAttempts)
if err == nil {
promNumGasBumps.WithLabelValues(ec.chainID.String()).Inc()
ec.lggr.Debugw("Rebroadcast bumping gas for Legacy tx", append(logFields, "bumpedGasPrice", bumpedGasPrice.String())...)
return ec.NewLegacyAttempt(etx, bumpedGasPrice, bumpedGasLimit)
ec.lggr.Debugw("Rebroadcast bumping gas for Legacy tx", append(logFields, "bumpedGasPrice", bumpedFee.Legacy.String())...)
return ec.NewLegacyAttempt(etx, bumpedFee.Legacy, bumpedGasLimit)
}
case 0x2: // EIP1559
var bumpedFee gas.DynamicFee
var bumpedFee gas.EvmFee
var bumpedGasLimit uint32
original := previousAttempt.DynamicFee()
bumpedFee, bumpedGasLimit, err = ec.estimator.BumpDynamicFee(ctx, original, etx.GasLimit, keySpecificMaxGasPriceWei, priorAttempts)
bumpedFee, bumpedGasLimit, err = ec.estimator.BumpFee(ctx, gas.EvmFee{Dynamic: &original}, etx.GasLimit, keySpecificMaxGasPriceWei, priorAttempts)
if err == nil {
promNumGasBumps.WithLabelValues(ec.chainID.String()).Inc()
ec.lggr.Debugw("Rebroadcast bumping gas for DynamicFee tx", append(logFields, "bumpedTipCap", bumpedFee.TipCap.String(), "bumpedFeeCap", bumpedFee.FeeCap.String())...)
return ec.NewDynamicFeeAttempt(etx, bumpedFee, bumpedGasLimit)
ec.lggr.Debugw("Rebroadcast bumping gas for DynamicFee tx", append(logFields, "bumpedTipCap", bumpedFee.Dynamic.TipCap.String(), "bumpedFeeCap", bumpedFee.Dynamic.FeeCap.String())...)
return ec.NewDynamicFeeAttempt(etx, *bumpedFee.Dynamic, bumpedGasLimit)
}
default:
err = errors.Errorf("invariant violation: Attempt %v had unrecognised transaction type %v"+
Expand Down
8 changes: 4 additions & 4 deletions core/chains/evm/txmgr/txmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type TxManager interface {
Trigger(addr common.Address)
CreateEthTransaction(newTx NewTx, qopts ...pg.QOpt) (etx EthTx, err error)
GetForwarderForEOA(eoa common.Address) (forwarder common.Address, err error)
GetGasEstimator() gas.Estimator
GetGasEstimator() gas.FeeEstimator
RegisterResumeCallback(fn ResumeCallback)
SendEther(chainID *big.Int, from, to common.Address, value assets.Eth, gasLimit uint32) (etx EthTx, err error)
Reset(f func(), addr common.Address, abandon bool) error
Expand All @@ -104,7 +104,7 @@ type Txm struct {
config Config
keyStore KeyStore
eventBroadcaster pg.EventBroadcaster
gasEstimator gas.Estimator
gasEstimator gas.FeeEstimator
chainID big.Int
checkerFactory TransmitCheckerFactory

Expand Down Expand Up @@ -554,7 +554,7 @@ func (b *Txm) checkEnabled(addr common.Address) error {
}

// GetGasEstimator returns the gas estimator, mostly useful for tests
func (b *Txm) GetGasEstimator() gas.Estimator {
func (b *Txm) GetGasEstimator() gas.FeeEstimator {
return b.gasEstimator
}

Expand Down Expand Up @@ -745,5 +745,5 @@ func (n *NullTxManager) Healthy() error { return nil }
func (n *NullTxManager) Ready() error { return nil }
func (n *NullTxManager) Name() string { return "" }
func (n *NullTxManager) HealthReport() map[string]error { return nil }
func (n *NullTxManager) GetGasEstimator() gas.Estimator { return nil }
func (n *NullTxManager) GetGasEstimator() gas.FeeEstimator { return nil }
func (n *NullTxManager) RegisterResumeCallback(fn ResumeCallback) {}

0 comments on commit 625c44f

Please sign in to comment.