-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Gas limit estimation feature #14041
Gas limit estimation feature #14041
Conversation
…estimation-feature
core/chains/evm/gas/models.go
Outdated
fee.Legacy, chainSpecificFeeLimit, err = e.EvmEstimator.GetLegacyGas(ctx, calldata, feeLimit, maxFeePrice, opts...) | ||
if err != nil { | ||
return | ||
if e.geCfg.EstimateGasLimit() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For readability, could you create a new function, called getEstimatedGasUsed()?
And the ethereum.CallMsg, .geCfg.EstimateGasLimit() checks, client calls, please move all to that function.
This makes it very clear to reader what part is for the GetFee(), vs for getEstimatedGasUsed().
core/chains/evm/gas/models.go
Outdated
@@ -261,32 +264,53 @@ func (e *evmFeeEstimator) L1Oracle() rollups.L1Oracle { | |||
return e.EvmEstimator.L1Oracle() | |||
} | |||
|
|||
func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) { | |||
func (e *evmFeeEstimator) GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After your changes, the return value from this function, param "chainSpecificFeeLimit", doesn't represent the absolute gas limit that caller provided. Instead, it represents what we estimate this specific attempt would consume, provided these fee params are used.
So better to rename the param to suggestedGasLimit. And in comment for this function, explain what this is.
Also, could you check the callers of this functions, whether they use this return value correctly, and next time when they gas bump, they pass in the input param "feeLimit" as original limit sent by caller, not the feeLimit used in previous attempt before a gas bump.
core/chains/evm/gas/models.go
Outdated
// Do not return error if estimating gas failed, we can still use the provided limit instead since it is an upper limit | ||
e.lggr.Errorw("failed to estimate gas limit. falling back to provided gas limit.", "toAddress", toAddress, "data", calldata, "gasLimit", chainSpecificFeeLimit, "error", estimateErr) | ||
} else { | ||
chainSpecificFeeLimit = estimatedGasLimit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This estimatedGasLimit is the estimated Gas returned to us by the chain.
Due to fluctuations or errors, the actual gas used may be slightly more than this, causing the actual Tx to fail.
I think we should add a buffer on top of what the RPC returns to us.
Make that buffer a config value, EstimatedGasBuffer, set to default of 25%.
This protects us from fluctuations.
Also, you need to ensure that feeLimit > estimatedGasLimit. If not, then we can error out. Basically the chain is saying the gas used will be more than what the callers allows us to use, thus making this Tx infeasible.
Lastly, ensure that SuggestedGasLimit = MIN(feeLimit, estimatedGasLimit+Buffer)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to avoid adding another config so I was using the existing LimitMultiplier
as the buffer. Though, I could see the distinction between the 2 causing confusion because LimitMultiplier
was expected to be added on top of the provided limit but estimated gas * EstimatedGasBuffer
would be expected to stay under the provided limit
@@ -157,6 +157,7 @@ type MockGasEstimatorConfig struct { | |||
FeeCapDefaultF *assets.Wei | |||
LimitMaxF uint64 | |||
ModeF string | |||
EstimateGasLimitF bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious what's the F for ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think we do this because the linter doesn't like it when the field has the same name as the method to access it. Not sure why "F" but just following the pattern here
@@ -186,7 +186,7 @@ func (w *chainWriter) GetFeeComponents(ctx context.Context) (*commontypes.ChainF | |||
return nil, fmt.Errorf("gas estimator not available") | |||
} | |||
|
|||
fee, _, err := w.ge.GetFee(ctx, nil, 0, w.maxGasPrice) | |||
fee, _, err := w.ge.GetFee(ctx, nil, 0, w.maxGasPrice, nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If e.geCfg.EstimateGasLimit()
is enabled, will we get/log error in this case with empty callMsg?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we would because all of the parameters for the eth_estimateGas
call are actually optional. We'd probably get some default value if EstimateGasLimit
is enabled but it won't be used in cases like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Joe does have a point though that if CW wants to access gas limits they would have to update their API and provide a to
address. Something to keep in mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, they would. If we move forward with exposing EstimateGas to products, we had discussed we'd do it through this. But they'd have to update the other parameters too.
core/chains/evm/gas/models.go
Outdated
@@ -33,11 +33,11 @@ type EvmFeeEstimator interface { | |||
|
|||
// L1Oracle returns the L1 gas price oracle only if the chain has one, e.g. OP stack L2s and Arbitrum. | |||
L1Oracle() rollups.L1Oracle | |||
GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) | |||
GetFee(ctx context.Context, calldata []byte, feeLimit uint64, maxFeePrice *assets.Wei, toAddress *common.Address, opts ...feetypes.Opt) (fee EvmFee, chainSpecificFeeLimit uint64, err error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps using common.Address
instead of a pointer simplifies the API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept this as a pointer so GetFee
callers that don't care about the gas limit can pass a nil
like in the case of the ChainWriter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but we also need to add a check here to make sure the address is properly set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The To
field in ethereum.CallMsg
is actually *common.Address
so we can pass this directly in whether it is set or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I'm guessing if we do and it's empty it's going to use the 0x0 which might not be the intended behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's a nil address I think geth handles it properly as an estimation for contract creation. If someone passes a non-nil empty common.Address
, it's still a valid address for estimation.
core/chains/evm/gas/models.go
Outdated
if err != nil { | ||
return | ||
if e.geCfg.EstimateGasLimit() { | ||
callMsg.Gas = chainSpecificFeeLimit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- We don't have to set a gas limit to get an estimation. This can be omitted.
- I'm interested in knowing what is the exact requirement from the product teams regarding gas limit estimation. Personally, I would remove any requirements for GasLimit limits if
EstimateGasLimit
is enabled. Usually, when we have fallback values people tend to set them wrong because they're not aware what is the impact. I've observed the same pattern with the gas estimators where we have default values that are only used once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I was using it as a way to cap the estimation to the limit products sent us but I'll remove this and add that restriction in code for better flexibility
- At least for the initial solution that's needed for Mantle, I'm trying to honor the gas limits products provide as a cap to avoid any changes in behavior/assumptions. Functions has mentioned that they would need us to honor the cap otherwise it could lead to failures at the contract level. We can discuss more in the thread though.
…estimation-feature
Data: calldata, | ||
} | ||
estimatedGas, estimateErr := e.ethClient.EstimateGas(ctx, callMsg) | ||
if estimateErr != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the future, we could perhaps add a retry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we technically already do. This would surface as a gas estimator error which are retryable here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm, one small nit
e.lggr.Errorw("failed to estimate gas limit. falling back to provided gas limit.", "callMsg", callMsg, "providedGasLimit", providedGasLimit, "error", estimateErr) | ||
return providedGasLimit, nil | ||
} | ||
return estimatedFeeLimit, fmt.Errorf("gas estimation failed and provided gas limit is 0: %w", estimateErr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
for providedGasLimit
, do we want to mention this provided gas limit is calculated from provided fee and multiplier ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think it's worth calling out to be explicit. I've made the change.
core/chains/evm/gas/models.go
Outdated
// If provided gas limit is not 0, fallback to it if the buffer causes the estimated gas limit to exceed it | ||
// The provided gas limit should be used as an upper bound to avoid unexpected behavior for products | ||
if providedGasLimit > 0 && estimatedFeeLimit > providedGasLimit { | ||
e.lggr.Debugw("estimated gas limit with buffer exceeds provided limit. falling back to the provided gas limit", "estimatedGasLimit", estimatedFeeLimit, "providedGasLimit", providedGasLimit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noticed one more here for the multiplier to make it consistent with line 369
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I just updated all of the logs. Also updated the log to be written more like spoken word than a formula.
…estimation-feature
Quality Gate passedIssues Measures |
BCI-3658
EstimateGasLimit
config to toggle the new feature