all: seperate consensus error and evm internal error

There are actually two types of error will be returned when
a tranaction/message call is executed: (a) consensus error
(b) evm internal error. The former should be converted to
a consensus issue, e.g. The sender doesn't enough asset to
purchase the gas it specifies. The latter is allowed since
evm itself is a blackbox and internal error is allowed to happen.

This PR emphasizes the difference by introducing a executionResult
structure. The evm error is embedded inside. So if any error
returned, it indicates consensus issue happens.

And also this PR improve the `EstimateGas` API to return the concrete
revert reason if the transaction always fails
This commit is contained in:
rjl493456442 2020-03-27 15:42:06 +08:00
parent 648b0cb714
commit c1e706a654
28 changed files with 339 additions and 209 deletions

View file

@ -49,7 +49,6 @@ var (
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block") errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
errBlockDoesNotExist = errors.New("block does not exist in blockchain") errBlockDoesNotExist = errors.New("block does not exist in blockchain")
errTransactionDoesNotExist = errors.New("transaction does not exist") errTransactionDoesNotExist = errors.New("transaction does not exist")
errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
) )
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
@ -349,8 +348,11 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
if err != nil { if err != nil {
return nil, err return nil, err
} }
rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state) res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
return rval, err if err != nil {
return nil, err
}
return res.Result, nil
} }
// PendingCallContract executes a contract call on the pending state. // PendingCallContract executes a contract call on the pending state.
@ -359,8 +361,11 @@ func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereu
defer b.mu.Unlock() defer b.mu.Unlock()
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
return rval, err if err != nil {
return nil, err
}
return res.Result, nil
} }
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving // PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
@ -398,22 +403,33 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
cap = hi cap = hi
// Create a helper to check if a gas allowance results in an executable transaction // Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) bool { executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
call.Gas = gas call.Gas = gas
snapshot := b.pendingState.Snapshot() snapshot := b.pendingState.Snapshot()
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) res, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
b.pendingState.RevertToSnapshot(snapshot) b.pendingState.RevertToSnapshot(snapshot)
if err != nil || failed { if err != nil {
return false if err == core.ErrInsufficientIntrinsicGas {
return true, nil, nil
} }
return true return true, nil, err
}
return res.Failed(), res, nil
} }
// Execute the binary search and hone in on an executable gas limit // Execute the binary search and hone in on an executable gas limit
for lo+1 < hi { for lo+1 < hi {
mid := (hi + lo) / 2 mid := (hi + lo) / 2
if !executable(mid) { failed, _, err := executable(mid)
// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accpeted no matter how many gas assigened.
// Return the error directly, don't struggle any more
if err != nil {
return 0, err
}
if failed {
lo = mid lo = mid
} else { } else {
hi = mid hi = mid
@ -421,8 +437,15 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
} }
// Reject the transaction as invalid if it still fails at the highest allowance // Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap { if hi == cap {
if !executable(hi) { failed, result, err := executable(hi)
return 0, errGasEstimationFailed if err != nil {
return 0, err
}
if failed {
if result != nil && len(result.RevertReason) != 0 {
return 0, fmt.Errorf("Reverted %x", result.RevertReason)
}
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
} }
} }
return hi, nil return hi, nil
@ -430,7 +453,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
// callContract implements common code between normal and pending contract calls. // callContract implements common code between normal and pending contract calls.
// state is modified during execution, make sure to copy it if necessary. // state is modified during execution, make sure to copy it if necessary.
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) { func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) (*core.ExecutionResult, error) {
// Ensure message is initialized properly. // Ensure message is initialized properly.
if call.GasPrice == nil { if call.GasPrice == nil {
call.GasPrice = big.NewInt(1) call.GasPrice = big.NewInt(1)

View file

@ -678,7 +678,7 @@ func (api *RetestethAPI) AccountRange(ctx context.Context,
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
// Ensure any modifications are committed to the state // Ensure any modifications are committed to the state
@ -788,7 +788,7 @@ func (api *RetestethAPI) StorageRangeAt(ctx context.Context,
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil) context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
// Ensure any modifications are committed to the state // Ensure any modifications are committed to the state

View file

@ -22,17 +22,48 @@ var (
// ErrKnownBlock is returned when a block to import is already known locally. // ErrKnownBlock is returned when a block to import is already known locally.
ErrKnownBlock = errors.New("block already known") ErrKnownBlock = errors.New("block already known")
// ErrGasLimitReached is returned by the gas pool if the amount of gas required
// by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")
// ErrBlacklistedHash is returned if a block to import is on the blacklist. // ErrBlacklistedHash is returned if a block to import is on the blacklist.
ErrBlacklistedHash = errors.New("blacklisted hash") ErrBlacklistedHash = errors.New("blacklisted hash")
// ErrNoGenesis is returned when there is no Genesis Block.
ErrNoGenesis = errors.New("genesis not found in chain")
)
// State transition consensus errors, any of them
// encountered can lead to consensus issue.
var (
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
// one present in the local chain.
ErrNonceTooLow = errors.New("nonce too low")
// ErrNonceTooHigh is returned if the nonce of a transaction is higher than the // ErrNonceTooHigh is returned if the nonce of a transaction is higher than the
// next one expected based on the local chain. // next one expected based on the local chain.
ErrNonceTooHigh = errors.New("nonce too high") ErrNonceTooHigh = errors.New("nonce too high")
// ErrNoGenesis is returned when there is no Genesis Block. // ErrGasLimitReached is returned by the gas pool if the amount of gas required
ErrNoGenesis = errors.New("genesis not found in chain") // by a transaction is higher than what's left in the block.
ErrGasLimitReached = errors.New("gas limit reached")
// ErrInsufficientBalanceForTransfer is returned if the transaction sender doesn't
// have enough balance for transfer(topmost call only).
ErrInsufficientBalanceForTransfer = errors.New("insufficient balance for transfer")
// ErrInsufficientBalanceForFee is returned if transaction sender doesn't have
// enough balance to cover transaction fee.
ErrInsufficientBalanceForFee = errors.New("insufficient balance to pay fee")
// ErrGasOverflow is returned when calculating gas usage.
ErrGasOverflow = errors.New("gas overflow")
// ErrInsufficientIntrinsicGas is returned when the gas limit speicified in transaction
// is not enought to cover intrinsic gas usage.
ErrInsufficientIntrinsicGas = errors.New("insufficient intrinsic gas")
) )
type ConsensusError struct {
Reason error
}
func (ce *ConsensusError) Unwarp() error {
return ce.Reason
}

View file

@ -131,7 +131,7 @@ func TestMergeDelete(t *testing.T) {
flipDrops := func() map[common.Hash]struct{} { flipDrops := func() map[common.Hash]struct{} {
return map[common.Hash]struct{}{ return map[common.Hash]struct{}{
h2: struct{}{}, h2: {},
} }
} }
flipAccs := func() map[common.Hash][]byte { flipAccs := func() map[common.Hash][]byte {
@ -141,7 +141,7 @@ func TestMergeDelete(t *testing.T) {
} }
flopDrops := func() map[common.Hash]struct{} { flopDrops := func() map[common.Hash]struct{} {
return map[common.Hash]struct{}{ return map[common.Hash]struct{}{
h1: struct{}{}, h1: {},
} }
} }
flopAccs := func() map[common.Hash][]byte { flopAccs := func() map[common.Hash][]byte {

View file

@ -121,10 +121,10 @@ func TestDiskMerge(t *testing.T) {
// Modify or delete some accounts, flatten everything onto disk // Modify or delete some accounts, flatten everything onto disk
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
accDelNoCache: struct{}{}, accDelNoCache: {},
accDelCache: struct{}{}, accDelCache: {},
conNukeNoCache: struct{}{}, conNukeNoCache: {},
conNukeCache: struct{}{}, conNukeCache: {},
}, map[common.Hash][]byte{ }, map[common.Hash][]byte{
accModNoCache: reverse(accModNoCache[:]), accModNoCache: reverse(accModNoCache[:]),
accModCache: reverse(accModCache[:]), accModCache: reverse(accModCache[:]),
@ -344,10 +344,10 @@ func TestDiskPartialMerge(t *testing.T) {
// Modify or delete some accounts, flatten everything onto disk // Modify or delete some accounts, flatten everything onto disk
if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{
accDelNoCache: struct{}{}, accDelNoCache: {},
accDelCache: struct{}{}, accDelCache: {},
conNukeNoCache: struct{}{}, conNukeNoCache: {},
conNukeCache: struct{}{}, conNukeCache: {},
}, map[common.Hash][]byte{ }, map[common.Hash][]byte{
accModNoCache: reverse(accModNoCache[:]), accModNoCache: reverse(accModNoCache[:]),
accModCache: reverse(accModCache[:]), accModCache: reverse(accModCache[:]),

View file

@ -402,7 +402,7 @@ func TestIteratorDeletions(t *testing.T) {
deleted := common.HexToHash("0x22") deleted := common.HexToHash("0x22")
destructed := map[common.Hash]struct{}{ destructed := map[common.Hash]struct{}{
deleted: struct{}{}, deleted: {},
} }
snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"),
destructed, randomAccountSet("0x11", "0x33"), nil) destructed, randomAccountSet("0x11", "0x33"), nil)

View file

@ -89,6 +89,6 @@ func precacheTransaction(config *params.ChainConfig, bc ChainContext, author *co
context := NewEVMContext(msg, header, bc, author) context := NewEVMContext(msg, header, bc, author)
vm := vm.NewEVM(context, statedb, config, cfg) vm := vm.NewEVM(context, statedb, config, cfg)
_, _, _, err = ApplyMessage(vm, msg, gaspool) _, err = ApplyMessage(vm, msg, gaspool)
return err return err
} }

View file

@ -96,7 +96,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// about the transaction and calling mechanisms. // about the transaction and calling mechanisms.
vmenv := vm.NewEVM(context, statedb, config, cfg) vmenv := vm.NewEVM(context, statedb, config, cfg)
// Apply the transaction to the current state (included in the env) // Apply the transaction to the current state (included in the env)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp) result, err := ApplyMessage(vmenv, msg, gp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -107,13 +107,13 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
} else { } else {
root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes() root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
} }
*usedGas += gas *usedGas += result.UsedGas
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
// based on the eip phase, we're passing whether the root touch-delete accounts. // based on the eip phase, we're passing whether the root touch-delete accounts.
receipt := types.NewReceipt(root, failed, *usedGas) receipt := types.NewReceipt(root, result.Failed(), *usedGas)
receipt.TxHash = tx.Hash() receipt.TxHash = tx.Hash()
receipt.GasUsed = gas receipt.GasUsed = result.UsedGas
// if the transaction created a contract, store the creation address in the receipt. // if the transaction created a contract, store the creation address in the receipt.
if msg.To() == nil { if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())

View file

@ -17,20 +17,14 @@
package core package core
import ( import (
"errors"
"math" "math"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
var (
errInsufficientBalanceForGas = errors.New("insufficient balance to pay for gas")
)
/* /*
The State Transitioning Model The State Transitioning Model
@ -63,7 +57,6 @@ type StateTransition struct {
// Message represents a message sent to a contract. // Message represents a message sent to a contract.
type Message interface { type Message interface {
From() common.Address From() common.Address
//FromFrontier() (common.Address, error)
To() *common.Address To() *common.Address
GasPrice() *big.Int GasPrice() *big.Int
@ -75,6 +68,24 @@ type Message interface {
Data() []byte Data() []byte
} }
// ExecutionResult includes all output after executing given evm message
// no matter the execution itself is successful or not.
type ExecutionResult struct {
UsedGas uint64 // Total used gas but include the refunded gas
Err error // Any error encountered during the exection(listed in core/vm/errors.go)
Result []byte // Returned value of the calling function
RevertReason []byte // Reason to perform revert thrown by solidity code
}
// Unwrap returns the internal evm error which allows us for further
// analysis outside.
func (result *ExecutionResult) Unwrap() error {
return result.Err
}
// Failed returns the indicator whether the execution is successful or not
func (result *ExecutionResult) Failed() bool { return result.Err != nil }
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) { func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 bool) (uint64, error) {
// Set the starting gas for the raw transaction // Set the starting gas for the raw transaction
@ -99,13 +110,13 @@ func IntrinsicGas(data []byte, contractCreation, isHomestead bool, isEIP2028 boo
nonZeroGas = params.TxDataNonZeroGasEIP2028 nonZeroGas = params.TxDataNonZeroGasEIP2028
} }
if (math.MaxUint64-gas)/nonZeroGas < nz { if (math.MaxUint64-gas)/nonZeroGas < nz {
return 0, vm.ErrOutOfGas return 0, ErrGasOverflow
} }
gas += nz * nonZeroGas gas += nz * nonZeroGas
z := uint64(len(data)) - nz z := uint64(len(data)) - nz
if (math.MaxUint64-gas)/params.TxDataZeroGas < z { if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
return 0, vm.ErrOutOfGas return 0, ErrGasOverflow
} }
gas += z * params.TxDataZeroGas gas += z * params.TxDataZeroGas
} }
@ -132,7 +143,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
// the gas used (which includes gas refunds) and an error if it failed. An error always // the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular // indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block. // state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) { func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) {
return NewStateTransition(evm, msg, gp).TransitionDb() return NewStateTransition(evm, msg, gp).TransitionDb()
} }
@ -144,19 +155,10 @@ func (st *StateTransition) to() common.Address {
return *st.msg.To() return *st.msg.To()
} }
func (st *StateTransition) useGas(amount uint64) error {
if st.gas < amount {
return vm.ErrOutOfGas
}
st.gas -= amount
return nil
}
func (st *StateTransition) buyGas() error { func (st *StateTransition) buyGas() error {
mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice)
if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 {
return errInsufficientBalanceForGas return ErrInsufficientBalanceForFee
} }
if err := st.gp.SubGas(st.msg.Gas()); err != nil { if err := st.gp.SubGas(st.msg.Gas()); err != nil {
return err return err
@ -182,11 +184,34 @@ func (st *StateTransition) preCheck() error {
} }
// TransitionDb will transition the state by applying the current message and // TransitionDb will transition the state by applying the current message and
// returning the result including the used gas. It returns an error if failed. // returning the evm execution result with following fields.
// An error indicates a consensus issue. //
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) { // - used gas:
if err = st.preCheck(); err != nil { // total gas used (including gas being refunded)
return // - execution result:
// the return value of the calling function
// - revert reason:
// reason to perform revert thrown by solidity code
// - concrete execution error:
// various **EVM** error which aborts the execution,
// e.g. ErrOutOfGas, ErrExecutionReverted
//
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
// 1. the nonce of the message caller is correct
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
// 3. the amount of gas required is available in the block
// 4. the purchased gas is enough to cover intrinsic usage
// 5. there is no overflow when calculating intrinsic gas
// 6. caller has enough balance to cover asset transfer for **topmost** call
// Check clauses 1-3, buy gas if everything is correct
if err := st.preCheck(); err != nil {
return nil, err
} }
msg := st.msg msg := st.msg
sender := vm.AccountRef(msg.From()) sender := vm.AccountRef(msg.From())
@ -194,42 +219,46 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber) istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)
contractCreation := msg.To() == nil contractCreation := msg.To() == nil
// Pay intrinsic gas // Check clauses 4-5, subtract intrinsic if everything is correct
gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul) gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
if err != nil { if err != nil {
return nil, 0, false, err return nil, err
} }
if err = st.useGas(gas); err != nil { if st.gas < gas {
return nil, 0, false, err return nil, ErrInsufficientIntrinsicGas
} }
st.gas -= gas
// Check clauses 6
if msg.Value().Sign() > 0 && !st.evm.CanTransfer(st.state, msg.From(), msg.Value()) {
return nil, ErrInsufficientBalanceForTransfer
}
var ( var (
evm = st.evm ret []byte
// vm errors do not effect consensus and are therefor vmerr error // vm errors do not effect consensus and are therefore not assigned to err
// not assigned to err, except for insufficient balance
// error.
vmerr error
) )
if contractCreation { if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
} else { } else {
// Increment the nonce for the next transaction // Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value) ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}
if vmerr != nil {
log.Debug("VM returned with error", "err", vmerr)
// The only possible consensus-error would be if there wasn't
// sufficient balance to make the transfer happen. The first
// balance transfer may never fail.
if vmerr == vm.ErrInsufficientBalance {
return nil, 0, false, vmerr
}
} }
st.refundGas() st.refundGas()
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return ret, st.gasUsed(), vmerr != nil, err var revert, result []byte
if vmerr == vm.ErrExecutionReverted {
revert = ret // Revert reason will be returned in ret iif the vmerr is ErrExecutionReverted
} else {
result = ret // Otherwise the ret represents the execution result, may nil
}
return &ExecutionResult{
UsedGas: st.gasUsed(),
Err: vmerr,
Result: result,
RevertReason: revert,
}, nil
} }
func (st *StateTransition) refundGas() { func (st *StateTransition) refundGas() {

View file

@ -59,10 +59,6 @@ var (
// ErrInvalidSender is returned if the transaction contains an invalid signature. // ErrInvalidSender is returned if the transaction contains an invalid signature.
ErrInvalidSender = errors.New("invalid sender") ErrInvalidSender = errors.New("invalid sender")
// ErrNonceTooLow is returned if the nonce of a transaction is lower than the
// one present in the local chain.
ErrNonceTooLow = errors.New("nonce too low")
// ErrUnderpriced is returned if a transaction's gas price is below the minimum // ErrUnderpriced is returned if a transaction's gas price is below the minimum
// configured for the transaction pool. // configured for the transaction pool.
ErrUnderpriced = errors.New("transaction underpriced") ErrUnderpriced = errors.New("transaction underpriced")

View file

@ -16,15 +16,51 @@
package vm package vm
import "errors" import (
"errors"
"fmt"
)
// List execution errors // List evm execution errors
var ( var (
ErrOutOfGas = errors.New("out of gas") ErrOutOfGas = errors.New("out of gas")
ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas")
ErrDepth = errors.New("max call depth exceeded") ErrDepth = errors.New("max call depth exceeded")
ErrTraceLimitReached = errors.New("the number of logs reached the specified limit")
ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrNoCompatibleInterpreter = errors.New("no compatible interpreter") ErrExecutionReverted = errors.New("execution reverted")
ErrMaxCodeSizeExceeded = errors.New("max code size exceeded")
ErrInvalidJump = errors.New("invalid jump destination")
ErrWriteProtection = errors.New("write protection")
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
ErrGasUintOverflow = errors.New("gas uint64 overflow")
) )
// ErrStackUnderflow wraps an evm error when the items on the stack less
// than the minimal requirement.
type ErrStackUnderflow struct {
stackLen int
required int
}
func (e *ErrStackUnderflow) Error() string {
return fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required)
}
// ErrStackOverflow wraps an evm error when the items on the stack exceeds
// the maximum allowance.
type ErrStackOverflow struct {
stackLen int
allowance int
}
func (e *ErrStackOverflow) Error() string {
return fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.allowance)
}
// ErrInvalidOpCode wraps an evm error when an invalid opcode is encountered.
type ErrInvalidOpCode struct {
opcode int
}
func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode 0x%x", e.opcode) }

View file

@ -17,6 +17,7 @@
package vm package vm
import ( import (
"errors"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"time" "time"
@ -67,7 +68,7 @@ func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, err
return interpreter.Run(contract, input, readOnly) return interpreter.Run(contract, input, readOnly)
} }
} }
return nil, ErrNoCompatibleInterpreter return nil, errors.New("no compatible interpreter")
} }
// Context provides the EVM with auxiliary information. Once provided // Context provides the EVM with auxiliary information. Once provided
@ -190,7 +191,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil return nil, gas, nil
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
@ -199,7 +199,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance return nil, gas, ErrInsufficientBalance
} }
var ( var (
to = AccountRef(addr) to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot() snapshot = evm.StateDB.Snapshot()
@ -246,7 +245,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas)
} }
} }
@ -264,16 +263,17 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil return nil, gas, nil
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
} }
// Fail if we're trying to transfer more than the available balance // Fail if we're trying to transfer more than the available balance
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) { // Note although it's noop to transfer X ether to caller itself. But
// if caller doesn't have enough balance, it can lead to an evm error.
// So the check here is necessary.
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance return nil, gas, ErrInsufficientBalance
} }
var ( var (
snapshot = evm.StateDB.Snapshot() snapshot = evm.StateDB.Snapshot()
to = AccountRef(caller.Address()) to = AccountRef(caller.Address())
@ -286,7 +286,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
ret, err = run(evm, contract, input, false) ret, err = run(evm, contract, input, false)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas)
} }
} }
@ -306,12 +306,10 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
} }
var ( var (
snapshot = evm.StateDB.Snapshot() snapshot = evm.StateDB.Snapshot()
to = AccountRef(caller.Address()) to = AccountRef(caller.Address())
) )
// Initialise a new contract and make initialise the delegate values // Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, to, nil, gas).AsDelegate() contract := NewContract(caller, to, nil, gas).AsDelegate()
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
@ -319,7 +317,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
ret, err = run(evm, contract, input, false) ret, err = run(evm, contract, input, false)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas)
} }
} }
@ -338,7 +336,6 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
} }
var ( var (
to = AccountRef(addr) to = AccountRef(addr)
snapshot = evm.StateDB.Snapshot() snapshot = evm.StateDB.Snapshot()
@ -360,7 +357,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
ret, err = run(evm, contract, input, true) ret, err = run(evm, contract, input, true)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas)
} }
} }
@ -441,13 +438,13 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.
if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) { if maxCodeSizeExceeded || (err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas)) {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) contract.UseGas(contract.Gas)
} }
} }
// Assign err if contract code size exceeds the max while the err is still empty. // Assign err if contract code size exceeds the max while the err is still empty.
if maxCodeSizeExceeded && err == nil { if maxCodeSizeExceeded && err == nil {
err = errMaxCodeSizeExceeded err = ErrMaxCodeSizeExceeded
} }
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)

View file

@ -46,7 +46,7 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *big.Int) (uint6
} }
} }
if !callCost.IsUint64() { if !callCost.IsUint64() {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return callCost.Uint64(), nil return callCost.Uint64(), nil

View file

@ -36,7 +36,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) {
// overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used // overflow. The constant 0x1FFFFFFFE0 is the highest number that can be used
// without overflowing the gas calculation. // without overflowing the gas calculation.
if newMemSize > 0x1FFFFFFFE0 { if newMemSize > 0x1FFFFFFFE0 {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
newMemSizeWords := toWordSize(newMemSize) newMemSizeWords := toWordSize(newMemSize)
newMemSize = newMemSizeWords * 32 newMemSize = newMemSizeWords * 32
@ -72,15 +72,15 @@ func memoryCopierGas(stackpos int) gasFunc {
// And gas for copying data, charged per word at param.CopyGas // And gas for copying data, charged per word at param.CopyGas
words, overflow := bigUint64(stack.Back(stackpos)) words, overflow := bigUint64(stack.Back(stackpos))
if overflow { if overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow { if words, overflow = math.SafeMul(toWordSize(words), params.CopyGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if gas, overflow = math.SafeAdd(gas, words); overflow { if gas, overflow = math.SafeAdd(gas, words); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -221,7 +221,7 @@ func makeGasLog(n uint64) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
requestedSize, overflow := bigUint64(stack.Back(1)) requestedSize, overflow := bigUint64(stack.Back(1))
if overflow { if overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
@ -230,18 +230,18 @@ func makeGasLog(n uint64) gasFunc {
} }
if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow { if gas, overflow = math.SafeAdd(gas, params.LogGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow { if gas, overflow = math.SafeAdd(gas, n*params.LogTopicGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
var memorySizeGas uint64 var memorySizeGas uint64
if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow { if memorySizeGas, overflow = math.SafeMul(requestedSize, params.LogDataGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow { if gas, overflow = math.SafeAdd(gas, memorySizeGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -254,13 +254,13 @@ func gasSha3(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
} }
wordGas, overflow := bigUint64(stack.Back(1)) wordGas, overflow := bigUint64(stack.Back(1))
if overflow { if overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if gas, overflow = math.SafeAdd(gas, wordGas); overflow { if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -288,13 +288,13 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
} }
wordGas, overflow := bigUint64(stack.Back(2)) wordGas, overflow := bigUint64(stack.Back(2))
if overflow { if overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow { if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
if gas, overflow = math.SafeAdd(gas, wordGas); overflow { if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -307,7 +307,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
overflow bool overflow bool
) )
if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -320,7 +320,7 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor
overflow bool overflow bool
) )
if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow { if gas, overflow = math.SafeAdd(gas, params.ExpGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -347,7 +347,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
} }
var overflow bool var overflow bool
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
@ -355,7 +355,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
return 0, err return 0, err
} }
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -373,14 +373,14 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
gas += params.CallValueTransferGas gas += params.CallValueTransferGas
} }
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil { if err != nil {
return 0, err return 0, err
} }
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -396,7 +396,7 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
} }
var overflow bool var overflow bool
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }
@ -412,7 +412,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
} }
var overflow bool var overflow bool
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, errGasUintOverflow return 0, ErrGasUintOverflow
} }
return gas, nil return gas, nil
} }

View file

@ -39,8 +39,8 @@ func TestMemoryGasCost(t *testing.T) {
} }
for i, tt := range tests { for i, tt := range tests {
v, err := memoryGasCost(&Memory{}, tt.size) v, err := memoryGasCost(&Memory{}, tt.size)
if (err == errGasUintOverflow) != tt.overflow { if (err == ErrGasUintOverflow) != tt.overflow {
t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == errGasUintOverflow, tt.overflow) t.Errorf("test %d: overflow mismatch: have %v, want %v", i, err == ErrGasUintOverflow, tt.overflow)
} }
if v != tt.cost { if v != tt.cost {
t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost) t.Errorf("test %d: gas cost mismatch: have %v, want %v", i, v, tt.cost)

View file

@ -17,7 +17,6 @@
package vm package vm
import ( import (
"errors"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -30,11 +29,6 @@ import (
var ( var (
bigZero = new(big.Int) bigZero = new(big.Int)
tt255 = math.BigPow(2, 255) tt255 = math.BigPow(2, 255)
errWriteProtection = errors.New("evm: write protection")
errReturnDataOutOfBounds = errors.New("evm: return data out of bounds")
errExecutionReverted = errors.New("evm: execution reverted")
errMaxCodeSizeExceeded = errors.New("evm: max code size exceeded")
errInvalidJump = errors.New("evm: invalid jump destination")
) )
func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
@ -468,7 +462,7 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *call
defer interpreter.intPool.put(memOffset, dataOffset, length, end) defer interpreter.intPool.put(memOffset, dataOffset, length, end)
if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() { if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() {
return nil, errReturnDataOutOfBounds return nil, ErrReturnDataOutOfBounds
} }
callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()]) callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()])
@ -643,7 +637,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]
func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
pos := callContext.stack.pop() pos := callContext.stack.pop()
if !callContext.contract.validJumpdest(pos) { if !callContext.contract.validJumpdest(pos) {
return nil, errInvalidJump return nil, ErrInvalidJump
} }
*pc = pos.Uint64() *pc = pos.Uint64()
@ -655,7 +649,7 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]b
pos, cond := callContext.stack.pop(), callContext.stack.pop() pos, cond := callContext.stack.pop(), callContext.stack.pop()
if cond.Sign() != 0 { if cond.Sign() != 0 {
if !callContext.contract.validJumpdest(pos) { if !callContext.contract.validJumpdest(pos) {
return nil, errInvalidJump return nil, ErrInvalidJump
} }
*pc = pos.Uint64() *pc = pos.Uint64()
} else { } else {
@ -712,7 +706,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]
callContext.contract.Gas += returnGas callContext.contract.Gas += returnGas
interpreter.intPool.put(value, offset, size) interpreter.intPool.put(value, offset, size)
if suberr == errExecutionReverted { if suberr == ErrExecutionReverted {
return res, nil return res, nil
} }
return nil, nil return nil, nil
@ -740,7 +734,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
callContext.contract.Gas += returnGas callContext.contract.Gas += returnGas
interpreter.intPool.put(endowment, offset, size, salt) interpreter.intPool.put(endowment, offset, size, salt)
if suberr == errExecutionReverted { if suberr == ErrExecutionReverted {
return res, nil return res, nil
} }
return nil, nil return nil, nil
@ -766,7 +760,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by
} else { } else {
callContext.stack.push(interpreter.intPool.get().SetUint64(1)) callContext.stack.push(interpreter.intPool.get().SetUint64(1))
} }
if err == nil || err == errExecutionReverted { if err == nil || err == ErrExecutionReverted {
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
callContext.contract.Gas += returnGas callContext.contract.Gas += returnGas
@ -795,7 +789,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) (
} else { } else {
callContext.stack.push(interpreter.intPool.get().SetUint64(1)) callContext.stack.push(interpreter.intPool.get().SetUint64(1))
} }
if err == nil || err == errExecutionReverted { if err == nil || err == ErrExecutionReverted {
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
callContext.contract.Gas += returnGas callContext.contract.Gas += returnGas
@ -820,7 +814,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCt
} else { } else {
callContext.stack.push(interpreter.intPool.get().SetUint64(1)) callContext.stack.push(interpreter.intPool.get().SetUint64(1))
} }
if err == nil || err == errExecutionReverted { if err == nil || err == ErrExecutionReverted {
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
callContext.contract.Gas += returnGas callContext.contract.Gas += returnGas
@ -845,7 +839,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx)
} else { } else {
callContext.stack.push(interpreter.intPool.get().SetUint64(1)) callContext.stack.push(interpreter.intPool.get().SetUint64(1))
} }
if err == nil || err == errExecutionReverted { if err == nil || err == ErrExecutionReverted {
callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
callContext.contract.Gas += returnGas callContext.contract.Gas += returnGas

View file

@ -17,7 +17,6 @@
package vm package vm
import ( import (
"fmt"
"hash" "hash"
"sync/atomic" "sync/atomic"
@ -137,7 +136,7 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// //
// It's important to note that any errors returned by the interpreter should be // It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for // considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left. // ErrExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
if in.intPool == nil { if in.intPool == nil {
in.intPool = poolOfIntPools.get() in.intPool = poolOfIntPools.get()
@ -223,13 +222,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
op = contract.GetOp(pc) op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op] operation := in.cfg.JumpTable[op]
if !operation.valid { if !operation.valid {
return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) return nil, &ErrInvalidOpCode{opcode: int(op)}
} }
// Validate stack // Validate stack
if sLen := stack.len(); sLen < operation.minStack { if sLen := stack.len(); sLen < operation.minStack {
return nil, fmt.Errorf("stack underflow (%d <=> %d)", sLen, operation.minStack) return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
} else if sLen > operation.maxStack { } else if sLen > operation.maxStack {
return nil, fmt.Errorf("stack limit reached %d (%d)", sLen, operation.maxStack) return nil, &ErrStackOverflow{stackLen: sLen, allowance: operation.maxStack}
} }
// If the operation is valid, enforce and write restrictions // If the operation is valid, enforce and write restrictions
if in.readOnly && in.evm.chainRules.IsByzantium { if in.readOnly && in.evm.chainRules.IsByzantium {
@ -239,7 +238,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// account to the others means the state is modified and should also // account to the others means the state is modified and should also
// return with an error. // return with an error.
if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) { if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
return nil, errWriteProtection return nil, ErrWriteProtection
} }
} }
// Static portion of gas // Static portion of gas
@ -256,12 +255,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
if operation.memorySize != nil { if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack) memSize, overflow := operation.memorySize(stack)
if overflow { if overflow {
return nil, errGasUintOverflow return nil, ErrGasUintOverflow
} }
// memory is expanded in words of 32 bytes. Gas // memory is expanded in words of 32 bytes. Gas
// is also calculated in words. // is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, errGasUintOverflow return nil, ErrGasUintOverflow
} }
} }
// Dynamic portion of gas // Dynamic portion of gas
@ -301,7 +300,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
case err != nil: case err != nil:
return nil, err return nil, err
case operation.reverts: case operation.reverts:
return res, errExecutionReverted return res, ErrExecutionReverted
case operation.halts: case operation.halts:
return res, nil return res, nil
case !operation.jumps: case !operation.jumps:

View file

@ -17,8 +17,6 @@
package vm package vm
import ( import (
"errors"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -29,8 +27,6 @@ type (
memorySizeFunc func(*Stack) (size uint64, overflow bool) memorySizeFunc func(*Stack) (size uint64, overflow bool)
) )
var errGasUintOverflow = errors.New("gas uint64 overflow")
type operation struct { type operation struct {
// execute is the operation function // execute is the operation function
execute executionFunc execute executionFunc

View file

@ -18,6 +18,7 @@ package vm
import ( import (
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
@ -29,6 +30,8 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
var errTraceLimitReached = errors.New("the number of logs reached the specified limit")
// Storage represents a contract's storage. // Storage represents a contract's storage.
type Storage map[common.Hash]common.Hash type Storage map[common.Hash]common.Hash
@ -140,7 +143,7 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea
func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error { func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
// check if already accumulated the specified number of logs // check if already accumulated the specified number of logs
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
return ErrTraceLimitReached return errTraceLimitReached
} }
// initialise new changed values storage container for this contract // initialise new changed values storage container for this contract

View file

@ -502,7 +502,7 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil) vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{})
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
failed = err failed = err
break break
} }
@ -596,7 +596,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
} }
// Execute the transaction and flush any traces to disk // Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf) vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vmConf)
_, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
if writer != nil { if writer != nil {
writer.Flush() writer.Flush()
} }
@ -758,7 +758,7 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
// Run the transaction with tracing enabled. // Run the transaction with tracing enabled.
vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer}) vmenv := vm.NewEVM(vmctx, statedb, api.eth.blockchain.Config(), vm.Config{Debug: true, Tracer: tracer})
ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
if err != nil { if err != nil {
return nil, fmt.Errorf("tracing failed: %v", err) return nil, fmt.Errorf("tracing failed: %v", err)
} }
@ -766,9 +766,9 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, v
switch tracer := tracer.(type) { switch tracer := tracer.(type) {
case *vm.StructLogger: case *vm.StructLogger:
return &ethapi.ExecutionResult{ return &ethapi.ExecutionResult{
Gas: gas, Gas: result.UsedGas,
Failed: failed, Failed: result.Failed(),
ReturnValue: fmt.Sprintf("%x", ret), ReturnValue: fmt.Sprintf("%x", result.Result),
StructLogs: ethapi.FormatLogs(tracer.StructLogs()), StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
}, nil }, nil
@ -812,7 +812,7 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree
} }
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, statedb, api.eth.blockchain.Config(), vm.Config{})
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return nil, vm.Context{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
// Ensure any modifications are committed to the state // Ensure any modifications are committed to the state

View file

@ -65,7 +65,7 @@
"value": "0x0" "value": "0x0"
} }
], ],
"error": "evm: invalid jump destination", "error": "invalid jump destination",
"from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8", "from": "0xe4a13bc304682a903e9472f469c33801dd18d9e8",
"gas": "0x435c8", "gas": "0x435c8",
"gasUsed": "0x435c8", "gasUsed": "0x435c8",

View file

@ -50,7 +50,7 @@
}, },
"input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750", "input": "0xf88b8206668504a817c8008303d09094c212e03b9e060e36facad5fd8f4435412ca22e6b80a451a34eb8000000000000000000000000000000000000000000000027fad02094277c000029a0692a3b4e7b2842f8dd7832e712c21e09f451f416c8976d5b8d02e8c0c2b4bea9a07645e90fc421b63dd755767fd93d3c03b4ec0c4d8fafa059558d08cf11d59750",
"result": { "result": {
"error": "evm: invalid jump destination", "error": "invalid jump destination",
"from": "0x70c9217d814985faef62b124420f8dfbddd96433", "from": "0x70c9217d814985faef62b124420f8dfbddd96433",
"gas": "0x37b38", "gas": "0x37b38",
"gasUsed": "0x37b38", "gasUsed": "0x37b38",

View file

@ -182,7 +182,7 @@ func TestPrestateTracerCreate2(t *testing.T) {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if _, _, _, err = st.TransitionDb(); err != nil { if _, err = st.TransitionDb(); err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }
// Retrieve the trace result and compare against the etalon // Retrieve the trace result and compare against the etalon
@ -256,7 +256,7 @@ func TestCallTracer(t *testing.T) {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()))
if _, _, _, err = st.TransitionDb(); err != nil { if _, err = st.TransitionDb(); err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }
// Retrieve the trace result and compare against the etalon // Retrieve the trace result and compare against the etalon

View file

@ -803,14 +803,17 @@ func (b *Block) Call(ctx context.Context, args struct {
return nil, err return nil, err
} }
} }
result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap()) result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
if err != nil {
return nil, err
}
status := hexutil.Uint64(1) status := hexutil.Uint64(1)
if failed { if result.Failed() {
status = 0 status = 0
} }
return &CallResult{ return &CallResult{
data: hexutil.Bytes(result), data: result.Result,
gasUsed: hexutil.Uint64(gas), gasUsed: hexutil.Uint64(result.UsedGas),
status: status, status: status,
}, err }, err
} }
@ -869,14 +872,17 @@ func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.CallArgs Data ethapi.CallArgs
}) (*CallResult, error) { }) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap()) result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
if err != nil {
return nil, err
}
status := hexutil.Uint64(1) status := hexutil.Uint64(1)
if failed { if result.Failed() {
status = 0 status = 0
} }
return &CallResult{ return &CallResult{
data: hexutil.Bytes(result), data: result.Result,
gasUsed: hexutil.Uint64(gas), gasUsed: hexutil.Uint64(result.UsedGas),
status: status, status: status,
}, err }, err
} }

View file

@ -789,12 +789,12 @@ type account struct {
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
} }
func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) { func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil { if state == nil || err != nil {
return nil, 0, false, err return nil, err
} }
// Override the fields of specified contracts before execution. // Override the fields of specified contracts before execution.
@ -812,7 +812,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
state.SetBalance(addr, (*big.Int)(*account.Balance)) state.SetBalance(addr, (*big.Int)(*account.Balance))
} }
if account.State != nil && account.StateDiff != nil { if account.State != nil && account.StateDiff != nil {
return nil, 0, false, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
} }
// Replace entire state if caller requires. // Replace entire state if caller requires.
if account.State != nil { if account.State != nil {
@ -825,7 +825,6 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
} }
} }
} }
// Setup context so it may be cancelled the call has completed // Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout. // or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc var cancel context.CancelFunc
@ -842,7 +841,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
msg := args.ToMessage(globalGasCap) msg := args.ToMessage(globalGasCap)
evm, vmError, err := b.GetEVM(ctx, msg, state, header) evm, vmError, err := b.GetEVM(ctx, msg, state, header)
if err != nil { if err != nil {
return nil, 0, false, err return nil, err
} }
// Wait for the context to be done and cancel the evm. Even if the // Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly) // EVM has finished, cancelling may be done (repeatedly)
@ -854,15 +853,15 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
// Setup the gas pool (also for unmetered requests) // Setup the gas pool (also for unmetered requests)
// and apply the message. // and apply the message.
gp := new(core.GasPool).AddGas(math.MaxUint64) gp := new(core.GasPool).AddGas(math.MaxUint64)
res, gas, failed, err := core.ApplyMessage(evm, msg, gp) result, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil { if err := vmError(); err != nil {
return nil, 0, false, err return nil, err
} }
// If the timer caused an abort, return an appropriate error message // If the timer caused an abort, return an appropriate error message
if evm.Cancelled() { if evm.Cancelled() {
return nil, 0, false, fmt.Errorf("execution aborted (timeout = %v)", timeout) return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
} }
return res, gas, failed, err return result, err
} }
// Call executes the given transaction on the state for the given block number. // Call executes the given transaction on the state for the given block number.
@ -876,8 +875,11 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
if overrides != nil { if overrides != nil {
accounts = *overrides accounts = *overrides
} }
result, _, _, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap()) result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
return (hexutil.Bytes)(result), err if err != nil {
return nil, err
}
return result.Result, nil
} }
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) { func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
@ -908,19 +910,30 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
args.From = new(common.Address) args.From = new(common.Address)
} }
// Create a helper to check if a gas allowance results in an executable transaction // Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) bool { executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas) args.Gas = (*hexutil.Uint64)(&gas)
_, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap) result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
if err != nil || failed { if err != nil {
return false if err == core.ErrInsufficientIntrinsicGas {
return true, nil, nil
} }
return true return true, nil, err
}
return result.Failed(), result, nil
} }
// Execute the binary search and hone in on an executable gas limit // Execute the binary search and hone in on an executable gas limit
for lo+1 < hi { for lo+1 < hi {
mid := (hi + lo) / 2 mid := (hi + lo) / 2
if !executable(mid) { failed, _, err := executable(mid)
// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accpeted no matter how many gas assigened.
// Return the error directly, don't struggle any more
if err != nil {
return 0, err
}
if failed {
lo = mid lo = mid
} else { } else {
hi = mid hi = mid
@ -928,8 +941,15 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
} }
// Reject the transaction as invalid if it still fails at the highest allowance // Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap { if hi == cap {
if !executable(hi) { failed, result, err := executable(hi)
return 0, fmt.Errorf("gas required exceeds allowance (%d) or always failing transaction", cap) if err != nil {
return 0, err
}
if failed {
if result != nil && len(result.RevertReason) != 0 {
return 0, fmt.Errorf("Reverted %x", result.RevertReason)
}
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
} }
} }
return hexutil.Uint64(hi), nil return hexutil.Uint64(hi), nil

View file

@ -135,8 +135,8 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(math.MaxUint64) gp := new(core.GasPool).AddGas(math.MaxUint64)
ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) result, _ := core.ApplyMessage(vmenv, msg, gp)
res = append(res, ret...) res = append(res, result.Result...)
} }
} else { } else {
header := lc.GetHeaderByHash(bhash) header := lc.GetHeaderByHash(bhash)
@ -146,9 +146,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
context := core.NewEVMContext(msg, header, lc, nil) context := core.NewEVMContext(msg, header, lc, nil)
vmenv := vm.NewEVM(context, state, config, vm.Config{}) vmenv := vm.NewEVM(context, state, config, vm.Config{})
gp := new(core.GasPool).AddGas(math.MaxUint64) gp := new(core.GasPool).AddGas(math.MaxUint64)
ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) result, _ := core.ApplyMessage(vmenv, msg, gp)
if state.Error() == nil { if state.Error() == nil {
res = append(res, ret...) res = append(res, result.Result...)
} }
} }
} }

View file

@ -198,8 +198,8 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
context := core.NewEVMContext(msg, header, chain, nil) context := core.NewEVMContext(msg, header, chain, nil)
vmenv := vm.NewEVM(context, st, config, vm.Config{}) vmenv := vm.NewEVM(context, st, config, vm.Config{})
gp := new(core.GasPool).AddGas(math.MaxUint64) gp := new(core.GasPool).AddGas(math.MaxUint64)
ret, _, _, _ := core.ApplyMessage(vmenv, msg, gp) result, _ := core.ApplyMessage(vmenv, msg, gp)
res = append(res, ret...) res = append(res, result.Result...)
if st.Error() != nil { if st.Error() != nil {
return res, st.Error() return res, st.Error()
} }

View file

@ -186,7 +186,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
gaspool := new(core.GasPool) gaspool := new(core.GasPool)
gaspool.AddGas(block.GasLimit()) gaspool.AddGas(block.GasLimit())
snapshot := statedb.Snapshot() snapshot := statedb.Snapshot()
if _, _, _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil {
statedb.RevertToSnapshot(snapshot) statedb.RevertToSnapshot(snapshot)
} }
// Commit block // Commit block