mirror of https://github.com/ethereum/go-ethereum
eth/tracers: live chain tracing with hooks (#29189)
Here we add a Go API for running tracing plugins within the main block import process. As an advanced user of geth, you can now create a Go file in eth/tracers/live/, and within that file register your custom tracer implementation. Then recompile geth and select your tracer on the command line. Hooks defined in the tracer will run whenever a block is processed. The hook system is defined in package core/tracing. It uses a struct with callbacks, instead of requiring an interface, for several reasons: - We plan to keep this API stable long-term. The core/tracing hook API does not depend on on deep geth internals. - There are a lot of hooks, and tracers will only need some of them. Using a struct allows you to implement only the hooks you want to actually use. All existing tracers in eth/tracers/native have been rewritten to use the new hook system. This change breaks compatibility with the vm.EVMLogger interface that we used to have. If you are a user of vm.EVMLogger, please migrate to core/tracing, and sorry for breaking your stuff. But we just couldn't have both the old and new tracing APIs coexist in the EVM. --------- Co-authored-by: Matthieu Vachon <matthieu.o.vachon@gmail.com> Co-authored-by: Delweng <delweng@gmail.com> Co-authored-by: Martin HS <martin@swende.se>pull/29318/head
parent
38eb8b3e20
commit
064f37d6f6
@ -1,81 +0,0 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package t8ntool |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"io" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/eth/tracers" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
) |
||||
|
||||
// traceWriter is an vm.EVMLogger which also holds an inner logger/tracer.
|
||||
// When the TxEnd event happens, the inner tracer result is written to the file, and
|
||||
// the file is closed.
|
||||
type traceWriter struct { |
||||
inner vm.EVMLogger |
||||
f io.WriteCloser |
||||
} |
||||
|
||||
// Compile-time interface check
|
||||
var _ = vm.EVMLogger((*traceWriter)(nil)) |
||||
|
||||
func (t *traceWriter) CaptureTxEnd(restGas uint64) { |
||||
t.inner.CaptureTxEnd(restGas) |
||||
defer t.f.Close() |
||||
|
||||
if tracer, ok := t.inner.(tracers.Tracer); ok { |
||||
result, err := tracer.GetResult() |
||||
if err != nil { |
||||
log.Warn("Error in tracer", "err", err) |
||||
return |
||||
} |
||||
err = json.NewEncoder(t.f).Encode(result) |
||||
if err != nil { |
||||
log.Warn("Error writing tracer output", "err", err) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureTxStart(gasLimit uint64) { t.inner.CaptureTxStart(gasLimit) } |
||||
func (t *traceWriter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { |
||||
t.inner.CaptureStart(env, from, to, create, input, gas, value) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureEnd(output []byte, gasUsed uint64, err error) { |
||||
t.inner.CaptureEnd(output, gasUsed, err) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { |
||||
t.inner.CaptureEnter(typ, from, to, input, gas, value) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureExit(output []byte, gasUsed uint64, err error) { |
||||
t.inner.CaptureExit(output, gasUsed, err) |
||||
} |
||||
|
||||
func (t *traceWriter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { |
||||
t.inner.CaptureState(pc, op, gas, cost, scope, rData, depth, err) |
||||
} |
||||
func (t *traceWriter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { |
||||
t.inner.CaptureFault(pc, op, gas, cost, scope, depth, err) |
||||
} |
@ -0,0 +1 @@ |
||||
This test does some EVM execution, and can be used to test the tracers and trace-outputs. |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { |
||||
"balance" : "0x016345785d8a0000", |
||||
"code" : "0x", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
}, |
||||
"0x1111111111111111111111111111111111111111" : { |
||||
"balance" : "0x1", |
||||
"code" : "0x604060406040604000", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
{ |
||||
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", |
||||
"currentNumber" : "0x01", |
||||
"currentTimestamp" : "0x03e8", |
||||
"currentGasLimit" : "0x1000000000", |
||||
"previousHash" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da", |
||||
"currentDataGasUsed" : "0x2000", |
||||
"parentTimestamp" : "0x00", |
||||
"parentDifficulty" : "0x00", |
||||
"parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"parentBeaconBlockRoot" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"currentRandom" : "0x0000000000000000000000000000000000000000000000000000000000020000", |
||||
"withdrawals" : [ |
||||
], |
||||
"parentBaseFee" : "0x08", |
||||
"parentGasUsed" : "0x00", |
||||
"parentGasLimit" : "0x1000000000", |
||||
"parentExcessBlobGas" : "0x1000", |
||||
"parentBlobGasUsed" : "0x2000" |
||||
} |
@ -0,0 +1 @@ |
||||
"hello world" |
@ -0,0 +1,6 @@ |
||||
{"pc":0,"op":96,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} |
||||
{"pc":2,"op":96,"gas":"0x13495","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"} |
||||
{"pc":4,"op":96,"gas":"0x13492","gasCost":"0x3","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"} |
||||
{"pc":6,"op":96,"gas":"0x1348f","gasCost":"0x3","memSize":0,"stack":["0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"} |
||||
{"pc":8,"op":0,"gas":"0x1348c","gasCost":"0x0","memSize":0,"stack":["0x40","0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"STOP"} |
||||
{"output":"","gasUsed":"0xc"} |
@ -0,0 +1,14 @@ |
||||
[ |
||||
{ |
||||
"gas": "0x186a0", |
||||
"gasPrice": "0x600", |
||||
"input": "0x", |
||||
"nonce": "0x0", |
||||
"to": "0x1111111111111111111111111111111111111111", |
||||
"value": "0x1", |
||||
"v" : "0x0", |
||||
"r" : "0x0", |
||||
"s" : "0x0", |
||||
"secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" |
||||
} |
||||
] |
@ -0,0 +1,275 @@ |
||||
// Copyright 2024 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tracing |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/holiman/uint256" |
||||
) |
||||
|
||||
// OpContext provides the context at which the opcode is being
|
||||
// executed in, including the memory, stack and various contract-level information.
|
||||
type OpContext interface { |
||||
MemoryData() []byte |
||||
StackData() []uint256.Int |
||||
Caller() common.Address |
||||
Address() common.Address |
||||
CallValue() *uint256.Int |
||||
CallInput() []byte |
||||
} |
||||
|
||||
// StateDB gives tracers access to the whole state.
|
||||
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 |
||||
} |
||||
|
||||
// VMContext provides the context for the EVM execution.
|
||||
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 |
||||
} |
||||
|
||||
type ( |
||||
/* |
||||
- VM events - |
||||
*/ |
||||
|
||||
// TxStartHook is called before the execution of a transaction starts.
|
||||
// Call simulations don't come with a valid signature. `from` field
|
||||
// to be used for address of the caller.
|
||||
TxStartHook = func(vm *VMContext, tx *types.Transaction, from common.Address) |
||||
|
||||
// TxEndHook is called after the execution of a transaction ends.
|
||||
TxEndHook = func(receipt *types.Receipt, err error) |
||||
|
||||
// EnterHook is invoked when the processing of a message starts.
|
||||
EnterHook = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) |
||||
|
||||
// ExitHook 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`.
|
||||
ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool) |
||||
|
||||
// OpcodeHook is invoked just prior to the execution of an opcode.
|
||||
OpcodeHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, rData []byte, depth int, err error) |
||||
|
||||
// FaultHook is invoked when an error occurs during the execution of an opcode.
|
||||
FaultHook = func(pc uint64, op byte, gas, cost uint64, scope OpContext, depth int, err error) |
||||
|
||||
// GasChangeHook is invoked when the gas changes.
|
||||
GasChangeHook = func(old, new uint64, reason GasChangeReason) |
||||
|
||||
/* |
||||
- Chain events - |
||||
*/ |
||||
|
||||
// BlockchainInitHook is called when the blockchain is initialized.
|
||||
BlockchainInitHook = func(chainConfig *params.ChainConfig) |
||||
|
||||
// BlockStartHook is called before executing `block`.
|
||||
// `td` is the total difficulty prior to `block`.
|
||||
BlockStartHook = func(event BlockEvent) |
||||
|
||||
// BlockEndHook is called after executing a block.
|
||||
BlockEndHook = func(err error) |
||||
|
||||
// SkippedBlockHook indicates a block was skipped during processing
|
||||
// due to it being known previously. This can happen e.g. when recovering
|
||||
// from a crash.
|
||||
SkippedBlockHook = func(event BlockEvent) |
||||
|
||||
// GenesisBlockHook is called when the genesis block is being processed.
|
||||
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc) |
||||
|
||||
/* |
||||
- State events - |
||||
*/ |
||||
|
||||
// BalanceChangeHook is called when the balance of an account changes.
|
||||
BalanceChangeHook = func(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) |
||||
|
||||
// NonceChangeHook is called when the nonce of an account changes.
|
||||
NonceChangeHook = func(addr common.Address, prev, new uint64) |
||||
|
||||
// CodeChangeHook is called when the code of an account changes.
|
||||
CodeChangeHook = func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) |
||||
|
||||
// StorageChangeHook is called when the storage of an account changes.
|
||||
StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash) |
||||
|
||||
// LogHook is called when a log is emitted.
|
||||
LogHook = func(log *types.Log) |
||||
) |
||||
|
||||
type Hooks struct { |
||||
// VM events
|
||||
OnTxStart TxStartHook |
||||
OnTxEnd TxEndHook |
||||
OnEnter EnterHook |
||||
OnExit ExitHook |
||||
OnOpcode OpcodeHook |
||||
OnFault FaultHook |
||||
OnGasChange GasChangeHook |
||||
// Chain events
|
||||
OnBlockchainInit BlockchainInitHook |
||||
OnBlockStart BlockStartHook |
||||
OnBlockEnd BlockEndHook |
||||
OnSkippedBlock SkippedBlockHook |
||||
OnGenesisBlock GenesisBlockHook |
||||
// State events
|
||||
OnBalanceChange BalanceChangeHook |
||||
OnNonceChange NonceChangeHook |
||||
OnCodeChange CodeChangeHook |
||||
OnStorageChange StorageChangeHook |
||||
OnLog LogHook |
||||
} |
||||
|
||||
// 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 |
||||
) |
||||
|
||||
// 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 = 0 |
||||
|
||||
// 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 GasChangeReason = 1 |
||||
// 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 GasChangeReason = 2 |
||||
// 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 GasChangeReason = 3 |
||||
// 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 GasChangeReason = 4 |
||||
|
||||
// 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 GasChangeReason = 5 |
||||
// 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 GasChangeReason = 6 |
||||
// 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 GasChangeReason = 7 |
||||
// GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE.
|
||||
GasChangeCallContractCreation GasChangeReason = 8 |
||||
// GasChangeContractCreation is the amount of gas that will be burned for a CREATE2.
|
||||
GasChangeCallContractCreation2 GasChangeReason = 9 |
||||
// GasChangeCallCodeStorage is the amount of gas that will be charged for code storage.
|
||||
GasChangeCallCodeStorage GasChangeReason = 10 |
||||
// 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 `OnOpcode` handling.
|
||||
GasChangeCallOpCode GasChangeReason = 11 |
||||
// GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution.
|
||||
GasChangeCallPrecompiledContract GasChangeReason = 12 |
||||
// GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules.
|
||||
GasChangeCallStorageColdAccess GasChangeReason = 13 |
||||
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
||||
GasChangeCallFailedExecution GasChangeReason = 14 |
||||
|
||||
// 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 |
||||
) |
@ -1,43 +0,0 @@ |
||||
// Copyright 2015 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
) |
||||
|
||||
// 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 { |
||||
// Transaction level
|
||||
CaptureTxStart(gasLimit uint64) |
||||
CaptureTxEnd(restGas uint64) |
||||
// Top call frame
|
||||
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) |
||||
CaptureEnd(output []byte, gasUsed uint64, err error) |
||||
// Rest of call frames
|
||||
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) |
||||
CaptureExit(output []byte, gasUsed uint64, err error) |
||||
// Opcode level
|
||||
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) |
||||
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) |
||||
} |
@ -0,0 +1,10 @@ |
||||
# Filling test cases |
||||
|
||||
To fill test cases for the built-in tracers, the `makeTest.js` script can be used. Given a transaction on a dev/test network, `makeTest.js` will fetch its prestate and then traces with the given configuration. |
||||
In the Geth console do: |
||||
|
||||
```terminal |
||||
let tx = '0x...' |
||||
loadScript('makeTest.js') |
||||
makeTest(tx, { tracer: 'callTracer' }) |
||||
``` |
@ -0,0 +1,48 @@ |
||||
// makeTest generates a test for the configured tracer by running
|
||||
// a prestate reassembled and a call trace run, assembling all the
|
||||
// gathered information into a test case.
|
||||
var makeTest = function(tx, traceConfig) { |
||||
// Generate the genesis block from the block, transaction and prestate data
|
||||
var block = eth.getBlock(eth.getTransaction(tx).blockHash); |
||||
var genesis = eth.getBlock(block.parentHash); |
||||
|
||||
delete genesis.gasUsed; |
||||
delete genesis.logsBloom; |
||||
delete genesis.parentHash; |
||||
delete genesis.receiptsRoot; |
||||
delete genesis.sha3Uncles; |
||||
delete genesis.size; |
||||
delete genesis.transactions; |
||||
delete genesis.transactionsRoot; |
||||
delete genesis.uncles; |
||||
|
||||
genesis.gasLimit = genesis.gasLimit.toString(); |
||||
genesis.number = genesis.number.toString(); |
||||
genesis.timestamp = genesis.timestamp.toString(); |
||||
|
||||
genesis.alloc = debug.traceTransaction(tx, {tracer: "prestateTracer"}); |
||||
for (var key in genesis.alloc) { |
||||
var nonce = genesis.alloc[key].nonce; |
||||
if (nonce) { |
||||
genesis.alloc[key].nonce = nonce.toString(); |
||||
} |
||||
} |
||||
genesis.config = admin.nodeInfo.protocols.eth.config; |
||||
|
||||
// Generate the call trace and produce the test input
|
||||
var result = debug.traceTransaction(tx, traceConfig); |
||||
delete result.time; |
||||
|
||||
console.log(JSON.stringify({ |
||||
genesis: genesis, |
||||
context: { |
||||
number: block.number.toString(), |
||||
difficulty: block.difficulty, |
||||
timestamp: block.timestamp.toString(), |
||||
gasLimit: block.gasLimit.toString(), |
||||
miner: block.miner, |
||||
}, |
||||
input: eth.getRawTransaction(tx), |
||||
result: result, |
||||
}, null, 2)); |
||||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,62 @@ |
||||
{ |
||||
"genesis": { |
||||
"baseFeePerGas": "875000000", |
||||
"difficulty": "0", |
||||
"extraData": "0xd983010d05846765746888676f312e32312e318664617277696e", |
||||
"gasLimit": "11511229", |
||||
"hash": "0xd462585c6c5a3b3bf14850ebcde71b6615b9aaf6541403f9a0457212dd0502e0", |
||||
"miner": "0x0000000000000000000000000000000000000000", |
||||
"mixHash": "0xfa51e868d6a7c0728f18800e4cc8d4cc1c87430cc9975e947eb6c9c03599b4e2", |
||||
"nonce": "0x0000000000000000", |
||||
"number": "1", |
||||
"stateRoot": "0xd2ebe0a7f3572ffe3e5b4c78147376d3fca767f236e4dd23f9151acfec7cb0d1", |
||||
"timestamp": "1699617692", |
||||
"totalDifficulty": "0", |
||||
"withdrawals": [], |
||||
"withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"alloc": { |
||||
"0x0000000000000000000000000000000000000000": { |
||||
"balance": "0x5208" |
||||
}, |
||||
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { |
||||
"balance": "0x8ac7230489e80000" |
||||
} |
||||
}, |
||||
"config": { |
||||
"chainId": 1337, |
||||
"homesteadBlock": 0, |
||||
"eip150Block": 0, |
||||
"eip155Block": 0, |
||||
"eip158Block": 0, |
||||
"byzantiumBlock": 0, |
||||
"constantinopleBlock": 0, |
||||
"petersburgBlock": 0, |
||||
"istanbulBlock": 0, |
||||
"muirGlacierBlock": 0, |
||||
"berlinBlock": 0, |
||||
"londonBlock": 0, |
||||
"arrowGlacierBlock": 0, |
||||
"grayGlacierBlock": 0, |
||||
"shanghaiTime": 0, |
||||
"terminalTotalDifficulty": 0, |
||||
"terminalTotalDifficultyPassed": true, |
||||
"isDev": true |
||||
} |
||||
}, |
||||
"context": { |
||||
"number": "2", |
||||
"difficulty": "0", |
||||
"timestamp": "1699617847", |
||||
"gasLimit": "11522469", |
||||
"miner": "0x0000000000000000000000000000000000000000" |
||||
}, |
||||
"input": "0x02f902b48205398084b2d05e0085011b1f3f8083031ca88080b90258608060405234801561001057600080fd5b5060405161001d906100e3565b604051809103906000f080158015610039573d6000803e3d6000fd5b50600080546001600160a01b0319166001600160a01b039290921691821781556040517fc66247bafd1305823857fb4c3e651e684d918df8554ef560bbbcb025fdd017039190a26000546040516360fe47b160e01b8152600560048201526001600160a01b03909116906360fe47b190602401600060405180830381600087803b1580156100c657600080fd5b505af11580156100da573d6000803e3d6000fd5b505050506100ef565b60ca8061018e83390190565b6091806100fd6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806380de699314602d575b600080fd5b600054603f906001600160a01b031681565b6040516001600160a01b03909116815260200160405180910390f3fea2646970667358221220dab781465e7f4cf20304cc388130a763508e20edd25b4bc8ea8f57743a0de8da64736f6c634300081700336080604052348015600f57600080fd5b5060ac8061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c806360fe47b11460375780636d4ce63c146049575b600080fd5b60476042366004605e565b600055565b005b60005460405190815260200160405180910390f35b600060208284031215606f57600080fd5b503591905056fea264697066735822122049e09da6320793487d58eaa7b97f802618a062cbc35f08ca1ce92c17349141f864736f6c63430008170033c080a01d4fce93ad08bf413052645721f20e6136830cf5a2759fa57e76a134e90899a7a0399a72832d52118991dc04c4f9e1c0fec3d5e441ad7d4b055f0cf03130d8f815", |
||||
"result": { |
||||
"0x0000000000000000000000000000000000000000": { |
||||
"balance": "0x5208" |
||||
}, |
||||
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { |
||||
"balance": "0x8ac7230489e80000" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,81 @@ |
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
package internal |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
|
||||
"github.com/holiman/uint256" |
||||
) |
||||
|
||||
const ( |
||||
memoryPadLimit = 1024 * 1024 |
||||
) |
||||
|
||||
// GetMemoryCopyPadded returns offset + size as a new slice.
|
||||
// It zero-pads the slice if it extends beyond memory bounds.
|
||||
func GetMemoryCopyPadded(m []byte, offset, size int64) ([]byte, error) { |
||||
if offset < 0 || size < 0 { |
||||
return nil, errors.New("offset or size must not be negative") |
||||
} |
||||
length := int64(len(m)) |
||||
if offset+size < length { // slice fully inside memory
|
||||
return memoryCopy(m, offset, size), nil |
||||
} |
||||
paddingNeeded := offset + size - length |
||||
if paddingNeeded > memoryPadLimit { |
||||
return nil, fmt.Errorf("reached limit for padding memory slice: %d", paddingNeeded) |
||||
} |
||||
cpy := make([]byte, size) |
||||
if overlap := length - offset; overlap > 0 { |
||||
copy(cpy, MemoryPtr(m, offset, overlap)) |
||||
} |
||||
return cpy, nil |
||||
} |
||||
|
||||
func memoryCopy(m []byte, offset, size int64) (cpy []byte) { |
||||
if size == 0 { |
||||
return nil |
||||
} |
||||
|
||||
if len(m) > int(offset) { |
||||
cpy = make([]byte, size) |
||||
copy(cpy, m[offset:offset+size]) |
||||
|
||||
return |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// MemoryPtr returns a pointer to a slice of memory.
|
||||
func MemoryPtr(m []byte, offset, size int64) []byte { |
||||
if size == 0 { |
||||
return nil |
||||
} |
||||
|
||||
if len(m) > int(offset) { |
||||
return m[offset : offset+size] |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Back returns the n'th item in stack
|
||||
func StackBack(st []uint256.Int, n int) *uint256.Int { |
||||
return &st[len(st)-n-1] |
||||
} |
@ -0,0 +1,60 @@ |
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
package internal |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
) |
||||
|
||||
func TestMemCopying(t *testing.T) { |
||||
for i, tc := range []struct { |
||||
memsize int64 |
||||
offset int64 |
||||
size int64 |
||||
wantErr string |
||||
wantSize int |
||||
}{ |
||||
{0, 0, 100, "", 100}, // Should pad up to 100
|
||||
{0, 100, 0, "", 0}, // No need to pad (0 size)
|
||||
{100, 50, 100, "", 100}, // Should pad 100-150
|
||||
{100, 50, 5, "", 5}, // Wanted range fully within memory
|
||||
{100, -50, 0, "offset or size must not be negative", 0}, // Error
|
||||
{0, 1, 1024*1024 + 1, "reached limit for padding memory slice: 1048578", 0}, // Error
|
||||
{10, 0, 1024*1024 + 100, "reached limit for padding memory slice: 1048666", 0}, // Error
|
||||
|
||||
} { |
||||
mem := vm.NewMemory() |
||||
mem.Resize(uint64(tc.memsize)) |
||||
cpy, err := GetMemoryCopyPadded(mem.Data(), tc.offset, tc.size) |
||||
if want := tc.wantErr; want != "" { |
||||
if err == nil { |
||||
t.Fatalf("test %d: want '%v' have no error", i, want) |
||||
} |
||||
if have := err.Error(); want != have { |
||||
t.Fatalf("test %d: want '%v' have '%v'", i, want, have) |
||||
} |
||||
continue |
||||
} |
||||
if err != nil { |
||||
t.Fatalf("test %d: unexpected error: %v", i, err) |
||||
} |
||||
if want, have := tc.wantSize, len(cpy); have != want { |
||||
t.Fatalf("test %d: want %v have %v", i, want, have) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
package tracers |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/tracing" |
||||
) |
||||
|
||||
type ctorFunc func(config json.RawMessage) (*tracing.Hooks, error) |
||||
|
||||
// LiveDirectory is the collection of tracers which can be used
|
||||
// during normal block import operations.
|
||||
var LiveDirectory = liveDirectory{elems: make(map[string]ctorFunc)} |
||||
|
||||
type liveDirectory struct { |
||||
elems map[string]ctorFunc |
||||
} |
||||
|
||||
// Register registers a tracer constructor by name.
|
||||
func (d *liveDirectory) Register(name string, f ctorFunc) { |
||||
d.elems[name] = f |
||||
} |
||||
|
||||
// New instantiates a tracer by name.
|
||||
func (d *liveDirectory) New(name string, config json.RawMessage) (*tracing.Hooks, error) { |
||||
if f, ok := d.elems[name]; ok { |
||||
return f(config) |
||||
} |
||||
return nil, errors.New("not found") |
||||
} |
@ -0,0 +1,96 @@ |
||||
package live |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/tracing" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/eth/tracers" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
func init() { |
||||
tracers.LiveDirectory.Register("noop", newNoopTracer) |
||||
} |
||||
|
||||
// noop is a no-op live tracer. It's there to
|
||||
// catch changes in the tracing interface, as well as
|
||||
// for testing live tracing performance. Can be removed
|
||||
// as soon as we have a real live tracer.
|
||||
type noop struct{} |
||||
|
||||
func newNoopTracer(_ json.RawMessage) (*tracing.Hooks, error) { |
||||
t := &noop{} |
||||
return &tracing.Hooks{ |
||||
OnTxStart: t.OnTxStart, |
||||
OnTxEnd: t.OnTxEnd, |
||||
OnEnter: t.OnEnter, |
||||
OnExit: t.OnExit, |
||||
OnOpcode: t.OnOpcode, |
||||
OnFault: t.OnFault, |
||||
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 |
||||
} |
||||
|
||||
func (t *noop) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { |
||||
} |
||||
|
||||
func (t *noop) OnFault(pc uint64, op byte, gas, cost uint64, _ tracing.OpContext, depth int, err error) { |
||||
} |
||||
|
||||
func (t *noop) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { |
||||
} |
||||
|
||||
func (t *noop) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { |
||||
} |
||||
|
||||
func (t *noop) OnTxStart(vm *tracing.VMContext, tx *types.Transaction, from common.Address) { |
||||
} |
||||
|
||||
func (t *noop) OnTxEnd(receipt *types.Receipt, err error) { |
||||
} |
||||
|
||||
func (t *noop) OnBlockStart(ev tracing.BlockEvent) { |
||||
} |
||||
|
||||
func (t *noop) OnBlockEnd(err error) { |
||||
} |
||||
|
||||
func (t *noop) OnSkippedBlock(ev tracing.BlockEvent) {} |
||||
|
||||
func (t *noop) OnBlockchainInit(chainConfig *params.ChainConfig) { |
||||
} |
||||
|
||||
func (t *noop) OnGenesisBlock(b *types.Block, alloc types.GenesisAlloc) { |
||||
} |
||||
|
||||
func (t *noop) OnBalanceChange(a common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { |
||||
} |
||||
|
||||
func (t *noop) OnNonceChange(a common.Address, prev, new uint64) { |
||||
} |
||||
|
||||
func (t *noop) OnCodeChange(a common.Address, prevCodeHash common.Hash, prev []byte, codeHash common.Hash, code []byte) { |
||||
} |
||||
|
||||
func (t *noop) OnStorageChange(a common.Address, k, prev, new common.Hash) { |
||||
} |
||||
|
||||
func (t *noop) OnLog(l *types.Log) { |
||||
|
||||
} |
||||
|
||||
func (t *noop) OnGasChange(old, new uint64, reason tracing.GasChangeReason) { |
||||
} |
Loading…
Reference in new issue