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 ef679e373..0ba6820e0 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 24c0c93ae..d90dceafc 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 395ed0471..f73bc1527 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 d5d21a45b..3c530962b 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 1e1086b13..c4b4339a2 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 19261827b..5fac0156f 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 37d7bb160..8297d3e1d 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 98d34bef2..2ed1b26fc 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 77519df81..e9bf828ea 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 95d27c64c..26df8aef4 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 456fcb8d4..192f3615d 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 7b9a1a0f9..0fab4a090 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 29f4c9b72..2c749edba 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 | 113 ++++++++++++++------------- core/vm/vm_jit_fake.go | 2 +- core/vm_env.go | 39 ++++++---- eth/api.go | 11 +-- miner/worker.go | 2 +- tests/state_test_util.go | 1 - tests/util.go | 11 ++- 30 files changed, 408 insertions(+), 230 deletions(-) create mode 100644 core/vm/logger_test.go diff --git a/cmd/ethtest/main.go b/cmd/ethtest/main.go index 67b965396..e19dca86b 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 0ba6820e0..2cc70d81b 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 68e09912b..a21fe71b5 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 d2f76eaa6..55daa63d7 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 69fb0b9db..3b05c2963 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 79a6c2191..60a97f4ce 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 534318ecd..cecb914a8 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 df979578e..d7cd24fa8 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 0e1ca5fff..7ae3c98b0 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 0f86325c6..326708118 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 3ca36a43a..38cd0e675 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 2887f6228..cc357aaca 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 022528374..af9bc567b 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 f73bc1527..2878b92d2 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 d23995218..59e8f1aa6 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 3c530962b..568218edd 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 5fac0156f..43b1dee2a 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 2bd02319f..8d333dfd2 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 000000000..77fee2c64 --- /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 e9bf828ea..ce64d7117 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 3e6057142..f88a20170 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 e5183052f..88c76c731 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 9b3340bb2..629108884 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 26df8aef4..f72c853a2 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 { - 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 -} diff --git a/core/vm/vm_jit_fake.go b/core/vm/vm_jit_fake.go index 192f3615d..b26cf1ad0 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 0fab4a090..880baa7b0 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 + 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 - 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 { +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 487d24ae7..beae3aabb 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 f3e95cb5f..a5eec2601 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 7d1701ff2..50be3a1ac 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 2c749edba..a0eb8158e 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 | 288 +++++++++++++++++++++++++++++++--------------- rpc/javascript.go | 27 ++++- 2 files changed, 218 insertions(+), 97 deletions(-) diff --git a/eth/api.go b/eth/api.go index beae3aabb..fbaa280f4 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 -} - -// 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 { - return nil, err - } - - res := TransactionExecutionResult{ + return &ExecutionResult{ Gas: gas, ReturnValue: fmt.Sprintf("%x", ret), - StructLogs: make([]structLogRes, len(structLogs)), + StructLogs: formatLogs(vmenv.StructLogs()), + }, 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() + + // 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) + + // 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) } - for index, trace := range structLogs { + // Execute the call and return + vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header(), nil) + gp := new(core.GasPool).AddGas(common.MaxBig) - 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) - } - - 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++ - } - - storageLength := len(trace.Stack) - if storageSize != -1 && storageSize < storageLength { - storageLength = storageSize - } - - 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 72290a2a6..c4fa80c0b 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 fbaa280f4..6a6ab6f43 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 3b05c2963..10aa48735 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 cecb914a8..2c3c2bb5c 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 d807f8ae8..4f3e11a50 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`)