Skip to content

Commit

Permalink
core/vm: improved EVM run loop & instruction calling (#3378)
Browse files Browse the repository at this point in the history
The run loop, which previously contained custom opcode executes have been
removed and has been simplified to a few checks.

Each operation consists of 4 elements: execution function, gas cost function,
stack validation function and memory size function. The execution function
implements the operation's runtime behaviour, the gas cost function implements
the operation gas costs function and greatly depends on the memory and stack,
the stack validation function validates the stack and makes sure that enough
items can be popped off and pushed on and the memory size function calculates
the memory required for the operation and returns it.

This commit also allows the EVM to go unmetered. This is helpful for offline
operations such as contract calls.
  • Loading branch information
obscuren authored and fjl committed Jan 5, 2017
1 parent 2126d81 commit bbc4ea4
Show file tree
Hide file tree
Showing 44 changed files with 1,653 additions and 1,996 deletions.
2 changes: 1 addition & 1 deletion accounts/abi/bind/backends/simulated.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEnvironment(evmContext, statedb, chainConfig, vm.Config{})
vmenv := vm.NewEVM(evmContext, statedb, chainConfig, vm.Config{})
gaspool := new(core.GasPool).AddGas(common.MaxBig)
ret, gasUsed, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
return ret, gasUsed, err
Expand Down
21 changes: 9 additions & 12 deletions cmd/evm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ var (
Name: "debug",
Usage: "output full trace logs",
}
ForceJitFlag = cli.BoolFlag{
Name: "forcejit",
Usage: "forces jit compilation",
}
DisableJitFlag = cli.BoolFlag{
Name: "nojit",
Usage: "disabled jit compilation",
}
CodeFlag = cli.StringFlag{
Name: "code",
Usage: "EVM code",
Expand Down Expand Up @@ -95,15 +87,17 @@ var (
Name: "create",
Usage: "indicates the action should be create rather than call",
}
DisableGasMeteringFlag = cli.BoolFlag{
Name: "nogasmetering",
Usage: "disable gas metering",
}
)

func init() {
app.Flags = []cli.Flag{
CreateFlag,
DebugFlag,
VerbosityFlag,
ForceJitFlag,
DisableJitFlag,
SysStatFlag,
CodeFlag,
CodeFileFlag,
Expand All @@ -112,6 +106,7 @@ func init() {
ValueFlag,
DumpFlag,
InputFlag,
DisableGasMeteringFlag,
}
app.Action = run
}
Expand Down Expand Up @@ -165,7 +160,8 @@ func run(ctx *cli.Context) error {
GasPrice: common.Big(ctx.GlobalString(PriceFlag.Name)),
Value: common.Big(ctx.GlobalString(ValueFlag.Name)),
EVMConfig: vm.Config{
Tracer: logger,
Tracer: logger,
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
},
})
} else {
Expand All @@ -179,7 +175,8 @@ func run(ctx *cli.Context) error {
GasPrice: common.Big(ctx.GlobalString(PriceFlag.Name)),
Value: common.Big(ctx.GlobalString(ValueFlag.Name)),
EVMConfig: vm.Config{
Tracer: logger,
Tracer: logger,
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
},
})
}
Expand Down
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
b.SetCoinbase(common.Address{})
}
b.statedb.StartRecord(tx.Hash(), common.Hash{}, len(b.txs))
receipt, _, _, err := ApplyTransaction(b.config, nil, b.gasPool, b.statedb, b.header, tx, b.header.GasUsed, vm.Config{})
receipt, _, err := ApplyTransaction(b.config, nil, b.gasPool, b.statedb, b.header, tx, b.header.GasUsed, vm.Config{})
if err != nil {
panic(err)
}
Expand Down
21 changes: 10 additions & 11 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,37 +74,36 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
for i, tx := range block.Transactions() {
//fmt.Println("tx:", i)
statedb.StartRecord(tx.Hash(), block.Hash(), i)
receipt, logs, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas, cfg)
receipt, _, err := ApplyTransaction(p.config, p.bc, gp, statedb, header, tx, totalUsedGas, cfg)
if err != nil {
return nil, nil, nil, err
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, logs...)
allLogs = append(allLogs, receipt.Logs...)
}
AccumulateRewards(statedb, header, block.Uncles())

