forked from cerc-io/plugeth
cmd/evm, core/vm, eth: implement api methods to do stdjson dump to local filesystem
This commit is contained in:
parent
09d588e0da
commit
42a914a84f
@ -89,7 +89,7 @@ func runCmd(ctx *cli.Context) error {
|
|||||||
genesisConfig *core.Genesis
|
genesisConfig *core.Genesis
|
||||||
)
|
)
|
||||||
if ctx.GlobalBool(MachineFlag.Name) {
|
if ctx.GlobalBool(MachineFlag.Name) {
|
||||||
tracer = NewJSONLogger(logconfig, os.Stdout)
|
tracer = vm.NewJSONLogger(logconfig, os.Stdout)
|
||||||
} else if ctx.GlobalBool(DebugFlag.Name) {
|
} else if ctx.GlobalBool(DebugFlag.Name) {
|
||||||
debugLogger = vm.NewStructLogger(logconfig)
|
debugLogger = vm.NewStructLogger(logconfig)
|
||||||
tracer = debugLogger
|
tracer = debugLogger
|
||||||
|
@ -68,7 +68,7 @@ func stateTestCmd(ctx *cli.Context) error {
|
|||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case ctx.GlobalBool(MachineFlag.Name):
|
case ctx.GlobalBool(MachineFlag.Name):
|
||||||
tracer = NewJSONLogger(config, os.Stderr)
|
tracer = vm.NewJSONLogger(config, os.Stderr)
|
||||||
|
|
||||||
case ctx.GlobalBool(DebugFlag.Name):
|
case ctx.GlobalBool(DebugFlag.Name):
|
||||||
debugger = vm.NewStructLogger(config)
|
debugger = vm.NewStructLogger(config)
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package main
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -24,17 +24,16 @@ import (
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type JSONLogger struct {
|
type JSONLogger struct {
|
||||||
encoder *json.Encoder
|
encoder *json.Encoder
|
||||||
cfg *vm.LogConfig
|
cfg *LogConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
|
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
|
||||||
// into the provided stream.
|
// 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}
|
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.
|
// 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 {
|
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 := vm.StructLog{
|
log := StructLog{
|
||||||
Pc: pc,
|
Pc: pc,
|
||||||
Op: op,
|
Op: op,
|
||||||
Gas: gas,
|
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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -17,11 +17,13 @@
|
|||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -60,6 +62,13 @@ type TraceConfig struct {
|
|||||||
Reexec *uint64
|
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.
|
// txTraceResult is the result of a single transaction trace.
|
||||||
type txTraceResult struct {
|
type txTraceResult struct {
|
||||||
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
|
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)
|
return api.TraceBlock(ctx, blob, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceBadBlock returns the structured logs created during the execution of a block
|
// TraceBadBlockByHash returns the structured logs created during the execution of a block
|
||||||
// within the blockchain 'badblocks' cache
|
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, blockHash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, index int, config *TraceConfig) ([]*txTraceResult, error) {
|
blocks := api.eth.blockchain.BadBlocks()
|
||||||
if blocks := api.eth.blockchain.BadBlocks(); index < len(blocks) {
|
for _, block := range blocks {
|
||||||
return api.traceBlock(ctx, blocks[index], config)
|
if block.Hash() == blockHash {
|
||||||
|
return api.traceBlock(ctx, block, config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("index out of range")
|
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("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
|
// 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
|
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.
|
// 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
|
// If no state is locally available for the given block, a number of blocks are
|
||||||
// attempted to be reexecuted to generate the desired state.
|
// 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 {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case *trie.MissingNodeError:
|
case *trie.MissingNodeError:
|
||||||
return nil, errors.New("required historical state unavailable")
|
return nil, fmt.Errorf("required historical state unavailable (reexec=%d)", reexec)
|
||||||
default:
|
default:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -520,7 +639,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
|
|||||||
for block.NumberU64() < origin {
|
for block.NumberU64() < origin {
|
||||||
// Print progress logs if long enough time elapsed
|
// Print progress logs if long enough time elapsed
|
||||||
if time.Since(logged) > 8*time.Second {
|
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()
|
logged = time.Now()
|
||||||
}
|
}
|
||||||
// Retrieve the next block to regenerate and process it
|
// 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{})
|
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
|
||||||
if err != nil {
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := statedb.Reset(root); err != nil {
|
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{})
|
database.TrieDB().Reference(root, common.Hash{})
|
||||||
if proot != (common.Hash{}) {
|
if proot != (common.Hash{}) {
|
||||||
|
@ -384,6 +384,16 @@ web3._extend({
|
|||||||
params: 1,
|
params: 1,
|
||||||
inputFormatter: [null]
|
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({
|
new web3._extend.Method({
|
||||||
name: 'traceBlockByNumber',
|
name: 'traceBlockByNumber',
|
||||||
call: 'debug_traceBlockByNumber',
|
call: 'debug_traceBlockByNumber',
|
||||||
|
Loading…
Reference in New Issue
Block a user