From 63c926cb773e3aaed254bb60626bd3e097077ddc Mon Sep 17 00:00:00 2001 From: Delweng Date: Thu, 28 Jul 2022 02:15:00 +0800 Subject: [PATCH] eth/tracers: add a simple diffstate implemention Signed-off-by: Delweng --- eth/tracers/native/diffstate.go | 190 ++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 eth/tracers/native/diffstate.go diff --git a/eth/tracers/native/diffstate.go b/eth/tracers/native/diffstate.go new file mode 100644 index 0000000000000..09426879c327c --- /dev/null +++ b/eth/tracers/native/diffstate.go @@ -0,0 +1,190 @@ +// Copyright 2022 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 native + +import ( + "encoding/json" + "math/big" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" +) + +func init() { + register("diffstateTracer", newdiffstateTracer) +} + +type diffstate = map[common.Address]*diffaccount +type diffaccount struct { + Before account `json:"before"` + After account `json:"after"` +} + +type diffstateTracer struct { + env *vm.EVM + diffstate diffstate + create bool + to common.Address + gasLimit uint64 // Amount of gas bought for the whole tx + interrupt uint32 // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption +} + +func newdiffstateTracer(ctx *tracers.Context) tracers.Tracer { + // First callframe contains tx context info + // and is populated on start and end. + return &diffstateTracer{diffstate: diffstate{}} +} + +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. +func (t *diffstateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.env = env + t.create = create + t.to = to + + t.lookupAccount(from) + t.lookupAccount(to) + + // The recipient balance includes the value transferred. + toBal := hexutil.MustDecodeBig(t.diffstate[to].Before.Balance) + toBal = new(big.Int).Sub(toBal, value) + t.diffstate[to].Before.Balance = hexutil.EncodeBig(toBal) + + // The sender balance is after reducing: value and gasLimit. + // We need to re-add them to get the pre-tx balance. + fromBal := hexutil.MustDecodeBig(t.diffstate[from].Before.Balance) + gasPrice := env.TxContext.GasPrice + consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit)) + fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas)) + t.diffstate[from].Before.Balance = hexutil.EncodeBig(fromBal) + t.diffstate[from].Before.Nonce-- +} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *diffstateTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { + if t.create { + // Exclude created contract. + delete(t.diffstate, t.to) + } + + for addr, diff := range t.diffstate { + for key := range diff.Before.Storage { + t.diffstate[addr].After.Storage[key] = t.env.StateDB.GetState(addr, key) + } + } +} + +// CaptureState implements the EVMLogger interface to trace a single step of VM execution. +func (t *diffstateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + stack := scope.Stack + stackData := stack.Data() + stackLen := len(stackData) + switch { + case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): + slot := common.Hash(stackData[stackLen-1].Bytes32()) + t.lookupStorage(scope.Contract.Address(), slot) + case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): + addr := common.Address(stackData[stackLen-1].Bytes20()) + t.lookupAccount(addr) + case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): + addr := common.Address(stackData[stackLen-2].Bytes20()) + t.lookupAccount(addr) + case op == vm.CREATE: + addr := scope.Contract.Address() + nonce := t.env.StateDB.GetNonce(addr) + t.lookupAccount(crypto.CreateAddress(addr, nonce)) + case stackLen >= 4 && op == vm.CREATE2: + offset := stackData[stackLen-2] + size := stackData[stackLen-3] + init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + inithash := crypto.Keccak256(init) + salt := stackData[stackLen-4] + t.lookupAccount(crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), inithash)) + } +} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *diffstateTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *diffstateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *diffstateTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +} + +func (t *diffstateTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit +} + +func (t *diffstateTracer) CaptureTxEnd(restGas uint64) {} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *diffstateTracer) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.diffstate) + if err != nil { + return nil, err + } + return json.RawMessage(res), t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *diffstateTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} + +// lookupAccount fetches details of an account and adds it to the diffstate +// if it doesn't exist there. +func (t *diffstateTracer) lookupAccount(addr common.Address) { + if _, ok := t.diffstate[addr]; ok { + return + } + t.diffstate[addr] = &diffaccount{ + Before: account{ + Balance: bigToHex(t.env.StateDB.GetBalance(addr)), + Nonce: t.env.StateDB.GetNonce(addr), + Code: bytesToHex(t.env.StateDB.GetCode(addr)), + Storage: make(map[common.Hash]common.Hash), + }, + After: account{ + Balance: bigToHex(t.env.StateDB.GetBalance(addr)), + Nonce: t.env.StateDB.GetNonce(addr), + Code: bytesToHex(t.env.StateDB.GetCode(addr)), + Storage: make(map[common.Hash]common.Hash), + }, + } +} + +// lookupStorage fetches the requested storage slot and adds +// it to the diffstate of the given contract. It assumes `lookupAccount` +// has been performed on the contract before. +func (t *diffstateTracer) lookupStorage(addr common.Address, key common.Hash) { + if _, ok := t.diffstate[addr].Before.Storage[key]; ok { + return + } + t.diffstate[addr].Before.Storage[key] = t.env.StateDB.GetState(addr, key) +}