From d2ec519f15dac78aa49f4f734b4e36fd6712ee1d Mon Sep 17 00:00:00 2001 From: maskpp Date: Tue, 29 Apr 2025 11:59:42 +0800 Subject: [PATCH] update standard trace to file api --- eth/tracers/api.go | 70 ++++++++++------------ eth/tracers/api_test.go | 129 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 40 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 2c36e1942b..5c0c4bdbf4 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -17,7 +17,7 @@ package tracers import ( - "bufio" + "bytes" "context" "encoding/json" "errors" @@ -34,7 +34,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/logger" @@ -778,7 +777,9 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block // Note: This copies the config, to not screw up the main config chainConfig, canon = overrideConfig(chainConfig, config.Overrides) } - evm := vm.NewEVM(vmctx, statedb, chainConfig, vm.Config{}) + writer := bytes.NewBuffer(nil) + vmConf := vm.Config{Tracer: logger.NewJSONLogger(&logConfig, writer)} + evm := vm.NewEVM(vmctx, statedb, chainConfig, vmConf) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { core.ProcessBeaconBlockRoot(*beaconRoot, evm) } @@ -786,50 +787,18 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block core.ProcessParentBlockHash(block.ParentHash(), evm) } for i, tx := range block.Transactions() { - // Prepare the transaction for un-traced execution - var ( - msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee()) - tracer *tracing.Hooks - dump *os.File - writer *bufio.Writer - err error - ) - // If the transaction needs tracing, swap out the configs - if tx.Hash() == txHash || txHash == (common.Hash{}) { - // Generate a unique temporary file to dump it into - prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) - if !canon { - prefix = fmt.Sprintf("%valt-", prefix) - } - dump, err = os.CreateTemp(os.TempDir(), prefix) - if err != nil { - return nil, err - } - dumps = append(dumps, dump.Name()) - - // Swap out the noop logger to the standard tracer - writer = bufio.NewWriter(dump) - tracer = logger.NewJSONLogger(&logConfig, writer) - } - // Execute the transaction and flush any traces to disk + msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) statedb.SetTxContext(tx.Hash(), i) - if tracer != nil && tracer.OnTxStart != nil { - tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) + if vmConf.Tracer.OnTxStart != nil { + vmConf.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) } vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) - if tracer != nil && tracer.OnTxEnd != nil { + if vmConf.Tracer.OnTxEnd != nil { var receipt *types.Receipt if err == nil { receipt = &types.Receipt{GasUsed: vmRet.UsedGas} } - tracer.OnTxEnd(receipt, err) - } - if writer != nil { - writer.Flush() - } - if dump != nil { - dump.Close() - log.Info("Wrote standard trace", "file", dump.Name()) + vmConf.Tracer.OnTxEnd(receipt, err) } if err != nil { return dumps, err @@ -838,6 +807,27 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect statedb.Finalise(evm.ChainConfig().IsEIP158(block.Number())) + // If the transaction needs tracing, write the trace to disk. + if tx.Hash() == txHash || txHash == (common.Hash{}) { + // Generate a unique temporary file to dump it into + prefix := fmt.Sprintf("block_%#x-%d-%#x-", block.Hash().Bytes()[:4], i, tx.Hash().Bytes()[:4]) + if !canon { + prefix = fmt.Sprintf("%valt-", prefix) + } + dump, err := os.CreateTemp(os.TempDir(), prefix) + if err != nil { + return nil, err + } + if _, err := dump.Write(writer.Bytes()); err != nil { + return nil, err + } + dump.Close() + dumps = append(dumps, dump.Name()) + log.Info("Wrote standard trace", "file", dump.Name()) + } + // Clean the trace buffer. + writer.Reset() + // If we've traced the transaction we were looking for, abort if tx.Hash() == txHash { break diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 529448e397..0dddfd2c38 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "math/big" + "os" "reflect" "slices" "sync/atomic" @@ -1214,3 +1215,131 @@ func TestTraceBlockWithBasefee(t *testing.T) { } } } + +func TestStandardTraceBlockToFile(t *testing.T) { + var ( + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + + aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + }, + } + txHashs := make([]common.Hash, 0, 2) + backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to AA, to kill it + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 0, + To: &aa, + Value: big.NewInt(0), + Gas: 50000, + GasPrice: b.BaseFee(), + Data: nil, + }), types.HomesteadSigner{}, key) + b.AddTx(tx) + txHashs = append(txHashs, tx.Hash()) + // One transaction to AA, to recreate it (but without storage + tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 1, + To: &aa, + Value: big.NewInt(1), + Gas: 100000, + GasPrice: b.BaseFee(), + Data: nil, + }), types.HomesteadSigner{}, key) + b.AddTx(tx) + txHashs = append(txHashs, tx.Hash()) + }) + defer backend.chain.Stop() + + var testSuite = []struct { + blockNumber rpc.BlockNumber + config *StdTraceConfig + wants []string + expectErr error + }{ + { + blockNumber: rpc.BlockNumber(0), + expectErr: errors.New("genesis is not traceable"), + }, + { + blockNumber: rpc.LatestBlockNumber, + config: nil, // TxHash is not set, so we get all traces. + wants: []string{ + `{"pc":0,"op":88,"gas":"0x7148","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PC"} +{"pc":1,"op":255,"gas":"0x7146","gasCost":"0x1db0","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SELFDESTRUCT"} +{"output":"","gasUsed":"0x0"} +{"output":"","gasUsed":"0x1db2"}`, + `{"output":"","gasUsed":"0x0"}`, + }, + }, + { + blockNumber: rpc.LatestBlockNumber, + config: &StdTraceConfig{TxHash: txHashs[0]}, + wants: []string{ + `{"pc":0,"op":88,"gas":"0x7148","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PC"} +{"pc":1,"op":255,"gas":"0x7146","gasCost":"0x1db0","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SELFDESTRUCT"} +{"output":"","gasUsed":"0x0"} +{"output":"","gasUsed":"0x1db2"}`, + }, + }, + { + blockNumber: rpc.LatestBlockNumber, + config: &StdTraceConfig{TxHash: txHashs[1]}, + wants: []string{ + `{"output":"","gasUsed":"0x0"}`, + }, + }, + } + + api := NewAPI(backend) + for i, tc := range testSuite { + block, _ := api.blockByNumber(context.Background(), tc.blockNumber) + tracers, err := api.StandardTraceBlockToFile(context.Background(), block.Hash(), tc.config) + if tc.expectErr != nil { + if err == nil { + t.Errorf("test %d, want error %v", i, tc.expectErr) + continue + } + if !reflect.DeepEqual(err, tc.expectErr) { + t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err) + } + continue + } + if err != nil { + t.Errorf("test %d, want no error, have %v", i, err) + continue + } + if len(tracers) != len(tc.wants) { + t.Errorf("test %d, result length mismatch, have %d, want %d", i, len(tracers), len(tc.wants)) + continue + } + for j, tracer := range tracers { + data, _ := os.ReadFile(tracer) + if string(data[:len(data)-1]) != tc.wants[j] { + t.Errorf("test %d, result mismatch, want %s, have %s", i, tc.wants[j], string(data)) + } + } + } +}