diff --git a/core/store/ledgerstore/ledger_store.go b/core/store/ledgerstore/ledger_store.go index a2fdaede7..f29a071d3 100644 --- a/core/store/ledgerstore/ledger_store.go +++ b/core/store/ledgerstore/ledger_store.go @@ -16,6 +16,7 @@ * along with The ontology. If not, see . */ //Storage of ledger + package ledgerstore import ( @@ -1447,7 +1448,15 @@ func (this *LedgerStoreImp) PreExecuteContract(tx *types.Transaction) (*sstate.P return this.PreExecuteContractWithParam(tx, param) } +func (this *LedgerStoreImp) TraceEip155Tx(msg types3.Message, tracer evm2.Tracer) (*types5.ExecutionResult, error) { + return this.executeEip155Tx(msg, evm2.Config{Debug: true, Tracer: tracer}) +} + func (this *LedgerStoreImp) PreExecuteEip155Tx(msg types3.Message) (*types5.ExecutionResult, error) { + return this.executeEip155Tx(msg, evm2.Config{}) +} + +func (this *LedgerStoreImp) executeEip155Tx(msg types3.Message, conf evm2.Config) (*types5.ExecutionResult, error) { height := this.GetCurrentBlockHeight() // use previous block time to make it predictable for easy test blockTime := uint32(time.Now().Unix()) @@ -1466,7 +1475,7 @@ func (this *LedgerStoreImp) PreExecuteEip155Tx(msg types3.Message) (*types5.Exec blockContext := evm.NewEVMBlockContext(height, blockTime, this) cache := this.GetCacheDB() statedb := storage.NewStateDB(cache, common2.Hash{}, common2.Hash(ctx.BlockHash), ong.OngBalanceHandle{}) - vmenv := evm2.NewEVM(blockContext, txContext, statedb, config, evm2.Config{}) + vmenv := evm2.NewEVM(blockContext, txContext, statedb, config, conf) res, err := evm.ApplyMessage(vmenv, msg, common2.Address(utils.GovernanceContractAddress)) return res, err } diff --git a/core/store/store.go b/core/store/store.go index c04acdec1..e2449c187 100644 --- a/core/store/store.go +++ b/core/store/store.go @@ -32,6 +32,7 @@ import ( types3 "github.com/ontio/ontology/smartcontract/service/evm/types" cstates "github.com/ontio/ontology/smartcontract/states" "github.com/ontio/ontology/smartcontract/storage" + "github.com/ontio/ontology/vm/evm" ) type ExecuteResult struct { @@ -78,6 +79,7 @@ type LedgerStore interface { PreExecuteContract(tx *types.Transaction) (*cstates.PreExecResult, error) PreExecuteContractBatch(txes []*types.Transaction, atomic bool) ([]*cstates.PreExecResult, uint32, error) PreExecuteEip155Tx(msg types2.Message) (*types3.ExecutionResult, error) + TraceEip155Tx(msg types2.Message, tracer evm.Tracer) (*types3.ExecutionResult, error) GetEventNotifyByTx(tx common.Uint256) (*event.ExecuteNotify, error) GetEventNotifyByBlock(height uint32) ([]*event.ExecuteNotify, error) GetEthCode(hash common2.Hash) ([]byte, error) diff --git a/http/ethrpc/debug/tracer.go b/http/ethrpc/debug/tracer.go new file mode 100644 index 000000000..2d9c371c9 --- /dev/null +++ b/http/ethrpc/debug/tracer.go @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology 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 ontology 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 ontology. If not, see . + */ + +package debug + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ontio/ontology/core/ledger" + "github.com/ontio/ontology/http/ethrpc/eth" + types2 "github.com/ontio/ontology/http/ethrpc/types" + "github.com/ontio/ontology/vm/evm" + "github.com/ontio/ontology/vm/evm/tracers" +) + +// DebugAPI is the collection of tracing APIs exposed over the private debugging endpoint. +type DebugAPI struct { +} + +// NewDebugAPI creates a new DebugAPI definition for the tracing methods of the Ethereum service. +func NewDebugAPI() *DebugAPI { + return &DebugAPI{} +} + +// TraceConfig holds extra parameters to trace functions. +type TraceConfig struct { + *evm.LogConfig + Tracer *string + Timeout *string + Reexec *uint64 +} + +// TraceCallConfig is the config for traceCall DebugAPI. It holds one more +// field to override the state for tracing. +type TraceCallConfig struct { + *evm.LogConfig + Tracer *string + Timeout *string + Reexec *uint64 + //StateOverrides *ethapi.StateOverride +} + +// TraceCall lets you trace a given eth_call. It collects the structured logs +// created during the execution of EVM if the given transaction was added on +// top of the provided block and returns them as a JSON object. +// You can provide -2 as a block number to trace on top of the pending block. +func (api *DebugAPI) TraceCall(args types2.CallArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { + // Execute the trace + msg := args.AsMessage(eth.RPCGasCap) + + var traceConfig *TraceConfig + if config != nil { + traceConfig = &TraceConfig{ + LogConfig: config.LogConfig, + Tracer: config.Tracer, + Timeout: config.Timeout, + Reexec: config.Reexec, + } + } + return api.traceTx(msg, traceConfig) +} + +// traceTx configures a new tracer according to the provided configuration, and +// executes the given message in the provided environment. The return value will +// be tracer dependent. +func (api *DebugAPI) traceTx(message types.Message, config *TraceConfig) (interface{}, error) { + // Assemble the structured logger or the JavaScript tracer + var ( + tracer evm.Tracer + err error + ) + switch { + case config == nil: + tracer = evm.NewStructLogger(nil) + case config.Tracer != nil: + switch *config.Tracer { + case "callTracer": + tracer = tracers.NewCallTracer() + default: + return nil, fmt.Errorf("unkown tracer type: %s", *config.Tracer) + } + default: + tracer = evm.NewStructLogger(config.LogConfig) + } + + result, err := ledger.DefLedger.TraceEip155Tx(message, nil) + if err != nil { + return nil, fmt.Errorf("tracing failed: %w", err) + } + + // Depending on the tracer type, format and return the output. + switch tracer := tracer.(type) { + case *evm.StructLogger: + // If the result contains a revert reason, return it. + returnVal := fmt.Sprintf("%x", result.Return()) + if len(result.Revert()) > 0 { + returnVal = fmt.Sprintf("%x", result.Revert()) + } + return &ExecutionResult{ + Gas: result.UsedGas, + Failed: result.Failed(), + ReturnValue: returnVal, + StructLogs: FormatLogs(tracer.StructLogs()), + }, nil + + case *tracers.CallTracer: + return tracer.GetResult() + + default: + panic(fmt.Sprintf("bad tracer type %T", tracer)) + } +} diff --git a/http/ethrpc/debug/types.go b/http/ethrpc/debug/types.go new file mode 100644 index 000000000..630eddcf8 --- /dev/null +++ b/http/ethrpc/debug/types.go @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology 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 ontology 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 ontology. If not, see . + */ + +package debug + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ontio/ontology/vm/evm" +) + +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as transaction +// execution status, the amount of gas used and the return value +type ExecutionResult struct { + Gas uint64 `json:"gas"` + Failed bool `json:"failed"` + 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"` + Op string `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Depth int `json:"depth"` + Error error `json:"error,omitempty"` + Stack *[]string `json:"stack,omitempty"` + Memory *[]string `json:"memory,omitempty"` + Storage *map[string]string `json:"storage,omitempty"` +} + +// FormatLogs formats EVM returned structured logs for json output +func FormatLogs(logs []evm.StructLog) []StructLogRes { + formatted := make([]StructLogRes, len(logs)) + for index, trace := range logs { + formatted[index] = StructLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Depth: trace.Depth, + Error: trace.Err, + } + if trace.Stack != nil { + stack := make([]string, len(trace.Stack)) + for i, stackValue := range trace.Stack { + stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32)) + } + formatted[index].Stack = &stack + } + if trace.Memory != nil { + memory := make([]string, 0, (len(trace.Memory)+31)/32) + for i := 0; i+32 <= len(trace.Memory); i += 32 { + memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + formatted[index].Memory = &memory + } + if trace.Storage != nil { + storage := make(map[string]string) + for i, storageValue := range trace.Storage { + storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + formatted[index].Storage = &storage + } + } + return formatted +} diff --git a/http/ethrpc/rpc_server.go b/http/ethrpc/rpc_server.go index d2771a04a..32880c526 100644 --- a/http/ethrpc/rpc_server.go +++ b/http/ethrpc/rpc_server.go @@ -28,6 +28,7 @@ import ( "github.com/ontio/ontology/core/store/ledgerstore" "github.com/ontio/ontology/http/base/actor" backend2 "github.com/ontio/ontology/http/ethrpc/backend" + "github.com/ontio/ontology/http/ethrpc/debug" "github.com/ontio/ontology/http/ethrpc/eth" filters2 "github.com/ontio/ontology/http/ethrpc/filters" "github.com/ontio/ontology/http/ethrpc/net" @@ -68,6 +69,9 @@ func StartEthServer(txpool *tp.TXPoolServer) error { if err := server.RegisterName("web3", web3.NewAPI()); err != nil { return err } + if err := server.RegisterName("debug", debug.NewDebugAPI()); err != nil { + return err + } // add cors wrapper wrappedCORSHandler := node.NewHTTPHandlerStack(server, cors, vhosts) diff --git a/vm/evm/evm.go b/vm/evm/evm.go index d7b5f8d28..cf2baab93 100644 --- a/vm/evm/evm.go +++ b/vm/evm/evm.go @@ -25,8 +25,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" + common2 "github.com/ontio/ontology/common" "github.com/ontio/ontology/core/types" - "github.com/ontio/ontology/smartcontract/service/native/utils" "github.com/ontio/ontology/vm/evm/errors" "github.com/ontio/ontology/vm/evm/params" ) @@ -554,8 +554,10 @@ func MakeOngTransferLog(stateDB StateDB, from, to common.Address, value *big.Int topic[1] = common.BytesToHash(from[:]) topic[2] = common.BytesToHash(to[:]) val := common.BytesToHash(value.Bytes()) + // copied from smartcontract/service/native/utils to avoid cycle dependencies + OngContractAddress, _ := common2.AddressParseFromBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}) sl := &types.StorageLog{ - Address: common.BytesToAddress(utils.OngContractAddress[:]), + Address: common.BytesToAddress(OngContractAddress[:]), Topics: topic, Data: val[:], } diff --git a/vm/evm/gas_table_test.go b/vm/evm/gas_table_test.go index be54f5f56..ef9b8faf9 100644 --- a/vm/evm/gas_table_test.go +++ b/vm/evm/gas_table_test.go @@ -18,18 +18,9 @@ package evm import ( - "math" - "math/big" "testing" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ontio/ontology/core/store/leveldbstore" - "github.com/ontio/ontology/core/store/overlaydb" - "github.com/ontio/ontology/smartcontract/service/native/ong" - "github.com/ontio/ontology/smartcontract/storage" "github.com/ontio/ontology/vm/evm/errors" - "github.com/ontio/ontology/vm/evm/params" ) func TestMemoryGasCost(t *testing.T) { @@ -51,62 +42,3 @@ func TestMemoryGasCost(t *testing.T) { } } } - -var eip2200Tests = []struct { - original byte - gaspool uint64 - input string - used uint64 - refund uint64 - failure error -}{ - {0, math.MaxUint64, "0x60006000556000600055", 1612, 0, nil}, // 0 -> 0 -> 0 - {0, math.MaxUint64, "0x60006000556001600055", 20812, 0, nil}, // 0 -> 0 -> 1 - {0, math.MaxUint64, "0x60016000556000600055", 20812, 19200, nil}, // 0 -> 1 -> 0 - {0, math.MaxUint64, "0x60016000556002600055", 20812, 0, nil}, // 0 -> 1 -> 2 - {0, math.MaxUint64, "0x60016000556001600055", 20812, 0, nil}, // 0 -> 1 -> 1 - {1, math.MaxUint64, "0x60006000556000600055", 5812, 15000, nil}, // 1 -> 0 -> 0 - {1, math.MaxUint64, "0x60006000556001600055", 5812, 4200, nil}, // 1 -> 0 -> 1 - {1, math.MaxUint64, "0x60006000556002600055", 5812, 0, nil}, // 1 -> 0 -> 2 - {1, math.MaxUint64, "0x60026000556000600055", 5812, 15000, nil}, // 1 -> 2 -> 0 - {1, math.MaxUint64, "0x60026000556003600055", 5812, 0, nil}, // 1 -> 2 -> 3 - {1, math.MaxUint64, "0x60026000556001600055", 5812, 4200, nil}, // 1 -> 2 -> 1 - {1, math.MaxUint64, "0x60026000556002600055", 5812, 0, nil}, // 1 -> 2 -> 2 - {1, math.MaxUint64, "0x60016000556000600055", 5812, 15000, nil}, // 1 -> 1 -> 0 - {1, math.MaxUint64, "0x60016000556002600055", 5812, 0, nil}, // 1 -> 1 -> 2 - {1, math.MaxUint64, "0x60016000556001600055", 1612, 0, nil}, // 1 -> 1 -> 1 - {0, math.MaxUint64, "0x600160005560006000556001600055", 40818, 19200, nil}, // 0 -> 1 -> 0 -> 1 - {1, math.MaxUint64, "0x600060005560016000556000600055", 10818, 19200, nil}, // 1 -> 0 -> 1 -> 0 - {1, 2306, "0x6001600055", 2306, 0, errors.ErrOutOfGas}, // 1 -> 1 (2300 sentry + 2xPUSH) - {1, 2307, "0x6001600055", 806, 0, nil}, // 1 -> 1 (2301 sentry + 2xPUSH) -} - -func TestEIP2200(t *testing.T) { - for i, tt := range eip2200Tests { - address := common.BytesToAddress([]byte("contract")) - - db := storage.NewCacheDB(overlaydb.NewOverlayDB(leveldbstore.NewMemLevelDBStore())) - statedb := storage.NewStateDB(db, common.Hash{}, common.Hash{}, ong.OngBalanceHandle{}) - statedb.CreateAccount(address) - statedb.SetCode(address, hexutil.MustDecode(tt.input)) - statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) - _ = statedb.Commit() // Push the state into the "original" slot - - vmctx := BlockContext{ - CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true }, - Transfer: func(StateDB, common.Address, common.Address, *big.Int) {}, - } - vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) - - _, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int)) - if err != tt.failure { - t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) - } - if used := tt.gaspool - gas; used != tt.used { - t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used) - } - if refund := vmenv.StateDB.GetRefund(); refund != tt.refund { - t.Errorf("test %d: gas refund mismatch: have %v, want %v", i, refund, tt.refund) - } - } -} diff --git a/vm/evm/tests/gas_table_test.go b/vm/evm/tests/gas_table_test.go new file mode 100644 index 000000000..ea85390ec --- /dev/null +++ b/vm/evm/tests/gas_table_test.go @@ -0,0 +1,93 @@ +// Copyright (C) 2021 The Ontology Authors +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package tests + +import ( + "math" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ontio/ontology/core/store/leveldbstore" + "github.com/ontio/ontology/core/store/overlaydb" + "github.com/ontio/ontology/smartcontract/service/native/ong" + "github.com/ontio/ontology/smartcontract/storage" + "github.com/ontio/ontology/vm/evm" + "github.com/ontio/ontology/vm/evm/errors" + "github.com/ontio/ontology/vm/evm/params" +) + +var eip2200Tests = []struct { + original byte + gaspool uint64 + input string + used uint64 + refund uint64 + failure error +}{ + {0, math.MaxUint64, "0x60006000556000600055", 1612, 0, nil}, // 0 -> 0 -> 0 + {0, math.MaxUint64, "0x60006000556001600055", 20812, 0, nil}, // 0 -> 0 -> 1 + {0, math.MaxUint64, "0x60016000556000600055", 20812, 19200, nil}, // 0 -> 1 -> 0 + {0, math.MaxUint64, "0x60016000556002600055", 20812, 0, nil}, // 0 -> 1 -> 2 + {0, math.MaxUint64, "0x60016000556001600055", 20812, 0, nil}, // 0 -> 1 -> 1 + {1, math.MaxUint64, "0x60006000556000600055", 5812, 15000, nil}, // 1 -> 0 -> 0 + {1, math.MaxUint64, "0x60006000556001600055", 5812, 4200, nil}, // 1 -> 0 -> 1 + {1, math.MaxUint64, "0x60006000556002600055", 5812, 0, nil}, // 1 -> 0 -> 2 + {1, math.MaxUint64, "0x60026000556000600055", 5812, 15000, nil}, // 1 -> 2 -> 0 + {1, math.MaxUint64, "0x60026000556003600055", 5812, 0, nil}, // 1 -> 2 -> 3 + {1, math.MaxUint64, "0x60026000556001600055", 5812, 4200, nil}, // 1 -> 2 -> 1 + {1, math.MaxUint64, "0x60026000556002600055", 5812, 0, nil}, // 1 -> 2 -> 2 + {1, math.MaxUint64, "0x60016000556000600055", 5812, 15000, nil}, // 1 -> 1 -> 0 + {1, math.MaxUint64, "0x60016000556002600055", 5812, 0, nil}, // 1 -> 1 -> 2 + {1, math.MaxUint64, "0x60016000556001600055", 1612, 0, nil}, // 1 -> 1 -> 1 + {0, math.MaxUint64, "0x600160005560006000556001600055", 40818, 19200, nil}, // 0 -> 1 -> 0 -> 1 + {1, math.MaxUint64, "0x600060005560016000556000600055", 10818, 19200, nil}, // 1 -> 0 -> 1 -> 0 + {1, 2306, "0x6001600055", 2306, 0, errors.ErrOutOfGas}, // 1 -> 1 (2300 sentry + 2xPUSH) + {1, 2307, "0x6001600055", 806, 0, nil}, // 1 -> 1 (2301 sentry + 2xPUSH) +} + +func TestEIP2200(t *testing.T) { + for i, tt := range eip2200Tests { + address := common.BytesToAddress([]byte("contract")) + + db := storage.NewCacheDB(overlaydb.NewOverlayDB(leveldbstore.NewMemLevelDBStore())) + statedb := storage.NewStateDB(db, common.Hash{}, common.Hash{}, ong.OngBalanceHandle{}) + statedb.CreateAccount(address) + statedb.SetCode(address, hexutil.MustDecode(tt.input)) + statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) + _ = statedb.Commit() // Push the state into the "original" slot + + vmctx := evm.BlockContext{ + CanTransfer: func(evm.StateDB, common.Address, *big.Int) bool { return true }, + Transfer: func(evm.StateDB, common.Address, common.Address, *big.Int) {}, + } + vmenv := evm.NewEVM(vmctx, evm.TxContext{}, statedb, params.AllEthashProtocolChanges, evm.Config{ExtraEips: []int{2200}}) + + _, gas, err := vmenv.Call(evm.AccountRef(common.Address{}), address, nil, tt.gaspool, new(big.Int)) + if err != tt.failure { + t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) + } + if used := tt.gaspool - gas; used != tt.used { + t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used) + } + if refund := vmenv.StateDB.GetRefund(); refund != tt.refund { + t.Errorf("test %d: gas refund mismatch: have %v, want %v", i, refund, tt.refund) + } + } +}