return receipts, allLogs, totalUsedGas, err
}

// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment.
//
// ApplyTransactions returns the generated receipts and vm logs during the
// execution of the state transition phase.
func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, vm.Logs, *big.Int, error) {
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, *big.Int, error) {
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}
// Create a new context to be used in the EVM environment
context := NewEVMContext(msg, header, bc)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEnvironment(context, statedb, config, vm.Config{})
vmenv := vm.NewEVM(context, statedb, config, vm.Config{})
// Apply the transaction to the current state (included in the env)
_, gas, err := ApplyMessage(vmenv, msg, gp)
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}

// Update the state with pending changes
Expand All @@ -125,7 +124,7 @@ func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, gp *GasPool, s

glog.V(logger.Debug).Infoln(receipt)

return receipt, receipt.Logs, gas, err
return receipt, gas, err
}

// AccumulateRewards credits the coinbase of the given block with the
Expand Down
10 changes: 5 additions & 5 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type StateTransition struct {
data []byte
state vm.StateDB

env *vm.Environment
env *vm.EVM
}

// Message represents a message sent to a contract.
Expand Down Expand Up @@ -106,7 +106,7 @@ func IntrinsicGas(data []byte, contractCreation, homestead bool) *big.Int {
}

// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(env *vm.Environment, msg Message, gp *GasPool) *StateTransition {
func NewStateTransition(env *vm.EVM, msg Message, gp *GasPool) *StateTransition {
return &StateTransition{
gp: gp,
env: env,
Expand All @@ -127,7 +127,7 @@ func NewStateTransition(env *vm.Environment, msg Message, gp *GasPool) *StateTra
// 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
// state and would never be accepted within a block.
func ApplyMessage(env *vm.Environment, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
func ApplyMessage(env *vm.EVM, msg Message, gp *GasPool) ([]byte, *big.Int, error) {
st := NewStateTransition(env, msg, gp)

ret, _, gasUsed, err := st.TransitionDb()
Expand Down Expand Up @@ -159,7 +159,7 @@ func (self *StateTransition) to() vm.Account {

func (self *StateTransition) useGas(amount *big.Int) error {
if self.gas.Cmp(amount) < 0 {
return vm.OutOfGasError
return vm.ErrOutOfGas
}
self.gas.Sub(self.gas, amount)

Expand Down Expand Up @@ -233,7 +233,7 @@ func (self *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *b
)
if contractCreation {
ret, _, vmerr = vmenv.Create(sender, self.data, self.gas, self.value)
if homestead && err == vm.CodeStoreOutOfGasError {
if homestead && err == vm.ErrCodeStoreOutOfGas {
self.gas = Big0
}
} else {
Expand Down
110 changes: 62 additions & 48 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,64 +26,45 @@ import (
"github.com/ethereum/go-ethereum/params"
)

// PrecompiledAccount represents a native ethereum contract
type PrecompiledAccount struct {
Gas func(l int) *big.Int
fn func(in []byte) []byte
// Precompiled contract is the basic interface for native Go contracts. The implementation
// requires a deterministic gas count based on the input size of the Run method of the
// contract.
type PrecompiledContract interface {
RequiredGas(inputSize int) *big.Int // RequiredPrice calculates the contract gas use
Run(input []byte) []byte // Run runs the precompiled contract
}

// Call calls the native function
func (self PrecompiledAccount) Call(in []byte) []byte {
return self.fn(in)
// Precompiled contains the default set of ethereum contracts
var PrecompiledContracts = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256{},
common.BytesToAddress([]byte{3}): &ripemd160{},
common.BytesToAddress([]byte{4}): &dataCopy{},
}

// Precompiled contains the default set of ethereum contracts
var Precompiled = PrecompiledContracts()

// PrecompiledContracts returns the default set of precompiled ethereum
// contracts defined by the ethereum yellow paper.
func PrecompiledContracts() map[string]*PrecompiledAccount {
return map[string]*PrecompiledAccount{
// ECRECOVER
string(common.LeftPadBytes([]byte{1}, 20)): &PrecompiledAccount{func(l int) *big.Int {
return params.EcrecoverGas
}, ecrecoverFunc},

// SHA256
string(common.LeftPadBytes([]byte{2}, 20)): &PrecompiledAccount{func(l int) *big.Int {
n := big.NewInt(int64(l+31) / 32)
n.Mul(n, params.Sha256WordGas)
return n.Add(n, params.Sha256Gas)
}, sha256Func},

// RIPEMD160
string(common.LeftPadBytes([]byte{3}, 20)): &PrecompiledAccount{func(l int) *big.Int {
n := big.NewInt(int64(l+31) / 32)
n.Mul(n, params.Ripemd160WordGas)
return n.Add(n, params.Ripemd160Gas)
}, ripemd160Func},

string(common.LeftPadBytes([]byte{4}, 20)): &PrecompiledAccount{func(l int) *big.Int {
n := big.NewInt(int64(l+31) / 32)
n.Mul(n, params.IdentityWordGas)

return n.Add(n, params.IdentityGas)
}, memCpy},
// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.RequiredGas(len(input))
if contract.UseGas(gas) {
ret = p.Run(input)

return ret, nil
} else {
return nil, ErrOutOfGas
}
}

func sha256Func(in []byte) []byte {
return crypto.Sha256(in)
}
// ECRECOVER implemented as a native contract
type ecrecover struct{}

func ripemd160Func(in []byte) []byte {
return common.LeftPadBytes(crypto.Ripemd160(in), 32)
func (c *ecrecover) RequiredGas(inputSize int) *big.Int {
return params.EcrecoverGas
}

const ecRecoverInputLength = 128
func (c *ecrecover) Run(in []byte) []byte {
const ecRecoverInputLength = 128

func ecrecoverFunc(in []byte) []byte {
in = common.RightPadBytes(in, 128)
in = common.RightPadBytes(in, ecRecoverInputLength)
// "in" is (hash, v, r, s), each 32 bytes
// but for ecrecover we want (r, s, v)

Expand All @@ -108,6 +89,39 @@ func ecrecoverFunc(in []byte) []byte {
return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32)
}

func memCpy(in []byte) []byte {
// SHA256 implemented as a native contract
type sha256 struct{}

func (c *sha256) RequiredGas(inputSize int) *big.Int {
n := big.NewInt(int64(inputSize+31) / 32)
n.Mul(n, params.Sha256WordGas)
return n.Add(n, params.Sha256Gas)
}
func (c *sha256) Run(in []byte) []byte {
return crypto.Sha256(in)
}

// RIPMED160 implemented as a native contract
type ripemd160 struct{}

func (c *ripemd160) RequiredGas(inputSize int) *big.Int {
n := big.NewInt(int64(inputSize+31) / 32)
n.Mul(n, params.Ripemd160WordGas)
return n.Add(n, params.Ripemd160Gas)
}
func (c *ripemd160) Run(in []byte) []byte {
return common.LeftPadBytes(crypto.Ripemd160(in), 32)
}

// data copy implemented as a native contract
type dataCopy struct{}

func (c *dataCopy) RequiredGas(inputSize int) *big.Int {
n := big.NewInt(int64(inputSize+31) / 32)
n.Mul(n, params.IdentityWordGas)

return n.Add(n, params.IdentityGas)
}
func (c *dataCopy) Run(in []byte) []byte {
return in
}
Loading

0 comments on commit bbc4ea4

Please sign in to comment.