core: Added EVM configuration options

The EVM is now initialised with an additional configured object that
allows you to turn on debugging options.
This commit is contained in:
Jeffrey Wilcke
2016-02-03 23:46:27 +01:00
committed by Jeffrey Wilcke
parent 342ae7ce7d
commit 14013372ae
30 changed files with 408 additions and 230 deletions

View File

@ -28,24 +28,54 @@ import (
"github.com/ethereum/go-ethereum/params"
)
// Vm is an EVM and implements VirtualMachine
type Vm struct {
env Environment
jumpTable vmJumpTable
// Config are the configuration options for the EVM
type Config struct {
Debug bool
EnableJit bool
ForceJit bool
Logger LogConfig
}
func EVM(env Environment) *Vm {
return &Vm{env: env, jumpTable: newJumpTable(env.BlockNumber())}
// EVM is used to run Ethereum based contracts and will utilise the
// passed environment to query external sources for state information.
// The EVM will run the byte code VM or JIT VM based on the passed
// configuration.
type EVM struct {
env Environment
jumpTable vmJumpTable
cfg *Config
logger *Logger
}
// New returns a new instance of the EVM.
func New(env Environment, cfg *Config) *EVM {
// initialise a default config if none is present
if cfg == nil {
cfg = new(Config)
}
var logger *Logger
if cfg.Debug {
logger = newLogger(cfg.Logger, env)
}
return &EVM{
env: env,
jumpTable: newJumpTable(env.BlockNumber()),
cfg: cfg,
logger: logger,
}
}
// Run loops and evaluates the contract's code with the given input data
func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
self.env.SetDepth(self.env.Depth() + 1)
defer self.env.SetDepth(self.env.Depth() - 1)
func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) {
evm.env.SetDepth(evm.env.Depth() + 1)
defer evm.env.SetDepth(evm.env.Depth() - 1)
if contract.CodeAddr != nil {
if p := Precompiled[contract.CodeAddr.Str()]; p != nil {
return self.RunPrecompiled(p, input, contract)
return evm.RunPrecompiled(p, input, contract)
}
}
@ -58,21 +88,21 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
codehash = crypto.Keccak256Hash(contract.Code) // codehash is used when doing jump dest caching
program *Program
)
if EnableJit {
if evm.cfg.EnableJit {
// If the JIT is enabled check the status of the JIT program,
// if it doesn't exist compile a new program in a separate
// goroutine or wait for compilation to finish if the JIT is
// forced.
switch GetProgramStatus(codehash) {
case progReady:
return RunProgram(GetProgram(codehash), self.env, contract, input)
return RunProgram(GetProgram(codehash), evm.env, contract, input)
case progUnknown:
if ForceJit {
if evm.cfg.ForceJit {
// Create and compile program
program = NewProgram(contract.Code)
perr := CompileProgram(program)
if perr == nil {
return RunProgram(program, self.env, contract, input)
return RunProgram(program, evm.env, contract, input)
}
glog.V(logger.Info).Infoln("error compiling program", err)
} else {
@ -95,10 +125,10 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
code = contract.Code
instrCount = 0
op OpCode // current opcode
mem = NewMemory() // bound memory
stack = newstack() // local stack
statedb = self.env.Db() // current state
op OpCode // current opcode
mem = NewMemory() // bound memory
stack = newstack() // local stack
statedb = evm.env.Db() // current state
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter
@ -123,8 +153,8 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
// User defer pattern to check for an error and, based on the error being nil or not, use all gas and return.
defer func() {
if err != nil {
self.log(pc, op, contract.Gas, cost, mem, stack, contract, err)
if err != nil && evm.cfg.Debug {
evm.logger.captureState(pc, op, contract.Gas, cost, mem, stack, contract, err)
}
}()
@ -143,7 +173,7 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
// move execution
fmt.Println("moved", it)
glog.V(logger.Info).Infoln("Moved execution to JIT")
return runProgram(program, pc, mem, stack, self.env, contract, input)
return runProgram(program, pc, mem, stack, evm.env, contract, input)
}
}
*/
@ -151,7 +181,7 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
// Get the memory location of pc
op = contract.GetOp(pc)
// calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err = calculateGasAndSize(self.env, contract, caller, op, statedb, mem, stack)
newMemSize, cost, err = calculateGasAndSize(evm.env, contract, caller, op, statedb, mem, stack)
if err != nil {
return nil, err
}
@ -165,14 +195,17 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
// Resize the memory calculated previously
mem.Resize(newMemSize.Uint64())
// Add a log message
self.log(pc, op, contract.Gas, cost, mem, stack, contract, nil)
if opPtr := self.jumpTable[op]; opPtr.valid {
if evm.cfg.Debug {
evm.logger.captureState(pc, op, contract.Gas, cost, mem, stack, contract, nil)
}
if opPtr := evm.jumpTable[op]; opPtr.valid {
if opPtr.fn != nil {
opPtr.fn(instruction{}, &pc, self.env, contract, mem, stack)
opPtr.fn(instruction{}, &pc, evm.env, contract, mem, stack)
} else {
switch op {
case PC:
opPc(instruction{data: new(big.Int).SetUint64(pc)}, &pc, self.env, contract, mem, stack)
opPc(instruction{data: new(big.Int).SetUint64(pc)}, &pc, evm.env, contract, mem, stack)
case JUMP:
if err := jump(pc, stack.pop()); err != nil {
return nil, err
@ -195,7 +228,7 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) {
return ret, nil
case SUICIDE:
opSuicide(instruction{}, nil, self.env, contract, mem, stack)
opSuicide(instruction{}, nil, evm.env, contract, mem, stack)
fallthrough
case STOP: // Stop the contract
@ -347,7 +380,7 @@ func calculateGasAndSize(env Environment, contract *Contract, caller ContractRef
}
// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go
func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) {
func (evm *EVM) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) {
gas := p.Gas(len(input))
if contract.UseGas(gas) {
ret = p.Call(input)
@ -357,27 +390,3 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Co
return nil, OutOfGasError
}
}
// log emits a log event to the environment for each opcode encountered. This is not to be confused with the
// LOG* opcode.
func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, contract *Contract, err error) {
if Debug || GenerateStructLogs {
mem := make([]byte, len(memory.Data()))
copy(mem, memory.Data())
stck := make([]*big.Int, len(stack.Data()))
for i, item := range stack.Data() {
stck[i] = new(big.Int).Set(item)
}
storage := make(map[common.Hash][]byte)
contract.self.EachStorage(func(k, v []byte) {
storage[common.BytesToHash(k)] = v
})
self.env.AddStructLog(StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, err})
}
}
// Environment returns the current workable state of the VM
func (self *Vm) Env() Environment {
return self.env
}