github.com/0xPolygon/supernets2-node@v0.0.0-20230711153321-2fe574524eaa/jsonrpc/endpoints_debug.go (about)

     1  package jsonrpc
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/0xPolygon/supernets2-node/jsonrpc/types"
    12  	"github.com/0xPolygon/supernets2-node/log"
    13  	"github.com/0xPolygon/supernets2-node/state"
    14  	"github.com/0xPolygon/supernets2-node/state/runtime/instrumentation"
    15  	"github.com/ethereum/go-ethereum/common"
    16  	ethTypes "github.com/ethereum/go-ethereum/core/types"
    17  	"github.com/jackc/pgx/v4"
    18  )
    19  
    20  var defaultTraceConfig = &traceConfig{
    21  	DisableStorage:   false,
    22  	DisableStack:     false,
    23  	EnableMemory:     false,
    24  	EnableReturnData: false,
    25  	Tracer:           nil,
    26  }
    27  
    28  // DebugEndpoints is the debug jsonrpc endpoint
    29  type DebugEndpoints struct {
    30  	state types.StateInterface
    31  	txMan DBTxManager
    32  }
    33  
    34  // NewDebugEndpoints returns DebugEndpoints
    35  func NewDebugEndpoints(state types.StateInterface) *DebugEndpoints {
    36  	return &DebugEndpoints{
    37  		state: state,
    38  	}
    39  }
    40  
    41  type traceConfig struct {
    42  	DisableStorage   bool            `json:"disableStorage"`
    43  	DisableStack     bool            `json:"disableStack"`
    44  	EnableMemory     bool            `json:"enableMemory"`
    45  	EnableReturnData bool            `json:"enableReturnData"`
    46  	Tracer           *string         `json:"tracer"`
    47  	TracerConfig     json.RawMessage `json:"tracerConfig"`
    48  }
    49  
    50  // StructLogRes represents the debug trace information for each opcode
    51  type StructLogRes struct {
    52  	Pc            uint64             `json:"pc"`
    53  	Op            string             `json:"op"`
    54  	Gas           uint64             `json:"gas"`
    55  	GasCost       uint64             `json:"gasCost"`
    56  	Depth         int                `json:"depth"`
    57  	Error         string             `json:"error,omitempty"`
    58  	Stack         *[]types.ArgBig    `json:"stack,omitempty"`
    59  	Memory        *[]string          `json:"memory,omitempty"`
    60  	Storage       *map[string]string `json:"storage,omitempty"`
    61  	RefundCounter uint64             `json:"refund,omitempty"`
    62  }
    63  
    64  type traceTransactionResponse struct {
    65  	Gas         uint64         `json:"gas"`
    66  	Failed      bool           `json:"failed"`
    67  	ReturnValue interface{}    `json:"returnValue"`
    68  	StructLogs  []StructLogRes `json:"structLogs"`
    69  }
    70  
    71  type traceBlockTransactionResponse struct {
    72  	Result interface{} `json:"result"`
    73  }
    74  
    75  // TraceTransaction creates a response for debug_traceTransaction request.
    76  // See https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracetransaction
    77  func (d *DebugEndpoints) TraceTransaction(hash types.ArgHash, cfg *traceConfig) (interface{}, types.Error) {
    78  	return d.txMan.NewDbTxScope(d.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) {
    79  		return d.buildTraceTransaction(ctx, hash.Hash(), cfg, dbTx)
    80  	})
    81  }
    82  
    83  // TraceBlockByNumber creates a response for debug_traceBlockByNumber request.
    84  // See https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtraceblockbynumber
    85  func (d *DebugEndpoints) TraceBlockByNumber(number types.BlockNumber, cfg *traceConfig) (interface{}, types.Error) {
    86  	return d.txMan.NewDbTxScope(d.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) {
    87  		blockNumber, rpcErr := number.GetNumericBlockNumber(ctx, d.state, dbTx)
    88  		if rpcErr != nil {
    89  			return nil, rpcErr
    90  		}
    91  
    92  		block, err := d.state.GetL2BlockByNumber(ctx, blockNumber, dbTx)
    93  		if errors.Is(err, state.ErrNotFound) {
    94  			return nil, types.NewRPCError(types.DefaultErrorCode, fmt.Sprintf("block #%d not found", blockNumber))
    95  		} else if err == state.ErrNotFound {
    96  			return RPCErrorResponse(types.DefaultErrorCode, "failed to get block by number", err)
    97  		}
    98  
    99  		traces, rpcErr := d.buildTraceBlock(ctx, block.Transactions(), cfg, dbTx)
   100  		if err != nil {
   101  			return nil, rpcErr
   102  		}
   103  
   104  		return traces, nil
   105  	})
   106  }
   107  
   108  // TraceBlockByHash creates a response for debug_traceBlockByHash request.
   109  // See https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtraceblockbyhash
   110  func (d *DebugEndpoints) TraceBlockByHash(hash types.ArgHash, cfg *traceConfig) (interface{}, types.Error) {
   111  	return d.txMan.NewDbTxScope(d.state, func(ctx context.Context, dbTx pgx.Tx) (interface{}, types.Error) {
   112  		block, err := d.state.GetL2BlockByHash(ctx, hash.Hash(), dbTx)
   113  		if errors.Is(err, state.ErrNotFound) {
   114  			return nil, types.NewRPCError(types.DefaultErrorCode, fmt.Sprintf("block %s not found", hash.Hash().String()))
   115  		} else if err == state.ErrNotFound {
   116  			return RPCErrorResponse(types.DefaultErrorCode, "failed to get block by hash", err)
   117  		}
   118  
   119  		traces, rpcErr := d.buildTraceBlock(ctx, block.Transactions(), cfg, dbTx)
   120  		if err != nil {
   121  			return nil, rpcErr
   122  		}
   123  
   124  		return traces, nil
   125  	})
   126  }
   127  
   128  func (d *DebugEndpoints) buildTraceBlock(ctx context.Context, txs []*ethTypes.Transaction, cfg *traceConfig, dbTx pgx.Tx) (interface{}, types.Error) {
   129  	traces := []traceBlockTransactionResponse{}
   130  	for _, tx := range txs {
   131  		traceTransaction, err := d.buildTraceTransaction(ctx, tx.Hash(), cfg, dbTx)
   132  		if err != nil {
   133  			errMsg := fmt.Sprintf("failed to get trace for transaction %v", tx.Hash().String())
   134  			return RPCErrorResponse(types.DefaultErrorCode, errMsg, err)
   135  		}
   136  		traceBlockTransaction := traceBlockTransactionResponse{
   137  			Result: traceTransaction,
   138  		}
   139  		traces = append(traces, traceBlockTransaction)
   140  	}
   141  
   142  	return traces, nil
   143  }
   144  
   145  func (d *DebugEndpoints) buildTraceTransaction(ctx context.Context, hash common.Hash, cfg *traceConfig, dbTx pgx.Tx) (interface{}, types.Error) {
   146  	traceCfg := cfg
   147  	if traceCfg == nil {
   148  		traceCfg = defaultTraceConfig
   149  	}
   150  
   151  	// check tracer
   152  	if traceCfg.Tracer != nil && *traceCfg.Tracer != "" && !isBuiltInTracer(*traceCfg.Tracer) && !isJSCustomTracer(*traceCfg.Tracer) {
   153  		return RPCErrorResponse(types.DefaultErrorCode, "invalid tracer", nil)
   154  	}
   155  
   156  	stateTraceConfig := state.TraceConfig{
   157  		DisableStack:     traceCfg.DisableStack,
   158  		DisableStorage:   traceCfg.DisableStorage,
   159  		EnableMemory:     traceCfg.EnableMemory,
   160  		EnableReturnData: traceCfg.EnableReturnData,
   161  		Tracer:           traceCfg.Tracer,
   162  		TracerConfig:     traceCfg.TracerConfig,
   163  	}
   164  	result, err := d.state.DebugTransaction(ctx, hash, stateTraceConfig, dbTx)
   165  	if errors.Is(err, state.ErrNotFound) {
   166  		return RPCErrorResponse(types.DefaultErrorCode, "transaction not found", nil)
   167  	} else if err != nil {
   168  		const errorMessage = "failed to get trace"
   169  		log.Errorf("%v: %v", errorMessage, err)
   170  		return nil, types.NewRPCError(types.DefaultErrorCode, errorMessage)
   171  	}
   172  
   173  	// if a tracer was specified, then return the trace result
   174  	if stateTraceConfig.Tracer != nil && *stateTraceConfig.Tracer != "" && len(result.ExecutorTraceResult) > 0 {
   175  		return result.ExecutorTraceResult, nil
   176  	}
   177  
   178  	receipt, err := d.state.GetTransactionReceipt(ctx, hash, dbTx)
   179  	if err != nil {
   180  		const errorMessage = "failed to tx receipt"
   181  		log.Errorf("%v: %v", errorMessage, err)
   182  		return nil, types.NewRPCError(types.DefaultErrorCode, errorMessage)
   183  	}
   184  
   185  	failed := receipt.Status == ethTypes.ReceiptStatusFailed
   186  	var returnValue interface{}
   187  	if stateTraceConfig.EnableReturnData {
   188  		returnValue = common.Bytes2Hex(result.ReturnValue)
   189  	}
   190  
   191  	structLogs := d.buildStructLogs(result.StructLogs, *traceCfg)
   192  
   193  	resp := traceTransactionResponse{
   194  		Gas:         result.GasUsed,
   195  		Failed:      failed,
   196  		ReturnValue: returnValue,
   197  		StructLogs:  structLogs,
   198  	}
   199  
   200  	return resp, nil
   201  }
   202  
   203  func (d *DebugEndpoints) buildStructLogs(stateStructLogs []instrumentation.StructLog, cfg traceConfig) []StructLogRes {
   204  	structLogs := make([]StructLogRes, 0, len(stateStructLogs))
   205  	for _, structLog := range stateStructLogs {
   206  		errRes := ""
   207  		if structLog.Err != nil {
   208  			errRes = structLog.Err.Error()
   209  		}
   210  
   211  		op := structLog.Op
   212  		if op == "SHA3" {
   213  			op = "KECCAK256"
   214  		}
   215  
   216  		structLogRes := StructLogRes{
   217  			Pc:            structLog.Pc,
   218  			Op:            op,
   219  			Gas:           structLog.Gas,
   220  			GasCost:       structLog.GasCost,
   221  			Depth:         structLog.Depth,
   222  			Error:         errRes,
   223  			RefundCounter: structLog.RefundCounter,
   224  		}
   225  
   226  		if !cfg.DisableStack {
   227  			stack := make([]types.ArgBig, 0, len(structLog.Stack))
   228  			for _, stackItem := range structLog.Stack {
   229  				if stackItem != nil {
   230  					stack = append(stack, types.ArgBig(*stackItem))
   231  				}
   232  			}
   233  			structLogRes.Stack = &stack
   234  		}
   235  
   236  		if cfg.EnableMemory {
   237  			const memoryChunkSize = 32
   238  			memory := make([]string, 0, len(structLog.Memory))
   239  			for i := 0; i < len(structLog.Memory); i = i + memoryChunkSize {
   240  				slice32Bytes := make([]byte, memoryChunkSize)
   241  				copy(slice32Bytes, structLog.Memory[i:i+memoryChunkSize])
   242  				memoryStringItem := hex.EncodeToString(slice32Bytes)
   243  				memory = append(memory, memoryStringItem)
   244  			}
   245  			structLogRes.Memory = &memory
   246  		}
   247  
   248  		if !cfg.DisableStorage && len(structLog.Storage) > 0 {
   249  			storage := make(map[string]string, len(structLog.Storage))
   250  			for storageKey, storageValue := range structLog.Storage {
   251  				k := hex.EncodeToString(storageKey.Bytes())
   252  				v := hex.EncodeToString(storageValue.Bytes())
   253  				storage[k] = v
   254  			}
   255  			structLogRes.Storage = &storage
   256  		}
   257  
   258  		structLogs = append(structLogs, structLogRes)
   259  	}
   260  	return structLogs
   261  }
   262  
   263  // isBuiltInTracer checks if the tracer is one of the
   264  // built-in tracers
   265  func isBuiltInTracer(tracer string) bool {
   266  	// built-in tracers
   267  	switch tracer {
   268  	case "callTracer", "4byteTracer", "prestateTracer", "noopTracer":
   269  		return true
   270  	default:
   271  		return false
   272  	}
   273  }
   274  
   275  // isJSCustomTracer checks if the tracer contains the
   276  // functions result and fault which are required for a custom tracer
   277  // https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer
   278  func isJSCustomTracer(tracer string) bool {
   279  	return strings.Contains(tracer, "result") && strings.Contains(tracer, "fault")
   280  }