github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/eth/tracers/logger/logger.go (about) 1 // Copyright 2021 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package logger 18 19 import ( 20 "encoding/hex" 21 "encoding/json" 22 "fmt" 23 "io" 24 "math/big" 25 "strings" 26 "sync/atomic" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/common/hexutil" 30 "github.com/ethereum/go-ethereum/common/math" 31 "github.com/ethereum/go-ethereum/core/tracing" 32 "github.com/ethereum/go-ethereum/core/types" 33 "github.com/ethereum/go-ethereum/core/vm" 34 "github.com/ethereum/go-ethereum/params" 35 "github.com/holiman/uint256" 36 ) 37 38 // Storage represents a contract's storage. 39 type Storage map[common.Hash]common.Hash 40 41 // Copy duplicates the current storage. 42 func (s Storage) Copy() Storage { 43 cpy := make(Storage, len(s)) 44 for key, value := range s { 45 cpy[key] = value 46 } 47 return cpy 48 } 49 50 // Config are the configuration options for structured logger the EVM 51 type Config struct { 52 EnableMemory bool // enable memory capture 53 DisableStack bool // disable stack capture 54 DisableStorage bool // disable storage capture 55 EnableReturnData bool // enable return data capture 56 Debug bool // print output during capture end 57 Limit int // maximum length of output, but zero means unlimited 58 // Chain overrides, can be used to execute a trace using future fork rules 59 Overrides *params.ChainConfig `json:"overrides,omitempty"` 60 } 61 62 //go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go 63 64 // StructLog is emitted to the EVM each cycle and lists information about the current internal state 65 // prior to the execution of the statement. 66 type StructLog struct { 67 Pc uint64 `json:"pc"` 68 Op vm.OpCode `json:"op"` 69 Gas uint64 `json:"gas"` 70 GasCost uint64 `json:"gasCost"` 71 Memory []byte `json:"memory,omitempty"` 72 MemorySize int `json:"memSize"` 73 Stack []uint256.Int `json:"stack"` 74 ReturnData []byte `json:"returnData,omitempty"` 75 Storage map[common.Hash]common.Hash `json:"-"` 76 Depth int `json:"depth"` 77 RefundCounter uint64 `json:"refund"` 78 Err error `json:"-"` 79 } 80 81 // overrides for gencodec 82 type structLogMarshaling struct { 83 Gas math.HexOrDecimal64 84 GasCost math.HexOrDecimal64 85 Memory hexutil.Bytes 86 ReturnData hexutil.Bytes 87 Stack []hexutil.U256 88 OpName string `json:"opName"` // adds call to OpName() in MarshalJSON 89 ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON 90 } 91 92 // OpName formats the operand name in a human-readable format. 93 func (s *StructLog) OpName() string { 94 return s.Op.String() 95 } 96 97 // ErrorString formats the log's error as a string. 98 func (s *StructLog) ErrorString() string { 99 if s.Err != nil { 100 return s.Err.Error() 101 } 102 return "" 103 } 104 105 // StructLogger is an EVM state logger and implements EVMLogger. 106 // 107 // StructLogger can capture state based on the given Log configuration and also keeps 108 // a track record of modified storage which is used in reporting snapshots of the 109 // contract their storage. 110 type StructLogger struct { 111 cfg Config 112 env *tracing.VMContext 113 114 storage map[common.Address]Storage 115 logs []StructLog 116 output []byte 117 err error 118 usedGas uint64 119 120 interrupt atomic.Bool // Atomic flag to signal execution interruption 121 reason error // Textual reason for the interruption 122 } 123 124 // NewStructLogger returns a new logger 125 func NewStructLogger(cfg *Config) *StructLogger { 126 logger := &StructLogger{ 127 storage: make(map[common.Address]Storage), 128 } 129 if cfg != nil { 130 logger.cfg = *cfg 131 } 132 return logger 133 } 134 135 func (l *StructLogger) Hooks() *tracing.Hooks { 136 return &tracing.Hooks{ 137 OnTxStart: l.OnTxStart, 138 OnTxEnd: l.OnTxEnd, 139 OnExit: l.OnExit, 140 OnOpcode: l.OnOpcode, 141 } 142 } 143 144 // Reset clears the data held by the logger. 145 func (l *StructLogger) Reset() { 146 l.storage = make(map[common.Address]Storage) 147 l.output = make([]byte, 0) 148 l.logs = l.logs[:0] 149 l.err = nil 150 } 151 152 // OnOpcode logs a new structured log message and pushes it out to the environment 153 // 154 // OnOpcode also tracks SLOAD/SSTORE ops to track storage change. 155 func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { 156 // If tracing was interrupted, set the error and stop 157 if l.interrupt.Load() { 158 return 159 } 160 // check if already accumulated the specified number of logs 161 if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { 162 return 163 } 164 165 op := vm.OpCode(opcode) 166 memory := scope.MemoryData() 167 stack := scope.StackData() 168 // Copy a snapshot of the current memory state to a new buffer 169 var mem []byte 170 if l.cfg.EnableMemory { 171 mem = make([]byte, len(memory)) 172 copy(mem, memory) 173 } 174 // Copy a snapshot of the current stack state to a new buffer 175 var stck []uint256.Int 176 if !l.cfg.DisableStack { 177 stck = make([]uint256.Int, len(stack)) 178 copy(stck, stack) 179 } 180 contractAddr := scope.Address() 181 stackLen := len(stack) 182 // Copy a snapshot of the current storage to a new container 183 var storage Storage 184 if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) { 185 // initialise new changed values storage container for this contract 186 // if not present. 187 if l.storage[contractAddr] == nil { 188 l.storage[contractAddr] = make(Storage) 189 } 190 // capture SLOAD opcodes and record the read entry in the local storage 191 if op == vm.SLOAD && stackLen >= 1 { 192 var ( 193 address = common.Hash(stack[stackLen-1].Bytes32()) 194 value = l.env.StateDB.GetState(contractAddr, address) 195 ) 196 l.storage[contractAddr][address] = value 197 storage = l.storage[contractAddr].Copy() 198 } else if op == vm.SSTORE && stackLen >= 2 { 199 // capture SSTORE opcodes and record the written entry in the local storage. 200 var ( 201 value = common.Hash(stack[stackLen-2].Bytes32()) 202 address = common.Hash(stack[stackLen-1].Bytes32()) 203 ) 204 l.storage[contractAddr][address] = value 205 storage = l.storage[contractAddr].Copy() 206 } 207 } 208 var rdata []byte 209 if l.cfg.EnableReturnData { 210 rdata = make([]byte, len(rData)) 211 copy(rdata, rData) 212 } 213 // create a new snapshot of the EVM. 214 log := StructLog{pc, op, gas, cost, mem, len(memory), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} 215 l.logs = append(l.logs, log) 216 } 217 218 // OnExit is called a call frame finishes processing. 219 func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 220 if depth != 0 { 221 return 222 } 223 l.output = output 224 l.err = err 225 if l.cfg.Debug { 226 fmt.Printf("%#x\n", output) 227 if err != nil { 228 fmt.Printf(" error: %v\n", err) 229 } 230 } 231 } 232 233 func (l *StructLogger) GetResult() (json.RawMessage, error) { 234 // Tracing aborted 235 if l.reason != nil { 236 return nil, l.reason 237 } 238 failed := l.err != nil 239 returnData := common.CopyBytes(l.output) 240 // Return data when successful and revert reason when reverted, otherwise empty. 241 returnVal := fmt.Sprintf("%x", returnData) 242 if failed && l.err != vm.ErrExecutionReverted { 243 returnVal = "" 244 } 245 return json.Marshal(&ExecutionResult{ 246 Gas: l.usedGas, 247 Failed: failed, 248 ReturnValue: returnVal, 249 StructLogs: formatLogs(l.StructLogs()), 250 }) 251 } 252 253 // Stop terminates execution of the tracer at the first opportune moment. 254 func (l *StructLogger) Stop(err error) { 255 l.reason = err 256 l.interrupt.Store(true) 257 } 258 259 func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { 260 l.env = env 261 } 262 263 func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) { 264 if err != nil { 265 // Don't override vm error 266 if l.err == nil { 267 l.err = err 268 } 269 return 270 } 271 l.usedGas = receipt.GasUsed 272 } 273 274 // StructLogs returns the captured log entries. 275 func (l *StructLogger) StructLogs() []StructLog { return l.logs } 276 277 // Error returns the VM error captured by the trace. 278 func (l *StructLogger) Error() error { return l.err } 279 280 // Output returns the VM return value captured by the trace. 281 func (l *StructLogger) Output() []byte { return l.output } 282 283 // WriteTrace writes a formatted trace to the given writer 284 func WriteTrace(writer io.Writer, logs []StructLog) { 285 for _, log := range logs { 286 fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost) 287 if log.Err != nil { 288 fmt.Fprintf(writer, " ERROR: %v", log.Err) 289 } 290 fmt.Fprintln(writer) 291 292 if len(log.Stack) > 0 { 293 fmt.Fprintln(writer, "Stack:") 294 for i := len(log.Stack) - 1; i >= 0; i-- { 295 fmt.Fprintf(writer, "%08d %s\n", len(log.Stack)-i-1, log.Stack[i].Hex()) 296 } 297 } 298 if len(log.Memory) > 0 { 299 fmt.Fprintln(writer, "Memory:") 300 fmt.Fprint(writer, hex.Dump(log.Memory)) 301 } 302 if len(log.Storage) > 0 { 303 fmt.Fprintln(writer, "Storage:") 304 for h, item := range log.Storage { 305 fmt.Fprintf(writer, "%x: %x\n", h, item) 306 } 307 } 308 if len(log.ReturnData) > 0 { 309 fmt.Fprintln(writer, "ReturnData:") 310 fmt.Fprint(writer, hex.Dump(log.ReturnData)) 311 } 312 fmt.Fprintln(writer) 313 } 314 } 315 316 // WriteLogs writes vm logs in a readable format to the given writer 317 func WriteLogs(writer io.Writer, logs []*types.Log) { 318 for _, log := range logs { 319 fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex) 320 321 for i, topic := range log.Topics { 322 fmt.Fprintf(writer, "%08d %x\n", i, topic) 323 } 324 325 fmt.Fprint(writer, hex.Dump(log.Data)) 326 fmt.Fprintln(writer) 327 } 328 } 329 330 type mdLogger struct { 331 out io.Writer 332 cfg *Config 333 env *tracing.VMContext 334 } 335 336 // NewMarkdownLogger creates a logger which outputs information in a format adapted 337 // for human readability, and is also a valid markdown table 338 func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger { 339 l := &mdLogger{out: writer, cfg: cfg} 340 if l.cfg == nil { 341 l.cfg = &Config{} 342 } 343 return l 344 } 345 346 func (t *mdLogger) Hooks() *tracing.Hooks { 347 return &tracing.Hooks{ 348 OnTxStart: t.OnTxStart, 349 OnEnter: t.OnEnter, 350 OnExit: t.OnExit, 351 OnOpcode: t.OnOpcode, 352 OnFault: t.OnFault, 353 } 354 } 355 356 func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { 357 t.env = env 358 } 359 360 func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 361 if depth != 0 { 362 return 363 } 364 create := vm.OpCode(typ) == vm.CREATE 365 if !create { 366 fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", 367 from.String(), to.String(), 368 input, gas, value) 369 } else { 370 fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `%#x`\nGas: `%d`\nValue `%v` wei\n", 371 from.String(), to.String(), 372 input, gas, value) 373 } 374 375 fmt.Fprintf(t.out, ` 376 | Pc | Op | Cost | Stack | RStack | Refund | 377 |-------|-------------|------|-----------|-----------|---------| 378 `) 379 } 380 381 func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 382 if depth == 0 { 383 fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", 384 output, gasUsed, err) 385 } 386 } 387 388 // OnOpcode also tracks SLOAD/SSTORE ops to track storage change. 389 func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { 390 stack := scope.StackData() 391 fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) 392 393 if !t.cfg.DisableStack { 394 // format stack 395 var a []string 396 for _, elem := range stack { 397 a = append(a, elem.Hex()) 398 } 399 b := fmt.Sprintf("[%v]", strings.Join(a, ",")) 400 fmt.Fprintf(t.out, "%10v |", b) 401 } 402 fmt.Fprintf(t.out, "%10v |", t.env.StateDB.GetRefund()) 403 fmt.Fprintln(t.out, "") 404 if err != nil { 405 fmt.Fprintf(t.out, "Error: %v\n", err) 406 } 407 } 408 409 func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { 410 fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) 411 } 412 413 // ExecutionResult groups all structured logs emitted by the EVM 414 // while replaying a transaction in debug mode as well as transaction 415 // execution status, the amount of gas used and the return value 416 type ExecutionResult struct { 417 Gas uint64 `json:"gas"` 418 Failed bool `json:"failed"` 419 ReturnValue string `json:"returnValue"` 420 StructLogs []StructLogRes `json:"structLogs"` 421 } 422 423 // StructLogRes stores a structured log emitted by the EVM while replaying a 424 // transaction in debug mode 425 type StructLogRes struct { 426 Pc uint64 `json:"pc"` 427 Op string `json:"op"` 428 Gas uint64 `json:"gas"` 429 GasCost uint64 `json:"gasCost"` 430 Depth int `json:"depth"` 431 Error string `json:"error,omitempty"` 432 Stack *[]string `json:"stack,omitempty"` 433 ReturnData string `json:"returnData,omitempty"` 434 Memory *[]string `json:"memory,omitempty"` 435 Storage *map[string]string `json:"storage,omitempty"` 436 RefundCounter uint64 `json:"refund,omitempty"` 437 } 438 439 // formatLogs formats EVM returned structured logs for json output 440 func formatLogs(logs []StructLog) []StructLogRes { 441 formatted := make([]StructLogRes, len(logs)) 442 for index, trace := range logs { 443 formatted[index] = StructLogRes{ 444 Pc: trace.Pc, 445 Op: trace.Op.String(), 446 Gas: trace.Gas, 447 GasCost: trace.GasCost, 448 Depth: trace.Depth, 449 Error: trace.ErrorString(), 450 RefundCounter: trace.RefundCounter, 451 } 452 if trace.Stack != nil { 453 stack := make([]string, len(trace.Stack)) 454 for i, stackValue := range trace.Stack { 455 stack[i] = stackValue.Hex() 456 } 457 formatted[index].Stack = &stack 458 } 459 if trace.ReturnData != nil && len(trace.ReturnData) > 0 { 460 formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String() 461 } 462 if trace.Memory != nil { 463 memory := make([]string, 0, (len(trace.Memory)+31)/32) 464 for i := 0; i+32 <= len(trace.Memory); i += 32 { 465 memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) 466 } 467 formatted[index].Memory = &memory 468 } 469 if trace.Storage != nil { 470 storage := make(map[string]string) 471 for i, storageValue := range trace.Storage { 472 storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) 473 } 474 formatted[index].Storage = &storage 475 } 476 } 477 return formatted 478 }