diff --git a/cmd/geth/config.go b/cmd/geth/config.go index d6ab18d805..95d59a04fa 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -192,8 +192,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { utils.Fatalf("Failed to create tracer %q: %v", name, err) } - cfg.Eth.VMTracer = t.VMLogger - cfg.Eth.LiveLogger = t + cfg.Eth.VMTracer = t } } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index abff65a09f..2490e79fe5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2207,8 +2207,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if err != nil { Fatalf("Failed to create tracer %q: %v", name, err) } - vmcfg.LiveLogger = t - vmcfg.Tracer = t.VMLogger + vmcfg.Tracer = t } } // Disable transaction indexing/unindexing by default. diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 9ab9026fa8..42bf2bd7f9 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" @@ -357,7 +358,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // Convert amount from gwei to wei. amount := new(big.Int).SetUint64(w.Amount) amount = amount.Mul(amount, big.NewInt(params.GWei)) - stateDB.AddBalance(w.Address, amount, state.BalanceIncreaseWithdrawal) + stateDB.AddBalance(w.Address, amount, live.BalanceIncreaseWithdrawal) } // No block reward which is issued by consensus layer instead. } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 5c00e5c7bc..e09bfa10a0 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" @@ -564,10 +565,10 @@ func accumulateRewards(config *params.ChainConfig, stateDB *state.StateDB, heade r.Sub(r, header.Number) r.Mul(r, blockReward) r.Div(r, big8) - stateDB.AddBalance(uncle.Coinbase, r, state.BalanceIncreaseRewardMineUncle) + stateDB.AddBalance(uncle.Coinbase, r, live.BalanceIncreaseRewardMineUncle) r.Div(blockReward, big32) reward.Add(reward, r) } - stateDB.AddBalance(header.Coinbase, reward, state.BalanceIncreaseRewardMineBlock) + stateDB.AddBalance(header.Coinbase, reward, live.BalanceIncreaseRewardMineBlock) } diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go index bc393a9cbc..1f97527588 100644 --- a/consensus/misc/dao.go +++ b/consensus/misc/dao.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" ) @@ -80,7 +81,7 @@ func ApplyDAOHardFork(statedb *state.StateDB) { // Move every DAO account and extra-balance account funds into the refund contract for _, addr := range params.DAODrainList() { - statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), state.BalanceIncreaseDaoContract) - statedb.SetBalance(addr, new(big.Int), state.BalanceDecreaseDaoAccount) + statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), live.BalanceIncreaseDaoContract) + statedb.SetBalance(addr, new(big.Int), live.BalanceDecreaseDaoAccount) } } diff --git a/core/blockchain.go b/core/blockchain.go index bb5a36ba44..babfcaf8f2 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -186,30 +186,11 @@ func DefaultCacheConfigWithScheme(scheme string) *CacheConfig { return &config } -// BlockEvent is emitted upon tracing an incoming block. -// It contains the block as well as consensus related information. -type BlockEvent struct { - Block *types.Block - TD *big.Int - Finalized *types.Header - Safe *types.Header -} - -// BlockchainLogger is used to collect traces during chain processing. -// Please make a copy of the referenced types if you intend to retain them. -type BlockchainLogger interface { - vm.EVMLogger - state.StateLogger - OnBlockchainInit(chainConfig *params.ChainConfig) - // OnBlockStart is called before executing `block`. - // `td` is the total difficulty prior to `block`. - OnBlockStart(event BlockEvent) - OnBlockEnd(err error) - // OnSkippedBlock indicates a block was skipped during processing - // due to it being known previously. This can happen e.g. when recovering - // from a crash. - OnSkippedBlock(event BlockEvent) - OnGenesisBlock(genesis *types.Block, alloc GenesisAlloc) +// txLookup is wrapper over transaction lookup along with the corresponding +// transaction object. +type txLookup struct { + lookup *rawdb.LegacyTxLookupEntry + transaction *types.Transaction } // BlockChain represents the canonical chain given a database with a genesis @@ -329,7 +310,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks), engine: engine, vmConfig: vmConfig, - logger: vmConfig.LiveLogger, + logger: vmConfig.Tracer, } bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) bc.forker = NewForkChoice(bc, shouldPreserve) @@ -1813,7 +1794,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) } stats.processed++ if bc.logger != nil && bc.logger.OnSkippedBlock != nil { - bc.logger.OnSkippedBlock(BlockEvent{ + bc.logger.OnSkippedBlock(live.BlockEvent{ Block: block, TD: bc.GetTd(block.ParentHash(), block.NumberU64()-1), Finalized: bc.CurrentFinalBlock(), @@ -1946,7 +1927,7 @@ type blockProcessingResult struct { func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, start time.Time, setHead bool) (_ *blockProcessingResult, blockEndErr error) { if bc.logger != nil && bc.logger.OnBlockStart != nil { td := bc.GetTd(block.ParentHash(), block.NumberU64()-1) - bc.logger.OnBlockStart(BlockEvent{ + bc.logger.OnBlockStart(live.BlockEvent{ Block: block, TD: td, Finalized: bc.CurrentFinalBlock(), diff --git a/core/chain_makers.go b/core/chain_makers.go index 2949162bfe..dbaeb67f28 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -100,7 +100,7 @@ func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{}) ) - ProcessBeaconBlockRoot(root, vmenv, b.statedb, nil) + ProcessBeaconBlockRoot(root, vmenv, b.statedb) } // addTx adds a transaction to the generated block. If no coinbase has diff --git a/core/evm.go b/core/evm.go index 8b724ac2cf..515f8d78a9 100644 --- a/core/evm.go +++ b/core/evm.go @@ -22,9 +22,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) // ChainContext supports retrieving headers and consensus parameters from the @@ -136,6 +136,6 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { // Transfer subtracts amount from sender and adds amount to recipient using the given Db func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { - db.SubBalance(sender, amount, state.BalanceChangeTransfer) - db.AddBalance(recipient, amount, state.BalanceChangeTransfer) + db.SubBalance(sender, amount, live.BalanceChangeTransfer) + db.AddBalance(recipient, amount, live.BalanceChangeTransfer) } diff --git a/core/genesis.go b/core/genesis.go index edd323f67d..8e58bb134e 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -131,7 +132,7 @@ func (ga *GenesisAlloc) hash() (common.Hash, error) { } for addr, account := range *ga { if account.Balance != nil { - statedb.AddBalance(addr, account.Balance, state.BalanceIncreaseGenesisBalance) + statedb.AddBalance(addr, account.Balance, live.BalanceIncreaseGenesisBalance) } statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) @@ -152,7 +153,9 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas } for addr, account := range *ga { if account.Balance != nil { - statedb.AddBalance(addr, account.Balance, state.BalanceIncreaseGenesisBalance) + // This is not actually logged via tracer because OnGenesisBlock + // already captures the allocations. + statedb.AddBalance(addr, account.Balance, live.BalanceIncreaseGenesisBalance) } statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) diff --git a/core/state/dump.go b/core/state/dump.go index 9ce6cd394b..1840e5b220 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -49,15 +49,14 @@ type DumpCollector interface { // DumpAccount represents an account in the state. type DumpAccount struct { - Balance string `json:"balance"` - Nonce uint64 `json:"nonce"` - Root hexutil.Bytes `json:"root"` - CodeHash hexutil.Bytes `json:"codeHash"` - Code hexutil.Bytes `json:"code,omitempty"` - Storage map[common.Hash]string `json:"storage,omitempty"` - Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode - SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key - + Balance string `json:"balance"` + Nonce uint64 `json:"nonce"` + Root hexutil.Bytes `json:"root"` + CodeHash hexutil.Bytes `json:"codeHash"` + Code hexutil.Bytes `json:"code,omitempty"` + Storage map[common.Hash]string `json:"storage,omitempty"` + Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode + AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key } // Dump represents the full dump in a collected format, as one large map. diff --git a/core/state/metadata.go b/core/state/metadata.go deleted file mode 100644 index 6978e67075..0000000000 --- a/core/state/metadata.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2023 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 state - -// BalanceChangeReason is used to indicate the reason for a balance change, useful -// for tracing and reporting. -type BalanceChangeReason byte - -const ( - BalanceChangeUnspecified BalanceChangeReason = 0 - - // Issuance - // BalanceIncreaseRewardMineUncle is a reward for mining an uncle block. - BalanceIncreaseRewardMineUncle BalanceChangeReason = 1 - // BalanceIncreaseRewardMineBlock is a reward for mining a block. - BalanceIncreaseRewardMineBlock BalanceChangeReason = 2 - // BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain. - BalanceIncreaseWithdrawal BalanceChangeReason = 3 - // BalanceIncreaseGenesisBalance is ether allocated at the genesis block. - BalanceIncreaseGenesisBalance BalanceChangeReason = 4 - - // Transaction fees - // BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance. - BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5 - // BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction. - // Part of this gas will be burnt as per EIP-1559 rules. - BalanceDecreaseGasBuy BalanceChangeReason = 6 - // BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution. - BalanceIncreaseGasReturn BalanceChangeReason = 7 - - // DAO fork - // BalanceIncreaseDaoContract is ether sent to the DAO refund contract. - BalanceIncreaseDaoContract BalanceChangeReason = 8 - // BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract. - BalanceDecreaseDaoAccount BalanceChangeReason = 9 - - // BalanceChangeTransfer is ether transferred via a call. - // it is a decrease for the sender and an increase for the recipient. - BalanceChangeTransfer BalanceChangeReason = 10 - // BalanceChangeTouchAccount is a transfer of zero value. It is only there to - // touch-create an account. - BalanceChangeTouchAccount BalanceChangeReason = 11 - - // BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account. - BalanceIncreaseSelfdestruct BalanceChangeReason = 12 - // BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct. - BalanceDecreaseSelfdestruct BalanceChangeReason = 13 - // BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed - // account within the same tx (captured at end of tx). - // Note it doesn't account for a self-destruct which appoints itself as recipient. - BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14 -) diff --git a/core/state/state_object.go b/core/state/state_object.go index b6decf7717..2c7ffbf896 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie/trienode" @@ -404,7 +405,7 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) { // AddBalance adds amount to s's balance. // It is used to add funds to the destination account of a transfer. -func (s *stateObject) AddBalance(amount *big.Int, reason BalanceChangeReason) { +func (s *stateObject) AddBalance(amount *big.Int, reason live.BalanceChangeReason) { // EIP161: We must check emptiness for the objects such that the account // clearing (0,0,0 objects) can take effect. if amount.Sign() == 0 { @@ -418,14 +419,14 @@ func (s *stateObject) AddBalance(amount *big.Int, reason BalanceChangeReason) { // SubBalance removes amount from s's balance. // It is used to remove funds from the origin account of a transfer. -func (s *stateObject) SubBalance(amount *big.Int, reason BalanceChangeReason) { - if amount.Sign() == 0 { +func (s *stateObject) SubBalance(amount *big.Int, reason live.BalanceChangeReason) { + if amount.IsZero() { return } s.SetBalance(new(big.Int).Sub(s.Balance(), amount), reason) } -func (s *stateObject) SetBalance(amount *big.Int, reason BalanceChangeReason) { +func (s *stateObject) SetBalance(amount *big.Int, reason live.BalanceChangeReason) { s.db.journal.append(balanceChange{ account: &s.address, prev: new(big.Int).Set(s.data.Balance), diff --git a/core/state/statedb.go b/core/state/statedb.go index 5eabbcaeb3..27bca3d2e8 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -48,20 +48,6 @@ type revision struct { journalIndex int } -// StateLogger is used to collect state update traces from EVM transaction -// execution. -// The following hooks are invoked post execution. I.e. looking up state -// after the hook should reflect the new value. -// Note that reference types are actual VM data structures; make copies -// if you need to retain them beyond the current call. -type StateLogger interface { - OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) - OnNonceChange(addr common.Address, prev, new uint64) - OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) - OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) - OnLog(log *types.Log) -} - // StateDB structs within the ethereum protocol are used to store anything // within the merkle trie. StateDBs take care of caching and storing // nested states. It's the general query interface to retrieve: @@ -397,23 +383,23 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { */ // AddBalance adds amount to the account associated with addr. -func (s *StateDB) AddBalance(addr common.Address, amount *big.Int, reason BalanceChangeReason) { - stateObject := s.GetOrNewStateObject(addr) +func (s *StateDB) AddBalance(addr common.Address, amount *big.Int, reason live.BalanceChangeReason) { + stateObject := s.getOrNewStateObject(addr) if stateObject != nil { stateObject.AddBalance(amount, reason) } } // SubBalance subtracts amount from the account associated with addr. -func (s *StateDB) SubBalance(addr common.Address, amount *big.Int, reason BalanceChangeReason) { - stateObject := s.GetOrNewStateObject(addr) +func (s *StateDB) SubBalance(addr common.Address, amount *big.Int, reason live.BalanceChangeReason) { + stateObject := s.getOrNewStateObject(addr) if stateObject != nil { stateObject.SubBalance(amount, reason) } } -func (s *StateDB) SetBalance(addr common.Address, amount *big.Int, reason BalanceChangeReason) { - stateObject := s.GetOrNewStateObject(addr) +func (s *StateDB) SetBalance(addr common.Address, amount *big.Int, reason live.BalanceChangeReason) { + stateObject := s.getOrNewStateObject(addr) if stateObject != nil { stateObject.SetBalance(amount, reason) } @@ -481,7 +467,7 @@ func (s *StateDB) SelfDestruct(addr common.Address) { prevbalance: new(big.Int).Set(stateObject.Balance()), }) if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 { - s.logger.OnBalanceChange(addr, prev, n, BalanceDecreaseSelfdestruct) + s.logger.OnBalanceChange(addr, prev, n, live.BalanceDecreaseSelfdestruct) } stateObject.markSelfdestructed() stateObject.data.Balance = new(big.Int) @@ -870,7 +856,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // If ether was sent to account post-selfdestruct it is burnt. if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 { - s.logger.OnBalanceChange(obj.address, bal, new(big.Int), BalanceDecreaseSelfdestructBurn) + s.logger.OnBalanceChange(obj.address, bal, new(big.Int), live.BalanceDecreaseSelfdestructBurn) } // We need to maintain account deletions explicitly (will remain // set indefinitely). Note only the first occurred self-destruct diff --git a/core/state_processor.go b/core/state_processor.go index 76dd0fdb1e..776a104223 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" ) @@ -78,7 +79,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg signer = types.MakeSigner(p.config, header.Number, header.Time) ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { - ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb, p.bc.logger) + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { @@ -110,11 +111,20 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // and uses the input parameters for its environment similar to ApplyTransaction. However, // this method takes an already created EVM instance as input. func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) { - if evm.Config.Tracer != nil { - evm.Config.Tracer.CaptureTxStart(evm, tx, msg.From) - defer func() { - evm.Config.Tracer.CaptureTxEnd(receipt, err) - }() + if evm.Config.Tracer != nil && evm.Config.Tracer.CaptureTxStart != nil { + evm.Config.Tracer.CaptureTxStart(&live.VMContext{ + ChainConfig: evm.ChainConfig(), + StateDB: statedb, + BlockNumber: evm.Context.BlockNumber, + Time: evm.Context.Time, + Coinbase: evm.Context.Coinbase, + Random: evm.Context.Random, + }, tx, msg.From) + if evm.Config.Tracer.CaptureTxEnd != nil { + defer func() { + evm.Config.Tracer.CaptureTxEnd(receipt, err) + }() + } } // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) @@ -183,7 +193,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // contract. This method is exported to be used in tests. -func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB, logger BlockchainLogger) { +func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) { // If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with // the new root msg := &Message{ diff --git a/core/state_transition.go b/core/state_transition.go index d734467443..f149d2a3a1 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -24,9 +24,9 @@ import ( "github.com/ethereum/go-ethereum/common" cmath "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" ) @@ -262,14 +262,14 @@ func (st *StateTransition) buyGas() error { return err } - if st.evm.Config.Tracer != nil { - st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, vm.GasChangeTxInitialBalance) + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil { + st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, live.GasChangeTxInitialBalance) } st.gasRemaining += st.msg.GasLimit st.initialGas = st.msg.GasLimit - st.state.SubBalance(st.msg.From, mgval, state.BalanceDecreaseGasBuy) + st.state.SubBalance(st.msg.From, mgval, live.BalanceDecreaseGasBuy) return nil } @@ -392,8 +392,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.gasRemaining < gas { return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining, gas) } - if t := st.evm.Config.Tracer; t != nil { - t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, vm.GasChangeTxIntrinsicGas) + if t := st.evm.Config.Tracer; t != nil && t.OnGasChange != nil { + t.OnGasChange(st.gasRemaining, st.gasRemaining-gas, live.GasChangeTxIntrinsicGas) } st.gasRemaining -= gas @@ -443,7 +443,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } else { fee := new(big.Int).SetUint64(st.gasUsed()) fee.Mul(fee, effectiveTip) - st.state.AddBalance(st.evm.Context.Coinbase, fee, state.BalanceIncreaseRewardTransactionFee) + st.state.AddBalance(st.evm.Context.Coinbase, fee, live.BalanceIncreaseRewardTransactionFee) } return &ExecutionResult{ @@ -460,18 +460,18 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { refund = st.state.GetRefund() } - if st.evm.Config.Tracer != nil && refund > 0 { - st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, vm.GasChangeTxRefunds) + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && refund > 0 { + st.evm.Config.Tracer.OnGasChange(st.gasRemaining, st.gasRemaining+refund, live.GasChangeTxRefunds) } st.gasRemaining += refund // Return ETH for remaining gas, exchanged at the original rate. remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice) - st.state.AddBalance(st.msg.From, remaining, state.BalanceIncreaseGasReturn) + st.state.AddBalance(st.msg.From, remaining, live.BalanceIncreaseGasReturn) - if st.evm.Config.Tracer != nil && st.gasRemaining > 0 { - st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, vm.GasChangeTxLeftOverReturned) + if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 { + st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, live.GasChangeTxLeftOverReturned) } // Also return remaining gas to the block gas counter so it is diff --git a/core/vm/contract.go b/core/vm/contract.go index f7dae713d1..fbfc3b125d 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -20,6 +20,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/holiman/uint256" ) @@ -159,11 +160,11 @@ func (c *Contract) Caller() common.Address { } // UseGas attempts the use gas and subtracts it and returns true on success -func (c *Contract) UseGas(gas uint64, logger EVMLogger, reason GasChangeReason) (ok bool) { +func (c *Contract) UseGas(gas uint64, logger *live.LiveLogger, reason live.GasChangeReason) (ok bool) { if c.Gas < gas { return false } - if logger != nil && reason != GasChangeIgnored { + if logger != nil && logger.OnGasChange != nil && reason != live.GasChangeIgnored { logger.OnGasChange(c.Gas, c.Gas-gas, reason) } c.Gas -= gas diff --git a/core/vm/contracts.go b/core/vm/contracts.go index bb5dc039f0..799665f0cb 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" "golang.org/x/crypto/ripemd160" ) @@ -168,13 +169,13 @@ func ActivePrecompiles(rules params.Rules) []common.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger EVMLogger) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64, logger *live.LiveLogger) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } - if logger != nil { - logger.OnGasChange(suppliedGas, suppliedGas-gasCost, GasChangeCallPrecompiledContract) + if logger != nil && logger.OnGasChange != nil { + logger.OnGasChange(suppliedGas, suppliedGas-gasCost, live.GasChangeCallPrecompiledContract) } suppliedGas -= gasCost output, err := p.Run(input) diff --git a/core/vm/evm.go b/core/vm/evm.go index 4960d08c77..b291bcfbc6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -22,9 +22,9 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -230,8 +230,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config.Tracer != nil { - evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution) + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, live.GasChangeCallFailedExecution) } gas = 0 @@ -286,8 +286,8 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config.Tracer != nil { - evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution) + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, live.GasChangeCallFailedExecution) } gas = 0 @@ -333,8 +333,8 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config.Tracer != nil { - evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution) + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, live.GasChangeCallFailedExecution) } gas = 0 } @@ -369,7 +369,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - evm.StateDB.AddBalance(addr, new(big.Int), state.BalanceChangeTouchAccount) + evm.StateDB.AddBalance(addr, new(big.Int), live.BalanceChangeTouchAccount) if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer) @@ -391,8 +391,8 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - if evm.Config.Tracer != nil { - evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution) + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, live.GasChangeCallFailedExecution) } gas = 0 @@ -442,8 +442,8 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { - if evm.Config.Tracer != nil { - evm.Config.Tracer.OnGasChange(gas, 0, GasChangeCallFailedExecution) + if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil { + evm.Config.Tracer.OnGasChange(gas, 0, live.GasChangeCallFailedExecution) } return nil, common.Address{}, 0, ErrContractAddressCollision @@ -479,7 +479,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // by the error checking condition below. if err == nil { createDataGas := uint64(len(ret)) * params.CreateDataGas - if contract.UseGas(createDataGas, evm.Config.Tracer, GasChangeCallCodeStorage) { + if contract.UseGas(createDataGas, evm.Config.Tracer, live.GasChangeCallCodeStorage) { evm.StateDB.SetCode(address, ret) } else { err = ErrCodeStoreOutOfGas @@ -492,7 +492,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas, evm.Config.Tracer, GasChangeCallFailedExecution) + contract.UseGas(contract.Gas, evm.Config.Tracer, live.GasChangeCallFailedExecution) } } @@ -522,19 +522,23 @@ func (evm *EVM) captureBegin(isRoot bool, typ OpCode, from common.Address, to co tracer := evm.Config.Tracer if isRoot { - tracer.CaptureStart(from, to, typ == CREATE || typ == CREATE2, input, startGas, value) - } else { - tracer.CaptureEnter(typ, from, to, input, startGas, value) + if tracer.CaptureStart != nil { + tracer.CaptureStart(from, to, typ == CREATE || typ == CREATE2, input, startGas, value) + } + } else if tracer.CaptureEnter != nil { + tracer.CaptureEnter(live.OpCode(typ), from, to, input, startGas, value) } - tracer.OnGasChange(0, startGas, GasChangeCallInitialBalance) + if tracer.OnGasChange != nil { + tracer.OnGasChange(0, startGas, live.GasChangeCallInitialBalance) + } } func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas uint64, ret []byte, err error) { tracer := evm.Config.Tracer - if leftOverGas != 0 { - tracer.OnGasChange(leftOverGas, 0, GasChangeCallLeftOverReturned) + if leftOverGas != 0 && tracer.OnGasChange != nil { + tracer.OnGasChange(leftOverGas, 0, live.GasChangeCallLeftOverReturned) } var reverted bool if err != nil { @@ -544,8 +548,22 @@ func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas reverted = false } if isRoot { - tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) - } else { + if tracer.CaptureEnd != nil { + tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) + } + } else if tracer.CaptureExit != nil { tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) } } + +func (evm *EVM) GetVMContext() *live.VMContext { + return &live.VMContext{ + Coinbase: evm.Context.Coinbase, + BlockNumber: evm.Context.BlockNumber, + Time: evm.Context.Time, + Random: evm.Context.Random, + GasPrice: evm.TxContext.GasPrice, + ChainConfig: evm.ChainConfig(), + StateDB: evm.StateDB, + } +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b9c5b153b7..31296aa1a5 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -18,9 +18,9 @@ package vm import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -594,7 +594,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b // reuse size int for stackvalue stackvalue := size - scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, GasChangeCallContractCreation) + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, live.GasChangeCallContractCreation) //TODO: use uint256.Int instead of converting with toBig() var bigVal = big0 if !value.IsZero() { @@ -616,7 +616,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b scope.Stack.push(&stackvalue) if interpreter.evm.Config.Tracer != nil && returnGas > 0 { - interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) + interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, live.GasChangeCallLeftOverRefunded) } scope.Contract.Gas += returnGas @@ -642,7 +642,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] ) // Apply EIP150 gas -= gas / 64 - scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, GasChangeCallContractCreation2) + scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, live.GasChangeCallContractCreation2) // reuse size int for stackvalue stackvalue := size //TODO: use uint256.Int instead of converting with toBig() @@ -661,7 +661,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] scope.Stack.push(&stackvalue) if interpreter.evm.Config.Tracer != nil && returnGas > 0 { - interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) + interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, live.GasChangeCallLeftOverRefunded) } scope.Contract.Gas += returnGas @@ -711,7 +711,7 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt } if interpreter.evm.Config.Tracer != nil && returnGas > 0 { - interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) + interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, live.GasChangeCallLeftOverRefunded) } scope.Contract.Gas += returnGas @@ -751,7 +751,7 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } if interpreter.evm.Config.Tracer != nil && returnGas > 0 { - interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) + interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, live.GasChangeCallLeftOverRefunded) } scope.Contract.Gas += returnGas @@ -784,7 +784,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } if interpreter.evm.Config.Tracer != nil && returnGas > 0 { - interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) + interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, live.GasChangeCallLeftOverRefunded) } scope.Contract.Gas += returnGas @@ -817,7 +817,7 @@ func opStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } if interpreter.evm.Config.Tracer != nil && returnGas > 0 { - interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, GasChangeCallLeftOverRefunded) + interpreter.evm.Config.Tracer.OnGasChange(scope.Contract.Gas, scope.Contract.Gas+returnGas, live.GasChangeCallLeftOverRefunded) } scope.Contract.Gas += returnGas @@ -855,10 +855,10 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext } beneficiary := scope.Stack.pop() balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) - interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, state.BalanceIncreaseSelfdestruct) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, live.BalanceIncreaseSelfdestruct) interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { - tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) + tracer.CaptureEnter(live.OpCode(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) tracer.CaptureExit([]byte{}, 0, nil, false) } return nil, errStopToken @@ -870,11 +870,11 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon } beneficiary := scope.Stack.pop() balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) - interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance, state.BalanceDecreaseSelfdestruct) - interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, state.BalanceIncreaseSelfdestruct) + interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance, live.BalanceDecreaseSelfdestruct) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, live.BalanceIncreaseSelfdestruct) interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { - tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) + tracer.CaptureEnter(live.OpCode(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) tracer.CaptureExit([]byte{}, 0, nil, false) } return nil, errStopToken diff --git a/core/vm/interface.go b/core/vm/interface.go index e96e4502fb..94aa015ed9 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -20,8 +20,8 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" ) @@ -29,8 +29,8 @@ import ( type StateDB interface { CreateAccount(common.Address) - SubBalance(common.Address, *big.Int, state.BalanceChangeReason) - AddBalance(common.Address, *big.Int, state.BalanceChangeReason) + SubBalance(common.Address, *big.Int, live.BalanceChangeReason) + AddBalance(common.Address, *big.Int, live.BalanceChangeReason) GetBalance(common.Address) *big.Int GetNonce(common.Address) uint64 diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 7750517e06..5f86299637 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -22,12 +22,12 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" ) // Config are the configuration options for the Interpreter type Config struct { - Tracer EVMLogger // Opcode logger - LiveLogger *live.LiveLogger + Tracer *live.LiveLogger NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages ExtraEips []int // Additional EIPS that are to be enabled @@ -41,6 +41,30 @@ type ScopeContext struct { Contract *Contract } +func (ctx *ScopeContext) GetMemoryData() []byte { + return ctx.Memory.Data() +} + +func (ctx *ScopeContext) GetStackData() []uint256.Int { + return ctx.Stack.Data() +} + +func (ctx *ScopeContext) GetCaller() common.Address { + return ctx.Contract.Caller() +} + +func (ctx *ScopeContext) GetAddress() common.Address { + return ctx.Contract.Address() +} + +func (ctx *ScopeContext) GetCallValue() *uint256.Int { + return ctx.Contract.Value() +} + +func (ctx *ScopeContext) GetCallInput() []byte { + return ctx.Contract.Input +} + // EVMInterpreter represents an EVM interpreter type EVMInterpreter struct { evm *EVM @@ -160,9 +184,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( defer func() { if err != nil { if !logged { - in.evm.Config.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) - } else { - in.evm.Config.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err)) + if in.evm.Config.Tracer.CaptureState != nil { + in.evm.Config.Tracer.CaptureState(pcCopy, live.OpCode(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) + } + } else if in.evm.Config.Tracer.CaptureFault != nil { + in.evm.Config.Tracer.CaptureFault(pcCopy, live.OpCode(op), gasCopy, cost, callContext, in.evm.depth, VMErrorFromErr(err)) } } }() @@ -187,7 +213,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } else if sLen > operation.maxStack { return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} } - if !contract.UseGas(cost, in.evm.Config.Tracer, GasChangeIgnored) { + if !contract.UseGas(cost, in.evm.Config.Tracer, live.GasChangeIgnored) { return nil, ErrOutOfGas } @@ -214,23 +240,31 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( var dynamicCost uint64 dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) cost += dynamicCost // for tracing - if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer, GasChangeIgnored) { + if err != nil || !contract.UseGas(dynamicCost, in.evm.Config.Tracer, live.GasChangeIgnored) { return nil, ErrOutOfGas } // Do tracing before memory expansion if debug { - in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode) - in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) - logged = true + if in.evm.Config.Tracer.OnGasChange != nil { + in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, live.GasChangeCallOpCode) + } + if in.evm.Config.Tracer.CaptureState != nil { + in.evm.Config.Tracer.CaptureState(pc, live.OpCode(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) + logged = true + } } if memorySize > 0 { mem.Resize(memorySize) } } else if debug { - in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, GasChangeCallOpCode) - in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) - logged = true + if in.evm.Config.Tracer.OnGasChange != nil { + in.evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, live.GasChangeCallOpCode) + } + if in.evm.Config.Tracer.CaptureState != nil { + in.evm.Config.Tracer.CaptureState(pc, live.OpCode(op), gasCopy, cost, callContext, in.returnData, in.evm.depth, VMErrorFromErr(err)) + logged = true + } } // execute the operation diff --git a/core/vm/logger.go b/core/vm/logger.go index e86bb5d98e..269aed509c 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -16,19 +16,12 @@ package vm -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - // 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. // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. -type EVMLogger interface { +/*type EVMLogger interface { // Transaction level // Call simulations don't come with a valid signature. `from` field // to be used for address of the caller. @@ -55,63 +48,4 @@ type EVMLogger interface { // Misc OnGasChange(old, new uint64, reason GasChangeReason) } - -// GasChangeReason is used to indicate the reason for a gas change, useful -// for tracing and reporting. -// -// There is essentially two types of gas changes, those that can be emitted once per transaction -// and those that can be emitted on a call basis, so possibly multiple times per transaction. -// -// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted -// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis. -type GasChangeReason byte - -const ( - GasChangeUnspecified GasChangeReason = iota - - // GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only - // one such gas change per transaction. - GasChangeTxInitialBalance - // GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is - // always exactly one of those per transaction. - GasChangeTxIntrinsicGas - // GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) - // this generates an increase in gas. There is at most one of such gas change per transaction. - GasChangeTxRefunds - // GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned - // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas - // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. - // There is at most one of such gas change per transaction. - GasChangeTxLeftOverReturned - - // GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only - // one such gas change per call. - GasChangeCallInitialBalance - // GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always - // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even - // will be emitted. - GasChangeCallLeftOverReturned - // GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it - // executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child. - // If there was no gas left to be refunded, no such even will be emitted. - GasChangeCallLeftOverRefunded - // GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE. - GasChangeCallContractCreation - // GasChangeContractCreation is the amount of gas that will be burned for a CREATE2. - GasChangeCallContractCreation2 - // GasChangeCallCodeStorage is the amount of gas that will be charged for code storage. - GasChangeCallCodeStorage - // GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was - // performed can be check by `CaptureState` handling. - GasChangeCallOpCode - // GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution. - GasChangeCallPrecompiledContract - // GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules. - GasChangeCallStorageColdAccess - // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. - GasChangeCallFailedExecution - - // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as - // it will be "manually" tracked by a direct emit of the gas change event. - GasChangeIgnored GasChangeReason = 0xFF -) +*/ diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 55c9bf34b9..e9f26a88c1 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" ) @@ -169,7 +170,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc { evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available // gas for call - if !contract.UseGas(coldCost, evm.Config.Tracer, GasChangeCallStorageColdAccess) { + if !contract.UseGas(coldCost, evm.Config.Tracer, live.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } } diff --git a/eth/backend.go b/eth/backend.go index abb6a7e7c2..1ba7552d76 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -193,7 +193,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, Tracer: config.VMTracer, - LiveLogger: config.LiveLogger, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index b5e2f2368c..11e43e41b1 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -29,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/txpool/blobpool" "github.com/ethereum/go-ethereum/core/txpool/legacypool" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/eth/tracers/directory/live" @@ -154,8 +153,7 @@ type Config struct { EnablePreimageRecording bool // Enables VM tracing - VMTracer vm.EVMLogger - LiveLogger *live.LiveLogger + VMTracer *live.LiveLogger // Miscellaneous options DocRoot string `toml:"-"` diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 30f0b23a56..422b672086 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -777,14 +777,14 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block // Swap out the noop logger to the standard tracer writer = bufio.NewWriter(dump) vmConf = vm.Config{ - Tracer: logger.NewJSONLogger(&logConfig, writer), + Tracer: logger.NewJSONLogger(&logConfig, writer).GetLogger(), EnablePreimageRecording: true, } } // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) statedb.SetTxContext(tx.Hash(), i) - vmConf.Tracer.CaptureTxStart(vmenv, tx, msg.From) + vmConf.Tracer.CaptureTxStart(vmenv.GetVMContext(), tx, msg.From) vmRet, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) vmConf.Tracer.CaptureTxEnd(&types.Receipt{GasUsed: vmRet.UsedGas}, err) if writer != nil { @@ -922,7 +922,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc // be tracer dependent. func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *directory.Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { var ( - tracer directory.Tracer + tracer *directory.Tracer err error timeout = defaultTraceTimeout ) @@ -930,15 +930,15 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *direc config = &TraceConfig{} } // Default tracer is the struct logger - tracer = logger.NewStructLogger(config.Config) + tracer = logger.NewStructLogger(config.Config).GetTracer() if config.Tracer != nil { tracer, err = directory.DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig) if err != nil { return nil, err } } - vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: big.NewInt(0)}, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) - statedb.SetLogger(tracer) + vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: big.NewInt(0)}, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer.LiveLogger, NoBaseFee: true}) + statedb.SetLogger(tracer.LiveLogger) // Define a meaningful timeout of a single transaction trace if config.Timeout != nil { diff --git a/eth/tracers/directory/live/dir.go b/eth/tracers/directory/live/dir.go index f6656ac79f..9e89c0f36c 100644 --- a/eth/tracers/directory/live/dir.go +++ b/eth/tracers/directory/live/dir.go @@ -6,15 +6,82 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) +type ScopeContext interface { + GetMemoryData() []byte + GetStackData() []uint256.Int + GetCaller() common.Address + GetAddress() common.Address + GetCallValue() *uint256.Int + GetCallInput() []byte +} + +type StateDB interface { + GetBalance(common.Address) *uint256.Int + GetNonce(common.Address) uint64 + GetCode(common.Address) []byte + GetState(common.Address, common.Hash) common.Hash + Exist(common.Address) bool + GetRefund() uint64 +} + +type VMContext struct { + Coinbase common.Address + BlockNumber *big.Int + Time uint64 + Random *common.Hash + // Effective tx gas price + GasPrice *big.Int + ChainConfig *params.ChainConfig + StateDB StateDB +} + +// BlockEvent is emitted upon tracing an incoming block. +// It contains the block as well as consensus related information. +type BlockEvent struct { + Block *types.Block + TD *big.Int + Finalized *types.Header + Safe *types.Header +} + +// OpCode is an EVM opcode +// TODO: provide utils for consumers +type OpCode byte + type LiveLogger struct { - VMLogger vm.EVMLogger + /* + - VM events - + */ + // Transaction level + // Call simulations don't come with a valid signature. `from` field + // to be used for address of the caller. + CaptureTxStart func(vm *VMContext, tx *types.Transaction, from common.Address) + CaptureTxEnd func(receipt *types.Receipt, err error) + // Top call frame + CaptureStart func(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) + // CaptureEnd is invoked when the processing of the top call ends. + // See docs for `CaptureExit` for info on the `reverted` parameter. + CaptureEnd func(output []byte, gasUsed uint64, err error, reverted bool) + // Rest of call frames + CaptureEnter func(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) + // CaptureExit is invoked when the processing of a message ends. + // `revert` is true when there was an error during the execution. + // Exceptionally, before the homestead hardfork a contract creation that + // ran out of gas when attempting to persist the code to database did not + // count as a call failure and did not cause a revert of the call. This will + // be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`. + CaptureExit func(output []byte, gasUsed uint64, err error, reverted bool) + // Opcode level + CaptureState func(pc uint64, op OpCode, gas, cost uint64, scope ScopeContext, rData []byte, depth int, err error) + CaptureFault func(pc uint64, op OpCode, gas, cost uint64, scope ScopeContext, depth int, err error) + CaptureKeccakPreimage func(hash common.Hash, data []byte) + // Misc + OnGasChange func(old, new uint64, reason GasChangeReason) /* - Chain events - @@ -22,18 +89,18 @@ type LiveLogger struct { OnBlockchainInit func(chainConfig *params.ChainConfig) // OnBlockStart is called before executing `block`. // `td` is the total difficulty prior to `block`. - OnBlockStart func(event core.BlockEvent) + OnBlockStart func(event BlockEvent) OnBlockEnd func(err error) // OnSkippedBlock indicates a block was skipped during processing // due to it being known previously. This can happen e.g. when recovering // from a crash. - OnSkippedBlock func(event core.BlockEvent) - OnGenesisBlock func(genesis *types.Block, alloc core.GenesisAlloc) + OnSkippedBlock func(event BlockEvent) + OnGenesisBlock func(genesis *types.Block, alloc types.GenesisAlloc) /* - State events - */ - OnBalanceChange func(addr common.Address, prev, new *big.Int, reason state.BalanceChangeReason) + OnBalanceChange func(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) OnNonceChange func(addr common.Address, prev, new uint64) OnCodeChange func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) OnStorageChange func(addr common.Address, slot common.Hash, prev, new common.Hash) diff --git a/eth/tracers/directory/noop.go b/eth/tracers/directory/noop.go index 5f6546cfbe..9c28770494 100644 --- a/eth/tracers/directory/noop.go +++ b/eth/tracers/directory/noop.go @@ -21,9 +21,8 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) func init() { @@ -35,8 +34,29 @@ func init() { type NoopTracer struct{} // newNoopTracer returns a new noop tracer. -func newNoopTracer(ctx *Context, _ json.RawMessage) (Tracer, error) { - return &NoopTracer{}, nil +func newNoopTracer(ctx *Context, _ json.RawMessage) (*Tracer, error) { + t := &NoopTracer{} + return &Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: t.CaptureTxStart, + CaptureTxEnd: t.CaptureTxEnd, + CaptureStart: t.CaptureStart, + CaptureEnd: t.CaptureEnd, + CaptureEnter: t.CaptureEnter, + CaptureExit: t.CaptureExit, + CaptureState: t.CaptureState, + CaptureFault: t.CaptureFault, + CaptureKeccakPreimage: t.CaptureKeccakPreimage, + OnGasChange: t.OnGasChange, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -48,21 +68,21 @@ func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error, revert } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *NoopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *NoopTracer) CaptureState(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { } // CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *NoopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { +func (t *NoopTracer) CaptureFault(pc uint64, op live.OpCode, gas, cost uint64, _ live.ScopeContext, depth int, err error) { } // CaptureKeccakPreimage is called during the KECCAK256 opcode. func (t *NoopTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) {} // OnGasChange is called when gas is either consumed or refunded. -func (t *NoopTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) {} +func (t *NoopTracer) OnGasChange(old, new uint64, reason live.GasChangeReason) {} // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *NoopTracer) CaptureEnter(typ live.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 @@ -70,11 +90,11 @@ func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { } -func (*NoopTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {} +func (*NoopTracer) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) {} func (*NoopTracer) CaptureTxEnd(receipt *types.Receipt, err error) {} -func (*NoopTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) { +func (*NoopTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason live.BalanceChangeReason) { } func (*NoopTracer) OnNonceChange(a common.Address, prev, new uint64) {} diff --git a/eth/tracers/directory/tracers.go b/eth/tracers/directory/tracers.go index b16d9d2fe6..4953c46c56 100644 --- a/eth/tracers/directory/tracers.go +++ b/eth/tracers/directory/tracers.go @@ -24,8 +24,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) // Context contains some contextual infos for a transaction execution that is not @@ -41,16 +40,15 @@ type Context struct { // for it to be available through the RPC interface. // This involves a method to retrieve results and one to // stop tracing. -type Tracer interface { - vm.EVMLogger - state.StateLogger - GetResult() (json.RawMessage, error) +type Tracer struct { + *live.LiveLogger + GetResult func() (json.RawMessage, error) // Stop terminates execution of the tracer at the first opportune moment. - Stop(err error) + Stop func(err error) } -type ctorFn func(*Context, json.RawMessage) (Tracer, error) -type jsCtorFn func(string, *Context, json.RawMessage) (Tracer, error) +type ctorFn func(*Context, json.RawMessage) (*Tracer, error) +type jsCtorFn func(string, *Context, json.RawMessage) (*Tracer, error) type elem struct { ctor ctorFn @@ -83,7 +81,7 @@ func (d *directory) RegisterJSEval(f jsCtorFn) { // New returns a new instance of a tracer, by iterating through the // registered lookups. Name is either name of an existing tracer // or an arbitrary JS code. -func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (Tracer, error) { +func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (*Tracer, error) { if elem, ok := d.elems[name]; ok { return elem.ctor(ctx, cfg) } diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index c3a9508202..f6ed3ed229 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -25,6 +25,7 @@ import ( "github.com/dop251/goja" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -42,9 +43,9 @@ func init() { if err != nil { panic(err) } - type ctorFn = func(*directory.Context, json.RawMessage) (directory.Tracer, error) + type ctorFn = func(*directory.Context, json.RawMessage) (*directory.Tracer, error) lookup := func(code string) ctorFn { - return func(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { + return func(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) { return newJsTracer(code, ctx, cfg) } } @@ -99,7 +100,7 @@ type jsTracer struct { directory.NoopTracer vm *goja.Runtime - env *vm.EVM + env *live.VMContext toBig toBigFn // Converts a hex string into a JS bigint toBuf toBufFn // Converts a []byte into a JS buffer fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte @@ -136,7 +137,7 @@ type jsTracer struct { // The methods `result` and `fault` are required to be present. // The methods `step`, `enter`, and `exit` are optional, but note that // `enter` and `exit` always go together. -func newJsTracer(code string, ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { +func newJsTracer(code string, ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) { vm := goja.New() // By default field names are exported to JS as is, i.e. capitalized. vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) @@ -209,22 +210,36 @@ func newJsTracer(code string, ctx *directory.Context, cfg json.RawMessage) (dire t.frameValue = t.frame.setupObject() t.frameResultValue = t.frameResult.setupObject() t.logValue = t.log.setupObject() - return t, nil + + return &directory.Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: t.CaptureTxStart, + CaptureTxEnd: t.CaptureTxEnd, + CaptureStart: t.CaptureStart, + CaptureEnd: t.CaptureEnd, + CaptureEnter: t.CaptureEnter, + CaptureExit: t.CaptureExit, + CaptureState: t.CaptureState, + CaptureFault: t.CaptureFault, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } // CaptureTxStart implements the Tracer interface and is invoked at the beginning of // transaction processing. -func (t *jsTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (t *jsTracer) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { t.env = env // Need statedb access for db object db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf} t.dbValue = db.setupObject() // Update list of precompiles based on current block - rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) + rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) - t.ctx["block"] = t.vm.ToValue(t.env.Context.BlockNumber.Uint64()) + t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber.Uint64()) t.ctx["gas"] = t.vm.ToValue(tx.Gas()) - gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String()) + gasPriceBig, err := t.toBig(t.vm, env.GasPrice.String()) if err != nil { t.err = err t.env.Cancel() @@ -284,7 +299,7 @@ func (t *jsTracer) CaptureStart(from common.Address, to common.Address, create b } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *jsTracer) CaptureState(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { if !t.traceStep { return } @@ -293,7 +308,7 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope } log := t.log - log.op.op = op + log.op.op = vm.OpCode(op) log.memory.memory = scope.Memory log.stack.stack = scope.Stack log.contract.contract = scope.Contract @@ -309,7 +324,7 @@ func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope } // CaptureFault implements the Tracer interface to trace an execution fault -func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (t *jsTracer) CaptureFault(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, depth int, err error) { if t.err != nil { return } @@ -328,7 +343,7 @@ func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *jsTracer) CaptureEnter(typ live.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { if !t.traceFrame { return } @@ -336,7 +351,7 @@ func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad return } - t.frame.typ = typ.String() + t.frame.typ = vm.OpCode(typ).String() t.frame.from = from t.frame.to = to t.frame.input = common.CopyBytes(input) @@ -681,7 +696,7 @@ func (s *stackObj) setupObject() *goja.Object { } type dbObj struct { - db vm.StateDB + db live.StateDB vm *goja.Runtime toBig toBigFn toBuf toBufFn diff --git a/eth/tracers/live/noop.go b/eth/tracers/live/noop.go index f9b8e19def..e099ef9ac5 100644 --- a/eth/tracers/live/noop.go +++ b/eth/tracers/live/noop.go @@ -5,10 +5,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" ) @@ -26,17 +23,26 @@ type noop struct{} func newNoopTracer(_ json.RawMessage) (*live.LiveLogger, error) { t := &noop{} return &live.LiveLogger{ - VMLogger: t, - OnBlockchainInit: t.OnBlockchainInit, - OnBlockStart: t.OnBlockStart, - OnBlockEnd: t.OnBlockEnd, - OnSkippedBlock: t.OnSkippedBlock, - OnGenesisBlock: t.OnGenesisBlock, - OnBalanceChange: t.OnBalanceChange, - OnNonceChange: t.OnNonceChange, - OnCodeChange: t.OnCodeChange, - OnStorageChange: t.OnStorageChange, - OnLog: t.OnLog, + CaptureTxStart: t.CaptureTxStart, + CaptureTxEnd: t.CaptureTxEnd, + CaptureStart: t.CaptureStart, + CaptureEnd: t.CaptureEnd, + CaptureEnter: t.CaptureEnter, + CaptureExit: t.CaptureExit, + CaptureState: t.CaptureState, + CaptureFault: t.CaptureFault, + CaptureKeccakPreimage: t.CaptureKeccakPreimage, + OnGasChange: t.OnGasChange, + OnBlockchainInit: t.OnBlockchainInit, + OnBlockStart: t.OnBlockStart, + OnBlockEnd: t.OnBlockEnd, + OnSkippedBlock: t.OnSkippedBlock, + OnGenesisBlock: t.OnGenesisBlock, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, }, nil } @@ -49,18 +55,18 @@ func (t *noop) CaptureEnd(output []byte, gasUsed uint64, err error, reverted boo } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *noop) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *noop) CaptureState(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { } // CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *noop) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { +func (t *noop) CaptureFault(pc uint64, op live.OpCode, gas, cost uint64, _ live.ScopeContext, depth int, err error) { } // CaptureKeccakPreimage is called during the KECCAK256 opcode. func (t *noop) CaptureKeccakPreimage(hash common.Hash, data []byte) {} // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *noop) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *noop) CaptureEnter(typ live.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 @@ -68,27 +74,27 @@ func (t *noop) CaptureEnter(typ vm.OpCode, from common.Address, to common.Addres func (t *noop) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { } -func (t *noop) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (t *noop) CaptureTxStart(vm *live.VMContext, tx *types.Transaction, from common.Address) { } func (t *noop) CaptureTxEnd(receipt *types.Receipt, err error) { } -func (t *noop) OnBlockStart(ev core.BlockEvent) { +func (t *noop) OnBlockStart(ev live.BlockEvent) { } func (t *noop) OnBlockEnd(err error) { } -func (t *noop) OnSkippedBlock(ev core.BlockEvent) {} +func (t *noop) OnSkippedBlock(ev live.BlockEvent) {} func (t *noop) OnBlockchainInit(chainConfig *params.ChainConfig) { } -func (t *noop) OnGenesisBlock(b *types.Block, alloc core.GenesisAlloc) { +func (t *noop) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { } -func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) { +func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason live.BalanceChangeReason) { } func (t *noop) OnNonceChange(a common.Address, prev, new uint64) { @@ -104,5 +110,5 @@ func (t *noop) OnLog(l *types.Log) { } -func (t *noop) OnGasChange(old, new uint64, reason vm.GasChangeReason) { +func (t *noop) OnGasChange(old, new uint64, reason live.GasChangeReason) { } diff --git a/eth/tracers/logger/access_list_tracer.go b/eth/tracers/logger/access_list_tracer.go index 7df66066b5..fdc990dfee 100644 --- a/eth/tracers/logger/access_list_tracer.go +++ b/eth/tracers/logger/access_list_tracer.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) // accessList is an accumulator for the set of accounts and storage slots an EVM @@ -132,14 +133,20 @@ func NewAccessListTracer(acl types.AccessList, from, to common.Address, precompi } } +func (a *AccessListTracer) GetLogger() *live.LiveLogger { + return &live.LiveLogger{ + CaptureState: a.CaptureState, + } +} + // CaptureState captures all opcodes that touch storage or addresses and adds them to the accesslist. -func (a *AccessListTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - stack := scope.Stack - stackData := stack.Data() +func (a *AccessListTracer) CaptureState(pc uint64, opcode live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { + stackData := scope.GetStackData() stackLen := len(stackData) + op := vm.OpCode(opcode) if (op == vm.SLOAD || op == vm.SSTORE) && stackLen >= 1 { slot := common.Hash(stackData[stackLen-1].Bytes32()) - a.list.addSlot(scope.Contract.Address(), slot) + a.list.addSlot(scope.GetAddress(), slot) } if (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT) && stackLen >= 1 { addr := common.Address(stackData[stackLen-1].Bytes20()) diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 3c22bd7886..3e8a68e6e5 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -109,7 +110,7 @@ func (s *StructLog) ErrorString() string { type StructLogger struct { directory.NoopTracer cfg Config - env *vm.EVM + env *live.VMContext storage map[common.Address]Storage logs []StructLog @@ -132,6 +133,19 @@ func NewStructLogger(cfg *Config) *StructLogger { return logger } +func (l *StructLogger) GetTracer() *directory.Tracer { + return &directory.Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: l.CaptureTxStart, + CaptureTxEnd: l.CaptureTxEnd, + CaptureEnd: l.CaptureEnd, + CaptureState: l.CaptureState, + }, + GetResult: l.GetResult, + Stop: l.Stop, + } +} + // Reset clears the data held by the logger. func (l *StructLogger) Reset() { l.storage = make(map[common.Address]Storage) @@ -143,7 +157,7 @@ func (l *StructLogger) Reset() { // 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) { +func (l *StructLogger) CaptureState(pc uint64, opcode live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { // If tracing was interrupted, set the error and stop if l.interrupt.Load() { return @@ -153,49 +167,49 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s return } - memory := scope.Memory - stack := scope.Stack - contract := scope.Contract + op := vm.OpCode(opcode) + memory := scope.GetMemoryData() + stack := scope.GetStackData() // Copy a snapshot of the current memory state to a new buffer var mem []byte if l.cfg.EnableMemory { - mem = make([]byte, len(memory.Data())) - copy(mem, memory.Data()) + mem = make([]byte, len(memory)) + copy(mem, memory) } // Copy a snapshot of the current stack state to a new buffer var stck []uint256.Int if !l.cfg.DisableStack { - stck = make([]uint256.Int, len(stack.Data())) - for i, item := range stack.Data() { + stck = make([]uint256.Int, len(stack)) + for i, item := range stack { stck[i] = item } } - stackData := stack.Data() - stackLen := len(stackData) + contractAddr := scope.GetAddress() + stackLen := len(stack) // Copy a snapshot of the current storage to a new container var storage Storage if !l.cfg.DisableStorage && (op == vm.SLOAD || op == vm.SSTORE) { // initialise new changed values storage container for this contract // if not present. - if l.storage[contract.Address()] == nil { - l.storage[contract.Address()] = make(Storage) + if l.storage[contractAddr] == nil { + l.storage[contractAddr] = make(Storage) } // capture SLOAD opcodes and record the read entry in the local storage if op == vm.SLOAD && stackLen >= 1 { var ( - address = common.Hash(stackData[stackLen-1].Bytes32()) - value = l.env.StateDB.GetState(contract.Address(), address) + address = common.Hash(stack[stackLen-1].Bytes32()) + value = l.env.StateDB.GetState(contractAddr, address) ) - l.storage[contract.Address()][address] = value - storage = l.storage[contract.Address()].Copy() + l.storage[contractAddr][address] = value + storage = l.storage[contractAddr].Copy() } else if op == vm.SSTORE && stackLen >= 2 { // capture SSTORE opcodes and record the written entry in the local storage. var ( - value = common.Hash(stackData[stackLen-2].Bytes32()) - address = common.Hash(stackData[stackLen-1].Bytes32()) + value = common.Hash(stack[stackLen-2].Bytes32()) + address = common.Hash(stack[stackLen-1].Bytes32()) ) - l.storage[contract.Address()][address] = value - storage = l.storage[contract.Address()].Copy() + l.storage[contractAddr][address] = value + storage = l.storage[contractAddr].Copy() } } var rdata []byte @@ -204,7 +218,7 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s 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.StateDB.GetRefund(), err} + log := StructLog{pc, op, gas, cost, mem, len(memory), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} l.logs = append(l.logs, log) } @@ -246,7 +260,7 @@ func (l *StructLogger) Stop(err error) { l.interrupt.Store(true) } -func (l *StructLogger) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (l *StructLogger) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { l.env = env } diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 1bf88f8899..fecfa0cb73 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -25,13 +25,14 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) type JSONLogger struct { directory.NoopTracer encoder *json.Encoder cfg *Config - env *vm.EVM + env *live.VMContext } // NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects @@ -44,31 +45,40 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *JSONLogger { return l } -func (l *JSONLogger) CaptureFault(pc uint64, op vm.OpCode, gas uint64, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (l *JSONLogger) GetLogger() *live.LiveLogger { + return &live.LiveLogger{ + CaptureTxStart: l.CaptureTxStart, + CaptureEnd: l.CaptureEnd, + CaptureState: l.CaptureState, + CaptureFault: l.CaptureFault, + } +} + +func (l *JSONLogger) CaptureFault(pc uint64, op live.OpCode, gas uint64, cost uint64, scope live.ScopeContext, depth int, err error) { // TODO: Add rData to this interface as well l.CaptureState(pc, op, gas, cost, scope, nil, depth, err) } // CaptureState outputs state information on the logger. -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 +func (l *JSONLogger) CaptureState(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { + memory := scope.GetMemoryData() + stack := scope.GetStackData() log := StructLog{ Pc: pc, - Op: op, + Op: vm.OpCode(op), Gas: gas, GasCost: cost, - MemorySize: memory.Len(), + MemorySize: len(memory), Depth: depth, RefundCounter: l.env.StateDB.GetRefund(), Err: err, } if l.cfg.EnableMemory { - log.Memory = memory.Data() + log.Memory = memory } if !l.cfg.DisableStack { - log.Stack = stack.Data() + log.Stack = stack } if l.cfg.EnableReturnData { log.ReturnData = rData @@ -90,6 +100,6 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error, revert l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) } -func (l *JSONLogger) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (l *JSONLogger) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { l.env = env } diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index c7c45cfaa4..aa2dbd906a 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) func init() { @@ -48,7 +49,6 @@ func init() { // } type fourByteTracer struct { directory.NoopTracer - env *vm.EVM ids map[string]int // ids aggregates the 4byte ids found interrupt atomic.Bool // Atomic flag to signal execution interruption reason error // Textual reason for the interruption @@ -57,11 +57,19 @@ type fourByteTracer struct { // newFourByteTracer returns a native go tracer which collects // 4 byte-identifiers of a tx, and implements vm.EVMLogger. -func newFourByteTracer(ctx *directory.Context, _ json.RawMessage) (directory.Tracer, error) { +func newFourByteTracer(ctx *directory.Context, _ json.RawMessage) (*directory.Tracer, error) { t := &fourByteTracer{ ids: make(map[string]int), } - return t, nil + return &directory.Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: t.CaptureTxStart, + CaptureStart: t.CaptureStart, + CaptureEnter: t.CaptureEnter, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } // isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go @@ -80,10 +88,9 @@ func (t *fourByteTracer) store(id []byte, size int) { t.ids[key] += 1 } -func (t *fourByteTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { - t.env = env +func (t *fourByteTracer) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { // Update list of precompiles based on current block - rules := t.env.ChainConfig().Rules(t.env.Context.BlockNumber, t.env.Context.Random != nil, t.env.Context.Time) + rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) } @@ -96,7 +103,7 @@ func (t *fourByteTracer) CaptureStart(from common.Address, to common.Address, cr } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *fourByteTracer) CaptureEnter(opcode live.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { // Skip if tracing was interrupted if t.interrupt.Load() { return @@ -104,6 +111,7 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm if len(input) < 4 { return } + op := vm.OpCode(opcode) // primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT if op != vm.DELEGATECALL && op != vm.STATICCALL && op != vm.CALL && op != vm.CALLCODE { diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 43bd0198da..74a13038d9 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) //go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go @@ -120,7 +121,28 @@ type callTracerConfig struct { // newCallTracer returns a native go tracer which tracks // call frames of a tx, and implements vm.EVMLogger. -func newCallTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { +func newCallTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) { + t, err := newCallTracerObject(ctx, cfg) + if err != nil { + return nil, err + } + return &directory.Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: t.CaptureTxStart, + CaptureTxEnd: t.CaptureTxEnd, + CaptureStart: t.CaptureStart, + CaptureEnd: t.CaptureEnd, + CaptureEnter: t.CaptureEnter, + CaptureExit: t.CaptureExit, + CaptureState: t.CaptureState, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil +} + +func newCallTracerObject(ctx *directory.Context, cfg json.RawMessage) (*callTracer, error) { var config callTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { @@ -154,11 +176,11 @@ func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error, revert } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *callTracer) CaptureState(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *callTracer) CaptureEnter(typ live.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { t.depth++ if t.config.OnlyTopCall { return @@ -170,7 +192,7 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. toCopy := to call := callFrame{ - Type: typ, + Type: vm.OpCode(typ), From: from, To: &toCopy, Input: common.CopyBytes(input), @@ -201,7 +223,7 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error, rever t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } -func (t *callTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (t *callTracer) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { t.gasLimit = tx.Gas() } diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index 26f114f44f..4cd5308e40 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) //go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go @@ -123,7 +124,7 @@ type flatCallTracerConfig struct { } // newFlatCallTracer returns a new flatCallTracer. -func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { +func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) { var config flatCallTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { @@ -133,16 +134,26 @@ func newFlatCallTracer(ctx *directory.Context, cfg json.RawMessage) (directory.T // Create inner call tracer with default configuration, don't forward // the OnlyTopCall or WithLog to inner for now - tracer, err := directory.DefaultDirectory.New("callTracer", ctx, nil) + t, err := newCallTracerObject(ctx, nil) if err != nil { return nil, err } - t, ok := tracer.(*callTracer) - if !ok { - return nil, errors.New("internal error: embedded tracer has wrong type") - } - return &flatCallTracer{tracer: t, ctx: ctx, config: config}, nil + ft := &flatCallTracer{tracer: t, ctx: ctx, config: config} + return &directory.Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: ft.CaptureTxStart, + CaptureTxEnd: ft.CaptureTxEnd, + CaptureStart: ft.CaptureStart, + CaptureEnd: ft.CaptureEnd, + CaptureEnter: ft.CaptureEnter, + CaptureExit: ft.CaptureExit, + CaptureState: ft.CaptureState, + CaptureFault: ft.CaptureFault, + }, + Stop: ft.Stop, + GetResult: ft.GetResult, + }, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. @@ -156,17 +167,17 @@ func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error, re } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *flatCallTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *flatCallTracer) CaptureState(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { t.tracer.CaptureState(pc, op, gas, cost, scope, rData, depth, err) } // CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *flatCallTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (t *flatCallTracer) CaptureFault(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, depth int, err error) { t.tracer.CaptureFault(pc, op, gas, cost, scope, depth, err) } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *flatCallTracer) CaptureEnter(typ live.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { t.tracer.CaptureEnter(typ, from, to, input, gas, value) // Child calls must have a value, even if it's zero. @@ -200,10 +211,10 @@ func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error, r } } -func (t *flatCallTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (t *flatCallTracer) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { t.tracer.CaptureTxStart(env, tx, from) // Update list of precompiles based on current block - rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time) + rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) t.activePrecompiles = vm.ActivePrecompiles(rules) } diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index 70af63e752..501f598385 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -21,10 +21,9 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" ) func init() { @@ -35,18 +34,18 @@ func init() { // runs multiple tracers in one go. type muxTracer struct { names []string - tracers []directory.Tracer + tracers []*directory.Tracer } // newMuxTracer returns a new mux tracer. -func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { +func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) { var config map[string]json.RawMessage if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { return nil, err } } - objects := make([]directory.Tracer, 0, len(config)) + objects := make([]*directory.Tracer, 0, len(config)) names := make([]string, 0, len(config)) for k, v := range config { t, err := directory.DefaultDirectory.New(k, ctx, v) @@ -57,55 +56,90 @@ func newMuxTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer names = append(names, k) } - return &muxTracer{names: names, tracers: objects}, nil + t := &muxTracer{names: names, tracers: objects} + return &directory.Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: t.CaptureTxStart, + CaptureTxEnd: t.CaptureTxEnd, + CaptureStart: t.CaptureStart, + CaptureEnd: t.CaptureEnd, + CaptureEnter: t.CaptureEnter, + CaptureExit: t.CaptureExit, + CaptureState: t.CaptureState, + CaptureFault: t.CaptureFault, + CaptureKeccakPreimage: t.CaptureKeccakPreimage, + OnGasChange: t.OnGasChange, + OnBalanceChange: t.OnBalanceChange, + OnNonceChange: t.OnNonceChange, + OnCodeChange: t.OnCodeChange, + OnStorageChange: t.OnStorageChange, + OnLog: t.OnLog, + }, + GetResult: t.GetResult, + Stop: t.Stop, + }, nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (t *muxTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { for _, t := range t.tracers { - t.CaptureStart(from, to, create, input, gas, value) + if t.CaptureStart != nil { + t.CaptureStart(from, to, create, input, gas, value) + } } } // CaptureEnd is called after the call finishes to finalize the tracing. func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { for _, t := range t.tracers { - t.CaptureEnd(output, gasUsed, err, reverted) + if t.CaptureEnd != nil { + t.CaptureEnd(output, gasUsed, err, reverted) + } } } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *muxTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *muxTracer) CaptureState(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { for _, t := range t.tracers { - t.CaptureState(pc, op, gas, cost, scope, rData, depth, err) + if t.CaptureState != nil { + t.CaptureState(pc, op, gas, cost, scope, rData, depth, err) + } } } // CaptureFault implements the EVMLogger interface to trace an execution fault. -func (t *muxTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (t *muxTracer) CaptureFault(pc uint64, op live.OpCode, gas, cost uint64, scope live.ScopeContext, depth int, err error) { for _, t := range t.tracers { - t.CaptureFault(pc, op, gas, cost, scope, depth, err) + if t.CaptureFault != nil { + t.CaptureFault(pc, op, gas, cost, scope, depth, err) + } } } // CaptureKeccakPreimage is called during the KECCAK256 opcode. func (t *muxTracer) CaptureKeccakPreimage(hash common.Hash, data []byte) { for _, t := range t.tracers { - t.CaptureKeccakPreimage(hash, data) + if t.CaptureKeccakPreimage != nil { + t.CaptureKeccakPreimage(hash, data) + } } } // CaptureGasConsumed is called when gas is consumed. -func (t *muxTracer) OnGasChange(old, new uint64, reason vm.GasChangeReason) { +func (t *muxTracer) OnGasChange(old, new uint64, reason live.GasChangeReason) { for _, t := range t.tracers { - t.OnGasChange(old, new, reason) + if t.OnGasChange != nil { + t.OnGasChange(old, new, reason) + } } } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *muxTracer) CaptureEnter(typ live.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { for _, t := range t.tracers { - t.CaptureEnter(typ, from, to, input, gas, value) + if t.CaptureEnter != nil { + t.CaptureEnter(typ, from, to, input, gas, value) + } } } @@ -113,49 +147,65 @@ func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.A // execute any code. func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { for _, t := range t.tracers { - t.CaptureExit(output, gasUsed, err, reverted) + if t.CaptureExit != nil { + t.CaptureExit(output, gasUsed, err, reverted) + } } } -func (t *muxTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (t *muxTracer) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { for _, t := range t.tracers { - t.CaptureTxStart(env, tx, from) + if t.CaptureTxStart != nil { + t.CaptureTxStart(env, tx, from) + } } } func (t *muxTracer) CaptureTxEnd(receipt *types.Receipt, err error) { for _, t := range t.tracers { - t.CaptureTxEnd(receipt, err) + if t.CaptureTxEnd != nil { + t.CaptureTxEnd(receipt, err) + } } } -func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason state.BalanceChangeReason) { +func (t *muxTracer) OnBalanceChange(a common.Address, prev, new *big.Int, reason live.BalanceChangeReason) { for _, t := range t.tracers { - t.OnBalanceChange(a, prev, new, reason) + if t.OnBalanceChange != nil { + t.OnBalanceChange(a, prev, new, reason) + } } } func (t *muxTracer) OnNonceChange(a common.Address, prev, new uint64) { for _, t := range t.tracers { - t.OnNonceChange(a, prev, new) + if t.OnNonceChange != nil { + t.OnNonceChange(a, prev, new) + } } } func (t *muxTracer) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { for _, t := range t.tracers { - t.OnCodeChange(a, prevCodeHash, prev, codeHash, code) + if t.OnCodeChange != nil { + t.OnCodeChange(a, prevCodeHash, prev, codeHash, code) + } } } func (t *muxTracer) OnStorageChange(a common.Address, k, prev, new common.Hash) { for _, t := range t.tracers { - t.OnStorageChange(a, k, prev, new) + if t.OnStorageChange != nil { + t.OnStorageChange(a, k, prev, new) + } } } func (t *muxTracer) OnLog(log *types.Log) { for _, t := range t.tracers { - t.OnLog(log) + if t.OnLog != nil { + t.OnLog(log) + } } } diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 8608ab69dc..88cd0009fa 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers/directory" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/log" ) @@ -58,7 +59,7 @@ type accountMarshaling struct { type prestateTracer struct { directory.NoopTracer - env *vm.EVM + env *live.VMContext pre stateMap post stateMap to common.Address @@ -73,24 +74,33 @@ type prestateTracerConfig struct { DiffMode bool `json:"diffMode"` // If true, this tracer will return state modifications } -func newPrestateTracer(ctx *directory.Context, cfg json.RawMessage) (directory.Tracer, error) { +func newPrestateTracer(ctx *directory.Context, cfg json.RawMessage) (*directory.Tracer, error) { var config prestateTracerConfig if cfg != nil { if err := json.Unmarshal(cfg, &config); err != nil { return nil, err } } - return &prestateTracer{ + t := &prestateTracer{ pre: stateMap{}, post: stateMap{}, config: config, created: make(map[common.Address]bool), deleted: make(map[common.Address]bool), + } + return &directory.Tracer{ + LiveLogger: &live.LiveLogger{ + CaptureTxStart: t.CaptureTxStart, + CaptureTxEnd: t.CaptureTxEnd, + CaptureState: t.CaptureState, + }, + GetResult: t.GetResult, + Stop: t.Stop, }, nil } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. -func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (t *prestateTracer) CaptureState(pc uint64, opcode live.OpCode, gas, cost uint64, scope live.ScopeContext, rData []byte, depth int, err error) { if err != nil { return } @@ -98,10 +108,10 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, if t.interrupt.Load() { return } - stack := scope.Stack - stackData := stack.Data() + op := vm.OpCode(opcode) + stackData := scope.GetStackData() stackLen := len(stackData) - caller := scope.Contract.Address() + caller := scope.GetAddress() switch { case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE): slot := common.Hash(stackData[stackLen-1].Bytes32()) @@ -136,7 +146,7 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, } } -func (t *prestateTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) { +func (t *prestateTracer) CaptureTxStart(env *live.VMContext, tx *types.Transaction, from common.Address) { t.env = env if tx.To() == nil { t.to = crypto.CreateAddress(from, env.StateDB.GetNonce(from)) @@ -147,7 +157,7 @@ func (t *prestateTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from t.lookupAccount(from) t.lookupAccount(t.to) - t.lookupAccount(env.Context.Coinbase) + t.lookupAccount(env.Coinbase) } func (t *prestateTracer) CaptureTxEnd(receipt *types.Receipt, err error) { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 4f792260ed..39d79413bd 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -40,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/directory/live" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" @@ -960,7 +961,7 @@ func (diff *StateOverride) Apply(statedb *state.StateDB) error { } // Override account balance. if account.Balance != nil { - statedb.SetBalance(addr, (*big.Int)(*account.Balance), state.BalanceChangeUnspecified) + statedb.SetBalance(addr, (*big.Int)(*account.Balance), live.BalanceChangeUnspecified) } if account.State != nil && account.StateDiff != nil { return fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex()) @@ -1639,7 +1640,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) - config := vm.Config{Tracer: tracer, NoBaseFee: true} + config := vm.Config{Tracer: tracer.GetLogger(), NoBaseFee: true} vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil) res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) if err != nil { diff --git a/miner/worker.go b/miner/worker.go index a5ea9571a9..95190d5c5d 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -976,7 +976,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { if header.ParentBeaconRoot != nil { context := core.NewEVMBlockContext(header, w.chain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, w.chainConfig, vm.Config{}) - core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state, nil) + core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) } return env, nil }