From 93f58574ac1bc0e1dbe254764db857e38f3a6cca Mon Sep 17 00:00:00 2001 From: kewei Date: Thu, 29 Jun 2023 18:59:56 +0800 Subject: [PATCH 1/6] state diff logger --- eth/tracers/api.go | 108 ++++++++++++++++++++++++++++++++ eth/tracers/logger/statediff.go | 63 +++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 eth/tracers/logger/statediff.go diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 740a38ab9fbf..b6fefa749d79 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -861,6 +861,114 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } +func (api *API) TraceCallMany(ctx context.Context, args []ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { + // Try to retrieve the specified block + var ( + err error + block *types.Block + ) + if hash, ok := blockNrOrHash.Hash(); ok { + block, err = api.blockByHash(ctx, hash) + } else if number, ok := blockNrOrHash.Number(); ok { + if number == rpc.PendingBlockNumber { + return nil, errors.New("tracing on top of pending is not supported") + } + block, err = api.blockByNumber(ctx, number) + } else { + return nil, errors.New("invalid arguments; neither block nor hash specified") + } + if err != nil { + return nil, err + } + // try to recompute the state + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec + } + statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false) + if err != nil { + return nil, err + } + defer release() + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) + // Apply the customization rules if required. + if config != nil { + if err := config.StateOverrides.Apply(statedb); err != nil { + return nil, err + } + config.BlockOverrides.Apply(&vmctx) + } + var traceConfig *TraceConfig + if config != nil { + traceConfig = &config.TraceConfig + } + + result := []interface{}{} + for _, arg := range args { + msg, err := arg.ToMessage(api.backend.RPCGasCap(), block.Header().Number) + if err != nil { + return nil, err + } + txctx := &Context{ + BlockHash: block.Hash(), + BlockNumber: block.Number(), + TxIndex: -1, + TxHash: common.Hash{}, + } + res, err := api.traceStateDiffTx(ctx, msg, txctx, vmctx, statedb, traceConfig) + if err != nil { + return nil, err + } + result = append(result, res) + } + return result, nil +} + +func (api *API) traceStateDiffTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { + var ( + tracer Tracer + err error + timeout = defaultTraceTimeout + txContext = core.NewEVMTxContext(message) + ) + if config == nil { + config = &TraceConfig{} + } + // Default tracer is the struct logger + tracer = logger.NewStructLogger(config.Config) + if config.Tracer != nil { + tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) + if err != nil { + return nil, err + } + } + vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) + + // Define a meaningful timeout of a single transaction trace + if config.Timeout != nil { + if timeout, err = time.ParseDuration(*config.Timeout); err != nil { + return nil, err + } + } + deadlineCtx, cancel := context.WithTimeout(ctx, timeout) + go func() { + <-deadlineCtx.Done() + if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) { + tracer.Stop(errors.New("execution timeout")) + // Stop evm execution. Note cancellation is not necessarily immediate. + vmenv.Cancel() + } + }() + defer cancel() + + // Call Prepare to clear out the statedb access list + statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) + if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil { + return nil, fmt.Errorf("tracing failed: %w", err) + } + return tracer.GetResult() +} + // TraceCall lets you trace a given eth_call. It collects the structured logs // created during the execution of EVM if the given transaction was added on // top of the provided block and returns them as a JSON object. diff --git a/eth/tracers/logger/statediff.go b/eth/tracers/logger/statediff.go new file mode 100644 index 000000000000..2a66ae634d0b --- /dev/null +++ b/eth/tracers/logger/statediff.go @@ -0,0 +1,63 @@ +package logger + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" +) + +type diff[T any] struct { + before T + after T +} + +type accountDiff struct { + balance diff[*big.Int] + storage map[common.Hash]diff[common.Hash] + code diff[[]byte] +} + +type accountsDiff map[common.Address]accountDiff + +func (a *accountsDiff) tryInitAddr(addr common.Address) { + accsDiff := *a + if _, ok := accsDiff[addr]; !ok { + accsDiff[addr] = accountDiff{ + balance: diff[*big.Int]{nil, nil}, + storage: make(map[common.Hash]diff[common.Hash]), + code: diff[[]byte]{nil, nil}, + } + } +} + +// update balance +func (a *accountsDiff) recordBalance(acc common.Address, before, after *big.Int) { + a.tryInitAddr(acc) + accsDiff := *a + diff := accsDiff[acc] + if diff.balance.before == nil { + // take only the initial balance + diff.balance.before = big.NewInt(0).Set(before) + } + diff.balance.after = big.NewInt(0).Set(after) + accsDiff[acc] = diff +} + +type StateDiffLogger struct { + accounts accountsDiff +} + +// transferFunc +func (l *StateDiffLogger) WrappedTransferFunc(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { + senderBalanceBefore := db.GetBalance(sender) + recipientBalanceBefore := db.GetBalance(recipient) + defer func() { + senderBalanceAfter := db.GetBalance(sender) + recipientBalanceAfter := db.GetBalance(recipient) + l.accounts.recordBalance(sender, senderBalanceBefore, senderBalanceAfter) + l.accounts.recordBalance(recipient, recipientBalanceBefore, recipientBalanceAfter) + }() + core.Transfer(db, sender, recipient, amount) +} From 472ea5db0a59a3d9b4d894300a9c3d690f64e76d Mon Sep 17 00:00:00 2001 From: kewei Date: Fri, 30 Jun 2023 17:41:07 +0800 Subject: [PATCH 2/6] almost there --- eth/tracers/api.go | 56 +-------- eth/tracers/logger/statediff.go | 63 ---------- eth/tracers/native/statediff.go | 198 ++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 117 deletions(-) delete mode 100644 eth/tracers/logger/statediff.go create mode 100644 eth/tracers/native/statediff.go diff --git a/eth/tracers/api.go b/eth/tracers/api.go index b6fefa749d79..74a4487d4fca 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -905,17 +905,11 @@ func (api *API) TraceCallMany(ctx context.Context, args []ethapi.TransactionArgs result := []interface{}{} for _, arg := range args { - msg, err := arg.ToMessage(api.backend.RPCGasCap(), block.Header().Number) + msg, err := arg.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) if err != nil { return nil, err } - txctx := &Context{ - BlockHash: block.Hash(), - BlockNumber: block.Number(), - TxIndex: -1, - TxHash: common.Hash{}, - } - res, err := api.traceStateDiffTx(ctx, msg, txctx, vmctx, statedb, traceConfig) + res, err := api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) if err != nil { return nil, err } @@ -924,51 +918,6 @@ func (api *API) TraceCallMany(ctx context.Context, args []ethapi.TransactionArgs return result, nil } -func (api *API) traceStateDiffTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { - var ( - tracer Tracer - err error - timeout = defaultTraceTimeout - txContext = core.NewEVMTxContext(message) - ) - if config == nil { - config = &TraceConfig{} - } - // Default tracer is the struct logger - tracer = logger.NewStructLogger(config.Config) - if config.Tracer != nil { - tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) - if err != nil { - return nil, err - } - } - vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) - - // Define a meaningful timeout of a single transaction trace - if config.Timeout != nil { - if timeout, err = time.ParseDuration(*config.Timeout); err != nil { - return nil, err - } - } - deadlineCtx, cancel := context.WithTimeout(ctx, timeout) - go func() { - <-deadlineCtx.Done() - if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) { - tracer.Stop(errors.New("execution timeout")) - // Stop evm execution. Note cancellation is not necessarily immediate. - vmenv.Cancel() - } - }() - defer cancel() - - // Call Prepare to clear out the statedb access list - statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) - if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil { - return nil, fmt.Errorf("tracing failed: %w", err) - } - return tracer.GetResult() -} - // TraceCall lets you trace a given eth_call. It collects the structured logs // created during the execution of EVM if the given transaction was added on // top of the provided block and returns them as a JSON object. @@ -1020,7 +969,6 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc if err != nil { return nil, err } - var traceConfig *TraceConfig if config != nil { traceConfig = &config.TraceConfig diff --git a/eth/tracers/logger/statediff.go b/eth/tracers/logger/statediff.go deleted file mode 100644 index 2a66ae634d0b..000000000000 --- a/eth/tracers/logger/statediff.go +++ /dev/null @@ -1,63 +0,0 @@ -package logger - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/vm" -) - -type diff[T any] struct { - before T - after T -} - -type accountDiff struct { - balance diff[*big.Int] - storage map[common.Hash]diff[common.Hash] - code diff[[]byte] -} - -type accountsDiff map[common.Address]accountDiff - -func (a *accountsDiff) tryInitAddr(addr common.Address) { - accsDiff := *a - if _, ok := accsDiff[addr]; !ok { - accsDiff[addr] = accountDiff{ - balance: diff[*big.Int]{nil, nil}, - storage: make(map[common.Hash]diff[common.Hash]), - code: diff[[]byte]{nil, nil}, - } - } -} - -// update balance -func (a *accountsDiff) recordBalance(acc common.Address, before, after *big.Int) { - a.tryInitAddr(acc) - accsDiff := *a - diff := accsDiff[acc] - if diff.balance.before == nil { - // take only the initial balance - diff.balance.before = big.NewInt(0).Set(before) - } - diff.balance.after = big.NewInt(0).Set(after) - accsDiff[acc] = diff -} - -type StateDiffLogger struct { - accounts accountsDiff -} - -// transferFunc -func (l *StateDiffLogger) WrappedTransferFunc(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { - senderBalanceBefore := db.GetBalance(sender) - recipientBalanceBefore := db.GetBalance(recipient) - defer func() { - senderBalanceAfter := db.GetBalance(sender) - recipientBalanceAfter := db.GetBalance(recipient) - l.accounts.recordBalance(sender, senderBalanceBefore, senderBalanceAfter) - l.accounts.recordBalance(recipient, recipientBalanceBefore, recipientBalanceAfter) - }() - core.Transfer(db, sender, recipient, amount) -} diff --git a/eth/tracers/native/statediff.go b/eth/tracers/native/statediff.go new file mode 100644 index 000000000000..433a2bec8a36 --- /dev/null +++ b/eth/tracers/native/statediff.go @@ -0,0 +1,198 @@ +package native + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + tracers.DefaultDirectory.Register("stateDiffTracer", newStateTracer, false) +} + +type diff[T any] struct { + before T + after T +} + +type accountDiff struct { + balanceDelta *big.Int + nonceDelta int + storage map[common.Hash]diff[common.Hash] + code diff[[]byte] +} + +// StateDiffLogger implements Tracer interface +type StateDiffTracer struct { + accounts map[common.Address]accountDiff + env *vm.EVM + gasLimit uint64 + tracer *callTracer +} + +func newStateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { + t, err := newCallTracer(ctx, cfg) + if err != nil { + return nil, err + } + return &StateDiffTracer{ + tracer: t.(*callTracer), + accounts: make(map[common.Address]accountDiff), + }, nil +} + +// transferFunc +func (l *StateDiffTracer) WrappedTransferFunc(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { + defer func() { + // record the balance change + l.recordBalanceChange(sender, big.NewInt(0).Neg(amount)) + l.recordBalanceChange(recipient, amount) + }() + core.Transfer(db, sender, recipient, amount) +} + +func (l *StateDiffTracer) CaptureTxStart(gasLimit uint64) { + l.gasLimit = gasLimit + l.tracer.CaptureTxStart(gasLimit) +} + +func (l *StateDiffTracer) CaptureTxEnd(restGas uint64) { + l.tracer.CaptureTxEnd(restGas) + caller := l.tracer.callstack[0].From + used := l.tracer.callstack[0].GasUsed + l.recordBalanceChange(caller, big.NewInt(-int64(used))) +} + +func (l *StateDiffTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + l.env = env + l.tracer.CaptureStart(env, from, to, create, input, gas, value) + if create { + // record noce increment + l.recordNonceIncrese(from) + } +} + +func (l *StateDiffTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + l.tracer.CaptureEnd(output, gasUsed, err) + callframe := l.tracer.callstack[0] + // record gas used + l.recordBalanceChange(callframe.From, big.NewInt(0).Neg(big.NewInt(int64(gasUsed)))) + + if callframe.Type == vm.CREATE || callframe.Type == vm.CREATE2 { + // record the code + contract := *callframe.To + l.recordCode(contract, l.env.StateDB.GetCode(contract)) + } +} + +func (l *StateDiffTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + l.tracer.CaptureEnter(typ, from, to, input, gas, value) + if typ == vm.CREATE || typ == vm.CREATE2 { + // record noce increment + l.recordNonceIncrese(from) + } +} + +func (l *StateDiffTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + l.tracer.CaptureExit(output, gasUsed, err) + // retrieve the last callframe in last callstack + lastCallStack := l.tracer.callstack[len(l.tracer.callstack)-1].Calls + callframe := lastCallStack[len(lastCallStack)-1] + // record gas used + l.recordBalanceChange(callframe.From, big.NewInt(0).Neg(big.NewInt(int64(gasUsed)))) + + switch callframe.Type { + case vm.CREATE, vm.CREATE2: + // record the code + contract := *callframe.To + l.recordCode(contract, callframe.Input) + case vm.SELFDESTRUCT: + // destruct this contract. code is empty and balance is zero + contract := *callframe.To + l.recordCode(contract, []byte{}) + l.recordBalanceChange(contract, big.NewInt(0).Neg(l.env.StateDB.GetBalance(contract))) + } +} + +func (l *StateDiffTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { + l.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err) +} + +func (l *StateDiffTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + if op == vm.SSTORE { + contract := scope.Contract + stack := scope.Stack + stackLen := len(stack.Data()) + if stackLen >= 2 { + value := common.Hash(stack.Data()[stackLen-2].Bytes32()) + address := common.Hash(stack.Data()[stackLen-1].Bytes32()) + l.recordStorage(contract.Address(), address, value) + } + } +} + +func (l *StateDiffTracer) GetResult() (json.RawMessage, error) { + return nil, nil +} + +func (l *StateDiffTracer) Stop(err error) { + +} + +func (l *StateDiffTracer) tryInitAccDiff(addr common.Address) bool { + if _, ok := l.accounts[addr]; !ok { + l.accounts[addr] = accountDiff{ + balanceDelta: big.NewInt(0), + storage: make(map[common.Hash]diff[common.Hash]), + code: diff[[]byte]{nil, nil}, + } + return true + } + return false +} + +func (l *StateDiffTracer) recordNonceIncrese(addr common.Address) { + l.tryInitAccDiff(addr) + diff := l.accounts[addr] + diff.nonceDelta++ + l.accounts[addr] = diff +} + +func (l *StateDiffTracer) recordCode(addr common.Address, code []byte) { + isInit := l.tryInitAccDiff(addr) + diff := l.accounts[addr] + if isInit { + // init non-nil code before change + beforeCode := l.env.StateDB.GetCode(addr) + if beforeCode == nil { + beforeCode = []byte{} + } + diff.code.before = beforeCode + } + + diff.code.after = code + l.accounts[addr] = diff +} + +func (l *StateDiffTracer) recordStorage(addr common.Address, key, after common.Hash) { + isInit := l.tryInitAccDiff(addr) + value := l.accounts[addr].storage[key] + value.after = after + if isInit { + // take only the initial value + value.before = l.env.StateDB.GetState(addr, key) + } + l.accounts[addr].storage[key] = value +} + +// update balance +func (l *StateDiffTracer) recordBalanceChange(addr common.Address, delta *big.Int) { + l.tryInitAccDiff(addr) + diff := l.accounts[addr] + diff.balanceDelta.Add(diff.balanceDelta, delta) + l.accounts[addr] = diff +} From 8955c258794f6f4d78088bd47c6d714a3d3d8045 Mon Sep 17 00:00:00 2001 From: kewei Date: Fri, 30 Jun 2023 19:04:03 +0800 Subject: [PATCH 3/6] report --- eth/tracers/native/statediff.go | 132 ++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 24 deletions(-) diff --git a/eth/tracers/native/statediff.go b/eth/tracers/native/statediff.go index 433a2bec8a36..413907d9c5ac 100644 --- a/eth/tracers/native/statediff.go +++ b/eth/tracers/native/statediff.go @@ -1,11 +1,12 @@ package native import ( + "encoding/hex" "encoding/json" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" ) @@ -30,7 +31,6 @@ type accountDiff struct { type StateDiffTracer struct { accounts map[common.Address]accountDiff env *vm.EVM - gasLimit uint64 tracer *callTracer } @@ -45,18 +45,7 @@ func newStateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, }, nil } -// transferFunc -func (l *StateDiffTracer) WrappedTransferFunc(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { - defer func() { - // record the balance change - l.recordBalanceChange(sender, big.NewInt(0).Neg(amount)) - l.recordBalanceChange(recipient, amount) - }() - core.Transfer(db, sender, recipient, amount) -} - func (l *StateDiffTracer) CaptureTxStart(gasLimit uint64) { - l.gasLimit = gasLimit l.tracer.CaptureTxStart(gasLimit) } @@ -82,10 +71,22 @@ func (l *StateDiffTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { // record gas used l.recordBalanceChange(callframe.From, big.NewInt(0).Neg(big.NewInt(int64(gasUsed)))) - if callframe.Type == vm.CREATE || callframe.Type == vm.CREATE2 { - // record the code - contract := *callframe.To - l.recordCode(contract, l.env.StateDB.GetCode(contract)) + opType := callframe.Type + switch opType { + case vm.CREATE, vm.CREATE2, vm.CALL: + if opType == vm.CREATE || opType == vm.CREATE2 { + // record the code + contract := *callframe.To + l.recordCode(contract, l.env.StateDB.GetCode(contract)) + } + // ether transfer + value := callframe.Value + if value != nil { + from := callframe.From + to := *callframe.To + l.recordBalanceChange(from, big.NewInt(0).Neg(value)) + l.recordBalanceChange(to, value) + } } } @@ -105,11 +106,22 @@ func (l *StateDiffTracer) CaptureExit(output []byte, gasUsed uint64, err error) // record gas used l.recordBalanceChange(callframe.From, big.NewInt(0).Neg(big.NewInt(int64(gasUsed)))) - switch callframe.Type { - case vm.CREATE, vm.CREATE2: - // record the code - contract := *callframe.To - l.recordCode(contract, callframe.Input) + opType := callframe.Type + switch opType { + case vm.CREATE, vm.CREATE2, vm.CALL: + if opType == vm.CREATE || opType == vm.CREATE2 { + // record the code + contract := *callframe.To + l.recordCode(contract, callframe.Input) + } + // ether transfer + value := callframe.Value + if value != nil { + from := callframe.From + to := *callframe.To + l.recordBalanceChange(from, big.NewInt(0).Neg(value)) + l.recordBalanceChange(to, value) + } case vm.SELFDESTRUCT: // destruct this contract. code is empty and balance is zero contract := *callframe.To @@ -136,11 +148,15 @@ func (l *StateDiffTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64 } func (l *StateDiffTracer) GetResult() (json.RawMessage, error) { - return nil, nil + result := map[string]accountReport{} + for addr, diff := range l.accounts { + result[addr.Hex()] = l.report(addr, diff) + } + return json.Marshal(result) } func (l *StateDiffTracer) Stop(err error) { - + l.tracer.Stop(err) } func (l *StateDiffTracer) tryInitAccDiff(addr common.Address) bool { @@ -196,3 +212,71 @@ func (l *StateDiffTracer) recordBalanceChange(addr common.Address, delta *big.In diff.balanceDelta.Add(diff.balanceDelta, delta) l.accounts[addr] = diff } + +type accountReport struct { + Balance any `json:"balance"` + Nonce any `json:"nonce"` + Code any `json:"code"` + Storage map[string]fromTo `json:"storage"` +} +type fromTo struct { + From string `json:"from"` + To string `json:"to"` +} + +func (l *StateDiffTracer) report(addr common.Address, a accountDiff) accountReport { + result := accountReport{ + Balance: "=", + Nonce: "=", + Code: "=", + Storage: make(map[string]fromTo), + } + if a.balanceDelta != nil && a.balanceDelta.Sign() != 0 { + delta := a.balanceDelta + current := l.env.StateDB.GetBalance(addr) + result.Balance = fromTo{ + // from = current - delta. transform to hex + From: fmt.Sprintf("0x%x", current.Sub(current, delta)), + To: fmt.Sprintf("0x%x", current), + } + } + if a.nonceDelta != 0 { + current := l.env.StateDB.GetNonce(addr) + result.Nonce = fromTo{ + // in hex + From: fmt.Sprintf("0x%x", current-uint64(a.nonceDelta)), + To: fmt.Sprintf("0x%x", current), + } + } + if a.code.before != nil || a.code.after != nil { + before, after := "", "" + if a.code.before != nil { + before = hex.EncodeToString(a.code.before) + } + if a.code.after != nil { + after = hex.EncodeToString(a.code.after) + } + if before != after { + result.Code = fromTo{ + From: before, + To: after, + } + } + } + for k, v := range a.storage { + before, after := "", "" + if v.before != (common.Hash{}) { + before = v.before.Hex() + } + if v.after != (common.Hash{}) { + after = v.after.Hex() + } + if before != after { + result.Storage[k.Hex()] = fromTo{ + From: before, + To: after, + } + } + } + return result +} From b909622780837ed5da9c6828c207a5e283acf4ce Mon Sep 17 00:00:00 2001 From: kewei Date: Fri, 30 Jun 2023 19:18:24 +0800 Subject: [PATCH 4/6] update --- eth/tracers/native/statediff.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/eth/tracers/native/statediff.go b/eth/tracers/native/statediff.go index 413907d9c5ac..755035d12aaa 100644 --- a/eth/tracers/native/statediff.go +++ b/eth/tracers/native/statediff.go @@ -264,13 +264,8 @@ func (l *StateDiffTracer) report(addr common.Address, a accountDiff) accountRepo } } for k, v := range a.storage { - before, after := "", "" - if v.before != (common.Hash{}) { - before = v.before.Hex() - } - if v.after != (common.Hash{}) { - after = v.after.Hex() - } + before := v.before.Hex() + after := v.after.Hex() if before != after { result.Storage[k.Hex()] = fromTo{ From: before, From bea9ce2ac7a36f89d09a559a72972b5762b277cc Mon Sep 17 00:00:00 2001 From: kewei Date: Sun, 2 Jul 2023 03:31:56 +0800 Subject: [PATCH 5/6] tracer rpc --- eth/api_trace.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++ eth/backend.go | 3 +++ eth/tracers/api.go | 3 ++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 eth/api_trace.go diff --git a/eth/api_trace.go b/eth/api_trace.go new file mode 100644 index 000000000000..2ca5e0a68253 --- /dev/null +++ b/eth/api_trace.go @@ -0,0 +1,48 @@ +package eth + +import ( + "context" + "encoding/json" + + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/rpc" +) + +type TraceAPI struct { + backend *EthAPIBackend + tracerAPI *tracers.API +} + +func NewTraceAPI(b *EthAPIBackend) *TraceAPI { + return &TraceAPI{ + backend: b, + tracerAPI: tracers.NewAPI(b), + } +} + +// CallMany simulate a series of transactions in latest block +func (api *TraceAPI) CallMany(ctx context.Context, txs []ethapi.TransactionArgs) (map[string]interface{}, error) { + // get latest block number + latestBlockNumOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + // prepare stateDiff tracer + tracerName := "stateDiffTracer" + config := tracers.TraceCallConfig{ + TraceConfig: tracers.TraceConfig{ + Tracer: &tracerName, + TracerConfig: json.RawMessage("{\"onlyTopCall\": false, \"withLog\": false}"), + }, + } + // trace + traceResult, err := api.tracerAPI.TraceCallMany(ctx, txs, latestBlockNumOrHash, &config) + if err != nil { + return nil, err + } + result := map[string]interface{}{ + "blockNumber": latestBlockNumOrHash.BlockNumber.String(), + "trace": map[string]interface{}{ + "stateDiff": traceResult, + }, + } + return result, nil +} diff --git a/eth/backend.go b/eth/backend.go index cc555f0c23fd..1d27dcc8f771 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -316,6 +316,9 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "net", Service: s.netRPCService, + }, { + Namespace: "trace", + Service: NewTraceAPI(s.APIBackend), }, }...) } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 74a4487d4fca..9a55a39143c9 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -861,7 +861,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) } -func (api *API) TraceCallMany(ctx context.Context, args []ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { +func (api *API) TraceCallMany(ctx context.Context, args []ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) ([]interface{}, error) { // Try to retrieve the specified block var ( err error @@ -903,6 +903,7 @@ func (api *API) TraceCallMany(ctx context.Context, args []ethapi.TransactionArgs traceConfig = &config.TraceConfig } + // loop over all the transactions and trace internal calls result := []interface{}{} for _, arg := range args { msg, err := arg.ToMessage(api.backend.RPCGasCap(), block.BaseFee()) From c4c872014840378eb6342c38334c025afcd8ea8b Mon Sep 17 00:00:00 2001 From: kewei Date: Sun, 2 Jul 2023 13:10:58 +0800 Subject: [PATCH 6/6] minor fix --- eth/api_trace.go | 4 +--- eth/tracers/api.go | 1 + eth/tracers/native/statediff.go | 33 +++++++++++++++++++++++---------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/eth/api_trace.go b/eth/api_trace.go index 2ca5e0a68253..2f27b9166400 100644 --- a/eth/api_trace.go +++ b/eth/api_trace.go @@ -40,9 +40,7 @@ func (api *TraceAPI) CallMany(ctx context.Context, txs []ethapi.TransactionArgs) } result := map[string]interface{}{ "blockNumber": latestBlockNumOrHash.BlockNumber.String(), - "trace": map[string]interface{}{ - "stateDiff": traceResult, - }, + "traceResult": traceResult, } return result, nil } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 9a55a39143c9..805cb4b88885 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -914,6 +914,7 @@ func (api *API) TraceCallMany(ctx context.Context, args []ethapi.TransactionArgs if err != nil { return nil, err } + // append all results result = append(result, res) } return result, nil diff --git a/eth/tracers/native/statediff.go b/eth/tracers/native/statediff.go index 755035d12aaa..19137997ecf5 100644 --- a/eth/tracers/native/statediff.go +++ b/eth/tracers/native/statediff.go @@ -51,9 +51,15 @@ func (l *StateDiffTracer) CaptureTxStart(gasLimit uint64) { func (l *StateDiffTracer) CaptureTxEnd(restGas uint64) { l.tracer.CaptureTxEnd(restGas) - caller := l.tracer.callstack[0].From - used := l.tracer.callstack[0].GasUsed + callFrame := l.tracer.callstack[0] + caller := callFrame.From + used := callFrame.GasUsed + // record gas used here instead of capture whenever gas is used, because need to consider intrinsic gas l.recordBalanceChange(caller, big.NewInt(-int64(used))) + // additional nonce increment when first call is not CREATE + if callFrame.Type != vm.CREATE { + l.recordNonceIncrese(caller) + } } func (l *StateDiffTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { @@ -68,8 +74,7 @@ func (l *StateDiffTracer) CaptureStart(env *vm.EVM, from common.Address, to comm func (l *StateDiffTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { l.tracer.CaptureEnd(output, gasUsed, err) callframe := l.tracer.callstack[0] - // record gas used - l.recordBalanceChange(callframe.From, big.NewInt(0).Neg(big.NewInt(int64(gasUsed)))) + // Note: do not record gasUsed here. All gas used value is recorded in TxEnd opType := callframe.Type switch opType { @@ -103,8 +108,7 @@ func (l *StateDiffTracer) CaptureExit(output []byte, gasUsed uint64, err error) // retrieve the last callframe in last callstack lastCallStack := l.tracer.callstack[len(l.tracer.callstack)-1].Calls callframe := lastCallStack[len(lastCallStack)-1] - // record gas used - l.recordBalanceChange(callframe.From, big.NewInt(0).Neg(big.NewInt(int64(gasUsed)))) + // Note: do not record gasUsed here. All gas used value is recorded in TxEnd opType := callframe.Type switch opType { @@ -142,15 +146,20 @@ func (l *StateDiffTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64 if stackLen >= 2 { value := common.Hash(stack.Data()[stackLen-2].Bytes32()) address := common.Hash(stack.Data()[stackLen-1].Bytes32()) + // record storage change l.recordStorage(contract.Address(), address, value) } } } func (l *StateDiffTracer) GetResult() (json.RawMessage, error) { - result := map[string]accountReport{} + stateDiffResult := map[string]accountReport{} for addr, diff := range l.accounts { - result[addr.Hex()] = l.report(addr, diff) + stateDiffResult[addr.Hex()] = l.report(addr, diff) + } + result := map[string]interface{}{ + // only stateDiff result is supported now + "stateDiff": stateDiffResult, } return json.Marshal(result) } @@ -231,15 +240,17 @@ func (l *StateDiffTracer) report(addr common.Address, a accountDiff) accountRepo Code: "=", Storage: make(map[string]fromTo), } + // balance if a.balanceDelta != nil && a.balanceDelta.Sign() != 0 { delta := a.balanceDelta current := l.env.StateDB.GetBalance(addr) result.Balance = fromTo{ // from = current - delta. transform to hex - From: fmt.Sprintf("0x%x", current.Sub(current, delta)), - To: fmt.Sprintf("0x%x", current), + From: fmt.Sprintf("0x%x", big.NewInt(0).Sub(current, delta).Text(16)), + To: fmt.Sprintf("0x%x", current.Text(16)), } } + // nonce if a.nonceDelta != 0 { current := l.env.StateDB.GetNonce(addr) result.Nonce = fromTo{ @@ -248,6 +259,7 @@ func (l *StateDiffTracer) report(addr common.Address, a accountDiff) accountRepo To: fmt.Sprintf("0x%x", current), } } + // code if a.code.before != nil || a.code.after != nil { before, after := "", "" if a.code.before != nil { @@ -263,6 +275,7 @@ func (l *StateDiffTracer) report(addr common.Address, a accountDiff) accountRepo } } } + // storage for k, v := range a.storage { before := v.before.Hex() after := v.after.Hex()