From 342ae7ce7dfd7a0eab2dd06bfa65199825279f05 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Thu, 21 Jan 2016 15:29:58 +0100 Subject: [PATCH 1/5] core, core/vm, tests: changed the initialisation behaviour of the EVM The EVM was previously initialised and created for every CALL, CALLCODE, DELEGATECALL and CREATE. This PR changes this behaviour so that the same EVM can be used through the session and beyond as long as the Environment sticks around. --- cmd/evm/main.go | 9 ++++++++- core/execution.go | 4 ++-- core/vm/common.go | 14 -------------- core/vm/environment.go | 4 +--- core/vm/instructions.go | 1 - core/vm/jit_test.go | 8 ++++++-- core/vm/jump_table.go | 12 +++++------- core/vm/jump_table_test.go | 4 ++-- core/vm/runtime/env.go | 8 +++++++- core/vm/vm.go | 13 +++++-------- core/vm/vm_jit_fake.go | 2 +- core/vm_env.go | 6 +++++- tests/util.go | 8 +++++++- 13 files changed, 49 insertions(+), 44 deletions(-) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index ef679e373f..0ba6820e0c 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/params" ) var ( @@ -174,17 +175,23 @@ type VMEnv struct { Gas *big.Int time *big.Int logs []vm.StructLog + + evm *vm.Vm } func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int) *VMEnv { - return &VMEnv{ + params.HomesteadBlock = new(big.Int) + env := &VMEnv{ state: state, transactor: &transactor, value: value, time: big.NewInt(time.Now().Unix()), } + env.evm = vm.EVM(env) + return env } +func (self *VMEnv) Vm() *vm.Vm { return self.evm } func (self *VMEnv) Db() vm.Database { return self.state } func (self *VMEnv) MakeSnapshot() vm.Database { return self.state.Copy() } func (self *VMEnv) SetSnapshot(db vm.Database) { self.state.Set(db.(*state.StateDB)) } diff --git a/core/execution.go b/core/execution.go index 24c0c93ae9..d90dceafc8 100644 --- a/core/execution.go +++ b/core/execution.go @@ -60,7 +60,7 @@ func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPric } func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.Address, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { - evm := vm.NewVm(env) + evm := env.Vm() // Depth check execution. Fail if we're trying to execute above the // limit. if env.Depth() > int(params.CallCreateDepth.Int64()) { @@ -136,7 +136,7 @@ func exec(env vm.Environment, caller vm.ContractRef, address, codeAddr *common.A } func execDelegateCall(env vm.Environment, caller vm.ContractRef, originAddr, toAddr, codeAddr *common.Address, input, code []byte, gas, gasPrice, value *big.Int) (ret []byte, addr common.Address, err error) { - evm := vm.NewVm(env) + evm := env.Vm() // Depth check execution. Fail if we're trying to execute above the // limit. if env.Depth() > int(params.CallCreateDepth.Int64()) { diff --git a/core/vm/common.go b/core/vm/common.go index 395ed04710..f73bc15270 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -21,7 +21,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/params" ) @@ -51,19 +50,6 @@ var ( max = big.NewInt(math.MaxInt64) // Maximum 64 bit integer ) -// NewVm returns a new VM based on the Environment -func NewVm(env Environment) VirtualMachine { - switch env.VmType() { - case JitVmTy: - return NewJitVm(env) - default: - glog.V(0).Infoln("unsupported vm type %d", env.VmType()) - fallthrough - case StdVmTy: - return New(env) - } -} - // calculates the memory size required for a step func calcMemSize(off, l *big.Int) *big.Int { if l.Cmp(common.Big0) == 0 { diff --git a/core/vm/environment.go b/core/vm/environment.go index d5d21a45b8..3c530962b8 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -58,10 +58,8 @@ type Environment interface { AddStructLog(StructLog) // Returns all coalesced structured logs StructLogs() []StructLog - // Type of the VM - VmType() Type - + Vm() *Vm // Current calling depth Depth() int SetDepth(i int) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 1e1086b133..c4b4339a20 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -597,7 +597,6 @@ func opDelegateCall(instr instruction, pc *uint64, env Environment, contract *Co toAddr := common.BigToAddress(to) args := memory.Get(inOffset.Int64(), inSize.Int64()) ret, err := env.DelegateCall(contract, toAddr, args, gas, contract.Price) - if err != nil { stack.push(new(big.Int)) } else { diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index 19261827b5..5fac0156f0 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -154,7 +154,7 @@ func runVmBench(test vmBench, b *testing.B) { context := NewContract(sender, sender, big.NewInt(100), big.NewInt(10000), big.NewInt(0)) context.Code = test.code context.CodeAddr = &common.Address{} - _, err := New(env).Run(context, test.input) + _, err := env.Vm().Run(context, test.input) if err != nil { b.Error(err) b.FailNow() @@ -165,12 +165,16 @@ func runVmBench(test vmBench, b *testing.B) { type Env struct { gasLimit *big.Int depth int + evm *Vm } func NewEnv() *Env { - return &Env{big.NewInt(10000), 0} + env := &Env{gasLimit: big.NewInt(10000), depth: 0} + env.evm = EVM(env) + return env } +func (self *Env) Vm() *Vm { return self.evm } func (self *Env) Origin() common.Address { return common.Address{} } func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) } func (self *Env) AddStructLog(log StructLog) { diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 37d7bb160f..8297d3e1d1 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -13,19 +13,15 @@ type jumpPtr struct { type vmJumpTable [256]jumpPtr -func (jt vmJumpTable) init(blockNumber *big.Int) { +func newJumpTable(blockNumber *big.Int) vmJumpTable { + var jumpTable vmJumpTable + // when initialising a new VM execution we must first check the homestead // changes. if params.IsHomestead(blockNumber) { jumpTable[DELEGATECALL] = jumpPtr{opDelegateCall, true} - } else { - jumpTable[DELEGATECALL] = jumpPtr{nil, false} } -} -var jumpTable vmJumpTable - -func init() { jumpTable[ADD] = jumpPtr{opAdd, true} jumpTable[SUB] = jumpPtr{opSub, true} jumpTable[MUL] = jumpPtr{opMul, true} @@ -156,4 +152,6 @@ func init() { jumpTable[JUMP] = jumpPtr{nil, true} jumpTable[JUMPI] = jumpPtr{nil, true} jumpTable[STOP] = jumpPtr{nil, true} + + return jumpTable } diff --git a/core/vm/jump_table_test.go b/core/vm/jump_table_test.go index 98d34bef20..2ed1b26fc7 100644 --- a/core/vm/jump_table_test.go +++ b/core/vm/jump_table_test.go @@ -10,13 +10,13 @@ import ( func TestInit(t *testing.T) { params.HomesteadBlock = big.NewInt(1) - jumpTable.init(big.NewInt(0)) + jumpTable := newJumpTable(big.NewInt(0)) if jumpTable[DELEGATECALL].valid { t.Error("Expected DELEGATECALL not to be present") } for _, n := range []int64{1, 2, 100} { - jumpTable.init(big.NewInt(n)) + jumpTable := newJumpTable(big.NewInt(n)) if !jumpTable[DELEGATECALL].valid { t.Error("Expected DELEGATECALL to be present for block", n) } diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 77519df816..e9bf828ea8 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -41,11 +41,13 @@ type Env struct { logs []vm.StructLog getHashFn func(uint64) common.Hash + + evm *vm.Vm } // NewEnv returns a new vm.Environment func NewEnv(cfg *Config, state *state.StateDB) vm.Environment { - return &Env{ + env := &Env{ state: state, origin: cfg.Origin, coinbase: cfg.Coinbase, @@ -54,6 +56,9 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment { difficulty: cfg.Difficulty, gasLimit: cfg.GasLimit, } + env.evm = vm.EVM(env) + + return env } func (self *Env) StructLogs() []vm.StructLog { @@ -64,6 +69,7 @@ func (self *Env) AddStructLog(log vm.StructLog) { self.logs = append(self.logs, log) } +func (self *Env) Vm() *vm.Vm { return self.evm } func (self *Env) Origin() common.Address { return self.origin } func (self *Env) BlockNumber() *big.Int { return self.number } func (self *Env) Coinbase() common.Address { return self.coinbase } diff --git a/core/vm/vm.go b/core/vm/vm.go index 95d27c64cb..26df8aef47 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -30,15 +30,12 @@ import ( // Vm is an EVM and implements VirtualMachine type Vm struct { - env Environment + env Environment + jumpTable vmJumpTable } -// New returns a new Vm -func New(env Environment) *Vm { - // init the jump table. Also prepares the homestead changes - jumpTable.init(env.BlockNumber()) - - return &Vm{env: env} +func EVM(env Environment) *Vm { + return &Vm{env: env, jumpTable: newJumpTable(env.BlockNumber())} } // Run loops and evaluates the contract's code with the given input data @@ -169,7 +166,7 @@ func (self *Vm) Run(contract *Contract, input []byte) (ret []byte, err error) { mem.Resize(newMemSize.Uint64()) // Add a log message self.log(pc, op, contract.Gas, cost, mem, stack, contract, nil) - if opPtr := jumpTable[op]; opPtr.valid { + if opPtr := self.jumpTable[op]; opPtr.valid { if opPtr.fn != nil { opPtr.fn(instruction{}, &pc, self.env, contract, mem, stack) } else { diff --git a/core/vm/vm_jit_fake.go b/core/vm/vm_jit_fake.go index 456fcb8d4b..192f3615d7 100644 --- a/core/vm/vm_jit_fake.go +++ b/core/vm/vm_jit_fake.go @@ -22,5 +22,5 @@ import "fmt" func NewJitVm(env Environment) VirtualMachine { fmt.Printf("Warning! EVM JIT not enabled.\n") - return New(env) + return EVM(env) } diff --git a/core/vm_env.go b/core/vm_env.go index 7b9a1a0f9a..0fab4a090d 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -51,10 +51,11 @@ type VMEnv struct { getHashFn func(uint64) common.Hash // structured logging logs []vm.StructLog + evm *vm.Vm } func NewEnv(state *state.StateDB, chain *BlockChain, msg Message, header *types.Header) *VMEnv { - return &VMEnv{ + env := &VMEnv{ chain: chain, state: state, header: header, @@ -62,8 +63,11 @@ func NewEnv(state *state.StateDB, chain *BlockChain, msg Message, header *types. typ: vm.StdVmTy, getHashFn: GetHashFn(header.ParentHash, chain), } + env.evm = vm.EVM(env) + return env } +func (self *VMEnv) Vm() *vm.Vm { return self.evm } func (self *VMEnv) Origin() common.Address { f, _ := self.msg.From(); return f } func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number } func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase } diff --git a/tests/util.go b/tests/util.go index 29f4c9b723..2c749edba9 100644 --- a/tests/util.go +++ b/tests/util.go @@ -143,12 +143,15 @@ type Env struct { logs []vm.StructLog vmTest bool + + evm *vm.Vm } func NewEnv(state *state.StateDB) *Env { - return &Env{ + env := &Env{ state: state, } + return env } func (self *Env) StructLogs() []vm.StructLog { @@ -171,9 +174,12 @@ func NewEnvFromMap(state *state.StateDB, envValues map[string]string, exeValues env.gasLimit = common.Big(envValues["currentGasLimit"]) env.Gas = new(big.Int) + env.evm = vm.EVM(env) + return env } +func (self *Env) Vm() *vm.Vm { return self.evm } func (self *Env) Origin() common.Address { return self.origin } func (self *Env) BlockNumber() *big.Int { return self.number } func (self *Env) Coinbase() common.Address { return self.coinbase } From 14013372aeca2d7f1d8c3a87b7df7c27868314be Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 3 Feb 2016 23:46:27 +0100 Subject: [PATCH 2/5] core: Added EVM configuration options The EVM is now initialised with an additional configured object that allows you to turn on debugging options. --- cmd/ethtest/main.go | 2 - cmd/evm/main.go | 15 ++-- cmd/geth/main.go | 1 - cmd/geth/usage.go | 1 - cmd/utils/flags.go | 11 --- common/registrar/ethreg/api.go | 2 +- core/blockchain.go | 2 +- core/blockchain_test.go | 4 +- core/chain_makers.go | 2 +- core/state/state_object.go | 33 ++++---- core/state_processor.go | 8 +- core/state_transition.go | 6 +- core/types.go | 2 +- core/vm/common.go | 5 -- core/vm/contract.go | 6 +- core/vm/environment.go | 35 +++------ core/vm/jit_test.go | 28 +++---- core/vm/logger.go | 133 +++++++++++++++++++++++++++++++- core/vm/logger_test.go | 104 +++++++++++++++++++++++++ core/vm/runtime/env.go | 14 +++- core/vm/runtime/runtime.go | 29 ------- core/vm/runtime/runtime_test.go | 15 ---- core/vm/virtual_machine.go | 1 - core/vm/vm.go | 109 ++++++++++++++------------ core/vm/vm_jit_fake.go | 2 +- core/vm_env.go | 41 ++++++---- eth/api.go | 11 +-- miner/worker.go | 2 +- tests/state_test_util.go | 1 - tests/util.go | 11 ++- 30 files changed, 407 insertions(+), 229 deletions(-) create mode 100644 core/vm/logger_test.go diff --git a/cmd/ethtest/main.go b/cmd/ethtest/main.go index 67b9653960..e19dca86b4 100644 --- a/cmd/ethtest/main.go +++ b/cmd/ethtest/main.go @@ -26,7 +26,6 @@ import ( "strings" "github.com/codegangsta/cli" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/tests" ) @@ -188,7 +187,6 @@ func setupApp(c *cli.Context) { continueOnError = c.GlobalBool(ContinueOnErrorFlag.Name) useStdIn := c.GlobalBool(ReadStdInFlag.Name) skipTests = strings.Split(c.GlobalString(SkipTestsFlag.Name), " ") - vm.Debug = c.GlobalBool(TraceFlag.Name) if !useStdIn { runSuite(flagTest, flagFile) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 0ba6820e0c..2cc70d81be 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -106,7 +106,6 @@ func init() { } func run(ctx *cli.Context) { - vm.Debug = ctx.GlobalBool(DebugFlag.Name) vm.ForceJit = ctx.GlobalBool(ForceJitFlag.Name) vm.EnableJit = !ctx.GlobalBool(DisableJitFlag.Name) @@ -119,7 +118,9 @@ func run(ctx *cli.Context) { receiver := statedb.CreateAccount(common.StringToAddress("receiver")) receiver.SetCode(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name))) - vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name))) + vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), &vm.Config{ + Debug: ctx.GlobalBool(DebugFlag.Name), + }) tstart := time.Now() ret, e := vmenv.Call( @@ -176,10 +177,10 @@ type VMEnv struct { time *big.Int logs []vm.StructLog - evm *vm.Vm + evm *vm.EVM } -func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int) *VMEnv { +func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int, cfg *vm.Config) *VMEnv { params.HomesteadBlock = new(big.Int) env := &VMEnv{ state: state, @@ -187,11 +188,13 @@ func NewEnv(state *state.StateDB, transactor common.Address, value *big.Int) *VM value: value, time: big.NewInt(time.Now().Unix()), } - env.evm = vm.EVM(env) + cfg.Logger.Collector = env + + env.evm = vm.New(env, cfg) return env } -func (self *VMEnv) Vm() *vm.Vm { return self.evm } +func (self *VMEnv) Vm() vm.Vm { return self.evm } func (self *VMEnv) Db() vm.Database { return self.state } func (self *VMEnv) MakeSnapshot() vm.Database { return self.state.Copy() } func (self *VMEnv) SetSnapshot(db vm.Database) { self.state.Set(db.(*state.StateDB)) } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 68e09912be..a21fe71b54 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -321,7 +321,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.WhisperEnabledFlag, utils.DevModeFlag, utils.TestNetFlag, - utils.VMDebugFlag, utils.VMForceJitFlag, utils.VMJitCacheFlag, utils.VMEnableJitFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index d2f76eaa68..55daa63d76 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -142,7 +142,6 @@ var AppHelpFlagGroups = []flagGroup{ { Name: "VIRTUAL MACHINE", Flags: []cli.Flag{ - utils.VMDebugFlag, utils.VMEnableJitFlag, utils.VMForceJitFlag, utils.VMJitCacheFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 69fb0b9db9..3b05c2963a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -203,11 +203,6 @@ var ( Value: "", } - // vm flags - VMDebugFlag = cli.BoolFlag{ - Name: "vmdebug", - Usage: "Virtual Machine debug output", - } VMForceJitFlag = cli.BoolFlag{ Name: "forcejit", Usage: "Force the JIT VM to take precedence", @@ -728,9 +723,6 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. if !ctx.GlobalIsSet(WhisperEnabledFlag.Name) { shhEnable = true } - if !ctx.GlobalIsSet(VMDebugFlag.Name) { - vm.Debug = true - } ethConf.PowTest = true } // Assemble and return the protocol stack @@ -771,9 +763,6 @@ func SetupVM(ctx *cli.Context) { vm.EnableJit = ctx.GlobalBool(VMEnableJitFlag.Name) vm.ForceJit = ctx.GlobalBool(VMForceJitFlag.Name) vm.SetJITCacheSize(ctx.GlobalInt(VMJitCacheFlag.Name)) - if ctx.GlobalIsSet(VMDebugFlag.Name) { - vm.Debug = ctx.GlobalBool(VMDebugFlag.Name) - } } // MakeChain creates a chain manager from set command line flags. diff --git a/common/registrar/ethreg/api.go b/common/registrar/ethreg/api.go index 79a6c2191e..60a97f4ce3 100644 --- a/common/registrar/ethreg/api.go +++ b/common/registrar/ethreg/api.go @@ -179,7 +179,7 @@ func (be *registryAPIBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr } header := be.bc.CurrentBlock().Header() - vmenv := core.NewEnv(statedb, be.bc, msg, header) + vmenv := core.NewEnv(statedb, be.bc, msg, header, nil) gp := new(core.GasPool).AddGas(common.MaxBig) res, gas, err := core.ApplyMessage(vmenv, msg, gp) diff --git a/core/blockchain.go b/core/blockchain.go index 534318ecdd..cecb914a8b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -891,7 +891,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { return i, err } // Process block using the parent state as reference point. - receipts, logs, usedGas, err := self.processor.Process(block, statedb) + receipts, logs, usedGas, err := self.processor.Process(block, statedb, nil) if err != nil { reportBlock(block, err) return i, err diff --git a/core/blockchain_test.go b/core/blockchain_test.go index df979578e6..d7cd24fa83 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -141,7 +141,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { if err != nil { return err } - receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb) + receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, nil) if err != nil { reportBlock(block, err) return err @@ -435,7 +435,7 @@ func (bproc) ValidateHeader(*types.Header, *types.Header, bool) error { return n func (bproc) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error { return nil } -func (bproc) Process(block *types.Block, statedb *state.StateDB) (types.Receipts, vm.Logs, *big.Int, error) { +func (bproc) Process(block *types.Block, statedb *state.StateDB, cfg *vm.Config) (types.Receipts, vm.Logs, *big.Int, error) { return nil, nil, nil, nil } diff --git a/core/chain_makers.go b/core/chain_makers.go index 0e1ca5fffb..7ae3c98b06 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -91,7 +91,7 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { b.SetCoinbase(common.Address{}) } b.statedb.StartRecord(tx.Hash(), common.Hash{}, len(b.txs)) - receipt, _, _, err := ApplyTransaction(nil, b.gasPool, b.statedb, b.header, tx, b.header.GasUsed) + receipt, _, _, err := ApplyTransaction(nil, b.gasPool, b.statedb, b.header, tx, b.header.GasUsed, nil) if err != nil { panic(err) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 0f86325c60..3267081189 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -38,7 +38,7 @@ func (self Code) String() string { return string(self) //strings.Join(Disassemble(self), " ") } -type Storage map[string]common.Hash +type Storage map[common.Hash]common.Hash func (self Storage) String() (str string) { for key, value := range self { @@ -112,13 +112,13 @@ func (c *StateObject) getAddr(addr common.Hash) common.Hash { return common.BytesToHash(ret) } -func (c *StateObject) setAddr(addr []byte, value common.Hash) { +func (c *StateObject) setAddr(addr, value common.Hash) { v, err := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00")) if err != nil { // if RLPing failed we better panic and not fail silently. This would be considered a consensus issue panic(err) } - c.trie.Update(addr, v) + c.trie.Update(addr[:], v) } func (self *StateObject) Storage() Storage { @@ -126,20 +126,19 @@ func (self *StateObject) Storage() Storage { } func (self *StateObject) GetState(key common.Hash) common.Hash { - strkey := key.Str() - value, exists := self.storage[strkey] + value, exists := self.storage[key] if !exists { value = self.getAddr(key) if (value != common.Hash{}) { - self.storage[strkey] = value + self.storage[key] = value } } return value } -func (self *StateObject) SetState(k, value common.Hash) { - self.storage[k.Str()] = value +func (self *StateObject) SetState(key, value common.Hash) { + self.storage[key] = value self.dirty = true } @@ -147,10 +146,10 @@ func (self *StateObject) SetState(k, value common.Hash) { func (self *StateObject) Update() { for key, value := range self.storage { if (value == common.Hash{}) { - self.trie.Delete([]byte(key)) + self.trie.Delete(key[:]) continue } - self.setAddr([]byte(key), value) + self.setAddr(key, value) } } @@ -245,24 +244,22 @@ func (self *StateObject) Value() *big.Int { panic("Value on StateObject should never be called") } -func (self *StateObject) EachStorage(cb func(key, value []byte)) { +func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) { // When iterating over the storage check the cache first - for h, v := range self.storage { - cb([]byte(h), v.Bytes()) + for h, value := range self.storage { + cb(h, value) } it := self.trie.Iterator() for it.Next() { // ignore cached values - key := self.trie.GetKey(it.Key) - if _, ok := self.storage[string(key)]; !ok { - cb(key, it.Value) + key := common.BytesToHash(self.trie.GetKey(it.Key)) + if _, ok := self.storage[key]; !ok { + cb(key, common.BytesToHash(it.Value)) } } } -// Encoding - type extStateObject struct { Nonce uint64 Balance *big.Int diff --git a/core/state_processor.go b/core/state_processor.go index 3ca36a43a9..38cd0e6752 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -31,7 +31,7 @@ func NewStateProcessor(bc *BlockChain) *StateProcessor { // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB) (types.Receipts, vm.Logs, *big.Int, error) { +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg *vm.Config) (types.Receipts, vm.Logs, *big.Int, error) { var ( receipts types.Receipts totalUsedGas = big.NewInt(0) @@ -43,7 +43,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB) (ty for i, tx := range block.Transactions() { statedb.StartRecord(tx.Hash(), block.Hash(), i) - receipt, logs, _, err := ApplyTransaction(p.bc, gp, statedb, header, tx, totalUsedGas) + receipt, logs, _, err := ApplyTransaction(p.bc, gp, statedb, header, tx, totalUsedGas, cfg) if err != nil { return nil, nil, totalUsedGas, err } @@ -60,8 +60,8 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB) (ty // // ApplyTransactions returns the generated receipts and vm logs during the // execution of the state transition phase. -func ApplyTransaction(bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int) (*types.Receipt, vm.Logs, *big.Int, error) { - _, gas, err := ApplyMessage(NewEnv(statedb, bc, tx, header), tx, gp) +func ApplyTransaction(bc *BlockChain, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg *vm.Config) (*types.Receipt, vm.Logs, *big.Int, error) { + _, gas, err := ApplyMessage(NewEnv(statedb, bc, tx, header, cfg), tx, gp) if err != nil { return nil, nil, nil, err } diff --git a/core/state_transition.go b/core/state_transition.go index 2887f62281..cc357aacae 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -137,6 +137,7 @@ func (self *StateTransition) from() (vm.Account, error) { } return self.state.GetAccount(f), nil } + func (self *StateTransition) to() vm.Account { if self.msg == nil { return nil @@ -193,7 +194,6 @@ func (self *StateTransition) preCheck() (err error) { } // Make sure this transaction's nonce is correct - //if sender.Nonce() != msg.Nonce() { if n := self.state.GetNonce(sender.Address()); n != msg.Nonce() { return NonceError(msg.Nonce(), n) } @@ -253,10 +253,6 @@ func (self *StateTransition) transitionDb() (ret []byte, usedGas *big.Int, err e err = nil } - if vm.Debug { - vm.StdErrFormat(vmenv.StructLogs()) - } - self.refundGas() self.state.AddBalance(self.env.Coinbase(), new(big.Int).Mul(self.gasUsed(), self.gasPrice)) diff --git a/core/types.go b/core/types.go index 0225283741..af9bc567b5 100644 --- a/core/types.go +++ b/core/types.go @@ -61,7 +61,7 @@ type HeaderValidator interface { // of gas used in the process and return an error if any of the internal rules // failed. type Processor interface { - Process(block *types.Block, statedb *state.StateDB) (types.Receipts, vm.Logs, *big.Int, error) + Process(block *types.Block, statedb *state.StateDB, cfg *vm.Config) (types.Receipts, vm.Logs, *big.Int, error) } // Backend is an interface defining the basic functionality for an operable node diff --git a/core/vm/common.go b/core/vm/common.go index f73bc15270..2878b92d2a 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -24,11 +24,6 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// Global Debug flag indicating Debug VM (full logging) -var Debug bool - -var GenerateStructLogs bool = false - // Type is the VM type accepted by **NewVm** type Type byte diff --git a/core/vm/contract.go b/core/vm/contract.go index d239952183..59e8f1aa6c 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -28,7 +28,7 @@ type ContractRef interface { Address() common.Address Value() *big.Int SetCode([]byte) - EachStorage(cb func(key, value []byte)) + ForEachStorage(callback func(key, value common.Hash) bool) } // Contract represents an ethereum contract in the state database. It contains @@ -156,6 +156,6 @@ func (self *Contract) SetCallCode(addr *common.Address, code []byte) { // EachStorage iterates the contract's storage and calls a method for every key // value pair. -func (self *Contract) EachStorage(cb func(key, value []byte)) { - self.caller.EachStorage(cb) +func (self *Contract) ForEachStorage(cb func(key, value common.Hash) bool) { + self.caller.ForEachStorage(cb) } diff --git a/core/vm/environment.go b/core/vm/environment.go index 3c530962b8..568218edd0 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -22,9 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -// Environment is is required by the virtual machine to get information from -// it's own isolated environment. - // Environment is an EVM requirement and helper which allows access to outside // information such as states. type Environment interface { @@ -54,12 +51,8 @@ type Environment interface { Transfer(from, to Account, amount *big.Int) // Adds a LOG to the state AddLog(*Log) - // Adds a structured log to the env - AddStructLog(StructLog) - // Returns all coalesced structured logs - StructLogs() []StructLog // Type of the VM - Vm() *Vm + Vm() Vm // Current calling depth Depth() int SetDepth(i int) @@ -74,7 +67,15 @@ type Environment interface { Create(me ContractRef, data []byte, gas, price, value *big.Int) ([]byte, common.Address, error) } -// Database is a EVM database for full state querying +// Vm is the basic interface for an implementation of the EVM. +type Vm interface { + // Run should execute the given contract with the input given in in + // and return the contract execution return bytes or an error if it + // failed. + Run(c *Contract, in []byte) ([]byte, error) +} + +// Database is a EVM database for full state querying. type Database interface { GetAccount(common.Address) Account CreateAccount(common.Address) Account @@ -99,19 +100,7 @@ type Database interface { IsDeleted(common.Address) bool } -// StructLog is emitted to the Environment each cycle and lists information about the current internal state -// prior to the execution of the statement. -type StructLog struct { - Pc uint64 - Op OpCode - Gas *big.Int - GasCost *big.Int - Memory []byte - Stack []*big.Int - Storage map[common.Hash][]byte - Err error -} - +// Account represents a contract or basic ethereum account. type Account interface { SubBalance(amount *big.Int) AddBalance(amount *big.Int) @@ -121,6 +110,6 @@ type Account interface { Address() common.Address ReturnGas(*big.Int, *big.Int) SetCode([]byte) - EachStorage(cb func(key, value []byte)) + ForEachStorage(cb func(key, value common.Hash) bool) Value() *big.Int } diff --git a/core/vm/jit_test.go b/core/vm/jit_test.go index 5fac0156f0..43b1dee2ab 100644 --- a/core/vm/jit_test.go +++ b/core/vm/jit_test.go @@ -125,17 +125,17 @@ type vmBench struct { type account struct{} -func (account) SubBalance(amount *big.Int) {} -func (account) AddBalance(amount *big.Int) {} -func (account) SetAddress(common.Address) {} -func (account) Value() *big.Int { return nil } -func (account) SetBalance(*big.Int) {} -func (account) SetNonce(uint64) {} -func (account) Balance() *big.Int { return nil } -func (account) Address() common.Address { return common.Address{} } -func (account) ReturnGas(*big.Int, *big.Int) {} -func (account) SetCode([]byte) {} -func (account) EachStorage(cb func(key, value []byte)) {} +func (account) SubBalance(amount *big.Int) {} +func (account) AddBalance(amount *big.Int) {} +func (account) SetAddress(common.Address) {} +func (account) Value() *big.Int { return nil } +func (account) SetBalance(*big.Int) {} +func (account) SetNonce(uint64) {} +func (account) Balance() *big.Int { return nil } +func (account) Address() common.Address { return common.Address{} } +func (account) ReturnGas(*big.Int, *big.Int) {} +func (account) SetCode([]byte) {} +func (account) ForEachStorage(cb func(key, value common.Hash) bool) {} func runVmBench(test vmBench, b *testing.B) { var sender account @@ -165,16 +165,16 @@ func runVmBench(test vmBench, b *testing.B) { type Env struct { gasLimit *big.Int depth int - evm *Vm + evm *EVM } func NewEnv() *Env { env := &Env{gasLimit: big.NewInt(10000), depth: 0} - env.evm = EVM(env) + env.evm = New(env, nil) return env } -func (self *Env) Vm() *Vm { return self.evm } +func (self *Env) Vm() Vm { return self.evm } func (self *Env) Origin() common.Address { return common.Address{} } func (self *Env) BlockNumber() *big.Int { return big.NewInt(0) } func (self *Env) AddStructLog(log StructLog) { diff --git a/core/vm/logger.go b/core/vm/logger.go index 2bd02319f6..8d333dfd25 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -18,12 +18,143 @@ package vm import ( "fmt" + "math/big" "os" "unicode" "github.com/ethereum/go-ethereum/common" ) +type Storage map[common.Hash]common.Hash + +func (self Storage) Copy() Storage { + cpy := make(Storage) + for key, value := range self { + cpy[key] = value + } + + return cpy +} + +// StructLogCollector is the basic interface to capture emited logs by the EVM logger. +type StructLogCollector interface { + // Adds the structured log to the collector. + AddStructLog(StructLog) +} + +// LogConfig are the configuration options for structured logger the EVM +type LogConfig struct { + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + FullStorage bool // show full storage (slow) + Collector StructLogCollector // the log collector +} + +// StructLog is emitted to the Environment each cycle and lists information about the current internal state +// prior to the execution of the statement. +type StructLog struct { + Pc uint64 + Op OpCode + Gas *big.Int + GasCost *big.Int + Memory []byte + Stack []*big.Int + Storage map[common.Hash]common.Hash + Depth int + Err error +} + +// Logger is an EVM state logger and implements VmLogger. +// +// Logger can capture state based on the given Log configuration and also keeps +// a track record of modified storage which is used in reporting snapshots of the +// contract their storage. +type Logger struct { + cfg LogConfig + + env Environment + changedValues map[common.Address]Storage +} + +// newLogger returns a new logger +func newLogger(cfg LogConfig, env Environment) *Logger { + return &Logger{ + cfg: cfg, + env: env, + changedValues: make(map[common.Address]Storage), + } +} + +// captureState logs a new structured log message and pushes it out to the environment +// +// captureState also tracks SSTORE ops to track dirty values. +func (l *Logger) captureState(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, contract *Contract, err error) { + // short circuit if no log collector is present + if l.cfg.Collector == nil { + return + } + + // initialise new changed values storage container for this contract + // if not present. + if l.changedValues[contract.Address()] == nil { + l.changedValues[contract.Address()] = make(Storage) + } + + // capture SSTORE opcodes and determine the changed value and store + // it in the local storage container. NOTE: we do not need to do any + // range checks here because that's already handler prior to calling + // this function. + switch op { + case SSTORE: + var ( + value = common.BigToHash(stack.data[stack.len()-2]) + address = common.BigToHash(stack.data[stack.len()-1]) + ) + l.changedValues[contract.Address()][address] = value + } + + // copy a snapstot of the current memory state to a new buffer + var mem []byte + if !l.cfg.DisableMemory { + mem = make([]byte, len(memory.Data())) + copy(mem, memory.Data()) + } + + // copy a snapshot of the current stack state to a new buffer + var stck []*big.Int + if !l.cfg.DisableStack { + stck = make([]*big.Int, len(stack.Data())) + for i, item := range stack.Data() { + stck[i] = new(big.Int).Set(item) + } + } + + // Copy the storage based on the settings specified in the log config. If full storage + // is disabled (default) we can use the simple Storage.Copy method, otherwise we use + // the state object to query for all values (slow process). + var storage Storage + if !l.cfg.DisableStorage { + if l.cfg.FullStorage { + storage = make(Storage) + // Get the contract account and loop over each storage entry. This may involve looping over + // the trie and is a very expensive process. + l.env.Db().GetAccount(contract.Address()).ForEachStorage(func(key, value common.Hash) bool { + storage[key] = value + // Return true, indicating we'd like to continue. + return true + }) + } else { + // copy a snapshot of the current storage to a new container. + storage = l.changedValues[contract.Address()].Copy() + } + } + // create a new snaptshot of the EVM. + log := StructLog{pc, op, new(big.Int).Set(gas), cost, mem, stck, storage, l.env.Depth(), err} + // Add the log to the collector + l.cfg.Collector.AddStructLog(log) +} + // StdErrFormat formats a slice of StructLogs to human readable format func StdErrFormat(logs []StructLog) { fmt.Fprintf(os.Stderr, "VM STAT %d OPs\n", len(logs)) @@ -61,7 +192,7 @@ func StdErrFormat(logs []StructLog) { fmt.Fprintln(os.Stderr, "STORAGE =", len(log.Storage)) for h, item := range log.Storage { - fmt.Fprintf(os.Stderr, "%x: %x\n", h, common.LeftPadBytes(item, 32)) + fmt.Fprintf(os.Stderr, "%x: %x\n", h, item) } fmt.Fprintln(os.Stderr) } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go new file mode 100644 index 0000000000..77fee2c649 --- /dev/null +++ b/core/vm/logger_test.go @@ -0,0 +1,104 @@ +// Copyright 2016 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 ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +type dummyContractRef struct { + calledForEach bool +} + +func (dummyContractRef) ReturnGas(*big.Int, *big.Int) {} +func (dummyContractRef) Address() common.Address { return common.Address{} } +func (dummyContractRef) Value() *big.Int { return new(big.Int) } +func (dummyContractRef) SetCode([]byte) {} +func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { + d.calledForEach = true +} +func (d *dummyContractRef) SubBalance(amount *big.Int) {} +func (d *dummyContractRef) AddBalance(amount *big.Int) {} +func (d *dummyContractRef) SetBalance(*big.Int) {} +func (d *dummyContractRef) SetNonce(uint64) {} +func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } + +type dummyEnv struct { + *Env + ref *dummyContractRef +} + +func newDummyEnv(ref *dummyContractRef) *dummyEnv { + return &dummyEnv{ + Env: NewEnv(), + ref: ref, + } +} +func (d dummyEnv) GetAccount(common.Address) Account { + return d.ref +} +func (d dummyEnv) AddStructLog(StructLog) {} + +func TestStoreCapture(t *testing.T) { + var ( + env = NewEnv() + logger = newLogger(LogConfig{Collector: env}, env) + mem = NewMemory() + stack = newstack() + contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), new(big.Int), new(big.Int)) + ) + stack.push(big.NewInt(1)) + stack.push(big.NewInt(0)) + + var index common.Hash + + logger.captureState(0, SSTORE, new(big.Int), new(big.Int), mem, stack, contract, nil) + if len(logger.changedValues[contract.Address()]) == 0 { + t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()])) + } + + exp := common.BigToHash(big.NewInt(1)) + if logger.changedValues[contract.Address()][index] != exp { + t.Errorf("expected %x, got %x", exp, logger.changedValues[contract.Address()][index]) + } +} + +func TestStorageCapture(t *testing.T) { + t.Skip("implementing this function is difficult. it requires all sort of interfaces to be implemented which isn't trivial. The value (the actual test) isn't worth it") + var ( + ref = &dummyContractRef{} + contract = NewContract(ref, ref, new(big.Int), new(big.Int), new(big.Int)) + env = newDummyEnv(ref) + logger = newLogger(LogConfig{Collector: env}, env) + mem = NewMemory() + stack = newstack() + ) + + logger.captureState(0, STOP, new(big.Int), new(big.Int), mem, stack, contract, nil) + if ref.calledForEach { + t.Error("didn't expect for each to be called") + } + + logger = newLogger(LogConfig{Collector: env, FullStorage: true}, env) + logger.captureState(0, STOP, new(big.Int), new(big.Int), mem, stack, contract, nil) + if !ref.calledForEach { + t.Error("expected for each to be called") + } +} diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index e9bf828ea8..ce64d71176 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -42,7 +42,7 @@ type Env struct { getHashFn func(uint64) common.Hash - evm *vm.Vm + evm *vm.EVM } // NewEnv returns a new vm.Environment @@ -56,7 +56,15 @@ func NewEnv(cfg *Config, state *state.StateDB) vm.Environment { difficulty: cfg.Difficulty, gasLimit: cfg.GasLimit, } - env.evm = vm.EVM(env) + env.evm = vm.New(env, &vm.Config{ + Debug: cfg.Debug, + EnableJit: !cfg.DisableJit, + ForceJit: !cfg.DisableJit, + + Logger: vm.LogConfig{ + Collector: env, + }, + }) return env } @@ -69,7 +77,7 @@ func (self *Env) AddStructLog(log vm.StructLog) { self.logs = append(self.logs, log) } -func (self *Env) Vm() *vm.Vm { return self.evm } +func (self *Env) Vm() vm.Vm { return self.evm } func (self *Env) Origin() common.Address { return self.origin } func (self *Env) BlockNumber() *big.Int { return self.number } func (self *Env) Coinbase() common.Address { return self.coinbase } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 3e60571423..f88a201702 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -22,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" ) @@ -84,17 +83,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { } setDefaults(cfg) - // defer the call to setting back the original values - defer func(debug, forceJit, enableJit bool) { - vm.Debug = debug - vm.ForceJit = forceJit - vm.EnableJit = enableJit - }(vm.Debug, vm.ForceJit, vm.EnableJit) - - vm.ForceJit = !cfg.DisableJit - vm.EnableJit = !cfg.DisableJit - vm.Debug = cfg.Debug - if cfg.State == nil { db, _ := ethdb.NewMemDatabase() cfg.State, _ = state.New(common.Hash{}, db) @@ -117,9 +105,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { cfg.Value, ) - if cfg.Debug { - vm.StdErrFormat(vmenv.StructLogs()) - } return ret, cfg.State, err } @@ -131,17 +116,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { setDefaults(cfg) - // defer the call to setting back the original values - defer func(debug, forceJit, enableJit bool) { - vm.Debug = debug - vm.ForceJit = forceJit - vm.EnableJit = enableJit - }(vm.Debug, vm.ForceJit, vm.EnableJit) - - vm.ForceJit = !cfg.DisableJit - vm.EnableJit = !cfg.DisableJit - vm.Debug = cfg.Debug - vmenv := NewEnv(cfg, cfg.State) sender := cfg.State.GetOrNewStateObject(cfg.Origin) @@ -155,8 +129,5 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, error) { cfg.Value, ) - if cfg.Debug { - vm.StdErrFormat(vmenv.StructLogs()) - } return ret, err } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index e5183052fa..88c76c7319 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -117,21 +117,6 @@ func TestCall(t *testing.T) { } } -func TestRestoreDefaults(t *testing.T) { - Execute(nil, nil, &Config{Debug: true}) - if vm.ForceJit { - t.Error("expected force jit to be disabled") - } - - if vm.Debug { - t.Error("expected debug to be disabled") - } - - if vm.EnableJit { - t.Error("expected jit to be disabled") - } -} - func BenchmarkCall(b *testing.B) { var definition = `[{"constant":true,"inputs":[],"name":"seller","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"abort","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"value","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"refund","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"buyer","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"confirmReceived","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"state","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":false,"inputs":[],"name":"confirmPurchase","outputs":[],"type":"function"},{"inputs":[],"type":"constructor"},{"anonymous":false,"inputs":[],"name":"Aborted","type":"event"},{"anonymous":false,"inputs":[],"name":"PurchaseConfirmed","type":"event"},{"anonymous":false,"inputs":[],"name":"ItemReceived","type":"event"},{"anonymous":false,"inputs":[],"name":"Refunded","type":"event"}]` diff --git a/core/vm/virtual_machine.go b/core/vm/virtual_machine.go index 9b3340bb21..6291088846 100644 --- a/core/vm/virtual_machine.go +++ b/core/vm/virtual_machine.go @@ -18,6 +18,5 @@ package vm // VirtualMachine is an EVM interface type VirtualMachine interface { - Env() Environment Run(*Contract, []byte) ([]byte, error) } diff --git a/core/vm/vm.go b/core/vm/vm.go index 26df8aef47..f72c853a25 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -28,24 +28,54 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// Vm is an EVM and implements VirtualMachine -type Vm struct { +// Config are the configuration options for the EVM +type Config struct { + Debug bool + EnableJit bool + ForceJit bool + Logger LogConfig +} + +// 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 } -func EVM(env Environment) *Vm { - return &Vm{env: env, jumpTable: newJumpTable(env.BlockNumber())} +// 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 -} diff --git a/core/vm/vm_jit_fake.go b/core/vm/vm_jit_fake.go index 192f3615d7..b26cf1ad0d 100644 --- a/core/vm/vm_jit_fake.go +++ b/core/vm/vm_jit_fake.go @@ -22,5 +22,5 @@ import "fmt" func NewJitVm(env Environment) VirtualMachine { fmt.Printf("Warning! EVM JIT not enabled.\n") - return EVM(env) + return New(env, nil) } diff --git a/core/vm_env.go b/core/vm_env.go index 0fab4a090d..880baa7b0f 100644 --- a/core/vm_env.go +++ b/core/vm_env.go @@ -41,33 +41,42 @@ func GetHashFn(ref common.Hash, chain *BlockChain) func(n uint64) common.Hash { } type VMEnv struct { - state *state.StateDB - header *types.Header - msg Message - depth int - chain *BlockChain - typ vm.Type - - getHashFn func(uint64) common.Hash - // structured logging - logs []vm.StructLog - evm *vm.Vm + state *state.StateDB // State to use for executing + evm *vm.EVM // The Ethereum Virtual Machine + depth int // Current execution depth + msg Message // Message appliod + + header *types.Header // Header information + chain *BlockChain // Blockchain handle + logs []vm.StructLog // Logs for the custom structured logger + getHashFn func(uint64) common.Hash // getHashFn callback is used to retrieve block hashes + } -func NewEnv(state *state.StateDB, chain *BlockChain, msg Message, header *types.Header) *VMEnv { +func NewEnv(state *state.StateDB, chain *BlockChain, msg Message, header *types.Header, cfg *vm.Config) *VMEnv { env := &VMEnv{ chain: chain, state: state, header: header, msg: msg, - typ: vm.StdVmTy, getHashFn: GetHashFn(header.ParentHash, chain), } - env.evm = vm.EVM(env) + + // initialise a default config if none present + if cfg == nil { + cfg = new(vm.Config) + } + + // if no log collector is present set self as the collector + if cfg.Logger.Collector == nil { + cfg.Logger.Collector = env + } + + env.evm = vm.New(env, cfg) return env } -func (self *VMEnv) Vm() *vm.Vm { return self.evm } +func (self *VMEnv) Vm() vm.Vm { return self.evm } func (self *VMEnv) Origin() common.Address { f, _ := self.msg.From(); return f } func (self *VMEnv) BlockNumber() *big.Int { return self.header.Number } func (self *VMEnv) Coinbase() common.Address { return self.header.Coinbase } @@ -78,8 +87,6 @@ func (self *VMEnv) Value() *big.Int { return self.msg.Value() } func (self *VMEnv) Db() vm.Database { return self.state } func (self *VMEnv) Depth() int { return self.depth } func (self *VMEnv) SetDepth(i int) { self.depth = i } -func (self *VMEnv) VmType() vm.Type { return self.typ } -func (self *VMEnv) SetVmType(t vm.Type) { self.typ = t } func (self *VMEnv) GetHash(n uint64) common.Hash { return self.getHashFn(n) } diff --git a/eth/api.go b/eth/api.go index 487d24ae7c..beae3aabb7 100644 --- a/eth/api.go +++ b/eth/api.go @@ -667,7 +667,7 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st } // Execute the call and return - vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header()) + vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header(), nil) gp := new(core.GasPool).AddGas(common.MaxBig) res, gas, err := core.ApplyMessage(vmenv, msg, gp) @@ -1513,9 +1513,6 @@ func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { if block == nil { return false, fmt.Errorf("block #%d not found", number) } - // Temporarily enable debugging - defer func(old bool) { vm.Debug = old }(vm.Debug) - vm.Debug = true // Validate and reprocess the block var ( @@ -1530,7 +1527,7 @@ func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { if err != nil { return false, err } - receipts, _, usedGas, err := processor.Process(block, statedb) + receipts, _, usedGas, err := processor.Process(block, statedb, nil) if err != nil { return false, err } @@ -1601,10 +1598,8 @@ func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLo data: tx.Data(), } - vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header()) + vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header(), nil) gp := new(core.GasPool).AddGas(block.GasLimit()) - vm.GenerateStructLogs = true - defer func() { vm.GenerateStructLogs = false }() ret, gas, err := core.ApplyMessage(vmenv, msg, gp) if err != nil { diff --git a/miner/worker.go b/miner/worker.go index f3e95cb5fe..a5eec26010 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -663,7 +663,7 @@ func (env *Work) commitTransactions(mux *event.TypeMux, transactions types.Trans func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, gp *core.GasPool) (error, vm.Logs) { snap := env.state.Copy() - receipt, logs, _, err := core.ApplyTransaction(bc, gp, env.state, env.header, tx, env.header.GasUsed) + receipt, logs, _, err := core.ApplyTransaction(bc, gp, env.state, env.header, tx, env.header.GasUsed, nil) if err != nil { env.state.Set(snap) return err, nil diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 7d1701ff26..50be3a1ac8 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -235,7 +235,6 @@ func RunState(statedb *state.StateDB, env, tx map[string]string) ([]byte, vm.Log } // Set pre compiled contracts vm.Precompiled = vm.PrecompiledContracts() - vm.Debug = false snapshot := statedb.Copy() gaspool := new(core.GasPool).AddGas(common.Big(env["currentGasLimit"])) diff --git a/tests/util.go b/tests/util.go index 2c749edba9..a0eb8158e0 100644 --- a/tests/util.go +++ b/tests/util.go @@ -28,8 +28,13 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/logger/glog" ) +func init() { + glog.SetV(0) +} + func checkLogs(tlog []Log, logs vm.Logs) error { if len(tlog) != len(logs) { @@ -144,7 +149,7 @@ type Env struct { vmTest bool - evm *vm.Vm + evm *vm.EVM } func NewEnv(state *state.StateDB) *Env { @@ -174,12 +179,12 @@ func NewEnvFromMap(state *state.StateDB, envValues map[string]string, exeValues env.gasLimit = common.Big(envValues["currentGasLimit"]) env.Gas = new(big.Int) - env.evm = vm.EVM(env) + env.evm = vm.New(env, nil) return env } -func (self *Env) Vm() *vm.Vm { return self.evm } +func (self *Env) Vm() vm.Vm { return self.evm } func (self *Env) Origin() common.Address { return self.origin } func (self *Env) BlockNumber() *big.Int { return self.number } func (self *Env) Coinbase() common.Address { return self.coinbase } From 3601320ccd0b3db59d1f720c8a2a2383f5c8435f Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 3 Feb 2016 23:47:58 +0100 Subject: [PATCH 3/5] eth, rpc: implemented block debugging rpc calls Implemented the following block debugging RPC calls * Block(RLP) * BlockByFile(fileName) * BlockByNumber(number) * BlockByHash(hash) --- eth/api.go | 280 ++++++++++++++++++++++++++++++++-------------- rpc/javascript.go | 27 ++++- 2 files changed, 214 insertions(+), 93 deletions(-) diff --git a/eth/api.go b/eth/api.go index beae3aabb7..fbaa280f44 100644 --- a/eth/api.go +++ b/eth/api.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "math/big" "os" "sync" @@ -1506,35 +1507,113 @@ func NewPrivateDebugAPI(eth *Ethereum) *PrivateDebugAPI { return &PrivateDebugAPI{eth: eth} } -// ProcessBlock reprocesses an already owned block. -func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { +// BlockTraceResults is the returned value when replaying a block to check for +// consensus results and full VM trace logs for all included transactions. +type BlockTraceResult struct { + Validated bool `json: "validated"` + StructLogs []structLogRes `json:"structLogs"` + Error error `json:"error"` +} + +// TraceBlock processes the given block's RLP but does not import the block in to +// the chain. +func (api *PrivateDebugAPI) TraceBlock(blockRlp []byte, config vm.Config) BlockTraceResult { + var block types.Block + err := rlp.Decode(bytes.NewReader(blockRlp), &block) + if err != nil { + return BlockTraceResult{Error: fmt.Errorf("could not decode block: %v", err)} + } + + validated, logs, err := api.traceBlock(&block, config) + return BlockTraceResult{ + Validated: validated, + StructLogs: formatLogs(logs), + Error: err, + } +} + +// TraceBlockFromFile loads the block's RLP from the given file name and attempts to +// process it but does not import the block in to the chain. +func (api *PrivateDebugAPI) TraceBlockFromFile(file string, config vm.Config) BlockTraceResult { + blockRlp, err := ioutil.ReadFile(file) + if err != nil { + return BlockTraceResult{Error: fmt.Errorf("could not read file: %v", err)} + } + return api.TraceBlock(blockRlp, config) +} + +// TraceProcessBlock processes the block by canonical block number. +func (api *PrivateDebugAPI) TraceBlockByNumber(number uint64, config vm.Config) BlockTraceResult { // Fetch the block that we aim to reprocess block := api.eth.BlockChain().GetBlockByNumber(number) if block == nil { - return false, fmt.Errorf("block #%d not found", number) + return BlockTraceResult{Error: fmt.Errorf("block #%d not found", number)} + } + + validated, logs, err := api.traceBlock(block, config) + return BlockTraceResult{ + Validated: validated, + StructLogs: formatLogs(logs), + Error: err, + } +} + +// TraceBlockByHash processes the block by hash. +func (api *PrivateDebugAPI) TraceBlockByHash(hash common.Hash, config vm.Config) BlockTraceResult { + // Fetch the block that we aim to reprocess + block := api.eth.BlockChain().GetBlock(hash) + if block == nil { + return BlockTraceResult{Error: fmt.Errorf("block #%x not found", hash)} } + validated, logs, err := api.traceBlock(block, config) + return BlockTraceResult{ + Validated: validated, + StructLogs: formatLogs(logs), + Error: err, + } +} + +// TraceCollector collects EVM structered logs. +// +// TraceCollector implements vm.Collector +type TraceCollector struct { + traces []vm.StructLog +} + +// AddStructLog adds a structered log. +func (t *TraceCollector) AddStructLog(slog vm.StructLog) { + t.traces = append(t.traces, slog) +} + +// traceBlock processes the given block but does not save the state. +func (api *PrivateDebugAPI) traceBlock(block *types.Block, config vm.Config) (bool, []vm.StructLog, error) { // Validate and reprocess the block var ( blockchain = api.eth.BlockChain() validator = blockchain.Validator() processor = blockchain.Processor() + collector = &TraceCollector{} ) + config.Debug = true // make sure debug is set. + config.Logger.Collector = collector + if err := core.ValidateHeader(blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash()), true, false); err != nil { - return false, err + return false, collector.traces, err } statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), api.eth.ChainDb()) if err != nil { - return false, err + return false, collector.traces, err } - receipts, _, usedGas, err := processor.Process(block, statedb, nil) + + receipts, _, usedGas, err := processor.Process(block, statedb, &config) if err != nil { - return false, err + return false, collector.traces, err } if err := validator.ValidateState(block, blockchain.GetBlock(block.ParentHash()), statedb, receipts, usedGas); err != nil { - return false, err + return false, collector.traces, err } - return true, nil + return true, collector.traces, nil } // SetHead rewinds the head of the blockchain to a previous block. @@ -1542,7 +1621,16 @@ func (api *PrivateDebugAPI) SetHead(number uint64) { api.eth.BlockChain().SetHead(number) } -// StructLogRes stores a structured log emitted by the EVM while replaying a +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as the amount of +// gas used and the return value +type ExecutionResult struct { + Gas *big.Int `json:"gas"` + ReturnValue string `json:"returnValue"` + StructLogs []structLogRes `json:"structLogs"` +} + +// structLogRes stores a structured log emitted by the EVM while replaying a // transaction in debug mode type structLogRes struct { Pc uint64 `json:"pc"` @@ -1551,42 +1639,73 @@ type structLogRes struct { GasCost *big.Int `json:"gasCost"` Error error `json:"error"` Stack []string `json:"stack"` - Memory map[string]string `json:"memory"` + Memory []string `json:"memory"` Storage map[string]string `json:"storage"` } -// TransactionExecutionRes groups all structured logs emitted by the EVM -// while replaying a transaction in debug mode as well as the amount of -// gas used and the return value -type TransactionExecutionResult struct { - Gas *big.Int `json:"gas"` - ReturnValue string `json:"returnValue"` - StructLogs []structLogRes `json:"structLogs"` +// VmLoggerOptions are the options used for debugging transactions and capturing +// specific data. +type VmLoggerOptions struct { + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + FullStorage bool // show full storage (slow) } -func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLog, []byte, *big.Int, error) { +// formatLogs formats EVM returned structured logs for json output +func formatLogs(structLogs []vm.StructLog) []structLogRes { + formattedStructLogs := make([]structLogRes, len(structLogs)) + for index, trace := range structLogs { + formattedStructLogs[index] = structLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Error: trace.Err, + Stack: make([]string, len(trace.Stack)), + Storage: make(map[string]string), + } + + for i, stackValue := range trace.Stack { + formattedStructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(stackValue.Bytes(), 32)) + } + + for i := 0; i+32 <= len(trace.Memory); i += 32 { + formattedStructLogs[index].Memory = append(formattedStructLogs[index].Memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + + for i, storageValue := range trace.Storage { + formattedStructLogs[index].Storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + } + return formattedStructLogs +} + +// TraceTransaction returns the structured logs created during the execution of EVM +// and returns them as a JSON object. +func (s *PrivateDebugAPI) TraceTransaction(txHash common.Hash, logger vm.LogConfig) (*ExecutionResult, error) { // Retrieve the tx from the chain tx, _, blockIndex, _ := core.GetTransaction(s.eth.ChainDb(), txHash) if tx == nil { - return nil, nil, nil, fmt.Errorf("Transaction not found") + return nil, fmt.Errorf("Transaction not found") } block := s.eth.BlockChain().GetBlockByNumber(blockIndex - 1) if block == nil { - return nil, nil, nil, fmt.Errorf("Unable to retrieve prior block") + return nil, fmt.Errorf("Unable to retrieve prior block") } // Create the state database stateDb, err := state.New(block.Root(), s.eth.ChainDb()) if err != nil { - return nil, nil, nil, err + return nil, err } txFrom, err := tx.FromFrontier() if err != nil { - return nil, nil, nil, fmt.Errorf("Unable to create transaction sender") + return nil, fmt.Errorf("Unable to create transaction sender") } from := stateDb.GetOrNewStateObject(txFrom) msg := callmsg{ @@ -1598,85 +1717,72 @@ func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLo data: tx.Data(), } - vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header(), nil) + vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header(), &vm.Config{ + Debug: true, + Logger: logger, + }) gp := new(core.GasPool).AddGas(block.GasLimit()) ret, gas, err := core.ApplyMessage(vmenv, msg, gp) if err != nil { - return nil, nil, nil, fmt.Errorf("Error executing transaction %v", err) + return nil, fmt.Errorf("Error executing transaction %v", err) } - return vmenv.StructLogs(), ret, gas, nil + return &ExecutionResult{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: formatLogs(vmenv.StructLogs()), + }, nil } -// Executes a transaction and returns the structured logs of the EVM -// gathered during the execution -func (s *PrivateDebugAPI) ReplayTransaction(txHash common.Hash, stackDepth int, memorySize int, storageSize int) (*TransactionExecutionResult, error) { - - structLogs, ret, gas, err := s.doReplayTransaction(txHash) - - if err != nil { +func (s *PublicBlockChainAPI) TraceCall(args CallArgs, blockNr rpc.BlockNumber) (*ExecutionResult, error) { + // Fetch the state associated with the block number + stateDb, block, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if stateDb == nil || err != nil { return nil, err } + stateDb = stateDb.Copy() - res := TransactionExecutionResult{ - Gas: gas, - ReturnValue: fmt.Sprintf("%x", ret), - StructLogs: make([]structLogRes, len(structLogs)), - } - - for index, trace := range structLogs { - - stackLength := len(trace.Stack) - - // Return full stack by default - if stackDepth != -1 && stackDepth < stackLength { - stackLength = stackDepth - } - - res.StructLogs[index] = structLogRes{ - Pc: trace.Pc, - Op: trace.Op.String(), - Gas: trace.Gas, - GasCost: trace.GasCost, - Error: trace.Err, - Stack: make([]string, stackLength), - Memory: make(map[string]string), - Storage: make(map[string]string), - } - - for i := 0; i < stackLength; i++ { - res.StructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(trace.Stack[i].Bytes(), 32)) - } - - addr := 0 - memorySizeLocal := memorySize - - // Return full memory by default - if memorySize == -1 { - memorySizeLocal = len(trace.Memory) + // Retrieve the account state object to interact with + var from *state.StateObject + if args.From == (common.Address{}) { + accounts, err := s.am.Accounts() + if err != nil || len(accounts) == 0 { + from = stateDb.GetOrNewStateObject(common.Address{}) + } else { + from = stateDb.GetOrNewStateObject(accounts[0].Address) } + } else { + from = stateDb.GetOrNewStateObject(args.From) + } + from.SetBalance(common.MaxBig) - for i := 0; i+16 <= len(trace.Memory) && addr < memorySizeLocal; i += 16 { - res.StructLogs[index].Memory[fmt.Sprintf("%04d", addr*16)] = fmt.Sprintf("%x", trace.Memory[i:i+16]) - addr++ - } + // Assemble the CALL invocation + msg := callmsg{ + from: from, + to: args.To, + gas: args.Gas.BigInt(), + gasPrice: args.GasPrice.BigInt(), + value: args.Value.BigInt(), + data: common.FromHex(args.Data), + } + if msg.gas.Cmp(common.Big0) == 0 { + msg.gas = big.NewInt(50000000) + } + if msg.gasPrice.Cmp(common.Big0) == 0 { + msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + } - storageLength := len(trace.Stack) - if storageSize != -1 && storageSize < storageLength { - storageLength = storageSize - } + // Execute the call and return + vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header(), nil) + gp := new(core.GasPool).AddGas(common.MaxBig) - i := 0 - for storageIndex, storageValue := range trace.Storage { - if i >= storageLength { - break - } - res.StructLogs[index].Storage[fmt.Sprintf("%x", storageIndex)] = fmt.Sprintf("%x", storageValue) - i++ - } - } - return &res, nil + ret, gas, err := core.ApplyMessage(vmenv, msg, gp) + return &ExecutionResult{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: formatLogs(vmenv.StructLogs()), + }, nil } // PublicNetAPI offers network related RPC methods diff --git a/rpc/javascript.go b/rpc/javascript.go index 72290a2a6a..c4fa80c0b1 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -291,9 +291,24 @@ web3._extend({ params: 1 }), new web3._extend.Method({ - name: 'processBlock', - call: 'debug_processBlock', - params: 1 + name: 'traceBlock', + call: 'debug_traceBlock', + params: 2 + }), + new web3._extend.Method({ + name: 'traceBlockByFile', + call: 'debug_traceBlockByFile', + params: 2 + }), + new web3._extend.Method({ + name: 'traceBlockByNumber', + call: 'debug_traceBlockByNumber', + params: 2 + }), + new web3._extend.Method({ + name: 'traceBlockByHash', + call: 'debug_traceBlockByHash', + params: 2 }), new web3._extend.Method({ name: 'seedHash', @@ -382,9 +397,9 @@ web3._extend({ params: 1 }), new web3._extend.Method({ - name: 'replayTransaction', - call: 'debug_replayTransaction', - params: 4 + name: 'traceTransaction', + call: 'debug_traceTransaction', + params: 2 }) ], properties: [] From 5f92606be2f7ddc53c9449770f5c96e5741e5c57 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Mon, 14 Mar 2016 15:58:14 +0100 Subject: [PATCH 4/5] eth/api: added root to the receipts --- eth/api.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eth/api.go b/eth/api.go index fbaa280f44..6a6ab6f43c 100644 --- a/eth/api.go +++ b/eth/api.go @@ -979,6 +979,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(txHash common.Hash) (ma } fields := map[string]interface{}{ + "root": common.Bytes2Hex(receipt.PostState), "blockHash": txBlock, "blockNumber": rpc.NewHexNumber(blockIndex), "transactionHash": txHash, @@ -1637,6 +1638,7 @@ type structLogRes struct { Op string `json:"op"` Gas *big.Int `json:"gas"` GasCost *big.Int `json:"gasCost"` + Depth int `json:"depth"` Error error `json:"error"` Stack []string `json:"stack"` Memory []string `json:"memory"` @@ -1661,6 +1663,7 @@ func formatLogs(structLogs []vm.StructLog) []structLogRes { Op: trace.Op.String(), Gas: trace.Gas, GasCost: trace.GasCost, + Depth: trace.Depth, Error: trace.Err, Stack: make([]string, len(trace.Stack)), Storage: make(map[string]string), From 0cfa21fc7f34d9da93abc41541dd4a98d70eb9dd Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Sat, 19 Mar 2016 18:07:09 +0100 Subject: [PATCH 5/5] core, eth, cmd: temporary work around for enabling the jit This commit serves as a temporary workaround for enabling the jit until the block customisation PR is merged in. --- cmd/utils/flags.go | 2 ++ core/blockchain.go | 7 ++++++- eth/backend.go | 9 +++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 3b05c2963a..10aa48735f 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -668,6 +668,8 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. ExtraData: MakeMinerExtra(extra, ctx), NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), DocRoot: ctx.GlobalString(DocRootFlag.Name), + EnableJit: ctx.GlobalBool(VMEnableJitFlag.Name), + ForceJit: ctx.GlobalBool(VMForceJitFlag.Name), GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)), GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)), GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)), diff --git a/core/blockchain.go b/core/blockchain.go index cecb914a8b..2c3c2bb5c2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -84,6 +84,7 @@ type BlockChain struct { chainDb ethdb.Database eventMux *event.TypeMux genesisBlock *types.Block + vmConfig *vm.Config mu sync.RWMutex // global mutex for locking chain operations chainmu sync.RWMutex // blockchain insertion lock @@ -162,6 +163,10 @@ func NewBlockChain(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*Bl return bc, nil } +func (self *BlockChain) SetConfig(vmConfig *vm.Config) { + self.vmConfig = vmConfig +} + func (self *BlockChain) getProcInterrupt() bool { return atomic.LoadInt32(&self.procInterrupt) == 1 } @@ -891,7 +896,7 @@ func (self *BlockChain) InsertChain(chain types.Blocks) (int, error) { return i, err } // Process block using the parent state as reference point. - receipts, logs, usedGas, err := self.processor.Process(block, statedb, nil) + receipts, logs, usedGas, err := self.processor.Process(block, statedb, self.vmConfig) if err != nil { reportBlock(block, err) return i, err diff --git a/eth/backend.go b/eth/backend.go index d807f8ae87..4f3e11a50c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/common/registrar/ethreg" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/ethdb" @@ -91,6 +92,9 @@ type Config struct { GpobaseStepUp int GpobaseCorrectionFactor int + EnableJit bool + ForceJit bool + TestGenesisBlock *types.Block // Genesis block to seed the chain database with (testing only!) TestGenesisState ethdb.Database // Genesis state to seed the database with (testing only!) } @@ -225,6 +229,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } //genesis := core.GenesisBlock(uint64(config.GenesisNonce), stateDb) eth.blockchain, err = core.NewBlockChain(chainDb, eth.pow, eth.EventMux()) + eth.blockchain.SetConfig(&vm.Config{ + EnableJit: config.EnableJit, + ForceJit: config.ForceJit, + }) + if err != nil { if err == core.ErrNoGenesis { return nil, fmt.Errorf(`Genesis block not found. Please supply a genesis block with the "--genesis /path/to/file" argument`)