github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/native/prestate.go (about)

     1  // Copyright 2022 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package native
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"math/big"
    24  	"sync/atomic"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/common/hexutil"
    28  	"github.com/ethereum/go-ethereum/core/tracing"
    29  	"github.com/ethereum/go-ethereum/core/types"
    30  	"github.com/ethereum/go-ethereum/core/vm"
    31  	"github.com/ethereum/go-ethereum/crypto"
    32  	"github.com/ethereum/go-ethereum/eth/tracers"
    33  	"github.com/ethereum/go-ethereum/eth/tracers/internal"
    34  	"github.com/ethereum/go-ethereum/log"
    35  	"github.com/ethereum/go-ethereum/params"
    36  )
    37  
    38  //go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
    39  
    40  func init() {
    41  	tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false)
    42  }
    43  
    44  type stateMap = map[common.Address]*account
    45  
    46  type account struct {
    47  	Balance *big.Int                    `json:"balance,omitempty"`
    48  	Code    []byte                      `json:"code,omitempty"`
    49  	Nonce   uint64                      `json:"nonce,omitempty"`
    50  	Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
    51  	empty   bool
    52  }
    53  
    54  func (a *account) exists() bool {
    55  	return a.Nonce > 0 || len(a.Code) > 0 || len(a.Storage) > 0 || (a.Balance != nil && a.Balance.Sign() != 0)
    56  }
    57  
    58  type accountMarshaling struct {
    59  	Balance *hexutil.Big
    60  	Code    hexutil.Bytes
    61  }
    62  
    63  type prestateTracer struct {
    64  	env         *tracing.VMContext
    65  	pre         stateMap
    66  	post        stateMap
    67  	to          common.Address
    68  	config      prestateTracerConfig
    69  	chainConfig *params.ChainConfig
    70  	interrupt   atomic.Bool // Atomic flag to signal execution interruption
    71  	reason      error       // Textual reason for the interruption
    72  	created     map[common.Address]bool
    73  	deleted     map[common.Address]bool
    74  }
    75  
    76  type prestateTracerConfig struct {
    77  	DiffMode       bool `json:"diffMode"`       // If true, this tracer will return state modifications
    78  	DisableCode    bool `json:"disableCode"`    // If true, this tracer will not return the contract code
    79  	DisableStorage bool `json:"disableStorage"` // If true, this tracer will not return the contract storage
    80  	IncludeEmpty   bool `json:"includeEmpty"`   // If true, this tracer will return empty state objects
    81  }
    82  
    83  func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) {
    84  	var config prestateTracerConfig
    85  	if err := json.Unmarshal(cfg, &config); err != nil {
    86  		return nil, err
    87  	}
    88  	// Diff mode has special semantics around account creating and deletion which
    89  	// requires it to include empty accounts and storage.
    90  	if config.DiffMode && config.IncludeEmpty {
    91  		return nil, errors.New("cannot use diffMode with includeEmpty")
    92  	}
    93  	t := &prestateTracer{
    94  		pre:         stateMap{},
    95  		post:        stateMap{},
    96  		config:      config,
    97  		chainConfig: chainConfig,
    98  		created:     make(map[common.Address]bool),
    99  		deleted:     make(map[common.Address]bool),
   100  	}
   101  	return &tracers.Tracer{
   102  		Hooks: &tracing.Hooks{
   103  			OnTxStart: t.OnTxStart,
   104  			OnTxEnd:   t.OnTxEnd,
   105  			OnOpcode:  t.OnOpcode,
   106  		},
   107  		GetResult: t.GetResult,
   108  		Stop:      t.Stop,
   109  	}, nil
   110  }
   111  
   112  // OnOpcode implements the EVMLogger interface to trace a single step of VM execution.
   113  func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
   114  	if err != nil {
   115  		return
   116  	}
   117  	// Skip if tracing was interrupted
   118  	if t.interrupt.Load() {
   119  		return
   120  	}
   121  	op := vm.OpCode(opcode)
   122  	stackData := scope.StackData()
   123  	stackLen := len(stackData)
   124  	caller := scope.Address()
   125  	switch {
   126  	case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
   127  		slot := common.Hash(stackData[stackLen-1].Bytes32())
   128  		t.lookupStorage(caller, slot)
   129  	case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT):
   130  		addr := common.Address(stackData[stackLen-1].Bytes20())
   131  		t.lookupAccount(addr)
   132  		if op == vm.SELFDESTRUCT {
   133  			t.deleted[caller] = true
   134  		}
   135  	case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE):
   136  		addr := common.Address(stackData[stackLen-2].Bytes20())
   137  		t.lookupAccount(addr)
   138  		// Lookup the delegation target
   139  		if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) {
   140  			code := t.env.StateDB.GetCode(addr)
   141  			if target, ok := types.ParseDelegation(code); ok {
   142  				t.lookupAccount(target)
   143  			}
   144  		}
   145  	case op == vm.CREATE:
   146  		nonce := t.env.StateDB.GetNonce(caller)
   147  		addr := crypto.CreateAddress(caller, nonce)
   148  		t.lookupAccount(addr)
   149  		t.created[addr] = true
   150  	case stackLen >= 4 && op == vm.CREATE2:
   151  		offset := stackData[stackLen-2]
   152  		size := stackData[stackLen-3]
   153  		init, err := internal.GetMemoryCopyPadded(scope.MemoryData(), int64(offset.Uint64()), int64(size.Uint64()))
   154  		if err != nil {
   155  			log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size)
   156  			return
   157  		}
   158  		inithash := crypto.Keccak256(init)
   159  		salt := stackData[stackLen-4]
   160  		addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash)
   161  		t.lookupAccount(addr)
   162  		t.created[addr] = true
   163  	}
   164  }
   165  
   166  func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
   167  	t.env = env
   168  	if tx.To() == nil {
   169  		t.to = crypto.CreateAddress(from, env.StateDB.GetNonce(from))
   170  		t.created[t.to] = true
   171  	} else {
   172  		t.to = *tx.To()
   173  		// Lookup the delegation target
   174  		if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) {
   175  			code := t.env.StateDB.GetCode(t.to)
   176  			if target, ok := types.ParseDelegation(code); ok {
   177  				t.lookupAccount(target)
   178  			}
   179  		}
   180  	}
   181  
   182  	t.lookupAccount(from)
   183  	t.lookupAccount(t.to)
   184  	t.lookupAccount(env.Coinbase)
   185  
   186  	// Add accounts with authorizations to the prestate before they get applied.
   187  	for _, auth := range tx.SetCodeAuthorizations() {
   188  		addr, err := auth.Authority()
   189  		if err != nil {
   190  			continue
   191  		}
   192  		t.lookupAccount(addr)
   193  	}
   194  }
   195  
   196  func (t *prestateTracer) OnTxEnd(receipt *types.Receipt, err error) {
   197  	if err != nil {
   198  		return
   199  	}
   200  	if t.config.DiffMode {
   201  		t.processDiffState()
   202  	}
   203  	// Remove accounts that were empty prior to execution. Unless
   204  	// user requested to include empty accounts.
   205  	if t.config.IncludeEmpty {
   206  		return
   207  	}
   208  	for addr, s := range t.pre {
   209  		if s.empty {
   210  			delete(t.pre, addr)
   211  		}
   212  	}
   213  }
   214  
   215  // GetResult returns the json-encoded nested list of call traces, and any
   216  // error arising from the encoding or forceful termination (via `Stop`).
   217  func (t *prestateTracer) GetResult() (json.RawMessage, error) {
   218  	var res []byte
   219  	var err error
   220  	if t.config.DiffMode {
   221  		res, err = json.Marshal(struct {
   222  			Post stateMap `json:"post"`
   223  			Pre  stateMap `json:"pre"`
   224  		}{t.post, t.pre})
   225  	} else {
   226  		res, err = json.Marshal(t.pre)
   227  	}
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	return json.RawMessage(res), t.reason
   232  }
   233  
   234  // Stop terminates execution of the tracer at the first opportune moment.
   235  func (t *prestateTracer) Stop(err error) {
   236  	t.reason = err
   237  	t.interrupt.Store(true)
   238  }
   239  
   240  func (t *prestateTracer) processDiffState() {
   241  	for addr, state := range t.pre {
   242  		// The deleted account's state is pruned from `post` but kept in `pre`
   243  		if _, ok := t.deleted[addr]; ok {
   244  			continue
   245  		}
   246  		modified := false
   247  		postAccount := &account{Storage: make(map[common.Hash]common.Hash)}
   248  		newBalance := t.env.StateDB.GetBalance(addr).ToBig()
   249  		newNonce := t.env.StateDB.GetNonce(addr)
   250  
   251  		if newBalance.Cmp(t.pre[addr].Balance) != 0 {
   252  			modified = true
   253  			postAccount.Balance = newBalance
   254  		}
   255  		if newNonce != t.pre[addr].Nonce {
   256  			modified = true
   257  			postAccount.Nonce = newNonce
   258  		}
   259  		if !t.config.DisableCode {
   260  			newCode := t.env.StateDB.GetCode(addr)
   261  			if !bytes.Equal(newCode, t.pre[addr].Code) {
   262  				modified = true
   263  				postAccount.Code = newCode
   264  			}
   265  		}
   266  
   267  		if !t.config.DisableStorage {
   268  			for key, val := range state.Storage {
   269  				// don't include the empty slot
   270  				if val == (common.Hash{}) {
   271  					delete(t.pre[addr].Storage, key)
   272  				}
   273  
   274  				newVal := t.env.StateDB.GetState(addr, key)
   275  				if val == newVal {
   276  					// Omit unchanged slots
   277  					delete(t.pre[addr].Storage, key)
   278  				} else {
   279  					modified = true
   280  					if newVal != (common.Hash{}) {
   281  						postAccount.Storage[key] = newVal
   282  					}
   283  				}
   284  			}
   285  		}
   286  
   287  		if modified {
   288  			t.post[addr] = postAccount
   289  		} else {
   290  			// if state is not modified, then no need to include into the pre state
   291  			delete(t.pre, addr)
   292  		}
   293  	}
   294  }
   295  
   296  // lookupAccount fetches details of an account and adds it to the prestate
   297  // if it doesn't exist there.
   298  func (t *prestateTracer) lookupAccount(addr common.Address) {
   299  	if _, ok := t.pre[addr]; ok {
   300  		return
   301  	}
   302  
   303  	acc := &account{
   304  		Balance: t.env.StateDB.GetBalance(addr).ToBig(),
   305  		Nonce:   t.env.StateDB.GetNonce(addr),
   306  		Code:    t.env.StateDB.GetCode(addr),
   307  	}
   308  	if !acc.exists() {
   309  		acc.empty = true
   310  	}
   311  	// The code must be fetched first for the emptiness check.
   312  	if t.config.DisableCode {
   313  		acc.Code = nil
   314  	}
   315  	if !t.config.DisableStorage {
   316  		acc.Storage = make(map[common.Hash]common.Hash)
   317  	}
   318  	t.pre[addr] = acc
   319  }
   320  
   321  // lookupStorage fetches the requested storage slot and adds
   322  // it to the prestate of the given contract. It assumes `lookupAccount`
   323  // has been performed on the contract before.
   324  func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) {
   325  	if t.config.DisableStorage {
   326  		return
   327  	}
   328  	if _, ok := t.pre[addr].Storage[key]; ok {
   329  		return
   330  	}
   331  	t.pre[addr].Storage[key] = t.env.StateDB.GetState(addr, key)
   332  }