Commit c5bd32b0 authored by obscuren's avatar obscuren

Refactored VM to two separate VMs; std & debug

Standard VM should be about 10x faster than the debug VM. Some error
checking has been removed, all of the log statements and therefor quite
some unnecessary if-statements.
parent 2e894b66
......@@ -224,7 +224,7 @@ func (self *StateTransition) TransitionState() (err error) {
// script section for the state object.
self.data = nil
code, err := self.Eval(msg, receiver.Init(), receiver, "init")
code, err := self.Eval(msg, receiver.Init(), receiver)
if err != nil {
self.state.Set(snapshot)
......@@ -235,7 +235,7 @@ func (self *StateTransition) TransitionState() (err error) {
msg.Output = code
} else {
if len(receiver.Code) > 0 {
ret, err := self.Eval(msg, receiver.Code, receiver, "code")
ret, err := self.Eval(msg, receiver.Code, receiver)
if err != nil {
self.state.Set(snapshot)
......@@ -262,7 +262,7 @@ func (self *StateTransition) transferValue(sender, receiver *ethstate.StateObjec
return nil
}
func (self *StateTransition) Eval(msg *ethstate.Message, script []byte, context *ethstate.StateObject, typ string) (ret []byte, err error) {
func (self *StateTransition) Eval(msg *ethstate.Message, script []byte, context *ethstate.StateObject) (ret []byte, err error) {
var (
transactor = self.Sender()
state = self.state
......@@ -270,9 +270,7 @@ func (self *StateTransition) Eval(msg *ethstate.Message, script []byte, context
callerClosure = ethvm.NewClosure(msg, transactor, context, script, self.gas, self.gasPrice)
)
vm := ethvm.New(env)
vm.Verbose = true
vm.Fn = typ
vm := ethvm.New(env, ethvm.Type(ethutil.Config.VmType))
ret, _, err = callerClosure.Call(vm, self.tx.Data)
......
......@@ -58,8 +58,7 @@ func (self *Pipe) ExecuteObject(object *Object, data []byte, value, gas, price *
self.Vm.State = self.World().State().Copy()
vm := ethvm.New(NewEnv(self.Vm.State, block, value.BigInt(), initiator.Address()))
vm.Verbose = true
vm := ethvm.New(NewEnv(self.Vm.State, block, value.BigInt(), initiator.Address()), ethvm.Type(ethutil.Config.VmType))
msg := ethvm.NewExecution(vm, object.Address(), data, gas.BigInt(), price.BigInt(), value.BigInt())
ret, err := msg.Exec(object.Address(), initiator)
......
......@@ -17,6 +17,7 @@ type ConfigManager struct {
Diff bool
DiffType string
Paranoia bool
VmType int
conf *globalconf.GlobalConf
}
......
......@@ -58,6 +58,26 @@ func (c *Closure) Get(x *big.Int) *ethutil.Value {
return c.Gets(x, big.NewInt(1))
}
func (c *Closure) GetOp(x int) OpCode {
return OpCode(c.GetByte(x))
}
func (c *Closure) GetByte(x int) byte {
if x < len(c.Code) {
return c.Code[x]
}
return 0
}
func (c *Closure) GetBytes(x, y int) []byte {
if x >= len(c.Code) || y >= len(c.Code) {
return nil
}
return c.Code[x : x+y]
}
func (c *Closure) Gets(x, y *big.Int) *ethutil.Value {
if x.Int64() >= int64(len(c.Code)) || y.Int64() >= int64(len(c.Code)) {
return ethutil.NewValue(0)
......@@ -76,7 +96,7 @@ func (c *Closure) Address() []byte {
return c.object.Address()
}
func (c *Closure) Call(vm *Vm, args []byte) ([]byte, *big.Int, error) {
func (c *Closure) Call(vm VirtualMachine, args []byte) ([]byte, *big.Int, error) {
c.Args = args
ret, err := vm.RunClosure(c)
......
package ethvm
import (
"math/big"
"github.com/ethereum/eth-go/ethlog"
"github.com/ethereum/eth-go/ethutil"
"math/big"
)
var vmlogger = ethlog.NewLogger("VM")
type Type int
const (
StandardVmTy Type = iota
DebugVmTy
MaxVmTy
)
var (
GasStep = big.NewInt(1)
GasSha = big.NewInt(20)
......@@ -24,4 +34,32 @@ var (
LogTyPretty byte = 0x1
LogTyDiff byte = 0x2
To256 = ethutil.To256
)
const MaxCallDepth = 1024
func calcMemSize(off, l *big.Int) *big.Int {
if l.Cmp(ethutil.Big0) == 0 {
return ethutil.Big0
}
return new(big.Int).Add(off, l)
}
// Simple helper
func u256(n int64) *big.Int {
return big.NewInt(n)
}
// Mainly used for print variables and passing to Print*
func toValue(val *big.Int) interface{} {
// Let's assume a string on right padded zero's
b := val.Bytes()
if b[0] != 0 && b[len(b)-1] == 0x0 && b[len(b)-2] == 0x0 {
return string(b)
}
return val
}
package ethvm
import "github.com/ethereum/eth-go/ethstate"
type Debugger interface {
BreakHook(step int, op OpCode, mem *Memory, stack *Stack, object *ethstate.StateObject) bool
StepHook(step int, op OpCode, mem *Memory, stack *Stack, object *ethstate.StateObject) bool
BreakPoints() []int64
SetCode(byteCode []byte)
}
package ethvm
import (
"math/big"
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
)
type Environment interface {
State() *ethstate.State
Origin() []byte
BlockNumber() *big.Int
PrevHash() []byte
Coinbase() []byte
Time() int64
Difficulty() *big.Int
Value() *big.Int
BlockHash() []byte
}
type Object interface {
GetStorage(key *big.Int) *ethutil.Value
SetStorage(key *big.Int, value *ethutil.Value)
}
package ethvm
import (
"fmt"
"math/big"
"github.com/ethereum/eth-go/ethstate"
"github.com/ethereum/eth-go/ethutil"
)
type Execution struct {
vm VirtualMachine
closure *Closure
address, input []byte
gas, price, value *big.Int
object *ethstate.StateObject
}
func NewExecution(vm VirtualMachine, address, input []byte, gas, gasPrice, value *big.Int) *Execution {
return &Execution{vm: vm, address: address, input: input, gas: gas, price: gasPrice, value: value}
}
func (self *Execution) Addr() []byte {
return self.address
}
func (self *Execution) Exec(codeAddr []byte, caller ClosureRef) (ret []byte, err error) {
env := self.vm.Env()
snapshot := env.State().Copy()
msg := env.State().Manifest().AddMessage(&ethstate.Message{
To: self.address, From: caller.Address(),
Input: self.input,
Origin: env.Origin(),
Block: env.BlockHash(), Timestamp: env.Time(), Coinbase: env.Coinbase(), Number: env.BlockNumber(),
Value: self.value,
})
object := caller.Object()
if object.Balance.Cmp(self.value) < 0 {
caller.ReturnGas(self.gas, self.price)
err = fmt.Errorf("Insufficient funds to transfer value. Req %v, has %v", self.value, object.Balance)
} else {
stateObject := env.State().GetOrNewStateObject(self.address)
self.object = stateObject
caller.Object().SubAmount(self.value)
stateObject.AddAmount(self.value)
// Precompiled contracts (address.go) 1, 2 & 3.
if p := Precompiled[ethutil.BigD(codeAddr).Uint64()]; p != nil {
if self.gas.Cmp(p.Gas) >= 0 {
ret = p.Call(self.input)
}
} else {
if self.vm.Depth() == MaxCallDepth {
return nil, fmt.Errorf("Max call depth exceeded (%d)", MaxCallDepth)
}
// Retrieve the executing code
code := env.State().GetCode(codeAddr)
// Create a new callable closure
c := NewClosure(msg, caller, stateObject, code, self.gas, self.price)
// Executer the closure and get the return value (if any)
ret, _, err = c.Call(self.vm, self.input)
msg.Output = ret
}
}
if err != nil {
env.State().Set(snapshot)
}
return
}
......@@ -4,7 +4,7 @@ import (
"fmt"
)
type OpCode int
type OpCode byte
// Op codes
const (
......
package ethvm
type VirtualMachine interface {
Env() Environment
RunClosure(*Closure) ([]byte, error)
Depth() int
}
This diff is collapsed.
This diff is collapsed.
......@@ -2,9 +2,9 @@ package ethvm
import (
"fmt"
"io/ioutil"
"log"
"math/big"
"os"
"testing"
"github.com/ethereum/eth-go/ethlog"
......@@ -17,6 +17,7 @@ type TestEnv struct {
func (self TestEnv) Origin() []byte { return nil }
func (self TestEnv) BlockNumber() *big.Int { return nil }
func (self TestEnv) BlockHash() []byte { return nil }
func (self TestEnv) PrevHash() []byte { return nil }
func (self TestEnv) Coinbase() []byte { return nil }
func (self TestEnv) Time() int64 { return 0 }
......@@ -24,20 +25,65 @@ func (self TestEnv) Difficulty() *big.Int { return nil }
func (self TestEnv) Value() *big.Int { return nil }
func (self TestEnv) State() *ethstate.State { return nil }
func TestVm(t *testing.T) {
ethlog.AddLogSystem(ethlog.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlog.LogLevel(4)))
func setup(level int, typ Type) (*Closure, VirtualMachine) {
// Pipe output to /dev/null
ethlog.AddLogSystem(ethlog.NewStdLogSystem(ioutil.Discard, log.LstdFlags, ethlog.LogLevel(level)))
ethutil.ReadConfig(".ethtest", "/tmp/ethtest", "")
stateObject := ethstate.NewStateObject([]byte{'j', 'e', 'f', 'f'})
callerClosure := NewClosure(stateObject, stateObject, []byte{0x60, 0x01}, big.NewInt(1000000), big.NewInt(0))
callerClosure := NewClosure(nil, stateObject, stateObject, []byte{
PUSH1, 1,
PUSH1, 0,
MSTORE,
PUSH1, 32,
PUSH1, 0,
RETURN,
}, big.NewInt(1000000), big.NewInt(0))
vm := New(TestEnv{})
vm.Verbose = true
return callerClosure, New(TestEnv{}, typ)
}
ret, _, e := callerClosure.Call(vm, nil)
func TestDebugVm(t *testing.T) {
closure, vm := setup(4, DebugVmTy)
ret, _, e := closure.Call(vm, nil)
if e != nil {
fmt.Println("error", e)
}
fmt.Println(ret)
if ret[len(ret)-1] != 1 {
t.Errorf("Expected VM to return 1, got", ret, "instead.")
}
}
func TestVm(t *testing.T) {
closure, vm := setup(4, StandardVmTy)
ret, _, e := closure.Call(vm, nil)
if e != nil {
fmt.Println("error", e)
}
if ret[len(ret)-1] != 1 {
t.Errorf("Expected VM to return 1, got", ret, "instead.")
}
}
func BenchmarkDebugVm(b *testing.B) {
closure, vm := setup(3, DebugVmTy)
b.ResetTimer()
for i := 0; i < b.N; i++ {
closure.Call(vm, nil)
}
}
func BenchmarkVm(b *testing.B) {
closure, vm := setup(3, StandardVmTy)
b.ResetTimer()
for i := 0; i < b.N; i++ {
closure.Call(vm, nil)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment