diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 8bb1913928c..99cb74e9a4d 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -44,6 +44,7 @@ import ( "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/crypto" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/rlp" "github.com/ledgerwatch/erigon/tests" @@ -109,7 +110,7 @@ func Main(ctx *cli.Context) error { } if ctx.Bool(TraceFlag.Name) { // Configure the EVM logger - logConfig := &vm.LogConfig{ + logConfig := &logger.LogConfig{ DisableStack: ctx.Bool(TraceDisableStackFlag.Name), DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name), @@ -131,7 +132,7 @@ func Main(ctx *cli.Context) error { return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err2)) } prevFile = traceFile - return vm.NewJSONLogger(logConfig, traceFile), nil + return logger.NewJSONLogger(logConfig, traceFile), nil } } else { getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) { diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index a65493984df..fe9726d6854 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -42,6 +42,7 @@ import ( "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/runtime" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/params" ) @@ -117,7 +118,7 @@ func runCmd(ctx *cli.Context) error { //glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) //glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) //log.Root().SetHandler(glogger) - logconfig := &vm.LogConfig{ + logconfig := &logger.LogConfig{ DisableMemory: ctx.Bool(DisableMemoryFlag.Name), DisableStack: ctx.Bool(DisableStackFlag.Name), DisableStorage: ctx.Bool(DisableStorageFlag.Name), @@ -127,7 +128,7 @@ func runCmd(ctx *cli.Context) error { var ( tracer vm.EVMLogger - debugLogger *vm.StructLogger + debugLogger *logger.StructLogger statedb *state.IntraBlockState chainConfig *params.ChainConfig sender = common.BytesToAddress([]byte("sender")) @@ -135,12 +136,12 @@ func runCmd(ctx *cli.Context) error { genesisConfig *core.Genesis ) if ctx.Bool(MachineFlag.Name) { - tracer = vm.NewJSONLogger(logconfig, os.Stdout) + tracer = logger.NewJSONLogger(logconfig, os.Stdout) } else if ctx.Bool(DebugFlag.Name) { - debugLogger = vm.NewStructLogger(logconfig) + debugLogger = logger.NewStructLogger(logconfig) tracer = debugLogger } else { - debugLogger = vm.NewStructLogger(logconfig) + debugLogger = logger.NewStructLogger(logconfig) } db := memdb.New() if ctx.String(GenesisFlag.Name) != "" { @@ -317,13 +318,13 @@ func runCmd(ctx *cli.Context) error { if printErr != nil { log.Warn("Failed to print to stderr", "err", printErr) } - vm.WriteTrace(os.Stderr, debugLogger.StructLogs()) + logger.WriteTrace(os.Stderr, debugLogger.StructLogs()) } _, printErr := fmt.Fprintln(os.Stderr, "#### LOGS ####") if printErr != nil { log.Warn("Failed to print to stderr", "err", printErr) } - vm.WriteLogs(os.Stderr, statedb.Logs()) + logger.WriteLogs(os.Stderr, statedb.Logs()) } if bench || ctx.Bool(StatDumpFlag.Name) { diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 46e5d889a5f..36fcfb638e4 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -30,6 +30,7 @@ import ( "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/tests" "github.com/ledgerwatch/erigon/turbo/trie" ) @@ -60,7 +61,7 @@ func stateTestCmd(ctx *cli.Context) error { log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StderrHandler)) // Configure the EVM logger - config := &vm.LogConfig{ + config := &logger.LogConfig{ DisableMemory: ctx.Bool(DisableMemoryFlag.Name), DisableStack: ctx.Bool(DisableStackFlag.Name), DisableStorage: ctx.Bool(DisableStorageFlag.Name), @@ -68,18 +69,18 @@ func stateTestCmd(ctx *cli.Context) error { } var ( tracer vm.EVMLogger - debugger *vm.StructLogger + debugger *logger.StructLogger ) switch { case ctx.Bool(MachineFlag.Name): - tracer = vm.NewJSONLogger(config, os.Stderr) + tracer = logger.NewJSONLogger(config, os.Stderr) case ctx.Bool(DebugFlag.Name): - debugger = vm.NewStructLogger(config) + debugger = logger.NewStructLogger(config) tracer = debugger default: - debugger = vm.NewStructLogger(config) + debugger = logger.NewStructLogger(config) } // Load the test content from the input file src, err := os.ReadFile(ctx.Args().First()) @@ -106,7 +107,7 @@ func aggregateResultsFromStateTests( ctx *cli.Context, stateTests map[string]tests.StateTest, tracer vm.EVMLogger, - debugger *vm.StructLogger, + debugger *logger.StructLogger, ) ([]StatetestResult, error) { // Iterate over all the stateTests, run them and aggregate the results cfg := vm.Config{ @@ -175,7 +176,7 @@ func aggregateResultsFromStateTests( if printErr != nil { log.Warn("Failed to write to stderr", "err", printErr) } - vm.WriteTrace(os.Stderr, debugger.StructLogs()) + logger.WriteTrace(os.Stderr, debugger.StructLogs()) } } } diff --git a/cmd/integration/commands/state_stages.go b/cmd/integration/commands/state_stages.go index d55d7ab8686..95cb514b0d8 100644 --- a/cmd/integration/commands/state_stages.go +++ b/cmd/integration/commands/state_stages.go @@ -28,11 +28,11 @@ import ( "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/eth/integrity" "github.com/ledgerwatch/erigon/eth/stagedsync" "github.com/ledgerwatch/erigon/eth/stagedsync/stages" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/node/nodecfg" "github.com/ledgerwatch/erigon/params" erigoncli "github.com/ledgerwatch/erigon/turbo/cli" @@ -218,7 +218,7 @@ func syncBySmallSteps(db kv.RwDB, miningConfig params.MiningConfig, ctx context. } traceStart := func() { - vmConfig.Tracer = vm.NewStructLogger(&vm.LogConfig{}) + vmConfig.Tracer = logger.NewStructLogger(&logger.LogConfig{}) vmConfig.Debug = true } traceStop := func(id int) { @@ -231,7 +231,7 @@ func syncBySmallSteps(db kv.RwDB, miningConfig params.MiningConfig, ctx context. } encoder := json.NewEncoder(w) encoder.SetIndent(" ", " ") - for _, l := range vm.FormatLogs(vmConfig.Tracer.(*vm.StructLogger).StructLogs()) { + for _, l := range logger.FormatLogs(vmConfig.Tracer.(*logger.StructLogger).StructLogs()) { if err2 := encoder.Encode(l); err2 != nil { panic(err2) } diff --git a/cmd/rpcdaemon/commands/otterscan_default_tracer.go b/cmd/rpcdaemon/commands/otterscan_default_tracer.go index 4d73ad823d2..20984296d98 100644 --- a/cmd/rpcdaemon/commands/otterscan_default_tracer.go +++ b/cmd/rpcdaemon/commands/otterscan_default_tracer.go @@ -33,14 +33,3 @@ func (t *DefaultTracer) CaptureEnd(output []byte, usedGas uint64, err error) { func (t *DefaultTracer) CaptureExit(output []byte, usedGas uint64, err error) { } - -func (t *DefaultTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (t *DefaultTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -func (t *DefaultTracer) CaptureAccountWrite(account common.Address) error { - return nil -} diff --git a/cmd/rpcdaemon/commands/otterscan_trace_operations.go b/cmd/rpcdaemon/commands/otterscan_trace_operations.go index f8483fe68ee..2fb956be626 100644 --- a/cmd/rpcdaemon/commands/otterscan_trace_operations.go +++ b/cmd/rpcdaemon/commands/otterscan_trace_operations.go @@ -49,8 +49,7 @@ func (t *OperationsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to c if typ == vm.CREATE2 { t.Results = append(t.Results, &InternalOperation{OP_CREATE2, from, to, (*hexutil.Big)(value.ToBig())}) } -} - -func (l *OperationsTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { - l.Results = append(l.Results, &InternalOperation{OP_SELF_DESTRUCT, from, to, (*hexutil.Big)(value.ToBig())}) + if typ == vm.SELFDESTRUCT { + t.Results = append(t.Results, &InternalOperation{OP_SELF_DESTRUCT, from, to, (*hexutil.Big)(value.ToBig())}) + } } diff --git a/cmd/rpcdaemon/commands/otterscan_trace_transaction.go b/cmd/rpcdaemon/commands/otterscan_trace_transaction.go index b8778055e72..9d268cbd110 100644 --- a/cmd/rpcdaemon/commands/otterscan_trace_transaction.go +++ b/cmd/rpcdaemon/commands/otterscan_trace_transaction.go @@ -83,6 +83,11 @@ func (t *TransactionTracer) captureStartOrEnter(typ vm.OpCode, from, to common.A t.Results = append(t.Results, &TraceEntry{"CREATE2", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy}) return } + + if typ == vm.SELFDESTRUCT { + last := t.Results[len(t.Results)-1] + t.Results = append(t.Results, &TraceEntry{"SELFDESTRUCT", last.Depth + 1, from, to, (*hexutil.Big)(value.ToBig()), nil}) + } } func (t *TransactionTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { @@ -98,8 +103,3 @@ func (t *TransactionTracer) CaptureEnter(typ vm.OpCode, from common.Address, to func (t *TransactionTracer) CaptureExit(output []byte, usedGas uint64, err error) { t.depth-- } - -func (l *TransactionTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { - last := l.Results[len(l.Results)-1] - l.Results = append(l.Results, &TraceEntry{"SELFDESTRUCT", last.Depth + 1, from, to, (*hexutil.Big)(value.ToBig()), nil}) -} diff --git a/cmd/rpcdaemon/commands/trace_adhoc.go b/cmd/rpcdaemon/commands/trace_adhoc.go index 28852c8ac84..3b56f6a8e44 100644 --- a/cmd/rpcdaemon/commands/trace_adhoc.go +++ b/cmd/rpcdaemon/commands/trace_adhoc.go @@ -313,6 +313,21 @@ func (ot *OeTracer) captureStartOrEnter(deep bool, typ vm.OpCode, from common.Ad action.Init = common.CopyBytes(input) action.Value.ToInt().Set(value.ToBig()) trace.Action = &action + } else if typ == vm.SELFDESTRUCT { + trace := &ParityTrace{} + trace.Type = SUICIDE + action := &SuicideTraceAction{} + action.Address = from + action.RefundAddress = to + action.Balance.ToInt().Set(value.ToBig()) + trace.Action = action + topTrace := ot.traceStack[len(ot.traceStack)-1] + traceIdx := topTrace.Subtraces + ot.traceAddr = append(ot.traceAddr, traceIdx) + topTrace.Subtraces++ + trace.TraceAddress = make([]int, len(ot.traceAddr)) + copy(trace.TraceAddress, ot.traceAddr) + ot.traceAddr = ot.traceAddr[:len(ot.traceAddr)-1] } else { action := CallTraceAction{} switch typ { @@ -568,31 +583,6 @@ func (ot *OeTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scop func (ot *OeTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, opDepth int, err error) { } -func (ot *OeTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { - trace := &ParityTrace{} - trace.Type = SUICIDE - action := &SuicideTraceAction{} - action.Address = from - action.RefundAddress = to - action.Balance.ToInt().Set(value.ToBig()) - trace.Action = action - topTrace := ot.traceStack[len(ot.traceStack)-1] - traceIdx := topTrace.Subtraces - ot.traceAddr = append(ot.traceAddr, traceIdx) - topTrace.Subtraces++ - trace.TraceAddress = make([]int, len(ot.traceAddr)) - copy(trace.TraceAddress, ot.traceAddr) - ot.traceAddr = ot.traceAddr[:len(ot.traceAddr)-1] - ot.r.Trace = append(ot.r.Trace, trace) -} - -func (ot *OeTracer) CaptureAccountRead(account common.Address) error { - return nil -} -func (ot *OeTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - // Implements core/state/StateWriter to provide state diffs type StateDiff struct { sdMap map[common.Address]*StateDiffAccount diff --git a/cmd/state/commands/opcode_tracer.go b/cmd/state/commands/opcode_tracer.go index ccb8bb40753..4b2c9233a28 100644 --- a/cmd/state/commands/opcode_tracer.go +++ b/cmd/state/commands/opcode_tracer.go @@ -384,15 +384,6 @@ func (ot *opcodeTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, } -func (ot *opcodeTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} -func (ot *opcodeTracer) CaptureAccountRead(account common.Address) error { - return nil -} -func (ot *opcodeTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - type segPrefix struct { BlockNum uint64 NumTxs uint @@ -580,7 +571,6 @@ func OpcodeTracer(genesis *core.Genesis, blockNum uint64, chaindata string, numB dbstate := state.NewPlainState(historyTx, block.NumberU64(), systemcontracts.SystemContractCodeLookup[chainConfig.ChainName]) intraBlockState := state.New(dbstate) - intraBlockState.SetTracer(ot) getHeader := func(hash common.Hash, number uint64) *types.Header { return rawdb.ReadHeader(historyTx, hash, number) } receipts, err1 := runBlock(ethash.NewFullFaker(), intraBlockState, noOpWriter, noOpWriter, chainConfig, getHeader, block, vmConfig, false) diff --git a/cmd/state/exec3/calltracer22.go b/cmd/state/exec3/calltracer22.go index 37f4fd9a27a..0368773bc3f 100644 --- a/cmd/state/exec3/calltracer22.go +++ b/cmd/state/exec3/calltracer22.go @@ -40,13 +40,3 @@ func (ct *CallTracer) CaptureEnd(output []byte, usedGas uint64, err error) { } func (ct *CallTracer) CaptureExit(output []byte, usedGas uint64, err error) { } -func (ct *CallTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { - ct.froms[from] = struct{}{} - ct.tos[to] = struct{}{} -} -func (ct *CallTracer) CaptureAccountRead(account common.Address) error { - return nil -} -func (ct *CallTracer) CaptureAccountWrite(account common.Address) error { - return nil -} diff --git a/core/state/intra_block_state.go b/core/state/intra_block_state.go index e61fad8dc02..81becada430 100644 --- a/core/state/intra_block_state.go +++ b/core/state/intra_block_state.go @@ -38,11 +38,6 @@ type revision struct { journalIndex int } -type StateTracer interface { - CaptureAccountRead(account common.Address) error - CaptureAccountWrite(account common.Address) error -} - // SystemAddress - sender address for internal state updates. var SystemAddress = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") @@ -86,7 +81,6 @@ type IntraBlockState struct { journal *journal validRevisions []revision nextRevisionID int - tracer StateTracer trace bool accessList *accessList balanceInc map[common.Address]*BalanceIncrease // Map of balance increases (without first reading the account) @@ -106,10 +100,6 @@ func New(stateReader StateReader) *IntraBlockState { } } -func (sdb *IntraBlockState) SetTracer(tracer StateTracer) { - sdb.tracer = tracer -} - func (sdb *IntraBlockState) SetTrace(trace bool) { sdb.trace = trace } @@ -194,12 +184,6 @@ func (sdb *IntraBlockState) SubRefund(gas uint64) { // Exist reports whether the given account address exists in the state. // Notably this also returns true for suicided accounts. func (sdb *IntraBlockState) Exist(addr common.Address) bool { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - } s := sdb.getStateObject(addr) return s != nil && !s.deleted } @@ -207,12 +191,6 @@ func (sdb *IntraBlockState) Exist(addr common.Address) bool { // Empty returns whether the state object is either non-existent // or empty according to the EIP161 specification (balance = nonce = code = 0) func (sdb *IntraBlockState) Empty(addr common.Address) bool { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - } so := sdb.getStateObject(addr) return so == nil || so.deleted || so.empty() } @@ -220,12 +198,6 @@ func (sdb *IntraBlockState) Empty(addr common.Address) bool { // GetBalance retrieves the balance from the given address or 0 if object not found // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) GetBalance(addr common.Address) *uint256.Int { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - } stateObject := sdb.getStateObject(addr) if stateObject != nil && !stateObject.deleted { return stateObject.Balance() @@ -235,12 +207,6 @@ func (sdb *IntraBlockState) GetBalance(addr common.Address) *uint256.Int { // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) GetNonce(addr common.Address) uint64 { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - } stateObject := sdb.getStateObject(addr) if stateObject != nil && !stateObject.deleted { return stateObject.Nonce() @@ -256,12 +222,6 @@ func (sdb *IntraBlockState) TxIndex() int { // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) GetCode(addr common.Address) []byte { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - } stateObject := sdb.getStateObject(addr) if stateObject != nil && !stateObject.deleted { if sdb.trace { @@ -277,12 +237,6 @@ func (sdb *IntraBlockState) GetCode(addr common.Address) []byte { // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) GetCodeSize(addr common.Address) int { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - } stateObject := sdb.getStateObject(addr) if stateObject == nil || stateObject.deleted { return 0 @@ -299,12 +253,6 @@ func (sdb *IntraBlockState) GetCodeSize(addr common.Address) int { // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) GetCodeHash(addr common.Address) common.Hash { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - } stateObject := sdb.getStateObject(addr) if stateObject == nil || stateObject.deleted { return common.Hash{} @@ -334,7 +282,7 @@ func (sdb *IntraBlockState) GetCommittedState(addr common.Address, key *common.H } } -func (sdb *IntraBlockState) HasSuicided(addr common.Address) bool { +func (sdb *IntraBlockState) HasSelfdestructed(addr common.Address) bool { stateObject := sdb.getStateObject(addr) if stateObject == nil { return false @@ -345,7 +293,7 @@ func (sdb *IntraBlockState) HasSuicided(addr common.Address) bool { if stateObject.created { return false } - return stateObject.suicided + return stateObject.selfdestructed } /* @@ -358,12 +306,6 @@ func (sdb *IntraBlockState) AddBalance(addr common.Address, amount *uint256.Int) if sdb.trace { fmt.Printf("AddBalance %x, %d\n", addr, amount) } - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountWrite(addr) - if sdb.trace { - fmt.Println("CaptureAccountWrite err", err) - } - } // If this account has not been read, add to the balance increment map _, needAccount := sdb.stateObjects[addr] if !needAccount && addr == ripemd && amount.IsZero() { @@ -394,13 +336,6 @@ func (sdb *IntraBlockState) SubBalance(addr common.Address, amount *uint256.Int) if sdb.trace { fmt.Printf("SubBalance %x, %d\n", addr, amount) } - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountWrite(addr) - if sdb.trace { - fmt.Println("CaptureAccountWrite err", err) - } - - } stateObject := sdb.GetOrNewStateObject(addr) if stateObject != nil { @@ -410,13 +345,6 @@ func (sdb *IntraBlockState) SubBalance(addr common.Address, amount *uint256.Int) // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) SetBalance(addr common.Address, amount *uint256.Int) { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountWrite(addr) - if sdb.trace { - fmt.Println("CaptureAccountWrite err", err) - } - } - stateObject := sdb.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetBalance(amount) @@ -425,13 +353,6 @@ func (sdb *IntraBlockState) SetBalance(addr common.Address, amount *uint256.Int) // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) SetNonce(addr common.Address, nonce uint64) { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountWrite(addr) - if sdb.trace { - fmt.Println("CaptureAccountWrite err", err) - } - } - stateObject := sdb.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetNonce(nonce) @@ -441,13 +362,6 @@ func (sdb *IntraBlockState) SetNonce(addr common.Address, nonce uint64) { // DESCRIBED: docs/programmers_guide/guide.md#code-hash // DESCRIBED: docs/programmers_guide/guide.md#address---identifier-of-an-account func (sdb *IntraBlockState) SetCode(addr common.Address, code []byte) { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountWrite(addr) - if sdb.trace { - fmt.Println("CaptureAccountWrite err", err) - } - } - stateObject := sdb.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetCode(crypto.Keccak256Hash(code), code) @@ -487,32 +401,22 @@ func (sdb *IntraBlockState) GetIncarnation(addr common.Address) uint64 { return 0 } -// Suicide marks the given account as suicided. +// Selfdestruct marks the given account as suicided. // This clears the account balance. // // The account's state object is still available until the state is committed, // getStateObject will return a non-nil account after Suicide. -func (sdb *IntraBlockState) Suicide(addr common.Address) bool { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace { - fmt.Println("CaptureAccountRead err", err) - } - err = sdb.tracer.CaptureAccountWrite(addr) - if sdb.trace { - fmt.Println("CaptureAccountWrite err", err) - } - } +func (sdb *IntraBlockState) Selfdestruct(addr common.Address) bool { stateObject := sdb.getStateObject(addr) if stateObject == nil || stateObject.deleted { return false } - sdb.journal.append(suicideChange{ + sdb.journal.append(selfdestructChange{ account: &addr, - prev: stateObject.suicided, + prev: stateObject.selfdestructed, prevbalance: *stateObject.Balance(), }) - stateObject.markSuicided() + stateObject.markSelfdestructed() stateObject.created = false stateObject.data.Balance.Clear() @@ -602,22 +506,10 @@ func (sdb *IntraBlockState) createObject(addr common.Address, previous *stateObj // // Carrying over the balance ensures that Ether doesn't disappear. func (sdb *IntraBlockState) CreateAccount(addr common.Address, contractCreation bool) { - if sdb.tracer != nil { - err := sdb.tracer.CaptureAccountRead(addr) - if sdb.trace && err != nil { - log.Error("error while CaptureAccountRead", "err", err) - } - - err = sdb.tracer.CaptureAccountWrite(addr) - if sdb.trace && err != nil { - log.Error("error while CaptureAccountWrite", "err", err) - } - } - var prevInc uint64 previous := sdb.getStateObject(addr) if contractCreation { - if previous != nil && previous.suicided { + if previous != nil && previous.selfdestructed { prevInc = previous.data.Incarnation } else { inc, err := sdb.stateReader.ReadAccountIncarnation(addr) @@ -640,7 +532,7 @@ func (sdb *IntraBlockState) CreateAccount(addr common.Address, contractCreation newObj.created = true newObj.data.Incarnation = prevInc + 1 } else { - newObj.suicided = false + newObj.selfdestructed = false } } @@ -675,13 +567,13 @@ func (sdb *IntraBlockState) GetRefund() uint64 { func updateAccount(EIP161Enabled bool, isAura bool, stateWriter StateWriter, addr common.Address, stateObject *stateObject, isDirty bool) error { emptyRemoval := EIP161Enabled && stateObject.empty() && (!isAura || addr != SystemAddress) - if stateObject.suicided || (isDirty && emptyRemoval) { + if stateObject.selfdestructed || (isDirty && emptyRemoval) { if err := stateWriter.DeleteAccount(addr, &stateObject.original); err != nil { return err } stateObject.deleted = true } - if isDirty && (stateObject.created || !stateObject.suicided) && !emptyRemoval { + if isDirty && (stateObject.created || !stateObject.selfdestructed) && !emptyRemoval { stateObject.deleted = false // Write any contract code associated with the state object if stateObject.code != nil && stateObject.dirtyCode { @@ -706,10 +598,10 @@ func updateAccount(EIP161Enabled bool, isAura bool, stateWriter StateWriter, add func printAccount(EIP161Enabled bool, addr common.Address, stateObject *stateObject, isDirty bool) { emptyRemoval := EIP161Enabled && stateObject.empty() - if stateObject.suicided || (isDirty && emptyRemoval) { + if stateObject.selfdestructed || (isDirty && emptyRemoval) { fmt.Printf("delete: %x\n", addr) } - if isDirty && (stateObject.created || !stateObject.suicided) && !emptyRemoval { + if isDirty && (stateObject.created || !stateObject.selfdestructed) && !emptyRemoval { // Write any contract code associated with the state object if stateObject.code != nil && stateObject.dirtyCode { fmt.Printf("UpdateCode: %x,%x\n", addr, stateObject.CodeHash()) diff --git a/core/state/journal.go b/core/state/journal.go index d5183dbbc55..61ad9e44238 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -105,9 +105,9 @@ type ( account *common.Address prev *stateObject } - suicideChange struct { + selfdestructChange struct { account *common.Address - prev bool // whether account had already suicided + prev bool // whether account had already selfdestructed prevbalance uint256.Int } @@ -180,15 +180,15 @@ func (ch resetObjectChange) dirtied() *common.Address { return nil } -func (ch suicideChange) revert(s *IntraBlockState) { +func (ch selfdestructChange) revert(s *IntraBlockState) { obj := s.getStateObject(*ch.account) if obj != nil { - obj.suicided = ch.prev + obj.selfdestructed = ch.prev obj.setBalance(&ch.prevbalance) } } -func (ch suicideChange) dirtied() *common.Address { +func (ch selfdestructChange) dirtied() *common.Address { return ch.account } diff --git a/core/state/state_object.go b/core/state/state_object.go index ffdcde3e770..638e2bcd176 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -82,12 +82,12 @@ type stateObject struct { fakeStorage Storage // Fake storage which constructed by caller for debugging purpose. // Cache flags. - // When an object is marked suicided it will be delete from the trie + // When an object is marked selfdestructed it will be delete from the trie // during the "update" phase of the state transition. - dirtyCode bool // true if the code was updated - suicided bool - deleted bool // true if account was deleted during the lifetime of this object - created bool // true if this object represents a newly created contract + dirtyCode bool // true if the code was updated + selfdestructed bool + deleted bool // true if account was deleted during the lifetime of this object + created bool // true if this object represents a newly created contract } // empty returns whether the account is considered empty. @@ -132,8 +132,8 @@ func (so *stateObject) setError(err error) { } } -func (so *stateObject) markSuicided() { - so.suicided = true +func (so *stateObject) markSelfdestructed() { + so.selfdestructed = true } func (so *stateObject) touch() { diff --git a/core/state/state_test.go b/core/state/state_test.go index 361eaf1c6de..48b403ca0ed 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -222,7 +222,7 @@ func TestSnapshot2(t *testing.T) { so0.SetBalance(uint256.NewInt(42)) so0.SetNonce(43) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) - so0.suicided = false + so0.selfdestructed = false so0.deleted = false state.setStateObject(stateobjaddr0, so0) @@ -242,7 +242,7 @@ func TestSnapshot2(t *testing.T) { so1.SetBalance(uint256.NewInt(52)) so1.SetNonce(53) so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}) - so1.suicided = true + so1.selfdestructed = true so1.deleted = true state.setStateObject(stateobjaddr1, so1) diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go deleted file mode 100644 index 77fbe217a40..00000000000 --- a/core/vm/access_list_tracer.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package vm - -import ( - "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/core/types" -) - -// accessList is an accumulator for the set of accounts and storage slots an EVM -// contract execution touches. -type accessList map[common.Address]accessListSlots - -// accessListSlots is an accumulator for the set of storage slots within a single -// contract that an EVM contract execution touches. -type accessListSlots map[common.Hash]struct{} - -// newAccessList creates a new accessList. -func newAccessList() accessList { - return make(map[common.Address]accessListSlots) -} - -// addAddress adds an address to the accesslist. -func (al accessList) addAddress(address common.Address) { - // Set address if not previously present - if _, present := al[address]; !present { - al[address] = make(map[common.Hash]struct{}) - } -} - -// addSlot adds a storage slot to the accesslist. -func (al accessList) addSlot(address common.Address, slot common.Hash) { - // Set address if not previously present - al.addAddress(address) - - // Set the slot on the surely existent storage set - al[address][slot] = struct{}{} -} - -// equal checks if the content of the current access list is the same as the -// content of the other one. -func (al accessList) equal(other accessList) bool { - // Cross reference the accounts first - if len(al) != len(other) { - return false - } - for addr := range al { - if _, ok := other[addr]; !ok { - return false - } - } - for addr := range other { - if _, ok := al[addr]; !ok { - return false - } - } - // Accounts match, cross reference the storage slots too - for addr, slots := range al { - otherslots := other[addr] - - if len(slots) != len(otherslots) { - return false - } - for hash := range slots { - if _, ok := otherslots[hash]; !ok { - return false - } - } - for hash := range otherslots { - if _, ok := slots[hash]; !ok { - return false - } - } - } - return true -} - -// accesslist converts the accesslist to a types.AccessList. -func (al accessList) accessList() types.AccessList { - acl := make(types.AccessList, 0, len(al)) - for addr, slots := range al { - tuple := types.AccessTuple{Address: addr} - for slot := range slots { - tuple.StorageKeys = append(tuple.StorageKeys, slot) - } - acl = append(acl, tuple) - } - return acl -} - -var _ EVMLogger = (*AccessListTracer)(nil) - -// AccessListTracer is a tracer that accumulates touched accounts and storage -// slots into an internal set. -type AccessListTracer struct { - excl map[common.Address]struct{} // Set of account to exclude from the list - list accessList // Set of accounts and storage slots touched -} - -func (a *AccessListTracer) CaptureAccountWrite(account common.Address) error { - panic("implement me") -} - -func (*AccessListTracer) CaptureTxStart(gasLimit uint64) {} - -func (*AccessListTracer) CaptureTxEnd(restGas uint64) {} - -// NewAccessListTracer creates a new tracer that can generate AccessLists. -// An optional AccessList can be specified to occupy slots and addresses in -// the resulting accesslist. -func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompiles []common.Address) *AccessListTracer { - excl := map[common.Address]struct{}{ - from: {}, to: {}, - } - for _, addr := range precompiles { - excl[addr] = struct{}{} - } - list := newAccessList() - for _, al := range acl { - if _, ok := excl[al.Address]; !ok { - list.addAddress(al.Address) - } - for _, slot := range al.StorageKeys { - list.addSlot(al.Address, slot) - } - } - return &AccessListTracer{ - excl: excl, - list: list, - } -} - -// CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist. -func (a *AccessListTracer) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { - stack := scope.Stack - if (op == SLOAD || op == SSTORE) && stack.Len() >= 1 { - slot := common.Hash(stack.Data[stack.Len()-1].Bytes32()) - a.list.addSlot(scope.Contract.Address(), slot) - } - if (op == EXTCODECOPY || op == EXTCODEHASH || op == EXTCODESIZE || op == BALANCE || op == SELFDESTRUCT) && stack.Len() >= 1 { - addr := common.Address(stack.Data[stack.Len()-1].Bytes20()) - if _, ok := a.excl[addr]; !ok { - a.list.addAddress(addr) - } - } - if (op == DELEGATECALL || op == CALL || op == STATICCALL || op == CALLCODE) && stack.Len() >= 5 { - addr := common.Address(stack.Data[stack.Len()-2].Bytes20()) - if _, ok := a.excl[addr]; !ok { - a.list.addAddress(addr) - } - } -} - -func (*AccessListTracer) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { -} - -func (*AccessListTracer) CaptureEnd(output []byte, usedGas uint64, err error) { -} - -func (a *AccessListTracer) CaptureStart(env *EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - panic("implement me") -} - -func (a *AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - panic("implement me") -} - -func (*AccessListTracer) CaptureExit(output []byte, usedGas uint64, err error) { -} - -func (a *AccessListTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (a *AccessListTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -// AccessList returns the current accesslist maintained by the tracer. -func (a *AccessListTracer) AccessList() types.AccessList { - return a.list.accessList() -} - -// Equal returns if the content of two access list traces are equal. -func (a *AccessListTracer) Equal(other *AccessListTracer) bool { - return a.list.equal(other.list) -} diff --git a/core/vm/evmtypes/evmtypes.go b/core/vm/evmtypes/evmtypes.go index 0a92625b743..30c005152e9 100644 --- a/core/vm/evmtypes/evmtypes.go +++ b/core/vm/evmtypes/evmtypes.go @@ -73,8 +73,8 @@ type IntraBlockState interface { GetState(address common.Address, slot *common.Hash, outValue *uint256.Int) SetState(common.Address, *common.Hash, uint256.Int) - Suicide(common.Address) bool - HasSuicided(common.Address) bool + Selfdestruct(common.Address) bool + HasSelfdestructed(common.Address) bool // Exist reports whether the given account exists in state. // Notably this should also return true for suicided accounts. diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index c3f96101d8b..a749f17a112 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -490,7 +490,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *stack.Stack, mem *Memo } } - if !evm.IntraBlockState().HasSuicided(contract.Address()) { + if !evm.IntraBlockState().HasSelfdestructed(contract.Address()) { evm.IntraBlockState().AddRefund(params.SelfdestructRefundGas) } return gas, nil diff --git a/core/vm/instructions.go b/core/vm/instructions.go index e2d966a7e5e..27836e6129b 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -864,7 +864,7 @@ func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt return nil, errStopToken } -func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { +func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { if interpreter.readOnly { return nil, ErrWriteProtection } @@ -877,10 +877,9 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, callerAddr, beneficiaryAddr, false /* precompile */, false /* create */, []byte{}, 0, balance, nil /* code */) interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil) } - interpreter.evm.Config().Tracer.CaptureSelfDestruct(callerAddr, beneficiaryAddr, balance) } interpreter.evm.IntraBlockState().AddBalance(beneficiaryAddr, balance) - interpreter.evm.IntraBlockState().Suicide(callerAddr) + interpreter.evm.IntraBlockState().Selfdestruct(callerAddr) return nil, errStopToken } diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 1ffbf729198..bf36e10395e 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -1179,7 +1179,7 @@ func newFrontierInstructionSet() JumpTable { memorySize: memoryReturn, }, SELFDESTRUCT: { - execute: opSuicide, + execute: opSelfdestruct, dynamicGas: gasSelfdestruct, numPop: 1, numPush: 0, diff --git a/core/vm/logger.go b/core/vm/logger.go index 1268192a52a..e9b0ba2f1b6 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -17,92 +17,11 @@ package vm import ( - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "math/big" - "os" - "strings" - "github.com/holiman/uint256" "github.com/ledgerwatch/erigon/common" - "github.com/ledgerwatch/erigon/common/hexutil" - "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/params" ) -var ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") - -// Storage represents a contract's storage. -type Storage map[common.Hash]common.Hash - -// Copy duplicates the current storage. -func (s Storage) Copy() Storage { - cpy := make(Storage) - for key, value := range s { - cpy[key] = value - } - return cpy -} - -// LogConfig are the configuration options for structured logger the EVM -type LogConfig struct { - DisableMemory bool // disable memory capture - DisableStack bool // disable stack capture - DisableStorage bool // disable storage capture - DisableReturnData bool // disable return data capture - Debug bool // print output during capture end - Limit int // maximum length of output, but zero means unlimited - // Chain overrides, can be used to execute a trace using future fork rules - Overrides *params.ChainConfig `json:"overrides,omitempty"` -} - -//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go - -// StructLog is emitted to the EVM each cycle and lists information about the current internal state -// prior to the execution of the statement. -type StructLog struct { - Pc uint64 `json:"pc"` - Op OpCode `json:"op"` - Gas uint64 `json:"gas"` - GasCost uint64 `json:"gasCost"` - Memory []byte `json:"memory"` - MemorySize int `json:"memSize"` - Stack []*big.Int `json:"stack"` - ReturnData []byte `json:"returnData"` - Storage map[common.Hash]common.Hash `json:"-"` - Depth int `json:"depth"` - RefundCounter uint64 `json:"refund"` - Err error `json:"-"` -} - -// overrides for gencodec -type structLogMarshaling struct { - Stack []*math.HexOrDecimal256 - Gas math.HexOrDecimal64 - GasCost math.HexOrDecimal64 - Memory hexutil.Bytes - ReturnData hexutil.Bytes - OpName string `json:"opName"` // adds call to OpName() in MarshalJSON - ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON -} - -// OpName formats the operand name in a human-readable format. -func (s *StructLog) OpName() string { - return s.Op.String() -} - -// ErrorString formats the log's error as a string. -func (s *StructLog) ErrorString() string { - if s.Err != nil { - return s.Err.Error() - } - return "" -} - // EVMLogger is used to collect execution traces from an EVM transaction // execution. CaptureState is called for each step of the VM with the // current VM state. @@ -121,10 +40,6 @@ type EVMLogger interface { // Opcode level CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) - - CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) - CaptureAccountRead(account common.Address) error - CaptureAccountWrite(account common.Address) error } // FlushableTracer is a Tracer extension whose accumulated traces has to be @@ -133,355 +48,3 @@ type FlushableTracer interface { EVMLogger Flush(tx types.Transaction) } - -// StructLogRes stores a structured log emitted by the EVM while replaying a -// transaction in debug mode -type StructLogRes struct { - Pc uint64 `json:"pc"` - Op string `json:"op"` - Gas uint64 `json:"gas"` - GasCost uint64 `json:"gasCost"` - Depth int `json:"depth"` - Error error `json:"error,omitempty"` - Stack *[]string `json:"stack,omitempty"` - Memory *[]string `json:"memory,omitempty"` - Storage *map[string]string `json:"storage,omitempty"` -} - -// StructLogger is an EVM state logger and implements Tracer. -// -// StructLogger can capture state based on the given Log configuration and also keeps -// a track record of modified storage which is used in reporting snapshots of the -// contract their storage. -type StructLogger struct { - cfg LogConfig - - storage map[common.Address]Storage - logs []StructLog - output []byte - err error - env *EVM -} - -// NewStructLogger returns a new logger -func NewStructLogger(cfg *LogConfig) *StructLogger { - logger := &StructLogger{ - storage: make(map[common.Address]Storage), - } - if cfg != nil { - logger.cfg = *cfg - } - return logger -} - -func (l *StructLogger) CaptureTxStart(gasLimit uint64) {} - -func (l *StructLogger) CaptureTxEnd(restGas uint64) {} - -// CaptureStart implements the Tracer interface to initialize the tracing operation. -func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - l.env = env -} - -// CaptureEnter implements the Tracer interface to initialize the tracing operation for an internal call. -func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -// CaptureState logs a new structured log message and pushes it out to the environment -// -// CaptureState also tracks SLOAD/SSTORE ops to track storage change. -func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { - memory := scope.Memory - stack := scope.Stack - contract := scope.Contract - - // check if already accumulated the specified number of logs - if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { - return - } - - // Copy a snapshot of the current memory state to a new buffer - var mem []byte - if !l.cfg.DisableMemory { - mem = make([]byte, len(memory.Data())) - copy(mem, memory.Data()) - } - // Copy a snapshot of the current stack state to a new buffer - var stck []*big.Int - if !l.cfg.DisableStack { - stck = make([]*big.Int, len(stack.Data)) - for i, item := range stack.Data { - stck[i] = new(big.Int).Set(item.ToBig()) - } - } - // Copy a snapshot of the current storage to a new container - var storage Storage - if !l.cfg.DisableStorage { - // initialise new changed values storage container for this contract - // if not present. - if l.storage[contract.Address()] == nil { - l.storage[contract.Address()] = make(Storage) - } - // capture SLOAD opcodes and record the read entry in the local storage - if op == SLOAD && stack.Len() >= 1 { - var ( - address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) - value uint256.Int - ) - l.env.IntraBlockState().GetState(contract.Address(), &address, &value) - l.storage[contract.Address()][address] = value.Bytes32() - } - // capture SSTORE opcodes and record the written entry in the local storage. - if op == SSTORE && stack.Len() >= 2 { - var ( - value = common.Hash(stack.Data[stack.Len()-2].Bytes32()) - address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) - ) - l.storage[contract.Address()][address] = value - } - storage = l.storage[contract.Address()].Copy() - } - var rdata []byte - if !l.cfg.DisableReturnData { - rdata = make([]byte, len(rData)) - copy(rdata, rData) - } - // create a new snapshot of the EVM. - log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.IntraBlockState().GetRefund(), err} - l.logs = append(l.logs, log) -} - -// CaptureFault implements the Tracer interface to trace an execution fault -// while running an opcode. -func (l *StructLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (l *StructLogger) CaptureEnd(output []byte, usedGas uint64, err error) { - l.output = output - l.err = err - if l.cfg.Debug { - fmt.Printf("0x%x\n", output) - if err != nil { - fmt.Printf(" error: %v\n", err) - } - } -} - -// CaptureExit is called after the internal call finishes to finalize the tracing. -func (l *StructLogger) CaptureExit(output []byte, usedGas uint64, err error) { -} - -func (l *StructLogger) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (l *StructLogger) CaptureAccountRead(account common.Address) error { - return nil -} - -func (l *StructLogger) CaptureAccountWrite(account common.Address) error { - return nil -} - -// StructLogs returns the captured log entries. -func (l *StructLogger) StructLogs() []StructLog { return l.logs } - -// Error returns the VM error captured by the trace. -func (l *StructLogger) Error() error { return l.err } - -// Output returns the VM return value captured by the trace. -func (l *StructLogger) Output() []byte { return l.output } - -func (l *StructLogger) Flush(tx types.Transaction) { - w, err1 := os.Create(fmt.Sprintf("txtrace_%x.txt", tx.Hash())) - if err1 != nil { - panic(err1) - } - encoder := json.NewEncoder(w) - logs := FormatLogs(l.StructLogs()) - if err2 := encoder.Encode(logs); err2 != nil { - panic(err2) - } - if err2 := w.Close(); err2 != nil { - panic(err2) - } -} - -// FormatLogs formats EVM returned structured logs for json output -func FormatLogs(logs []StructLog) []StructLogRes { - formatted := make([]StructLogRes, len(logs)) - for index, trace := range logs { - formatted[index] = StructLogRes{ - Pc: trace.Pc, - Op: trace.Op.String(), - Gas: trace.Gas, - GasCost: trace.GasCost, - Depth: trace.Depth, - Error: trace.Err, - } - if trace.Stack != nil { - stack := make([]string, len(trace.Stack)) - for i, stackValue := range trace.Stack { - stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32)) - } - formatted[index].Stack = &stack - } - if trace.Memory != nil { - memory := make([]string, 0, (len(trace.Memory)+31)/32) - for i := 0; i+32 <= len(trace.Memory); i += 32 { - memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) - } - formatted[index].Memory = &memory - } - if trace.Storage != nil { - storage := make(map[string]string) - for i, storageValue := range trace.Storage { - storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) - } - formatted[index].Storage = &storage - } - } - return formatted -} - -// WriteTrace writes a formatted trace to the given writer -func WriteTrace(writer io.Writer, logs []StructLog) { - for _, log := range logs { - fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost) - if log.Err != nil { - fmt.Fprintf(writer, " ERROR: %v", log.Err) - } - fmt.Fprintln(writer) - - if len(log.Stack) > 0 { - fmt.Fprintln(writer, "Stack:") - for i := len(log.Stack) - 1; i >= 0; i-- { - fmt.Fprintf(writer, "%08d %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) - } - } - if len(log.Memory) > 0 { - fmt.Fprintln(writer, "Memory:") - fmt.Fprint(writer, hex.Dump(log.Memory)) - } - if len(log.Storage) > 0 { - fmt.Fprintln(writer, "Storage:") - for h, item := range log.Storage { - fmt.Fprintf(writer, "%x: %x\n", h, item) - } - } - if len(log.ReturnData) > 0 { - fmt.Fprintln(writer, "ReturnData:") - fmt.Fprint(writer, hex.Dump(log.ReturnData)) - } - fmt.Fprintln(writer) - } -} - -// WriteLogs writes vm logs in a readable format to the given writer -func WriteLogs(writer io.Writer, logs []*types.Log) { - for _, log := range logs { - fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex) - - for i, topic := range log.Topics { - fmt.Fprintf(writer, "%08d %x\n", i, topic) - } - - fmt.Fprint(writer, hex.Dump(log.Data)) - fmt.Fprintln(writer) - } -} - -type mdLogger struct { - out io.Writer - cfg *LogConfig - env *EVM -} - -// NewMarkdownLogger creates a logger which outputs information in a format adapted -// for human readability, and is also a valid markdown table -func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { - l := &mdLogger{writer, cfg, nil} - if l.cfg == nil { - l.cfg = &LogConfig{} - } - return l -} - -func (t *mdLogger) CaptureTxStart(gasLimit uint64) {} - -func (t *mdLogger) CaptureTxEnd(restGas uint64) {} - -func (t *mdLogger) captureStartOrEnter(from, to common.Address, create bool, input []byte, gas uint64, value *uint256.Int) { - if !create { - fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", - from.String(), to.String(), - input, gas, value) - } else { - fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", - from.String(), to.String(), - input, gas, value) - } - - fmt.Fprintf(t.out, ` -| Pc | Op | Cost | Stack | RStack | Refund | -|-------|-------------|------|-----------|-----------|---------| -`) -} - -func (t *mdLogger) CaptureStart(env *EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { //nolint:interfacer - t.env = env - t.captureStartOrEnter(from, to, create, input, gas, value) -} - -func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { //nolint:interfacer - t.captureStartOrEnter(from, to, create, input, gas, value) -} - -func (t *mdLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { - stack := scope.Stack - - fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) - - if !t.cfg.DisableStack { - // format stack - var a []string - for _, elem := range stack.Data { - a = append(a, fmt.Sprintf("%v", elem.String())) - } - b := fmt.Sprintf("[%v]", strings.Join(a, ",")) - fmt.Fprintf(t.out, "%10v |", b) - } - fmt.Fprintf(t.out, "%10v |", t.env.IntraBlockState().GetRefund()) - fmt.Fprintln(t.out, "") - if err != nil { - fmt.Fprintf(t.out, "Error: %v\n", err) - } -} - -func (t *mdLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { - fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) -} - -func (t *mdLogger) captureEndOrExit(output []byte, usedGas uint64, err error) { - fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", - output, usedGas, err) -} - -func (t *mdLogger) CaptureEnd(output []byte, usedGas uint64, err error) { - t.captureEndOrExit(output, usedGas, err) -} - -func (t *mdLogger) CaptureExit(output []byte, usedGas uint64, err error) { - t.captureEndOrExit(output, usedGas, err) -} - -func (t *mdLogger) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (t *mdLogger) CaptureAccountRead(account common.Address) error { - return nil -} - -func (t *mdLogger) CaptureAccountWrite(account common.Address) error { - return nil -} diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 4b54daa4a8a..3cc0daa1172 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -241,7 +241,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { if evm.IntraBlockState().Empty(address) && !evm.IntraBlockState().GetBalance(contract.Address()).IsZero() { gas += params.CreateBySelfdestructGas } - if refundsEnabled && !evm.IntraBlockState().HasSuicided(contract.Address()) { + if refundsEnabled && !evm.IntraBlockState().HasSelfdestructed(contract.Address()) { evm.IntraBlockState().AddRefund(params.SelfdestructRefundGas) } return gas, nil diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 2cf6f888d8d..bd77de38967 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -32,6 +32,7 @@ import ( "github.com/ledgerwatch/erigon/core/state" "github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/params" ) @@ -507,7 +508,7 @@ func TestEip2929Cases(t *testing.T) { Execute(code, nil, &Config{ EVMConfig: vm.Config{ Debug: true, - Tracer: vm.NewMarkdownLogger(nil, os.Stdout), + Tracer: logger.NewMarkdownLogger(nil, os.Stdout), ExtraEips: []int{2929}, }, }, 0) diff --git a/eth/calltracer/calltracer.go b/eth/calltracer/calltracer.go index f139b28a197..b2fe9d9f879 100644 --- a/eth/calltracer/calltracer.go +++ b/eth/calltracer/calltracer.go @@ -27,6 +27,7 @@ func NewCallTracer() *CallTracer { func (ct *CallTracer) CaptureTxStart(gasLimit uint64) {} func (ct *CallTracer) CaptureTxEnd(restGas uint64) {} +// CaptureStart and CaptureEnter also capture SELFDESTRUCT opcode invocations func (ct *CallTracer) captureStartOrEnter(from, to common.Address, create bool, code []byte) { ct.froms[from] = struct{}{} @@ -56,16 +57,6 @@ func (ct *CallTracer) CaptureEnd(output []byte, usedGas uint64, err error) { } func (ct *CallTracer) CaptureExit(output []byte, usedGas uint64, err error) { } -func (ct *CallTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { - ct.froms[from] = struct{}{} - ct.tos[to] = false -} -func (ct *CallTracer) CaptureAccountRead(account common.Address) error { - return nil -} -func (ct *CallTracer) CaptureAccountWrite(account common.Address) error { - return nil -} func (ct *CallTracer) WriteToDb(tx kv.StatelessWriteTx, block *types.Block, vmConfig vm.Config) error { ct.tos[block.Coinbase()] = false diff --git a/eth/stagedsync/stage_execute.go b/eth/stagedsync/stage_execute.go index 78fb2a155eb..540362c574e 100644 --- a/eth/stagedsync/stage_execute.go +++ b/eth/stagedsync/stage_execute.go @@ -34,6 +34,7 @@ import ( "github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/eth/ethconfig/estimate" "github.com/ledgerwatch/erigon/eth/stagedsync/stages" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/ethdb" "github.com/ledgerwatch/erigon/ethdb/olddb" "github.com/ledgerwatch/erigon/ethdb/prune" @@ -152,7 +153,7 @@ func executeBlock( } getTracer := func(txIndex int, txHash ecom.Hash) (vm.EVMLogger, error) { - return vm.NewStructLogger(&vm.LogConfig{}), nil + return logger.NewStructLogger(&logger.LogConfig{}), nil } callTracer := calltracer.NewCallTracer() diff --git a/eth/tracers/api.go b/eth/tracers/api.go index c1854e36db0..bb3b80be069 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1,13 +1,13 @@ package tracers import ( - "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/turbo/adapter/ethapi" ) // TraceConfig holds extra parameters to trace functions. type TraceConfig struct { - *vm.LogConfig + *logger.LogConfig Tracer *string Timeout *string Reexec *uint64 diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 1b31f7a6cbe..86af2a2bda3 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -513,16 +513,6 @@ func (t *jsTracer) setTypeConverters() error { return nil } -func (t *jsTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -func (t *jsTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - -func (t *jsTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) {} - type opObj struct { vm *goja.Runtime op vm.OpCode diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 0d485a82cb0..347990a1f43 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -177,14 +177,6 @@ func (*AccessListTracer) CaptureEnd(output []byte, usedGas uint64, err error) { } func (*AccessListTracer) CaptureExit(output []byte, usedGas uint64, err error) { } -func (*AccessListTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} -func (*AccessListTracer) CaptureAccountRead(account common.Address) error { - return nil -} -func (*AccessListTracer) CaptureAccountWrite(account common.Address) error { - return nil -} // AccessList returns the current accesslist maintained by the tracer. func (a *AccessListTracer) AccessList() types.AccessList { diff --git a/core/vm/gen_structlog.go b/eth/tracers/logger/gen_structlog.go similarity index 94% rename from core/vm/gen_structlog.go rename to eth/tracers/logger/gen_structlog.go index adaf717d00e..ee370b0f254 100644 --- a/core/vm/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -1,6 +1,4 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package vm +package logger import ( "encoding/json" @@ -9,6 +7,7 @@ import ( "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/common/math" + "github.com/ledgerwatch/erigon/core/vm" ) var _ = (*structLogMarshaling)(nil) @@ -17,7 +16,7 @@ var _ = (*structLogMarshaling)(nil) func (s StructLog) MarshalJSON() ([]byte, error) { type StructLog struct { Pc uint64 `json:"pc"` - Op OpCode `json:"op"` + Op vm.OpCode `json:"op"` Gas math.HexOrDecimal64 `json:"gas"` GasCost math.HexOrDecimal64 `json:"gasCost"` Memory hexutil.Bytes `json:"memory"` @@ -59,7 +58,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { func (s *StructLog) UnmarshalJSON(input []byte) error { type StructLog struct { Pc *uint64 `json:"pc"` - Op *OpCode `json:"op"` + Op *vm.OpCode `json:"op"` Gas *math.HexOrDecimal64 `json:"gas"` GasCost *math.HexOrDecimal64 `json:"gasCost"` Memory *hexutil.Bytes `json:"memory"` diff --git a/eth/tracers/logger/json_stream.go b/eth/tracers/logger/json_stream.go new file mode 100644 index 00000000000..ee4eeab7de2 --- /dev/null +++ b/eth/tracers/logger/json_stream.go @@ -0,0 +1,197 @@ +package logger + +import ( + "context" + "encoding/hex" + "sort" + + "github.com/holiman/uint256" + jsoniter "github.com/json-iterator/go" + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/core/vm" +) + +// JsonStreamLogger is an EVM state logger and implements Tracer. +// +// JsonStreamLogger can capture state based on the given Log configuration and also keeps +// a track record of modified storage which is used in reporting snapshots of the +// contract their storage. +type JsonStreamLogger struct { + ctx context.Context + cfg LogConfig + stream *jsoniter.Stream + hexEncodeBuf [128]byte + firstCapture bool + + locations common.Hashes // For sorting + storage map[common.Address]Storage + logs []StructLog + output []byte //nolint + err error //nolint + env *vm.EVM +} + +// NewStructLogger returns a new logger +func NewJsonStreamLogger(cfg *LogConfig, ctx context.Context, stream *jsoniter.Stream) *JsonStreamLogger { + logger := &JsonStreamLogger{ + ctx: ctx, + stream: stream, + storage: make(map[common.Address]Storage), + firstCapture: true, + } + if cfg != nil { + logger.cfg = *cfg + } + return logger +} + +func (l *JsonStreamLogger) CaptureTxStart(gasLimit uint64) {} + +func (l *JsonStreamLogger) CaptureTxEnd(restGas uint64) {} + +// CaptureStart implements the Tracer interface to initialize the tracing operation. +func (l *JsonStreamLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + l.env = env +} + +func (l *JsonStreamLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +} + +// CaptureState logs a new structured log message and pushes it out to the environment +// +// CaptureState also tracks SLOAD/SSTORE ops to track storage change. +func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + contract := scope.Contract + memory := scope.Memory + stack := scope.Stack + + select { + case <-l.ctx.Done(): + return + default: + } + // check if already accumulated the specified number of logs + if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { + return + } + if !l.firstCapture { + l.stream.WriteMore() + } else { + l.firstCapture = false + } + var outputStorage bool + if !l.cfg.DisableStorage { + // initialise new changed values storage container for this contract + // if not present. + if l.storage[contract.Address()] == nil { + l.storage[contract.Address()] = make(Storage) + } + // capture SLOAD opcodes and record the read entry in the local storage + if op == vm.SLOAD && stack.Len() >= 1 { + var ( + address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) + value uint256.Int + ) + l.env.IntraBlockState().GetState(contract.Address(), &address, &value) + l.storage[contract.Address()][address] = value.Bytes32() + outputStorage = true + } + // capture SSTORE opcodes and record the written entry in the local storage. + if op == vm.SSTORE && stack.Len() >= 2 { + var ( + value = common.Hash(stack.Data[stack.Len()-2].Bytes32()) + address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) + ) + l.storage[contract.Address()][address] = value + outputStorage = true + } + } + // create a new snapshot of the EVM. + l.stream.WriteObjectStart() + l.stream.WriteObjectField("pc") + l.stream.WriteUint64(pc) + l.stream.WriteMore() + l.stream.WriteObjectField("op") + l.stream.WriteString(op.String()) + l.stream.WriteMore() + l.stream.WriteObjectField("gas") + l.stream.WriteUint64(gas) + l.stream.WriteMore() + l.stream.WriteObjectField("gasCost") + l.stream.WriteUint64(cost) + l.stream.WriteMore() + l.stream.WriteObjectField("depth") + l.stream.WriteInt(depth) + if err != nil { + l.stream.WriteMore() + l.stream.WriteObjectField("error") + l.stream.WriteObjectStart() + l.stream.WriteObjectEnd() + //l.stream.WriteString(err.Error()) + } + if !l.cfg.DisableStack { + l.stream.WriteMore() + l.stream.WriteObjectField("stack") + l.stream.WriteArrayStart() + for i, stackValue := range stack.Data { + if i > 0 { + l.stream.WriteMore() + } + l.stream.WriteString(stackValue.String()) + } + l.stream.WriteArrayEnd() + } + if !l.cfg.DisableMemory { + memData := memory.Data() + l.stream.WriteMore() + l.stream.WriteObjectField("memory") + l.stream.WriteArrayStart() + for i := 0; i+32 <= len(memData); i += 32 { + if i > 0 { + l.stream.WriteMore() + } + l.stream.WriteString(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], memData[i:i+32])])) + } + l.stream.WriteArrayEnd() + } + if outputStorage { + l.stream.WriteMore() + l.stream.WriteObjectField("storage") + l.stream.WriteObjectStart() + first := true + // Sort storage by locations for easier comparison with geth + if l.locations != nil { + l.locations = l.locations[:0] + } + s := l.storage[contract.Address()] + for loc := range s { + l.locations = append(l.locations, loc) + } + sort.Sort(l.locations) + for _, loc := range l.locations { + value := s[loc] + if first { + first = false + } else { + l.stream.WriteMore() + } + l.stream.WriteObjectField(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], loc[:])])) + l.stream.WriteString(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], value[:])])) + } + l.stream.WriteObjectEnd() + } + l.stream.WriteObjectEnd() + _ = l.stream.Flush() +} + +// CaptureFault implements the Tracer interface to trace an execution fault +// while running an opcode. +func (l *JsonStreamLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (l *JsonStreamLogger) CaptureEnd(output []byte, usedGas uint64, err error) { +} + +func (l *JsonStreamLogger) CaptureExit(output []byte, usedGas uint64, err error) { +} diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go new file mode 100644 index 00000000000..67f77128c19 --- /dev/null +++ b/eth/tracers/logger/logger.go @@ -0,0 +1,419 @@ +package logger + +import ( + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "os" + "strings" + + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/common/hexutil" + "github.com/ledgerwatch/erigon/common/math" + "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/params" +) + +var ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") + +// Storage represents a contract's storage. +type Storage map[common.Hash]common.Hash + +// Copy duplicates the current storage. +func (s Storage) Copy() Storage { + cpy := make(Storage) + for key, value := range s { + cpy[key] = value + } + return cpy +} + +// LogConfig are the configuration options for structured logger the EVM +type LogConfig struct { + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + DisableReturnData bool // disable return data capture + Debug bool // print output during capture end + Limit int // maximum length of output, but zero means unlimited + // Chain overrides, can be used to execute a trace using future fork rules + Overrides *params.ChainConfig `json:"overrides,omitempty"` +} + +//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go + +// StructLog is emitted to the EVM each cycle and lists information about the current internal state +// prior to the execution of the statement. +type StructLog struct { + Pc uint64 `json:"pc"` + Op vm.OpCode `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Memory []byte `json:"memory"` + MemorySize int `json:"memSize"` + Stack []*big.Int `json:"stack"` + ReturnData []byte `json:"returnData"` + Storage map[common.Hash]common.Hash `json:"-"` + Depth int `json:"depth"` + RefundCounter uint64 `json:"refund"` + Err error `json:"-"` +} + +// overrides for gencodec +type structLogMarshaling struct { + Stack []*math.HexOrDecimal256 + Gas math.HexOrDecimal64 + GasCost math.HexOrDecimal64 + Memory hexutil.Bytes + ReturnData hexutil.Bytes + OpName string `json:"opName"` // adds call to OpName() in MarshalJSON + ErrorString string `json:"error"` // adds call to ErrorString() in MarshalJSON +} + +// OpName formats the operand name in a human-readable format. +func (s *StructLog) OpName() string { + return s.Op.String() +} + +// ErrorString formats the log's error as a string. +func (s *StructLog) ErrorString() string { + if s.Err != nil { + return s.Err.Error() + } + return "" +} + +// StructLogRes stores a structured log emitted by the EVM while replaying a +// transaction in debug mode +type StructLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Depth int `json:"depth"` + Error error `json:"error,omitempty"` + Stack *[]string `json:"stack,omitempty"` + Memory *[]string `json:"memory,omitempty"` + Storage *map[string]string `json:"storage,omitempty"` +} + +// StructLogger is an EVM state logger and implements Tracer. +// +// StructLogger can capture state based on the given Log configuration and also keeps +// a track record of modified storage which is used in reporting snapshots of the +// contract their storage. +type StructLogger struct { + cfg LogConfig + + storage map[common.Address]Storage + logs []StructLog + output []byte + err error + env *vm.EVM +} + +// NewStructLogger returns a new logger +func NewStructLogger(cfg *LogConfig) *StructLogger { + logger := &StructLogger{ + storage: make(map[common.Address]Storage), + } + if cfg != nil { + logger.cfg = *cfg + } + return logger +} + +func (l *StructLogger) CaptureTxStart(gasLimit uint64) {} + +func (l *StructLogger) CaptureTxEnd(restGas uint64) {} + +// CaptureStart implements the Tracer interface to initialize the tracing operation. +func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { + l.env = env +} + +// CaptureEnter implements the Tracer interface to initialize the tracing operation for an internal call. +func (l *StructLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +} + +// CaptureState logs a new structured log message and pushes it out to the environment +// +// CaptureState also tracks SLOAD/SSTORE ops to track storage change. +func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + memory := scope.Memory + stack := scope.Stack + contract := scope.Contract + + // check if already accumulated the specified number of logs + if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { + return + } + + // Copy a snapshot of the current memory state to a new buffer + var mem []byte + if !l.cfg.DisableMemory { + mem = make([]byte, len(memory.Data())) + copy(mem, memory.Data()) + } + // Copy a snapshot of the current stack state to a new buffer + var stck []*big.Int + if !l.cfg.DisableStack { + stck = make([]*big.Int, len(stack.Data)) + for i, item := range stack.Data { + stck[i] = new(big.Int).Set(item.ToBig()) + } + } + // Copy a snapshot of the current storage to a new container + var storage Storage + if !l.cfg.DisableStorage { + // initialise new changed values storage container for this contract + // if not present. + if l.storage[contract.Address()] == nil { + l.storage[contract.Address()] = make(Storage) + } + // capture SLOAD opcodes and record the read entry in the local storage + if op == vm.SLOAD && stack.Len() >= 1 { + var ( + address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) + value uint256.Int + ) + l.env.IntraBlockState().GetState(contract.Address(), &address, &value) + l.storage[contract.Address()][address] = value.Bytes32() + } + // capture SSTORE opcodes and record the written entry in the local storage. + if op == vm.SSTORE && stack.Len() >= 2 { + var ( + value = common.Hash(stack.Data[stack.Len()-2].Bytes32()) + address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) + ) + l.storage[contract.Address()][address] = value + } + storage = l.storage[contract.Address()].Copy() + } + var rdata []byte + if !l.cfg.DisableReturnData { + rdata = make([]byte, len(rData)) + copy(rdata, rData) + } + // create a new snapshot of the EVM. + log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.IntraBlockState().GetRefund(), err} + l.logs = append(l.logs, log) +} + +// CaptureFault implements the Tracer interface to trace an execution fault +// while running an opcode. +func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (l *StructLogger) CaptureEnd(output []byte, usedGas uint64, err error) { + l.output = output + l.err = err + if l.cfg.Debug { + fmt.Printf("0x%x\n", output) + if err != nil { + fmt.Printf(" error: %v\n", err) + } + } +} + +// CaptureExit is called after the internal call finishes to finalize the tracing. +func (l *StructLogger) CaptureExit(output []byte, usedGas uint64, err error) { +} + +// StructLogs returns the captured log entries. +func (l *StructLogger) StructLogs() []StructLog { return l.logs } + +// Error returns the VM error captured by the trace. +func (l *StructLogger) Error() error { return l.err } + +// Output returns the VM return value captured by the trace. +func (l *StructLogger) Output() []byte { return l.output } + +func (l *StructLogger) Flush(tx types.Transaction) { + w, err1 := os.Create(fmt.Sprintf("txtrace_%x.txt", tx.Hash())) + if err1 != nil { + panic(err1) + } + encoder := json.NewEncoder(w) + logs := FormatLogs(l.StructLogs()) + if err2 := encoder.Encode(logs); err2 != nil { + panic(err2) + } + if err2 := w.Close(); err2 != nil { + panic(err2) + } +} + +// FormatLogs formats EVM returned structured logs for json output +func FormatLogs(logs []StructLog) []StructLogRes { + formatted := make([]StructLogRes, len(logs)) + for index, trace := range logs { + formatted[index] = StructLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Depth: trace.Depth, + Error: trace.Err, + } + if trace.Stack != nil { + stack := make([]string, len(trace.Stack)) + for i, stackValue := range trace.Stack { + stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32)) + } + formatted[index].Stack = &stack + } + if trace.Memory != nil { + memory := make([]string, 0, (len(trace.Memory)+31)/32) + for i := 0; i+32 <= len(trace.Memory); i += 32 { + memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + formatted[index].Memory = &memory + } + if trace.Storage != nil { + storage := make(map[string]string) + for i, storageValue := range trace.Storage { + storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + formatted[index].Storage = &storage + } + } + return formatted +} + +// WriteTrace writes a formatted trace to the given writer +func WriteTrace(writer io.Writer, logs []StructLog) { + for _, log := range logs { + fmt.Fprintf(writer, "%-16spc=%08d gas=%v cost=%v", log.Op, log.Pc, log.Gas, log.GasCost) + if log.Err != nil { + fmt.Fprintf(writer, " ERROR: %v", log.Err) + } + fmt.Fprintln(writer) + + if len(log.Stack) > 0 { + fmt.Fprintln(writer, "Stack:") + for i := len(log.Stack) - 1; i >= 0; i-- { + fmt.Fprintf(writer, "%08d %x\n", len(log.Stack)-i-1, math.PaddedBigBytes(log.Stack[i], 32)) + } + } + if len(log.Memory) > 0 { + fmt.Fprintln(writer, "Memory:") + fmt.Fprint(writer, hex.Dump(log.Memory)) + } + if len(log.Storage) > 0 { + fmt.Fprintln(writer, "Storage:") + for h, item := range log.Storage { + fmt.Fprintf(writer, "%x: %x\n", h, item) + } + } + if len(log.ReturnData) > 0 { + fmt.Fprintln(writer, "ReturnData:") + fmt.Fprint(writer, hex.Dump(log.ReturnData)) + } + fmt.Fprintln(writer) + } +} + +// WriteLogs writes vm logs in a readable format to the given writer +func WriteLogs(writer io.Writer, logs []*types.Log) { + for _, log := range logs { + fmt.Fprintf(writer, "LOG%d: %x bn=%d txi=%x\n", len(log.Topics), log.Address, log.BlockNumber, log.TxIndex) + + for i, topic := range log.Topics { + fmt.Fprintf(writer, "%08d %x\n", i, topic) + } + + fmt.Fprint(writer, hex.Dump(log.Data)) + fmt.Fprintln(writer) + } +} + +type mdLogger struct { + out io.Writer + cfg *LogConfig + env *vm.EVM +} + +// NewMarkdownLogger creates a logger which outputs information in a format adapted +// for human readability, and is also a valid markdown table +func NewMarkdownLogger(cfg *LogConfig, writer io.Writer) *mdLogger { + l := &mdLogger{writer, cfg, nil} + if l.cfg == nil { + l.cfg = &LogConfig{} + } + return l +} + +func (t *mdLogger) CaptureTxStart(gasLimit uint64) {} + +func (t *mdLogger) CaptureTxEnd(restGas uint64) {} + +func (t *mdLogger) captureStartOrEnter(from, to common.Address, create bool, input []byte, gas uint64, value *uint256.Int) { + if !create { + fmt.Fprintf(t.out, "From: `%v`\nTo: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } else { + fmt.Fprintf(t.out, "From: `%v`\nCreate at: `%v`\nData: `0x%x`\nGas: `%d`\nValue `%v` wei\n", + from.String(), to.String(), + input, gas, value) + } + + fmt.Fprintf(t.out, ` +| Pc | Op | Cost | Stack | RStack | Refund | +|-------|-------------|------|-----------|-----------|---------| +`) +} + +func (t *mdLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { //nolint:interfacer + t.env = env + t.captureStartOrEnter(from, to, create, input, gas, value) +} + +func (t *mdLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { //nolint:interfacer + t.captureStartOrEnter(from, to, create, input, gas, value) +} + +func (t *mdLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + stack := scope.Stack + + fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) + + if !t.cfg.DisableStack { + // format stack + var a []string + for _, elem := range stack.Data { + a = append(a, fmt.Sprintf("%v", elem.String())) + } + b := fmt.Sprintf("[%v]", strings.Join(a, ",")) + fmt.Fprintf(t.out, "%10v |", b) + } + fmt.Fprintf(t.out, "%10v |", t.env.IntraBlockState().GetRefund()) + fmt.Fprintln(t.out, "") + if err != nil { + fmt.Fprintf(t.out, "Error: %v\n", err) + } +} + +func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { + fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) +} + +func (t *mdLogger) captureEndOrExit(output []byte, usedGas uint64, err error) { + fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n", + output, usedGas, err) +} + +func (t *mdLogger) CaptureEnd(output []byte, usedGas uint64, err error) { + t.captureEndOrExit(output, usedGas, err) +} + +func (t *mdLogger) CaptureExit(output []byte, usedGas uint64, err error) { + t.captureEndOrExit(output, usedGas, err) +} diff --git a/core/vm/logger_json.go b/eth/tracers/logger/logger_json.go similarity index 74% rename from core/vm/logger_json.go rename to eth/tracers/logger/logger_json.go index 3ebc04988a7..9beb87da1e1 100644 --- a/core/vm/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package vm +package logger import ( "encoding/json" @@ -24,12 +24,13 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/erigon/common" "github.com/ledgerwatch/erigon/common/math" + "github.com/ledgerwatch/erigon/core/vm" ) type JSONLogger struct { encoder *json.Encoder cfg *LogConfig - env *EVM + env *vm.EVM } // NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects @@ -46,15 +47,15 @@ func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {} func (l *JSONLogger) CaptureTxEnd(restGas uint64) {} -func (l *JSONLogger) CaptureStart(env *EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (l *JSONLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { l.env = env } -func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { +func (l *JSONLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { } // CaptureState outputs state information on the logger. -func (l *JSONLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { +func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { memory := scope.Memory stack := scope.Stack @@ -84,7 +85,7 @@ func (l *JSONLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope } // CaptureFault outputs state information on the logger. -func (l *JSONLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { +func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { } // CaptureEnd is triggered at end of execution. @@ -103,14 +104,3 @@ func (l *JSONLogger) CaptureEnd(output []byte, usedGas uint64, err error) { func (l *JSONLogger) CaptureExit(output []byte, usedGas uint64, err error) { } - -func (l *JSONLogger) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (l *JSONLogger) CaptureAccountRead(account common.Address) error { - return nil -} - -func (l *JSONLogger) CaptureAccountWrite(account common.Address) error { - return nil -} diff --git a/core/vm/logger_test.go b/eth/tracers/logger/logger_test.go similarity index 57% rename from core/vm/logger_test.go rename to eth/tracers/logger/logger_test.go index 9ad7a0613b4..02a0627279f 100644 --- a/core/vm/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -14,13 +14,15 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package vm +package logger import ( "math/big" "testing" "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/vm" "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/common" @@ -28,19 +30,42 @@ import ( "github.com/ledgerwatch/erigon/params" ) +type dummyContractRef struct { + calledForEach bool +} + +func (dummyContractRef) ReturnGas(*big.Int) {} +func (dummyContractRef) Address() common.Address { return common.Address{} } +func (dummyContractRef) Value() *big.Int { return new(big.Int) } +func (dummyContractRef) SetCode(common.Hash, []byte) {} +func (d *dummyContractRef) ForEachStorage(callback func(key, value common.Hash) bool) { + d.calledForEach = true +} +func (d *dummyContractRef) SubBalance(amount *big.Int) {} +func (d *dummyContractRef) AddBalance(amount *big.Int) {} +func (d *dummyContractRef) SetBalance(*big.Int) {} +func (d *dummyContractRef) SetNonce(uint64) {} +func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } + +type dummyStatedb struct { + state.IntraBlockState +} + +func (*dummyStatedb) GetRefund() uint64 { return 1337 } + func TestStoreCapture(t *testing.T) { var ( - env = NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) + env = vm.NewEVM(evmtypes.BlockContext{}, evmtypes.TxContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{}) logger = NewStructLogger(nil) - mem = NewMemory() + mem = vm.NewMemory() stack = stack.New() - contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(uint256.Int), 0, false /* skipAnalysis */) + contract = vm.NewContract(&dummyContractRef{}, &dummyContractRef{}, new(uint256.Int), 0, false /* skipAnalysis */) ) stack.Push(uint256.NewInt(1)) stack.Push(uint256.NewInt(0)) var index common.Hash logger.CaptureStart(env, common.Address{}, common.Address{}, false, false, nil, 0, nil, nil) - logger.CaptureState(0, SSTORE, 0, 0, &ScopeContext{ + logger.CaptureState(0, vm.SSTORE, 0, 0, &vm.ScopeContext{ Memory: mem, Stack: stack, Contract: contract, diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index e62cc5e9954..98a32efdd14 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -111,17 +111,6 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm t.store(input[0:4], len(input)-4) } -func (t *fourByteTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (t *fourByteTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -func (t *fourByteTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *fourByteTracer) GetResult() (json.RawMessage, error) { diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 63284dbd686..458e8deb554 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -239,17 +239,6 @@ func (t *callTracer) CaptureTxEnd(restGas uint64) { } } -func (t *callTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (t *callTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -func (t *callTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *callTracer) GetResult() (json.RawMessage, error) { diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 77ca6c5d57f..76695514ed3 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -113,17 +113,6 @@ func (t *muxTracer) CaptureTxEnd(restGas uint64) { } } -func (t *muxTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (t *muxTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -func (t *muxTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - // GetResult returns an empty json object. func (t *muxTracer) GetResult() (json.RawMessage, error) { resObject := make(map[string]json.RawMessage) diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index 59e52530675..ca7c5d7f79a 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -67,17 +67,6 @@ func (*noopTracer) CaptureTxStart(gasLimit uint64) {} func (*noopTracer) CaptureTxEnd(restGas uint64) {} -func (*noopTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (*noopTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -func (*noopTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - // GetResult returns an empty json object. func (t *noopTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(`{}`), nil diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 7db3437ba53..154e419210b 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -236,17 +236,6 @@ func (t *prestateTracer) CaptureTxEnd(restGas uint64) { } } -func (t *prestateTracer) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (t *prestateTracer) CaptureAccountRead(account common.Address) error { - return nil -} - -func (t *prestateTracer) CaptureAccountWrite(account common.Address) error { - return nil -} - // GetResult returns the json-encoded nested list of call traces, and any // error arising from the encoding or forceful termination (via `Stop`). func (t *prestateTracer) GetResult() (json.RawMessage, error) { diff --git a/turbo/adapter/ethapi/api.go b/turbo/adapter/ethapi/api.go index a158357550f..0560173cddf 100644 --- a/turbo/adapter/ethapi/api.go +++ b/turbo/adapter/ethapi/api.go @@ -28,7 +28,7 @@ import ( "github.com/ledgerwatch/erigon/common/math" "github.com/ledgerwatch/erigon/core" "github.com/ledgerwatch/erigon/core/types" - "github.com/ledgerwatch/erigon/core/vm" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/log/v3" ) @@ -215,7 +215,7 @@ type StructLogRes struct { } // FormatLogs formats EVM returned structured logs for json output -func FormatLogs(logs []vm.StructLog) []StructLogRes { +func FormatLogs(logs []logger.StructLog) []StructLogRes { formatted := make([]StructLogRes, len(logs)) for index, trace := range logs { formatted[index] = StructLogRes{ diff --git a/turbo/transactions/tracing.go b/turbo/transactions/tracing.go index b67b5c5069d..755d2903aee 100644 --- a/turbo/transactions/tracing.go +++ b/turbo/transactions/tracing.go @@ -6,10 +6,8 @@ import ( "encoding/json" "errors" "fmt" - "sort" "time" - "github.com/holiman/uint256" jsoniter "github.com/json-iterator/go" "github.com/ledgerwatch/erigon-lib/kv" state2 "github.com/ledgerwatch/erigon-lib/state" @@ -22,6 +20,7 @@ import ( "github.com/ledgerwatch/erigon/core/vm/evmtypes" "github.com/ledgerwatch/erigon/eth/stagedsync" "github.com/ledgerwatch/erigon/eth/tracers" + "github.com/ledgerwatch/erigon/eth/tracers/logger" "github.com/ledgerwatch/erigon/params" "github.com/ledgerwatch/erigon/turbo/rpchelper" "github.com/ledgerwatch/erigon/turbo/services" @@ -166,11 +165,11 @@ func TraceTx( streaming = false case config == nil: - tracer = NewJsonStreamLogger(nil, ctx, stream) + tracer = logger.NewJsonStreamLogger(nil, ctx, stream) streaming = true default: - tracer = NewJsonStreamLogger(config.LogConfig, ctx, stream) + tracer = logger.NewJsonStreamLogger(config.LogConfig, ctx, stream) streaming = true } // Run the transaction with tracing enabled. @@ -219,199 +218,3 @@ func TraceTx( } return nil } - -// StructLogger is an EVM state logger and implements Tracer. -// -// StructLogger can capture state based on the given Log configuration and also keeps -// a track record of modified storage which is used in reporting snapshots of the -// contract their storage. -type JsonStreamLogger struct { - ctx context.Context - cfg vm.LogConfig - stream *jsoniter.Stream - hexEncodeBuf [128]byte - firstCapture bool - - locations common.Hashes // For sorting - storage map[common.Address]vm.Storage - logs []vm.StructLog - output []byte //nolint - err error //nolint - env *vm.EVM -} - -// NewStructLogger returns a new logger -func NewJsonStreamLogger(cfg *vm.LogConfig, ctx context.Context, stream *jsoniter.Stream) *JsonStreamLogger { - logger := &JsonStreamLogger{ - ctx: ctx, - stream: stream, - storage: make(map[common.Address]vm.Storage), - firstCapture: true, - } - if cfg != nil { - logger.cfg = *cfg - } - return logger -} - -func (l *JsonStreamLogger) CaptureTxStart(gasLimit uint64) {} - -func (l *JsonStreamLogger) CaptureTxEnd(restGas uint64) {} - -// CaptureStart implements the Tracer interface to initialize the tracing operation. -func (l *JsonStreamLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { - l.env = env -} - -func (l *JsonStreamLogger) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { -} - -// CaptureState logs a new structured log message and pushes it out to the environment -// -// CaptureState also tracks SLOAD/SSTORE ops to track storage change. -func (l *JsonStreamLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - contract := scope.Contract - memory := scope.Memory - stack := scope.Stack - - select { - case <-l.ctx.Done(): - return - default: - } - // check if already accumulated the specified number of logs - if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { - return - } - if !l.firstCapture { - l.stream.WriteMore() - } else { - l.firstCapture = false - } - var outputStorage bool - if !l.cfg.DisableStorage { - // initialise new changed values storage container for this contract - // if not present. - if l.storage[contract.Address()] == nil { - l.storage[contract.Address()] = make(vm.Storage) - } - // capture SLOAD opcodes and record the read entry in the local storage - if op == vm.SLOAD && stack.Len() >= 1 { - var ( - address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) - value uint256.Int - ) - l.env.IntraBlockState().GetState(contract.Address(), &address, &value) - l.storage[contract.Address()][address] = value.Bytes32() - outputStorage = true - } - // capture SSTORE opcodes and record the written entry in the local storage. - if op == vm.SSTORE && stack.Len() >= 2 { - var ( - value = common.Hash(stack.Data[stack.Len()-2].Bytes32()) - address = common.Hash(stack.Data[stack.Len()-1].Bytes32()) - ) - l.storage[contract.Address()][address] = value - outputStorage = true - } - } - // create a new snapshot of the EVM. - l.stream.WriteObjectStart() - l.stream.WriteObjectField("pc") - l.stream.WriteUint64(pc) - l.stream.WriteMore() - l.stream.WriteObjectField("op") - l.stream.WriteString(op.String()) - l.stream.WriteMore() - l.stream.WriteObjectField("gas") - l.stream.WriteUint64(gas) - l.stream.WriteMore() - l.stream.WriteObjectField("gasCost") - l.stream.WriteUint64(cost) - l.stream.WriteMore() - l.stream.WriteObjectField("depth") - l.stream.WriteInt(depth) - if err != nil { - l.stream.WriteMore() - l.stream.WriteObjectField("error") - l.stream.WriteObjectStart() - l.stream.WriteObjectEnd() - //l.stream.WriteString(err.Error()) - } - if !l.cfg.DisableStack { - l.stream.WriteMore() - l.stream.WriteObjectField("stack") - l.stream.WriteArrayStart() - for i, stackValue := range stack.Data { - if i > 0 { - l.stream.WriteMore() - } - l.stream.WriteString(stackValue.String()) - } - l.stream.WriteArrayEnd() - } - if !l.cfg.DisableMemory { - memData := memory.Data() - l.stream.WriteMore() - l.stream.WriteObjectField("memory") - l.stream.WriteArrayStart() - for i := 0; i+32 <= len(memData); i += 32 { - if i > 0 { - l.stream.WriteMore() - } - l.stream.WriteString(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], memData[i:i+32])])) - } - l.stream.WriteArrayEnd() - } - if outputStorage { - l.stream.WriteMore() - l.stream.WriteObjectField("storage") - l.stream.WriteObjectStart() - first := true - // Sort storage by locations for easier comparison with geth - if l.locations != nil { - l.locations = l.locations[:0] - } - s := l.storage[contract.Address()] - for loc := range s { - l.locations = append(l.locations, loc) - } - sort.Sort(l.locations) - for _, loc := range l.locations { - value := s[loc] - if first { - first = false - } else { - l.stream.WriteMore() - } - l.stream.WriteObjectField(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], loc[:])])) - l.stream.WriteString(string(l.hexEncodeBuf[0:hex.Encode(l.hexEncodeBuf[:], value[:])])) - } - l.stream.WriteObjectEnd() - } - l.stream.WriteObjectEnd() - _ = l.stream.Flush() -} - -// CaptureFault implements the Tracer interface to trace an execution fault -// while running an opcode. -func (l *JsonStreamLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { -} - -// CaptureEnd is called after the call finishes to finalize the tracing. -func (l *JsonStreamLogger) CaptureEnd(output []byte, usedGas uint64, err error) { -} - -func (l *JsonStreamLogger) CaptureExit(output []byte, usedGas uint64, err error) { -} - -func (l *JsonStreamLogger) CaptureSelfDestruct(from common.Address, to common.Address, value *uint256.Int) { -} - -func (l *JsonStreamLogger) CaptureAccountRead(account common.Address) error { - return nil -} - -func (l *JsonStreamLogger) CaptureAccountWrite(account common.Address) error { - return nil -}