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 }