diff --git a/core/state_transition.go b/core/state_transition.go index bddfe49a26..1322f47dad 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -148,6 +148,30 @@ func ApplyMessage(evm *vm.EVM, msg vm.Message, gp *GasPool) ([]byte, uint64, boo return NewStateTransition(evm, msg, gp).TransitionDb() } +// NewStateTransitionGasEstimator returns a special state transition for estimating gas consumption. +// Estimation runs the given message as if gas were free, which allows binary search under the +// assumption that the execution is not dependent on gas limit or gas price, with the exception of +// "out of gas" errors. +func NewStateTransitionGasEstimator(evm *vm.EVM, msg vm.Message, gp *GasPool) *StateTransition { + return &StateTransition{ + gp: gp, + evm: evm, + msg: msg, + gasPrice: common.Big0, + value: msg.Value(), + data: msg.Data(), + state: evm.StateDB, + gasPriceMinimum: common.Big0, + } +} + +// ApplyEstimatorMessage applies the given message in a way that allows for estimation of gas consumption. +// Returns the gas used (which does not include gas refunds) and an error if it failed. +func ApplyEstimatorMessage(evm *vm.EVM, msg vm.Message, gp *GasPool) ([]byte, uint64, bool, error) { + log.Trace("Estimating gas for message", "from", msg.From(), "nonce", msg.Nonce(), "to", msg.To(), "fee currency", msg.FeeCurrency(), "gateway fee recipient", msg.GatewayFeeRecipient(), "gateway fee", msg.GatewayFee(), "gas limit", msg.Gas(), "value", msg.Value(), "data", msg.Data()) + return NewStateTransitionGasEstimator(evm, msg, gp).TransitionDb() +} + // to returns the recipient of the message. func (st *StateTransition) to() common.Address { if st.msg == nil || st.msg.To() == nil /* contract creation */ { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0e1551e410..d93b4a3702 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -683,7 +683,7 @@ type CallArgs struct { Data hexutil.Bytes `json:"data"` } -func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, timeout time.Duration) ([]byte, uint64, bool, error) { +func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, timeout time.Duration, estimate bool) (res []byte, gas uint64, failed bool, err error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) state, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr) @@ -738,10 +738,13 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr evm.Cancel() }() - // Setup the gas pool (also for unmetered requests) - // and apply the message. + // Setup the gas pool (also for unmetered requests) and apply the message. gp := new(core.GasPool).AddGas(math.MaxUint64) - res, gas, failed, err := core.ApplyMessage(evm, msg, gp) + if estimate { + res, gas, failed, err = core.ApplyEstimatorMessage(evm, msg, gp) + } else { + res, gas, failed, err = core.ApplyMessage(evm, msg, gp) + } if err := vmError(); err != nil { return nil, 0, false, err @@ -752,7 +755,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // Call executes the given transaction on the state for the given block number. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - result, _, _, err := s.doCall(ctx, args, blockNr, 5*time.Second) + result, _, _, err := s.doCall(ctx, args, blockNr, 5*time.Second, false) return (hexutil.Bytes)(result), err } @@ -781,7 +784,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h executable := func(gas uint64) bool { args.Gas = hexutil.Uint64(gas) - _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, 0) + _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, 0, true) if err != nil || failed { return false }