Skip to content

Commit

Permalink
core/vm: Add EVMC support
Browse files Browse the repository at this point in the history
  • Loading branch information
chfast committed Nov 14, 2018
1 parent d987357 commit 0fbae02
Show file tree
Hide file tree
Showing 2 changed files with 339 additions and 17 deletions.
31 changes: 14 additions & 17 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,24 +140,21 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
}

if chainConfig.IsEWASM(ctx.BlockNumber) {
// to be implemented by EVM-C and Wagon PRs.
// if vmConfig.EWASMInterpreter != "" {
// extIntOpts := strings.Split(vmConfig.EWASMInterpreter, ":")
// path := extIntOpts[0]
// options := []string{}
// if len(extIntOpts) > 1 {
// options = extIntOpts[1..]
// }
// evm.interpreters = append(evm.interpreters, NewEVMVCInterpreter(evm, vmConfig, options))
// } else {
// evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig))
// }
panic("No supported ewasm interpreter yet.")
}

// vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
// as we always want to have the built-in EVM as the failover option.
if vmConfig.EWASMInterpreter != "" {
evm.interpreters = append(evm.interpreters, NewEVMC(vmConfig.EWASMInterpreter, evm))
} else {
panic("The default ewasm interpreter not supported yet.")
}
}

if vmConfig.EVMInterpreter != "" {
// Create custom EVM.
evm.interpreters = append(evm.interpreters, NewEVMC(vmConfig.EVMInterpreter, evm))
}

// Keep the built-in EVM as the failover option.
evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))

evm.interpreter = evm.interpreters[0]

return evm
Expand Down
325 changes: 325 additions & 0 deletions core/vm/evmc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package vm

import (
"bytes"
"fmt"
"math/big"
"strings"
"sync"

"github.com/ethereum/evmc/bindings/go/evmc"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)

type EVMC struct {
instance *evmc.Instance
env *EVM
readOnly bool // TODO: The readOnly flag should not be here.
}

var (
createMu sync.Mutex
evmcConfig string // The configuration the instance was created with.
evmcInstance *evmc.Instance
)

func createVM(config string) *evmc.Instance {
createMu.Lock()
defer createMu.Unlock()

if evmcInstance == nil {
options := strings.Split(config, ",")
path := options[0]

if path == "" {
panic("EVMC VM path not provided, set --vm.(evm|ewasm)=/path/to/vm")
}

var err error
evmcInstance, err = evmc.Load(path)
if err != nil {
panic(err.Error())
}
log.Info("EVMC VM loaded", "name", evmcInstance.Name(), "version", evmcInstance.Version(), "path", path)

for _, option := range options[1:] {
if idx := strings.Index(option, "="); idx >= 0 {
name := option[:idx]
value := option[idx+1:]
err := evmcInstance.SetOption(name, value)
if err == nil {
log.Info("EVMC VM option set", "name", name, "value", value)
} else {
log.Warn("EVMC VM option setting failed", "name", name, "error", err)
}
}
}

evm1Cap := evmcInstance.HasCapability(evmc.CapabilityEVM1)
ewasmCap := evmcInstance.HasCapability(evmc.CapabilityEWASM)
log.Info("EVMC VM capabilities", "evm1", evm1Cap, "ewasm", ewasmCap)

evmcConfig = config // Remember the config.
} else if evmcConfig != config {
log.Error("New EVMC VM requested", "newconfig", config, "oldconfig", evmcConfig)
}
return evmcInstance
}

func NewEVMC(options string, env *EVM) *EVMC {
return &EVMC{createVM(options), env, false}
}

// Implements evmc.HostContext interface.
type HostContext struct {
env *EVM
contract *Contract
}

func (host *HostContext) AccountExists(addr common.Address) bool {
env := host.env
eip158 := env.ChainConfig().IsEIP158(env.BlockNumber)
if eip158 {
if !env.StateDB.Empty(addr) {
return true
}
} else if env.StateDB.Exist(addr) {
return true
}
return false
}

func (host *HostContext) GetStorage(addr common.Address, key common.Hash) common.Hash {
env := host.env
return env.StateDB.GetState(addr, key)
}

func (host *HostContext) SetStorage(addr common.Address, key common.Hash, value common.Hash) (status evmc.StorageStatus) {
env := host.env

oldValue := env.StateDB.GetState(addr, key)
if oldValue == value {
return evmc.StorageUnchanged
}

env.StateDB.SetState(addr, key, value)

zero := common.Hash{}
status = evmc.StorageModified
if oldValue == zero {
return evmc.StorageAdded
} else if value == zero {
env.StateDB.AddRefund(params.SstoreRefundGas)
return evmc.StorageDeleted
}
return evmc.StorageModified
}

func (host *HostContext) GetBalance(addr common.Address) common.Hash {
env := host.env
balance := env.StateDB.GetBalance(addr)
return common.BigToHash(balance)
}

func (host *HostContext) GetCodeSize(addr common.Address) int {
env := host.env
return env.StateDB.GetCodeSize(addr)
}

func (host *HostContext) GetCodeHash(addr common.Address) common.Hash {
env := host.env
return env.StateDB.GetCodeHash(addr)
}

