diff --git a/cmd/geth/main.go b/cmd/geth/main.go index edf2a557a7..0a4367d5bc 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -112,6 +112,7 @@ var ( utils.TestnetFlag, utils.RinkebyFlag, utils.VMEnableDebugFlag, + utils.EVMCPathFlag, utils.NetworkIdFlag, utils.RPCCORSDomainFlag, utils.RPCVirtualHostsFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index d934c6b021..3dc4bc4c27 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -201,6 +201,7 @@ var AppHelpFlagGroups = []flagGroup{ Name: "VIRTUAL MACHINE", Flags: []cli.Flag{ utils.VMEnableDebugFlag, + utils.EVMCPathFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 41a1ac35fe..e30bd4540b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -355,6 +355,11 @@ var ( Name: "vmdebug", Usage: "Record information useful for VM and contract debugging", } + EVMCPathFlag = cli.StringFlag{ + Name: "vm", + Usage: "Path to EVMC compatible VM plugin", + } + // Logging and debug settings EthStatsURLFlag = cli.StringFlag{ Name: "ethstats", @@ -1070,6 +1075,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // TODO(fjl): force-enable this in --dev mode cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name) } + if ctx.GlobalIsSet(EVMCPathFlag.Name) { + cfg.EVMCPath = ctx.GlobalString(EVMCPathFlag.Name) + } // Override any default configs for hard coded networks. switch { @@ -1250,7 +1258,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { cache.TrieNodeLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 } - vmcfg := vm.Config{EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name)} + vmcfg := vm.Config{EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name), EVMCPath: ctx.GlobalString(EVMCPathFlag.Name)} chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg) if err != nil { Fatalf("Can't create BlockChain: %v", err) diff --git a/core/vm/evm.go b/core/vm/evm.go index ea46209742..570bfc0a8d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -101,7 +101,7 @@ type EVM struct { vmConfig Config // global (to this context) ethereum virtual machine // used throughout the execution of the tx. - interpreter *Interpreter + interpreter *EVMJIT // abort is used to abort the EVM calling operations // NOTE: must be set atomically abort int32 @@ -122,7 +122,7 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon chainRules: chainConfig.Rules(ctx.BlockNumber), } - evm.interpreter = NewInterpreter(evm, vmConfig) + evm.interpreter = NewJit(evm, vmConfig) return evm } @@ -400,4 +400,4 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } // Interpreter returns the EVM interpreter -func (evm *EVM) Interpreter() *Interpreter { return evm.interpreter } +func (evm *EVM) Interpreter() *EVMJIT { return evm.interpreter } diff --git a/core/vm/evmjit.go b/core/vm/evmjit.go new file mode 100644 index 0000000000..490476ec7b --- /dev/null +++ b/core/vm/evmjit.go @@ -0,0 +1,584 @@ +// Copyright 2017 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 + +/* + +#include +#include +#include + + +static const struct evmc_context_fn_table* get_context_fn_table() +{ + int account_exists(void*, void*); + void get_storage(struct evmc_uint256be*, void*, void*, struct evmc_uint256be*); + void set_storage(void*, void*, void*, void*); + void get_balance(void*, void*, void*); + size_t get_code_size(void*, void*); + size_t copy_code(void*, void*, size_t, uint8_t*, size_t); + void selfdestruct(void*, void*, void*); + void call(struct evmc_result*, void*, struct evmc_message*); + void getTxCtx(void*, void*); + void getBlockHash(void*, void*, long long); + void emit_log(void*, void*, void*, size_t, void*, size_t); + + static const struct evmc_context_fn_table fn_table = { + (evmc_account_exists_fn) account_exists, + (evmc_get_storage_fn) get_storage, + (evmc_set_storage_fn) set_storage, + (evmc_get_balance_fn) get_balance, + (evmc_get_code_size_fn) get_code_size, + (evmc_copy_code_fn) copy_code, + (evmc_selfdestruct_fn) selfdestruct, + (evmc_call_fn) call, + (evmc_get_tx_context_fn) getTxCtx, + (evmc_get_block_hash_fn) getBlockHash, + (evmc_emit_log_fn) emit_log + }; + return &fn_table; +} + + +static struct evmc_result evmc_execute( + struct evmc_instance* instance, + struct evmc_context* context, + enum evmc_revision rev, + const struct evmc_message* msg, + uint8_t const* code, + size_t code_size +) +{ + return instance->execute(instance, context, rev, msg, code, code_size); +} + +static void evmc_release_result(struct evmc_result* result) +{ + result->release(result); +} + +static void free_result_output(const struct evmc_result* result) +{ + free((void*)result->output_data); +} + +static void add_result_releaser(struct evmc_result* result) +{ + result->release = free_result_output; +} + +typedef struct evmc_instance* (*evmc_create_fn)(); + +static struct evmc_instance* create(void* create_symbol) +{ + evmc_create_fn fn = (evmc_create_fn)create_symbol; + return fn(); +} + +#cgo CFLAGS: -I/home/chfast/Projects/ethereum/cpp-ethereum/evmc/include +#cgo LDFLAGS: -ldl +*/ +import "C" + +import ( + "fmt" + "math/big" + "reflect" + "sync" + "unsafe" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/log" +) + +type EVMJIT struct { + jit *C.struct_evmc_instance + env *EVM + intPool *intPool + readOnly bool + returnData []byte // Last CALL's return data for subsequent reuse +} + +type EVMCContext struct { + contract *Contract + env *EVM +} + +type ContextWrapper struct { + c C.struct_evmc_context + index int +} + +var loadMu sync.Mutex +var createSymbol unsafe.Pointer +var loaded = false + +func loadVM(path string) { + if len(path) == 0 { + panic("EVMC path not provided, use --vm flag") + } + + loadMu.Lock() + defer loadMu.Unlock() + + if loaded { + return + } + + cpath := C.CString(path) + defer C.free(unsafe.Pointer(cpath)) + handle := C.dlopen(cpath, C.RTLD_LAZY) + if handle == nil { + panic(fmt.Sprintf("cannot open %s", path)) + } + + centrypoint := C.CString("evmc_create") + defer C.free(unsafe.Pointer((centrypoint))) + + C.dlerror() + createSymbol = C.dlsym(handle, centrypoint) + err := C.dlerror() + if err != nil { + panic(fmt.Sprintf("cannot find evmc_create in %s", path)) + } + loaded = true + log.Info("EVMC VM loaded", "path", path) +} + +func NewJit(env *EVM, cfg Config) *EVMJIT { + loadVM(cfg.EVMCPath) + // FIXME: Destroy the instance later. + // FIXME: Create the instance once. + return &EVMJIT{C.create(createSymbol), env, nil, false, nil} +} + + +var contextMap = make(map[int]*EVMCContext) +var contextMapMu sync.Mutex + +func pinCtx(ctx *EVMCContext) int { + contextMapMu.Lock() + + // Find empty slot in the map starting from the map length. + id := len(contextMap) + for contextMap[id] != nil { + id++ + } + contextMap[id] = ctx + contextMapMu.Unlock() + return id +} + +func unpinCtx(id int) { + contextMapMu.Lock() + delete(contextMap, id) + contextMapMu.Unlock() +} + +func getCtx(idx int) *EVMCContext { + contextMapMu.Lock() + defer contextMapMu.Unlock() + return contextMap[idx] +} + +func getEnv(pCtx unsafe.Pointer) *EVM { + ctxWrapper := (*ContextWrapper)(pCtx) + return getCtx(ctxWrapper.index).env +} + +func getContract(pCtx unsafe.Pointer) *Contract { + ctxWrapper := (*ContextWrapper)(pCtx) + return getCtx(ctxWrapper.index).contract +} + +func HashToEvmc(hash common.Hash) C.struct_evmc_uint256be { + return C.struct_evmc_uint256be{*(*[32]C.uint8_t)(unsafe.Pointer(&hash[0]))} +} + +func AddressToEvmc(addr common.Address) C.struct_evmc_address { + return C.struct_evmc_address{*(*[20]C.uint8_t)(unsafe.Pointer(&addr[0]))} +} + +func BigToEvmc(i *big.Int) C.struct_evmc_uint256be { + return HashToEvmc(common.BigToHash(i)) +} + +func assert(cond bool, msg string) { + if !cond { + panic(fmt.Sprintf("Assertion failure! %v", msg)) + } +} + +func GoByteSlice(data unsafe.Pointer, size C.size_t) []byte { + var sliceHeader reflect.SliceHeader + sliceHeader.Data = uintptr(data) + sliceHeader.Len = int(size) + sliceHeader.Cap = int(size) + return *(*[]byte)(unsafe.Pointer(&sliceHeader)) +} + +func EvmcHashToSlice(uint256 *C.struct_evmc_uint256be) []byte { + return GoByteSlice(unsafe.Pointer(uint256), 32) +} + +//export account_exists +func account_exists(pCtx unsafe.Pointer, pAddr unsafe.Pointer) C.int { + // Get the execution context. + env := getEnv(pCtx) + + arg := GoByteSlice(pAddr, 20) + var addr common.Address + copy(addr[:], arg[:]) + eip158 := env.ChainConfig().IsEIP158(env.BlockNumber) + var exist C.int + if eip158 { + if !env.StateDB.Empty(addr) { + exist = 1 + } + } else if env.StateDB.Exist(addr) { + exist = 1 + } + // fmt.Printf("EXISTS? %x : %v\n", addr, exist) + return exist +} + +//export get_storage +func get_storage(pResult *C.struct_evmc_uint256be, pCtx unsafe.Pointer, pAddr unsafe.Pointer, pArg *C.struct_evmc_uint256be) { + result := EvmcHashToSlice(pResult) + env := getEnv(pCtx) + + var addr common.Address + copy(addr[:], GoByteSlice(pAddr, 20)) + + arg := *(*[32]byte)(unsafe.Pointer(pArg)) + val := env.StateDB.GetState(addr, arg) + copy(result, val[:]) + // fmt.Printf("SLOAD %x: %x\n", addr, val) +} + +//export set_storage +func set_storage(pCtx unsafe.Pointer, pAddr unsafe.Pointer, pArg1 unsafe.Pointer, pArg2 unsafe.Pointer) { + env := getEnv(pCtx) + + var addr common.Address + copy(addr[:], GoByteSlice(pAddr, 20)) + + key := *(*[32]byte)(pArg1) + newVal := *(*[32]byte)(pArg2) + oldVal := env.StateDB.GetState(addr, key) + env.StateDB.SetState(addr, key, newVal) + if oldVal != (common.Hash{}) && newVal == (common.Hash{}) { + env.StateDB.AddRefund(params.SstoreRefundGas) + } + // fmt.Printf("EVMJIT STORE %x: %x := %x\n", addr, key, newVal) +} + +//export get_balance +func get_balance(pResult unsafe.Pointer, pCtx unsafe.Pointer, pAddr unsafe.Pointer) { + result := GoByteSlice(pResult, 32) + env := getEnv(pCtx) + + var addr common.Address + copy(addr[:], GoByteSlice(pAddr, 20)) + balance := env.StateDB.GetBalance(addr) + val := common.BigToHash(balance) + copy(result, val[:]) + // fmt.Printf("BALANCE %x : %v\n", addr, balance) +} + +//export get_code_size +func get_code_size(pCtx unsafe.Pointer, pAddr unsafe.Pointer) C.size_t { + env := getEnv(pCtx) + + var addr common.Address + copy(addr[:], GoByteSlice(pAddr, 20)) + return C.size_t(env.StateDB.GetCodeSize(addr)); +} + +//export copy_code +func copy_code(pCtx unsafe.Pointer, pAddr unsafe.Pointer, offset C.size_t, p *C.uint8_t, size C.size_t) C.size_t { + env := getEnv(pCtx) + + var addr common.Address + copy(addr[:], GoByteSlice(pAddr, 20)) + code := env.StateDB.GetCode(addr) + length := C.size_t(len(code)) + + if offset >= length { + return 0 + } + + toCopy := length - offset; + if toCopy > size { + toCopy = size + } + + out := GoByteSlice(unsafe.Pointer(p), size) + + copy(out, code[offset:]) + return toCopy +} + +//export selfdestruct +func selfdestruct(pCtx unsafe.Pointer, pAddr unsafe.Pointer, pArg unsafe.Pointer) { + env := getEnv(pCtx) + + var addr common.Address + copy(addr[:], GoByteSlice(pAddr, 20)) + + var beneficiary common.Address + copy(beneficiary[:], GoByteSlice(pArg, 20)) + + db := env.StateDB + if !db.HasSuicided(addr) { + db.AddRefund(params.SuicideRefundGas) + } + balance := db.GetBalance(addr) + db.AddBalance(beneficiary, balance) + db.Suicide(addr) +} + +//export getTxCtx +func getTxCtx(pResult unsafe.Pointer, pCtx unsafe.Pointer) { + env := getEnv(pCtx) + txCtx := (*C.struct_evmc_tx_context)(pResult) + txCtx.tx_gas_price = BigToEvmc(env.GasPrice) + txCtx.tx_origin = AddressToEvmc(env.Origin) + txCtx.block_coinbase = AddressToEvmc(env.Coinbase) + txCtx.block_number = C.int64_t(env.BlockNumber.Int64()) + txCtx.block_timestamp = C.int64_t(env.Time.Int64()) + txCtx.block_gas_limit = C.int64_t(env.GasLimit) + txCtx.block_difficulty = BigToEvmc(env.Difficulty) +} + +//export getBlockHash +func getBlockHash(pResult unsafe.Pointer, pCtx unsafe.Pointer, number int64) { + // Represent the result memory as Go slice of 32 bytes. + result := GoByteSlice(pResult, 32) + env := getEnv(pCtx) + b := env.BlockNumber.Int64() + a := b - 256 + var hash common.Hash + if number >= a && number < b { + hash = env.GetHash(uint64(number)) + } + copy(result, hash[:]) +} + +//export emit_log +func emit_log(pCtx unsafe.Pointer, pAddr unsafe.Pointer, pData unsafe.Pointer, dataSize C.size_t, pTopics unsafe.Pointer, topicsCount C.size_t) { + env := getEnv(pCtx) + + var addr common.Address + copy(addr[:], GoByteSlice(pAddr, 20)) + + data := C.GoBytes(pData, C.int(dataSize)) + tData := C.GoBytes(pTopics, C.int(topicsCount * 32)) + + nTopics := int(topicsCount) + topics := make([]common.Hash, nTopics) + for i := 0; i < nTopics; i++ { + copy(topics[i][:], tData[i*32:(i+1)*32]) + } + env.StateDB.AddLog(&types.Log{ + Address: addr, + Topics: topics, + Data: data, + BlockNumber: env.BlockNumber.Uint64(), + }) +} + +//export call +func call(result *C.struct_evmc_result, pCtx unsafe.Pointer, msg *C.struct_evmc_message) { + env := getEnv(pCtx) + contract := getContract(pCtx) + + addr := *(*common.Address)(unsafe.Pointer(&msg.destination)) + value := (*(*common.Hash)(unsafe.Pointer(&msg.value))).Big() + input := GoByteSlice(unsafe.Pointer(msg.input_data), msg.input_size) + gas := uint64(msg.gas) + + var output []byte + var gasLeft uint64 + var err error + + switch msg.kind { + case C.EVMC_CALL: + staticCall := (msg.flags & C.EVMC_STATIC) != 0 + if staticCall { + // fmt.Printf("STATICCALL(gas %d, %x)\n", gas, addr) + output, gasLeft, err = env.StaticCall(contract, addr, input, gas) + } else { + // fmt.Printf("CALL(gas %d, %x)\n", gas, addr) + output, gasLeft, err = env.Call(contract, addr, input, gas, value) + } + + + case C.EVMC_CALLCODE: + // fmt.Printf("CALLCODE(gas %d, %x, value %d)\n", gas, addr, value) + output, gasLeft, err = env.CallCode(contract, addr, input, gas, value) + + case C.EVMC_DELEGATECALL: + // fmt.Printf("DELEGATECALL(gas %d, %x)\n", gas, addr) + output, gasLeft, err = env.DelegateCall(contract, addr, input, gas) + + case C.EVMC_CREATE: + // fmt.Printf("CREATE(gas %d, %x)\n", gas, addr) + var createAddr common.Address + var createOutput []byte + createOutput, createAddr, gasLeft, err = env.Create(contract, input, gas, value) + isHomestead := env.ChainConfig().IsHomestead(env.BlockNumber) + if !isHomestead && err == ErrCodeStoreOutOfGas { + err = nil + } + if err == nil { + // Copy create address to result. + ca := GoByteSlice(unsafe.Pointer(&result.create_address.bytes), 20) + copy(ca, createAddr[:]) + } else 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 + } + } + + assert(gasLeft <= gas, fmt.Sprintf("%d <= %d", gasLeft, gas)) + //fmt.Printf("Gas left %d, err: %s, output: %x\n", gasLeft, err, output) + result.gas_left = C.int64_t(gasLeft) + + // Map error to status code. + if err == nil { + result.status_code = C.EVMC_SUCCESS + } else if err == errExecutionReverted { + result.status_code = C.EVMC_REVERT + } else { + result.status_code = C.EVMC_FAILURE + } + + if len(output) > 0 { + cOutput := C.CBytes(output) + result.output_data = (*C.uint8_t)(cOutput) + result.output_size = C.size_t(len(output)) + // We can use result.release = C.release_result_output, but there are + // some problems with linking. Probably definition of + // release_result_output() must be in different C file. + C.add_result_releaser(result) + } else { + result.output_data = nil + result.output_size = 0 + result.release = nil + } +} + +func ptr(bytes []byte) *C.uint8_t { + header := *(*reflect.SliceHeader)(unsafe.Pointer(&bytes)) + return (*C.uint8_t)(unsafe.Pointer(header.Data)) +} + +func getRevision(env *EVM) C.enum_evmc_revision { + n := env.BlockNumber + if env.ChainConfig().IsByzantium(n) { + return C.EVMC_BYZANTIUM + } + if env.ChainConfig().IsEIP158(n) { + return C.EVMC_SPURIOUS_DRAGON + } + if env.ChainConfig().IsEIP150(n) { + return C.EVMC_TANGERINE_WHISTLE + } + if env.ChainConfig().IsHomestead(n) { + return C.EVMC_HOMESTEAD + } + return C.EVMC_FRONTIER +} + +func (evm *EVMJIT) Run(contract *Contract, input []byte) (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 + } + + code := contract.Code + codePtr := (*C.uint8_t)(unsafe.Pointer(&code[0])) + codeSize := C.size_t(len(code)) + gas := C.int64_t(contract.Gas) + rev := getRevision(evm.env) + + // Create context for this execution. + wrapper := ContextWrapper{} + wrapper.c.fn_table = C.get_context_fn_table() + wrapper.index = pinCtx(&EVMCContext{contract, evm.env}) + + var msg C.struct_evmc_message + msg.destination = AddressToEvmc(contract.Address()) + msg.sender = AddressToEvmc(contract.Caller()) + msg.value = BigToEvmc(contract.value) + msg.gas = gas + msg.depth = C.int32_t(evm.env.depth - 1) + codeHash := crypto.Keccak256Hash(code) + msg.code_hash = HashToEvmc(codeHash) + if evm.readOnly { + msg.flags = C.EVMC_STATIC + } else { + msg.flags = 0 + } + + if len(input) > 0 { + cInput := C.CBytes(input) + msg.input_data = (*C.uint8_t)(cInput) + msg.input_size = C.size_t(len(input)) + defer C.free(cInput) + } else { + msg.input_data = nil + msg.input_size = 0 + } + + // fmt.Printf("EVMJIT pre Run (gas %d %d mode: %d, env: %d) %x %x\n", contract.Gas, gas, rev, wrapper.index, codeHash, contract.Address()) + + r := C.evmc_execute(evm.jit, &wrapper.c, rev, &msg, codePtr, codeSize) + + unpinCtx(wrapper.index) + + // fmt.Printf("EVMJIT Run [%d]: %d %d %x\n", evm.env.depth - 1, r.status_code, r.gas_left, contract.Address()) + if r.gas_left > gas { + panic(fmt.Sprintf("gas left: %d, gas: %d, status: %d", r.gas_left, gas, r.status_code)) + } + contract.Gas = uint64(r.gas_left) + // fmt.Printf("Gas left: %d\n", contract.Gas) + output := C.GoBytes(unsafe.Pointer(r.output_data), C.int(r.output_size)) + + if r.status_code == C.EVMC_REVERT { + err = errExecutionReverted + } else if r.status_code != C.EVMC_SUCCESS { + // EVMJIT does not informs about the kind of the EVM expection. + err = ErrOutOfGas + } + + if r.release != nil { + // fmt.Printf("Releasing result with %p\n", r.release) + C.evmc_release_result(&r) + } + + return output, err +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 7090e0261f..0cf31df366 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -39,6 +39,9 @@ type Config struct { // may be left uninitialised and will be set to the default // table. JumpTable [256]operation + + // Path to EVMC VM module + EVMCPath string } // Interpreter is used to run Ethereum based contracts and will utilise the diff --git a/eth/backend.go b/eth/backend.go index a18abdfb56..5055387287 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -143,7 +143,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) } var ( - vmConfig = vm.Config{EnablePreimageRecording: config.EnablePreimageRecording} + vmConfig = vm.Config{EnablePreimageRecording: config.EnablePreimageRecording, EVMCPath: config.EVMCPath} cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout} ) eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, eth.chainConfig, eth.engine, vmConfig) diff --git a/eth/config.go b/eth/config.go index 426d2bf1ef..383dbdda57 100644 --- a/eth/config.go +++ b/eth/config.go @@ -112,6 +112,9 @@ type Config struct { // Enables tracking of SHA3 preimages in the VM EnablePreimageRecording bool + // Path to EVMC VM module + EVMCPath string + // Miscellaneous options DocRoot string `toml:"-"` } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index a4d35044cd..452c8f1a0a 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -380,11 +380,12 @@ func (s *Service) login(conn *websocket.Conn) error { network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) protocol = fmt.Sprintf("les/%d", les.ClientProtocolVersions[0]) } + node := strings.Replace(infos.Name, "Geth", "Geth+EVMC", 1) auth := &authMsg{ ID: s.node, Info: nodeInfo{ Name: s.node, - Node: infos.Name, + Node: node, Port: infos.Ports.Listener, Network: network, Protocol: protocol,