From c212f32fa67e28b8c49cae1b031ddc7f649b7e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Sep 2018 15:22:08 +0200 Subject: [PATCH] core/vm: Add EVMC support --- core/vm/evm.go | 31 ++-- core/vm/evmc.go | 372 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 386 insertions(+), 17 deletions(-) create mode 100644 core/vm/evmc.go diff --git a/core/vm/evm.go b/core/vm/evm.go index ba4d1e9eb8c5..4adfa1f2aa4f 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -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 diff --git a/core/vm/evmc.go b/core/vm/evmc.go new file mode 100644 index 000000000000..8b3fd7441563 --- /dev/null +++ b/core/vm/evmc.go @@ -0,0 +1,372 @@ +// 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 . + +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 + } + + current := env.StateDB.GetState(addr, key) + original := env.StateDB.GetCommittedState(addr, key) + + env.StateDB.SetState(addr, key, value) + + isConstantinople := env.ChainConfig().IsConstantinople(env.BlockNumber) + if !isConstantinople { + + 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 + } + + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return evmc.StorageAdded + } + if value == (common.Hash{}) { // delete slot (2.1.2b) + env.StateDB.AddRefund(params.NetSstoreClearRefund) + return evmc.StorageDeleted + } + return evmc.StorageModified + } + if original != (common.Hash{}) { + if current == (common.Hash{}) { // recreate slot (2.2.1.1) + env.StateDB.SubRefund(params.NetSstoreClearRefund) + } else if value == (common.Hash{}) { // delete slot (2.2.1.2) + env.StateDB.AddRefund(params.NetSstoreClearRefund) + } + } + if original == value { + if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1) + env.StateDB.AddRefund(params.NetSstoreResetClearRefund) + } else { // reset to original existing slot (2.2.2.2) + env.StateDB.AddRefund(params.NetSstoreResetRefund) + } + } + return evmc.StorageModifiedAgain +} + +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 + if env.StateDB.Empty(addr) { + return common.Hash{} + } + 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, salt *big.Int) (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 + } + case evmc.Create2: + var createOutput []byte + createOutput, createAddr, gasLeftU, err = env.Create2(host.contract, input, gasU, value, salt) + 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 + } + default: + panic(fmt.Errorf("EVMC: Unknown call kind %d", kind)) + } + + // 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") + if bytes.HasPrefix(code, wasmPreamble) { + cap = evmc.CapabilityEWASM + } + // FIXME: Optimize. Access capabilities once. + return evm.instance.HasCapability(cap) +}