github.com/ethereum/go-ethereum@v1.16.1/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 "errors" 23 "fmt" 24 "io" 25 "maps" 26 "math/big" 27 "strings" 28 "sync/atomic" 29 30 "github.com/ethereum/go-ethereum/common" 31 "github.com/ethereum/go-ethereum/common/hexutil" 32 "github.com/ethereum/go-ethereum/common/math" 33 "github.com/ethereum/go-ethereum/core/tracing" 34 "github.com/ethereum/go-ethereum/core/types" 35 "github.com/ethereum/go-ethereum/core/vm" 36 "github.com/ethereum/go-ethereum/params" 37 "github.com/holiman/uint256" 38 ) 39 40 // Storage represents a contract's storage. 41 type Storage map[common.Hash]common.Hash 42 43 // Config are the configuration options for structured logger the EVM 44 type Config struct { 45 EnableMemory bool // enable memory capture 46 DisableStack bool // disable stack capture 47 DisableStorage bool // disable storage capture 48 EnableReturnData bool // enable return data capture 49 Limit int // maximum size of output, but zero means unlimited 50 // Chain overrides, can be used to execute a trace using future fork rules 51 Overrides *params.ChainConfig `json:"overrides,omitempty"` 52 } 53 54 //go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go 55 56 // StructLog is emitted to the EVM each cycle and lists information about the 57 // current internal state prior to the execution of the statement. 58 type StructLog struct { 59 Pc uint64 `json:"pc"` 60 Op vm.OpCode `json:"op"` 61 Gas uint64 `json:"gas"` 62 GasCost uint64 `json:"gasCost"` 63 Memory []byte `json:"memory,omitempty"` 64 MemorySize int `json:"memSize"` 65 Stack []uint256.Int `json:"stack"` 66 ReturnData []byte `json:"returnData,omitempty"` 67 Storage map[common.Hash]common.Hash `json:"-"` 68 Depth int `json:"depth"` 69 RefundCounter uint64 `json:"refund"` 70 Err error `json:"-"` 71 } 72 73 // overrides for gencodec 74 type structLogMarshaling struct { 75 Gas math.HexOrDecimal64 76 GasCost math.HexOrDecimal64 77 Memory hexutil.Bytes 78 ReturnData hexutil.Bytes 79 Stack []hexutil.U256 80 OpName string `json:"opName"` // adds call to OpName() in MarshalJSON 81 ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON 82 } 83 84 // OpName formats the operand name in a human-readable format. 85 func (s *StructLog) OpName() string { 86 return s.Op.String() 87 } 88 89 // ErrorString formats the log's error as a string. 90 func (s *StructLog) ErrorString() string { 91 if s.Err != nil { 92 return s.Err.Error() 93 } 94 return "" 95 } 96 97 // WriteTo writes the human-readable log data into the supplied writer. 98 func (s *StructLog) WriteTo(writer io.Writer) { 99 fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", s.Op, s.Pc, s.Gas, s.GasCost) 100 if s.Err != nil { 101 fmt.Fprintf(writer, " ERROR: %v", s.Err) 102 } 103 fmt.Fprintln(writer) 104 105 if len(s.Stack) > 0 { 106 fmt.Fprintln(writer, "Stack:") 107 for i := len(s.Stack) - 1; i >= 0; i-- { 108 fmt.Fprintf(writer, "%08d %s\n", len(s.Stack)-i-1, s.Stack[i].Hex()) 109 } 110 } 111 if len(s.Memory) > 0 { 112 fmt.Fprintln(writer, "Memory:") 113 fmt.Fprint(writer, hex.Dump(s.Memory)) 114 } 115 if len(s.Storage) > 0 { 116 fmt.Fprintln(writer, "Storage:") 117 for h, item := range s.Storage { 118 fmt.Fprintf(writer, "%x: %x\n", h, item) 119 } 120 } 121 if len(s.ReturnData) > 0 { 122 fmt.Fprintln(writer, "ReturnData:") 123 fmt.Fprint(writer, hex.Dump(s.ReturnData)) 124 } 125 fmt.Fprintln(writer) 126 } 127 128 // structLogLegacy stores a structured log emitted by the EVM while replaying a 129 // transaction in debug mode. It's the legacy format used in tracer. The differences 130 // between the structLog json and the 'legacy' json are: 131 // 132 // op: 133 // Legacy uses string (e.g. "SSTORE"), non-legacy uses a byte. 134 // non-legacy has an 'opName' field containing the op name. 135 // 136 // gas, gasCost: 137 // Legacy uses integers, non-legacy hex-strings 138 // 139 // memory: 140 // Legacy uses a list of 64-char strings, each representing 32-byte chunks 141 // of evm memory. Non-legacy just uses a string of hexdata, no chunking. 142 // 143 // storage: 144 // Legacy has a storage field while non-legacy doesn't. 145 type structLogLegacy struct { 146 Pc uint64 `json:"pc"` 147 Op string `json:"op"` 148 Gas uint64 `json:"gas"` 149 GasCost uint64 `json:"gasCost"` 150 Depth int `json:"depth"` 151 Error string `json:"error,omitempty"` 152 Stack *[]string `json:"stack,omitempty"` 153 ReturnData string `json:"returnData,omitempty"` 154 Memory *[]string `json:"memory,omitempty"` 155 Storage *map[string]string `json:"storage,omitempty"` 156 RefundCounter uint64 `json:"refund,omitempty"` 157 } 158 159 // toLegacyJSON converts the structLog to legacy json-encoded legacy form. 160 func (s *StructLog) toLegacyJSON() json.RawMessage { 161 msg := structLogLegacy{ 162 Pc: s.Pc, 163 Op: s.Op.String(), 164 Gas: s.Gas, 165 GasCost: s.GasCost, 166 Depth: s.Depth, 167 Error: s.ErrorString(), 168 RefundCounter: s.RefundCounter, 169 } 170 if s.Stack != nil { 171 stack := make([]string, len(s.Stack)) 172 for i, stackValue := range s.Stack { 173 stack[i] = stackValue.Hex() 174 } 175 msg.Stack = &stack 176 } 177 if len(s.ReturnData) > 0 { 178 msg.ReturnData = hexutil.Bytes(s.ReturnData).String() 179 } 180 if len(s.Memory) > 0 { 181 memory := make([]string, 0, (len(s.Memory)+31)/32) 182 for i := 0; i < len(s.Memory); i += 32 { 183 end := i + 32 184 if end > len(s.Memory) { 185 end = len(s.Memory) 186 } 187 memory = append(memory, fmt.Sprintf("%x", s.Memory[i:end])) 188 } 189 msg.Memory = &memory 190 } 191 if len(s.Storage) > 0 { 192 storage := make(map[string]string) 193 for i, storageValue := range s.Storage { 194 storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) 195 } 196 msg.Storage = &storage 197 } 198 element, _ := json.Marshal(msg) 199 return element 200 } 201 202 // StructLogger is an EVM state logger and implements EVMLogger. 203 // 204 // StructLogger can capture state based on the given Log configuration and also keeps 205 // a track record of modified storage which is used in reporting snapshots of the 206 // contract their storage. 207 // 208 // A StructLogger can either yield it's output immediately (streaming) or store for 209 // later output. 210 type StructLogger struct { 211 cfg Config 212 env *tracing.VMContext 213 214 storage map[common.Address]Storage 215 output []byte 216 err error 217 usedGas uint64 218 219 writer io.Writer // If set, the logger will stream instead of store logs 220 logs []json.RawMessage // buffer of json-encoded logs 221 resultSize int 222 223 interrupt atomic.Bool // Atomic flag to signal execution interruption 224 reason error // Textual reason for the interruption 225 skip bool // skip processing hooks. 226 } 227 228 // NewStreamingStructLogger returns a new streaming logger. 229 func NewStreamingStructLogger(cfg *Config, writer io.Writer) *StructLogger { 230 l := NewStructLogger(cfg) 231 l.writer = writer 232 return l 233 } 234 235 // NewStructLogger construct a new (non-streaming) struct logger. 236 func NewStructLogger(cfg *Config) *StructLogger { 237 logger := &StructLogger{ 238 storage: make(map[common.Address]Storage), 239 logs: make([]json.RawMessage, 0), 240 } 241 if cfg != nil { 242 logger.cfg = *cfg 243 } 244 return logger 245 } 246 247 func (l *StructLogger) Hooks() *tracing.Hooks { 248 return &tracing.Hooks{ 249 OnTxStart: l.OnTxStart, 250 OnTxEnd: l.OnTxEnd, 251 OnSystemCallStartV2: l.OnSystemCallStart, 252 OnSystemCallEnd: l.OnSystemCallEnd, 253 OnExit: l.OnExit, 254 OnOpcode: l.OnOpcode, 255 } 256 } 257 258 // OnOpcode logs a new structured log message and pushes it out to the environment 259 // 260 // OnOpcode also tracks SLOAD/SSTORE ops to track storage change. 261 func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { 262 // If tracing was interrupted, exit 263 if l.interrupt.Load() { 264 return 265 } 266 // Processing a system call. 267 if l.skip { 268 return 269 } 270 // check if already accumulated the size of the response. 271 if l.cfg.Limit != 0 && l.resultSize > l.cfg.Limit { 272 return 273 } 274 var ( 275 op = vm.OpCode(opcode) 276 memory = scope.MemoryData() 277 contractAddr = scope.Address() 278 stack = scope.StackData() 279 stackLen = len(stack) 280 ) 281 log := StructLog{pc, op, gas, cost, nil, len(memory), nil, nil, nil, depth, l.env.StateDB.GetRefund(), err} 282 if l.cfg.EnableMemory { 283 log.Memory = memory 284 } 285 if !l.cfg.DisableStack { 286 log.Stack = scope.StackData() 287 } 288 if l.cfg.EnableReturnData { 289 log.ReturnData = rData 290 } 291 292 // Copy a snapshot of the current storage to a new container 293 var storage Storage 294 if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) { 295 // initialise new changed values storage container for this contract 296 // if not present. 297 if l.storage[contractAddr] == nil { 298 l.storage[contractAddr] = make(Storage) 299 } 300 // capture SLOAD opcodes and record the read entry in the local storage 301 if op == vm.SLOAD && stackLen >= 1 { 302 var ( 303 address = common.Hash(stack[stackLen-1].Bytes32()) 304 value = l.env.StateDB.GetState(contractAddr, address) 305 ) 306 l.storage[contractAddr][address] = value 307 storage = maps.Clone(l.storage[contractAddr]) 308 } else if op == vm.SSTORE && stackLen >= 2 { 309 // capture SSTORE opcodes and record the written entry in the local storage. 310 var ( 311 value = common.Hash(stack[stackLen-2].Bytes32()) 312 address = common.Hash(stack[stackLen-1].Bytes32()) 313 ) 314 l.storage[contractAddr][address] = value 315 storage = maps.Clone(l.storage[contractAddr]) 316 } 317 } 318 log.Storage = storage 319 320 // create a log 321 if l.writer == nil { 322 entry := log.toLegacyJSON() 323 l.resultSize += len(entry) 324 l.logs = append(l.logs, entry) 325 return 326 } 327 log.WriteTo(l.writer) 328 } 329 330 // OnExit is called a call frame finishes processing. 331 func (l *StructLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 332 if depth != 0 { 333 return 334 } 335 if l.skip { 336 return 337 } 338 l.output = output 339 l.err = err 340 // TODO @holiman, should we output the per-scope output? 341 //if l.cfg.Debug { 342 // fmt.Printf("%#x\n", output) 343 // if err != nil { 344 // fmt.Printf(" error: %v\n", err) 345 // } 346 //} 347 } 348 349 func (l *StructLogger) GetResult() (json.RawMessage, error) { 350 // Tracing aborted 351 if l.reason != nil { 352 return nil, l.reason 353 } 354 failed := l.err != nil 355 returnData := common.CopyBytes(l.output) 356 // Return data when successful and revert reason when reverted, otherwise empty. 357 if failed && !errors.Is(l.err, vm.ErrExecutionReverted) { 358 returnData = []byte{} 359 } 360 return json.Marshal(&ExecutionResult{ 361 Gas: l.usedGas, 362 Failed: failed, 363 ReturnValue: returnData, 364 StructLogs: l.logs, 365 }) 366 } 367 368 // Stop terminates execution of the tracer at the first opportune moment. 369 func (l *StructLogger) Stop(err error) { 370 l.reason = err 371 l.interrupt.Store(true) 372 } 373 374 func (l *StructLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { 375 l.env = env 376 } 377 func (l *StructLogger) OnSystemCallStart(env *tracing.VMContext) { 378 l.skip = true 379 } 380 381 func (l *StructLogger) OnSystemCallEnd() { 382 l.skip = false 383 } 384 385 func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) { 386 if err != nil { 387 // Don't override vm error 388 if l.err == nil { 389 l.err = err 390 } 391 return 392 } 393 if receipt != nil { 394 l.usedGas = receipt.GasUsed 395 } 396 } 397 398 // Error returns the VM error captured by the trace. 399 func (l *StructLogger) Error() error { return l.err } 400 401 // Output returns the VM return value captured by the trace. 402 func (l *StructLogger) Output() []byte { return l.output } 403 404 // WriteTrace writes a formatted trace to the given writer 405 // @deprecated 406 func WriteTrace(writer io.Writer, logs []StructLog) { 407 for _, log := range logs { 408 log.WriteTo(writer) 409 } 410 } 411 412 type mdLogger struct { 413 out io.Writer 414 cfg *Config 415 env *tracing.VMContext 416 skip bool 417 } 418 419 // NewMarkdownLogger creates a logger which outputs information in a format adapted 420 // for human readability, and is also a valid markdown table 421 func NewMarkdownLogger(cfg *Config, writer io.Writer) *mdLogger { 422 l := &mdLogger{out: writer, cfg: cfg} 423 if l.cfg == nil { 424 l.cfg = &Config{} 425 } 426 return l 427 } 428 429 func (t *mdLogger) Hooks() *tracing.Hooks { 430 return &tracing.Hooks{ 431 OnTxStart: t.OnTxStart, 432 OnSystemCallStartV2: t.OnSystemCallStart, 433 OnSystemCallEnd: t.OnSystemCallEnd, 434 OnEnter: t.OnEnter, 435 OnExit: t.OnExit, 436 OnOpcode: t.OnOpcode, 437 OnFault: t.OnFault, 438 } 439 } 440 441 func (t *mdLogger) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { 442 t.env = env 443 } 444 445 func (t *mdLogger) OnSystemCallStart(env *tracing.VMContext) { 446 t.skip = true 447 } 448 449 func (t *mdLogger) OnSystemCallEnd() { 450 t.skip = false 451 } 452 453 func (t *mdLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 454 if t.skip { 455 return 456 } 457 if depth != 0 { 458 return 459 } 460 if create := vm.OpCode(typ) == vm.CREATE; !create { 461 fmt.Fprintf(t.out, "Pre-execution info:\n"+ 462 " - from: `%v`\n"+ 463 " - to: `%v`\n"+ 464 " - data: `%#x`\n"+ 465 " - gas: `%d`\n"+ 466 " - value: `%v` wei\n", 467 from.String(), to.String(), input, gas, value) 468 } else { 469 fmt.Fprintf(t.out, "Pre-execution info:\n"+ 470 " - from: `%v`\n"+ 471 " - create: `%v`\n"+ 472 " - data: `%#x`\n"+ 473 " - gas: `%d`\n"+ 474 " - value: `%v` wei\n", 475 from.String(), to.String(), input, gas, value) 476 } 477 fmt.Fprintf(t.out, ` 478 | Pc | Op | Cost | Refund | Stack | 479 |-------|-------------|------|-----------|-----------| 480 `) 481 } 482 483 func (t *mdLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 484 if t.skip { 485 return 486 } 487 if depth == 0 { 488 fmt.Fprintf(t.out, "\nPost-execution info:\n"+ 489 " - output: `%#x`\n"+ 490 " - consumed gas: `%d`\n"+ 491 " - error: `%v`\n", 492 output, gasUsed, err) 493 } 494 } 495 496 // OnOpcode also tracks SLOAD/SSTORE ops to track storage change. 497 func (t *mdLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { 498 if t.skip { 499 return 500 } 501 stack := scope.StackData() 502 fmt.Fprintf(t.out, "| %4d | %10v | %3d |%10v |", pc, vm.OpCode(op).String(), 503 cost, t.env.StateDB.GetRefund()) 504 505 if !t.cfg.DisableStack { 506 // format stack 507 var a []string 508 for _, elem := range stack { 509 a = append(a, elem.Hex()) 510 } 511 b := fmt.Sprintf("[%v]", strings.Join(a, ",")) 512 fmt.Fprintf(t.out, "%10v |", b) 513 } 514 fmt.Fprintln(t.out, "") 515 if err != nil { 516 fmt.Fprintf(t.out, "Error: %v\n", err) 517 } 518 } 519 520 func (t *mdLogger) OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { 521 if t.skip { 522 return 523 } 524 fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) 525 } 526 527 // ExecutionResult groups all structured logs emitted by the EVM 528 // while replaying a transaction in debug mode as well as transaction 529 // execution status, the amount of gas used and the return value 530 type ExecutionResult struct { 531 Gas uint64 `json:"gas"` 532 Failed bool `json:"failed"` 533 ReturnValue hexutil.Bytes `json:"returnValue"` 534 StructLogs []json.RawMessage `json:"structLogs"` 535 }