github.com/ethereum/go-ethereum@v1.16.1/core/tracing/journal.go (about)

     1  // Copyright 2025 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 tracing
    18  
    19  import (
    20  	"fmt"
    21  	"math/big"
    22  
    23  	"github.com/ethereum/go-ethereum/common"
    24  	"github.com/ethereum/go-ethereum/core/types"
    25  )
    26  
    27  // journal is a state change journal to be wrapped around a tracer.
    28  // It will emit the state change hooks with reverse values when a call reverts.
    29  type journal struct {
    30  	hooks     *Hooks
    31  	entries   []entry
    32  	revisions []int
    33  }
    34  
    35  type entry interface {
    36  	revert(tracer *Hooks)
    37  }
    38  
    39  // WrapWithJournal wraps the given tracer with a journaling layer.
    40  func WrapWithJournal(hooks *Hooks) (*Hooks, error) {
    41  	if hooks == nil {
    42  		return nil, fmt.Errorf("wrapping nil tracer")
    43  	}
    44  	// No state change to journal, return the wrapped hooks as is
    45  	if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnStorageChange == nil {
    46  		return hooks, nil
    47  	}
    48  	if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil {
    49  		return nil, fmt.Errorf("cannot have both OnNonceChange and OnNonceChangeV2")
    50  	}
    51  
    52  	// Create a new Hooks instance and copy all hooks
    53  	wrapped := *hooks
    54  
    55  	// Create journal
    56  	j := &journal{hooks: hooks}
    57  	// Scope hooks need to be re-implemented.
    58  	wrapped.OnTxEnd = j.OnTxEnd
    59  	wrapped.OnEnter = j.OnEnter
    60  	wrapped.OnExit = j.OnExit
    61  	// Wrap state change hooks.
    62  	if hooks.OnBalanceChange != nil {
    63  		wrapped.OnBalanceChange = j.OnBalanceChange
    64  	}
    65  	if hooks.OnNonceChange != nil || hooks.OnNonceChangeV2 != nil {
    66  		// Regardless of which hook version is used in the tracer,
    67  		// the journal will want to capture the nonce change reason.
    68  		wrapped.OnNonceChangeV2 = j.OnNonceChangeV2
    69  		// A precaution to ensure EVM doesn't call both hooks.
    70  		wrapped.OnNonceChange = nil
    71  	}
    72  	if hooks.OnCodeChange != nil {
    73  		wrapped.OnCodeChange = j.OnCodeChange
    74  	}
    75  	if hooks.OnStorageChange != nil {
    76  		wrapped.OnStorageChange = j.OnStorageChange
    77  	}
    78  
    79  	return &wrapped, nil
    80  }
    81  
    82  // reset clears the journal, after this operation the journal can be used anew.
    83  // It is semantically similar to calling 'NewJournal', but the underlying slices
    84  // can be reused.
    85  func (j *journal) reset() {
    86  	j.entries = j.entries[:0]
    87  	j.revisions = j.revisions[:0]
    88  }
    89  
    90  // snapshot records a revision and stores it to the revision stack.
    91  func (j *journal) snapshot() {
    92  	rev := len(j.entries)
    93  	j.revisions = append(j.revisions, rev)
    94  }
    95  
    96  // revert reverts all state changes up to the last tracked revision.
    97  func (j *journal) revert(hooks *Hooks) {
    98  	// Replay the journal entries above the last revision to undo changes,
    99  	// then remove the reverted changes from the journal.
   100  	rev := j.revisions[len(j.revisions)-1]
   101  	for i := len(j.entries) - 1; i >= rev; i-- {
   102  		j.entries[i].revert(hooks)
   103  	}
   104  	j.entries = j.entries[:rev]
   105  	j.popRevision()
   106  }
   107  
   108  // popRevision removes an item from the revision stack. This basically forgets about
   109  // the last call to snapshot() and moves to the one prior.
   110  func (j *journal) popRevision() {
   111  	j.revisions = j.revisions[:len(j.revisions)-1]
   112  }
   113  
   114  // OnTxEnd resets the journal since each transaction has its own EVM call stack.
   115  func (j *journal) OnTxEnd(receipt *types.Receipt, err error) {
   116  	j.reset()
   117  	if j.hooks.OnTxEnd != nil {
   118  		j.hooks.OnTxEnd(receipt, err)
   119  	}
   120  }
   121  
   122  // OnEnter is invoked for each EVM call frame and records a journal revision.
   123  func (j *journal) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
   124  	j.snapshot()
   125  	if j.hooks.OnEnter != nil {
   126  		j.hooks.OnEnter(depth, typ, from, to, input, gas, value)
   127  	}
   128  }
   129  
   130  // OnExit is invoked when an EVM call frame ends.
   131  // If the call has reverted, all state changes made by that frame are undone.
   132  // If the call did not revert, we forget about changes in that revision.
   133  func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
   134  	if reverted {
   135  		j.revert(j.hooks)
   136  	} else {
   137  		j.popRevision()
   138  	}
   139  	if j.hooks.OnExit != nil {
   140  		j.hooks.OnExit(depth, output, gasUsed, err, reverted)
   141  	}
   142  }
   143  
   144  func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) {
   145  	j.entries = append(j.entries, balanceChange{addr: addr, prev: prev, new: new})
   146  	if j.hooks.OnBalanceChange != nil {
   147  		j.hooks.OnBalanceChange(addr, prev, new, reason)
   148  	}
   149  }
   150  
   151  func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason NonceChangeReason) {
   152  	// When a contract is created, the nonce of the creator is incremented.
   153  	// This change is not reverted when the creation fails.
   154  	if reason != NonceChangeContractCreator {
   155  		j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new})
   156  	}
   157  	if j.hooks.OnNonceChangeV2 != nil {
   158  		j.hooks.OnNonceChangeV2(addr, prev, new, reason)
   159  	} else if j.hooks.OnNonceChange != nil {
   160  		j.hooks.OnNonceChange(addr, prev, new)
   161  	}
   162  }
   163  
   164  func (j *journal) OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) {
   165  	j.entries = append(j.entries, codeChange{
   166  		addr:         addr,
   167  		prevCodeHash: prevCodeHash,
   168  		prevCode:     prevCode,
   169  		newCodeHash:  codeHash,
   170  		newCode:      code,
   171  	})
   172  	if j.hooks.OnCodeChange != nil {
   173  		j.hooks.OnCodeChange(addr, prevCodeHash, prevCode, codeHash, code)
   174  	}
   175  }
   176  
   177  func (j *journal) OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) {
   178  	j.entries = append(j.entries, storageChange{addr: addr, slot: slot, prev: prev, new: new})
   179  	if j.hooks.OnStorageChange != nil {
   180  		j.hooks.OnStorageChange(addr, slot, prev, new)
   181  	}
   182  }
   183  
   184  type (
   185  	balanceChange struct {
   186  		addr common.Address
   187  		prev *big.Int
   188  		new  *big.Int
   189  	}
   190  
   191  	nonceChange struct {
   192  		addr common.Address
   193  		prev uint64
   194  		new  uint64
   195  	}
   196  
   197  	codeChange struct {
   198  		addr         common.Address
   199  		prevCodeHash common.Hash
   200  		prevCode     []byte
   201  		newCodeHash  common.Hash
   202  		newCode      []byte
   203  	}
   204  
   205  	storageChange struct {
   206  		addr common.Address
   207  		slot common.Hash
   208  		prev common.Hash
   209  		new  common.Hash
   210  	}
   211  )
   212  
   213  func (b balanceChange) revert(hooks *Hooks) {
   214  	if hooks.OnBalanceChange != nil {
   215  		hooks.OnBalanceChange(b.addr, b.new, b.prev, BalanceChangeRevert)
   216  	}
   217  }
   218  
   219  func (n nonceChange) revert(hooks *Hooks) {
   220  	if hooks.OnNonceChangeV2 != nil {
   221  		hooks.OnNonceChangeV2(n.addr, n.new, n.prev, NonceChangeRevert)
   222  	} else if hooks.OnNonceChange != nil {
   223  		hooks.OnNonceChange(n.addr, n.new, n.prev)
   224  	}
   225  }
   226  
   227  func (c codeChange) revert(hooks *Hooks) {
   228  	if hooks.OnCodeChange != nil {
   229  		hooks.OnCodeChange(c.addr, c.newCodeHash, c.newCode, c.prevCodeHash, c.prevCode)
   230  	}
   231  }
   232  
   233  func (s storageChange) revert(hooks *Hooks) {
   234  	if hooks.OnStorageChange != nil {
   235  		hooks.OnStorageChange(s.addr, s.slot, s.new, s.prev)
   236  	}
   237  }