aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/evm/runner.go2
-rw-r--r--cmd/evm/staterunner.go2
-rw-r--r--core/vm/logger_json.go (renamed from cmd/evm/json_logger.go)13
-rw-r--r--eth/api_tracer.go141
-rw-r--r--internal/web3ext/web3ext.go10
5 files changed, 148 insertions, 20 deletions
diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go
index a9c8a38ca..54b67ce10 100644
--- a/cmd/evm/runner.go
+++ b/cmd/evm/runner.go
@@ -89,7 +89,7 @@ func runCmd(ctx *cli.Context) error {
genesisConfig *core.Genesis
)
if ctx.GlobalBool(MachineFlag.Name) {
- tracer = NewJSONLogger(logconfig, os.Stdout)
+ tracer = vm.NewJSONLogger(logconfig, os.Stdout)
} else if ctx.GlobalBool(DebugFlag.Name) {
debugLogger = vm.NewStructLogger(logconfig)
tracer = debugLogger
diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go
index 06c9be380..b3c69d9b9 100644
--- a/cmd/evm/staterunner.go
+++ b/cmd/evm/staterunner.go
@@ -68,7 +68,7 @@ func stateTestCmd(ctx *cli.Context) error {
)
switch {
case ctx.GlobalBool(MachineFlag.Name):
- tracer = NewJSONLogger(config, os.Stderr)
+ tracer = vm.NewJSONLogger(config, os.Stderr)
case ctx.GlobalBool(DebugFlag.Name):
debugger = vm.NewStructLogger(config)
diff --git a/cmd/evm/json_logger.go b/core/vm/logger_json.go
index 50cb4f0e4..ac3c40759 100644
--- a/cmd/evm/json_logger.go
+++ b/core/vm/logger_json.go
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
-package main
+package vm
import (
"encoding/json"
@@ -24,17 +24,16 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/core/vm"
)
type JSONLogger struct {
encoder *json.Encoder
- cfg *vm.LogConfig
+ cfg *LogConfig
}
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream.
-func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
+func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger {
return &JSONLogger{json.NewEncoder(writer), cfg}
}
@@ -43,8 +42,8 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create
}
// CaptureState outputs state information on the logger.
-func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
- log := vm.StructLog{
+func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
+ log := StructLog{
Pc: pc,
Op: op,
Gas: gas,
@@ -65,7 +64,7 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
}
// CaptureFault outputs state information on the logger.
-func (l *JSONLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
+func (l *JSONLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
return nil
}
diff --git a/eth/api_tracer.go b/eth/api_tracer.go
index 2ebbcc5fd..77d52d944 100644
--- a/eth/api_tracer.go
+++ b/eth/api_tracer.go
@@ -17,11 +17,13 @@
package eth
import (
+ "bufio"
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
+ "os"
"runtime"
"sync"
"time"
@@ -60,6 +62,13 @@ type TraceConfig struct {
Reexec *uint64
}
+// StdTraceConfig holds extra parameters to standard-json trace functions.
+type StdTraceConfig struct {
+ *vm.LogConfig
+ Reexec *uint64
+ TxHash *common.Hash
+}
+
// txTraceResult is the result of a single transaction trace.
type txTraceResult struct {
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
@@ -391,13 +400,37 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string,
return api.TraceBlock(ctx, blob, config)
}
-// TraceBadBlock returns the structured logs created during the execution of a block
-// within the blockchain 'badblocks' cache
-func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, index int, config *TraceConfig) ([]*txTraceResult, error) {
- if blocks := api.eth.blockchain.BadBlocks(); index < len(blocks) {
- return api.traceBlock(ctx, blocks[index], config)
+// TraceBadBlockByHash returns the structured logs created during the execution of a block
+func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, blockHash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
+ blocks := api.eth.blockchain.BadBlocks()
+ for _, block := range blocks {
+ if block.Hash() == blockHash {
+ return api.traceBlock(ctx, block, config)
+ }
+ }
+ return nil, fmt.Errorf("hash not found among bad blocks")
+}
+
+// StandardTraceBadBlockToFile dumps the standard-json logs to files on the local filesystem,
+// and returns a list of files to the caller.
+func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, blockHash common.Hash, stdConfig *StdTraceConfig) ([]string, error) {
+ blocks := api.eth.blockchain.BadBlocks()
+ for _, block := range blocks {
+ if block.Hash() == blockHash {
+ return api.standardTraceBlockToFile(ctx, block, stdConfig)
+ }
}
- return nil, fmt.Errorf("index out of range")
+ return nil, fmt.Errorf("hash not found among bad blocks")
+}
+
+// StandardTraceBlockToFile dumps the standard-json logs to files on the local filesystem,
+// and returns a list of files to the caller.
+func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, blockHash common.Hash, stdConfig *StdTraceConfig) ([]string, error) {
+ block := api.eth.blockchain.GetBlockByHash(blockHash)
+ if block == nil {
+ return nil, fmt.Errorf("block #%x not found", blockHash)
+ }
+ return api.standardTraceBlockToFile(ctx, block, stdConfig)
}
// traceBlock configures a new tracer according to the provided configuration, and
@@ -481,6 +514,92 @@ func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block,
return results, nil
}
+// standardTraceBlockToFile configures a new tracer which uses standard-json output, and
+// traces either a full block or an individual transaction. The return value will be one filename
+// per transaction traced.
+func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block *types.Block, stdConfig *StdTraceConfig) ([]string, error) {
+ // Create the parent state database
+ if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
+ return nil, err
+ }
+ parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
+ if parent == nil {
+ return nil, fmt.Errorf("parent %x not found", block.ParentHash())
+ }
+ var (
+ signer = types.MakeSigner(api.config, block.Number())
+ done = false
+ blockPrefix = fmt.Sprintf("block_0x%x", block.Hash().Bytes()[:4])
+ usedLogConfig = &vm.LogConfig{Debug: true}
+ files []string
+ reExec_val = defaultTraceReexec
+ txHash *common.Hash
+ )
+ if stdConfig != nil {
+ if stdConfig.Reexec != nil {
+ reExec_val = *stdConfig.Reexec
+ }
+ if stdConfig.LogConfig != nil {
+ usedLogConfig.DisableMemory = stdConfig.LogConfig.DisableMemory
+ usedLogConfig.DisableStack = stdConfig.LogConfig.DisableStack
+ usedLogConfig.DisableStorage = stdConfig.LogConfig.DisableStorage
+ usedLogConfig.Limit = stdConfig.LogConfig.Limit
+ }
+ txHash = stdConfig.TxHash
+ }
+ statedb, err := api.computeStateDB(parent, reExec_val)
+ if err != nil {
+ return nil, err
+ }
+
+ for i, tx := range block.Transactions() {
+ var (
+ outfile *os.File
+ err error
+ )
+ msg, _ := tx.AsMessage(signer)
+ vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
+ vmConf := vm.Config{}
+ if txHash == nil || bytes.Equal(txHash.Bytes(), tx.Hash().Bytes()) {
+ prefix := fmt.Sprintf("%v-%d-0x%x-", blockPrefix, i, tx.Hash().Bytes()[:4])
+ // Open a file to dump trace into
+ outfile, err = ioutil.TempFile(os.TempDir(), prefix)
+ if err != nil {
+ return nil, err
+ }
+ files = append(files, outfile.Name())
+ vmConf = vm.Config{
+ Debug: true,
+ Tracer: vm.NewJSONLogger(usedLogConfig, bufio.NewWriter(outfile)),
+ EnablePreimageRecording: true,
+ }
+ if txHash != nil { // Only one tx to trace
+ done = true
+ }
+ }
+ vmenv := vm.NewEVM(vmctx, statedb, api.config, vmConf)
+ _, _, _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()))
+
+ if outfile != nil {
+ outfile.Close()
+ log.Info("Wrote trace", "file", outfile.Name())
+ }
+ if err != nil {
+ return files, err
+ }
+ // Finalize the state so any modifications are written to the trie
+ statedb.Finalise(true)
+
+ if done {
+ break
+ }
+ }
+ if txHash != nil && !done {
+ return nil, fmt.Errorf("transaction hash not found in block")
+ }
+ return files, nil
+}
+
// computeStateDB retrieves the state database associated with a certain block.
// If no state is locally available for the given block, a number of blocks are
// attempted to be reexecuted to generate the desired state.
@@ -506,7 +625,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
if err != nil {
switch err.(type) {
case *trie.MissingNodeError:
- return nil, errors.New("required historical state unavailable")
+ return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
default:
return nil, err
}
@@ -520,7 +639,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
for block.NumberU64() < origin {
// Print progress logs if long enough time elapsed
if time.Since(logged) > 8*time.Second {
- log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "elapsed", time.Since(start))
+ log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "remaining", origin-block.NumberU64()-1, "elapsed", time.Since(start))
logged = time.Now()
}
// Retrieve the next block to regenerate and process it
@@ -529,15 +648,15 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
}
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("processing block %d failed: %v", block.NumberU64(), err)
}
// Finalize the state so any modifications are written to the trie
- root, err := statedb.Commit(true)
+ root, err := statedb.Commit(api.eth.blockchain.Config().IsEIP158(block.Number()))
if err != nil {
return nil, err
}
if err := statedb.Reset(root); err != nil {
- return nil, err
+ return nil, fmt.Errorf("state reset after block %d failed: %v", block.NumberU64(), err)
}
database.TrieDB().Reference(root, common.Hash{})
if proot != (common.Hash{}) {
diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go
index a5f319653..f36835d8a 100644
--- a/internal/web3ext/web3ext.go
+++ b/internal/web3ext/web3ext.go
@@ -385,6 +385,16 @@ web3._extend({
inputFormatter: [null]
}),
new web3._extend.Method({
+ name: 'standardTraceBadBlockToFile',
+ call: 'debug_standardTraceBadBlockToFile',
+ params: 2,
+ }),
+ new web3._extend.Method({
+ name: 'standardTraceBlockToFile',
+ call: 'debug_standardTraceBlockToFile',
+ params: 2,
+ }),
+ new web3._extend.Method({
name: 'traceBlockByNumber',
call: 'debug_traceBlockByNumber',
params: 2,