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

fix: handle revert errors in gas estimation for executable transactions #1656

Open
wants to merge 8 commits into
base: zkevm
Choose a base branch
from

Conversation

afa7789
Copy link
Collaborator

@afa7789 afa7789 commented Jan 21, 2025

I added a result.Revert() check and returned it if that's the error when we find the location amidst the binary search

@afa7789 afa7789 enabled auto-merge January 22, 2025 05:53
}
if failed {
if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
return 0, ethapi2.NewRevertError(result)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@afa7789 great job! Curious why you removed the check for error being vm.ErrExecutionReverted and treat all errors here as revert errors. Could you please elaborate on that?

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 is almost the same piece of code we had in the ending of the function.

something could happen before we wouldn't know that it revert/failed, so now we first test the code for reverts/failures with max gas, and then look for gas estimation.

so any type of errors/reverts we try to get that first, and then we look for the estimate gas.

Copy link
Collaborator

Choose a reason for hiding this comment

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

These lines returned revert error in case it was a revert error, otherwise, just an error:

				if len(result.Revert()) > 0 {
					return 0, ethapi2.NewRevertError(result)
				}
				return 0, result.Err

This is what I am talking about, now all errors here are revert errors. Idk if that is correct, I'd leave the old way.
Next, code error is left as is:

		if err != nil {
			return 0, err
		}

Which means we still return it the same old way it was before, I dont see any change here, if it fails. If there is no extra reason in the err, then we still dont know what happened.
Overall, I believe this is a good PR that optimizes a case for exceeding the gas limit, I just don't see if the original issue is resolved.
@revitteth Can you please give us some more clues on the issue? There is not much description.

Copy link
Collaborator Author

@afa7789 afa7789 Jan 22, 2025

Choose a reason for hiding this comment

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

we could move the err check, or even the failed.

but it's not 100% related, an error is related to a failure in the code itself and not the contract which would revert.

as you can see in executable, when we error we don't return a result.

	// Create a helper to check if a gas allowance results in an executable transaction
	executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
		result, err := caller.DoCallWithNewGas(ctx, gas)
		if err != nil {
			if errors.Is(err, core.ErrIntrinsicGas) {
				// Special case, raise gas limit
				return true, nil, nil
			}
			// Bail out
			return true, nil, err
		}

		return result.Failed(), result, nil
	}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

func (r *ReusableCaller) DoCallWithNewGas(
	ctx context.Context,
	newGas uint64,
) (*core.ExecutionResult, error) {
	var cancel context.CancelFunc
	if r.callTimeout > 0 {
		ctx, cancel = context.WithTimeout(ctx, r.callTimeout)
	} else {
		ctx, cancel = context.WithCancel(ctx)
	}

	// Make sure the context is cancelled when the call has completed
	// this makes sure resources are cleaned up.
	defer cancel()

	r.message.ChangeGas(r.gasCap, newGas)

	// reset the EVM so that we can continue to use it with the new context
	txCtx := core.NewEVMTxContext(r.message)
	r.intraBlockState = state.New(r.stateReader)
	r.evm.Reset(txCtx, r.intraBlockState)

	timedOut := false
	go func() {
		<-ctx.Done()
		timedOut = true
	}()

	gp := new(core.GasPool).AddGas(r.message.Gas()).AddBlobGas(r.message.BlobGas())

	result, err := core.ApplyMessage(r.evm, r.message, gp, true /* refunds */, false /* gasBailout */)
	if err != nil {
		return nil, err
	}

	// If the timer caused an abort, return an appropriate error message
	if timedOut {
		return nil, fmt.Errorf("execution aborted (timeout = %v)", r.callTimeout)
	}

	return result, nil
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants