github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/eth/tracers/api.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 tracers 18 19 import ( 20 "bufio" 21 "bytes" 22 "context" 23 "encoding/hex" 24 "errors" 25 "fmt" 26 "io/ioutil" 27 "math/big" 28 "os" 29 "path/filepath" 30 "runtime" 31 "sync" 32 "time" 33 34 "github.com/ethereum/go-ethereum" 35 "github.com/ethereum/go-ethereum/common" 36 "github.com/ethereum/go-ethereum/common/hexutil" 37 "github.com/ethereum/go-ethereum/consensus" 38 "github.com/ethereum/go-ethereum/consensus/bor/statefull" 39 "github.com/ethereum/go-ethereum/core" 40 "github.com/ethereum/go-ethereum/core/rawdb" 41 "github.com/ethereum/go-ethereum/core/state" 42 "github.com/ethereum/go-ethereum/core/types" 43 "github.com/ethereum/go-ethereum/core/vm" 44 "github.com/ethereum/go-ethereum/eth/tracers/logger" 45 "github.com/ethereum/go-ethereum/ethdb" 46 "github.com/ethereum/go-ethereum/internal/ethapi" 47 "github.com/ethereum/go-ethereum/log" 48 "github.com/ethereum/go-ethereum/params" 49 "github.com/ethereum/go-ethereum/rlp" 50 "github.com/ethereum/go-ethereum/rpc" 51 ) 52 53 const ( 54 // defaultTraceTimeout is the amount of time a single transaction can execute 55 // by default before being forcefully aborted. 56 defaultTraceTimeout = 5 * time.Second 57 58 // defaultTraceReexec is the number of blocks the tracer is willing to go back 59 // and reexecute to produce missing historical state necessary to run a specific 60 // trace. 61 defaultTraceReexec = uint64(128) 62 63 // defaultTracechainMemLimit is the size of the triedb, at which traceChain 64 // switches over and tries to use a disk-backed database instead of building 65 // on top of memory. 66 // For non-archive nodes, this limit _will_ be overblown, as disk-backed tries 67 // will only be found every ~15K blocks or so. 68 defaultTracechainMemLimit = common.StorageSize(500 * 1024 * 1024) 69 70 defaultPath = string(".") 71 72 defaultIOFlag = false 73 ) 74 75 var defaultBorTraceEnabled = newBoolPtr(false) 76 77 var allowIOTracing = false // Change this to true to enable IO tracing for debugging 78 79 // Backend interface provides the common API services (that are provided by 80 // both full and light clients) with access to necessary functions. 81 type Backend interface { 82 HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) 83 HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) 84 BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) 85 BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) 86 GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) 87 RPCGasCap() uint64 88 ChainConfig() *params.ChainConfig 89 Engine() consensus.Engine 90 ChainDb() ethdb.Database 91 // StateAtBlock returns the state corresponding to the stateroot of the block. 92 // N.B: For executing transactions on block N, the required stateRoot is block N-1, 93 // so this method should be called with the parent. 94 StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) 95 StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) 96 97 // Bor related APIs 98 GetBorBlockTransactionWithBlockHash(ctx context.Context, txHash common.Hash, blockHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) 99 } 100 101 // API is the collection of tracing APIs exposed over the private debugging endpoint. 102 type API struct { 103 backend Backend 104 } 105 106 // NewAPI creates a new API definition for the tracing methods of the Ethereum service. 107 func NewAPI(backend Backend) *API { 108 return &API{backend: backend} 109 } 110 111 type chainContext struct { 112 api *API 113 ctx context.Context 114 } 115 116 func (context *chainContext) Engine() consensus.Engine { 117 return context.api.backend.Engine() 118 } 119 120 func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { 121 header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) 122 if err != nil { 123 return nil 124 } 125 if header.Hash() == hash { 126 return header 127 } 128 header, err = context.api.backend.HeaderByHash(context.ctx, hash) 129 if err != nil { 130 return nil 131 } 132 return header 133 } 134 135 // chainContext construts the context reader which is used by the evm for reading 136 // the necessary chain context. 137 func (api *API) chainContext(ctx context.Context) core.ChainContext { 138 return &chainContext{api: api, ctx: ctx} 139 } 140 141 // blockByNumber is the wrapper of the chain access function offered by the backend. 142 // It will return an error if the block is not found. 143 func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { 144 block, err := api.backend.BlockByNumber(ctx, number) 145 if err != nil { 146 return nil, err 147 } 148 if block == nil { 149 return nil, fmt.Errorf("block #%d not found", number) 150 } 151 return block, nil 152 } 153 154 // blockByHash is the wrapper of the chain access function offered by the backend. 155 // It will return an error if the block is not found. 156 func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { 157 block, err := api.backend.BlockByHash(ctx, hash) 158 if err != nil { 159 return nil, err 160 } 161 if block == nil { 162 return nil, fmt.Errorf("block %s not found", hash.Hex()) 163 } 164 return block, nil 165 } 166 167 // blockByNumberAndHash is the wrapper of the chain access function offered by 168 // the backend. It will return an error if the block is not found. 169 // 170 // Note this function is friendly for the light client which can only retrieve the 171 // historical(before the CHT) header/block by number. 172 func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) { 173 block, err := api.blockByNumber(ctx, number) 174 if err != nil { 175 return nil, err 176 } 177 if block.Hash() == hash { 178 return block, nil 179 } 180 return api.blockByHash(ctx, hash) 181 } 182 183 // returns block transactions along with state-sync transaction if present 184 func (api *API) getAllBlockTransactions(ctx context.Context, block *types.Block) (types.Transactions, bool) { 185 txs := block.Transactions() 186 187 stateSyncPresent := false 188 189 borReceipt := rawdb.ReadBorReceipt(api.backend.ChainDb(), block.Hash(), block.NumberU64(), api.backend.ChainConfig()) 190 if borReceipt != nil { 191 txHash := types.GetDerivedBorTxHash(types.BorReceiptKey(block.Number().Uint64(), block.Hash())) 192 if txHash != (common.Hash{}) { 193 borTx, _, _, _, _ := api.backend.GetBorBlockTransactionWithBlockHash(ctx, txHash, block.Hash()) 194 txs = append(txs, borTx) 195 stateSyncPresent = true 196 } 197 } 198 199 return txs, stateSyncPresent 200 } 201 202 // TraceConfig holds extra parameters to trace functions. 203 type TraceConfig struct { 204 *logger.Config 205 Tracer *string 206 Timeout *string 207 Reexec *uint64 208 Path *string 209 IOFlag *bool 210 BorTraceEnabled *bool 211 BorTx *bool 212 } 213 214 // TraceCallConfig is the config for traceCall API. It holds one more 215 // field to override the state for tracing. 216 type TraceCallConfig struct { 217 *logger.Config 218 Tracer *string 219 Timeout *string 220 Reexec *uint64 221 StateOverrides *ethapi.StateOverride 222 } 223 224 // StdTraceConfig holds extra parameters to standard-json trace functions. 225 type StdTraceConfig struct { 226 logger.Config 227 Reexec *uint64 228 TxHash common.Hash 229 BorTraceEnabled *bool 230 } 231 232 // txTraceResult is the result of a single transaction trace. 233 type txTraceResult struct { 234 Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer 235 Error string `json:"error,omitempty"` // Trace failure produced by the tracer 236 } 237 238 // blockTraceTask represents a single block trace task when an entire chain is 239 // being traced. 240 type blockTraceTask struct { 241 statedb *state.StateDB // Intermediate state prepped for tracing 242 block *types.Block // Block to trace the transactions from 243 rootref common.Hash // Trie root reference held for this task 244 results []*txTraceResult // Trace results procudes by the task 245 } 246 247 // blockTraceResult represets the results of tracing a single block when an entire 248 // chain is being traced. 249 type blockTraceResult struct { 250 Block hexutil.Uint64 `json:"block"` // Block number corresponding to this trace 251 Hash common.Hash `json:"hash"` // Block hash corresponding to this trace 252 Traces []*txTraceResult `json:"traces"` // Trace results produced by the task 253 } 254 255 // txTraceTask represents a single transaction trace task when an entire block 256 // is being traced. 257 type txTraceTask struct { 258 statedb *state.StateDB // Intermediate state prepped for tracing 259 index int // Transaction offset in the block 260 } 261 262 // TraceChain returns the structured logs created during the execution of EVM 263 // between two blocks (excluding start) and returns them as a JSON object. 264 func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace 265 from, err := api.blockByNumber(ctx, start) 266 if err != nil { 267 return nil, err 268 } 269 to, err := api.blockByNumber(ctx, end) 270 if err != nil { 271 return nil, err 272 } 273 if from.Number().Cmp(to.Number()) >= 0 { 274 return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start) 275 } 276 return api.traceChain(ctx, from, to, config) 277 } 278 279 // traceChain configures a new tracer according to the provided configuration, and 280 // executes all the transactions contained within. The return value will be one item 281 // per transaction, dependent on the requested tracer. 282 func (api *API) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { 283 if config == nil { 284 config = &TraceConfig{ 285 BorTraceEnabled: defaultBorTraceEnabled, 286 BorTx: newBoolPtr(false), 287 } 288 } 289 290 if config.BorTraceEnabled == nil { 291 config.BorTraceEnabled = defaultBorTraceEnabled 292 } 293 // Tracing a chain is a **long** operation, only do with subscriptions 294 notifier, supported := rpc.NotifierFromContext(ctx) 295 if !supported { 296 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 297 } 298 sub := notifier.CreateSubscription() 299 300 // Prepare all the states for tracing. Note this procedure can take very 301 // long time. Timeout mechanism is necessary. 302 reexec := defaultTraceReexec 303 if config != nil && config.Reexec != nil { 304 reexec = *config.Reexec 305 } 306 blocks := int(end.NumberU64() - start.NumberU64()) 307 threads := runtime.NumCPU() 308 if threads > blocks { 309 threads = blocks 310 } 311 var ( 312 pend = new(sync.WaitGroup) 313 tasks = make(chan *blockTraceTask, threads) 314 results = make(chan *blockTraceTask, threads) 315 localctx = context.Background() 316 ) 317 for th := 0; th < threads; th++ { 318 pend.Add(1) 319 go func() { 320 defer pend.Done() 321 322 // Fetch and execute the next block trace tasks 323 for task := range tasks { 324 signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) 325 blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), nil) 326 // Trace all the transactions contained within 327 txs, stateSyncPresent := api.getAllBlockTransactions(ctx, task.block) 328 if !*config.BorTraceEnabled && stateSyncPresent { 329 txs = txs[:len(txs)-1] 330 stateSyncPresent = false 331 } 332 333 for i, tx := range txs { 334 msg, _ := tx.AsMessage(signer, task.block.BaseFee()) 335 txctx := &Context{ 336 BlockHash: task.block.Hash(), 337 TxIndex: i, 338 TxHash: tx.Hash(), 339 } 340 341 var res interface{} 342 343 var err error 344 345 if stateSyncPresent && i == len(txs)-1 { 346 if *config.BorTraceEnabled { 347 config.BorTx = newBoolPtr(true) 348 res, err = api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config) 349 } 350 } else { 351 res, err = api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config) 352 } 353 354 if err != nil { 355 task.results[i] = &txTraceResult{Error: err.Error()} 356 log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) 357 break 358 } 359 360 // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect 361 task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number())) 362 task.results[i] = &txTraceResult{Result: res} 363 } 364 // Stream the result back to the user or abort on teardown 365 select { 366 case results <- task: 367 case <-notifier.Closed(): 368 return 369 } 370 } 371 }() 372 } 373 // Start a goroutine to feed all the blocks into the tracers 374 var ( 375 begin = time.Now() 376 derefTodo []common.Hash // list of hashes to dereference from the db 377 derefsMu sync.Mutex // mutex for the derefs 378 ) 379 380 go func() { 381 var ( 382 logged time.Time 383 number uint64 384 traced uint64 385 failed error 386 parent common.Hash 387 statedb *state.StateDB 388 ) 389 // Ensure everything is properly cleaned up on any exit path 390 defer func() { 391 close(tasks) 392 pend.Wait() 393 394 switch { 395 case failed != nil: 396 log.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed) 397 case number < end.NumberU64(): 398 log.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin)) 399 default: 400 log.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin)) 401 } 402 close(results) 403 }() 404 var preferDisk bool 405 // Feed all the blocks both into the tracer, as well as fast process concurrently 406 for number = start.NumberU64(); number < end.NumberU64(); number++ { 407 // Stop tracing if interruption was requested 408 select { 409 case <-notifier.Closed(): 410 return 411 default: 412 } 413 // clean out any derefs 414 derefsMu.Lock() 415 for _, h := range derefTodo { 416 statedb.Database().TrieDB().Dereference(h) 417 } 418 derefTodo = derefTodo[:0] 419 derefsMu.Unlock() 420 421 // Print progress logs if long enough time elapsed 422 if time.Since(logged) > 8*time.Second { 423 logged = time.Now() 424 log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) 425 } 426 // Retrieve the parent state to trace on top 427 block, err := api.blockByNumber(localctx, rpc.BlockNumber(number)) 428 if err != nil { 429 failed = err 430 break 431 } 432 // Prepare the statedb for tracing. Don't use the live database for 433 // tracing to avoid persisting state junks into the database. 434 statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false, preferDisk) 435 if err != nil { 436 failed = err 437 break 438 } 439 if trieDb := statedb.Database().TrieDB(); trieDb != nil { 440 // Hold the reference for tracer, will be released at the final stage 441 trieDb.Reference(block.Root(), common.Hash{}) 442 443 // Release the parent state because it's already held by the tracer 444 if parent != (common.Hash{}) { 445 trieDb.Dereference(parent) 446 } 447 // Prefer disk if the trie db memory grows too much 448 s1, s2 := trieDb.Size() 449 if !preferDisk && (s1+s2) > defaultTracechainMemLimit { 450 log.Info("Switching to prefer-disk mode for tracing", "size", s1+s2) 451 preferDisk = true 452 } 453 } 454 parent = block.Root() 455 456 next, err := api.blockByNumber(localctx, rpc.BlockNumber(number+1)) 457 if err != nil { 458 failed = err 459 break 460 } 461 // Send the block over to the concurrent tracers (if not in the fast-forward phase) 462 txs := next.Transactions() 463 select { 464 case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))}: 465 case <-notifier.Closed(): 466 return 467 } 468 traced += uint64(len(txs)) 469 } 470 }() 471 472 // Keep reading the trace results and stream the to the user 473 go func() { 474 var ( 475 done = make(map[uint64]*blockTraceResult) 476 next = start.NumberU64() + 1 477 ) 478 for res := range results { 479 // Queue up next received result 480 result := &blockTraceResult{ 481 Block: hexutil.Uint64(res.block.NumberU64()), 482 Hash: res.block.Hash(), 483 Traces: res.results, 484 } 485 // Schedule any parent tries held in memory by this task for dereferencing 486 done[uint64(result.Block)] = result 487 derefsMu.Lock() 488 derefTodo = append(derefTodo, res.rootref) 489 derefsMu.Unlock() 490 // Stream completed traces to the user, aborting on the first error 491 for result, ok := done[next]; ok; result, ok = done[next] { 492 if len(result.Traces) > 0 || next == end.NumberU64() { 493 notifier.Notify(sub.ID, result) 494 } 495 delete(done, next) 496 next++ 497 } 498 } 499 }() 500 return sub, nil 501 } 502 503 func newBoolPtr(bb bool) *bool { 504 b := bb 505 return &b 506 } 507 508 // TraceBlockByNumber returns the structured logs created during the execution of 509 // EVM and returns them as a JSON object. 510 func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { 511 block, err := api.blockByNumber(ctx, number) 512 if err != nil { 513 return nil, err 514 } 515 return api.traceBlock(ctx, block, config) 516 } 517 518 // TraceBlockByHash returns the structured logs created during the execution of 519 // EVM and returns them as a JSON object. 520 func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { 521 block, err := api.blockByHash(ctx, hash) 522 if err != nil { 523 return nil, err 524 } 525 return api.traceBlock(ctx, block, config) 526 } 527 528 // TraceBlock returns the structured logs created during the execution of EVM 529 // and returns them as a JSON object. 530 func (api *API) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *TraceConfig) ([]*txTraceResult, error) { 531 block := new(types.Block) 532 if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { 533 return nil, fmt.Errorf("could not decode block: %v", err) 534 } 535 return api.traceBlock(ctx, block, config) 536 } 537 538 // TraceBlockFromFile returns the structured logs created during the execution of 539 // EVM and returns them as a JSON object. 540 func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { 541 blob, err := ioutil.ReadFile(file) 542 if err != nil { 543 return nil, fmt.Errorf("could not read file: %v", err) 544 } 545 return api.TraceBlock(ctx, blob, config) 546 } 547 548 // TraceBadBlock returns the structured logs created during the execution of 549 // EVM against a block pulled from the pool of bad ones and returns them as a JSON 550 // object. 551 func (api *API) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { 552 block := rawdb.ReadBadBlock(api.backend.ChainDb(), hash) 553 if block == nil { 554 return nil, fmt.Errorf("bad block %#x not found", hash) 555 } 556 return api.traceBlock(ctx, block, config) 557 } 558 559 // StandardTraceBlockToFile dumps the structured logs created during the 560 // execution of EVM to the local file system and returns a list of files 561 // to the caller. 562 func (api *API) StandardTraceBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { 563 block, err := api.blockByHash(ctx, hash) 564 if err != nil { 565 return nil, err 566 } 567 return api.standardTraceBlockToFile(ctx, block, config) 568 } 569 570 func prepareCallMessage(msg core.Message) statefull.Callmsg { 571 return statefull.Callmsg{ 572 CallMsg: ethereum.CallMsg{ 573 From: msg.From(), 574 To: msg.To(), 575 Gas: msg.Gas(), 576 GasPrice: msg.GasPrice(), 577 GasFeeCap: msg.GasFeeCap(), 578 GasTipCap: msg.GasTipCap(), 579 Value: msg.Value(), 580 Data: msg.Data(), 581 AccessList: msg.AccessList(), 582 }} 583 } 584 585 // IntermediateRoots executes a block (bad- or canon- or side-), and returns a list 586 // of intermediate roots: the stateroot after each transaction. 587 func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config *TraceConfig) ([]common.Hash, error) { 588 if config == nil { 589 config = &TraceConfig{ 590 BorTraceEnabled: defaultBorTraceEnabled, 591 BorTx: newBoolPtr(false), 592 } 593 } 594 595 if config.BorTraceEnabled == nil { 596 config.BorTraceEnabled = defaultBorTraceEnabled 597 } 598 599 block, _ := api.blockByHash(ctx, hash) 600 if block == nil { 601 // Check in the bad blocks 602 block = rawdb.ReadBadBlock(api.backend.ChainDb(), hash) 603 } 604 if block == nil { 605 return nil, fmt.Errorf("block %#x not found", hash) 606 } 607 if block.NumberU64() == 0 { 608 return nil, errors.New("genesis is not traceable") 609 } 610 parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) 611 if err != nil { 612 return nil, err 613 } 614 reexec := defaultTraceReexec 615 if config != nil && config.Reexec != nil { 616 reexec = *config.Reexec 617 } 618 statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) 619 if err != nil { 620 return nil, err 621 } 622 var ( 623 roots []common.Hash 624 signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) 625 chainConfig = api.backend.ChainConfig() 626 vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) 627 deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) 628 ) 629 630 txs, stateSyncPresent := api.getAllBlockTransactions(ctx, block) 631 for i, tx := range txs { 632 var ( 633 msg, _ = tx.AsMessage(signer, block.BaseFee()) 634 txContext = core.NewEVMTxContext(msg) 635 vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) 636 ) 637 statedb.Prepare(tx.Hash(), i) 638 //nolint: nestif 639 if stateSyncPresent && i == len(txs)-1 { 640 if *config.BorTraceEnabled { 641 callmsg := prepareCallMessage(msg) 642 643 if _, err := statefull.ApplyMessage(ctx, callmsg, statedb, block.Header(), api.backend.ChainConfig(), api.chainContext(ctx)); err != nil { 644 log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) 645 // We intentionally don't return the error here: if we do, then the RPC server will not 646 // return the roots. Most likely, the caller already knows that a certain transaction fails to 647 // be included, but still want the intermediate roots that led to that point. 648 // It may happen the tx_N causes an erroneous state, which in turn causes tx_N+M to not be 649 // executable. 650 // N.B: This should never happen while tracing canon blocks, only when tracing bad blocks. 651 return roots, nil 652 } 653 } else { 654 break 655 } 656 } else { 657 // nolint : contextcheck 658 if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background()); err != nil { 659 log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) 660 // We intentionally don't return the error here: if we do, then the RPC server will not 661 // return the roots. Most likely, the caller already knows that a certain transaction fails to 662 // be included, but still want the intermediate roots that led to that point. 663 // It may happen the tx_N causes an erroneous state, which in turn causes tx_N+M to not be 664 // executable. 665 // N.B: This should never happen while tracing canon blocks, only when tracing bad blocks. 666 return roots, nil 667 } 668 669 } 670 671 // calling IntermediateRoot will internally call Finalize on the state 672 // so any modifications are written to the trie 673 roots = append(roots, statedb.IntermediateRoot(deleteEmptyObjects)) 674 } 675 return roots, nil 676 } 677 678 // StandardTraceBadBlockToFile dumps the structured logs created during the 679 // execution of EVM against a block pulled from the pool of bad ones to the 680 // local file system and returns a list of files to the caller. 681 func (api *API) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) { 682 block := rawdb.ReadBadBlock(api.backend.ChainDb(), hash) 683 if block == nil { 684 return nil, fmt.Errorf("bad block %#x not found", hash) 685 } 686 return api.standardTraceBlockToFile(ctx, block, config) 687 } 688 689 // traceBlock configures a new tracer according to the provided configuration, and 690 // executes all the transactions contained within. The return value will be one item 691 // per transaction, dependent on the requestd tracer. 692 func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { 693 694 if config == nil { 695 config = &TraceConfig{ 696 BorTraceEnabled: defaultBorTraceEnabled, 697 BorTx: newBoolPtr(false), 698 } 699 } 700 701 if config.BorTraceEnabled == nil { 702 config.BorTraceEnabled = defaultBorTraceEnabled 703 } 704 705 if block.NumberU64() == 0 { 706 return nil, errors.New("genesis is not traceable") 707 } 708 709 parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) 710 if err != nil { 711 return nil, err 712 } 713 714 reexec := defaultTraceReexec 715 if config != nil && config.Reexec != nil { 716 reexec = *config.Reexec 717 } 718 719 path := defaultPath 720 if config != nil && config.Path != nil { 721 path = *config.Path 722 } 723 724 ioflag := defaultIOFlag 725 if allowIOTracing && config != nil && config.IOFlag != nil { 726 ioflag = *config.IOFlag 727 } 728 729 statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) 730 if err != nil { 731 return nil, err 732 } 733 734 // create and add empty mvHashMap in statedb as StateAtBlock does not have mvHashmap in it. 735 if ioflag { 736 statedb.AddEmptyMVHashMap() 737 } 738 739 // Execute all the transaction contained within the block concurrently 740 var ( 741 signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) 742 txs, stateSyncPresent = api.getAllBlockTransactions(ctx, block) 743 results = make([]*txTraceResult, len(txs)) 744 745 pend = new(sync.WaitGroup) 746 jobs = make(chan *txTraceTask, len(txs)) 747 ) 748 threads := runtime.NumCPU() 749 if threads > len(txs) { 750 threads = len(txs) 751 } 752 blockHash := block.Hash() 753 for th := 0; th < threads; th++ { 754 pend.Add(1) 755 go func() { 756 blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) 757 defer pend.Done() 758 // Fetch and execute the next transaction trace tasks 759 for task := range jobs { 760 msg, _ := txs[task.index].AsMessage(signer, block.BaseFee()) 761 txctx := &Context{ 762 BlockHash: blockHash, 763 TxIndex: task.index, 764 TxHash: txs[task.index].Hash(), 765 } 766 767 var res interface{} 768 769 var err error 770 771 if stateSyncPresent && task.index == len(txs)-1 { 772 if *config.BorTraceEnabled { 773 config.BorTx = newBoolPtr(true) 774 res, err = api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) 775 } else { 776 break 777 } 778 } else { 779 res, err = api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) 780 } 781 if err != nil { 782 results[task.index] = &txTraceResult{Error: err.Error()} 783 continue 784 } 785 results[task.index] = &txTraceResult{Result: res} 786 } 787 }() 788 } 789 790 var IOdump string 791 792 var RWstruct []state.DumpStruct 793 794 var london bool 795 796 if ioflag { 797 IOdump = "TransactionIndex, Incarnation, VersionTxIdx, VersionInc, Path, Operation\n" 798 RWstruct = []state.DumpStruct{} 799 } 800 // Feed the transactions into the tracers and return 801 var failed error 802 blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) 803 804 if ioflag { 805 london = api.backend.ChainConfig().IsLondon(block.Number()) 806 } 807 808 for i, tx := range txs { 809 if ioflag { 810 // copy of statedb 811 statedb = statedb.Copy() 812 } 813 814 // Send the trace task over for execution 815 jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} 816 817 // Generate the next state snapshot fast without tracing 818 msg, _ := tx.AsMessage(signer, block.BaseFee()) 819 statedb.Prepare(tx.Hash(), i) 820 821 vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) 822 823 // nolint: nestif 824 if !ioflag { 825 //nolint: nestif 826 if stateSyncPresent && i == len(txs)-1 { 827 if *config.BorTraceEnabled { 828 callmsg := prepareCallMessage(msg) 829 // nolint : contextcheck 830 if _, err := statefull.ApplyBorMessage(*vmenv, callmsg); err != nil { 831 failed = err 832 break 833 } 834 } else { 835 break 836 } 837 } else { 838 // nolint : contextcheck 839 if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background()); err != nil { 840 failed = err 841 break 842 } 843 // Finalize the state so any modifications are written to the trie 844 // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect 845 statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) 846 } 847 } else { 848 coinbaseBalance := statedb.GetBalance(blockCtx.Coinbase) 849 // nolint : contextcheck 850 result, err := core.ApplyMessageNoFeeBurnOrTip(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background()) 851 852 if err != nil { 853 failed = err 854 break 855 } 856 857 if london { 858 statedb.AddBalance(result.BurntContractAddress, result.FeeBurnt) 859 } 860 861 statedb.AddBalance(blockCtx.Coinbase, result.FeeTipped) 862 output1 := new(big.Int).SetBytes(result.SenderInitBalance.Bytes()) 863 output2 := new(big.Int).SetBytes(coinbaseBalance.Bytes()) 864 865 // Deprecating transfer log and will be removed in future fork. PLEASE DO NOT USE this transfer log going forward. Parameters won't get updated as expected going forward with EIP1559 866 // add transfer log 867 core.AddFeeTransferLog( 868 statedb, 869 870 msg.From(), 871 blockCtx.Coinbase, 872 873 result.FeeTipped, 874 result.SenderInitBalance, 875 coinbaseBalance, 876 output1.Sub(output1, result.FeeTipped), 877 output2.Add(output2, result.FeeTipped), 878 ) 879 880 // Finalize the state so any modifications are written to the trie 881 // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect 882 statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) 883 statedb.FlushMVWriteSet() 884 885 structRead := statedb.GetReadMapDump() 886 structWrite := statedb.GetWriteMapDump() 887 888 RWstruct = append(RWstruct, structRead...) 889 RWstruct = append(RWstruct, structWrite...) 890 } 891 } 892 893 if ioflag { 894 for _, val := range RWstruct { 895 IOdump += fmt.Sprintf("%v , %v, %v , %v, ", val.TxIdx, val.TxInc, val.VerIdx, val.VerInc) + hex.EncodeToString(val.Path) + ", " + val.Op 896 } 897 898 // make sure that the file exists and write IOdump 899 err = ioutil.WriteFile(filepath.Join(path, "data.csv"), []byte(fmt.Sprint(IOdump)), 0600) 900 if err != nil { 901 return nil, err 902 } 903 } 904 905 close(jobs) 906 pend.Wait() 907 908 // If execution failed in between, abort 909 if failed != nil { 910 return nil, failed 911 } 912 913 if !*config.BorTraceEnabled && stateSyncPresent { 914 return results[:len(results)-1], nil 915 } else { 916 return results, nil 917 } 918 } 919 920 // standardTraceBlockToFile configures a new tracer which uses standard JSON output, 921 // and traces either a full block or an individual transaction. The return value will 922 // be one filename per transaction traced. 923 func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block, config *StdTraceConfig) ([]string, error) { 924 if config == nil { 925 config = &StdTraceConfig{ 926 BorTraceEnabled: defaultBorTraceEnabled, 927 } 928 } 929 930 if config.BorTraceEnabled == nil { 931 config.BorTraceEnabled = defaultBorTraceEnabled 932 } 933 // If we're tracing a single transaction, make sure it's present 934 if config != nil && config.TxHash != (common.Hash{}) { 935 if !api.containsTx(ctx, block, config.TxHash) { 936 return nil, fmt.Errorf("transaction %#x not found in block", config.TxHash) 937 } 938 } 939 if block.NumberU64() == 0 { 940 return nil, errors.New("genesis is not traceable") 941 } 942 parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) 943 if err != nil { 944 return nil, err 945 } 946 reexec := defaultTraceReexec 947 if config != nil && config.Reexec != nil { 948 reexec = *config.Reexec 949 } 950 statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) 951 if err != nil { 952 return nil, err 953 } 954 // Retrieve the tracing configurations, or use default values 955 var ( 956 logConfig logger.Config 957 txHash common.Hash 958 ) 959 if config != nil { 960 logConfig = config.Config 961 txHash = config.TxHash 962 } 963 logConfig.Debug = true 964 965 // Execute transaction, either tracing all or just the requested one 966 var ( 967 dumps []string 968 signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) 969 chainConfig = api.backend.ChainConfig() 970 vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) 971 canon = true 972 ) 973 // Check if there are any overrides: the caller may wish to enable a future 974 // fork when executing this block. Note, such overrides are only applicable to the 975 // actual specified block, not any preceding blocks that we have to go through 976 // in order to obtain the state. 977 // Therefore, it's perfectly valid to specify `"futureForkBlock": 0`, to enable `futureFork` 978 979 if config != nil && config.Overrides != nil { 980 // Copy the config, to not screw up the main config 981 // Note: the Clique-part is _not_ deep copied 982 chainConfigCopy := new(params.ChainConfig) 983 *chainConfigCopy = *chainConfig 984 chainConfig = chainConfigCopy 985 if berlin := config.Config.Overrides.BerlinBlock; berlin != nil { 986 chainConfig.BerlinBlock = berlin 987 canon = false 988 } 989 } 990 991 txs, stateSyncPresent := api.getAllBlockTransactions(ctx, block) 992 if !*config.BorTraceEnabled && stateSyncPresent { 993 txs = txs[:len(txs)-1] 994 stateSyncPresent = false 995 } 996 997 for i, tx := range txs { 998 // Prepare the trasaction for un-traced execution 999 var ( 1000 msg, _ = tx.AsMessage(signer, block.BaseFee()) 1001 txContext = core.NewEVMTxContext(msg) 1002 vmConf vm.Config 1003 dump *os.File 1004 writer *bufio.Writer 1005 err error 1006 ) 1007 // If the transaction needs tracing, swap out the configs 1008 if tx.Hash() == txHash || txHash == (common.Hash{}) { 1009 // Generate a unique temporary file to dump it into 1010 prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) 1011 if !canon { 1012 prefix = fmt.Sprintf("%valt-", prefix) 1013 } 1014 dump, err = ioutil.TempFile(os.TempDir(), prefix) 1015 if err != nil { 1016 return nil, err 1017 } 1018 dumps = append(dumps, dump.Name()) 1019 1020 // Swap out the noop logger to the standard tracer 1021 writer = bufio.NewWriter(dump) 1022 vmConf = vm.Config{ 1023 Debug: true, 1024 Tracer: logger.NewJSONLogger(&logConfig, writer), 1025 EnablePreimageRecording: true, 1026 } 1027 } 1028 // Execute the transaction and flush any traces to disk 1029 vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) 1030 statedb.Prepare(tx.Hash(), i) 1031 //nolint: nestif 1032 if stateSyncPresent && i == len(txs)-1 { 1033 if *config.BorTraceEnabled { 1034 callmsg := prepareCallMessage(msg) 1035 _, err = statefull.ApplyBorMessage(*vmenv, callmsg) 1036 1037 if writer != nil { 1038 writer.Flush() 1039 } 1040 } 1041 } else { 1042 // nolint : contextcheck 1043 _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), context.Background()) 1044 if writer != nil { 1045 writer.Flush() 1046 } 1047 } 1048 1049 if dump != nil { 1050 dump.Close() 1051 log.Info("Wrote standard trace", "file", dump.Name()) 1052 } 1053 if err != nil { 1054 return dumps, err 1055 } 1056 // Finalize the state so any modifications are written to the trie 1057 // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect 1058 statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) 1059 1060 // If we've traced the transaction we were looking for, abort 1061 if tx.Hash() == txHash { 1062 break 1063 } 1064 } 1065 return dumps, nil 1066 } 1067 1068 // containsTx reports whether the transaction with a certain hash 1069 // is contained within the specified block. 1070 func (api *API) containsTx(ctx context.Context, block *types.Block, hash common.Hash) bool { 1071 txs, _ := api.getAllBlockTransactions(ctx, block) 1072 for _, tx := range txs { 1073 if tx.Hash() == hash { 1074 return true 1075 } 1076 } 1077 return false 1078 } 1079 1080 // TraceTransaction returns the structured logs created during the execution of EVM 1081 // and returns them as a JSON object. 1082 func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { 1083 if config == nil { 1084 config = &TraceConfig{ 1085 BorTraceEnabled: defaultBorTraceEnabled, 1086 BorTx: newBoolPtr(false), 1087 } 1088 } 1089 1090 if config.BorTraceEnabled == nil { 1091 config.BorTraceEnabled = defaultBorTraceEnabled 1092 } 1093 1094 tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) 1095 if tx == nil { 1096 // For BorTransaction, there will be no trace available 1097 tx, _, _, _ = rawdb.ReadBorTransaction(api.backend.ChainDb(), hash) 1098 if tx != nil { 1099 return ðapi.ExecutionResult{ 1100 StructLogs: make([]ethapi.StructLogRes, 0), 1101 }, nil 1102 } 1103 } 1104 if err != nil { 1105 return nil, err 1106 } 1107 1108 // It shouldn't happen in practice. 1109 if blockNumber == 0 { 1110 return nil, errors.New("genesis is not traceable") 1111 } 1112 reexec := defaultTraceReexec 1113 if config != nil && config.Reexec != nil { 1114 reexec = *config.Reexec 1115 } 1116 block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) 1117 if err != nil { 1118 return nil, err 1119 } 1120 msg, vmctx, statedb, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) 1121 if err != nil { 1122 return nil, err 1123 } 1124 txctx := &Context{ 1125 BlockHash: blockHash, 1126 TxIndex: int(index), 1127 TxHash: hash, 1128 } 1129 1130 return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) 1131 } 1132 1133 // TraceCall lets you trace a given eth_call. It collects the structured logs 1134 // created during the execution of EVM if the given transaction was added on 1135 // top of the provided block and returns them as a JSON object. 1136 // You can provide -2 as a block number to trace on top of the pending block. 1137 func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { 1138 // Try to retrieve the specified block 1139 var ( 1140 err error 1141 block *types.Block 1142 ) 1143 if hash, ok := blockNrOrHash.Hash(); ok { 1144 block, err = api.blockByHash(ctx, hash) 1145 } else if number, ok := blockNrOrHash.Number(); ok { 1146 block, err = api.blockByNumber(ctx, number) 1147 } else { 1148 return nil, errors.New("invalid arguments; neither block nor hash specified") 1149 } 1150 if err != nil { 1151 return nil, err 1152 } 1153 // try to recompute the state 1154 reexec := defaultTraceReexec 1155 if config != nil && config.Reexec != nil { 1156 reexec = *config.Reexec 1157 } 1158 statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false) 1159 if err != nil { 1160 return nil, err 1161 } 1162 // Apply the customized state rules if required. 1163 if config != nil { 1164 if err := config.StateOverrides.Apply(statedb); err != nil { 1165 return nil, err 1166 } 1167 } 1168 // Execute the trace 1169 msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) 1170 if err != nil { 1171 return nil, err 1172 } 1173 vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) 1174 1175 var traceConfig *TraceConfig 1176 if config != nil { 1177 traceConfig = &TraceConfig{ 1178 Config: config.Config, 1179 Tracer: config.Tracer, 1180 Timeout: config.Timeout, 1181 Reexec: config.Reexec, 1182 } 1183 } 1184 1185 return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) 1186 } 1187 1188 // traceTx configures a new tracer according to the provided configuration, and 1189 // executes the given message in the provided environment. The return value will 1190 // be tracer dependent. 1191 func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { 1192 1193 if config == nil { 1194 config = &TraceConfig{ 1195 BorTraceEnabled: defaultBorTraceEnabled, 1196 BorTx: newBoolPtr(false), 1197 } 1198 } 1199 1200 if config.BorTraceEnabled == nil { 1201 config.BorTraceEnabled = defaultBorTraceEnabled 1202 } 1203 1204 // Assemble the structured logger or the JavaScript tracer 1205 var ( 1206 tracer vm.EVMLogger 1207 err error 1208 txContext = core.NewEVMTxContext(message) 1209 ) 1210 switch { 1211 case config == nil: 1212 tracer = logger.NewStructLogger(nil) 1213 case config.Tracer != nil: 1214 // Define a meaningful timeout of a single transaction trace 1215 timeout := defaultTraceTimeout 1216 if config.Timeout != nil { 1217 if timeout, err = time.ParseDuration(*config.Timeout); err != nil { 1218 return nil, err 1219 } 1220 } 1221 if t, err := New(*config.Tracer, txctx); err != nil { 1222 return nil, err 1223 } else { 1224 deadlineCtx, cancel := context.WithTimeout(ctx, timeout) 1225 go func() { 1226 <-deadlineCtx.Done() 1227 if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) { 1228 t.Stop(errors.New("execution timeout")) 1229 } 1230 }() 1231 defer cancel() 1232 tracer = t 1233 } 1234 default: 1235 tracer = logger.NewStructLogger(config.Config) 1236 } 1237 // Run the transaction with tracing enabled. 1238 vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) 1239 1240 // Call Prepare to clear out the statedb access list 1241 statedb.Prepare(txctx.TxHash, txctx.TxIndex) 1242 1243 var result *core.ExecutionResult 1244 1245 if config.BorTx == nil { 1246 config.BorTx = newBoolPtr(false) 1247 } 1248 1249 if *config.BorTx { 1250 callmsg := prepareCallMessage(message) 1251 if result, err = statefull.ApplyBorMessage(*vmenv, callmsg); err != nil { 1252 return nil, fmt.Errorf("tracing failed: %w", err) 1253 } 1254 } else { 1255 // nolint : contextcheck 1256 result, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), context.Background()) 1257 if err != nil { 1258 return nil, fmt.Errorf("tracing failed: %w", err) 1259 } 1260 } 1261 1262 // Depending on the tracer type, format and return the output. 1263 switch tracer := tracer.(type) { 1264 case *logger.StructLogger: 1265 // If the result contains a revert reason, return it. 1266 returnVal := fmt.Sprintf("%x", result.Return()) 1267 if len(result.Revert()) > 0 { 1268 returnVal = fmt.Sprintf("%x", result.Revert()) 1269 } 1270 return ðapi.ExecutionResult{ 1271 Gas: result.UsedGas, 1272 Failed: result.Failed(), 1273 ReturnValue: returnVal, 1274 StructLogs: ethapi.FormatLogs(tracer.StructLogs()), 1275 }, nil 1276 1277 case Tracer: 1278 return tracer.GetResult() 1279 1280 default: 1281 panic(fmt.Sprintf("bad tracer type %T", tracer)) 1282 } 1283 } 1284 1285 // APIs return the collection of RPC services the tracer package offers. 1286 func APIs(backend Backend) []rpc.API { 1287 // Append all the local APIs and return 1288 return []rpc.API{ 1289 { 1290 Namespace: "debug", 1291 Version: "1.0", 1292 Service: NewAPI(backend), 1293 Public: false, 1294 }, 1295 } 1296 }