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)