func (host *HostContext) GetCode(addr common.Address) []byte {
env := host.env
return env.StateDB.GetCode(addr)
}

func (host *HostContext) Selfdestruct(addr common.Address, beneficiary common.Address) {
env := host.env
db := env.StateDB
if !db.HasSuicided(addr) {
db.AddRefund(params.SuicideRefundGas)
}
balance := db.GetBalance(addr)
db.AddBalance(beneficiary, balance)
db.Suicide(addr)
}

func (host *HostContext) GetTxContext() (gasPrice common.Hash, origin common.Address, coinbase common.Address,
number int64, timestamp int64, gasLimit int64, difficulty common.Hash) {

env := host.env
gasPrice = common.BigToHash(env.GasPrice)
origin = env.Origin
coinbase = env.Coinbase
number = env.BlockNumber.Int64()
timestamp = env.Time.Int64()
gasLimit = int64(env.GasLimit)
difficulty = common.BigToHash(env.Difficulty)

return gasPrice, origin, coinbase, number, timestamp, gasLimit, difficulty
}

func (host *HostContext) GetBlockHash(number int64) common.Hash {
env := host.env
b := env.BlockNumber.Int64()
if number >= (b-256) && number < b {
return env.GetHash(uint64(number))
}
return common.Hash{}
}

func (host *HostContext) EmitLog(addr common.Address, topics []common.Hash, data []byte) {
env := host.env
env.StateDB.AddLog(&types.Log{
Address: addr,
Topics: topics,
Data: data,
BlockNumber: env.BlockNumber.Uint64(),
})
}

func (host *HostContext) Call(kind evmc.CallKind,
destination common.Address, sender common.Address, value *big.Int, input []byte, gas int64, depth int,
static bool) (output []byte, gasLeft int64, createAddr common.Address, err error) {

env := host.env

gasU := uint64(gas)
var gasLeftU uint64

switch kind {
case evmc.Call:
if static {
output, gasLeftU, err = env.StaticCall(host.contract, destination, input, gasU)
} else {
output, gasLeftU, err = env.Call(host.contract, destination, input, gasU, value)
}
case evmc.DelegateCall:
output, gasLeftU, err = env.DelegateCall(host.contract, destination, input, gasU)
case evmc.CallCode:
output, gasLeftU, err = env.CallCode(host.contract, destination, input, gasU, value)
case evmc.Create:
var createOutput []byte
createOutput, createAddr, gasLeftU, err = env.Create(host.contract, input, gasU, value)
isHomestead := env.ChainConfig().IsHomestead(env.BlockNumber)
if !isHomestead && err == ErrCodeStoreOutOfGas {
err = nil
}
if err == errExecutionReverted {
// Assign return buffer from REVERT.
// TODO: Bad API design: return data buffer and the code is returned in the same place. In worst case
// the code is returned also when there is not enough funds to deploy the code.
output = createOutput
}
}

// Map errors.
if err == errExecutionReverted {
err = evmc.Revert
} else if err != nil {
err = evmc.Failure
}

gasLeft = int64(gasLeftU)
return output, gasLeft, createAddr, err
}

func getRevision(env *EVM) evmc.Revision {
n := env.BlockNumber
conf := env.ChainConfig()
if conf.IsConstantinople(n) {
return evmc.Constantinople
}
if conf.IsByzantium(n) {
return evmc.Byzantium
}
if conf.IsEIP158(n) {
return evmc.SpuriousDragon
}
if conf.IsEIP150(n) {
return evmc.TangerineWhistle
}
if conf.IsHomestead(n) {
return evmc.Homestead
}
return evmc.Frontier
}

func (evm *EVMC) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
evm.env.depth++
defer func() { evm.env.depth-- }()

// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}

kind := evmc.Call
if evm.env.StateDB.GetCodeSize(contract.Address()) == 0 {
// Guess if this is a CREATE.
kind = evmc.Create
}

// Make sure the readOnly is only set if we aren't in readOnly yet.
// This makes also sure that the readOnly flag isn't removed for child calls.
if readOnly && !evm.readOnly {
evm.readOnly = true
defer func() { evm.readOnly = false }()
}

output, gasLeft, err := evm.instance.Execute(
&HostContext{evm.env, contract},
getRevision(evm.env),
kind,
evm.readOnly,
evm.env.depth-1,
int64(contract.Gas),
contract.Address(),
contract.Caller(),
input,
common.BigToHash(contract.value),
contract.Code,
common.Hash{})

contract.Gas = uint64(gasLeft)

if err == evmc.Revert {
err = errExecutionReverted
} else if evmcError, ok := err.(evmc.Error); ok && evmcError.IsInternalError() {
panic(fmt.Sprintf("EVMC VM internal error: %s", evmcError.Error()))
}

return output, err
}

func (evm *EVMC) CanRun(code []byte) bool {
cap := evmc.CapabilityEVM1
wasmPreamble := []byte("\x00asm\x01\x00\x00\x00")
if bytes.HasPrefix(code, wasmPreamble) {
cap = evmc.CapabilityEWASM
}
// FIXME: Optimize. Access capabilities once.
return evm.instance.HasCapability(cap)
}

0 comments on commit 0fbae02

Please sign in to comment.